diff --git a/license/thirdpartylegalnotices.txt b/license/thirdpartylegalnotices.txt index 3b011067..406ff9b4 100644 --- a/license/thirdpartylegalnotices.txt +++ b/license/thirdpartylegalnotices.txt @@ -580,6 +580,123 @@ Mbed TLS & l8w8jwt // limitations under the License. //////////////////////////////////////////////////////////////////////////// +************************************************************************************ +DirtySDK (EA WebKit) +************************************************************************************ + + // Copyright (C) 1999-2007, 2009-2010, 2012-2013 Electronic Arts Inc + // + // Redistribution and use in source and binary forms, with or without + // modification, are permitted provided that the following conditions + // are met: + // + // 1. Redistributions of source code must retain the above copyright + // notice, this list of conditions and the following disclaimer. + // 2. Redistributions in binary form must reproduce the above copyright + // notice, this list of conditions and the following disclaimer in the + // documentation and/or other materials provided with the distribution. + // 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of + // its contributors may be used to endorse or promote products derived + // from this software without specific prior written permission. + // + // THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY + // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + // DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY + // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + //////////////////////////////////////////////////////////////////////////// + +************************************************************************************ +EAThread (EA WebKit) +************************************************************************************ + + //-------------------------------------------------------------------------- + // Copyright (C) 2017 Electronic Arts Inc. All rights reserved. + // + // Redistribution and use in source and binary forms, with or without + // modification, are permitted provided that the following conditions + // are met: + // + // 1. Redistributions of source code must retain the above copyright + // notice, this list of conditions and the following disclaimer. + // 2. Redistributions in binary form must reproduce the above copyright + // notice, this list of conditions and the following disclaimer in the + // documentation and/or other materials provided with the distribution. + // 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of + // its contributors may be used to endorse or promote products derived + // from this software without specific prior written permission. + // + // THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY + // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + // DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY + // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + //-------------------------------------------------------------------------- + // + // Additional licenses also apply to this software package as detailed below. + // + //-------------------------------------------------------------------------- + // Copyright (c) 2015 Jeff Preshing + // + // This software is provided 'as-is', without any express or implied + // warranty. In no event will the authors be held liable for any damages + // arising from the use of this software. + // + // Permission is granted to anyone to use this software for any purpose, + // including commercial applications, and to alter it and redistribute it + // freely, subject to the following restrictions: + // + // 1. The origin of this software must not be misrepresented; you must not + // claim that you wrote the original software. If you use this software + // in a product, an acknowledgement in the product documentation would be + // appreciated but is not required. + // 2. Altered source versions must be plainly marked as such, and must not be + // misrepresented as being the original software. + // 3. This notice may not be removed or altered from any source distribution. + //-------------------------------------------------------------------------- + //////////////////////////////////////////////////////////////////////////// + +************************************************************************************ +EABase (EA WebKit) +************************************************************************************ + + // Copyright (C) 2002-2013 Electronic Arts Inc + // + // Redistribution and use in source and binary forms, with or without + // modification, are permitted provided that the following conditions + // are met: + // + // 1. Redistributions of source code must retain the above copyright + // notice, this list of conditions and the following disclaimer. + // 2. Redistributions in binary form must reproduce the above copyright + // notice, this list of conditions and the following disclaimer in the + // documentation and/or other materials provided with the distribution. + // 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of + // its contributors may be used to endorse or promote products derived + // from this software without specific prior written permission. + // + // THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY + // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + // DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY + // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + //////////////////////////////////////////////////////////////////////////// + ************************************************************************************ Zstandard ************************************************************************************ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5602f9c7..aea24402 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,6 +46,10 @@ add_subdirectory( thirdparty/cppnet ) set( FOLDER_CONTEXT "Thirdparty/Networking" ) add_subdirectory( thirdparty/protobuf ) add_subdirectory( thirdparty/curl ) +add_subdirectory( thirdparty/dirtysdk ) + +set( FOLDER_CONTEXT "Thirdparty/Threading" ) +add_subdirectory( thirdparty/ea/EAThread ) set( FOLDER_CONTEXT "Tools" ) add_subdirectory( sdklauncher ) diff --git a/src/cmake/Macros.cmake b/src/cmake/Macros.cmake index c4e70460..0f395b36 100644 --- a/src/cmake/Macros.cmake +++ b/src/cmake/Macros.cmake @@ -141,6 +141,7 @@ macro( thirdparty_suppress_warnings ) /wd4244 # Type conversion truncation; protobuf has many, but this appears intentional. /wd4245 # 'return': conversion signed/unsigned mismatch /wd4267 # Type conversion truncation; protobuf has many, but this appears intentional. + /wd4295 # Array is too small to include terminating null character. /wd4307 # Integral constant overflow. /wd4389 # Signed/unsigned mismatch. /wd4456 # Declaration hides previous local declaration. diff --git a/src/cmake/Options.cmake b/src/cmake/Options.cmake index a5972304..ada74d78 100644 --- a/src/cmake/Options.cmake +++ b/src/cmake/Options.cmake @@ -48,7 +48,7 @@ macro( apply_project_settings ) # https://rapidjson.org/md_doc_features.html # https://github.com/Tencent/rapidjson/issues/1227 # https://github.com/Tencent/rapidjson/issues/2260 - "RAPIDJSON_PARSE_DEFAULT_FLAGS=kParseIterativeFlag" + "RAPIDJSON_PARSE_DEFAULT_FLAGS=kParseIterativeFlag|kParseValidateEncodingFlag" # Target is 64bits only. "PLATFORM_64BITS" diff --git a/src/codecs/miles/miles_impl.cpp b/src/codecs/miles/miles_impl.cpp index ed56aa71..9de48fa9 100644 --- a/src/codecs/miles/miles_impl.cpp +++ b/src/codecs/miles/miles_impl.cpp @@ -40,9 +40,10 @@ bool Miles_Initialize() // if we are loading english and the file is still not found, we can let it hit the regular engine error, since that is not recoverable if (!FileSystem()->FileExists(baseStreamFilePath.c_str())) { - Error(eDLL_T::AUDIO, NO_ERROR, "%s: attempted to load language '%s' but the required stream bank (%s) was not found. falling back to english...\n", pszLanguage, baseStreamFilePath.c_str()); + Error(eDLL_T::AUDIO, NO_ERROR, "%s: attempted to load language '%s' but the required streaming source file (%s) was not found. falling back to english...\n", __FUNCTION__, pszLanguage, baseStreamFilePath.c_str()); pszLanguage = MILES_DEFAULT_LANGUAGE; + miles_language->SetValue(pszLanguage); } } @@ -67,7 +68,27 @@ void MilesQueueEventRun(Miles::Queue* queue, const char* eventName) void MilesBankPatch(Miles::Bank* bank, char* streamPatch, char* localizedStreamPatch) { - // TODO [REXX]: add print for patch loading when Miles::Bank struct is mapped out a bit better with file name + if (miles_debug.GetBool()) + { + Msg(eDLL_T::AUDIO, + "%s: patching bank \"%s\". stream patches: \"%s\", \"%s\"\n", + __FUNCTION__, + bank->GetBankName(), + V_UnqualifiedFileName(streamPatch), V_UnqualifiedFileName(localizedStreamPatch) + ); + } + + const Miles::BankHeader_t* header = bank->GetHeader(); + + if (header->bankIndex >= header->project->bankCount) + Error(eDLL_T::AUDIO, EXIT_FAILURE, + "%s: Attempted to patch bank '%s' that identified itself as bank idx %i.\nProject expects a highest index of %i\n", + __FUNCTION__, + bank->GetBankName(), + header->bankIndex, + header->project->bankCount - 1 + ); + v_MilesBankPatch(bank, streamPatch, localizedStreamPatch); } diff --git a/src/codecs/miles/miles_types.h b/src/codecs/miles/miles_types.h index bae8703a..3ac735e1 100644 --- a/src/codecs/miles/miles_types.h +++ b/src/codecs/miles/miles_types.h @@ -4,6 +4,9 @@ constexpr char MILES_DEFAULT_LANGUAGE[] = "english"; namespace Miles { + constexpr int TEMPLATEID_FLAG_SOURCE = 0x40000000; + + struct Queue { char gap0[0x8]; @@ -11,8 +14,107 @@ namespace Miles char gap10[0x20]; }; + struct BankHeader_t; + + struct Source_t + { + BankHeader_t* bank; // reserved on disk - written at runtime + char gap8[80]; + }; + + struct Event_t + { + int nameOffset; + int unkOffset; // offset into BankHeader_t::unk_68 data - some sort of event metadata? + }; + + // internal project data structure + struct IntProjectData_t + { + char gap0[0xCF0]; + int bankCount; + BankHeader_t** loadedBanks; + }; + + struct BankHeader_t + { + int magic; // 'CBNK' + int version; // 32 + uint32_t dataSize; + + int bankMagic; // 'BANK' + const char* bankName; + + void* unk_18; + IntProjectData_t* project; + + void* unk_28; + void* unk_30; + void* unk_38; + void* unk_40; // used to index into both sources and localised sources + + Source_t* sources; + Source_t* localizedSources; + + void* unk_58; + Event_t* events; + + void* unk_68; + const char* stringTable; + + void* unk_78; + void* unk_80; + void* unk_88; + + uint8_t bankIndex; + // 3 byte padding + + uint32_t localizedSourceCount; + uint32_t sourceCount; + + uint32_t patchCount; + uint32_t eventCount; + + uint32_t count_A4; + uint32_t count_A8; + uint32_t count_AC; + + uint32_t buildTag; + + uint32_t unk_B4; + uint32_t someDataSize; + + const char* GetBankName() const + { + return bankName; + } + + }; + static_assert(offsetof(BankHeader_t, project) == 0x20); + static_assert(offsetof(BankHeader_t, stringTable) == 0x70); + static_assert(offsetof(BankHeader_t, unk_B4) == 0xB4); + + struct Bank { - // TODO [REXX]: map out this struct and its internal counterpart + void* internalData; + void* unk_8; + + char* fileData; + + int unk_18; + char gap_1c[4]; + + const Miles::BankHeader_t* GetHeader() const + { + return reinterpret_cast(fileData); + } + + const char* GetBankName() const + { + return GetHeader()->GetBankName(); + } }; + + static_assert(sizeof(Bank) == 0x20); } \ No newline at end of file diff --git a/src/common/callback.cpp b/src/common/callback.cpp index 1a0ef548..8ea1e366 100644 --- a/src/common/callback.cpp +++ b/src/common/callback.cpp @@ -562,7 +562,7 @@ void Cmd_Exec_f(const CCommand& args) // Prevent users from running neo strafe commands and other quick hacks. // TODO: when reBar becomes a thing, we should verify this function and // flag users that patch them out. - if (!ThreadInServerFrameThread() && g_pClientState->IsActive()) + if (g_pClientState->IsActive() && !ThreadInServerFrameThread()) { const int execQuota = sv_quota_scriptExecsPerSecond.GetInt(); diff --git a/src/common/global.cpp b/src/common/global.cpp index 244583bf..d18725d6 100644 --- a/src/common/global.cpp +++ b/src/common/global.cpp @@ -111,6 +111,10 @@ ConVar* net_usesocketsforloopback; ConVar* net_data_block_enabled = nullptr; ConVar* net_datablock_networkLossForSlowSpeed = nullptr; ConVar* net_compressDataBlock = nullptr; + +ConVar* net_showmsg = nullptr; +ConVar* net_blockmsg = nullptr; +ConVar* net_showpeaks = nullptr; //----------------------------------------------------------------------------- // RUI | #ifndef DEDICATED @@ -183,6 +187,10 @@ void ConVar_InitShipped(void) net_datablock_networkLossForSlowSpeed = g_pCVar->FindVar("net_datablock_networkLossForSlowSpeed"); net_usesocketsforloopback = g_pCVar->FindVar("net_usesocketsforloopback"); + + net_showmsg = g_pCVar->FindVar("net_showmsg"); + net_blockmsg = g_pCVar->FindVar("net_blockmsg"); + net_showpeaks = g_pCVar->FindVar("net_showpeaks"); #ifndef CLIENT_DLL sv_stats = g_pCVar->FindVar("sv_stats"); diff --git a/src/common/global.h b/src/common/global.h index 568b42b5..e958199b 100644 --- a/src/common/global.h +++ b/src/common/global.h @@ -98,6 +98,10 @@ extern ConVar* net_data_block_enabled; extern ConVar* net_datablock_networkLossForSlowSpeed; extern ConVar* net_compressDataBlock; +extern ConVar* net_showmsg; +extern ConVar* net_blockmsg; +extern ConVar* net_showpeaks; + extern ConVar ssl_verify_peer; extern ConVar curl_timeout; extern ConVar curl_debug; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 81430858..d8f575bf 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -41,6 +41,8 @@ target_link_libraries( ${PROJECT_NAME} PRIVATE "wldap32.lib" "ws2_32.lib" "Rpcrt4.lib" + "iphlpapi.lib" + "Winmm.lib" "vpc" "memoverride" @@ -56,6 +58,7 @@ target_link_libraries( ${PROJECT_NAME} PRIVATE "vphysics" "SigCache_Pb" + "LiveAPI_Pb" "SV_RCon_Pb" "CL_RCon_Pb" @@ -75,6 +78,9 @@ target_link_libraries( ${PROJECT_NAME} PRIVATE "libdetour" "navdebugutils" + "EAThread" + "DirtySDK" + "networksystem" "pluginsystem" "filesystem" @@ -156,6 +162,11 @@ target_compile_definitions( ${PROJECT_NAME} PRIVATE endif() +target_include_directories( ${PROJECT_NAME} PRIVATE + "${THIRDPARTY_SOURCE_DIR}/dirtysdk/include/" + "${THIRDPARTY_SOURCE_DIR}/ea/" +) + target_link_options( ${PROJECT_NAME} PRIVATE "/STACK:8000000" # Match game executable stack reserve size diff --git a/src/core/assert.h b/src/core/assert.h index e4c2e88f..50ce39ae 100644 --- a/src/core/assert.h +++ b/src/core/assert.h @@ -11,7 +11,4 @@ //#else # define Assert(condition, ...) assert(condition) # define AssertFatalMsg Assert - -// TODO: this needs to go to dbg.h -# define AssertMsg(condition, ...) assert(condition) //#endif diff --git a/src/core/dllmain.cpp b/src/core/dllmain.cpp index f9efc6e2..5415390a 100644 --- a/src/core/dllmain.cpp +++ b/src/core/dllmain.cpp @@ -3,6 +3,7 @@ #include "core/init.h" #include "core/logdef.h" #include "core/logger.h" +#include "tier0/cpu.h" #include "tier0/basetypes.h" #include "tier0/crashhandler.h" #include "tier0/commandline.h" @@ -25,6 +26,12 @@ bool g_bSdkInitialized = false; +bool g_bSdkInitCallInitiated = false; +bool g_bSdkShutdownCallInitiated = false; + +bool g_bSdkShutdownInitiatedFromConsoleHandler = false; +HMODULE s_hModuleHandle = NULL; + //############################################################################# // UTILITY //############################################################################# @@ -76,6 +83,28 @@ void Tier0_Init() void SDK_Init() { + assert(!g_bSdkInitialized); + + CheckSystemCPU(); // Check CPU as early as possible; error out if CPU isn't supported. + + if (g_bSdkInitCallInitiated) + { + spdlog::error("Recursive initialization!\n"); + return; + } + + // Set after checking cpu and initializing MathLib since we check CPU + // features there. Else we crash on the recursive initialization error as + // SpdLog uses SSE features. + g_bSdkInitCallInitiated = true; + + MathLib_Init(); // Initialize Mathlib. + + PEB64* pEnv = CModule::GetProcessEnvironmentBlock(); + + g_GameDll.InitFromBase(pEnv->ImageBaseAddress); + g_SDKDll.InitFromBase((QWORD)s_hModuleHandle); + Tier0_Init(); if (!CommandLine()->CheckParm("-launcher")) @@ -95,7 +124,9 @@ void SDK_Init() SpdLog_Init(bAnsiColor); Show_Emblem(); - Winsock_Init(); // Initialize Winsock. + Winsock_Startup(); // Initialize Winsock. + DirtySDK_Startup(); + Systems_Init(); WinSys_Init(); @@ -118,13 +149,25 @@ void SDK_Shutdown() { assert(g_bSdkInitialized); - if (!g_bSdkInitialized) + // Also check CPU in shutdown, since this function is exported, if they + // call this with an unsupported CPU we should let them know rather than + // crashing the process. + CheckSystemCPU(); + + if (g_bSdkShutdownCallInitiated) { spdlog::error("Recursive shutdown!\n"); return; } - g_bSdkInitialized = false; + g_bSdkShutdownCallInitiated = true; + + if (!g_bSdkInitialized) + { + spdlog::error("Not initialized!\n"); + return; + } + Msg(eDLL_T::NONE, "GameSDK shutdown initiated\n"); curl_global_cleanup(); @@ -135,10 +178,18 @@ void SDK_Shutdown() WinSys_Shutdown(); Systems_Shutdown(); + + DirtySDK_Shutdown(); Winsock_Shutdown(); SpdLog_Shutdown(); - Console_Shutdown(); + + // If the shutdown was initiated from the console window itself, don't + // shutdown the console as it would otherwise deadlock in FreeConsole! + if (!g_bSdkShutdownInitiatedFromConsoleHandler) + Console_Shutdown(); + + g_bSdkInitialized = false; } //############################################################################# @@ -147,27 +198,18 @@ void SDK_Shutdown() BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) { - CheckCPU(); // Check CPU as early as possible; error out if CPU isn't supported. - MathLib_Init(); // Initialize Mathlib. - NOTE_UNUSED(lpReserved); switch (dwReason) { case DLL_PROCESS_ATTACH: { - PEB64* pEnv = CModule::GetProcessEnvironmentBlock(); - - g_GameDll.InitFromBase(pEnv->ImageBaseAddress); - g_SDKDll.InitFromBase((QWORD)hModule); - - SDK_Init(); + s_hModuleHandle = hModule; break; } - case DLL_PROCESS_DETACH: { - SDK_Shutdown(); + s_hModuleHandle = NULL; break; } } diff --git a/src/core/init.cpp b/src/core/init.cpp index 31b7a171..3677676d 100644 --- a/src/core/init.cpp +++ b/src/core/init.cpp @@ -59,6 +59,9 @@ #include "engine/server/datablock_sender.h" #endif // !CLIENT_DLL #include "studiorender/studiorendercontext.h" +#ifndef CLIENT_DLL +#include "rtech/liveapi/liveapi.h" +#endif // !CLIENT_DLL #include "rtech/rstdlib.h" #include "rtech/rson.h" #include "rtech/async/asyncio.h" @@ -148,6 +151,11 @@ #include "windows/id3dx.h" #endif // !DEDICATED +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/proto/protowebsocket.h" + ///////////////////////////////////////////////////////////////////////////////////////////////// // @@ -242,6 +250,8 @@ void Systems_Init() ServerScriptRegister_Callback = Script_RegisterServerFunctions; CoreServerScriptRegister_Callback = Script_RegisterCoreServerFunctions; AdminPanelScriptRegister_Callback = Script_RegisterAdminPanelFunctions; + + ServerScriptRegisterEnum_Callback = Script_RegisterServerEnums; #endif// !CLIENT_DLL #ifndef SERVER_DLL @@ -275,6 +285,10 @@ void Systems_Shutdown() RCONClient()->Shutdown(); #endif // !SERVER_DLL +#ifndef CLIENT_DLL + LiveAPISystem()->Shutdown(); +#endif// !CLIENT_DLL + CFastTimer shutdownTimer; shutdownTimer.Start(); @@ -309,25 +323,51 @@ void Systems_Shutdown() // ///////////////////////////////////////////////////// -void Winsock_Init() +void Winsock_Startup() { WSAData wsaData{}; - int nError = ::WSAStartup(MAKEWORD(2, 2), &wsaData); + const int nError = ::WSAStartup(MAKEWORD(2, 2), &wsaData); + if (nError != 0) { - Error(eDLL_T::COMMON, NO_ERROR, "%s: Failed to start Winsock: (%s)\n", + Error(eDLL_T::COMMON, 0, "%s: Windows Sockets API startup failure: (%s)\n", __FUNCTION__, NET_ErrorString(WSAGetLastError())); } } + void Winsock_Shutdown() { - int nError = ::WSACleanup(); + const int nError = ::WSACleanup(); + if (nError != 0) { - Error(eDLL_T::COMMON, NO_ERROR, "%s: Failed to stop Winsock: (%s)\n", + Error(eDLL_T::COMMON, 0, "%s: Windows Sockets API shutdown failure: (%s)\n", __FUNCTION__, NET_ErrorString(WSAGetLastError())); } } + +void DirtySDK_Startup() +{ + const int32_t netConStartupRet = NetConnStartup("-servicename=sourcesdk"); + + if (netConStartupRet < 0) + { + Error(eDLL_T::COMMON, 0, "%s: Network connection module startup failure: (%i)\n", + __FUNCTION__, netConStartupRet); + } +} + +void DirtySDK_Shutdown() +{ + const int32_t netConShutdownRet = NetConnShutdown(0); + + if (netConShutdownRet < 0) + { + Error(eDLL_T::COMMON, 0, "%s: Network connection module shutdown failure: (%i)\n", + __FUNCTION__, netConShutdownRet); + } +} + void QuerySystemInfo() { #ifndef DEDICATED @@ -378,33 +418,6 @@ void QuerySystemInfo() } } -void CheckCPU() // Respawn's engine and our SDK utilize POPCNT, SSE3 and SSSE3 (Supplemental SSE 3 Instructions). -{ - CpuIdResult_t cpuResult; - __cpuid(reinterpret_cast(&cpuResult), 1); - - char szBuf[1024]; - - if ((cpuResult.ecx & (1 << 0)) == 0) - { - V_snprintf(szBuf, sizeof(szBuf), "CPU does not have %s!\n", "SSE 3"); - MessageBoxA(NULL, szBuf, "Unsupported CPU", MB_ICONERROR | MB_OK); - ExitProcess(0xFFFFFFFF); - } - if ((cpuResult.ecx & (1 << 9)) == 0) - { - V_snprintf(szBuf, sizeof(szBuf), "CPU does not have %s!\n", "SSSE 3 (Supplemental SSE 3 Instructions)"); - MessageBoxA(NULL, szBuf, "Unsupported CPU", MB_ICONERROR | MB_OK); - ExitProcess(0xFFFFFFFF); - } - if ((cpuResult.ecx & (1 << 23)) == 0) - { - V_snprintf(szBuf, sizeof(szBuf), "CPU does not have %s!\n", "POPCNT"); - MessageBoxA(NULL, szBuf, "Unsupported CPU", MB_ICONERROR | MB_OK); - ExitProcess(0xFFFFFFFF); - } -} - #if defined (DEDICATED) #define SIGDB_FILE "cfg/server/startup.bin" #elif defined (CLIENT_DLL) diff --git a/src/core/init.h b/src/core/init.h index 0647794e..7b35872c 100644 --- a/src/core/init.h +++ b/src/core/init.h @@ -1,18 +1,20 @@ #pragma once -void SDK_Init(); -void SDK_Shutdown(); +PLATFORM_INTERFACE void SDK_Init(); +PLATFORM_INTERFACE void SDK_Shutdown(); void Systems_Init(); void Systems_Shutdown(); -void Winsock_Init(); +void Winsock_Startup(); void Winsock_Shutdown(); +void DirtySDK_Startup(); +void DirtySDK_Shutdown(); void QuerySystemInfo(); -void CheckCPU(); void DetourInit(); void DetourAddress(); void DetourRegister(); extern bool g_bSdkInitialized; +extern bool g_bSdkShutdownInitiatedFromConsoleHandler; diff --git a/src/core/logdef.cpp b/src/core/logdef.cpp index ea2006f8..d7441c8f 100644 --- a/src/core/logdef.cpp +++ b/src/core/logdef.cpp @@ -101,6 +101,8 @@ void SpdLog_Init(const bool bAnsiColor) #endif // !_TOOLS spdlog::set_level(spdlog::level::trace); + spdlog::flush_every(std::chrono::seconds(5)); + bInitialized = true; } diff --git a/src/engine/host_state.cpp b/src/engine/host_state.cpp index 579c1918..09213da5 100644 --- a/src/engine/host_state.cpp +++ b/src/engine/host_state.cpp @@ -35,6 +35,7 @@ #include "engine/cmodel_bsp.h" #ifndef CLIENT_DLL #include "engine/server/server.h" +#include "rtech/liveapi/liveapi.h" #endif // !CLIENT_DLL #include "rtech/stryder/stryder.h" #include "rtech/playlists/playlists.h" @@ -305,6 +306,10 @@ void CHostState::Setup(void) RCONClient()->Init(); #endif // !DEDICATED +#ifndef CLIENT_DLL + LiveAPISystem()->Init(); +#endif // !CLIENT_DLL + if (net_useRandomKey.GetBool()) { NET_GenerateKey(); diff --git a/src/engine/net_chan.cpp b/src/engine/net_chan.cpp index 2645d95b..bef2d6c6 100644 --- a/src/engine/net_chan.cpp +++ b/src/engine/net_chan.cpp @@ -372,7 +372,7 @@ void CNetChan::_Shutdown(CNetChan* pChan, const char* szReason, uint8_t bBadRep, bool CNetChan::_ProcessMessages(CNetChan* pChan, bf_read* pBuf) { #ifndef CLIENT_DLL - if (!ThreadInServerFrameThread() || !net_processTimeBudget.GetInt()) + if (!net_processTimeBudget.GetInt() || !ThreadInServerFrameThread()) return pChan->ProcessMessages(pBuf); const double flStartTime = Plat_FloatTime(); @@ -417,7 +417,25 @@ bool CNetChan::_ProcessMessages(CNetChan* pChan, bf_read* pBuf) bool CNetChan::ProcessMessages(bf_read* buf) { m_bStopProcessing = false; - //const double flStartTime = Plat_FloatTime(); + + const char* showMsgName = net_showmsg->GetString(); + const char* blockMsgName = net_blockmsg->GetString(); + const int netPeak = net_showpeaks->GetInt(); + + if (*showMsgName == '0') + { + showMsgName = NULL; // dont do strcmp all the time + } + + if (*blockMsgName == '0') + { + blockMsgName = NULL; // dont do strcmp all the time + } + + if (netPeak > 0 && netPeak < buf->GetNumBytesLeft()) + { + showMsgName = "1"; // show messages for this packet only + } while (true) { @@ -457,6 +475,26 @@ bool CNetChan::ProcessMessages(bf_read* buf) return false; } + if (showMsgName) + { + if ((*showMsgName == '1') || !Q_stricmp(showMsgName, netMsg->GetName())) + { + Msg(eDLL_T::ENGINE, "%s(%s): Received: %s\n", + __FUNCTION__, GetAddress(), netMsg->ToString()); + } + } + + if (blockMsgName) + { + if ((*blockMsgName == '1') || !Q_stricmp(blockMsgName, netMsg->GetName())) + { + Msg(eDLL_T::ENGINE, "%s(%s): Blocked: %s\n", + __FUNCTION__, GetAddress(), netMsg->ToString()); + + continue; + } + } + // Netmessage calls the Process function that was registered by // it's MessageHandler. m_bProcessingMessages = true; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 23866ba4..b5efc531 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -19,6 +19,7 @@ #include "ebisusdk/EbisuSDK.h" #include "public/edict.h" #include "pluginsystem/pluginsystem.h" +#include "rtech/liveapi/liveapi.h" //--------------------------------------------------------------------------------- // Console variables @@ -215,6 +216,7 @@ void CServer::BroadcastMessage(CNetMessage* const msg, const bool onlyActive, co void CServer::FrameJob(double flFrameTime, bool bRunOverlays, bool bUpdateFrame) { CServer__FrameJob(flFrameTime, bRunOverlays, bUpdateFrame); + LiveAPISystem()->RunFrame(); } //--------------------------------------------------------------------------------- diff --git a/src/engine/server/sv_main.cpp b/src/engine/server/sv_main.cpp index 07c5a631..9ad96112 100644 --- a/src/engine/server/sv_main.cpp +++ b/src/engine/server/sv_main.cpp @@ -12,7 +12,6 @@ #include "networksystem/pylon.h" #include "networksystem/bansystem.h" #include "engine/client/client.h" -#include "tier1/cvar.h" #include "server.h" //----------------------------------------------------------------------------- diff --git a/src/engine/server/sv_rcon.cpp b/src/engine/server/sv_rcon.cpp index fdac5d5d..b1acbf3d 100644 --- a/src/engine/server/sv_rcon.cpp +++ b/src/engine/server/sv_rcon.cpp @@ -382,37 +382,36 @@ void CRConServer::Authenticate(const cl_rcon::request& request, CConnectedNetCon { return; } - else // Authorize. + + // Authorize. + if (Comparator(request.requestmsg())) { - if (Comparator(request.requestmsg())) + data.m_bAuthorized = true; + if (++m_nAuthConnections >= sv_rcon_maxconnections.GetInt()) { - data.m_bAuthorized = true; - if (++m_nAuthConnections >= sv_rcon_maxconnections.GetInt()) - { - m_Socket.CloseListenSocket(); - CloseNonAuthConnection(); - } - - const char* pSendLogs = (!sv_rcon_sendlogs.GetBool() || data.m_bInputOnly) ? "0" : "1"; - - SendEncode(data.m_hSocket, s_AuthMessage, pSendLogs, - sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast(eDLL_T::NETCON)); + m_Socket.CloseListenSocket(); + CloseNonAuthConnection(); } - else // Bad password. + + const char* pSendLogs = (!sv_rcon_sendlogs.GetBool() || data.m_bInputOnly) ? "0" : "1"; + + SendEncode(data.m_hSocket, s_AuthMessage, pSendLogs, + sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast(eDLL_T::NETCON)); + } + else // Bad password. + { + const netadr_t& netAdr = m_Socket.GetAcceptedSocketAddress(m_nConnIndex); + if (sv_rcon_debug.GetBool()) { - const netadr_t& netAdr = m_Socket.GetAcceptedSocketAddress(m_nConnIndex); - if (sv_rcon_debug.GetBool()) - { - Msg(eDLL_T::SERVER, "Bad RCON password attempt from '%s'\n", netAdr.ToString()); - } - - SendEncode(data.m_hSocket, s_WrongPwMessage, "", - sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast(eDLL_T::NETCON)); - - data.m_bAuthorized = false; - data.m_bValidated = false; - data.m_nFailedAttempts++; + Msg(eDLL_T::SERVER, "Bad RCON password attempt from '%s'\n", netAdr.ToString()); } + + SendEncode(data.m_hSocket, s_WrongPwMessage, "", + sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast(eDLL_T::NETCON)); + + data.m_bAuthorized = false; + data.m_bValidated = false; + data.m_nFailedAttempts++; } } @@ -510,7 +509,7 @@ bool CRConServer::ProcessMessage(const char* pMsgBuf, const int nMsgLen) //----------------------------------------------------------------------------- void CRConServer::Execute(const cl_rcon::request& request) const { - const string& commandString = request.requestmsg().c_str(); + const string& commandString = request.requestmsg(); const char* const pCommandString = commandString.c_str(); ConCommandBase* pCommandBase = g_pCVar->FindCommandBase(pCommandString); @@ -523,6 +522,9 @@ void CRConServer::Execute(const cl_rcon::request& request) const const char* const pValueString = request.requestval().c_str(); + if (pCommandBase->IsFlagSet(FCVAR_SERVER_FRAME_THREAD)) + ThreadJoinServerJob(); + if (!pCommandBase->IsCommand()) { // Here we want to skip over the command string in the value buffer. diff --git a/src/engine/sys_utils.cpp b/src/engine/sys_utils.cpp index 4da106dc..89f70815 100644 --- a/src/engine/sys_utils.cpp +++ b/src/engine/sys_utils.cpp @@ -114,6 +114,23 @@ int Sys_GetProcessUpTime(char* szBuffer) return v_Sys_GetProcessUpTime(szBuffer); } +//----------------------------------------------------------------------------- +// Purpose: Gets the build string of the game (defined in build.txt), if the file +// is absent, the changelist # will be returned instead +//----------------------------------------------------------------------------- +const char* Sys_GetBuildString(void) +{ + return v_Sys_GetBuildString(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the platform string +//----------------------------------------------------------------------------- +const char* Sys_GetPlatformString(void) +{ + return "PC"; +} + void VSys_Utils::Detour(const bool bAttach) const { DetourSetup(&v_Error, &_Error, bAttach); diff --git a/src/engine/sys_utils.h b/src/engine/sys_utils.h index e2bca018..cf0feefd 100644 --- a/src/engine/sys_utils.h +++ b/src/engine/sys_utils.h @@ -4,6 +4,7 @@ inline void(*v_Error)(const char* fmt, ...); inline void(*v_Warning)(int, const char* fmt, ...); inline int(*v_Sys_GetProcessUpTime)(char* szBuffer); +inline const char* (*v_Sys_GetBuildString)(void); #ifndef DEDICATED inline void(*v_Con_NPrintf)(int pos, const char* fmt, ...); #endif // !DEDICATED @@ -11,6 +12,8 @@ inline void(*v_Con_NPrintf)(int pos, const char* fmt, ...); /////////////////////////////////////////////////////////////////////////////// int Sys_GetProcessUpTime(char* szBuffer); +const char* Sys_GetBuildString(void); +const char* Sys_GetPlatformString(void); /////////////////////////////////////////////////////////////////////////////// class VSys_Utils : public IDetour @@ -20,6 +23,7 @@ class VSys_Utils : public IDetour LogFunAdr("Error", v_Error); LogFunAdr("Warning", v_Warning); LogFunAdr("Sys_GetProcessUpTime", v_Sys_GetProcessUpTime); + LogFunAdr("Sys_GetProcessUpTime", v_Sys_GetBuildString); #ifndef DEDICATED LogFunAdr("Con_NPrintf", v_Con_NPrintf); #endif // !DEDICATED @@ -29,6 +33,7 @@ class VSys_Utils : public IDetour g_GameDll.FindPatternSIMD("48 89 4C 24 08 48 89 54 24 10 4C 89 44 24 18 4C 89 4C 24 20 53 55 41 54 41 56 B8 58 10 ?? ?? E8").GetPtr(v_Error); g_GameDll.FindPatternSIMD("48 89 54 24 ?? 4C 89 44 24 ?? 4C 89 4C 24 ?? 48 83 EC 28 4C 8D 44 24 ?? E8 ?? ?? ?? ?? 48 83 C4 28 C3 CC CC CC CC CC CC CC CC CC CC CC CC CC CC 48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 8B 05 ?? ?? ?? ??").GetPtr(v_Warning); g_GameDll.FindPatternSIMD("40 57 48 83 EC 30 48 8B F9 8B 0D ?? ?? ?? ??").GetPtr(v_Sys_GetProcessUpTime); + g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? 48 8B E8").FollowNearCallSelf().GetPtr(v_Sys_GetBuildString); #ifndef DEDICATED g_GameDll.FindPatternSIMD("48 89 4C 24 ?? 48 89 54 24 ?? 4C 89 44 24 ?? 4C 89 4C 24 ?? C3").GetPtr(v_Con_NPrintf); #endif // !DEDICATED diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index 0cade5e0..b24557d3 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -41,7 +41,7 @@ endif() if( ${PROJECT_NAME} STREQUAL "server_static" ) -add_sources( SOURCE_GROUP "Server" +add_sources( SOURCE_GROUP "AI" "server/ai_network.cpp" "server/ai_network.h" "server/ai_networkmanager.cpp" @@ -49,36 +49,59 @@ add_sources( SOURCE_GROUP "Server" "server/ai_node.h" "server/ai_utility.cpp" "server/ai_utility.h" + "server/detour_impl.h" +) + +add_sources( SOURCE_GROUP "Entity" "server/baseanimating.cpp" "server/baseanimating.h" "server/baseanimatingoverlay.h" "server/basecombatcharacter.h" "server/baseentity.cpp" "server/baseentity.h" - "server/cbase.cpp" - "server/cbase.h" - "server/detour_impl.h" "server/entitylist.cpp" "server/entitylist.h" "server/entityoutput.cpp" "server/entityoutput.h" +) + +add_sources( SOURCE_GROUP "Network" + "server/networkproperty.cpp" + "server/networkproperty.h" +) + +add_sources( SOURCE_GROUP "Player" + "server/player.cpp" + "server/player.h" + "server/playerlocaldata.h" +) + +add_sources( SOURCE_GROUP "Script" + "server/vscript_server.cpp" + "server/vscript_server.h" +) + +add_sources( SOURCE_GROUP "Physics" + "server/physics_main.cpp" + "server/physics_main.h" +) + +add_sources( SOURCE_GROUP "Utility" + "server/cbase.cpp" + "server/cbase.h" "server/gameinterface.cpp" "server/gameinterface.h" "server/movehelper_server.cpp" "server/movehelper_server.h" - "server/networkproperty.cpp" - "server/networkproperty.h" - "server/physics_main.cpp" - "server/physics_main.h" - "server/player.cpp" - "server/player.h" - "server/playerlocaldata.h" "server/util_server.cpp" "server/util_server.h" "server/variant_t.cpp" "server/variant_t.h" - "server/vscript_server.cpp" - "server/vscript_server.h" +) + +add_sources( SOURCE_GROUP "LiveAPI" + "server/liveapi/liveapi.cpp" + "server/liveapi/liveapi.h" ) add_sources( SOURCE_GROUP "Public" @@ -152,6 +175,7 @@ endif() target_include_directories( ${PROJECT_NAME} PRIVATE "${ENGINE_SOURCE_DIR}/tier0/" "${ENGINE_SOURCE_DIR}/tier1/" + "${THIRDPARTY_SOURCE_DIR}/mbedtls/include" ) endmacro() diff --git a/src/game/client/vscript_client.cpp b/src/game/client/vscript_client.cpp index bf420316..81e451b8 100644 --- a/src/game/client/vscript_client.cpp +++ b/src/game/client/vscript_client.cpp @@ -60,7 +60,7 @@ static ConCommand script_ui("script_ui", SQVM_UIScript_f, "Run input code as UI //----------------------------------------------------------------------------- // Purpose: checks if the server index is valid, raises an error if not //----------------------------------------------------------------------------- -static SQBool Script_CheckServerIndex(HSQUIRRELVM v, SQInteger iServer) +static SQBool Script_CheckServerIndexAndFailure(HSQUIRRELVM v, SQInteger iServer) { SQInteger iCount = static_cast(g_ServerListManager.m_vServerList.size()); @@ -69,6 +69,11 @@ static SQBool Script_CheckServerIndex(HSQUIRRELVM v, SQInteger iServer) v_SQVM_RaiseError(v, "Index must be less than %i.\n", iCount); return false; } + else if (iServer == -1) // If its still -1, then 'sq_getinteger' failed + { + v_SQVM_RaiseError(v, "Invalid argument type provided.\n"); + return false; + } return true; } @@ -89,7 +94,7 @@ namespace VScriptCode g_ServerListManager.RefreshServerList(serverMessage, iCount); sq_pushinteger(v, static_cast(iCount)); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -100,7 +105,7 @@ namespace VScriptCode size_t iCount = g_ServerListManager.m_vServerList.size(); sq_pushinteger(v, static_cast(iCount)); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -108,10 +113,13 @@ namespace VScriptCode //----------------------------------------------------------------------------- SQRESULT GetHiddenServerName(HSQUIRRELVM v) { - SQChar* privateToken = sq_getstring(v, 1); + const SQChar* privateToken = nullptr; - if (!VALID_CHARSTAR(privateToken)) - return SQ_OK; + if (SQ_FAILED(sq_getstring(v, 2, &privateToken)) || VALID_CHARSTAR(privateToken)) + { + v_SQVM_ScriptError("Empty or null private token"); + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); + } string hiddenServerRequestMessage; NetGameServer_t serverListing; @@ -120,28 +128,22 @@ namespace VScriptCode if (!result) { if (hiddenServerRequestMessage.empty()) - { sq_pushstring(v, "Request failed", -1); - } else { hiddenServerRequestMessage = Format("Request failed: %s", hiddenServerRequestMessage.c_str()); sq_pushstring(v, hiddenServerRequestMessage.c_str(), -1); } - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } if (serverListing.name.empty()) { if (hiddenServerRequestMessage.empty()) - { hiddenServerRequestMessage = Format("Server listing empty"); - } else - { hiddenServerRequestMessage = Format("Server listing empty: %s", hiddenServerRequestMessage.c_str()); - } sq_pushstring(v, hiddenServerRequestMessage.c_str(), -1); } @@ -151,7 +153,7 @@ namespace VScriptCode sq_pushstring(v, hiddenServerRequestMessage.c_str(), -1); } - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -160,17 +162,17 @@ namespace VScriptCode SQRESULT GetServerName(HSQUIRRELVM v) { AUTO_LOCK(g_ServerListManager.m_Mutex); - SQInteger iServer = sq_getinteger(v, 1); - if (!Script_CheckServerIndex(v, iServer)) - { - return SQ_ERROR; - } + SQInteger iServer = -1; + sq_getinteger(v, 2, &iServer); + + if (!Script_CheckServerIndexAndFailure(v, iServer)) + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); const string& serverName = g_ServerListManager.m_vServerList[iServer].name; sq_pushstring(v, serverName.c_str(), -1); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -179,17 +181,17 @@ namespace VScriptCode SQRESULT GetServerDescription(HSQUIRRELVM v) { AUTO_LOCK(g_ServerListManager.m_Mutex); - SQInteger iServer = sq_getinteger(v, 1); - if (!Script_CheckServerIndex(v, iServer)) - { - return SQ_ERROR; - } + SQInteger iServer = -1; + sq_getinteger(v, 2, &iServer); + + if (!Script_CheckServerIndexAndFailure(v, iServer)) + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); const string& serverDescription = g_ServerListManager.m_vServerList[iServer].description; sq_pushstring(v, serverDescription.c_str(), -1); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -198,17 +200,17 @@ namespace VScriptCode SQRESULT GetServerMap(HSQUIRRELVM v) { AUTO_LOCK(g_ServerListManager.m_Mutex); - SQInteger iServer = sq_getinteger(v, 1); - if (!Script_CheckServerIndex(v, iServer)) - { - return SQ_ERROR; - } + SQInteger iServer = -1; + sq_getinteger(v, 2, &iServer); + + if (!Script_CheckServerIndexAndFailure(v, iServer)) + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); const string& svServerMapName = g_ServerListManager.m_vServerList[iServer].map; sq_pushstring(v, svServerMapName.c_str(), -1); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -217,17 +219,17 @@ namespace VScriptCode SQRESULT GetServerPlaylist(HSQUIRRELVM v) { AUTO_LOCK(g_ServerListManager.m_Mutex); - SQInteger iServer = sq_getinteger(v, 1); - if (!Script_CheckServerIndex(v, iServer)) - { - return SQ_ERROR; - } + SQInteger iServer = -1; + sq_getinteger(v, 2, &iServer); + + if (!Script_CheckServerIndexAndFailure(v, iServer)) + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); const string& serverPlaylist = g_ServerListManager.m_vServerList[iServer].playlist; sq_pushstring(v, serverPlaylist.c_str(), -1); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -236,17 +238,17 @@ namespace VScriptCode SQRESULT GetServerCurrentPlayers(HSQUIRRELVM v) { AUTO_LOCK(g_ServerListManager.m_Mutex); - SQInteger iServer = sq_getinteger(v, 1); - if (!Script_CheckServerIndex(v, iServer)) - { - return SQ_ERROR; - } + SQInteger iServer = -1; + sq_getinteger(v, 2, &iServer); + + if (!Script_CheckServerIndexAndFailure(v, iServer)) + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); const SQInteger playerCount = g_ServerListManager.m_vServerList[iServer].numPlayers; sq_pushinteger(v, playerCount); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -255,17 +257,17 @@ namespace VScriptCode SQRESULT GetServerMaxPlayers(HSQUIRRELVM v) { AUTO_LOCK(g_ServerListManager.m_Mutex); - SQInteger iServer = sq_getinteger(v, 1); - if (!Script_CheckServerIndex(v, iServer)) - { - return SQ_ERROR; - } + SQInteger iServer = -1; + sq_getinteger(v, 2, &iServer); + + if (!Script_CheckServerIndexAndFailure(v, iServer)) + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); const SQInteger maxPlayers = g_ServerListManager.m_vServerList[iServer].maxPlayers; sq_pushinteger(v, maxPlayers); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -283,7 +285,10 @@ namespace VScriptCode PromoRightDesc }; - R5RPromoData ePromoIndex = static_cast(sq_getinteger(v, 1)); + SQInteger idx = 0; + sq_getinteger(v, 2, &idx); + + R5RPromoData ePromoIndex = static_cast(idx); const char* pszPromoKey; switch (ePromoIndex) @@ -326,7 +331,7 @@ namespace VScriptCode } sq_pushstring(v, pszPromoKey, -1); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } SQRESULT GetEULAContents(HSQUIRRELVM v) @@ -349,7 +354,7 @@ namespace VScriptCode sq_pushstring(v, error.c_str(), -1); } - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -357,16 +362,24 @@ namespace VScriptCode //----------------------------------------------------------------------------- SQRESULT ConnectToServer(HSQUIRRELVM v) { - SQChar* ipAddress = sq_getstring(v, 1); - SQChar* cryptoKey = sq_getstring(v, 2); + const SQChar* ipAddress = nullptr; + if (SQ_FAILED(sq_getstring(v, 2, &ipAddress))) + { + v_SQVM_ScriptError("Missing ip address"); + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); + } - if (!VALID_CHARSTAR(ipAddress) || VALID_CHARSTAR(cryptoKey)) - return SQ_OK; + const SQChar* cryptoKey = nullptr; + if (SQ_FAILED(sq_getstring(v, 3, &cryptoKey))) + { + v_SQVM_ScriptError("Missing encryption key"); + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); + } Msg(eDLL_T::UI, "Connecting to server with ip address '%s' and encryption key '%s'\n", ipAddress, cryptoKey); g_ServerListManager.ConnectToServer(ipAddress, cryptoKey); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -375,11 +388,13 @@ namespace VScriptCode SQRESULT ConnectToListedServer(HSQUIRRELVM v) { AUTO_LOCK(g_ServerListManager.m_Mutex); - SQInteger iServer = sq_getinteger(v, 1); - if (!Script_CheckServerIndex(v, iServer)) + SQInteger iServer = -1; + sq_getinteger(v, 2, &iServer); + + if (!Script_CheckServerIndexAndFailure(v, iServer)) { - return SQ_ERROR; + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); } const NetGameServer_t& gameServer = g_ServerListManager.m_vServerList[iServer]; @@ -387,7 +402,7 @@ namespace VScriptCode g_ServerListManager.ConnectToServer(gameServer.address, gameServer.port, gameServer.netKey); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -395,15 +410,19 @@ namespace VScriptCode //----------------------------------------------------------------------------- SQRESULT ConnectToHiddenServer(HSQUIRRELVM v) { - SQChar* privateToken = sq_getstring(v, 1); + const SQChar* privateToken = nullptr; + const SQRESULT strRet = sq_getstring(v, 2, &privateToken); - if (!VALID_CHARSTAR(privateToken)) - return SQ_OK; + if (SQ_FAILED(strRet) || VALID_CHARSTAR(privateToken)) + { + v_SQVM_ScriptError("Empty or null private token"); + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); + } string hiddenServerRequestMessage; NetGameServer_t netListing; - bool result = g_MasterServer.GetServerByToken(netListing, hiddenServerRequestMessage, privateToken); // Send token connect request. + const bool result = g_MasterServer.GetServerByToken(netListing, hiddenServerRequestMessage, privateToken); // Send token connect request. if (result) { g_ServerListManager.ConnectToServer(netListing.address, netListing.port, netListing.netKey); @@ -413,7 +432,7 @@ namespace VScriptCode Warning(eDLL_T::UI, "Failed to connect to private server: %s\n", hiddenServerRequestMessage.c_str()); } - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -422,7 +441,7 @@ namespace VScriptCode SQRESULT IsClientDLL(HSQUIRRELVM v) { sq_pushbool(v, ::IsClientDLL()); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } } } diff --git a/src/game/server/ai_utility.cpp b/src/game/server/ai_utility.cpp index f0aa8836..98de8a73 100644 --- a/src/game/server/ai_utility.cpp +++ b/src/game/server/ai_utility.cpp @@ -12,6 +12,8 @@ #include "game/server/detour_impl.h" #include "game/server/ai_networkmanager.h" +#include "vscript/languages/squirrel_re/vsquirrel.h" + static ConVar navmesh_always_reachable("navmesh_always_reachable", "0", FCVAR_DEVELOPMENTONLY, "Marks goal poly from agent poly as reachable regardless of table data ( !slower! )"); inline uint32_t g_HullMasks[10] = // Hull mask table [r5apex_ds.exe + 131a2f8]. @@ -46,7 +48,7 @@ void ClearNavMeshForHull(int hullSize) // Frees tiles, polys, tris, anything dynamically // allocated for this navmesh, and the navmesh itself. v_Detour_FreeNavMesh(nav); - delete nav; + free(nav); g_pNavMesh[hullSize] = nullptr; } @@ -128,12 +130,17 @@ bool Detour_IsLoaded() //----------------------------------------------------------------------------- void Detour_HotSwap() { + Assert(ThreadInMainOrServerFrameThread()); + g_pServerScript->ExecuteCodeCallback("CodeCallback_OnNavMeshHotSwapBegin"); + // Free and re-init NavMesh. Detour_LevelShutdown(); v_Detour_LevelInit(); if (!Detour_IsLoaded()) Error(eDLL_T::SERVER, NOERROR, "%s - Failed to hot swap NavMesh\n", __FUNCTION__); + + g_pServerScript->ExecuteCodeCallback("CodeCallback_OnNavMeshHotSwapEnd"); } /* @@ -161,7 +168,7 @@ static void Detour_HotSwap_f() Msg(eDLL_T::SERVER, "Hot swap took '%lf' seconds\n", timer.GetDuration().GetSeconds()); } -static ConCommand navmesh_hotswap("navmesh_hotswap", Detour_HotSwap_f, "Hot swap the NavMesh for all hulls", FCVAR_DEVELOPMENTONLY); +static ConCommand navmesh_hotswap("navmesh_hotswap", Detour_HotSwap_f, "Hot swap the NavMesh for all hulls", FCVAR_DEVELOPMENTONLY | FCVAR_SERVER_FRAME_THREAD); /////////////////////////////////////////////////////////////////////////////// void VRecast::Detour(const bool bAttach) const diff --git a/src/game/server/liveapi/liveapi.cpp b/src/game/server/liveapi/liveapi.cpp new file mode 100644 index 00000000..41ff6b92 --- /dev/null +++ b/src/game/server/liveapi/liveapi.cpp @@ -0,0 +1,2321 @@ +//=============================================================================// +// +// Purpose: ServerGameDLL LiveAPI implementation +// +// ---------------------------------------------------------------------------- +// TODO: +// - Add code callback for observer target changed ( event ObserverSwitched ) +// - Add code callback for player weapon switched ( event WeaponSwitched ) +// +//=============================================================================// +#include "tier1/depthcounter.h" +#include "mbedtls/include/mbedtls/sha512.h" +#include "rtech/liveapi/liveapi.h" +#include "engine/sys_utils.h" +#include "vscript/languages/squirrel_re/include/sqtable.h" +#include "vscript/languages/squirrel_re/include/sqarray.h" +#include "game/server/vscript_server.h" +#include "liveapi.h" + +#pragma warning(push) +#pragma warning(disable : 4505) +#include "protoc/events.pb.h" +#pragma warning(pop) + +#define LIVEAPI_MAX_ITEM_DEPTH 64 // The total nesting depth cannot exceed this number +#define LIVEAPI_SHA512_HASH_SIZE 64 + + +/* + ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ███╗ ███╗███████╗███████╗███████╗ █████╗ ██████╗ ███████╗███████╗ + ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ████╗ ████║██╔════╝██╔════╝██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔════╝ + █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██╔████╔██║█████╗ ███████╗███████╗███████║██║ ███╗█████╗ ███████╗ + ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║╚██╔╝██║██╔══╝ ╚════██║╚════██║██╔══██║██║ ██║██╔══╝ ╚════██║ + ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ╚═╝ ██║███████╗███████║███████║██║ ██║╚██████╔╝███████╗███████║ + ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝ + NOTE: messages are statically allocated to save on runtime overhead, each message is cleared after usage. +*/ + +static rtech::liveapi::AmmoUsed s_ammoUsed; +static rtech::liveapi::ArenasItemDeselected s_arenasItemDeselected; +static rtech::liveapi::ArenasItemSelected s_arenasItemSelected; +static rtech::liveapi::BannerCollected s_bannerCollected; +static rtech::liveapi::BlackMarketAction s_blackMarketAction; +//static rtech::liveapi::ChangeCamera s_changeCamera; +static rtech::liveapi::CharacterSelected s_characterSelected; +//static rtech::liveapi::CustomMatch_CreateLobby s_customMatch_CreateLobby; +//static rtech::liveapi::CustomMatch_GetLobbyPlayers s_customMatch_GetLobbyPlayers; +//static rtech::liveapi::CustomMatch_GetSettings s_customMatch_GetSettings; +//static rtech::liveapi::CustomMatch_JoinLobby s_customMatch_JoinLobby; +//static rtech::liveapi::CustomMatch_KickPlayer s_customMatch_KickPlayer; +//static rtech::liveapi::CustomMatch_LeaveLobby s_customMatch_LeaveLobby; +//static rtech::liveapi::CustomMatch_LobbyPlayers s_customMatch_LobbyPlayers; +//static rtech::liveapi::CustomMatch_SendChat s_customMatch_SendChat; +//static rtech::liveapi::CustomMatch_SetMatchmaking s_customMatch_SetMatchmaking; +//static rtech::liveapi::CustomMatch_SetReady s_customMatch_SetReady; +//static rtech::liveapi::CustomMatch_SetSettings s_customMatch_SetSettings; +//static rtech::liveapi::CustomMatch_SetTeam s_customMatch_SetTeam; +//static rtech::liveapi::CustomMatch_SetTeamName s_customMatch_SetTeamName; +static rtech::liveapi::CustomEvent s_customEvent; +static rtech::liveapi::GameStateChanged s_gameStateChanged; +static rtech::liveapi::GibraltarShieldAbsorbed s_gibraltarShieldAbsorbed; +static rtech::liveapi::GrenadeThrown s_grenadeThrown; +static rtech::liveapi::Init s_init; +static rtech::liveapi::InventoryDrop s_inventoryDrop; +static rtech::liveapi::InventoryPickUp s_inventoryPickUp; +static rtech::liveapi::InventoryUse s_inventoryUse; +static rtech::liveapi::LegendUpgradeSelected s_legendUpgradeSelected; +static rtech::liveapi::LiveAPIEvent s_liveAPIEvent; +static rtech::liveapi::MatchSetup s_matchSetup; +static rtech::liveapi::MatchStateEnd s_matchStateEnd; +static rtech::liveapi::ObserverAnnotation s_observerAnnotation; +static rtech::liveapi::ObserverSwitched s_observerSwitched; +//static rtech::liveapi::PauseToggle s_pauseToggle; +static rtech::liveapi::PlayerAbilityUsed s_playerAbilityUsed; +static rtech::liveapi::PlayerAssist s_playerAssist; +static rtech::liveapi::PlayerConnected s_playerConnected; +static rtech::liveapi::PlayerDamaged s_playerDamaged; +static rtech::liveapi::PlayerDisconnected s_playerDisconnected; +static rtech::liveapi::PlayerDowned s_playerDowned; +static rtech::liveapi::PlayerKilled s_playerKilled; +static rtech::liveapi::PlayerRespawnTeam s_playerRespawnTeam; +static rtech::liveapi::PlayerRevive s_playerRevive; +static rtech::liveapi::PlayerStatChanged s_playerStatChanged; +static rtech::liveapi::PlayerUpgradeTierChanged s_playerUpgradeTierChanged; +//static rtech::liveapi::Request s_request; +//static rtech::liveapi::RequestStatus s_requestStatus; +//static rtech::liveapi::Response s_response; +static rtech::liveapi::RevenantForgedShadowDamaged s_revenantForgedShadowDamaged; +static rtech::liveapi::RingFinishedClosing s_ringFinishedClosing; +static rtech::liveapi::RingStartClosing s_ringStartClosing; +static rtech::liveapi::SquadEliminated s_squadEliminated; +static rtech::liveapi::WarpGateUsed s_warpGateUsed; +static rtech::liveapi::WeaponSwitched s_weaponSwitched; +static rtech::liveapi::WraithPortal s_wraithPortal; +static rtech::liveapi::ZiplineUsed s_ziplineUsed; + + +/* + ███████╗███╗ ██╗██╗ ██╗███╗ ███╗███████╗██████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗███████╗ + ██╔════╝████╗ ██║██║ ██║████╗ ████║██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝ + █████╗ ██╔██╗ ██║██║ ██║██╔████╔██║█████╗ ██████╔╝███████║ ██║ ██║██║ ██║██╔██╗ ██║███████╗ + ██╔══╝ ██║╚██╗██║██║ ██║██║╚██╔╝██║██╔══╝ ██╔══██╗██╔══██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║ + ███████╗██║ ╚████║╚██████╔╝██║ ╚═╝ ██║███████╗██║ ██║██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║███████║ + ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ + NOTE: the values here must align with the enumeration exposed to scripts; see section "abstractions"! +*/ + +enum class eLiveAPI_EventTypes +{ + ammoUsed, + arenasItemDeselected, + arenasItemSelected, + + bannerCollected, + blackMarketAction, + //changeCamera, + characterSelected, + //checkedState, + //clientState, + + //customMatch_CreateLobby, + //customMatch_GetLobbyPlayers, + //customMatch_GetSettings, + //customMatch_JoinLobby, + //customMatch_KickPlayer, + //customMatch_LeaveLobby, + //customMatch_LobbyPlayer, + //customMatch_LobbyPlayers, + //customMatch_SendChat, + //customMatch_SetMatchmaking, + //customMatch_SetReady, + //customMatch_SetSettings, + //customMatch_SetTeam, + //customMatch_SetTeamName, + customEvent, + datacenter, + //gameConVar, + gameStateChanged, + gibraltarShieldAbsorbed, + //globalVars, + grenadeThrown, + init, + + inventoryDrop, + inventoryItem, + inventoryPickUp, + inventoryUse, + + legendUpgradeSelected, + liveAPIEvent, + loadoutConfiguration, + matchSetup, + matchStateEnd, + observerAnnotation, + observerSwitched, + //pauseToggle, + + player, + playerAbilityUsed, + playerAssist, + playerConnected, + playerDamaged, + playerDisconnected, + playerDowned, + playerKilled, + playerRespawnTeam, + playerRevive, + playerStatChanged, + playerUpgradeTierChanged, + + //request, + //requestStatus, + //response, + + revenantForgedShadowDamaged, + + ringFinishedClosing, + ringStartClosing, + + //runCommand, + //scriptCall, + squadEliminated, + //stateCheck, + //svcMsgOverflow, + //svcMsgRemoteScript, + vector3, + version, + warpGateUsed, + weaponSwitched, + wraithPortal, + ziplineUsed, +}; + + +/* + ██╗ ██╗████████╗██╗██╗ ██╗████████╗██╗ ██╗ ██╗ ███╗ ███╗ █████╗ ██████╗██████╗ ██████╗ ███████╗ + ██║ ██║╚══██╔══╝██║██║ ██║╚══██╔══╝╚██╗ ██╔╝ ██║ ████╗ ████║██╔══██╗██╔════╝██╔══██╗██╔═══██╗██╔════╝ + ██║ ██║ ██║ ██║██║ ██║ ██║ ╚████╔╝ ████████╗ ██╔████╔██║███████║██║ ██████╔╝██║ ██║███████╗ + ██║ ██║ ██║ ██║██║ ██║ ██║ ╚██╔╝ ██╔═██╔═╝ ██║╚██╔╝██║██╔══██║██║ ██╔══██╗██║ ██║╚════██║ + ╚██████╔╝ ██║ ██║███████╗██║ ██║ ██║ ██████║ ██║ ╚═╝ ██║██║ ██║╚██████╗██║ ██║╚██████╔╝███████║ + ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ +*/ + +static const char* LiveAPI_EventTypeToString(const eLiveAPI_EventTypes eventType) +{ + switch (eventType) + { + case eLiveAPI_EventTypes::ammoUsed: return "ammoUsed"; + case eLiveAPI_EventTypes::arenasItemDeselected: return "arenasItemDeselected"; + case eLiveAPI_EventTypes::arenasItemSelected: return "arenasItemSelected"; + case eLiveAPI_EventTypes::bannerCollected: return "bannerCollected"; + case eLiveAPI_EventTypes::blackMarketAction: return "blackMarketAction"; + //case eLiveAPI_EventTypes::changeCamera: return "changeCamera"; + case eLiveAPI_EventTypes::characterSelected: return "characterSelected"; + //case eLiveAPI_EventTypes::checkedState: return "checkedState"; + //case eLiveAPI_EventTypes::clientState: return "clientState"; + //case eLiveAPI_EventTypes::customMatch_CreateLobby: return "customMatch_CreateLobby"; + //case eLiveAPI_EventTypes::customMatch_GetLobbyPlayers: return "customMatch_GetLobbyPlayers"; + //case eLiveAPI_EventTypes::customMatch_GetSettings: return "customMatch_GetSettings"; + //case eLiveAPI_EventTypes::customMatch_JoinLobby: return "customMatch_JoinLobby"; + //case eLiveAPI_EventTypes::customMatch_KickPlayer: return "customMatch_KickPlayer"; + //case eLiveAPI_EventTypes::customMatch_LeaveLobby: return "customMatch_LeaveLobby"; + //case eLiveAPI_EventTypes::customMatch_LobbyPlayer: return "customMatch_LobbyPlayer"; + //case eLiveAPI_EventTypes::customMatch_LobbyPlayers: return "customMatch_LobbyPlayers"; + //case eLiveAPI_EventTypes::customMatch_SendChat: return "customMatch_SendChat"; + //case eLiveAPI_EventTypes::customMatch_SetMatchmaking: return "customMatch_SetMatchmaking"; + //case eLiveAPI_EventTypes::customMatch_SetReady: return "customMatch_SetReady"; + //case eLiveAPI_EventTypes::customMatch_SetSettings: return "customMatch_SetSettings"; + //case eLiveAPI_EventTypes::customMatch_SetTeam: return "customMatch_SetTeam"; + //case eLiveAPI_EventTypes::customMatch_SetTeamName: return "customMatch_SetTeamName"; + case eLiveAPI_EventTypes::customEvent: return "customEvent"; + case eLiveAPI_EventTypes::datacenter: return "datacenter"; + //case eLiveAPI_EventTypes::gameConVar: return "gameConVar"; + case eLiveAPI_EventTypes::gameStateChanged: return "gameStateChanged"; + case eLiveAPI_EventTypes::gibraltarShieldAbsorbed: return "gibraltarShieldAbsorbed"; + //case eLiveAPI_EventTypes::globalVars: return "globalVars"; + case eLiveAPI_EventTypes::grenadeThrown: return "grenadeThrown"; + case eLiveAPI_EventTypes::init: return "init"; + case eLiveAPI_EventTypes::inventoryDrop: return "inventoryDrop"; + case eLiveAPI_EventTypes::inventoryItem: return "inventoryItem"; + case eLiveAPI_EventTypes::inventoryPickUp: return "inventoryPickUp"; + case eLiveAPI_EventTypes::inventoryUse: return "inventoryUse"; + case eLiveAPI_EventTypes::legendUpgradeSelected: return "legendUpgradeSelected"; + case eLiveAPI_EventTypes::liveAPIEvent: return "liveAPIEvent"; + case eLiveAPI_EventTypes::loadoutConfiguration: return "loadoutConfiguration"; + case eLiveAPI_EventTypes::matchSetup: return "matchSetup"; + case eLiveAPI_EventTypes::matchStateEnd: return "matchStateEnd"; + case eLiveAPI_EventTypes::observerAnnotation: return "observerAnnotation"; + case eLiveAPI_EventTypes::observerSwitched: return "observerSwitched"; + //case eLiveAPI_EventTypes::pauseToggle: return "pauseToggle"; + case eLiveAPI_EventTypes::player: return "player"; + case eLiveAPI_EventTypes::playerAbilityUsed: return "playerAbilityUsed"; + case eLiveAPI_EventTypes::playerAssist: return "playerAssist"; + case eLiveAPI_EventTypes::playerConnected: return "playerConnected"; + case eLiveAPI_EventTypes::playerDamaged: return "playerDamaged"; + case eLiveAPI_EventTypes::playerDisconnected: return "playerDisconnected"; + case eLiveAPI_EventTypes::playerDowned: return "playerDowned"; + case eLiveAPI_EventTypes::playerKilled: return "playerKilled"; + case eLiveAPI_EventTypes::playerRespawnTeam: return "playerRespawnTeam"; + case eLiveAPI_EventTypes::playerRevive: return "playerRevive"; + case eLiveAPI_EventTypes::playerStatChanged: return "playerStatChanged"; + case eLiveAPI_EventTypes::playerUpgradeTierChanged: return "playerUpgradeTierChanged"; + //case eLiveAPI_EventTypes::request: return "request"; + //case eLiveAPI_EventTypes::requestStatus: return "requestStatus"; + //case eLiveAPI_EventTypes::response: return "response"; + case eLiveAPI_EventTypes::revenantForgedShadowDamaged: return "revenantForgedShadowDamaged"; + case eLiveAPI_EventTypes::ringFinishedClosing: return "ringFinishedClosing"; + case eLiveAPI_EventTypes::ringStartClosing: return "ringStartClosing"; + //case eLiveAPI_EventTypes::runCommand: return "runCommand"; + //case eLiveAPI_EventTypes::scriptCall: return "scriptCall"; + case eLiveAPI_EventTypes::squadEliminated: return "squadEliminated"; + //case eLiveAPI_EventTypes::stateCheck: return "stateCheck"; + //case eLiveAPI_EventTypes::svcMsgOverflow: return "svcMsgOverflow"; + //case eLiveAPI_EventTypes::svcMsgRemoteScript: return "svcMsgRemoteScript"; + case eLiveAPI_EventTypes::vector3: return "vector3"; + case eLiveAPI_EventTypes::version: return "version"; + case eLiveAPI_EventTypes::warpGateUsed: return "warpGateUsed"; + case eLiveAPI_EventTypes::weaponSwitched: return "weaponSwitched"; + case eLiveAPI_EventTypes::wraithPortal: return "wraithPortal"; + case eLiveAPI_EventTypes::ziplineUsed: return "ziplineUsed"; + + default: Assert(0); return "(unknown)"; + }; +} + +static bool LiveAPI_EnsureType(HSQUIRRELVM const v, const SQObjectPtr& obj, const SQObjectType expectType, + const google::protobuf::Message* const eventMsg, const SQInteger fieldNum) +{ + if (sq_type(obj) == expectType) + return true; + + const char* const expectTypeName = IdType2Name(expectType); + const char* const gotTypeName = IdType2Name(sq_type(obj)); + + if (eventMsg) + { + const google::protobuf::Descriptor* const descriptor = eventMsg->GetDescriptor(); + + if (fieldNum == -1) + { + v_SQVM_RaiseError(v, "Expected type \"%s\", got type \"%s\" for message \"%s\".", expectTypeName, gotTypeName, + descriptor->name().c_str()); + } + else + { + const google::protobuf::FieldDescriptor* const fieldDescriptor = descriptor->field(fieldNum); + + v_SQVM_RaiseError(v, "Expected type \"%s\", got type \"%s\" for field \"%s.%s\".", expectTypeName, gotTypeName, + descriptor->name().c_str(), fieldDescriptor->name().c_str()); + } + } + else + { + v_SQVM_RaiseError(v, "Expected type \"%s\", got type \"%s\".", expectTypeName, gotTypeName); + } + + return false; +} + +static bool LiveAPI_CheckSwitchType(HSQUIRRELVM const v, const SQObjectPtr& obj) +{ + if (sq_type(obj) == OT_INTEGER) + return true; + + v_SQVM_RaiseError(v, "Cannot switch on type \"%s\".", IdType2Name(sq_type(obj))); + return false; +} + +#define LIVEAPI_ENSURE_TYPE(v, obj, expectType, eventMsg, fieldNum) { if (!LiveAPI_EnsureType(v, obj, expectType, eventMsg, fieldNum)) return false; } +#define LIVEAPI_EMPTY_TABLE_ERROR(v, eventMsg) { v_SQVM_RaiseError(v, "Empty iterable on message \"%s\".", eventMsg->GetTypeName().c_str()); return false; } +#define LIVEAPI_FIELD_ERROR(v, fieldNum, eventMsg) { v_SQVM_RaiseError(v, "Field \"%d\" doesn't exist in message \"%s\".", fieldNum, eventMsg->GetTypeName().c_str()); return false; } +#define LIVEAPI_ONEOF_FIELD_ERROR(v, fieldNum, eventMsg) { v_SQVM_RaiseError(v, "Tried to set member \"%d\" of oneof field in message \"%s\" while another has already been set.", fieldNum, eventMsg->GetTypeName().c_str()); return false; } +#define LIVEAPI_UNSUPPORTED_TYPE_ERROR(v, gotType, eventMsg) {v_SQVM_RaiseError(v, "Value type \"%s\" is not supported for message \"%s\".\n", IdType2Name(gotType), eventMsg->GetTypeName().c_str()); return false; } +#define LIVEAPI_CHECK_RECURSION_DEPTH(v, currDepth) { if (currDepth > LIVEAPI_MAX_ITEM_DEPTH) { v_SQVM_RaiseError(v, "Exceeded nesting depth limit of \"%i\".", LIVEAPI_MAX_ITEM_DEPTH); return false; }} + + +/* + ██╗███╗ ██╗████████╗███████╗██████╗ ███╗ ███╗███████╗██████╗ ██╗ █████╗ ██████╗ ██╗ ██╗ + ██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗████╗ ████║██╔════╝██╔══██╗██║██╔══██╗██╔══██╗╚██╗ ██╔╝ + ██║██╔██╗ ██║ ██║ █████╗ ██████╔╝██╔████╔██║█████╗ ██║ ██║██║███████║██████╔╝ ╚████╔╝ + ██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██║╚██╔╝██║██╔══╝ ██║ ██║██║██╔══██║██╔══██╗ ╚██╔╝ + ██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ╚═╝ ██║███████╗██████╔╝██║██║ ██║██║ ██║ ██║ + ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═════╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ + Not used directly, but as part of other messages +*/ + +template +static void LiveAPI_SetCommonMessageFields(T const msg, const eLiveAPI_EventTypes eventType) +{ + if (msg->timestamp() == 0) + msg->set_timestamp(GetUnixTimeStamp()); + if (msg->category().empty()) + msg->set_category(LiveAPI_EventTypeToString(eventType)); +} + +static void LiveAPI_SetVector3D(rtech::liveapi::Vector3* const msgVec, const Vector3D* const dataVec) +{ + msgVec->set_x(dataVec->x); + msgVec->set_y(dataVec->y); + msgVec->set_z(dataVec->z); +} + +static void LiveAPI_SetDataCenter(rtech::liveapi::Datacenter* const msg, const char* const name) +{ + LiveAPI_SetCommonMessageFields(msg, eLiveAPI_EventTypes::datacenter); + msg->set_name(name); +} + +static void LiveAPI_SetVersion(rtech::liveapi::Version* const msg) +{ + msg->set_major_num(LIVEAPI_MAJOR_VERSION); + msg->set_minor_num(LIVEAPI_MINOR_VERSION); + msg->set_build_stamp(g_SDKDll.GetNTHeaders()->FileHeader.TimeDateStamp); + msg->set_revision(LIVEAPI_REVISION); +} + +static void LiveAPI_SetNucleusHash(std::string* const msg, const SQString* const nucleusId) +{ + static const char hexChars[] = "0123456789abcdef"; + uint8_t nucleusIdHash[LIVEAPI_SHA512_HASH_SIZE]; + + mbedtls_sha512(reinterpret_cast(nucleusId->_val), nucleusId->_len, nucleusIdHash, NULL); + + const size_t hashSize = liveapi_truncate_hash_fields.GetBool() + ? LIVEAPI_SHA512_HASH_SIZE / 4 + : LIVEAPI_SHA512_HASH_SIZE; + + msg->reserve((hashSize * 2) + 1); + msg->resize((hashSize * 2)); + + for (size_t i = 0; i < hashSize; i++) + { + (*msg)[i * 2] = hexChars[(nucleusIdHash[i] >> 4) & 0xf]; + (*msg)[i * 2 + 1] = hexChars[nucleusIdHash[i] & 0xf]; + } +} + +static bool LiveAPI_SetPlayerIdentityFields(HSQUIRRELVM const v, const SQTable* const table, rtech::liveapi::Player* const playerMsg) +{ + bool ranLoop = false; + + SQ_FOR_EACH_TABLE(table, i) + { + const SQTable::_HashNode& node = table->_nodes[i]; + + if (sq_isnull(node.key)) + continue; + + if (!ranLoop) + ranLoop = true; + + if (!LiveAPI_CheckSwitchType(v, node.key)) + return false; + + const SQInteger fieldNum = _integer(node.key); + const SQObjectPtr& obj = node.val; + + switch (fieldNum) + { + case rtech::liveapi::Player::kNameFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, playerMsg, fieldNum); + playerMsg->set_name(_stringval(obj)); + + break; + } + case rtech::liveapi::Player::kTeamIdFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, playerMsg, fieldNum); + playerMsg->set_teamid(_integer(obj)); + + break; + } + case rtech::liveapi::Player::kPosFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_VECTOR, playerMsg, fieldNum); + LiveAPI_SetVector3D(playerMsg->mutable_pos(), _vector3d(obj)); + + break; + } + case rtech::liveapi::Player::kAnglesFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_VECTOR, playerMsg, fieldNum); + LiveAPI_SetVector3D(playerMsg->mutable_angles(), _vector3d(obj)); + + break; + } + case rtech::liveapi::Player::kCurrentHealthFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, playerMsg, fieldNum); + playerMsg->set_currenthealth(_integer(obj)); + + break; + } + case rtech::liveapi::Player::kMaxHealthFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, playerMsg, fieldNum); + playerMsg->set_maxhealth(_integer(obj)); + + break; + } + case rtech::liveapi::Player::kShieldHealthFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, playerMsg, fieldNum); + playerMsg->set_shieldhealth(_integer(obj)); + + break; + } + case rtech::liveapi::Player::kShieldMaxHealthFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, playerMsg, fieldNum); + playerMsg->set_shieldmaxhealth(_integer(obj)); + + break; + } + case rtech::liveapi::Player::kNucleusHashFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, playerMsg, fieldNum); + LiveAPI_SetNucleusHash(playerMsg->mutable_nucleushash(), _string(obj)); + + break; + } + case rtech::liveapi::Player::kHardwareNameFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, playerMsg, fieldNum); + playerMsg->set_hardwarename(_stringval(obj)); + + break; + } + case rtech::liveapi::Player::kTeamNameFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, playerMsg, fieldNum); + playerMsg->set_teamname(_stringval(obj)); + + break; + } + case rtech::liveapi::Player::kSquadIndexFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, playerMsg, fieldNum); + playerMsg->set_squadindex(_integer(obj)); + + break; + } + case rtech::liveapi::Player::kCharacterFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, playerMsg, fieldNum); + playerMsg->set_character(_stringval(obj)); + + break; + } + case rtech::liveapi::Player::kSkinFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, playerMsg, fieldNum); + playerMsg->set_skin(_stringval(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, playerMsg); + } + } + + if (!ranLoop) + LIVEAPI_EMPTY_TABLE_ERROR(v, playerMsg); + + return true; +} + +static bool LiveAPI_SetInventoryItem(HSQUIRRELVM const v, const SQTable* const table, rtech::liveapi::InventoryItem* const event) +{ + bool ranLoop = false; + + SQ_FOR_EACH_TABLE(table, i) + { + const SQTable::_HashNode& node = table->_nodes[i]; + + if (sq_isnull(node.key)) + continue; + + if (!ranLoop) + ranLoop = true; + + if (!LiveAPI_CheckSwitchType(v, node.key)) + return false; + + const SQInteger fieldNum = _integer(node.key); + const SQObjectPtr& obj = node.val; + + switch (fieldNum) + { + case rtech::liveapi::InventoryItem::kQuantityFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_quantity(_integer(obj)); + + break; + } + case rtech::liveapi::InventoryItem::kItemFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_item(_stringval(obj)); + + break; + } + case rtech::liveapi::InventoryItem::kExtraDataFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_extradata(_stringval(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + } + + if (!ranLoop) + LIVEAPI_EMPTY_TABLE_ERROR(v, event); + + return true; +} + +static bool LiveAPI_SetLoadoutConfiguration(HSQUIRRELVM const v, rtech::liveapi::LoadoutConfiguration* const msg, const SQTable* const table) +{ + bool ranOuterLoop = false; + bool ranInnerLoop = false; + + SQ_FOR_EACH_TABLE(table, i) + { + const SQTable::_HashNode& node = table->_nodes[i]; + + if (sq_isnull(node.key)) + continue; + + if (!ranOuterLoop) + ranOuterLoop = true; + + if (!LiveAPI_CheckSwitchType(v, node.key)) + return false; + + const SQInteger fieldNum = _integer(node.key); + const SQObjectPtr& obj = node.val; + + rtech::liveapi::InventoryItem* (rtech::liveapi::LoadoutConfiguration:: * addFunctor)(); + + switch (fieldNum) + { + case rtech::liveapi::LoadoutConfiguration::kWeaponsFieldNumber: + { + addFunctor = &rtech::liveapi::LoadoutConfiguration::add_weapons; + break; + } + case rtech::liveapi::LoadoutConfiguration::kEquipmentFieldNumber: + { + addFunctor = &rtech::liveapi::LoadoutConfiguration::add_equipment; + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, msg); + } + + LIVEAPI_ENSURE_TYPE(v, obj, OT_ARRAY, msg, fieldNum); + const SQArray* const fieldArray = _array(obj); + + for (SQInteger j = 0; j < fieldArray->Size(); j++) + { + const SQObject& fieldObj = fieldArray->_values[j]; + + if (sq_isnull(fieldObj)) + continue; + + if (!ranInnerLoop) + ranInnerLoop = true; + + LIVEAPI_ENSURE_TYPE(v, fieldObj, OT_TABLE, msg, fieldNum); + + if (!LiveAPI_SetInventoryItem(v, _table(fieldObj), (msg->*addFunctor)())) + return false; + } + } + + if (!ranOuterLoop || !ranInnerLoop) + LIVEAPI_EMPTY_TABLE_ERROR(v, msg); + + return true; +} + + +/* + ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ██╗ ██╗ █████╗ ███╗ ██╗██████╗ ██╗ ███████╗██████╗ ███████╗ + ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ██║ ██║██╔══██╗████╗ ██║██╔══██╗██║ ██╔════╝██╔══██╗██╔════╝ + █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ███████║███████║██╔██╗ ██║██║ ██║██║ █████╗ ██████╔╝███████╗ + ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██╔══██║██╔══██║██║╚██╗██║██║ ██║██║ ██╔══╝ ██╔══██╗╚════██║ + ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║██║ ██║██║ ╚████║██████╔╝███████╗███████╗██║ ██║███████║ + ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝ + NOTE: one handler may be used for multiple events to cut down on boilerplate code, this is possible if a message + has the same structure layout as another message; the actual field numbers don't matter, but the field names do! +*/ + +static bool LiveAPI_HandleInitEvent(rtech::liveapi::Init* const event, const eLiveAPI_EventTypes eventType) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + event->set_gameversion(Sys_GetBuildString()); + LiveAPI_SetVersion(event->mutable_apiversion()); + event->set_platform(Sys_GetPlatformString()); + event->set_name(liveapi_session_name.GetString()); + + return true; +} + +static bool LiveAPI_HandleMatchSetup(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::MatchSetup* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::MatchSetup::kMapFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_map(_stringval(obj)); + + break; + } + case rtech::liveapi::MatchSetup::kPlaylistNameFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_playlistname(_stringval(obj)); + + break; + } + case rtech::liveapi::MatchSetup::kPlaylistDescFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_playlistdesc(_stringval(obj)); + + break; + } + case rtech::liveapi::MatchSetup::kDatacenterFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + LiveAPI_SetDataCenter(event->mutable_datacenter(), _stringval(obj)); + + break; + } + case rtech::liveapi::MatchSetup::kAimAssistOnFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_BOOL, event, fieldNum); + event->set_aimassiston(_bool(obj)); + + break; + } + case rtech::liveapi::MatchSetup::kAnonymousModeFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_BOOL, event, fieldNum); + event->set_anonymousmode(_bool(obj)); + + break; + } + case rtech::liveapi::MatchSetup::kServerIdFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_serverid(_stringval(obj)); + + break; + } + case rtech::liveapi::MatchSetup::kStartingLoadoutFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + + if (!LiveAPI_SetLoadoutConfiguration(v, event->mutable_startingloadout(), _table(obj))) + return false; + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleAmmoUsed(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::AmmoUsed* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::AmmoUsed::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::AmmoUsed::kAmmoTypeFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_ammotype(_stringval(obj)); + + break; + } + case rtech::liveapi::AmmoUsed::kAmountUsedFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_amountused(_integer(obj)); + + break; + } + case rtech::liveapi::AmmoUsed::kOldAmmoCountFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_oldammocount(_integer(obj)); + + break; + } + case rtech::liveapi::AmmoUsed::kNewAmmoCountFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_newammocount(_integer(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +template +static bool LiveAPI_HandleInventoryChange(HSQUIRRELVM const v, const SQObject& obj, T const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case event->kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case event->kItemFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_item(_stringval(obj)); + + break; + } + case event->kQuantityFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_quantity(_integer(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleInventoryDrop(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::InventoryDrop* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::InventoryDrop::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::InventoryDrop::kItemFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_item(_stringval(obj)); + + break; + } + case rtech::liveapi::InventoryDrop::kQuantityFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_quantity(_integer(obj)); + + break; + } + case rtech::liveapi::InventoryDrop::kExtraDataFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_ARRAY, event, fieldNum); + const SQArray* const fieldArray = _array(obj); + + for (SQInteger j = 0; j < fieldArray->Size(); j++) + { + const SQObject& fieldObj = fieldArray->_values[j]; + + if (sq_isnull(fieldObj)) + continue; + + LIVEAPI_ENSURE_TYPE(v, fieldObj, OT_STRING, event, fieldNum); + event->add_extradata(_stringval(fieldObj)); + } + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleGameStateChanged(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::GameStateChanged* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::GameStateChanged::kStateFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_state(_stringval(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleMatchStateEnd(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::MatchStateEnd* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::MatchStateEnd::kStateFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_state(_stringval(obj)); + + break; + } + case rtech::liveapi::MatchStateEnd::kWinnersFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_ARRAY, event, fieldNum); + const SQArray* const fieldArray = _array(obj); + + for (SQInteger j = 0; j < fieldArray->Size(); j++) + { + const SQObject& fieldObj = fieldArray->_values[j]; + + if (sq_isnull(fieldObj)) + continue; + + LIVEAPI_ENSURE_TYPE(v, fieldObj, OT_TABLE, event, fieldNum); + + if (!LiveAPI_SetPlayerIdentityFields(v, _table(fieldObj), event->add_winners())) + return false; // failure + } + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleObserverSwitched(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::ObserverSwitched* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::ObserverSwitched::kObserverFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_observer())) + return false; + + break; + } + case rtech::liveapi::ObserverSwitched::kTargetFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_target())) + return false; + + break; + } + case rtech::liveapi::ObserverSwitched::kTargetTeamFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_ARRAY, event, fieldNum); + const SQArray* const fieldArray = _array(obj); + + for (SQInteger j = 0; j < fieldArray->Size(); j++) + { + const SQObject& fieldObj = fieldArray->_values[j]; + + if (sq_isnull(fieldObj)) + continue; + + LIVEAPI_ENSURE_TYPE(v, fieldObj, OT_TABLE, event, fieldNum); + + if (!LiveAPI_SetPlayerIdentityFields(v, _table(fieldObj), event->add_targetteam())) + return false; // failure + } + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleObserverAnnotation(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::ObserverAnnotation* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::ObserverAnnotation::kAnnotationSerialFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_annotationserial(_integer(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleBannerCollected(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::BannerCollected* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::BannerCollected::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::BannerCollected::kCollectedFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_collected())) + return false; + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandlePlayerRevive(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::PlayerRevive* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::PlayerRevive::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::PlayerRevive::kRevivedFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_revived())) + return false; + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandlePlayerDisconnected(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::PlayerDisconnected* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::PlayerDisconnected::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::PlayerDisconnected::kCanReconnectFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_BOOL, event, fieldNum); + event->set_canreconnect(_bool(obj)); + + break; + } + case rtech::liveapi::PlayerDisconnected::kIsAliveFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_BOOL, event, fieldNum); + event->set_isalive(_bool(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +template +static bool LiveAPI_HandlePlayerAttackCommon(HSQUIRRELVM const v, const SQObject& obj, T const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case event->kAttackerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_attacker())) + return false; + + break; + } + case event->kVictimFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_victim())) + return false; + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandlePlayerDowned(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::PlayerDowned* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::PlayerDowned::kAttackerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_attacker())) + return false; + + break; + } + case rtech::liveapi::PlayerDowned::kVictimFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_victim())) + return false; + + break; + } + case rtech::liveapi::PlayerDowned::kWeaponFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_weapon(_stringval(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandlePlayerKilled(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::PlayerKilled* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::PlayerKilled::kAttackerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_attacker())) + return false; + + break; + } + case rtech::liveapi::PlayerKilled::kVictimFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_victim())) + return false; + + break; + } + case rtech::liveapi::PlayerKilled::kAwardedToFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_awardedto())) + return false; + + break; + } + case rtech::liveapi::PlayerKilled::kWeaponFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_weapon(_stringval(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +template +static bool LiveAPI_HandleAbilityDamaged(HSQUIRRELVM const v, const SQObject& obj, T const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case event->kAttackerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_attacker())) + return false; + + break; + } + case event->kVictimFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_victim())) + return false; + + break; + } + case event->kDamageInflictedFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_damageinflicted(_integer(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandlePlayerDamaged(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::PlayerDamaged* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::PlayerDamaged::kAttackerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_attacker())) + return false; + + break; + } + case rtech::liveapi::PlayerDamaged::kVictimFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_victim())) + return false; + + break; + } + case rtech::liveapi::PlayerDamaged::kWeaponFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_weapon(_stringval(obj)); + + break; + } + case rtech::liveapi::PlayerDamaged::kDamageInflictedFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_damageinflicted(_integer(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandlePlayerAssist(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::PlayerAssist* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::PlayerAssist::kAssistantFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_assistant())) + return false; + + break; + } + case rtech::liveapi::PlayerAssist::kVictimFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_victim())) + return false; + + break; + } + case rtech::liveapi::PlayerAssist::kWeaponFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_weapon(_stringval(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +// CharacterSelected +// PlayerConnected +// WraithPortal +// WarpGateUsed +template +static bool LiveAPI_HandleSimplePlayerMessage(HSQUIRRELVM const v, const SQObject& obj, T const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case event->kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleRingFinishedClosing(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::RingFinishedClosing* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case event->kStageFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_stage(_integer(obj)); + + break; + } + case event->kCenterFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_VECTOR, event, fieldNum); + LiveAPI_SetVector3D(event->mutable_center(), _vector3d(obj)); + + break; + } + case event->kCurrentRadiusFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_FLOAT, event, fieldNum); + event->set_currentradius(_float(obj)); + + break; + } + case event->kShrinkDurationFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_FLOAT, event, fieldNum); + event->set_shrinkduration(_float(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleDeathFieldStartClosing(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::RingStartClosing* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::RingStartClosing::kStageFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_stage(_integer(obj)); + + break; + } + case rtech::liveapi::RingStartClosing::kCenterFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_VECTOR, event, fieldNum); + LiveAPI_SetVector3D(event->mutable_center(), _vector3d(obj)); + + break; + } + case rtech::liveapi::RingStartClosing::kCurrentRadiusFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_FLOAT, event, fieldNum); + event->set_currentradius(_float(obj)); + + break; + } + case rtech::liveapi::RingStartClosing::kEndRadiusFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_FLOAT, event, fieldNum); + event->set_endradius(_float(obj)); + + break; + } + case rtech::liveapi::RingStartClosing::kShrinkDurationFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_FLOAT, event, fieldNum); + event->set_shrinkduration(_float(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleSquadEliminated(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::SquadEliminated* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::SquadEliminated::kPlayersFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_ARRAY, event, fieldNum); + const SQArray* const fieldArray = _array(obj); + + for (SQInteger j = 0; j < fieldArray->Size(); j++) + { + const SQObject& fieldObj = fieldArray->_values[j]; + + if (sq_isnull(fieldObj)) + continue; + + LIVEAPI_ENSURE_TYPE(v, fieldObj, OT_TABLE, event, fieldNum); + rtech::liveapi::Player* const player = event->add_players(); + + if (!LiveAPI_SetPlayerIdentityFields(v, _table(fieldObj), player)) + return false; // failure + } + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +template +static bool LiveAPI_HandleLinkedEntityEvent(HSQUIRRELVM const v, const SQObject& obj, T const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case event->kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case event->kLinkedEntityFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_linkedentity(_stringval(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleWeaponSwitchedEvent(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::WeaponSwitched* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::WeaponSwitched::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::WeaponSwitched::kOldWeaponFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_oldweapon(_stringval(obj)); + + break; + } + case rtech::liveapi::WeaponSwitched::kNewWeaponFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_newweapon(_stringval(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleBlackMarketActionEvent(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::BlackMarketAction* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::BlackMarketAction::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::BlackMarketAction::kItemFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_item(_stringval(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandlePlayerUpgradeTierChanged(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::PlayerUpgradeTierChanged* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::PlayerUpgradeTierChanged::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::PlayerUpgradeTierChanged::kLevelFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_level(_integer(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandleLegendUpgradeSelected(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::LegendUpgradeSelected* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::LegendUpgradeSelected::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::LegendUpgradeSelected::kUpgradeNameFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_upgradename(_stringval(obj)); + + break; + } + case rtech::liveapi::LegendUpgradeSelected::kUpgradeDescFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_upgradedesc(_stringval(obj)); + + break; + } + case rtech::liveapi::LegendUpgradeSelected::kLevelFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_level(_integer(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandlePlayerRespawnTeam(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::PlayerRespawnTeam* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::PlayerRespawnTeam::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::PlayerRespawnTeam::kRespawnedFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_ARRAY, event, fieldNum); + const SQArray* const fieldArray = _array(obj); + + for (SQInteger j = 0; j < fieldArray->Size(); j++) + { + const SQObject& fieldObj = fieldArray->_values[j]; + + if (sq_isnull(fieldObj)) + continue; + + LIVEAPI_ENSURE_TYPE(v, fieldObj, OT_TABLE, event, fieldNum); + rtech::liveapi::Player* const player = event->add_respawned(); + + if (!LiveAPI_SetPlayerIdentityFields(v, _table(fieldObj), player)) + return false; // failure + } + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static bool LiveAPI_HandlePlayerStatChanged(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::PlayerStatChanged* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::PlayerStatChanged::kPlayerFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetPlayerIdentityFields(v, _table(obj), event->mutable_player())) + return false; + + break; + } + case rtech::liveapi::PlayerStatChanged::kStatNameFieldNumber: + { + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_statname(_stringval(obj)); + + break; + } + case rtech::liveapi::PlayerStatChanged::kIntValueFieldNumber: + { + if (event->newValue_case() != rtech::liveapi::PlayerStatChanged::NEWVALUE_NOT_SET) + LIVEAPI_ONEOF_FIELD_ERROR(v, fieldNum, event); + + LIVEAPI_ENSURE_TYPE(v, obj, OT_INTEGER, event, fieldNum); + event->set_intvalue(_integer(obj)); + + break; + } + case rtech::liveapi::PlayerStatChanged::kFloatValueFieldNumber: + { + if (event->newValue_case() != rtech::liveapi::PlayerStatChanged::NEWVALUE_NOT_SET) + LIVEAPI_ONEOF_FIELD_ERROR(v, fieldNum, event); + + LIVEAPI_ENSURE_TYPE(v, obj, OT_FLOAT, event, fieldNum); + event->set_floatvalue(_float(obj)); + + break; + } + case rtech::liveapi::PlayerStatChanged::kBoolValueFieldNumber: + { + if (event->newValue_case() != rtech::liveapi::PlayerStatChanged::NEWVALUE_NOT_SET) + LIVEAPI_ONEOF_FIELD_ERROR(v, fieldNum, event); + + LIVEAPI_ENSURE_TYPE(v, obj, OT_BOOL, event, fieldNum); + event->set_boolvalue(_bool(obj)); + + break; + } + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +static void LiveAPI_SetCustomVectorField(google::protobuf::Struct* const structData, const Vector3D* const vecData) +{ + google::protobuf::Map* const fieldData = structData->mutable_fields(); + + (*fieldData)["x"].set_number_value(vecData->x); + (*fieldData)["y"].set_number_value(vecData->y); + (*fieldData)["z"].set_number_value(vecData->z); +} + +static bool LiveAPI_SetCustomTableFields(HSQUIRRELVM const v, google::protobuf::Struct* const structData, const SQTable* const tableData); +static bool LiveAPI_SetCustomArrayFields(HSQUIRRELVM const v, google::protobuf::ListValue* const listData, const SQArray* const arrayData); + +static int s_currentDepth = 0; + +static bool LiveAPI_SetCustomArrayFields(HSQUIRRELVM const v, google::protobuf::ListValue* const listData, const SQArray* const arrayData) +{ + CDepthCounter counter(s_currentDepth); + bool ranLoop = false; + + for (SQInteger i = 0; i < arrayData->Size(); i++) + { + const SQObject& valueObj = arrayData->_values[i]; + + if (sq_isnull(valueObj)) + continue; + + if (!ranLoop) + ranLoop = true; + + const SQObjectType valueType = sq_type(valueObj); + + switch (valueType) + { + case OT_BOOL: + listData->add_values()->set_bool_value(_bool(valueObj)); + break; + case OT_INTEGER: + listData->add_values()->set_number_value(_integer(valueObj)); + break; + case OT_FLOAT: + listData->add_values()->set_number_value(_float(valueObj)); + break; + case OT_STRING: + listData->add_values()->set_string_value(_stringval(valueObj)); + break; + case OT_VECTOR: + LiveAPI_SetCustomVectorField(listData->add_values()->mutable_struct_value(), _vector3d(valueObj)); + break; + case OT_ARRAY: + LIVEAPI_CHECK_RECURSION_DEPTH(v, counter.Get()); + + if (arrayData == _array(valueObj)) + { + v_SQVM_RaiseError(v, "Attempted to nest array at depth \"%i\" into itself at index \"%i\".", counter.Get(), i); + return false; + } + + if (!LiveAPI_SetCustomArrayFields(v, listData->add_values()->mutable_list_value(), _array(valueObj))) + return false; + + break; + case OT_TABLE: + LIVEAPI_CHECK_RECURSION_DEPTH(v, counter.Get()); + + if (!LiveAPI_SetCustomTableFields(v, listData->add_values()->mutable_struct_value(), _table(valueObj))) + return false; + + break; + default: + LIVEAPI_UNSUPPORTED_TYPE_ERROR(v, valueType, listData); + } + } + + if (!ranLoop) + LIVEAPI_EMPTY_TABLE_ERROR(v, listData); + + return true; +} + +static bool LiveAPI_SetCustomTableFields(HSQUIRRELVM const v, google::protobuf::Struct* const structData, const SQTable* const tableData) +{ + CDepthCounter counter(s_currentDepth); + bool ranLoop = false; + + SQ_FOR_EACH_TABLE(tableData, i) + { + const SQTable::_HashNode& node = tableData->_nodes[i]; + + if (sq_isnull(node.key)) + continue; + + if (!ranLoop) + ranLoop = true; + + const SQObjectType keyType = sq_type(node.key); + + if (keyType != OT_STRING) + { + v_SQVM_RaiseError(v, "Key in table must be a \"%s\", got \"%s\" for message \"%s\" at depth \"%i\" at index \"%i\".", + IdType2Name(OT_STRING), IdType2Name(keyType), structData->GetTypeName().c_str(), counter.Get(), i); + + return false; + } + + const SQObjectType valueType = sq_type(node.val); + + switch (valueType) + { + case OT_BOOL: + (*structData->mutable_fields())[_stringval(node.key)].set_bool_value(_bool(node.val)); + break; + case OT_INTEGER: + (*structData->mutable_fields())[_stringval(node.key)].set_number_value(_integer(node.val)); + break; + case OT_FLOAT: + (*structData->mutable_fields())[_stringval(node.key)].set_number_value(_float(node.val)); + break; + case OT_STRING: + (*structData->mutable_fields())[_stringval(node.key)].set_string_value(_stringval(node.val)); + break; + case OT_VECTOR: + LiveAPI_SetCustomVectorField((*structData->mutable_fields())[_stringval(node.key)].mutable_struct_value(), _vector3d(node.val)); + break; + case OT_ARRAY: + LIVEAPI_CHECK_RECURSION_DEPTH(v, counter.Get()); + + if (!LiveAPI_SetCustomArrayFields(v, (*structData->mutable_fields())[_stringval(node.key)].mutable_list_value(), _array(node.val))) + return false; + + break; + case OT_TABLE: + LIVEAPI_CHECK_RECURSION_DEPTH(v, counter.Get()); + + if (tableData == _table(node.val)) + { + v_SQVM_RaiseError(v, "Attempted to nest table at depth \"%i\" into itself at index \"%i\".", counter.Get(), i); + return false; + } + + if (!LiveAPI_SetCustomTableFields(v, (*structData->mutable_fields())[_stringval(node.key)].mutable_struct_value(), _table(node.val))) + return false; + + break; + default: + LIVEAPI_UNSUPPORTED_TYPE_ERROR(v, valueType, structData); + } + } + + if (!ranLoop) + LIVEAPI_EMPTY_TABLE_ERROR(v, structData); + + return true; +} + +static bool LiveAPI_HandleCustomEvent(HSQUIRRELVM const v, const SQObject& obj, rtech::liveapi::CustomEvent* const event, + const eLiveAPI_EventTypes eventType, const SQInteger fieldNum) +{ + LiveAPI_SetCommonMessageFields(event, eventType); + + switch (fieldNum) + { + case rtech::liveapi::CustomEvent::kNameFieldNumber: + LIVEAPI_ENSURE_TYPE(v, obj, OT_STRING, event, fieldNum); + event->set_name(_stringval(obj)); + + break; + case rtech::liveapi::CustomEvent::kDataFieldNumber: + LIVEAPI_ENSURE_TYPE(v, obj, OT_TABLE, event, fieldNum); + if (!LiveAPI_SetCustomTableFields(v, event->mutable_data(), _table(obj))) + return false; + + break; + default: + LIVEAPI_FIELD_ERROR(v, fieldNum, event); + } + + return true; +} + +/* + ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ██████╗ ██╗███████╗██████╗ █████╗ ████████╗ ██████╗██╗ ██╗███████╗██████╗ + ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ██╔══██╗██║██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██║ ██║██╔════╝██╔══██╗ + █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ██║██║███████╗██████╔╝███████║ ██║ ██║ ███████║█████╗ ██████╔╝ + ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██║╚════██║██╔═══╝ ██╔══██║ ██║ ██║ ██╔══██║██╔══╝ ██╔══██╗ + ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██████╔╝██║███████║██║ ██║ ██║ ██║ ╚██████╗██║ ██║███████╗██║ ██║ + ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ +*/ + +static void LiveAPI_SendEvent(const google::protobuf::Message* const msg) +{ + s_liveAPIEvent.set_event_size(msg->ByteSize()); + s_liveAPIEvent.mutable_gamemessage()->PackFrom(*msg); + + LiveAPISystem()->LogEvent(&s_liveAPIEvent, &s_liveAPIEvent.gamemessage()); + s_liveAPIEvent.Clear(); +} + +static bool LiveAPI_HandleEventByCategory(HSQUIRRELVM const v, const SQTable* const table, const eLiveAPI_EventTypes eventType) +{ + google::protobuf::Message* msg = nullptr; + + SQ_FOR_EACH_TABLE(table, i) + { + const SQTable::_HashNode& node = table->_nodes[i]; + + if (sq_isnull(node.key)) + continue; + + if (!LiveAPI_CheckSwitchType(v, node.key)) + return false; + + const SQInteger fieldNum = _integer(node.key); + const SQObjectPtr& obj = node.val; + + bool ret = false; + + switch (eventType) + { + case eLiveAPI_EventTypes::init: + msg = &s_init; + ret = LiveAPI_HandleInitEvent(&s_init, eventType); + break; + case eLiveAPI_EventTypes::matchSetup: + msg = &s_matchSetup; + ret = LiveAPI_HandleMatchSetup(v, obj, &s_matchSetup, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::ammoUsed: + msg = &s_ammoUsed; + ret = LiveAPI_HandleAmmoUsed(v, obj, &s_ammoUsed, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::arenasItemDeselected: + msg = &s_arenasItemDeselected; + ret = LiveAPI_HandleInventoryChange(v, obj, &s_arenasItemDeselected, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::arenasItemSelected: + msg = &s_arenasItemSelected; + ret = LiveAPI_HandleInventoryChange(v, obj, &s_arenasItemSelected, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::bannerCollected: + msg = &s_bannerCollected; + ret = LiveAPI_HandleBannerCollected(v, obj, &s_bannerCollected, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::customEvent: + msg = &s_customEvent; + ret = LiveAPI_HandleCustomEvent(v, obj, &s_customEvent, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::inventoryPickUp: + msg = &s_inventoryPickUp; + ret = LiveAPI_HandleInventoryChange(v, obj, &s_inventoryPickUp, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::inventoryDrop: + msg = &s_inventoryDrop; + ret = LiveAPI_HandleInventoryDrop(v, obj, &s_inventoryDrop, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::inventoryUse: + msg = &s_inventoryUse; + ret = LiveAPI_HandleInventoryChange(v, obj, &s_inventoryUse, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::gameStateChanged: + msg = &s_gameStateChanged; + ret = LiveAPI_HandleGameStateChanged(v, obj, &s_gameStateChanged, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::matchStateEnd: + msg = &s_matchStateEnd; + ret = LiveAPI_HandleMatchStateEnd(v, obj, &s_matchStateEnd, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::characterSelected: + msg = &s_characterSelected; + ret = LiveAPI_HandleSimplePlayerMessage(v, obj, &s_characterSelected, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::warpGateUsed: + msg = &s_warpGateUsed; + ret = LiveAPI_HandleSimplePlayerMessage(v, obj, &s_warpGateUsed, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::wraithPortal: + msg = &s_wraithPortal; + ret = LiveAPI_HandleSimplePlayerMessage(v, obj, &s_wraithPortal, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerConnected: + msg = &s_playerConnected; + ret = LiveAPI_HandleSimplePlayerMessage(v, obj, &s_playerConnected, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerRevive: + msg = &s_playerRevive; + ret = LiveAPI_HandlePlayerRevive(v, obj, &s_playerRevive, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerDisconnected: + msg = &s_playerDisconnected; + ret = LiveAPI_HandlePlayerDisconnected(v, obj, &s_playerDisconnected, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerDamaged: + msg = &s_playerDamaged; + ret = LiveAPI_HandlePlayerDamaged(v, obj, &s_playerDamaged, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerDowned: + msg = &s_playerDowned; + ret = LiveAPI_HandlePlayerDowned(v, obj, &s_playerDowned, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerKilled: + msg = &s_playerKilled; + ret = LiveAPI_HandlePlayerKilled(v, obj, &s_playerKilled, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerAssist: + msg = &s_playerAssist; + ret = LiveAPI_HandlePlayerAssist(v, obj, &s_playerAssist, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerRespawnTeam: + msg = &s_playerRespawnTeam; + ret = LiveAPI_HandlePlayerRespawnTeam(v, obj, &s_playerRespawnTeam, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerStatChanged: + msg = &s_playerStatChanged; + ret = LiveAPI_HandlePlayerStatChanged(v, obj, &s_playerStatChanged, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerUpgradeTierChanged: + msg = &s_playerUpgradeTierChanged; + ret = LiveAPI_HandlePlayerUpgradeTierChanged(v, obj, &s_playerUpgradeTierChanged, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::legendUpgradeSelected: + msg = &s_legendUpgradeSelected; + ret = LiveAPI_HandleLegendUpgradeSelected(v, obj, &s_legendUpgradeSelected, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::gibraltarShieldAbsorbed: + msg = &s_gibraltarShieldAbsorbed; + ret = LiveAPI_HandleAbilityDamaged(v, obj, &s_gibraltarShieldAbsorbed, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::revenantForgedShadowDamaged: + msg = &s_revenantForgedShadowDamaged; + ret = LiveAPI_HandleAbilityDamaged(v, obj, &s_revenantForgedShadowDamaged, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::ringStartClosing: + msg = &s_ringStartClosing; + ret = LiveAPI_HandleDeathFieldStartClosing(v, obj, &s_ringStartClosing, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::ringFinishedClosing: + msg = &s_ringFinishedClosing; + ret = LiveAPI_HandleRingFinishedClosing(v, obj, &s_ringFinishedClosing, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::squadEliminated: + msg = &s_squadEliminated; + ret = LiveAPI_HandleSquadEliminated(v, obj, &s_squadEliminated, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::ziplineUsed: + msg = &s_ziplineUsed; + ret = LiveAPI_HandleLinkedEntityEvent(v, obj, &s_ziplineUsed, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::grenadeThrown: + msg = &s_grenadeThrown; + ret = LiveAPI_HandleLinkedEntityEvent(v, obj, &s_grenadeThrown, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::playerAbilityUsed: + msg = &s_playerAbilityUsed; + ret = LiveAPI_HandleLinkedEntityEvent(v, obj, &s_playerAbilityUsed, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::weaponSwitched: + msg = &s_weaponSwitched; + ret = LiveAPI_HandleWeaponSwitchedEvent(v, obj, &s_weaponSwitched, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::blackMarketAction: + msg = &s_blackMarketAction; + ret = LiveAPI_HandleBlackMarketActionEvent(v, obj, &s_blackMarketAction, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::observerSwitched: + msg = &s_observerSwitched; + ret = LiveAPI_HandleObserverSwitched(v, obj, &s_observerSwitched, eventType, fieldNum); + break; + case eLiveAPI_EventTypes::observerAnnotation: + msg = &s_observerAnnotation; + ret = LiveAPI_HandleObserverAnnotation(v, obj, &s_observerAnnotation, eventType, fieldNum); + break; + default: + v_SQVM_RaiseError(v, "Event type \"%d\" not found.", eventType); + return false; + } + + if (!ret) + { + Assert(msg); + + msg->Clear(); + return false; + } + } + + if (!msg) // Script bug, e.g. giving an empty table (either completely empty or filled with null) + { + v_SQVM_RaiseError(v, "Empty table on event type \"%d\".", eventType); + return false; + } + + LiveAPI_SendEvent(msg); + msg->Clear(); + + return true; +} + +/* + █████╗ ██████╗ ███████╗████████╗██████╗ █████╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗███████╗ + ██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝ + ███████║██████╔╝███████╗ ██║ ██████╔╝███████║██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗ + ██╔══██║██╔══██╗╚════██║ ██║ ██╔══██╗██╔══██║██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║ + ██║ ██║██████╔╝███████║ ██║ ██║ ██║██║ ██║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║███████║ + ╚═╝ ╚═╝╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ + Code exposed to scripts +*/ + +namespace VScriptCode +{ + namespace Server + { + SQRESULT LiveAPI_IsValidToRun(HSQUIRRELVM v); + SQRESULT LiveAPI_LogRaw(HSQUIRRELVM v); + + SQRESULT LiveAPI_StartLogging(HSQUIRRELVM v); + SQRESULT LiveAPI_StopLogging(HSQUIRRELVM v); + } +} + +SQRESULT VScriptCode::Server::LiveAPI_IsValidToRun(HSQUIRRELVM v) +{ + sq_pushbool(v, LiveAPISystem()->IsValidToRun()); + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); +} + +SQRESULT VScriptCode::Server::LiveAPI_LogRaw(HSQUIRRELVM v) +{ + if (!LiveAPISystem()->IsEnabled()) + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); + + SQRESULT result = SQ_OK; + const SQObjectPtr& object = v->GetUp(-2); + + if (sq_istable(object)) + { + const SQTable* const table = _table(object); + SQInteger eventType = 0; + + if (SQ_FAILED(sq_getinteger(v, 3, &eventType))) + { + v_SQVM_ScriptError("Second argument must be an integer."); + result = SQ_FAIL; + } + else if (!LiveAPI_HandleEventByCategory(v, table, eLiveAPI_EventTypes(eventType))) + result = SQ_ERROR; + } + else + { + v_SQVM_ScriptError("First argument must be a table."); + result = SQ_FAIL; + } + + SCRIPT_CHECK_AND_RETURN(v, result); +} + +SQRESULT VScriptCode::Server::LiveAPI_StartLogging(HSQUIRRELVM v) +{ + LiveAPISystem()->CreateLogger(); + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); +} + +SQRESULT VScriptCode::Server::LiveAPI_StopLogging(HSQUIRRELVM v) +{ + LiveAPISystem()->DestroyLogger(); + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); +} + +void Script_RegisterLiveAPIFunctions(CSquirrelVM* const s) +{ + DEFINE_SERVER_SCRIPTFUNC_NAMED(s, LiveAPI_IsValidToRun, "Whether the LiveAPI system is enabled and able to run", "bool", ""); + DEFINE_SERVER_SCRIPTFUNC_NAMED(s, LiveAPI_LogRaw, "VM bridge to the LiveAPI logger from scripts", "void", "table< int, var > data, int eventType"); + + DEFINE_SERVER_SCRIPTFUNC_NAMED(s, LiveAPI_StartLogging, "Start the LiveAPI session logger", "void", ""); + DEFINE_SERVER_SCRIPTFUNC_NAMED(s, LiveAPI_StopLogging, "Stop the LiveAPI session logger", "void", ""); +} + +void Script_RegisterLiveAPIEnums(CSquirrelVM* const s) +{ + DEFINE_SCRIPTENUM_NAMED(s, "eLiveAPI_EventTypes", 0, + "ammoUsed", + "arenasItemDeselected", + "arenasItemSelected", + "bannerCollected", + "blackMarketAction", + //"changeCamera", + "characterSelected", + //"checkedState", + //"clientState", + //"customMatch_CreateLobby", + //"customMatch_GetLobbyPlayers", + //"customMatch_GetSettings", + //"customMatch_JoinLobby", + //"customMatch_KickPlayer", + //"customMatch_LeaveLobby", + //"customMatch_LobbyPlayer", + //"customMatch_LobbyPlayers", + //"customMatch_SendChat", + //"customMatch_SetMatchmaking", + //"customMatch_SetReady", + //"customMatch_SetSettings", + //"customMatch_SetTeam", + //"customMatch_SetTeamName", + "customEvent", + "datacenter", + //"gameConVar", + "gameStateChanged", + "gibraltarShieldAbsorbed", + //"globalVars", + "grenadeThrown", + "init", + "inventoryDrop", + "inventoryItem", + "inventoryPickUp", + "inventoryUse", + "legendUpgradeSelected", + "liveAPIEvent", + "loadoutConfiguration", + "matchSetup", + "matchStateEnd", + "observerAnnotation", + "observerSwitched", + //"pauseToggle", + "player", + "playerAbilityUsed", + "playerAssist", + "playerConnected", + "playerDamaged", + "playerDisconnected", + "playerDowned", + "playerKilled", + "playerRespawnTeam", + "playerRevive", + "playerStatChanged", + "playerUpgradeTierChanged", + //"request", + //"requestStatus", + //"response", + "revenantForgedShadowDamaged", + "ringFinishedClosing", + "ringStartClosing", + //"runCommand", + //"scriptCall", + "squadEliminated", + //"stateCheck", + //"svcMsgOverflow", + //"svcMsgRemoteScript", + "vector3", + "version", + "warpGateUsed", + "weaponSwitched", + "wraithPortal", + "ziplineUsed" + ); +} diff --git a/src/game/server/liveapi/liveapi.h b/src/game/server/liveapi/liveapi.h new file mode 100644 index 00000000..df589e9f --- /dev/null +++ b/src/game/server/liveapi/liveapi.h @@ -0,0 +1,11 @@ +#ifndef SERVER_LIVEAPI_H +#define SERVER_LIVEAPI_H + +#define LIVEAPI_MAJOR_VERSION 0 +#define LIVEAPI_MINOR_VERSION 1 +#define LIVEAPI_REVISION "Rev: " MKSTRING(LIVEAPI_MAJOR_VERSION) "." MKSTRING(LIVEAPI_MINOR_VERSION) + +extern void Script_RegisterLiveAPIFunctions(CSquirrelVM* const s); +extern void Script_RegisterLiveAPIEnums(CSquirrelVM* const s); + +#endif // SERVER_LIVEAPI_H diff --git a/src/game/server/vscript_server.cpp b/src/game/server/vscript_server.cpp index 43ad588b..2123d4d7 100644 --- a/src/game/server/vscript_server.cpp +++ b/src/game/server/vscript_server.cpp @@ -14,6 +14,7 @@ #include "vscript/vscript.h" #include "vscript/languages/squirrel_re/include/sqvm.h" +#include "liveapi/liveapi.h" #include "vscript_server.h" #include #include @@ -33,7 +34,7 @@ static void SQVM_ServerScript_f(const CCommand& args) Script_Execute(args.ArgS(), SQCONTEXT::SERVER); } } -static ConCommand script("script", SQVM_ServerScript_f, "Run input code as SERVER script on the VM", FCVAR_DEVELOPMENTONLY | FCVAR_GAMEDLL | FCVAR_CHEAT); +static ConCommand script("script", SQVM_ServerScript_f, "Run input code as SERVER script on the VM", FCVAR_DEVELOPMENTONLY | FCVAR_GAMEDLL | FCVAR_CHEAT | FCVAR_SERVER_FRAME_THREAD); namespace VScriptCode { @@ -47,17 +48,25 @@ namespace VScriptCode //----------------------------------------------------------------------------- SQRESULT CreateServer(HSQUIRRELVM v) { - SQChar* serverName = sq_getstring(v, 1); - SQChar* serverDescription = sq_getstring(v, 2); - SQChar* serverMapName = sq_getstring(v, 3); - SQChar* serverPlaylist = sq_getstring(v, 4); - ServerVisibility_e serverVisibility = static_cast(sq_getinteger(v, 5)); + const SQChar* serverName = nullptr; + const SQChar* serverDescription = nullptr; + const SQChar* serverMapName = nullptr; + const SQChar* serverPlaylist = nullptr; + + sq_getstring(v, 2, &serverName); + sq_getstring(v, 3, &serverDescription); + sq_getstring(v, 4, &serverMapName); + sq_getstring(v, 5, &serverPlaylist); + + SQInteger serverVisibility = 0; + sq_getinteger(v, 6, &serverVisibility); if (!VALID_CHARSTAR(serverName) || !VALID_CHARSTAR(serverMapName) || !VALID_CHARSTAR(serverPlaylist)) { - return SQ_OK; + v_SQVM_ScriptError("Empty or null server criteria"); + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); } // Adjust browser settings. @@ -69,13 +78,10 @@ namespace VScriptCode details.playlist = serverPlaylist; // Launch server. - g_ServerHostManager.SetVisibility(serverVisibility); + g_ServerHostManager.SetVisibility(ServerVisibility_e(serverVisibility)); g_ServerHostManager.LaunchServer(g_pServer->IsActive()); - return SQ_OK; - - //v_SQVM_RaiseError(v, "\"%s\" is not supported on client builds.\n", "CreateServer"); - //return SQ_ERROR; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- // Purpose: shuts the server down and disconnects all clients @@ -85,7 +91,7 @@ namespace VScriptCode if (g_pHostState->m_bActiveGame) g_pHostState->m_iNextState = HostStates_t::HS_GAME_SHUTDOWN; - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -93,16 +99,24 @@ namespace VScriptCode //----------------------------------------------------------------------------- SQRESULT KickPlayerByName(HSQUIRRELVM v) { - SQChar* playerName = sq_getstring(v, 1); - SQChar* reason = sq_getstring(v, 2); + const SQChar* playerName = nullptr; + const SQChar* reason = nullptr; + + sq_getstring(v, 2, &playerName); + sq_getstring(v, 3, &reason); + + if (!VALID_CHARSTAR(playerName)) + { + v_SQVM_ScriptError("Empty or null player name"); + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); + } // Discard empty strings, this will use the default message instead. if (!VALID_CHARSTAR(reason)) reason = nullptr; g_BanSystem.KickPlayerByName(playerName, reason); - - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -110,16 +124,24 @@ namespace VScriptCode //----------------------------------------------------------------------------- SQRESULT KickPlayerById(HSQUIRRELVM v) { - SQChar* playerHandle = sq_getstring(v, 1); - SQChar* reason = sq_getstring(v, 2); + const SQChar* playerHandle = nullptr; + const SQChar* reason = nullptr; + + sq_getstring(v, 2, &playerHandle); + sq_getstring(v, 3, &reason); + + if (!VALID_CHARSTAR(playerHandle)) + { + v_SQVM_ScriptError("Empty or null player handle"); + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); + } // Discard empty strings, this will use the default message instead. if (!VALID_CHARSTAR(reason)) reason = nullptr; g_BanSystem.KickPlayerById(playerHandle, reason); - - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -127,16 +149,24 @@ namespace VScriptCode //----------------------------------------------------------------------------- SQRESULT BanPlayerByName(HSQUIRRELVM v) { - SQChar* playerName = sq_getstring(v, 1); - SQChar* reason = sq_getstring(v, 2); + const SQChar* playerName = nullptr; + const SQChar* reason = nullptr; + + sq_getstring(v, 2, &playerName); + sq_getstring(v, 3, &reason); + + if (!VALID_CHARSTAR(playerName)) + { + v_SQVM_ScriptError("Empty or null player name"); + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); + } // Discard empty strings, this will use the default message instead. if (!VALID_CHARSTAR(reason)) reason = nullptr; g_BanSystem.BanPlayerByName(playerName, reason); - - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -144,16 +174,24 @@ namespace VScriptCode //----------------------------------------------------------------------------- SQRESULT BanPlayerById(HSQUIRRELVM v) { - SQChar* playerHandle = sq_getstring(v, 1); - SQChar* reason = sq_getstring(v, 2); + const SQChar* playerHandle = nullptr; + const SQChar* reason = nullptr; + + sq_getstring(v, 2, &playerHandle); + sq_getstring(v, 3, &reason); + + if (!VALID_CHARSTAR(playerHandle)) + { + v_SQVM_ScriptError("Empty or null player handle"); + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); + } // Discard empty strings, this will use the default message instead. if (!VALID_CHARSTAR(reason)) reason = nullptr; g_BanSystem.BanPlayerById(playerHandle, reason); - - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -161,10 +199,18 @@ namespace VScriptCode //----------------------------------------------------------------------------- SQRESULT UnbanPlayer(HSQUIRRELVM v) { - SQChar* szCriteria = sq_getstring(v, 1); + const SQChar* szCriteria = nullptr; + sq_getstring(v, 2, &szCriteria); + + if (!VALID_CHARSTAR(szCriteria)) + { + v_SQVM_ScriptError("Empty or null player criteria"); + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); + } + g_BanSystem.UnbanPlayer(szCriteria); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -173,7 +219,7 @@ namespace VScriptCode SQRESULT GetNumHumanPlayers(HSQUIRRELVM v) { sq_pushinteger(v, g_pServer->GetNumHumanPlayers()); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -182,7 +228,7 @@ namespace VScriptCode SQRESULT GetNumFakeClients(HSQUIRRELVM v) { sq_pushinteger(v, g_pServer->GetNumFakeClients()); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -191,9 +237,9 @@ namespace VScriptCode SQRESULT IsServerActive(HSQUIRRELVM v) { bool isActive = g_pServer->IsActive(); - sq_pushbool(v, isActive); - return SQ_OK; + + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -202,7 +248,7 @@ namespace VScriptCode SQRESULT IsDedicated(HSQUIRRELVM v) { sq_pushbool(v, ::IsDedicated()); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } } } @@ -216,6 +262,13 @@ void Script_RegisterServerFunctions(CSquirrelVM* s) Script_RegisterCommonAbstractions(s); Script_RegisterCoreServerFunctions(s); Script_RegisterAdminPanelFunctions(s); + + Script_RegisterLiveAPIFunctions(s); +} + +void Script_RegisterServerEnums(CSquirrelVM* const s) +{ + Script_RegisterLiveAPIEnums(s); } //--------------------------------------------------------------------------------- diff --git a/src/game/server/vscript_server.h b/src/game/server/vscript_server.h index fc22dc32..d75cd394 100644 --- a/src/game/server/vscript_server.h +++ b/src/game/server/vscript_server.h @@ -1,5 +1,6 @@ #ifndef VSCRIPT_SERVER_H #define VSCRIPT_SERVER_H +#include "vscript/languages/squirrel_re/vsquirrel.h" namespace VScriptCode { @@ -26,6 +27,8 @@ void Script_RegisterServerFunctions(CSquirrelVM* s); void Script_RegisterCoreServerFunctions(CSquirrelVM* s); void Script_RegisterAdminPanelFunctions(CSquirrelVM* s); +void Script_RegisterServerEnums(CSquirrelVM* const s); + #define DEFINE_SERVER_SCRIPTFUNC_NAMED(s, functionName, helpString, \ returnType, parameters) \ s->RegisterFunction(#functionName, MKSTRING(Script_##functionName), \ diff --git a/src/game/shared/vscript_shared.cpp b/src/game/shared/vscript_shared.cpp index 646955ba..2ac26e93 100644 --- a/src/game/shared/vscript_shared.cpp +++ b/src/game/shared/vscript_shared.cpp @@ -28,7 +28,7 @@ namespace VScriptCode SQRESULT GetSDKVersion(HSQUIRRELVM v) { sq_pushstring(v, SDK_VERSION, -1); - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -39,7 +39,7 @@ namespace VScriptCode std::lock_guard l(g_InstalledMapsMutex); if (g_InstalledMaps.IsEmpty()) - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); sq_newarray(v, 0); @@ -51,7 +51,7 @@ namespace VScriptCode sq_arrayappend(v, -2); } - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } //----------------------------------------------------------------------------- @@ -62,7 +62,7 @@ namespace VScriptCode std::lock_guard l(g_PlaylistsVecMutex); if (g_vAllPlaylists.empty()) - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); sq_newarray(v, 0); for (const string& it : g_vAllPlaylists) @@ -71,7 +71,7 @@ namespace VScriptCode sq_arrayappend(v, -2); } - return SQ_OK; + SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } SQRESULT ScriptError(HSQUIRRELVM v) @@ -80,17 +80,10 @@ namespace VScriptCode SQInteger a4 = 0; if (SQVM_sprintf(v, 0, 1, &a4, &pString) < 0) - return SQ_ERROR; + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); v_SQVM_ScriptError("%s", pString); - - // this should be moved to a wrapper for all script funcs - if (*reinterpret_cast(&v->_sharedstate->gap43b9[127])) - { - v_SQVM_ThrowError(*reinterpret_cast(&v->_sharedstate->gap43b9[111]), v); - } - - return SQ_ERROR; + SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); } } } @@ -118,3 +111,37 @@ void Script_RegisterListenServerConstants(CSquirrelVM* s) const SQBool hasListenServer = !IsClientDLL(); s->RegisterConstant("LISTEN_SERVER", hasListenServer); } + +//--------------------------------------------------------------------------------- +// Purpose: server enums +// Input : *s - +//--------------------------------------------------------------------------------- +void Script_RegisterCommonEnums_Server(CSquirrelVM* const s) +{ + v_Script_RegisterCommonEnums_Server(s); + + if (ServerScriptRegisterEnum_Callback) + ServerScriptRegisterEnum_Callback(s); +} + +//--------------------------------------------------------------------------------- +// Purpose: client/ui enums +// Input : *s - +//--------------------------------------------------------------------------------- +void Script_RegisterCommonEnums_Client(CSquirrelVM* const s) +{ + v_Script_RegisterCommonEnums_Client(s); + + const SQCONTEXT context = s->GetContext(); + + if (context == SQCONTEXT::CLIENT && ClientScriptRegisterEnum_Callback) + ClientScriptRegisterEnum_Callback(s); + else if (context == SQCONTEXT::UI && UIScriptRegisterEnum_Callback) + UIScriptRegisterEnum_Callback(s); +} + +void VScriptShared::Detour(const bool bAttach) const +{ + DetourSetup(&v_Script_RegisterCommonEnums_Server, &Script_RegisterCommonEnums_Server, bAttach); + DetourSetup(&v_Script_RegisterCommonEnums_Client, &Script_RegisterCommonEnums_Client, bAttach); +} diff --git a/src/game/shared/vscript_shared.h b/src/game/shared/vscript_shared.h index e5e87593..dd57ef08 100644 --- a/src/game/shared/vscript_shared.h +++ b/src/game/shared/vscript_shared.h @@ -3,6 +3,9 @@ #include "vscript/languages/squirrel_re/include/squirrel.h" #include "vscript/languages/squirrel_re/vsquirrel.h" +inline void (*v_Script_RegisterCommonEnums_Server)(CSquirrelVM* const s); +inline void (*v_Script_RegisterCommonEnums_Client)(CSquirrelVM* const s); + inline void*(*v_Script_Remote_BeginRegisteringFunctions)(void); inline void*(*v_RestoreRemoteChecksumsFromSaveGame)(void* a1, void* a2); @@ -32,13 +35,22 @@ class VScriptShared : public IDetour { virtual void GetAdr(void) const { + LogFunAdr("Script_RegisterCommonEnums_Server", v_Script_RegisterCommonEnums_Server); + LogFunAdr("Script_RegisterCommonEnums_Client", v_Script_RegisterCommonEnums_Client); + LogFunAdr("Remote_BeginRegisteringFunctions", v_Script_Remote_BeginRegisteringFunctions); LogFunAdr("RestoreRemoteChecksumsFromSaveGame", v_RestoreRemoteChecksumsFromSaveGame); + LogVarAdr("g_nServerRemoteChecksum", g_nServerRemoteChecksum); LogVarAdr("g_nClientRemoteChecksum", g_nClientRemoteChecksum); } virtual void GetFun(void) const { + g_GameDll.FindPatternSIMD("E8 ? ? ? ? 48 8B CB E8 ? ? ? ? 48 8B 43 08 BF ? ? ? ? 48 8B 50 60 48 8D 05 ? ? ? ? 48 89 82 ? ? ? ? 65 48 8B 04 25 ? ? ? ? 48 03 38 8B 07 39 05 ? ? ? ? 0F 8F ? ? ? ? 48 8D 05 ? ? ? ? 48 8D 0D ? ? ? ? 48 89 05 ? ? ? ? E8 ? ? ? ? 48 8D 05 ? ? ? ? 89 35 ? ? ? ? 48 89 05 ? ? ? ? 4C 8D 35 ? ? ? ?") + .FollowNearCallSelf().GetPtr(v_Script_RegisterCommonEnums_Server); + g_GameDll.FindPatternSIMD("E8 ? ? ? ? 48 8B CB E8 ? ? ? ? 48 8B 43 08 BF ? ? ? ? 48 8B 50 60 48 8D 05 ? ? ? ? 48 89 82 ? ? ? ? 65 48 8B 04 25 ? ? ? ? 48 03 38 8B 07 39 05 ? ? ? ? 0F 8F ? ? ? ? 48 8D 05 ? ? ? ? 48 8D 0D ? ? ? ? 48 89 05 ? ? ? ? E8 ? ? ? ? 48 8D 05 ? ? ? ? 89 35 ? ? ? ? 48 89 05 ? ? ? ? 4C 8D 3D ? ? ? ?") + .FollowNearCallSelf().GetPtr(v_Script_RegisterCommonEnums_Client); + g_GameDll.FindPatternSIMD("48 83 EC 28 83 3D ?? ?? ?? ?? ?? 74 10").GetPtr(v_Script_Remote_BeginRegisteringFunctions); g_GameDll.FindPatternSIMD("48 89 4C 24 ?? 41 54 48 83 EC 40").GetPtr(v_RestoreRemoteChecksumsFromSaveGame); } @@ -48,7 +60,7 @@ class VScriptShared : public IDetour g_nClientRemoteChecksum = CMemory(v_Script_Remote_BeginRegisteringFunctions).Offset(0x0).FindPatternSelf("89 05", CMemory::Direction::DOWN, 150).ResolveRelativeAddressSelf(0x2, 0x6).RCast(); } virtual void GetCon(void) const { } - virtual void Detour(const bool bAttach) const { } + virtual void Detour(const bool bAttach) const; }; /////////////////////////////////////////////////////////////////////////////// diff --git a/src/gameui/IConsole.cpp b/src/gameui/IConsole.cpp index ffc6efdc..9bf9fb05 100644 --- a/src/gameui/IConsole.cpp +++ b/src/gameui/IConsole.cpp @@ -252,8 +252,8 @@ bool CConsole::DrawSurface(void) const static int colorLoggerWindowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_HorizontalScrollbar | - ImGuiWindowFlags_AlwaysVerticalScrollbar | - ImGuiWindowFlags_NoNavInputs; + ImGuiWindowFlags_NoNavInputs | + ImGuiWindowFlags_OverlayHorizontalScrollbar; ImGui::BeginChild(m_loggerLabel, ImVec2(0, -footerHeightReserve), loggerFlags, colorLoggerWindowFlags); diff --git a/src/launcher/launcher.cpp b/src/launcher/launcher.cpp index 2e1e2fee..4f7763dd 100644 --- a/src/launcher/launcher.cpp +++ b/src/launcher/launcher.cpp @@ -6,25 +6,13 @@ //===========================================================================// #include "core/stdafx.h" #include "core/logdef.h" +#include "core/init.h" #include "tier0/crashhandler.h" #include "tier0/commandline.h" #include "tier1/strtools.h" #include "launcher/launcher.h" #include -int LauncherMain(HINSTANCE hInstance) -{ - // Flush buffers every 5 seconds for every logger. - // Has to be done here, don't move this to SpdLog - // init, as this could cause deadlocks on certain - // compilers (VS2017)!!! - spdlog::flush_every(std::chrono::seconds(5)); - - int results = v_LauncherMain(hInstance); - Msg(eDLL_T::NONE, "%s returned: %s\n", __FUNCTION__, ExitCodeToString(results)); - return results; -} - // Remove all but the last -game parameter. // This is for mods based off something other than Half-Life 2 (like HL2MP mods). // The Steam UI does 'steam -applaunch 320 -game c:\steam\steamapps\sourcemods\modname', but applaunch inserts @@ -52,19 +40,6 @@ void RemoveSpuriousGameParameters() } } -const char* ExitCodeToString(int nCode) -{ - switch (nCode) - { - case EXIT_SUCCESS: - return "EXIT_SUCCESS"; - case EXIT_FAILURE: - return "EXIT_FAILURE"; - default: - return "UNKNOWN_EXIT_CODE"; - } -} - LONG WINAPI TopLevelExceptionFilter(EXCEPTION_POINTERS* pExceptionPointers) { // Don't run the unhandled exception filter from the @@ -79,7 +54,6 @@ LONG WINAPI TopLevelExceptionFilter(EXCEPTION_POINTERS* pExceptionPointers) void VLauncher::Detour(const bool bAttach) const { - DetourSetup(&v_LauncherMain, &LauncherMain, bAttach); DetourSetup(&v_TopLevelExceptionFilter, &TopLevelExceptionFilter, bAttach); DetourSetup(&v_RemoveSpuriousGameParameters, &RemoveSpuriousGameParameters, bAttach); } diff --git a/src/launcher/launcher.h b/src/launcher/launcher.h index a7660975..c398e89d 100644 --- a/src/launcher/launcher.h +++ b/src/launcher/launcher.h @@ -1,12 +1,13 @@ #ifndef LAUNCHER_H #define LAUNCHER_H +// NOTE: cannot hook this! this function is hooked by loader.dll and is also +// used to gracefully shutdown the SDK. inline int(*v_LauncherMain)(HINSTANCE hInstance); + inline LONG(*v_TopLevelExceptionFilter)(EXCEPTION_POINTERS* pExceptionPointer); inline void(*v_RemoveSpuriousGameParameters)(void); -const char* ExitCodeToString(int nCode); - /////////////////////////////////////////////////////////////////////////////// class VLauncher : public IDetour { diff --git a/src/loader/loader.cpp b/src/loader/loader.cpp index b24b6a26..39bc1beb 100644 --- a/src/loader/loader.cpp +++ b/src/loader/loader.cpp @@ -39,10 +39,14 @@ static const IMAGE_DOS_HEADER* s_DosHeader = nullptr; static const IMAGE_NT_HEADERS64* s_NtHeaders = nullptr; static HMODULE s_SdkModule = NULL; +typedef void (*InitFunc)(void); +static InitFunc s_SdkInitFunc = NULL; +static InitFunc s_SdkShutdownFunc = NULL; + //----------------------------------------------------------------------------- -// WinMain function pointer +// LauncherMain function pointer //----------------------------------------------------------------------------- -static int (*v_WinMain)(HINSTANCE, HINSTANCE, LPSTR, int) = nullptr; +static int (*v_LauncherMain)(HINSTANCE, HINSTANCE, LPSTR, int) = nullptr; //----------------------------------------------------------------------------- // Purpose: Terminates the process with an error when called @@ -100,16 +104,49 @@ static void InitGameSDK(const LPSTR lpCmdLine) { Assert(0); FatalError("Failed to load SDK: error code = %08x\n", GetLastError()); + + return; + } + + s_SdkInitFunc = (InitFunc)GetProcAddress(s_SdkModule, "SDK_Init"); + if (s_SdkInitFunc) + s_SdkShutdownFunc = (InitFunc)GetProcAddress(s_SdkModule, "SDK_Shutdown"); + + if (!s_SdkInitFunc || !s_SdkShutdownFunc) + { + Assert(0); + FatalError("Loaded SDK is invalid: error code = %08x\n", GetLastError()); + + return; + } + + s_SdkInitFunc(); +} + +//----------------------------------------------------------------------------- +// Purpose: Unloads the SDK module +//----------------------------------------------------------------------------- +static void ShutdownGameSDK() +{ + if (s_SdkModule) + { + s_SdkShutdownFunc(); + + FreeLibrary(s_SdkModule); + s_SdkModule = NULL; } } //----------------------------------------------------------------------------- -// Purpose: WinMain hook; loads the SDK before LauncherMain +// Purpose: LauncherMain hook; loads the SDK before the game inits //----------------------------------------------------------------------------- -int WINAPI hWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +int WINAPI hLauncherMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { InitGameSDK(lpCmdLine); // Init GameSDK, internal function calls LauncherMain. - return v_WinMain(hInstance, hPrevInstance, lpCmdLine, nShowCmd); + const int ret = v_LauncherMain(hInstance, hPrevInstance, lpCmdLine, nShowCmd); + ShutdownGameSDK(); + + return ret; } //----------------------------------------------------------------------------- @@ -120,7 +157,7 @@ static void AttachEP() DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); - DetourAttach(&v_WinMain, &hWinMain); + DetourAttach(&v_LauncherMain, &hLauncherMain); HRESULT hr = DetourTransactionCommit(); if (hr != NO_ERROR) // Failed to hook into the process, terminate... @@ -138,7 +175,7 @@ static void DetachEP() DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); - DetourDetach(&v_WinMain, &hWinMain); + DetourDetach(&v_LauncherMain, &hLauncherMain); HRESULT hr = DetourTransactionCommit(); Assert(hr != NO_ERROR); @@ -159,7 +196,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) s_NtHeaders = (IMAGE_NT_HEADERS64*)((uintptr_t)s_DosHeader + (uintptr_t)s_DosHeader->e_lfanew); - v_WinMain = CModule::GetExportedSymbol((QWORD)s_DosHeader, "WinMain") + v_LauncherMain = CModule::GetExportedSymbol((QWORD)s_DosHeader, "LauncherMain") .RCast(); AttachEP(); @@ -168,9 +205,6 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) case DLL_PROCESS_DETACH: { - if (s_SdkModule) - FreeLibrary(s_SdkModule); - DetachEP(); break; } diff --git a/src/mathlib/mathlib_base.cpp b/src/mathlib/mathlib_base.cpp index 65602e24..1a87be64 100644 --- a/src/mathlib/mathlib_base.cpp +++ b/src/mathlib/mathlib_base.cpp @@ -4059,11 +4059,6 @@ void MathLib_Init(float gamma, float texGamma, float brightness, int overbright) // FIXME: Hook SSE into VectorAligned + Vector4DAligned -#if !defined( _GAMECONSOLE ) - CheckCPUforSSE2(); -#endif //!360 - - s_bMathlibInitialized = true; InitSinCosTable(); diff --git a/src/protoc/CMakeLists.txt b/src/protoc/CMakeLists.txt index 88ff5c7f..8e78d772 100644 --- a/src/protoc/CMakeLists.txt +++ b/src/protoc/CMakeLists.txt @@ -1,4 +1,16 @@ cmake_minimum_required( VERSION 3.16 ) +add_module( "lib" "LiveAPI_Pb" "vpc" ${FOLDER_CONTEXT} FALSE TRUE ) + +start_sources() + +add_sources( SOURCE_GROUP "Runtime" + "events.pb.cc" + "events.pb.h" +) + +end_sources() +thirdparty_suppress_warnings() + add_module( "lib" "SigCache_Pb" "vpc" ${FOLDER_CONTEXT} FALSE TRUE ) start_sources() diff --git a/src/protoc/events.pb.cc b/src/protoc/events.pb.cc new file mode 100644 index 00000000..a64f40cb --- /dev/null +++ b/src/protoc/events.pb.cc @@ -0,0 +1,22396 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: events.proto + +#include "events.pb.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +// @@protoc_insertion_point(includes) +#include + +PROTOBUF_PRAGMA_INIT_SEG + +namespace _pb = ::PROTOBUF_NAMESPACE_ID; +namespace _pbi = _pb::internal; + +namespace rtech { +namespace liveapi { +PROTOBUF_CONSTEXPR Vector3::Vector3( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.x_)*/0 + , /*decltype(_impl_.y_)*/0 + , /*decltype(_impl_.z_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct Vector3DefaultTypeInternal { + PROTOBUF_CONSTEXPR Vector3DefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~Vector3DefaultTypeInternal() {} + union { + Vector3 _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 Vector3DefaultTypeInternal _Vector3_default_instance_; +PROTOBUF_CONSTEXPR Player::Player( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.name_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.nucleushash_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.hardwarename_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.teamname_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.character_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.skin_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.pos_)*/nullptr + , /*decltype(_impl_.angles_)*/nullptr + , /*decltype(_impl_.teamid_)*/0u + , /*decltype(_impl_.currenthealth_)*/0u + , /*decltype(_impl_.maxhealth_)*/0u + , /*decltype(_impl_.shieldhealth_)*/0u + , /*decltype(_impl_.shieldmaxhealth_)*/0u + , /*decltype(_impl_.squadindex_)*/0u + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerDefaultTypeInternal() {} + union { + Player _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerDefaultTypeInternal _Player_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_LobbyPlayer::CustomMatch_LobbyPlayer( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.name_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.nucleushash_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.hardwarename_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.teamid_)*/0u + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomMatch_LobbyPlayerDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_LobbyPlayerDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_LobbyPlayerDefaultTypeInternal() {} + union { + CustomMatch_LobbyPlayer _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_LobbyPlayerDefaultTypeInternal _CustomMatch_LobbyPlayer_default_instance_; +PROTOBUF_CONSTEXPR Datacenter::Datacenter( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.name_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct DatacenterDefaultTypeInternal { + PROTOBUF_CONSTEXPR DatacenterDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DatacenterDefaultTypeInternal() {} + union { + Datacenter _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DatacenterDefaultTypeInternal _Datacenter_default_instance_; +PROTOBUF_CONSTEXPR Version::Version( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.revision_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.major_num_)*/0u + , /*decltype(_impl_.minor_num_)*/0u + , /*decltype(_impl_.build_stamp_)*/0u + , /*decltype(_impl_._cached_size_)*/{}} {} +struct VersionDefaultTypeInternal { + PROTOBUF_CONSTEXPR VersionDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~VersionDefaultTypeInternal() {} + union { + Version _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 VersionDefaultTypeInternal _Version_default_instance_; +PROTOBUF_CONSTEXPR InventoryItem::InventoryItem( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.item_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.extradata_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.quantity_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct InventoryItemDefaultTypeInternal { + PROTOBUF_CONSTEXPR InventoryItemDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~InventoryItemDefaultTypeInternal() {} + union { + InventoryItem _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 InventoryItemDefaultTypeInternal _InventoryItem_default_instance_; +PROTOBUF_CONSTEXPR LoadoutConfiguration::LoadoutConfiguration( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.weapons_)*/{} + , /*decltype(_impl_.equipment_)*/{} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct LoadoutConfigurationDefaultTypeInternal { + PROTOBUF_CONSTEXPR LoadoutConfigurationDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~LoadoutConfigurationDefaultTypeInternal() {} + union { + LoadoutConfiguration _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 LoadoutConfigurationDefaultTypeInternal _LoadoutConfiguration_default_instance_; +PROTOBUF_CONSTEXPR Init::Init( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.gameversion_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.platform_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.name_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.apiversion_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct InitDefaultTypeInternal { + PROTOBUF_CONSTEXPR InitDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~InitDefaultTypeInternal() {} + union { + Init _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 InitDefaultTypeInternal _Init_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_LobbyPlayers::CustomMatch_LobbyPlayers( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.players_)*/{} + , /*decltype(_impl_.playertoken_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomMatch_LobbyPlayersDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_LobbyPlayersDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_LobbyPlayersDefaultTypeInternal() {} + union { + CustomMatch_LobbyPlayers _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_LobbyPlayersDefaultTypeInternal _CustomMatch_LobbyPlayers_default_instance_; +PROTOBUF_CONSTEXPR ObserverSwitched::ObserverSwitched( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.targetteam_)*/{} + , /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.observer_)*/nullptr + , /*decltype(_impl_.target_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct ObserverSwitchedDefaultTypeInternal { + PROTOBUF_CONSTEXPR ObserverSwitchedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ObserverSwitchedDefaultTypeInternal() {} + union { + ObserverSwitched _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ObserverSwitchedDefaultTypeInternal _ObserverSwitched_default_instance_; +PROTOBUF_CONSTEXPR ObserverAnnotation::ObserverAnnotation( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.annotationserial_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct ObserverAnnotationDefaultTypeInternal { + PROTOBUF_CONSTEXPR ObserverAnnotationDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ObserverAnnotationDefaultTypeInternal() {} + union { + ObserverAnnotation _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ObserverAnnotationDefaultTypeInternal _ObserverAnnotation_default_instance_; +PROTOBUF_CONSTEXPR MatchSetup::MatchSetup( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.map_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.playlistname_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.playlistdesc_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.serverid_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.datacenter_)*/nullptr + , /*decltype(_impl_.startingloadout_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.aimassiston_)*/false + , /*decltype(_impl_.anonymousmode_)*/false + , /*decltype(_impl_._cached_size_)*/{}} {} +struct MatchSetupDefaultTypeInternal { + PROTOBUF_CONSTEXPR MatchSetupDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~MatchSetupDefaultTypeInternal() {} + union { + MatchSetup _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 MatchSetupDefaultTypeInternal _MatchSetup_default_instance_; +PROTOBUF_CONSTEXPR GameStateChanged::GameStateChanged( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.state_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct GameStateChangedDefaultTypeInternal { + PROTOBUF_CONSTEXPR GameStateChangedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~GameStateChangedDefaultTypeInternal() {} + union { + GameStateChanged _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 GameStateChangedDefaultTypeInternal _GameStateChanged_default_instance_; +PROTOBUF_CONSTEXPR CharacterSelected::CharacterSelected( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CharacterSelectedDefaultTypeInternal { + PROTOBUF_CONSTEXPR CharacterSelectedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CharacterSelectedDefaultTypeInternal() {} + union { + CharacterSelected _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CharacterSelectedDefaultTypeInternal _CharacterSelected_default_instance_; +PROTOBUF_CONSTEXPR MatchStateEnd::MatchStateEnd( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.winners_)*/{} + , /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.state_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct MatchStateEndDefaultTypeInternal { + PROTOBUF_CONSTEXPR MatchStateEndDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~MatchStateEndDefaultTypeInternal() {} + union { + MatchStateEnd _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 MatchStateEndDefaultTypeInternal _MatchStateEnd_default_instance_; +PROTOBUF_CONSTEXPR RingStartClosing::RingStartClosing( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.center_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.stage_)*/0u + , /*decltype(_impl_.currentradius_)*/0 + , /*decltype(_impl_.endradius_)*/0 + , /*decltype(_impl_.shrinkduration_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct RingStartClosingDefaultTypeInternal { + PROTOBUF_CONSTEXPR RingStartClosingDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~RingStartClosingDefaultTypeInternal() {} + union { + RingStartClosing _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 RingStartClosingDefaultTypeInternal _RingStartClosing_default_instance_; +PROTOBUF_CONSTEXPR RingFinishedClosing::RingFinishedClosing( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.center_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.stage_)*/0u + , /*decltype(_impl_.currentradius_)*/0 + , /*decltype(_impl_.shrinkduration_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct RingFinishedClosingDefaultTypeInternal { + PROTOBUF_CONSTEXPR RingFinishedClosingDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~RingFinishedClosingDefaultTypeInternal() {} + union { + RingFinishedClosing _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 RingFinishedClosingDefaultTypeInternal _RingFinishedClosing_default_instance_; +PROTOBUF_CONSTEXPR PlayerConnected::PlayerConnected( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerConnectedDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerConnectedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerConnectedDefaultTypeInternal() {} + union { + PlayerConnected _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerConnectedDefaultTypeInternal _PlayerConnected_default_instance_; +PROTOBUF_CONSTEXPR PlayerDisconnected::PlayerDisconnected( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.canreconnect_)*/false + , /*decltype(_impl_.isalive_)*/false + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerDisconnectedDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerDisconnectedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerDisconnectedDefaultTypeInternal() {} + union { + PlayerDisconnected _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerDisconnectedDefaultTypeInternal _PlayerDisconnected_default_instance_; +PROTOBUF_CONSTEXPR PlayerStatChanged::PlayerStatChanged( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.statname_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.newValue_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_._oneof_case_)*/{}} {} +struct PlayerStatChangedDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerStatChangedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerStatChangedDefaultTypeInternal() {} + union { + PlayerStatChanged _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerStatChangedDefaultTypeInternal _PlayerStatChanged_default_instance_; +PROTOBUF_CONSTEXPR PlayerUpgradeTierChanged::PlayerUpgradeTierChanged( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.level_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerUpgradeTierChangedDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerUpgradeTierChangedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerUpgradeTierChangedDefaultTypeInternal() {} + union { + PlayerUpgradeTierChanged _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerUpgradeTierChangedDefaultTypeInternal _PlayerUpgradeTierChanged_default_instance_; +PROTOBUF_CONSTEXPR PlayerDamaged::PlayerDamaged( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.weapon_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.attacker_)*/nullptr + , /*decltype(_impl_.victim_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.damageinflicted_)*/0u + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerDamagedDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerDamagedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerDamagedDefaultTypeInternal() {} + union { + PlayerDamaged _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerDamagedDefaultTypeInternal _PlayerDamaged_default_instance_; +PROTOBUF_CONSTEXPR PlayerKilled::PlayerKilled( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.weapon_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.attacker_)*/nullptr + , /*decltype(_impl_.victim_)*/nullptr + , /*decltype(_impl_.awardedto_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerKilledDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerKilledDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerKilledDefaultTypeInternal() {} + union { + PlayerKilled _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerKilledDefaultTypeInternal _PlayerKilled_default_instance_; +PROTOBUF_CONSTEXPR PlayerDowned::PlayerDowned( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.weapon_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.attacker_)*/nullptr + , /*decltype(_impl_.victim_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerDownedDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerDownedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerDownedDefaultTypeInternal() {} + union { + PlayerDowned _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerDownedDefaultTypeInternal _PlayerDowned_default_instance_; +PROTOBUF_CONSTEXPR PlayerAssist::PlayerAssist( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.weapon_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.assistant_)*/nullptr + , /*decltype(_impl_.victim_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerAssistDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerAssistDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerAssistDefaultTypeInternal() {} + union { + PlayerAssist _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerAssistDefaultTypeInternal _PlayerAssist_default_instance_; +PROTOBUF_CONSTEXPR SquadEliminated::SquadEliminated( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.players_)*/{} + , /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct SquadEliminatedDefaultTypeInternal { + PROTOBUF_CONSTEXPR SquadEliminatedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~SquadEliminatedDefaultTypeInternal() {} + union { + SquadEliminated _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 SquadEliminatedDefaultTypeInternal _SquadEliminated_default_instance_; +PROTOBUF_CONSTEXPR GibraltarShieldAbsorbed::GibraltarShieldAbsorbed( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.attacker_)*/nullptr + , /*decltype(_impl_.victim_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.damageinflicted_)*/0u + , /*decltype(_impl_._cached_size_)*/{}} {} +struct GibraltarShieldAbsorbedDefaultTypeInternal { + PROTOBUF_CONSTEXPR GibraltarShieldAbsorbedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~GibraltarShieldAbsorbedDefaultTypeInternal() {} + union { + GibraltarShieldAbsorbed _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 GibraltarShieldAbsorbedDefaultTypeInternal _GibraltarShieldAbsorbed_default_instance_; +PROTOBUF_CONSTEXPR RevenantForgedShadowDamaged::RevenantForgedShadowDamaged( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.attacker_)*/nullptr + , /*decltype(_impl_.victim_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.damageinflicted_)*/0u + , /*decltype(_impl_._cached_size_)*/{}} {} +struct RevenantForgedShadowDamagedDefaultTypeInternal { + PROTOBUF_CONSTEXPR RevenantForgedShadowDamagedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~RevenantForgedShadowDamagedDefaultTypeInternal() {} + union { + RevenantForgedShadowDamaged _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 RevenantForgedShadowDamagedDefaultTypeInternal _RevenantForgedShadowDamaged_default_instance_; +PROTOBUF_CONSTEXPR PlayerRespawnTeam::PlayerRespawnTeam( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.respawned_)*/{} + , /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerRespawnTeamDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerRespawnTeamDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerRespawnTeamDefaultTypeInternal() {} + union { + PlayerRespawnTeam _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerRespawnTeamDefaultTypeInternal _PlayerRespawnTeam_default_instance_; +PROTOBUF_CONSTEXPR PlayerRevive::PlayerRevive( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.revived_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerReviveDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerReviveDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerReviveDefaultTypeInternal() {} + union { + PlayerRevive _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerReviveDefaultTypeInternal _PlayerRevive_default_instance_; +PROTOBUF_CONSTEXPR ArenasItemSelected::ArenasItemSelected( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.item_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.quantity_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct ArenasItemSelectedDefaultTypeInternal { + PROTOBUF_CONSTEXPR ArenasItemSelectedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ArenasItemSelectedDefaultTypeInternal() {} + union { + ArenasItemSelected _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ArenasItemSelectedDefaultTypeInternal _ArenasItemSelected_default_instance_; +PROTOBUF_CONSTEXPR ArenasItemDeselected::ArenasItemDeselected( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.item_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.quantity_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct ArenasItemDeselectedDefaultTypeInternal { + PROTOBUF_CONSTEXPR ArenasItemDeselectedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ArenasItemDeselectedDefaultTypeInternal() {} + union { + ArenasItemDeselected _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ArenasItemDeselectedDefaultTypeInternal _ArenasItemDeselected_default_instance_; +PROTOBUF_CONSTEXPR InventoryPickUp::InventoryPickUp( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.item_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.quantity_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct InventoryPickUpDefaultTypeInternal { + PROTOBUF_CONSTEXPR InventoryPickUpDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~InventoryPickUpDefaultTypeInternal() {} + union { + InventoryPickUp _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 InventoryPickUpDefaultTypeInternal _InventoryPickUp_default_instance_; +PROTOBUF_CONSTEXPR InventoryDrop::InventoryDrop( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.extradata_)*/{} + , /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.item_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.quantity_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct InventoryDropDefaultTypeInternal { + PROTOBUF_CONSTEXPR InventoryDropDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~InventoryDropDefaultTypeInternal() {} + union { + InventoryDrop _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 InventoryDropDefaultTypeInternal _InventoryDrop_default_instance_; +PROTOBUF_CONSTEXPR InventoryUse::InventoryUse( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.item_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.quantity_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct InventoryUseDefaultTypeInternal { + PROTOBUF_CONSTEXPR InventoryUseDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~InventoryUseDefaultTypeInternal() {} + union { + InventoryUse _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 InventoryUseDefaultTypeInternal _InventoryUse_default_instance_; +PROTOBUF_CONSTEXPR BannerCollected::BannerCollected( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.collected_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct BannerCollectedDefaultTypeInternal { + PROTOBUF_CONSTEXPR BannerCollectedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~BannerCollectedDefaultTypeInternal() {} + union { + BannerCollected _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 BannerCollectedDefaultTypeInternal _BannerCollected_default_instance_; +PROTOBUF_CONSTEXPR PlayerAbilityUsed::PlayerAbilityUsed( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.linkedentity_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PlayerAbilityUsedDefaultTypeInternal { + PROTOBUF_CONSTEXPR PlayerAbilityUsedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PlayerAbilityUsedDefaultTypeInternal() {} + union { + PlayerAbilityUsed _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PlayerAbilityUsedDefaultTypeInternal _PlayerAbilityUsed_default_instance_; +PROTOBUF_CONSTEXPR LegendUpgradeSelected::LegendUpgradeSelected( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.upgradename_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.upgradedesc_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.level_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct LegendUpgradeSelectedDefaultTypeInternal { + PROTOBUF_CONSTEXPR LegendUpgradeSelectedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~LegendUpgradeSelectedDefaultTypeInternal() {} + union { + LegendUpgradeSelected _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 LegendUpgradeSelectedDefaultTypeInternal _LegendUpgradeSelected_default_instance_; +PROTOBUF_CONSTEXPR ZiplineUsed::ZiplineUsed( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.linkedentity_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct ZiplineUsedDefaultTypeInternal { + PROTOBUF_CONSTEXPR ZiplineUsedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ZiplineUsedDefaultTypeInternal() {} + union { + ZiplineUsed _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ZiplineUsedDefaultTypeInternal _ZiplineUsed_default_instance_; +PROTOBUF_CONSTEXPR GrenadeThrown::GrenadeThrown( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.linkedentity_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct GrenadeThrownDefaultTypeInternal { + PROTOBUF_CONSTEXPR GrenadeThrownDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~GrenadeThrownDefaultTypeInternal() {} + union { + GrenadeThrown _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 GrenadeThrownDefaultTypeInternal _GrenadeThrown_default_instance_; +PROTOBUF_CONSTEXPR BlackMarketAction::BlackMarketAction( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.item_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct BlackMarketActionDefaultTypeInternal { + PROTOBUF_CONSTEXPR BlackMarketActionDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~BlackMarketActionDefaultTypeInternal() {} + union { + BlackMarketAction _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 BlackMarketActionDefaultTypeInternal _BlackMarketAction_default_instance_; +PROTOBUF_CONSTEXPR WraithPortal::WraithPortal( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct WraithPortalDefaultTypeInternal { + PROTOBUF_CONSTEXPR WraithPortalDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~WraithPortalDefaultTypeInternal() {} + union { + WraithPortal _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 WraithPortalDefaultTypeInternal _WraithPortal_default_instance_; +PROTOBUF_CONSTEXPR WarpGateUsed::WarpGateUsed( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct WarpGateUsedDefaultTypeInternal { + PROTOBUF_CONSTEXPR WarpGateUsedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~WarpGateUsedDefaultTypeInternal() {} + union { + WarpGateUsed _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 WarpGateUsedDefaultTypeInternal _WarpGateUsed_default_instance_; +PROTOBUF_CONSTEXPR AmmoUsed::AmmoUsed( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.ammotype_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.amountused_)*/0u + , /*decltype(_impl_.oldammocount_)*/0u + , /*decltype(_impl_.newammocount_)*/0u + , /*decltype(_impl_._cached_size_)*/{}} {} +struct AmmoUsedDefaultTypeInternal { + PROTOBUF_CONSTEXPR AmmoUsedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~AmmoUsedDefaultTypeInternal() {} + union { + AmmoUsed _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 AmmoUsedDefaultTypeInternal _AmmoUsed_default_instance_; +PROTOBUF_CONSTEXPR WeaponSwitched::WeaponSwitched( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.oldweapon_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.newweapon_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.player_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct WeaponSwitchedDefaultTypeInternal { + PROTOBUF_CONSTEXPR WeaponSwitchedDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~WeaponSwitchedDefaultTypeInternal() {} + union { + WeaponSwitched _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 WeaponSwitchedDefaultTypeInternal _WeaponSwitched_default_instance_; +PROTOBUF_CONSTEXPR CustomEvent::CustomEvent( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.category_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.name_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.data_)*/nullptr + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomEventDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomEventDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomEventDefaultTypeInternal() {} + union { + CustomEvent _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomEventDefaultTypeInternal _CustomEvent_default_instance_; +PROTOBUF_CONSTEXPR ChangeCamera::ChangeCamera( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.target_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_._oneof_case_)*/{}} {} +struct ChangeCameraDefaultTypeInternal { + PROTOBUF_CONSTEXPR ChangeCameraDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ChangeCameraDefaultTypeInternal() {} + union { + ChangeCamera _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ChangeCameraDefaultTypeInternal _ChangeCamera_default_instance_; +PROTOBUF_CONSTEXPR PauseToggle::PauseToggle( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.pretimer_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct PauseToggleDefaultTypeInternal { + PROTOBUF_CONSTEXPR PauseToggleDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~PauseToggleDefaultTypeInternal() {} + union { + PauseToggle _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PauseToggleDefaultTypeInternal _PauseToggle_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_CreateLobby::CustomMatch_CreateLobby( + ::_pbi::ConstantInitialized) {} +struct CustomMatch_CreateLobbyDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_CreateLobbyDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_CreateLobbyDefaultTypeInternal() {} + union { + CustomMatch_CreateLobby _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_CreateLobbyDefaultTypeInternal _CustomMatch_CreateLobby_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_JoinLobby::CustomMatch_JoinLobby( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.roletoken_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomMatch_JoinLobbyDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_JoinLobbyDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_JoinLobbyDefaultTypeInternal() {} + union { + CustomMatch_JoinLobby _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_JoinLobbyDefaultTypeInternal _CustomMatch_JoinLobby_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_LeaveLobby::CustomMatch_LeaveLobby( + ::_pbi::ConstantInitialized) {} +struct CustomMatch_LeaveLobbyDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_LeaveLobbyDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_LeaveLobbyDefaultTypeInternal() {} + union { + CustomMatch_LeaveLobby _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_LeaveLobbyDefaultTypeInternal _CustomMatch_LeaveLobby_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_SetReady::CustomMatch_SetReady( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.isready_)*/false + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomMatch_SetReadyDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_SetReadyDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_SetReadyDefaultTypeInternal() {} + union { + CustomMatch_SetReady _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_SetReadyDefaultTypeInternal _CustomMatch_SetReady_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_GetLobbyPlayers::CustomMatch_GetLobbyPlayers( + ::_pbi::ConstantInitialized) {} +struct CustomMatch_GetLobbyPlayersDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_GetLobbyPlayersDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_GetLobbyPlayersDefaultTypeInternal() {} + union { + CustomMatch_GetLobbyPlayers _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_GetLobbyPlayersDefaultTypeInternal _CustomMatch_GetLobbyPlayers_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_SetMatchmaking::CustomMatch_SetMatchmaking( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.enabled_)*/false + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomMatch_SetMatchmakingDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_SetMatchmakingDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_SetMatchmakingDefaultTypeInternal() {} + union { + CustomMatch_SetMatchmaking _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_SetMatchmakingDefaultTypeInternal _CustomMatch_SetMatchmaking_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_SetTeam::CustomMatch_SetTeam( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.targethardwarename_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.targetnucleushash_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.teamid_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomMatch_SetTeamDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_SetTeamDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_SetTeamDefaultTypeInternal() {} + union { + CustomMatch_SetTeam _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_SetTeamDefaultTypeInternal _CustomMatch_SetTeam_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_KickPlayer::CustomMatch_KickPlayer( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.targethardwarename_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.targetnucleushash_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomMatch_KickPlayerDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_KickPlayerDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_KickPlayerDefaultTypeInternal() {} + union { + CustomMatch_KickPlayer _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_KickPlayerDefaultTypeInternal _CustomMatch_KickPlayer_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_SetSettings::CustomMatch_SetSettings( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.playlistname_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.adminchat_)*/false + , /*decltype(_impl_.teamrename_)*/false + , /*decltype(_impl_.selfassign_)*/false + , /*decltype(_impl_.aimassist_)*/false + , /*decltype(_impl_.anonmode_)*/false + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomMatch_SetSettingsDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_SetSettingsDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_SetSettingsDefaultTypeInternal() {} + union { + CustomMatch_SetSettings _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_SetSettingsDefaultTypeInternal _CustomMatch_SetSettings_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_GetSettings::CustomMatch_GetSettings( + ::_pbi::ConstantInitialized) {} +struct CustomMatch_GetSettingsDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_GetSettingsDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_GetSettingsDefaultTypeInternal() {} + union { + CustomMatch_GetSettings _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_GetSettingsDefaultTypeInternal _CustomMatch_GetSettings_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_SetTeamName::CustomMatch_SetTeamName( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.teamname_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.teamid_)*/0 + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomMatch_SetTeamNameDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_SetTeamNameDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_SetTeamNameDefaultTypeInternal() {} + union { + CustomMatch_SetTeamName _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_SetTeamNameDefaultTypeInternal _CustomMatch_SetTeamName_default_instance_; +PROTOBUF_CONSTEXPR CustomMatch_SendChat::CustomMatch_SendChat( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.text_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct CustomMatch_SendChatDefaultTypeInternal { + PROTOBUF_CONSTEXPR CustomMatch_SendChatDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CustomMatch_SendChatDefaultTypeInternal() {} + union { + CustomMatch_SendChat _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CustomMatch_SendChatDefaultTypeInternal _CustomMatch_SendChat_default_instance_; +PROTOBUF_CONSTEXPR Request::Request( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.presharedkey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.withack_)*/false + , /*decltype(_impl_.actions_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_._oneof_case_)*/{}} {} +struct RequestDefaultTypeInternal { + PROTOBUF_CONSTEXPR RequestDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~RequestDefaultTypeInternal() {} + union { + Request _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 RequestDefaultTypeInternal _Request_default_instance_; +PROTOBUF_CONSTEXPR RequestStatus::RequestStatus( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.status_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct RequestStatusDefaultTypeInternal { + PROTOBUF_CONSTEXPR RequestStatusDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~RequestStatusDefaultTypeInternal() {} + union { + RequestStatus _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 RequestStatusDefaultTypeInternal _RequestStatus_default_instance_; +PROTOBUF_CONSTEXPR Response::Response( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.result_)*/nullptr + , /*decltype(_impl_.success_)*/false + , /*decltype(_impl_._cached_size_)*/{}} {} +struct ResponseDefaultTypeInternal { + PROTOBUF_CONSTEXPR ResponseDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ResponseDefaultTypeInternal() {} + union { + Response _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ResponseDefaultTypeInternal _Response_default_instance_; +PROTOBUF_CONSTEXPR LiveAPIEvent::LiveAPIEvent( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.gamemessage_)*/nullptr + , /*decltype(_impl_.event_size_)*/0u + , /*decltype(_impl_._cached_size_)*/{}} {} +struct LiveAPIEventDefaultTypeInternal { + PROTOBUF_CONSTEXPR LiveAPIEventDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~LiveAPIEventDefaultTypeInternal() {} + union { + LiveAPIEvent _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 LiveAPIEventDefaultTypeInternal _LiveAPIEvent_default_instance_; +} // namespace liveapi +} // namespace rtech +static ::_pb::Metadata file_level_metadata_events_2eproto[64]; +static const ::_pb::EnumDescriptor* file_level_enum_descriptors_events_2eproto[1]; +static constexpr ::_pb::ServiceDescriptor const** file_level_service_descriptors_events_2eproto = nullptr; + +const uint32_t TableStruct_events_2eproto::offsets[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = { + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Vector3, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Vector3, _impl_.x_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Vector3, _impl_.y_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Vector3, _impl_.z_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.name_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.teamid_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.pos_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.angles_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.currenthealth_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.maxhealth_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.shieldhealth_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.shieldmaxhealth_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.nucleushash_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.hardwarename_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.teamname_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.squadindex_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.character_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Player, _impl_.skin_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_LobbyPlayer, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_LobbyPlayer, _impl_.name_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_LobbyPlayer, _impl_.teamid_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_LobbyPlayer, _impl_.nucleushash_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_LobbyPlayer, _impl_.hardwarename_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Datacenter, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Datacenter, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Datacenter, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Datacenter, _impl_.name_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Version, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Version, _impl_.major_num_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Version, _impl_.minor_num_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Version, _impl_.build_stamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Version, _impl_.revision_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryItem, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryItem, _impl_.quantity_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryItem, _impl_.item_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryItem, _impl_.extradata_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LoadoutConfiguration, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LoadoutConfiguration, _impl_.weapons_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LoadoutConfiguration, _impl_.equipment_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Init, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Init, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Init, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Init, _impl_.gameversion_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Init, _impl_.apiversion_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Init, _impl_.platform_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Init, _impl_.name_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_LobbyPlayers, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_LobbyPlayers, _impl_.playertoken_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_LobbyPlayers, _impl_.players_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ObserverSwitched, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ObserverSwitched, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ObserverSwitched, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ObserverSwitched, _impl_.observer_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ObserverSwitched, _impl_.target_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ObserverSwitched, _impl_.targetteam_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ObserverAnnotation, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ObserverAnnotation, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ObserverAnnotation, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ObserverAnnotation, _impl_.annotationserial_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _impl_.map_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _impl_.playlistname_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _impl_.playlistdesc_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _impl_.datacenter_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _impl_.aimassiston_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _impl_.anonymousmode_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _impl_.serverid_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchSetup, _impl_.startingloadout_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GameStateChanged, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GameStateChanged, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GameStateChanged, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GameStateChanged, _impl_.state_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CharacterSelected, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CharacterSelected, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CharacterSelected, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CharacterSelected, _impl_.player_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchStateEnd, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchStateEnd, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchStateEnd, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchStateEnd, _impl_.state_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::MatchStateEnd, _impl_.winners_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingStartClosing, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingStartClosing, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingStartClosing, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingStartClosing, _impl_.stage_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingStartClosing, _impl_.center_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingStartClosing, _impl_.currentradius_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingStartClosing, _impl_.endradius_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingStartClosing, _impl_.shrinkduration_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingFinishedClosing, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingFinishedClosing, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingFinishedClosing, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingFinishedClosing, _impl_.stage_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingFinishedClosing, _impl_.center_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingFinishedClosing, _impl_.currentradius_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RingFinishedClosing, _impl_.shrinkduration_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerConnected, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerConnected, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerConnected, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerConnected, _impl_.player_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDisconnected, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDisconnected, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDisconnected, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDisconnected, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDisconnected, _impl_.canreconnect_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDisconnected, _impl_.isalive_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerStatChanged, _internal_metadata_), + ~0u, // no _extensions_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerStatChanged, _impl_._oneof_case_[0]), + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerStatChanged, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerStatChanged, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerStatChanged, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerStatChanged, _impl_.statname_), + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerStatChanged, _impl_.newValue_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerUpgradeTierChanged, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerUpgradeTierChanged, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerUpgradeTierChanged, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerUpgradeTierChanged, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerUpgradeTierChanged, _impl_.level_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDamaged, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDamaged, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDamaged, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDamaged, _impl_.attacker_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDamaged, _impl_.victim_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDamaged, _impl_.weapon_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDamaged, _impl_.damageinflicted_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerKilled, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerKilled, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerKilled, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerKilled, _impl_.attacker_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerKilled, _impl_.victim_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerKilled, _impl_.awardedto_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerKilled, _impl_.weapon_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDowned, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDowned, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDowned, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDowned, _impl_.attacker_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDowned, _impl_.victim_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerDowned, _impl_.weapon_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAssist, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAssist, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAssist, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAssist, _impl_.assistant_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAssist, _impl_.victim_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAssist, _impl_.weapon_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::SquadEliminated, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::SquadEliminated, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::SquadEliminated, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::SquadEliminated, _impl_.players_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GibraltarShieldAbsorbed, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GibraltarShieldAbsorbed, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GibraltarShieldAbsorbed, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GibraltarShieldAbsorbed, _impl_.attacker_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GibraltarShieldAbsorbed, _impl_.victim_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GibraltarShieldAbsorbed, _impl_.damageinflicted_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RevenantForgedShadowDamaged, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RevenantForgedShadowDamaged, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RevenantForgedShadowDamaged, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RevenantForgedShadowDamaged, _impl_.attacker_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RevenantForgedShadowDamaged, _impl_.victim_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RevenantForgedShadowDamaged, _impl_.damageinflicted_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerRespawnTeam, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerRespawnTeam, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerRespawnTeam, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerRespawnTeam, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerRespawnTeam, _impl_.respawned_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerRevive, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerRevive, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerRevive, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerRevive, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerRevive, _impl_.revived_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemSelected, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemSelected, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemSelected, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemSelected, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemSelected, _impl_.item_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemSelected, _impl_.quantity_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemDeselected, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemDeselected, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemDeselected, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemDeselected, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemDeselected, _impl_.item_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ArenasItemDeselected, _impl_.quantity_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryPickUp, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryPickUp, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryPickUp, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryPickUp, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryPickUp, _impl_.item_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryPickUp, _impl_.quantity_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryDrop, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryDrop, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryDrop, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryDrop, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryDrop, _impl_.item_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryDrop, _impl_.quantity_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryDrop, _impl_.extradata_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryUse, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryUse, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryUse, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryUse, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryUse, _impl_.item_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::InventoryUse, _impl_.quantity_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::BannerCollected, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::BannerCollected, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::BannerCollected, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::BannerCollected, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::BannerCollected, _impl_.collected_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAbilityUsed, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAbilityUsed, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAbilityUsed, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAbilityUsed, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PlayerAbilityUsed, _impl_.linkedentity_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LegendUpgradeSelected, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LegendUpgradeSelected, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LegendUpgradeSelected, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LegendUpgradeSelected, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LegendUpgradeSelected, _impl_.upgradename_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LegendUpgradeSelected, _impl_.upgradedesc_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LegendUpgradeSelected, _impl_.level_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ZiplineUsed, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ZiplineUsed, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ZiplineUsed, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ZiplineUsed, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ZiplineUsed, _impl_.linkedentity_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GrenadeThrown, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GrenadeThrown, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GrenadeThrown, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GrenadeThrown, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::GrenadeThrown, _impl_.linkedentity_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::BlackMarketAction, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::BlackMarketAction, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::BlackMarketAction, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::BlackMarketAction, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::BlackMarketAction, _impl_.item_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WraithPortal, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WraithPortal, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WraithPortal, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WraithPortal, _impl_.player_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WarpGateUsed, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WarpGateUsed, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WarpGateUsed, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WarpGateUsed, _impl_.player_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::AmmoUsed, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::AmmoUsed, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::AmmoUsed, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::AmmoUsed, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::AmmoUsed, _impl_.ammotype_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::AmmoUsed, _impl_.amountused_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::AmmoUsed, _impl_.oldammocount_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::AmmoUsed, _impl_.newammocount_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WeaponSwitched, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WeaponSwitched, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WeaponSwitched, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WeaponSwitched, _impl_.player_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WeaponSwitched, _impl_.oldweapon_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::WeaponSwitched, _impl_.newweapon_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomEvent, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomEvent, _impl_.timestamp_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomEvent, _impl_.category_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomEvent, _impl_.name_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomEvent, _impl_.data_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ChangeCamera, _internal_metadata_), + ~0u, // no _extensions_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ChangeCamera, _impl_._oneof_case_[0]), + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::ChangeCamera, _impl_.target_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PauseToggle, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::PauseToggle, _impl_.pretimer_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_CreateLobby, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_JoinLobby, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_JoinLobby, _impl_.roletoken_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_LeaveLobby, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetReady, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetReady, _impl_.isready_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_GetLobbyPlayers, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetMatchmaking, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetMatchmaking, _impl_.enabled_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetTeam, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetTeam, _impl_.teamid_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetTeam, _impl_.targethardwarename_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetTeam, _impl_.targetnucleushash_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_KickPlayer, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_KickPlayer, _impl_.targethardwarename_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_KickPlayer, _impl_.targetnucleushash_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetSettings, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetSettings, _impl_.playlistname_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetSettings, _impl_.adminchat_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetSettings, _impl_.teamrename_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetSettings, _impl_.selfassign_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetSettings, _impl_.aimassist_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetSettings, _impl_.anonmode_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_GetSettings, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetTeamName, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetTeamName, _impl_.teamid_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SetTeamName, _impl_.teamname_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SendChat, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::CustomMatch_SendChat, _impl_.text_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Request, _internal_metadata_), + ~0u, // no _extensions_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Request, _impl_._oneof_case_[0]), + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Request, _impl_.withack_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Request, _impl_.presharedkey_), + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Request, _impl_.actions_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RequestStatus, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::RequestStatus, _impl_.status_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Response, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Response, _impl_.success_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::Response, _impl_.result_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LiveAPIEvent, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LiveAPIEvent, _impl_.event_size_), + PROTOBUF_FIELD_OFFSET(::rtech::liveapi::LiveAPIEvent, _impl_.gamemessage_), +}; +static const ::_pbi::MigrationSchema schemas[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = { + { 0, -1, -1, sizeof(::rtech::liveapi::Vector3)}, + { 9, -1, -1, sizeof(::rtech::liveapi::Player)}, + { 29, -1, -1, sizeof(::rtech::liveapi::CustomMatch_LobbyPlayer)}, + { 39, -1, -1, sizeof(::rtech::liveapi::Datacenter)}, + { 48, -1, -1, sizeof(::rtech::liveapi::Version)}, + { 58, -1, -1, sizeof(::rtech::liveapi::InventoryItem)}, + { 67, -1, -1, sizeof(::rtech::liveapi::LoadoutConfiguration)}, + { 75, -1, -1, sizeof(::rtech::liveapi::Init)}, + { 87, -1, -1, sizeof(::rtech::liveapi::CustomMatch_LobbyPlayers)}, + { 95, -1, -1, sizeof(::rtech::liveapi::ObserverSwitched)}, + { 106, -1, -1, sizeof(::rtech::liveapi::ObserverAnnotation)}, + { 115, -1, -1, sizeof(::rtech::liveapi::MatchSetup)}, + { 131, -1, -1, sizeof(::rtech::liveapi::GameStateChanged)}, + { 140, -1, -1, sizeof(::rtech::liveapi::CharacterSelected)}, + { 149, -1, -1, sizeof(::rtech::liveapi::MatchStateEnd)}, + { 159, -1, -1, sizeof(::rtech::liveapi::RingStartClosing)}, + { 172, -1, -1, sizeof(::rtech::liveapi::RingFinishedClosing)}, + { 184, -1, -1, sizeof(::rtech::liveapi::PlayerConnected)}, + { 193, -1, -1, sizeof(::rtech::liveapi::PlayerDisconnected)}, + { 204, -1, -1, sizeof(::rtech::liveapi::PlayerStatChanged)}, + { 218, -1, -1, sizeof(::rtech::liveapi::PlayerUpgradeTierChanged)}, + { 228, -1, -1, sizeof(::rtech::liveapi::PlayerDamaged)}, + { 240, -1, -1, sizeof(::rtech::liveapi::PlayerKilled)}, + { 252, -1, -1, sizeof(::rtech::liveapi::PlayerDowned)}, + { 263, -1, -1, sizeof(::rtech::liveapi::PlayerAssist)}, + { 274, -1, -1, sizeof(::rtech::liveapi::SquadEliminated)}, + { 283, -1, -1, sizeof(::rtech::liveapi::GibraltarShieldAbsorbed)}, + { 294, -1, -1, sizeof(::rtech::liveapi::RevenantForgedShadowDamaged)}, + { 305, -1, -1, sizeof(::rtech::liveapi::PlayerRespawnTeam)}, + { 315, -1, -1, sizeof(::rtech::liveapi::PlayerRevive)}, + { 325, -1, -1, sizeof(::rtech::liveapi::ArenasItemSelected)}, + { 336, -1, -1, sizeof(::rtech::liveapi::ArenasItemDeselected)}, + { 347, -1, -1, sizeof(::rtech::liveapi::InventoryPickUp)}, + { 358, -1, -1, sizeof(::rtech::liveapi::InventoryDrop)}, + { 370, -1, -1, sizeof(::rtech::liveapi::InventoryUse)}, + { 381, -1, -1, sizeof(::rtech::liveapi::BannerCollected)}, + { 391, -1, -1, sizeof(::rtech::liveapi::PlayerAbilityUsed)}, + { 401, -1, -1, sizeof(::rtech::liveapi::LegendUpgradeSelected)}, + { 413, -1, -1, sizeof(::rtech::liveapi::ZiplineUsed)}, + { 423, -1, -1, sizeof(::rtech::liveapi::GrenadeThrown)}, + { 433, -1, -1, sizeof(::rtech::liveapi::BlackMarketAction)}, + { 443, -1, -1, sizeof(::rtech::liveapi::WraithPortal)}, + { 452, -1, -1, sizeof(::rtech::liveapi::WarpGateUsed)}, + { 461, -1, -1, sizeof(::rtech::liveapi::AmmoUsed)}, + { 474, -1, -1, sizeof(::rtech::liveapi::WeaponSwitched)}, + { 485, -1, -1, sizeof(::rtech::liveapi::CustomEvent)}, + { 495, -1, -1, sizeof(::rtech::liveapi::ChangeCamera)}, + { 504, -1, -1, sizeof(::rtech::liveapi::PauseToggle)}, + { 511, -1, -1, sizeof(::rtech::liveapi::CustomMatch_CreateLobby)}, + { 517, -1, -1, sizeof(::rtech::liveapi::CustomMatch_JoinLobby)}, + { 524, -1, -1, sizeof(::rtech::liveapi::CustomMatch_LeaveLobby)}, + { 530, -1, -1, sizeof(::rtech::liveapi::CustomMatch_SetReady)}, + { 537, -1, -1, sizeof(::rtech::liveapi::CustomMatch_GetLobbyPlayers)}, + { 543, -1, -1, sizeof(::rtech::liveapi::CustomMatch_SetMatchmaking)}, + { 550, -1, -1, sizeof(::rtech::liveapi::CustomMatch_SetTeam)}, + { 559, -1, -1, sizeof(::rtech::liveapi::CustomMatch_KickPlayer)}, + { 567, -1, -1, sizeof(::rtech::liveapi::CustomMatch_SetSettings)}, + { 579, -1, -1, sizeof(::rtech::liveapi::CustomMatch_GetSettings)}, + { 585, -1, -1, sizeof(::rtech::liveapi::CustomMatch_SetTeamName)}, + { 593, -1, -1, sizeof(::rtech::liveapi::CustomMatch_SendChat)}, + { 600, -1, -1, sizeof(::rtech::liveapi::Request)}, + { 623, -1, -1, sizeof(::rtech::liveapi::RequestStatus)}, + { 630, -1, -1, sizeof(::rtech::liveapi::Response)}, + { 638, -1, -1, sizeof(::rtech::liveapi::LiveAPIEvent)}, +}; + +static const ::_pb::Message* const file_default_instances[] = { + &::rtech::liveapi::_Vector3_default_instance_._instance, + &::rtech::liveapi::_Player_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_LobbyPlayer_default_instance_._instance, + &::rtech::liveapi::_Datacenter_default_instance_._instance, + &::rtech::liveapi::_Version_default_instance_._instance, + &::rtech::liveapi::_InventoryItem_default_instance_._instance, + &::rtech::liveapi::_LoadoutConfiguration_default_instance_._instance, + &::rtech::liveapi::_Init_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_LobbyPlayers_default_instance_._instance, + &::rtech::liveapi::_ObserverSwitched_default_instance_._instance, + &::rtech::liveapi::_ObserverAnnotation_default_instance_._instance, + &::rtech::liveapi::_MatchSetup_default_instance_._instance, + &::rtech::liveapi::_GameStateChanged_default_instance_._instance, + &::rtech::liveapi::_CharacterSelected_default_instance_._instance, + &::rtech::liveapi::_MatchStateEnd_default_instance_._instance, + &::rtech::liveapi::_RingStartClosing_default_instance_._instance, + &::rtech::liveapi::_RingFinishedClosing_default_instance_._instance, + &::rtech::liveapi::_PlayerConnected_default_instance_._instance, + &::rtech::liveapi::_PlayerDisconnected_default_instance_._instance, + &::rtech::liveapi::_PlayerStatChanged_default_instance_._instance, + &::rtech::liveapi::_PlayerUpgradeTierChanged_default_instance_._instance, + &::rtech::liveapi::_PlayerDamaged_default_instance_._instance, + &::rtech::liveapi::_PlayerKilled_default_instance_._instance, + &::rtech::liveapi::_PlayerDowned_default_instance_._instance, + &::rtech::liveapi::_PlayerAssist_default_instance_._instance, + &::rtech::liveapi::_SquadEliminated_default_instance_._instance, + &::rtech::liveapi::_GibraltarShieldAbsorbed_default_instance_._instance, + &::rtech::liveapi::_RevenantForgedShadowDamaged_default_instance_._instance, + &::rtech::liveapi::_PlayerRespawnTeam_default_instance_._instance, + &::rtech::liveapi::_PlayerRevive_default_instance_._instance, + &::rtech::liveapi::_ArenasItemSelected_default_instance_._instance, + &::rtech::liveapi::_ArenasItemDeselected_default_instance_._instance, + &::rtech::liveapi::_InventoryPickUp_default_instance_._instance, + &::rtech::liveapi::_InventoryDrop_default_instance_._instance, + &::rtech::liveapi::_InventoryUse_default_instance_._instance, + &::rtech::liveapi::_BannerCollected_default_instance_._instance, + &::rtech::liveapi::_PlayerAbilityUsed_default_instance_._instance, + &::rtech::liveapi::_LegendUpgradeSelected_default_instance_._instance, + &::rtech::liveapi::_ZiplineUsed_default_instance_._instance, + &::rtech::liveapi::_GrenadeThrown_default_instance_._instance, + &::rtech::liveapi::_BlackMarketAction_default_instance_._instance, + &::rtech::liveapi::_WraithPortal_default_instance_._instance, + &::rtech::liveapi::_WarpGateUsed_default_instance_._instance, + &::rtech::liveapi::_AmmoUsed_default_instance_._instance, + &::rtech::liveapi::_WeaponSwitched_default_instance_._instance, + &::rtech::liveapi::_CustomEvent_default_instance_._instance, + &::rtech::liveapi::_ChangeCamera_default_instance_._instance, + &::rtech::liveapi::_PauseToggle_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_CreateLobby_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_JoinLobby_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_LeaveLobby_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_SetReady_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_GetLobbyPlayers_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_SetMatchmaking_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_SetTeam_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_KickPlayer_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_SetSettings_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_GetSettings_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_SetTeamName_default_instance_._instance, + &::rtech::liveapi::_CustomMatch_SendChat_default_instance_._instance, + &::rtech::liveapi::_Request_default_instance_._instance, + &::rtech::liveapi::_RequestStatus_default_instance_._instance, + &::rtech::liveapi::_Response_default_instance_._instance, + &::rtech::liveapi::_LiveAPIEvent_default_instance_._instance, +}; + +const char descriptor_table_protodef_events_2eproto[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = + "\n\014events.proto\022\rrtech.liveapi\032\034google/pr" + "otobuf/struct.proto\032\031google/protobuf/any" + ".proto\"*\n\007Vector3\022\t\n\001x\030\001 \001(\002\022\t\n\001y\030\002 \001(\002\022" + "\t\n\001z\030\003 \001(\002\"\276\002\n\006Player\022\014\n\004name\030\001 \001(\t\022\016\n\006t" + "eamId\030\002 \001(\r\022#\n\003pos\030\003 \001(\0132\026.rtech.liveapi" + ".Vector3\022&\n\006angles\030\004 \001(\0132\026.rtech.liveapi" + ".Vector3\022\025\n\rcurrentHealth\030\005 \001(\r\022\021\n\tmaxHe" + "alth\030\006 \001(\r\022\024\n\014shieldHealth\030\007 \001(\r\022\027\n\017shie" + "ldMaxHealth\030\010 \001(\r\022\023\n\013nucleusHash\030\t \001(\t\022\024" + "\n\014hardwareName\030\n \001(\t\022\020\n\010teamName\030\013 \001(\t\022\022" + "\n\nsquadIndex\030\014 \001(\r\022\021\n\tcharacter\030\r \001(\t\022\014\n" + "\004skin\030\016 \001(\t\"b\n\027CustomMatch_LobbyPlayer\022\014" + "\n\004name\030\001 \001(\t\022\016\n\006teamId\030\002 \001(\r\022\023\n\013nucleusH" + "ash\030\003 \001(\t\022\024\n\014hardwareName\030\004 \001(\t\"\?\n\nDatac" + "enter\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001" + "(\t\022\014\n\004name\030\003 \001(\t\"V\n\007Version\022\021\n\tmajor_num" + "\030\001 \001(\r\022\021\n\tminor_num\030\002 \001(\r\022\023\n\013build_stamp" + "\030\003 \001(\r\022\020\n\010revision\030\004 \001(\t\"B\n\rInventoryIte" + "m\022\020\n\010quantity\030\001 \001(\005\022\014\n\004item\030\002 \001(\t\022\021\n\text" + "raData\030\003 \001(\t\"v\n\024LoadoutConfiguration\022-\n\007" + "weapons\030\001 \003(\0132\034.rtech.liveapi.InventoryI" + "tem\022/\n\tequipment\030\002 \003(\0132\034.rtech.liveapi.I" + "nventoryItem\"\214\001\n\004Init\022\021\n\ttimestamp\030\001 \001(\004" + "\022\020\n\010category\030\002 \001(\t\022\023\n\013gameVersion\030\003 \001(\t\022" + "*\n\napiVersion\030\004 \001(\0132\026.rtech.liveapi.Vers" + "ion\022\020\n\010platform\030\005 \001(\t\022\014\n\004name\030\006 \001(\t\"h\n\030C" + "ustomMatch_LobbyPlayers\022\023\n\013playerToken\030\001" + " \001(\t\0227\n\007players\030\002 \003(\0132&.rtech.liveapi.Cu" + "stomMatch_LobbyPlayer\"\262\001\n\020ObserverSwitch" + "ed\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022" + "\'\n\010observer\030\003 \001(\0132\025.rtech.liveapi.Player" + "\022%\n\006target\030\004 \001(\0132\025.rtech.liveapi.Player\022" + ")\n\ntargetTeam\030\005 \003(\0132\025.rtech.liveapi.Play" + "er\"S\n\022ObserverAnnotation\022\021\n\ttimestamp\030\001 " + "\001(\004\022\020\n\010category\030\002 \001(\t\022\030\n\020annotationSeria" + "l\030\003 \001(\005\"\225\002\n\nMatchSetup\022\021\n\ttimestamp\030\001 \001(" + "\004\022\020\n\010category\030\002 \001(\t\022\013\n\003map\030\003 \001(\t\022\024\n\014play" + "listName\030\004 \001(\t\022\024\n\014playlistDesc\030\005 \001(\t\022-\n\n" + "datacenter\030\006 \001(\0132\031.rtech.liveapi.Datacen" + "ter\022\023\n\013aimAssistOn\030\007 \001(\010\022\025\n\ranonymousMod" + "e\030\010 \001(\010\022\020\n\010serverId\030\t \001(\t\022<\n\017startingLoa" + "dout\030\n \001(\0132#.rtech.liveapi.LoadoutConfig" + "uration\"F\n\020GameStateChanged\022\021\n\ttimestamp" + "\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022\r\n\005state\030\003 \001(\t\"" + "_\n\021CharacterSelected\022\021\n\ttimestamp\030\001 \001(\004\022" + "\020\n\010category\030\002 \001(\t\022%\n\006player\030\003 \001(\0132\025.rtec" + "h.liveapi.Player\"k\n\rMatchStateEnd\022\021\n\ttim" + "estamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022\r\n\005state\030" + "\003 \001(\t\022&\n\007winners\030\004 \003(\0132\025.rtech.liveapi.P" + "layer\"\260\001\n\020RingStartClosing\022\021\n\ttimestamp\030" + "\001 \001(\004\022\020\n\010category\030\002 \001(\t\022\r\n\005stage\030\003 \001(\r\022&" + "\n\006center\030\004 \001(\0132\026.rtech.liveapi.Vector3\022\025" + "\n\rcurrentRadius\030\005 \001(\002\022\021\n\tendRadius\030\006 \001(\002" + "\022\026\n\016shrinkDuration\030\007 \001(\002\"\240\001\n\023RingFinishe" + "dClosing\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030" + "\002 \001(\t\022\r\n\005stage\030\003 \001(\r\022&\n\006center\030\004 \001(\0132\026.r" + "tech.liveapi.Vector3\022\025\n\rcurrentRadius\030\005 " + "\001(\002\022\026\n\016shrinkDuration\030\007 \001(\002\"]\n\017PlayerCon" + "nected\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 " + "\001(\t\022%\n\006player\030\003 \001(\0132\025.rtech.liveapi.Play" + "er\"\207\001\n\022PlayerDisconnected\022\021\n\ttimestamp\030\001" + " \001(\004\022\020\n\010category\030\002 \001(\t\022%\n\006player\030\003 \001(\0132\025" + ".rtech.liveapi.Player\022\024\n\014canReconnect\030\004 " + "\001(\010\022\017\n\007isAlive\030\005 \001(\010\"\274\001\n\021PlayerStatChang" + "ed\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022" + "%\n\006player\030\003 \001(\0132\025.rtech.liveapi.Player\022\020" + "\n\010statName\030\004 \001(\t\022\022\n\010intValue\030\005 \001(\rH\000\022\024\n\n" + "floatValue\030\006 \001(\002H\000\022\023\n\tboolValue\030\007 \001(\010H\000B" + "\n\n\010newValue\"u\n\030PlayerUpgradeTierChanged\022" + "\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022%\n\006" + "player\030\003 \001(\0132\025.rtech.liveapi.Player\022\r\n\005l" + "evel\030\004 \001(\005\"\255\001\n\rPlayerDamaged\022\021\n\ttimestam" + "p\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022\'\n\010attacker\030\003 " + "\001(\0132\025.rtech.liveapi.Player\022%\n\006victim\030\004 \001" + "(\0132\025.rtech.liveapi.Player\022\016\n\006weapon\030\005 \001(" + "\t\022\027\n\017damageInflicted\030\006 \001(\r\"\275\001\n\014PlayerKil" + "led\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t" + "\022\'\n\010attacker\030\003 \001(\0132\025.rtech.liveapi.Playe" + "r\022%\n\006victim\030\004 \001(\0132\025.rtech.liveapi.Player" + "\022(\n\tawardedTo\030\005 \001(\0132\025.rtech.liveapi.Play" + "er\022\016\n\006weapon\030\006 \001(\t\"\223\001\n\014PlayerDowned\022\021\n\tt" + "imestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022\'\n\010atta" + "cker\030\003 \001(\0132\025.rtech.liveapi.Player\022%\n\006vic" + "tim\030\004 \001(\0132\025.rtech.liveapi.Player\022\016\n\006weap" + "on\030\005 \001(\t\"\224\001\n\014PlayerAssist\022\021\n\ttimestamp\030\001" + " \001(\004\022\020\n\010category\030\002 \001(\t\022(\n\tassistant\030\003 \001(" + "\0132\025.rtech.liveapi.Player\022%\n\006victim\030\004 \001(\013" + "2\025.rtech.liveapi.Player\022\016\n\006weapon\030\005 \001(\t\"" + "^\n\017SquadEliminated\022\021\n\ttimestamp\030\001 \001(\004\022\020\n" + "\010category\030\002 \001(\t\022&\n\007players\030\003 \003(\0132\025.rtech" + ".liveapi.Player\"\247\001\n\027GibraltarShieldAbsor" + "bed\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t" + "\022\'\n\010attacker\030\003 \001(\0132\025.rtech.liveapi.Playe" + "r\022%\n\006victim\030\004 \001(\0132\025.rtech.liveapi.Player" + "\022\027\n\017damageInflicted\030\006 \001(\r\"\253\001\n\033RevenantFo" + "rgedShadowDamaged\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010" + "category\030\002 \001(\t\022\'\n\010attacker\030\003 \001(\0132\025.rtech" + ".liveapi.Player\022%\n\006victim\030\004 \001(\0132\025.rtech." + "liveapi.Player\022\027\n\017damageInflicted\030\006 \001(\r\"" + "\211\001\n\021PlayerRespawnTeam\022\021\n\ttimestamp\030\001 \001(\004" + "\022\020\n\010category\030\002 \001(\t\022%\n\006player\030\003 \001(\0132\025.rte" + "ch.liveapi.Player\022(\n\trespawned\030\004 \003(\0132\025.r" + "tech.liveapi.Player\"\202\001\n\014PlayerRevive\022\021\n\t" + "timestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022%\n\006pla" + "yer\030\003 \001(\0132\025.rtech.liveapi.Player\022&\n\007revi" + "ved\030\004 \001(\0132\025.rtech.liveapi.Player\"\200\001\n\022Are" + "nasItemSelected\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010ca" + "tegory\030\002 \001(\t\022%\n\006player\030\003 \001(\0132\025.rtech.liv" + "eapi.Player\022\014\n\004item\030\004 \001(\t\022\020\n\010quantity\030\005 " + "\001(\005\"\202\001\n\024ArenasItemDeselected\022\021\n\ttimestam" + "p\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022%\n\006player\030\003 \001(" + "\0132\025.rtech.liveapi.Player\022\014\n\004item\030\004 \001(\t\022\020" + "\n\010quantity\030\005 \001(\005\"}\n\017InventoryPickUp\022\021\n\tt" + "imestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022%\n\006play" + "er\030\003 \001(\0132\025.rtech.liveapi.Player\022\014\n\004item\030" + "\004 \001(\t\022\020\n\010quantity\030\005 \001(\005\"\216\001\n\rInventoryDro" + "p\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022%" + "\n\006player\030\003 \001(\0132\025.rtech.liveapi.Player\022\014\n" + "\004item\030\004 \001(\t\022\020\n\010quantity\030\005 \001(\005\022\021\n\textraDa" + "ta\030\006 \003(\t\"z\n\014InventoryUse\022\021\n\ttimestamp\030\001 " + "\001(\004\022\020\n\010category\030\002 \001(\t\022%\n\006player\030\003 \001(\0132\025." + "rtech.liveapi.Player\022\014\n\004item\030\004 \001(\t\022\020\n\010qu" + "antity\030\005 \001(\005\"\207\001\n\017BannerCollected\022\021\n\ttime" + "stamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022%\n\006player\030" + "\003 \001(\0132\025.rtech.liveapi.Player\022(\n\tcollecte" + "d\030\004 \001(\0132\025.rtech.liveapi.Player\"u\n\021Player" + "AbilityUsed\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010catego" + "ry\030\002 \001(\t\022%\n\006player\030\003 \001(\0132\025.rtech.liveapi" + ".Player\022\024\n\014linkedEntity\030\004 \001(\t\"\234\001\n\025Legend" + "UpgradeSelected\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010ca" + "tegory\030\002 \001(\t\022%\n\006player\030\003 \001(\0132\025.rtech.liv" + "eapi.Player\022\023\n\013upgradeName\030\004 \001(\t\022\023\n\013upgr" + "adeDesc\030\005 \001(\t\022\r\n\005level\030\006 \001(\005\"o\n\013ZiplineU" + "sed\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t" + "\022%\n\006player\030\003 \001(\0132\025.rtech.liveapi.Player\022" + "\024\n\014linkedEntity\030\004 \001(\t\"q\n\rGrenadeThrown\022\021" + "\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022%\n\006p" + "layer\030\003 \001(\0132\025.rtech.liveapi.Player\022\024\n\014li" + "nkedEntity\030\004 \001(\t\"m\n\021BlackMarketAction\022\021\n" + "\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022%\n\006pl" + "ayer\030\003 \001(\0132\025.rtech.liveapi.Player\022\014\n\004ite" + "m\030\004 \001(\t\"Z\n\014WraithPortal\022\021\n\ttimestamp\030\001 \001" + "(\004\022\020\n\010category\030\002 \001(\t\022%\n\006player\030\003 \001(\0132\025.r" + "tech.liveapi.Player\"Z\n\014WarpGateUsed\022\021\n\tt" + "imestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022%\n\006play" + "er\030\003 \001(\0132\025.rtech.liveapi.Player\"\250\001\n\010Ammo" + "Used\022\021\n\ttimestamp\030\001 \001(\004\022\020\n\010category\030\002 \001(" + "\t\022%\n\006player\030\003 \001(\0132\025.rtech.liveapi.Player" + "\022\020\n\010ammoType\030\004 \001(\t\022\022\n\namountUsed\030\005 \001(\r\022\024" + "\n\014oldAmmoCount\030\006 \001(\r\022\024\n\014newAmmoCount\030\007 \001" + "(\r\"\202\001\n\016WeaponSwitched\022\021\n\ttimestamp\030\001 \001(\004" + "\022\020\n\010category\030\002 \001(\t\022%\n\006player\030\003 \001(\0132\025.rte" + "ch.liveapi.Player\022\021\n\toldWeapon\030\004 \001(\t\022\021\n\t" + "newWeapon\030\005 \001(\t\"g\n\013CustomEvent\022\021\n\ttimest" + "amp\030\001 \001(\004\022\020\n\010category\030\002 \001(\t\022\014\n\004name\030\003 \001(" + "\t\022%\n\004data\030\004 \001(\0132\027.google.protobuf.Struct" + "\"X\n\014ChangeCamera\022.\n\003poi\030\001 \001(\0162\037.rtech.li" + "veapi.PlayerOfInterestH\000\022\016\n\004name\030\002 \001(\tH\000" + "B\010\n\006target\"\037\n\013PauseToggle\022\020\n\010preTimer\030\001 " + "\001(\002\"\031\n\027CustomMatch_CreateLobby\"*\n\025Custom" + "Match_JoinLobby\022\021\n\troleToken\030\001 \001(\t\"\030\n\026Cu" + "stomMatch_LeaveLobby\"\'\n\024CustomMatch_SetR" + "eady\022\017\n\007isReady\030\001 \001(\010\"\035\n\033CustomMatch_Get" + "LobbyPlayers\"-\n\032CustomMatch_SetMatchmaki" + "ng\022\017\n\007enabled\030\001 \001(\010\"\\\n\023CustomMatch_SetTe" + "am\022\016\n\006teamId\030\001 \001(\005\022\032\n\022targetHardwareName" + "\030\002 \001(\t\022\031\n\021targetNucleusHash\030\003 \001(\t\"O\n\026Cus" + "tomMatch_KickPlayer\022\032\n\022targetHardwareNam" + "e\030\001 \001(\t\022\031\n\021targetNucleusHash\030\002 \001(\t\"\217\001\n\027C" + "ustomMatch_SetSettings\022\024\n\014playlistName\030\001" + " \001(\t\022\021\n\tadminChat\030\002 \001(\010\022\022\n\nteamRename\030\003 " + "\001(\010\022\022\n\nselfAssign\030\004 \001(\010\022\021\n\taimAssist\030\005 \001" + "(\010\022\020\n\010anonMode\030\006 \001(\010\"\031\n\027CustomMatch_GetS" + "ettings\";\n\027CustomMatch_SetTeamName\022\016\n\006te" + "amId\030\001 \001(\005\022\020\n\010teamName\030\002 \001(\t\"$\n\024CustomMa" + "tch_SendChat\022\014\n\004text\030\001 \001(\t\"\226\010\n\007Request\022\017" + "\n\007withAck\030\001 \001(\010\022\024\n\014preSharedKey\030\002 \001(\t\0220\n" + "\tchangeCam\030\004 \001(\0132\033.rtech.liveapi.ChangeC" + "ameraH\000\0221\n\013pauseToggle\030\005 \001(\0132\032.rtech.liv" + "eapi.PauseToggleH\000\022I\n\027customMatch_Create" + "Lobby\030\n \001(\0132&.rtech.liveapi.CustomMatch_" + "CreateLobbyH\000\022E\n\025customMatch_JoinLobby\030\013" + " \001(\0132$.rtech.liveapi.CustomMatch_JoinLob" + "byH\000\022G\n\026customMatch_LeaveLobby\030\014 \001(\0132%.r" + "tech.liveapi.CustomMatch_LeaveLobbyH\000\022C\n" + "\024customMatch_SetReady\030\r \001(\0132#.rtech.live" + "api.CustomMatch_SetReadyH\000\022O\n\032customMatc" + "h_SetMatchmaking\030\016 \001(\0132).rtech.liveapi.C" + "ustomMatch_SetMatchmakingH\000\022A\n\023customMat" + "ch_SetTeam\030\017 \001(\0132\".rtech.liveapi.CustomM" + "atch_SetTeamH\000\022G\n\026customMatch_KickPlayer" + "\030\020 \001(\0132%.rtech.liveapi.CustomMatch_KickP" + "layerH\000\022I\n\027customMatch_SetSettings\030\021 \001(\013" + "2&.rtech.liveapi.CustomMatch_SetSettings" + "H\000\022C\n\024customMatch_SendChat\030\022 \001(\0132#.rtech" + ".liveapi.CustomMatch_SendChatH\000\022Q\n\033custo" + "mMatch_GetLobbyPlayers\030\023 \001(\0132*.rtech.liv" + "eapi.CustomMatch_GetLobbyPlayersH\000\022I\n\027cu" + "stomMatch_SetTeamName\030\024 \001(\0132&.rtech.live" + "api.CustomMatch_SetTeamNameH\000\022I\n\027customM" + "atch_GetSettings\030\025 \001(\0132&.rtech.liveapi.C" + "ustomMatch_GetSettingsH\000B\t\n\007actions\"\037\n\rR" + "equestStatus\022\016\n\006status\030\001 \001(\t\"A\n\010Response" + "\022\017\n\007success\030\001 \001(\010\022$\n\006result\030\002 \001(\0132\024.goog" + "le.protobuf.Any\"M\n\014LiveAPIEvent\022\022\n\nevent" + "_size\030\001 \001(\007\022)\n\013gameMessage\030\003 \001(\0132\024.googl" + "e.protobuf.Any*\210\001\n\020PlayerOfInterest\022\017\n\013U" + "NSPECIFIED\020\000\022\010\n\004NEXT\020\001\022\014\n\010PREVIOUS\020\002\022\017\n\013" + "KILL_LEADER\020\003\022\021\n\rCLOSEST_ENEMY\020\004\022\022\n\016CLOS" + "EST_PLAYER\020\005\022\023\n\017LATEST_ATTACKER\020\006b\006proto" + "3" + ; +static const ::_pbi::DescriptorTable* const descriptor_table_events_2eproto_deps[2] = { + &::descriptor_table_google_2fprotobuf_2fany_2eproto, + &::descriptor_table_google_2fprotobuf_2fstruct_2eproto, +}; +static ::_pbi::once_flag descriptor_table_events_2eproto_once; +const ::_pbi::DescriptorTable descriptor_table_events_2eproto = { + false, false, 8401, descriptor_table_protodef_events_2eproto, + "events.proto", + &descriptor_table_events_2eproto_once, descriptor_table_events_2eproto_deps, 2, 64, + schemas, file_default_instances, TableStruct_events_2eproto::offsets, + file_level_metadata_events_2eproto, file_level_enum_descriptors_events_2eproto, + file_level_service_descriptors_events_2eproto, +}; +PROTOBUF_ATTRIBUTE_WEAK const ::_pbi::DescriptorTable* descriptor_table_events_2eproto_getter() { + return &descriptor_table_events_2eproto; +} + +// Force running AddDescriptors() at dynamic initialization time. +PROTOBUF_ATTRIBUTE_INIT_PRIORITY2 static ::_pbi::AddDescriptorsRunner dynamic_init_dummy_events_2eproto(&descriptor_table_events_2eproto); +namespace rtech { +namespace liveapi { +const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* PlayerOfInterest_descriptor() { + ::PROTOBUF_NAMESPACE_ID::internal::AssignDescriptors(&descriptor_table_events_2eproto); + return file_level_enum_descriptors_events_2eproto[0]; +} +bool PlayerOfInterest_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + return true; + default: + return false; + } +} + + +// =================================================================== + +class Vector3::_Internal { + public: +}; + +Vector3::Vector3(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.Vector3) +} +Vector3::Vector3(const Vector3& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + Vector3* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.x_){} + , decltype(_impl_.y_){} + , decltype(_impl_.z_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + ::memcpy(&_impl_.x_, &from._impl_.x_, + static_cast(reinterpret_cast(&_impl_.z_) - + reinterpret_cast(&_impl_.x_)) + sizeof(_impl_.z_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.Vector3) +} + +inline void Vector3::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.x_){0} + , decltype(_impl_.y_){0} + , decltype(_impl_.z_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +Vector3::~Vector3() { + // @@protoc_insertion_point(destructor:rtech.liveapi.Vector3) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Vector3::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); +} + +void Vector3::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Vector3::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.Vector3) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + ::memset(&_impl_.x_, 0, static_cast( + reinterpret_cast(&_impl_.z_) - + reinterpret_cast(&_impl_.x_)) + sizeof(_impl_.z_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* Vector3::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // float x = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 13)) { + _impl_.x_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else + goto handle_unusual; + continue; + // float y = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 21)) { + _impl_.y_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else + goto handle_unusual; + continue; + // float z = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 29)) { + _impl_.z_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Vector3::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.Vector3) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // float x = 1; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_x = this->_internal_x(); + uint32_t raw_x; + memcpy(&raw_x, &tmp_x, sizeof(tmp_x)); + if (raw_x != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray(1, this->_internal_x(), target); + } + + // float y = 2; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_y = this->_internal_y(); + uint32_t raw_y; + memcpy(&raw_y, &tmp_y, sizeof(tmp_y)); + if (raw_y != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray(2, this->_internal_y(), target); + } + + // float z = 3; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_z = this->_internal_z(); + uint32_t raw_z; + memcpy(&raw_z, &tmp_z, sizeof(tmp_z)); + if (raw_z != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray(3, this->_internal_z(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.Vector3) + return target; +} + +size_t Vector3::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.Vector3) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // float x = 1; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_x = this->_internal_x(); + uint32_t raw_x; + memcpy(&raw_x, &tmp_x, sizeof(tmp_x)); + if (raw_x != 0) { + total_size += 1 + 4; + } + + // float y = 2; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_y = this->_internal_y(); + uint32_t raw_y; + memcpy(&raw_y, &tmp_y, sizeof(tmp_y)); + if (raw_y != 0) { + total_size += 1 + 4; + } + + // float z = 3; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_z = this->_internal_z(); + uint32_t raw_z; + memcpy(&raw_z, &tmp_z, sizeof(tmp_z)); + if (raw_z != 0) { + total_size += 1 + 4; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData Vector3::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + Vector3::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*Vector3::GetClassData() const { return &_class_data_; } + + +void Vector3::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.Vector3) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_x = from._internal_x(); + uint32_t raw_x; + memcpy(&raw_x, &tmp_x, sizeof(tmp_x)); + if (raw_x != 0) { + _this->_internal_set_x(from._internal_x()); + } + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_y = from._internal_y(); + uint32_t raw_y; + memcpy(&raw_y, &tmp_y, sizeof(tmp_y)); + if (raw_y != 0) { + _this->_internal_set_y(from._internal_y()); + } + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_z = from._internal_z(); + uint32_t raw_z; + memcpy(&raw_z, &tmp_z, sizeof(tmp_z)); + if (raw_z != 0) { + _this->_internal_set_z(from._internal_z()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void Vector3::CopyFrom(const Vector3& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.Vector3) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Vector3::IsInitialized() const { + return true; +} + +void Vector3::InternalSwap(Vector3* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(Vector3, _impl_.z_) + + sizeof(Vector3::_impl_.z_) + - PROTOBUF_FIELD_OFFSET(Vector3, _impl_.x_)>( + reinterpret_cast(&_impl_.x_), + reinterpret_cast(&other->_impl_.x_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata Vector3::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[0]); +} + +// =================================================================== + +class Player::_Internal { + public: + static const ::rtech::liveapi::Vector3& pos(const Player* msg); + static const ::rtech::liveapi::Vector3& angles(const Player* msg); +}; + +const ::rtech::liveapi::Vector3& +Player::_Internal::pos(const Player* msg) { + return *msg->_impl_.pos_; +} +const ::rtech::liveapi::Vector3& +Player::_Internal::angles(const Player* msg) { + return *msg->_impl_.angles_; +} +Player::Player(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.Player) +} +Player::Player(const Player& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + Player* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.name_){} + , decltype(_impl_.nucleushash_){} + , decltype(_impl_.hardwarename_){} + , decltype(_impl_.teamname_){} + , decltype(_impl_.character_){} + , decltype(_impl_.skin_){} + , decltype(_impl_.pos_){nullptr} + , decltype(_impl_.angles_){nullptr} + , decltype(_impl_.teamid_){} + , decltype(_impl_.currenthealth_){} + , decltype(_impl_.maxhealth_){} + , decltype(_impl_.shieldhealth_){} + , decltype(_impl_.shieldmaxhealth_){} + , decltype(_impl_.squadindex_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_name().empty()) { + _this->_impl_.name_.Set(from._internal_name(), + _this->GetArenaForAllocation()); + } + _impl_.nucleushash_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.nucleushash_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_nucleushash().empty()) { + _this->_impl_.nucleushash_.Set(from._internal_nucleushash(), + _this->GetArenaForAllocation()); + } + _impl_.hardwarename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.hardwarename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_hardwarename().empty()) { + _this->_impl_.hardwarename_.Set(from._internal_hardwarename(), + _this->GetArenaForAllocation()); + } + _impl_.teamname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.teamname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_teamname().empty()) { + _this->_impl_.teamname_.Set(from._internal_teamname(), + _this->GetArenaForAllocation()); + } + _impl_.character_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.character_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_character().empty()) { + _this->_impl_.character_.Set(from._internal_character(), + _this->GetArenaForAllocation()); + } + _impl_.skin_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.skin_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_skin().empty()) { + _this->_impl_.skin_.Set(from._internal_skin(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_pos()) { + _this->_impl_.pos_ = new ::rtech::liveapi::Vector3(*from._impl_.pos_); + } + if (from._internal_has_angles()) { + _this->_impl_.angles_ = new ::rtech::liveapi::Vector3(*from._impl_.angles_); + } + ::memcpy(&_impl_.teamid_, &from._impl_.teamid_, + static_cast(reinterpret_cast(&_impl_.squadindex_) - + reinterpret_cast(&_impl_.teamid_)) + sizeof(_impl_.squadindex_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.Player) +} + +inline void Player::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.name_){} + , decltype(_impl_.nucleushash_){} + , decltype(_impl_.hardwarename_){} + , decltype(_impl_.teamname_){} + , decltype(_impl_.character_){} + , decltype(_impl_.skin_){} + , decltype(_impl_.pos_){nullptr} + , decltype(_impl_.angles_){nullptr} + , decltype(_impl_.teamid_){0u} + , decltype(_impl_.currenthealth_){0u} + , decltype(_impl_.maxhealth_){0u} + , decltype(_impl_.shieldhealth_){0u} + , decltype(_impl_.shieldmaxhealth_){0u} + , decltype(_impl_.squadindex_){0u} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.nucleushash_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.nucleushash_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.hardwarename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.hardwarename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.teamname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.teamname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.character_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.character_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.skin_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.skin_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +Player::~Player() { + // @@protoc_insertion_point(destructor:rtech.liveapi.Player) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Player::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.name_.Destroy(); + _impl_.nucleushash_.Destroy(); + _impl_.hardwarename_.Destroy(); + _impl_.teamname_.Destroy(); + _impl_.character_.Destroy(); + _impl_.skin_.Destroy(); + if (this != internal_default_instance()) delete _impl_.pos_; + if (this != internal_default_instance()) delete _impl_.angles_; +} + +void Player::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Player::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.Player) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.name_.ClearToEmpty(); + _impl_.nucleushash_.ClearToEmpty(); + _impl_.hardwarename_.ClearToEmpty(); + _impl_.teamname_.ClearToEmpty(); + _impl_.character_.ClearToEmpty(); + _impl_.skin_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.pos_ != nullptr) { + delete _impl_.pos_; + } + _impl_.pos_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.angles_ != nullptr) { + delete _impl_.angles_; + } + _impl_.angles_ = nullptr; + ::memset(&_impl_.teamid_, 0, static_cast( + reinterpret_cast(&_impl_.squadindex_) - + reinterpret_cast(&_impl_.teamid_)) + sizeof(_impl_.squadindex_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* Player::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string name = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_name(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Player.name")); + } else + goto handle_unusual; + continue; + // uint32 teamId = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 16)) { + _impl_.teamid_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Vector3 pos = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_pos(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Vector3 angles = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_angles(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // uint32 currentHealth = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _impl_.currenthealth_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // uint32 maxHealth = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 48)) { + _impl_.maxhealth_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // uint32 shieldHealth = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 56)) { + _impl_.shieldhealth_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // uint32 shieldMaxHealth = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 64)) { + _impl_.shieldmaxhealth_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string nucleusHash = 9; + case 9: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 74)) { + auto str = _internal_mutable_nucleushash(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Player.nucleusHash")); + } else + goto handle_unusual; + continue; + // string hardwareName = 10; + case 10: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 82)) { + auto str = _internal_mutable_hardwarename(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Player.hardwareName")); + } else + goto handle_unusual; + continue; + // string teamName = 11; + case 11: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 90)) { + auto str = _internal_mutable_teamname(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Player.teamName")); + } else + goto handle_unusual; + continue; + // uint32 squadIndex = 12; + case 12: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 96)) { + _impl_.squadindex_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string character = 13; + case 13: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 106)) { + auto str = _internal_mutable_character(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Player.character")); + } else + goto handle_unusual; + continue; + // string skin = 14; + case 14: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 114)) { + auto str = _internal_mutable_skin(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Player.skin")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Player::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.Player) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string name = 1; + if (!this->_internal_name().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_name().data(), static_cast(this->_internal_name().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Player.name"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_name(), target); + } + + // uint32 teamId = 2; + if (this->_internal_teamid() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(2, this->_internal_teamid(), target); + } + + // .rtech.liveapi.Vector3 pos = 3; + if (this->_internal_has_pos()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::pos(this), + _Internal::pos(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Vector3 angles = 4; + if (this->_internal_has_angles()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::angles(this), + _Internal::angles(this).GetCachedSize(), target, stream); + } + + // uint32 currentHealth = 5; + if (this->_internal_currenthealth() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(5, this->_internal_currenthealth(), target); + } + + // uint32 maxHealth = 6; + if (this->_internal_maxhealth() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(6, this->_internal_maxhealth(), target); + } + + // uint32 shieldHealth = 7; + if (this->_internal_shieldhealth() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(7, this->_internal_shieldhealth(), target); + } + + // uint32 shieldMaxHealth = 8; + if (this->_internal_shieldmaxhealth() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(8, this->_internal_shieldmaxhealth(), target); + } + + // string nucleusHash = 9; + if (!this->_internal_nucleushash().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_nucleushash().data(), static_cast(this->_internal_nucleushash().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Player.nucleusHash"); + target = stream->WriteStringMaybeAliased( + 9, this->_internal_nucleushash(), target); + } + + // string hardwareName = 10; + if (!this->_internal_hardwarename().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_hardwarename().data(), static_cast(this->_internal_hardwarename().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Player.hardwareName"); + target = stream->WriteStringMaybeAliased( + 10, this->_internal_hardwarename(), target); + } + + // string teamName = 11; + if (!this->_internal_teamname().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_teamname().data(), static_cast(this->_internal_teamname().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Player.teamName"); + target = stream->WriteStringMaybeAliased( + 11, this->_internal_teamname(), target); + } + + // uint32 squadIndex = 12; + if (this->_internal_squadindex() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(12, this->_internal_squadindex(), target); + } + + // string character = 13; + if (!this->_internal_character().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_character().data(), static_cast(this->_internal_character().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Player.character"); + target = stream->WriteStringMaybeAliased( + 13, this->_internal_character(), target); + } + + // string skin = 14; + if (!this->_internal_skin().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_skin().data(), static_cast(this->_internal_skin().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Player.skin"); + target = stream->WriteStringMaybeAliased( + 14, this->_internal_skin(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.Player) + return target; +} + +size_t Player::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.Player) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string name = 1; + if (!this->_internal_name().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + // string nucleusHash = 9; + if (!this->_internal_nucleushash().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_nucleushash()); + } + + // string hardwareName = 10; + if (!this->_internal_hardwarename().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_hardwarename()); + } + + // string teamName = 11; + if (!this->_internal_teamname().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_teamname()); + } + + // string character = 13; + if (!this->_internal_character().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_character()); + } + + // string skin = 14; + if (!this->_internal_skin().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_skin()); + } + + // .rtech.liveapi.Vector3 pos = 3; + if (this->_internal_has_pos()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.pos_); + } + + // .rtech.liveapi.Vector3 angles = 4; + if (this->_internal_has_angles()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.angles_); + } + + // uint32 teamId = 2; + if (this->_internal_teamid() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_teamid()); + } + + // uint32 currentHealth = 5; + if (this->_internal_currenthealth() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_currenthealth()); + } + + // uint32 maxHealth = 6; + if (this->_internal_maxhealth() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_maxhealth()); + } + + // uint32 shieldHealth = 7; + if (this->_internal_shieldhealth() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_shieldhealth()); + } + + // uint32 shieldMaxHealth = 8; + if (this->_internal_shieldmaxhealth() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_shieldmaxhealth()); + } + + // uint32 squadIndex = 12; + if (this->_internal_squadindex() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_squadindex()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData Player::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + Player::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*Player::GetClassData() const { return &_class_data_; } + + +void Player::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.Player) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_name().empty()) { + _this->_internal_set_name(from._internal_name()); + } + if (!from._internal_nucleushash().empty()) { + _this->_internal_set_nucleushash(from._internal_nucleushash()); + } + if (!from._internal_hardwarename().empty()) { + _this->_internal_set_hardwarename(from._internal_hardwarename()); + } + if (!from._internal_teamname().empty()) { + _this->_internal_set_teamname(from._internal_teamname()); + } + if (!from._internal_character().empty()) { + _this->_internal_set_character(from._internal_character()); + } + if (!from._internal_skin().empty()) { + _this->_internal_set_skin(from._internal_skin()); + } + if (from._internal_has_pos()) { + _this->_internal_mutable_pos()->::rtech::liveapi::Vector3::MergeFrom( + from._internal_pos()); + } + if (from._internal_has_angles()) { + _this->_internal_mutable_angles()->::rtech::liveapi::Vector3::MergeFrom( + from._internal_angles()); + } + if (from._internal_teamid() != 0) { + _this->_internal_set_teamid(from._internal_teamid()); + } + if (from._internal_currenthealth() != 0) { + _this->_internal_set_currenthealth(from._internal_currenthealth()); + } + if (from._internal_maxhealth() != 0) { + _this->_internal_set_maxhealth(from._internal_maxhealth()); + } + if (from._internal_shieldhealth() != 0) { + _this->_internal_set_shieldhealth(from._internal_shieldhealth()); + } + if (from._internal_shieldmaxhealth() != 0) { + _this->_internal_set_shieldmaxhealth(from._internal_shieldmaxhealth()); + } + if (from._internal_squadindex() != 0) { + _this->_internal_set_squadindex(from._internal_squadindex()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void Player::CopyFrom(const Player& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.Player) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Player::IsInitialized() const { + return true; +} + +void Player::InternalSwap(Player* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.name_, lhs_arena, + &other->_impl_.name_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.nucleushash_, lhs_arena, + &other->_impl_.nucleushash_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.hardwarename_, lhs_arena, + &other->_impl_.hardwarename_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.teamname_, lhs_arena, + &other->_impl_.teamname_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.character_, lhs_arena, + &other->_impl_.character_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.skin_, lhs_arena, + &other->_impl_.skin_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(Player, _impl_.squadindex_) + + sizeof(Player::_impl_.squadindex_) + - PROTOBUF_FIELD_OFFSET(Player, _impl_.pos_)>( + reinterpret_cast(&_impl_.pos_), + reinterpret_cast(&other->_impl_.pos_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata Player::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[1]); +} + +// =================================================================== + +class CustomMatch_LobbyPlayer::_Internal { + public: +}; + +CustomMatch_LobbyPlayer::CustomMatch_LobbyPlayer(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_LobbyPlayer) +} +CustomMatch_LobbyPlayer::CustomMatch_LobbyPlayer(const CustomMatch_LobbyPlayer& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomMatch_LobbyPlayer* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.name_){} + , decltype(_impl_.nucleushash_){} + , decltype(_impl_.hardwarename_){} + , decltype(_impl_.teamid_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_name().empty()) { + _this->_impl_.name_.Set(from._internal_name(), + _this->GetArenaForAllocation()); + } + _impl_.nucleushash_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.nucleushash_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_nucleushash().empty()) { + _this->_impl_.nucleushash_.Set(from._internal_nucleushash(), + _this->GetArenaForAllocation()); + } + _impl_.hardwarename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.hardwarename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_hardwarename().empty()) { + _this->_impl_.hardwarename_.Set(from._internal_hardwarename(), + _this->GetArenaForAllocation()); + } + _this->_impl_.teamid_ = from._impl_.teamid_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_LobbyPlayer) +} + +inline void CustomMatch_LobbyPlayer::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.name_){} + , decltype(_impl_.nucleushash_){} + , decltype(_impl_.hardwarename_){} + , decltype(_impl_.teamid_){0u} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.nucleushash_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.nucleushash_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.hardwarename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.hardwarename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CustomMatch_LobbyPlayer::~CustomMatch_LobbyPlayer() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomMatch_LobbyPlayer) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomMatch_LobbyPlayer::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.name_.Destroy(); + _impl_.nucleushash_.Destroy(); + _impl_.hardwarename_.Destroy(); +} + +void CustomMatch_LobbyPlayer::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomMatch_LobbyPlayer::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomMatch_LobbyPlayer) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.name_.ClearToEmpty(); + _impl_.nucleushash_.ClearToEmpty(); + _impl_.hardwarename_.ClearToEmpty(); + _impl_.teamid_ = 0u; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomMatch_LobbyPlayer::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string name = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_name(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_LobbyPlayer.name")); + } else + goto handle_unusual; + continue; + // uint32 teamId = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 16)) { + _impl_.teamid_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string nucleusHash = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_nucleushash(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_LobbyPlayer.nucleusHash")); + } else + goto handle_unusual; + continue; + // string hardwareName = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_hardwarename(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_LobbyPlayer.hardwareName")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomMatch_LobbyPlayer::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomMatch_LobbyPlayer) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string name = 1; + if (!this->_internal_name().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_name().data(), static_cast(this->_internal_name().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_LobbyPlayer.name"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_name(), target); + } + + // uint32 teamId = 2; + if (this->_internal_teamid() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(2, this->_internal_teamid(), target); + } + + // string nucleusHash = 3; + if (!this->_internal_nucleushash().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_nucleushash().data(), static_cast(this->_internal_nucleushash().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_LobbyPlayer.nucleusHash"); + target = stream->WriteStringMaybeAliased( + 3, this->_internal_nucleushash(), target); + } + + // string hardwareName = 4; + if (!this->_internal_hardwarename().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_hardwarename().data(), static_cast(this->_internal_hardwarename().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_LobbyPlayer.hardwareName"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_hardwarename(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomMatch_LobbyPlayer) + return target; +} + +size_t CustomMatch_LobbyPlayer::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomMatch_LobbyPlayer) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string name = 1; + if (!this->_internal_name().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + // string nucleusHash = 3; + if (!this->_internal_nucleushash().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_nucleushash()); + } + + // string hardwareName = 4; + if (!this->_internal_hardwarename().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_hardwarename()); + } + + // uint32 teamId = 2; + if (this->_internal_teamid() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_teamid()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_LobbyPlayer::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomMatch_LobbyPlayer::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_LobbyPlayer::GetClassData() const { return &_class_data_; } + + +void CustomMatch_LobbyPlayer::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomMatch_LobbyPlayer) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_name().empty()) { + _this->_internal_set_name(from._internal_name()); + } + if (!from._internal_nucleushash().empty()) { + _this->_internal_set_nucleushash(from._internal_nucleushash()); + } + if (!from._internal_hardwarename().empty()) { + _this->_internal_set_hardwarename(from._internal_hardwarename()); + } + if (from._internal_teamid() != 0) { + _this->_internal_set_teamid(from._internal_teamid()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomMatch_LobbyPlayer::CopyFrom(const CustomMatch_LobbyPlayer& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomMatch_LobbyPlayer) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomMatch_LobbyPlayer::IsInitialized() const { + return true; +} + +void CustomMatch_LobbyPlayer::InternalSwap(CustomMatch_LobbyPlayer* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.name_, lhs_arena, + &other->_impl_.name_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.nucleushash_, lhs_arena, + &other->_impl_.nucleushash_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.hardwarename_, lhs_arena, + &other->_impl_.hardwarename_, rhs_arena + ); + swap(_impl_.teamid_, other->_impl_.teamid_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_LobbyPlayer::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[2]); +} + +// =================================================================== + +class Datacenter::_Internal { + public: +}; + +Datacenter::Datacenter(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.Datacenter) +} +Datacenter::Datacenter(const Datacenter& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + Datacenter* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.name_){} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_name().empty()) { + _this->_impl_.name_.Set(from._internal_name(), + _this->GetArenaForAllocation()); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.Datacenter) +} + +inline void Datacenter::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.name_){} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +Datacenter::~Datacenter() { + // @@protoc_insertion_point(destructor:rtech.liveapi.Datacenter) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Datacenter::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.name_.Destroy(); +} + +void Datacenter::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Datacenter::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.Datacenter) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.name_.ClearToEmpty(); + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* Datacenter::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Datacenter.category")); + } else + goto handle_unusual; + continue; + // string name = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_name(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Datacenter.name")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Datacenter::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.Datacenter) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Datacenter.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // string name = 3; + if (!this->_internal_name().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_name().data(), static_cast(this->_internal_name().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Datacenter.name"); + target = stream->WriteStringMaybeAliased( + 3, this->_internal_name(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.Datacenter) + return target; +} + +size_t Datacenter::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.Datacenter) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string name = 3; + if (!this->_internal_name().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData Datacenter::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + Datacenter::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*Datacenter::GetClassData() const { return &_class_data_; } + + +void Datacenter::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.Datacenter) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_name().empty()) { + _this->_internal_set_name(from._internal_name()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void Datacenter::CopyFrom(const Datacenter& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.Datacenter) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Datacenter::IsInitialized() const { + return true; +} + +void Datacenter::InternalSwap(Datacenter* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.name_, lhs_arena, + &other->_impl_.name_, rhs_arena + ); + swap(_impl_.timestamp_, other->_impl_.timestamp_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata Datacenter::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[3]); +} + +// =================================================================== + +class Version::_Internal { + public: +}; + +Version::Version(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.Version) +} +Version::Version(const Version& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + Version* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.revision_){} + , decltype(_impl_.major_num_){} + , decltype(_impl_.minor_num_){} + , decltype(_impl_.build_stamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.revision_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.revision_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_revision().empty()) { + _this->_impl_.revision_.Set(from._internal_revision(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.major_num_, &from._impl_.major_num_, + static_cast(reinterpret_cast(&_impl_.build_stamp_) - + reinterpret_cast(&_impl_.major_num_)) + sizeof(_impl_.build_stamp_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.Version) +} + +inline void Version::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.revision_){} + , decltype(_impl_.major_num_){0u} + , decltype(_impl_.minor_num_){0u} + , decltype(_impl_.build_stamp_){0u} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.revision_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.revision_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +Version::~Version() { + // @@protoc_insertion_point(destructor:rtech.liveapi.Version) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Version::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.revision_.Destroy(); +} + +void Version::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Version::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.Version) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.revision_.ClearToEmpty(); + ::memset(&_impl_.major_num_, 0, static_cast( + reinterpret_cast(&_impl_.build_stamp_) - + reinterpret_cast(&_impl_.major_num_)) + sizeof(_impl_.build_stamp_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* Version::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint32 major_num = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.major_num_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // uint32 minor_num = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 16)) { + _impl_.minor_num_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // uint32 build_stamp = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 24)) { + _impl_.build_stamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string revision = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_revision(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Version.revision")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Version::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.Version) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint32 major_num = 1; + if (this->_internal_major_num() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(1, this->_internal_major_num(), target); + } + + // uint32 minor_num = 2; + if (this->_internal_minor_num() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(2, this->_internal_minor_num(), target); + } + + // uint32 build_stamp = 3; + if (this->_internal_build_stamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(3, this->_internal_build_stamp(), target); + } + + // string revision = 4; + if (!this->_internal_revision().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_revision().data(), static_cast(this->_internal_revision().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Version.revision"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_revision(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.Version) + return target; +} + +size_t Version::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.Version) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string revision = 4; + if (!this->_internal_revision().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_revision()); + } + + // uint32 major_num = 1; + if (this->_internal_major_num() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_major_num()); + } + + // uint32 minor_num = 2; + if (this->_internal_minor_num() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_minor_num()); + } + + // uint32 build_stamp = 3; + if (this->_internal_build_stamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_build_stamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData Version::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + Version::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*Version::GetClassData() const { return &_class_data_; } + + +void Version::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.Version) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_revision().empty()) { + _this->_internal_set_revision(from._internal_revision()); + } + if (from._internal_major_num() != 0) { + _this->_internal_set_major_num(from._internal_major_num()); + } + if (from._internal_minor_num() != 0) { + _this->_internal_set_minor_num(from._internal_minor_num()); + } + if (from._internal_build_stamp() != 0) { + _this->_internal_set_build_stamp(from._internal_build_stamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void Version::CopyFrom(const Version& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.Version) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Version::IsInitialized() const { + return true; +} + +void Version::InternalSwap(Version* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.revision_, lhs_arena, + &other->_impl_.revision_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(Version, _impl_.build_stamp_) + + sizeof(Version::_impl_.build_stamp_) + - PROTOBUF_FIELD_OFFSET(Version, _impl_.major_num_)>( + reinterpret_cast(&_impl_.major_num_), + reinterpret_cast(&other->_impl_.major_num_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata Version::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[4]); +} + +// =================================================================== + +class InventoryItem::_Internal { + public: +}; + +InventoryItem::InventoryItem(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.InventoryItem) +} +InventoryItem::InventoryItem(const InventoryItem& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + InventoryItem* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.item_){} + , decltype(_impl_.extradata_){} + , decltype(_impl_.quantity_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_item().empty()) { + _this->_impl_.item_.Set(from._internal_item(), + _this->GetArenaForAllocation()); + } + _impl_.extradata_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.extradata_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_extradata().empty()) { + _this->_impl_.extradata_.Set(from._internal_extradata(), + _this->GetArenaForAllocation()); + } + _this->_impl_.quantity_ = from._impl_.quantity_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.InventoryItem) +} + +inline void InventoryItem::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.item_){} + , decltype(_impl_.extradata_){} + , decltype(_impl_.quantity_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.extradata_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.extradata_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +InventoryItem::~InventoryItem() { + // @@protoc_insertion_point(destructor:rtech.liveapi.InventoryItem) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void InventoryItem::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.item_.Destroy(); + _impl_.extradata_.Destroy(); +} + +void InventoryItem::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void InventoryItem::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.InventoryItem) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.item_.ClearToEmpty(); + _impl_.extradata_.ClearToEmpty(); + _impl_.quantity_ = 0; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* InventoryItem::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // int32 quantity = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.quantity_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string item = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_item(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.InventoryItem.item")); + } else + goto handle_unusual; + continue; + // string extraData = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_extradata(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.InventoryItem.extraData")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* InventoryItem::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.InventoryItem) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // int32 quantity = 1; + if (this->_internal_quantity() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(1, this->_internal_quantity(), target); + } + + // string item = 2; + if (!this->_internal_item().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_item().data(), static_cast(this->_internal_item().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.InventoryItem.item"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_item(), target); + } + + // string extraData = 3; + if (!this->_internal_extradata().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_extradata().data(), static_cast(this->_internal_extradata().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.InventoryItem.extraData"); + target = stream->WriteStringMaybeAliased( + 3, this->_internal_extradata(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.InventoryItem) + return target; +} + +size_t InventoryItem::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.InventoryItem) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string item = 2; + if (!this->_internal_item().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_item()); + } + + // string extraData = 3; + if (!this->_internal_extradata().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_extradata()); + } + + // int32 quantity = 1; + if (this->_internal_quantity() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_quantity()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData InventoryItem::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + InventoryItem::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*InventoryItem::GetClassData() const { return &_class_data_; } + + +void InventoryItem::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.InventoryItem) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_item().empty()) { + _this->_internal_set_item(from._internal_item()); + } + if (!from._internal_extradata().empty()) { + _this->_internal_set_extradata(from._internal_extradata()); + } + if (from._internal_quantity() != 0) { + _this->_internal_set_quantity(from._internal_quantity()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void InventoryItem::CopyFrom(const InventoryItem& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.InventoryItem) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool InventoryItem::IsInitialized() const { + return true; +} + +void InventoryItem::InternalSwap(InventoryItem* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.item_, lhs_arena, + &other->_impl_.item_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.extradata_, lhs_arena, + &other->_impl_.extradata_, rhs_arena + ); + swap(_impl_.quantity_, other->_impl_.quantity_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata InventoryItem::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[5]); +} + +// =================================================================== + +class LoadoutConfiguration::_Internal { + public: +}; + +LoadoutConfiguration::LoadoutConfiguration(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.LoadoutConfiguration) +} +LoadoutConfiguration::LoadoutConfiguration(const LoadoutConfiguration& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + LoadoutConfiguration* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.weapons_){from._impl_.weapons_} + , decltype(_impl_.equipment_){from._impl_.equipment_} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.LoadoutConfiguration) +} + +inline void LoadoutConfiguration::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.weapons_){arena} + , decltype(_impl_.equipment_){arena} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +LoadoutConfiguration::~LoadoutConfiguration() { + // @@protoc_insertion_point(destructor:rtech.liveapi.LoadoutConfiguration) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void LoadoutConfiguration::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.weapons_.~RepeatedPtrField(); + _impl_.equipment_.~RepeatedPtrField(); +} + +void LoadoutConfiguration::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void LoadoutConfiguration::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.LoadoutConfiguration) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.weapons_.Clear(); + _impl_.equipment_.Clear(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* LoadoutConfiguration::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // repeated .rtech.liveapi.InventoryItem weapons = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_weapons(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<10>(ptr)); + } else + goto handle_unusual; + continue; + // repeated .rtech.liveapi.InventoryItem equipment = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_equipment(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<18>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* LoadoutConfiguration::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.LoadoutConfiguration) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // repeated .rtech.liveapi.InventoryItem weapons = 1; + for (unsigned i = 0, + n = static_cast(this->_internal_weapons_size()); i < n; i++) { + const auto& repfield = this->_internal_weapons(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, repfield, repfield.GetCachedSize(), target, stream); + } + + // repeated .rtech.liveapi.InventoryItem equipment = 2; + for (unsigned i = 0, + n = static_cast(this->_internal_equipment_size()); i < n; i++) { + const auto& repfield = this->_internal_equipment(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(2, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.LoadoutConfiguration) + return target; +} + +size_t LoadoutConfiguration::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.LoadoutConfiguration) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .rtech.liveapi.InventoryItem weapons = 1; + total_size += 1UL * this->_internal_weapons_size(); + for (const auto& msg : this->_impl_.weapons_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // repeated .rtech.liveapi.InventoryItem equipment = 2; + total_size += 1UL * this->_internal_equipment_size(); + for (const auto& msg : this->_impl_.equipment_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData LoadoutConfiguration::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + LoadoutConfiguration::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*LoadoutConfiguration::GetClassData() const { return &_class_data_; } + + +void LoadoutConfiguration::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.LoadoutConfiguration) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.weapons_.MergeFrom(from._impl_.weapons_); + _this->_impl_.equipment_.MergeFrom(from._impl_.equipment_); + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void LoadoutConfiguration::CopyFrom(const LoadoutConfiguration& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.LoadoutConfiguration) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LoadoutConfiguration::IsInitialized() const { + return true; +} + +void LoadoutConfiguration::InternalSwap(LoadoutConfiguration* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.weapons_.InternalSwap(&other->_impl_.weapons_); + _impl_.equipment_.InternalSwap(&other->_impl_.equipment_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata LoadoutConfiguration::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[6]); +} + +// =================================================================== + +class Init::_Internal { + public: + static const ::rtech::liveapi::Version& apiversion(const Init* msg); +}; + +const ::rtech::liveapi::Version& +Init::_Internal::apiversion(const Init* msg) { + return *msg->_impl_.apiversion_; +} +Init::Init(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.Init) +} +Init::Init(const Init& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + Init* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.gameversion_){} + , decltype(_impl_.platform_){} + , decltype(_impl_.name_){} + , decltype(_impl_.apiversion_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.gameversion_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.gameversion_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_gameversion().empty()) { + _this->_impl_.gameversion_.Set(from._internal_gameversion(), + _this->GetArenaForAllocation()); + } + _impl_.platform_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.platform_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_platform().empty()) { + _this->_impl_.platform_.Set(from._internal_platform(), + _this->GetArenaForAllocation()); + } + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_name().empty()) { + _this->_impl_.name_.Set(from._internal_name(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_apiversion()) { + _this->_impl_.apiversion_ = new ::rtech::liveapi::Version(*from._impl_.apiversion_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.Init) +} + +inline void Init::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.gameversion_){} + , decltype(_impl_.platform_){} + , decltype(_impl_.name_){} + , decltype(_impl_.apiversion_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.gameversion_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.gameversion_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.platform_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.platform_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +Init::~Init() { + // @@protoc_insertion_point(destructor:rtech.liveapi.Init) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Init::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.gameversion_.Destroy(); + _impl_.platform_.Destroy(); + _impl_.name_.Destroy(); + if (this != internal_default_instance()) delete _impl_.apiversion_; +} + +void Init::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Init::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.Init) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.gameversion_.ClearToEmpty(); + _impl_.platform_.ClearToEmpty(); + _impl_.name_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.apiversion_ != nullptr) { + delete _impl_.apiversion_; + } + _impl_.apiversion_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* Init::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Init.category")); + } else + goto handle_unusual; + continue; + // string gameVersion = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_gameversion(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Init.gameVersion")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Version apiVersion = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_apiversion(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string platform = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_platform(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Init.platform")); + } else + goto handle_unusual; + continue; + // string name = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + auto str = _internal_mutable_name(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Init.name")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Init::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.Init) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Init.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // string gameVersion = 3; + if (!this->_internal_gameversion().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_gameversion().data(), static_cast(this->_internal_gameversion().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Init.gameVersion"); + target = stream->WriteStringMaybeAliased( + 3, this->_internal_gameversion(), target); + } + + // .rtech.liveapi.Version apiVersion = 4; + if (this->_internal_has_apiversion()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::apiversion(this), + _Internal::apiversion(this).GetCachedSize(), target, stream); + } + + // string platform = 5; + if (!this->_internal_platform().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_platform().data(), static_cast(this->_internal_platform().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Init.platform"); + target = stream->WriteStringMaybeAliased( + 5, this->_internal_platform(), target); + } + + // string name = 6; + if (!this->_internal_name().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_name().data(), static_cast(this->_internal_name().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Init.name"); + target = stream->WriteStringMaybeAliased( + 6, this->_internal_name(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.Init) + return target; +} + +size_t Init::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.Init) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string gameVersion = 3; + if (!this->_internal_gameversion().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_gameversion()); + } + + // string platform = 5; + if (!this->_internal_platform().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_platform()); + } + + // string name = 6; + if (!this->_internal_name().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + // .rtech.liveapi.Version apiVersion = 4; + if (this->_internal_has_apiversion()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.apiversion_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData Init::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + Init::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*Init::GetClassData() const { return &_class_data_; } + + +void Init::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.Init) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_gameversion().empty()) { + _this->_internal_set_gameversion(from._internal_gameversion()); + } + if (!from._internal_platform().empty()) { + _this->_internal_set_platform(from._internal_platform()); + } + if (!from._internal_name().empty()) { + _this->_internal_set_name(from._internal_name()); + } + if (from._internal_has_apiversion()) { + _this->_internal_mutable_apiversion()->::rtech::liveapi::Version::MergeFrom( + from._internal_apiversion()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void Init::CopyFrom(const Init& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.Init) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Init::IsInitialized() const { + return true; +} + +void Init::InternalSwap(Init* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.gameversion_, lhs_arena, + &other->_impl_.gameversion_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.platform_, lhs_arena, + &other->_impl_.platform_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.name_, lhs_arena, + &other->_impl_.name_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(Init, _impl_.timestamp_) + + sizeof(Init::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(Init, _impl_.apiversion_)>( + reinterpret_cast(&_impl_.apiversion_), + reinterpret_cast(&other->_impl_.apiversion_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata Init::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[7]); +} + +// =================================================================== + +class CustomMatch_LobbyPlayers::_Internal { + public: +}; + +CustomMatch_LobbyPlayers::CustomMatch_LobbyPlayers(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_LobbyPlayers) +} +CustomMatch_LobbyPlayers::CustomMatch_LobbyPlayers(const CustomMatch_LobbyPlayers& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomMatch_LobbyPlayers* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.players_){from._impl_.players_} + , decltype(_impl_.playertoken_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.playertoken_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.playertoken_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_playertoken().empty()) { + _this->_impl_.playertoken_.Set(from._internal_playertoken(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_LobbyPlayers) +} + +inline void CustomMatch_LobbyPlayers::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.players_){arena} + , decltype(_impl_.playertoken_){} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.playertoken_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.playertoken_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CustomMatch_LobbyPlayers::~CustomMatch_LobbyPlayers() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomMatch_LobbyPlayers) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomMatch_LobbyPlayers::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.players_.~RepeatedPtrField(); + _impl_.playertoken_.Destroy(); +} + +void CustomMatch_LobbyPlayers::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomMatch_LobbyPlayers::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomMatch_LobbyPlayers) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.players_.Clear(); + _impl_.playertoken_.ClearToEmpty(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomMatch_LobbyPlayers::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string playerToken = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_playertoken(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_LobbyPlayers.playerToken")); + } else + goto handle_unusual; + continue; + // repeated .rtech.liveapi.CustomMatch_LobbyPlayer players = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_players(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<18>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomMatch_LobbyPlayers::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomMatch_LobbyPlayers) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string playerToken = 1; + if (!this->_internal_playertoken().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_playertoken().data(), static_cast(this->_internal_playertoken().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_LobbyPlayers.playerToken"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_playertoken(), target); + } + + // repeated .rtech.liveapi.CustomMatch_LobbyPlayer players = 2; + for (unsigned i = 0, + n = static_cast(this->_internal_players_size()); i < n; i++) { + const auto& repfield = this->_internal_players(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(2, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomMatch_LobbyPlayers) + return target; +} + +size_t CustomMatch_LobbyPlayers::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomMatch_LobbyPlayers) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .rtech.liveapi.CustomMatch_LobbyPlayer players = 2; + total_size += 1UL * this->_internal_players_size(); + for (const auto& msg : this->_impl_.players_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // string playerToken = 1; + if (!this->_internal_playertoken().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_playertoken()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_LobbyPlayers::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomMatch_LobbyPlayers::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_LobbyPlayers::GetClassData() const { return &_class_data_; } + + +void CustomMatch_LobbyPlayers::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomMatch_LobbyPlayers) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.players_.MergeFrom(from._impl_.players_); + if (!from._internal_playertoken().empty()) { + _this->_internal_set_playertoken(from._internal_playertoken()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomMatch_LobbyPlayers::CopyFrom(const CustomMatch_LobbyPlayers& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomMatch_LobbyPlayers) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomMatch_LobbyPlayers::IsInitialized() const { + return true; +} + +void CustomMatch_LobbyPlayers::InternalSwap(CustomMatch_LobbyPlayers* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.players_.InternalSwap(&other->_impl_.players_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.playertoken_, lhs_arena, + &other->_impl_.playertoken_, rhs_arena + ); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_LobbyPlayers::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[8]); +} + +// =================================================================== + +class ObserverSwitched::_Internal { + public: + static const ::rtech::liveapi::Player& observer(const ObserverSwitched* msg); + static const ::rtech::liveapi::Player& target(const ObserverSwitched* msg); +}; + +const ::rtech::liveapi::Player& +ObserverSwitched::_Internal::observer(const ObserverSwitched* msg) { + return *msg->_impl_.observer_; +} +const ::rtech::liveapi::Player& +ObserverSwitched::_Internal::target(const ObserverSwitched* msg) { + return *msg->_impl_.target_; +} +ObserverSwitched::ObserverSwitched(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.ObserverSwitched) +} +ObserverSwitched::ObserverSwitched(const ObserverSwitched& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + ObserverSwitched* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.targetteam_){from._impl_.targetteam_} + , decltype(_impl_.category_){} + , decltype(_impl_.observer_){nullptr} + , decltype(_impl_.target_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_observer()) { + _this->_impl_.observer_ = new ::rtech::liveapi::Player(*from._impl_.observer_); + } + if (from._internal_has_target()) { + _this->_impl_.target_ = new ::rtech::liveapi::Player(*from._impl_.target_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.ObserverSwitched) +} + +inline void ObserverSwitched::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.targetteam_){arena} + , decltype(_impl_.category_){} + , decltype(_impl_.observer_){nullptr} + , decltype(_impl_.target_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ObserverSwitched::~ObserverSwitched() { + // @@protoc_insertion_point(destructor:rtech.liveapi.ObserverSwitched) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ObserverSwitched::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.targetteam_.~RepeatedPtrField(); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.observer_; + if (this != internal_default_instance()) delete _impl_.target_; +} + +void ObserverSwitched::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ObserverSwitched::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.ObserverSwitched) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.targetteam_.Clear(); + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.observer_ != nullptr) { + delete _impl_.observer_; + } + _impl_.observer_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.target_ != nullptr) { + delete _impl_.target_; + } + _impl_.target_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* ObserverSwitched::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.ObserverSwitched.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player observer = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_observer(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player target = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_target(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .rtech.liveapi.Player targetTeam = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_targetteam(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<42>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ObserverSwitched::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.ObserverSwitched) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.ObserverSwitched.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player observer = 3; + if (this->_internal_has_observer()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::observer(this), + _Internal::observer(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Player target = 4; + if (this->_internal_has_target()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::target(this), + _Internal::target(this).GetCachedSize(), target, stream); + } + + // repeated .rtech.liveapi.Player targetTeam = 5; + for (unsigned i = 0, + n = static_cast(this->_internal_targetteam_size()); i < n; i++) { + const auto& repfield = this->_internal_targetteam(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(5, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.ObserverSwitched) + return target; +} + +size_t ObserverSwitched::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.ObserverSwitched) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .rtech.liveapi.Player targetTeam = 5; + total_size += 1UL * this->_internal_targetteam_size(); + for (const auto& msg : this->_impl_.targetteam_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player observer = 3; + if (this->_internal_has_observer()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.observer_); + } + + // .rtech.liveapi.Player target = 4; + if (this->_internal_has_target()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.target_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData ObserverSwitched::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + ObserverSwitched::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*ObserverSwitched::GetClassData() const { return &_class_data_; } + + +void ObserverSwitched::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.ObserverSwitched) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.targetteam_.MergeFrom(from._impl_.targetteam_); + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_observer()) { + _this->_internal_mutable_observer()->::rtech::liveapi::Player::MergeFrom( + from._internal_observer()); + } + if (from._internal_has_target()) { + _this->_internal_mutable_target()->::rtech::liveapi::Player::MergeFrom( + from._internal_target()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void ObserverSwitched::CopyFrom(const ObserverSwitched& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.ObserverSwitched) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ObserverSwitched::IsInitialized() const { + return true; +} + +void ObserverSwitched::InternalSwap(ObserverSwitched* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.targetteam_.InternalSwap(&other->_impl_.targetteam_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ObserverSwitched, _impl_.timestamp_) + + sizeof(ObserverSwitched::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(ObserverSwitched, _impl_.observer_)>( + reinterpret_cast(&_impl_.observer_), + reinterpret_cast(&other->_impl_.observer_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata ObserverSwitched::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[9]); +} + +// =================================================================== + +class ObserverAnnotation::_Internal { + public: +}; + +ObserverAnnotation::ObserverAnnotation(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.ObserverAnnotation) +} +ObserverAnnotation::ObserverAnnotation(const ObserverAnnotation& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + ObserverAnnotation* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.annotationserial_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.annotationserial_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.annotationserial_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.ObserverAnnotation) +} + +inline void ObserverAnnotation::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.annotationserial_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ObserverAnnotation::~ObserverAnnotation() { + // @@protoc_insertion_point(destructor:rtech.liveapi.ObserverAnnotation) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ObserverAnnotation::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); +} + +void ObserverAnnotation::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ObserverAnnotation::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.ObserverAnnotation) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.annotationserial_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.annotationserial_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* ObserverAnnotation::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.ObserverAnnotation.category")); + } else + goto handle_unusual; + continue; + // int32 annotationSerial = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 24)) { + _impl_.annotationserial_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ObserverAnnotation::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.ObserverAnnotation) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.ObserverAnnotation.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // int32 annotationSerial = 3; + if (this->_internal_annotationserial() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(3, this->_internal_annotationserial(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.ObserverAnnotation) + return target; +} + +size_t ObserverAnnotation::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.ObserverAnnotation) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // int32 annotationSerial = 3; + if (this->_internal_annotationserial() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_annotationserial()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData ObserverAnnotation::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + ObserverAnnotation::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*ObserverAnnotation::GetClassData() const { return &_class_data_; } + + +void ObserverAnnotation::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.ObserverAnnotation) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_annotationserial() != 0) { + _this->_internal_set_annotationserial(from._internal_annotationserial()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void ObserverAnnotation::CopyFrom(const ObserverAnnotation& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.ObserverAnnotation) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ObserverAnnotation::IsInitialized() const { + return true; +} + +void ObserverAnnotation::InternalSwap(ObserverAnnotation* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ObserverAnnotation, _impl_.annotationserial_) + + sizeof(ObserverAnnotation::_impl_.annotationserial_) + - PROTOBUF_FIELD_OFFSET(ObserverAnnotation, _impl_.timestamp_)>( + reinterpret_cast(&_impl_.timestamp_), + reinterpret_cast(&other->_impl_.timestamp_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata ObserverAnnotation::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[10]); +} + +// =================================================================== + +class MatchSetup::_Internal { + public: + static const ::rtech::liveapi::Datacenter& datacenter(const MatchSetup* msg); + static const ::rtech::liveapi::LoadoutConfiguration& startingloadout(const MatchSetup* msg); +}; + +const ::rtech::liveapi::Datacenter& +MatchSetup::_Internal::datacenter(const MatchSetup* msg) { + return *msg->_impl_.datacenter_; +} +const ::rtech::liveapi::LoadoutConfiguration& +MatchSetup::_Internal::startingloadout(const MatchSetup* msg) { + return *msg->_impl_.startingloadout_; +} +MatchSetup::MatchSetup(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.MatchSetup) +} +MatchSetup::MatchSetup(const MatchSetup& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + MatchSetup* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.map_){} + , decltype(_impl_.playlistname_){} + , decltype(_impl_.playlistdesc_){} + , decltype(_impl_.serverid_){} + , decltype(_impl_.datacenter_){nullptr} + , decltype(_impl_.startingloadout_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.aimassiston_){} + , decltype(_impl_.anonymousmode_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.map_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.map_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_map().empty()) { + _this->_impl_.map_.Set(from._internal_map(), + _this->GetArenaForAllocation()); + } + _impl_.playlistname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.playlistname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_playlistname().empty()) { + _this->_impl_.playlistname_.Set(from._internal_playlistname(), + _this->GetArenaForAllocation()); + } + _impl_.playlistdesc_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.playlistdesc_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_playlistdesc().empty()) { + _this->_impl_.playlistdesc_.Set(from._internal_playlistdesc(), + _this->GetArenaForAllocation()); + } + _impl_.serverid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.serverid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_serverid().empty()) { + _this->_impl_.serverid_.Set(from._internal_serverid(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_datacenter()) { + _this->_impl_.datacenter_ = new ::rtech::liveapi::Datacenter(*from._impl_.datacenter_); + } + if (from._internal_has_startingloadout()) { + _this->_impl_.startingloadout_ = new ::rtech::liveapi::LoadoutConfiguration(*from._impl_.startingloadout_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.anonymousmode_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.anonymousmode_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.MatchSetup) +} + +inline void MatchSetup::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.map_){} + , decltype(_impl_.playlistname_){} + , decltype(_impl_.playlistdesc_){} + , decltype(_impl_.serverid_){} + , decltype(_impl_.datacenter_){nullptr} + , decltype(_impl_.startingloadout_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.aimassiston_){false} + , decltype(_impl_.anonymousmode_){false} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.map_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.map_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.playlistname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.playlistname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.playlistdesc_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.playlistdesc_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.serverid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.serverid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +MatchSetup::~MatchSetup() { + // @@protoc_insertion_point(destructor:rtech.liveapi.MatchSetup) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void MatchSetup::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.map_.Destroy(); + _impl_.playlistname_.Destroy(); + _impl_.playlistdesc_.Destroy(); + _impl_.serverid_.Destroy(); + if (this != internal_default_instance()) delete _impl_.datacenter_; + if (this != internal_default_instance()) delete _impl_.startingloadout_; +} + +void MatchSetup::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void MatchSetup::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.MatchSetup) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.map_.ClearToEmpty(); + _impl_.playlistname_.ClearToEmpty(); + _impl_.playlistdesc_.ClearToEmpty(); + _impl_.serverid_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.datacenter_ != nullptr) { + delete _impl_.datacenter_; + } + _impl_.datacenter_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.startingloadout_ != nullptr) { + delete _impl_.startingloadout_; + } + _impl_.startingloadout_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.anonymousmode_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.anonymousmode_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* MatchSetup::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.MatchSetup.category")); + } else + goto handle_unusual; + continue; + // string map = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_map(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.MatchSetup.map")); + } else + goto handle_unusual; + continue; + // string playlistName = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_playlistname(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.MatchSetup.playlistName")); + } else + goto handle_unusual; + continue; + // string playlistDesc = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_playlistdesc(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.MatchSetup.playlistDesc")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Datacenter datacenter = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + ptr = ctx->ParseMessage(_internal_mutable_datacenter(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // bool aimAssistOn = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 56)) { + _impl_.aimassiston_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // bool anonymousMode = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 64)) { + _impl_.anonymousmode_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string serverId = 9; + case 9: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 74)) { + auto str = _internal_mutable_serverid(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.MatchSetup.serverId")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.LoadoutConfiguration startingLoadout = 10; + case 10: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 82)) { + ptr = ctx->ParseMessage(_internal_mutable_startingloadout(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* MatchSetup::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.MatchSetup) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.MatchSetup.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // string map = 3; + if (!this->_internal_map().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_map().data(), static_cast(this->_internal_map().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.MatchSetup.map"); + target = stream->WriteStringMaybeAliased( + 3, this->_internal_map(), target); + } + + // string playlistName = 4; + if (!this->_internal_playlistname().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_playlistname().data(), static_cast(this->_internal_playlistname().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.MatchSetup.playlistName"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_playlistname(), target); + } + + // string playlistDesc = 5; + if (!this->_internal_playlistdesc().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_playlistdesc().data(), static_cast(this->_internal_playlistdesc().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.MatchSetup.playlistDesc"); + target = stream->WriteStringMaybeAliased( + 5, this->_internal_playlistdesc(), target); + } + + // .rtech.liveapi.Datacenter datacenter = 6; + if (this->_internal_has_datacenter()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(6, _Internal::datacenter(this), + _Internal::datacenter(this).GetCachedSize(), target, stream); + } + + // bool aimAssistOn = 7; + if (this->_internal_aimassiston() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(7, this->_internal_aimassiston(), target); + } + + // bool anonymousMode = 8; + if (this->_internal_anonymousmode() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(8, this->_internal_anonymousmode(), target); + } + + // string serverId = 9; + if (!this->_internal_serverid().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_serverid().data(), static_cast(this->_internal_serverid().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.MatchSetup.serverId"); + target = stream->WriteStringMaybeAliased( + 9, this->_internal_serverid(), target); + } + + // .rtech.liveapi.LoadoutConfiguration startingLoadout = 10; + if (this->_internal_has_startingloadout()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(10, _Internal::startingloadout(this), + _Internal::startingloadout(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.MatchSetup) + return target; +} + +size_t MatchSetup::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.MatchSetup) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string map = 3; + if (!this->_internal_map().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_map()); + } + + // string playlistName = 4; + if (!this->_internal_playlistname().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_playlistname()); + } + + // string playlistDesc = 5; + if (!this->_internal_playlistdesc().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_playlistdesc()); + } + + // string serverId = 9; + if (!this->_internal_serverid().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_serverid()); + } + + // .rtech.liveapi.Datacenter datacenter = 6; + if (this->_internal_has_datacenter()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.datacenter_); + } + + // .rtech.liveapi.LoadoutConfiguration startingLoadout = 10; + if (this->_internal_has_startingloadout()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.startingloadout_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // bool aimAssistOn = 7; + if (this->_internal_aimassiston() != 0) { + total_size += 1 + 1; + } + + // bool anonymousMode = 8; + if (this->_internal_anonymousmode() != 0) { + total_size += 1 + 1; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData MatchSetup::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + MatchSetup::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*MatchSetup::GetClassData() const { return &_class_data_; } + + +void MatchSetup::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.MatchSetup) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_map().empty()) { + _this->_internal_set_map(from._internal_map()); + } + if (!from._internal_playlistname().empty()) { + _this->_internal_set_playlistname(from._internal_playlistname()); + } + if (!from._internal_playlistdesc().empty()) { + _this->_internal_set_playlistdesc(from._internal_playlistdesc()); + } + if (!from._internal_serverid().empty()) { + _this->_internal_set_serverid(from._internal_serverid()); + } + if (from._internal_has_datacenter()) { + _this->_internal_mutable_datacenter()->::rtech::liveapi::Datacenter::MergeFrom( + from._internal_datacenter()); + } + if (from._internal_has_startingloadout()) { + _this->_internal_mutable_startingloadout()->::rtech::liveapi::LoadoutConfiguration::MergeFrom( + from._internal_startingloadout()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_aimassiston() != 0) { + _this->_internal_set_aimassiston(from._internal_aimassiston()); + } + if (from._internal_anonymousmode() != 0) { + _this->_internal_set_anonymousmode(from._internal_anonymousmode()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void MatchSetup::CopyFrom(const MatchSetup& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.MatchSetup) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool MatchSetup::IsInitialized() const { + return true; +} + +void MatchSetup::InternalSwap(MatchSetup* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.map_, lhs_arena, + &other->_impl_.map_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.playlistname_, lhs_arena, + &other->_impl_.playlistname_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.playlistdesc_, lhs_arena, + &other->_impl_.playlistdesc_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.serverid_, lhs_arena, + &other->_impl_.serverid_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(MatchSetup, _impl_.anonymousmode_) + + sizeof(MatchSetup::_impl_.anonymousmode_) + - PROTOBUF_FIELD_OFFSET(MatchSetup, _impl_.datacenter_)>( + reinterpret_cast(&_impl_.datacenter_), + reinterpret_cast(&other->_impl_.datacenter_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata MatchSetup::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[11]); +} + +// =================================================================== + +class GameStateChanged::_Internal { + public: +}; + +GameStateChanged::GameStateChanged(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.GameStateChanged) +} +GameStateChanged::GameStateChanged(const GameStateChanged& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + GameStateChanged* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.state_){} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.state_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.state_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_state().empty()) { + _this->_impl_.state_.Set(from._internal_state(), + _this->GetArenaForAllocation()); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.GameStateChanged) +} + +inline void GameStateChanged::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.state_){} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.state_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.state_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +GameStateChanged::~GameStateChanged() { + // @@protoc_insertion_point(destructor:rtech.liveapi.GameStateChanged) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void GameStateChanged::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.state_.Destroy(); +} + +void GameStateChanged::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void GameStateChanged::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.GameStateChanged) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.state_.ClearToEmpty(); + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* GameStateChanged::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.GameStateChanged.category")); + } else + goto handle_unusual; + continue; + // string state = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_state(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.GameStateChanged.state")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* GameStateChanged::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.GameStateChanged) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.GameStateChanged.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // string state = 3; + if (!this->_internal_state().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_state().data(), static_cast(this->_internal_state().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.GameStateChanged.state"); + target = stream->WriteStringMaybeAliased( + 3, this->_internal_state(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.GameStateChanged) + return target; +} + +size_t GameStateChanged::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.GameStateChanged) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string state = 3; + if (!this->_internal_state().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_state()); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData GameStateChanged::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + GameStateChanged::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GameStateChanged::GetClassData() const { return &_class_data_; } + + +void GameStateChanged::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.GameStateChanged) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_state().empty()) { + _this->_internal_set_state(from._internal_state()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void GameStateChanged::CopyFrom(const GameStateChanged& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.GameStateChanged) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool GameStateChanged::IsInitialized() const { + return true; +} + +void GameStateChanged::InternalSwap(GameStateChanged* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.state_, lhs_arena, + &other->_impl_.state_, rhs_arena + ); + swap(_impl_.timestamp_, other->_impl_.timestamp_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata GameStateChanged::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[12]); +} + +// =================================================================== + +class CharacterSelected::_Internal { + public: + static const ::rtech::liveapi::Player& player(const CharacterSelected* msg); +}; + +const ::rtech::liveapi::Player& +CharacterSelected::_Internal::player(const CharacterSelected* msg) { + return *msg->_impl_.player_; +} +CharacterSelected::CharacterSelected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CharacterSelected) +} +CharacterSelected::CharacterSelected(const CharacterSelected& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CharacterSelected* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CharacterSelected) +} + +inline void CharacterSelected::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CharacterSelected::~CharacterSelected() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CharacterSelected) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CharacterSelected::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void CharacterSelected::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CharacterSelected::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CharacterSelected) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CharacterSelected::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CharacterSelected.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CharacterSelected::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CharacterSelected) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CharacterSelected.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CharacterSelected) + return target; +} + +size_t CharacterSelected::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CharacterSelected) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CharacterSelected::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CharacterSelected::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CharacterSelected::GetClassData() const { return &_class_data_; } + + +void CharacterSelected::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CharacterSelected) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CharacterSelected::CopyFrom(const CharacterSelected& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CharacterSelected) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CharacterSelected::IsInitialized() const { + return true; +} + +void CharacterSelected::InternalSwap(CharacterSelected* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(CharacterSelected, _impl_.timestamp_) + + sizeof(CharacterSelected::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(CharacterSelected, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CharacterSelected::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[13]); +} + +// =================================================================== + +class MatchStateEnd::_Internal { + public: +}; + +MatchStateEnd::MatchStateEnd(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.MatchStateEnd) +} +MatchStateEnd::MatchStateEnd(const MatchStateEnd& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + MatchStateEnd* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.winners_){from._impl_.winners_} + , decltype(_impl_.category_){} + , decltype(_impl_.state_){} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.state_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.state_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_state().empty()) { + _this->_impl_.state_.Set(from._internal_state(), + _this->GetArenaForAllocation()); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.MatchStateEnd) +} + +inline void MatchStateEnd::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.winners_){arena} + , decltype(_impl_.category_){} + , decltype(_impl_.state_){} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.state_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.state_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +MatchStateEnd::~MatchStateEnd() { + // @@protoc_insertion_point(destructor:rtech.liveapi.MatchStateEnd) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void MatchStateEnd::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.winners_.~RepeatedPtrField(); + _impl_.category_.Destroy(); + _impl_.state_.Destroy(); +} + +void MatchStateEnd::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void MatchStateEnd::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.MatchStateEnd) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.winners_.Clear(); + _impl_.category_.ClearToEmpty(); + _impl_.state_.ClearToEmpty(); + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* MatchStateEnd::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.MatchStateEnd.category")); + } else + goto handle_unusual; + continue; + // string state = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_state(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.MatchStateEnd.state")); + } else + goto handle_unusual; + continue; + // repeated .rtech.liveapi.Player winners = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_winners(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<34>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* MatchStateEnd::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.MatchStateEnd) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.MatchStateEnd.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // string state = 3; + if (!this->_internal_state().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_state().data(), static_cast(this->_internal_state().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.MatchStateEnd.state"); + target = stream->WriteStringMaybeAliased( + 3, this->_internal_state(), target); + } + + // repeated .rtech.liveapi.Player winners = 4; + for (unsigned i = 0, + n = static_cast(this->_internal_winners_size()); i < n; i++) { + const auto& repfield = this->_internal_winners(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.MatchStateEnd) + return target; +} + +size_t MatchStateEnd::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.MatchStateEnd) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .rtech.liveapi.Player winners = 4; + total_size += 1UL * this->_internal_winners_size(); + for (const auto& msg : this->_impl_.winners_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string state = 3; + if (!this->_internal_state().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_state()); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData MatchStateEnd::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + MatchStateEnd::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*MatchStateEnd::GetClassData() const { return &_class_data_; } + + +void MatchStateEnd::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.MatchStateEnd) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.winners_.MergeFrom(from._impl_.winners_); + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_state().empty()) { + _this->_internal_set_state(from._internal_state()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void MatchStateEnd::CopyFrom(const MatchStateEnd& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.MatchStateEnd) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool MatchStateEnd::IsInitialized() const { + return true; +} + +void MatchStateEnd::InternalSwap(MatchStateEnd* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.winners_.InternalSwap(&other->_impl_.winners_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.state_, lhs_arena, + &other->_impl_.state_, rhs_arena + ); + swap(_impl_.timestamp_, other->_impl_.timestamp_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata MatchStateEnd::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[14]); +} + +// =================================================================== + +class RingStartClosing::_Internal { + public: + static const ::rtech::liveapi::Vector3& center(const RingStartClosing* msg); +}; + +const ::rtech::liveapi::Vector3& +RingStartClosing::_Internal::center(const RingStartClosing* msg) { + return *msg->_impl_.center_; +} +RingStartClosing::RingStartClosing(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.RingStartClosing) +} +RingStartClosing::RingStartClosing(const RingStartClosing& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + RingStartClosing* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.center_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.stage_){} + , decltype(_impl_.currentradius_){} + , decltype(_impl_.endradius_){} + , decltype(_impl_.shrinkduration_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_center()) { + _this->_impl_.center_ = new ::rtech::liveapi::Vector3(*from._impl_.center_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.shrinkduration_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.shrinkduration_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.RingStartClosing) +} + +inline void RingStartClosing::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.center_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.stage_){0u} + , decltype(_impl_.currentradius_){0} + , decltype(_impl_.endradius_){0} + , decltype(_impl_.shrinkduration_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +RingStartClosing::~RingStartClosing() { + // @@protoc_insertion_point(destructor:rtech.liveapi.RingStartClosing) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void RingStartClosing::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.center_; +} + +void RingStartClosing::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void RingStartClosing::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.RingStartClosing) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.center_ != nullptr) { + delete _impl_.center_; + } + _impl_.center_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.shrinkduration_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.shrinkduration_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* RingStartClosing::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.RingStartClosing.category")); + } else + goto handle_unusual; + continue; + // uint32 stage = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 24)) { + _impl_.stage_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Vector3 center = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_center(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // float currentRadius = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 45)) { + _impl_.currentradius_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else + goto handle_unusual; + continue; + // float endRadius = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 53)) { + _impl_.endradius_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else + goto handle_unusual; + continue; + // float shrinkDuration = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 61)) { + _impl_.shrinkduration_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* RingStartClosing::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.RingStartClosing) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.RingStartClosing.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // uint32 stage = 3; + if (this->_internal_stage() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(3, this->_internal_stage(), target); + } + + // .rtech.liveapi.Vector3 center = 4; + if (this->_internal_has_center()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::center(this), + _Internal::center(this).GetCachedSize(), target, stream); + } + + // float currentRadius = 5; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_currentradius = this->_internal_currentradius(); + uint32_t raw_currentradius; + memcpy(&raw_currentradius, &tmp_currentradius, sizeof(tmp_currentradius)); + if (raw_currentradius != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray(5, this->_internal_currentradius(), target); + } + + // float endRadius = 6; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_endradius = this->_internal_endradius(); + uint32_t raw_endradius; + memcpy(&raw_endradius, &tmp_endradius, sizeof(tmp_endradius)); + if (raw_endradius != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray(6, this->_internal_endradius(), target); + } + + // float shrinkDuration = 7; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_shrinkduration = this->_internal_shrinkduration(); + uint32_t raw_shrinkduration; + memcpy(&raw_shrinkduration, &tmp_shrinkduration, sizeof(tmp_shrinkduration)); + if (raw_shrinkduration != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray(7, this->_internal_shrinkduration(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.RingStartClosing) + return target; +} + +size_t RingStartClosing::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.RingStartClosing) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Vector3 center = 4; + if (this->_internal_has_center()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.center_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // uint32 stage = 3; + if (this->_internal_stage() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_stage()); + } + + // float currentRadius = 5; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_currentradius = this->_internal_currentradius(); + uint32_t raw_currentradius; + memcpy(&raw_currentradius, &tmp_currentradius, sizeof(tmp_currentradius)); + if (raw_currentradius != 0) { + total_size += 1 + 4; + } + + // float endRadius = 6; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_endradius = this->_internal_endradius(); + uint32_t raw_endradius; + memcpy(&raw_endradius, &tmp_endradius, sizeof(tmp_endradius)); + if (raw_endradius != 0) { + total_size += 1 + 4; + } + + // float shrinkDuration = 7; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_shrinkduration = this->_internal_shrinkduration(); + uint32_t raw_shrinkduration; + memcpy(&raw_shrinkduration, &tmp_shrinkduration, sizeof(tmp_shrinkduration)); + if (raw_shrinkduration != 0) { + total_size += 1 + 4; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData RingStartClosing::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + RingStartClosing::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*RingStartClosing::GetClassData() const { return &_class_data_; } + + +void RingStartClosing::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.RingStartClosing) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_center()) { + _this->_internal_mutable_center()->::rtech::liveapi::Vector3::MergeFrom( + from._internal_center()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_stage() != 0) { + _this->_internal_set_stage(from._internal_stage()); + } + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_currentradius = from._internal_currentradius(); + uint32_t raw_currentradius; + memcpy(&raw_currentradius, &tmp_currentradius, sizeof(tmp_currentradius)); + if (raw_currentradius != 0) { + _this->_internal_set_currentradius(from._internal_currentradius()); + } + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_endradius = from._internal_endradius(); + uint32_t raw_endradius; + memcpy(&raw_endradius, &tmp_endradius, sizeof(tmp_endradius)); + if (raw_endradius != 0) { + _this->_internal_set_endradius(from._internal_endradius()); + } + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_shrinkduration = from._internal_shrinkduration(); + uint32_t raw_shrinkduration; + memcpy(&raw_shrinkduration, &tmp_shrinkduration, sizeof(tmp_shrinkduration)); + if (raw_shrinkduration != 0) { + _this->_internal_set_shrinkduration(from._internal_shrinkduration()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void RingStartClosing::CopyFrom(const RingStartClosing& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.RingStartClosing) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool RingStartClosing::IsInitialized() const { + return true; +} + +void RingStartClosing::InternalSwap(RingStartClosing* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(RingStartClosing, _impl_.shrinkduration_) + + sizeof(RingStartClosing::_impl_.shrinkduration_) + - PROTOBUF_FIELD_OFFSET(RingStartClosing, _impl_.center_)>( + reinterpret_cast(&_impl_.center_), + reinterpret_cast(&other->_impl_.center_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata RingStartClosing::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[15]); +} + +// =================================================================== + +class RingFinishedClosing::_Internal { + public: + static const ::rtech::liveapi::Vector3& center(const RingFinishedClosing* msg); +}; + +const ::rtech::liveapi::Vector3& +RingFinishedClosing::_Internal::center(const RingFinishedClosing* msg) { + return *msg->_impl_.center_; +} +RingFinishedClosing::RingFinishedClosing(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.RingFinishedClosing) +} +RingFinishedClosing::RingFinishedClosing(const RingFinishedClosing& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + RingFinishedClosing* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.center_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.stage_){} + , decltype(_impl_.currentradius_){} + , decltype(_impl_.shrinkduration_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_center()) { + _this->_impl_.center_ = new ::rtech::liveapi::Vector3(*from._impl_.center_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.shrinkduration_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.shrinkduration_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.RingFinishedClosing) +} + +inline void RingFinishedClosing::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.center_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.stage_){0u} + , decltype(_impl_.currentradius_){0} + , decltype(_impl_.shrinkduration_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +RingFinishedClosing::~RingFinishedClosing() { + // @@protoc_insertion_point(destructor:rtech.liveapi.RingFinishedClosing) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void RingFinishedClosing::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.center_; +} + +void RingFinishedClosing::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void RingFinishedClosing::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.RingFinishedClosing) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.center_ != nullptr) { + delete _impl_.center_; + } + _impl_.center_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.shrinkduration_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.shrinkduration_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* RingFinishedClosing::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.RingFinishedClosing.category")); + } else + goto handle_unusual; + continue; + // uint32 stage = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 24)) { + _impl_.stage_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Vector3 center = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_center(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // float currentRadius = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 45)) { + _impl_.currentradius_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else + goto handle_unusual; + continue; + // float shrinkDuration = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 61)) { + _impl_.shrinkduration_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* RingFinishedClosing::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.RingFinishedClosing) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.RingFinishedClosing.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // uint32 stage = 3; + if (this->_internal_stage() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(3, this->_internal_stage(), target); + } + + // .rtech.liveapi.Vector3 center = 4; + if (this->_internal_has_center()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::center(this), + _Internal::center(this).GetCachedSize(), target, stream); + } + + // float currentRadius = 5; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_currentradius = this->_internal_currentradius(); + uint32_t raw_currentradius; + memcpy(&raw_currentradius, &tmp_currentradius, sizeof(tmp_currentradius)); + if (raw_currentradius != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray(5, this->_internal_currentradius(), target); + } + + // float shrinkDuration = 7; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_shrinkduration = this->_internal_shrinkduration(); + uint32_t raw_shrinkduration; + memcpy(&raw_shrinkduration, &tmp_shrinkduration, sizeof(tmp_shrinkduration)); + if (raw_shrinkduration != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray(7, this->_internal_shrinkduration(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.RingFinishedClosing) + return target; +} + +size_t RingFinishedClosing::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.RingFinishedClosing) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Vector3 center = 4; + if (this->_internal_has_center()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.center_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // uint32 stage = 3; + if (this->_internal_stage() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_stage()); + } + + // float currentRadius = 5; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_currentradius = this->_internal_currentradius(); + uint32_t raw_currentradius; + memcpy(&raw_currentradius, &tmp_currentradius, sizeof(tmp_currentradius)); + if (raw_currentradius != 0) { + total_size += 1 + 4; + } + + // float shrinkDuration = 7; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_shrinkduration = this->_internal_shrinkduration(); + uint32_t raw_shrinkduration; + memcpy(&raw_shrinkduration, &tmp_shrinkduration, sizeof(tmp_shrinkduration)); + if (raw_shrinkduration != 0) { + total_size += 1 + 4; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData RingFinishedClosing::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + RingFinishedClosing::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*RingFinishedClosing::GetClassData() const { return &_class_data_; } + + +void RingFinishedClosing::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.RingFinishedClosing) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_center()) { + _this->_internal_mutable_center()->::rtech::liveapi::Vector3::MergeFrom( + from._internal_center()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_stage() != 0) { + _this->_internal_set_stage(from._internal_stage()); + } + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_currentradius = from._internal_currentradius(); + uint32_t raw_currentradius; + memcpy(&raw_currentradius, &tmp_currentradius, sizeof(tmp_currentradius)); + if (raw_currentradius != 0) { + _this->_internal_set_currentradius(from._internal_currentradius()); + } + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_shrinkduration = from._internal_shrinkduration(); + uint32_t raw_shrinkduration; + memcpy(&raw_shrinkduration, &tmp_shrinkduration, sizeof(tmp_shrinkduration)); + if (raw_shrinkduration != 0) { + _this->_internal_set_shrinkduration(from._internal_shrinkduration()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void RingFinishedClosing::CopyFrom(const RingFinishedClosing& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.RingFinishedClosing) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool RingFinishedClosing::IsInitialized() const { + return true; +} + +void RingFinishedClosing::InternalSwap(RingFinishedClosing* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(RingFinishedClosing, _impl_.shrinkduration_) + + sizeof(RingFinishedClosing::_impl_.shrinkduration_) + - PROTOBUF_FIELD_OFFSET(RingFinishedClosing, _impl_.center_)>( + reinterpret_cast(&_impl_.center_), + reinterpret_cast(&other->_impl_.center_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata RingFinishedClosing::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[16]); +} + +// =================================================================== + +class PlayerConnected::_Internal { + public: + static const ::rtech::liveapi::Player& player(const PlayerConnected* msg); +}; + +const ::rtech::liveapi::Player& +PlayerConnected::_Internal::player(const PlayerConnected* msg) { + return *msg->_impl_.player_; +} +PlayerConnected::PlayerConnected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerConnected) +} +PlayerConnected::PlayerConnected(const PlayerConnected& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerConnected* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerConnected) +} + +inline void PlayerConnected::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +PlayerConnected::~PlayerConnected() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerConnected) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerConnected::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void PlayerConnected::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerConnected::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerConnected) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerConnected::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerConnected.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerConnected::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerConnected) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerConnected.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerConnected) + return target; +} + +size_t PlayerConnected::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerConnected) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerConnected::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerConnected::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerConnected::GetClassData() const { return &_class_data_; } + + +void PlayerConnected::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerConnected) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerConnected::CopyFrom(const PlayerConnected& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerConnected) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerConnected::IsInitialized() const { + return true; +} + +void PlayerConnected::InternalSwap(PlayerConnected* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerConnected, _impl_.timestamp_) + + sizeof(PlayerConnected::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(PlayerConnected, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerConnected::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[17]); +} + +// =================================================================== + +class PlayerDisconnected::_Internal { + public: + static const ::rtech::liveapi::Player& player(const PlayerDisconnected* msg); +}; + +const ::rtech::liveapi::Player& +PlayerDisconnected::_Internal::player(const PlayerDisconnected* msg) { + return *msg->_impl_.player_; +} +PlayerDisconnected::PlayerDisconnected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerDisconnected) +} +PlayerDisconnected::PlayerDisconnected(const PlayerDisconnected& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerDisconnected* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.canreconnect_){} + , decltype(_impl_.isalive_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.isalive_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.isalive_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerDisconnected) +} + +inline void PlayerDisconnected::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.canreconnect_){false} + , decltype(_impl_.isalive_){false} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +PlayerDisconnected::~PlayerDisconnected() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerDisconnected) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerDisconnected::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void PlayerDisconnected::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerDisconnected::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerDisconnected) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.isalive_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.isalive_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerDisconnected::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerDisconnected.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // bool canReconnect = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 32)) { + _impl_.canreconnect_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // bool isAlive = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _impl_.isalive_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerDisconnected::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerDisconnected) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerDisconnected.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // bool canReconnect = 4; + if (this->_internal_canreconnect() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(4, this->_internal_canreconnect(), target); + } + + // bool isAlive = 5; + if (this->_internal_isalive() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(5, this->_internal_isalive(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerDisconnected) + return target; +} + +size_t PlayerDisconnected::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerDisconnected) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // bool canReconnect = 4; + if (this->_internal_canreconnect() != 0) { + total_size += 1 + 1; + } + + // bool isAlive = 5; + if (this->_internal_isalive() != 0) { + total_size += 1 + 1; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerDisconnected::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerDisconnected::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerDisconnected::GetClassData() const { return &_class_data_; } + + +void PlayerDisconnected::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerDisconnected) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_canreconnect() != 0) { + _this->_internal_set_canreconnect(from._internal_canreconnect()); + } + if (from._internal_isalive() != 0) { + _this->_internal_set_isalive(from._internal_isalive()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerDisconnected::CopyFrom(const PlayerDisconnected& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerDisconnected) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerDisconnected::IsInitialized() const { + return true; +} + +void PlayerDisconnected::InternalSwap(PlayerDisconnected* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerDisconnected, _impl_.isalive_) + + sizeof(PlayerDisconnected::_impl_.isalive_) + - PROTOBUF_FIELD_OFFSET(PlayerDisconnected, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerDisconnected::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[18]); +} + +// =================================================================== + +class PlayerStatChanged::_Internal { + public: + static const ::rtech::liveapi::Player& player(const PlayerStatChanged* msg); +}; + +const ::rtech::liveapi::Player& +PlayerStatChanged::_Internal::player(const PlayerStatChanged* msg) { + return *msg->_impl_.player_; +} +PlayerStatChanged::PlayerStatChanged(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerStatChanged) +} +PlayerStatChanged::PlayerStatChanged(const PlayerStatChanged& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerStatChanged* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.statname_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.newValue_){} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_._oneof_case_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.statname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.statname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_statname().empty()) { + _this->_impl_.statname_.Set(from._internal_statname(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + clear_has_newValue(); + switch (from.newValue_case()) { + case kIntValue: { + _this->_internal_set_intvalue(from._internal_intvalue()); + break; + } + case kFloatValue: { + _this->_internal_set_floatvalue(from._internal_floatvalue()); + break; + } + case kBoolValue: { + _this->_internal_set_boolvalue(from._internal_boolvalue()); + break; + } + case NEWVALUE_NOT_SET: { + break; + } + } + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerStatChanged) +} + +inline void PlayerStatChanged::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.statname_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.newValue_){} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_._oneof_case_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.statname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.statname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + clear_has_newValue(); +} + +PlayerStatChanged::~PlayerStatChanged() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerStatChanged) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerStatChanged::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.statname_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; + if (has_newValue()) { + clear_newValue(); + } +} + +void PlayerStatChanged::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerStatChanged::clear_newValue() { +// @@protoc_insertion_point(one_of_clear_start:rtech.liveapi.PlayerStatChanged) + switch (newValue_case()) { + case kIntValue: { + // No need to clear + break; + } + case kFloatValue: { + // No need to clear + break; + } + case kBoolValue: { + // No need to clear + break; + } + case NEWVALUE_NOT_SET: { + break; + } + } + _impl_._oneof_case_[0] = NEWVALUE_NOT_SET; +} + + +void PlayerStatChanged::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerStatChanged) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.statname_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + clear_newValue(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerStatChanged::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerStatChanged.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string statName = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_statname(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerStatChanged.statName")); + } else + goto handle_unusual; + continue; + // uint32 intValue = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _internal_set_intvalue(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr)); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // float floatValue = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 53)) { + _internal_set_floatvalue(::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr)); + ptr += sizeof(float); + } else + goto handle_unusual; + continue; + // bool boolValue = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 56)) { + _internal_set_boolvalue(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr)); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerStatChanged::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerStatChanged) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerStatChanged.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string statName = 4; + if (!this->_internal_statname().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_statname().data(), static_cast(this->_internal_statname().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerStatChanged.statName"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_statname(), target); + } + + // uint32 intValue = 5; + if (_internal_has_intvalue()) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(5, this->_internal_intvalue(), target); + } + + // float floatValue = 6; + if (_internal_has_floatvalue()) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray(6, this->_internal_floatvalue(), target); + } + + // bool boolValue = 7; + if (_internal_has_boolvalue()) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(7, this->_internal_boolvalue(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerStatChanged) + return target; +} + +size_t PlayerStatChanged::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerStatChanged) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string statName = 4; + if (!this->_internal_statname().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_statname()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + switch (newValue_case()) { + // uint32 intValue = 5; + case kIntValue: { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_intvalue()); + break; + } + // float floatValue = 6; + case kFloatValue: { + total_size += 1 + 4; + break; + } + // bool boolValue = 7; + case kBoolValue: { + total_size += 1 + 1; + break; + } + case NEWVALUE_NOT_SET: { + break; + } + } + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerStatChanged::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerStatChanged::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerStatChanged::GetClassData() const { return &_class_data_; } + + +void PlayerStatChanged::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerStatChanged) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_statname().empty()) { + _this->_internal_set_statname(from._internal_statname()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + switch (from.newValue_case()) { + case kIntValue: { + _this->_internal_set_intvalue(from._internal_intvalue()); + break; + } + case kFloatValue: { + _this->_internal_set_floatvalue(from._internal_floatvalue()); + break; + } + case kBoolValue: { + _this->_internal_set_boolvalue(from._internal_boolvalue()); + break; + } + case NEWVALUE_NOT_SET: { + break; + } + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerStatChanged::CopyFrom(const PlayerStatChanged& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerStatChanged) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerStatChanged::IsInitialized() const { + return true; +} + +void PlayerStatChanged::InternalSwap(PlayerStatChanged* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.statname_, lhs_arena, + &other->_impl_.statname_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerStatChanged, _impl_.timestamp_) + + sizeof(PlayerStatChanged::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(PlayerStatChanged, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); + swap(_impl_.newValue_, other->_impl_.newValue_); + swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerStatChanged::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[19]); +} + +// =================================================================== + +class PlayerUpgradeTierChanged::_Internal { + public: + static const ::rtech::liveapi::Player& player(const PlayerUpgradeTierChanged* msg); +}; + +const ::rtech::liveapi::Player& +PlayerUpgradeTierChanged::_Internal::player(const PlayerUpgradeTierChanged* msg) { + return *msg->_impl_.player_; +} +PlayerUpgradeTierChanged::PlayerUpgradeTierChanged(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerUpgradeTierChanged) +} +PlayerUpgradeTierChanged::PlayerUpgradeTierChanged(const PlayerUpgradeTierChanged& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerUpgradeTierChanged* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.level_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.level_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.level_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerUpgradeTierChanged) +} + +inline void PlayerUpgradeTierChanged::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.level_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +PlayerUpgradeTierChanged::~PlayerUpgradeTierChanged() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerUpgradeTierChanged) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerUpgradeTierChanged::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void PlayerUpgradeTierChanged::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerUpgradeTierChanged::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerUpgradeTierChanged) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.level_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.level_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerUpgradeTierChanged::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerUpgradeTierChanged.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // int32 level = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 32)) { + _impl_.level_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerUpgradeTierChanged::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerUpgradeTierChanged) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerUpgradeTierChanged.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // int32 level = 4; + if (this->_internal_level() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(4, this->_internal_level(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerUpgradeTierChanged) + return target; +} + +size_t PlayerUpgradeTierChanged::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerUpgradeTierChanged) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // int32 level = 4; + if (this->_internal_level() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_level()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerUpgradeTierChanged::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerUpgradeTierChanged::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerUpgradeTierChanged::GetClassData() const { return &_class_data_; } + + +void PlayerUpgradeTierChanged::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerUpgradeTierChanged) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_level() != 0) { + _this->_internal_set_level(from._internal_level()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerUpgradeTierChanged::CopyFrom(const PlayerUpgradeTierChanged& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerUpgradeTierChanged) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerUpgradeTierChanged::IsInitialized() const { + return true; +} + +void PlayerUpgradeTierChanged::InternalSwap(PlayerUpgradeTierChanged* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerUpgradeTierChanged, _impl_.level_) + + sizeof(PlayerUpgradeTierChanged::_impl_.level_) + - PROTOBUF_FIELD_OFFSET(PlayerUpgradeTierChanged, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerUpgradeTierChanged::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[20]); +} + +// =================================================================== + +class PlayerDamaged::_Internal { + public: + static const ::rtech::liveapi::Player& attacker(const PlayerDamaged* msg); + static const ::rtech::liveapi::Player& victim(const PlayerDamaged* msg); +}; + +const ::rtech::liveapi::Player& +PlayerDamaged::_Internal::attacker(const PlayerDamaged* msg) { + return *msg->_impl_.attacker_; +} +const ::rtech::liveapi::Player& +PlayerDamaged::_Internal::victim(const PlayerDamaged* msg) { + return *msg->_impl_.victim_; +} +PlayerDamaged::PlayerDamaged(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerDamaged) +} +PlayerDamaged::PlayerDamaged(const PlayerDamaged& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerDamaged* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.weapon_){} + , decltype(_impl_.attacker_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.damageinflicted_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.weapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_weapon().empty()) { + _this->_impl_.weapon_.Set(from._internal_weapon(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_attacker()) { + _this->_impl_.attacker_ = new ::rtech::liveapi::Player(*from._impl_.attacker_); + } + if (from._internal_has_victim()) { + _this->_impl_.victim_ = new ::rtech::liveapi::Player(*from._impl_.victim_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.damageinflicted_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.damageinflicted_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerDamaged) +} + +inline void PlayerDamaged::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.weapon_){} + , decltype(_impl_.attacker_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.damageinflicted_){0u} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +PlayerDamaged::~PlayerDamaged() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerDamaged) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerDamaged::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.weapon_.Destroy(); + if (this != internal_default_instance()) delete _impl_.attacker_; + if (this != internal_default_instance()) delete _impl_.victim_; +} + +void PlayerDamaged::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerDamaged::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerDamaged) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.weapon_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.attacker_ != nullptr) { + delete _impl_.attacker_; + } + _impl_.attacker_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.damageinflicted_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.damageinflicted_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerDamaged::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerDamaged.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player attacker = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_attacker(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player victim = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_victim(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string weapon = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_weapon(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerDamaged.weapon")); + } else + goto handle_unusual; + continue; + // uint32 damageInflicted = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 48)) { + _impl_.damageinflicted_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerDamaged::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerDamaged) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerDamaged.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player attacker = 3; + if (this->_internal_has_attacker()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::attacker(this), + _Internal::attacker(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::victim(this), + _Internal::victim(this).GetCachedSize(), target, stream); + } + + // string weapon = 5; + if (!this->_internal_weapon().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_weapon().data(), static_cast(this->_internal_weapon().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerDamaged.weapon"); + target = stream->WriteStringMaybeAliased( + 5, this->_internal_weapon(), target); + } + + // uint32 damageInflicted = 6; + if (this->_internal_damageinflicted() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(6, this->_internal_damageinflicted(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerDamaged) + return target; +} + +size_t PlayerDamaged::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerDamaged) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string weapon = 5; + if (!this->_internal_weapon().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_weapon()); + } + + // .rtech.liveapi.Player attacker = 3; + if (this->_internal_has_attacker()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.attacker_); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.victim_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // uint32 damageInflicted = 6; + if (this->_internal_damageinflicted() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_damageinflicted()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerDamaged::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerDamaged::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerDamaged::GetClassData() const { return &_class_data_; } + + +void PlayerDamaged::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerDamaged) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_weapon().empty()) { + _this->_internal_set_weapon(from._internal_weapon()); + } + if (from._internal_has_attacker()) { + _this->_internal_mutable_attacker()->::rtech::liveapi::Player::MergeFrom( + from._internal_attacker()); + } + if (from._internal_has_victim()) { + _this->_internal_mutable_victim()->::rtech::liveapi::Player::MergeFrom( + from._internal_victim()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_damageinflicted() != 0) { + _this->_internal_set_damageinflicted(from._internal_damageinflicted()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerDamaged::CopyFrom(const PlayerDamaged& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerDamaged) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerDamaged::IsInitialized() const { + return true; +} + +void PlayerDamaged::InternalSwap(PlayerDamaged* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.weapon_, lhs_arena, + &other->_impl_.weapon_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerDamaged, _impl_.damageinflicted_) + + sizeof(PlayerDamaged::_impl_.damageinflicted_) + - PROTOBUF_FIELD_OFFSET(PlayerDamaged, _impl_.attacker_)>( + reinterpret_cast(&_impl_.attacker_), + reinterpret_cast(&other->_impl_.attacker_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerDamaged::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[21]); +} + +// =================================================================== + +class PlayerKilled::_Internal { + public: + static const ::rtech::liveapi::Player& attacker(const PlayerKilled* msg); + static const ::rtech::liveapi::Player& victim(const PlayerKilled* msg); + static const ::rtech::liveapi::Player& awardedto(const PlayerKilled* msg); +}; + +const ::rtech::liveapi::Player& +PlayerKilled::_Internal::attacker(const PlayerKilled* msg) { + return *msg->_impl_.attacker_; +} +const ::rtech::liveapi::Player& +PlayerKilled::_Internal::victim(const PlayerKilled* msg) { + return *msg->_impl_.victim_; +} +const ::rtech::liveapi::Player& +PlayerKilled::_Internal::awardedto(const PlayerKilled* msg) { + return *msg->_impl_.awardedto_; +} +PlayerKilled::PlayerKilled(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerKilled) +} +PlayerKilled::PlayerKilled(const PlayerKilled& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerKilled* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.weapon_){} + , decltype(_impl_.attacker_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.awardedto_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.weapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_weapon().empty()) { + _this->_impl_.weapon_.Set(from._internal_weapon(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_attacker()) { + _this->_impl_.attacker_ = new ::rtech::liveapi::Player(*from._impl_.attacker_); + } + if (from._internal_has_victim()) { + _this->_impl_.victim_ = new ::rtech::liveapi::Player(*from._impl_.victim_); + } + if (from._internal_has_awardedto()) { + _this->_impl_.awardedto_ = new ::rtech::liveapi::Player(*from._impl_.awardedto_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerKilled) +} + +inline void PlayerKilled::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.weapon_){} + , decltype(_impl_.attacker_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.awardedto_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +PlayerKilled::~PlayerKilled() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerKilled) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerKilled::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.weapon_.Destroy(); + if (this != internal_default_instance()) delete _impl_.attacker_; + if (this != internal_default_instance()) delete _impl_.victim_; + if (this != internal_default_instance()) delete _impl_.awardedto_; +} + +void PlayerKilled::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerKilled::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerKilled) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.weapon_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.attacker_ != nullptr) { + delete _impl_.attacker_; + } + _impl_.attacker_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.awardedto_ != nullptr) { + delete _impl_.awardedto_; + } + _impl_.awardedto_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerKilled::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerKilled.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player attacker = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_attacker(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player victim = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_victim(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player awardedTo = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + ptr = ctx->ParseMessage(_internal_mutable_awardedto(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string weapon = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + auto str = _internal_mutable_weapon(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerKilled.weapon")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerKilled::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerKilled) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerKilled.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player attacker = 3; + if (this->_internal_has_attacker()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::attacker(this), + _Internal::attacker(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::victim(this), + _Internal::victim(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Player awardedTo = 5; + if (this->_internal_has_awardedto()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(5, _Internal::awardedto(this), + _Internal::awardedto(this).GetCachedSize(), target, stream); + } + + // string weapon = 6; + if (!this->_internal_weapon().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_weapon().data(), static_cast(this->_internal_weapon().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerKilled.weapon"); + target = stream->WriteStringMaybeAliased( + 6, this->_internal_weapon(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerKilled) + return target; +} + +size_t PlayerKilled::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerKilled) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string weapon = 6; + if (!this->_internal_weapon().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_weapon()); + } + + // .rtech.liveapi.Player attacker = 3; + if (this->_internal_has_attacker()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.attacker_); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.victim_); + } + + // .rtech.liveapi.Player awardedTo = 5; + if (this->_internal_has_awardedto()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.awardedto_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerKilled::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerKilled::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerKilled::GetClassData() const { return &_class_data_; } + + +void PlayerKilled::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerKilled) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_weapon().empty()) { + _this->_internal_set_weapon(from._internal_weapon()); + } + if (from._internal_has_attacker()) { + _this->_internal_mutable_attacker()->::rtech::liveapi::Player::MergeFrom( + from._internal_attacker()); + } + if (from._internal_has_victim()) { + _this->_internal_mutable_victim()->::rtech::liveapi::Player::MergeFrom( + from._internal_victim()); + } + if (from._internal_has_awardedto()) { + _this->_internal_mutable_awardedto()->::rtech::liveapi::Player::MergeFrom( + from._internal_awardedto()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerKilled::CopyFrom(const PlayerKilled& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerKilled) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerKilled::IsInitialized() const { + return true; +} + +void PlayerKilled::InternalSwap(PlayerKilled* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.weapon_, lhs_arena, + &other->_impl_.weapon_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerKilled, _impl_.timestamp_) + + sizeof(PlayerKilled::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(PlayerKilled, _impl_.attacker_)>( + reinterpret_cast(&_impl_.attacker_), + reinterpret_cast(&other->_impl_.attacker_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerKilled::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[22]); +} + +// =================================================================== + +class PlayerDowned::_Internal { + public: + static const ::rtech::liveapi::Player& attacker(const PlayerDowned* msg); + static const ::rtech::liveapi::Player& victim(const PlayerDowned* msg); +}; + +const ::rtech::liveapi::Player& +PlayerDowned::_Internal::attacker(const PlayerDowned* msg) { + return *msg->_impl_.attacker_; +} +const ::rtech::liveapi::Player& +PlayerDowned::_Internal::victim(const PlayerDowned* msg) { + return *msg->_impl_.victim_; +} +PlayerDowned::PlayerDowned(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerDowned) +} +PlayerDowned::PlayerDowned(const PlayerDowned& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerDowned* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.weapon_){} + , decltype(_impl_.attacker_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.weapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_weapon().empty()) { + _this->_impl_.weapon_.Set(from._internal_weapon(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_attacker()) { + _this->_impl_.attacker_ = new ::rtech::liveapi::Player(*from._impl_.attacker_); + } + if (from._internal_has_victim()) { + _this->_impl_.victim_ = new ::rtech::liveapi::Player(*from._impl_.victim_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerDowned) +} + +inline void PlayerDowned::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.weapon_){} + , decltype(_impl_.attacker_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +PlayerDowned::~PlayerDowned() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerDowned) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerDowned::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.weapon_.Destroy(); + if (this != internal_default_instance()) delete _impl_.attacker_; + if (this != internal_default_instance()) delete _impl_.victim_; +} + +void PlayerDowned::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerDowned::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerDowned) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.weapon_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.attacker_ != nullptr) { + delete _impl_.attacker_; + } + _impl_.attacker_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerDowned::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerDowned.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player attacker = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_attacker(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player victim = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_victim(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string weapon = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_weapon(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerDowned.weapon")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerDowned::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerDowned) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerDowned.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player attacker = 3; + if (this->_internal_has_attacker()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::attacker(this), + _Internal::attacker(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::victim(this), + _Internal::victim(this).GetCachedSize(), target, stream); + } + + // string weapon = 5; + if (!this->_internal_weapon().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_weapon().data(), static_cast(this->_internal_weapon().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerDowned.weapon"); + target = stream->WriteStringMaybeAliased( + 5, this->_internal_weapon(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerDowned) + return target; +} + +size_t PlayerDowned::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerDowned) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string weapon = 5; + if (!this->_internal_weapon().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_weapon()); + } + + // .rtech.liveapi.Player attacker = 3; + if (this->_internal_has_attacker()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.attacker_); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.victim_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerDowned::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerDowned::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerDowned::GetClassData() const { return &_class_data_; } + + +void PlayerDowned::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerDowned) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_weapon().empty()) { + _this->_internal_set_weapon(from._internal_weapon()); + } + if (from._internal_has_attacker()) { + _this->_internal_mutable_attacker()->::rtech::liveapi::Player::MergeFrom( + from._internal_attacker()); + } + if (from._internal_has_victim()) { + _this->_internal_mutable_victim()->::rtech::liveapi::Player::MergeFrom( + from._internal_victim()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerDowned::CopyFrom(const PlayerDowned& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerDowned) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerDowned::IsInitialized() const { + return true; +} + +void PlayerDowned::InternalSwap(PlayerDowned* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.weapon_, lhs_arena, + &other->_impl_.weapon_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerDowned, _impl_.timestamp_) + + sizeof(PlayerDowned::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(PlayerDowned, _impl_.attacker_)>( + reinterpret_cast(&_impl_.attacker_), + reinterpret_cast(&other->_impl_.attacker_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerDowned::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[23]); +} + +// =================================================================== + +class PlayerAssist::_Internal { + public: + static const ::rtech::liveapi::Player& assistant(const PlayerAssist* msg); + static const ::rtech::liveapi::Player& victim(const PlayerAssist* msg); +}; + +const ::rtech::liveapi::Player& +PlayerAssist::_Internal::assistant(const PlayerAssist* msg) { + return *msg->_impl_.assistant_; +} +const ::rtech::liveapi::Player& +PlayerAssist::_Internal::victim(const PlayerAssist* msg) { + return *msg->_impl_.victim_; +} +PlayerAssist::PlayerAssist(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerAssist) +} +PlayerAssist::PlayerAssist(const PlayerAssist& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerAssist* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.weapon_){} + , decltype(_impl_.assistant_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.weapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_weapon().empty()) { + _this->_impl_.weapon_.Set(from._internal_weapon(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_assistant()) { + _this->_impl_.assistant_ = new ::rtech::liveapi::Player(*from._impl_.assistant_); + } + if (from._internal_has_victim()) { + _this->_impl_.victim_ = new ::rtech::liveapi::Player(*from._impl_.victim_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerAssist) +} + +inline void PlayerAssist::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.weapon_){} + , decltype(_impl_.assistant_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.weapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +PlayerAssist::~PlayerAssist() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerAssist) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerAssist::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.weapon_.Destroy(); + if (this != internal_default_instance()) delete _impl_.assistant_; + if (this != internal_default_instance()) delete _impl_.victim_; +} + +void PlayerAssist::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerAssist::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerAssist) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.weapon_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.assistant_ != nullptr) { + delete _impl_.assistant_; + } + _impl_.assistant_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerAssist::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerAssist.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player assistant = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_assistant(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player victim = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_victim(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string weapon = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_weapon(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerAssist.weapon")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerAssist::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerAssist) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerAssist.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player assistant = 3; + if (this->_internal_has_assistant()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::assistant(this), + _Internal::assistant(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::victim(this), + _Internal::victim(this).GetCachedSize(), target, stream); + } + + // string weapon = 5; + if (!this->_internal_weapon().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_weapon().data(), static_cast(this->_internal_weapon().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerAssist.weapon"); + target = stream->WriteStringMaybeAliased( + 5, this->_internal_weapon(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerAssist) + return target; +} + +size_t PlayerAssist::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerAssist) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string weapon = 5; + if (!this->_internal_weapon().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_weapon()); + } + + // .rtech.liveapi.Player assistant = 3; + if (this->_internal_has_assistant()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.assistant_); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.victim_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerAssist::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerAssist::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerAssist::GetClassData() const { return &_class_data_; } + + +void PlayerAssist::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerAssist) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_weapon().empty()) { + _this->_internal_set_weapon(from._internal_weapon()); + } + if (from._internal_has_assistant()) { + _this->_internal_mutable_assistant()->::rtech::liveapi::Player::MergeFrom( + from._internal_assistant()); + } + if (from._internal_has_victim()) { + _this->_internal_mutable_victim()->::rtech::liveapi::Player::MergeFrom( + from._internal_victim()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerAssist::CopyFrom(const PlayerAssist& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerAssist) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerAssist::IsInitialized() const { + return true; +} + +void PlayerAssist::InternalSwap(PlayerAssist* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.weapon_, lhs_arena, + &other->_impl_.weapon_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerAssist, _impl_.timestamp_) + + sizeof(PlayerAssist::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(PlayerAssist, _impl_.assistant_)>( + reinterpret_cast(&_impl_.assistant_), + reinterpret_cast(&other->_impl_.assistant_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerAssist::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[24]); +} + +// =================================================================== + +class SquadEliminated::_Internal { + public: +}; + +SquadEliminated::SquadEliminated(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.SquadEliminated) +} +SquadEliminated::SquadEliminated(const SquadEliminated& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + SquadEliminated* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.players_){from._impl_.players_} + , decltype(_impl_.category_){} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.SquadEliminated) +} + +inline void SquadEliminated::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.players_){arena} + , decltype(_impl_.category_){} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +SquadEliminated::~SquadEliminated() { + // @@protoc_insertion_point(destructor:rtech.liveapi.SquadEliminated) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void SquadEliminated::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.players_.~RepeatedPtrField(); + _impl_.category_.Destroy(); +} + +void SquadEliminated::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void SquadEliminated::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.SquadEliminated) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.players_.Clear(); + _impl_.category_.ClearToEmpty(); + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* SquadEliminated::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.SquadEliminated.category")); + } else + goto handle_unusual; + continue; + // repeated .rtech.liveapi.Player players = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_players(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<26>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* SquadEliminated::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.SquadEliminated) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.SquadEliminated.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // repeated .rtech.liveapi.Player players = 3; + for (unsigned i = 0, + n = static_cast(this->_internal_players_size()); i < n; i++) { + const auto& repfield = this->_internal_players(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.SquadEliminated) + return target; +} + +size_t SquadEliminated::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.SquadEliminated) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .rtech.liveapi.Player players = 3; + total_size += 1UL * this->_internal_players_size(); + for (const auto& msg : this->_impl_.players_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData SquadEliminated::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + SquadEliminated::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*SquadEliminated::GetClassData() const { return &_class_data_; } + + +void SquadEliminated::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.SquadEliminated) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.players_.MergeFrom(from._impl_.players_); + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void SquadEliminated::CopyFrom(const SquadEliminated& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.SquadEliminated) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool SquadEliminated::IsInitialized() const { + return true; +} + +void SquadEliminated::InternalSwap(SquadEliminated* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.players_.InternalSwap(&other->_impl_.players_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + swap(_impl_.timestamp_, other->_impl_.timestamp_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata SquadEliminated::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[25]); +} + +// =================================================================== + +class GibraltarShieldAbsorbed::_Internal { + public: + static const ::rtech::liveapi::Player& attacker(const GibraltarShieldAbsorbed* msg); + static const ::rtech::liveapi::Player& victim(const GibraltarShieldAbsorbed* msg); +}; + +const ::rtech::liveapi::Player& +GibraltarShieldAbsorbed::_Internal::attacker(const GibraltarShieldAbsorbed* msg) { + return *msg->_impl_.attacker_; +} +const ::rtech::liveapi::Player& +GibraltarShieldAbsorbed::_Internal::victim(const GibraltarShieldAbsorbed* msg) { + return *msg->_impl_.victim_; +} +GibraltarShieldAbsorbed::GibraltarShieldAbsorbed(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.GibraltarShieldAbsorbed) +} +GibraltarShieldAbsorbed::GibraltarShieldAbsorbed(const GibraltarShieldAbsorbed& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + GibraltarShieldAbsorbed* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.attacker_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.damageinflicted_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_attacker()) { + _this->_impl_.attacker_ = new ::rtech::liveapi::Player(*from._impl_.attacker_); + } + if (from._internal_has_victim()) { + _this->_impl_.victim_ = new ::rtech::liveapi::Player(*from._impl_.victim_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.damageinflicted_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.damageinflicted_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.GibraltarShieldAbsorbed) +} + +inline void GibraltarShieldAbsorbed::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.attacker_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.damageinflicted_){0u} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +GibraltarShieldAbsorbed::~GibraltarShieldAbsorbed() { + // @@protoc_insertion_point(destructor:rtech.liveapi.GibraltarShieldAbsorbed) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void GibraltarShieldAbsorbed::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.attacker_; + if (this != internal_default_instance()) delete _impl_.victim_; +} + +void GibraltarShieldAbsorbed::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void GibraltarShieldAbsorbed::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.GibraltarShieldAbsorbed) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.attacker_ != nullptr) { + delete _impl_.attacker_; + } + _impl_.attacker_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.damageinflicted_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.damageinflicted_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* GibraltarShieldAbsorbed::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.GibraltarShieldAbsorbed.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player attacker = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_attacker(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player victim = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_victim(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // uint32 damageInflicted = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 48)) { + _impl_.damageinflicted_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* GibraltarShieldAbsorbed::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.GibraltarShieldAbsorbed) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.GibraltarShieldAbsorbed.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player attacker = 3; + if (this->_internal_has_attacker()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::attacker(this), + _Internal::attacker(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::victim(this), + _Internal::victim(this).GetCachedSize(), target, stream); + } + + // uint32 damageInflicted = 6; + if (this->_internal_damageinflicted() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(6, this->_internal_damageinflicted(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.GibraltarShieldAbsorbed) + return target; +} + +size_t GibraltarShieldAbsorbed::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.GibraltarShieldAbsorbed) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player attacker = 3; + if (this->_internal_has_attacker()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.attacker_); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.victim_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // uint32 damageInflicted = 6; + if (this->_internal_damageinflicted() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_damageinflicted()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData GibraltarShieldAbsorbed::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + GibraltarShieldAbsorbed::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GibraltarShieldAbsorbed::GetClassData() const { return &_class_data_; } + + +void GibraltarShieldAbsorbed::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.GibraltarShieldAbsorbed) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_attacker()) { + _this->_internal_mutable_attacker()->::rtech::liveapi::Player::MergeFrom( + from._internal_attacker()); + } + if (from._internal_has_victim()) { + _this->_internal_mutable_victim()->::rtech::liveapi::Player::MergeFrom( + from._internal_victim()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_damageinflicted() != 0) { + _this->_internal_set_damageinflicted(from._internal_damageinflicted()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void GibraltarShieldAbsorbed::CopyFrom(const GibraltarShieldAbsorbed& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.GibraltarShieldAbsorbed) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool GibraltarShieldAbsorbed::IsInitialized() const { + return true; +} + +void GibraltarShieldAbsorbed::InternalSwap(GibraltarShieldAbsorbed* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(GibraltarShieldAbsorbed, _impl_.damageinflicted_) + + sizeof(GibraltarShieldAbsorbed::_impl_.damageinflicted_) + - PROTOBUF_FIELD_OFFSET(GibraltarShieldAbsorbed, _impl_.attacker_)>( + reinterpret_cast(&_impl_.attacker_), + reinterpret_cast(&other->_impl_.attacker_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata GibraltarShieldAbsorbed::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[26]); +} + +// =================================================================== + +class RevenantForgedShadowDamaged::_Internal { + public: + static const ::rtech::liveapi::Player& attacker(const RevenantForgedShadowDamaged* msg); + static const ::rtech::liveapi::Player& victim(const RevenantForgedShadowDamaged* msg); +}; + +const ::rtech::liveapi::Player& +RevenantForgedShadowDamaged::_Internal::attacker(const RevenantForgedShadowDamaged* msg) { + return *msg->_impl_.attacker_; +} +const ::rtech::liveapi::Player& +RevenantForgedShadowDamaged::_Internal::victim(const RevenantForgedShadowDamaged* msg) { + return *msg->_impl_.victim_; +} +RevenantForgedShadowDamaged::RevenantForgedShadowDamaged(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.RevenantForgedShadowDamaged) +} +RevenantForgedShadowDamaged::RevenantForgedShadowDamaged(const RevenantForgedShadowDamaged& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + RevenantForgedShadowDamaged* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.attacker_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.damageinflicted_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_attacker()) { + _this->_impl_.attacker_ = new ::rtech::liveapi::Player(*from._impl_.attacker_); + } + if (from._internal_has_victim()) { + _this->_impl_.victim_ = new ::rtech::liveapi::Player(*from._impl_.victim_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.damageinflicted_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.damageinflicted_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.RevenantForgedShadowDamaged) +} + +inline void RevenantForgedShadowDamaged::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.attacker_){nullptr} + , decltype(_impl_.victim_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.damageinflicted_){0u} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +RevenantForgedShadowDamaged::~RevenantForgedShadowDamaged() { + // @@protoc_insertion_point(destructor:rtech.liveapi.RevenantForgedShadowDamaged) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void RevenantForgedShadowDamaged::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.attacker_; + if (this != internal_default_instance()) delete _impl_.victim_; +} + +void RevenantForgedShadowDamaged::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void RevenantForgedShadowDamaged::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.RevenantForgedShadowDamaged) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.attacker_ != nullptr) { + delete _impl_.attacker_; + } + _impl_.attacker_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.damageinflicted_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.damageinflicted_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* RevenantForgedShadowDamaged::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.RevenantForgedShadowDamaged.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player attacker = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_attacker(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player victim = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_victim(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // uint32 damageInflicted = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 48)) { + _impl_.damageinflicted_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* RevenantForgedShadowDamaged::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.RevenantForgedShadowDamaged) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.RevenantForgedShadowDamaged.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player attacker = 3; + if (this->_internal_has_attacker()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::attacker(this), + _Internal::attacker(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::victim(this), + _Internal::victim(this).GetCachedSize(), target, stream); + } + + // uint32 damageInflicted = 6; + if (this->_internal_damageinflicted() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(6, this->_internal_damageinflicted(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.RevenantForgedShadowDamaged) + return target; +} + +size_t RevenantForgedShadowDamaged::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.RevenantForgedShadowDamaged) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player attacker = 3; + if (this->_internal_has_attacker()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.attacker_); + } + + // .rtech.liveapi.Player victim = 4; + if (this->_internal_has_victim()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.victim_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // uint32 damageInflicted = 6; + if (this->_internal_damageinflicted() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_damageinflicted()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData RevenantForgedShadowDamaged::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + RevenantForgedShadowDamaged::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*RevenantForgedShadowDamaged::GetClassData() const { return &_class_data_; } + + +void RevenantForgedShadowDamaged::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.RevenantForgedShadowDamaged) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_attacker()) { + _this->_internal_mutable_attacker()->::rtech::liveapi::Player::MergeFrom( + from._internal_attacker()); + } + if (from._internal_has_victim()) { + _this->_internal_mutable_victim()->::rtech::liveapi::Player::MergeFrom( + from._internal_victim()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_damageinflicted() != 0) { + _this->_internal_set_damageinflicted(from._internal_damageinflicted()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void RevenantForgedShadowDamaged::CopyFrom(const RevenantForgedShadowDamaged& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.RevenantForgedShadowDamaged) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool RevenantForgedShadowDamaged::IsInitialized() const { + return true; +} + +void RevenantForgedShadowDamaged::InternalSwap(RevenantForgedShadowDamaged* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(RevenantForgedShadowDamaged, _impl_.damageinflicted_) + + sizeof(RevenantForgedShadowDamaged::_impl_.damageinflicted_) + - PROTOBUF_FIELD_OFFSET(RevenantForgedShadowDamaged, _impl_.attacker_)>( + reinterpret_cast(&_impl_.attacker_), + reinterpret_cast(&other->_impl_.attacker_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata RevenantForgedShadowDamaged::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[27]); +} + +// =================================================================== + +class PlayerRespawnTeam::_Internal { + public: + static const ::rtech::liveapi::Player& player(const PlayerRespawnTeam* msg); +}; + +const ::rtech::liveapi::Player& +PlayerRespawnTeam::_Internal::player(const PlayerRespawnTeam* msg) { + return *msg->_impl_.player_; +} +PlayerRespawnTeam::PlayerRespawnTeam(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerRespawnTeam) +} +PlayerRespawnTeam::PlayerRespawnTeam(const PlayerRespawnTeam& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerRespawnTeam* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.respawned_){from._impl_.respawned_} + , decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerRespawnTeam) +} + +inline void PlayerRespawnTeam::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.respawned_){arena} + , decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +PlayerRespawnTeam::~PlayerRespawnTeam() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerRespawnTeam) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerRespawnTeam::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.respawned_.~RepeatedPtrField(); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void PlayerRespawnTeam::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerRespawnTeam::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerRespawnTeam) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.respawned_.Clear(); + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerRespawnTeam::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerRespawnTeam.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .rtech.liveapi.Player respawned = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_respawned(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<34>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerRespawnTeam::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerRespawnTeam) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerRespawnTeam.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // repeated .rtech.liveapi.Player respawned = 4; + for (unsigned i = 0, + n = static_cast(this->_internal_respawned_size()); i < n; i++) { + const auto& repfield = this->_internal_respawned(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerRespawnTeam) + return target; +} + +size_t PlayerRespawnTeam::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerRespawnTeam) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .rtech.liveapi.Player respawned = 4; + total_size += 1UL * this->_internal_respawned_size(); + for (const auto& msg : this->_impl_.respawned_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerRespawnTeam::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerRespawnTeam::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerRespawnTeam::GetClassData() const { return &_class_data_; } + + +void PlayerRespawnTeam::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerRespawnTeam) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.respawned_.MergeFrom(from._impl_.respawned_); + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerRespawnTeam::CopyFrom(const PlayerRespawnTeam& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerRespawnTeam) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerRespawnTeam::IsInitialized() const { + return true; +} + +void PlayerRespawnTeam::InternalSwap(PlayerRespawnTeam* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.respawned_.InternalSwap(&other->_impl_.respawned_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerRespawnTeam, _impl_.timestamp_) + + sizeof(PlayerRespawnTeam::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(PlayerRespawnTeam, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerRespawnTeam::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[28]); +} + +// =================================================================== + +class PlayerRevive::_Internal { + public: + static const ::rtech::liveapi::Player& player(const PlayerRevive* msg); + static const ::rtech::liveapi::Player& revived(const PlayerRevive* msg); +}; + +const ::rtech::liveapi::Player& +PlayerRevive::_Internal::player(const PlayerRevive* msg) { + return *msg->_impl_.player_; +} +const ::rtech::liveapi::Player& +PlayerRevive::_Internal::revived(const PlayerRevive* msg) { + return *msg->_impl_.revived_; +} +PlayerRevive::PlayerRevive(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerRevive) +} +PlayerRevive::PlayerRevive(const PlayerRevive& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerRevive* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.revived_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + if (from._internal_has_revived()) { + _this->_impl_.revived_ = new ::rtech::liveapi::Player(*from._impl_.revived_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerRevive) +} + +inline void PlayerRevive::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.revived_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +PlayerRevive::~PlayerRevive() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerRevive) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerRevive::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; + if (this != internal_default_instance()) delete _impl_.revived_; +} + +void PlayerRevive::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerRevive::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerRevive) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.revived_ != nullptr) { + delete _impl_.revived_; + } + _impl_.revived_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerRevive::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerRevive.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player revived = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_revived(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerRevive::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerRevive) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerRevive.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Player revived = 4; + if (this->_internal_has_revived()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::revived(this), + _Internal::revived(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerRevive) + return target; +} + +size_t PlayerRevive::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerRevive) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // .rtech.liveapi.Player revived = 4; + if (this->_internal_has_revived()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.revived_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerRevive::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerRevive::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerRevive::GetClassData() const { return &_class_data_; } + + +void PlayerRevive::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerRevive) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_has_revived()) { + _this->_internal_mutable_revived()->::rtech::liveapi::Player::MergeFrom( + from._internal_revived()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerRevive::CopyFrom(const PlayerRevive& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerRevive) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerRevive::IsInitialized() const { + return true; +} + +void PlayerRevive::InternalSwap(PlayerRevive* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerRevive, _impl_.timestamp_) + + sizeof(PlayerRevive::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(PlayerRevive, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerRevive::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[29]); +} + +// =================================================================== + +class ArenasItemSelected::_Internal { + public: + static const ::rtech::liveapi::Player& player(const ArenasItemSelected* msg); +}; + +const ::rtech::liveapi::Player& +ArenasItemSelected::_Internal::player(const ArenasItemSelected* msg) { + return *msg->_impl_.player_; +} +ArenasItemSelected::ArenasItemSelected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.ArenasItemSelected) +} +ArenasItemSelected::ArenasItemSelected(const ArenasItemSelected& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + ArenasItemSelected* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.quantity_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_item().empty()) { + _this->_impl_.item_.Set(from._internal_item(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.quantity_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.quantity_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.ArenasItemSelected) +} + +inline void ArenasItemSelected::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.quantity_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ArenasItemSelected::~ArenasItemSelected() { + // @@protoc_insertion_point(destructor:rtech.liveapi.ArenasItemSelected) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ArenasItemSelected::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.item_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void ArenasItemSelected::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ArenasItemSelected::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.ArenasItemSelected) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.item_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.quantity_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.quantity_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* ArenasItemSelected::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.ArenasItemSelected.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string item = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_item(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.ArenasItemSelected.item")); + } else + goto handle_unusual; + continue; + // int32 quantity = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _impl_.quantity_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ArenasItemSelected::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.ArenasItemSelected) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.ArenasItemSelected.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_item().data(), static_cast(this->_internal_item().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.ArenasItemSelected.item"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_item(), target); + } + + // int32 quantity = 5; + if (this->_internal_quantity() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(5, this->_internal_quantity(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.ArenasItemSelected) + return target; +} + +size_t ArenasItemSelected::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.ArenasItemSelected) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_item()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // int32 quantity = 5; + if (this->_internal_quantity() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_quantity()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData ArenasItemSelected::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + ArenasItemSelected::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*ArenasItemSelected::GetClassData() const { return &_class_data_; } + + +void ArenasItemSelected::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.ArenasItemSelected) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_item().empty()) { + _this->_internal_set_item(from._internal_item()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_quantity() != 0) { + _this->_internal_set_quantity(from._internal_quantity()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void ArenasItemSelected::CopyFrom(const ArenasItemSelected& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.ArenasItemSelected) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ArenasItemSelected::IsInitialized() const { + return true; +} + +void ArenasItemSelected::InternalSwap(ArenasItemSelected* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.item_, lhs_arena, + &other->_impl_.item_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ArenasItemSelected, _impl_.quantity_) + + sizeof(ArenasItemSelected::_impl_.quantity_) + - PROTOBUF_FIELD_OFFSET(ArenasItemSelected, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata ArenasItemSelected::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[30]); +} + +// =================================================================== + +class ArenasItemDeselected::_Internal { + public: + static const ::rtech::liveapi::Player& player(const ArenasItemDeselected* msg); +}; + +const ::rtech::liveapi::Player& +ArenasItemDeselected::_Internal::player(const ArenasItemDeselected* msg) { + return *msg->_impl_.player_; +} +ArenasItemDeselected::ArenasItemDeselected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.ArenasItemDeselected) +} +ArenasItemDeselected::ArenasItemDeselected(const ArenasItemDeselected& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + ArenasItemDeselected* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.quantity_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_item().empty()) { + _this->_impl_.item_.Set(from._internal_item(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.quantity_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.quantity_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.ArenasItemDeselected) +} + +inline void ArenasItemDeselected::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.quantity_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ArenasItemDeselected::~ArenasItemDeselected() { + // @@protoc_insertion_point(destructor:rtech.liveapi.ArenasItemDeselected) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ArenasItemDeselected::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.item_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void ArenasItemDeselected::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ArenasItemDeselected::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.ArenasItemDeselected) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.item_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.quantity_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.quantity_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* ArenasItemDeselected::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.ArenasItemDeselected.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string item = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_item(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.ArenasItemDeselected.item")); + } else + goto handle_unusual; + continue; + // int32 quantity = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _impl_.quantity_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ArenasItemDeselected::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.ArenasItemDeselected) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.ArenasItemDeselected.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_item().data(), static_cast(this->_internal_item().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.ArenasItemDeselected.item"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_item(), target); + } + + // int32 quantity = 5; + if (this->_internal_quantity() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(5, this->_internal_quantity(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.ArenasItemDeselected) + return target; +} + +size_t ArenasItemDeselected::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.ArenasItemDeselected) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_item()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // int32 quantity = 5; + if (this->_internal_quantity() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_quantity()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData ArenasItemDeselected::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + ArenasItemDeselected::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*ArenasItemDeselected::GetClassData() const { return &_class_data_; } + + +void ArenasItemDeselected::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.ArenasItemDeselected) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_item().empty()) { + _this->_internal_set_item(from._internal_item()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_quantity() != 0) { + _this->_internal_set_quantity(from._internal_quantity()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void ArenasItemDeselected::CopyFrom(const ArenasItemDeselected& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.ArenasItemDeselected) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ArenasItemDeselected::IsInitialized() const { + return true; +} + +void ArenasItemDeselected::InternalSwap(ArenasItemDeselected* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.item_, lhs_arena, + &other->_impl_.item_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ArenasItemDeselected, _impl_.quantity_) + + sizeof(ArenasItemDeselected::_impl_.quantity_) + - PROTOBUF_FIELD_OFFSET(ArenasItemDeselected, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata ArenasItemDeselected::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[31]); +} + +// =================================================================== + +class InventoryPickUp::_Internal { + public: + static const ::rtech::liveapi::Player& player(const InventoryPickUp* msg); +}; + +const ::rtech::liveapi::Player& +InventoryPickUp::_Internal::player(const InventoryPickUp* msg) { + return *msg->_impl_.player_; +} +InventoryPickUp::InventoryPickUp(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.InventoryPickUp) +} +InventoryPickUp::InventoryPickUp(const InventoryPickUp& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + InventoryPickUp* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.quantity_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_item().empty()) { + _this->_impl_.item_.Set(from._internal_item(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.quantity_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.quantity_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.InventoryPickUp) +} + +inline void InventoryPickUp::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.quantity_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +InventoryPickUp::~InventoryPickUp() { + // @@protoc_insertion_point(destructor:rtech.liveapi.InventoryPickUp) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void InventoryPickUp::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.item_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void InventoryPickUp::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void InventoryPickUp::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.InventoryPickUp) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.item_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.quantity_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.quantity_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* InventoryPickUp::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.InventoryPickUp.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string item = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_item(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.InventoryPickUp.item")); + } else + goto handle_unusual; + continue; + // int32 quantity = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _impl_.quantity_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* InventoryPickUp::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.InventoryPickUp) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.InventoryPickUp.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_item().data(), static_cast(this->_internal_item().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.InventoryPickUp.item"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_item(), target); + } + + // int32 quantity = 5; + if (this->_internal_quantity() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(5, this->_internal_quantity(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.InventoryPickUp) + return target; +} + +size_t InventoryPickUp::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.InventoryPickUp) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_item()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // int32 quantity = 5; + if (this->_internal_quantity() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_quantity()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData InventoryPickUp::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + InventoryPickUp::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*InventoryPickUp::GetClassData() const { return &_class_data_; } + + +void InventoryPickUp::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.InventoryPickUp) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_item().empty()) { + _this->_internal_set_item(from._internal_item()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_quantity() != 0) { + _this->_internal_set_quantity(from._internal_quantity()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void InventoryPickUp::CopyFrom(const InventoryPickUp& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.InventoryPickUp) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool InventoryPickUp::IsInitialized() const { + return true; +} + +void InventoryPickUp::InternalSwap(InventoryPickUp* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.item_, lhs_arena, + &other->_impl_.item_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(InventoryPickUp, _impl_.quantity_) + + sizeof(InventoryPickUp::_impl_.quantity_) + - PROTOBUF_FIELD_OFFSET(InventoryPickUp, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata InventoryPickUp::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[32]); +} + +// =================================================================== + +class InventoryDrop::_Internal { + public: + static const ::rtech::liveapi::Player& player(const InventoryDrop* msg); +}; + +const ::rtech::liveapi::Player& +InventoryDrop::_Internal::player(const InventoryDrop* msg) { + return *msg->_impl_.player_; +} +InventoryDrop::InventoryDrop(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.InventoryDrop) +} +InventoryDrop::InventoryDrop(const InventoryDrop& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + InventoryDrop* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.extradata_){from._impl_.extradata_} + , decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.quantity_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_item().empty()) { + _this->_impl_.item_.Set(from._internal_item(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.quantity_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.quantity_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.InventoryDrop) +} + +inline void InventoryDrop::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.extradata_){arena} + , decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.quantity_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +InventoryDrop::~InventoryDrop() { + // @@protoc_insertion_point(destructor:rtech.liveapi.InventoryDrop) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void InventoryDrop::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.extradata_.~RepeatedPtrField(); + _impl_.category_.Destroy(); + _impl_.item_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void InventoryDrop::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void InventoryDrop::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.InventoryDrop) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.extradata_.Clear(); + _impl_.category_.ClearToEmpty(); + _impl_.item_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.quantity_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.quantity_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* InventoryDrop::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.InventoryDrop.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string item = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_item(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.InventoryDrop.item")); + } else + goto handle_unusual; + continue; + // int32 quantity = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _impl_.quantity_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated string extraData = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_extradata(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.InventoryDrop.extraData")); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<50>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* InventoryDrop::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.InventoryDrop) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.InventoryDrop.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_item().data(), static_cast(this->_internal_item().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.InventoryDrop.item"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_item(), target); + } + + // int32 quantity = 5; + if (this->_internal_quantity() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(5, this->_internal_quantity(), target); + } + + // repeated string extraData = 6; + for (int i = 0, n = this->_internal_extradata_size(); i < n; i++) { + const auto& s = this->_internal_extradata(i); + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + s.data(), static_cast(s.length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.InventoryDrop.extraData"); + target = stream->WriteString(6, s, target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.InventoryDrop) + return target; +} + +size_t InventoryDrop::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.InventoryDrop) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated string extraData = 6; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.extradata_.size()); + for (int i = 0, n = _impl_.extradata_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + _impl_.extradata_.Get(i)); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_item()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // int32 quantity = 5; + if (this->_internal_quantity() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_quantity()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData InventoryDrop::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + InventoryDrop::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*InventoryDrop::GetClassData() const { return &_class_data_; } + + +void InventoryDrop::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.InventoryDrop) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.extradata_.MergeFrom(from._impl_.extradata_); + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_item().empty()) { + _this->_internal_set_item(from._internal_item()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_quantity() != 0) { + _this->_internal_set_quantity(from._internal_quantity()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void InventoryDrop::CopyFrom(const InventoryDrop& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.InventoryDrop) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool InventoryDrop::IsInitialized() const { + return true; +} + +void InventoryDrop::InternalSwap(InventoryDrop* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.extradata_.InternalSwap(&other->_impl_.extradata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.item_, lhs_arena, + &other->_impl_.item_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(InventoryDrop, _impl_.quantity_) + + sizeof(InventoryDrop::_impl_.quantity_) + - PROTOBUF_FIELD_OFFSET(InventoryDrop, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata InventoryDrop::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[33]); +} + +// =================================================================== + +class InventoryUse::_Internal { + public: + static const ::rtech::liveapi::Player& player(const InventoryUse* msg); +}; + +const ::rtech::liveapi::Player& +InventoryUse::_Internal::player(const InventoryUse* msg) { + return *msg->_impl_.player_; +} +InventoryUse::InventoryUse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.InventoryUse) +} +InventoryUse::InventoryUse(const InventoryUse& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + InventoryUse* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.quantity_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_item().empty()) { + _this->_impl_.item_.Set(from._internal_item(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.quantity_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.quantity_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.InventoryUse) +} + +inline void InventoryUse::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.quantity_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +InventoryUse::~InventoryUse() { + // @@protoc_insertion_point(destructor:rtech.liveapi.InventoryUse) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void InventoryUse::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.item_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void InventoryUse::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void InventoryUse::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.InventoryUse) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.item_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.quantity_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.quantity_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* InventoryUse::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.InventoryUse.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string item = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_item(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.InventoryUse.item")); + } else + goto handle_unusual; + continue; + // int32 quantity = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _impl_.quantity_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* InventoryUse::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.InventoryUse) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.InventoryUse.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_item().data(), static_cast(this->_internal_item().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.InventoryUse.item"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_item(), target); + } + + // int32 quantity = 5; + if (this->_internal_quantity() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(5, this->_internal_quantity(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.InventoryUse) + return target; +} + +size_t InventoryUse::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.InventoryUse) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_item()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // int32 quantity = 5; + if (this->_internal_quantity() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_quantity()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData InventoryUse::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + InventoryUse::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*InventoryUse::GetClassData() const { return &_class_data_; } + + +void InventoryUse::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.InventoryUse) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_item().empty()) { + _this->_internal_set_item(from._internal_item()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_quantity() != 0) { + _this->_internal_set_quantity(from._internal_quantity()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void InventoryUse::CopyFrom(const InventoryUse& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.InventoryUse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool InventoryUse::IsInitialized() const { + return true; +} + +void InventoryUse::InternalSwap(InventoryUse* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.item_, lhs_arena, + &other->_impl_.item_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(InventoryUse, _impl_.quantity_) + + sizeof(InventoryUse::_impl_.quantity_) + - PROTOBUF_FIELD_OFFSET(InventoryUse, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata InventoryUse::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[34]); +} + +// =================================================================== + +class BannerCollected::_Internal { + public: + static const ::rtech::liveapi::Player& player(const BannerCollected* msg); + static const ::rtech::liveapi::Player& collected(const BannerCollected* msg); +}; + +const ::rtech::liveapi::Player& +BannerCollected::_Internal::player(const BannerCollected* msg) { + return *msg->_impl_.player_; +} +const ::rtech::liveapi::Player& +BannerCollected::_Internal::collected(const BannerCollected* msg) { + return *msg->_impl_.collected_; +} +BannerCollected::BannerCollected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.BannerCollected) +} +BannerCollected::BannerCollected(const BannerCollected& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + BannerCollected* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.collected_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + if (from._internal_has_collected()) { + _this->_impl_.collected_ = new ::rtech::liveapi::Player(*from._impl_.collected_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.BannerCollected) +} + +inline void BannerCollected::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.collected_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +BannerCollected::~BannerCollected() { + // @@protoc_insertion_point(destructor:rtech.liveapi.BannerCollected) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void BannerCollected::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; + if (this != internal_default_instance()) delete _impl_.collected_; +} + +void BannerCollected::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void BannerCollected::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.BannerCollected) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + if (GetArenaForAllocation() == nullptr && _impl_.collected_ != nullptr) { + delete _impl_.collected_; + } + _impl_.collected_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* BannerCollected::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.BannerCollected.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player collected = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_collected(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* BannerCollected::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.BannerCollected) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.BannerCollected.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.Player collected = 4; + if (this->_internal_has_collected()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::collected(this), + _Internal::collected(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.BannerCollected) + return target; +} + +size_t BannerCollected::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.BannerCollected) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // .rtech.liveapi.Player collected = 4; + if (this->_internal_has_collected()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.collected_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData BannerCollected::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + BannerCollected::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*BannerCollected::GetClassData() const { return &_class_data_; } + + +void BannerCollected::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.BannerCollected) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_has_collected()) { + _this->_internal_mutable_collected()->::rtech::liveapi::Player::MergeFrom( + from._internal_collected()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void BannerCollected::CopyFrom(const BannerCollected& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.BannerCollected) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool BannerCollected::IsInitialized() const { + return true; +} + +void BannerCollected::InternalSwap(BannerCollected* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(BannerCollected, _impl_.timestamp_) + + sizeof(BannerCollected::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(BannerCollected, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata BannerCollected::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[35]); +} + +// =================================================================== + +class PlayerAbilityUsed::_Internal { + public: + static const ::rtech::liveapi::Player& player(const PlayerAbilityUsed* msg); +}; + +const ::rtech::liveapi::Player& +PlayerAbilityUsed::_Internal::player(const PlayerAbilityUsed* msg) { + return *msg->_impl_.player_; +} +PlayerAbilityUsed::PlayerAbilityUsed(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PlayerAbilityUsed) +} +PlayerAbilityUsed::PlayerAbilityUsed(const PlayerAbilityUsed& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PlayerAbilityUsed* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.linkedentity_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.linkedentity_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.linkedentity_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_linkedentity().empty()) { + _this->_impl_.linkedentity_.Set(from._internal_linkedentity(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PlayerAbilityUsed) +} + +inline void PlayerAbilityUsed::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.linkedentity_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.linkedentity_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.linkedentity_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +PlayerAbilityUsed::~PlayerAbilityUsed() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PlayerAbilityUsed) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PlayerAbilityUsed::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.linkedentity_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void PlayerAbilityUsed::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PlayerAbilityUsed::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PlayerAbilityUsed) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.linkedentity_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PlayerAbilityUsed::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerAbilityUsed.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string linkedEntity = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_linkedentity(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.PlayerAbilityUsed.linkedEntity")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PlayerAbilityUsed::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PlayerAbilityUsed) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerAbilityUsed.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string linkedEntity = 4; + if (!this->_internal_linkedentity().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_linkedentity().data(), static_cast(this->_internal_linkedentity().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.PlayerAbilityUsed.linkedEntity"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_linkedentity(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PlayerAbilityUsed) + return target; +} + +size_t PlayerAbilityUsed::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PlayerAbilityUsed) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string linkedEntity = 4; + if (!this->_internal_linkedentity().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_linkedentity()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PlayerAbilityUsed::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PlayerAbilityUsed::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PlayerAbilityUsed::GetClassData() const { return &_class_data_; } + + +void PlayerAbilityUsed::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PlayerAbilityUsed) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_linkedentity().empty()) { + _this->_internal_set_linkedentity(from._internal_linkedentity()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PlayerAbilityUsed::CopyFrom(const PlayerAbilityUsed& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PlayerAbilityUsed) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PlayerAbilityUsed::IsInitialized() const { + return true; +} + +void PlayerAbilityUsed::InternalSwap(PlayerAbilityUsed* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.linkedentity_, lhs_arena, + &other->_impl_.linkedentity_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(PlayerAbilityUsed, _impl_.timestamp_) + + sizeof(PlayerAbilityUsed::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(PlayerAbilityUsed, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PlayerAbilityUsed::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[36]); +} + +// =================================================================== + +class LegendUpgradeSelected::_Internal { + public: + static const ::rtech::liveapi::Player& player(const LegendUpgradeSelected* msg); +}; + +const ::rtech::liveapi::Player& +LegendUpgradeSelected::_Internal::player(const LegendUpgradeSelected* msg) { + return *msg->_impl_.player_; +} +LegendUpgradeSelected::LegendUpgradeSelected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.LegendUpgradeSelected) +} +LegendUpgradeSelected::LegendUpgradeSelected(const LegendUpgradeSelected& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + LegendUpgradeSelected* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.upgradename_){} + , decltype(_impl_.upgradedesc_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.level_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.upgradename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.upgradename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_upgradename().empty()) { + _this->_impl_.upgradename_.Set(from._internal_upgradename(), + _this->GetArenaForAllocation()); + } + _impl_.upgradedesc_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.upgradedesc_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_upgradedesc().empty()) { + _this->_impl_.upgradedesc_.Set(from._internal_upgradedesc(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.level_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.level_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.LegendUpgradeSelected) +} + +inline void LegendUpgradeSelected::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.upgradename_){} + , decltype(_impl_.upgradedesc_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.level_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.upgradename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.upgradename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.upgradedesc_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.upgradedesc_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +LegendUpgradeSelected::~LegendUpgradeSelected() { + // @@protoc_insertion_point(destructor:rtech.liveapi.LegendUpgradeSelected) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void LegendUpgradeSelected::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.upgradename_.Destroy(); + _impl_.upgradedesc_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void LegendUpgradeSelected::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void LegendUpgradeSelected::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.LegendUpgradeSelected) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.upgradename_.ClearToEmpty(); + _impl_.upgradedesc_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.level_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.level_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* LegendUpgradeSelected::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.LegendUpgradeSelected.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string upgradeName = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_upgradename(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.LegendUpgradeSelected.upgradeName")); + } else + goto handle_unusual; + continue; + // string upgradeDesc = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_upgradedesc(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.LegendUpgradeSelected.upgradeDesc")); + } else + goto handle_unusual; + continue; + // int32 level = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 48)) { + _impl_.level_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* LegendUpgradeSelected::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.LegendUpgradeSelected) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.LegendUpgradeSelected.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string upgradeName = 4; + if (!this->_internal_upgradename().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_upgradename().data(), static_cast(this->_internal_upgradename().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.LegendUpgradeSelected.upgradeName"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_upgradename(), target); + } + + // string upgradeDesc = 5; + if (!this->_internal_upgradedesc().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_upgradedesc().data(), static_cast(this->_internal_upgradedesc().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.LegendUpgradeSelected.upgradeDesc"); + target = stream->WriteStringMaybeAliased( + 5, this->_internal_upgradedesc(), target); + } + + // int32 level = 6; + if (this->_internal_level() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(6, this->_internal_level(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.LegendUpgradeSelected) + return target; +} + +size_t LegendUpgradeSelected::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.LegendUpgradeSelected) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string upgradeName = 4; + if (!this->_internal_upgradename().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_upgradename()); + } + + // string upgradeDesc = 5; + if (!this->_internal_upgradedesc().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_upgradedesc()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // int32 level = 6; + if (this->_internal_level() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_level()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData LegendUpgradeSelected::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + LegendUpgradeSelected::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*LegendUpgradeSelected::GetClassData() const { return &_class_data_; } + + +void LegendUpgradeSelected::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.LegendUpgradeSelected) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_upgradename().empty()) { + _this->_internal_set_upgradename(from._internal_upgradename()); + } + if (!from._internal_upgradedesc().empty()) { + _this->_internal_set_upgradedesc(from._internal_upgradedesc()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_level() != 0) { + _this->_internal_set_level(from._internal_level()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void LegendUpgradeSelected::CopyFrom(const LegendUpgradeSelected& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.LegendUpgradeSelected) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LegendUpgradeSelected::IsInitialized() const { + return true; +} + +void LegendUpgradeSelected::InternalSwap(LegendUpgradeSelected* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.upgradename_, lhs_arena, + &other->_impl_.upgradename_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.upgradedesc_, lhs_arena, + &other->_impl_.upgradedesc_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(LegendUpgradeSelected, _impl_.level_) + + sizeof(LegendUpgradeSelected::_impl_.level_) + - PROTOBUF_FIELD_OFFSET(LegendUpgradeSelected, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata LegendUpgradeSelected::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[37]); +} + +// =================================================================== + +class ZiplineUsed::_Internal { + public: + static const ::rtech::liveapi::Player& player(const ZiplineUsed* msg); +}; + +const ::rtech::liveapi::Player& +ZiplineUsed::_Internal::player(const ZiplineUsed* msg) { + return *msg->_impl_.player_; +} +ZiplineUsed::ZiplineUsed(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.ZiplineUsed) +} +ZiplineUsed::ZiplineUsed(const ZiplineUsed& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + ZiplineUsed* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.linkedentity_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.linkedentity_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.linkedentity_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_linkedentity().empty()) { + _this->_impl_.linkedentity_.Set(from._internal_linkedentity(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.ZiplineUsed) +} + +inline void ZiplineUsed::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.linkedentity_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.linkedentity_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.linkedentity_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ZiplineUsed::~ZiplineUsed() { + // @@protoc_insertion_point(destructor:rtech.liveapi.ZiplineUsed) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ZiplineUsed::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.linkedentity_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void ZiplineUsed::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ZiplineUsed::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.ZiplineUsed) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.linkedentity_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* ZiplineUsed::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.ZiplineUsed.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string linkedEntity = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_linkedentity(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.ZiplineUsed.linkedEntity")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ZiplineUsed::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.ZiplineUsed) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.ZiplineUsed.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string linkedEntity = 4; + if (!this->_internal_linkedentity().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_linkedentity().data(), static_cast(this->_internal_linkedentity().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.ZiplineUsed.linkedEntity"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_linkedentity(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.ZiplineUsed) + return target; +} + +size_t ZiplineUsed::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.ZiplineUsed) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string linkedEntity = 4; + if (!this->_internal_linkedentity().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_linkedentity()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData ZiplineUsed::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + ZiplineUsed::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*ZiplineUsed::GetClassData() const { return &_class_data_; } + + +void ZiplineUsed::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.ZiplineUsed) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_linkedentity().empty()) { + _this->_internal_set_linkedentity(from._internal_linkedentity()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void ZiplineUsed::CopyFrom(const ZiplineUsed& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.ZiplineUsed) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ZiplineUsed::IsInitialized() const { + return true; +} + +void ZiplineUsed::InternalSwap(ZiplineUsed* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.linkedentity_, lhs_arena, + &other->_impl_.linkedentity_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ZiplineUsed, _impl_.timestamp_) + + sizeof(ZiplineUsed::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(ZiplineUsed, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata ZiplineUsed::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[38]); +} + +// =================================================================== + +class GrenadeThrown::_Internal { + public: + static const ::rtech::liveapi::Player& player(const GrenadeThrown* msg); +}; + +const ::rtech::liveapi::Player& +GrenadeThrown::_Internal::player(const GrenadeThrown* msg) { + return *msg->_impl_.player_; +} +GrenadeThrown::GrenadeThrown(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.GrenadeThrown) +} +GrenadeThrown::GrenadeThrown(const GrenadeThrown& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + GrenadeThrown* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.linkedentity_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.linkedentity_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.linkedentity_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_linkedentity().empty()) { + _this->_impl_.linkedentity_.Set(from._internal_linkedentity(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.GrenadeThrown) +} + +inline void GrenadeThrown::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.linkedentity_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.linkedentity_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.linkedentity_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +GrenadeThrown::~GrenadeThrown() { + // @@protoc_insertion_point(destructor:rtech.liveapi.GrenadeThrown) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void GrenadeThrown::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.linkedentity_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void GrenadeThrown::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void GrenadeThrown::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.GrenadeThrown) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.linkedentity_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* GrenadeThrown::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.GrenadeThrown.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string linkedEntity = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_linkedentity(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.GrenadeThrown.linkedEntity")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* GrenadeThrown::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.GrenadeThrown) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.GrenadeThrown.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string linkedEntity = 4; + if (!this->_internal_linkedentity().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_linkedentity().data(), static_cast(this->_internal_linkedentity().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.GrenadeThrown.linkedEntity"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_linkedentity(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.GrenadeThrown) + return target; +} + +size_t GrenadeThrown::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.GrenadeThrown) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string linkedEntity = 4; + if (!this->_internal_linkedentity().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_linkedentity()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData GrenadeThrown::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + GrenadeThrown::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GrenadeThrown::GetClassData() const { return &_class_data_; } + + +void GrenadeThrown::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.GrenadeThrown) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_linkedentity().empty()) { + _this->_internal_set_linkedentity(from._internal_linkedentity()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void GrenadeThrown::CopyFrom(const GrenadeThrown& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.GrenadeThrown) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool GrenadeThrown::IsInitialized() const { + return true; +} + +void GrenadeThrown::InternalSwap(GrenadeThrown* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.linkedentity_, lhs_arena, + &other->_impl_.linkedentity_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(GrenadeThrown, _impl_.timestamp_) + + sizeof(GrenadeThrown::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(GrenadeThrown, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata GrenadeThrown::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[39]); +} + +// =================================================================== + +class BlackMarketAction::_Internal { + public: + static const ::rtech::liveapi::Player& player(const BlackMarketAction* msg); +}; + +const ::rtech::liveapi::Player& +BlackMarketAction::_Internal::player(const BlackMarketAction* msg) { + return *msg->_impl_.player_; +} +BlackMarketAction::BlackMarketAction(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.BlackMarketAction) +} +BlackMarketAction::BlackMarketAction(const BlackMarketAction& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + BlackMarketAction* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_item().empty()) { + _this->_impl_.item_.Set(from._internal_item(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.BlackMarketAction) +} + +inline void BlackMarketAction::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.item_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.item_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +BlackMarketAction::~BlackMarketAction() { + // @@protoc_insertion_point(destructor:rtech.liveapi.BlackMarketAction) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void BlackMarketAction::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.item_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void BlackMarketAction::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void BlackMarketAction::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.BlackMarketAction) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.item_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* BlackMarketAction::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.BlackMarketAction.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string item = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_item(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.BlackMarketAction.item")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* BlackMarketAction::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.BlackMarketAction) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.BlackMarketAction.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_item().data(), static_cast(this->_internal_item().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.BlackMarketAction.item"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_item(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.BlackMarketAction) + return target; +} + +size_t BlackMarketAction::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.BlackMarketAction) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string item = 4; + if (!this->_internal_item().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_item()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData BlackMarketAction::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + BlackMarketAction::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*BlackMarketAction::GetClassData() const { return &_class_data_; } + + +void BlackMarketAction::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.BlackMarketAction) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_item().empty()) { + _this->_internal_set_item(from._internal_item()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void BlackMarketAction::CopyFrom(const BlackMarketAction& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.BlackMarketAction) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool BlackMarketAction::IsInitialized() const { + return true; +} + +void BlackMarketAction::InternalSwap(BlackMarketAction* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.item_, lhs_arena, + &other->_impl_.item_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(BlackMarketAction, _impl_.timestamp_) + + sizeof(BlackMarketAction::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(BlackMarketAction, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata BlackMarketAction::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[40]); +} + +// =================================================================== + +class WraithPortal::_Internal { + public: + static const ::rtech::liveapi::Player& player(const WraithPortal* msg); +}; + +const ::rtech::liveapi::Player& +WraithPortal::_Internal::player(const WraithPortal* msg) { + return *msg->_impl_.player_; +} +WraithPortal::WraithPortal(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.WraithPortal) +} +WraithPortal::WraithPortal(const WraithPortal& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + WraithPortal* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.WraithPortal) +} + +inline void WraithPortal::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +WraithPortal::~WraithPortal() { + // @@protoc_insertion_point(destructor:rtech.liveapi.WraithPortal) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void WraithPortal::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void WraithPortal::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void WraithPortal::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.WraithPortal) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* WraithPortal::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.WraithPortal.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* WraithPortal::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.WraithPortal) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.WraithPortal.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.WraithPortal) + return target; +} + +size_t WraithPortal::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.WraithPortal) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData WraithPortal::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + WraithPortal::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*WraithPortal::GetClassData() const { return &_class_data_; } + + +void WraithPortal::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.WraithPortal) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void WraithPortal::CopyFrom(const WraithPortal& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.WraithPortal) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool WraithPortal::IsInitialized() const { + return true; +} + +void WraithPortal::InternalSwap(WraithPortal* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(WraithPortal, _impl_.timestamp_) + + sizeof(WraithPortal::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(WraithPortal, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata WraithPortal::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[41]); +} + +// =================================================================== + +class WarpGateUsed::_Internal { + public: + static const ::rtech::liveapi::Player& player(const WarpGateUsed* msg); +}; + +const ::rtech::liveapi::Player& +WarpGateUsed::_Internal::player(const WarpGateUsed* msg) { + return *msg->_impl_.player_; +} +WarpGateUsed::WarpGateUsed(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.WarpGateUsed) +} +WarpGateUsed::WarpGateUsed(const WarpGateUsed& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + WarpGateUsed* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.WarpGateUsed) +} + +inline void WarpGateUsed::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +WarpGateUsed::~WarpGateUsed() { + // @@protoc_insertion_point(destructor:rtech.liveapi.WarpGateUsed) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void WarpGateUsed::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void WarpGateUsed::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void WarpGateUsed::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.WarpGateUsed) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* WarpGateUsed::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.WarpGateUsed.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* WarpGateUsed::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.WarpGateUsed) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.WarpGateUsed.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.WarpGateUsed) + return target; +} + +size_t WarpGateUsed::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.WarpGateUsed) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData WarpGateUsed::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + WarpGateUsed::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*WarpGateUsed::GetClassData() const { return &_class_data_; } + + +void WarpGateUsed::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.WarpGateUsed) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void WarpGateUsed::CopyFrom(const WarpGateUsed& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.WarpGateUsed) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool WarpGateUsed::IsInitialized() const { + return true; +} + +void WarpGateUsed::InternalSwap(WarpGateUsed* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(WarpGateUsed, _impl_.timestamp_) + + sizeof(WarpGateUsed::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(WarpGateUsed, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata WarpGateUsed::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[42]); +} + +// =================================================================== + +class AmmoUsed::_Internal { + public: + static const ::rtech::liveapi::Player& player(const AmmoUsed* msg); +}; + +const ::rtech::liveapi::Player& +AmmoUsed::_Internal::player(const AmmoUsed* msg) { + return *msg->_impl_.player_; +} +AmmoUsed::AmmoUsed(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.AmmoUsed) +} +AmmoUsed::AmmoUsed(const AmmoUsed& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + AmmoUsed* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.ammotype_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.amountused_){} + , decltype(_impl_.oldammocount_){} + , decltype(_impl_.newammocount_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.ammotype_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.ammotype_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_ammotype().empty()) { + _this->_impl_.ammotype_.Set(from._internal_ammotype(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.newammocount_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.newammocount_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.AmmoUsed) +} + +inline void AmmoUsed::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.ammotype_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.amountused_){0u} + , decltype(_impl_.oldammocount_){0u} + , decltype(_impl_.newammocount_){0u} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.ammotype_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.ammotype_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +AmmoUsed::~AmmoUsed() { + // @@protoc_insertion_point(destructor:rtech.liveapi.AmmoUsed) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void AmmoUsed::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.ammotype_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void AmmoUsed::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void AmmoUsed::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.AmmoUsed) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.ammotype_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.newammocount_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.newammocount_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* AmmoUsed::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.AmmoUsed.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string ammoType = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_ammotype(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.AmmoUsed.ammoType")); + } else + goto handle_unusual; + continue; + // uint32 amountUsed = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _impl_.amountused_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // uint32 oldAmmoCount = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 48)) { + _impl_.oldammocount_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // uint32 newAmmoCount = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 56)) { + _impl_.newammocount_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* AmmoUsed::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.AmmoUsed) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.AmmoUsed.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string ammoType = 4; + if (!this->_internal_ammotype().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_ammotype().data(), static_cast(this->_internal_ammotype().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.AmmoUsed.ammoType"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_ammotype(), target); + } + + // uint32 amountUsed = 5; + if (this->_internal_amountused() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(5, this->_internal_amountused(), target); + } + + // uint32 oldAmmoCount = 6; + if (this->_internal_oldammocount() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(6, this->_internal_oldammocount(), target); + } + + // uint32 newAmmoCount = 7; + if (this->_internal_newammocount() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(7, this->_internal_newammocount(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.AmmoUsed) + return target; +} + +size_t AmmoUsed::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.AmmoUsed) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string ammoType = 4; + if (!this->_internal_ammotype().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_ammotype()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // uint32 amountUsed = 5; + if (this->_internal_amountused() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_amountused()); + } + + // uint32 oldAmmoCount = 6; + if (this->_internal_oldammocount() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_oldammocount()); + } + + // uint32 newAmmoCount = 7; + if (this->_internal_newammocount() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_newammocount()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData AmmoUsed::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + AmmoUsed::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*AmmoUsed::GetClassData() const { return &_class_data_; } + + +void AmmoUsed::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.AmmoUsed) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_ammotype().empty()) { + _this->_internal_set_ammotype(from._internal_ammotype()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + if (from._internal_amountused() != 0) { + _this->_internal_set_amountused(from._internal_amountused()); + } + if (from._internal_oldammocount() != 0) { + _this->_internal_set_oldammocount(from._internal_oldammocount()); + } + if (from._internal_newammocount() != 0) { + _this->_internal_set_newammocount(from._internal_newammocount()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void AmmoUsed::CopyFrom(const AmmoUsed& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.AmmoUsed) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool AmmoUsed::IsInitialized() const { + return true; +} + +void AmmoUsed::InternalSwap(AmmoUsed* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.ammotype_, lhs_arena, + &other->_impl_.ammotype_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(AmmoUsed, _impl_.newammocount_) + + sizeof(AmmoUsed::_impl_.newammocount_) + - PROTOBUF_FIELD_OFFSET(AmmoUsed, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata AmmoUsed::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[43]); +} + +// =================================================================== + +class WeaponSwitched::_Internal { + public: + static const ::rtech::liveapi::Player& player(const WeaponSwitched* msg); +}; + +const ::rtech::liveapi::Player& +WeaponSwitched::_Internal::player(const WeaponSwitched* msg) { + return *msg->_impl_.player_; +} +WeaponSwitched::WeaponSwitched(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.WeaponSwitched) +} +WeaponSwitched::WeaponSwitched(const WeaponSwitched& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + WeaponSwitched* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.oldweapon_){} + , decltype(_impl_.newweapon_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.oldweapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.oldweapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_oldweapon().empty()) { + _this->_impl_.oldweapon_.Set(from._internal_oldweapon(), + _this->GetArenaForAllocation()); + } + _impl_.newweapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.newweapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_newweapon().empty()) { + _this->_impl_.newweapon_.Set(from._internal_newweapon(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_player()) { + _this->_impl_.player_ = new ::rtech::liveapi::Player(*from._impl_.player_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.WeaponSwitched) +} + +inline void WeaponSwitched::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.oldweapon_){} + , decltype(_impl_.newweapon_){} + , decltype(_impl_.player_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.oldweapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.oldweapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.newweapon_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.newweapon_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +WeaponSwitched::~WeaponSwitched() { + // @@protoc_insertion_point(destructor:rtech.liveapi.WeaponSwitched) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void WeaponSwitched::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.oldweapon_.Destroy(); + _impl_.newweapon_.Destroy(); + if (this != internal_default_instance()) delete _impl_.player_; +} + +void WeaponSwitched::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void WeaponSwitched::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.WeaponSwitched) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.oldweapon_.ClearToEmpty(); + _impl_.newweapon_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* WeaponSwitched::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.WeaponSwitched.category")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.Player player = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_player(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string oldWeapon = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_oldweapon(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.WeaponSwitched.oldWeapon")); + } else + goto handle_unusual; + continue; + // string newWeapon = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_newweapon(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.WeaponSwitched.newWeapon")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* WeaponSwitched::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.WeaponSwitched) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.WeaponSwitched.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::player(this), + _Internal::player(this).GetCachedSize(), target, stream); + } + + // string oldWeapon = 4; + if (!this->_internal_oldweapon().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_oldweapon().data(), static_cast(this->_internal_oldweapon().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.WeaponSwitched.oldWeapon"); + target = stream->WriteStringMaybeAliased( + 4, this->_internal_oldweapon(), target); + } + + // string newWeapon = 5; + if (!this->_internal_newweapon().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_newweapon().data(), static_cast(this->_internal_newweapon().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.WeaponSwitched.newWeapon"); + target = stream->WriteStringMaybeAliased( + 5, this->_internal_newweapon(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.WeaponSwitched) + return target; +} + +size_t WeaponSwitched::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.WeaponSwitched) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string oldWeapon = 4; + if (!this->_internal_oldweapon().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_oldweapon()); + } + + // string newWeapon = 5; + if (!this->_internal_newweapon().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_newweapon()); + } + + // .rtech.liveapi.Player player = 3; + if (this->_internal_has_player()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.player_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData WeaponSwitched::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + WeaponSwitched::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*WeaponSwitched::GetClassData() const { return &_class_data_; } + + +void WeaponSwitched::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.WeaponSwitched) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_oldweapon().empty()) { + _this->_internal_set_oldweapon(from._internal_oldweapon()); + } + if (!from._internal_newweapon().empty()) { + _this->_internal_set_newweapon(from._internal_newweapon()); + } + if (from._internal_has_player()) { + _this->_internal_mutable_player()->::rtech::liveapi::Player::MergeFrom( + from._internal_player()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void WeaponSwitched::CopyFrom(const WeaponSwitched& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.WeaponSwitched) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool WeaponSwitched::IsInitialized() const { + return true; +} + +void WeaponSwitched::InternalSwap(WeaponSwitched* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.oldweapon_, lhs_arena, + &other->_impl_.oldweapon_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.newweapon_, lhs_arena, + &other->_impl_.newweapon_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(WeaponSwitched, _impl_.timestamp_) + + sizeof(WeaponSwitched::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(WeaponSwitched, _impl_.player_)>( + reinterpret_cast(&_impl_.player_), + reinterpret_cast(&other->_impl_.player_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata WeaponSwitched::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[44]); +} + +// =================================================================== + +class CustomEvent::_Internal { + public: + static const ::PROTOBUF_NAMESPACE_ID::Struct& data(const CustomEvent* msg); +}; + +const ::PROTOBUF_NAMESPACE_ID::Struct& +CustomEvent::_Internal::data(const CustomEvent* msg) { + return *msg->_impl_.data_; +} +void CustomEvent::clear_data() { + if (GetArenaForAllocation() == nullptr && _impl_.data_ != nullptr) { + delete _impl_.data_; + } + _impl_.data_ = nullptr; +} +CustomEvent::CustomEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomEvent) +} +CustomEvent::CustomEvent(const CustomEvent& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomEvent* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.name_){} + , decltype(_impl_.data_){nullptr} + , decltype(_impl_.timestamp_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_category().empty()) { + _this->_impl_.category_.Set(from._internal_category(), + _this->GetArenaForAllocation()); + } + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_name().empty()) { + _this->_impl_.name_.Set(from._internal_name(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_data()) { + _this->_impl_.data_ = new ::PROTOBUF_NAMESPACE_ID::Struct(*from._impl_.data_); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomEvent) +} + +inline void CustomEvent::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.category_){} + , decltype(_impl_.name_){} + , decltype(_impl_.data_){nullptr} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.category_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.category_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CustomEvent::~CustomEvent() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomEvent) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomEvent::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.category_.Destroy(); + _impl_.name_.Destroy(); + if (this != internal_default_instance()) delete _impl_.data_; +} + +void CustomEvent::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomEvent::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomEvent) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.category_.ClearToEmpty(); + _impl_.name_.ClearToEmpty(); + if (GetArenaForAllocation() == nullptr && _impl_.data_ != nullptr) { + delete _impl_.data_; + } + _impl_.data_ = nullptr; + _impl_.timestamp_ = uint64_t{0u}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomEvent::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string category = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_category(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomEvent.category")); + } else + goto handle_unusual; + continue; + // string name = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_name(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomEvent.name")); + } else + goto handle_unusual; + continue; + // .google.protobuf.Struct data = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_data(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomEvent::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomEvent) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // string category = 2; + if (!this->_internal_category().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_category().data(), static_cast(this->_internal_category().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomEvent.category"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_category(), target); + } + + // string name = 3; + if (!this->_internal_name().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_name().data(), static_cast(this->_internal_name().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomEvent.name"); + target = stream->WriteStringMaybeAliased( + 3, this->_internal_name(), target); + } + + // .google.protobuf.Struct data = 4; + if (this->_internal_has_data()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::data(this), + _Internal::data(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomEvent) + return target; +} + +size_t CustomEvent::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomEvent) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string category = 2; + if (!this->_internal_category().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_category()); + } + + // string name = 3; + if (!this->_internal_name().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + // .google.protobuf.Struct data = 4; + if (this->_internal_has_data()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.data_); + } + + // uint64 timestamp = 1; + if (this->_internal_timestamp() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomEvent::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomEvent::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomEvent::GetClassData() const { return &_class_data_; } + + +void CustomEvent::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomEvent) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_category().empty()) { + _this->_internal_set_category(from._internal_category()); + } + if (!from._internal_name().empty()) { + _this->_internal_set_name(from._internal_name()); + } + if (from._internal_has_data()) { + _this->_internal_mutable_data()->::PROTOBUF_NAMESPACE_ID::Struct::MergeFrom( + from._internal_data()); + } + if (from._internal_timestamp() != 0) { + _this->_internal_set_timestamp(from._internal_timestamp()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomEvent::CopyFrom(const CustomEvent& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomEvent) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomEvent::IsInitialized() const { + return true; +} + +void CustomEvent::InternalSwap(CustomEvent* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.category_, lhs_arena, + &other->_impl_.category_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.name_, lhs_arena, + &other->_impl_.name_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(CustomEvent, _impl_.timestamp_) + + sizeof(CustomEvent::_impl_.timestamp_) + - PROTOBUF_FIELD_OFFSET(CustomEvent, _impl_.data_)>( + reinterpret_cast(&_impl_.data_), + reinterpret_cast(&other->_impl_.data_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomEvent::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[45]); +} + +// =================================================================== + +class ChangeCamera::_Internal { + public: +}; + +ChangeCamera::ChangeCamera(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.ChangeCamera) +} +ChangeCamera::ChangeCamera(const ChangeCamera& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + ChangeCamera* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.target_){} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_._oneof_case_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + clear_has_target(); + switch (from.target_case()) { + case kPoi: { + _this->_internal_set_poi(from._internal_poi()); + break; + } + case kName: { + _this->_internal_set_name(from._internal_name()); + break; + } + case TARGET_NOT_SET: { + break; + } + } + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.ChangeCamera) +} + +inline void ChangeCamera::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.target_){} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_._oneof_case_)*/{} + }; + clear_has_target(); +} + +ChangeCamera::~ChangeCamera() { + // @@protoc_insertion_point(destructor:rtech.liveapi.ChangeCamera) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ChangeCamera::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + if (has_target()) { + clear_target(); + } +} + +void ChangeCamera::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ChangeCamera::clear_target() { +// @@protoc_insertion_point(one_of_clear_start:rtech.liveapi.ChangeCamera) + switch (target_case()) { + case kPoi: { + // No need to clear + break; + } + case kName: { + _impl_.target_.name_.Destroy(); + break; + } + case TARGET_NOT_SET: { + break; + } + } + _impl_._oneof_case_[0] = TARGET_NOT_SET; +} + + +void ChangeCamera::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.ChangeCamera) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + clear_target(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* ChangeCamera::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // .rtech.liveapi.PlayerOfInterest poi = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + _internal_set_poi(static_cast<::rtech::liveapi::PlayerOfInterest>(val)); + } else + goto handle_unusual; + continue; + // string name = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_name(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.ChangeCamera.name")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ChangeCamera::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.ChangeCamera) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // .rtech.liveapi.PlayerOfInterest poi = 1; + if (_internal_has_poi()) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_poi(), target); + } + + // string name = 2; + if (_internal_has_name()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_name().data(), static_cast(this->_internal_name().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.ChangeCamera.name"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_name(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.ChangeCamera) + return target; +} + +size_t ChangeCamera::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.ChangeCamera) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + switch (target_case()) { + // .rtech.liveapi.PlayerOfInterest poi = 1; + case kPoi: { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_poi()); + break; + } + // string name = 2; + case kName: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + break; + } + case TARGET_NOT_SET: { + break; + } + } + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData ChangeCamera::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + ChangeCamera::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*ChangeCamera::GetClassData() const { return &_class_data_; } + + +void ChangeCamera::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.ChangeCamera) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + switch (from.target_case()) { + case kPoi: { + _this->_internal_set_poi(from._internal_poi()); + break; + } + case kName: { + _this->_internal_set_name(from._internal_name()); + break; + } + case TARGET_NOT_SET: { + break; + } + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void ChangeCamera::CopyFrom(const ChangeCamera& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.ChangeCamera) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ChangeCamera::IsInitialized() const { + return true; +} + +void ChangeCamera::InternalSwap(ChangeCamera* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_.target_, other->_impl_.target_); + swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]); +} + +::PROTOBUF_NAMESPACE_ID::Metadata ChangeCamera::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[46]); +} + +// =================================================================== + +class PauseToggle::_Internal { + public: +}; + +PauseToggle::PauseToggle(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.PauseToggle) +} +PauseToggle::PauseToggle(const PauseToggle& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + PauseToggle* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.pretimer_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _this->_impl_.pretimer_ = from._impl_.pretimer_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.PauseToggle) +} + +inline void PauseToggle::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.pretimer_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +PauseToggle::~PauseToggle() { + // @@protoc_insertion_point(destructor:rtech.liveapi.PauseToggle) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void PauseToggle::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); +} + +void PauseToggle::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void PauseToggle::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.PauseToggle) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.pretimer_ = 0; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* PauseToggle::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // float preTimer = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 13)) { + _impl_.pretimer_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* PauseToggle::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.PauseToggle) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // float preTimer = 1; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_pretimer = this->_internal_pretimer(); + uint32_t raw_pretimer; + memcpy(&raw_pretimer, &tmp_pretimer, sizeof(tmp_pretimer)); + if (raw_pretimer != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray(1, this->_internal_pretimer(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.PauseToggle) + return target; +} + +size_t PauseToggle::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.PauseToggle) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // float preTimer = 1; + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_pretimer = this->_internal_pretimer(); + uint32_t raw_pretimer; + memcpy(&raw_pretimer, &tmp_pretimer, sizeof(tmp_pretimer)); + if (raw_pretimer != 0) { + total_size += 1 + 4; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData PauseToggle::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + PauseToggle::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*PauseToggle::GetClassData() const { return &_class_data_; } + + +void PauseToggle::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.PauseToggle) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + static_assert(sizeof(uint32_t) == sizeof(float), "Code assumes uint32_t and float are the same size."); + float tmp_pretimer = from._internal_pretimer(); + uint32_t raw_pretimer; + memcpy(&raw_pretimer, &tmp_pretimer, sizeof(tmp_pretimer)); + if (raw_pretimer != 0) { + _this->_internal_set_pretimer(from._internal_pretimer()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void PauseToggle::CopyFrom(const PauseToggle& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.PauseToggle) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool PauseToggle::IsInitialized() const { + return true; +} + +void PauseToggle::InternalSwap(PauseToggle* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_.pretimer_, other->_impl_.pretimer_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata PauseToggle::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[47]); +} + +// =================================================================== + +class CustomMatch_CreateLobby::_Internal { + public: +}; + +CustomMatch_CreateLobby::CustomMatch_CreateLobby(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase(arena, is_message_owned) { + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_CreateLobby) +} +CustomMatch_CreateLobby::CustomMatch_CreateLobby(const CustomMatch_CreateLobby& from) + : ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase() { + CustomMatch_CreateLobby* const _this = this; (void)_this; + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_CreateLobby) +} + + + + + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_CreateLobby::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyImpl, + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeImpl, +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_CreateLobby::GetClassData() const { return &_class_data_; } + + + + + + + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_CreateLobby::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[48]); +} + +// =================================================================== + +class CustomMatch_JoinLobby::_Internal { + public: +}; + +CustomMatch_JoinLobby::CustomMatch_JoinLobby(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_JoinLobby) +} +CustomMatch_JoinLobby::CustomMatch_JoinLobby(const CustomMatch_JoinLobby& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomMatch_JoinLobby* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.roletoken_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.roletoken_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.roletoken_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_roletoken().empty()) { + _this->_impl_.roletoken_.Set(from._internal_roletoken(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_JoinLobby) +} + +inline void CustomMatch_JoinLobby::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.roletoken_){} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.roletoken_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.roletoken_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CustomMatch_JoinLobby::~CustomMatch_JoinLobby() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomMatch_JoinLobby) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomMatch_JoinLobby::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.roletoken_.Destroy(); +} + +void CustomMatch_JoinLobby::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomMatch_JoinLobby::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomMatch_JoinLobby) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.roletoken_.ClearToEmpty(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomMatch_JoinLobby::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string roleToken = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_roletoken(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_JoinLobby.roleToken")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomMatch_JoinLobby::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomMatch_JoinLobby) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string roleToken = 1; + if (!this->_internal_roletoken().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_roletoken().data(), static_cast(this->_internal_roletoken().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_JoinLobby.roleToken"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_roletoken(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomMatch_JoinLobby) + return target; +} + +size_t CustomMatch_JoinLobby::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomMatch_JoinLobby) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string roleToken = 1; + if (!this->_internal_roletoken().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_roletoken()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_JoinLobby::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomMatch_JoinLobby::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_JoinLobby::GetClassData() const { return &_class_data_; } + + +void CustomMatch_JoinLobby::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomMatch_JoinLobby) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_roletoken().empty()) { + _this->_internal_set_roletoken(from._internal_roletoken()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomMatch_JoinLobby::CopyFrom(const CustomMatch_JoinLobby& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomMatch_JoinLobby) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomMatch_JoinLobby::IsInitialized() const { + return true; +} + +void CustomMatch_JoinLobby::InternalSwap(CustomMatch_JoinLobby* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.roletoken_, lhs_arena, + &other->_impl_.roletoken_, rhs_arena + ); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_JoinLobby::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[49]); +} + +// =================================================================== + +class CustomMatch_LeaveLobby::_Internal { + public: +}; + +CustomMatch_LeaveLobby::CustomMatch_LeaveLobby(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase(arena, is_message_owned) { + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_LeaveLobby) +} +CustomMatch_LeaveLobby::CustomMatch_LeaveLobby(const CustomMatch_LeaveLobby& from) + : ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase() { + CustomMatch_LeaveLobby* const _this = this; (void)_this; + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_LeaveLobby) +} + + + + + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_LeaveLobby::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyImpl, + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeImpl, +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_LeaveLobby::GetClassData() const { return &_class_data_; } + + + + + + + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_LeaveLobby::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[50]); +} + +// =================================================================== + +class CustomMatch_SetReady::_Internal { + public: +}; + +CustomMatch_SetReady::CustomMatch_SetReady(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_SetReady) +} +CustomMatch_SetReady::CustomMatch_SetReady(const CustomMatch_SetReady& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomMatch_SetReady* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.isready_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _this->_impl_.isready_ = from._impl_.isready_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_SetReady) +} + +inline void CustomMatch_SetReady::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.isready_){false} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +CustomMatch_SetReady::~CustomMatch_SetReady() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomMatch_SetReady) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomMatch_SetReady::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); +} + +void CustomMatch_SetReady::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomMatch_SetReady::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomMatch_SetReady) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.isready_ = false; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomMatch_SetReady::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // bool isReady = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.isready_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomMatch_SetReady::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomMatch_SetReady) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // bool isReady = 1; + if (this->_internal_isready() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(1, this->_internal_isready(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomMatch_SetReady) + return target; +} + +size_t CustomMatch_SetReady::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomMatch_SetReady) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // bool isReady = 1; + if (this->_internal_isready() != 0) { + total_size += 1 + 1; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_SetReady::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomMatch_SetReady::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_SetReady::GetClassData() const { return &_class_data_; } + + +void CustomMatch_SetReady::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomMatch_SetReady) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (from._internal_isready() != 0) { + _this->_internal_set_isready(from._internal_isready()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomMatch_SetReady::CopyFrom(const CustomMatch_SetReady& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomMatch_SetReady) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomMatch_SetReady::IsInitialized() const { + return true; +} + +void CustomMatch_SetReady::InternalSwap(CustomMatch_SetReady* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_.isready_, other->_impl_.isready_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_SetReady::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[51]); +} + +// =================================================================== + +class CustomMatch_GetLobbyPlayers::_Internal { + public: +}; + +CustomMatch_GetLobbyPlayers::CustomMatch_GetLobbyPlayers(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase(arena, is_message_owned) { + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_GetLobbyPlayers) +} +CustomMatch_GetLobbyPlayers::CustomMatch_GetLobbyPlayers(const CustomMatch_GetLobbyPlayers& from) + : ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase() { + CustomMatch_GetLobbyPlayers* const _this = this; (void)_this; + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_GetLobbyPlayers) +} + + + + + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_GetLobbyPlayers::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyImpl, + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeImpl, +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_GetLobbyPlayers::GetClassData() const { return &_class_data_; } + + + + + + + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_GetLobbyPlayers::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[52]); +} + +// =================================================================== + +class CustomMatch_SetMatchmaking::_Internal { + public: +}; + +CustomMatch_SetMatchmaking::CustomMatch_SetMatchmaking(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_SetMatchmaking) +} +CustomMatch_SetMatchmaking::CustomMatch_SetMatchmaking(const CustomMatch_SetMatchmaking& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomMatch_SetMatchmaking* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.enabled_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _this->_impl_.enabled_ = from._impl_.enabled_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_SetMatchmaking) +} + +inline void CustomMatch_SetMatchmaking::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.enabled_){false} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +CustomMatch_SetMatchmaking::~CustomMatch_SetMatchmaking() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomMatch_SetMatchmaking) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomMatch_SetMatchmaking::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); +} + +void CustomMatch_SetMatchmaking::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomMatch_SetMatchmaking::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomMatch_SetMatchmaking) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.enabled_ = false; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomMatch_SetMatchmaking::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // bool enabled = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.enabled_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomMatch_SetMatchmaking::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomMatch_SetMatchmaking) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // bool enabled = 1; + if (this->_internal_enabled() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(1, this->_internal_enabled(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomMatch_SetMatchmaking) + return target; +} + +size_t CustomMatch_SetMatchmaking::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomMatch_SetMatchmaking) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // bool enabled = 1; + if (this->_internal_enabled() != 0) { + total_size += 1 + 1; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_SetMatchmaking::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomMatch_SetMatchmaking::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_SetMatchmaking::GetClassData() const { return &_class_data_; } + + +void CustomMatch_SetMatchmaking::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomMatch_SetMatchmaking) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (from._internal_enabled() != 0) { + _this->_internal_set_enabled(from._internal_enabled()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomMatch_SetMatchmaking::CopyFrom(const CustomMatch_SetMatchmaking& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomMatch_SetMatchmaking) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomMatch_SetMatchmaking::IsInitialized() const { + return true; +} + +void CustomMatch_SetMatchmaking::InternalSwap(CustomMatch_SetMatchmaking* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_.enabled_, other->_impl_.enabled_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_SetMatchmaking::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[53]); +} + +// =================================================================== + +class CustomMatch_SetTeam::_Internal { + public: +}; + +CustomMatch_SetTeam::CustomMatch_SetTeam(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_SetTeam) +} +CustomMatch_SetTeam::CustomMatch_SetTeam(const CustomMatch_SetTeam& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomMatch_SetTeam* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.targethardwarename_){} + , decltype(_impl_.targetnucleushash_){} + , decltype(_impl_.teamid_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.targethardwarename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.targethardwarename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_targethardwarename().empty()) { + _this->_impl_.targethardwarename_.Set(from._internal_targethardwarename(), + _this->GetArenaForAllocation()); + } + _impl_.targetnucleushash_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.targetnucleushash_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_targetnucleushash().empty()) { + _this->_impl_.targetnucleushash_.Set(from._internal_targetnucleushash(), + _this->GetArenaForAllocation()); + } + _this->_impl_.teamid_ = from._impl_.teamid_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_SetTeam) +} + +inline void CustomMatch_SetTeam::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.targethardwarename_){} + , decltype(_impl_.targetnucleushash_){} + , decltype(_impl_.teamid_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.targethardwarename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.targethardwarename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.targetnucleushash_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.targetnucleushash_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CustomMatch_SetTeam::~CustomMatch_SetTeam() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomMatch_SetTeam) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomMatch_SetTeam::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.targethardwarename_.Destroy(); + _impl_.targetnucleushash_.Destroy(); +} + +void CustomMatch_SetTeam::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomMatch_SetTeam::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomMatch_SetTeam) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.targethardwarename_.ClearToEmpty(); + _impl_.targetnucleushash_.ClearToEmpty(); + _impl_.teamid_ = 0; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomMatch_SetTeam::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // int32 teamId = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.teamid_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string targetHardwareName = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_targethardwarename(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_SetTeam.targetHardwareName")); + } else + goto handle_unusual; + continue; + // string targetNucleusHash = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_targetnucleushash(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_SetTeam.targetNucleusHash")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomMatch_SetTeam::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomMatch_SetTeam) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // int32 teamId = 1; + if (this->_internal_teamid() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(1, this->_internal_teamid(), target); + } + + // string targetHardwareName = 2; + if (!this->_internal_targethardwarename().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_targethardwarename().data(), static_cast(this->_internal_targethardwarename().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_SetTeam.targetHardwareName"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_targethardwarename(), target); + } + + // string targetNucleusHash = 3; + if (!this->_internal_targetnucleushash().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_targetnucleushash().data(), static_cast(this->_internal_targetnucleushash().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_SetTeam.targetNucleusHash"); + target = stream->WriteStringMaybeAliased( + 3, this->_internal_targetnucleushash(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomMatch_SetTeam) + return target; +} + +size_t CustomMatch_SetTeam::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomMatch_SetTeam) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string targetHardwareName = 2; + if (!this->_internal_targethardwarename().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_targethardwarename()); + } + + // string targetNucleusHash = 3; + if (!this->_internal_targetnucleushash().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_targetnucleushash()); + } + + // int32 teamId = 1; + if (this->_internal_teamid() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_teamid()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_SetTeam::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomMatch_SetTeam::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_SetTeam::GetClassData() const { return &_class_data_; } + + +void CustomMatch_SetTeam::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomMatch_SetTeam) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_targethardwarename().empty()) { + _this->_internal_set_targethardwarename(from._internal_targethardwarename()); + } + if (!from._internal_targetnucleushash().empty()) { + _this->_internal_set_targetnucleushash(from._internal_targetnucleushash()); + } + if (from._internal_teamid() != 0) { + _this->_internal_set_teamid(from._internal_teamid()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomMatch_SetTeam::CopyFrom(const CustomMatch_SetTeam& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomMatch_SetTeam) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomMatch_SetTeam::IsInitialized() const { + return true; +} + +void CustomMatch_SetTeam::InternalSwap(CustomMatch_SetTeam* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.targethardwarename_, lhs_arena, + &other->_impl_.targethardwarename_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.targetnucleushash_, lhs_arena, + &other->_impl_.targetnucleushash_, rhs_arena + ); + swap(_impl_.teamid_, other->_impl_.teamid_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_SetTeam::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[54]); +} + +// =================================================================== + +class CustomMatch_KickPlayer::_Internal { + public: +}; + +CustomMatch_KickPlayer::CustomMatch_KickPlayer(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_KickPlayer) +} +CustomMatch_KickPlayer::CustomMatch_KickPlayer(const CustomMatch_KickPlayer& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomMatch_KickPlayer* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.targethardwarename_){} + , decltype(_impl_.targetnucleushash_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.targethardwarename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.targethardwarename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_targethardwarename().empty()) { + _this->_impl_.targethardwarename_.Set(from._internal_targethardwarename(), + _this->GetArenaForAllocation()); + } + _impl_.targetnucleushash_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.targetnucleushash_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_targetnucleushash().empty()) { + _this->_impl_.targetnucleushash_.Set(from._internal_targetnucleushash(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_KickPlayer) +} + +inline void CustomMatch_KickPlayer::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.targethardwarename_){} + , decltype(_impl_.targetnucleushash_){} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.targethardwarename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.targethardwarename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.targetnucleushash_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.targetnucleushash_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CustomMatch_KickPlayer::~CustomMatch_KickPlayer() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomMatch_KickPlayer) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomMatch_KickPlayer::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.targethardwarename_.Destroy(); + _impl_.targetnucleushash_.Destroy(); +} + +void CustomMatch_KickPlayer::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomMatch_KickPlayer::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomMatch_KickPlayer) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.targethardwarename_.ClearToEmpty(); + _impl_.targetnucleushash_.ClearToEmpty(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomMatch_KickPlayer::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string targetHardwareName = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_targethardwarename(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_KickPlayer.targetHardwareName")); + } else + goto handle_unusual; + continue; + // string targetNucleusHash = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_targetnucleushash(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_KickPlayer.targetNucleusHash")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomMatch_KickPlayer::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomMatch_KickPlayer) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string targetHardwareName = 1; + if (!this->_internal_targethardwarename().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_targethardwarename().data(), static_cast(this->_internal_targethardwarename().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_KickPlayer.targetHardwareName"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_targethardwarename(), target); + } + + // string targetNucleusHash = 2; + if (!this->_internal_targetnucleushash().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_targetnucleushash().data(), static_cast(this->_internal_targetnucleushash().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_KickPlayer.targetNucleusHash"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_targetnucleushash(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomMatch_KickPlayer) + return target; +} + +size_t CustomMatch_KickPlayer::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomMatch_KickPlayer) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string targetHardwareName = 1; + if (!this->_internal_targethardwarename().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_targethardwarename()); + } + + // string targetNucleusHash = 2; + if (!this->_internal_targetnucleushash().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_targetnucleushash()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_KickPlayer::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomMatch_KickPlayer::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_KickPlayer::GetClassData() const { return &_class_data_; } + + +void CustomMatch_KickPlayer::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomMatch_KickPlayer) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_targethardwarename().empty()) { + _this->_internal_set_targethardwarename(from._internal_targethardwarename()); + } + if (!from._internal_targetnucleushash().empty()) { + _this->_internal_set_targetnucleushash(from._internal_targetnucleushash()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomMatch_KickPlayer::CopyFrom(const CustomMatch_KickPlayer& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomMatch_KickPlayer) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomMatch_KickPlayer::IsInitialized() const { + return true; +} + +void CustomMatch_KickPlayer::InternalSwap(CustomMatch_KickPlayer* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.targethardwarename_, lhs_arena, + &other->_impl_.targethardwarename_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.targetnucleushash_, lhs_arena, + &other->_impl_.targetnucleushash_, rhs_arena + ); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_KickPlayer::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[55]); +} + +// =================================================================== + +class CustomMatch_SetSettings::_Internal { + public: +}; + +CustomMatch_SetSettings::CustomMatch_SetSettings(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_SetSettings) +} +CustomMatch_SetSettings::CustomMatch_SetSettings(const CustomMatch_SetSettings& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomMatch_SetSettings* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.playlistname_){} + , decltype(_impl_.adminchat_){} + , decltype(_impl_.teamrename_){} + , decltype(_impl_.selfassign_){} + , decltype(_impl_.aimassist_){} + , decltype(_impl_.anonmode_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.playlistname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.playlistname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_playlistname().empty()) { + _this->_impl_.playlistname_.Set(from._internal_playlistname(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.adminchat_, &from._impl_.adminchat_, + static_cast(reinterpret_cast(&_impl_.anonmode_) - + reinterpret_cast(&_impl_.adminchat_)) + sizeof(_impl_.anonmode_)); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_SetSettings) +} + +inline void CustomMatch_SetSettings::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.playlistname_){} + , decltype(_impl_.adminchat_){false} + , decltype(_impl_.teamrename_){false} + , decltype(_impl_.selfassign_){false} + , decltype(_impl_.aimassist_){false} + , decltype(_impl_.anonmode_){false} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.playlistname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.playlistname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CustomMatch_SetSettings::~CustomMatch_SetSettings() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomMatch_SetSettings) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomMatch_SetSettings::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.playlistname_.Destroy(); +} + +void CustomMatch_SetSettings::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomMatch_SetSettings::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomMatch_SetSettings) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.playlistname_.ClearToEmpty(); + ::memset(&_impl_.adminchat_, 0, static_cast( + reinterpret_cast(&_impl_.anonmode_) - + reinterpret_cast(&_impl_.adminchat_)) + sizeof(_impl_.anonmode_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomMatch_SetSettings::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string playlistName = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_playlistname(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_SetSettings.playlistName")); + } else + goto handle_unusual; + continue; + // bool adminChat = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 16)) { + _impl_.adminchat_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // bool teamRename = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 24)) { + _impl_.teamrename_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // bool selfAssign = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 32)) { + _impl_.selfassign_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // bool aimAssist = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _impl_.aimassist_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // bool anonMode = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 48)) { + _impl_.anonmode_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomMatch_SetSettings::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomMatch_SetSettings) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string playlistName = 1; + if (!this->_internal_playlistname().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_playlistname().data(), static_cast(this->_internal_playlistname().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_SetSettings.playlistName"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_playlistname(), target); + } + + // bool adminChat = 2; + if (this->_internal_adminchat() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(2, this->_internal_adminchat(), target); + } + + // bool teamRename = 3; + if (this->_internal_teamrename() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(3, this->_internal_teamrename(), target); + } + + // bool selfAssign = 4; + if (this->_internal_selfassign() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(4, this->_internal_selfassign(), target); + } + + // bool aimAssist = 5; + if (this->_internal_aimassist() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(5, this->_internal_aimassist(), target); + } + + // bool anonMode = 6; + if (this->_internal_anonmode() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(6, this->_internal_anonmode(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomMatch_SetSettings) + return target; +} + +size_t CustomMatch_SetSettings::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomMatch_SetSettings) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string playlistName = 1; + if (!this->_internal_playlistname().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_playlistname()); + } + + // bool adminChat = 2; + if (this->_internal_adminchat() != 0) { + total_size += 1 + 1; + } + + // bool teamRename = 3; + if (this->_internal_teamrename() != 0) { + total_size += 1 + 1; + } + + // bool selfAssign = 4; + if (this->_internal_selfassign() != 0) { + total_size += 1 + 1; + } + + // bool aimAssist = 5; + if (this->_internal_aimassist() != 0) { + total_size += 1 + 1; + } + + // bool anonMode = 6; + if (this->_internal_anonmode() != 0) { + total_size += 1 + 1; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_SetSettings::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomMatch_SetSettings::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_SetSettings::GetClassData() const { return &_class_data_; } + + +void CustomMatch_SetSettings::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomMatch_SetSettings) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_playlistname().empty()) { + _this->_internal_set_playlistname(from._internal_playlistname()); + } + if (from._internal_adminchat() != 0) { + _this->_internal_set_adminchat(from._internal_adminchat()); + } + if (from._internal_teamrename() != 0) { + _this->_internal_set_teamrename(from._internal_teamrename()); + } + if (from._internal_selfassign() != 0) { + _this->_internal_set_selfassign(from._internal_selfassign()); + } + if (from._internal_aimassist() != 0) { + _this->_internal_set_aimassist(from._internal_aimassist()); + } + if (from._internal_anonmode() != 0) { + _this->_internal_set_anonmode(from._internal_anonmode()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomMatch_SetSettings::CopyFrom(const CustomMatch_SetSettings& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomMatch_SetSettings) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomMatch_SetSettings::IsInitialized() const { + return true; +} + +void CustomMatch_SetSettings::InternalSwap(CustomMatch_SetSettings* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.playlistname_, lhs_arena, + &other->_impl_.playlistname_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(CustomMatch_SetSettings, _impl_.anonmode_) + + sizeof(CustomMatch_SetSettings::_impl_.anonmode_) + - PROTOBUF_FIELD_OFFSET(CustomMatch_SetSettings, _impl_.adminchat_)>( + reinterpret_cast(&_impl_.adminchat_), + reinterpret_cast(&other->_impl_.adminchat_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_SetSettings::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[56]); +} + +// =================================================================== + +class CustomMatch_GetSettings::_Internal { + public: +}; + +CustomMatch_GetSettings::CustomMatch_GetSettings(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase(arena, is_message_owned) { + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_GetSettings) +} +CustomMatch_GetSettings::CustomMatch_GetSettings(const CustomMatch_GetSettings& from) + : ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase() { + CustomMatch_GetSettings* const _this = this; (void)_this; + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_GetSettings) +} + + + + + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_GetSettings::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyImpl, + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeImpl, +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_GetSettings::GetClassData() const { return &_class_data_; } + + + + + + + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_GetSettings::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[57]); +} + +// =================================================================== + +class CustomMatch_SetTeamName::_Internal { + public: +}; + +CustomMatch_SetTeamName::CustomMatch_SetTeamName(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_SetTeamName) +} +CustomMatch_SetTeamName::CustomMatch_SetTeamName(const CustomMatch_SetTeamName& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomMatch_SetTeamName* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.teamname_){} + , decltype(_impl_.teamid_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.teamname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.teamname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_teamname().empty()) { + _this->_impl_.teamname_.Set(from._internal_teamname(), + _this->GetArenaForAllocation()); + } + _this->_impl_.teamid_ = from._impl_.teamid_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_SetTeamName) +} + +inline void CustomMatch_SetTeamName::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.teamname_){} + , decltype(_impl_.teamid_){0} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.teamname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.teamname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CustomMatch_SetTeamName::~CustomMatch_SetTeamName() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomMatch_SetTeamName) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomMatch_SetTeamName::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.teamname_.Destroy(); +} + +void CustomMatch_SetTeamName::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomMatch_SetTeamName::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomMatch_SetTeamName) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.teamname_.ClearToEmpty(); + _impl_.teamid_ = 0; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomMatch_SetTeamName::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // int32 teamId = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.teamid_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string teamName = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_teamname(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_SetTeamName.teamName")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomMatch_SetTeamName::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomMatch_SetTeamName) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // int32 teamId = 1; + if (this->_internal_teamid() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(1, this->_internal_teamid(), target); + } + + // string teamName = 2; + if (!this->_internal_teamname().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_teamname().data(), static_cast(this->_internal_teamname().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_SetTeamName.teamName"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_teamname(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomMatch_SetTeamName) + return target; +} + +size_t CustomMatch_SetTeamName::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomMatch_SetTeamName) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string teamName = 2; + if (!this->_internal_teamname().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_teamname()); + } + + // int32 teamId = 1; + if (this->_internal_teamid() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_teamid()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_SetTeamName::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomMatch_SetTeamName::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_SetTeamName::GetClassData() const { return &_class_data_; } + + +void CustomMatch_SetTeamName::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomMatch_SetTeamName) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_teamname().empty()) { + _this->_internal_set_teamname(from._internal_teamname()); + } + if (from._internal_teamid() != 0) { + _this->_internal_set_teamid(from._internal_teamid()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomMatch_SetTeamName::CopyFrom(const CustomMatch_SetTeamName& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomMatch_SetTeamName) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomMatch_SetTeamName::IsInitialized() const { + return true; +} + +void CustomMatch_SetTeamName::InternalSwap(CustomMatch_SetTeamName* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.teamname_, lhs_arena, + &other->_impl_.teamname_, rhs_arena + ); + swap(_impl_.teamid_, other->_impl_.teamid_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_SetTeamName::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[58]); +} + +// =================================================================== + +class CustomMatch_SendChat::_Internal { + public: +}; + +CustomMatch_SendChat::CustomMatch_SendChat(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.CustomMatch_SendChat) +} +CustomMatch_SendChat::CustomMatch_SendChat(const CustomMatch_SendChat& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + CustomMatch_SendChat* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.text_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.text_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.text_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_text().empty()) { + _this->_impl_.text_.Set(from._internal_text(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.CustomMatch_SendChat) +} + +inline void CustomMatch_SendChat::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.text_){} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.text_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.text_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CustomMatch_SendChat::~CustomMatch_SendChat() { + // @@protoc_insertion_point(destructor:rtech.liveapi.CustomMatch_SendChat) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CustomMatch_SendChat::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.text_.Destroy(); +} + +void CustomMatch_SendChat::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CustomMatch_SendChat::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.CustomMatch_SendChat) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.text_.ClearToEmpty(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* CustomMatch_SendChat::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string text = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_text(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.CustomMatch_SendChat.text")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CustomMatch_SendChat::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.CustomMatch_SendChat) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string text = 1; + if (!this->_internal_text().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_text().data(), static_cast(this->_internal_text().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.CustomMatch_SendChat.text"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_text(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.CustomMatch_SendChat) + return target; +} + +size_t CustomMatch_SendChat::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.CustomMatch_SendChat) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string text = 1; + if (!this->_internal_text().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_text()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData CustomMatch_SendChat::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + CustomMatch_SendChat::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*CustomMatch_SendChat::GetClassData() const { return &_class_data_; } + + +void CustomMatch_SendChat::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.CustomMatch_SendChat) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_text().empty()) { + _this->_internal_set_text(from._internal_text()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void CustomMatch_SendChat::CopyFrom(const CustomMatch_SendChat& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.CustomMatch_SendChat) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CustomMatch_SendChat::IsInitialized() const { + return true; +} + +void CustomMatch_SendChat::InternalSwap(CustomMatch_SendChat* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.text_, lhs_arena, + &other->_impl_.text_, rhs_arena + ); +} + +::PROTOBUF_NAMESPACE_ID::Metadata CustomMatch_SendChat::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[59]); +} + +// =================================================================== + +class Request::_Internal { + public: + static const ::rtech::liveapi::ChangeCamera& changecam(const Request* msg); + static const ::rtech::liveapi::PauseToggle& pausetoggle(const Request* msg); + static const ::rtech::liveapi::CustomMatch_CreateLobby& custommatch_createlobby(const Request* msg); + static const ::rtech::liveapi::CustomMatch_JoinLobby& custommatch_joinlobby(const Request* msg); + static const ::rtech::liveapi::CustomMatch_LeaveLobby& custommatch_leavelobby(const Request* msg); + static const ::rtech::liveapi::CustomMatch_SetReady& custommatch_setready(const Request* msg); + static const ::rtech::liveapi::CustomMatch_SetMatchmaking& custommatch_setmatchmaking(const Request* msg); + static const ::rtech::liveapi::CustomMatch_SetTeam& custommatch_setteam(const Request* msg); + static const ::rtech::liveapi::CustomMatch_KickPlayer& custommatch_kickplayer(const Request* msg); + static const ::rtech::liveapi::CustomMatch_SetSettings& custommatch_setsettings(const Request* msg); + static const ::rtech::liveapi::CustomMatch_SendChat& custommatch_sendchat(const Request* msg); + static const ::rtech::liveapi::CustomMatch_GetLobbyPlayers& custommatch_getlobbyplayers(const Request* msg); + static const ::rtech::liveapi::CustomMatch_SetTeamName& custommatch_setteamname(const Request* msg); + static const ::rtech::liveapi::CustomMatch_GetSettings& custommatch_getsettings(const Request* msg); +}; + +const ::rtech::liveapi::ChangeCamera& +Request::_Internal::changecam(const Request* msg) { + return *msg->_impl_.actions_.changecam_; +} +const ::rtech::liveapi::PauseToggle& +Request::_Internal::pausetoggle(const Request* msg) { + return *msg->_impl_.actions_.pausetoggle_; +} +const ::rtech::liveapi::CustomMatch_CreateLobby& +Request::_Internal::custommatch_createlobby(const Request* msg) { + return *msg->_impl_.actions_.custommatch_createlobby_; +} +const ::rtech::liveapi::CustomMatch_JoinLobby& +Request::_Internal::custommatch_joinlobby(const Request* msg) { + return *msg->_impl_.actions_.custommatch_joinlobby_; +} +const ::rtech::liveapi::CustomMatch_LeaveLobby& +Request::_Internal::custommatch_leavelobby(const Request* msg) { + return *msg->_impl_.actions_.custommatch_leavelobby_; +} +const ::rtech::liveapi::CustomMatch_SetReady& +Request::_Internal::custommatch_setready(const Request* msg) { + return *msg->_impl_.actions_.custommatch_setready_; +} +const ::rtech::liveapi::CustomMatch_SetMatchmaking& +Request::_Internal::custommatch_setmatchmaking(const Request* msg) { + return *msg->_impl_.actions_.custommatch_setmatchmaking_; +} +const ::rtech::liveapi::CustomMatch_SetTeam& +Request::_Internal::custommatch_setteam(const Request* msg) { + return *msg->_impl_.actions_.custommatch_setteam_; +} +const ::rtech::liveapi::CustomMatch_KickPlayer& +Request::_Internal::custommatch_kickplayer(const Request* msg) { + return *msg->_impl_.actions_.custommatch_kickplayer_; +} +const ::rtech::liveapi::CustomMatch_SetSettings& +Request::_Internal::custommatch_setsettings(const Request* msg) { + return *msg->_impl_.actions_.custommatch_setsettings_; +} +const ::rtech::liveapi::CustomMatch_SendChat& +Request::_Internal::custommatch_sendchat(const Request* msg) { + return *msg->_impl_.actions_.custommatch_sendchat_; +} +const ::rtech::liveapi::CustomMatch_GetLobbyPlayers& +Request::_Internal::custommatch_getlobbyplayers(const Request* msg) { + return *msg->_impl_.actions_.custommatch_getlobbyplayers_; +} +const ::rtech::liveapi::CustomMatch_SetTeamName& +Request::_Internal::custommatch_setteamname(const Request* msg) { + return *msg->_impl_.actions_.custommatch_setteamname_; +} +const ::rtech::liveapi::CustomMatch_GetSettings& +Request::_Internal::custommatch_getsettings(const Request* msg) { + return *msg->_impl_.actions_.custommatch_getsettings_; +} +void Request::set_allocated_changecam(::rtech::liveapi::ChangeCamera* changecam) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (changecam) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(changecam); + if (message_arena != submessage_arena) { + changecam = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, changecam, submessage_arena); + } + set_has_changecam(); + _impl_.actions_.changecam_ = changecam; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.changeCam) +} +void Request::set_allocated_pausetoggle(::rtech::liveapi::PauseToggle* pausetoggle) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (pausetoggle) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(pausetoggle); + if (message_arena != submessage_arena) { + pausetoggle = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, pausetoggle, submessage_arena); + } + set_has_pausetoggle(); + _impl_.actions_.pausetoggle_ = pausetoggle; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.pauseToggle) +} +void Request::set_allocated_custommatch_createlobby(::rtech::liveapi::CustomMatch_CreateLobby* custommatch_createlobby) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_createlobby) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_createlobby); + if (message_arena != submessage_arena) { + custommatch_createlobby = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_createlobby, submessage_arena); + } + set_has_custommatch_createlobby(); + _impl_.actions_.custommatch_createlobby_ = custommatch_createlobby; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_CreateLobby) +} +void Request::set_allocated_custommatch_joinlobby(::rtech::liveapi::CustomMatch_JoinLobby* custommatch_joinlobby) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_joinlobby) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_joinlobby); + if (message_arena != submessage_arena) { + custommatch_joinlobby = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_joinlobby, submessage_arena); + } + set_has_custommatch_joinlobby(); + _impl_.actions_.custommatch_joinlobby_ = custommatch_joinlobby; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_JoinLobby) +} +void Request::set_allocated_custommatch_leavelobby(::rtech::liveapi::CustomMatch_LeaveLobby* custommatch_leavelobby) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_leavelobby) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_leavelobby); + if (message_arena != submessage_arena) { + custommatch_leavelobby = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_leavelobby, submessage_arena); + } + set_has_custommatch_leavelobby(); + _impl_.actions_.custommatch_leavelobby_ = custommatch_leavelobby; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_LeaveLobby) +} +void Request::set_allocated_custommatch_setready(::rtech::liveapi::CustomMatch_SetReady* custommatch_setready) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_setready) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_setready); + if (message_arena != submessage_arena) { + custommatch_setready = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_setready, submessage_arena); + } + set_has_custommatch_setready(); + _impl_.actions_.custommatch_setready_ = custommatch_setready; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_SetReady) +} +void Request::set_allocated_custommatch_setmatchmaking(::rtech::liveapi::CustomMatch_SetMatchmaking* custommatch_setmatchmaking) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_setmatchmaking) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_setmatchmaking); + if (message_arena != submessage_arena) { + custommatch_setmatchmaking = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_setmatchmaking, submessage_arena); + } + set_has_custommatch_setmatchmaking(); + _impl_.actions_.custommatch_setmatchmaking_ = custommatch_setmatchmaking; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_SetMatchmaking) +} +void Request::set_allocated_custommatch_setteam(::rtech::liveapi::CustomMatch_SetTeam* custommatch_setteam) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_setteam) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_setteam); + if (message_arena != submessage_arena) { + custommatch_setteam = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_setteam, submessage_arena); + } + set_has_custommatch_setteam(); + _impl_.actions_.custommatch_setteam_ = custommatch_setteam; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_SetTeam) +} +void Request::set_allocated_custommatch_kickplayer(::rtech::liveapi::CustomMatch_KickPlayer* custommatch_kickplayer) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_kickplayer) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_kickplayer); + if (message_arena != submessage_arena) { + custommatch_kickplayer = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_kickplayer, submessage_arena); + } + set_has_custommatch_kickplayer(); + _impl_.actions_.custommatch_kickplayer_ = custommatch_kickplayer; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_KickPlayer) +} +void Request::set_allocated_custommatch_setsettings(::rtech::liveapi::CustomMatch_SetSettings* custommatch_setsettings) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_setsettings) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_setsettings); + if (message_arena != submessage_arena) { + custommatch_setsettings = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_setsettings, submessage_arena); + } + set_has_custommatch_setsettings(); + _impl_.actions_.custommatch_setsettings_ = custommatch_setsettings; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_SetSettings) +} +void Request::set_allocated_custommatch_sendchat(::rtech::liveapi::CustomMatch_SendChat* custommatch_sendchat) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_sendchat) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_sendchat); + if (message_arena != submessage_arena) { + custommatch_sendchat = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_sendchat, submessage_arena); + } + set_has_custommatch_sendchat(); + _impl_.actions_.custommatch_sendchat_ = custommatch_sendchat; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_SendChat) +} +void Request::set_allocated_custommatch_getlobbyplayers(::rtech::liveapi::CustomMatch_GetLobbyPlayers* custommatch_getlobbyplayers) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_getlobbyplayers) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_getlobbyplayers); + if (message_arena != submessage_arena) { + custommatch_getlobbyplayers = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_getlobbyplayers, submessage_arena); + } + set_has_custommatch_getlobbyplayers(); + _impl_.actions_.custommatch_getlobbyplayers_ = custommatch_getlobbyplayers; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_GetLobbyPlayers) +} +void Request::set_allocated_custommatch_setteamname(::rtech::liveapi::CustomMatch_SetTeamName* custommatch_setteamname) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_setteamname) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_setteamname); + if (message_arena != submessage_arena) { + custommatch_setteamname = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_setteamname, submessage_arena); + } + set_has_custommatch_setteamname(); + _impl_.actions_.custommatch_setteamname_ = custommatch_setteamname; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_SetTeamName) +} +void Request::set_allocated_custommatch_getsettings(::rtech::liveapi::CustomMatch_GetSettings* custommatch_getsettings) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_actions(); + if (custommatch_getsettings) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(custommatch_getsettings); + if (message_arena != submessage_arena) { + custommatch_getsettings = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, custommatch_getsettings, submessage_arena); + } + set_has_custommatch_getsettings(); + _impl_.actions_.custommatch_getsettings_ = custommatch_getsettings; + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.customMatch_GetSettings) +} +Request::Request(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.Request) +} +Request::Request(const Request& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + Request* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.presharedkey_){} + , decltype(_impl_.withack_){} + , decltype(_impl_.actions_){} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_._oneof_case_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.presharedkey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.presharedkey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_presharedkey().empty()) { + _this->_impl_.presharedkey_.Set(from._internal_presharedkey(), + _this->GetArenaForAllocation()); + } + _this->_impl_.withack_ = from._impl_.withack_; + clear_has_actions(); + switch (from.actions_case()) { + case kChangeCam: { + _this->_internal_mutable_changecam()->::rtech::liveapi::ChangeCamera::MergeFrom( + from._internal_changecam()); + break; + } + case kPauseToggle: { + _this->_internal_mutable_pausetoggle()->::rtech::liveapi::PauseToggle::MergeFrom( + from._internal_pausetoggle()); + break; + } + case kCustomMatchCreateLobby: { + _this->_internal_mutable_custommatch_createlobby()->::rtech::liveapi::CustomMatch_CreateLobby::MergeFrom( + from._internal_custommatch_createlobby()); + break; + } + case kCustomMatchJoinLobby: { + _this->_internal_mutable_custommatch_joinlobby()->::rtech::liveapi::CustomMatch_JoinLobby::MergeFrom( + from._internal_custommatch_joinlobby()); + break; + } + case kCustomMatchLeaveLobby: { + _this->_internal_mutable_custommatch_leavelobby()->::rtech::liveapi::CustomMatch_LeaveLobby::MergeFrom( + from._internal_custommatch_leavelobby()); + break; + } + case kCustomMatchSetReady: { + _this->_internal_mutable_custommatch_setready()->::rtech::liveapi::CustomMatch_SetReady::MergeFrom( + from._internal_custommatch_setready()); + break; + } + case kCustomMatchSetMatchmaking: { + _this->_internal_mutable_custommatch_setmatchmaking()->::rtech::liveapi::CustomMatch_SetMatchmaking::MergeFrom( + from._internal_custommatch_setmatchmaking()); + break; + } + case kCustomMatchSetTeam: { + _this->_internal_mutable_custommatch_setteam()->::rtech::liveapi::CustomMatch_SetTeam::MergeFrom( + from._internal_custommatch_setteam()); + break; + } + case kCustomMatchKickPlayer: { + _this->_internal_mutable_custommatch_kickplayer()->::rtech::liveapi::CustomMatch_KickPlayer::MergeFrom( + from._internal_custommatch_kickplayer()); + break; + } + case kCustomMatchSetSettings: { + _this->_internal_mutable_custommatch_setsettings()->::rtech::liveapi::CustomMatch_SetSettings::MergeFrom( + from._internal_custommatch_setsettings()); + break; + } + case kCustomMatchSendChat: { + _this->_internal_mutable_custommatch_sendchat()->::rtech::liveapi::CustomMatch_SendChat::MergeFrom( + from._internal_custommatch_sendchat()); + break; + } + case kCustomMatchGetLobbyPlayers: { + _this->_internal_mutable_custommatch_getlobbyplayers()->::rtech::liveapi::CustomMatch_GetLobbyPlayers::MergeFrom( + from._internal_custommatch_getlobbyplayers()); + break; + } + case kCustomMatchSetTeamName: { + _this->_internal_mutable_custommatch_setteamname()->::rtech::liveapi::CustomMatch_SetTeamName::MergeFrom( + from._internal_custommatch_setteamname()); + break; + } + case kCustomMatchGetSettings: { + _this->_internal_mutable_custommatch_getsettings()->::rtech::liveapi::CustomMatch_GetSettings::MergeFrom( + from._internal_custommatch_getsettings()); + break; + } + case ACTIONS_NOT_SET: { + break; + } + } + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.Request) +} + +inline void Request::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.presharedkey_){} + , decltype(_impl_.withack_){false} + , decltype(_impl_.actions_){} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_._oneof_case_)*/{} + }; + _impl_.presharedkey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.presharedkey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + clear_has_actions(); +} + +Request::~Request() { + // @@protoc_insertion_point(destructor:rtech.liveapi.Request) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Request::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.presharedkey_.Destroy(); + if (has_actions()) { + clear_actions(); + } +} + +void Request::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Request::clear_actions() { +// @@protoc_insertion_point(one_of_clear_start:rtech.liveapi.Request) + switch (actions_case()) { + case kChangeCam: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.changecam_; + } + break; + } + case kPauseToggle: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.pausetoggle_; + } + break; + } + case kCustomMatchCreateLobby: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_createlobby_; + } + break; + } + case kCustomMatchJoinLobby: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_joinlobby_; + } + break; + } + case kCustomMatchLeaveLobby: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_leavelobby_; + } + break; + } + case kCustomMatchSetReady: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_setready_; + } + break; + } + case kCustomMatchSetMatchmaking: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_setmatchmaking_; + } + break; + } + case kCustomMatchSetTeam: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_setteam_; + } + break; + } + case kCustomMatchKickPlayer: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_kickplayer_; + } + break; + } + case kCustomMatchSetSettings: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_setsettings_; + } + break; + } + case kCustomMatchSendChat: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_sendchat_; + } + break; + } + case kCustomMatchGetLobbyPlayers: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_getlobbyplayers_; + } + break; + } + case kCustomMatchSetTeamName: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_setteamname_; + } + break; + } + case kCustomMatchGetSettings: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_getsettings_; + } + break; + } + case ACTIONS_NOT_SET: { + break; + } + } + _impl_._oneof_case_[0] = ACTIONS_NOT_SET; +} + + +void Request::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.Request) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.presharedkey_.ClearToEmpty(); + _impl_.withack_ = false; + clear_actions(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* Request::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // bool withAck = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.withack_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // string preSharedKey = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_presharedkey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.Request.preSharedKey")); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.ChangeCamera changeCam = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_changecam(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.PauseToggle pauseToggle = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + ptr = ctx->ParseMessage(_internal_mutable_pausetoggle(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_CreateLobby customMatch_CreateLobby = 10; + case 10: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 82)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_createlobby(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_JoinLobby customMatch_JoinLobby = 11; + case 11: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 90)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_joinlobby(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_LeaveLobby customMatch_LeaveLobby = 12; + case 12: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 98)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_leavelobby(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_SetReady customMatch_SetReady = 13; + case 13: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 106)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_setready(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_SetMatchmaking customMatch_SetMatchmaking = 14; + case 14: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 114)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_setmatchmaking(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_SetTeam customMatch_SetTeam = 15; + case 15: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 122)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_setteam(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_KickPlayer customMatch_KickPlayer = 16; + case 16: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 130)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_kickplayer(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_SetSettings customMatch_SetSettings = 17; + case 17: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 138)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_setsettings(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_SendChat customMatch_SendChat = 18; + case 18: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 146)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_sendchat(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_GetLobbyPlayers customMatch_GetLobbyPlayers = 19; + case 19: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 154)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_getlobbyplayers(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_SetTeamName customMatch_SetTeamName = 20; + case 20: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 162)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_setteamname(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .rtech.liveapi.CustomMatch_GetSettings customMatch_GetSettings = 21; + case 21: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 170)) { + ptr = ctx->ParseMessage(_internal_mutable_custommatch_getsettings(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Request::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.Request) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // bool withAck = 1; + if (this->_internal_withack() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(1, this->_internal_withack(), target); + } + + // string preSharedKey = 2; + if (!this->_internal_presharedkey().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_presharedkey().data(), static_cast(this->_internal_presharedkey().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.Request.preSharedKey"); + target = stream->WriteStringMaybeAliased( + 2, this->_internal_presharedkey(), target); + } + + // .rtech.liveapi.ChangeCamera changeCam = 4; + if (_internal_has_changecam()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::changecam(this), + _Internal::changecam(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.PauseToggle pauseToggle = 5; + if (_internal_has_pausetoggle()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(5, _Internal::pausetoggle(this), + _Internal::pausetoggle(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_CreateLobby customMatch_CreateLobby = 10; + if (_internal_has_custommatch_createlobby()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(10, _Internal::custommatch_createlobby(this), + _Internal::custommatch_createlobby(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_JoinLobby customMatch_JoinLobby = 11; + if (_internal_has_custommatch_joinlobby()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(11, _Internal::custommatch_joinlobby(this), + _Internal::custommatch_joinlobby(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_LeaveLobby customMatch_LeaveLobby = 12; + if (_internal_has_custommatch_leavelobby()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(12, _Internal::custommatch_leavelobby(this), + _Internal::custommatch_leavelobby(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_SetReady customMatch_SetReady = 13; + if (_internal_has_custommatch_setready()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(13, _Internal::custommatch_setready(this), + _Internal::custommatch_setready(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_SetMatchmaking customMatch_SetMatchmaking = 14; + if (_internal_has_custommatch_setmatchmaking()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(14, _Internal::custommatch_setmatchmaking(this), + _Internal::custommatch_setmatchmaking(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_SetTeam customMatch_SetTeam = 15; + if (_internal_has_custommatch_setteam()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(15, _Internal::custommatch_setteam(this), + _Internal::custommatch_setteam(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_KickPlayer customMatch_KickPlayer = 16; + if (_internal_has_custommatch_kickplayer()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(16, _Internal::custommatch_kickplayer(this), + _Internal::custommatch_kickplayer(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_SetSettings customMatch_SetSettings = 17; + if (_internal_has_custommatch_setsettings()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(17, _Internal::custommatch_setsettings(this), + _Internal::custommatch_setsettings(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_SendChat customMatch_SendChat = 18; + if (_internal_has_custommatch_sendchat()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(18, _Internal::custommatch_sendchat(this), + _Internal::custommatch_sendchat(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_GetLobbyPlayers customMatch_GetLobbyPlayers = 19; + if (_internal_has_custommatch_getlobbyplayers()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(19, _Internal::custommatch_getlobbyplayers(this), + _Internal::custommatch_getlobbyplayers(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_SetTeamName customMatch_SetTeamName = 20; + if (_internal_has_custommatch_setteamname()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(20, _Internal::custommatch_setteamname(this), + _Internal::custommatch_setteamname(this).GetCachedSize(), target, stream); + } + + // .rtech.liveapi.CustomMatch_GetSettings customMatch_GetSettings = 21; + if (_internal_has_custommatch_getsettings()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(21, _Internal::custommatch_getsettings(this), + _Internal::custommatch_getsettings(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.Request) + return target; +} + +size_t Request::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.Request) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string preSharedKey = 2; + if (!this->_internal_presharedkey().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_presharedkey()); + } + + // bool withAck = 1; + if (this->_internal_withack() != 0) { + total_size += 1 + 1; + } + + switch (actions_case()) { + // .rtech.liveapi.ChangeCamera changeCam = 4; + case kChangeCam: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.changecam_); + break; + } + // .rtech.liveapi.PauseToggle pauseToggle = 5; + case kPauseToggle: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.pausetoggle_); + break; + } + // .rtech.liveapi.CustomMatch_CreateLobby customMatch_CreateLobby = 10; + case kCustomMatchCreateLobby: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_createlobby_); + break; + } + // .rtech.liveapi.CustomMatch_JoinLobby customMatch_JoinLobby = 11; + case kCustomMatchJoinLobby: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_joinlobby_); + break; + } + // .rtech.liveapi.CustomMatch_LeaveLobby customMatch_LeaveLobby = 12; + case kCustomMatchLeaveLobby: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_leavelobby_); + break; + } + // .rtech.liveapi.CustomMatch_SetReady customMatch_SetReady = 13; + case kCustomMatchSetReady: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_setready_); + break; + } + // .rtech.liveapi.CustomMatch_SetMatchmaking customMatch_SetMatchmaking = 14; + case kCustomMatchSetMatchmaking: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_setmatchmaking_); + break; + } + // .rtech.liveapi.CustomMatch_SetTeam customMatch_SetTeam = 15; + case kCustomMatchSetTeam: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_setteam_); + break; + } + // .rtech.liveapi.CustomMatch_KickPlayer customMatch_KickPlayer = 16; + case kCustomMatchKickPlayer: { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_kickplayer_); + break; + } + // .rtech.liveapi.CustomMatch_SetSettings customMatch_SetSettings = 17; + case kCustomMatchSetSettings: { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_setsettings_); + break; + } + // .rtech.liveapi.CustomMatch_SendChat customMatch_SendChat = 18; + case kCustomMatchSendChat: { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_sendchat_); + break; + } + // .rtech.liveapi.CustomMatch_GetLobbyPlayers customMatch_GetLobbyPlayers = 19; + case kCustomMatchGetLobbyPlayers: { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_getlobbyplayers_); + break; + } + // .rtech.liveapi.CustomMatch_SetTeamName customMatch_SetTeamName = 20; + case kCustomMatchSetTeamName: { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_setteamname_); + break; + } + // .rtech.liveapi.CustomMatch_GetSettings customMatch_GetSettings = 21; + case kCustomMatchGetSettings: { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.actions_.custommatch_getsettings_); + break; + } + case ACTIONS_NOT_SET: { + break; + } + } + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData Request::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + Request::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*Request::GetClassData() const { return &_class_data_; } + + +void Request::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.Request) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_presharedkey().empty()) { + _this->_internal_set_presharedkey(from._internal_presharedkey()); + } + if (from._internal_withack() != 0) { + _this->_internal_set_withack(from._internal_withack()); + } + switch (from.actions_case()) { + case kChangeCam: { + _this->_internal_mutable_changecam()->::rtech::liveapi::ChangeCamera::MergeFrom( + from._internal_changecam()); + break; + } + case kPauseToggle: { + _this->_internal_mutable_pausetoggle()->::rtech::liveapi::PauseToggle::MergeFrom( + from._internal_pausetoggle()); + break; + } + case kCustomMatchCreateLobby: { + _this->_internal_mutable_custommatch_createlobby()->::rtech::liveapi::CustomMatch_CreateLobby::MergeFrom( + from._internal_custommatch_createlobby()); + break; + } + case kCustomMatchJoinLobby: { + _this->_internal_mutable_custommatch_joinlobby()->::rtech::liveapi::CustomMatch_JoinLobby::MergeFrom( + from._internal_custommatch_joinlobby()); + break; + } + case kCustomMatchLeaveLobby: { + _this->_internal_mutable_custommatch_leavelobby()->::rtech::liveapi::CustomMatch_LeaveLobby::MergeFrom( + from._internal_custommatch_leavelobby()); + break; + } + case kCustomMatchSetReady: { + _this->_internal_mutable_custommatch_setready()->::rtech::liveapi::CustomMatch_SetReady::MergeFrom( + from._internal_custommatch_setready()); + break; + } + case kCustomMatchSetMatchmaking: { + _this->_internal_mutable_custommatch_setmatchmaking()->::rtech::liveapi::CustomMatch_SetMatchmaking::MergeFrom( + from._internal_custommatch_setmatchmaking()); + break; + } + case kCustomMatchSetTeam: { + _this->_internal_mutable_custommatch_setteam()->::rtech::liveapi::CustomMatch_SetTeam::MergeFrom( + from._internal_custommatch_setteam()); + break; + } + case kCustomMatchKickPlayer: { + _this->_internal_mutable_custommatch_kickplayer()->::rtech::liveapi::CustomMatch_KickPlayer::MergeFrom( + from._internal_custommatch_kickplayer()); + break; + } + case kCustomMatchSetSettings: { + _this->_internal_mutable_custommatch_setsettings()->::rtech::liveapi::CustomMatch_SetSettings::MergeFrom( + from._internal_custommatch_setsettings()); + break; + } + case kCustomMatchSendChat: { + _this->_internal_mutable_custommatch_sendchat()->::rtech::liveapi::CustomMatch_SendChat::MergeFrom( + from._internal_custommatch_sendchat()); + break; + } + case kCustomMatchGetLobbyPlayers: { + _this->_internal_mutable_custommatch_getlobbyplayers()->::rtech::liveapi::CustomMatch_GetLobbyPlayers::MergeFrom( + from._internal_custommatch_getlobbyplayers()); + break; + } + case kCustomMatchSetTeamName: { + _this->_internal_mutable_custommatch_setteamname()->::rtech::liveapi::CustomMatch_SetTeamName::MergeFrom( + from._internal_custommatch_setteamname()); + break; + } + case kCustomMatchGetSettings: { + _this->_internal_mutable_custommatch_getsettings()->::rtech::liveapi::CustomMatch_GetSettings::MergeFrom( + from._internal_custommatch_getsettings()); + break; + } + case ACTIONS_NOT_SET: { + break; + } + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void Request::CopyFrom(const Request& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.Request) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Request::IsInitialized() const { + return true; +} + +void Request::InternalSwap(Request* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.presharedkey_, lhs_arena, + &other->_impl_.presharedkey_, rhs_arena + ); + swap(_impl_.withack_, other->_impl_.withack_); + swap(_impl_.actions_, other->_impl_.actions_); + swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]); +} + +::PROTOBUF_NAMESPACE_ID::Metadata Request::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[60]); +} + +// =================================================================== + +class RequestStatus::_Internal { + public: +}; + +RequestStatus::RequestStatus(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.RequestStatus) +} +RequestStatus::RequestStatus(const RequestStatus& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + RequestStatus* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.status_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.status_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.status_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_status().empty()) { + _this->_impl_.status_.Set(from._internal_status(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.RequestStatus) +} + +inline void RequestStatus::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.status_){} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.status_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.status_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +RequestStatus::~RequestStatus() { + // @@protoc_insertion_point(destructor:rtech.liveapi.RequestStatus) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void RequestStatus::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.status_.Destroy(); +} + +void RequestStatus::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void RequestStatus::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.RequestStatus) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.status_.ClearToEmpty(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* RequestStatus::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string status = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_status(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "rtech.liveapi.RequestStatus.status")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* RequestStatus::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.RequestStatus) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string status = 1; + if (!this->_internal_status().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_status().data(), static_cast(this->_internal_status().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "rtech.liveapi.RequestStatus.status"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_status(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.RequestStatus) + return target; +} + +size_t RequestStatus::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.RequestStatus) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string status = 1; + if (!this->_internal_status().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_status()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData RequestStatus::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + RequestStatus::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*RequestStatus::GetClassData() const { return &_class_data_; } + + +void RequestStatus::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.RequestStatus) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_status().empty()) { + _this->_internal_set_status(from._internal_status()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void RequestStatus::CopyFrom(const RequestStatus& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.RequestStatus) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool RequestStatus::IsInitialized() const { + return true; +} + +void RequestStatus::InternalSwap(RequestStatus* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.status_, lhs_arena, + &other->_impl_.status_, rhs_arena + ); +} + +::PROTOBUF_NAMESPACE_ID::Metadata RequestStatus::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[61]); +} + +// =================================================================== + +class Response::_Internal { + public: + static const ::PROTOBUF_NAMESPACE_ID::Any& result(const Response* msg); +}; + +const ::PROTOBUF_NAMESPACE_ID::Any& +Response::_Internal::result(const Response* msg) { + return *msg->_impl_.result_; +} +void Response::clear_result() { + if (GetArenaForAllocation() == nullptr && _impl_.result_ != nullptr) { + delete _impl_.result_; + } + _impl_.result_ = nullptr; +} +Response::Response(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.Response) +} +Response::Response(const Response& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + Response* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.result_){nullptr} + , decltype(_impl_.success_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + if (from._internal_has_result()) { + _this->_impl_.result_ = new ::PROTOBUF_NAMESPACE_ID::Any(*from._impl_.result_); + } + _this->_impl_.success_ = from._impl_.success_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.Response) +} + +inline void Response::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.result_){nullptr} + , decltype(_impl_.success_){false} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +Response::~Response() { + // @@protoc_insertion_point(destructor:rtech.liveapi.Response) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Response::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + if (this != internal_default_instance()) delete _impl_.result_; +} + +void Response::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Response::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.Response) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + if (GetArenaForAllocation() == nullptr && _impl_.result_ != nullptr) { + delete _impl_.result_; + } + _impl_.result_ = nullptr; + _impl_.success_ = false; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* Response::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // bool success = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _impl_.success_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .google.protobuf.Any result = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + ptr = ctx->ParseMessage(_internal_mutable_result(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Response::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.Response) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // bool success = 1; + if (this->_internal_success() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(1, this->_internal_success(), target); + } + + // .google.protobuf.Any result = 2; + if (this->_internal_has_result()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(2, _Internal::result(this), + _Internal::result(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.Response) + return target; +} + +size_t Response::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.Response) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // .google.protobuf.Any result = 2; + if (this->_internal_has_result()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.result_); + } + + // bool success = 1; + if (this->_internal_success() != 0) { + total_size += 1 + 1; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData Response::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + Response::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*Response::GetClassData() const { return &_class_data_; } + + +void Response::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.Response) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (from._internal_has_result()) { + _this->_internal_mutable_result()->::PROTOBUF_NAMESPACE_ID::Any::MergeFrom( + from._internal_result()); + } + if (from._internal_success() != 0) { + _this->_internal_set_success(from._internal_success()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void Response::CopyFrom(const Response& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.Response) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Response::IsInitialized() const { + return true; +} + +void Response::InternalSwap(Response* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(Response, _impl_.success_) + + sizeof(Response::_impl_.success_) + - PROTOBUF_FIELD_OFFSET(Response, _impl_.result_)>( + reinterpret_cast(&_impl_.result_), + reinterpret_cast(&other->_impl_.result_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata Response::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[62]); +} + +// =================================================================== + +class LiveAPIEvent::_Internal { + public: + static const ::PROTOBUF_NAMESPACE_ID::Any& gamemessage(const LiveAPIEvent* msg); +}; + +const ::PROTOBUF_NAMESPACE_ID::Any& +LiveAPIEvent::_Internal::gamemessage(const LiveAPIEvent* msg) { + return *msg->_impl_.gamemessage_; +} +void LiveAPIEvent::clear_gamemessage() { + if (GetArenaForAllocation() == nullptr && _impl_.gamemessage_ != nullptr) { + delete _impl_.gamemessage_; + } + _impl_.gamemessage_ = nullptr; +} +LiveAPIEvent::LiveAPIEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:rtech.liveapi.LiveAPIEvent) +} +LiveAPIEvent::LiveAPIEvent(const LiveAPIEvent& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + LiveAPIEvent* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.gamemessage_){nullptr} + , decltype(_impl_.event_size_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + if (from._internal_has_gamemessage()) { + _this->_impl_.gamemessage_ = new ::PROTOBUF_NAMESPACE_ID::Any(*from._impl_.gamemessage_); + } + _this->_impl_.event_size_ = from._impl_.event_size_; + // @@protoc_insertion_point(copy_constructor:rtech.liveapi.LiveAPIEvent) +} + +inline void LiveAPIEvent::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.gamemessage_){nullptr} + , decltype(_impl_.event_size_){0u} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +LiveAPIEvent::~LiveAPIEvent() { + // @@protoc_insertion_point(destructor:rtech.liveapi.LiveAPIEvent) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void LiveAPIEvent::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + if (this != internal_default_instance()) delete _impl_.gamemessage_; +} + +void LiveAPIEvent::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void LiveAPIEvent::Clear() { +// @@protoc_insertion_point(message_clear_start:rtech.liveapi.LiveAPIEvent) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + if (GetArenaForAllocation() == nullptr && _impl_.gamemessage_ != nullptr) { + delete _impl_.gamemessage_; + } + _impl_.gamemessage_ = nullptr; + _impl_.event_size_ = 0u; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* LiveAPIEvent::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // fixed32 event_size = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 13)) { + _impl_.event_size_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(uint32_t); + } else + goto handle_unusual; + continue; + // .google.protobuf.Any gameMessage = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_gamemessage(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* LiveAPIEvent::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:rtech.liveapi.LiveAPIEvent) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // fixed32 event_size = 1; + if (this->_internal_event_size() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFixed32ToArray(1, this->_internal_event_size(), target); + } + + // .google.protobuf.Any gameMessage = 3; + if (this->_internal_has_gamemessage()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::gamemessage(this), + _Internal::gamemessage(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:rtech.liveapi.LiveAPIEvent) + return target; +} + +size_t LiveAPIEvent::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:rtech.liveapi.LiveAPIEvent) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // .google.protobuf.Any gameMessage = 3; + if (this->_internal_has_gamemessage()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.gamemessage_); + } + + // fixed32 event_size = 1; + if (this->_internal_event_size() != 0) { + total_size += 1 + 4; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData LiveAPIEvent::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + LiveAPIEvent::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*LiveAPIEvent::GetClassData() const { return &_class_data_; } + + +void LiveAPIEvent::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:rtech.liveapi.LiveAPIEvent) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (from._internal_has_gamemessage()) { + _this->_internal_mutable_gamemessage()->::PROTOBUF_NAMESPACE_ID::Any::MergeFrom( + from._internal_gamemessage()); + } + if (from._internal_event_size() != 0) { + _this->_internal_set_event_size(from._internal_event_size()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void LiveAPIEvent::CopyFrom(const LiveAPIEvent& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:rtech.liveapi.LiveAPIEvent) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LiveAPIEvent::IsInitialized() const { + return true; +} + +void LiveAPIEvent::InternalSwap(LiveAPIEvent* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(LiveAPIEvent, _impl_.event_size_) + + sizeof(LiveAPIEvent::_impl_.event_size_) + - PROTOBUF_FIELD_OFFSET(LiveAPIEvent, _impl_.gamemessage_)>( + reinterpret_cast(&_impl_.gamemessage_), + reinterpret_cast(&other->_impl_.gamemessage_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata LiveAPIEvent::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_events_2eproto_getter, &descriptor_table_events_2eproto_once, + file_level_metadata_events_2eproto[63]); +} + +// @@protoc_insertion_point(namespace_scope) +} // namespace liveapi +} // namespace rtech +PROTOBUF_NAMESPACE_OPEN +template<> PROTOBUF_NOINLINE ::rtech::liveapi::Vector3* +Arena::CreateMaybeMessage< ::rtech::liveapi::Vector3 >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::Vector3 >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::Player* +Arena::CreateMaybeMessage< ::rtech::liveapi::Player >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::Player >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_LobbyPlayer* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_LobbyPlayer >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_LobbyPlayer >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::Datacenter* +Arena::CreateMaybeMessage< ::rtech::liveapi::Datacenter >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::Datacenter >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::Version* +Arena::CreateMaybeMessage< ::rtech::liveapi::Version >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::Version >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::InventoryItem* +Arena::CreateMaybeMessage< ::rtech::liveapi::InventoryItem >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::InventoryItem >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::LoadoutConfiguration* +Arena::CreateMaybeMessage< ::rtech::liveapi::LoadoutConfiguration >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::LoadoutConfiguration >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::Init* +Arena::CreateMaybeMessage< ::rtech::liveapi::Init >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::Init >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_LobbyPlayers* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_LobbyPlayers >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_LobbyPlayers >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::ObserverSwitched* +Arena::CreateMaybeMessage< ::rtech::liveapi::ObserverSwitched >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::ObserverSwitched >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::ObserverAnnotation* +Arena::CreateMaybeMessage< ::rtech::liveapi::ObserverAnnotation >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::ObserverAnnotation >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::MatchSetup* +Arena::CreateMaybeMessage< ::rtech::liveapi::MatchSetup >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::MatchSetup >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::GameStateChanged* +Arena::CreateMaybeMessage< ::rtech::liveapi::GameStateChanged >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::GameStateChanged >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CharacterSelected* +Arena::CreateMaybeMessage< ::rtech::liveapi::CharacterSelected >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CharacterSelected >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::MatchStateEnd* +Arena::CreateMaybeMessage< ::rtech::liveapi::MatchStateEnd >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::MatchStateEnd >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::RingStartClosing* +Arena::CreateMaybeMessage< ::rtech::liveapi::RingStartClosing >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::RingStartClosing >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::RingFinishedClosing* +Arena::CreateMaybeMessage< ::rtech::liveapi::RingFinishedClosing >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::RingFinishedClosing >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerConnected* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerConnected >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerConnected >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerDisconnected* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerDisconnected >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerDisconnected >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerStatChanged* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerStatChanged >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerStatChanged >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerUpgradeTierChanged* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerUpgradeTierChanged >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerUpgradeTierChanged >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerDamaged* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerDamaged >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerDamaged >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerKilled* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerKilled >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerKilled >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerDowned* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerDowned >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerDowned >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerAssist* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerAssist >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerAssist >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::SquadEliminated* +Arena::CreateMaybeMessage< ::rtech::liveapi::SquadEliminated >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::SquadEliminated >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::GibraltarShieldAbsorbed* +Arena::CreateMaybeMessage< ::rtech::liveapi::GibraltarShieldAbsorbed >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::GibraltarShieldAbsorbed >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::RevenantForgedShadowDamaged* +Arena::CreateMaybeMessage< ::rtech::liveapi::RevenantForgedShadowDamaged >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::RevenantForgedShadowDamaged >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerRespawnTeam* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerRespawnTeam >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerRespawnTeam >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerRevive* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerRevive >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerRevive >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::ArenasItemSelected* +Arena::CreateMaybeMessage< ::rtech::liveapi::ArenasItemSelected >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::ArenasItemSelected >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::ArenasItemDeselected* +Arena::CreateMaybeMessage< ::rtech::liveapi::ArenasItemDeselected >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::ArenasItemDeselected >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::InventoryPickUp* +Arena::CreateMaybeMessage< ::rtech::liveapi::InventoryPickUp >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::InventoryPickUp >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::InventoryDrop* +Arena::CreateMaybeMessage< ::rtech::liveapi::InventoryDrop >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::InventoryDrop >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::InventoryUse* +Arena::CreateMaybeMessage< ::rtech::liveapi::InventoryUse >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::InventoryUse >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::BannerCollected* +Arena::CreateMaybeMessage< ::rtech::liveapi::BannerCollected >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::BannerCollected >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PlayerAbilityUsed* +Arena::CreateMaybeMessage< ::rtech::liveapi::PlayerAbilityUsed >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PlayerAbilityUsed >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::LegendUpgradeSelected* +Arena::CreateMaybeMessage< ::rtech::liveapi::LegendUpgradeSelected >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::LegendUpgradeSelected >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::ZiplineUsed* +Arena::CreateMaybeMessage< ::rtech::liveapi::ZiplineUsed >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::ZiplineUsed >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::GrenadeThrown* +Arena::CreateMaybeMessage< ::rtech::liveapi::GrenadeThrown >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::GrenadeThrown >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::BlackMarketAction* +Arena::CreateMaybeMessage< ::rtech::liveapi::BlackMarketAction >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::BlackMarketAction >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::WraithPortal* +Arena::CreateMaybeMessage< ::rtech::liveapi::WraithPortal >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::WraithPortal >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::WarpGateUsed* +Arena::CreateMaybeMessage< ::rtech::liveapi::WarpGateUsed >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::WarpGateUsed >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::AmmoUsed* +Arena::CreateMaybeMessage< ::rtech::liveapi::AmmoUsed >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::AmmoUsed >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::WeaponSwitched* +Arena::CreateMaybeMessage< ::rtech::liveapi::WeaponSwitched >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::WeaponSwitched >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomEvent* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomEvent >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomEvent >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::ChangeCamera* +Arena::CreateMaybeMessage< ::rtech::liveapi::ChangeCamera >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::ChangeCamera >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::PauseToggle* +Arena::CreateMaybeMessage< ::rtech::liveapi::PauseToggle >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::PauseToggle >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_CreateLobby* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_CreateLobby >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_CreateLobby >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_JoinLobby* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_JoinLobby >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_JoinLobby >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_LeaveLobby* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_LeaveLobby >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_LeaveLobby >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_SetReady* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SetReady >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_SetReady >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_GetLobbyPlayers* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_GetLobbyPlayers >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_GetLobbyPlayers >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_SetMatchmaking* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SetMatchmaking >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_SetMatchmaking >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_SetTeam* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SetTeam >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_SetTeam >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_KickPlayer* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_KickPlayer >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_KickPlayer >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_SetSettings* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SetSettings >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_SetSettings >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_GetSettings* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_GetSettings >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_GetSettings >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_SetTeamName* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SetTeamName >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_SetTeamName >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::CustomMatch_SendChat* +Arena::CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SendChat >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::CustomMatch_SendChat >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::Request* +Arena::CreateMaybeMessage< ::rtech::liveapi::Request >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::Request >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::RequestStatus* +Arena::CreateMaybeMessage< ::rtech::liveapi::RequestStatus >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::RequestStatus >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::Response* +Arena::CreateMaybeMessage< ::rtech::liveapi::Response >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::Response >(arena); +} +template<> PROTOBUF_NOINLINE ::rtech::liveapi::LiveAPIEvent* +Arena::CreateMaybeMessage< ::rtech::liveapi::LiveAPIEvent >(Arena* arena) { + return Arena::CreateMessageInternal< ::rtech::liveapi::LiveAPIEvent >(arena); +} +PROTOBUF_NAMESPACE_CLOSE + +// @@protoc_insertion_point(global_scope) +#include diff --git a/src/protoc/events.pb.h b/src/protoc/events.pb.h new file mode 100644 index 00000000..e59b7327 --- /dev/null +++ b/src/protoc/events.pb.h @@ -0,0 +1,25973 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: events.proto + +#ifndef GOOGLE_PROTOBUF_INCLUDED_events_2eproto +#define GOOGLE_PROTOBUF_INCLUDED_events_2eproto + +#include +#include + +#include +#if PROTOBUF_VERSION < 3021000 +#error This file was generated by a newer version of protoc which is +#error incompatible with your Protocol Buffer headers. Please update +#error your headers. +#endif +#if 3021012 < PROTOBUF_MIN_PROTOC_VERSION +#error This file was generated by an older version of protoc which is +#error incompatible with your Protocol Buffer headers. Please +#error regenerate this file with a newer version of protoc. +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: export +#include // IWYU pragma: export +#include +#include +#include +#include +// @@protoc_insertion_point(includes) +#include +#define PROTOBUF_INTERNAL_EXPORT_events_2eproto +PROTOBUF_NAMESPACE_OPEN +namespace internal { +class AnyMetadata; +} // namespace internal +PROTOBUF_NAMESPACE_CLOSE + +// Internal implementation detail -- do not use these members. +struct TableStruct_events_2eproto { + static const uint32_t offsets[]; +}; +extern const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable descriptor_table_events_2eproto; +namespace rtech { +namespace liveapi { +class AmmoUsed; +struct AmmoUsedDefaultTypeInternal; +extern AmmoUsedDefaultTypeInternal _AmmoUsed_default_instance_; +class ArenasItemDeselected; +struct ArenasItemDeselectedDefaultTypeInternal; +extern ArenasItemDeselectedDefaultTypeInternal _ArenasItemDeselected_default_instance_; +class ArenasItemSelected; +struct ArenasItemSelectedDefaultTypeInternal; +extern ArenasItemSelectedDefaultTypeInternal _ArenasItemSelected_default_instance_; +class BannerCollected; +struct BannerCollectedDefaultTypeInternal; +extern BannerCollectedDefaultTypeInternal _BannerCollected_default_instance_; +class BlackMarketAction; +struct BlackMarketActionDefaultTypeInternal; +extern BlackMarketActionDefaultTypeInternal _BlackMarketAction_default_instance_; +class ChangeCamera; +struct ChangeCameraDefaultTypeInternal; +extern ChangeCameraDefaultTypeInternal _ChangeCamera_default_instance_; +class CharacterSelected; +struct CharacterSelectedDefaultTypeInternal; +extern CharacterSelectedDefaultTypeInternal _CharacterSelected_default_instance_; +class CustomEvent; +struct CustomEventDefaultTypeInternal; +extern CustomEventDefaultTypeInternal _CustomEvent_default_instance_; +class CustomMatch_CreateLobby; +struct CustomMatch_CreateLobbyDefaultTypeInternal; +extern CustomMatch_CreateLobbyDefaultTypeInternal _CustomMatch_CreateLobby_default_instance_; +class CustomMatch_GetLobbyPlayers; +struct CustomMatch_GetLobbyPlayersDefaultTypeInternal; +extern CustomMatch_GetLobbyPlayersDefaultTypeInternal _CustomMatch_GetLobbyPlayers_default_instance_; +class CustomMatch_GetSettings; +struct CustomMatch_GetSettingsDefaultTypeInternal; +extern CustomMatch_GetSettingsDefaultTypeInternal _CustomMatch_GetSettings_default_instance_; +class CustomMatch_JoinLobby; +struct CustomMatch_JoinLobbyDefaultTypeInternal; +extern CustomMatch_JoinLobbyDefaultTypeInternal _CustomMatch_JoinLobby_default_instance_; +class CustomMatch_KickPlayer; +struct CustomMatch_KickPlayerDefaultTypeInternal; +extern CustomMatch_KickPlayerDefaultTypeInternal _CustomMatch_KickPlayer_default_instance_; +class CustomMatch_LeaveLobby; +struct CustomMatch_LeaveLobbyDefaultTypeInternal; +extern CustomMatch_LeaveLobbyDefaultTypeInternal _CustomMatch_LeaveLobby_default_instance_; +class CustomMatch_LobbyPlayer; +struct CustomMatch_LobbyPlayerDefaultTypeInternal; +extern CustomMatch_LobbyPlayerDefaultTypeInternal _CustomMatch_LobbyPlayer_default_instance_; +class CustomMatch_LobbyPlayers; +struct CustomMatch_LobbyPlayersDefaultTypeInternal; +extern CustomMatch_LobbyPlayersDefaultTypeInternal _CustomMatch_LobbyPlayers_default_instance_; +class CustomMatch_SendChat; +struct CustomMatch_SendChatDefaultTypeInternal; +extern CustomMatch_SendChatDefaultTypeInternal _CustomMatch_SendChat_default_instance_; +class CustomMatch_SetMatchmaking; +struct CustomMatch_SetMatchmakingDefaultTypeInternal; +extern CustomMatch_SetMatchmakingDefaultTypeInternal _CustomMatch_SetMatchmaking_default_instance_; +class CustomMatch_SetReady; +struct CustomMatch_SetReadyDefaultTypeInternal; +extern CustomMatch_SetReadyDefaultTypeInternal _CustomMatch_SetReady_default_instance_; +class CustomMatch_SetSettings; +struct CustomMatch_SetSettingsDefaultTypeInternal; +extern CustomMatch_SetSettingsDefaultTypeInternal _CustomMatch_SetSettings_default_instance_; +class CustomMatch_SetTeam; +struct CustomMatch_SetTeamDefaultTypeInternal; +extern CustomMatch_SetTeamDefaultTypeInternal _CustomMatch_SetTeam_default_instance_; +class CustomMatch_SetTeamName; +struct CustomMatch_SetTeamNameDefaultTypeInternal; +extern CustomMatch_SetTeamNameDefaultTypeInternal _CustomMatch_SetTeamName_default_instance_; +class Datacenter; +struct DatacenterDefaultTypeInternal; +extern DatacenterDefaultTypeInternal _Datacenter_default_instance_; +class GameStateChanged; +struct GameStateChangedDefaultTypeInternal; +extern GameStateChangedDefaultTypeInternal _GameStateChanged_default_instance_; +class GibraltarShieldAbsorbed; +struct GibraltarShieldAbsorbedDefaultTypeInternal; +extern GibraltarShieldAbsorbedDefaultTypeInternal _GibraltarShieldAbsorbed_default_instance_; +class GrenadeThrown; +struct GrenadeThrownDefaultTypeInternal; +extern GrenadeThrownDefaultTypeInternal _GrenadeThrown_default_instance_; +class Init; +struct InitDefaultTypeInternal; +extern InitDefaultTypeInternal _Init_default_instance_; +class InventoryDrop; +struct InventoryDropDefaultTypeInternal; +extern InventoryDropDefaultTypeInternal _InventoryDrop_default_instance_; +class InventoryItem; +struct InventoryItemDefaultTypeInternal; +extern InventoryItemDefaultTypeInternal _InventoryItem_default_instance_; +class InventoryPickUp; +struct InventoryPickUpDefaultTypeInternal; +extern InventoryPickUpDefaultTypeInternal _InventoryPickUp_default_instance_; +class InventoryUse; +struct InventoryUseDefaultTypeInternal; +extern InventoryUseDefaultTypeInternal _InventoryUse_default_instance_; +class LegendUpgradeSelected; +struct LegendUpgradeSelectedDefaultTypeInternal; +extern LegendUpgradeSelectedDefaultTypeInternal _LegendUpgradeSelected_default_instance_; +class LiveAPIEvent; +struct LiveAPIEventDefaultTypeInternal; +extern LiveAPIEventDefaultTypeInternal _LiveAPIEvent_default_instance_; +class LoadoutConfiguration; +struct LoadoutConfigurationDefaultTypeInternal; +extern LoadoutConfigurationDefaultTypeInternal _LoadoutConfiguration_default_instance_; +class MatchSetup; +struct MatchSetupDefaultTypeInternal; +extern MatchSetupDefaultTypeInternal _MatchSetup_default_instance_; +class MatchStateEnd; +struct MatchStateEndDefaultTypeInternal; +extern MatchStateEndDefaultTypeInternal _MatchStateEnd_default_instance_; +class ObserverAnnotation; +struct ObserverAnnotationDefaultTypeInternal; +extern ObserverAnnotationDefaultTypeInternal _ObserverAnnotation_default_instance_; +class ObserverSwitched; +struct ObserverSwitchedDefaultTypeInternal; +extern ObserverSwitchedDefaultTypeInternal _ObserverSwitched_default_instance_; +class PauseToggle; +struct PauseToggleDefaultTypeInternal; +extern PauseToggleDefaultTypeInternal _PauseToggle_default_instance_; +class Player; +struct PlayerDefaultTypeInternal; +extern PlayerDefaultTypeInternal _Player_default_instance_; +class PlayerAbilityUsed; +struct PlayerAbilityUsedDefaultTypeInternal; +extern PlayerAbilityUsedDefaultTypeInternal _PlayerAbilityUsed_default_instance_; +class PlayerAssist; +struct PlayerAssistDefaultTypeInternal; +extern PlayerAssistDefaultTypeInternal _PlayerAssist_default_instance_; +class PlayerConnected; +struct PlayerConnectedDefaultTypeInternal; +extern PlayerConnectedDefaultTypeInternal _PlayerConnected_default_instance_; +class PlayerDamaged; +struct PlayerDamagedDefaultTypeInternal; +extern PlayerDamagedDefaultTypeInternal _PlayerDamaged_default_instance_; +class PlayerDisconnected; +struct PlayerDisconnectedDefaultTypeInternal; +extern PlayerDisconnectedDefaultTypeInternal _PlayerDisconnected_default_instance_; +class PlayerDowned; +struct PlayerDownedDefaultTypeInternal; +extern PlayerDownedDefaultTypeInternal _PlayerDowned_default_instance_; +class PlayerKilled; +struct PlayerKilledDefaultTypeInternal; +extern PlayerKilledDefaultTypeInternal _PlayerKilled_default_instance_; +class PlayerRespawnTeam; +struct PlayerRespawnTeamDefaultTypeInternal; +extern PlayerRespawnTeamDefaultTypeInternal _PlayerRespawnTeam_default_instance_; +class PlayerRevive; +struct PlayerReviveDefaultTypeInternal; +extern PlayerReviveDefaultTypeInternal _PlayerRevive_default_instance_; +class PlayerStatChanged; +struct PlayerStatChangedDefaultTypeInternal; +extern PlayerStatChangedDefaultTypeInternal _PlayerStatChanged_default_instance_; +class PlayerUpgradeTierChanged; +struct PlayerUpgradeTierChangedDefaultTypeInternal; +extern PlayerUpgradeTierChangedDefaultTypeInternal _PlayerUpgradeTierChanged_default_instance_; +class Request; +struct RequestDefaultTypeInternal; +extern RequestDefaultTypeInternal _Request_default_instance_; +class RequestStatus; +struct RequestStatusDefaultTypeInternal; +extern RequestStatusDefaultTypeInternal _RequestStatus_default_instance_; +class Response; +struct ResponseDefaultTypeInternal; +extern ResponseDefaultTypeInternal _Response_default_instance_; +class RevenantForgedShadowDamaged; +struct RevenantForgedShadowDamagedDefaultTypeInternal; +extern RevenantForgedShadowDamagedDefaultTypeInternal _RevenantForgedShadowDamaged_default_instance_; +class RingFinishedClosing; +struct RingFinishedClosingDefaultTypeInternal; +extern RingFinishedClosingDefaultTypeInternal _RingFinishedClosing_default_instance_; +class RingStartClosing; +struct RingStartClosingDefaultTypeInternal; +extern RingStartClosingDefaultTypeInternal _RingStartClosing_default_instance_; +class SquadEliminated; +struct SquadEliminatedDefaultTypeInternal; +extern SquadEliminatedDefaultTypeInternal _SquadEliminated_default_instance_; +class Vector3; +struct Vector3DefaultTypeInternal; +extern Vector3DefaultTypeInternal _Vector3_default_instance_; +class Version; +struct VersionDefaultTypeInternal; +extern VersionDefaultTypeInternal _Version_default_instance_; +class WarpGateUsed; +struct WarpGateUsedDefaultTypeInternal; +extern WarpGateUsedDefaultTypeInternal _WarpGateUsed_default_instance_; +class WeaponSwitched; +struct WeaponSwitchedDefaultTypeInternal; +extern WeaponSwitchedDefaultTypeInternal _WeaponSwitched_default_instance_; +class WraithPortal; +struct WraithPortalDefaultTypeInternal; +extern WraithPortalDefaultTypeInternal _WraithPortal_default_instance_; +class ZiplineUsed; +struct ZiplineUsedDefaultTypeInternal; +extern ZiplineUsedDefaultTypeInternal _ZiplineUsed_default_instance_; +} // namespace liveapi +} // namespace rtech +PROTOBUF_NAMESPACE_OPEN +template<> ::rtech::liveapi::AmmoUsed* Arena::CreateMaybeMessage<::rtech::liveapi::AmmoUsed>(Arena*); +template<> ::rtech::liveapi::ArenasItemDeselected* Arena::CreateMaybeMessage<::rtech::liveapi::ArenasItemDeselected>(Arena*); +template<> ::rtech::liveapi::ArenasItemSelected* Arena::CreateMaybeMessage<::rtech::liveapi::ArenasItemSelected>(Arena*); +template<> ::rtech::liveapi::BannerCollected* Arena::CreateMaybeMessage<::rtech::liveapi::BannerCollected>(Arena*); +template<> ::rtech::liveapi::BlackMarketAction* Arena::CreateMaybeMessage<::rtech::liveapi::BlackMarketAction>(Arena*); +template<> ::rtech::liveapi::ChangeCamera* Arena::CreateMaybeMessage<::rtech::liveapi::ChangeCamera>(Arena*); +template<> ::rtech::liveapi::CharacterSelected* Arena::CreateMaybeMessage<::rtech::liveapi::CharacterSelected>(Arena*); +template<> ::rtech::liveapi::CustomEvent* Arena::CreateMaybeMessage<::rtech::liveapi::CustomEvent>(Arena*); +template<> ::rtech::liveapi::CustomMatch_CreateLobby* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_CreateLobby>(Arena*); +template<> ::rtech::liveapi::CustomMatch_GetLobbyPlayers* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_GetLobbyPlayers>(Arena*); +template<> ::rtech::liveapi::CustomMatch_GetSettings* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_GetSettings>(Arena*); +template<> ::rtech::liveapi::CustomMatch_JoinLobby* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_JoinLobby>(Arena*); +template<> ::rtech::liveapi::CustomMatch_KickPlayer* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_KickPlayer>(Arena*); +template<> ::rtech::liveapi::CustomMatch_LeaveLobby* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_LeaveLobby>(Arena*); +template<> ::rtech::liveapi::CustomMatch_LobbyPlayer* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_LobbyPlayer>(Arena*); +template<> ::rtech::liveapi::CustomMatch_LobbyPlayers* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_LobbyPlayers>(Arena*); +template<> ::rtech::liveapi::CustomMatch_SendChat* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_SendChat>(Arena*); +template<> ::rtech::liveapi::CustomMatch_SetMatchmaking* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_SetMatchmaking>(Arena*); +template<> ::rtech::liveapi::CustomMatch_SetReady* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_SetReady>(Arena*); +template<> ::rtech::liveapi::CustomMatch_SetSettings* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_SetSettings>(Arena*); +template<> ::rtech::liveapi::CustomMatch_SetTeam* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_SetTeam>(Arena*); +template<> ::rtech::liveapi::CustomMatch_SetTeamName* Arena::CreateMaybeMessage<::rtech::liveapi::CustomMatch_SetTeamName>(Arena*); +template<> ::rtech::liveapi::Datacenter* Arena::CreateMaybeMessage<::rtech::liveapi::Datacenter>(Arena*); +template<> ::rtech::liveapi::GameStateChanged* Arena::CreateMaybeMessage<::rtech::liveapi::GameStateChanged>(Arena*); +template<> ::rtech::liveapi::GibraltarShieldAbsorbed* Arena::CreateMaybeMessage<::rtech::liveapi::GibraltarShieldAbsorbed>(Arena*); +template<> ::rtech::liveapi::GrenadeThrown* Arena::CreateMaybeMessage<::rtech::liveapi::GrenadeThrown>(Arena*); +template<> ::rtech::liveapi::Init* Arena::CreateMaybeMessage<::rtech::liveapi::Init>(Arena*); +template<> ::rtech::liveapi::InventoryDrop* Arena::CreateMaybeMessage<::rtech::liveapi::InventoryDrop>(Arena*); +template<> ::rtech::liveapi::InventoryItem* Arena::CreateMaybeMessage<::rtech::liveapi::InventoryItem>(Arena*); +template<> ::rtech::liveapi::InventoryPickUp* Arena::CreateMaybeMessage<::rtech::liveapi::InventoryPickUp>(Arena*); +template<> ::rtech::liveapi::InventoryUse* Arena::CreateMaybeMessage<::rtech::liveapi::InventoryUse>(Arena*); +template<> ::rtech::liveapi::LegendUpgradeSelected* Arena::CreateMaybeMessage<::rtech::liveapi::LegendUpgradeSelected>(Arena*); +template<> ::rtech::liveapi::LiveAPIEvent* Arena::CreateMaybeMessage<::rtech::liveapi::LiveAPIEvent>(Arena*); +template<> ::rtech::liveapi::LoadoutConfiguration* Arena::CreateMaybeMessage<::rtech::liveapi::LoadoutConfiguration>(Arena*); +template<> ::rtech::liveapi::MatchSetup* Arena::CreateMaybeMessage<::rtech::liveapi::MatchSetup>(Arena*); +template<> ::rtech::liveapi::MatchStateEnd* Arena::CreateMaybeMessage<::rtech::liveapi::MatchStateEnd>(Arena*); +template<> ::rtech::liveapi::ObserverAnnotation* Arena::CreateMaybeMessage<::rtech::liveapi::ObserverAnnotation>(Arena*); +template<> ::rtech::liveapi::ObserverSwitched* Arena::CreateMaybeMessage<::rtech::liveapi::ObserverSwitched>(Arena*); +template<> ::rtech::liveapi::PauseToggle* Arena::CreateMaybeMessage<::rtech::liveapi::PauseToggle>(Arena*); +template<> ::rtech::liveapi::Player* Arena::CreateMaybeMessage<::rtech::liveapi::Player>(Arena*); +template<> ::rtech::liveapi::PlayerAbilityUsed* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerAbilityUsed>(Arena*); +template<> ::rtech::liveapi::PlayerAssist* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerAssist>(Arena*); +template<> ::rtech::liveapi::PlayerConnected* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerConnected>(Arena*); +template<> ::rtech::liveapi::PlayerDamaged* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerDamaged>(Arena*); +template<> ::rtech::liveapi::PlayerDisconnected* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerDisconnected>(Arena*); +template<> ::rtech::liveapi::PlayerDowned* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerDowned>(Arena*); +template<> ::rtech::liveapi::PlayerKilled* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerKilled>(Arena*); +template<> ::rtech::liveapi::PlayerRespawnTeam* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerRespawnTeam>(Arena*); +template<> ::rtech::liveapi::PlayerRevive* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerRevive>(Arena*); +template<> ::rtech::liveapi::PlayerStatChanged* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerStatChanged>(Arena*); +template<> ::rtech::liveapi::PlayerUpgradeTierChanged* Arena::CreateMaybeMessage<::rtech::liveapi::PlayerUpgradeTierChanged>(Arena*); +template<> ::rtech::liveapi::Request* Arena::CreateMaybeMessage<::rtech::liveapi::Request>(Arena*); +template<> ::rtech::liveapi::RequestStatus* Arena::CreateMaybeMessage<::rtech::liveapi::RequestStatus>(Arena*); +template<> ::rtech::liveapi::Response* Arena::CreateMaybeMessage<::rtech::liveapi::Response>(Arena*); +template<> ::rtech::liveapi::RevenantForgedShadowDamaged* Arena::CreateMaybeMessage<::rtech::liveapi::RevenantForgedShadowDamaged>(Arena*); +template<> ::rtech::liveapi::RingFinishedClosing* Arena::CreateMaybeMessage<::rtech::liveapi::RingFinishedClosing>(Arena*); +template<> ::rtech::liveapi::RingStartClosing* Arena::CreateMaybeMessage<::rtech::liveapi::RingStartClosing>(Arena*); +template<> ::rtech::liveapi::SquadEliminated* Arena::CreateMaybeMessage<::rtech::liveapi::SquadEliminated>(Arena*); +template<> ::rtech::liveapi::Vector3* Arena::CreateMaybeMessage<::rtech::liveapi::Vector3>(Arena*); +template<> ::rtech::liveapi::Version* Arena::CreateMaybeMessage<::rtech::liveapi::Version>(Arena*); +template<> ::rtech::liveapi::WarpGateUsed* Arena::CreateMaybeMessage<::rtech::liveapi::WarpGateUsed>(Arena*); +template<> ::rtech::liveapi::WeaponSwitched* Arena::CreateMaybeMessage<::rtech::liveapi::WeaponSwitched>(Arena*); +template<> ::rtech::liveapi::WraithPortal* Arena::CreateMaybeMessage<::rtech::liveapi::WraithPortal>(Arena*); +template<> ::rtech::liveapi::ZiplineUsed* Arena::CreateMaybeMessage<::rtech::liveapi::ZiplineUsed>(Arena*); +PROTOBUF_NAMESPACE_CLOSE +namespace rtech { +namespace liveapi { + +enum PlayerOfInterest : int { + UNSPECIFIED = 0, + NEXT = 1, + PREVIOUS = 2, + KILL_LEADER = 3, + CLOSEST_ENEMY = 4, + CLOSEST_PLAYER = 5, + LATEST_ATTACKER = 6, + PlayerOfInterest_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits::min(), + PlayerOfInterest_INT_MAX_SENTINEL_DO_NOT_USE_ = std::numeric_limits::max() +}; +bool PlayerOfInterest_IsValid(int value); +constexpr PlayerOfInterest PlayerOfInterest_MIN = UNSPECIFIED; +constexpr PlayerOfInterest PlayerOfInterest_MAX = LATEST_ATTACKER; +constexpr int PlayerOfInterest_ARRAYSIZE = PlayerOfInterest_MAX + 1; + +const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* PlayerOfInterest_descriptor(); +template +inline const std::string& PlayerOfInterest_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function PlayerOfInterest_Name."); + return ::PROTOBUF_NAMESPACE_ID::internal::NameOfEnum( + PlayerOfInterest_descriptor(), enum_t_value); +} +inline bool PlayerOfInterest_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, PlayerOfInterest* value) { + return ::PROTOBUF_NAMESPACE_ID::internal::ParseNamedEnum( + PlayerOfInterest_descriptor(), name, value); +} +// =================================================================== + +class Vector3 final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.Vector3) */ { + public: + inline Vector3() : Vector3(nullptr) {} + ~Vector3() override; + explicit PROTOBUF_CONSTEXPR Vector3(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Vector3(const Vector3& from); + Vector3(Vector3&& from) noexcept + : Vector3() { + *this = ::std::move(from); + } + + inline Vector3& operator=(const Vector3& from) { + CopyFrom(from); + return *this; + } + inline Vector3& operator=(Vector3&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const Vector3& default_instance() { + return *internal_default_instance(); + } + static inline const Vector3* internal_default_instance() { + return reinterpret_cast( + &_Vector3_default_instance_); + } + static constexpr int kIndexInFileMessages = + 0; + + friend void swap(Vector3& a, Vector3& b) { + a.Swap(&b); + } + inline void Swap(Vector3* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Vector3* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Vector3* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const Vector3& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const Vector3& from) { + Vector3::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(Vector3* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.Vector3"; + } + protected: + explicit Vector3(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kXFieldNumber = 1, + kYFieldNumber = 2, + kZFieldNumber = 3, + }; + // float x = 1; + void clear_x(); + float x() const; + void set_x(float value); + private: + float _internal_x() const; + void _internal_set_x(float value); + public: + + // float y = 2; + void clear_y(); + float y() const; + void set_y(float value); + private: + float _internal_y() const; + void _internal_set_y(float value); + public: + + // float z = 3; + void clear_z(); + float z() const; + void set_z(float value); + private: + float _internal_z() const; + void _internal_set_z(float value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.Vector3) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + float x_; + float y_; + float z_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class Player final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.Player) */ { + public: + inline Player() : Player(nullptr) {} + ~Player() override; + explicit PROTOBUF_CONSTEXPR Player(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Player(const Player& from); + Player(Player&& from) noexcept + : Player() { + *this = ::std::move(from); + } + + inline Player& operator=(const Player& from) { + CopyFrom(from); + return *this; + } + inline Player& operator=(Player&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const Player& default_instance() { + return *internal_default_instance(); + } + static inline const Player* internal_default_instance() { + return reinterpret_cast( + &_Player_default_instance_); + } + static constexpr int kIndexInFileMessages = + 1; + + friend void swap(Player& a, Player& b) { + a.Swap(&b); + } + inline void Swap(Player* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Player* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Player* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const Player& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const Player& from) { + Player::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(Player* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.Player"; + } + protected: + explicit Player(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kNameFieldNumber = 1, + kNucleusHashFieldNumber = 9, + kHardwareNameFieldNumber = 10, + kTeamNameFieldNumber = 11, + kCharacterFieldNumber = 13, + kSkinFieldNumber = 14, + kPosFieldNumber = 3, + kAnglesFieldNumber = 4, + kTeamIdFieldNumber = 2, + kCurrentHealthFieldNumber = 5, + kMaxHealthFieldNumber = 6, + kShieldHealthFieldNumber = 7, + kShieldMaxHealthFieldNumber = 8, + kSquadIndexFieldNumber = 12, + }; + // string name = 1; + void clear_name(); + const std::string& name() const; + template + void set_name(ArgT0&& arg0, ArgT... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* name); + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); + std::string* _internal_mutable_name(); + public: + + // string nucleusHash = 9; + void clear_nucleushash(); + const std::string& nucleushash() const; + template + void set_nucleushash(ArgT0&& arg0, ArgT... args); + std::string* mutable_nucleushash(); + PROTOBUF_NODISCARD std::string* release_nucleushash(); + void set_allocated_nucleushash(std::string* nucleushash); + private: + const std::string& _internal_nucleushash() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_nucleushash(const std::string& value); + std::string* _internal_mutable_nucleushash(); + public: + + // string hardwareName = 10; + void clear_hardwarename(); + const std::string& hardwarename() const; + template + void set_hardwarename(ArgT0&& arg0, ArgT... args); + std::string* mutable_hardwarename(); + PROTOBUF_NODISCARD std::string* release_hardwarename(); + void set_allocated_hardwarename(std::string* hardwarename); + private: + const std::string& _internal_hardwarename() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_hardwarename(const std::string& value); + std::string* _internal_mutable_hardwarename(); + public: + + // string teamName = 11; + void clear_teamname(); + const std::string& teamname() const; + template + void set_teamname(ArgT0&& arg0, ArgT... args); + std::string* mutable_teamname(); + PROTOBUF_NODISCARD std::string* release_teamname(); + void set_allocated_teamname(std::string* teamname); + private: + const std::string& _internal_teamname() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_teamname(const std::string& value); + std::string* _internal_mutable_teamname(); + public: + + // string character = 13; + void clear_character(); + const std::string& character() const; + template + void set_character(ArgT0&& arg0, ArgT... args); + std::string* mutable_character(); + PROTOBUF_NODISCARD std::string* release_character(); + void set_allocated_character(std::string* character); + private: + const std::string& _internal_character() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_character(const std::string& value); + std::string* _internal_mutable_character(); + public: + + // string skin = 14; + void clear_skin(); + const std::string& skin() const; + template + void set_skin(ArgT0&& arg0, ArgT... args); + std::string* mutable_skin(); + PROTOBUF_NODISCARD std::string* release_skin(); + void set_allocated_skin(std::string* skin); + private: + const std::string& _internal_skin() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_skin(const std::string& value); + std::string* _internal_mutable_skin(); + public: + + // .rtech.liveapi.Vector3 pos = 3; + bool has_pos() const; + private: + bool _internal_has_pos() const; + public: + void clear_pos(); + const ::rtech::liveapi::Vector3& pos() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Vector3* release_pos(); + ::rtech::liveapi::Vector3* mutable_pos(); + void set_allocated_pos(::rtech::liveapi::Vector3* pos); + private: + const ::rtech::liveapi::Vector3& _internal_pos() const; + ::rtech::liveapi::Vector3* _internal_mutable_pos(); + public: + void unsafe_arena_set_allocated_pos( + ::rtech::liveapi::Vector3* pos); + ::rtech::liveapi::Vector3* unsafe_arena_release_pos(); + + // .rtech.liveapi.Vector3 angles = 4; + bool has_angles() const; + private: + bool _internal_has_angles() const; + public: + void clear_angles(); + const ::rtech::liveapi::Vector3& angles() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Vector3* release_angles(); + ::rtech::liveapi::Vector3* mutable_angles(); + void set_allocated_angles(::rtech::liveapi::Vector3* angles); + private: + const ::rtech::liveapi::Vector3& _internal_angles() const; + ::rtech::liveapi::Vector3* _internal_mutable_angles(); + public: + void unsafe_arena_set_allocated_angles( + ::rtech::liveapi::Vector3* angles); + ::rtech::liveapi::Vector3* unsafe_arena_release_angles(); + + // uint32 teamId = 2; + void clear_teamid(); + uint32_t teamid() const; + void set_teamid(uint32_t value); + private: + uint32_t _internal_teamid() const; + void _internal_set_teamid(uint32_t value); + public: + + // uint32 currentHealth = 5; + void clear_currenthealth(); + uint32_t currenthealth() const; + void set_currenthealth(uint32_t value); + private: + uint32_t _internal_currenthealth() const; + void _internal_set_currenthealth(uint32_t value); + public: + + // uint32 maxHealth = 6; + void clear_maxhealth(); + uint32_t maxhealth() const; + void set_maxhealth(uint32_t value); + private: + uint32_t _internal_maxhealth() const; + void _internal_set_maxhealth(uint32_t value); + public: + + // uint32 shieldHealth = 7; + void clear_shieldhealth(); + uint32_t shieldhealth() const; + void set_shieldhealth(uint32_t value); + private: + uint32_t _internal_shieldhealth() const; + void _internal_set_shieldhealth(uint32_t value); + public: + + // uint32 shieldMaxHealth = 8; + void clear_shieldmaxhealth(); + uint32_t shieldmaxhealth() const; + void set_shieldmaxhealth(uint32_t value); + private: + uint32_t _internal_shieldmaxhealth() const; + void _internal_set_shieldmaxhealth(uint32_t value); + public: + + // uint32 squadIndex = 12; + void clear_squadindex(); + uint32_t squadindex() const; + void set_squadindex(uint32_t value); + private: + uint32_t _internal_squadindex() const; + void _internal_set_squadindex(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.Player) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr nucleushash_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr hardwarename_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr teamname_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr character_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr skin_; + ::rtech::liveapi::Vector3* pos_; + ::rtech::liveapi::Vector3* angles_; + uint32_t teamid_; + uint32_t currenthealth_; + uint32_t maxhealth_; + uint32_t shieldhealth_; + uint32_t shieldmaxhealth_; + uint32_t squadindex_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_LobbyPlayer final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_LobbyPlayer) */ { + public: + inline CustomMatch_LobbyPlayer() : CustomMatch_LobbyPlayer(nullptr) {} + ~CustomMatch_LobbyPlayer() override; + explicit PROTOBUF_CONSTEXPR CustomMatch_LobbyPlayer(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_LobbyPlayer(const CustomMatch_LobbyPlayer& from); + CustomMatch_LobbyPlayer(CustomMatch_LobbyPlayer&& from) noexcept + : CustomMatch_LobbyPlayer() { + *this = ::std::move(from); + } + + inline CustomMatch_LobbyPlayer& operator=(const CustomMatch_LobbyPlayer& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_LobbyPlayer& operator=(CustomMatch_LobbyPlayer&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_LobbyPlayer& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_LobbyPlayer* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_LobbyPlayer_default_instance_); + } + static constexpr int kIndexInFileMessages = + 2; + + friend void swap(CustomMatch_LobbyPlayer& a, CustomMatch_LobbyPlayer& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_LobbyPlayer* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_LobbyPlayer* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_LobbyPlayer* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomMatch_LobbyPlayer& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomMatch_LobbyPlayer& from) { + CustomMatch_LobbyPlayer::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomMatch_LobbyPlayer* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_LobbyPlayer"; + } + protected: + explicit CustomMatch_LobbyPlayer(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kNameFieldNumber = 1, + kNucleusHashFieldNumber = 3, + kHardwareNameFieldNumber = 4, + kTeamIdFieldNumber = 2, + }; + // string name = 1; + void clear_name(); + const std::string& name() const; + template + void set_name(ArgT0&& arg0, ArgT... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* name); + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); + std::string* _internal_mutable_name(); + public: + + // string nucleusHash = 3; + void clear_nucleushash(); + const std::string& nucleushash() const; + template + void set_nucleushash(ArgT0&& arg0, ArgT... args); + std::string* mutable_nucleushash(); + PROTOBUF_NODISCARD std::string* release_nucleushash(); + void set_allocated_nucleushash(std::string* nucleushash); + private: + const std::string& _internal_nucleushash() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_nucleushash(const std::string& value); + std::string* _internal_mutable_nucleushash(); + public: + + // string hardwareName = 4; + void clear_hardwarename(); + const std::string& hardwarename() const; + template + void set_hardwarename(ArgT0&& arg0, ArgT... args); + std::string* mutable_hardwarename(); + PROTOBUF_NODISCARD std::string* release_hardwarename(); + void set_allocated_hardwarename(std::string* hardwarename); + private: + const std::string& _internal_hardwarename() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_hardwarename(const std::string& value); + std::string* _internal_mutable_hardwarename(); + public: + + // uint32 teamId = 2; + void clear_teamid(); + uint32_t teamid() const; + void set_teamid(uint32_t value); + private: + uint32_t _internal_teamid() const; + void _internal_set_teamid(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_LobbyPlayer) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr nucleushash_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr hardwarename_; + uint32_t teamid_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class Datacenter final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.Datacenter) */ { + public: + inline Datacenter() : Datacenter(nullptr) {} + ~Datacenter() override; + explicit PROTOBUF_CONSTEXPR Datacenter(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Datacenter(const Datacenter& from); + Datacenter(Datacenter&& from) noexcept + : Datacenter() { + *this = ::std::move(from); + } + + inline Datacenter& operator=(const Datacenter& from) { + CopyFrom(from); + return *this; + } + inline Datacenter& operator=(Datacenter&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const Datacenter& default_instance() { + return *internal_default_instance(); + } + static inline const Datacenter* internal_default_instance() { + return reinterpret_cast( + &_Datacenter_default_instance_); + } + static constexpr int kIndexInFileMessages = + 3; + + friend void swap(Datacenter& a, Datacenter& b) { + a.Swap(&b); + } + inline void Swap(Datacenter* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Datacenter* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Datacenter* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const Datacenter& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const Datacenter& from) { + Datacenter::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(Datacenter* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.Datacenter"; + } + protected: + explicit Datacenter(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kNameFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string name = 3; + void clear_name(); + const std::string& name() const; + template + void set_name(ArgT0&& arg0, ArgT... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* name); + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); + std::string* _internal_mutable_name(); + public: + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.Datacenter) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class Version final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.Version) */ { + public: + inline Version() : Version(nullptr) {} + ~Version() override; + explicit PROTOBUF_CONSTEXPR Version(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Version(const Version& from); + Version(Version&& from) noexcept + : Version() { + *this = ::std::move(from); + } + + inline Version& operator=(const Version& from) { + CopyFrom(from); + return *this; + } + inline Version& operator=(Version&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const Version& default_instance() { + return *internal_default_instance(); + } + static inline const Version* internal_default_instance() { + return reinterpret_cast( + &_Version_default_instance_); + } + static constexpr int kIndexInFileMessages = + 4; + + friend void swap(Version& a, Version& b) { + a.Swap(&b); + } + inline void Swap(Version* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Version* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Version* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const Version& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const Version& from) { + Version::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(Version* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.Version"; + } + protected: + explicit Version(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kRevisionFieldNumber = 4, + kMajorNumFieldNumber = 1, + kMinorNumFieldNumber = 2, + kBuildStampFieldNumber = 3, + }; + // string revision = 4; + void clear_revision(); + const std::string& revision() const; + template + void set_revision(ArgT0&& arg0, ArgT... args); + std::string* mutable_revision(); + PROTOBUF_NODISCARD std::string* release_revision(); + void set_allocated_revision(std::string* revision); + private: + const std::string& _internal_revision() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_revision(const std::string& value); + std::string* _internal_mutable_revision(); + public: + + // uint32 major_num = 1; + void clear_major_num(); + uint32_t major_num() const; + void set_major_num(uint32_t value); + private: + uint32_t _internal_major_num() const; + void _internal_set_major_num(uint32_t value); + public: + + // uint32 minor_num = 2; + void clear_minor_num(); + uint32_t minor_num() const; + void set_minor_num(uint32_t value); + private: + uint32_t _internal_minor_num() const; + void _internal_set_minor_num(uint32_t value); + public: + + // uint32 build_stamp = 3; + void clear_build_stamp(); + uint32_t build_stamp() const; + void set_build_stamp(uint32_t value); + private: + uint32_t _internal_build_stamp() const; + void _internal_set_build_stamp(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.Version) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr revision_; + uint32_t major_num_; + uint32_t minor_num_; + uint32_t build_stamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class InventoryItem final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.InventoryItem) */ { + public: + inline InventoryItem() : InventoryItem(nullptr) {} + ~InventoryItem() override; + explicit PROTOBUF_CONSTEXPR InventoryItem(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + InventoryItem(const InventoryItem& from); + InventoryItem(InventoryItem&& from) noexcept + : InventoryItem() { + *this = ::std::move(from); + } + + inline InventoryItem& operator=(const InventoryItem& from) { + CopyFrom(from); + return *this; + } + inline InventoryItem& operator=(InventoryItem&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const InventoryItem& default_instance() { + return *internal_default_instance(); + } + static inline const InventoryItem* internal_default_instance() { + return reinterpret_cast( + &_InventoryItem_default_instance_); + } + static constexpr int kIndexInFileMessages = + 5; + + friend void swap(InventoryItem& a, InventoryItem& b) { + a.Swap(&b); + } + inline void Swap(InventoryItem* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(InventoryItem* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + InventoryItem* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const InventoryItem& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const InventoryItem& from) { + InventoryItem::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(InventoryItem* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.InventoryItem"; + } + protected: + explicit InventoryItem(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kItemFieldNumber = 2, + kExtraDataFieldNumber = 3, + kQuantityFieldNumber = 1, + }; + // string item = 2; + void clear_item(); + const std::string& item() const; + template + void set_item(ArgT0&& arg0, ArgT... args); + std::string* mutable_item(); + PROTOBUF_NODISCARD std::string* release_item(); + void set_allocated_item(std::string* item); + private: + const std::string& _internal_item() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_item(const std::string& value); + std::string* _internal_mutable_item(); + public: + + // string extraData = 3; + void clear_extradata(); + const std::string& extradata() const; + template + void set_extradata(ArgT0&& arg0, ArgT... args); + std::string* mutable_extradata(); + PROTOBUF_NODISCARD std::string* release_extradata(); + void set_allocated_extradata(std::string* extradata); + private: + const std::string& _internal_extradata() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_extradata(const std::string& value); + std::string* _internal_mutable_extradata(); + public: + + // int32 quantity = 1; + void clear_quantity(); + int32_t quantity() const; + void set_quantity(int32_t value); + private: + int32_t _internal_quantity() const; + void _internal_set_quantity(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.InventoryItem) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr item_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr extradata_; + int32_t quantity_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class LoadoutConfiguration final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.LoadoutConfiguration) */ { + public: + inline LoadoutConfiguration() : LoadoutConfiguration(nullptr) {} + ~LoadoutConfiguration() override; + explicit PROTOBUF_CONSTEXPR LoadoutConfiguration(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + LoadoutConfiguration(const LoadoutConfiguration& from); + LoadoutConfiguration(LoadoutConfiguration&& from) noexcept + : LoadoutConfiguration() { + *this = ::std::move(from); + } + + inline LoadoutConfiguration& operator=(const LoadoutConfiguration& from) { + CopyFrom(from); + return *this; + } + inline LoadoutConfiguration& operator=(LoadoutConfiguration&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const LoadoutConfiguration& default_instance() { + return *internal_default_instance(); + } + static inline const LoadoutConfiguration* internal_default_instance() { + return reinterpret_cast( + &_LoadoutConfiguration_default_instance_); + } + static constexpr int kIndexInFileMessages = + 6; + + friend void swap(LoadoutConfiguration& a, LoadoutConfiguration& b) { + a.Swap(&b); + } + inline void Swap(LoadoutConfiguration* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(LoadoutConfiguration* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + LoadoutConfiguration* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const LoadoutConfiguration& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const LoadoutConfiguration& from) { + LoadoutConfiguration::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(LoadoutConfiguration* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.LoadoutConfiguration"; + } + protected: + explicit LoadoutConfiguration(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kWeaponsFieldNumber = 1, + kEquipmentFieldNumber = 2, + }; + // repeated .rtech.liveapi.InventoryItem weapons = 1; + int weapons_size() const; + private: + int _internal_weapons_size() const; + public: + void clear_weapons(); + ::rtech::liveapi::InventoryItem* mutable_weapons(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::InventoryItem >* + mutable_weapons(); + private: + const ::rtech::liveapi::InventoryItem& _internal_weapons(int index) const; + ::rtech::liveapi::InventoryItem* _internal_add_weapons(); + public: + const ::rtech::liveapi::InventoryItem& weapons(int index) const; + ::rtech::liveapi::InventoryItem* add_weapons(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::InventoryItem >& + weapons() const; + + // repeated .rtech.liveapi.InventoryItem equipment = 2; + int equipment_size() const; + private: + int _internal_equipment_size() const; + public: + void clear_equipment(); + ::rtech::liveapi::InventoryItem* mutable_equipment(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::InventoryItem >* + mutable_equipment(); + private: + const ::rtech::liveapi::InventoryItem& _internal_equipment(int index) const; + ::rtech::liveapi::InventoryItem* _internal_add_equipment(); + public: + const ::rtech::liveapi::InventoryItem& equipment(int index) const; + ::rtech::liveapi::InventoryItem* add_equipment(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::InventoryItem >& + equipment() const; + + // @@protoc_insertion_point(class_scope:rtech.liveapi.LoadoutConfiguration) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::InventoryItem > weapons_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::InventoryItem > equipment_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class Init final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.Init) */ { + public: + inline Init() : Init(nullptr) {} + ~Init() override; + explicit PROTOBUF_CONSTEXPR Init(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Init(const Init& from); + Init(Init&& from) noexcept + : Init() { + *this = ::std::move(from); + } + + inline Init& operator=(const Init& from) { + CopyFrom(from); + return *this; + } + inline Init& operator=(Init&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const Init& default_instance() { + return *internal_default_instance(); + } + static inline const Init* internal_default_instance() { + return reinterpret_cast( + &_Init_default_instance_); + } + static constexpr int kIndexInFileMessages = + 7; + + friend void swap(Init& a, Init& b) { + a.Swap(&b); + } + inline void Swap(Init* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Init* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Init* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const Init& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const Init& from) { + Init::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(Init* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.Init"; + } + protected: + explicit Init(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kGameVersionFieldNumber = 3, + kPlatformFieldNumber = 5, + kNameFieldNumber = 6, + kApiVersionFieldNumber = 4, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string gameVersion = 3; + void clear_gameversion(); + const std::string& gameversion() const; + template + void set_gameversion(ArgT0&& arg0, ArgT... args); + std::string* mutable_gameversion(); + PROTOBUF_NODISCARD std::string* release_gameversion(); + void set_allocated_gameversion(std::string* gameversion); + private: + const std::string& _internal_gameversion() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_gameversion(const std::string& value); + std::string* _internal_mutable_gameversion(); + public: + + // string platform = 5; + void clear_platform(); + const std::string& platform() const; + template + void set_platform(ArgT0&& arg0, ArgT... args); + std::string* mutable_platform(); + PROTOBUF_NODISCARD std::string* release_platform(); + void set_allocated_platform(std::string* platform); + private: + const std::string& _internal_platform() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_platform(const std::string& value); + std::string* _internal_mutable_platform(); + public: + + // string name = 6; + void clear_name(); + const std::string& name() const; + template + void set_name(ArgT0&& arg0, ArgT... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* name); + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); + std::string* _internal_mutable_name(); + public: + + // .rtech.liveapi.Version apiVersion = 4; + bool has_apiversion() const; + private: + bool _internal_has_apiversion() const; + public: + void clear_apiversion(); + const ::rtech::liveapi::Version& apiversion() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Version* release_apiversion(); + ::rtech::liveapi::Version* mutable_apiversion(); + void set_allocated_apiversion(::rtech::liveapi::Version* apiversion); + private: + const ::rtech::liveapi::Version& _internal_apiversion() const; + ::rtech::liveapi::Version* _internal_mutable_apiversion(); + public: + void unsafe_arena_set_allocated_apiversion( + ::rtech::liveapi::Version* apiversion); + ::rtech::liveapi::Version* unsafe_arena_release_apiversion(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.Init) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr gameversion_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr platform_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; + ::rtech::liveapi::Version* apiversion_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_LobbyPlayers final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_LobbyPlayers) */ { + public: + inline CustomMatch_LobbyPlayers() : CustomMatch_LobbyPlayers(nullptr) {} + ~CustomMatch_LobbyPlayers() override; + explicit PROTOBUF_CONSTEXPR CustomMatch_LobbyPlayers(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_LobbyPlayers(const CustomMatch_LobbyPlayers& from); + CustomMatch_LobbyPlayers(CustomMatch_LobbyPlayers&& from) noexcept + : CustomMatch_LobbyPlayers() { + *this = ::std::move(from); + } + + inline CustomMatch_LobbyPlayers& operator=(const CustomMatch_LobbyPlayers& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_LobbyPlayers& operator=(CustomMatch_LobbyPlayers&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_LobbyPlayers& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_LobbyPlayers* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_LobbyPlayers_default_instance_); + } + static constexpr int kIndexInFileMessages = + 8; + + friend void swap(CustomMatch_LobbyPlayers& a, CustomMatch_LobbyPlayers& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_LobbyPlayers* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_LobbyPlayers* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_LobbyPlayers* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomMatch_LobbyPlayers& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomMatch_LobbyPlayers& from) { + CustomMatch_LobbyPlayers::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomMatch_LobbyPlayers* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_LobbyPlayers"; + } + protected: + explicit CustomMatch_LobbyPlayers(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kPlayersFieldNumber = 2, + kPlayerTokenFieldNumber = 1, + }; + // repeated .rtech.liveapi.CustomMatch_LobbyPlayer players = 2; + int players_size() const; + private: + int _internal_players_size() const; + public: + void clear_players(); + ::rtech::liveapi::CustomMatch_LobbyPlayer* mutable_players(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::CustomMatch_LobbyPlayer >* + mutable_players(); + private: + const ::rtech::liveapi::CustomMatch_LobbyPlayer& _internal_players(int index) const; + ::rtech::liveapi::CustomMatch_LobbyPlayer* _internal_add_players(); + public: + const ::rtech::liveapi::CustomMatch_LobbyPlayer& players(int index) const; + ::rtech::liveapi::CustomMatch_LobbyPlayer* add_players(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::CustomMatch_LobbyPlayer >& + players() const; + + // string playerToken = 1; + void clear_playertoken(); + const std::string& playertoken() const; + template + void set_playertoken(ArgT0&& arg0, ArgT... args); + std::string* mutable_playertoken(); + PROTOBUF_NODISCARD std::string* release_playertoken(); + void set_allocated_playertoken(std::string* playertoken); + private: + const std::string& _internal_playertoken() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_playertoken(const std::string& value); + std::string* _internal_mutable_playertoken(); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_LobbyPlayers) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::CustomMatch_LobbyPlayer > players_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr playertoken_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class ObserverSwitched final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.ObserverSwitched) */ { + public: + inline ObserverSwitched() : ObserverSwitched(nullptr) {} + ~ObserverSwitched() override; + explicit PROTOBUF_CONSTEXPR ObserverSwitched(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ObserverSwitched(const ObserverSwitched& from); + ObserverSwitched(ObserverSwitched&& from) noexcept + : ObserverSwitched() { + *this = ::std::move(from); + } + + inline ObserverSwitched& operator=(const ObserverSwitched& from) { + CopyFrom(from); + return *this; + } + inline ObserverSwitched& operator=(ObserverSwitched&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const ObserverSwitched& default_instance() { + return *internal_default_instance(); + } + static inline const ObserverSwitched* internal_default_instance() { + return reinterpret_cast( + &_ObserverSwitched_default_instance_); + } + static constexpr int kIndexInFileMessages = + 9; + + friend void swap(ObserverSwitched& a, ObserverSwitched& b) { + a.Swap(&b); + } + inline void Swap(ObserverSwitched* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ObserverSwitched* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ObserverSwitched* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const ObserverSwitched& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const ObserverSwitched& from) { + ObserverSwitched::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(ObserverSwitched* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.ObserverSwitched"; + } + protected: + explicit ObserverSwitched(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kTargetTeamFieldNumber = 5, + kCategoryFieldNumber = 2, + kObserverFieldNumber = 3, + kTargetFieldNumber = 4, + kTimestampFieldNumber = 1, + }; + // repeated .rtech.liveapi.Player targetTeam = 5; + int targetteam_size() const; + private: + int _internal_targetteam_size() const; + public: + void clear_targetteam(); + ::rtech::liveapi::Player* mutable_targetteam(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >* + mutable_targetteam(); + private: + const ::rtech::liveapi::Player& _internal_targetteam(int index) const; + ::rtech::liveapi::Player* _internal_add_targetteam(); + public: + const ::rtech::liveapi::Player& targetteam(int index) const; + ::rtech::liveapi::Player* add_targetteam(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >& + targetteam() const; + + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player observer = 3; + bool has_observer() const; + private: + bool _internal_has_observer() const; + public: + void clear_observer(); + const ::rtech::liveapi::Player& observer() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_observer(); + ::rtech::liveapi::Player* mutable_observer(); + void set_allocated_observer(::rtech::liveapi::Player* observer); + private: + const ::rtech::liveapi::Player& _internal_observer() const; + ::rtech::liveapi::Player* _internal_mutable_observer(); + public: + void unsafe_arena_set_allocated_observer( + ::rtech::liveapi::Player* observer); + ::rtech::liveapi::Player* unsafe_arena_release_observer(); + + // .rtech.liveapi.Player target = 4; + bool has_target() const; + private: + bool _internal_has_target() const; + public: + void clear_target(); + const ::rtech::liveapi::Player& target() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_target(); + ::rtech::liveapi::Player* mutable_target(); + void set_allocated_target(::rtech::liveapi::Player* target); + private: + const ::rtech::liveapi::Player& _internal_target() const; + ::rtech::liveapi::Player* _internal_mutable_target(); + public: + void unsafe_arena_set_allocated_target( + ::rtech::liveapi::Player* target); + ::rtech::liveapi::Player* unsafe_arena_release_target(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.ObserverSwitched) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player > targetteam_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* observer_; + ::rtech::liveapi::Player* target_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class ObserverAnnotation final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.ObserverAnnotation) */ { + public: + inline ObserverAnnotation() : ObserverAnnotation(nullptr) {} + ~ObserverAnnotation() override; + explicit PROTOBUF_CONSTEXPR ObserverAnnotation(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ObserverAnnotation(const ObserverAnnotation& from); + ObserverAnnotation(ObserverAnnotation&& from) noexcept + : ObserverAnnotation() { + *this = ::std::move(from); + } + + inline ObserverAnnotation& operator=(const ObserverAnnotation& from) { + CopyFrom(from); + return *this; + } + inline ObserverAnnotation& operator=(ObserverAnnotation&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const ObserverAnnotation& default_instance() { + return *internal_default_instance(); + } + static inline const ObserverAnnotation* internal_default_instance() { + return reinterpret_cast( + &_ObserverAnnotation_default_instance_); + } + static constexpr int kIndexInFileMessages = + 10; + + friend void swap(ObserverAnnotation& a, ObserverAnnotation& b) { + a.Swap(&b); + } + inline void Swap(ObserverAnnotation* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ObserverAnnotation* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ObserverAnnotation* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const ObserverAnnotation& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const ObserverAnnotation& from) { + ObserverAnnotation::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(ObserverAnnotation* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.ObserverAnnotation"; + } + protected: + explicit ObserverAnnotation(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kTimestampFieldNumber = 1, + kAnnotationSerialFieldNumber = 3, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // int32 annotationSerial = 3; + void clear_annotationserial(); + int32_t annotationserial() const; + void set_annotationserial(int32_t value); + private: + int32_t _internal_annotationserial() const; + void _internal_set_annotationserial(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.ObserverAnnotation) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + uint64_t timestamp_; + int32_t annotationserial_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class MatchSetup final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.MatchSetup) */ { + public: + inline MatchSetup() : MatchSetup(nullptr) {} + ~MatchSetup() override; + explicit PROTOBUF_CONSTEXPR MatchSetup(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + MatchSetup(const MatchSetup& from); + MatchSetup(MatchSetup&& from) noexcept + : MatchSetup() { + *this = ::std::move(from); + } + + inline MatchSetup& operator=(const MatchSetup& from) { + CopyFrom(from); + return *this; + } + inline MatchSetup& operator=(MatchSetup&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const MatchSetup& default_instance() { + return *internal_default_instance(); + } + static inline const MatchSetup* internal_default_instance() { + return reinterpret_cast( + &_MatchSetup_default_instance_); + } + static constexpr int kIndexInFileMessages = + 11; + + friend void swap(MatchSetup& a, MatchSetup& b) { + a.Swap(&b); + } + inline void Swap(MatchSetup* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(MatchSetup* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + MatchSetup* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const MatchSetup& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const MatchSetup& from) { + MatchSetup::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(MatchSetup* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.MatchSetup"; + } + protected: + explicit MatchSetup(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kMapFieldNumber = 3, + kPlaylistNameFieldNumber = 4, + kPlaylistDescFieldNumber = 5, + kServerIdFieldNumber = 9, + kDatacenterFieldNumber = 6, + kStartingLoadoutFieldNumber = 10, + kTimestampFieldNumber = 1, + kAimAssistOnFieldNumber = 7, + kAnonymousModeFieldNumber = 8, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string map = 3; + void clear_map(); + const std::string& map() const; + template + void set_map(ArgT0&& arg0, ArgT... args); + std::string* mutable_map(); + PROTOBUF_NODISCARD std::string* release_map(); + void set_allocated_map(std::string* map); + private: + const std::string& _internal_map() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_map(const std::string& value); + std::string* _internal_mutable_map(); + public: + + // string playlistName = 4; + void clear_playlistname(); + const std::string& playlistname() const; + template + void set_playlistname(ArgT0&& arg0, ArgT... args); + std::string* mutable_playlistname(); + PROTOBUF_NODISCARD std::string* release_playlistname(); + void set_allocated_playlistname(std::string* playlistname); + private: + const std::string& _internal_playlistname() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_playlistname(const std::string& value); + std::string* _internal_mutable_playlistname(); + public: + + // string playlistDesc = 5; + void clear_playlistdesc(); + const std::string& playlistdesc() const; + template + void set_playlistdesc(ArgT0&& arg0, ArgT... args); + std::string* mutable_playlistdesc(); + PROTOBUF_NODISCARD std::string* release_playlistdesc(); + void set_allocated_playlistdesc(std::string* playlistdesc); + private: + const std::string& _internal_playlistdesc() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_playlistdesc(const std::string& value); + std::string* _internal_mutable_playlistdesc(); + public: + + // string serverId = 9; + void clear_serverid(); + const std::string& serverid() const; + template + void set_serverid(ArgT0&& arg0, ArgT... args); + std::string* mutable_serverid(); + PROTOBUF_NODISCARD std::string* release_serverid(); + void set_allocated_serverid(std::string* serverid); + private: + const std::string& _internal_serverid() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_serverid(const std::string& value); + std::string* _internal_mutable_serverid(); + public: + + // .rtech.liveapi.Datacenter datacenter = 6; + bool has_datacenter() const; + private: + bool _internal_has_datacenter() const; + public: + void clear_datacenter(); + const ::rtech::liveapi::Datacenter& datacenter() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Datacenter* release_datacenter(); + ::rtech::liveapi::Datacenter* mutable_datacenter(); + void set_allocated_datacenter(::rtech::liveapi::Datacenter* datacenter); + private: + const ::rtech::liveapi::Datacenter& _internal_datacenter() const; + ::rtech::liveapi::Datacenter* _internal_mutable_datacenter(); + public: + void unsafe_arena_set_allocated_datacenter( + ::rtech::liveapi::Datacenter* datacenter); + ::rtech::liveapi::Datacenter* unsafe_arena_release_datacenter(); + + // .rtech.liveapi.LoadoutConfiguration startingLoadout = 10; + bool has_startingloadout() const; + private: + bool _internal_has_startingloadout() const; + public: + void clear_startingloadout(); + const ::rtech::liveapi::LoadoutConfiguration& startingloadout() const; + PROTOBUF_NODISCARD ::rtech::liveapi::LoadoutConfiguration* release_startingloadout(); + ::rtech::liveapi::LoadoutConfiguration* mutable_startingloadout(); + void set_allocated_startingloadout(::rtech::liveapi::LoadoutConfiguration* startingloadout); + private: + const ::rtech::liveapi::LoadoutConfiguration& _internal_startingloadout() const; + ::rtech::liveapi::LoadoutConfiguration* _internal_mutable_startingloadout(); + public: + void unsafe_arena_set_allocated_startingloadout( + ::rtech::liveapi::LoadoutConfiguration* startingloadout); + ::rtech::liveapi::LoadoutConfiguration* unsafe_arena_release_startingloadout(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // bool aimAssistOn = 7; + void clear_aimassiston(); + bool aimassiston() const; + void set_aimassiston(bool value); + private: + bool _internal_aimassiston() const; + void _internal_set_aimassiston(bool value); + public: + + // bool anonymousMode = 8; + void clear_anonymousmode(); + bool anonymousmode() const; + void set_anonymousmode(bool value); + private: + bool _internal_anonymousmode() const; + void _internal_set_anonymousmode(bool value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.MatchSetup) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr map_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr playlistname_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr playlistdesc_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr serverid_; + ::rtech::liveapi::Datacenter* datacenter_; + ::rtech::liveapi::LoadoutConfiguration* startingloadout_; + uint64_t timestamp_; + bool aimassiston_; + bool anonymousmode_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class GameStateChanged final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.GameStateChanged) */ { + public: + inline GameStateChanged() : GameStateChanged(nullptr) {} + ~GameStateChanged() override; + explicit PROTOBUF_CONSTEXPR GameStateChanged(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + GameStateChanged(const GameStateChanged& from); + GameStateChanged(GameStateChanged&& from) noexcept + : GameStateChanged() { + *this = ::std::move(from); + } + + inline GameStateChanged& operator=(const GameStateChanged& from) { + CopyFrom(from); + return *this; + } + inline GameStateChanged& operator=(GameStateChanged&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const GameStateChanged& default_instance() { + return *internal_default_instance(); + } + static inline const GameStateChanged* internal_default_instance() { + return reinterpret_cast( + &_GameStateChanged_default_instance_); + } + static constexpr int kIndexInFileMessages = + 12; + + friend void swap(GameStateChanged& a, GameStateChanged& b) { + a.Swap(&b); + } + inline void Swap(GameStateChanged* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(GameStateChanged* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + GameStateChanged* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const GameStateChanged& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const GameStateChanged& from) { + GameStateChanged::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(GameStateChanged* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.GameStateChanged"; + } + protected: + explicit GameStateChanged(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kStateFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string state = 3; + void clear_state(); + const std::string& state() const; + template + void set_state(ArgT0&& arg0, ArgT... args); + std::string* mutable_state(); + PROTOBUF_NODISCARD std::string* release_state(); + void set_allocated_state(std::string* state); + private: + const std::string& _internal_state() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_state(const std::string& value); + std::string* _internal_mutable_state(); + public: + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.GameStateChanged) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr state_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CharacterSelected final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CharacterSelected) */ { + public: + inline CharacterSelected() : CharacterSelected(nullptr) {} + ~CharacterSelected() override; + explicit PROTOBUF_CONSTEXPR CharacterSelected(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CharacterSelected(const CharacterSelected& from); + CharacterSelected(CharacterSelected&& from) noexcept + : CharacterSelected() { + *this = ::std::move(from); + } + + inline CharacterSelected& operator=(const CharacterSelected& from) { + CopyFrom(from); + return *this; + } + inline CharacterSelected& operator=(CharacterSelected&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CharacterSelected& default_instance() { + return *internal_default_instance(); + } + static inline const CharacterSelected* internal_default_instance() { + return reinterpret_cast( + &_CharacterSelected_default_instance_); + } + static constexpr int kIndexInFileMessages = + 13; + + friend void swap(CharacterSelected& a, CharacterSelected& b) { + a.Swap(&b); + } + inline void Swap(CharacterSelected* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CharacterSelected* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CharacterSelected* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CharacterSelected& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CharacterSelected& from) { + CharacterSelected::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CharacterSelected* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CharacterSelected"; + } + protected: + explicit CharacterSelected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CharacterSelected) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class MatchStateEnd final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.MatchStateEnd) */ { + public: + inline MatchStateEnd() : MatchStateEnd(nullptr) {} + ~MatchStateEnd() override; + explicit PROTOBUF_CONSTEXPR MatchStateEnd(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + MatchStateEnd(const MatchStateEnd& from); + MatchStateEnd(MatchStateEnd&& from) noexcept + : MatchStateEnd() { + *this = ::std::move(from); + } + + inline MatchStateEnd& operator=(const MatchStateEnd& from) { + CopyFrom(from); + return *this; + } + inline MatchStateEnd& operator=(MatchStateEnd&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const MatchStateEnd& default_instance() { + return *internal_default_instance(); + } + static inline const MatchStateEnd* internal_default_instance() { + return reinterpret_cast( + &_MatchStateEnd_default_instance_); + } + static constexpr int kIndexInFileMessages = + 14; + + friend void swap(MatchStateEnd& a, MatchStateEnd& b) { + a.Swap(&b); + } + inline void Swap(MatchStateEnd* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(MatchStateEnd* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + MatchStateEnd* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const MatchStateEnd& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const MatchStateEnd& from) { + MatchStateEnd::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(MatchStateEnd* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.MatchStateEnd"; + } + protected: + explicit MatchStateEnd(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kWinnersFieldNumber = 4, + kCategoryFieldNumber = 2, + kStateFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // repeated .rtech.liveapi.Player winners = 4; + int winners_size() const; + private: + int _internal_winners_size() const; + public: + void clear_winners(); + ::rtech::liveapi::Player* mutable_winners(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >* + mutable_winners(); + private: + const ::rtech::liveapi::Player& _internal_winners(int index) const; + ::rtech::liveapi::Player* _internal_add_winners(); + public: + const ::rtech::liveapi::Player& winners(int index) const; + ::rtech::liveapi::Player* add_winners(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >& + winners() const; + + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string state = 3; + void clear_state(); + const std::string& state() const; + template + void set_state(ArgT0&& arg0, ArgT... args); + std::string* mutable_state(); + PROTOBUF_NODISCARD std::string* release_state(); + void set_allocated_state(std::string* state); + private: + const std::string& _internal_state() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_state(const std::string& value); + std::string* _internal_mutable_state(); + public: + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.MatchStateEnd) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player > winners_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr state_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class RingStartClosing final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.RingStartClosing) */ { + public: + inline RingStartClosing() : RingStartClosing(nullptr) {} + ~RingStartClosing() override; + explicit PROTOBUF_CONSTEXPR RingStartClosing(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + RingStartClosing(const RingStartClosing& from); + RingStartClosing(RingStartClosing&& from) noexcept + : RingStartClosing() { + *this = ::std::move(from); + } + + inline RingStartClosing& operator=(const RingStartClosing& from) { + CopyFrom(from); + return *this; + } + inline RingStartClosing& operator=(RingStartClosing&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const RingStartClosing& default_instance() { + return *internal_default_instance(); + } + static inline const RingStartClosing* internal_default_instance() { + return reinterpret_cast( + &_RingStartClosing_default_instance_); + } + static constexpr int kIndexInFileMessages = + 15; + + friend void swap(RingStartClosing& a, RingStartClosing& b) { + a.Swap(&b); + } + inline void Swap(RingStartClosing* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(RingStartClosing* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + RingStartClosing* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const RingStartClosing& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const RingStartClosing& from) { + RingStartClosing::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(RingStartClosing* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.RingStartClosing"; + } + protected: + explicit RingStartClosing(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kCenterFieldNumber = 4, + kTimestampFieldNumber = 1, + kStageFieldNumber = 3, + kCurrentRadiusFieldNumber = 5, + kEndRadiusFieldNumber = 6, + kShrinkDurationFieldNumber = 7, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Vector3 center = 4; + bool has_center() const; + private: + bool _internal_has_center() const; + public: + void clear_center(); + const ::rtech::liveapi::Vector3& center() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Vector3* release_center(); + ::rtech::liveapi::Vector3* mutable_center(); + void set_allocated_center(::rtech::liveapi::Vector3* center); + private: + const ::rtech::liveapi::Vector3& _internal_center() const; + ::rtech::liveapi::Vector3* _internal_mutable_center(); + public: + void unsafe_arena_set_allocated_center( + ::rtech::liveapi::Vector3* center); + ::rtech::liveapi::Vector3* unsafe_arena_release_center(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // uint32 stage = 3; + void clear_stage(); + uint32_t stage() const; + void set_stage(uint32_t value); + private: + uint32_t _internal_stage() const; + void _internal_set_stage(uint32_t value); + public: + + // float currentRadius = 5; + void clear_currentradius(); + float currentradius() const; + void set_currentradius(float value); + private: + float _internal_currentradius() const; + void _internal_set_currentradius(float value); + public: + + // float endRadius = 6; + void clear_endradius(); + float endradius() const; + void set_endradius(float value); + private: + float _internal_endradius() const; + void _internal_set_endradius(float value); + public: + + // float shrinkDuration = 7; + void clear_shrinkduration(); + float shrinkduration() const; + void set_shrinkduration(float value); + private: + float _internal_shrinkduration() const; + void _internal_set_shrinkduration(float value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.RingStartClosing) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Vector3* center_; + uint64_t timestamp_; + uint32_t stage_; + float currentradius_; + float endradius_; + float shrinkduration_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class RingFinishedClosing final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.RingFinishedClosing) */ { + public: + inline RingFinishedClosing() : RingFinishedClosing(nullptr) {} + ~RingFinishedClosing() override; + explicit PROTOBUF_CONSTEXPR RingFinishedClosing(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + RingFinishedClosing(const RingFinishedClosing& from); + RingFinishedClosing(RingFinishedClosing&& from) noexcept + : RingFinishedClosing() { + *this = ::std::move(from); + } + + inline RingFinishedClosing& operator=(const RingFinishedClosing& from) { + CopyFrom(from); + return *this; + } + inline RingFinishedClosing& operator=(RingFinishedClosing&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const RingFinishedClosing& default_instance() { + return *internal_default_instance(); + } + static inline const RingFinishedClosing* internal_default_instance() { + return reinterpret_cast( + &_RingFinishedClosing_default_instance_); + } + static constexpr int kIndexInFileMessages = + 16; + + friend void swap(RingFinishedClosing& a, RingFinishedClosing& b) { + a.Swap(&b); + } + inline void Swap(RingFinishedClosing* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(RingFinishedClosing* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + RingFinishedClosing* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const RingFinishedClosing& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const RingFinishedClosing& from) { + RingFinishedClosing::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(RingFinishedClosing* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.RingFinishedClosing"; + } + protected: + explicit RingFinishedClosing(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kCenterFieldNumber = 4, + kTimestampFieldNumber = 1, + kStageFieldNumber = 3, + kCurrentRadiusFieldNumber = 5, + kShrinkDurationFieldNumber = 7, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Vector3 center = 4; + bool has_center() const; + private: + bool _internal_has_center() const; + public: + void clear_center(); + const ::rtech::liveapi::Vector3& center() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Vector3* release_center(); + ::rtech::liveapi::Vector3* mutable_center(); + void set_allocated_center(::rtech::liveapi::Vector3* center); + private: + const ::rtech::liveapi::Vector3& _internal_center() const; + ::rtech::liveapi::Vector3* _internal_mutable_center(); + public: + void unsafe_arena_set_allocated_center( + ::rtech::liveapi::Vector3* center); + ::rtech::liveapi::Vector3* unsafe_arena_release_center(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // uint32 stage = 3; + void clear_stage(); + uint32_t stage() const; + void set_stage(uint32_t value); + private: + uint32_t _internal_stage() const; + void _internal_set_stage(uint32_t value); + public: + + // float currentRadius = 5; + void clear_currentradius(); + float currentradius() const; + void set_currentradius(float value); + private: + float _internal_currentradius() const; + void _internal_set_currentradius(float value); + public: + + // float shrinkDuration = 7; + void clear_shrinkduration(); + float shrinkduration() const; + void set_shrinkduration(float value); + private: + float _internal_shrinkduration() const; + void _internal_set_shrinkduration(float value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.RingFinishedClosing) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Vector3* center_; + uint64_t timestamp_; + uint32_t stage_; + float currentradius_; + float shrinkduration_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerConnected final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerConnected) */ { + public: + inline PlayerConnected() : PlayerConnected(nullptr) {} + ~PlayerConnected() override; + explicit PROTOBUF_CONSTEXPR PlayerConnected(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerConnected(const PlayerConnected& from); + PlayerConnected(PlayerConnected&& from) noexcept + : PlayerConnected() { + *this = ::std::move(from); + } + + inline PlayerConnected& operator=(const PlayerConnected& from) { + CopyFrom(from); + return *this; + } + inline PlayerConnected& operator=(PlayerConnected&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerConnected& default_instance() { + return *internal_default_instance(); + } + static inline const PlayerConnected* internal_default_instance() { + return reinterpret_cast( + &_PlayerConnected_default_instance_); + } + static constexpr int kIndexInFileMessages = + 17; + + friend void swap(PlayerConnected& a, PlayerConnected& b) { + a.Swap(&b); + } + inline void Swap(PlayerConnected* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerConnected* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerConnected* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerConnected& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerConnected& from) { + PlayerConnected::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerConnected* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerConnected"; + } + protected: + explicit PlayerConnected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerConnected) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerDisconnected final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerDisconnected) */ { + public: + inline PlayerDisconnected() : PlayerDisconnected(nullptr) {} + ~PlayerDisconnected() override; + explicit PROTOBUF_CONSTEXPR PlayerDisconnected(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerDisconnected(const PlayerDisconnected& from); + PlayerDisconnected(PlayerDisconnected&& from) noexcept + : PlayerDisconnected() { + *this = ::std::move(from); + } + + inline PlayerDisconnected& operator=(const PlayerDisconnected& from) { + CopyFrom(from); + return *this; + } + inline PlayerDisconnected& operator=(PlayerDisconnected&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerDisconnected& default_instance() { + return *internal_default_instance(); + } + static inline const PlayerDisconnected* internal_default_instance() { + return reinterpret_cast( + &_PlayerDisconnected_default_instance_); + } + static constexpr int kIndexInFileMessages = + 18; + + friend void swap(PlayerDisconnected& a, PlayerDisconnected& b) { + a.Swap(&b); + } + inline void Swap(PlayerDisconnected* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerDisconnected* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerDisconnected* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerDisconnected& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerDisconnected& from) { + PlayerDisconnected::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerDisconnected* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerDisconnected"; + } + protected: + explicit PlayerDisconnected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + kCanReconnectFieldNumber = 4, + kIsAliveFieldNumber = 5, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // bool canReconnect = 4; + void clear_canreconnect(); + bool canreconnect() const; + void set_canreconnect(bool value); + private: + bool _internal_canreconnect() const; + void _internal_set_canreconnect(bool value); + public: + + // bool isAlive = 5; + void clear_isalive(); + bool isalive() const; + void set_isalive(bool value); + private: + bool _internal_isalive() const; + void _internal_set_isalive(bool value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerDisconnected) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + bool canreconnect_; + bool isalive_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerStatChanged final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerStatChanged) */ { + public: + inline PlayerStatChanged() : PlayerStatChanged(nullptr) {} + ~PlayerStatChanged() override; + explicit PROTOBUF_CONSTEXPR PlayerStatChanged(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerStatChanged(const PlayerStatChanged& from); + PlayerStatChanged(PlayerStatChanged&& from) noexcept + : PlayerStatChanged() { + *this = ::std::move(from); + } + + inline PlayerStatChanged& operator=(const PlayerStatChanged& from) { + CopyFrom(from); + return *this; + } + inline PlayerStatChanged& operator=(PlayerStatChanged&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerStatChanged& default_instance() { + return *internal_default_instance(); + } + enum NewValueCase { + kIntValue = 5, + kFloatValue = 6, + kBoolValue = 7, + NEWVALUE_NOT_SET = 0, + }; + + static inline const PlayerStatChanged* internal_default_instance() { + return reinterpret_cast( + &_PlayerStatChanged_default_instance_); + } + static constexpr int kIndexInFileMessages = + 19; + + friend void swap(PlayerStatChanged& a, PlayerStatChanged& b) { + a.Swap(&b); + } + inline void Swap(PlayerStatChanged* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerStatChanged* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerStatChanged* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerStatChanged& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerStatChanged& from) { + PlayerStatChanged::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerStatChanged* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerStatChanged"; + } + protected: + explicit PlayerStatChanged(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kStatNameFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + kIntValueFieldNumber = 5, + kFloatValueFieldNumber = 6, + kBoolValueFieldNumber = 7, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string statName = 4; + void clear_statname(); + const std::string& statname() const; + template + void set_statname(ArgT0&& arg0, ArgT... args); + std::string* mutable_statname(); + PROTOBUF_NODISCARD std::string* release_statname(); + void set_allocated_statname(std::string* statname); + private: + const std::string& _internal_statname() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_statname(const std::string& value); + std::string* _internal_mutable_statname(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // uint32 intValue = 5; + bool has_intvalue() const; + private: + bool _internal_has_intvalue() const; + public: + void clear_intvalue(); + uint32_t intvalue() const; + void set_intvalue(uint32_t value); + private: + uint32_t _internal_intvalue() const; + void _internal_set_intvalue(uint32_t value); + public: + + // float floatValue = 6; + bool has_floatvalue() const; + private: + bool _internal_has_floatvalue() const; + public: + void clear_floatvalue(); + float floatvalue() const; + void set_floatvalue(float value); + private: + float _internal_floatvalue() const; + void _internal_set_floatvalue(float value); + public: + + // bool boolValue = 7; + bool has_boolvalue() const; + private: + bool _internal_has_boolvalue() const; + public: + void clear_boolvalue(); + bool boolvalue() const; + void set_boolvalue(bool value); + private: + bool _internal_boolvalue() const; + void _internal_set_boolvalue(bool value); + public: + + void clear_newValue(); + NewValueCase newValue_case() const; + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerStatChanged) + private: + class _Internal; + void set_has_intvalue(); + void set_has_floatvalue(); + void set_has_boolvalue(); + + inline bool has_newValue() const; + inline void clear_has_newValue(); + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr statname_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + union NewValueUnion { + constexpr NewValueUnion() : _constinit_{} {} + ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_; + uint32_t intvalue_; + float floatvalue_; + bool boolvalue_; + } newValue_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + uint32_t _oneof_case_[1]; + + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerUpgradeTierChanged final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerUpgradeTierChanged) */ { + public: + inline PlayerUpgradeTierChanged() : PlayerUpgradeTierChanged(nullptr) {} + ~PlayerUpgradeTierChanged() override; + explicit PROTOBUF_CONSTEXPR PlayerUpgradeTierChanged(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerUpgradeTierChanged(const PlayerUpgradeTierChanged& from); + PlayerUpgradeTierChanged(PlayerUpgradeTierChanged&& from) noexcept + : PlayerUpgradeTierChanged() { + *this = ::std::move(from); + } + + inline PlayerUpgradeTierChanged& operator=(const PlayerUpgradeTierChanged& from) { + CopyFrom(from); + return *this; + } + inline PlayerUpgradeTierChanged& operator=(PlayerUpgradeTierChanged&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerUpgradeTierChanged& default_instance() { + return *internal_default_instance(); + } + static inline const PlayerUpgradeTierChanged* internal_default_instance() { + return reinterpret_cast( + &_PlayerUpgradeTierChanged_default_instance_); + } + static constexpr int kIndexInFileMessages = + 20; + + friend void swap(PlayerUpgradeTierChanged& a, PlayerUpgradeTierChanged& b) { + a.Swap(&b); + } + inline void Swap(PlayerUpgradeTierChanged* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerUpgradeTierChanged* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerUpgradeTierChanged* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerUpgradeTierChanged& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerUpgradeTierChanged& from) { + PlayerUpgradeTierChanged::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerUpgradeTierChanged* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerUpgradeTierChanged"; + } + protected: + explicit PlayerUpgradeTierChanged(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + kLevelFieldNumber = 4, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // int32 level = 4; + void clear_level(); + int32_t level() const; + void set_level(int32_t value); + private: + int32_t _internal_level() const; + void _internal_set_level(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerUpgradeTierChanged) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + int32_t level_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerDamaged final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerDamaged) */ { + public: + inline PlayerDamaged() : PlayerDamaged(nullptr) {} + ~PlayerDamaged() override; + explicit PROTOBUF_CONSTEXPR PlayerDamaged(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerDamaged(const PlayerDamaged& from); + PlayerDamaged(PlayerDamaged&& from) noexcept + : PlayerDamaged() { + *this = ::std::move(from); + } + + inline PlayerDamaged& operator=(const PlayerDamaged& from) { + CopyFrom(from); + return *this; + } + inline PlayerDamaged& operator=(PlayerDamaged&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerDamaged& default_instance() { + return *internal_default_instance(); + } + static inline const PlayerDamaged* internal_default_instance() { + return reinterpret_cast( + &_PlayerDamaged_default_instance_); + } + static constexpr int kIndexInFileMessages = + 21; + + friend void swap(PlayerDamaged& a, PlayerDamaged& b) { + a.Swap(&b); + } + inline void Swap(PlayerDamaged* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerDamaged* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerDamaged* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerDamaged& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerDamaged& from) { + PlayerDamaged::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerDamaged* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerDamaged"; + } + protected: + explicit PlayerDamaged(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kWeaponFieldNumber = 5, + kAttackerFieldNumber = 3, + kVictimFieldNumber = 4, + kTimestampFieldNumber = 1, + kDamageInflictedFieldNumber = 6, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string weapon = 5; + void clear_weapon(); + const std::string& weapon() const; + template + void set_weapon(ArgT0&& arg0, ArgT... args); + std::string* mutable_weapon(); + PROTOBUF_NODISCARD std::string* release_weapon(); + void set_allocated_weapon(std::string* weapon); + private: + const std::string& _internal_weapon() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_weapon(const std::string& value); + std::string* _internal_mutable_weapon(); + public: + + // .rtech.liveapi.Player attacker = 3; + bool has_attacker() const; + private: + bool _internal_has_attacker() const; + public: + void clear_attacker(); + const ::rtech::liveapi::Player& attacker() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_attacker(); + ::rtech::liveapi::Player* mutable_attacker(); + void set_allocated_attacker(::rtech::liveapi::Player* attacker); + private: + const ::rtech::liveapi::Player& _internal_attacker() const; + ::rtech::liveapi::Player* _internal_mutable_attacker(); + public: + void unsafe_arena_set_allocated_attacker( + ::rtech::liveapi::Player* attacker); + ::rtech::liveapi::Player* unsafe_arena_release_attacker(); + + // .rtech.liveapi.Player victim = 4; + bool has_victim() const; + private: + bool _internal_has_victim() const; + public: + void clear_victim(); + const ::rtech::liveapi::Player& victim() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_victim(); + ::rtech::liveapi::Player* mutable_victim(); + void set_allocated_victim(::rtech::liveapi::Player* victim); + private: + const ::rtech::liveapi::Player& _internal_victim() const; + ::rtech::liveapi::Player* _internal_mutable_victim(); + public: + void unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim); + ::rtech::liveapi::Player* unsafe_arena_release_victim(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // uint32 damageInflicted = 6; + void clear_damageinflicted(); + uint32_t damageinflicted() const; + void set_damageinflicted(uint32_t value); + private: + uint32_t _internal_damageinflicted() const; + void _internal_set_damageinflicted(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerDamaged) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr weapon_; + ::rtech::liveapi::Player* attacker_; + ::rtech::liveapi::Player* victim_; + uint64_t timestamp_; + uint32_t damageinflicted_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerKilled final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerKilled) */ { + public: + inline PlayerKilled() : PlayerKilled(nullptr) {} + ~PlayerKilled() override; + explicit PROTOBUF_CONSTEXPR PlayerKilled(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerKilled(const PlayerKilled& from); + PlayerKilled(PlayerKilled&& from) noexcept + : PlayerKilled() { + *this = ::std::move(from); + } + + inline PlayerKilled& operator=(const PlayerKilled& from) { + CopyFrom(from); + return *this; + } + inline PlayerKilled& operator=(PlayerKilled&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerKilled& default_instance() { + return *internal_default_instance(); + } + static inline const PlayerKilled* internal_default_instance() { + return reinterpret_cast( + &_PlayerKilled_default_instance_); + } + static constexpr int kIndexInFileMessages = + 22; + + friend void swap(PlayerKilled& a, PlayerKilled& b) { + a.Swap(&b); + } + inline void Swap(PlayerKilled* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerKilled* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerKilled* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerKilled& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerKilled& from) { + PlayerKilled::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerKilled* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerKilled"; + } + protected: + explicit PlayerKilled(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kWeaponFieldNumber = 6, + kAttackerFieldNumber = 3, + kVictimFieldNumber = 4, + kAwardedToFieldNumber = 5, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string weapon = 6; + void clear_weapon(); + const std::string& weapon() const; + template + void set_weapon(ArgT0&& arg0, ArgT... args); + std::string* mutable_weapon(); + PROTOBUF_NODISCARD std::string* release_weapon(); + void set_allocated_weapon(std::string* weapon); + private: + const std::string& _internal_weapon() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_weapon(const std::string& value); + std::string* _internal_mutable_weapon(); + public: + + // .rtech.liveapi.Player attacker = 3; + bool has_attacker() const; + private: + bool _internal_has_attacker() const; + public: + void clear_attacker(); + const ::rtech::liveapi::Player& attacker() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_attacker(); + ::rtech::liveapi::Player* mutable_attacker(); + void set_allocated_attacker(::rtech::liveapi::Player* attacker); + private: + const ::rtech::liveapi::Player& _internal_attacker() const; + ::rtech::liveapi::Player* _internal_mutable_attacker(); + public: + void unsafe_arena_set_allocated_attacker( + ::rtech::liveapi::Player* attacker); + ::rtech::liveapi::Player* unsafe_arena_release_attacker(); + + // .rtech.liveapi.Player victim = 4; + bool has_victim() const; + private: + bool _internal_has_victim() const; + public: + void clear_victim(); + const ::rtech::liveapi::Player& victim() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_victim(); + ::rtech::liveapi::Player* mutable_victim(); + void set_allocated_victim(::rtech::liveapi::Player* victim); + private: + const ::rtech::liveapi::Player& _internal_victim() const; + ::rtech::liveapi::Player* _internal_mutable_victim(); + public: + void unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim); + ::rtech::liveapi::Player* unsafe_arena_release_victim(); + + // .rtech.liveapi.Player awardedTo = 5; + bool has_awardedto() const; + private: + bool _internal_has_awardedto() const; + public: + void clear_awardedto(); + const ::rtech::liveapi::Player& awardedto() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_awardedto(); + ::rtech::liveapi::Player* mutable_awardedto(); + void set_allocated_awardedto(::rtech::liveapi::Player* awardedto); + private: + const ::rtech::liveapi::Player& _internal_awardedto() const; + ::rtech::liveapi::Player* _internal_mutable_awardedto(); + public: + void unsafe_arena_set_allocated_awardedto( + ::rtech::liveapi::Player* awardedto); + ::rtech::liveapi::Player* unsafe_arena_release_awardedto(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerKilled) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr weapon_; + ::rtech::liveapi::Player* attacker_; + ::rtech::liveapi::Player* victim_; + ::rtech::liveapi::Player* awardedto_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerDowned final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerDowned) */ { + public: + inline PlayerDowned() : PlayerDowned(nullptr) {} + ~PlayerDowned() override; + explicit PROTOBUF_CONSTEXPR PlayerDowned(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerDowned(const PlayerDowned& from); + PlayerDowned(PlayerDowned&& from) noexcept + : PlayerDowned() { + *this = ::std::move(from); + } + + inline PlayerDowned& operator=(const PlayerDowned& from) { + CopyFrom(from); + return *this; + } + inline PlayerDowned& operator=(PlayerDowned&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerDowned& default_instance() { + return *internal_default_instance(); + } + static inline const PlayerDowned* internal_default_instance() { + return reinterpret_cast( + &_PlayerDowned_default_instance_); + } + static constexpr int kIndexInFileMessages = + 23; + + friend void swap(PlayerDowned& a, PlayerDowned& b) { + a.Swap(&b); + } + inline void Swap(PlayerDowned* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerDowned* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerDowned* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerDowned& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerDowned& from) { + PlayerDowned::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerDowned* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerDowned"; + } + protected: + explicit PlayerDowned(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kWeaponFieldNumber = 5, + kAttackerFieldNumber = 3, + kVictimFieldNumber = 4, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string weapon = 5; + void clear_weapon(); + const std::string& weapon() const; + template + void set_weapon(ArgT0&& arg0, ArgT... args); + std::string* mutable_weapon(); + PROTOBUF_NODISCARD std::string* release_weapon(); + void set_allocated_weapon(std::string* weapon); + private: + const std::string& _internal_weapon() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_weapon(const std::string& value); + std::string* _internal_mutable_weapon(); + public: + + // .rtech.liveapi.Player attacker = 3; + bool has_attacker() const; + private: + bool _internal_has_attacker() const; + public: + void clear_attacker(); + const ::rtech::liveapi::Player& attacker() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_attacker(); + ::rtech::liveapi::Player* mutable_attacker(); + void set_allocated_attacker(::rtech::liveapi::Player* attacker); + private: + const ::rtech::liveapi::Player& _internal_attacker() const; + ::rtech::liveapi::Player* _internal_mutable_attacker(); + public: + void unsafe_arena_set_allocated_attacker( + ::rtech::liveapi::Player* attacker); + ::rtech::liveapi::Player* unsafe_arena_release_attacker(); + + // .rtech.liveapi.Player victim = 4; + bool has_victim() const; + private: + bool _internal_has_victim() const; + public: + void clear_victim(); + const ::rtech::liveapi::Player& victim() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_victim(); + ::rtech::liveapi::Player* mutable_victim(); + void set_allocated_victim(::rtech::liveapi::Player* victim); + private: + const ::rtech::liveapi::Player& _internal_victim() const; + ::rtech::liveapi::Player* _internal_mutable_victim(); + public: + void unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim); + ::rtech::liveapi::Player* unsafe_arena_release_victim(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerDowned) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr weapon_; + ::rtech::liveapi::Player* attacker_; + ::rtech::liveapi::Player* victim_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerAssist final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerAssist) */ { + public: + inline PlayerAssist() : PlayerAssist(nullptr) {} + ~PlayerAssist() override; + explicit PROTOBUF_CONSTEXPR PlayerAssist(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerAssist(const PlayerAssist& from); + PlayerAssist(PlayerAssist&& from) noexcept + : PlayerAssist() { + *this = ::std::move(from); + } + + inline PlayerAssist& operator=(const PlayerAssist& from) { + CopyFrom(from); + return *this; + } + inline PlayerAssist& operator=(PlayerAssist&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerAssist& default_instance() { + return *internal_default_instance(); + } + static inline const PlayerAssist* internal_default_instance() { + return reinterpret_cast( + &_PlayerAssist_default_instance_); + } + static constexpr int kIndexInFileMessages = + 24; + + friend void swap(PlayerAssist& a, PlayerAssist& b) { + a.Swap(&b); + } + inline void Swap(PlayerAssist* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerAssist* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerAssist* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerAssist& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerAssist& from) { + PlayerAssist::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerAssist* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerAssist"; + } + protected: + explicit PlayerAssist(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kWeaponFieldNumber = 5, + kAssistantFieldNumber = 3, + kVictimFieldNumber = 4, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string weapon = 5; + void clear_weapon(); + const std::string& weapon() const; + template + void set_weapon(ArgT0&& arg0, ArgT... args); + std::string* mutable_weapon(); + PROTOBUF_NODISCARD std::string* release_weapon(); + void set_allocated_weapon(std::string* weapon); + private: + const std::string& _internal_weapon() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_weapon(const std::string& value); + std::string* _internal_mutable_weapon(); + public: + + // .rtech.liveapi.Player assistant = 3; + bool has_assistant() const; + private: + bool _internal_has_assistant() const; + public: + void clear_assistant(); + const ::rtech::liveapi::Player& assistant() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_assistant(); + ::rtech::liveapi::Player* mutable_assistant(); + void set_allocated_assistant(::rtech::liveapi::Player* assistant); + private: + const ::rtech::liveapi::Player& _internal_assistant() const; + ::rtech::liveapi::Player* _internal_mutable_assistant(); + public: + void unsafe_arena_set_allocated_assistant( + ::rtech::liveapi::Player* assistant); + ::rtech::liveapi::Player* unsafe_arena_release_assistant(); + + // .rtech.liveapi.Player victim = 4; + bool has_victim() const; + private: + bool _internal_has_victim() const; + public: + void clear_victim(); + const ::rtech::liveapi::Player& victim() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_victim(); + ::rtech::liveapi::Player* mutable_victim(); + void set_allocated_victim(::rtech::liveapi::Player* victim); + private: + const ::rtech::liveapi::Player& _internal_victim() const; + ::rtech::liveapi::Player* _internal_mutable_victim(); + public: + void unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim); + ::rtech::liveapi::Player* unsafe_arena_release_victim(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerAssist) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr weapon_; + ::rtech::liveapi::Player* assistant_; + ::rtech::liveapi::Player* victim_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class SquadEliminated final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.SquadEliminated) */ { + public: + inline SquadEliminated() : SquadEliminated(nullptr) {} + ~SquadEliminated() override; + explicit PROTOBUF_CONSTEXPR SquadEliminated(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + SquadEliminated(const SquadEliminated& from); + SquadEliminated(SquadEliminated&& from) noexcept + : SquadEliminated() { + *this = ::std::move(from); + } + + inline SquadEliminated& operator=(const SquadEliminated& from) { + CopyFrom(from); + return *this; + } + inline SquadEliminated& operator=(SquadEliminated&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const SquadEliminated& default_instance() { + return *internal_default_instance(); + } + static inline const SquadEliminated* internal_default_instance() { + return reinterpret_cast( + &_SquadEliminated_default_instance_); + } + static constexpr int kIndexInFileMessages = + 25; + + friend void swap(SquadEliminated& a, SquadEliminated& b) { + a.Swap(&b); + } + inline void Swap(SquadEliminated* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(SquadEliminated* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + SquadEliminated* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const SquadEliminated& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const SquadEliminated& from) { + SquadEliminated::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(SquadEliminated* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.SquadEliminated"; + } + protected: + explicit SquadEliminated(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kPlayersFieldNumber = 3, + kCategoryFieldNumber = 2, + kTimestampFieldNumber = 1, + }; + // repeated .rtech.liveapi.Player players = 3; + int players_size() const; + private: + int _internal_players_size() const; + public: + void clear_players(); + ::rtech::liveapi::Player* mutable_players(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >* + mutable_players(); + private: + const ::rtech::liveapi::Player& _internal_players(int index) const; + ::rtech::liveapi::Player* _internal_add_players(); + public: + const ::rtech::liveapi::Player& players(int index) const; + ::rtech::liveapi::Player* add_players(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >& + players() const; + + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.SquadEliminated) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player > players_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class GibraltarShieldAbsorbed final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.GibraltarShieldAbsorbed) */ { + public: + inline GibraltarShieldAbsorbed() : GibraltarShieldAbsorbed(nullptr) {} + ~GibraltarShieldAbsorbed() override; + explicit PROTOBUF_CONSTEXPR GibraltarShieldAbsorbed(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + GibraltarShieldAbsorbed(const GibraltarShieldAbsorbed& from); + GibraltarShieldAbsorbed(GibraltarShieldAbsorbed&& from) noexcept + : GibraltarShieldAbsorbed() { + *this = ::std::move(from); + } + + inline GibraltarShieldAbsorbed& operator=(const GibraltarShieldAbsorbed& from) { + CopyFrom(from); + return *this; + } + inline GibraltarShieldAbsorbed& operator=(GibraltarShieldAbsorbed&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const GibraltarShieldAbsorbed& default_instance() { + return *internal_default_instance(); + } + static inline const GibraltarShieldAbsorbed* internal_default_instance() { + return reinterpret_cast( + &_GibraltarShieldAbsorbed_default_instance_); + } + static constexpr int kIndexInFileMessages = + 26; + + friend void swap(GibraltarShieldAbsorbed& a, GibraltarShieldAbsorbed& b) { + a.Swap(&b); + } + inline void Swap(GibraltarShieldAbsorbed* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(GibraltarShieldAbsorbed* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + GibraltarShieldAbsorbed* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const GibraltarShieldAbsorbed& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const GibraltarShieldAbsorbed& from) { + GibraltarShieldAbsorbed::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(GibraltarShieldAbsorbed* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.GibraltarShieldAbsorbed"; + } + protected: + explicit GibraltarShieldAbsorbed(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kAttackerFieldNumber = 3, + kVictimFieldNumber = 4, + kTimestampFieldNumber = 1, + kDamageInflictedFieldNumber = 6, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player attacker = 3; + bool has_attacker() const; + private: + bool _internal_has_attacker() const; + public: + void clear_attacker(); + const ::rtech::liveapi::Player& attacker() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_attacker(); + ::rtech::liveapi::Player* mutable_attacker(); + void set_allocated_attacker(::rtech::liveapi::Player* attacker); + private: + const ::rtech::liveapi::Player& _internal_attacker() const; + ::rtech::liveapi::Player* _internal_mutable_attacker(); + public: + void unsafe_arena_set_allocated_attacker( + ::rtech::liveapi::Player* attacker); + ::rtech::liveapi::Player* unsafe_arena_release_attacker(); + + // .rtech.liveapi.Player victim = 4; + bool has_victim() const; + private: + bool _internal_has_victim() const; + public: + void clear_victim(); + const ::rtech::liveapi::Player& victim() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_victim(); + ::rtech::liveapi::Player* mutable_victim(); + void set_allocated_victim(::rtech::liveapi::Player* victim); + private: + const ::rtech::liveapi::Player& _internal_victim() const; + ::rtech::liveapi::Player* _internal_mutable_victim(); + public: + void unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim); + ::rtech::liveapi::Player* unsafe_arena_release_victim(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // uint32 damageInflicted = 6; + void clear_damageinflicted(); + uint32_t damageinflicted() const; + void set_damageinflicted(uint32_t value); + private: + uint32_t _internal_damageinflicted() const; + void _internal_set_damageinflicted(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.GibraltarShieldAbsorbed) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* attacker_; + ::rtech::liveapi::Player* victim_; + uint64_t timestamp_; + uint32_t damageinflicted_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class RevenantForgedShadowDamaged final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.RevenantForgedShadowDamaged) */ { + public: + inline RevenantForgedShadowDamaged() : RevenantForgedShadowDamaged(nullptr) {} + ~RevenantForgedShadowDamaged() override; + explicit PROTOBUF_CONSTEXPR RevenantForgedShadowDamaged(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + RevenantForgedShadowDamaged(const RevenantForgedShadowDamaged& from); + RevenantForgedShadowDamaged(RevenantForgedShadowDamaged&& from) noexcept + : RevenantForgedShadowDamaged() { + *this = ::std::move(from); + } + + inline RevenantForgedShadowDamaged& operator=(const RevenantForgedShadowDamaged& from) { + CopyFrom(from); + return *this; + } + inline RevenantForgedShadowDamaged& operator=(RevenantForgedShadowDamaged&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const RevenantForgedShadowDamaged& default_instance() { + return *internal_default_instance(); + } + static inline const RevenantForgedShadowDamaged* internal_default_instance() { + return reinterpret_cast( + &_RevenantForgedShadowDamaged_default_instance_); + } + static constexpr int kIndexInFileMessages = + 27; + + friend void swap(RevenantForgedShadowDamaged& a, RevenantForgedShadowDamaged& b) { + a.Swap(&b); + } + inline void Swap(RevenantForgedShadowDamaged* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(RevenantForgedShadowDamaged* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + RevenantForgedShadowDamaged* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const RevenantForgedShadowDamaged& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const RevenantForgedShadowDamaged& from) { + RevenantForgedShadowDamaged::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(RevenantForgedShadowDamaged* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.RevenantForgedShadowDamaged"; + } + protected: + explicit RevenantForgedShadowDamaged(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kAttackerFieldNumber = 3, + kVictimFieldNumber = 4, + kTimestampFieldNumber = 1, + kDamageInflictedFieldNumber = 6, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player attacker = 3; + bool has_attacker() const; + private: + bool _internal_has_attacker() const; + public: + void clear_attacker(); + const ::rtech::liveapi::Player& attacker() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_attacker(); + ::rtech::liveapi::Player* mutable_attacker(); + void set_allocated_attacker(::rtech::liveapi::Player* attacker); + private: + const ::rtech::liveapi::Player& _internal_attacker() const; + ::rtech::liveapi::Player* _internal_mutable_attacker(); + public: + void unsafe_arena_set_allocated_attacker( + ::rtech::liveapi::Player* attacker); + ::rtech::liveapi::Player* unsafe_arena_release_attacker(); + + // .rtech.liveapi.Player victim = 4; + bool has_victim() const; + private: + bool _internal_has_victim() const; + public: + void clear_victim(); + const ::rtech::liveapi::Player& victim() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_victim(); + ::rtech::liveapi::Player* mutable_victim(); + void set_allocated_victim(::rtech::liveapi::Player* victim); + private: + const ::rtech::liveapi::Player& _internal_victim() const; + ::rtech::liveapi::Player* _internal_mutable_victim(); + public: + void unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim); + ::rtech::liveapi::Player* unsafe_arena_release_victim(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // uint32 damageInflicted = 6; + void clear_damageinflicted(); + uint32_t damageinflicted() const; + void set_damageinflicted(uint32_t value); + private: + uint32_t _internal_damageinflicted() const; + void _internal_set_damageinflicted(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.RevenantForgedShadowDamaged) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* attacker_; + ::rtech::liveapi::Player* victim_; + uint64_t timestamp_; + uint32_t damageinflicted_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerRespawnTeam final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerRespawnTeam) */ { + public: + inline PlayerRespawnTeam() : PlayerRespawnTeam(nullptr) {} + ~PlayerRespawnTeam() override; + explicit PROTOBUF_CONSTEXPR PlayerRespawnTeam(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerRespawnTeam(const PlayerRespawnTeam& from); + PlayerRespawnTeam(PlayerRespawnTeam&& from) noexcept + : PlayerRespawnTeam() { + *this = ::std::move(from); + } + + inline PlayerRespawnTeam& operator=(const PlayerRespawnTeam& from) { + CopyFrom(from); + return *this; + } + inline PlayerRespawnTeam& operator=(PlayerRespawnTeam&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerRespawnTeam& default_instance() { + return *internal_default_instance(); + } + static inline const PlayerRespawnTeam* internal_default_instance() { + return reinterpret_cast( + &_PlayerRespawnTeam_default_instance_); + } + static constexpr int kIndexInFileMessages = + 28; + + friend void swap(PlayerRespawnTeam& a, PlayerRespawnTeam& b) { + a.Swap(&b); + } + inline void Swap(PlayerRespawnTeam* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerRespawnTeam* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerRespawnTeam* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerRespawnTeam& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerRespawnTeam& from) { + PlayerRespawnTeam::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerRespawnTeam* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerRespawnTeam"; + } + protected: + explicit PlayerRespawnTeam(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kRespawnedFieldNumber = 4, + kCategoryFieldNumber = 2, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // repeated .rtech.liveapi.Player respawned = 4; + int respawned_size() const; + private: + int _internal_respawned_size() const; + public: + void clear_respawned(); + ::rtech::liveapi::Player* mutable_respawned(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >* + mutable_respawned(); + private: + const ::rtech::liveapi::Player& _internal_respawned(int index) const; + ::rtech::liveapi::Player* _internal_add_respawned(); + public: + const ::rtech::liveapi::Player& respawned(int index) const; + ::rtech::liveapi::Player* add_respawned(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >& + respawned() const; + + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerRespawnTeam) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player > respawned_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerRevive final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerRevive) */ { + public: + inline PlayerRevive() : PlayerRevive(nullptr) {} + ~PlayerRevive() override; + explicit PROTOBUF_CONSTEXPR PlayerRevive(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerRevive(const PlayerRevive& from); + PlayerRevive(PlayerRevive&& from) noexcept + : PlayerRevive() { + *this = ::std::move(from); + } + + inline PlayerRevive& operator=(const PlayerRevive& from) { + CopyFrom(from); + return *this; + } + inline PlayerRevive& operator=(PlayerRevive&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerRevive& default_instance() { + return *internal_default_instance(); + } + static inline const PlayerRevive* internal_default_instance() { + return reinterpret_cast( + &_PlayerRevive_default_instance_); + } + static constexpr int kIndexInFileMessages = + 29; + + friend void swap(PlayerRevive& a, PlayerRevive& b) { + a.Swap(&b); + } + inline void Swap(PlayerRevive* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerRevive* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerRevive* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerRevive& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerRevive& from) { + PlayerRevive::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerRevive* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerRevive"; + } + protected: + explicit PlayerRevive(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kPlayerFieldNumber = 3, + kRevivedFieldNumber = 4, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // .rtech.liveapi.Player revived = 4; + bool has_revived() const; + private: + bool _internal_has_revived() const; + public: + void clear_revived(); + const ::rtech::liveapi::Player& revived() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_revived(); + ::rtech::liveapi::Player* mutable_revived(); + void set_allocated_revived(::rtech::liveapi::Player* revived); + private: + const ::rtech::liveapi::Player& _internal_revived() const; + ::rtech::liveapi::Player* _internal_mutable_revived(); + public: + void unsafe_arena_set_allocated_revived( + ::rtech::liveapi::Player* revived); + ::rtech::liveapi::Player* unsafe_arena_release_revived(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerRevive) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* player_; + ::rtech::liveapi::Player* revived_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class ArenasItemSelected final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.ArenasItemSelected) */ { + public: + inline ArenasItemSelected() : ArenasItemSelected(nullptr) {} + ~ArenasItemSelected() override; + explicit PROTOBUF_CONSTEXPR ArenasItemSelected(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ArenasItemSelected(const ArenasItemSelected& from); + ArenasItemSelected(ArenasItemSelected&& from) noexcept + : ArenasItemSelected() { + *this = ::std::move(from); + } + + inline ArenasItemSelected& operator=(const ArenasItemSelected& from) { + CopyFrom(from); + return *this; + } + inline ArenasItemSelected& operator=(ArenasItemSelected&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const ArenasItemSelected& default_instance() { + return *internal_default_instance(); + } + static inline const ArenasItemSelected* internal_default_instance() { + return reinterpret_cast( + &_ArenasItemSelected_default_instance_); + } + static constexpr int kIndexInFileMessages = + 30; + + friend void swap(ArenasItemSelected& a, ArenasItemSelected& b) { + a.Swap(&b); + } + inline void Swap(ArenasItemSelected* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ArenasItemSelected* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ArenasItemSelected* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const ArenasItemSelected& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const ArenasItemSelected& from) { + ArenasItemSelected::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(ArenasItemSelected* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.ArenasItemSelected"; + } + protected: + explicit ArenasItemSelected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kItemFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + kQuantityFieldNumber = 5, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string item = 4; + void clear_item(); + const std::string& item() const; + template + void set_item(ArgT0&& arg0, ArgT... args); + std::string* mutable_item(); + PROTOBUF_NODISCARD std::string* release_item(); + void set_allocated_item(std::string* item); + private: + const std::string& _internal_item() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_item(const std::string& value); + std::string* _internal_mutable_item(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // int32 quantity = 5; + void clear_quantity(); + int32_t quantity() const; + void set_quantity(int32_t value); + private: + int32_t _internal_quantity() const; + void _internal_set_quantity(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.ArenasItemSelected) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr item_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + int32_t quantity_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class ArenasItemDeselected final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.ArenasItemDeselected) */ { + public: + inline ArenasItemDeselected() : ArenasItemDeselected(nullptr) {} + ~ArenasItemDeselected() override; + explicit PROTOBUF_CONSTEXPR ArenasItemDeselected(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ArenasItemDeselected(const ArenasItemDeselected& from); + ArenasItemDeselected(ArenasItemDeselected&& from) noexcept + : ArenasItemDeselected() { + *this = ::std::move(from); + } + + inline ArenasItemDeselected& operator=(const ArenasItemDeselected& from) { + CopyFrom(from); + return *this; + } + inline ArenasItemDeselected& operator=(ArenasItemDeselected&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const ArenasItemDeselected& default_instance() { + return *internal_default_instance(); + } + static inline const ArenasItemDeselected* internal_default_instance() { + return reinterpret_cast( + &_ArenasItemDeselected_default_instance_); + } + static constexpr int kIndexInFileMessages = + 31; + + friend void swap(ArenasItemDeselected& a, ArenasItemDeselected& b) { + a.Swap(&b); + } + inline void Swap(ArenasItemDeselected* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ArenasItemDeselected* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ArenasItemDeselected* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const ArenasItemDeselected& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const ArenasItemDeselected& from) { + ArenasItemDeselected::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(ArenasItemDeselected* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.ArenasItemDeselected"; + } + protected: + explicit ArenasItemDeselected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kItemFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + kQuantityFieldNumber = 5, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string item = 4; + void clear_item(); + const std::string& item() const; + template + void set_item(ArgT0&& arg0, ArgT... args); + std::string* mutable_item(); + PROTOBUF_NODISCARD std::string* release_item(); + void set_allocated_item(std::string* item); + private: + const std::string& _internal_item() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_item(const std::string& value); + std::string* _internal_mutable_item(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // int32 quantity = 5; + void clear_quantity(); + int32_t quantity() const; + void set_quantity(int32_t value); + private: + int32_t _internal_quantity() const; + void _internal_set_quantity(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.ArenasItemDeselected) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr item_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + int32_t quantity_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class InventoryPickUp final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.InventoryPickUp) */ { + public: + inline InventoryPickUp() : InventoryPickUp(nullptr) {} + ~InventoryPickUp() override; + explicit PROTOBUF_CONSTEXPR InventoryPickUp(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + InventoryPickUp(const InventoryPickUp& from); + InventoryPickUp(InventoryPickUp&& from) noexcept + : InventoryPickUp() { + *this = ::std::move(from); + } + + inline InventoryPickUp& operator=(const InventoryPickUp& from) { + CopyFrom(from); + return *this; + } + inline InventoryPickUp& operator=(InventoryPickUp&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const InventoryPickUp& default_instance() { + return *internal_default_instance(); + } + static inline const InventoryPickUp* internal_default_instance() { + return reinterpret_cast( + &_InventoryPickUp_default_instance_); + } + static constexpr int kIndexInFileMessages = + 32; + + friend void swap(InventoryPickUp& a, InventoryPickUp& b) { + a.Swap(&b); + } + inline void Swap(InventoryPickUp* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(InventoryPickUp* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + InventoryPickUp* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const InventoryPickUp& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const InventoryPickUp& from) { + InventoryPickUp::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(InventoryPickUp* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.InventoryPickUp"; + } + protected: + explicit InventoryPickUp(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kItemFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + kQuantityFieldNumber = 5, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string item = 4; + void clear_item(); + const std::string& item() const; + template + void set_item(ArgT0&& arg0, ArgT... args); + std::string* mutable_item(); + PROTOBUF_NODISCARD std::string* release_item(); + void set_allocated_item(std::string* item); + private: + const std::string& _internal_item() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_item(const std::string& value); + std::string* _internal_mutable_item(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // int32 quantity = 5; + void clear_quantity(); + int32_t quantity() const; + void set_quantity(int32_t value); + private: + int32_t _internal_quantity() const; + void _internal_set_quantity(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.InventoryPickUp) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr item_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + int32_t quantity_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class InventoryDrop final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.InventoryDrop) */ { + public: + inline InventoryDrop() : InventoryDrop(nullptr) {} + ~InventoryDrop() override; + explicit PROTOBUF_CONSTEXPR InventoryDrop(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + InventoryDrop(const InventoryDrop& from); + InventoryDrop(InventoryDrop&& from) noexcept + : InventoryDrop() { + *this = ::std::move(from); + } + + inline InventoryDrop& operator=(const InventoryDrop& from) { + CopyFrom(from); + return *this; + } + inline InventoryDrop& operator=(InventoryDrop&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const InventoryDrop& default_instance() { + return *internal_default_instance(); + } + static inline const InventoryDrop* internal_default_instance() { + return reinterpret_cast( + &_InventoryDrop_default_instance_); + } + static constexpr int kIndexInFileMessages = + 33; + + friend void swap(InventoryDrop& a, InventoryDrop& b) { + a.Swap(&b); + } + inline void Swap(InventoryDrop* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(InventoryDrop* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + InventoryDrop* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const InventoryDrop& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const InventoryDrop& from) { + InventoryDrop::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(InventoryDrop* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.InventoryDrop"; + } + protected: + explicit InventoryDrop(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kExtraDataFieldNumber = 6, + kCategoryFieldNumber = 2, + kItemFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + kQuantityFieldNumber = 5, + }; + // repeated string extraData = 6; + int extradata_size() const; + private: + int _internal_extradata_size() const; + public: + void clear_extradata(); + const std::string& extradata(int index) const; + std::string* mutable_extradata(int index); + void set_extradata(int index, const std::string& value); + void set_extradata(int index, std::string&& value); + void set_extradata(int index, const char* value); + void set_extradata(int index, const char* value, size_t size); + std::string* add_extradata(); + void add_extradata(const std::string& value); + void add_extradata(std::string&& value); + void add_extradata(const char* value); + void add_extradata(const char* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& extradata() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* mutable_extradata(); + private: + const std::string& _internal_extradata(int index) const; + std::string* _internal_add_extradata(); + public: + + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string item = 4; + void clear_item(); + const std::string& item() const; + template + void set_item(ArgT0&& arg0, ArgT... args); + std::string* mutable_item(); + PROTOBUF_NODISCARD std::string* release_item(); + void set_allocated_item(std::string* item); + private: + const std::string& _internal_item() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_item(const std::string& value); + std::string* _internal_mutable_item(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // int32 quantity = 5; + void clear_quantity(); + int32_t quantity() const; + void set_quantity(int32_t value); + private: + int32_t _internal_quantity() const; + void _internal_set_quantity(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.InventoryDrop) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField extradata_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr item_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + int32_t quantity_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class InventoryUse final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.InventoryUse) */ { + public: + inline InventoryUse() : InventoryUse(nullptr) {} + ~InventoryUse() override; + explicit PROTOBUF_CONSTEXPR InventoryUse(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + InventoryUse(const InventoryUse& from); + InventoryUse(InventoryUse&& from) noexcept + : InventoryUse() { + *this = ::std::move(from); + } + + inline InventoryUse& operator=(const InventoryUse& from) { + CopyFrom(from); + return *this; + } + inline InventoryUse& operator=(InventoryUse&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const InventoryUse& default_instance() { + return *internal_default_instance(); + } + static inline const InventoryUse* internal_default_instance() { + return reinterpret_cast( + &_InventoryUse_default_instance_); + } + static constexpr int kIndexInFileMessages = + 34; + + friend void swap(InventoryUse& a, InventoryUse& b) { + a.Swap(&b); + } + inline void Swap(InventoryUse* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(InventoryUse* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + InventoryUse* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const InventoryUse& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const InventoryUse& from) { + InventoryUse::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(InventoryUse* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.InventoryUse"; + } + protected: + explicit InventoryUse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kItemFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + kQuantityFieldNumber = 5, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string item = 4; + void clear_item(); + const std::string& item() const; + template + void set_item(ArgT0&& arg0, ArgT... args); + std::string* mutable_item(); + PROTOBUF_NODISCARD std::string* release_item(); + void set_allocated_item(std::string* item); + private: + const std::string& _internal_item() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_item(const std::string& value); + std::string* _internal_mutable_item(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // int32 quantity = 5; + void clear_quantity(); + int32_t quantity() const; + void set_quantity(int32_t value); + private: + int32_t _internal_quantity() const; + void _internal_set_quantity(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.InventoryUse) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr item_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + int32_t quantity_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class BannerCollected final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.BannerCollected) */ { + public: + inline BannerCollected() : BannerCollected(nullptr) {} + ~BannerCollected() override; + explicit PROTOBUF_CONSTEXPR BannerCollected(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + BannerCollected(const BannerCollected& from); + BannerCollected(BannerCollected&& from) noexcept + : BannerCollected() { + *this = ::std::move(from); + } + + inline BannerCollected& operator=(const BannerCollected& from) { + CopyFrom(from); + return *this; + } + inline BannerCollected& operator=(BannerCollected&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const BannerCollected& default_instance() { + return *internal_default_instance(); + } + static inline const BannerCollected* internal_default_instance() { + return reinterpret_cast( + &_BannerCollected_default_instance_); + } + static constexpr int kIndexInFileMessages = + 35; + + friend void swap(BannerCollected& a, BannerCollected& b) { + a.Swap(&b); + } + inline void Swap(BannerCollected* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(BannerCollected* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + BannerCollected* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const BannerCollected& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const BannerCollected& from) { + BannerCollected::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(BannerCollected* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.BannerCollected"; + } + protected: + explicit BannerCollected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kPlayerFieldNumber = 3, + kCollectedFieldNumber = 4, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // .rtech.liveapi.Player collected = 4; + bool has_collected() const; + private: + bool _internal_has_collected() const; + public: + void clear_collected(); + const ::rtech::liveapi::Player& collected() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_collected(); + ::rtech::liveapi::Player* mutable_collected(); + void set_allocated_collected(::rtech::liveapi::Player* collected); + private: + const ::rtech::liveapi::Player& _internal_collected() const; + ::rtech::liveapi::Player* _internal_mutable_collected(); + public: + void unsafe_arena_set_allocated_collected( + ::rtech::liveapi::Player* collected); + ::rtech::liveapi::Player* unsafe_arena_release_collected(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.BannerCollected) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* player_; + ::rtech::liveapi::Player* collected_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PlayerAbilityUsed final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PlayerAbilityUsed) */ { + public: + inline PlayerAbilityUsed() : PlayerAbilityUsed(nullptr) {} + ~PlayerAbilityUsed() override; + explicit PROTOBUF_CONSTEXPR PlayerAbilityUsed(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PlayerAbilityUsed(const PlayerAbilityUsed& from); + PlayerAbilityUsed(PlayerAbilityUsed&& from) noexcept + : PlayerAbilityUsed() { + *this = ::std::move(from); + } + + inline PlayerAbilityUsed& operator=(const PlayerAbilityUsed& from) { + CopyFrom(from); + return *this; + } + inline PlayerAbilityUsed& operator=(PlayerAbilityUsed&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PlayerAbilityUsed& default_instance() { + return *internal_default_instance(); + } + static inline const PlayerAbilityUsed* internal_default_instance() { + return reinterpret_cast( + &_PlayerAbilityUsed_default_instance_); + } + static constexpr int kIndexInFileMessages = + 36; + + friend void swap(PlayerAbilityUsed& a, PlayerAbilityUsed& b) { + a.Swap(&b); + } + inline void Swap(PlayerAbilityUsed* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PlayerAbilityUsed* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PlayerAbilityUsed* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PlayerAbilityUsed& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PlayerAbilityUsed& from) { + PlayerAbilityUsed::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PlayerAbilityUsed* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PlayerAbilityUsed"; + } + protected: + explicit PlayerAbilityUsed(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kLinkedEntityFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string linkedEntity = 4; + void clear_linkedentity(); + const std::string& linkedentity() const; + template + void set_linkedentity(ArgT0&& arg0, ArgT... args); + std::string* mutable_linkedentity(); + PROTOBUF_NODISCARD std::string* release_linkedentity(); + void set_allocated_linkedentity(std::string* linkedentity); + private: + const std::string& _internal_linkedentity() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_linkedentity(const std::string& value); + std::string* _internal_mutable_linkedentity(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PlayerAbilityUsed) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr linkedentity_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class LegendUpgradeSelected final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.LegendUpgradeSelected) */ { + public: + inline LegendUpgradeSelected() : LegendUpgradeSelected(nullptr) {} + ~LegendUpgradeSelected() override; + explicit PROTOBUF_CONSTEXPR LegendUpgradeSelected(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + LegendUpgradeSelected(const LegendUpgradeSelected& from); + LegendUpgradeSelected(LegendUpgradeSelected&& from) noexcept + : LegendUpgradeSelected() { + *this = ::std::move(from); + } + + inline LegendUpgradeSelected& operator=(const LegendUpgradeSelected& from) { + CopyFrom(from); + return *this; + } + inline LegendUpgradeSelected& operator=(LegendUpgradeSelected&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const LegendUpgradeSelected& default_instance() { + return *internal_default_instance(); + } + static inline const LegendUpgradeSelected* internal_default_instance() { + return reinterpret_cast( + &_LegendUpgradeSelected_default_instance_); + } + static constexpr int kIndexInFileMessages = + 37; + + friend void swap(LegendUpgradeSelected& a, LegendUpgradeSelected& b) { + a.Swap(&b); + } + inline void Swap(LegendUpgradeSelected* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(LegendUpgradeSelected* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + LegendUpgradeSelected* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const LegendUpgradeSelected& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const LegendUpgradeSelected& from) { + LegendUpgradeSelected::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(LegendUpgradeSelected* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.LegendUpgradeSelected"; + } + protected: + explicit LegendUpgradeSelected(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kUpgradeNameFieldNumber = 4, + kUpgradeDescFieldNumber = 5, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + kLevelFieldNumber = 6, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string upgradeName = 4; + void clear_upgradename(); + const std::string& upgradename() const; + template + void set_upgradename(ArgT0&& arg0, ArgT... args); + std::string* mutable_upgradename(); + PROTOBUF_NODISCARD std::string* release_upgradename(); + void set_allocated_upgradename(std::string* upgradename); + private: + const std::string& _internal_upgradename() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_upgradename(const std::string& value); + std::string* _internal_mutable_upgradename(); + public: + + // string upgradeDesc = 5; + void clear_upgradedesc(); + const std::string& upgradedesc() const; + template + void set_upgradedesc(ArgT0&& arg0, ArgT... args); + std::string* mutable_upgradedesc(); + PROTOBUF_NODISCARD std::string* release_upgradedesc(); + void set_allocated_upgradedesc(std::string* upgradedesc); + private: + const std::string& _internal_upgradedesc() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_upgradedesc(const std::string& value); + std::string* _internal_mutable_upgradedesc(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // int32 level = 6; + void clear_level(); + int32_t level() const; + void set_level(int32_t value); + private: + int32_t _internal_level() const; + void _internal_set_level(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.LegendUpgradeSelected) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr upgradename_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr upgradedesc_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + int32_t level_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class ZiplineUsed final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.ZiplineUsed) */ { + public: + inline ZiplineUsed() : ZiplineUsed(nullptr) {} + ~ZiplineUsed() override; + explicit PROTOBUF_CONSTEXPR ZiplineUsed(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ZiplineUsed(const ZiplineUsed& from); + ZiplineUsed(ZiplineUsed&& from) noexcept + : ZiplineUsed() { + *this = ::std::move(from); + } + + inline ZiplineUsed& operator=(const ZiplineUsed& from) { + CopyFrom(from); + return *this; + } + inline ZiplineUsed& operator=(ZiplineUsed&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const ZiplineUsed& default_instance() { + return *internal_default_instance(); + } + static inline const ZiplineUsed* internal_default_instance() { + return reinterpret_cast( + &_ZiplineUsed_default_instance_); + } + static constexpr int kIndexInFileMessages = + 38; + + friend void swap(ZiplineUsed& a, ZiplineUsed& b) { + a.Swap(&b); + } + inline void Swap(ZiplineUsed* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ZiplineUsed* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ZiplineUsed* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const ZiplineUsed& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const ZiplineUsed& from) { + ZiplineUsed::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(ZiplineUsed* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.ZiplineUsed"; + } + protected: + explicit ZiplineUsed(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kLinkedEntityFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string linkedEntity = 4; + void clear_linkedentity(); + const std::string& linkedentity() const; + template + void set_linkedentity(ArgT0&& arg0, ArgT... args); + std::string* mutable_linkedentity(); + PROTOBUF_NODISCARD std::string* release_linkedentity(); + void set_allocated_linkedentity(std::string* linkedentity); + private: + const std::string& _internal_linkedentity() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_linkedentity(const std::string& value); + std::string* _internal_mutable_linkedentity(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.ZiplineUsed) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr linkedentity_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class GrenadeThrown final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.GrenadeThrown) */ { + public: + inline GrenadeThrown() : GrenadeThrown(nullptr) {} + ~GrenadeThrown() override; + explicit PROTOBUF_CONSTEXPR GrenadeThrown(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + GrenadeThrown(const GrenadeThrown& from); + GrenadeThrown(GrenadeThrown&& from) noexcept + : GrenadeThrown() { + *this = ::std::move(from); + } + + inline GrenadeThrown& operator=(const GrenadeThrown& from) { + CopyFrom(from); + return *this; + } + inline GrenadeThrown& operator=(GrenadeThrown&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const GrenadeThrown& default_instance() { + return *internal_default_instance(); + } + static inline const GrenadeThrown* internal_default_instance() { + return reinterpret_cast( + &_GrenadeThrown_default_instance_); + } + static constexpr int kIndexInFileMessages = + 39; + + friend void swap(GrenadeThrown& a, GrenadeThrown& b) { + a.Swap(&b); + } + inline void Swap(GrenadeThrown* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(GrenadeThrown* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + GrenadeThrown* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const GrenadeThrown& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const GrenadeThrown& from) { + GrenadeThrown::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(GrenadeThrown* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.GrenadeThrown"; + } + protected: + explicit GrenadeThrown(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kLinkedEntityFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string linkedEntity = 4; + void clear_linkedentity(); + const std::string& linkedentity() const; + template + void set_linkedentity(ArgT0&& arg0, ArgT... args); + std::string* mutable_linkedentity(); + PROTOBUF_NODISCARD std::string* release_linkedentity(); + void set_allocated_linkedentity(std::string* linkedentity); + private: + const std::string& _internal_linkedentity() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_linkedentity(const std::string& value); + std::string* _internal_mutable_linkedentity(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.GrenadeThrown) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr linkedentity_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class BlackMarketAction final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.BlackMarketAction) */ { + public: + inline BlackMarketAction() : BlackMarketAction(nullptr) {} + ~BlackMarketAction() override; + explicit PROTOBUF_CONSTEXPR BlackMarketAction(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + BlackMarketAction(const BlackMarketAction& from); + BlackMarketAction(BlackMarketAction&& from) noexcept + : BlackMarketAction() { + *this = ::std::move(from); + } + + inline BlackMarketAction& operator=(const BlackMarketAction& from) { + CopyFrom(from); + return *this; + } + inline BlackMarketAction& operator=(BlackMarketAction&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const BlackMarketAction& default_instance() { + return *internal_default_instance(); + } + static inline const BlackMarketAction* internal_default_instance() { + return reinterpret_cast( + &_BlackMarketAction_default_instance_); + } + static constexpr int kIndexInFileMessages = + 40; + + friend void swap(BlackMarketAction& a, BlackMarketAction& b) { + a.Swap(&b); + } + inline void Swap(BlackMarketAction* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(BlackMarketAction* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + BlackMarketAction* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const BlackMarketAction& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const BlackMarketAction& from) { + BlackMarketAction::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(BlackMarketAction* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.BlackMarketAction"; + } + protected: + explicit BlackMarketAction(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kItemFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string item = 4; + void clear_item(); + const std::string& item() const; + template + void set_item(ArgT0&& arg0, ArgT... args); + std::string* mutable_item(); + PROTOBUF_NODISCARD std::string* release_item(); + void set_allocated_item(std::string* item); + private: + const std::string& _internal_item() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_item(const std::string& value); + std::string* _internal_mutable_item(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.BlackMarketAction) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr item_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class WraithPortal final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.WraithPortal) */ { + public: + inline WraithPortal() : WraithPortal(nullptr) {} + ~WraithPortal() override; + explicit PROTOBUF_CONSTEXPR WraithPortal(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + WraithPortal(const WraithPortal& from); + WraithPortal(WraithPortal&& from) noexcept + : WraithPortal() { + *this = ::std::move(from); + } + + inline WraithPortal& operator=(const WraithPortal& from) { + CopyFrom(from); + return *this; + } + inline WraithPortal& operator=(WraithPortal&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const WraithPortal& default_instance() { + return *internal_default_instance(); + } + static inline const WraithPortal* internal_default_instance() { + return reinterpret_cast( + &_WraithPortal_default_instance_); + } + static constexpr int kIndexInFileMessages = + 41; + + friend void swap(WraithPortal& a, WraithPortal& b) { + a.Swap(&b); + } + inline void Swap(WraithPortal* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(WraithPortal* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + WraithPortal* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const WraithPortal& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const WraithPortal& from) { + WraithPortal::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(WraithPortal* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.WraithPortal"; + } + protected: + explicit WraithPortal(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.WraithPortal) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class WarpGateUsed final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.WarpGateUsed) */ { + public: + inline WarpGateUsed() : WarpGateUsed(nullptr) {} + ~WarpGateUsed() override; + explicit PROTOBUF_CONSTEXPR WarpGateUsed(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + WarpGateUsed(const WarpGateUsed& from); + WarpGateUsed(WarpGateUsed&& from) noexcept + : WarpGateUsed() { + *this = ::std::move(from); + } + + inline WarpGateUsed& operator=(const WarpGateUsed& from) { + CopyFrom(from); + return *this; + } + inline WarpGateUsed& operator=(WarpGateUsed&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const WarpGateUsed& default_instance() { + return *internal_default_instance(); + } + static inline const WarpGateUsed* internal_default_instance() { + return reinterpret_cast( + &_WarpGateUsed_default_instance_); + } + static constexpr int kIndexInFileMessages = + 42; + + friend void swap(WarpGateUsed& a, WarpGateUsed& b) { + a.Swap(&b); + } + inline void Swap(WarpGateUsed* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(WarpGateUsed* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + WarpGateUsed* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const WarpGateUsed& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const WarpGateUsed& from) { + WarpGateUsed::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(WarpGateUsed* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.WarpGateUsed"; + } + protected: + explicit WarpGateUsed(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.WarpGateUsed) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class AmmoUsed final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.AmmoUsed) */ { + public: + inline AmmoUsed() : AmmoUsed(nullptr) {} + ~AmmoUsed() override; + explicit PROTOBUF_CONSTEXPR AmmoUsed(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + AmmoUsed(const AmmoUsed& from); + AmmoUsed(AmmoUsed&& from) noexcept + : AmmoUsed() { + *this = ::std::move(from); + } + + inline AmmoUsed& operator=(const AmmoUsed& from) { + CopyFrom(from); + return *this; + } + inline AmmoUsed& operator=(AmmoUsed&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const AmmoUsed& default_instance() { + return *internal_default_instance(); + } + static inline const AmmoUsed* internal_default_instance() { + return reinterpret_cast( + &_AmmoUsed_default_instance_); + } + static constexpr int kIndexInFileMessages = + 43; + + friend void swap(AmmoUsed& a, AmmoUsed& b) { + a.Swap(&b); + } + inline void Swap(AmmoUsed* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(AmmoUsed* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + AmmoUsed* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const AmmoUsed& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const AmmoUsed& from) { + AmmoUsed::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(AmmoUsed* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.AmmoUsed"; + } + protected: + explicit AmmoUsed(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kAmmoTypeFieldNumber = 4, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + kAmountUsedFieldNumber = 5, + kOldAmmoCountFieldNumber = 6, + kNewAmmoCountFieldNumber = 7, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string ammoType = 4; + void clear_ammotype(); + const std::string& ammotype() const; + template + void set_ammotype(ArgT0&& arg0, ArgT... args); + std::string* mutable_ammotype(); + PROTOBUF_NODISCARD std::string* release_ammotype(); + void set_allocated_ammotype(std::string* ammotype); + private: + const std::string& _internal_ammotype() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_ammotype(const std::string& value); + std::string* _internal_mutable_ammotype(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // uint32 amountUsed = 5; + void clear_amountused(); + uint32_t amountused() const; + void set_amountused(uint32_t value); + private: + uint32_t _internal_amountused() const; + void _internal_set_amountused(uint32_t value); + public: + + // uint32 oldAmmoCount = 6; + void clear_oldammocount(); + uint32_t oldammocount() const; + void set_oldammocount(uint32_t value); + private: + uint32_t _internal_oldammocount() const; + void _internal_set_oldammocount(uint32_t value); + public: + + // uint32 newAmmoCount = 7; + void clear_newammocount(); + uint32_t newammocount() const; + void set_newammocount(uint32_t value); + private: + uint32_t _internal_newammocount() const; + void _internal_set_newammocount(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.AmmoUsed) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr ammotype_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + uint32_t amountused_; + uint32_t oldammocount_; + uint32_t newammocount_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class WeaponSwitched final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.WeaponSwitched) */ { + public: + inline WeaponSwitched() : WeaponSwitched(nullptr) {} + ~WeaponSwitched() override; + explicit PROTOBUF_CONSTEXPR WeaponSwitched(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + WeaponSwitched(const WeaponSwitched& from); + WeaponSwitched(WeaponSwitched&& from) noexcept + : WeaponSwitched() { + *this = ::std::move(from); + } + + inline WeaponSwitched& operator=(const WeaponSwitched& from) { + CopyFrom(from); + return *this; + } + inline WeaponSwitched& operator=(WeaponSwitched&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const WeaponSwitched& default_instance() { + return *internal_default_instance(); + } + static inline const WeaponSwitched* internal_default_instance() { + return reinterpret_cast( + &_WeaponSwitched_default_instance_); + } + static constexpr int kIndexInFileMessages = + 44; + + friend void swap(WeaponSwitched& a, WeaponSwitched& b) { + a.Swap(&b); + } + inline void Swap(WeaponSwitched* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(WeaponSwitched* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + WeaponSwitched* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const WeaponSwitched& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const WeaponSwitched& from) { + WeaponSwitched::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(WeaponSwitched* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.WeaponSwitched"; + } + protected: + explicit WeaponSwitched(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kOldWeaponFieldNumber = 4, + kNewWeaponFieldNumber = 5, + kPlayerFieldNumber = 3, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string oldWeapon = 4; + void clear_oldweapon(); + const std::string& oldweapon() const; + template + void set_oldweapon(ArgT0&& arg0, ArgT... args); + std::string* mutable_oldweapon(); + PROTOBUF_NODISCARD std::string* release_oldweapon(); + void set_allocated_oldweapon(std::string* oldweapon); + private: + const std::string& _internal_oldweapon() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_oldweapon(const std::string& value); + std::string* _internal_mutable_oldweapon(); + public: + + // string newWeapon = 5; + void clear_newweapon(); + const std::string& newweapon() const; + template + void set_newweapon(ArgT0&& arg0, ArgT... args); + std::string* mutable_newweapon(); + PROTOBUF_NODISCARD std::string* release_newweapon(); + void set_allocated_newweapon(std::string* newweapon); + private: + const std::string& _internal_newweapon() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_newweapon(const std::string& value); + std::string* _internal_mutable_newweapon(); + public: + + // .rtech.liveapi.Player player = 3; + bool has_player() const; + private: + bool _internal_has_player() const; + public: + void clear_player(); + const ::rtech::liveapi::Player& player() const; + PROTOBUF_NODISCARD ::rtech::liveapi::Player* release_player(); + ::rtech::liveapi::Player* mutable_player(); + void set_allocated_player(::rtech::liveapi::Player* player); + private: + const ::rtech::liveapi::Player& _internal_player() const; + ::rtech::liveapi::Player* _internal_mutable_player(); + public: + void unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player); + ::rtech::liveapi::Player* unsafe_arena_release_player(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.WeaponSwitched) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr oldweapon_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr newweapon_; + ::rtech::liveapi::Player* player_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomEvent final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomEvent) */ { + public: + inline CustomEvent() : CustomEvent(nullptr) {} + ~CustomEvent() override; + explicit PROTOBUF_CONSTEXPR CustomEvent(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomEvent(const CustomEvent& from); + CustomEvent(CustomEvent&& from) noexcept + : CustomEvent() { + *this = ::std::move(from); + } + + inline CustomEvent& operator=(const CustomEvent& from) { + CopyFrom(from); + return *this; + } + inline CustomEvent& operator=(CustomEvent&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomEvent& default_instance() { + return *internal_default_instance(); + } + static inline const CustomEvent* internal_default_instance() { + return reinterpret_cast( + &_CustomEvent_default_instance_); + } + static constexpr int kIndexInFileMessages = + 45; + + friend void swap(CustomEvent& a, CustomEvent& b) { + a.Swap(&b); + } + inline void Swap(CustomEvent* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomEvent* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomEvent* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomEvent& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomEvent& from) { + CustomEvent::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomEvent* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomEvent"; + } + protected: + explicit CustomEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kCategoryFieldNumber = 2, + kNameFieldNumber = 3, + kDataFieldNumber = 4, + kTimestampFieldNumber = 1, + }; + // string category = 2; + void clear_category(); + const std::string& category() const; + template + void set_category(ArgT0&& arg0, ArgT... args); + std::string* mutable_category(); + PROTOBUF_NODISCARD std::string* release_category(); + void set_allocated_category(std::string* category); + private: + const std::string& _internal_category() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_category(const std::string& value); + std::string* _internal_mutable_category(); + public: + + // string name = 3; + void clear_name(); + const std::string& name() const; + template + void set_name(ArgT0&& arg0, ArgT... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* name); + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); + std::string* _internal_mutable_name(); + public: + + // .google.protobuf.Struct data = 4; + bool has_data() const; + private: + bool _internal_has_data() const; + public: + void clear_data(); + const ::PROTOBUF_NAMESPACE_ID::Struct& data() const; + PROTOBUF_NODISCARD ::PROTOBUF_NAMESPACE_ID::Struct* release_data(); + ::PROTOBUF_NAMESPACE_ID::Struct* mutable_data(); + void set_allocated_data(::PROTOBUF_NAMESPACE_ID::Struct* data); + private: + const ::PROTOBUF_NAMESPACE_ID::Struct& _internal_data() const; + ::PROTOBUF_NAMESPACE_ID::Struct* _internal_mutable_data(); + public: + void unsafe_arena_set_allocated_data( + ::PROTOBUF_NAMESPACE_ID::Struct* data); + ::PROTOBUF_NAMESPACE_ID::Struct* unsafe_arena_release_data(); + + // uint64 timestamp = 1; + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomEvent) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr category_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; + ::PROTOBUF_NAMESPACE_ID::Struct* data_; + uint64_t timestamp_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class ChangeCamera final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.ChangeCamera) */ { + public: + inline ChangeCamera() : ChangeCamera(nullptr) {} + ~ChangeCamera() override; + explicit PROTOBUF_CONSTEXPR ChangeCamera(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ChangeCamera(const ChangeCamera& from); + ChangeCamera(ChangeCamera&& from) noexcept + : ChangeCamera() { + *this = ::std::move(from); + } + + inline ChangeCamera& operator=(const ChangeCamera& from) { + CopyFrom(from); + return *this; + } + inline ChangeCamera& operator=(ChangeCamera&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const ChangeCamera& default_instance() { + return *internal_default_instance(); + } + enum TargetCase { + kPoi = 1, + kName = 2, + TARGET_NOT_SET = 0, + }; + + static inline const ChangeCamera* internal_default_instance() { + return reinterpret_cast( + &_ChangeCamera_default_instance_); + } + static constexpr int kIndexInFileMessages = + 46; + + friend void swap(ChangeCamera& a, ChangeCamera& b) { + a.Swap(&b); + } + inline void Swap(ChangeCamera* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ChangeCamera* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ChangeCamera* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const ChangeCamera& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const ChangeCamera& from) { + ChangeCamera::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(ChangeCamera* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.ChangeCamera"; + } + protected: + explicit ChangeCamera(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kPoiFieldNumber = 1, + kNameFieldNumber = 2, + }; + // .rtech.liveapi.PlayerOfInterest poi = 1; + bool has_poi() const; + private: + bool _internal_has_poi() const; + public: + void clear_poi(); + ::rtech::liveapi::PlayerOfInterest poi() const; + void set_poi(::rtech::liveapi::PlayerOfInterest value); + private: + ::rtech::liveapi::PlayerOfInterest _internal_poi() const; + void _internal_set_poi(::rtech::liveapi::PlayerOfInterest value); + public: + + // string name = 2; + bool has_name() const; + private: + bool _internal_has_name() const; + public: + void clear_name(); + const std::string& name() const; + template + void set_name(ArgT0&& arg0, ArgT... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* name); + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); + std::string* _internal_mutable_name(); + public: + + void clear_target(); + TargetCase target_case() const; + // @@protoc_insertion_point(class_scope:rtech.liveapi.ChangeCamera) + private: + class _Internal; + void set_has_poi(); + void set_has_name(); + + inline bool has_target() const; + inline void clear_has_target(); + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + union TargetUnion { + constexpr TargetUnion() : _constinit_{} {} + ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_; + int poi_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; + } target_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + uint32_t _oneof_case_[1]; + + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class PauseToggle final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.PauseToggle) */ { + public: + inline PauseToggle() : PauseToggle(nullptr) {} + ~PauseToggle() override; + explicit PROTOBUF_CONSTEXPR PauseToggle(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + PauseToggle(const PauseToggle& from); + PauseToggle(PauseToggle&& from) noexcept + : PauseToggle() { + *this = ::std::move(from); + } + + inline PauseToggle& operator=(const PauseToggle& from) { + CopyFrom(from); + return *this; + } + inline PauseToggle& operator=(PauseToggle&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const PauseToggle& default_instance() { + return *internal_default_instance(); + } + static inline const PauseToggle* internal_default_instance() { + return reinterpret_cast( + &_PauseToggle_default_instance_); + } + static constexpr int kIndexInFileMessages = + 47; + + friend void swap(PauseToggle& a, PauseToggle& b) { + a.Swap(&b); + } + inline void Swap(PauseToggle* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(PauseToggle* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + PauseToggle* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const PauseToggle& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const PauseToggle& from) { + PauseToggle::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(PauseToggle* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.PauseToggle"; + } + protected: + explicit PauseToggle(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kPreTimerFieldNumber = 1, + }; + // float preTimer = 1; + void clear_pretimer(); + float pretimer() const; + void set_pretimer(float value); + private: + float _internal_pretimer() const; + void _internal_set_pretimer(float value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.PauseToggle) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + float pretimer_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_CreateLobby final : + public ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_CreateLobby) */ { + public: + inline CustomMatch_CreateLobby() : CustomMatch_CreateLobby(nullptr) {} + explicit PROTOBUF_CONSTEXPR CustomMatch_CreateLobby(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_CreateLobby(const CustomMatch_CreateLobby& from); + CustomMatch_CreateLobby(CustomMatch_CreateLobby&& from) noexcept + : CustomMatch_CreateLobby() { + *this = ::std::move(from); + } + + inline CustomMatch_CreateLobby& operator=(const CustomMatch_CreateLobby& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_CreateLobby& operator=(CustomMatch_CreateLobby&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_CreateLobby& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_CreateLobby* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_CreateLobby_default_instance_); + } + static constexpr int kIndexInFileMessages = + 48; + + friend void swap(CustomMatch_CreateLobby& a, CustomMatch_CreateLobby& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_CreateLobby* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_CreateLobby* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_CreateLobby* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyFrom; + inline void CopyFrom(const CustomMatch_CreateLobby& from) { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyImpl(*this, from); + } + using ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeFrom; + void MergeFrom(const CustomMatch_CreateLobby& from) { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeImpl(*this, from); + } + public: + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_CreateLobby"; + } + protected: + explicit CustomMatch_CreateLobby(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_CreateLobby) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_JoinLobby final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_JoinLobby) */ { + public: + inline CustomMatch_JoinLobby() : CustomMatch_JoinLobby(nullptr) {} + ~CustomMatch_JoinLobby() override; + explicit PROTOBUF_CONSTEXPR CustomMatch_JoinLobby(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_JoinLobby(const CustomMatch_JoinLobby& from); + CustomMatch_JoinLobby(CustomMatch_JoinLobby&& from) noexcept + : CustomMatch_JoinLobby() { + *this = ::std::move(from); + } + + inline CustomMatch_JoinLobby& operator=(const CustomMatch_JoinLobby& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_JoinLobby& operator=(CustomMatch_JoinLobby&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_JoinLobby& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_JoinLobby* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_JoinLobby_default_instance_); + } + static constexpr int kIndexInFileMessages = + 49; + + friend void swap(CustomMatch_JoinLobby& a, CustomMatch_JoinLobby& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_JoinLobby* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_JoinLobby* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_JoinLobby* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomMatch_JoinLobby& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomMatch_JoinLobby& from) { + CustomMatch_JoinLobby::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomMatch_JoinLobby* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_JoinLobby"; + } + protected: + explicit CustomMatch_JoinLobby(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kRoleTokenFieldNumber = 1, + }; + // string roleToken = 1; + void clear_roletoken(); + const std::string& roletoken() const; + template + void set_roletoken(ArgT0&& arg0, ArgT... args); + std::string* mutable_roletoken(); + PROTOBUF_NODISCARD std::string* release_roletoken(); + void set_allocated_roletoken(std::string* roletoken); + private: + const std::string& _internal_roletoken() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_roletoken(const std::string& value); + std::string* _internal_mutable_roletoken(); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_JoinLobby) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr roletoken_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_LeaveLobby final : + public ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_LeaveLobby) */ { + public: + inline CustomMatch_LeaveLobby() : CustomMatch_LeaveLobby(nullptr) {} + explicit PROTOBUF_CONSTEXPR CustomMatch_LeaveLobby(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_LeaveLobby(const CustomMatch_LeaveLobby& from); + CustomMatch_LeaveLobby(CustomMatch_LeaveLobby&& from) noexcept + : CustomMatch_LeaveLobby() { + *this = ::std::move(from); + } + + inline CustomMatch_LeaveLobby& operator=(const CustomMatch_LeaveLobby& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_LeaveLobby& operator=(CustomMatch_LeaveLobby&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_LeaveLobby& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_LeaveLobby* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_LeaveLobby_default_instance_); + } + static constexpr int kIndexInFileMessages = + 50; + + friend void swap(CustomMatch_LeaveLobby& a, CustomMatch_LeaveLobby& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_LeaveLobby* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_LeaveLobby* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_LeaveLobby* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyFrom; + inline void CopyFrom(const CustomMatch_LeaveLobby& from) { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyImpl(*this, from); + } + using ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeFrom; + void MergeFrom(const CustomMatch_LeaveLobby& from) { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeImpl(*this, from); + } + public: + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_LeaveLobby"; + } + protected: + explicit CustomMatch_LeaveLobby(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_LeaveLobby) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_SetReady final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_SetReady) */ { + public: + inline CustomMatch_SetReady() : CustomMatch_SetReady(nullptr) {} + ~CustomMatch_SetReady() override; + explicit PROTOBUF_CONSTEXPR CustomMatch_SetReady(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_SetReady(const CustomMatch_SetReady& from); + CustomMatch_SetReady(CustomMatch_SetReady&& from) noexcept + : CustomMatch_SetReady() { + *this = ::std::move(from); + } + + inline CustomMatch_SetReady& operator=(const CustomMatch_SetReady& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_SetReady& operator=(CustomMatch_SetReady&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_SetReady& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_SetReady* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_SetReady_default_instance_); + } + static constexpr int kIndexInFileMessages = + 51; + + friend void swap(CustomMatch_SetReady& a, CustomMatch_SetReady& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_SetReady* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_SetReady* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_SetReady* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomMatch_SetReady& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomMatch_SetReady& from) { + CustomMatch_SetReady::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomMatch_SetReady* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_SetReady"; + } + protected: + explicit CustomMatch_SetReady(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kIsReadyFieldNumber = 1, + }; + // bool isReady = 1; + void clear_isready(); + bool isready() const; + void set_isready(bool value); + private: + bool _internal_isready() const; + void _internal_set_isready(bool value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_SetReady) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + bool isready_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_GetLobbyPlayers final : + public ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_GetLobbyPlayers) */ { + public: + inline CustomMatch_GetLobbyPlayers() : CustomMatch_GetLobbyPlayers(nullptr) {} + explicit PROTOBUF_CONSTEXPR CustomMatch_GetLobbyPlayers(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_GetLobbyPlayers(const CustomMatch_GetLobbyPlayers& from); + CustomMatch_GetLobbyPlayers(CustomMatch_GetLobbyPlayers&& from) noexcept + : CustomMatch_GetLobbyPlayers() { + *this = ::std::move(from); + } + + inline CustomMatch_GetLobbyPlayers& operator=(const CustomMatch_GetLobbyPlayers& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_GetLobbyPlayers& operator=(CustomMatch_GetLobbyPlayers&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_GetLobbyPlayers& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_GetLobbyPlayers* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_GetLobbyPlayers_default_instance_); + } + static constexpr int kIndexInFileMessages = + 52; + + friend void swap(CustomMatch_GetLobbyPlayers& a, CustomMatch_GetLobbyPlayers& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_GetLobbyPlayers* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_GetLobbyPlayers* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_GetLobbyPlayers* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyFrom; + inline void CopyFrom(const CustomMatch_GetLobbyPlayers& from) { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyImpl(*this, from); + } + using ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeFrom; + void MergeFrom(const CustomMatch_GetLobbyPlayers& from) { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeImpl(*this, from); + } + public: + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_GetLobbyPlayers"; + } + protected: + explicit CustomMatch_GetLobbyPlayers(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_GetLobbyPlayers) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_SetMatchmaking final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_SetMatchmaking) */ { + public: + inline CustomMatch_SetMatchmaking() : CustomMatch_SetMatchmaking(nullptr) {} + ~CustomMatch_SetMatchmaking() override; + explicit PROTOBUF_CONSTEXPR CustomMatch_SetMatchmaking(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_SetMatchmaking(const CustomMatch_SetMatchmaking& from); + CustomMatch_SetMatchmaking(CustomMatch_SetMatchmaking&& from) noexcept + : CustomMatch_SetMatchmaking() { + *this = ::std::move(from); + } + + inline CustomMatch_SetMatchmaking& operator=(const CustomMatch_SetMatchmaking& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_SetMatchmaking& operator=(CustomMatch_SetMatchmaking&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_SetMatchmaking& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_SetMatchmaking* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_SetMatchmaking_default_instance_); + } + static constexpr int kIndexInFileMessages = + 53; + + friend void swap(CustomMatch_SetMatchmaking& a, CustomMatch_SetMatchmaking& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_SetMatchmaking* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_SetMatchmaking* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_SetMatchmaking* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomMatch_SetMatchmaking& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomMatch_SetMatchmaking& from) { + CustomMatch_SetMatchmaking::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomMatch_SetMatchmaking* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_SetMatchmaking"; + } + protected: + explicit CustomMatch_SetMatchmaking(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kEnabledFieldNumber = 1, + }; + // bool enabled = 1; + void clear_enabled(); + bool enabled() const; + void set_enabled(bool value); + private: + bool _internal_enabled() const; + void _internal_set_enabled(bool value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_SetMatchmaking) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + bool enabled_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_SetTeam final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_SetTeam) */ { + public: + inline CustomMatch_SetTeam() : CustomMatch_SetTeam(nullptr) {} + ~CustomMatch_SetTeam() override; + explicit PROTOBUF_CONSTEXPR CustomMatch_SetTeam(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_SetTeam(const CustomMatch_SetTeam& from); + CustomMatch_SetTeam(CustomMatch_SetTeam&& from) noexcept + : CustomMatch_SetTeam() { + *this = ::std::move(from); + } + + inline CustomMatch_SetTeam& operator=(const CustomMatch_SetTeam& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_SetTeam& operator=(CustomMatch_SetTeam&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_SetTeam& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_SetTeam* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_SetTeam_default_instance_); + } + static constexpr int kIndexInFileMessages = + 54; + + friend void swap(CustomMatch_SetTeam& a, CustomMatch_SetTeam& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_SetTeam* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_SetTeam* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_SetTeam* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomMatch_SetTeam& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomMatch_SetTeam& from) { + CustomMatch_SetTeam::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomMatch_SetTeam* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_SetTeam"; + } + protected: + explicit CustomMatch_SetTeam(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kTargetHardwareNameFieldNumber = 2, + kTargetNucleusHashFieldNumber = 3, + kTeamIdFieldNumber = 1, + }; + // string targetHardwareName = 2; + void clear_targethardwarename(); + const std::string& targethardwarename() const; + template + void set_targethardwarename(ArgT0&& arg0, ArgT... args); + std::string* mutable_targethardwarename(); + PROTOBUF_NODISCARD std::string* release_targethardwarename(); + void set_allocated_targethardwarename(std::string* targethardwarename); + private: + const std::string& _internal_targethardwarename() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_targethardwarename(const std::string& value); + std::string* _internal_mutable_targethardwarename(); + public: + + // string targetNucleusHash = 3; + void clear_targetnucleushash(); + const std::string& targetnucleushash() const; + template + void set_targetnucleushash(ArgT0&& arg0, ArgT... args); + std::string* mutable_targetnucleushash(); + PROTOBUF_NODISCARD std::string* release_targetnucleushash(); + void set_allocated_targetnucleushash(std::string* targetnucleushash); + private: + const std::string& _internal_targetnucleushash() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_targetnucleushash(const std::string& value); + std::string* _internal_mutable_targetnucleushash(); + public: + + // int32 teamId = 1; + void clear_teamid(); + int32_t teamid() const; + void set_teamid(int32_t value); + private: + int32_t _internal_teamid() const; + void _internal_set_teamid(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_SetTeam) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr targethardwarename_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr targetnucleushash_; + int32_t teamid_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_KickPlayer final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_KickPlayer) */ { + public: + inline CustomMatch_KickPlayer() : CustomMatch_KickPlayer(nullptr) {} + ~CustomMatch_KickPlayer() override; + explicit PROTOBUF_CONSTEXPR CustomMatch_KickPlayer(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_KickPlayer(const CustomMatch_KickPlayer& from); + CustomMatch_KickPlayer(CustomMatch_KickPlayer&& from) noexcept + : CustomMatch_KickPlayer() { + *this = ::std::move(from); + } + + inline CustomMatch_KickPlayer& operator=(const CustomMatch_KickPlayer& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_KickPlayer& operator=(CustomMatch_KickPlayer&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_KickPlayer& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_KickPlayer* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_KickPlayer_default_instance_); + } + static constexpr int kIndexInFileMessages = + 55; + + friend void swap(CustomMatch_KickPlayer& a, CustomMatch_KickPlayer& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_KickPlayer* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_KickPlayer* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_KickPlayer* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomMatch_KickPlayer& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomMatch_KickPlayer& from) { + CustomMatch_KickPlayer::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomMatch_KickPlayer* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_KickPlayer"; + } + protected: + explicit CustomMatch_KickPlayer(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kTargetHardwareNameFieldNumber = 1, + kTargetNucleusHashFieldNumber = 2, + }; + // string targetHardwareName = 1; + void clear_targethardwarename(); + const std::string& targethardwarename() const; + template + void set_targethardwarename(ArgT0&& arg0, ArgT... args); + std::string* mutable_targethardwarename(); + PROTOBUF_NODISCARD std::string* release_targethardwarename(); + void set_allocated_targethardwarename(std::string* targethardwarename); + private: + const std::string& _internal_targethardwarename() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_targethardwarename(const std::string& value); + std::string* _internal_mutable_targethardwarename(); + public: + + // string targetNucleusHash = 2; + void clear_targetnucleushash(); + const std::string& targetnucleushash() const; + template + void set_targetnucleushash(ArgT0&& arg0, ArgT... args); + std::string* mutable_targetnucleushash(); + PROTOBUF_NODISCARD std::string* release_targetnucleushash(); + void set_allocated_targetnucleushash(std::string* targetnucleushash); + private: + const std::string& _internal_targetnucleushash() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_targetnucleushash(const std::string& value); + std::string* _internal_mutable_targetnucleushash(); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_KickPlayer) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr targethardwarename_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr targetnucleushash_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_SetSettings final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_SetSettings) */ { + public: + inline CustomMatch_SetSettings() : CustomMatch_SetSettings(nullptr) {} + ~CustomMatch_SetSettings() override; + explicit PROTOBUF_CONSTEXPR CustomMatch_SetSettings(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_SetSettings(const CustomMatch_SetSettings& from); + CustomMatch_SetSettings(CustomMatch_SetSettings&& from) noexcept + : CustomMatch_SetSettings() { + *this = ::std::move(from); + } + + inline CustomMatch_SetSettings& operator=(const CustomMatch_SetSettings& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_SetSettings& operator=(CustomMatch_SetSettings&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_SetSettings& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_SetSettings* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_SetSettings_default_instance_); + } + static constexpr int kIndexInFileMessages = + 56; + + friend void swap(CustomMatch_SetSettings& a, CustomMatch_SetSettings& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_SetSettings* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_SetSettings* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_SetSettings* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomMatch_SetSettings& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomMatch_SetSettings& from) { + CustomMatch_SetSettings::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomMatch_SetSettings* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_SetSettings"; + } + protected: + explicit CustomMatch_SetSettings(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kPlaylistNameFieldNumber = 1, + kAdminChatFieldNumber = 2, + kTeamRenameFieldNumber = 3, + kSelfAssignFieldNumber = 4, + kAimAssistFieldNumber = 5, + kAnonModeFieldNumber = 6, + }; + // string playlistName = 1; + void clear_playlistname(); + const std::string& playlistname() const; + template + void set_playlistname(ArgT0&& arg0, ArgT... args); + std::string* mutable_playlistname(); + PROTOBUF_NODISCARD std::string* release_playlistname(); + void set_allocated_playlistname(std::string* playlistname); + private: + const std::string& _internal_playlistname() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_playlistname(const std::string& value); + std::string* _internal_mutable_playlistname(); + public: + + // bool adminChat = 2; + void clear_adminchat(); + bool adminchat() const; + void set_adminchat(bool value); + private: + bool _internal_adminchat() const; + void _internal_set_adminchat(bool value); + public: + + // bool teamRename = 3; + void clear_teamrename(); + bool teamrename() const; + void set_teamrename(bool value); + private: + bool _internal_teamrename() const; + void _internal_set_teamrename(bool value); + public: + + // bool selfAssign = 4; + void clear_selfassign(); + bool selfassign() const; + void set_selfassign(bool value); + private: + bool _internal_selfassign() const; + void _internal_set_selfassign(bool value); + public: + + // bool aimAssist = 5; + void clear_aimassist(); + bool aimassist() const; + void set_aimassist(bool value); + private: + bool _internal_aimassist() const; + void _internal_set_aimassist(bool value); + public: + + // bool anonMode = 6; + void clear_anonmode(); + bool anonmode() const; + void set_anonmode(bool value); + private: + bool _internal_anonmode() const; + void _internal_set_anonmode(bool value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_SetSettings) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr playlistname_; + bool adminchat_; + bool teamrename_; + bool selfassign_; + bool aimassist_; + bool anonmode_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_GetSettings final : + public ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_GetSettings) */ { + public: + inline CustomMatch_GetSettings() : CustomMatch_GetSettings(nullptr) {} + explicit PROTOBUF_CONSTEXPR CustomMatch_GetSettings(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_GetSettings(const CustomMatch_GetSettings& from); + CustomMatch_GetSettings(CustomMatch_GetSettings&& from) noexcept + : CustomMatch_GetSettings() { + *this = ::std::move(from); + } + + inline CustomMatch_GetSettings& operator=(const CustomMatch_GetSettings& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_GetSettings& operator=(CustomMatch_GetSettings&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_GetSettings& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_GetSettings* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_GetSettings_default_instance_); + } + static constexpr int kIndexInFileMessages = + 57; + + friend void swap(CustomMatch_GetSettings& a, CustomMatch_GetSettings& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_GetSettings* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_GetSettings* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_GetSettings* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyFrom; + inline void CopyFrom(const CustomMatch_GetSettings& from) { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::CopyImpl(*this, from); + } + using ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeFrom; + void MergeFrom(const CustomMatch_GetSettings& from) { + ::PROTOBUF_NAMESPACE_ID::internal::ZeroFieldsBase::MergeImpl(*this, from); + } + public: + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_GetSettings"; + } + protected: + explicit CustomMatch_GetSettings(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_GetSettings) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_SetTeamName final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_SetTeamName) */ { + public: + inline CustomMatch_SetTeamName() : CustomMatch_SetTeamName(nullptr) {} + ~CustomMatch_SetTeamName() override; + explicit PROTOBUF_CONSTEXPR CustomMatch_SetTeamName(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_SetTeamName(const CustomMatch_SetTeamName& from); + CustomMatch_SetTeamName(CustomMatch_SetTeamName&& from) noexcept + : CustomMatch_SetTeamName() { + *this = ::std::move(from); + } + + inline CustomMatch_SetTeamName& operator=(const CustomMatch_SetTeamName& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_SetTeamName& operator=(CustomMatch_SetTeamName&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_SetTeamName& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_SetTeamName* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_SetTeamName_default_instance_); + } + static constexpr int kIndexInFileMessages = + 58; + + friend void swap(CustomMatch_SetTeamName& a, CustomMatch_SetTeamName& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_SetTeamName* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_SetTeamName* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_SetTeamName* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomMatch_SetTeamName& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomMatch_SetTeamName& from) { + CustomMatch_SetTeamName::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomMatch_SetTeamName* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_SetTeamName"; + } + protected: + explicit CustomMatch_SetTeamName(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kTeamNameFieldNumber = 2, + kTeamIdFieldNumber = 1, + }; + // string teamName = 2; + void clear_teamname(); + const std::string& teamname() const; + template + void set_teamname(ArgT0&& arg0, ArgT... args); + std::string* mutable_teamname(); + PROTOBUF_NODISCARD std::string* release_teamname(); + void set_allocated_teamname(std::string* teamname); + private: + const std::string& _internal_teamname() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_teamname(const std::string& value); + std::string* _internal_mutable_teamname(); + public: + + // int32 teamId = 1; + void clear_teamid(); + int32_t teamid() const; + void set_teamid(int32_t value); + private: + int32_t _internal_teamid() const; + void _internal_set_teamid(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_SetTeamName) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr teamname_; + int32_t teamid_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class CustomMatch_SendChat final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.CustomMatch_SendChat) */ { + public: + inline CustomMatch_SendChat() : CustomMatch_SendChat(nullptr) {} + ~CustomMatch_SendChat() override; + explicit PROTOBUF_CONSTEXPR CustomMatch_SendChat(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CustomMatch_SendChat(const CustomMatch_SendChat& from); + CustomMatch_SendChat(CustomMatch_SendChat&& from) noexcept + : CustomMatch_SendChat() { + *this = ::std::move(from); + } + + inline CustomMatch_SendChat& operator=(const CustomMatch_SendChat& from) { + CopyFrom(from); + return *this; + } + inline CustomMatch_SendChat& operator=(CustomMatch_SendChat&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const CustomMatch_SendChat& default_instance() { + return *internal_default_instance(); + } + static inline const CustomMatch_SendChat* internal_default_instance() { + return reinterpret_cast( + &_CustomMatch_SendChat_default_instance_); + } + static constexpr int kIndexInFileMessages = + 59; + + friend void swap(CustomMatch_SendChat& a, CustomMatch_SendChat& b) { + a.Swap(&b); + } + inline void Swap(CustomMatch_SendChat* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CustomMatch_SendChat* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CustomMatch_SendChat* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const CustomMatch_SendChat& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const CustomMatch_SendChat& from) { + CustomMatch_SendChat::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(CustomMatch_SendChat* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.CustomMatch_SendChat"; + } + protected: + explicit CustomMatch_SendChat(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kTextFieldNumber = 1, + }; + // string text = 1; + void clear_text(); + const std::string& text() const; + template + void set_text(ArgT0&& arg0, ArgT... args); + std::string* mutable_text(); + PROTOBUF_NODISCARD std::string* release_text(); + void set_allocated_text(std::string* text); + private: + const std::string& _internal_text() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_text(const std::string& value); + std::string* _internal_mutable_text(); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.CustomMatch_SendChat) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr text_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class Request final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.Request) */ { + public: + inline Request() : Request(nullptr) {} + ~Request() override; + explicit PROTOBUF_CONSTEXPR Request(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Request(const Request& from); + Request(Request&& from) noexcept + : Request() { + *this = ::std::move(from); + } + + inline Request& operator=(const Request& from) { + CopyFrom(from); + return *this; + } + inline Request& operator=(Request&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const Request& default_instance() { + return *internal_default_instance(); + } + enum ActionsCase { + kChangeCam = 4, + kPauseToggle = 5, + kCustomMatchCreateLobby = 10, + kCustomMatchJoinLobby = 11, + kCustomMatchLeaveLobby = 12, + kCustomMatchSetReady = 13, + kCustomMatchSetMatchmaking = 14, + kCustomMatchSetTeam = 15, + kCustomMatchKickPlayer = 16, + kCustomMatchSetSettings = 17, + kCustomMatchSendChat = 18, + kCustomMatchGetLobbyPlayers = 19, + kCustomMatchSetTeamName = 20, + kCustomMatchGetSettings = 21, + ACTIONS_NOT_SET = 0, + }; + + static inline const Request* internal_default_instance() { + return reinterpret_cast( + &_Request_default_instance_); + } + static constexpr int kIndexInFileMessages = + 60; + + friend void swap(Request& a, Request& b) { + a.Swap(&b); + } + inline void Swap(Request* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Request* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Request* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const Request& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const Request& from) { + Request::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(Request* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.Request"; + } + protected: + explicit Request(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kPreSharedKeyFieldNumber = 2, + kWithAckFieldNumber = 1, + kChangeCamFieldNumber = 4, + kPauseToggleFieldNumber = 5, + kCustomMatchCreateLobbyFieldNumber = 10, + kCustomMatchJoinLobbyFieldNumber = 11, + kCustomMatchLeaveLobbyFieldNumber = 12, + kCustomMatchSetReadyFieldNumber = 13, + kCustomMatchSetMatchmakingFieldNumber = 14, + kCustomMatchSetTeamFieldNumber = 15, + kCustomMatchKickPlayerFieldNumber = 16, + kCustomMatchSetSettingsFieldNumber = 17, + kCustomMatchSendChatFieldNumber = 18, + kCustomMatchGetLobbyPlayersFieldNumber = 19, + kCustomMatchSetTeamNameFieldNumber = 20, + kCustomMatchGetSettingsFieldNumber = 21, + }; + // string preSharedKey = 2; + void clear_presharedkey(); + const std::string& presharedkey() const; + template + void set_presharedkey(ArgT0&& arg0, ArgT... args); + std::string* mutable_presharedkey(); + PROTOBUF_NODISCARD std::string* release_presharedkey(); + void set_allocated_presharedkey(std::string* presharedkey); + private: + const std::string& _internal_presharedkey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_presharedkey(const std::string& value); + std::string* _internal_mutable_presharedkey(); + public: + + // bool withAck = 1; + void clear_withack(); + bool withack() const; + void set_withack(bool value); + private: + bool _internal_withack() const; + void _internal_set_withack(bool value); + public: + + // .rtech.liveapi.ChangeCamera changeCam = 4; + bool has_changecam() const; + private: + bool _internal_has_changecam() const; + public: + void clear_changecam(); + const ::rtech::liveapi::ChangeCamera& changecam() const; + PROTOBUF_NODISCARD ::rtech::liveapi::ChangeCamera* release_changecam(); + ::rtech::liveapi::ChangeCamera* mutable_changecam(); + void set_allocated_changecam(::rtech::liveapi::ChangeCamera* changecam); + private: + const ::rtech::liveapi::ChangeCamera& _internal_changecam() const; + ::rtech::liveapi::ChangeCamera* _internal_mutable_changecam(); + public: + void unsafe_arena_set_allocated_changecam( + ::rtech::liveapi::ChangeCamera* changecam); + ::rtech::liveapi::ChangeCamera* unsafe_arena_release_changecam(); + + // .rtech.liveapi.PauseToggle pauseToggle = 5; + bool has_pausetoggle() const; + private: + bool _internal_has_pausetoggle() const; + public: + void clear_pausetoggle(); + const ::rtech::liveapi::PauseToggle& pausetoggle() const; + PROTOBUF_NODISCARD ::rtech::liveapi::PauseToggle* release_pausetoggle(); + ::rtech::liveapi::PauseToggle* mutable_pausetoggle(); + void set_allocated_pausetoggle(::rtech::liveapi::PauseToggle* pausetoggle); + private: + const ::rtech::liveapi::PauseToggle& _internal_pausetoggle() const; + ::rtech::liveapi::PauseToggle* _internal_mutable_pausetoggle(); + public: + void unsafe_arena_set_allocated_pausetoggle( + ::rtech::liveapi::PauseToggle* pausetoggle); + ::rtech::liveapi::PauseToggle* unsafe_arena_release_pausetoggle(); + + // .rtech.liveapi.CustomMatch_CreateLobby customMatch_CreateLobby = 10; + bool has_custommatch_createlobby() const; + private: + bool _internal_has_custommatch_createlobby() const; + public: + void clear_custommatch_createlobby(); + const ::rtech::liveapi::CustomMatch_CreateLobby& custommatch_createlobby() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_CreateLobby* release_custommatch_createlobby(); + ::rtech::liveapi::CustomMatch_CreateLobby* mutable_custommatch_createlobby(); + void set_allocated_custommatch_createlobby(::rtech::liveapi::CustomMatch_CreateLobby* custommatch_createlobby); + private: + const ::rtech::liveapi::CustomMatch_CreateLobby& _internal_custommatch_createlobby() const; + ::rtech::liveapi::CustomMatch_CreateLobby* _internal_mutable_custommatch_createlobby(); + public: + void unsafe_arena_set_allocated_custommatch_createlobby( + ::rtech::liveapi::CustomMatch_CreateLobby* custommatch_createlobby); + ::rtech::liveapi::CustomMatch_CreateLobby* unsafe_arena_release_custommatch_createlobby(); + + // .rtech.liveapi.CustomMatch_JoinLobby customMatch_JoinLobby = 11; + bool has_custommatch_joinlobby() const; + private: + bool _internal_has_custommatch_joinlobby() const; + public: + void clear_custommatch_joinlobby(); + const ::rtech::liveapi::CustomMatch_JoinLobby& custommatch_joinlobby() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_JoinLobby* release_custommatch_joinlobby(); + ::rtech::liveapi::CustomMatch_JoinLobby* mutable_custommatch_joinlobby(); + void set_allocated_custommatch_joinlobby(::rtech::liveapi::CustomMatch_JoinLobby* custommatch_joinlobby); + private: + const ::rtech::liveapi::CustomMatch_JoinLobby& _internal_custommatch_joinlobby() const; + ::rtech::liveapi::CustomMatch_JoinLobby* _internal_mutable_custommatch_joinlobby(); + public: + void unsafe_arena_set_allocated_custommatch_joinlobby( + ::rtech::liveapi::CustomMatch_JoinLobby* custommatch_joinlobby); + ::rtech::liveapi::CustomMatch_JoinLobby* unsafe_arena_release_custommatch_joinlobby(); + + // .rtech.liveapi.CustomMatch_LeaveLobby customMatch_LeaveLobby = 12; + bool has_custommatch_leavelobby() const; + private: + bool _internal_has_custommatch_leavelobby() const; + public: + void clear_custommatch_leavelobby(); + const ::rtech::liveapi::CustomMatch_LeaveLobby& custommatch_leavelobby() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_LeaveLobby* release_custommatch_leavelobby(); + ::rtech::liveapi::CustomMatch_LeaveLobby* mutable_custommatch_leavelobby(); + void set_allocated_custommatch_leavelobby(::rtech::liveapi::CustomMatch_LeaveLobby* custommatch_leavelobby); + private: + const ::rtech::liveapi::CustomMatch_LeaveLobby& _internal_custommatch_leavelobby() const; + ::rtech::liveapi::CustomMatch_LeaveLobby* _internal_mutable_custommatch_leavelobby(); + public: + void unsafe_arena_set_allocated_custommatch_leavelobby( + ::rtech::liveapi::CustomMatch_LeaveLobby* custommatch_leavelobby); + ::rtech::liveapi::CustomMatch_LeaveLobby* unsafe_arena_release_custommatch_leavelobby(); + + // .rtech.liveapi.CustomMatch_SetReady customMatch_SetReady = 13; + bool has_custommatch_setready() const; + private: + bool _internal_has_custommatch_setready() const; + public: + void clear_custommatch_setready(); + const ::rtech::liveapi::CustomMatch_SetReady& custommatch_setready() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_SetReady* release_custommatch_setready(); + ::rtech::liveapi::CustomMatch_SetReady* mutable_custommatch_setready(); + void set_allocated_custommatch_setready(::rtech::liveapi::CustomMatch_SetReady* custommatch_setready); + private: + const ::rtech::liveapi::CustomMatch_SetReady& _internal_custommatch_setready() const; + ::rtech::liveapi::CustomMatch_SetReady* _internal_mutable_custommatch_setready(); + public: + void unsafe_arena_set_allocated_custommatch_setready( + ::rtech::liveapi::CustomMatch_SetReady* custommatch_setready); + ::rtech::liveapi::CustomMatch_SetReady* unsafe_arena_release_custommatch_setready(); + + // .rtech.liveapi.CustomMatch_SetMatchmaking customMatch_SetMatchmaking = 14; + bool has_custommatch_setmatchmaking() const; + private: + bool _internal_has_custommatch_setmatchmaking() const; + public: + void clear_custommatch_setmatchmaking(); + const ::rtech::liveapi::CustomMatch_SetMatchmaking& custommatch_setmatchmaking() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_SetMatchmaking* release_custommatch_setmatchmaking(); + ::rtech::liveapi::CustomMatch_SetMatchmaking* mutable_custommatch_setmatchmaking(); + void set_allocated_custommatch_setmatchmaking(::rtech::liveapi::CustomMatch_SetMatchmaking* custommatch_setmatchmaking); + private: + const ::rtech::liveapi::CustomMatch_SetMatchmaking& _internal_custommatch_setmatchmaking() const; + ::rtech::liveapi::CustomMatch_SetMatchmaking* _internal_mutable_custommatch_setmatchmaking(); + public: + void unsafe_arena_set_allocated_custommatch_setmatchmaking( + ::rtech::liveapi::CustomMatch_SetMatchmaking* custommatch_setmatchmaking); + ::rtech::liveapi::CustomMatch_SetMatchmaking* unsafe_arena_release_custommatch_setmatchmaking(); + + // .rtech.liveapi.CustomMatch_SetTeam customMatch_SetTeam = 15; + bool has_custommatch_setteam() const; + private: + bool _internal_has_custommatch_setteam() const; + public: + void clear_custommatch_setteam(); + const ::rtech::liveapi::CustomMatch_SetTeam& custommatch_setteam() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_SetTeam* release_custommatch_setteam(); + ::rtech::liveapi::CustomMatch_SetTeam* mutable_custommatch_setteam(); + void set_allocated_custommatch_setteam(::rtech::liveapi::CustomMatch_SetTeam* custommatch_setteam); + private: + const ::rtech::liveapi::CustomMatch_SetTeam& _internal_custommatch_setteam() const; + ::rtech::liveapi::CustomMatch_SetTeam* _internal_mutable_custommatch_setteam(); + public: + void unsafe_arena_set_allocated_custommatch_setteam( + ::rtech::liveapi::CustomMatch_SetTeam* custommatch_setteam); + ::rtech::liveapi::CustomMatch_SetTeam* unsafe_arena_release_custommatch_setteam(); + + // .rtech.liveapi.CustomMatch_KickPlayer customMatch_KickPlayer = 16; + bool has_custommatch_kickplayer() const; + private: + bool _internal_has_custommatch_kickplayer() const; + public: + void clear_custommatch_kickplayer(); + const ::rtech::liveapi::CustomMatch_KickPlayer& custommatch_kickplayer() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_KickPlayer* release_custommatch_kickplayer(); + ::rtech::liveapi::CustomMatch_KickPlayer* mutable_custommatch_kickplayer(); + void set_allocated_custommatch_kickplayer(::rtech::liveapi::CustomMatch_KickPlayer* custommatch_kickplayer); + private: + const ::rtech::liveapi::CustomMatch_KickPlayer& _internal_custommatch_kickplayer() const; + ::rtech::liveapi::CustomMatch_KickPlayer* _internal_mutable_custommatch_kickplayer(); + public: + void unsafe_arena_set_allocated_custommatch_kickplayer( + ::rtech::liveapi::CustomMatch_KickPlayer* custommatch_kickplayer); + ::rtech::liveapi::CustomMatch_KickPlayer* unsafe_arena_release_custommatch_kickplayer(); + + // .rtech.liveapi.CustomMatch_SetSettings customMatch_SetSettings = 17; + bool has_custommatch_setsettings() const; + private: + bool _internal_has_custommatch_setsettings() const; + public: + void clear_custommatch_setsettings(); + const ::rtech::liveapi::CustomMatch_SetSettings& custommatch_setsettings() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_SetSettings* release_custommatch_setsettings(); + ::rtech::liveapi::CustomMatch_SetSettings* mutable_custommatch_setsettings(); + void set_allocated_custommatch_setsettings(::rtech::liveapi::CustomMatch_SetSettings* custommatch_setsettings); + private: + const ::rtech::liveapi::CustomMatch_SetSettings& _internal_custommatch_setsettings() const; + ::rtech::liveapi::CustomMatch_SetSettings* _internal_mutable_custommatch_setsettings(); + public: + void unsafe_arena_set_allocated_custommatch_setsettings( + ::rtech::liveapi::CustomMatch_SetSettings* custommatch_setsettings); + ::rtech::liveapi::CustomMatch_SetSettings* unsafe_arena_release_custommatch_setsettings(); + + // .rtech.liveapi.CustomMatch_SendChat customMatch_SendChat = 18; + bool has_custommatch_sendchat() const; + private: + bool _internal_has_custommatch_sendchat() const; + public: + void clear_custommatch_sendchat(); + const ::rtech::liveapi::CustomMatch_SendChat& custommatch_sendchat() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_SendChat* release_custommatch_sendchat(); + ::rtech::liveapi::CustomMatch_SendChat* mutable_custommatch_sendchat(); + void set_allocated_custommatch_sendchat(::rtech::liveapi::CustomMatch_SendChat* custommatch_sendchat); + private: + const ::rtech::liveapi::CustomMatch_SendChat& _internal_custommatch_sendchat() const; + ::rtech::liveapi::CustomMatch_SendChat* _internal_mutable_custommatch_sendchat(); + public: + void unsafe_arena_set_allocated_custommatch_sendchat( + ::rtech::liveapi::CustomMatch_SendChat* custommatch_sendchat); + ::rtech::liveapi::CustomMatch_SendChat* unsafe_arena_release_custommatch_sendchat(); + + // .rtech.liveapi.CustomMatch_GetLobbyPlayers customMatch_GetLobbyPlayers = 19; + bool has_custommatch_getlobbyplayers() const; + private: + bool _internal_has_custommatch_getlobbyplayers() const; + public: + void clear_custommatch_getlobbyplayers(); + const ::rtech::liveapi::CustomMatch_GetLobbyPlayers& custommatch_getlobbyplayers() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_GetLobbyPlayers* release_custommatch_getlobbyplayers(); + ::rtech::liveapi::CustomMatch_GetLobbyPlayers* mutable_custommatch_getlobbyplayers(); + void set_allocated_custommatch_getlobbyplayers(::rtech::liveapi::CustomMatch_GetLobbyPlayers* custommatch_getlobbyplayers); + private: + const ::rtech::liveapi::CustomMatch_GetLobbyPlayers& _internal_custommatch_getlobbyplayers() const; + ::rtech::liveapi::CustomMatch_GetLobbyPlayers* _internal_mutable_custommatch_getlobbyplayers(); + public: + void unsafe_arena_set_allocated_custommatch_getlobbyplayers( + ::rtech::liveapi::CustomMatch_GetLobbyPlayers* custommatch_getlobbyplayers); + ::rtech::liveapi::CustomMatch_GetLobbyPlayers* unsafe_arena_release_custommatch_getlobbyplayers(); + + // .rtech.liveapi.CustomMatch_SetTeamName customMatch_SetTeamName = 20; + bool has_custommatch_setteamname() const; + private: + bool _internal_has_custommatch_setteamname() const; + public: + void clear_custommatch_setteamname(); + const ::rtech::liveapi::CustomMatch_SetTeamName& custommatch_setteamname() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_SetTeamName* release_custommatch_setteamname(); + ::rtech::liveapi::CustomMatch_SetTeamName* mutable_custommatch_setteamname(); + void set_allocated_custommatch_setteamname(::rtech::liveapi::CustomMatch_SetTeamName* custommatch_setteamname); + private: + const ::rtech::liveapi::CustomMatch_SetTeamName& _internal_custommatch_setteamname() const; + ::rtech::liveapi::CustomMatch_SetTeamName* _internal_mutable_custommatch_setteamname(); + public: + void unsafe_arena_set_allocated_custommatch_setteamname( + ::rtech::liveapi::CustomMatch_SetTeamName* custommatch_setteamname); + ::rtech::liveapi::CustomMatch_SetTeamName* unsafe_arena_release_custommatch_setteamname(); + + // .rtech.liveapi.CustomMatch_GetSettings customMatch_GetSettings = 21; + bool has_custommatch_getsettings() const; + private: + bool _internal_has_custommatch_getsettings() const; + public: + void clear_custommatch_getsettings(); + const ::rtech::liveapi::CustomMatch_GetSettings& custommatch_getsettings() const; + PROTOBUF_NODISCARD ::rtech::liveapi::CustomMatch_GetSettings* release_custommatch_getsettings(); + ::rtech::liveapi::CustomMatch_GetSettings* mutable_custommatch_getsettings(); + void set_allocated_custommatch_getsettings(::rtech::liveapi::CustomMatch_GetSettings* custommatch_getsettings); + private: + const ::rtech::liveapi::CustomMatch_GetSettings& _internal_custommatch_getsettings() const; + ::rtech::liveapi::CustomMatch_GetSettings* _internal_mutable_custommatch_getsettings(); + public: + void unsafe_arena_set_allocated_custommatch_getsettings( + ::rtech::liveapi::CustomMatch_GetSettings* custommatch_getsettings); + ::rtech::liveapi::CustomMatch_GetSettings* unsafe_arena_release_custommatch_getsettings(); + + void clear_actions(); + ActionsCase actions_case() const; + // @@protoc_insertion_point(class_scope:rtech.liveapi.Request) + private: + class _Internal; + void set_has_changecam(); + void set_has_pausetoggle(); + void set_has_custommatch_createlobby(); + void set_has_custommatch_joinlobby(); + void set_has_custommatch_leavelobby(); + void set_has_custommatch_setready(); + void set_has_custommatch_setmatchmaking(); + void set_has_custommatch_setteam(); + void set_has_custommatch_kickplayer(); + void set_has_custommatch_setsettings(); + void set_has_custommatch_sendchat(); + void set_has_custommatch_getlobbyplayers(); + void set_has_custommatch_setteamname(); + void set_has_custommatch_getsettings(); + + inline bool has_actions() const; + inline void clear_has_actions(); + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr presharedkey_; + bool withack_; + union ActionsUnion { + constexpr ActionsUnion() : _constinit_{} {} + ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_; + ::rtech::liveapi::ChangeCamera* changecam_; + ::rtech::liveapi::PauseToggle* pausetoggle_; + ::rtech::liveapi::CustomMatch_CreateLobby* custommatch_createlobby_; + ::rtech::liveapi::CustomMatch_JoinLobby* custommatch_joinlobby_; + ::rtech::liveapi::CustomMatch_LeaveLobby* custommatch_leavelobby_; + ::rtech::liveapi::CustomMatch_SetReady* custommatch_setready_; + ::rtech::liveapi::CustomMatch_SetMatchmaking* custommatch_setmatchmaking_; + ::rtech::liveapi::CustomMatch_SetTeam* custommatch_setteam_; + ::rtech::liveapi::CustomMatch_KickPlayer* custommatch_kickplayer_; + ::rtech::liveapi::CustomMatch_SetSettings* custommatch_setsettings_; + ::rtech::liveapi::CustomMatch_SendChat* custommatch_sendchat_; + ::rtech::liveapi::CustomMatch_GetLobbyPlayers* custommatch_getlobbyplayers_; + ::rtech::liveapi::CustomMatch_SetTeamName* custommatch_setteamname_; + ::rtech::liveapi::CustomMatch_GetSettings* custommatch_getsettings_; + } actions_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + uint32_t _oneof_case_[1]; + + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class RequestStatus final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.RequestStatus) */ { + public: + inline RequestStatus() : RequestStatus(nullptr) {} + ~RequestStatus() override; + explicit PROTOBUF_CONSTEXPR RequestStatus(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + RequestStatus(const RequestStatus& from); + RequestStatus(RequestStatus&& from) noexcept + : RequestStatus() { + *this = ::std::move(from); + } + + inline RequestStatus& operator=(const RequestStatus& from) { + CopyFrom(from); + return *this; + } + inline RequestStatus& operator=(RequestStatus&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const RequestStatus& default_instance() { + return *internal_default_instance(); + } + static inline const RequestStatus* internal_default_instance() { + return reinterpret_cast( + &_RequestStatus_default_instance_); + } + static constexpr int kIndexInFileMessages = + 61; + + friend void swap(RequestStatus& a, RequestStatus& b) { + a.Swap(&b); + } + inline void Swap(RequestStatus* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(RequestStatus* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + RequestStatus* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const RequestStatus& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const RequestStatus& from) { + RequestStatus::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(RequestStatus* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.RequestStatus"; + } + protected: + explicit RequestStatus(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kStatusFieldNumber = 1, + }; + // string status = 1; + void clear_status(); + const std::string& status() const; + template + void set_status(ArgT0&& arg0, ArgT... args); + std::string* mutable_status(); + PROTOBUF_NODISCARD std::string* release_status(); + void set_allocated_status(std::string* status); + private: + const std::string& _internal_status() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_status(const std::string& value); + std::string* _internal_mutable_status(); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.RequestStatus) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr status_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class Response final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.Response) */ { + public: + inline Response() : Response(nullptr) {} + ~Response() override; + explicit PROTOBUF_CONSTEXPR Response(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Response(const Response& from); + Response(Response&& from) noexcept + : Response() { + *this = ::std::move(from); + } + + inline Response& operator=(const Response& from) { + CopyFrom(from); + return *this; + } + inline Response& operator=(Response&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const Response& default_instance() { + return *internal_default_instance(); + } + static inline const Response* internal_default_instance() { + return reinterpret_cast( + &_Response_default_instance_); + } + static constexpr int kIndexInFileMessages = + 62; + + friend void swap(Response& a, Response& b) { + a.Swap(&b); + } + inline void Swap(Response* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Response* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Response* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const Response& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const Response& from) { + Response::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(Response* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.Response"; + } + protected: + explicit Response(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kResultFieldNumber = 2, + kSuccessFieldNumber = 1, + }; + // .google.protobuf.Any result = 2; + bool has_result() const; + private: + bool _internal_has_result() const; + public: + void clear_result(); + const ::PROTOBUF_NAMESPACE_ID::Any& result() const; + PROTOBUF_NODISCARD ::PROTOBUF_NAMESPACE_ID::Any* release_result(); + ::PROTOBUF_NAMESPACE_ID::Any* mutable_result(); + void set_allocated_result(::PROTOBUF_NAMESPACE_ID::Any* result); + private: + const ::PROTOBUF_NAMESPACE_ID::Any& _internal_result() const; + ::PROTOBUF_NAMESPACE_ID::Any* _internal_mutable_result(); + public: + void unsafe_arena_set_allocated_result( + ::PROTOBUF_NAMESPACE_ID::Any* result); + ::PROTOBUF_NAMESPACE_ID::Any* unsafe_arena_release_result(); + + // bool success = 1; + void clear_success(); + bool success() const; + void set_success(bool value); + private: + bool _internal_success() const; + void _internal_set_success(bool value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.Response) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::Any* result_; + bool success_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// ------------------------------------------------------------------- + +class LiveAPIEvent final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:rtech.liveapi.LiveAPIEvent) */ { + public: + inline LiveAPIEvent() : LiveAPIEvent(nullptr) {} + ~LiveAPIEvent() override; + explicit PROTOBUF_CONSTEXPR LiveAPIEvent(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + LiveAPIEvent(const LiveAPIEvent& from); + LiveAPIEvent(LiveAPIEvent&& from) noexcept + : LiveAPIEvent() { + *this = ::std::move(from); + } + + inline LiveAPIEvent& operator=(const LiveAPIEvent& from) { + CopyFrom(from); + return *this; + } + inline LiveAPIEvent& operator=(LiveAPIEvent&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const LiveAPIEvent& default_instance() { + return *internal_default_instance(); + } + static inline const LiveAPIEvent* internal_default_instance() { + return reinterpret_cast( + &_LiveAPIEvent_default_instance_); + } + static constexpr int kIndexInFileMessages = + 63; + + friend void swap(LiveAPIEvent& a, LiveAPIEvent& b) { + a.Swap(&b); + } + inline void Swap(LiveAPIEvent* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(LiveAPIEvent* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + LiveAPIEvent* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const LiveAPIEvent& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const LiveAPIEvent& from) { + LiveAPIEvent::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(LiveAPIEvent* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "rtech.liveapi.LiveAPIEvent"; + } + protected: + explicit LiveAPIEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kGameMessageFieldNumber = 3, + kEventSizeFieldNumber = 1, + }; + // .google.protobuf.Any gameMessage = 3; + bool has_gamemessage() const; + private: + bool _internal_has_gamemessage() const; + public: + void clear_gamemessage(); + const ::PROTOBUF_NAMESPACE_ID::Any& gamemessage() const; + PROTOBUF_NODISCARD ::PROTOBUF_NAMESPACE_ID::Any* release_gamemessage(); + ::PROTOBUF_NAMESPACE_ID::Any* mutable_gamemessage(); + void set_allocated_gamemessage(::PROTOBUF_NAMESPACE_ID::Any* gamemessage); + private: + const ::PROTOBUF_NAMESPACE_ID::Any& _internal_gamemessage() const; + ::PROTOBUF_NAMESPACE_ID::Any* _internal_mutable_gamemessage(); + public: + void unsafe_arena_set_allocated_gamemessage( + ::PROTOBUF_NAMESPACE_ID::Any* gamemessage); + ::PROTOBUF_NAMESPACE_ID::Any* unsafe_arena_release_gamemessage(); + + // fixed32 event_size = 1; + void clear_event_size(); + uint32_t event_size() const; + void set_event_size(uint32_t value); + private: + uint32_t _internal_event_size() const; + void _internal_set_event_size(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:rtech.liveapi.LiveAPIEvent) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::Any* gamemessage_; + uint32_t event_size_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_events_2eproto; +}; +// =================================================================== + + +// =================================================================== + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// Vector3 + +// float x = 1; +inline void Vector3::clear_x() { + _impl_.x_ = 0; +} +inline float Vector3::_internal_x() const { + return _impl_.x_; +} +inline float Vector3::x() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Vector3.x) + return _internal_x(); +} +inline void Vector3::_internal_set_x(float value) { + + _impl_.x_ = value; +} +inline void Vector3::set_x(float value) { + _internal_set_x(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Vector3.x) +} + +// float y = 2; +inline void Vector3::clear_y() { + _impl_.y_ = 0; +} +inline float Vector3::_internal_y() const { + return _impl_.y_; +} +inline float Vector3::y() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Vector3.y) + return _internal_y(); +} +inline void Vector3::_internal_set_y(float value) { + + _impl_.y_ = value; +} +inline void Vector3::set_y(float value) { + _internal_set_y(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Vector3.y) +} + +// float z = 3; +inline void Vector3::clear_z() { + _impl_.z_ = 0; +} +inline float Vector3::_internal_z() const { + return _impl_.z_; +} +inline float Vector3::z() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Vector3.z) + return _internal_z(); +} +inline void Vector3::_internal_set_z(float value) { + + _impl_.z_ = value; +} +inline void Vector3::set_z(float value) { + _internal_set_z(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Vector3.z) +} + +// ------------------------------------------------------------------- + +// Player + +// string name = 1; +inline void Player::clear_name() { + _impl_.name_.ClearToEmpty(); +} +inline const std::string& Player::name() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Player::set_name(ArgT0&& arg0, ArgT... args) { + + _impl_.name_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.name) +} +inline std::string* Player::mutable_name() { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Player.name) + return _s; +} +inline const std::string& Player::_internal_name() const { + return _impl_.name_.Get(); +} +inline void Player::_internal_set_name(const std::string& value) { + + _impl_.name_.Set(value, GetArenaForAllocation()); +} +inline std::string* Player::_internal_mutable_name() { + + return _impl_.name_.Mutable(GetArenaForAllocation()); +} +inline std::string* Player::release_name() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Player.name) + return _impl_.name_.Release(); +} +inline void Player::set_allocated_name(std::string* name) { + if (name != nullptr) { + + } else { + + } + _impl_.name_.SetAllocated(name, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Player.name) +} + +// uint32 teamId = 2; +inline void Player::clear_teamid() { + _impl_.teamid_ = 0u; +} +inline uint32_t Player::_internal_teamid() const { + return _impl_.teamid_; +} +inline uint32_t Player::teamid() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.teamId) + return _internal_teamid(); +} +inline void Player::_internal_set_teamid(uint32_t value) { + + _impl_.teamid_ = value; +} +inline void Player::set_teamid(uint32_t value) { + _internal_set_teamid(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.teamId) +} + +// .rtech.liveapi.Vector3 pos = 3; +inline bool Player::_internal_has_pos() const { + return this != internal_default_instance() && _impl_.pos_ != nullptr; +} +inline bool Player::has_pos() const { + return _internal_has_pos(); +} +inline void Player::clear_pos() { + if (GetArenaForAllocation() == nullptr && _impl_.pos_ != nullptr) { + delete _impl_.pos_; + } + _impl_.pos_ = nullptr; +} +inline const ::rtech::liveapi::Vector3& Player::_internal_pos() const { + const ::rtech::liveapi::Vector3* p = _impl_.pos_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Vector3_default_instance_); +} +inline const ::rtech::liveapi::Vector3& Player::pos() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.pos) + return _internal_pos(); +} +inline void Player::unsafe_arena_set_allocated_pos( + ::rtech::liveapi::Vector3* pos) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.pos_); + } + _impl_.pos_ = pos; + if (pos) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Player.pos) +} +inline ::rtech::liveapi::Vector3* Player::release_pos() { + + ::rtech::liveapi::Vector3* temp = _impl_.pos_; + _impl_.pos_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Vector3* Player::unsafe_arena_release_pos() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Player.pos) + + ::rtech::liveapi::Vector3* temp = _impl_.pos_; + _impl_.pos_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Vector3* Player::_internal_mutable_pos() { + + if (_impl_.pos_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Vector3>(GetArenaForAllocation()); + _impl_.pos_ = p; + } + return _impl_.pos_; +} +inline ::rtech::liveapi::Vector3* Player::mutable_pos() { + ::rtech::liveapi::Vector3* _msg = _internal_mutable_pos(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Player.pos) + return _msg; +} +inline void Player::set_allocated_pos(::rtech::liveapi::Vector3* pos) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.pos_; + } + if (pos) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(pos); + if (message_arena != submessage_arena) { + pos = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, pos, submessage_arena); + } + + } else { + + } + _impl_.pos_ = pos; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Player.pos) +} + +// .rtech.liveapi.Vector3 angles = 4; +inline bool Player::_internal_has_angles() const { + return this != internal_default_instance() && _impl_.angles_ != nullptr; +} +inline bool Player::has_angles() const { + return _internal_has_angles(); +} +inline void Player::clear_angles() { + if (GetArenaForAllocation() == nullptr && _impl_.angles_ != nullptr) { + delete _impl_.angles_; + } + _impl_.angles_ = nullptr; +} +inline const ::rtech::liveapi::Vector3& Player::_internal_angles() const { + const ::rtech::liveapi::Vector3* p = _impl_.angles_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Vector3_default_instance_); +} +inline const ::rtech::liveapi::Vector3& Player::angles() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.angles) + return _internal_angles(); +} +inline void Player::unsafe_arena_set_allocated_angles( + ::rtech::liveapi::Vector3* angles) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.angles_); + } + _impl_.angles_ = angles; + if (angles) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Player.angles) +} +inline ::rtech::liveapi::Vector3* Player::release_angles() { + + ::rtech::liveapi::Vector3* temp = _impl_.angles_; + _impl_.angles_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Vector3* Player::unsafe_arena_release_angles() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Player.angles) + + ::rtech::liveapi::Vector3* temp = _impl_.angles_; + _impl_.angles_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Vector3* Player::_internal_mutable_angles() { + + if (_impl_.angles_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Vector3>(GetArenaForAllocation()); + _impl_.angles_ = p; + } + return _impl_.angles_; +} +inline ::rtech::liveapi::Vector3* Player::mutable_angles() { + ::rtech::liveapi::Vector3* _msg = _internal_mutable_angles(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Player.angles) + return _msg; +} +inline void Player::set_allocated_angles(::rtech::liveapi::Vector3* angles) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.angles_; + } + if (angles) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(angles); + if (message_arena != submessage_arena) { + angles = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, angles, submessage_arena); + } + + } else { + + } + _impl_.angles_ = angles; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Player.angles) +} + +// uint32 currentHealth = 5; +inline void Player::clear_currenthealth() { + _impl_.currenthealth_ = 0u; +} +inline uint32_t Player::_internal_currenthealth() const { + return _impl_.currenthealth_; +} +inline uint32_t Player::currenthealth() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.currentHealth) + return _internal_currenthealth(); +} +inline void Player::_internal_set_currenthealth(uint32_t value) { + + _impl_.currenthealth_ = value; +} +inline void Player::set_currenthealth(uint32_t value) { + _internal_set_currenthealth(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.currentHealth) +} + +// uint32 maxHealth = 6; +inline void Player::clear_maxhealth() { + _impl_.maxhealth_ = 0u; +} +inline uint32_t Player::_internal_maxhealth() const { + return _impl_.maxhealth_; +} +inline uint32_t Player::maxhealth() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.maxHealth) + return _internal_maxhealth(); +} +inline void Player::_internal_set_maxhealth(uint32_t value) { + + _impl_.maxhealth_ = value; +} +inline void Player::set_maxhealth(uint32_t value) { + _internal_set_maxhealth(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.maxHealth) +} + +// uint32 shieldHealth = 7; +inline void Player::clear_shieldhealth() { + _impl_.shieldhealth_ = 0u; +} +inline uint32_t Player::_internal_shieldhealth() const { + return _impl_.shieldhealth_; +} +inline uint32_t Player::shieldhealth() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.shieldHealth) + return _internal_shieldhealth(); +} +inline void Player::_internal_set_shieldhealth(uint32_t value) { + + _impl_.shieldhealth_ = value; +} +inline void Player::set_shieldhealth(uint32_t value) { + _internal_set_shieldhealth(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.shieldHealth) +} + +// uint32 shieldMaxHealth = 8; +inline void Player::clear_shieldmaxhealth() { + _impl_.shieldmaxhealth_ = 0u; +} +inline uint32_t Player::_internal_shieldmaxhealth() const { + return _impl_.shieldmaxhealth_; +} +inline uint32_t Player::shieldmaxhealth() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.shieldMaxHealth) + return _internal_shieldmaxhealth(); +} +inline void Player::_internal_set_shieldmaxhealth(uint32_t value) { + + _impl_.shieldmaxhealth_ = value; +} +inline void Player::set_shieldmaxhealth(uint32_t value) { + _internal_set_shieldmaxhealth(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.shieldMaxHealth) +} + +// string nucleusHash = 9; +inline void Player::clear_nucleushash() { + _impl_.nucleushash_.ClearToEmpty(); +} +inline const std::string& Player::nucleushash() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.nucleusHash) + return _internal_nucleushash(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Player::set_nucleushash(ArgT0&& arg0, ArgT... args) { + + _impl_.nucleushash_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.nucleusHash) +} +inline std::string* Player::mutable_nucleushash() { + std::string* _s = _internal_mutable_nucleushash(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Player.nucleusHash) + return _s; +} +inline const std::string& Player::_internal_nucleushash() const { + return _impl_.nucleushash_.Get(); +} +inline void Player::_internal_set_nucleushash(const std::string& value) { + + _impl_.nucleushash_.Set(value, GetArenaForAllocation()); +} +inline std::string* Player::_internal_mutable_nucleushash() { + + return _impl_.nucleushash_.Mutable(GetArenaForAllocation()); +} +inline std::string* Player::release_nucleushash() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Player.nucleusHash) + return _impl_.nucleushash_.Release(); +} +inline void Player::set_allocated_nucleushash(std::string* nucleushash) { + if (nucleushash != nullptr) { + + } else { + + } + _impl_.nucleushash_.SetAllocated(nucleushash, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.nucleushash_.IsDefault()) { + _impl_.nucleushash_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Player.nucleusHash) +} + +// string hardwareName = 10; +inline void Player::clear_hardwarename() { + _impl_.hardwarename_.ClearToEmpty(); +} +inline const std::string& Player::hardwarename() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.hardwareName) + return _internal_hardwarename(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Player::set_hardwarename(ArgT0&& arg0, ArgT... args) { + + _impl_.hardwarename_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.hardwareName) +} +inline std::string* Player::mutable_hardwarename() { + std::string* _s = _internal_mutable_hardwarename(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Player.hardwareName) + return _s; +} +inline const std::string& Player::_internal_hardwarename() const { + return _impl_.hardwarename_.Get(); +} +inline void Player::_internal_set_hardwarename(const std::string& value) { + + _impl_.hardwarename_.Set(value, GetArenaForAllocation()); +} +inline std::string* Player::_internal_mutable_hardwarename() { + + return _impl_.hardwarename_.Mutable(GetArenaForAllocation()); +} +inline std::string* Player::release_hardwarename() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Player.hardwareName) + return _impl_.hardwarename_.Release(); +} +inline void Player::set_allocated_hardwarename(std::string* hardwarename) { + if (hardwarename != nullptr) { + + } else { + + } + _impl_.hardwarename_.SetAllocated(hardwarename, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.hardwarename_.IsDefault()) { + _impl_.hardwarename_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Player.hardwareName) +} + +// string teamName = 11; +inline void Player::clear_teamname() { + _impl_.teamname_.ClearToEmpty(); +} +inline const std::string& Player::teamname() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.teamName) + return _internal_teamname(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Player::set_teamname(ArgT0&& arg0, ArgT... args) { + + _impl_.teamname_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.teamName) +} +inline std::string* Player::mutable_teamname() { + std::string* _s = _internal_mutable_teamname(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Player.teamName) + return _s; +} +inline const std::string& Player::_internal_teamname() const { + return _impl_.teamname_.Get(); +} +inline void Player::_internal_set_teamname(const std::string& value) { + + _impl_.teamname_.Set(value, GetArenaForAllocation()); +} +inline std::string* Player::_internal_mutable_teamname() { + + return _impl_.teamname_.Mutable(GetArenaForAllocation()); +} +inline std::string* Player::release_teamname() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Player.teamName) + return _impl_.teamname_.Release(); +} +inline void Player::set_allocated_teamname(std::string* teamname) { + if (teamname != nullptr) { + + } else { + + } + _impl_.teamname_.SetAllocated(teamname, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.teamname_.IsDefault()) { + _impl_.teamname_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Player.teamName) +} + +// uint32 squadIndex = 12; +inline void Player::clear_squadindex() { + _impl_.squadindex_ = 0u; +} +inline uint32_t Player::_internal_squadindex() const { + return _impl_.squadindex_; +} +inline uint32_t Player::squadindex() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.squadIndex) + return _internal_squadindex(); +} +inline void Player::_internal_set_squadindex(uint32_t value) { + + _impl_.squadindex_ = value; +} +inline void Player::set_squadindex(uint32_t value) { + _internal_set_squadindex(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.squadIndex) +} + +// string character = 13; +inline void Player::clear_character() { + _impl_.character_.ClearToEmpty(); +} +inline const std::string& Player::character() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.character) + return _internal_character(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Player::set_character(ArgT0&& arg0, ArgT... args) { + + _impl_.character_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.character) +} +inline std::string* Player::mutable_character() { + std::string* _s = _internal_mutable_character(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Player.character) + return _s; +} +inline const std::string& Player::_internal_character() const { + return _impl_.character_.Get(); +} +inline void Player::_internal_set_character(const std::string& value) { + + _impl_.character_.Set(value, GetArenaForAllocation()); +} +inline std::string* Player::_internal_mutable_character() { + + return _impl_.character_.Mutable(GetArenaForAllocation()); +} +inline std::string* Player::release_character() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Player.character) + return _impl_.character_.Release(); +} +inline void Player::set_allocated_character(std::string* character) { + if (character != nullptr) { + + } else { + + } + _impl_.character_.SetAllocated(character, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.character_.IsDefault()) { + _impl_.character_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Player.character) +} + +// string skin = 14; +inline void Player::clear_skin() { + _impl_.skin_.ClearToEmpty(); +} +inline const std::string& Player::skin() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Player.skin) + return _internal_skin(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Player::set_skin(ArgT0&& arg0, ArgT... args) { + + _impl_.skin_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Player.skin) +} +inline std::string* Player::mutable_skin() { + std::string* _s = _internal_mutable_skin(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Player.skin) + return _s; +} +inline const std::string& Player::_internal_skin() const { + return _impl_.skin_.Get(); +} +inline void Player::_internal_set_skin(const std::string& value) { + + _impl_.skin_.Set(value, GetArenaForAllocation()); +} +inline std::string* Player::_internal_mutable_skin() { + + return _impl_.skin_.Mutable(GetArenaForAllocation()); +} +inline std::string* Player::release_skin() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Player.skin) + return _impl_.skin_.Release(); +} +inline void Player::set_allocated_skin(std::string* skin) { + if (skin != nullptr) { + + } else { + + } + _impl_.skin_.SetAllocated(skin, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.skin_.IsDefault()) { + _impl_.skin_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Player.skin) +} + +// ------------------------------------------------------------------- + +// CustomMatch_LobbyPlayer + +// string name = 1; +inline void CustomMatch_LobbyPlayer::clear_name() { + _impl_.name_.ClearToEmpty(); +} +inline const std::string& CustomMatch_LobbyPlayer::name() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_LobbyPlayer.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_LobbyPlayer::set_name(ArgT0&& arg0, ArgT... args) { + + _impl_.name_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_LobbyPlayer.name) +} +inline std::string* CustomMatch_LobbyPlayer::mutable_name() { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_LobbyPlayer.name) + return _s; +} +inline const std::string& CustomMatch_LobbyPlayer::_internal_name() const { + return _impl_.name_.Get(); +} +inline void CustomMatch_LobbyPlayer::_internal_set_name(const std::string& value) { + + _impl_.name_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_LobbyPlayer::_internal_mutable_name() { + + return _impl_.name_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_LobbyPlayer::release_name() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_LobbyPlayer.name) + return _impl_.name_.Release(); +} +inline void CustomMatch_LobbyPlayer::set_allocated_name(std::string* name) { + if (name != nullptr) { + + } else { + + } + _impl_.name_.SetAllocated(name, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_LobbyPlayer.name) +} + +// uint32 teamId = 2; +inline void CustomMatch_LobbyPlayer::clear_teamid() { + _impl_.teamid_ = 0u; +} +inline uint32_t CustomMatch_LobbyPlayer::_internal_teamid() const { + return _impl_.teamid_; +} +inline uint32_t CustomMatch_LobbyPlayer::teamid() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_LobbyPlayer.teamId) + return _internal_teamid(); +} +inline void CustomMatch_LobbyPlayer::_internal_set_teamid(uint32_t value) { + + _impl_.teamid_ = value; +} +inline void CustomMatch_LobbyPlayer::set_teamid(uint32_t value) { + _internal_set_teamid(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_LobbyPlayer.teamId) +} + +// string nucleusHash = 3; +inline void CustomMatch_LobbyPlayer::clear_nucleushash() { + _impl_.nucleushash_.ClearToEmpty(); +} +inline const std::string& CustomMatch_LobbyPlayer::nucleushash() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_LobbyPlayer.nucleusHash) + return _internal_nucleushash(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_LobbyPlayer::set_nucleushash(ArgT0&& arg0, ArgT... args) { + + _impl_.nucleushash_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_LobbyPlayer.nucleusHash) +} +inline std::string* CustomMatch_LobbyPlayer::mutable_nucleushash() { + std::string* _s = _internal_mutable_nucleushash(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_LobbyPlayer.nucleusHash) + return _s; +} +inline const std::string& CustomMatch_LobbyPlayer::_internal_nucleushash() const { + return _impl_.nucleushash_.Get(); +} +inline void CustomMatch_LobbyPlayer::_internal_set_nucleushash(const std::string& value) { + + _impl_.nucleushash_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_LobbyPlayer::_internal_mutable_nucleushash() { + + return _impl_.nucleushash_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_LobbyPlayer::release_nucleushash() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_LobbyPlayer.nucleusHash) + return _impl_.nucleushash_.Release(); +} +inline void CustomMatch_LobbyPlayer::set_allocated_nucleushash(std::string* nucleushash) { + if (nucleushash != nullptr) { + + } else { + + } + _impl_.nucleushash_.SetAllocated(nucleushash, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.nucleushash_.IsDefault()) { + _impl_.nucleushash_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_LobbyPlayer.nucleusHash) +} + +// string hardwareName = 4; +inline void CustomMatch_LobbyPlayer::clear_hardwarename() { + _impl_.hardwarename_.ClearToEmpty(); +} +inline const std::string& CustomMatch_LobbyPlayer::hardwarename() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_LobbyPlayer.hardwareName) + return _internal_hardwarename(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_LobbyPlayer::set_hardwarename(ArgT0&& arg0, ArgT... args) { + + _impl_.hardwarename_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_LobbyPlayer.hardwareName) +} +inline std::string* CustomMatch_LobbyPlayer::mutable_hardwarename() { + std::string* _s = _internal_mutable_hardwarename(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_LobbyPlayer.hardwareName) + return _s; +} +inline const std::string& CustomMatch_LobbyPlayer::_internal_hardwarename() const { + return _impl_.hardwarename_.Get(); +} +inline void CustomMatch_LobbyPlayer::_internal_set_hardwarename(const std::string& value) { + + _impl_.hardwarename_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_LobbyPlayer::_internal_mutable_hardwarename() { + + return _impl_.hardwarename_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_LobbyPlayer::release_hardwarename() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_LobbyPlayer.hardwareName) + return _impl_.hardwarename_.Release(); +} +inline void CustomMatch_LobbyPlayer::set_allocated_hardwarename(std::string* hardwarename) { + if (hardwarename != nullptr) { + + } else { + + } + _impl_.hardwarename_.SetAllocated(hardwarename, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.hardwarename_.IsDefault()) { + _impl_.hardwarename_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_LobbyPlayer.hardwareName) +} + +// ------------------------------------------------------------------- + +// Datacenter + +// uint64 timestamp = 1; +inline void Datacenter::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t Datacenter::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t Datacenter::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Datacenter.timestamp) + return _internal_timestamp(); +} +inline void Datacenter::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void Datacenter::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Datacenter.timestamp) +} + +// string category = 2; +inline void Datacenter::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& Datacenter::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Datacenter.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Datacenter::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Datacenter.category) +} +inline std::string* Datacenter::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Datacenter.category) + return _s; +} +inline const std::string& Datacenter::_internal_category() const { + return _impl_.category_.Get(); +} +inline void Datacenter::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* Datacenter::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* Datacenter::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Datacenter.category) + return _impl_.category_.Release(); +} +inline void Datacenter::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Datacenter.category) +} + +// string name = 3; +inline void Datacenter::clear_name() { + _impl_.name_.ClearToEmpty(); +} +inline const std::string& Datacenter::name() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Datacenter.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Datacenter::set_name(ArgT0&& arg0, ArgT... args) { + + _impl_.name_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Datacenter.name) +} +inline std::string* Datacenter::mutable_name() { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Datacenter.name) + return _s; +} +inline const std::string& Datacenter::_internal_name() const { + return _impl_.name_.Get(); +} +inline void Datacenter::_internal_set_name(const std::string& value) { + + _impl_.name_.Set(value, GetArenaForAllocation()); +} +inline std::string* Datacenter::_internal_mutable_name() { + + return _impl_.name_.Mutable(GetArenaForAllocation()); +} +inline std::string* Datacenter::release_name() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Datacenter.name) + return _impl_.name_.Release(); +} +inline void Datacenter::set_allocated_name(std::string* name) { + if (name != nullptr) { + + } else { + + } + _impl_.name_.SetAllocated(name, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Datacenter.name) +} + +// ------------------------------------------------------------------- + +// Version + +// uint32 major_num = 1; +inline void Version::clear_major_num() { + _impl_.major_num_ = 0u; +} +inline uint32_t Version::_internal_major_num() const { + return _impl_.major_num_; +} +inline uint32_t Version::major_num() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Version.major_num) + return _internal_major_num(); +} +inline void Version::_internal_set_major_num(uint32_t value) { + + _impl_.major_num_ = value; +} +inline void Version::set_major_num(uint32_t value) { + _internal_set_major_num(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Version.major_num) +} + +// uint32 minor_num = 2; +inline void Version::clear_minor_num() { + _impl_.minor_num_ = 0u; +} +inline uint32_t Version::_internal_minor_num() const { + return _impl_.minor_num_; +} +inline uint32_t Version::minor_num() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Version.minor_num) + return _internal_minor_num(); +} +inline void Version::_internal_set_minor_num(uint32_t value) { + + _impl_.minor_num_ = value; +} +inline void Version::set_minor_num(uint32_t value) { + _internal_set_minor_num(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Version.minor_num) +} + +// uint32 build_stamp = 3; +inline void Version::clear_build_stamp() { + _impl_.build_stamp_ = 0u; +} +inline uint32_t Version::_internal_build_stamp() const { + return _impl_.build_stamp_; +} +inline uint32_t Version::build_stamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Version.build_stamp) + return _internal_build_stamp(); +} +inline void Version::_internal_set_build_stamp(uint32_t value) { + + _impl_.build_stamp_ = value; +} +inline void Version::set_build_stamp(uint32_t value) { + _internal_set_build_stamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Version.build_stamp) +} + +// string revision = 4; +inline void Version::clear_revision() { + _impl_.revision_.ClearToEmpty(); +} +inline const std::string& Version::revision() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Version.revision) + return _internal_revision(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Version::set_revision(ArgT0&& arg0, ArgT... args) { + + _impl_.revision_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Version.revision) +} +inline std::string* Version::mutable_revision() { + std::string* _s = _internal_mutable_revision(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Version.revision) + return _s; +} +inline const std::string& Version::_internal_revision() const { + return _impl_.revision_.Get(); +} +inline void Version::_internal_set_revision(const std::string& value) { + + _impl_.revision_.Set(value, GetArenaForAllocation()); +} +inline std::string* Version::_internal_mutable_revision() { + + return _impl_.revision_.Mutable(GetArenaForAllocation()); +} +inline std::string* Version::release_revision() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Version.revision) + return _impl_.revision_.Release(); +} +inline void Version::set_allocated_revision(std::string* revision) { + if (revision != nullptr) { + + } else { + + } + _impl_.revision_.SetAllocated(revision, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.revision_.IsDefault()) { + _impl_.revision_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Version.revision) +} + +// ------------------------------------------------------------------- + +// InventoryItem + +// int32 quantity = 1; +inline void InventoryItem::clear_quantity() { + _impl_.quantity_ = 0; +} +inline int32_t InventoryItem::_internal_quantity() const { + return _impl_.quantity_; +} +inline int32_t InventoryItem::quantity() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryItem.quantity) + return _internal_quantity(); +} +inline void InventoryItem::_internal_set_quantity(int32_t value) { + + _impl_.quantity_ = value; +} +inline void InventoryItem::set_quantity(int32_t value) { + _internal_set_quantity(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryItem.quantity) +} + +// string item = 2; +inline void InventoryItem::clear_item() { + _impl_.item_.ClearToEmpty(); +} +inline const std::string& InventoryItem::item() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryItem.item) + return _internal_item(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void InventoryItem::set_item(ArgT0&& arg0, ArgT... args) { + + _impl_.item_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryItem.item) +} +inline std::string* InventoryItem::mutable_item() { + std::string* _s = _internal_mutable_item(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryItem.item) + return _s; +} +inline const std::string& InventoryItem::_internal_item() const { + return _impl_.item_.Get(); +} +inline void InventoryItem::_internal_set_item(const std::string& value) { + + _impl_.item_.Set(value, GetArenaForAllocation()); +} +inline std::string* InventoryItem::_internal_mutable_item() { + + return _impl_.item_.Mutable(GetArenaForAllocation()); +} +inline std::string* InventoryItem::release_item() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryItem.item) + return _impl_.item_.Release(); +} +inline void InventoryItem::set_allocated_item(std::string* item) { + if (item != nullptr) { + + } else { + + } + _impl_.item_.SetAllocated(item, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.item_.IsDefault()) { + _impl_.item_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryItem.item) +} + +// string extraData = 3; +inline void InventoryItem::clear_extradata() { + _impl_.extradata_.ClearToEmpty(); +} +inline const std::string& InventoryItem::extradata() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryItem.extraData) + return _internal_extradata(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void InventoryItem::set_extradata(ArgT0&& arg0, ArgT... args) { + + _impl_.extradata_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryItem.extraData) +} +inline std::string* InventoryItem::mutable_extradata() { + std::string* _s = _internal_mutable_extradata(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryItem.extraData) + return _s; +} +inline const std::string& InventoryItem::_internal_extradata() const { + return _impl_.extradata_.Get(); +} +inline void InventoryItem::_internal_set_extradata(const std::string& value) { + + _impl_.extradata_.Set(value, GetArenaForAllocation()); +} +inline std::string* InventoryItem::_internal_mutable_extradata() { + + return _impl_.extradata_.Mutable(GetArenaForAllocation()); +} +inline std::string* InventoryItem::release_extradata() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryItem.extraData) + return _impl_.extradata_.Release(); +} +inline void InventoryItem::set_allocated_extradata(std::string* extradata) { + if (extradata != nullptr) { + + } else { + + } + _impl_.extradata_.SetAllocated(extradata, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.extradata_.IsDefault()) { + _impl_.extradata_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryItem.extraData) +} + +// ------------------------------------------------------------------- + +// LoadoutConfiguration + +// repeated .rtech.liveapi.InventoryItem weapons = 1; +inline int LoadoutConfiguration::_internal_weapons_size() const { + return _impl_.weapons_.size(); +} +inline int LoadoutConfiguration::weapons_size() const { + return _internal_weapons_size(); +} +inline void LoadoutConfiguration::clear_weapons() { + _impl_.weapons_.Clear(); +} +inline ::rtech::liveapi::InventoryItem* LoadoutConfiguration::mutable_weapons(int index) { + // @@protoc_insertion_point(field_mutable:rtech.liveapi.LoadoutConfiguration.weapons) + return _impl_.weapons_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::InventoryItem >* +LoadoutConfiguration::mutable_weapons() { + // @@protoc_insertion_point(field_mutable_list:rtech.liveapi.LoadoutConfiguration.weapons) + return &_impl_.weapons_; +} +inline const ::rtech::liveapi::InventoryItem& LoadoutConfiguration::_internal_weapons(int index) const { + return _impl_.weapons_.Get(index); +} +inline const ::rtech::liveapi::InventoryItem& LoadoutConfiguration::weapons(int index) const { + // @@protoc_insertion_point(field_get:rtech.liveapi.LoadoutConfiguration.weapons) + return _internal_weapons(index); +} +inline ::rtech::liveapi::InventoryItem* LoadoutConfiguration::_internal_add_weapons() { + return _impl_.weapons_.Add(); +} +inline ::rtech::liveapi::InventoryItem* LoadoutConfiguration::add_weapons() { + ::rtech::liveapi::InventoryItem* _add = _internal_add_weapons(); + // @@protoc_insertion_point(field_add:rtech.liveapi.LoadoutConfiguration.weapons) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::InventoryItem >& +LoadoutConfiguration::weapons() const { + // @@protoc_insertion_point(field_list:rtech.liveapi.LoadoutConfiguration.weapons) + return _impl_.weapons_; +} + +// repeated .rtech.liveapi.InventoryItem equipment = 2; +inline int LoadoutConfiguration::_internal_equipment_size() const { + return _impl_.equipment_.size(); +} +inline int LoadoutConfiguration::equipment_size() const { + return _internal_equipment_size(); +} +inline void LoadoutConfiguration::clear_equipment() { + _impl_.equipment_.Clear(); +} +inline ::rtech::liveapi::InventoryItem* LoadoutConfiguration::mutable_equipment(int index) { + // @@protoc_insertion_point(field_mutable:rtech.liveapi.LoadoutConfiguration.equipment) + return _impl_.equipment_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::InventoryItem >* +LoadoutConfiguration::mutable_equipment() { + // @@protoc_insertion_point(field_mutable_list:rtech.liveapi.LoadoutConfiguration.equipment) + return &_impl_.equipment_; +} +inline const ::rtech::liveapi::InventoryItem& LoadoutConfiguration::_internal_equipment(int index) const { + return _impl_.equipment_.Get(index); +} +inline const ::rtech::liveapi::InventoryItem& LoadoutConfiguration::equipment(int index) const { + // @@protoc_insertion_point(field_get:rtech.liveapi.LoadoutConfiguration.equipment) + return _internal_equipment(index); +} +inline ::rtech::liveapi::InventoryItem* LoadoutConfiguration::_internal_add_equipment() { + return _impl_.equipment_.Add(); +} +inline ::rtech::liveapi::InventoryItem* LoadoutConfiguration::add_equipment() { + ::rtech::liveapi::InventoryItem* _add = _internal_add_equipment(); + // @@protoc_insertion_point(field_add:rtech.liveapi.LoadoutConfiguration.equipment) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::InventoryItem >& +LoadoutConfiguration::equipment() const { + // @@protoc_insertion_point(field_list:rtech.liveapi.LoadoutConfiguration.equipment) + return _impl_.equipment_; +} + +// ------------------------------------------------------------------- + +// Init + +// uint64 timestamp = 1; +inline void Init::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t Init::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t Init::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Init.timestamp) + return _internal_timestamp(); +} +inline void Init::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void Init::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Init.timestamp) +} + +// string category = 2; +inline void Init::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& Init::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Init.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Init::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Init.category) +} +inline std::string* Init::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Init.category) + return _s; +} +inline const std::string& Init::_internal_category() const { + return _impl_.category_.Get(); +} +inline void Init::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* Init::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* Init::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Init.category) + return _impl_.category_.Release(); +} +inline void Init::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Init.category) +} + +// string gameVersion = 3; +inline void Init::clear_gameversion() { + _impl_.gameversion_.ClearToEmpty(); +} +inline const std::string& Init::gameversion() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Init.gameVersion) + return _internal_gameversion(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Init::set_gameversion(ArgT0&& arg0, ArgT... args) { + + _impl_.gameversion_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Init.gameVersion) +} +inline std::string* Init::mutable_gameversion() { + std::string* _s = _internal_mutable_gameversion(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Init.gameVersion) + return _s; +} +inline const std::string& Init::_internal_gameversion() const { + return _impl_.gameversion_.Get(); +} +inline void Init::_internal_set_gameversion(const std::string& value) { + + _impl_.gameversion_.Set(value, GetArenaForAllocation()); +} +inline std::string* Init::_internal_mutable_gameversion() { + + return _impl_.gameversion_.Mutable(GetArenaForAllocation()); +} +inline std::string* Init::release_gameversion() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Init.gameVersion) + return _impl_.gameversion_.Release(); +} +inline void Init::set_allocated_gameversion(std::string* gameversion) { + if (gameversion != nullptr) { + + } else { + + } + _impl_.gameversion_.SetAllocated(gameversion, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.gameversion_.IsDefault()) { + _impl_.gameversion_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Init.gameVersion) +} + +// .rtech.liveapi.Version apiVersion = 4; +inline bool Init::_internal_has_apiversion() const { + return this != internal_default_instance() && _impl_.apiversion_ != nullptr; +} +inline bool Init::has_apiversion() const { + return _internal_has_apiversion(); +} +inline void Init::clear_apiversion() { + if (GetArenaForAllocation() == nullptr && _impl_.apiversion_ != nullptr) { + delete _impl_.apiversion_; + } + _impl_.apiversion_ = nullptr; +} +inline const ::rtech::liveapi::Version& Init::_internal_apiversion() const { + const ::rtech::liveapi::Version* p = _impl_.apiversion_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Version_default_instance_); +} +inline const ::rtech::liveapi::Version& Init::apiversion() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Init.apiVersion) + return _internal_apiversion(); +} +inline void Init::unsafe_arena_set_allocated_apiversion( + ::rtech::liveapi::Version* apiversion) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.apiversion_); + } + _impl_.apiversion_ = apiversion; + if (apiversion) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Init.apiVersion) +} +inline ::rtech::liveapi::Version* Init::release_apiversion() { + + ::rtech::liveapi::Version* temp = _impl_.apiversion_; + _impl_.apiversion_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Version* Init::unsafe_arena_release_apiversion() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Init.apiVersion) + + ::rtech::liveapi::Version* temp = _impl_.apiversion_; + _impl_.apiversion_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Version* Init::_internal_mutable_apiversion() { + + if (_impl_.apiversion_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Version>(GetArenaForAllocation()); + _impl_.apiversion_ = p; + } + return _impl_.apiversion_; +} +inline ::rtech::liveapi::Version* Init::mutable_apiversion() { + ::rtech::liveapi::Version* _msg = _internal_mutable_apiversion(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Init.apiVersion) + return _msg; +} +inline void Init::set_allocated_apiversion(::rtech::liveapi::Version* apiversion) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.apiversion_; + } + if (apiversion) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(apiversion); + if (message_arena != submessage_arena) { + apiversion = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, apiversion, submessage_arena); + } + + } else { + + } + _impl_.apiversion_ = apiversion; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Init.apiVersion) +} + +// string platform = 5; +inline void Init::clear_platform() { + _impl_.platform_.ClearToEmpty(); +} +inline const std::string& Init::platform() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Init.platform) + return _internal_platform(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Init::set_platform(ArgT0&& arg0, ArgT... args) { + + _impl_.platform_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Init.platform) +} +inline std::string* Init::mutable_platform() { + std::string* _s = _internal_mutable_platform(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Init.platform) + return _s; +} +inline const std::string& Init::_internal_platform() const { + return _impl_.platform_.Get(); +} +inline void Init::_internal_set_platform(const std::string& value) { + + _impl_.platform_.Set(value, GetArenaForAllocation()); +} +inline std::string* Init::_internal_mutable_platform() { + + return _impl_.platform_.Mutable(GetArenaForAllocation()); +} +inline std::string* Init::release_platform() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Init.platform) + return _impl_.platform_.Release(); +} +inline void Init::set_allocated_platform(std::string* platform) { + if (platform != nullptr) { + + } else { + + } + _impl_.platform_.SetAllocated(platform, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.platform_.IsDefault()) { + _impl_.platform_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Init.platform) +} + +// string name = 6; +inline void Init::clear_name() { + _impl_.name_.ClearToEmpty(); +} +inline const std::string& Init::name() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Init.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Init::set_name(ArgT0&& arg0, ArgT... args) { + + _impl_.name_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Init.name) +} +inline std::string* Init::mutable_name() { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Init.name) + return _s; +} +inline const std::string& Init::_internal_name() const { + return _impl_.name_.Get(); +} +inline void Init::_internal_set_name(const std::string& value) { + + _impl_.name_.Set(value, GetArenaForAllocation()); +} +inline std::string* Init::_internal_mutable_name() { + + return _impl_.name_.Mutable(GetArenaForAllocation()); +} +inline std::string* Init::release_name() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Init.name) + return _impl_.name_.Release(); +} +inline void Init::set_allocated_name(std::string* name) { + if (name != nullptr) { + + } else { + + } + _impl_.name_.SetAllocated(name, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Init.name) +} + +// ------------------------------------------------------------------- + +// CustomMatch_LobbyPlayers + +// string playerToken = 1; +inline void CustomMatch_LobbyPlayers::clear_playertoken() { + _impl_.playertoken_.ClearToEmpty(); +} +inline const std::string& CustomMatch_LobbyPlayers::playertoken() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_LobbyPlayers.playerToken) + return _internal_playertoken(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_LobbyPlayers::set_playertoken(ArgT0&& arg0, ArgT... args) { + + _impl_.playertoken_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_LobbyPlayers.playerToken) +} +inline std::string* CustomMatch_LobbyPlayers::mutable_playertoken() { + std::string* _s = _internal_mutable_playertoken(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_LobbyPlayers.playerToken) + return _s; +} +inline const std::string& CustomMatch_LobbyPlayers::_internal_playertoken() const { + return _impl_.playertoken_.Get(); +} +inline void CustomMatch_LobbyPlayers::_internal_set_playertoken(const std::string& value) { + + _impl_.playertoken_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_LobbyPlayers::_internal_mutable_playertoken() { + + return _impl_.playertoken_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_LobbyPlayers::release_playertoken() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_LobbyPlayers.playerToken) + return _impl_.playertoken_.Release(); +} +inline void CustomMatch_LobbyPlayers::set_allocated_playertoken(std::string* playertoken) { + if (playertoken != nullptr) { + + } else { + + } + _impl_.playertoken_.SetAllocated(playertoken, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.playertoken_.IsDefault()) { + _impl_.playertoken_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_LobbyPlayers.playerToken) +} + +// repeated .rtech.liveapi.CustomMatch_LobbyPlayer players = 2; +inline int CustomMatch_LobbyPlayers::_internal_players_size() const { + return _impl_.players_.size(); +} +inline int CustomMatch_LobbyPlayers::players_size() const { + return _internal_players_size(); +} +inline void CustomMatch_LobbyPlayers::clear_players() { + _impl_.players_.Clear(); +} +inline ::rtech::liveapi::CustomMatch_LobbyPlayer* CustomMatch_LobbyPlayers::mutable_players(int index) { + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_LobbyPlayers.players) + return _impl_.players_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::CustomMatch_LobbyPlayer >* +CustomMatch_LobbyPlayers::mutable_players() { + // @@protoc_insertion_point(field_mutable_list:rtech.liveapi.CustomMatch_LobbyPlayers.players) + return &_impl_.players_; +} +inline const ::rtech::liveapi::CustomMatch_LobbyPlayer& CustomMatch_LobbyPlayers::_internal_players(int index) const { + return _impl_.players_.Get(index); +} +inline const ::rtech::liveapi::CustomMatch_LobbyPlayer& CustomMatch_LobbyPlayers::players(int index) const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_LobbyPlayers.players) + return _internal_players(index); +} +inline ::rtech::liveapi::CustomMatch_LobbyPlayer* CustomMatch_LobbyPlayers::_internal_add_players() { + return _impl_.players_.Add(); +} +inline ::rtech::liveapi::CustomMatch_LobbyPlayer* CustomMatch_LobbyPlayers::add_players() { + ::rtech::liveapi::CustomMatch_LobbyPlayer* _add = _internal_add_players(); + // @@protoc_insertion_point(field_add:rtech.liveapi.CustomMatch_LobbyPlayers.players) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::CustomMatch_LobbyPlayer >& +CustomMatch_LobbyPlayers::players() const { + // @@protoc_insertion_point(field_list:rtech.liveapi.CustomMatch_LobbyPlayers.players) + return _impl_.players_; +} + +// ------------------------------------------------------------------- + +// ObserverSwitched + +// uint64 timestamp = 1; +inline void ObserverSwitched::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t ObserverSwitched::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t ObserverSwitched::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ObserverSwitched.timestamp) + return _internal_timestamp(); +} +inline void ObserverSwitched::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void ObserverSwitched::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.ObserverSwitched.timestamp) +} + +// string category = 2; +inline void ObserverSwitched::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& ObserverSwitched::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ObserverSwitched.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ObserverSwitched::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.ObserverSwitched.category) +} +inline std::string* ObserverSwitched::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ObserverSwitched.category) + return _s; +} +inline const std::string& ObserverSwitched::_internal_category() const { + return _impl_.category_.Get(); +} +inline void ObserverSwitched::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* ObserverSwitched::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* ObserverSwitched::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ObserverSwitched.category) + return _impl_.category_.Release(); +} +inline void ObserverSwitched::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ObserverSwitched.category) +} + +// .rtech.liveapi.Player observer = 3; +inline bool ObserverSwitched::_internal_has_observer() const { + return this != internal_default_instance() && _impl_.observer_ != nullptr; +} +inline bool ObserverSwitched::has_observer() const { + return _internal_has_observer(); +} +inline void ObserverSwitched::clear_observer() { + if (GetArenaForAllocation() == nullptr && _impl_.observer_ != nullptr) { + delete _impl_.observer_; + } + _impl_.observer_ = nullptr; +} +inline const ::rtech::liveapi::Player& ObserverSwitched::_internal_observer() const { + const ::rtech::liveapi::Player* p = _impl_.observer_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& ObserverSwitched::observer() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ObserverSwitched.observer) + return _internal_observer(); +} +inline void ObserverSwitched::unsafe_arena_set_allocated_observer( + ::rtech::liveapi::Player* observer) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.observer_); + } + _impl_.observer_ = observer; + if (observer) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.ObserverSwitched.observer) +} +inline ::rtech::liveapi::Player* ObserverSwitched::release_observer() { + + ::rtech::liveapi::Player* temp = _impl_.observer_; + _impl_.observer_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* ObserverSwitched::unsafe_arena_release_observer() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ObserverSwitched.observer) + + ::rtech::liveapi::Player* temp = _impl_.observer_; + _impl_.observer_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* ObserverSwitched::_internal_mutable_observer() { + + if (_impl_.observer_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.observer_ = p; + } + return _impl_.observer_; +} +inline ::rtech::liveapi::Player* ObserverSwitched::mutable_observer() { + ::rtech::liveapi::Player* _msg = _internal_mutable_observer(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ObserverSwitched.observer) + return _msg; +} +inline void ObserverSwitched::set_allocated_observer(::rtech::liveapi::Player* observer) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.observer_; + } + if (observer) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(observer); + if (message_arena != submessage_arena) { + observer = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, observer, submessage_arena); + } + + } else { + + } + _impl_.observer_ = observer; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ObserverSwitched.observer) +} + +// .rtech.liveapi.Player target = 4; +inline bool ObserverSwitched::_internal_has_target() const { + return this != internal_default_instance() && _impl_.target_ != nullptr; +} +inline bool ObserverSwitched::has_target() const { + return _internal_has_target(); +} +inline void ObserverSwitched::clear_target() { + if (GetArenaForAllocation() == nullptr && _impl_.target_ != nullptr) { + delete _impl_.target_; + } + _impl_.target_ = nullptr; +} +inline const ::rtech::liveapi::Player& ObserverSwitched::_internal_target() const { + const ::rtech::liveapi::Player* p = _impl_.target_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& ObserverSwitched::target() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ObserverSwitched.target) + return _internal_target(); +} +inline void ObserverSwitched::unsafe_arena_set_allocated_target( + ::rtech::liveapi::Player* target) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.target_); + } + _impl_.target_ = target; + if (target) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.ObserverSwitched.target) +} +inline ::rtech::liveapi::Player* ObserverSwitched::release_target() { + + ::rtech::liveapi::Player* temp = _impl_.target_; + _impl_.target_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* ObserverSwitched::unsafe_arena_release_target() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ObserverSwitched.target) + + ::rtech::liveapi::Player* temp = _impl_.target_; + _impl_.target_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* ObserverSwitched::_internal_mutable_target() { + + if (_impl_.target_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.target_ = p; + } + return _impl_.target_; +} +inline ::rtech::liveapi::Player* ObserverSwitched::mutable_target() { + ::rtech::liveapi::Player* _msg = _internal_mutable_target(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ObserverSwitched.target) + return _msg; +} +inline void ObserverSwitched::set_allocated_target(::rtech::liveapi::Player* target) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.target_; + } + if (target) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(target); + if (message_arena != submessage_arena) { + target = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, target, submessage_arena); + } + + } else { + + } + _impl_.target_ = target; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ObserverSwitched.target) +} + +// repeated .rtech.liveapi.Player targetTeam = 5; +inline int ObserverSwitched::_internal_targetteam_size() const { + return _impl_.targetteam_.size(); +} +inline int ObserverSwitched::targetteam_size() const { + return _internal_targetteam_size(); +} +inline void ObserverSwitched::clear_targetteam() { + _impl_.targetteam_.Clear(); +} +inline ::rtech::liveapi::Player* ObserverSwitched::mutable_targetteam(int index) { + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ObserverSwitched.targetTeam) + return _impl_.targetteam_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >* +ObserverSwitched::mutable_targetteam() { + // @@protoc_insertion_point(field_mutable_list:rtech.liveapi.ObserverSwitched.targetTeam) + return &_impl_.targetteam_; +} +inline const ::rtech::liveapi::Player& ObserverSwitched::_internal_targetteam(int index) const { + return _impl_.targetteam_.Get(index); +} +inline const ::rtech::liveapi::Player& ObserverSwitched::targetteam(int index) const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ObserverSwitched.targetTeam) + return _internal_targetteam(index); +} +inline ::rtech::liveapi::Player* ObserverSwitched::_internal_add_targetteam() { + return _impl_.targetteam_.Add(); +} +inline ::rtech::liveapi::Player* ObserverSwitched::add_targetteam() { + ::rtech::liveapi::Player* _add = _internal_add_targetteam(); + // @@protoc_insertion_point(field_add:rtech.liveapi.ObserverSwitched.targetTeam) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >& +ObserverSwitched::targetteam() const { + // @@protoc_insertion_point(field_list:rtech.liveapi.ObserverSwitched.targetTeam) + return _impl_.targetteam_; +} + +// ------------------------------------------------------------------- + +// ObserverAnnotation + +// uint64 timestamp = 1; +inline void ObserverAnnotation::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t ObserverAnnotation::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t ObserverAnnotation::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ObserverAnnotation.timestamp) + return _internal_timestamp(); +} +inline void ObserverAnnotation::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void ObserverAnnotation::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.ObserverAnnotation.timestamp) +} + +// string category = 2; +inline void ObserverAnnotation::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& ObserverAnnotation::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ObserverAnnotation.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ObserverAnnotation::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.ObserverAnnotation.category) +} +inline std::string* ObserverAnnotation::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ObserverAnnotation.category) + return _s; +} +inline const std::string& ObserverAnnotation::_internal_category() const { + return _impl_.category_.Get(); +} +inline void ObserverAnnotation::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* ObserverAnnotation::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* ObserverAnnotation::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ObserverAnnotation.category) + return _impl_.category_.Release(); +} +inline void ObserverAnnotation::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ObserverAnnotation.category) +} + +// int32 annotationSerial = 3; +inline void ObserverAnnotation::clear_annotationserial() { + _impl_.annotationserial_ = 0; +} +inline int32_t ObserverAnnotation::_internal_annotationserial() const { + return _impl_.annotationserial_; +} +inline int32_t ObserverAnnotation::annotationserial() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ObserverAnnotation.annotationSerial) + return _internal_annotationserial(); +} +inline void ObserverAnnotation::_internal_set_annotationserial(int32_t value) { + + _impl_.annotationserial_ = value; +} +inline void ObserverAnnotation::set_annotationserial(int32_t value) { + _internal_set_annotationserial(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.ObserverAnnotation.annotationSerial) +} + +// ------------------------------------------------------------------- + +// MatchSetup + +// uint64 timestamp = 1; +inline void MatchSetup::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t MatchSetup::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t MatchSetup::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchSetup.timestamp) + return _internal_timestamp(); +} +inline void MatchSetup::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void MatchSetup::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchSetup.timestamp) +} + +// string category = 2; +inline void MatchSetup::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& MatchSetup::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchSetup.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void MatchSetup::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchSetup.category) +} +inline std::string* MatchSetup::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.MatchSetup.category) + return _s; +} +inline const std::string& MatchSetup::_internal_category() const { + return _impl_.category_.Get(); +} +inline void MatchSetup::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* MatchSetup::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* MatchSetup::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.MatchSetup.category) + return _impl_.category_.Release(); +} +inline void MatchSetup::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.MatchSetup.category) +} + +// string map = 3; +inline void MatchSetup::clear_map() { + _impl_.map_.ClearToEmpty(); +} +inline const std::string& MatchSetup::map() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchSetup.map) + return _internal_map(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void MatchSetup::set_map(ArgT0&& arg0, ArgT... args) { + + _impl_.map_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchSetup.map) +} +inline std::string* MatchSetup::mutable_map() { + std::string* _s = _internal_mutable_map(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.MatchSetup.map) + return _s; +} +inline const std::string& MatchSetup::_internal_map() const { + return _impl_.map_.Get(); +} +inline void MatchSetup::_internal_set_map(const std::string& value) { + + _impl_.map_.Set(value, GetArenaForAllocation()); +} +inline std::string* MatchSetup::_internal_mutable_map() { + + return _impl_.map_.Mutable(GetArenaForAllocation()); +} +inline std::string* MatchSetup::release_map() { + // @@protoc_insertion_point(field_release:rtech.liveapi.MatchSetup.map) + return _impl_.map_.Release(); +} +inline void MatchSetup::set_allocated_map(std::string* map) { + if (map != nullptr) { + + } else { + + } + _impl_.map_.SetAllocated(map, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.map_.IsDefault()) { + _impl_.map_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.MatchSetup.map) +} + +// string playlistName = 4; +inline void MatchSetup::clear_playlistname() { + _impl_.playlistname_.ClearToEmpty(); +} +inline const std::string& MatchSetup::playlistname() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchSetup.playlistName) + return _internal_playlistname(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void MatchSetup::set_playlistname(ArgT0&& arg0, ArgT... args) { + + _impl_.playlistname_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchSetup.playlistName) +} +inline std::string* MatchSetup::mutable_playlistname() { + std::string* _s = _internal_mutable_playlistname(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.MatchSetup.playlistName) + return _s; +} +inline const std::string& MatchSetup::_internal_playlistname() const { + return _impl_.playlistname_.Get(); +} +inline void MatchSetup::_internal_set_playlistname(const std::string& value) { + + _impl_.playlistname_.Set(value, GetArenaForAllocation()); +} +inline std::string* MatchSetup::_internal_mutable_playlistname() { + + return _impl_.playlistname_.Mutable(GetArenaForAllocation()); +} +inline std::string* MatchSetup::release_playlistname() { + // @@protoc_insertion_point(field_release:rtech.liveapi.MatchSetup.playlistName) + return _impl_.playlistname_.Release(); +} +inline void MatchSetup::set_allocated_playlistname(std::string* playlistname) { + if (playlistname != nullptr) { + + } else { + + } + _impl_.playlistname_.SetAllocated(playlistname, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.playlistname_.IsDefault()) { + _impl_.playlistname_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.MatchSetup.playlistName) +} + +// string playlistDesc = 5; +inline void MatchSetup::clear_playlistdesc() { + _impl_.playlistdesc_.ClearToEmpty(); +} +inline const std::string& MatchSetup::playlistdesc() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchSetup.playlistDesc) + return _internal_playlistdesc(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void MatchSetup::set_playlistdesc(ArgT0&& arg0, ArgT... args) { + + _impl_.playlistdesc_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchSetup.playlistDesc) +} +inline std::string* MatchSetup::mutable_playlistdesc() { + std::string* _s = _internal_mutable_playlistdesc(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.MatchSetup.playlistDesc) + return _s; +} +inline const std::string& MatchSetup::_internal_playlistdesc() const { + return _impl_.playlistdesc_.Get(); +} +inline void MatchSetup::_internal_set_playlistdesc(const std::string& value) { + + _impl_.playlistdesc_.Set(value, GetArenaForAllocation()); +} +inline std::string* MatchSetup::_internal_mutable_playlistdesc() { + + return _impl_.playlistdesc_.Mutable(GetArenaForAllocation()); +} +inline std::string* MatchSetup::release_playlistdesc() { + // @@protoc_insertion_point(field_release:rtech.liveapi.MatchSetup.playlistDesc) + return _impl_.playlistdesc_.Release(); +} +inline void MatchSetup::set_allocated_playlistdesc(std::string* playlistdesc) { + if (playlistdesc != nullptr) { + + } else { + + } + _impl_.playlistdesc_.SetAllocated(playlistdesc, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.playlistdesc_.IsDefault()) { + _impl_.playlistdesc_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.MatchSetup.playlistDesc) +} + +// .rtech.liveapi.Datacenter datacenter = 6; +inline bool MatchSetup::_internal_has_datacenter() const { + return this != internal_default_instance() && _impl_.datacenter_ != nullptr; +} +inline bool MatchSetup::has_datacenter() const { + return _internal_has_datacenter(); +} +inline void MatchSetup::clear_datacenter() { + if (GetArenaForAllocation() == nullptr && _impl_.datacenter_ != nullptr) { + delete _impl_.datacenter_; + } + _impl_.datacenter_ = nullptr; +} +inline const ::rtech::liveapi::Datacenter& MatchSetup::_internal_datacenter() const { + const ::rtech::liveapi::Datacenter* p = _impl_.datacenter_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Datacenter_default_instance_); +} +inline const ::rtech::liveapi::Datacenter& MatchSetup::datacenter() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchSetup.datacenter) + return _internal_datacenter(); +} +inline void MatchSetup::unsafe_arena_set_allocated_datacenter( + ::rtech::liveapi::Datacenter* datacenter) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.datacenter_); + } + _impl_.datacenter_ = datacenter; + if (datacenter) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.MatchSetup.datacenter) +} +inline ::rtech::liveapi::Datacenter* MatchSetup::release_datacenter() { + + ::rtech::liveapi::Datacenter* temp = _impl_.datacenter_; + _impl_.datacenter_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Datacenter* MatchSetup::unsafe_arena_release_datacenter() { + // @@protoc_insertion_point(field_release:rtech.liveapi.MatchSetup.datacenter) + + ::rtech::liveapi::Datacenter* temp = _impl_.datacenter_; + _impl_.datacenter_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Datacenter* MatchSetup::_internal_mutable_datacenter() { + + if (_impl_.datacenter_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Datacenter>(GetArenaForAllocation()); + _impl_.datacenter_ = p; + } + return _impl_.datacenter_; +} +inline ::rtech::liveapi::Datacenter* MatchSetup::mutable_datacenter() { + ::rtech::liveapi::Datacenter* _msg = _internal_mutable_datacenter(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.MatchSetup.datacenter) + return _msg; +} +inline void MatchSetup::set_allocated_datacenter(::rtech::liveapi::Datacenter* datacenter) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.datacenter_; + } + if (datacenter) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(datacenter); + if (message_arena != submessage_arena) { + datacenter = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, datacenter, submessage_arena); + } + + } else { + + } + _impl_.datacenter_ = datacenter; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.MatchSetup.datacenter) +} + +// bool aimAssistOn = 7; +inline void MatchSetup::clear_aimassiston() { + _impl_.aimassiston_ = false; +} +inline bool MatchSetup::_internal_aimassiston() const { + return _impl_.aimassiston_; +} +inline bool MatchSetup::aimassiston() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchSetup.aimAssistOn) + return _internal_aimassiston(); +} +inline void MatchSetup::_internal_set_aimassiston(bool value) { + + _impl_.aimassiston_ = value; +} +inline void MatchSetup::set_aimassiston(bool value) { + _internal_set_aimassiston(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchSetup.aimAssistOn) +} + +// bool anonymousMode = 8; +inline void MatchSetup::clear_anonymousmode() { + _impl_.anonymousmode_ = false; +} +inline bool MatchSetup::_internal_anonymousmode() const { + return _impl_.anonymousmode_; +} +inline bool MatchSetup::anonymousmode() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchSetup.anonymousMode) + return _internal_anonymousmode(); +} +inline void MatchSetup::_internal_set_anonymousmode(bool value) { + + _impl_.anonymousmode_ = value; +} +inline void MatchSetup::set_anonymousmode(bool value) { + _internal_set_anonymousmode(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchSetup.anonymousMode) +} + +// string serverId = 9; +inline void MatchSetup::clear_serverid() { + _impl_.serverid_.ClearToEmpty(); +} +inline const std::string& MatchSetup::serverid() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchSetup.serverId) + return _internal_serverid(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void MatchSetup::set_serverid(ArgT0&& arg0, ArgT... args) { + + _impl_.serverid_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchSetup.serverId) +} +inline std::string* MatchSetup::mutable_serverid() { + std::string* _s = _internal_mutable_serverid(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.MatchSetup.serverId) + return _s; +} +inline const std::string& MatchSetup::_internal_serverid() const { + return _impl_.serverid_.Get(); +} +inline void MatchSetup::_internal_set_serverid(const std::string& value) { + + _impl_.serverid_.Set(value, GetArenaForAllocation()); +} +inline std::string* MatchSetup::_internal_mutable_serverid() { + + return _impl_.serverid_.Mutable(GetArenaForAllocation()); +} +inline std::string* MatchSetup::release_serverid() { + // @@protoc_insertion_point(field_release:rtech.liveapi.MatchSetup.serverId) + return _impl_.serverid_.Release(); +} +inline void MatchSetup::set_allocated_serverid(std::string* serverid) { + if (serverid != nullptr) { + + } else { + + } + _impl_.serverid_.SetAllocated(serverid, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.serverid_.IsDefault()) { + _impl_.serverid_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.MatchSetup.serverId) +} + +// .rtech.liveapi.LoadoutConfiguration startingLoadout = 10; +inline bool MatchSetup::_internal_has_startingloadout() const { + return this != internal_default_instance() && _impl_.startingloadout_ != nullptr; +} +inline bool MatchSetup::has_startingloadout() const { + return _internal_has_startingloadout(); +} +inline void MatchSetup::clear_startingloadout() { + if (GetArenaForAllocation() == nullptr && _impl_.startingloadout_ != nullptr) { + delete _impl_.startingloadout_; + } + _impl_.startingloadout_ = nullptr; +} +inline const ::rtech::liveapi::LoadoutConfiguration& MatchSetup::_internal_startingloadout() const { + const ::rtech::liveapi::LoadoutConfiguration* p = _impl_.startingloadout_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_LoadoutConfiguration_default_instance_); +} +inline const ::rtech::liveapi::LoadoutConfiguration& MatchSetup::startingloadout() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchSetup.startingLoadout) + return _internal_startingloadout(); +} +inline void MatchSetup::unsafe_arena_set_allocated_startingloadout( + ::rtech::liveapi::LoadoutConfiguration* startingloadout) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.startingloadout_); + } + _impl_.startingloadout_ = startingloadout; + if (startingloadout) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.MatchSetup.startingLoadout) +} +inline ::rtech::liveapi::LoadoutConfiguration* MatchSetup::release_startingloadout() { + + ::rtech::liveapi::LoadoutConfiguration* temp = _impl_.startingloadout_; + _impl_.startingloadout_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::LoadoutConfiguration* MatchSetup::unsafe_arena_release_startingloadout() { + // @@protoc_insertion_point(field_release:rtech.liveapi.MatchSetup.startingLoadout) + + ::rtech::liveapi::LoadoutConfiguration* temp = _impl_.startingloadout_; + _impl_.startingloadout_ = nullptr; + return temp; +} +inline ::rtech::liveapi::LoadoutConfiguration* MatchSetup::_internal_mutable_startingloadout() { + + if (_impl_.startingloadout_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::LoadoutConfiguration>(GetArenaForAllocation()); + _impl_.startingloadout_ = p; + } + return _impl_.startingloadout_; +} +inline ::rtech::liveapi::LoadoutConfiguration* MatchSetup::mutable_startingloadout() { + ::rtech::liveapi::LoadoutConfiguration* _msg = _internal_mutable_startingloadout(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.MatchSetup.startingLoadout) + return _msg; +} +inline void MatchSetup::set_allocated_startingloadout(::rtech::liveapi::LoadoutConfiguration* startingloadout) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.startingloadout_; + } + if (startingloadout) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(startingloadout); + if (message_arena != submessage_arena) { + startingloadout = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, startingloadout, submessage_arena); + } + + } else { + + } + _impl_.startingloadout_ = startingloadout; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.MatchSetup.startingLoadout) +} + +// ------------------------------------------------------------------- + +// GameStateChanged + +// uint64 timestamp = 1; +inline void GameStateChanged::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t GameStateChanged::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t GameStateChanged::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GameStateChanged.timestamp) + return _internal_timestamp(); +} +inline void GameStateChanged::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void GameStateChanged::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.GameStateChanged.timestamp) +} + +// string category = 2; +inline void GameStateChanged::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& GameStateChanged::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GameStateChanged.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void GameStateChanged::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.GameStateChanged.category) +} +inline std::string* GameStateChanged::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.GameStateChanged.category) + return _s; +} +inline const std::string& GameStateChanged::_internal_category() const { + return _impl_.category_.Get(); +} +inline void GameStateChanged::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* GameStateChanged::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* GameStateChanged::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.GameStateChanged.category) + return _impl_.category_.Release(); +} +inline void GameStateChanged::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.GameStateChanged.category) +} + +// string state = 3; +inline void GameStateChanged::clear_state() { + _impl_.state_.ClearToEmpty(); +} +inline const std::string& GameStateChanged::state() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GameStateChanged.state) + return _internal_state(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void GameStateChanged::set_state(ArgT0&& arg0, ArgT... args) { + + _impl_.state_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.GameStateChanged.state) +} +inline std::string* GameStateChanged::mutable_state() { + std::string* _s = _internal_mutable_state(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.GameStateChanged.state) + return _s; +} +inline const std::string& GameStateChanged::_internal_state() const { + return _impl_.state_.Get(); +} +inline void GameStateChanged::_internal_set_state(const std::string& value) { + + _impl_.state_.Set(value, GetArenaForAllocation()); +} +inline std::string* GameStateChanged::_internal_mutable_state() { + + return _impl_.state_.Mutable(GetArenaForAllocation()); +} +inline std::string* GameStateChanged::release_state() { + // @@protoc_insertion_point(field_release:rtech.liveapi.GameStateChanged.state) + return _impl_.state_.Release(); +} +inline void GameStateChanged::set_allocated_state(std::string* state) { + if (state != nullptr) { + + } else { + + } + _impl_.state_.SetAllocated(state, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.state_.IsDefault()) { + _impl_.state_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.GameStateChanged.state) +} + +// ------------------------------------------------------------------- + +// CharacterSelected + +// uint64 timestamp = 1; +inline void CharacterSelected::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t CharacterSelected::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t CharacterSelected::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CharacterSelected.timestamp) + return _internal_timestamp(); +} +inline void CharacterSelected::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void CharacterSelected::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CharacterSelected.timestamp) +} + +// string category = 2; +inline void CharacterSelected::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& CharacterSelected::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CharacterSelected.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CharacterSelected::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CharacterSelected.category) +} +inline std::string* CharacterSelected::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CharacterSelected.category) + return _s; +} +inline const std::string& CharacterSelected::_internal_category() const { + return _impl_.category_.Get(); +} +inline void CharacterSelected::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* CharacterSelected::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* CharacterSelected::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CharacterSelected.category) + return _impl_.category_.Release(); +} +inline void CharacterSelected::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CharacterSelected.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool CharacterSelected::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool CharacterSelected::has_player() const { + return _internal_has_player(); +} +inline void CharacterSelected::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& CharacterSelected::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& CharacterSelected::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CharacterSelected.player) + return _internal_player(); +} +inline void CharacterSelected::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.CharacterSelected.player) +} +inline ::rtech::liveapi::Player* CharacterSelected::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* CharacterSelected::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CharacterSelected.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* CharacterSelected::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* CharacterSelected::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CharacterSelected.player) + return _msg; +} +inline void CharacterSelected::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CharacterSelected.player) +} + +// ------------------------------------------------------------------- + +// MatchStateEnd + +// uint64 timestamp = 1; +inline void MatchStateEnd::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t MatchStateEnd::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t MatchStateEnd::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchStateEnd.timestamp) + return _internal_timestamp(); +} +inline void MatchStateEnd::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void MatchStateEnd::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchStateEnd.timestamp) +} + +// string category = 2; +inline void MatchStateEnd::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& MatchStateEnd::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchStateEnd.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void MatchStateEnd::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchStateEnd.category) +} +inline std::string* MatchStateEnd::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.MatchStateEnd.category) + return _s; +} +inline const std::string& MatchStateEnd::_internal_category() const { + return _impl_.category_.Get(); +} +inline void MatchStateEnd::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* MatchStateEnd::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* MatchStateEnd::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.MatchStateEnd.category) + return _impl_.category_.Release(); +} +inline void MatchStateEnd::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.MatchStateEnd.category) +} + +// string state = 3; +inline void MatchStateEnd::clear_state() { + _impl_.state_.ClearToEmpty(); +} +inline const std::string& MatchStateEnd::state() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchStateEnd.state) + return _internal_state(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void MatchStateEnd::set_state(ArgT0&& arg0, ArgT... args) { + + _impl_.state_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.MatchStateEnd.state) +} +inline std::string* MatchStateEnd::mutable_state() { + std::string* _s = _internal_mutable_state(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.MatchStateEnd.state) + return _s; +} +inline const std::string& MatchStateEnd::_internal_state() const { + return _impl_.state_.Get(); +} +inline void MatchStateEnd::_internal_set_state(const std::string& value) { + + _impl_.state_.Set(value, GetArenaForAllocation()); +} +inline std::string* MatchStateEnd::_internal_mutable_state() { + + return _impl_.state_.Mutable(GetArenaForAllocation()); +} +inline std::string* MatchStateEnd::release_state() { + // @@protoc_insertion_point(field_release:rtech.liveapi.MatchStateEnd.state) + return _impl_.state_.Release(); +} +inline void MatchStateEnd::set_allocated_state(std::string* state) { + if (state != nullptr) { + + } else { + + } + _impl_.state_.SetAllocated(state, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.state_.IsDefault()) { + _impl_.state_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.MatchStateEnd.state) +} + +// repeated .rtech.liveapi.Player winners = 4; +inline int MatchStateEnd::_internal_winners_size() const { + return _impl_.winners_.size(); +} +inline int MatchStateEnd::winners_size() const { + return _internal_winners_size(); +} +inline void MatchStateEnd::clear_winners() { + _impl_.winners_.Clear(); +} +inline ::rtech::liveapi::Player* MatchStateEnd::mutable_winners(int index) { + // @@protoc_insertion_point(field_mutable:rtech.liveapi.MatchStateEnd.winners) + return _impl_.winners_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >* +MatchStateEnd::mutable_winners() { + // @@protoc_insertion_point(field_mutable_list:rtech.liveapi.MatchStateEnd.winners) + return &_impl_.winners_; +} +inline const ::rtech::liveapi::Player& MatchStateEnd::_internal_winners(int index) const { + return _impl_.winners_.Get(index); +} +inline const ::rtech::liveapi::Player& MatchStateEnd::winners(int index) const { + // @@protoc_insertion_point(field_get:rtech.liveapi.MatchStateEnd.winners) + return _internal_winners(index); +} +inline ::rtech::liveapi::Player* MatchStateEnd::_internal_add_winners() { + return _impl_.winners_.Add(); +} +inline ::rtech::liveapi::Player* MatchStateEnd::add_winners() { + ::rtech::liveapi::Player* _add = _internal_add_winners(); + // @@protoc_insertion_point(field_add:rtech.liveapi.MatchStateEnd.winners) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >& +MatchStateEnd::winners() const { + // @@protoc_insertion_point(field_list:rtech.liveapi.MatchStateEnd.winners) + return _impl_.winners_; +} + +// ------------------------------------------------------------------- + +// RingStartClosing + +// uint64 timestamp = 1; +inline void RingStartClosing::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t RingStartClosing::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t RingStartClosing::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingStartClosing.timestamp) + return _internal_timestamp(); +} +inline void RingStartClosing::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void RingStartClosing::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingStartClosing.timestamp) +} + +// string category = 2; +inline void RingStartClosing::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& RingStartClosing::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingStartClosing.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void RingStartClosing::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingStartClosing.category) +} +inline std::string* RingStartClosing::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.RingStartClosing.category) + return _s; +} +inline const std::string& RingStartClosing::_internal_category() const { + return _impl_.category_.Get(); +} +inline void RingStartClosing::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* RingStartClosing::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* RingStartClosing::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.RingStartClosing.category) + return _impl_.category_.Release(); +} +inline void RingStartClosing::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.RingStartClosing.category) +} + +// uint32 stage = 3; +inline void RingStartClosing::clear_stage() { + _impl_.stage_ = 0u; +} +inline uint32_t RingStartClosing::_internal_stage() const { + return _impl_.stage_; +} +inline uint32_t RingStartClosing::stage() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingStartClosing.stage) + return _internal_stage(); +} +inline void RingStartClosing::_internal_set_stage(uint32_t value) { + + _impl_.stage_ = value; +} +inline void RingStartClosing::set_stage(uint32_t value) { + _internal_set_stage(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingStartClosing.stage) +} + +// .rtech.liveapi.Vector3 center = 4; +inline bool RingStartClosing::_internal_has_center() const { + return this != internal_default_instance() && _impl_.center_ != nullptr; +} +inline bool RingStartClosing::has_center() const { + return _internal_has_center(); +} +inline void RingStartClosing::clear_center() { + if (GetArenaForAllocation() == nullptr && _impl_.center_ != nullptr) { + delete _impl_.center_; + } + _impl_.center_ = nullptr; +} +inline const ::rtech::liveapi::Vector3& RingStartClosing::_internal_center() const { + const ::rtech::liveapi::Vector3* p = _impl_.center_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Vector3_default_instance_); +} +inline const ::rtech::liveapi::Vector3& RingStartClosing::center() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingStartClosing.center) + return _internal_center(); +} +inline void RingStartClosing::unsafe_arena_set_allocated_center( + ::rtech::liveapi::Vector3* center) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.center_); + } + _impl_.center_ = center; + if (center) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.RingStartClosing.center) +} +inline ::rtech::liveapi::Vector3* RingStartClosing::release_center() { + + ::rtech::liveapi::Vector3* temp = _impl_.center_; + _impl_.center_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Vector3* RingStartClosing::unsafe_arena_release_center() { + // @@protoc_insertion_point(field_release:rtech.liveapi.RingStartClosing.center) + + ::rtech::liveapi::Vector3* temp = _impl_.center_; + _impl_.center_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Vector3* RingStartClosing::_internal_mutable_center() { + + if (_impl_.center_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Vector3>(GetArenaForAllocation()); + _impl_.center_ = p; + } + return _impl_.center_; +} +inline ::rtech::liveapi::Vector3* RingStartClosing::mutable_center() { + ::rtech::liveapi::Vector3* _msg = _internal_mutable_center(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.RingStartClosing.center) + return _msg; +} +inline void RingStartClosing::set_allocated_center(::rtech::liveapi::Vector3* center) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.center_; + } + if (center) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(center); + if (message_arena != submessage_arena) { + center = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, center, submessage_arena); + } + + } else { + + } + _impl_.center_ = center; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.RingStartClosing.center) +} + +// float currentRadius = 5; +inline void RingStartClosing::clear_currentradius() { + _impl_.currentradius_ = 0; +} +inline float RingStartClosing::_internal_currentradius() const { + return _impl_.currentradius_; +} +inline float RingStartClosing::currentradius() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingStartClosing.currentRadius) + return _internal_currentradius(); +} +inline void RingStartClosing::_internal_set_currentradius(float value) { + + _impl_.currentradius_ = value; +} +inline void RingStartClosing::set_currentradius(float value) { + _internal_set_currentradius(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingStartClosing.currentRadius) +} + +// float endRadius = 6; +inline void RingStartClosing::clear_endradius() { + _impl_.endradius_ = 0; +} +inline float RingStartClosing::_internal_endradius() const { + return _impl_.endradius_; +} +inline float RingStartClosing::endradius() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingStartClosing.endRadius) + return _internal_endradius(); +} +inline void RingStartClosing::_internal_set_endradius(float value) { + + _impl_.endradius_ = value; +} +inline void RingStartClosing::set_endradius(float value) { + _internal_set_endradius(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingStartClosing.endRadius) +} + +// float shrinkDuration = 7; +inline void RingStartClosing::clear_shrinkduration() { + _impl_.shrinkduration_ = 0; +} +inline float RingStartClosing::_internal_shrinkduration() const { + return _impl_.shrinkduration_; +} +inline float RingStartClosing::shrinkduration() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingStartClosing.shrinkDuration) + return _internal_shrinkduration(); +} +inline void RingStartClosing::_internal_set_shrinkduration(float value) { + + _impl_.shrinkduration_ = value; +} +inline void RingStartClosing::set_shrinkduration(float value) { + _internal_set_shrinkduration(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingStartClosing.shrinkDuration) +} + +// ------------------------------------------------------------------- + +// RingFinishedClosing + +// uint64 timestamp = 1; +inline void RingFinishedClosing::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t RingFinishedClosing::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t RingFinishedClosing::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingFinishedClosing.timestamp) + return _internal_timestamp(); +} +inline void RingFinishedClosing::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void RingFinishedClosing::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingFinishedClosing.timestamp) +} + +// string category = 2; +inline void RingFinishedClosing::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& RingFinishedClosing::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingFinishedClosing.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void RingFinishedClosing::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingFinishedClosing.category) +} +inline std::string* RingFinishedClosing::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.RingFinishedClosing.category) + return _s; +} +inline const std::string& RingFinishedClosing::_internal_category() const { + return _impl_.category_.Get(); +} +inline void RingFinishedClosing::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* RingFinishedClosing::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* RingFinishedClosing::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.RingFinishedClosing.category) + return _impl_.category_.Release(); +} +inline void RingFinishedClosing::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.RingFinishedClosing.category) +} + +// uint32 stage = 3; +inline void RingFinishedClosing::clear_stage() { + _impl_.stage_ = 0u; +} +inline uint32_t RingFinishedClosing::_internal_stage() const { + return _impl_.stage_; +} +inline uint32_t RingFinishedClosing::stage() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingFinishedClosing.stage) + return _internal_stage(); +} +inline void RingFinishedClosing::_internal_set_stage(uint32_t value) { + + _impl_.stage_ = value; +} +inline void RingFinishedClosing::set_stage(uint32_t value) { + _internal_set_stage(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingFinishedClosing.stage) +} + +// .rtech.liveapi.Vector3 center = 4; +inline bool RingFinishedClosing::_internal_has_center() const { + return this != internal_default_instance() && _impl_.center_ != nullptr; +} +inline bool RingFinishedClosing::has_center() const { + return _internal_has_center(); +} +inline void RingFinishedClosing::clear_center() { + if (GetArenaForAllocation() == nullptr && _impl_.center_ != nullptr) { + delete _impl_.center_; + } + _impl_.center_ = nullptr; +} +inline const ::rtech::liveapi::Vector3& RingFinishedClosing::_internal_center() const { + const ::rtech::liveapi::Vector3* p = _impl_.center_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Vector3_default_instance_); +} +inline const ::rtech::liveapi::Vector3& RingFinishedClosing::center() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingFinishedClosing.center) + return _internal_center(); +} +inline void RingFinishedClosing::unsafe_arena_set_allocated_center( + ::rtech::liveapi::Vector3* center) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.center_); + } + _impl_.center_ = center; + if (center) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.RingFinishedClosing.center) +} +inline ::rtech::liveapi::Vector3* RingFinishedClosing::release_center() { + + ::rtech::liveapi::Vector3* temp = _impl_.center_; + _impl_.center_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Vector3* RingFinishedClosing::unsafe_arena_release_center() { + // @@protoc_insertion_point(field_release:rtech.liveapi.RingFinishedClosing.center) + + ::rtech::liveapi::Vector3* temp = _impl_.center_; + _impl_.center_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Vector3* RingFinishedClosing::_internal_mutable_center() { + + if (_impl_.center_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Vector3>(GetArenaForAllocation()); + _impl_.center_ = p; + } + return _impl_.center_; +} +inline ::rtech::liveapi::Vector3* RingFinishedClosing::mutable_center() { + ::rtech::liveapi::Vector3* _msg = _internal_mutable_center(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.RingFinishedClosing.center) + return _msg; +} +inline void RingFinishedClosing::set_allocated_center(::rtech::liveapi::Vector3* center) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.center_; + } + if (center) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(center); + if (message_arena != submessage_arena) { + center = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, center, submessage_arena); + } + + } else { + + } + _impl_.center_ = center; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.RingFinishedClosing.center) +} + +// float currentRadius = 5; +inline void RingFinishedClosing::clear_currentradius() { + _impl_.currentradius_ = 0; +} +inline float RingFinishedClosing::_internal_currentradius() const { + return _impl_.currentradius_; +} +inline float RingFinishedClosing::currentradius() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingFinishedClosing.currentRadius) + return _internal_currentradius(); +} +inline void RingFinishedClosing::_internal_set_currentradius(float value) { + + _impl_.currentradius_ = value; +} +inline void RingFinishedClosing::set_currentradius(float value) { + _internal_set_currentradius(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingFinishedClosing.currentRadius) +} + +// float shrinkDuration = 7; +inline void RingFinishedClosing::clear_shrinkduration() { + _impl_.shrinkduration_ = 0; +} +inline float RingFinishedClosing::_internal_shrinkduration() const { + return _impl_.shrinkduration_; +} +inline float RingFinishedClosing::shrinkduration() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RingFinishedClosing.shrinkDuration) + return _internal_shrinkduration(); +} +inline void RingFinishedClosing::_internal_set_shrinkduration(float value) { + + _impl_.shrinkduration_ = value; +} +inline void RingFinishedClosing::set_shrinkduration(float value) { + _internal_set_shrinkduration(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RingFinishedClosing.shrinkDuration) +} + +// ------------------------------------------------------------------- + +// PlayerConnected + +// uint64 timestamp = 1; +inline void PlayerConnected::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerConnected::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerConnected::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerConnected.timestamp) + return _internal_timestamp(); +} +inline void PlayerConnected::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerConnected::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerConnected.timestamp) +} + +// string category = 2; +inline void PlayerConnected::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerConnected::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerConnected.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerConnected::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerConnected.category) +} +inline std::string* PlayerConnected::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerConnected.category) + return _s; +} +inline const std::string& PlayerConnected::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerConnected::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerConnected::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerConnected::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerConnected.category) + return _impl_.category_.Release(); +} +inline void PlayerConnected::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerConnected.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool PlayerConnected::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool PlayerConnected::has_player() const { + return _internal_has_player(); +} +inline void PlayerConnected::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerConnected::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerConnected::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerConnected.player) + return _internal_player(); +} +inline void PlayerConnected::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerConnected.player) +} +inline ::rtech::liveapi::Player* PlayerConnected::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerConnected::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerConnected.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerConnected::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* PlayerConnected::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerConnected.player) + return _msg; +} +inline void PlayerConnected::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerConnected.player) +} + +// ------------------------------------------------------------------- + +// PlayerDisconnected + +// uint64 timestamp = 1; +inline void PlayerDisconnected::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerDisconnected::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerDisconnected::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDisconnected.timestamp) + return _internal_timestamp(); +} +inline void PlayerDisconnected::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerDisconnected::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDisconnected.timestamp) +} + +// string category = 2; +inline void PlayerDisconnected::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerDisconnected::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDisconnected.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerDisconnected::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDisconnected.category) +} +inline std::string* PlayerDisconnected::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerDisconnected.category) + return _s; +} +inline const std::string& PlayerDisconnected::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerDisconnected::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerDisconnected::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerDisconnected::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerDisconnected.category) + return _impl_.category_.Release(); +} +inline void PlayerDisconnected::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerDisconnected.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool PlayerDisconnected::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool PlayerDisconnected::has_player() const { + return _internal_has_player(); +} +inline void PlayerDisconnected::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerDisconnected::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerDisconnected::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDisconnected.player) + return _internal_player(); +} +inline void PlayerDisconnected::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerDisconnected.player) +} +inline ::rtech::liveapi::Player* PlayerDisconnected::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerDisconnected::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerDisconnected.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerDisconnected::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* PlayerDisconnected::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerDisconnected.player) + return _msg; +} +inline void PlayerDisconnected::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerDisconnected.player) +} + +// bool canReconnect = 4; +inline void PlayerDisconnected::clear_canreconnect() { + _impl_.canreconnect_ = false; +} +inline bool PlayerDisconnected::_internal_canreconnect() const { + return _impl_.canreconnect_; +} +inline bool PlayerDisconnected::canreconnect() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDisconnected.canReconnect) + return _internal_canreconnect(); +} +inline void PlayerDisconnected::_internal_set_canreconnect(bool value) { + + _impl_.canreconnect_ = value; +} +inline void PlayerDisconnected::set_canreconnect(bool value) { + _internal_set_canreconnect(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDisconnected.canReconnect) +} + +// bool isAlive = 5; +inline void PlayerDisconnected::clear_isalive() { + _impl_.isalive_ = false; +} +inline bool PlayerDisconnected::_internal_isalive() const { + return _impl_.isalive_; +} +inline bool PlayerDisconnected::isalive() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDisconnected.isAlive) + return _internal_isalive(); +} +inline void PlayerDisconnected::_internal_set_isalive(bool value) { + + _impl_.isalive_ = value; +} +inline void PlayerDisconnected::set_isalive(bool value) { + _internal_set_isalive(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDisconnected.isAlive) +} + +// ------------------------------------------------------------------- + +// PlayerStatChanged + +// uint64 timestamp = 1; +inline void PlayerStatChanged::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerStatChanged::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerStatChanged::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerStatChanged.timestamp) + return _internal_timestamp(); +} +inline void PlayerStatChanged::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerStatChanged::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerStatChanged.timestamp) +} + +// string category = 2; +inline void PlayerStatChanged::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerStatChanged::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerStatChanged.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerStatChanged::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerStatChanged.category) +} +inline std::string* PlayerStatChanged::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerStatChanged.category) + return _s; +} +inline const std::string& PlayerStatChanged::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerStatChanged::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerStatChanged::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerStatChanged::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerStatChanged.category) + return _impl_.category_.Release(); +} +inline void PlayerStatChanged::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerStatChanged.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool PlayerStatChanged::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool PlayerStatChanged::has_player() const { + return _internal_has_player(); +} +inline void PlayerStatChanged::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerStatChanged::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerStatChanged::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerStatChanged.player) + return _internal_player(); +} +inline void PlayerStatChanged::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerStatChanged.player) +} +inline ::rtech::liveapi::Player* PlayerStatChanged::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerStatChanged::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerStatChanged.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerStatChanged::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* PlayerStatChanged::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerStatChanged.player) + return _msg; +} +inline void PlayerStatChanged::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerStatChanged.player) +} + +// string statName = 4; +inline void PlayerStatChanged::clear_statname() { + _impl_.statname_.ClearToEmpty(); +} +inline const std::string& PlayerStatChanged::statname() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerStatChanged.statName) + return _internal_statname(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerStatChanged::set_statname(ArgT0&& arg0, ArgT... args) { + + _impl_.statname_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerStatChanged.statName) +} +inline std::string* PlayerStatChanged::mutable_statname() { + std::string* _s = _internal_mutable_statname(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerStatChanged.statName) + return _s; +} +inline const std::string& PlayerStatChanged::_internal_statname() const { + return _impl_.statname_.Get(); +} +inline void PlayerStatChanged::_internal_set_statname(const std::string& value) { + + _impl_.statname_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerStatChanged::_internal_mutable_statname() { + + return _impl_.statname_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerStatChanged::release_statname() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerStatChanged.statName) + return _impl_.statname_.Release(); +} +inline void PlayerStatChanged::set_allocated_statname(std::string* statname) { + if (statname != nullptr) { + + } else { + + } + _impl_.statname_.SetAllocated(statname, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.statname_.IsDefault()) { + _impl_.statname_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerStatChanged.statName) +} + +// uint32 intValue = 5; +inline bool PlayerStatChanged::_internal_has_intvalue() const { + return newValue_case() == kIntValue; +} +inline bool PlayerStatChanged::has_intvalue() const { + return _internal_has_intvalue(); +} +inline void PlayerStatChanged::set_has_intvalue() { + _impl_._oneof_case_[0] = kIntValue; +} +inline void PlayerStatChanged::clear_intvalue() { + if (_internal_has_intvalue()) { + _impl_.newValue_.intvalue_ = 0u; + clear_has_newValue(); + } +} +inline uint32_t PlayerStatChanged::_internal_intvalue() const { + if (_internal_has_intvalue()) { + return _impl_.newValue_.intvalue_; + } + return 0u; +} +inline void PlayerStatChanged::_internal_set_intvalue(uint32_t value) { + if (!_internal_has_intvalue()) { + clear_newValue(); + set_has_intvalue(); + } + _impl_.newValue_.intvalue_ = value; +} +inline uint32_t PlayerStatChanged::intvalue() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerStatChanged.intValue) + return _internal_intvalue(); +} +inline void PlayerStatChanged::set_intvalue(uint32_t value) { + _internal_set_intvalue(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerStatChanged.intValue) +} + +// float floatValue = 6; +inline bool PlayerStatChanged::_internal_has_floatvalue() const { + return newValue_case() == kFloatValue; +} +inline bool PlayerStatChanged::has_floatvalue() const { + return _internal_has_floatvalue(); +} +inline void PlayerStatChanged::set_has_floatvalue() { + _impl_._oneof_case_[0] = kFloatValue; +} +inline void PlayerStatChanged::clear_floatvalue() { + if (_internal_has_floatvalue()) { + _impl_.newValue_.floatvalue_ = 0; + clear_has_newValue(); + } +} +inline float PlayerStatChanged::_internal_floatvalue() const { + if (_internal_has_floatvalue()) { + return _impl_.newValue_.floatvalue_; + } + return 0; +} +inline void PlayerStatChanged::_internal_set_floatvalue(float value) { + if (!_internal_has_floatvalue()) { + clear_newValue(); + set_has_floatvalue(); + } + _impl_.newValue_.floatvalue_ = value; +} +inline float PlayerStatChanged::floatvalue() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerStatChanged.floatValue) + return _internal_floatvalue(); +} +inline void PlayerStatChanged::set_floatvalue(float value) { + _internal_set_floatvalue(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerStatChanged.floatValue) +} + +// bool boolValue = 7; +inline bool PlayerStatChanged::_internal_has_boolvalue() const { + return newValue_case() == kBoolValue; +} +inline bool PlayerStatChanged::has_boolvalue() const { + return _internal_has_boolvalue(); +} +inline void PlayerStatChanged::set_has_boolvalue() { + _impl_._oneof_case_[0] = kBoolValue; +} +inline void PlayerStatChanged::clear_boolvalue() { + if (_internal_has_boolvalue()) { + _impl_.newValue_.boolvalue_ = false; + clear_has_newValue(); + } +} +inline bool PlayerStatChanged::_internal_boolvalue() const { + if (_internal_has_boolvalue()) { + return _impl_.newValue_.boolvalue_; + } + return false; +} +inline void PlayerStatChanged::_internal_set_boolvalue(bool value) { + if (!_internal_has_boolvalue()) { + clear_newValue(); + set_has_boolvalue(); + } + _impl_.newValue_.boolvalue_ = value; +} +inline bool PlayerStatChanged::boolvalue() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerStatChanged.boolValue) + return _internal_boolvalue(); +} +inline void PlayerStatChanged::set_boolvalue(bool value) { + _internal_set_boolvalue(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerStatChanged.boolValue) +} + +inline bool PlayerStatChanged::has_newValue() const { + return newValue_case() != NEWVALUE_NOT_SET; +} +inline void PlayerStatChanged::clear_has_newValue() { + _impl_._oneof_case_[0] = NEWVALUE_NOT_SET; +} +inline PlayerStatChanged::NewValueCase PlayerStatChanged::newValue_case() const { + return PlayerStatChanged::NewValueCase(_impl_._oneof_case_[0]); +} +// ------------------------------------------------------------------- + +// PlayerUpgradeTierChanged + +// uint64 timestamp = 1; +inline void PlayerUpgradeTierChanged::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerUpgradeTierChanged::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerUpgradeTierChanged::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerUpgradeTierChanged.timestamp) + return _internal_timestamp(); +} +inline void PlayerUpgradeTierChanged::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerUpgradeTierChanged::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerUpgradeTierChanged.timestamp) +} + +// string category = 2; +inline void PlayerUpgradeTierChanged::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerUpgradeTierChanged::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerUpgradeTierChanged.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerUpgradeTierChanged::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerUpgradeTierChanged.category) +} +inline std::string* PlayerUpgradeTierChanged::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerUpgradeTierChanged.category) + return _s; +} +inline const std::string& PlayerUpgradeTierChanged::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerUpgradeTierChanged::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerUpgradeTierChanged::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerUpgradeTierChanged::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerUpgradeTierChanged.category) + return _impl_.category_.Release(); +} +inline void PlayerUpgradeTierChanged::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerUpgradeTierChanged.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool PlayerUpgradeTierChanged::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool PlayerUpgradeTierChanged::has_player() const { + return _internal_has_player(); +} +inline void PlayerUpgradeTierChanged::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerUpgradeTierChanged::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerUpgradeTierChanged::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerUpgradeTierChanged.player) + return _internal_player(); +} +inline void PlayerUpgradeTierChanged::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerUpgradeTierChanged.player) +} +inline ::rtech::liveapi::Player* PlayerUpgradeTierChanged::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerUpgradeTierChanged::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerUpgradeTierChanged.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerUpgradeTierChanged::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* PlayerUpgradeTierChanged::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerUpgradeTierChanged.player) + return _msg; +} +inline void PlayerUpgradeTierChanged::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerUpgradeTierChanged.player) +} + +// int32 level = 4; +inline void PlayerUpgradeTierChanged::clear_level() { + _impl_.level_ = 0; +} +inline int32_t PlayerUpgradeTierChanged::_internal_level() const { + return _impl_.level_; +} +inline int32_t PlayerUpgradeTierChanged::level() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerUpgradeTierChanged.level) + return _internal_level(); +} +inline void PlayerUpgradeTierChanged::_internal_set_level(int32_t value) { + + _impl_.level_ = value; +} +inline void PlayerUpgradeTierChanged::set_level(int32_t value) { + _internal_set_level(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerUpgradeTierChanged.level) +} + +// ------------------------------------------------------------------- + +// PlayerDamaged + +// uint64 timestamp = 1; +inline void PlayerDamaged::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerDamaged::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerDamaged::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDamaged.timestamp) + return _internal_timestamp(); +} +inline void PlayerDamaged::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerDamaged::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDamaged.timestamp) +} + +// string category = 2; +inline void PlayerDamaged::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerDamaged::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDamaged.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerDamaged::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDamaged.category) +} +inline std::string* PlayerDamaged::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerDamaged.category) + return _s; +} +inline const std::string& PlayerDamaged::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerDamaged::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerDamaged::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerDamaged::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerDamaged.category) + return _impl_.category_.Release(); +} +inline void PlayerDamaged::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerDamaged.category) +} + +// .rtech.liveapi.Player attacker = 3; +inline bool PlayerDamaged::_internal_has_attacker() const { + return this != internal_default_instance() && _impl_.attacker_ != nullptr; +} +inline bool PlayerDamaged::has_attacker() const { + return _internal_has_attacker(); +} +inline void PlayerDamaged::clear_attacker() { + if (GetArenaForAllocation() == nullptr && _impl_.attacker_ != nullptr) { + delete _impl_.attacker_; + } + _impl_.attacker_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerDamaged::_internal_attacker() const { + const ::rtech::liveapi::Player* p = _impl_.attacker_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerDamaged::attacker() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDamaged.attacker) + return _internal_attacker(); +} +inline void PlayerDamaged::unsafe_arena_set_allocated_attacker( + ::rtech::liveapi::Player* attacker) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.attacker_); + } + _impl_.attacker_ = attacker; + if (attacker) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerDamaged.attacker) +} +inline ::rtech::liveapi::Player* PlayerDamaged::release_attacker() { + + ::rtech::liveapi::Player* temp = _impl_.attacker_; + _impl_.attacker_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerDamaged::unsafe_arena_release_attacker() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerDamaged.attacker) + + ::rtech::liveapi::Player* temp = _impl_.attacker_; + _impl_.attacker_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerDamaged::_internal_mutable_attacker() { + + if (_impl_.attacker_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.attacker_ = p; + } + return _impl_.attacker_; +} +inline ::rtech::liveapi::Player* PlayerDamaged::mutable_attacker() { + ::rtech::liveapi::Player* _msg = _internal_mutable_attacker(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerDamaged.attacker) + return _msg; +} +inline void PlayerDamaged::set_allocated_attacker(::rtech::liveapi::Player* attacker) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.attacker_; + } + if (attacker) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(attacker); + if (message_arena != submessage_arena) { + attacker = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, attacker, submessage_arena); + } + + } else { + + } + _impl_.attacker_ = attacker; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerDamaged.attacker) +} + +// .rtech.liveapi.Player victim = 4; +inline bool PlayerDamaged::_internal_has_victim() const { + return this != internal_default_instance() && _impl_.victim_ != nullptr; +} +inline bool PlayerDamaged::has_victim() const { + return _internal_has_victim(); +} +inline void PlayerDamaged::clear_victim() { + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerDamaged::_internal_victim() const { + const ::rtech::liveapi::Player* p = _impl_.victim_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerDamaged::victim() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDamaged.victim) + return _internal_victim(); +} +inline void PlayerDamaged::unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.victim_); + } + _impl_.victim_ = victim; + if (victim) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerDamaged.victim) +} +inline ::rtech::liveapi::Player* PlayerDamaged::release_victim() { + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerDamaged::unsafe_arena_release_victim() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerDamaged.victim) + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerDamaged::_internal_mutable_victim() { + + if (_impl_.victim_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.victim_ = p; + } + return _impl_.victim_; +} +inline ::rtech::liveapi::Player* PlayerDamaged::mutable_victim() { + ::rtech::liveapi::Player* _msg = _internal_mutable_victim(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerDamaged.victim) + return _msg; +} +inline void PlayerDamaged::set_allocated_victim(::rtech::liveapi::Player* victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.victim_; + } + if (victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(victim); + if (message_arena != submessage_arena) { + victim = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, victim, submessage_arena); + } + + } else { + + } + _impl_.victim_ = victim; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerDamaged.victim) +} + +// string weapon = 5; +inline void PlayerDamaged::clear_weapon() { + _impl_.weapon_.ClearToEmpty(); +} +inline const std::string& PlayerDamaged::weapon() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDamaged.weapon) + return _internal_weapon(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerDamaged::set_weapon(ArgT0&& arg0, ArgT... args) { + + _impl_.weapon_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDamaged.weapon) +} +inline std::string* PlayerDamaged::mutable_weapon() { + std::string* _s = _internal_mutable_weapon(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerDamaged.weapon) + return _s; +} +inline const std::string& PlayerDamaged::_internal_weapon() const { + return _impl_.weapon_.Get(); +} +inline void PlayerDamaged::_internal_set_weapon(const std::string& value) { + + _impl_.weapon_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerDamaged::_internal_mutable_weapon() { + + return _impl_.weapon_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerDamaged::release_weapon() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerDamaged.weapon) + return _impl_.weapon_.Release(); +} +inline void PlayerDamaged::set_allocated_weapon(std::string* weapon) { + if (weapon != nullptr) { + + } else { + + } + _impl_.weapon_.SetAllocated(weapon, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.weapon_.IsDefault()) { + _impl_.weapon_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerDamaged.weapon) +} + +// uint32 damageInflicted = 6; +inline void PlayerDamaged::clear_damageinflicted() { + _impl_.damageinflicted_ = 0u; +} +inline uint32_t PlayerDamaged::_internal_damageinflicted() const { + return _impl_.damageinflicted_; +} +inline uint32_t PlayerDamaged::damageinflicted() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDamaged.damageInflicted) + return _internal_damageinflicted(); +} +inline void PlayerDamaged::_internal_set_damageinflicted(uint32_t value) { + + _impl_.damageinflicted_ = value; +} +inline void PlayerDamaged::set_damageinflicted(uint32_t value) { + _internal_set_damageinflicted(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDamaged.damageInflicted) +} + +// ------------------------------------------------------------------- + +// PlayerKilled + +// uint64 timestamp = 1; +inline void PlayerKilled::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerKilled::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerKilled::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerKilled.timestamp) + return _internal_timestamp(); +} +inline void PlayerKilled::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerKilled::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerKilled.timestamp) +} + +// string category = 2; +inline void PlayerKilled::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerKilled::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerKilled.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerKilled::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerKilled.category) +} +inline std::string* PlayerKilled::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerKilled.category) + return _s; +} +inline const std::string& PlayerKilled::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerKilled::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerKilled::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerKilled::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerKilled.category) + return _impl_.category_.Release(); +} +inline void PlayerKilled::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerKilled.category) +} + +// .rtech.liveapi.Player attacker = 3; +inline bool PlayerKilled::_internal_has_attacker() const { + return this != internal_default_instance() && _impl_.attacker_ != nullptr; +} +inline bool PlayerKilled::has_attacker() const { + return _internal_has_attacker(); +} +inline void PlayerKilled::clear_attacker() { + if (GetArenaForAllocation() == nullptr && _impl_.attacker_ != nullptr) { + delete _impl_.attacker_; + } + _impl_.attacker_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerKilled::_internal_attacker() const { + const ::rtech::liveapi::Player* p = _impl_.attacker_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerKilled::attacker() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerKilled.attacker) + return _internal_attacker(); +} +inline void PlayerKilled::unsafe_arena_set_allocated_attacker( + ::rtech::liveapi::Player* attacker) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.attacker_); + } + _impl_.attacker_ = attacker; + if (attacker) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerKilled.attacker) +} +inline ::rtech::liveapi::Player* PlayerKilled::release_attacker() { + + ::rtech::liveapi::Player* temp = _impl_.attacker_; + _impl_.attacker_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerKilled::unsafe_arena_release_attacker() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerKilled.attacker) + + ::rtech::liveapi::Player* temp = _impl_.attacker_; + _impl_.attacker_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerKilled::_internal_mutable_attacker() { + + if (_impl_.attacker_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.attacker_ = p; + } + return _impl_.attacker_; +} +inline ::rtech::liveapi::Player* PlayerKilled::mutable_attacker() { + ::rtech::liveapi::Player* _msg = _internal_mutable_attacker(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerKilled.attacker) + return _msg; +} +inline void PlayerKilled::set_allocated_attacker(::rtech::liveapi::Player* attacker) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.attacker_; + } + if (attacker) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(attacker); + if (message_arena != submessage_arena) { + attacker = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, attacker, submessage_arena); + } + + } else { + + } + _impl_.attacker_ = attacker; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerKilled.attacker) +} + +// .rtech.liveapi.Player victim = 4; +inline bool PlayerKilled::_internal_has_victim() const { + return this != internal_default_instance() && _impl_.victim_ != nullptr; +} +inline bool PlayerKilled::has_victim() const { + return _internal_has_victim(); +} +inline void PlayerKilled::clear_victim() { + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerKilled::_internal_victim() const { + const ::rtech::liveapi::Player* p = _impl_.victim_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerKilled::victim() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerKilled.victim) + return _internal_victim(); +} +inline void PlayerKilled::unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.victim_); + } + _impl_.victim_ = victim; + if (victim) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerKilled.victim) +} +inline ::rtech::liveapi::Player* PlayerKilled::release_victim() { + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerKilled::unsafe_arena_release_victim() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerKilled.victim) + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerKilled::_internal_mutable_victim() { + + if (_impl_.victim_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.victim_ = p; + } + return _impl_.victim_; +} +inline ::rtech::liveapi::Player* PlayerKilled::mutable_victim() { + ::rtech::liveapi::Player* _msg = _internal_mutable_victim(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerKilled.victim) + return _msg; +} +inline void PlayerKilled::set_allocated_victim(::rtech::liveapi::Player* victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.victim_; + } + if (victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(victim); + if (message_arena != submessage_arena) { + victim = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, victim, submessage_arena); + } + + } else { + + } + _impl_.victim_ = victim; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerKilled.victim) +} + +// .rtech.liveapi.Player awardedTo = 5; +inline bool PlayerKilled::_internal_has_awardedto() const { + return this != internal_default_instance() && _impl_.awardedto_ != nullptr; +} +inline bool PlayerKilled::has_awardedto() const { + return _internal_has_awardedto(); +} +inline void PlayerKilled::clear_awardedto() { + if (GetArenaForAllocation() == nullptr && _impl_.awardedto_ != nullptr) { + delete _impl_.awardedto_; + } + _impl_.awardedto_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerKilled::_internal_awardedto() const { + const ::rtech::liveapi::Player* p = _impl_.awardedto_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerKilled::awardedto() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerKilled.awardedTo) + return _internal_awardedto(); +} +inline void PlayerKilled::unsafe_arena_set_allocated_awardedto( + ::rtech::liveapi::Player* awardedto) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.awardedto_); + } + _impl_.awardedto_ = awardedto; + if (awardedto) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerKilled.awardedTo) +} +inline ::rtech::liveapi::Player* PlayerKilled::release_awardedto() { + + ::rtech::liveapi::Player* temp = _impl_.awardedto_; + _impl_.awardedto_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerKilled::unsafe_arena_release_awardedto() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerKilled.awardedTo) + + ::rtech::liveapi::Player* temp = _impl_.awardedto_; + _impl_.awardedto_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerKilled::_internal_mutable_awardedto() { + + if (_impl_.awardedto_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.awardedto_ = p; + } + return _impl_.awardedto_; +} +inline ::rtech::liveapi::Player* PlayerKilled::mutable_awardedto() { + ::rtech::liveapi::Player* _msg = _internal_mutable_awardedto(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerKilled.awardedTo) + return _msg; +} +inline void PlayerKilled::set_allocated_awardedto(::rtech::liveapi::Player* awardedto) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.awardedto_; + } + if (awardedto) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(awardedto); + if (message_arena != submessage_arena) { + awardedto = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, awardedto, submessage_arena); + } + + } else { + + } + _impl_.awardedto_ = awardedto; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerKilled.awardedTo) +} + +// string weapon = 6; +inline void PlayerKilled::clear_weapon() { + _impl_.weapon_.ClearToEmpty(); +} +inline const std::string& PlayerKilled::weapon() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerKilled.weapon) + return _internal_weapon(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerKilled::set_weapon(ArgT0&& arg0, ArgT... args) { + + _impl_.weapon_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerKilled.weapon) +} +inline std::string* PlayerKilled::mutable_weapon() { + std::string* _s = _internal_mutable_weapon(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerKilled.weapon) + return _s; +} +inline const std::string& PlayerKilled::_internal_weapon() const { + return _impl_.weapon_.Get(); +} +inline void PlayerKilled::_internal_set_weapon(const std::string& value) { + + _impl_.weapon_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerKilled::_internal_mutable_weapon() { + + return _impl_.weapon_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerKilled::release_weapon() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerKilled.weapon) + return _impl_.weapon_.Release(); +} +inline void PlayerKilled::set_allocated_weapon(std::string* weapon) { + if (weapon != nullptr) { + + } else { + + } + _impl_.weapon_.SetAllocated(weapon, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.weapon_.IsDefault()) { + _impl_.weapon_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerKilled.weapon) +} + +// ------------------------------------------------------------------- + +// PlayerDowned + +// uint64 timestamp = 1; +inline void PlayerDowned::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerDowned::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerDowned::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDowned.timestamp) + return _internal_timestamp(); +} +inline void PlayerDowned::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerDowned::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDowned.timestamp) +} + +// string category = 2; +inline void PlayerDowned::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerDowned::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDowned.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerDowned::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDowned.category) +} +inline std::string* PlayerDowned::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerDowned.category) + return _s; +} +inline const std::string& PlayerDowned::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerDowned::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerDowned::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerDowned::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerDowned.category) + return _impl_.category_.Release(); +} +inline void PlayerDowned::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerDowned.category) +} + +// .rtech.liveapi.Player attacker = 3; +inline bool PlayerDowned::_internal_has_attacker() const { + return this != internal_default_instance() && _impl_.attacker_ != nullptr; +} +inline bool PlayerDowned::has_attacker() const { + return _internal_has_attacker(); +} +inline void PlayerDowned::clear_attacker() { + if (GetArenaForAllocation() == nullptr && _impl_.attacker_ != nullptr) { + delete _impl_.attacker_; + } + _impl_.attacker_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerDowned::_internal_attacker() const { + const ::rtech::liveapi::Player* p = _impl_.attacker_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerDowned::attacker() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDowned.attacker) + return _internal_attacker(); +} +inline void PlayerDowned::unsafe_arena_set_allocated_attacker( + ::rtech::liveapi::Player* attacker) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.attacker_); + } + _impl_.attacker_ = attacker; + if (attacker) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerDowned.attacker) +} +inline ::rtech::liveapi::Player* PlayerDowned::release_attacker() { + + ::rtech::liveapi::Player* temp = _impl_.attacker_; + _impl_.attacker_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerDowned::unsafe_arena_release_attacker() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerDowned.attacker) + + ::rtech::liveapi::Player* temp = _impl_.attacker_; + _impl_.attacker_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerDowned::_internal_mutable_attacker() { + + if (_impl_.attacker_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.attacker_ = p; + } + return _impl_.attacker_; +} +inline ::rtech::liveapi::Player* PlayerDowned::mutable_attacker() { + ::rtech::liveapi::Player* _msg = _internal_mutable_attacker(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerDowned.attacker) + return _msg; +} +inline void PlayerDowned::set_allocated_attacker(::rtech::liveapi::Player* attacker) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.attacker_; + } + if (attacker) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(attacker); + if (message_arena != submessage_arena) { + attacker = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, attacker, submessage_arena); + } + + } else { + + } + _impl_.attacker_ = attacker; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerDowned.attacker) +} + +// .rtech.liveapi.Player victim = 4; +inline bool PlayerDowned::_internal_has_victim() const { + return this != internal_default_instance() && _impl_.victim_ != nullptr; +} +inline bool PlayerDowned::has_victim() const { + return _internal_has_victim(); +} +inline void PlayerDowned::clear_victim() { + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerDowned::_internal_victim() const { + const ::rtech::liveapi::Player* p = _impl_.victim_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerDowned::victim() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDowned.victim) + return _internal_victim(); +} +inline void PlayerDowned::unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.victim_); + } + _impl_.victim_ = victim; + if (victim) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerDowned.victim) +} +inline ::rtech::liveapi::Player* PlayerDowned::release_victim() { + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerDowned::unsafe_arena_release_victim() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerDowned.victim) + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerDowned::_internal_mutable_victim() { + + if (_impl_.victim_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.victim_ = p; + } + return _impl_.victim_; +} +inline ::rtech::liveapi::Player* PlayerDowned::mutable_victim() { + ::rtech::liveapi::Player* _msg = _internal_mutable_victim(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerDowned.victim) + return _msg; +} +inline void PlayerDowned::set_allocated_victim(::rtech::liveapi::Player* victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.victim_; + } + if (victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(victim); + if (message_arena != submessage_arena) { + victim = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, victim, submessage_arena); + } + + } else { + + } + _impl_.victim_ = victim; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerDowned.victim) +} + +// string weapon = 5; +inline void PlayerDowned::clear_weapon() { + _impl_.weapon_.ClearToEmpty(); +} +inline const std::string& PlayerDowned::weapon() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerDowned.weapon) + return _internal_weapon(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerDowned::set_weapon(ArgT0&& arg0, ArgT... args) { + + _impl_.weapon_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerDowned.weapon) +} +inline std::string* PlayerDowned::mutable_weapon() { + std::string* _s = _internal_mutable_weapon(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerDowned.weapon) + return _s; +} +inline const std::string& PlayerDowned::_internal_weapon() const { + return _impl_.weapon_.Get(); +} +inline void PlayerDowned::_internal_set_weapon(const std::string& value) { + + _impl_.weapon_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerDowned::_internal_mutable_weapon() { + + return _impl_.weapon_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerDowned::release_weapon() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerDowned.weapon) + return _impl_.weapon_.Release(); +} +inline void PlayerDowned::set_allocated_weapon(std::string* weapon) { + if (weapon != nullptr) { + + } else { + + } + _impl_.weapon_.SetAllocated(weapon, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.weapon_.IsDefault()) { + _impl_.weapon_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerDowned.weapon) +} + +// ------------------------------------------------------------------- + +// PlayerAssist + +// uint64 timestamp = 1; +inline void PlayerAssist::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerAssist::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerAssist::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerAssist.timestamp) + return _internal_timestamp(); +} +inline void PlayerAssist::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerAssist::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerAssist.timestamp) +} + +// string category = 2; +inline void PlayerAssist::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerAssist::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerAssist.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerAssist::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerAssist.category) +} +inline std::string* PlayerAssist::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerAssist.category) + return _s; +} +inline const std::string& PlayerAssist::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerAssist::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerAssist::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerAssist::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerAssist.category) + return _impl_.category_.Release(); +} +inline void PlayerAssist::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerAssist.category) +} + +// .rtech.liveapi.Player assistant = 3; +inline bool PlayerAssist::_internal_has_assistant() const { + return this != internal_default_instance() && _impl_.assistant_ != nullptr; +} +inline bool PlayerAssist::has_assistant() const { + return _internal_has_assistant(); +} +inline void PlayerAssist::clear_assistant() { + if (GetArenaForAllocation() == nullptr && _impl_.assistant_ != nullptr) { + delete _impl_.assistant_; + } + _impl_.assistant_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerAssist::_internal_assistant() const { + const ::rtech::liveapi::Player* p = _impl_.assistant_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerAssist::assistant() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerAssist.assistant) + return _internal_assistant(); +} +inline void PlayerAssist::unsafe_arena_set_allocated_assistant( + ::rtech::liveapi::Player* assistant) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.assistant_); + } + _impl_.assistant_ = assistant; + if (assistant) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerAssist.assistant) +} +inline ::rtech::liveapi::Player* PlayerAssist::release_assistant() { + + ::rtech::liveapi::Player* temp = _impl_.assistant_; + _impl_.assistant_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerAssist::unsafe_arena_release_assistant() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerAssist.assistant) + + ::rtech::liveapi::Player* temp = _impl_.assistant_; + _impl_.assistant_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerAssist::_internal_mutable_assistant() { + + if (_impl_.assistant_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.assistant_ = p; + } + return _impl_.assistant_; +} +inline ::rtech::liveapi::Player* PlayerAssist::mutable_assistant() { + ::rtech::liveapi::Player* _msg = _internal_mutable_assistant(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerAssist.assistant) + return _msg; +} +inline void PlayerAssist::set_allocated_assistant(::rtech::liveapi::Player* assistant) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.assistant_; + } + if (assistant) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(assistant); + if (message_arena != submessage_arena) { + assistant = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, assistant, submessage_arena); + } + + } else { + + } + _impl_.assistant_ = assistant; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerAssist.assistant) +} + +// .rtech.liveapi.Player victim = 4; +inline bool PlayerAssist::_internal_has_victim() const { + return this != internal_default_instance() && _impl_.victim_ != nullptr; +} +inline bool PlayerAssist::has_victim() const { + return _internal_has_victim(); +} +inline void PlayerAssist::clear_victim() { + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerAssist::_internal_victim() const { + const ::rtech::liveapi::Player* p = _impl_.victim_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerAssist::victim() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerAssist.victim) + return _internal_victim(); +} +inline void PlayerAssist::unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.victim_); + } + _impl_.victim_ = victim; + if (victim) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerAssist.victim) +} +inline ::rtech::liveapi::Player* PlayerAssist::release_victim() { + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerAssist::unsafe_arena_release_victim() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerAssist.victim) + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerAssist::_internal_mutable_victim() { + + if (_impl_.victim_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.victim_ = p; + } + return _impl_.victim_; +} +inline ::rtech::liveapi::Player* PlayerAssist::mutable_victim() { + ::rtech::liveapi::Player* _msg = _internal_mutable_victim(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerAssist.victim) + return _msg; +} +inline void PlayerAssist::set_allocated_victim(::rtech::liveapi::Player* victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.victim_; + } + if (victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(victim); + if (message_arena != submessage_arena) { + victim = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, victim, submessage_arena); + } + + } else { + + } + _impl_.victim_ = victim; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerAssist.victim) +} + +// string weapon = 5; +inline void PlayerAssist::clear_weapon() { + _impl_.weapon_.ClearToEmpty(); +} +inline const std::string& PlayerAssist::weapon() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerAssist.weapon) + return _internal_weapon(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerAssist::set_weapon(ArgT0&& arg0, ArgT... args) { + + _impl_.weapon_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerAssist.weapon) +} +inline std::string* PlayerAssist::mutable_weapon() { + std::string* _s = _internal_mutable_weapon(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerAssist.weapon) + return _s; +} +inline const std::string& PlayerAssist::_internal_weapon() const { + return _impl_.weapon_.Get(); +} +inline void PlayerAssist::_internal_set_weapon(const std::string& value) { + + _impl_.weapon_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerAssist::_internal_mutable_weapon() { + + return _impl_.weapon_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerAssist::release_weapon() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerAssist.weapon) + return _impl_.weapon_.Release(); +} +inline void PlayerAssist::set_allocated_weapon(std::string* weapon) { + if (weapon != nullptr) { + + } else { + + } + _impl_.weapon_.SetAllocated(weapon, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.weapon_.IsDefault()) { + _impl_.weapon_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerAssist.weapon) +} + +// ------------------------------------------------------------------- + +// SquadEliminated + +// uint64 timestamp = 1; +inline void SquadEliminated::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t SquadEliminated::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t SquadEliminated::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.SquadEliminated.timestamp) + return _internal_timestamp(); +} +inline void SquadEliminated::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void SquadEliminated::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.SquadEliminated.timestamp) +} + +// string category = 2; +inline void SquadEliminated::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& SquadEliminated::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.SquadEliminated.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void SquadEliminated::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.SquadEliminated.category) +} +inline std::string* SquadEliminated::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.SquadEliminated.category) + return _s; +} +inline const std::string& SquadEliminated::_internal_category() const { + return _impl_.category_.Get(); +} +inline void SquadEliminated::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* SquadEliminated::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* SquadEliminated::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.SquadEliminated.category) + return _impl_.category_.Release(); +} +inline void SquadEliminated::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.SquadEliminated.category) +} + +// repeated .rtech.liveapi.Player players = 3; +inline int SquadEliminated::_internal_players_size() const { + return _impl_.players_.size(); +} +inline int SquadEliminated::players_size() const { + return _internal_players_size(); +} +inline void SquadEliminated::clear_players() { + _impl_.players_.Clear(); +} +inline ::rtech::liveapi::Player* SquadEliminated::mutable_players(int index) { + // @@protoc_insertion_point(field_mutable:rtech.liveapi.SquadEliminated.players) + return _impl_.players_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >* +SquadEliminated::mutable_players() { + // @@protoc_insertion_point(field_mutable_list:rtech.liveapi.SquadEliminated.players) + return &_impl_.players_; +} +inline const ::rtech::liveapi::Player& SquadEliminated::_internal_players(int index) const { + return _impl_.players_.Get(index); +} +inline const ::rtech::liveapi::Player& SquadEliminated::players(int index) const { + // @@protoc_insertion_point(field_get:rtech.liveapi.SquadEliminated.players) + return _internal_players(index); +} +inline ::rtech::liveapi::Player* SquadEliminated::_internal_add_players() { + return _impl_.players_.Add(); +} +inline ::rtech::liveapi::Player* SquadEliminated::add_players() { + ::rtech::liveapi::Player* _add = _internal_add_players(); + // @@protoc_insertion_point(field_add:rtech.liveapi.SquadEliminated.players) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >& +SquadEliminated::players() const { + // @@protoc_insertion_point(field_list:rtech.liveapi.SquadEliminated.players) + return _impl_.players_; +} + +// ------------------------------------------------------------------- + +// GibraltarShieldAbsorbed + +// uint64 timestamp = 1; +inline void GibraltarShieldAbsorbed::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t GibraltarShieldAbsorbed::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t GibraltarShieldAbsorbed::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GibraltarShieldAbsorbed.timestamp) + return _internal_timestamp(); +} +inline void GibraltarShieldAbsorbed::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void GibraltarShieldAbsorbed::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.GibraltarShieldAbsorbed.timestamp) +} + +// string category = 2; +inline void GibraltarShieldAbsorbed::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& GibraltarShieldAbsorbed::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GibraltarShieldAbsorbed.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void GibraltarShieldAbsorbed::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.GibraltarShieldAbsorbed.category) +} +inline std::string* GibraltarShieldAbsorbed::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.GibraltarShieldAbsorbed.category) + return _s; +} +inline const std::string& GibraltarShieldAbsorbed::_internal_category() const { + return _impl_.category_.Get(); +} +inline void GibraltarShieldAbsorbed::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* GibraltarShieldAbsorbed::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* GibraltarShieldAbsorbed::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.GibraltarShieldAbsorbed.category) + return _impl_.category_.Release(); +} +inline void GibraltarShieldAbsorbed::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.GibraltarShieldAbsorbed.category) +} + +// .rtech.liveapi.Player attacker = 3; +inline bool GibraltarShieldAbsorbed::_internal_has_attacker() const { + return this != internal_default_instance() && _impl_.attacker_ != nullptr; +} +inline bool GibraltarShieldAbsorbed::has_attacker() const { + return _internal_has_attacker(); +} +inline void GibraltarShieldAbsorbed::clear_attacker() { + if (GetArenaForAllocation() == nullptr && _impl_.attacker_ != nullptr) { + delete _impl_.attacker_; + } + _impl_.attacker_ = nullptr; +} +inline const ::rtech::liveapi::Player& GibraltarShieldAbsorbed::_internal_attacker() const { + const ::rtech::liveapi::Player* p = _impl_.attacker_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& GibraltarShieldAbsorbed::attacker() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GibraltarShieldAbsorbed.attacker) + return _internal_attacker(); +} +inline void GibraltarShieldAbsorbed::unsafe_arena_set_allocated_attacker( + ::rtech::liveapi::Player* attacker) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.attacker_); + } + _impl_.attacker_ = attacker; + if (attacker) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.GibraltarShieldAbsorbed.attacker) +} +inline ::rtech::liveapi::Player* GibraltarShieldAbsorbed::release_attacker() { + + ::rtech::liveapi::Player* temp = _impl_.attacker_; + _impl_.attacker_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* GibraltarShieldAbsorbed::unsafe_arena_release_attacker() { + // @@protoc_insertion_point(field_release:rtech.liveapi.GibraltarShieldAbsorbed.attacker) + + ::rtech::liveapi::Player* temp = _impl_.attacker_; + _impl_.attacker_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* GibraltarShieldAbsorbed::_internal_mutable_attacker() { + + if (_impl_.attacker_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.attacker_ = p; + } + return _impl_.attacker_; +} +inline ::rtech::liveapi::Player* GibraltarShieldAbsorbed::mutable_attacker() { + ::rtech::liveapi::Player* _msg = _internal_mutable_attacker(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.GibraltarShieldAbsorbed.attacker) + return _msg; +} +inline void GibraltarShieldAbsorbed::set_allocated_attacker(::rtech::liveapi::Player* attacker) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.attacker_; + } + if (attacker) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(attacker); + if (message_arena != submessage_arena) { + attacker = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, attacker, submessage_arena); + } + + } else { + + } + _impl_.attacker_ = attacker; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.GibraltarShieldAbsorbed.attacker) +} + +// .rtech.liveapi.Player victim = 4; +inline bool GibraltarShieldAbsorbed::_internal_has_victim() const { + return this != internal_default_instance() && _impl_.victim_ != nullptr; +} +inline bool GibraltarShieldAbsorbed::has_victim() const { + return _internal_has_victim(); +} +inline void GibraltarShieldAbsorbed::clear_victim() { + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; +} +inline const ::rtech::liveapi::Player& GibraltarShieldAbsorbed::_internal_victim() const { + const ::rtech::liveapi::Player* p = _impl_.victim_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& GibraltarShieldAbsorbed::victim() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GibraltarShieldAbsorbed.victim) + return _internal_victim(); +} +inline void GibraltarShieldAbsorbed::unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.victim_); + } + _impl_.victim_ = victim; + if (victim) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.GibraltarShieldAbsorbed.victim) +} +inline ::rtech::liveapi::Player* GibraltarShieldAbsorbed::release_victim() { + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* GibraltarShieldAbsorbed::unsafe_arena_release_victim() { + // @@protoc_insertion_point(field_release:rtech.liveapi.GibraltarShieldAbsorbed.victim) + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* GibraltarShieldAbsorbed::_internal_mutable_victim() { + + if (_impl_.victim_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.victim_ = p; + } + return _impl_.victim_; +} +inline ::rtech::liveapi::Player* GibraltarShieldAbsorbed::mutable_victim() { + ::rtech::liveapi::Player* _msg = _internal_mutable_victim(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.GibraltarShieldAbsorbed.victim) + return _msg; +} +inline void GibraltarShieldAbsorbed::set_allocated_victim(::rtech::liveapi::Player* victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.victim_; + } + if (victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(victim); + if (message_arena != submessage_arena) { + victim = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, victim, submessage_arena); + } + + } else { + + } + _impl_.victim_ = victim; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.GibraltarShieldAbsorbed.victim) +} + +// uint32 damageInflicted = 6; +inline void GibraltarShieldAbsorbed::clear_damageinflicted() { + _impl_.damageinflicted_ = 0u; +} +inline uint32_t GibraltarShieldAbsorbed::_internal_damageinflicted() const { + return _impl_.damageinflicted_; +} +inline uint32_t GibraltarShieldAbsorbed::damageinflicted() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GibraltarShieldAbsorbed.damageInflicted) + return _internal_damageinflicted(); +} +inline void GibraltarShieldAbsorbed::_internal_set_damageinflicted(uint32_t value) { + + _impl_.damageinflicted_ = value; +} +inline void GibraltarShieldAbsorbed::set_damageinflicted(uint32_t value) { + _internal_set_damageinflicted(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.GibraltarShieldAbsorbed.damageInflicted) +} + +// ------------------------------------------------------------------- + +// RevenantForgedShadowDamaged + +// uint64 timestamp = 1; +inline void RevenantForgedShadowDamaged::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t RevenantForgedShadowDamaged::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t RevenantForgedShadowDamaged::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RevenantForgedShadowDamaged.timestamp) + return _internal_timestamp(); +} +inline void RevenantForgedShadowDamaged::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void RevenantForgedShadowDamaged::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RevenantForgedShadowDamaged.timestamp) +} + +// string category = 2; +inline void RevenantForgedShadowDamaged::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& RevenantForgedShadowDamaged::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RevenantForgedShadowDamaged.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void RevenantForgedShadowDamaged::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.RevenantForgedShadowDamaged.category) +} +inline std::string* RevenantForgedShadowDamaged::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.RevenantForgedShadowDamaged.category) + return _s; +} +inline const std::string& RevenantForgedShadowDamaged::_internal_category() const { + return _impl_.category_.Get(); +} +inline void RevenantForgedShadowDamaged::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* RevenantForgedShadowDamaged::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* RevenantForgedShadowDamaged::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.RevenantForgedShadowDamaged.category) + return _impl_.category_.Release(); +} +inline void RevenantForgedShadowDamaged::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.RevenantForgedShadowDamaged.category) +} + +// .rtech.liveapi.Player attacker = 3; +inline bool RevenantForgedShadowDamaged::_internal_has_attacker() const { + return this != internal_default_instance() && _impl_.attacker_ != nullptr; +} +inline bool RevenantForgedShadowDamaged::has_attacker() const { + return _internal_has_attacker(); +} +inline void RevenantForgedShadowDamaged::clear_attacker() { + if (GetArenaForAllocation() == nullptr && _impl_.attacker_ != nullptr) { + delete _impl_.attacker_; + } + _impl_.attacker_ = nullptr; +} +inline const ::rtech::liveapi::Player& RevenantForgedShadowDamaged::_internal_attacker() const { + const ::rtech::liveapi::Player* p = _impl_.attacker_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& RevenantForgedShadowDamaged::attacker() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RevenantForgedShadowDamaged.attacker) + return _internal_attacker(); +} +inline void RevenantForgedShadowDamaged::unsafe_arena_set_allocated_attacker( + ::rtech::liveapi::Player* attacker) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.attacker_); + } + _impl_.attacker_ = attacker; + if (attacker) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.RevenantForgedShadowDamaged.attacker) +} +inline ::rtech::liveapi::Player* RevenantForgedShadowDamaged::release_attacker() { + + ::rtech::liveapi::Player* temp = _impl_.attacker_; + _impl_.attacker_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* RevenantForgedShadowDamaged::unsafe_arena_release_attacker() { + // @@protoc_insertion_point(field_release:rtech.liveapi.RevenantForgedShadowDamaged.attacker) + + ::rtech::liveapi::Player* temp = _impl_.attacker_; + _impl_.attacker_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* RevenantForgedShadowDamaged::_internal_mutable_attacker() { + + if (_impl_.attacker_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.attacker_ = p; + } + return _impl_.attacker_; +} +inline ::rtech::liveapi::Player* RevenantForgedShadowDamaged::mutable_attacker() { + ::rtech::liveapi::Player* _msg = _internal_mutable_attacker(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.RevenantForgedShadowDamaged.attacker) + return _msg; +} +inline void RevenantForgedShadowDamaged::set_allocated_attacker(::rtech::liveapi::Player* attacker) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.attacker_; + } + if (attacker) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(attacker); + if (message_arena != submessage_arena) { + attacker = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, attacker, submessage_arena); + } + + } else { + + } + _impl_.attacker_ = attacker; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.RevenantForgedShadowDamaged.attacker) +} + +// .rtech.liveapi.Player victim = 4; +inline bool RevenantForgedShadowDamaged::_internal_has_victim() const { + return this != internal_default_instance() && _impl_.victim_ != nullptr; +} +inline bool RevenantForgedShadowDamaged::has_victim() const { + return _internal_has_victim(); +} +inline void RevenantForgedShadowDamaged::clear_victim() { + if (GetArenaForAllocation() == nullptr && _impl_.victim_ != nullptr) { + delete _impl_.victim_; + } + _impl_.victim_ = nullptr; +} +inline const ::rtech::liveapi::Player& RevenantForgedShadowDamaged::_internal_victim() const { + const ::rtech::liveapi::Player* p = _impl_.victim_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& RevenantForgedShadowDamaged::victim() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RevenantForgedShadowDamaged.victim) + return _internal_victim(); +} +inline void RevenantForgedShadowDamaged::unsafe_arena_set_allocated_victim( + ::rtech::liveapi::Player* victim) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.victim_); + } + _impl_.victim_ = victim; + if (victim) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.RevenantForgedShadowDamaged.victim) +} +inline ::rtech::liveapi::Player* RevenantForgedShadowDamaged::release_victim() { + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* RevenantForgedShadowDamaged::unsafe_arena_release_victim() { + // @@protoc_insertion_point(field_release:rtech.liveapi.RevenantForgedShadowDamaged.victim) + + ::rtech::liveapi::Player* temp = _impl_.victim_; + _impl_.victim_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* RevenantForgedShadowDamaged::_internal_mutable_victim() { + + if (_impl_.victim_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.victim_ = p; + } + return _impl_.victim_; +} +inline ::rtech::liveapi::Player* RevenantForgedShadowDamaged::mutable_victim() { + ::rtech::liveapi::Player* _msg = _internal_mutable_victim(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.RevenantForgedShadowDamaged.victim) + return _msg; +} +inline void RevenantForgedShadowDamaged::set_allocated_victim(::rtech::liveapi::Player* victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.victim_; + } + if (victim) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(victim); + if (message_arena != submessage_arena) { + victim = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, victim, submessage_arena); + } + + } else { + + } + _impl_.victim_ = victim; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.RevenantForgedShadowDamaged.victim) +} + +// uint32 damageInflicted = 6; +inline void RevenantForgedShadowDamaged::clear_damageinflicted() { + _impl_.damageinflicted_ = 0u; +} +inline uint32_t RevenantForgedShadowDamaged::_internal_damageinflicted() const { + return _impl_.damageinflicted_; +} +inline uint32_t RevenantForgedShadowDamaged::damageinflicted() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RevenantForgedShadowDamaged.damageInflicted) + return _internal_damageinflicted(); +} +inline void RevenantForgedShadowDamaged::_internal_set_damageinflicted(uint32_t value) { + + _impl_.damageinflicted_ = value; +} +inline void RevenantForgedShadowDamaged::set_damageinflicted(uint32_t value) { + _internal_set_damageinflicted(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.RevenantForgedShadowDamaged.damageInflicted) +} + +// ------------------------------------------------------------------- + +// PlayerRespawnTeam + +// uint64 timestamp = 1; +inline void PlayerRespawnTeam::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerRespawnTeam::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerRespawnTeam::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerRespawnTeam.timestamp) + return _internal_timestamp(); +} +inline void PlayerRespawnTeam::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerRespawnTeam::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerRespawnTeam.timestamp) +} + +// string category = 2; +inline void PlayerRespawnTeam::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerRespawnTeam::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerRespawnTeam.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerRespawnTeam::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerRespawnTeam.category) +} +inline std::string* PlayerRespawnTeam::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerRespawnTeam.category) + return _s; +} +inline const std::string& PlayerRespawnTeam::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerRespawnTeam::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerRespawnTeam::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerRespawnTeam::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerRespawnTeam.category) + return _impl_.category_.Release(); +} +inline void PlayerRespawnTeam::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerRespawnTeam.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool PlayerRespawnTeam::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool PlayerRespawnTeam::has_player() const { + return _internal_has_player(); +} +inline void PlayerRespawnTeam::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerRespawnTeam::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerRespawnTeam::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerRespawnTeam.player) + return _internal_player(); +} +inline void PlayerRespawnTeam::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerRespawnTeam.player) +} +inline ::rtech::liveapi::Player* PlayerRespawnTeam::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerRespawnTeam::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerRespawnTeam.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerRespawnTeam::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* PlayerRespawnTeam::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerRespawnTeam.player) + return _msg; +} +inline void PlayerRespawnTeam::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerRespawnTeam.player) +} + +// repeated .rtech.liveapi.Player respawned = 4; +inline int PlayerRespawnTeam::_internal_respawned_size() const { + return _impl_.respawned_.size(); +} +inline int PlayerRespawnTeam::respawned_size() const { + return _internal_respawned_size(); +} +inline void PlayerRespawnTeam::clear_respawned() { + _impl_.respawned_.Clear(); +} +inline ::rtech::liveapi::Player* PlayerRespawnTeam::mutable_respawned(int index) { + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerRespawnTeam.respawned) + return _impl_.respawned_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >* +PlayerRespawnTeam::mutable_respawned() { + // @@protoc_insertion_point(field_mutable_list:rtech.liveapi.PlayerRespawnTeam.respawned) + return &_impl_.respawned_; +} +inline const ::rtech::liveapi::Player& PlayerRespawnTeam::_internal_respawned(int index) const { + return _impl_.respawned_.Get(index); +} +inline const ::rtech::liveapi::Player& PlayerRespawnTeam::respawned(int index) const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerRespawnTeam.respawned) + return _internal_respawned(index); +} +inline ::rtech::liveapi::Player* PlayerRespawnTeam::_internal_add_respawned() { + return _impl_.respawned_.Add(); +} +inline ::rtech::liveapi::Player* PlayerRespawnTeam::add_respawned() { + ::rtech::liveapi::Player* _add = _internal_add_respawned(); + // @@protoc_insertion_point(field_add:rtech.liveapi.PlayerRespawnTeam.respawned) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::rtech::liveapi::Player >& +PlayerRespawnTeam::respawned() const { + // @@protoc_insertion_point(field_list:rtech.liveapi.PlayerRespawnTeam.respawned) + return _impl_.respawned_; +} + +// ------------------------------------------------------------------- + +// PlayerRevive + +// uint64 timestamp = 1; +inline void PlayerRevive::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerRevive::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerRevive::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerRevive.timestamp) + return _internal_timestamp(); +} +inline void PlayerRevive::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerRevive::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerRevive.timestamp) +} + +// string category = 2; +inline void PlayerRevive::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerRevive::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerRevive.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerRevive::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerRevive.category) +} +inline std::string* PlayerRevive::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerRevive.category) + return _s; +} +inline const std::string& PlayerRevive::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerRevive::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerRevive::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerRevive::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerRevive.category) + return _impl_.category_.Release(); +} +inline void PlayerRevive::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerRevive.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool PlayerRevive::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool PlayerRevive::has_player() const { + return _internal_has_player(); +} +inline void PlayerRevive::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerRevive::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerRevive::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerRevive.player) + return _internal_player(); +} +inline void PlayerRevive::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerRevive.player) +} +inline ::rtech::liveapi::Player* PlayerRevive::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerRevive::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerRevive.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerRevive::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* PlayerRevive::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerRevive.player) + return _msg; +} +inline void PlayerRevive::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerRevive.player) +} + +// .rtech.liveapi.Player revived = 4; +inline bool PlayerRevive::_internal_has_revived() const { + return this != internal_default_instance() && _impl_.revived_ != nullptr; +} +inline bool PlayerRevive::has_revived() const { + return _internal_has_revived(); +} +inline void PlayerRevive::clear_revived() { + if (GetArenaForAllocation() == nullptr && _impl_.revived_ != nullptr) { + delete _impl_.revived_; + } + _impl_.revived_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerRevive::_internal_revived() const { + const ::rtech::liveapi::Player* p = _impl_.revived_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerRevive::revived() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerRevive.revived) + return _internal_revived(); +} +inline void PlayerRevive::unsafe_arena_set_allocated_revived( + ::rtech::liveapi::Player* revived) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.revived_); + } + _impl_.revived_ = revived; + if (revived) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerRevive.revived) +} +inline ::rtech::liveapi::Player* PlayerRevive::release_revived() { + + ::rtech::liveapi::Player* temp = _impl_.revived_; + _impl_.revived_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerRevive::unsafe_arena_release_revived() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerRevive.revived) + + ::rtech::liveapi::Player* temp = _impl_.revived_; + _impl_.revived_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerRevive::_internal_mutable_revived() { + + if (_impl_.revived_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.revived_ = p; + } + return _impl_.revived_; +} +inline ::rtech::liveapi::Player* PlayerRevive::mutable_revived() { + ::rtech::liveapi::Player* _msg = _internal_mutable_revived(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerRevive.revived) + return _msg; +} +inline void PlayerRevive::set_allocated_revived(::rtech::liveapi::Player* revived) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.revived_; + } + if (revived) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(revived); + if (message_arena != submessage_arena) { + revived = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, revived, submessage_arena); + } + + } else { + + } + _impl_.revived_ = revived; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerRevive.revived) +} + +// ------------------------------------------------------------------- + +// ArenasItemSelected + +// uint64 timestamp = 1; +inline void ArenasItemSelected::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t ArenasItemSelected::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t ArenasItemSelected::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ArenasItemSelected.timestamp) + return _internal_timestamp(); +} +inline void ArenasItemSelected::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void ArenasItemSelected::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.ArenasItemSelected.timestamp) +} + +// string category = 2; +inline void ArenasItemSelected::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& ArenasItemSelected::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ArenasItemSelected.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ArenasItemSelected::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.ArenasItemSelected.category) +} +inline std::string* ArenasItemSelected::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ArenasItemSelected.category) + return _s; +} +inline const std::string& ArenasItemSelected::_internal_category() const { + return _impl_.category_.Get(); +} +inline void ArenasItemSelected::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* ArenasItemSelected::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* ArenasItemSelected::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ArenasItemSelected.category) + return _impl_.category_.Release(); +} +inline void ArenasItemSelected::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ArenasItemSelected.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool ArenasItemSelected::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool ArenasItemSelected::has_player() const { + return _internal_has_player(); +} +inline void ArenasItemSelected::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& ArenasItemSelected::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& ArenasItemSelected::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ArenasItemSelected.player) + return _internal_player(); +} +inline void ArenasItemSelected::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.ArenasItemSelected.player) +} +inline ::rtech::liveapi::Player* ArenasItemSelected::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* ArenasItemSelected::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ArenasItemSelected.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* ArenasItemSelected::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* ArenasItemSelected::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ArenasItemSelected.player) + return _msg; +} +inline void ArenasItemSelected::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ArenasItemSelected.player) +} + +// string item = 4; +inline void ArenasItemSelected::clear_item() { + _impl_.item_.ClearToEmpty(); +} +inline const std::string& ArenasItemSelected::item() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ArenasItemSelected.item) + return _internal_item(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ArenasItemSelected::set_item(ArgT0&& arg0, ArgT... args) { + + _impl_.item_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.ArenasItemSelected.item) +} +inline std::string* ArenasItemSelected::mutable_item() { + std::string* _s = _internal_mutable_item(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ArenasItemSelected.item) + return _s; +} +inline const std::string& ArenasItemSelected::_internal_item() const { + return _impl_.item_.Get(); +} +inline void ArenasItemSelected::_internal_set_item(const std::string& value) { + + _impl_.item_.Set(value, GetArenaForAllocation()); +} +inline std::string* ArenasItemSelected::_internal_mutable_item() { + + return _impl_.item_.Mutable(GetArenaForAllocation()); +} +inline std::string* ArenasItemSelected::release_item() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ArenasItemSelected.item) + return _impl_.item_.Release(); +} +inline void ArenasItemSelected::set_allocated_item(std::string* item) { + if (item != nullptr) { + + } else { + + } + _impl_.item_.SetAllocated(item, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.item_.IsDefault()) { + _impl_.item_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ArenasItemSelected.item) +} + +// int32 quantity = 5; +inline void ArenasItemSelected::clear_quantity() { + _impl_.quantity_ = 0; +} +inline int32_t ArenasItemSelected::_internal_quantity() const { + return _impl_.quantity_; +} +inline int32_t ArenasItemSelected::quantity() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ArenasItemSelected.quantity) + return _internal_quantity(); +} +inline void ArenasItemSelected::_internal_set_quantity(int32_t value) { + + _impl_.quantity_ = value; +} +inline void ArenasItemSelected::set_quantity(int32_t value) { + _internal_set_quantity(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.ArenasItemSelected.quantity) +} + +// ------------------------------------------------------------------- + +// ArenasItemDeselected + +// uint64 timestamp = 1; +inline void ArenasItemDeselected::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t ArenasItemDeselected::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t ArenasItemDeselected::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ArenasItemDeselected.timestamp) + return _internal_timestamp(); +} +inline void ArenasItemDeselected::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void ArenasItemDeselected::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.ArenasItemDeselected.timestamp) +} + +// string category = 2; +inline void ArenasItemDeselected::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& ArenasItemDeselected::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ArenasItemDeselected.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ArenasItemDeselected::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.ArenasItemDeselected.category) +} +inline std::string* ArenasItemDeselected::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ArenasItemDeselected.category) + return _s; +} +inline const std::string& ArenasItemDeselected::_internal_category() const { + return _impl_.category_.Get(); +} +inline void ArenasItemDeselected::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* ArenasItemDeselected::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* ArenasItemDeselected::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ArenasItemDeselected.category) + return _impl_.category_.Release(); +} +inline void ArenasItemDeselected::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ArenasItemDeselected.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool ArenasItemDeselected::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool ArenasItemDeselected::has_player() const { + return _internal_has_player(); +} +inline void ArenasItemDeselected::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& ArenasItemDeselected::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& ArenasItemDeselected::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ArenasItemDeselected.player) + return _internal_player(); +} +inline void ArenasItemDeselected::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.ArenasItemDeselected.player) +} +inline ::rtech::liveapi::Player* ArenasItemDeselected::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* ArenasItemDeselected::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ArenasItemDeselected.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* ArenasItemDeselected::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* ArenasItemDeselected::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ArenasItemDeselected.player) + return _msg; +} +inline void ArenasItemDeselected::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ArenasItemDeselected.player) +} + +// string item = 4; +inline void ArenasItemDeselected::clear_item() { + _impl_.item_.ClearToEmpty(); +} +inline const std::string& ArenasItemDeselected::item() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ArenasItemDeselected.item) + return _internal_item(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ArenasItemDeselected::set_item(ArgT0&& arg0, ArgT... args) { + + _impl_.item_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.ArenasItemDeselected.item) +} +inline std::string* ArenasItemDeselected::mutable_item() { + std::string* _s = _internal_mutable_item(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ArenasItemDeselected.item) + return _s; +} +inline const std::string& ArenasItemDeselected::_internal_item() const { + return _impl_.item_.Get(); +} +inline void ArenasItemDeselected::_internal_set_item(const std::string& value) { + + _impl_.item_.Set(value, GetArenaForAllocation()); +} +inline std::string* ArenasItemDeselected::_internal_mutable_item() { + + return _impl_.item_.Mutable(GetArenaForAllocation()); +} +inline std::string* ArenasItemDeselected::release_item() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ArenasItemDeselected.item) + return _impl_.item_.Release(); +} +inline void ArenasItemDeselected::set_allocated_item(std::string* item) { + if (item != nullptr) { + + } else { + + } + _impl_.item_.SetAllocated(item, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.item_.IsDefault()) { + _impl_.item_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ArenasItemDeselected.item) +} + +// int32 quantity = 5; +inline void ArenasItemDeselected::clear_quantity() { + _impl_.quantity_ = 0; +} +inline int32_t ArenasItemDeselected::_internal_quantity() const { + return _impl_.quantity_; +} +inline int32_t ArenasItemDeselected::quantity() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ArenasItemDeselected.quantity) + return _internal_quantity(); +} +inline void ArenasItemDeselected::_internal_set_quantity(int32_t value) { + + _impl_.quantity_ = value; +} +inline void ArenasItemDeselected::set_quantity(int32_t value) { + _internal_set_quantity(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.ArenasItemDeselected.quantity) +} + +// ------------------------------------------------------------------- + +// InventoryPickUp + +// uint64 timestamp = 1; +inline void InventoryPickUp::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t InventoryPickUp::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t InventoryPickUp::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryPickUp.timestamp) + return _internal_timestamp(); +} +inline void InventoryPickUp::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void InventoryPickUp::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryPickUp.timestamp) +} + +// string category = 2; +inline void InventoryPickUp::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& InventoryPickUp::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryPickUp.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void InventoryPickUp::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryPickUp.category) +} +inline std::string* InventoryPickUp::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryPickUp.category) + return _s; +} +inline const std::string& InventoryPickUp::_internal_category() const { + return _impl_.category_.Get(); +} +inline void InventoryPickUp::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* InventoryPickUp::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* InventoryPickUp::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryPickUp.category) + return _impl_.category_.Release(); +} +inline void InventoryPickUp::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryPickUp.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool InventoryPickUp::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool InventoryPickUp::has_player() const { + return _internal_has_player(); +} +inline void InventoryPickUp::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& InventoryPickUp::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& InventoryPickUp::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryPickUp.player) + return _internal_player(); +} +inline void InventoryPickUp::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.InventoryPickUp.player) +} +inline ::rtech::liveapi::Player* InventoryPickUp::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* InventoryPickUp::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryPickUp.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* InventoryPickUp::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* InventoryPickUp::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryPickUp.player) + return _msg; +} +inline void InventoryPickUp::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryPickUp.player) +} + +// string item = 4; +inline void InventoryPickUp::clear_item() { + _impl_.item_.ClearToEmpty(); +} +inline const std::string& InventoryPickUp::item() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryPickUp.item) + return _internal_item(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void InventoryPickUp::set_item(ArgT0&& arg0, ArgT... args) { + + _impl_.item_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryPickUp.item) +} +inline std::string* InventoryPickUp::mutable_item() { + std::string* _s = _internal_mutable_item(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryPickUp.item) + return _s; +} +inline const std::string& InventoryPickUp::_internal_item() const { + return _impl_.item_.Get(); +} +inline void InventoryPickUp::_internal_set_item(const std::string& value) { + + _impl_.item_.Set(value, GetArenaForAllocation()); +} +inline std::string* InventoryPickUp::_internal_mutable_item() { + + return _impl_.item_.Mutable(GetArenaForAllocation()); +} +inline std::string* InventoryPickUp::release_item() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryPickUp.item) + return _impl_.item_.Release(); +} +inline void InventoryPickUp::set_allocated_item(std::string* item) { + if (item != nullptr) { + + } else { + + } + _impl_.item_.SetAllocated(item, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.item_.IsDefault()) { + _impl_.item_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryPickUp.item) +} + +// int32 quantity = 5; +inline void InventoryPickUp::clear_quantity() { + _impl_.quantity_ = 0; +} +inline int32_t InventoryPickUp::_internal_quantity() const { + return _impl_.quantity_; +} +inline int32_t InventoryPickUp::quantity() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryPickUp.quantity) + return _internal_quantity(); +} +inline void InventoryPickUp::_internal_set_quantity(int32_t value) { + + _impl_.quantity_ = value; +} +inline void InventoryPickUp::set_quantity(int32_t value) { + _internal_set_quantity(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryPickUp.quantity) +} + +// ------------------------------------------------------------------- + +// InventoryDrop + +// uint64 timestamp = 1; +inline void InventoryDrop::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t InventoryDrop::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t InventoryDrop::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryDrop.timestamp) + return _internal_timestamp(); +} +inline void InventoryDrop::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void InventoryDrop::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryDrop.timestamp) +} + +// string category = 2; +inline void InventoryDrop::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& InventoryDrop::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryDrop.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void InventoryDrop::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryDrop.category) +} +inline std::string* InventoryDrop::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryDrop.category) + return _s; +} +inline const std::string& InventoryDrop::_internal_category() const { + return _impl_.category_.Get(); +} +inline void InventoryDrop::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* InventoryDrop::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* InventoryDrop::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryDrop.category) + return _impl_.category_.Release(); +} +inline void InventoryDrop::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryDrop.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool InventoryDrop::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool InventoryDrop::has_player() const { + return _internal_has_player(); +} +inline void InventoryDrop::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& InventoryDrop::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& InventoryDrop::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryDrop.player) + return _internal_player(); +} +inline void InventoryDrop::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.InventoryDrop.player) +} +inline ::rtech::liveapi::Player* InventoryDrop::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* InventoryDrop::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryDrop.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* InventoryDrop::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* InventoryDrop::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryDrop.player) + return _msg; +} +inline void InventoryDrop::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryDrop.player) +} + +// string item = 4; +inline void InventoryDrop::clear_item() { + _impl_.item_.ClearToEmpty(); +} +inline const std::string& InventoryDrop::item() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryDrop.item) + return _internal_item(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void InventoryDrop::set_item(ArgT0&& arg0, ArgT... args) { + + _impl_.item_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryDrop.item) +} +inline std::string* InventoryDrop::mutable_item() { + std::string* _s = _internal_mutable_item(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryDrop.item) + return _s; +} +inline const std::string& InventoryDrop::_internal_item() const { + return _impl_.item_.Get(); +} +inline void InventoryDrop::_internal_set_item(const std::string& value) { + + _impl_.item_.Set(value, GetArenaForAllocation()); +} +inline std::string* InventoryDrop::_internal_mutable_item() { + + return _impl_.item_.Mutable(GetArenaForAllocation()); +} +inline std::string* InventoryDrop::release_item() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryDrop.item) + return _impl_.item_.Release(); +} +inline void InventoryDrop::set_allocated_item(std::string* item) { + if (item != nullptr) { + + } else { + + } + _impl_.item_.SetAllocated(item, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.item_.IsDefault()) { + _impl_.item_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryDrop.item) +} + +// int32 quantity = 5; +inline void InventoryDrop::clear_quantity() { + _impl_.quantity_ = 0; +} +inline int32_t InventoryDrop::_internal_quantity() const { + return _impl_.quantity_; +} +inline int32_t InventoryDrop::quantity() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryDrop.quantity) + return _internal_quantity(); +} +inline void InventoryDrop::_internal_set_quantity(int32_t value) { + + _impl_.quantity_ = value; +} +inline void InventoryDrop::set_quantity(int32_t value) { + _internal_set_quantity(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryDrop.quantity) +} + +// repeated string extraData = 6; +inline int InventoryDrop::_internal_extradata_size() const { + return _impl_.extradata_.size(); +} +inline int InventoryDrop::extradata_size() const { + return _internal_extradata_size(); +} +inline void InventoryDrop::clear_extradata() { + _impl_.extradata_.Clear(); +} +inline std::string* InventoryDrop::add_extradata() { + std::string* _s = _internal_add_extradata(); + // @@protoc_insertion_point(field_add_mutable:rtech.liveapi.InventoryDrop.extraData) + return _s; +} +inline const std::string& InventoryDrop::_internal_extradata(int index) const { + return _impl_.extradata_.Get(index); +} +inline const std::string& InventoryDrop::extradata(int index) const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryDrop.extraData) + return _internal_extradata(index); +} +inline std::string* InventoryDrop::mutable_extradata(int index) { + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryDrop.extraData) + return _impl_.extradata_.Mutable(index); +} +inline void InventoryDrop::set_extradata(int index, const std::string& value) { + _impl_.extradata_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryDrop.extraData) +} +inline void InventoryDrop::set_extradata(int index, std::string&& value) { + _impl_.extradata_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryDrop.extraData) +} +inline void InventoryDrop::set_extradata(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.extradata_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:rtech.liveapi.InventoryDrop.extraData) +} +inline void InventoryDrop::set_extradata(int index, const char* value, size_t size) { + _impl_.extradata_.Mutable(index)->assign( + reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:rtech.liveapi.InventoryDrop.extraData) +} +inline std::string* InventoryDrop::_internal_add_extradata() { + return _impl_.extradata_.Add(); +} +inline void InventoryDrop::add_extradata(const std::string& value) { + _impl_.extradata_.Add()->assign(value); + // @@protoc_insertion_point(field_add:rtech.liveapi.InventoryDrop.extraData) +} +inline void InventoryDrop::add_extradata(std::string&& value) { + _impl_.extradata_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:rtech.liveapi.InventoryDrop.extraData) +} +inline void InventoryDrop::add_extradata(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.extradata_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:rtech.liveapi.InventoryDrop.extraData) +} +inline void InventoryDrop::add_extradata(const char* value, size_t size) { + _impl_.extradata_.Add()->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_add_pointer:rtech.liveapi.InventoryDrop.extraData) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& +InventoryDrop::extradata() const { + // @@protoc_insertion_point(field_list:rtech.liveapi.InventoryDrop.extraData) + return _impl_.extradata_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* +InventoryDrop::mutable_extradata() { + // @@protoc_insertion_point(field_mutable_list:rtech.liveapi.InventoryDrop.extraData) + return &_impl_.extradata_; +} + +// ------------------------------------------------------------------- + +// InventoryUse + +// uint64 timestamp = 1; +inline void InventoryUse::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t InventoryUse::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t InventoryUse::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryUse.timestamp) + return _internal_timestamp(); +} +inline void InventoryUse::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void InventoryUse::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryUse.timestamp) +} + +// string category = 2; +inline void InventoryUse::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& InventoryUse::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryUse.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void InventoryUse::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryUse.category) +} +inline std::string* InventoryUse::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryUse.category) + return _s; +} +inline const std::string& InventoryUse::_internal_category() const { + return _impl_.category_.Get(); +} +inline void InventoryUse::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* InventoryUse::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* InventoryUse::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryUse.category) + return _impl_.category_.Release(); +} +inline void InventoryUse::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryUse.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool InventoryUse::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool InventoryUse::has_player() const { + return _internal_has_player(); +} +inline void InventoryUse::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& InventoryUse::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& InventoryUse::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryUse.player) + return _internal_player(); +} +inline void InventoryUse::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.InventoryUse.player) +} +inline ::rtech::liveapi::Player* InventoryUse::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* InventoryUse::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryUse.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* InventoryUse::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* InventoryUse::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryUse.player) + return _msg; +} +inline void InventoryUse::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryUse.player) +} + +// string item = 4; +inline void InventoryUse::clear_item() { + _impl_.item_.ClearToEmpty(); +} +inline const std::string& InventoryUse::item() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryUse.item) + return _internal_item(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void InventoryUse::set_item(ArgT0&& arg0, ArgT... args) { + + _impl_.item_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryUse.item) +} +inline std::string* InventoryUse::mutable_item() { + std::string* _s = _internal_mutable_item(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.InventoryUse.item) + return _s; +} +inline const std::string& InventoryUse::_internal_item() const { + return _impl_.item_.Get(); +} +inline void InventoryUse::_internal_set_item(const std::string& value) { + + _impl_.item_.Set(value, GetArenaForAllocation()); +} +inline std::string* InventoryUse::_internal_mutable_item() { + + return _impl_.item_.Mutable(GetArenaForAllocation()); +} +inline std::string* InventoryUse::release_item() { + // @@protoc_insertion_point(field_release:rtech.liveapi.InventoryUse.item) + return _impl_.item_.Release(); +} +inline void InventoryUse::set_allocated_item(std::string* item) { + if (item != nullptr) { + + } else { + + } + _impl_.item_.SetAllocated(item, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.item_.IsDefault()) { + _impl_.item_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.InventoryUse.item) +} + +// int32 quantity = 5; +inline void InventoryUse::clear_quantity() { + _impl_.quantity_ = 0; +} +inline int32_t InventoryUse::_internal_quantity() const { + return _impl_.quantity_; +} +inline int32_t InventoryUse::quantity() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.InventoryUse.quantity) + return _internal_quantity(); +} +inline void InventoryUse::_internal_set_quantity(int32_t value) { + + _impl_.quantity_ = value; +} +inline void InventoryUse::set_quantity(int32_t value) { + _internal_set_quantity(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.InventoryUse.quantity) +} + +// ------------------------------------------------------------------- + +// BannerCollected + +// uint64 timestamp = 1; +inline void BannerCollected::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t BannerCollected::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t BannerCollected::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.BannerCollected.timestamp) + return _internal_timestamp(); +} +inline void BannerCollected::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void BannerCollected::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.BannerCollected.timestamp) +} + +// string category = 2; +inline void BannerCollected::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& BannerCollected::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.BannerCollected.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void BannerCollected::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.BannerCollected.category) +} +inline std::string* BannerCollected::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.BannerCollected.category) + return _s; +} +inline const std::string& BannerCollected::_internal_category() const { + return _impl_.category_.Get(); +} +inline void BannerCollected::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* BannerCollected::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* BannerCollected::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.BannerCollected.category) + return _impl_.category_.Release(); +} +inline void BannerCollected::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.BannerCollected.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool BannerCollected::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool BannerCollected::has_player() const { + return _internal_has_player(); +} +inline void BannerCollected::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& BannerCollected::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& BannerCollected::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.BannerCollected.player) + return _internal_player(); +} +inline void BannerCollected::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.BannerCollected.player) +} +inline ::rtech::liveapi::Player* BannerCollected::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* BannerCollected::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.BannerCollected.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* BannerCollected::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* BannerCollected::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.BannerCollected.player) + return _msg; +} +inline void BannerCollected::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.BannerCollected.player) +} + +// .rtech.liveapi.Player collected = 4; +inline bool BannerCollected::_internal_has_collected() const { + return this != internal_default_instance() && _impl_.collected_ != nullptr; +} +inline bool BannerCollected::has_collected() const { + return _internal_has_collected(); +} +inline void BannerCollected::clear_collected() { + if (GetArenaForAllocation() == nullptr && _impl_.collected_ != nullptr) { + delete _impl_.collected_; + } + _impl_.collected_ = nullptr; +} +inline const ::rtech::liveapi::Player& BannerCollected::_internal_collected() const { + const ::rtech::liveapi::Player* p = _impl_.collected_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& BannerCollected::collected() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.BannerCollected.collected) + return _internal_collected(); +} +inline void BannerCollected::unsafe_arena_set_allocated_collected( + ::rtech::liveapi::Player* collected) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.collected_); + } + _impl_.collected_ = collected; + if (collected) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.BannerCollected.collected) +} +inline ::rtech::liveapi::Player* BannerCollected::release_collected() { + + ::rtech::liveapi::Player* temp = _impl_.collected_; + _impl_.collected_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* BannerCollected::unsafe_arena_release_collected() { + // @@protoc_insertion_point(field_release:rtech.liveapi.BannerCollected.collected) + + ::rtech::liveapi::Player* temp = _impl_.collected_; + _impl_.collected_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* BannerCollected::_internal_mutable_collected() { + + if (_impl_.collected_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.collected_ = p; + } + return _impl_.collected_; +} +inline ::rtech::liveapi::Player* BannerCollected::mutable_collected() { + ::rtech::liveapi::Player* _msg = _internal_mutable_collected(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.BannerCollected.collected) + return _msg; +} +inline void BannerCollected::set_allocated_collected(::rtech::liveapi::Player* collected) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.collected_; + } + if (collected) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(collected); + if (message_arena != submessage_arena) { + collected = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, collected, submessage_arena); + } + + } else { + + } + _impl_.collected_ = collected; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.BannerCollected.collected) +} + +// ------------------------------------------------------------------- + +// PlayerAbilityUsed + +// uint64 timestamp = 1; +inline void PlayerAbilityUsed::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t PlayerAbilityUsed::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t PlayerAbilityUsed::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerAbilityUsed.timestamp) + return _internal_timestamp(); +} +inline void PlayerAbilityUsed::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void PlayerAbilityUsed::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerAbilityUsed.timestamp) +} + +// string category = 2; +inline void PlayerAbilityUsed::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& PlayerAbilityUsed::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerAbilityUsed.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerAbilityUsed::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerAbilityUsed.category) +} +inline std::string* PlayerAbilityUsed::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerAbilityUsed.category) + return _s; +} +inline const std::string& PlayerAbilityUsed::_internal_category() const { + return _impl_.category_.Get(); +} +inline void PlayerAbilityUsed::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerAbilityUsed::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerAbilityUsed::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerAbilityUsed.category) + return _impl_.category_.Release(); +} +inline void PlayerAbilityUsed::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerAbilityUsed.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool PlayerAbilityUsed::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool PlayerAbilityUsed::has_player() const { + return _internal_has_player(); +} +inline void PlayerAbilityUsed::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& PlayerAbilityUsed::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& PlayerAbilityUsed::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerAbilityUsed.player) + return _internal_player(); +} +inline void PlayerAbilityUsed::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.PlayerAbilityUsed.player) +} +inline ::rtech::liveapi::Player* PlayerAbilityUsed::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* PlayerAbilityUsed::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerAbilityUsed.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* PlayerAbilityUsed::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* PlayerAbilityUsed::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerAbilityUsed.player) + return _msg; +} +inline void PlayerAbilityUsed::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerAbilityUsed.player) +} + +// string linkedEntity = 4; +inline void PlayerAbilityUsed::clear_linkedentity() { + _impl_.linkedentity_.ClearToEmpty(); +} +inline const std::string& PlayerAbilityUsed::linkedentity() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PlayerAbilityUsed.linkedEntity) + return _internal_linkedentity(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void PlayerAbilityUsed::set_linkedentity(ArgT0&& arg0, ArgT... args) { + + _impl_.linkedentity_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.PlayerAbilityUsed.linkedEntity) +} +inline std::string* PlayerAbilityUsed::mutable_linkedentity() { + std::string* _s = _internal_mutable_linkedentity(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.PlayerAbilityUsed.linkedEntity) + return _s; +} +inline const std::string& PlayerAbilityUsed::_internal_linkedentity() const { + return _impl_.linkedentity_.Get(); +} +inline void PlayerAbilityUsed::_internal_set_linkedentity(const std::string& value) { + + _impl_.linkedentity_.Set(value, GetArenaForAllocation()); +} +inline std::string* PlayerAbilityUsed::_internal_mutable_linkedentity() { + + return _impl_.linkedentity_.Mutable(GetArenaForAllocation()); +} +inline std::string* PlayerAbilityUsed::release_linkedentity() { + // @@protoc_insertion_point(field_release:rtech.liveapi.PlayerAbilityUsed.linkedEntity) + return _impl_.linkedentity_.Release(); +} +inline void PlayerAbilityUsed::set_allocated_linkedentity(std::string* linkedentity) { + if (linkedentity != nullptr) { + + } else { + + } + _impl_.linkedentity_.SetAllocated(linkedentity, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.linkedentity_.IsDefault()) { + _impl_.linkedentity_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.PlayerAbilityUsed.linkedEntity) +} + +// ------------------------------------------------------------------- + +// LegendUpgradeSelected + +// uint64 timestamp = 1; +inline void LegendUpgradeSelected::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t LegendUpgradeSelected::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t LegendUpgradeSelected::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.LegendUpgradeSelected.timestamp) + return _internal_timestamp(); +} +inline void LegendUpgradeSelected::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void LegendUpgradeSelected::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.LegendUpgradeSelected.timestamp) +} + +// string category = 2; +inline void LegendUpgradeSelected::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& LegendUpgradeSelected::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.LegendUpgradeSelected.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void LegendUpgradeSelected::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.LegendUpgradeSelected.category) +} +inline std::string* LegendUpgradeSelected::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.LegendUpgradeSelected.category) + return _s; +} +inline const std::string& LegendUpgradeSelected::_internal_category() const { + return _impl_.category_.Get(); +} +inline void LegendUpgradeSelected::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* LegendUpgradeSelected::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* LegendUpgradeSelected::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.LegendUpgradeSelected.category) + return _impl_.category_.Release(); +} +inline void LegendUpgradeSelected::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.LegendUpgradeSelected.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool LegendUpgradeSelected::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool LegendUpgradeSelected::has_player() const { + return _internal_has_player(); +} +inline void LegendUpgradeSelected::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& LegendUpgradeSelected::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& LegendUpgradeSelected::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.LegendUpgradeSelected.player) + return _internal_player(); +} +inline void LegendUpgradeSelected::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.LegendUpgradeSelected.player) +} +inline ::rtech::liveapi::Player* LegendUpgradeSelected::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* LegendUpgradeSelected::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.LegendUpgradeSelected.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* LegendUpgradeSelected::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* LegendUpgradeSelected::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.LegendUpgradeSelected.player) + return _msg; +} +inline void LegendUpgradeSelected::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.LegendUpgradeSelected.player) +} + +// string upgradeName = 4; +inline void LegendUpgradeSelected::clear_upgradename() { + _impl_.upgradename_.ClearToEmpty(); +} +inline const std::string& LegendUpgradeSelected::upgradename() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.LegendUpgradeSelected.upgradeName) + return _internal_upgradename(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void LegendUpgradeSelected::set_upgradename(ArgT0&& arg0, ArgT... args) { + + _impl_.upgradename_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.LegendUpgradeSelected.upgradeName) +} +inline std::string* LegendUpgradeSelected::mutable_upgradename() { + std::string* _s = _internal_mutable_upgradename(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.LegendUpgradeSelected.upgradeName) + return _s; +} +inline const std::string& LegendUpgradeSelected::_internal_upgradename() const { + return _impl_.upgradename_.Get(); +} +inline void LegendUpgradeSelected::_internal_set_upgradename(const std::string& value) { + + _impl_.upgradename_.Set(value, GetArenaForAllocation()); +} +inline std::string* LegendUpgradeSelected::_internal_mutable_upgradename() { + + return _impl_.upgradename_.Mutable(GetArenaForAllocation()); +} +inline std::string* LegendUpgradeSelected::release_upgradename() { + // @@protoc_insertion_point(field_release:rtech.liveapi.LegendUpgradeSelected.upgradeName) + return _impl_.upgradename_.Release(); +} +inline void LegendUpgradeSelected::set_allocated_upgradename(std::string* upgradename) { + if (upgradename != nullptr) { + + } else { + + } + _impl_.upgradename_.SetAllocated(upgradename, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.upgradename_.IsDefault()) { + _impl_.upgradename_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.LegendUpgradeSelected.upgradeName) +} + +// string upgradeDesc = 5; +inline void LegendUpgradeSelected::clear_upgradedesc() { + _impl_.upgradedesc_.ClearToEmpty(); +} +inline const std::string& LegendUpgradeSelected::upgradedesc() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.LegendUpgradeSelected.upgradeDesc) + return _internal_upgradedesc(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void LegendUpgradeSelected::set_upgradedesc(ArgT0&& arg0, ArgT... args) { + + _impl_.upgradedesc_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.LegendUpgradeSelected.upgradeDesc) +} +inline std::string* LegendUpgradeSelected::mutable_upgradedesc() { + std::string* _s = _internal_mutable_upgradedesc(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.LegendUpgradeSelected.upgradeDesc) + return _s; +} +inline const std::string& LegendUpgradeSelected::_internal_upgradedesc() const { + return _impl_.upgradedesc_.Get(); +} +inline void LegendUpgradeSelected::_internal_set_upgradedesc(const std::string& value) { + + _impl_.upgradedesc_.Set(value, GetArenaForAllocation()); +} +inline std::string* LegendUpgradeSelected::_internal_mutable_upgradedesc() { + + return _impl_.upgradedesc_.Mutable(GetArenaForAllocation()); +} +inline std::string* LegendUpgradeSelected::release_upgradedesc() { + // @@protoc_insertion_point(field_release:rtech.liveapi.LegendUpgradeSelected.upgradeDesc) + return _impl_.upgradedesc_.Release(); +} +inline void LegendUpgradeSelected::set_allocated_upgradedesc(std::string* upgradedesc) { + if (upgradedesc != nullptr) { + + } else { + + } + _impl_.upgradedesc_.SetAllocated(upgradedesc, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.upgradedesc_.IsDefault()) { + _impl_.upgradedesc_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.LegendUpgradeSelected.upgradeDesc) +} + +// int32 level = 6; +inline void LegendUpgradeSelected::clear_level() { + _impl_.level_ = 0; +} +inline int32_t LegendUpgradeSelected::_internal_level() const { + return _impl_.level_; +} +inline int32_t LegendUpgradeSelected::level() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.LegendUpgradeSelected.level) + return _internal_level(); +} +inline void LegendUpgradeSelected::_internal_set_level(int32_t value) { + + _impl_.level_ = value; +} +inline void LegendUpgradeSelected::set_level(int32_t value) { + _internal_set_level(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.LegendUpgradeSelected.level) +} + +// ------------------------------------------------------------------- + +// ZiplineUsed + +// uint64 timestamp = 1; +inline void ZiplineUsed::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t ZiplineUsed::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t ZiplineUsed::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ZiplineUsed.timestamp) + return _internal_timestamp(); +} +inline void ZiplineUsed::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void ZiplineUsed::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.ZiplineUsed.timestamp) +} + +// string category = 2; +inline void ZiplineUsed::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& ZiplineUsed::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ZiplineUsed.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ZiplineUsed::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.ZiplineUsed.category) +} +inline std::string* ZiplineUsed::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ZiplineUsed.category) + return _s; +} +inline const std::string& ZiplineUsed::_internal_category() const { + return _impl_.category_.Get(); +} +inline void ZiplineUsed::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* ZiplineUsed::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* ZiplineUsed::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ZiplineUsed.category) + return _impl_.category_.Release(); +} +inline void ZiplineUsed::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ZiplineUsed.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool ZiplineUsed::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool ZiplineUsed::has_player() const { + return _internal_has_player(); +} +inline void ZiplineUsed::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& ZiplineUsed::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& ZiplineUsed::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ZiplineUsed.player) + return _internal_player(); +} +inline void ZiplineUsed::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.ZiplineUsed.player) +} +inline ::rtech::liveapi::Player* ZiplineUsed::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* ZiplineUsed::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ZiplineUsed.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* ZiplineUsed::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* ZiplineUsed::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ZiplineUsed.player) + return _msg; +} +inline void ZiplineUsed::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ZiplineUsed.player) +} + +// string linkedEntity = 4; +inline void ZiplineUsed::clear_linkedentity() { + _impl_.linkedentity_.ClearToEmpty(); +} +inline const std::string& ZiplineUsed::linkedentity() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ZiplineUsed.linkedEntity) + return _internal_linkedentity(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ZiplineUsed::set_linkedentity(ArgT0&& arg0, ArgT... args) { + + _impl_.linkedentity_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.ZiplineUsed.linkedEntity) +} +inline std::string* ZiplineUsed::mutable_linkedentity() { + std::string* _s = _internal_mutable_linkedentity(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ZiplineUsed.linkedEntity) + return _s; +} +inline const std::string& ZiplineUsed::_internal_linkedentity() const { + return _impl_.linkedentity_.Get(); +} +inline void ZiplineUsed::_internal_set_linkedentity(const std::string& value) { + + _impl_.linkedentity_.Set(value, GetArenaForAllocation()); +} +inline std::string* ZiplineUsed::_internal_mutable_linkedentity() { + + return _impl_.linkedentity_.Mutable(GetArenaForAllocation()); +} +inline std::string* ZiplineUsed::release_linkedentity() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ZiplineUsed.linkedEntity) + return _impl_.linkedentity_.Release(); +} +inline void ZiplineUsed::set_allocated_linkedentity(std::string* linkedentity) { + if (linkedentity != nullptr) { + + } else { + + } + _impl_.linkedentity_.SetAllocated(linkedentity, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.linkedentity_.IsDefault()) { + _impl_.linkedentity_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ZiplineUsed.linkedEntity) +} + +// ------------------------------------------------------------------- + +// GrenadeThrown + +// uint64 timestamp = 1; +inline void GrenadeThrown::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t GrenadeThrown::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t GrenadeThrown::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GrenadeThrown.timestamp) + return _internal_timestamp(); +} +inline void GrenadeThrown::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void GrenadeThrown::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.GrenadeThrown.timestamp) +} + +// string category = 2; +inline void GrenadeThrown::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& GrenadeThrown::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GrenadeThrown.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void GrenadeThrown::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.GrenadeThrown.category) +} +inline std::string* GrenadeThrown::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.GrenadeThrown.category) + return _s; +} +inline const std::string& GrenadeThrown::_internal_category() const { + return _impl_.category_.Get(); +} +inline void GrenadeThrown::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* GrenadeThrown::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* GrenadeThrown::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.GrenadeThrown.category) + return _impl_.category_.Release(); +} +inline void GrenadeThrown::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.GrenadeThrown.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool GrenadeThrown::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool GrenadeThrown::has_player() const { + return _internal_has_player(); +} +inline void GrenadeThrown::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& GrenadeThrown::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& GrenadeThrown::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GrenadeThrown.player) + return _internal_player(); +} +inline void GrenadeThrown::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.GrenadeThrown.player) +} +inline ::rtech::liveapi::Player* GrenadeThrown::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* GrenadeThrown::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.GrenadeThrown.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* GrenadeThrown::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* GrenadeThrown::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.GrenadeThrown.player) + return _msg; +} +inline void GrenadeThrown::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.GrenadeThrown.player) +} + +// string linkedEntity = 4; +inline void GrenadeThrown::clear_linkedentity() { + _impl_.linkedentity_.ClearToEmpty(); +} +inline const std::string& GrenadeThrown::linkedentity() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.GrenadeThrown.linkedEntity) + return _internal_linkedentity(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void GrenadeThrown::set_linkedentity(ArgT0&& arg0, ArgT... args) { + + _impl_.linkedentity_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.GrenadeThrown.linkedEntity) +} +inline std::string* GrenadeThrown::mutable_linkedentity() { + std::string* _s = _internal_mutable_linkedentity(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.GrenadeThrown.linkedEntity) + return _s; +} +inline const std::string& GrenadeThrown::_internal_linkedentity() const { + return _impl_.linkedentity_.Get(); +} +inline void GrenadeThrown::_internal_set_linkedentity(const std::string& value) { + + _impl_.linkedentity_.Set(value, GetArenaForAllocation()); +} +inline std::string* GrenadeThrown::_internal_mutable_linkedentity() { + + return _impl_.linkedentity_.Mutable(GetArenaForAllocation()); +} +inline std::string* GrenadeThrown::release_linkedentity() { + // @@protoc_insertion_point(field_release:rtech.liveapi.GrenadeThrown.linkedEntity) + return _impl_.linkedentity_.Release(); +} +inline void GrenadeThrown::set_allocated_linkedentity(std::string* linkedentity) { + if (linkedentity != nullptr) { + + } else { + + } + _impl_.linkedentity_.SetAllocated(linkedentity, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.linkedentity_.IsDefault()) { + _impl_.linkedentity_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.GrenadeThrown.linkedEntity) +} + +// ------------------------------------------------------------------- + +// BlackMarketAction + +// uint64 timestamp = 1; +inline void BlackMarketAction::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t BlackMarketAction::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t BlackMarketAction::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.BlackMarketAction.timestamp) + return _internal_timestamp(); +} +inline void BlackMarketAction::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void BlackMarketAction::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.BlackMarketAction.timestamp) +} + +// string category = 2; +inline void BlackMarketAction::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& BlackMarketAction::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.BlackMarketAction.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void BlackMarketAction::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.BlackMarketAction.category) +} +inline std::string* BlackMarketAction::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.BlackMarketAction.category) + return _s; +} +inline const std::string& BlackMarketAction::_internal_category() const { + return _impl_.category_.Get(); +} +inline void BlackMarketAction::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* BlackMarketAction::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* BlackMarketAction::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.BlackMarketAction.category) + return _impl_.category_.Release(); +} +inline void BlackMarketAction::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.BlackMarketAction.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool BlackMarketAction::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool BlackMarketAction::has_player() const { + return _internal_has_player(); +} +inline void BlackMarketAction::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& BlackMarketAction::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& BlackMarketAction::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.BlackMarketAction.player) + return _internal_player(); +} +inline void BlackMarketAction::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.BlackMarketAction.player) +} +inline ::rtech::liveapi::Player* BlackMarketAction::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* BlackMarketAction::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.BlackMarketAction.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* BlackMarketAction::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* BlackMarketAction::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.BlackMarketAction.player) + return _msg; +} +inline void BlackMarketAction::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.BlackMarketAction.player) +} + +// string item = 4; +inline void BlackMarketAction::clear_item() { + _impl_.item_.ClearToEmpty(); +} +inline const std::string& BlackMarketAction::item() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.BlackMarketAction.item) + return _internal_item(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void BlackMarketAction::set_item(ArgT0&& arg0, ArgT... args) { + + _impl_.item_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.BlackMarketAction.item) +} +inline std::string* BlackMarketAction::mutable_item() { + std::string* _s = _internal_mutable_item(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.BlackMarketAction.item) + return _s; +} +inline const std::string& BlackMarketAction::_internal_item() const { + return _impl_.item_.Get(); +} +inline void BlackMarketAction::_internal_set_item(const std::string& value) { + + _impl_.item_.Set(value, GetArenaForAllocation()); +} +inline std::string* BlackMarketAction::_internal_mutable_item() { + + return _impl_.item_.Mutable(GetArenaForAllocation()); +} +inline std::string* BlackMarketAction::release_item() { + // @@protoc_insertion_point(field_release:rtech.liveapi.BlackMarketAction.item) + return _impl_.item_.Release(); +} +inline void BlackMarketAction::set_allocated_item(std::string* item) { + if (item != nullptr) { + + } else { + + } + _impl_.item_.SetAllocated(item, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.item_.IsDefault()) { + _impl_.item_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.BlackMarketAction.item) +} + +// ------------------------------------------------------------------- + +// WraithPortal + +// uint64 timestamp = 1; +inline void WraithPortal::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t WraithPortal::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t WraithPortal::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WraithPortal.timestamp) + return _internal_timestamp(); +} +inline void WraithPortal::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void WraithPortal::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.WraithPortal.timestamp) +} + +// string category = 2; +inline void WraithPortal::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& WraithPortal::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WraithPortal.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void WraithPortal::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.WraithPortal.category) +} +inline std::string* WraithPortal::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.WraithPortal.category) + return _s; +} +inline const std::string& WraithPortal::_internal_category() const { + return _impl_.category_.Get(); +} +inline void WraithPortal::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* WraithPortal::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* WraithPortal::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.WraithPortal.category) + return _impl_.category_.Release(); +} +inline void WraithPortal::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.WraithPortal.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool WraithPortal::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool WraithPortal::has_player() const { + return _internal_has_player(); +} +inline void WraithPortal::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& WraithPortal::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& WraithPortal::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WraithPortal.player) + return _internal_player(); +} +inline void WraithPortal::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.WraithPortal.player) +} +inline ::rtech::liveapi::Player* WraithPortal::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* WraithPortal::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.WraithPortal.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* WraithPortal::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* WraithPortal::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.WraithPortal.player) + return _msg; +} +inline void WraithPortal::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.WraithPortal.player) +} + +// ------------------------------------------------------------------- + +// WarpGateUsed + +// uint64 timestamp = 1; +inline void WarpGateUsed::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t WarpGateUsed::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t WarpGateUsed::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WarpGateUsed.timestamp) + return _internal_timestamp(); +} +inline void WarpGateUsed::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void WarpGateUsed::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.WarpGateUsed.timestamp) +} + +// string category = 2; +inline void WarpGateUsed::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& WarpGateUsed::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WarpGateUsed.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void WarpGateUsed::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.WarpGateUsed.category) +} +inline std::string* WarpGateUsed::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.WarpGateUsed.category) + return _s; +} +inline const std::string& WarpGateUsed::_internal_category() const { + return _impl_.category_.Get(); +} +inline void WarpGateUsed::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* WarpGateUsed::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* WarpGateUsed::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.WarpGateUsed.category) + return _impl_.category_.Release(); +} +inline void WarpGateUsed::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.WarpGateUsed.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool WarpGateUsed::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool WarpGateUsed::has_player() const { + return _internal_has_player(); +} +inline void WarpGateUsed::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& WarpGateUsed::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& WarpGateUsed::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WarpGateUsed.player) + return _internal_player(); +} +inline void WarpGateUsed::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.WarpGateUsed.player) +} +inline ::rtech::liveapi::Player* WarpGateUsed::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* WarpGateUsed::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.WarpGateUsed.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* WarpGateUsed::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* WarpGateUsed::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.WarpGateUsed.player) + return _msg; +} +inline void WarpGateUsed::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.WarpGateUsed.player) +} + +// ------------------------------------------------------------------- + +// AmmoUsed + +// uint64 timestamp = 1; +inline void AmmoUsed::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t AmmoUsed::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t AmmoUsed::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.AmmoUsed.timestamp) + return _internal_timestamp(); +} +inline void AmmoUsed::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void AmmoUsed::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.AmmoUsed.timestamp) +} + +// string category = 2; +inline void AmmoUsed::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& AmmoUsed::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.AmmoUsed.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void AmmoUsed::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.AmmoUsed.category) +} +inline std::string* AmmoUsed::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.AmmoUsed.category) + return _s; +} +inline const std::string& AmmoUsed::_internal_category() const { + return _impl_.category_.Get(); +} +inline void AmmoUsed::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* AmmoUsed::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* AmmoUsed::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.AmmoUsed.category) + return _impl_.category_.Release(); +} +inline void AmmoUsed::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.AmmoUsed.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool AmmoUsed::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool AmmoUsed::has_player() const { + return _internal_has_player(); +} +inline void AmmoUsed::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& AmmoUsed::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& AmmoUsed::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.AmmoUsed.player) + return _internal_player(); +} +inline void AmmoUsed::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.AmmoUsed.player) +} +inline ::rtech::liveapi::Player* AmmoUsed::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* AmmoUsed::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.AmmoUsed.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* AmmoUsed::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* AmmoUsed::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.AmmoUsed.player) + return _msg; +} +inline void AmmoUsed::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.AmmoUsed.player) +} + +// string ammoType = 4; +inline void AmmoUsed::clear_ammotype() { + _impl_.ammotype_.ClearToEmpty(); +} +inline const std::string& AmmoUsed::ammotype() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.AmmoUsed.ammoType) + return _internal_ammotype(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void AmmoUsed::set_ammotype(ArgT0&& arg0, ArgT... args) { + + _impl_.ammotype_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.AmmoUsed.ammoType) +} +inline std::string* AmmoUsed::mutable_ammotype() { + std::string* _s = _internal_mutable_ammotype(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.AmmoUsed.ammoType) + return _s; +} +inline const std::string& AmmoUsed::_internal_ammotype() const { + return _impl_.ammotype_.Get(); +} +inline void AmmoUsed::_internal_set_ammotype(const std::string& value) { + + _impl_.ammotype_.Set(value, GetArenaForAllocation()); +} +inline std::string* AmmoUsed::_internal_mutable_ammotype() { + + return _impl_.ammotype_.Mutable(GetArenaForAllocation()); +} +inline std::string* AmmoUsed::release_ammotype() { + // @@protoc_insertion_point(field_release:rtech.liveapi.AmmoUsed.ammoType) + return _impl_.ammotype_.Release(); +} +inline void AmmoUsed::set_allocated_ammotype(std::string* ammotype) { + if (ammotype != nullptr) { + + } else { + + } + _impl_.ammotype_.SetAllocated(ammotype, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.ammotype_.IsDefault()) { + _impl_.ammotype_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.AmmoUsed.ammoType) +} + +// uint32 amountUsed = 5; +inline void AmmoUsed::clear_amountused() { + _impl_.amountused_ = 0u; +} +inline uint32_t AmmoUsed::_internal_amountused() const { + return _impl_.amountused_; +} +inline uint32_t AmmoUsed::amountused() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.AmmoUsed.amountUsed) + return _internal_amountused(); +} +inline void AmmoUsed::_internal_set_amountused(uint32_t value) { + + _impl_.amountused_ = value; +} +inline void AmmoUsed::set_amountused(uint32_t value) { + _internal_set_amountused(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.AmmoUsed.amountUsed) +} + +// uint32 oldAmmoCount = 6; +inline void AmmoUsed::clear_oldammocount() { + _impl_.oldammocount_ = 0u; +} +inline uint32_t AmmoUsed::_internal_oldammocount() const { + return _impl_.oldammocount_; +} +inline uint32_t AmmoUsed::oldammocount() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.AmmoUsed.oldAmmoCount) + return _internal_oldammocount(); +} +inline void AmmoUsed::_internal_set_oldammocount(uint32_t value) { + + _impl_.oldammocount_ = value; +} +inline void AmmoUsed::set_oldammocount(uint32_t value) { + _internal_set_oldammocount(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.AmmoUsed.oldAmmoCount) +} + +// uint32 newAmmoCount = 7; +inline void AmmoUsed::clear_newammocount() { + _impl_.newammocount_ = 0u; +} +inline uint32_t AmmoUsed::_internal_newammocount() const { + return _impl_.newammocount_; +} +inline uint32_t AmmoUsed::newammocount() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.AmmoUsed.newAmmoCount) + return _internal_newammocount(); +} +inline void AmmoUsed::_internal_set_newammocount(uint32_t value) { + + _impl_.newammocount_ = value; +} +inline void AmmoUsed::set_newammocount(uint32_t value) { + _internal_set_newammocount(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.AmmoUsed.newAmmoCount) +} + +// ------------------------------------------------------------------- + +// WeaponSwitched + +// uint64 timestamp = 1; +inline void WeaponSwitched::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t WeaponSwitched::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t WeaponSwitched::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WeaponSwitched.timestamp) + return _internal_timestamp(); +} +inline void WeaponSwitched::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void WeaponSwitched::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.WeaponSwitched.timestamp) +} + +// string category = 2; +inline void WeaponSwitched::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& WeaponSwitched::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WeaponSwitched.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void WeaponSwitched::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.WeaponSwitched.category) +} +inline std::string* WeaponSwitched::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.WeaponSwitched.category) + return _s; +} +inline const std::string& WeaponSwitched::_internal_category() const { + return _impl_.category_.Get(); +} +inline void WeaponSwitched::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* WeaponSwitched::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* WeaponSwitched::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.WeaponSwitched.category) + return _impl_.category_.Release(); +} +inline void WeaponSwitched::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.WeaponSwitched.category) +} + +// .rtech.liveapi.Player player = 3; +inline bool WeaponSwitched::_internal_has_player() const { + return this != internal_default_instance() && _impl_.player_ != nullptr; +} +inline bool WeaponSwitched::has_player() const { + return _internal_has_player(); +} +inline void WeaponSwitched::clear_player() { + if (GetArenaForAllocation() == nullptr && _impl_.player_ != nullptr) { + delete _impl_.player_; + } + _impl_.player_ = nullptr; +} +inline const ::rtech::liveapi::Player& WeaponSwitched::_internal_player() const { + const ::rtech::liveapi::Player* p = _impl_.player_; + return p != nullptr ? *p : reinterpret_cast( + ::rtech::liveapi::_Player_default_instance_); +} +inline const ::rtech::liveapi::Player& WeaponSwitched::player() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WeaponSwitched.player) + return _internal_player(); +} +inline void WeaponSwitched::unsafe_arena_set_allocated_player( + ::rtech::liveapi::Player* player) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.player_); + } + _impl_.player_ = player; + if (player) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.WeaponSwitched.player) +} +inline ::rtech::liveapi::Player* WeaponSwitched::release_player() { + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::rtech::liveapi::Player* WeaponSwitched::unsafe_arena_release_player() { + // @@protoc_insertion_point(field_release:rtech.liveapi.WeaponSwitched.player) + + ::rtech::liveapi::Player* temp = _impl_.player_; + _impl_.player_ = nullptr; + return temp; +} +inline ::rtech::liveapi::Player* WeaponSwitched::_internal_mutable_player() { + + if (_impl_.player_ == nullptr) { + auto* p = CreateMaybeMessage<::rtech::liveapi::Player>(GetArenaForAllocation()); + _impl_.player_ = p; + } + return _impl_.player_; +} +inline ::rtech::liveapi::Player* WeaponSwitched::mutable_player() { + ::rtech::liveapi::Player* _msg = _internal_mutable_player(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.WeaponSwitched.player) + return _msg; +} +inline void WeaponSwitched::set_allocated_player(::rtech::liveapi::Player* player) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.player_; + } + if (player) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(player); + if (message_arena != submessage_arena) { + player = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, player, submessage_arena); + } + + } else { + + } + _impl_.player_ = player; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.WeaponSwitched.player) +} + +// string oldWeapon = 4; +inline void WeaponSwitched::clear_oldweapon() { + _impl_.oldweapon_.ClearToEmpty(); +} +inline const std::string& WeaponSwitched::oldweapon() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WeaponSwitched.oldWeapon) + return _internal_oldweapon(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void WeaponSwitched::set_oldweapon(ArgT0&& arg0, ArgT... args) { + + _impl_.oldweapon_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.WeaponSwitched.oldWeapon) +} +inline std::string* WeaponSwitched::mutable_oldweapon() { + std::string* _s = _internal_mutable_oldweapon(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.WeaponSwitched.oldWeapon) + return _s; +} +inline const std::string& WeaponSwitched::_internal_oldweapon() const { + return _impl_.oldweapon_.Get(); +} +inline void WeaponSwitched::_internal_set_oldweapon(const std::string& value) { + + _impl_.oldweapon_.Set(value, GetArenaForAllocation()); +} +inline std::string* WeaponSwitched::_internal_mutable_oldweapon() { + + return _impl_.oldweapon_.Mutable(GetArenaForAllocation()); +} +inline std::string* WeaponSwitched::release_oldweapon() { + // @@protoc_insertion_point(field_release:rtech.liveapi.WeaponSwitched.oldWeapon) + return _impl_.oldweapon_.Release(); +} +inline void WeaponSwitched::set_allocated_oldweapon(std::string* oldweapon) { + if (oldweapon != nullptr) { + + } else { + + } + _impl_.oldweapon_.SetAllocated(oldweapon, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.oldweapon_.IsDefault()) { + _impl_.oldweapon_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.WeaponSwitched.oldWeapon) +} + +// string newWeapon = 5; +inline void WeaponSwitched::clear_newweapon() { + _impl_.newweapon_.ClearToEmpty(); +} +inline const std::string& WeaponSwitched::newweapon() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.WeaponSwitched.newWeapon) + return _internal_newweapon(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void WeaponSwitched::set_newweapon(ArgT0&& arg0, ArgT... args) { + + _impl_.newweapon_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.WeaponSwitched.newWeapon) +} +inline std::string* WeaponSwitched::mutable_newweapon() { + std::string* _s = _internal_mutable_newweapon(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.WeaponSwitched.newWeapon) + return _s; +} +inline const std::string& WeaponSwitched::_internal_newweapon() const { + return _impl_.newweapon_.Get(); +} +inline void WeaponSwitched::_internal_set_newweapon(const std::string& value) { + + _impl_.newweapon_.Set(value, GetArenaForAllocation()); +} +inline std::string* WeaponSwitched::_internal_mutable_newweapon() { + + return _impl_.newweapon_.Mutable(GetArenaForAllocation()); +} +inline std::string* WeaponSwitched::release_newweapon() { + // @@protoc_insertion_point(field_release:rtech.liveapi.WeaponSwitched.newWeapon) + return _impl_.newweapon_.Release(); +} +inline void WeaponSwitched::set_allocated_newweapon(std::string* newweapon) { + if (newweapon != nullptr) { + + } else { + + } + _impl_.newweapon_.SetAllocated(newweapon, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.newweapon_.IsDefault()) { + _impl_.newweapon_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.WeaponSwitched.newWeapon) +} + +// ------------------------------------------------------------------- + +// CustomEvent + +// uint64 timestamp = 1; +inline void CustomEvent::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; +} +inline uint64_t CustomEvent::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t CustomEvent::timestamp() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomEvent.timestamp) + return _internal_timestamp(); +} +inline void CustomEvent::_internal_set_timestamp(uint64_t value) { + + _impl_.timestamp_ = value; +} +inline void CustomEvent::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomEvent.timestamp) +} + +// string category = 2; +inline void CustomEvent::clear_category() { + _impl_.category_.ClearToEmpty(); +} +inline const std::string& CustomEvent::category() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomEvent.category) + return _internal_category(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomEvent::set_category(ArgT0&& arg0, ArgT... args) { + + _impl_.category_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomEvent.category) +} +inline std::string* CustomEvent::mutable_category() { + std::string* _s = _internal_mutable_category(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomEvent.category) + return _s; +} +inline const std::string& CustomEvent::_internal_category() const { + return _impl_.category_.Get(); +} +inline void CustomEvent::_internal_set_category(const std::string& value) { + + _impl_.category_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomEvent::_internal_mutable_category() { + + return _impl_.category_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomEvent::release_category() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomEvent.category) + return _impl_.category_.Release(); +} +inline void CustomEvent::set_allocated_category(std::string* category) { + if (category != nullptr) { + + } else { + + } + _impl_.category_.SetAllocated(category, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.category_.IsDefault()) { + _impl_.category_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomEvent.category) +} + +// string name = 3; +inline void CustomEvent::clear_name() { + _impl_.name_.ClearToEmpty(); +} +inline const std::string& CustomEvent::name() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomEvent.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomEvent::set_name(ArgT0&& arg0, ArgT... args) { + + _impl_.name_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomEvent.name) +} +inline std::string* CustomEvent::mutable_name() { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomEvent.name) + return _s; +} +inline const std::string& CustomEvent::_internal_name() const { + return _impl_.name_.Get(); +} +inline void CustomEvent::_internal_set_name(const std::string& value) { + + _impl_.name_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomEvent::_internal_mutable_name() { + + return _impl_.name_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomEvent::release_name() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomEvent.name) + return _impl_.name_.Release(); +} +inline void CustomEvent::set_allocated_name(std::string* name) { + if (name != nullptr) { + + } else { + + } + _impl_.name_.SetAllocated(name, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomEvent.name) +} + +// .google.protobuf.Struct data = 4; +inline bool CustomEvent::_internal_has_data() const { + return this != internal_default_instance() && _impl_.data_ != nullptr; +} +inline bool CustomEvent::has_data() const { + return _internal_has_data(); +} +inline const ::PROTOBUF_NAMESPACE_ID::Struct& CustomEvent::_internal_data() const { + const ::PROTOBUF_NAMESPACE_ID::Struct* p = _impl_.data_; + return p != nullptr ? *p : reinterpret_cast( + ::PROTOBUF_NAMESPACE_ID::_Struct_default_instance_); +} +inline const ::PROTOBUF_NAMESPACE_ID::Struct& CustomEvent::data() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomEvent.data) + return _internal_data(); +} +inline void CustomEvent::unsafe_arena_set_allocated_data( + ::PROTOBUF_NAMESPACE_ID::Struct* data) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.data_); + } + _impl_.data_ = data; + if (data) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.CustomEvent.data) +} +inline ::PROTOBUF_NAMESPACE_ID::Struct* CustomEvent::release_data() { + + ::PROTOBUF_NAMESPACE_ID::Struct* temp = _impl_.data_; + _impl_.data_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::PROTOBUF_NAMESPACE_ID::Struct* CustomEvent::unsafe_arena_release_data() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomEvent.data) + + ::PROTOBUF_NAMESPACE_ID::Struct* temp = _impl_.data_; + _impl_.data_ = nullptr; + return temp; +} +inline ::PROTOBUF_NAMESPACE_ID::Struct* CustomEvent::_internal_mutable_data() { + + if (_impl_.data_ == nullptr) { + auto* p = CreateMaybeMessage<::PROTOBUF_NAMESPACE_ID::Struct>(GetArenaForAllocation()); + _impl_.data_ = p; + } + return _impl_.data_; +} +inline ::PROTOBUF_NAMESPACE_ID::Struct* CustomEvent::mutable_data() { + ::PROTOBUF_NAMESPACE_ID::Struct* _msg = _internal_mutable_data(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomEvent.data) + return _msg; +} +inline void CustomEvent::set_allocated_data(::PROTOBUF_NAMESPACE_ID::Struct* data) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete reinterpret_cast< ::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.data_); + } + if (data) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena( + reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(data)); + if (message_arena != submessage_arena) { + data = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, data, submessage_arena); + } + + } else { + + } + _impl_.data_ = data; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomEvent.data) +} + +// ------------------------------------------------------------------- + +// ChangeCamera + +// .rtech.liveapi.PlayerOfInterest poi = 1; +inline bool ChangeCamera::_internal_has_poi() const { + return target_case() == kPoi; +} +inline bool ChangeCamera::has_poi() const { + return _internal_has_poi(); +} +inline void ChangeCamera::set_has_poi() { + _impl_._oneof_case_[0] = kPoi; +} +inline void ChangeCamera::clear_poi() { + if (_internal_has_poi()) { + _impl_.target_.poi_ = 0; + clear_has_target(); + } +} +inline ::rtech::liveapi::PlayerOfInterest ChangeCamera::_internal_poi() const { + if (_internal_has_poi()) { + return static_cast< ::rtech::liveapi::PlayerOfInterest >(_impl_.target_.poi_); + } + return static_cast< ::rtech::liveapi::PlayerOfInterest >(0); +} +inline ::rtech::liveapi::PlayerOfInterest ChangeCamera::poi() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ChangeCamera.poi) + return _internal_poi(); +} +inline void ChangeCamera::_internal_set_poi(::rtech::liveapi::PlayerOfInterest value) { + if (!_internal_has_poi()) { + clear_target(); + set_has_poi(); + } + _impl_.target_.poi_ = value; +} +inline void ChangeCamera::set_poi(::rtech::liveapi::PlayerOfInterest value) { + _internal_set_poi(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.ChangeCamera.poi) +} + +// string name = 2; +inline bool ChangeCamera::_internal_has_name() const { + return target_case() == kName; +} +inline bool ChangeCamera::has_name() const { + return _internal_has_name(); +} +inline void ChangeCamera::set_has_name() { + _impl_._oneof_case_[0] = kName; +} +inline void ChangeCamera::clear_name() { + if (_internal_has_name()) { + _impl_.target_.name_.Destroy(); + clear_has_target(); + } +} +inline const std::string& ChangeCamera::name() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.ChangeCamera.name) + return _internal_name(); +} +template +inline void ChangeCamera::set_name(ArgT0&& arg0, ArgT... args) { + if (!_internal_has_name()) { + clear_target(); + set_has_name(); + _impl_.target_.name_.InitDefault(); + } + _impl_.target_.name_.Set( static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.ChangeCamera.name) +} +inline std::string* ChangeCamera::mutable_name() { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.ChangeCamera.name) + return _s; +} +inline const std::string& ChangeCamera::_internal_name() const { + if (_internal_has_name()) { + return _impl_.target_.name_.Get(); + } + return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(); +} +inline void ChangeCamera::_internal_set_name(const std::string& value) { + if (!_internal_has_name()) { + clear_target(); + set_has_name(); + _impl_.target_.name_.InitDefault(); + } + _impl_.target_.name_.Set(value, GetArenaForAllocation()); +} +inline std::string* ChangeCamera::_internal_mutable_name() { + if (!_internal_has_name()) { + clear_target(); + set_has_name(); + _impl_.target_.name_.InitDefault(); + } + return _impl_.target_.name_.Mutable( GetArenaForAllocation()); +} +inline std::string* ChangeCamera::release_name() { + // @@protoc_insertion_point(field_release:rtech.liveapi.ChangeCamera.name) + if (_internal_has_name()) { + clear_has_target(); + return _impl_.target_.name_.Release(); + } else { + return nullptr; + } +} +inline void ChangeCamera::set_allocated_name(std::string* name) { + if (has_target()) { + clear_target(); + } + if (name != nullptr) { + set_has_name(); + _impl_.target_.name_.InitAllocated(name, GetArenaForAllocation()); + } + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.ChangeCamera.name) +} + +inline bool ChangeCamera::has_target() const { + return target_case() != TARGET_NOT_SET; +} +inline void ChangeCamera::clear_has_target() { + _impl_._oneof_case_[0] = TARGET_NOT_SET; +} +inline ChangeCamera::TargetCase ChangeCamera::target_case() const { + return ChangeCamera::TargetCase(_impl_._oneof_case_[0]); +} +// ------------------------------------------------------------------- + +// PauseToggle + +// float preTimer = 1; +inline void PauseToggle::clear_pretimer() { + _impl_.pretimer_ = 0; +} +inline float PauseToggle::_internal_pretimer() const { + return _impl_.pretimer_; +} +inline float PauseToggle::pretimer() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.PauseToggle.preTimer) + return _internal_pretimer(); +} +inline void PauseToggle::_internal_set_pretimer(float value) { + + _impl_.pretimer_ = value; +} +inline void PauseToggle::set_pretimer(float value) { + _internal_set_pretimer(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.PauseToggle.preTimer) +} + +// ------------------------------------------------------------------- + +// CustomMatch_CreateLobby + +// ------------------------------------------------------------------- + +// CustomMatch_JoinLobby + +// string roleToken = 1; +inline void CustomMatch_JoinLobby::clear_roletoken() { + _impl_.roletoken_.ClearToEmpty(); +} +inline const std::string& CustomMatch_JoinLobby::roletoken() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_JoinLobby.roleToken) + return _internal_roletoken(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_JoinLobby::set_roletoken(ArgT0&& arg0, ArgT... args) { + + _impl_.roletoken_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_JoinLobby.roleToken) +} +inline std::string* CustomMatch_JoinLobby::mutable_roletoken() { + std::string* _s = _internal_mutable_roletoken(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_JoinLobby.roleToken) + return _s; +} +inline const std::string& CustomMatch_JoinLobby::_internal_roletoken() const { + return _impl_.roletoken_.Get(); +} +inline void CustomMatch_JoinLobby::_internal_set_roletoken(const std::string& value) { + + _impl_.roletoken_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_JoinLobby::_internal_mutable_roletoken() { + + return _impl_.roletoken_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_JoinLobby::release_roletoken() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_JoinLobby.roleToken) + return _impl_.roletoken_.Release(); +} +inline void CustomMatch_JoinLobby::set_allocated_roletoken(std::string* roletoken) { + if (roletoken != nullptr) { + + } else { + + } + _impl_.roletoken_.SetAllocated(roletoken, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.roletoken_.IsDefault()) { + _impl_.roletoken_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_JoinLobby.roleToken) +} + +// ------------------------------------------------------------------- + +// CustomMatch_LeaveLobby + +// ------------------------------------------------------------------- + +// CustomMatch_SetReady + +// bool isReady = 1; +inline void CustomMatch_SetReady::clear_isready() { + _impl_.isready_ = false; +} +inline bool CustomMatch_SetReady::_internal_isready() const { + return _impl_.isready_; +} +inline bool CustomMatch_SetReady::isready() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetReady.isReady) + return _internal_isready(); +} +inline void CustomMatch_SetReady::_internal_set_isready(bool value) { + + _impl_.isready_ = value; +} +inline void CustomMatch_SetReady::set_isready(bool value) { + _internal_set_isready(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetReady.isReady) +} + +// ------------------------------------------------------------------- + +// CustomMatch_GetLobbyPlayers + +// ------------------------------------------------------------------- + +// CustomMatch_SetMatchmaking + +// bool enabled = 1; +inline void CustomMatch_SetMatchmaking::clear_enabled() { + _impl_.enabled_ = false; +} +inline bool CustomMatch_SetMatchmaking::_internal_enabled() const { + return _impl_.enabled_; +} +inline bool CustomMatch_SetMatchmaking::enabled() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetMatchmaking.enabled) + return _internal_enabled(); +} +inline void CustomMatch_SetMatchmaking::_internal_set_enabled(bool value) { + + _impl_.enabled_ = value; +} +inline void CustomMatch_SetMatchmaking::set_enabled(bool value) { + _internal_set_enabled(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetMatchmaking.enabled) +} + +// ------------------------------------------------------------------- + +// CustomMatch_SetTeam + +// int32 teamId = 1; +inline void CustomMatch_SetTeam::clear_teamid() { + _impl_.teamid_ = 0; +} +inline int32_t CustomMatch_SetTeam::_internal_teamid() const { + return _impl_.teamid_; +} +inline int32_t CustomMatch_SetTeam::teamid() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetTeam.teamId) + return _internal_teamid(); +} +inline void CustomMatch_SetTeam::_internal_set_teamid(int32_t value) { + + _impl_.teamid_ = value; +} +inline void CustomMatch_SetTeam::set_teamid(int32_t value) { + _internal_set_teamid(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetTeam.teamId) +} + +// string targetHardwareName = 2; +inline void CustomMatch_SetTeam::clear_targethardwarename() { + _impl_.targethardwarename_.ClearToEmpty(); +} +inline const std::string& CustomMatch_SetTeam::targethardwarename() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetTeam.targetHardwareName) + return _internal_targethardwarename(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_SetTeam::set_targethardwarename(ArgT0&& arg0, ArgT... args) { + + _impl_.targethardwarename_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetTeam.targetHardwareName) +} +inline std::string* CustomMatch_SetTeam::mutable_targethardwarename() { + std::string* _s = _internal_mutable_targethardwarename(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_SetTeam.targetHardwareName) + return _s; +} +inline const std::string& CustomMatch_SetTeam::_internal_targethardwarename() const { + return _impl_.targethardwarename_.Get(); +} +inline void CustomMatch_SetTeam::_internal_set_targethardwarename(const std::string& value) { + + _impl_.targethardwarename_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_SetTeam::_internal_mutable_targethardwarename() { + + return _impl_.targethardwarename_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_SetTeam::release_targethardwarename() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_SetTeam.targetHardwareName) + return _impl_.targethardwarename_.Release(); +} +inline void CustomMatch_SetTeam::set_allocated_targethardwarename(std::string* targethardwarename) { + if (targethardwarename != nullptr) { + + } else { + + } + _impl_.targethardwarename_.SetAllocated(targethardwarename, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.targethardwarename_.IsDefault()) { + _impl_.targethardwarename_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_SetTeam.targetHardwareName) +} + +// string targetNucleusHash = 3; +inline void CustomMatch_SetTeam::clear_targetnucleushash() { + _impl_.targetnucleushash_.ClearToEmpty(); +} +inline const std::string& CustomMatch_SetTeam::targetnucleushash() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetTeam.targetNucleusHash) + return _internal_targetnucleushash(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_SetTeam::set_targetnucleushash(ArgT0&& arg0, ArgT... args) { + + _impl_.targetnucleushash_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetTeam.targetNucleusHash) +} +inline std::string* CustomMatch_SetTeam::mutable_targetnucleushash() { + std::string* _s = _internal_mutable_targetnucleushash(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_SetTeam.targetNucleusHash) + return _s; +} +inline const std::string& CustomMatch_SetTeam::_internal_targetnucleushash() const { + return _impl_.targetnucleushash_.Get(); +} +inline void CustomMatch_SetTeam::_internal_set_targetnucleushash(const std::string& value) { + + _impl_.targetnucleushash_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_SetTeam::_internal_mutable_targetnucleushash() { + + return _impl_.targetnucleushash_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_SetTeam::release_targetnucleushash() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_SetTeam.targetNucleusHash) + return _impl_.targetnucleushash_.Release(); +} +inline void CustomMatch_SetTeam::set_allocated_targetnucleushash(std::string* targetnucleushash) { + if (targetnucleushash != nullptr) { + + } else { + + } + _impl_.targetnucleushash_.SetAllocated(targetnucleushash, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.targetnucleushash_.IsDefault()) { + _impl_.targetnucleushash_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_SetTeam.targetNucleusHash) +} + +// ------------------------------------------------------------------- + +// CustomMatch_KickPlayer + +// string targetHardwareName = 1; +inline void CustomMatch_KickPlayer::clear_targethardwarename() { + _impl_.targethardwarename_.ClearToEmpty(); +} +inline const std::string& CustomMatch_KickPlayer::targethardwarename() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_KickPlayer.targetHardwareName) + return _internal_targethardwarename(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_KickPlayer::set_targethardwarename(ArgT0&& arg0, ArgT... args) { + + _impl_.targethardwarename_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_KickPlayer.targetHardwareName) +} +inline std::string* CustomMatch_KickPlayer::mutable_targethardwarename() { + std::string* _s = _internal_mutable_targethardwarename(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_KickPlayer.targetHardwareName) + return _s; +} +inline const std::string& CustomMatch_KickPlayer::_internal_targethardwarename() const { + return _impl_.targethardwarename_.Get(); +} +inline void CustomMatch_KickPlayer::_internal_set_targethardwarename(const std::string& value) { + + _impl_.targethardwarename_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_KickPlayer::_internal_mutable_targethardwarename() { + + return _impl_.targethardwarename_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_KickPlayer::release_targethardwarename() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_KickPlayer.targetHardwareName) + return _impl_.targethardwarename_.Release(); +} +inline void CustomMatch_KickPlayer::set_allocated_targethardwarename(std::string* targethardwarename) { + if (targethardwarename != nullptr) { + + } else { + + } + _impl_.targethardwarename_.SetAllocated(targethardwarename, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.targethardwarename_.IsDefault()) { + _impl_.targethardwarename_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_KickPlayer.targetHardwareName) +} + +// string targetNucleusHash = 2; +inline void CustomMatch_KickPlayer::clear_targetnucleushash() { + _impl_.targetnucleushash_.ClearToEmpty(); +} +inline const std::string& CustomMatch_KickPlayer::targetnucleushash() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_KickPlayer.targetNucleusHash) + return _internal_targetnucleushash(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_KickPlayer::set_targetnucleushash(ArgT0&& arg0, ArgT... args) { + + _impl_.targetnucleushash_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_KickPlayer.targetNucleusHash) +} +inline std::string* CustomMatch_KickPlayer::mutable_targetnucleushash() { + std::string* _s = _internal_mutable_targetnucleushash(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_KickPlayer.targetNucleusHash) + return _s; +} +inline const std::string& CustomMatch_KickPlayer::_internal_targetnucleushash() const { + return _impl_.targetnucleushash_.Get(); +} +inline void CustomMatch_KickPlayer::_internal_set_targetnucleushash(const std::string& value) { + + _impl_.targetnucleushash_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_KickPlayer::_internal_mutable_targetnucleushash() { + + return _impl_.targetnucleushash_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_KickPlayer::release_targetnucleushash() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_KickPlayer.targetNucleusHash) + return _impl_.targetnucleushash_.Release(); +} +inline void CustomMatch_KickPlayer::set_allocated_targetnucleushash(std::string* targetnucleushash) { + if (targetnucleushash != nullptr) { + + } else { + + } + _impl_.targetnucleushash_.SetAllocated(targetnucleushash, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.targetnucleushash_.IsDefault()) { + _impl_.targetnucleushash_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_KickPlayer.targetNucleusHash) +} + +// ------------------------------------------------------------------- + +// CustomMatch_SetSettings + +// string playlistName = 1; +inline void CustomMatch_SetSettings::clear_playlistname() { + _impl_.playlistname_.ClearToEmpty(); +} +inline const std::string& CustomMatch_SetSettings::playlistname() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetSettings.playlistName) + return _internal_playlistname(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_SetSettings::set_playlistname(ArgT0&& arg0, ArgT... args) { + + _impl_.playlistname_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetSettings.playlistName) +} +inline std::string* CustomMatch_SetSettings::mutable_playlistname() { + std::string* _s = _internal_mutable_playlistname(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_SetSettings.playlistName) + return _s; +} +inline const std::string& CustomMatch_SetSettings::_internal_playlistname() const { + return _impl_.playlistname_.Get(); +} +inline void CustomMatch_SetSettings::_internal_set_playlistname(const std::string& value) { + + _impl_.playlistname_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_SetSettings::_internal_mutable_playlistname() { + + return _impl_.playlistname_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_SetSettings::release_playlistname() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_SetSettings.playlistName) + return _impl_.playlistname_.Release(); +} +inline void CustomMatch_SetSettings::set_allocated_playlistname(std::string* playlistname) { + if (playlistname != nullptr) { + + } else { + + } + _impl_.playlistname_.SetAllocated(playlistname, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.playlistname_.IsDefault()) { + _impl_.playlistname_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_SetSettings.playlistName) +} + +// bool adminChat = 2; +inline void CustomMatch_SetSettings::clear_adminchat() { + _impl_.adminchat_ = false; +} +inline bool CustomMatch_SetSettings::_internal_adminchat() const { + return _impl_.adminchat_; +} +inline bool CustomMatch_SetSettings::adminchat() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetSettings.adminChat) + return _internal_adminchat(); +} +inline void CustomMatch_SetSettings::_internal_set_adminchat(bool value) { + + _impl_.adminchat_ = value; +} +inline void CustomMatch_SetSettings::set_adminchat(bool value) { + _internal_set_adminchat(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetSettings.adminChat) +} + +// bool teamRename = 3; +inline void CustomMatch_SetSettings::clear_teamrename() { + _impl_.teamrename_ = false; +} +inline bool CustomMatch_SetSettings::_internal_teamrename() const { + return _impl_.teamrename_; +} +inline bool CustomMatch_SetSettings::teamrename() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetSettings.teamRename) + return _internal_teamrename(); +} +inline void CustomMatch_SetSettings::_internal_set_teamrename(bool value) { + + _impl_.teamrename_ = value; +} +inline void CustomMatch_SetSettings::set_teamrename(bool value) { + _internal_set_teamrename(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetSettings.teamRename) +} + +// bool selfAssign = 4; +inline void CustomMatch_SetSettings::clear_selfassign() { + _impl_.selfassign_ = false; +} +inline bool CustomMatch_SetSettings::_internal_selfassign() const { + return _impl_.selfassign_; +} +inline bool CustomMatch_SetSettings::selfassign() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetSettings.selfAssign) + return _internal_selfassign(); +} +inline void CustomMatch_SetSettings::_internal_set_selfassign(bool value) { + + _impl_.selfassign_ = value; +} +inline void CustomMatch_SetSettings::set_selfassign(bool value) { + _internal_set_selfassign(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetSettings.selfAssign) +} + +// bool aimAssist = 5; +inline void CustomMatch_SetSettings::clear_aimassist() { + _impl_.aimassist_ = false; +} +inline bool CustomMatch_SetSettings::_internal_aimassist() const { + return _impl_.aimassist_; +} +inline bool CustomMatch_SetSettings::aimassist() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetSettings.aimAssist) + return _internal_aimassist(); +} +inline void CustomMatch_SetSettings::_internal_set_aimassist(bool value) { + + _impl_.aimassist_ = value; +} +inline void CustomMatch_SetSettings::set_aimassist(bool value) { + _internal_set_aimassist(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetSettings.aimAssist) +} + +// bool anonMode = 6; +inline void CustomMatch_SetSettings::clear_anonmode() { + _impl_.anonmode_ = false; +} +inline bool CustomMatch_SetSettings::_internal_anonmode() const { + return _impl_.anonmode_; +} +inline bool CustomMatch_SetSettings::anonmode() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetSettings.anonMode) + return _internal_anonmode(); +} +inline void CustomMatch_SetSettings::_internal_set_anonmode(bool value) { + + _impl_.anonmode_ = value; +} +inline void CustomMatch_SetSettings::set_anonmode(bool value) { + _internal_set_anonmode(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetSettings.anonMode) +} + +// ------------------------------------------------------------------- + +// CustomMatch_GetSettings + +// ------------------------------------------------------------------- + +// CustomMatch_SetTeamName + +// int32 teamId = 1; +inline void CustomMatch_SetTeamName::clear_teamid() { + _impl_.teamid_ = 0; +} +inline int32_t CustomMatch_SetTeamName::_internal_teamid() const { + return _impl_.teamid_; +} +inline int32_t CustomMatch_SetTeamName::teamid() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetTeamName.teamId) + return _internal_teamid(); +} +inline void CustomMatch_SetTeamName::_internal_set_teamid(int32_t value) { + + _impl_.teamid_ = value; +} +inline void CustomMatch_SetTeamName::set_teamid(int32_t value) { + _internal_set_teamid(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetTeamName.teamId) +} + +// string teamName = 2; +inline void CustomMatch_SetTeamName::clear_teamname() { + _impl_.teamname_.ClearToEmpty(); +} +inline const std::string& CustomMatch_SetTeamName::teamname() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SetTeamName.teamName) + return _internal_teamname(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_SetTeamName::set_teamname(ArgT0&& arg0, ArgT... args) { + + _impl_.teamname_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SetTeamName.teamName) +} +inline std::string* CustomMatch_SetTeamName::mutable_teamname() { + std::string* _s = _internal_mutable_teamname(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_SetTeamName.teamName) + return _s; +} +inline const std::string& CustomMatch_SetTeamName::_internal_teamname() const { + return _impl_.teamname_.Get(); +} +inline void CustomMatch_SetTeamName::_internal_set_teamname(const std::string& value) { + + _impl_.teamname_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_SetTeamName::_internal_mutable_teamname() { + + return _impl_.teamname_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_SetTeamName::release_teamname() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_SetTeamName.teamName) + return _impl_.teamname_.Release(); +} +inline void CustomMatch_SetTeamName::set_allocated_teamname(std::string* teamname) { + if (teamname != nullptr) { + + } else { + + } + _impl_.teamname_.SetAllocated(teamname, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.teamname_.IsDefault()) { + _impl_.teamname_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_SetTeamName.teamName) +} + +// ------------------------------------------------------------------- + +// CustomMatch_SendChat + +// string text = 1; +inline void CustomMatch_SendChat::clear_text() { + _impl_.text_.ClearToEmpty(); +} +inline const std::string& CustomMatch_SendChat::text() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.CustomMatch_SendChat.text) + return _internal_text(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CustomMatch_SendChat::set_text(ArgT0&& arg0, ArgT... args) { + + _impl_.text_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.CustomMatch_SendChat.text) +} +inline std::string* CustomMatch_SendChat::mutable_text() { + std::string* _s = _internal_mutable_text(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.CustomMatch_SendChat.text) + return _s; +} +inline const std::string& CustomMatch_SendChat::_internal_text() const { + return _impl_.text_.Get(); +} +inline void CustomMatch_SendChat::_internal_set_text(const std::string& value) { + + _impl_.text_.Set(value, GetArenaForAllocation()); +} +inline std::string* CustomMatch_SendChat::_internal_mutable_text() { + + return _impl_.text_.Mutable(GetArenaForAllocation()); +} +inline std::string* CustomMatch_SendChat::release_text() { + // @@protoc_insertion_point(field_release:rtech.liveapi.CustomMatch_SendChat.text) + return _impl_.text_.Release(); +} +inline void CustomMatch_SendChat::set_allocated_text(std::string* text) { + if (text != nullptr) { + + } else { + + } + _impl_.text_.SetAllocated(text, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.text_.IsDefault()) { + _impl_.text_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.CustomMatch_SendChat.text) +} + +// ------------------------------------------------------------------- + +// Request + +// bool withAck = 1; +inline void Request::clear_withack() { + _impl_.withack_ = false; +} +inline bool Request::_internal_withack() const { + return _impl_.withack_; +} +inline bool Request::withack() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.withAck) + return _internal_withack(); +} +inline void Request::_internal_set_withack(bool value) { + + _impl_.withack_ = value; +} +inline void Request::set_withack(bool value) { + _internal_set_withack(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Request.withAck) +} + +// string preSharedKey = 2; +inline void Request::clear_presharedkey() { + _impl_.presharedkey_.ClearToEmpty(); +} +inline const std::string& Request::presharedkey() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.preSharedKey) + return _internal_presharedkey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Request::set_presharedkey(ArgT0&& arg0, ArgT... args) { + + _impl_.presharedkey_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.Request.preSharedKey) +} +inline std::string* Request::mutable_presharedkey() { + std::string* _s = _internal_mutable_presharedkey(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.preSharedKey) + return _s; +} +inline const std::string& Request::_internal_presharedkey() const { + return _impl_.presharedkey_.Get(); +} +inline void Request::_internal_set_presharedkey(const std::string& value) { + + _impl_.presharedkey_.Set(value, GetArenaForAllocation()); +} +inline std::string* Request::_internal_mutable_presharedkey() { + + return _impl_.presharedkey_.Mutable(GetArenaForAllocation()); +} +inline std::string* Request::release_presharedkey() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.preSharedKey) + return _impl_.presharedkey_.Release(); +} +inline void Request::set_allocated_presharedkey(std::string* presharedkey) { + if (presharedkey != nullptr) { + + } else { + + } + _impl_.presharedkey_.SetAllocated(presharedkey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.presharedkey_.IsDefault()) { + _impl_.presharedkey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Request.preSharedKey) +} + +// .rtech.liveapi.ChangeCamera changeCam = 4; +inline bool Request::_internal_has_changecam() const { + return actions_case() == kChangeCam; +} +inline bool Request::has_changecam() const { + return _internal_has_changecam(); +} +inline void Request::set_has_changecam() { + _impl_._oneof_case_[0] = kChangeCam; +} +inline void Request::clear_changecam() { + if (_internal_has_changecam()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.changecam_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::ChangeCamera* Request::release_changecam() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.changeCam) + if (_internal_has_changecam()) { + clear_has_actions(); + ::rtech::liveapi::ChangeCamera* temp = _impl_.actions_.changecam_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.changecam_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::ChangeCamera& Request::_internal_changecam() const { + return _internal_has_changecam() + ? *_impl_.actions_.changecam_ + : reinterpret_cast< ::rtech::liveapi::ChangeCamera&>(::rtech::liveapi::_ChangeCamera_default_instance_); +} +inline const ::rtech::liveapi::ChangeCamera& Request::changecam() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.changeCam) + return _internal_changecam(); +} +inline ::rtech::liveapi::ChangeCamera* Request::unsafe_arena_release_changecam() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.changeCam) + if (_internal_has_changecam()) { + clear_has_actions(); + ::rtech::liveapi::ChangeCamera* temp = _impl_.actions_.changecam_; + _impl_.actions_.changecam_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_changecam(::rtech::liveapi::ChangeCamera* changecam) { + clear_actions(); + if (changecam) { + set_has_changecam(); + _impl_.actions_.changecam_ = changecam; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.changeCam) +} +inline ::rtech::liveapi::ChangeCamera* Request::_internal_mutable_changecam() { + if (!_internal_has_changecam()) { + clear_actions(); + set_has_changecam(); + _impl_.actions_.changecam_ = CreateMaybeMessage< ::rtech::liveapi::ChangeCamera >(GetArenaForAllocation()); + } + return _impl_.actions_.changecam_; +} +inline ::rtech::liveapi::ChangeCamera* Request::mutable_changecam() { + ::rtech::liveapi::ChangeCamera* _msg = _internal_mutable_changecam(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.changeCam) + return _msg; +} + +// .rtech.liveapi.PauseToggle pauseToggle = 5; +inline bool Request::_internal_has_pausetoggle() const { + return actions_case() == kPauseToggle; +} +inline bool Request::has_pausetoggle() const { + return _internal_has_pausetoggle(); +} +inline void Request::set_has_pausetoggle() { + _impl_._oneof_case_[0] = kPauseToggle; +} +inline void Request::clear_pausetoggle() { + if (_internal_has_pausetoggle()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.pausetoggle_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::PauseToggle* Request::release_pausetoggle() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.pauseToggle) + if (_internal_has_pausetoggle()) { + clear_has_actions(); + ::rtech::liveapi::PauseToggle* temp = _impl_.actions_.pausetoggle_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.pausetoggle_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::PauseToggle& Request::_internal_pausetoggle() const { + return _internal_has_pausetoggle() + ? *_impl_.actions_.pausetoggle_ + : reinterpret_cast< ::rtech::liveapi::PauseToggle&>(::rtech::liveapi::_PauseToggle_default_instance_); +} +inline const ::rtech::liveapi::PauseToggle& Request::pausetoggle() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.pauseToggle) + return _internal_pausetoggle(); +} +inline ::rtech::liveapi::PauseToggle* Request::unsafe_arena_release_pausetoggle() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.pauseToggle) + if (_internal_has_pausetoggle()) { + clear_has_actions(); + ::rtech::liveapi::PauseToggle* temp = _impl_.actions_.pausetoggle_; + _impl_.actions_.pausetoggle_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_pausetoggle(::rtech::liveapi::PauseToggle* pausetoggle) { + clear_actions(); + if (pausetoggle) { + set_has_pausetoggle(); + _impl_.actions_.pausetoggle_ = pausetoggle; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.pauseToggle) +} +inline ::rtech::liveapi::PauseToggle* Request::_internal_mutable_pausetoggle() { + if (!_internal_has_pausetoggle()) { + clear_actions(); + set_has_pausetoggle(); + _impl_.actions_.pausetoggle_ = CreateMaybeMessage< ::rtech::liveapi::PauseToggle >(GetArenaForAllocation()); + } + return _impl_.actions_.pausetoggle_; +} +inline ::rtech::liveapi::PauseToggle* Request::mutable_pausetoggle() { + ::rtech::liveapi::PauseToggle* _msg = _internal_mutable_pausetoggle(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.pauseToggle) + return _msg; +} + +// .rtech.liveapi.CustomMatch_CreateLobby customMatch_CreateLobby = 10; +inline bool Request::_internal_has_custommatch_createlobby() const { + return actions_case() == kCustomMatchCreateLobby; +} +inline bool Request::has_custommatch_createlobby() const { + return _internal_has_custommatch_createlobby(); +} +inline void Request::set_has_custommatch_createlobby() { + _impl_._oneof_case_[0] = kCustomMatchCreateLobby; +} +inline void Request::clear_custommatch_createlobby() { + if (_internal_has_custommatch_createlobby()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_createlobby_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_CreateLobby* Request::release_custommatch_createlobby() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_CreateLobby) + if (_internal_has_custommatch_createlobby()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_CreateLobby* temp = _impl_.actions_.custommatch_createlobby_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_createlobby_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_CreateLobby& Request::_internal_custommatch_createlobby() const { + return _internal_has_custommatch_createlobby() + ? *_impl_.actions_.custommatch_createlobby_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_CreateLobby&>(::rtech::liveapi::_CustomMatch_CreateLobby_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_CreateLobby& Request::custommatch_createlobby() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_CreateLobby) + return _internal_custommatch_createlobby(); +} +inline ::rtech::liveapi::CustomMatch_CreateLobby* Request::unsafe_arena_release_custommatch_createlobby() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_CreateLobby) + if (_internal_has_custommatch_createlobby()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_CreateLobby* temp = _impl_.actions_.custommatch_createlobby_; + _impl_.actions_.custommatch_createlobby_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_createlobby(::rtech::liveapi::CustomMatch_CreateLobby* custommatch_createlobby) { + clear_actions(); + if (custommatch_createlobby) { + set_has_custommatch_createlobby(); + _impl_.actions_.custommatch_createlobby_ = custommatch_createlobby; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_CreateLobby) +} +inline ::rtech::liveapi::CustomMatch_CreateLobby* Request::_internal_mutable_custommatch_createlobby() { + if (!_internal_has_custommatch_createlobby()) { + clear_actions(); + set_has_custommatch_createlobby(); + _impl_.actions_.custommatch_createlobby_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_CreateLobby >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_createlobby_; +} +inline ::rtech::liveapi::CustomMatch_CreateLobby* Request::mutable_custommatch_createlobby() { + ::rtech::liveapi::CustomMatch_CreateLobby* _msg = _internal_mutable_custommatch_createlobby(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_CreateLobby) + return _msg; +} + +// .rtech.liveapi.CustomMatch_JoinLobby customMatch_JoinLobby = 11; +inline bool Request::_internal_has_custommatch_joinlobby() const { + return actions_case() == kCustomMatchJoinLobby; +} +inline bool Request::has_custommatch_joinlobby() const { + return _internal_has_custommatch_joinlobby(); +} +inline void Request::set_has_custommatch_joinlobby() { + _impl_._oneof_case_[0] = kCustomMatchJoinLobby; +} +inline void Request::clear_custommatch_joinlobby() { + if (_internal_has_custommatch_joinlobby()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_joinlobby_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_JoinLobby* Request::release_custommatch_joinlobby() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_JoinLobby) + if (_internal_has_custommatch_joinlobby()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_JoinLobby* temp = _impl_.actions_.custommatch_joinlobby_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_joinlobby_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_JoinLobby& Request::_internal_custommatch_joinlobby() const { + return _internal_has_custommatch_joinlobby() + ? *_impl_.actions_.custommatch_joinlobby_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_JoinLobby&>(::rtech::liveapi::_CustomMatch_JoinLobby_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_JoinLobby& Request::custommatch_joinlobby() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_JoinLobby) + return _internal_custommatch_joinlobby(); +} +inline ::rtech::liveapi::CustomMatch_JoinLobby* Request::unsafe_arena_release_custommatch_joinlobby() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_JoinLobby) + if (_internal_has_custommatch_joinlobby()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_JoinLobby* temp = _impl_.actions_.custommatch_joinlobby_; + _impl_.actions_.custommatch_joinlobby_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_joinlobby(::rtech::liveapi::CustomMatch_JoinLobby* custommatch_joinlobby) { + clear_actions(); + if (custommatch_joinlobby) { + set_has_custommatch_joinlobby(); + _impl_.actions_.custommatch_joinlobby_ = custommatch_joinlobby; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_JoinLobby) +} +inline ::rtech::liveapi::CustomMatch_JoinLobby* Request::_internal_mutable_custommatch_joinlobby() { + if (!_internal_has_custommatch_joinlobby()) { + clear_actions(); + set_has_custommatch_joinlobby(); + _impl_.actions_.custommatch_joinlobby_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_JoinLobby >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_joinlobby_; +} +inline ::rtech::liveapi::CustomMatch_JoinLobby* Request::mutable_custommatch_joinlobby() { + ::rtech::liveapi::CustomMatch_JoinLobby* _msg = _internal_mutable_custommatch_joinlobby(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_JoinLobby) + return _msg; +} + +// .rtech.liveapi.CustomMatch_LeaveLobby customMatch_LeaveLobby = 12; +inline bool Request::_internal_has_custommatch_leavelobby() const { + return actions_case() == kCustomMatchLeaveLobby; +} +inline bool Request::has_custommatch_leavelobby() const { + return _internal_has_custommatch_leavelobby(); +} +inline void Request::set_has_custommatch_leavelobby() { + _impl_._oneof_case_[0] = kCustomMatchLeaveLobby; +} +inline void Request::clear_custommatch_leavelobby() { + if (_internal_has_custommatch_leavelobby()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_leavelobby_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_LeaveLobby* Request::release_custommatch_leavelobby() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_LeaveLobby) + if (_internal_has_custommatch_leavelobby()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_LeaveLobby* temp = _impl_.actions_.custommatch_leavelobby_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_leavelobby_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_LeaveLobby& Request::_internal_custommatch_leavelobby() const { + return _internal_has_custommatch_leavelobby() + ? *_impl_.actions_.custommatch_leavelobby_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_LeaveLobby&>(::rtech::liveapi::_CustomMatch_LeaveLobby_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_LeaveLobby& Request::custommatch_leavelobby() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_LeaveLobby) + return _internal_custommatch_leavelobby(); +} +inline ::rtech::liveapi::CustomMatch_LeaveLobby* Request::unsafe_arena_release_custommatch_leavelobby() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_LeaveLobby) + if (_internal_has_custommatch_leavelobby()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_LeaveLobby* temp = _impl_.actions_.custommatch_leavelobby_; + _impl_.actions_.custommatch_leavelobby_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_leavelobby(::rtech::liveapi::CustomMatch_LeaveLobby* custommatch_leavelobby) { + clear_actions(); + if (custommatch_leavelobby) { + set_has_custommatch_leavelobby(); + _impl_.actions_.custommatch_leavelobby_ = custommatch_leavelobby; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_LeaveLobby) +} +inline ::rtech::liveapi::CustomMatch_LeaveLobby* Request::_internal_mutable_custommatch_leavelobby() { + if (!_internal_has_custommatch_leavelobby()) { + clear_actions(); + set_has_custommatch_leavelobby(); + _impl_.actions_.custommatch_leavelobby_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_LeaveLobby >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_leavelobby_; +} +inline ::rtech::liveapi::CustomMatch_LeaveLobby* Request::mutable_custommatch_leavelobby() { + ::rtech::liveapi::CustomMatch_LeaveLobby* _msg = _internal_mutable_custommatch_leavelobby(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_LeaveLobby) + return _msg; +} + +// .rtech.liveapi.CustomMatch_SetReady customMatch_SetReady = 13; +inline bool Request::_internal_has_custommatch_setready() const { + return actions_case() == kCustomMatchSetReady; +} +inline bool Request::has_custommatch_setready() const { + return _internal_has_custommatch_setready(); +} +inline void Request::set_has_custommatch_setready() { + _impl_._oneof_case_[0] = kCustomMatchSetReady; +} +inline void Request::clear_custommatch_setready() { + if (_internal_has_custommatch_setready()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_setready_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_SetReady* Request::release_custommatch_setready() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_SetReady) + if (_internal_has_custommatch_setready()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SetReady* temp = _impl_.actions_.custommatch_setready_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_setready_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_SetReady& Request::_internal_custommatch_setready() const { + return _internal_has_custommatch_setready() + ? *_impl_.actions_.custommatch_setready_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_SetReady&>(::rtech::liveapi::_CustomMatch_SetReady_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_SetReady& Request::custommatch_setready() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_SetReady) + return _internal_custommatch_setready(); +} +inline ::rtech::liveapi::CustomMatch_SetReady* Request::unsafe_arena_release_custommatch_setready() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_SetReady) + if (_internal_has_custommatch_setready()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SetReady* temp = _impl_.actions_.custommatch_setready_; + _impl_.actions_.custommatch_setready_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_setready(::rtech::liveapi::CustomMatch_SetReady* custommatch_setready) { + clear_actions(); + if (custommatch_setready) { + set_has_custommatch_setready(); + _impl_.actions_.custommatch_setready_ = custommatch_setready; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_SetReady) +} +inline ::rtech::liveapi::CustomMatch_SetReady* Request::_internal_mutable_custommatch_setready() { + if (!_internal_has_custommatch_setready()) { + clear_actions(); + set_has_custommatch_setready(); + _impl_.actions_.custommatch_setready_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SetReady >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_setready_; +} +inline ::rtech::liveapi::CustomMatch_SetReady* Request::mutable_custommatch_setready() { + ::rtech::liveapi::CustomMatch_SetReady* _msg = _internal_mutable_custommatch_setready(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_SetReady) + return _msg; +} + +// .rtech.liveapi.CustomMatch_SetMatchmaking customMatch_SetMatchmaking = 14; +inline bool Request::_internal_has_custommatch_setmatchmaking() const { + return actions_case() == kCustomMatchSetMatchmaking; +} +inline bool Request::has_custommatch_setmatchmaking() const { + return _internal_has_custommatch_setmatchmaking(); +} +inline void Request::set_has_custommatch_setmatchmaking() { + _impl_._oneof_case_[0] = kCustomMatchSetMatchmaking; +} +inline void Request::clear_custommatch_setmatchmaking() { + if (_internal_has_custommatch_setmatchmaking()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_setmatchmaking_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_SetMatchmaking* Request::release_custommatch_setmatchmaking() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_SetMatchmaking) + if (_internal_has_custommatch_setmatchmaking()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SetMatchmaking* temp = _impl_.actions_.custommatch_setmatchmaking_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_setmatchmaking_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_SetMatchmaking& Request::_internal_custommatch_setmatchmaking() const { + return _internal_has_custommatch_setmatchmaking() + ? *_impl_.actions_.custommatch_setmatchmaking_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_SetMatchmaking&>(::rtech::liveapi::_CustomMatch_SetMatchmaking_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_SetMatchmaking& Request::custommatch_setmatchmaking() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_SetMatchmaking) + return _internal_custommatch_setmatchmaking(); +} +inline ::rtech::liveapi::CustomMatch_SetMatchmaking* Request::unsafe_arena_release_custommatch_setmatchmaking() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_SetMatchmaking) + if (_internal_has_custommatch_setmatchmaking()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SetMatchmaking* temp = _impl_.actions_.custommatch_setmatchmaking_; + _impl_.actions_.custommatch_setmatchmaking_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_setmatchmaking(::rtech::liveapi::CustomMatch_SetMatchmaking* custommatch_setmatchmaking) { + clear_actions(); + if (custommatch_setmatchmaking) { + set_has_custommatch_setmatchmaking(); + _impl_.actions_.custommatch_setmatchmaking_ = custommatch_setmatchmaking; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_SetMatchmaking) +} +inline ::rtech::liveapi::CustomMatch_SetMatchmaking* Request::_internal_mutable_custommatch_setmatchmaking() { + if (!_internal_has_custommatch_setmatchmaking()) { + clear_actions(); + set_has_custommatch_setmatchmaking(); + _impl_.actions_.custommatch_setmatchmaking_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SetMatchmaking >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_setmatchmaking_; +} +inline ::rtech::liveapi::CustomMatch_SetMatchmaking* Request::mutable_custommatch_setmatchmaking() { + ::rtech::liveapi::CustomMatch_SetMatchmaking* _msg = _internal_mutable_custommatch_setmatchmaking(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_SetMatchmaking) + return _msg; +} + +// .rtech.liveapi.CustomMatch_SetTeam customMatch_SetTeam = 15; +inline bool Request::_internal_has_custommatch_setteam() const { + return actions_case() == kCustomMatchSetTeam; +} +inline bool Request::has_custommatch_setteam() const { + return _internal_has_custommatch_setteam(); +} +inline void Request::set_has_custommatch_setteam() { + _impl_._oneof_case_[0] = kCustomMatchSetTeam; +} +inline void Request::clear_custommatch_setteam() { + if (_internal_has_custommatch_setteam()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_setteam_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_SetTeam* Request::release_custommatch_setteam() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_SetTeam) + if (_internal_has_custommatch_setteam()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SetTeam* temp = _impl_.actions_.custommatch_setteam_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_setteam_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_SetTeam& Request::_internal_custommatch_setteam() const { + return _internal_has_custommatch_setteam() + ? *_impl_.actions_.custommatch_setteam_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_SetTeam&>(::rtech::liveapi::_CustomMatch_SetTeam_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_SetTeam& Request::custommatch_setteam() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_SetTeam) + return _internal_custommatch_setteam(); +} +inline ::rtech::liveapi::CustomMatch_SetTeam* Request::unsafe_arena_release_custommatch_setteam() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_SetTeam) + if (_internal_has_custommatch_setteam()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SetTeam* temp = _impl_.actions_.custommatch_setteam_; + _impl_.actions_.custommatch_setteam_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_setteam(::rtech::liveapi::CustomMatch_SetTeam* custommatch_setteam) { + clear_actions(); + if (custommatch_setteam) { + set_has_custommatch_setteam(); + _impl_.actions_.custommatch_setteam_ = custommatch_setteam; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_SetTeam) +} +inline ::rtech::liveapi::CustomMatch_SetTeam* Request::_internal_mutable_custommatch_setteam() { + if (!_internal_has_custommatch_setteam()) { + clear_actions(); + set_has_custommatch_setteam(); + _impl_.actions_.custommatch_setteam_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SetTeam >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_setteam_; +} +inline ::rtech::liveapi::CustomMatch_SetTeam* Request::mutable_custommatch_setteam() { + ::rtech::liveapi::CustomMatch_SetTeam* _msg = _internal_mutable_custommatch_setteam(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_SetTeam) + return _msg; +} + +// .rtech.liveapi.CustomMatch_KickPlayer customMatch_KickPlayer = 16; +inline bool Request::_internal_has_custommatch_kickplayer() const { + return actions_case() == kCustomMatchKickPlayer; +} +inline bool Request::has_custommatch_kickplayer() const { + return _internal_has_custommatch_kickplayer(); +} +inline void Request::set_has_custommatch_kickplayer() { + _impl_._oneof_case_[0] = kCustomMatchKickPlayer; +} +inline void Request::clear_custommatch_kickplayer() { + if (_internal_has_custommatch_kickplayer()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_kickplayer_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_KickPlayer* Request::release_custommatch_kickplayer() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_KickPlayer) + if (_internal_has_custommatch_kickplayer()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_KickPlayer* temp = _impl_.actions_.custommatch_kickplayer_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_kickplayer_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_KickPlayer& Request::_internal_custommatch_kickplayer() const { + return _internal_has_custommatch_kickplayer() + ? *_impl_.actions_.custommatch_kickplayer_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_KickPlayer&>(::rtech::liveapi::_CustomMatch_KickPlayer_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_KickPlayer& Request::custommatch_kickplayer() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_KickPlayer) + return _internal_custommatch_kickplayer(); +} +inline ::rtech::liveapi::CustomMatch_KickPlayer* Request::unsafe_arena_release_custommatch_kickplayer() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_KickPlayer) + if (_internal_has_custommatch_kickplayer()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_KickPlayer* temp = _impl_.actions_.custommatch_kickplayer_; + _impl_.actions_.custommatch_kickplayer_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_kickplayer(::rtech::liveapi::CustomMatch_KickPlayer* custommatch_kickplayer) { + clear_actions(); + if (custommatch_kickplayer) { + set_has_custommatch_kickplayer(); + _impl_.actions_.custommatch_kickplayer_ = custommatch_kickplayer; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_KickPlayer) +} +inline ::rtech::liveapi::CustomMatch_KickPlayer* Request::_internal_mutable_custommatch_kickplayer() { + if (!_internal_has_custommatch_kickplayer()) { + clear_actions(); + set_has_custommatch_kickplayer(); + _impl_.actions_.custommatch_kickplayer_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_KickPlayer >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_kickplayer_; +} +inline ::rtech::liveapi::CustomMatch_KickPlayer* Request::mutable_custommatch_kickplayer() { + ::rtech::liveapi::CustomMatch_KickPlayer* _msg = _internal_mutable_custommatch_kickplayer(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_KickPlayer) + return _msg; +} + +// .rtech.liveapi.CustomMatch_SetSettings customMatch_SetSettings = 17; +inline bool Request::_internal_has_custommatch_setsettings() const { + return actions_case() == kCustomMatchSetSettings; +} +inline bool Request::has_custommatch_setsettings() const { + return _internal_has_custommatch_setsettings(); +} +inline void Request::set_has_custommatch_setsettings() { + _impl_._oneof_case_[0] = kCustomMatchSetSettings; +} +inline void Request::clear_custommatch_setsettings() { + if (_internal_has_custommatch_setsettings()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_setsettings_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_SetSettings* Request::release_custommatch_setsettings() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_SetSettings) + if (_internal_has_custommatch_setsettings()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SetSettings* temp = _impl_.actions_.custommatch_setsettings_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_setsettings_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_SetSettings& Request::_internal_custommatch_setsettings() const { + return _internal_has_custommatch_setsettings() + ? *_impl_.actions_.custommatch_setsettings_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_SetSettings&>(::rtech::liveapi::_CustomMatch_SetSettings_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_SetSettings& Request::custommatch_setsettings() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_SetSettings) + return _internal_custommatch_setsettings(); +} +inline ::rtech::liveapi::CustomMatch_SetSettings* Request::unsafe_arena_release_custommatch_setsettings() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_SetSettings) + if (_internal_has_custommatch_setsettings()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SetSettings* temp = _impl_.actions_.custommatch_setsettings_; + _impl_.actions_.custommatch_setsettings_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_setsettings(::rtech::liveapi::CustomMatch_SetSettings* custommatch_setsettings) { + clear_actions(); + if (custommatch_setsettings) { + set_has_custommatch_setsettings(); + _impl_.actions_.custommatch_setsettings_ = custommatch_setsettings; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_SetSettings) +} +inline ::rtech::liveapi::CustomMatch_SetSettings* Request::_internal_mutable_custommatch_setsettings() { + if (!_internal_has_custommatch_setsettings()) { + clear_actions(); + set_has_custommatch_setsettings(); + _impl_.actions_.custommatch_setsettings_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SetSettings >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_setsettings_; +} +inline ::rtech::liveapi::CustomMatch_SetSettings* Request::mutable_custommatch_setsettings() { + ::rtech::liveapi::CustomMatch_SetSettings* _msg = _internal_mutable_custommatch_setsettings(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_SetSettings) + return _msg; +} + +// .rtech.liveapi.CustomMatch_SendChat customMatch_SendChat = 18; +inline bool Request::_internal_has_custommatch_sendchat() const { + return actions_case() == kCustomMatchSendChat; +} +inline bool Request::has_custommatch_sendchat() const { + return _internal_has_custommatch_sendchat(); +} +inline void Request::set_has_custommatch_sendchat() { + _impl_._oneof_case_[0] = kCustomMatchSendChat; +} +inline void Request::clear_custommatch_sendchat() { + if (_internal_has_custommatch_sendchat()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_sendchat_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_SendChat* Request::release_custommatch_sendchat() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_SendChat) + if (_internal_has_custommatch_sendchat()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SendChat* temp = _impl_.actions_.custommatch_sendchat_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_sendchat_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_SendChat& Request::_internal_custommatch_sendchat() const { + return _internal_has_custommatch_sendchat() + ? *_impl_.actions_.custommatch_sendchat_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_SendChat&>(::rtech::liveapi::_CustomMatch_SendChat_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_SendChat& Request::custommatch_sendchat() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_SendChat) + return _internal_custommatch_sendchat(); +} +inline ::rtech::liveapi::CustomMatch_SendChat* Request::unsafe_arena_release_custommatch_sendchat() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_SendChat) + if (_internal_has_custommatch_sendchat()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SendChat* temp = _impl_.actions_.custommatch_sendchat_; + _impl_.actions_.custommatch_sendchat_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_sendchat(::rtech::liveapi::CustomMatch_SendChat* custommatch_sendchat) { + clear_actions(); + if (custommatch_sendchat) { + set_has_custommatch_sendchat(); + _impl_.actions_.custommatch_sendchat_ = custommatch_sendchat; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_SendChat) +} +inline ::rtech::liveapi::CustomMatch_SendChat* Request::_internal_mutable_custommatch_sendchat() { + if (!_internal_has_custommatch_sendchat()) { + clear_actions(); + set_has_custommatch_sendchat(); + _impl_.actions_.custommatch_sendchat_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SendChat >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_sendchat_; +} +inline ::rtech::liveapi::CustomMatch_SendChat* Request::mutable_custommatch_sendchat() { + ::rtech::liveapi::CustomMatch_SendChat* _msg = _internal_mutable_custommatch_sendchat(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_SendChat) + return _msg; +} + +// .rtech.liveapi.CustomMatch_GetLobbyPlayers customMatch_GetLobbyPlayers = 19; +inline bool Request::_internal_has_custommatch_getlobbyplayers() const { + return actions_case() == kCustomMatchGetLobbyPlayers; +} +inline bool Request::has_custommatch_getlobbyplayers() const { + return _internal_has_custommatch_getlobbyplayers(); +} +inline void Request::set_has_custommatch_getlobbyplayers() { + _impl_._oneof_case_[0] = kCustomMatchGetLobbyPlayers; +} +inline void Request::clear_custommatch_getlobbyplayers() { + if (_internal_has_custommatch_getlobbyplayers()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_getlobbyplayers_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_GetLobbyPlayers* Request::release_custommatch_getlobbyplayers() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_GetLobbyPlayers) + if (_internal_has_custommatch_getlobbyplayers()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_GetLobbyPlayers* temp = _impl_.actions_.custommatch_getlobbyplayers_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_getlobbyplayers_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_GetLobbyPlayers& Request::_internal_custommatch_getlobbyplayers() const { + return _internal_has_custommatch_getlobbyplayers() + ? *_impl_.actions_.custommatch_getlobbyplayers_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_GetLobbyPlayers&>(::rtech::liveapi::_CustomMatch_GetLobbyPlayers_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_GetLobbyPlayers& Request::custommatch_getlobbyplayers() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_GetLobbyPlayers) + return _internal_custommatch_getlobbyplayers(); +} +inline ::rtech::liveapi::CustomMatch_GetLobbyPlayers* Request::unsafe_arena_release_custommatch_getlobbyplayers() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_GetLobbyPlayers) + if (_internal_has_custommatch_getlobbyplayers()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_GetLobbyPlayers* temp = _impl_.actions_.custommatch_getlobbyplayers_; + _impl_.actions_.custommatch_getlobbyplayers_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_getlobbyplayers(::rtech::liveapi::CustomMatch_GetLobbyPlayers* custommatch_getlobbyplayers) { + clear_actions(); + if (custommatch_getlobbyplayers) { + set_has_custommatch_getlobbyplayers(); + _impl_.actions_.custommatch_getlobbyplayers_ = custommatch_getlobbyplayers; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_GetLobbyPlayers) +} +inline ::rtech::liveapi::CustomMatch_GetLobbyPlayers* Request::_internal_mutable_custommatch_getlobbyplayers() { + if (!_internal_has_custommatch_getlobbyplayers()) { + clear_actions(); + set_has_custommatch_getlobbyplayers(); + _impl_.actions_.custommatch_getlobbyplayers_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_GetLobbyPlayers >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_getlobbyplayers_; +} +inline ::rtech::liveapi::CustomMatch_GetLobbyPlayers* Request::mutable_custommatch_getlobbyplayers() { + ::rtech::liveapi::CustomMatch_GetLobbyPlayers* _msg = _internal_mutable_custommatch_getlobbyplayers(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_GetLobbyPlayers) + return _msg; +} + +// .rtech.liveapi.CustomMatch_SetTeamName customMatch_SetTeamName = 20; +inline bool Request::_internal_has_custommatch_setteamname() const { + return actions_case() == kCustomMatchSetTeamName; +} +inline bool Request::has_custommatch_setteamname() const { + return _internal_has_custommatch_setteamname(); +} +inline void Request::set_has_custommatch_setteamname() { + _impl_._oneof_case_[0] = kCustomMatchSetTeamName; +} +inline void Request::clear_custommatch_setteamname() { + if (_internal_has_custommatch_setteamname()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_setteamname_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_SetTeamName* Request::release_custommatch_setteamname() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_SetTeamName) + if (_internal_has_custommatch_setteamname()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SetTeamName* temp = _impl_.actions_.custommatch_setteamname_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_setteamname_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_SetTeamName& Request::_internal_custommatch_setteamname() const { + return _internal_has_custommatch_setteamname() + ? *_impl_.actions_.custommatch_setteamname_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_SetTeamName&>(::rtech::liveapi::_CustomMatch_SetTeamName_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_SetTeamName& Request::custommatch_setteamname() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_SetTeamName) + return _internal_custommatch_setteamname(); +} +inline ::rtech::liveapi::CustomMatch_SetTeamName* Request::unsafe_arena_release_custommatch_setteamname() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_SetTeamName) + if (_internal_has_custommatch_setteamname()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_SetTeamName* temp = _impl_.actions_.custommatch_setteamname_; + _impl_.actions_.custommatch_setteamname_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_setteamname(::rtech::liveapi::CustomMatch_SetTeamName* custommatch_setteamname) { + clear_actions(); + if (custommatch_setteamname) { + set_has_custommatch_setteamname(); + _impl_.actions_.custommatch_setteamname_ = custommatch_setteamname; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_SetTeamName) +} +inline ::rtech::liveapi::CustomMatch_SetTeamName* Request::_internal_mutable_custommatch_setteamname() { + if (!_internal_has_custommatch_setteamname()) { + clear_actions(); + set_has_custommatch_setteamname(); + _impl_.actions_.custommatch_setteamname_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_SetTeamName >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_setteamname_; +} +inline ::rtech::liveapi::CustomMatch_SetTeamName* Request::mutable_custommatch_setteamname() { + ::rtech::liveapi::CustomMatch_SetTeamName* _msg = _internal_mutable_custommatch_setteamname(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_SetTeamName) + return _msg; +} + +// .rtech.liveapi.CustomMatch_GetSettings customMatch_GetSettings = 21; +inline bool Request::_internal_has_custommatch_getsettings() const { + return actions_case() == kCustomMatchGetSettings; +} +inline bool Request::has_custommatch_getsettings() const { + return _internal_has_custommatch_getsettings(); +} +inline void Request::set_has_custommatch_getsettings() { + _impl_._oneof_case_[0] = kCustomMatchGetSettings; +} +inline void Request::clear_custommatch_getsettings() { + if (_internal_has_custommatch_getsettings()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.actions_.custommatch_getsettings_; + } + clear_has_actions(); + } +} +inline ::rtech::liveapi::CustomMatch_GetSettings* Request::release_custommatch_getsettings() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Request.customMatch_GetSettings) + if (_internal_has_custommatch_getsettings()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_GetSettings* temp = _impl_.actions_.custommatch_getsettings_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.actions_.custommatch_getsettings_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::rtech::liveapi::CustomMatch_GetSettings& Request::_internal_custommatch_getsettings() const { + return _internal_has_custommatch_getsettings() + ? *_impl_.actions_.custommatch_getsettings_ + : reinterpret_cast< ::rtech::liveapi::CustomMatch_GetSettings&>(::rtech::liveapi::_CustomMatch_GetSettings_default_instance_); +} +inline const ::rtech::liveapi::CustomMatch_GetSettings& Request::custommatch_getsettings() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Request.customMatch_GetSettings) + return _internal_custommatch_getsettings(); +} +inline ::rtech::liveapi::CustomMatch_GetSettings* Request::unsafe_arena_release_custommatch_getsettings() { + // @@protoc_insertion_point(field_unsafe_arena_release:rtech.liveapi.Request.customMatch_GetSettings) + if (_internal_has_custommatch_getsettings()) { + clear_has_actions(); + ::rtech::liveapi::CustomMatch_GetSettings* temp = _impl_.actions_.custommatch_getsettings_; + _impl_.actions_.custommatch_getsettings_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void Request::unsafe_arena_set_allocated_custommatch_getsettings(::rtech::liveapi::CustomMatch_GetSettings* custommatch_getsettings) { + clear_actions(); + if (custommatch_getsettings) { + set_has_custommatch_getsettings(); + _impl_.actions_.custommatch_getsettings_ = custommatch_getsettings; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Request.customMatch_GetSettings) +} +inline ::rtech::liveapi::CustomMatch_GetSettings* Request::_internal_mutable_custommatch_getsettings() { + if (!_internal_has_custommatch_getsettings()) { + clear_actions(); + set_has_custommatch_getsettings(); + _impl_.actions_.custommatch_getsettings_ = CreateMaybeMessage< ::rtech::liveapi::CustomMatch_GetSettings >(GetArenaForAllocation()); + } + return _impl_.actions_.custommatch_getsettings_; +} +inline ::rtech::liveapi::CustomMatch_GetSettings* Request::mutable_custommatch_getsettings() { + ::rtech::liveapi::CustomMatch_GetSettings* _msg = _internal_mutable_custommatch_getsettings(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Request.customMatch_GetSettings) + return _msg; +} + +inline bool Request::has_actions() const { + return actions_case() != ACTIONS_NOT_SET; +} +inline void Request::clear_has_actions() { + _impl_._oneof_case_[0] = ACTIONS_NOT_SET; +} +inline Request::ActionsCase Request::actions_case() const { + return Request::ActionsCase(_impl_._oneof_case_[0]); +} +// ------------------------------------------------------------------- + +// RequestStatus + +// string status = 1; +inline void RequestStatus::clear_status() { + _impl_.status_.ClearToEmpty(); +} +inline const std::string& RequestStatus::status() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.RequestStatus.status) + return _internal_status(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void RequestStatus::set_status(ArgT0&& arg0, ArgT... args) { + + _impl_.status_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:rtech.liveapi.RequestStatus.status) +} +inline std::string* RequestStatus::mutable_status() { + std::string* _s = _internal_mutable_status(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.RequestStatus.status) + return _s; +} +inline const std::string& RequestStatus::_internal_status() const { + return _impl_.status_.Get(); +} +inline void RequestStatus::_internal_set_status(const std::string& value) { + + _impl_.status_.Set(value, GetArenaForAllocation()); +} +inline std::string* RequestStatus::_internal_mutable_status() { + + return _impl_.status_.Mutable(GetArenaForAllocation()); +} +inline std::string* RequestStatus::release_status() { + // @@protoc_insertion_point(field_release:rtech.liveapi.RequestStatus.status) + return _impl_.status_.Release(); +} +inline void RequestStatus::set_allocated_status(std::string* status) { + if (status != nullptr) { + + } else { + + } + _impl_.status_.SetAllocated(status, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.status_.IsDefault()) { + _impl_.status_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.RequestStatus.status) +} + +// ------------------------------------------------------------------- + +// Response + +// bool success = 1; +inline void Response::clear_success() { + _impl_.success_ = false; +} +inline bool Response::_internal_success() const { + return _impl_.success_; +} +inline bool Response::success() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Response.success) + return _internal_success(); +} +inline void Response::_internal_set_success(bool value) { + + _impl_.success_ = value; +} +inline void Response::set_success(bool value) { + _internal_set_success(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.Response.success) +} + +// .google.protobuf.Any result = 2; +inline bool Response::_internal_has_result() const { + return this != internal_default_instance() && _impl_.result_ != nullptr; +} +inline bool Response::has_result() const { + return _internal_has_result(); +} +inline const ::PROTOBUF_NAMESPACE_ID::Any& Response::_internal_result() const { + const ::PROTOBUF_NAMESPACE_ID::Any* p = _impl_.result_; + return p != nullptr ? *p : reinterpret_cast( + ::PROTOBUF_NAMESPACE_ID::_Any_default_instance_); +} +inline const ::PROTOBUF_NAMESPACE_ID::Any& Response::result() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.Response.result) + return _internal_result(); +} +inline void Response::unsafe_arena_set_allocated_result( + ::PROTOBUF_NAMESPACE_ID::Any* result) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.result_); + } + _impl_.result_ = result; + if (result) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.Response.result) +} +inline ::PROTOBUF_NAMESPACE_ID::Any* Response::release_result() { + + ::PROTOBUF_NAMESPACE_ID::Any* temp = _impl_.result_; + _impl_.result_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::PROTOBUF_NAMESPACE_ID::Any* Response::unsafe_arena_release_result() { + // @@protoc_insertion_point(field_release:rtech.liveapi.Response.result) + + ::PROTOBUF_NAMESPACE_ID::Any* temp = _impl_.result_; + _impl_.result_ = nullptr; + return temp; +} +inline ::PROTOBUF_NAMESPACE_ID::Any* Response::_internal_mutable_result() { + + if (_impl_.result_ == nullptr) { + auto* p = CreateMaybeMessage<::PROTOBUF_NAMESPACE_ID::Any>(GetArenaForAllocation()); + _impl_.result_ = p; + } + return _impl_.result_; +} +inline ::PROTOBUF_NAMESPACE_ID::Any* Response::mutable_result() { + ::PROTOBUF_NAMESPACE_ID::Any* _msg = _internal_mutable_result(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.Response.result) + return _msg; +} +inline void Response::set_allocated_result(::PROTOBUF_NAMESPACE_ID::Any* result) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete reinterpret_cast< ::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.result_); + } + if (result) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena( + reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(result)); + if (message_arena != submessage_arena) { + result = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, result, submessage_arena); + } + + } else { + + } + _impl_.result_ = result; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.Response.result) +} + +// ------------------------------------------------------------------- + +// LiveAPIEvent + +// fixed32 event_size = 1; +inline void LiveAPIEvent::clear_event_size() { + _impl_.event_size_ = 0u; +} +inline uint32_t LiveAPIEvent::_internal_event_size() const { + return _impl_.event_size_; +} +inline uint32_t LiveAPIEvent::event_size() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.LiveAPIEvent.event_size) + return _internal_event_size(); +} +inline void LiveAPIEvent::_internal_set_event_size(uint32_t value) { + + _impl_.event_size_ = value; +} +inline void LiveAPIEvent::set_event_size(uint32_t value) { + _internal_set_event_size(value); + // @@protoc_insertion_point(field_set:rtech.liveapi.LiveAPIEvent.event_size) +} + +// .google.protobuf.Any gameMessage = 3; +inline bool LiveAPIEvent::_internal_has_gamemessage() const { + return this != internal_default_instance() && _impl_.gamemessage_ != nullptr; +} +inline bool LiveAPIEvent::has_gamemessage() const { + return _internal_has_gamemessage(); +} +inline const ::PROTOBUF_NAMESPACE_ID::Any& LiveAPIEvent::_internal_gamemessage() const { + const ::PROTOBUF_NAMESPACE_ID::Any* p = _impl_.gamemessage_; + return p != nullptr ? *p : reinterpret_cast( + ::PROTOBUF_NAMESPACE_ID::_Any_default_instance_); +} +inline const ::PROTOBUF_NAMESPACE_ID::Any& LiveAPIEvent::gamemessage() const { + // @@protoc_insertion_point(field_get:rtech.liveapi.LiveAPIEvent.gameMessage) + return _internal_gamemessage(); +} +inline void LiveAPIEvent::unsafe_arena_set_allocated_gamemessage( + ::PROTOBUF_NAMESPACE_ID::Any* gamemessage) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.gamemessage_); + } + _impl_.gamemessage_ = gamemessage; + if (gamemessage) { + + } else { + + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:rtech.liveapi.LiveAPIEvent.gameMessage) +} +inline ::PROTOBUF_NAMESPACE_ID::Any* LiveAPIEvent::release_gamemessage() { + + ::PROTOBUF_NAMESPACE_ID::Any* temp = _impl_.gamemessage_; + _impl_.gamemessage_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::PROTOBUF_NAMESPACE_ID::Any* LiveAPIEvent::unsafe_arena_release_gamemessage() { + // @@protoc_insertion_point(field_release:rtech.liveapi.LiveAPIEvent.gameMessage) + + ::PROTOBUF_NAMESPACE_ID::Any* temp = _impl_.gamemessage_; + _impl_.gamemessage_ = nullptr; + return temp; +} +inline ::PROTOBUF_NAMESPACE_ID::Any* LiveAPIEvent::_internal_mutable_gamemessage() { + + if (_impl_.gamemessage_ == nullptr) { + auto* p = CreateMaybeMessage<::PROTOBUF_NAMESPACE_ID::Any>(GetArenaForAllocation()); + _impl_.gamemessage_ = p; + } + return _impl_.gamemessage_; +} +inline ::PROTOBUF_NAMESPACE_ID::Any* LiveAPIEvent::mutable_gamemessage() { + ::PROTOBUF_NAMESPACE_ID::Any* _msg = _internal_mutable_gamemessage(); + // @@protoc_insertion_point(field_mutable:rtech.liveapi.LiveAPIEvent.gameMessage) + return _msg; +} +inline void LiveAPIEvent::set_allocated_gamemessage(::PROTOBUF_NAMESPACE_ID::Any* gamemessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete reinterpret_cast< ::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.gamemessage_); + } + if (gamemessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena( + reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(gamemessage)); + if (message_arena != submessage_arena) { + gamemessage = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, gamemessage, submessage_arena); + } + + } else { + + } + _impl_.gamemessage_ = gamemessage; + // @@protoc_insertion_point(field_set_allocated:rtech.liveapi.LiveAPIEvent.gameMessage) +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif // __GNUC__ +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + + +// @@protoc_insertion_point(namespace_scope) + +} // namespace liveapi +} // namespace rtech + +PROTOBUF_NAMESPACE_OPEN + +template <> struct is_proto_enum< ::rtech::liveapi::PlayerOfInterest> : ::std::true_type {}; +template <> +inline const EnumDescriptor* GetEnumDescriptor< ::rtech::liveapi::PlayerOfInterest>() { + return ::rtech::liveapi::PlayerOfInterest_descriptor(); +} + +PROTOBUF_NAMESPACE_CLOSE + +// @@protoc_insertion_point(global_scope) + +#include +#endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_events_2eproto diff --git a/src/public/rtech/ipakfile.h b/src/public/rtech/ipakfile.h index e238de27..e2c81a10 100644 --- a/src/public/rtech/ipakfile.h +++ b/src/public/rtech/ipakfile.h @@ -548,15 +548,11 @@ struct PakDecoder_t size_t bufferSizeNeeded; - union - { - // current byte and current bit of byte - uint64_t currentByte; - }; - + // current byte and current bit of byte + uint64_t currentByte; uint32_t currentBit; - uint32_t dword6C; + uint32_t dword6C; uint64_t qword70; union diff --git a/src/public/tier0/dbg.h b/src/public/tier0/dbg.h index fcc69833..14e8fe50 100644 --- a/src/public/tier0/dbg.h +++ b/src/public/tier0/dbg.h @@ -161,6 +161,38 @@ template inline void AssertValidReadWritePtr(T* /*ptr*/, int count = 1) #define AssertValidThis() #endif +//----------------------------------------------------------------------------- +// Macro to protect functions that are not reentrant + +#ifdef _DEBUG +class CReentryGuard +{ +public: + CReentryGuard(int* pSemaphore) + : m_pSemaphore(pSemaphore) + { + ++(*m_pSemaphore); + } + + ~CReentryGuard() + { + --(*m_pSemaphore); + } + +private: + int* m_pSemaphore; +}; + +#define ASSERT_NO_REENTRY() \ + static int fSemaphore##__LINE__; \ + Assert( !fSemaphore##__LINE__ ); \ + CReentryGuard ReentryGuard##__LINE__( &fSemaphore##__LINE__ ) +#else +#define ASSERT_NO_REENTRY() +#endif + +#define AssertMsg(condition, ...) assert(condition) + typedef void (*CoreMsgVCallbackSink_t)(LogType_t logType, LogLevel_t logLevel, eDLL_T context, const char* pszLogger, const char* pszFormat, va_list args, const UINT exitCode, const char* pszUptimeOverride); diff --git a/src/public/tier0/jobthread.h b/src/public/tier0/jobthread.h index 744ac7db..8ddb6c4a 100644 --- a/src/public/tier0/jobthread.h +++ b/src/public/tier0/jobthread.h @@ -30,18 +30,56 @@ struct JobContext_s __int64 unknownInt; }; +typedef struct JobUserData_s +{ + JobUserData_s(int32_t si) + { + data.sint = si; + } + JobUserData_s(uint32_t ui) + { + data.uint = ui; + } + JobUserData_s(int64_t si) + { + data.sint = si; + } + JobUserData_s(uint64_t ui) + { + data.uint = ui; + } + JobUserData_s(double sa) + { + data.scal = sa; + } + JobUserData_s(void* pt) + { + data.ptr = pt; + } + + union + { + int64_t sint; + uint64_t uint; + double scal; + void* ptr; + } data; +} JobUserData_t; + // Array size = 2048*sizeof(JobContext_s) inline JobContext_s* job_JT_Context = nullptr; extern bool JT_IsJobDone(const JobID_t jobId); extern JobID_t JTGuts_AddJob(JobTypeID_t jobTypeId, JobID_t jobId, void* callbackFunc, void* callbackArg); +extern JobID_t JT_GetCurrentJob(); inline void(*JT_ParallelCall)(void); inline void*(*JT_HelpWithAnything)(bool bShouldLoadPak); -inline bool(*JT_HelpWithJobTypes)(JobHelpCallback_t, JobFifoLock_s* pFifoLock, __int64 a3, __int64 a4); -inline __int64(*JT_HelpWithJobTypesOrSleep)(JobHelpCallback_t, JobFifoLock_s* pFifoLock, __int64 a3, __int64 a4, volatile signed __int64* a5, char a6); +inline bool(*JT_HelpWithJobTypes)(JobHelpCallback_t, JobUserData_t userData, __int64 a3, __int64 a4); +inline __int64(*JT_HelpWithJobTypesOrSleep)(JobHelpCallback_t, JobUserData_t userData, __int64 a3, __int64 a4, volatile signed __int64* a5, char a6); +inline __int64(*JT_WaitForJobAndOnlyHelpWithJobTypes)(JobID_t, uint64_t unkMask1, uint64_t unkMask2); inline bool(*JT_AcquireFifoLockOrHelp)(struct JobFifoLock_s* pFifo); inline void(*JT_ReleaseFifoLock)(struct JobFifoLock_s* pFifo); @@ -61,6 +99,7 @@ class VJobThread : public IDetour LogFunAdr("JT_HelpWithAnything", JT_HelpWithAnything); LogFunAdr("JT_HelpWithJobTypes", JT_HelpWithJobTypes); LogFunAdr("JT_HelpWithJobTypesOrSleep", JT_HelpWithJobTypesOrSleep); + LogFunAdr("JT_WaitForJobAndOnlyHelpWithJobTypes", JT_WaitForJobAndOnlyHelpWithJobTypes); LogFunAdr("JT_AcquireFifoLockOrHelp", JT_AcquireFifoLockOrHelp); LogFunAdr("JT_ReleaseFifoLock", JT_ReleaseFifoLock); @@ -76,6 +115,7 @@ class VJobThread : public IDetour g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 80 3D ?? ?? ?? ?? ??").GetPtr(JT_HelpWithAnything); g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 4C 89 4C 24 ?? 4C 89 44 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 83 EC 60").GetPtr(JT_HelpWithJobTypes); g_GameDll.FindPatternSIMD("4C 89 4C 24 ?? 4C 89 44 24 ?? 48 89 54 24 ?? 48 89 4C 24 ?? 55 53 56 57 41 54 41 55 41 56 41 57 48 8D 6C 24 ??").GetPtr(JT_HelpWithJobTypesOrSleep); + g_GameDll.FindPatternSIMD("48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 30 8B F9").GetPtr(JT_WaitForJobAndOnlyHelpWithJobTypes); g_GameDll.FindPatternSIMD("48 83 EC 08 65 48 8B 04 25 ?? ?? ?? ?? 4C 8B C1").GetPtr(JT_AcquireFifoLockOrHelp); g_GameDll.FindPatternSIMD("48 83 EC 28 44 8B 11").GetPtr(JT_ReleaseFifoLock); g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 30 65 48 8B 04 25 ?? ?? ?? ?? BA ?? ?? ?? ??").GetPtr(JT_AllocateJob); diff --git a/src/public/tier0/threadtools.h b/src/public/tier0/threadtools.h index a531269c..e47e2379 100644 --- a/src/public/tier0/threadtools.h +++ b/src/public/tier0/threadtools.h @@ -1,6 +1,9 @@ #ifndef THREADTOOLS_H #define THREADTOOLS_H #include "dbg.h" +#ifndef BUILDING_MATHLIB +#include "jobthread.h" +#endif // BUILDING_MATHLIB inline void ThreadSleep(unsigned nMilliseconds) { @@ -116,16 +119,14 @@ FORCEINLINE ThreadId_t ThreadGetCurrentId() extern ThreadId_t* g_ThreadMainThreadID; extern ThreadId_t* g_ThreadServerFrameThreadID; +inline JobID_t* g_CurrentServerFrameJobID; +inline JobID_t* g_AllocatedServerFrameJobID; -FORCEINLINE bool ThreadInMainThread() -{ - return (ThreadGetCurrentId() == (*g_ThreadMainThreadID)); -} - -FORCEINLINE bool ThreadInServerFrameThread() -{ - return (ThreadGetCurrentId() == (*g_ThreadServerFrameThreadID)); -} +PLATFORM_INTERFACE bool ThreadInMainThread(); +PLATFORM_INTERFACE bool ThreadInServerFrameThread(); +PLATFORM_INTERFACE bool ThreadInMainOrServerFrameThread(); +PLATFORM_INTERFACE bool ThreadCouldDoServerWork(); +PLATFORM_INTERFACE void ThreadJoinServerJob(); #endif // !BUILDING_MATHLIB @@ -263,7 +264,7 @@ typedef CInterlockedIntT CInterlockedUInt; #ifndef BUILDING_MATHLIB //============================================================================= -inline ThreadId_t(*v_DeclareCurrentThreadIsMainThread)(void); + #endif // !BUILDING_MATHLIB @@ -431,9 +432,10 @@ FORCEINLINE bool CThreadSpinRWLock::TryLockForWrite() if (!(curValue.m_i32 & 0x00010000) && ThreadInterlockedAssignIf((LONG*)&curValue.m_i32, 0x00010000, 0)) { ThreadMemoryBarrier(); - Assert(m_iWriteDepth == 0 && m_writerId == 0); + Assert(m_writerId == 0); m_writerId = ThreadGetCurrentId(); #ifdef REENTRANT_THREAD_SPIN_RW_LOCK + Assert(m_iWriteDepth == 0); m_iWriteDepth++; #endif return true; @@ -561,15 +563,20 @@ class VThreadTools : public IDetour { virtual void GetAdr(void) const { - LogFunAdr("DeclareCurrentThreadIsMainThread", v_DeclareCurrentThreadIsMainThread); LogVarAdr("g_ThreadMainThreadID", g_ThreadMainThreadID); LogVarAdr("g_ThreadServerFrameThreadID", g_ThreadServerFrameThreadID); + LogVarAdr("g_CurrentServerFrameJobID", g_CurrentServerFrameJobID); + LogVarAdr("g_AllocatedServerFrameJobID", g_AllocatedServerFrameJobID); } - virtual void GetFun(void) const + virtual void GetFun(void) const { } + virtual void GetVar(void) const { - g_GameDll.FindPatternSIMD("48 83 EC 28 FF 15 ?? ?? ?? ?? 89 05 ?? ?? ?? ?? 48 83 C4 28").GetPtr(v_DeclareCurrentThreadIsMainThread); + g_GameDll.FindPatternSIMD("66 89 54 24 ?? 53 55 56 57 41 54 48 81 EC ?? ?? ?? ??") + .FindPatternSelf("39 05").ResolveRelativeAddressSelf(2, 6).GetPtr(g_CurrentServerFrameJobID); + + g_GameDll.FindPatternSIMD("48 83 EC 28 FF 15 ?? ?? ?? ?? 8B 0D ?? ?? ?? ??") + .FindPatternSelf("8B 0D").ResolveRelativeAddressSelf(2, 6).GetPtr(g_AllocatedServerFrameJobID); } - virtual void GetVar(void) const { } virtual void GetCon(void) const { } virtual void Detour(const bool /*bAttach*/) const { } }; diff --git a/src/public/tier0/utility.h b/src/public/tier0/utility.h index cf8e2e1a..f245e34e 100644 --- a/src/public/tier0/utility.h +++ b/src/public/tier0/utility.h @@ -122,6 +122,7 @@ int CompareIPv6(const IN6_ADDR& ipA, const IN6_ADDR& ipB); ///////////////////////////////////////////////////////////////////////////// // Time +uint64_t GetUnixTimeStamp(); std::chrono::nanoseconds IntervalToDuration(const float flInterval); ///////////////////////////////////////////////////////////////////////////// diff --git a/src/public/tier1/depthcounter.h b/src/public/tier1/depthcounter.h new file mode 100644 index 00000000..269d5ff0 --- /dev/null +++ b/src/public/tier1/depthcounter.h @@ -0,0 +1,31 @@ +//=============================================================================// +// +// Purpose: simple class that could be used to manage depths of nested elements +// +//=============================================================================// +#ifndef TIER1_DEPTHCOUNTER_H +#define TIER1_DEPTHCOUNTER_H + +template +class CDepthCounter +{ +public: + CDepthCounter(T& counter) : ref(counter) + { + ref++; + } + ~CDepthCounter() + { + ref--; + } + + T Get() + { + return ref; + } + +private: + T& ref; +}; + +#endif // TIER1_DEPTHCOUNTER_H diff --git a/src/public/tier1/strtools.h b/src/public/tier1/strtools.h index ba644b57..20508762 100644 --- a/src/public/tier1/strtools.h +++ b/src/public/tier1/strtools.h @@ -90,6 +90,10 @@ ssize_t V_vsnprintfRet(char* pDest, size_t maxLen, const char* pFormat, va_list // Strip white space at the beginning and end of a string ssize_t V_StrTrim(char* pStr); +class CUtlStringList; +void V_SplitString2(const char* pString, const char** pSeparators, ssize_t nSeparators, CUtlStringList& outStrings); +void V_SplitString(const char* pString, const char* pSeparator, CUtlStringList& outStrings); + int V_UTF8ToUnicode(const char* pUTF8, wchar_t* pwchDest, int cubDestSizeInBytes); int V_UnicodeToUTF8(const wchar_t* pUnicode, char* pUTF8, int cubDestSizeInBytes); diff --git a/src/public/tier1/utlhash.h b/src/public/tier1/utlhash.h new file mode 100644 index 00000000..014504e5 --- /dev/null +++ b/src/public/tier1/utlhash.h @@ -0,0 +1,1299 @@ +//====== Copyright 1996-2005, Valve Corporation, All rights reserved. =======// +// +// Purpose: +// +// $NoKeywords: $ +// +// Serialization/unserialization buffer +//=============================================================================// + +#ifndef UTLHASH_H +#define UTLHASH_H +#pragma once + +#include +#include "tier0/commonmacros.h" +#include "utlmemory.h" +#include "utlvector.h" +#include "utllinkedlist.h" +#include "utllinkedlist.h" +#include "generichash.h" + +typedef unsigned int UtlHashHandle_t; + +template +class CUtlHash +{ +public: + // compare and key functions - implemented by the + typedef C CompareFunc_t; + typedef K KeyFunc_t; + + // constructor/deconstructor + explicit CUtlHash( int bucketCount = 0, int growCount = 0, int initCount = 0, + CompareFunc_t compareFunc = 0, KeyFunc_t keyFunc = 0 ); + ~CUtlHash(); + + // invalid handle + static UtlHashHandle_t InvalidHandle( void ) { return ( UtlHashHandle_t )~0; } + bool IsValidHandle( UtlHashHandle_t handle ) const; + + // size + int Count( void ) const; + + // memory + void Purge( void ); + + // insertion methods + UtlHashHandle_t Insert( Data const &src ); + UtlHashHandle_t Insert( Data const &src, bool *pDidInsert ); + UtlHashHandle_t AllocEntryFromKey( Data const &src ); + + // removal methods + void Remove( UtlHashHandle_t handle ); + void RemoveAll(); + + // retrieval methods + UtlHashHandle_t Find( Data const &src ) const; + + Data &Element( UtlHashHandle_t handle ); + Data const &Element( UtlHashHandle_t handle ) const; + Data &operator[]( UtlHashHandle_t handle ); + Data const &operator[]( UtlHashHandle_t handle ) const; + + UtlHashHandle_t GetFirstHandle() const; + UtlHashHandle_t GetNextHandle( UtlHashHandle_t h ) const; + + // debugging!! + void Log( const char *filename ); + void Dump(); + +protected: + + int GetBucketIndex( UtlHashHandle_t handle ) const; + int GetKeyDataIndex( UtlHashHandle_t handle ) const; + UtlHashHandle_t BuildHandle( int ndxBucket, int ndxKeyData ) const; + + bool DoFind( Data const &src, unsigned int *pBucket, int *pIndex ) const; + +protected: + + // handle upper 16 bits = bucket index (bucket heads) + // handle lower 16 bits = key index (bucket list) + typedef CUtlVector HashBucketList_t; + CUtlVector m_Buckets; + + CompareFunc_t m_CompareFunc; // function used to handle unique compares on data + KeyFunc_t m_KeyFunc; // function used to generate the key value + + bool m_bPowerOfTwo; // if the bucket value is a power of two, + unsigned int m_ModMask; // use the mod mask to "mod" +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +CUtlHash::CUtlHash( int bucketCount, int growCount, int initCount, + CompareFunc_t compareFunc, KeyFunc_t keyFunc ) : + m_CompareFunc( compareFunc ), + m_KeyFunc( keyFunc ) +{ + bucketCount = Min(bucketCount, 65536); + m_Buckets.SetSize( bucketCount ); + for( int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++ ) + { + m_Buckets[ndxBucket].SetSize( initCount ); + m_Buckets[ndxBucket].SetGrowSize( growCount ); + } + + // check to see if the bucket count is a power of 2 and set up + // optimizations appropriately + m_bPowerOfTwo = IsPowerOfTwo( bucketCount ); + m_ModMask = m_bPowerOfTwo ? (bucketCount-1) : 0; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +CUtlHash::~CUtlHash() +{ + Purge(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CUtlHash::IsValidHandle( UtlHashHandle_t handle ) const +{ + int ndxBucket = GetBucketIndex( handle ); + int ndxKeyData = GetKeyDataIndex( handle ); + + // ndxBucket and ndxKeyData can't possibly be less than zero -- take a + // look at the definition of the Get..Index functions for why. However, + // if you override those functions, you will need to override this one + // as well. + if( /*( ndxBucket >= 0 ) && */ ( ndxBucket < m_Buckets.Count() ) ) + { + if( /*( ndxKeyData >= 0 ) && */ ( ndxKeyData < m_Buckets[ndxBucket].Count() ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline int CUtlHash::Count( void ) const +{ + int count = 0; + + int bucketCount = m_Buckets.Count(); + for( int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++ ) + { + count += m_Buckets[ndxBucket].Count(); + } + + return count; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline int CUtlHash::GetBucketIndex( UtlHashHandle_t handle ) const +{ + return ( ( ( handle >> 16 ) & 0x0000ffff ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline int CUtlHash::GetKeyDataIndex( UtlHashHandle_t handle ) const +{ + return ( handle & 0x0000ffff ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::BuildHandle( int ndxBucket, int ndxKeyData ) const +{ + Assert( ( ndxBucket >= 0 ) && ( ndxBucket < 65536 ) ); + Assert( ( ndxKeyData >= 0 ) && ( ndxKeyData < 65536 ) ); + + UtlHashHandle_t handle = ndxKeyData; + handle |= ( ndxBucket << 16 ); + + return handle; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CUtlHash::Purge( void ) +{ + int bucketCount = m_Buckets.Count(); + for( int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++ ) + { + m_Buckets[ndxBucket].Purge(); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CUtlHash::DoFind( Data const &src, unsigned int *pBucket, int *pIndex ) const +{ + // generate the data "key" + unsigned int key = m_KeyFunc( src ); + + // hash the "key" - get the correct hash table "bucket" + unsigned int ndxBucket; + if( m_bPowerOfTwo ) + { + *pBucket = ndxBucket = ( key & m_ModMask ); + } + else + { + int bucketCount = m_Buckets.Count(); + *pBucket = ndxBucket = key % bucketCount; + } + + int ndxKeyData = 0; + const CUtlVector &bucket = m_Buckets[ndxBucket]; + int keyDataCount = bucket.Count(); + for( ndxKeyData = 0; ndxKeyData < keyDataCount; ndxKeyData++ ) + { + if( m_CompareFunc( bucket.Element( ndxKeyData ), src ) ) + break; + } + + if( ndxKeyData == keyDataCount ) + return false; + + *pIndex = ndxKeyData; + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::Find( Data const &src ) const +{ + unsigned int ndxBucket; + int ndxKeyData = 0; + + if ( DoFind( src, &ndxBucket, &ndxKeyData ) ) + { + return ( BuildHandle( ndxBucket, ndxKeyData ) ); + } + return ( InvalidHandle() ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::Insert( Data const &src ) +{ + unsigned int ndxBucket; + int ndxKeyData = 0; + + if ( DoFind( src, &ndxBucket, &ndxKeyData ) ) + { + return ( BuildHandle( ndxBucket, ndxKeyData ) ); + } + + ndxKeyData = m_Buckets[ndxBucket].AddToTail( src ); + + return ( BuildHandle( ndxBucket, ndxKeyData ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::Insert( Data const &src, bool *pDidInsert ) +{ + unsigned int ndxBucket; + int ndxKeyData = 0; + + if ( DoFind( src, &ndxBucket, &ndxKeyData ) ) + { + *pDidInsert = false; + return ( BuildHandle( ndxBucket, ndxKeyData ) ); + } + + *pDidInsert = true; + ndxKeyData = m_Buckets[ndxBucket].AddToTail( src ); + + return ( BuildHandle( ndxBucket, ndxKeyData ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::AllocEntryFromKey( Data const &src ) +{ + unsigned int ndxBucket; + int ndxKeyData = 0; + + if ( DoFind( src, &ndxBucket, &ndxKeyData ) ) + { + return ( BuildHandle( ndxBucket, ndxKeyData ) ); + } + + ndxKeyData = m_Buckets[ndxBucket].AddToTail(); + + return ( BuildHandle( ndxBucket, ndxKeyData ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CUtlHash::Remove( UtlHashHandle_t handle ) +{ + Assert( IsValidHandle( handle ) ); + + // check to see if the bucket exists + int ndxBucket = GetBucketIndex( handle ); + int ndxKeyData = GetKeyDataIndex( handle ); + + if( m_Buckets[ndxBucket].IsValidIndex( ndxKeyData ) ) + { + m_Buckets[ndxBucket].FastRemove( ndxKeyData ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CUtlHash::RemoveAll() +{ + int bucketCount = m_Buckets.Count(); + for( int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++ ) + { + m_Buckets[ndxBucket].RemoveAll(); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline Data &CUtlHash::Element( UtlHashHandle_t handle ) +{ + int ndxBucket = GetBucketIndex( handle ); + int ndxKeyData = GetKeyDataIndex( handle ); + + return ( m_Buckets[ndxBucket].Element( ndxKeyData ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline Data const &CUtlHash::Element( UtlHashHandle_t handle ) const +{ + int ndxBucket = GetBucketIndex( handle ); + int ndxKeyData = GetKeyDataIndex( handle ); + + return ( m_Buckets[ndxBucket].Element( ndxKeyData ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline Data &CUtlHash::operator[]( UtlHashHandle_t handle ) +{ + int ndxBucket = GetBucketIndex( handle ); + int ndxKeyData = GetKeyDataIndex( handle ); + + return ( m_Buckets[ndxBucket].Element( ndxKeyData ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline Data const &CUtlHash::operator[]( UtlHashHandle_t handle ) const +{ + int ndxBucket = GetBucketIndex( handle ); + int ndxKeyData = GetKeyDataIndex( handle ); + + return ( m_Buckets[ndxBucket].Element( ndxKeyData ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::GetFirstHandle() const +{ + return GetNextHandle( ( UtlHashHandle_t )-1 ); +} + +template +inline UtlHashHandle_t CUtlHash::GetNextHandle( UtlHashHandle_t handle ) const +{ + ++handle; // start at the first possible handle after the one given + + int bi = GetBucketIndex( handle ); + int ki = GetKeyDataIndex( handle ); + + int nBuckets = m_Buckets.Count(); + for ( ; bi < nBuckets; ++bi ) + { + if ( ki < m_Buckets[ bi ].Count() ) + return BuildHandle( bi, ki ); + + ki = 0; + } + + return InvalidHandle(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CUtlHash::Log( const char *filename ) +{ + FILE *pDebugFp; + pDebugFp = fopen( filename, "w" ); + if( !pDebugFp ) + return; + + int maxBucketSize = 0; + int numBucketsEmpty = 0; + + int bucketCount = m_Buckets.Count(); + fprintf( pDebugFp, "\n%d Buckets\n", bucketCount ); + + for( int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++ ) + { + int count = m_Buckets[ndxBucket].Count(); + + if( count > maxBucketSize ) { maxBucketSize = count; } + if( count == 0 ) + numBucketsEmpty++; + + fprintf( pDebugFp, "Bucket %d: %d\n", ndxBucket, count ); + } + + fprintf( pDebugFp, "\nBucketHeads Used: %d\n", bucketCount - numBucketsEmpty ); + fprintf( pDebugFp, "Max Bucket Size: %d\n", maxBucketSize ); + + fclose( pDebugFp ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CUtlHash::Dump( ) +{ + int maxBucketSize = 0; + int numBucketsEmpty = 0; + + int bucketCount = m_Buckets.Count(); + Msg( eDLL_T::COMMON, "\n%d Buckets\n", bucketCount ); + + for( int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++ ) + { + int count = m_Buckets[ndxBucket].Count(); + + if( count > maxBucketSize ) { maxBucketSize = count; } + if( count == 0 ) + numBucketsEmpty++; + + Msg( eDLL_T::COMMON, "Bucket %d: %d\n", ndxBucket, count ); + } + + Msg( eDLL_T::COMMON, "\nBucketHeads Used: %d\n", bucketCount - numBucketsEmpty ); + Msg( eDLL_T::COMMON, "Max Bucket Size: %d\n", maxBucketSize ); +} + +//============================================================================= +// +// Fast Hash +// +// Number of buckets must be a power of 2. +// Key must be 32-bits (unsigned int). +// +typedef intp UtlHashFastHandle_t; + +#define UTLHASH_POOL_SCALAR 2 + +class CUtlHashFastNoHash +{ +public: + static int Hash( int key, int bucketMask ) + { + return ( key & bucketMask ); + } +}; + +class CUtlHashFastGenericHash +{ +public: + static int Hash( int key, int bucketMask ) + { + return ( HashIntConventional( key ) & bucketMask ); + } +}; + +template +class CUtlHashFast +{ +public: + + // Constructor/Deconstructor. + CUtlHashFast(); + ~CUtlHashFast(); + + // Memory. + void Purge( void ); + + // Invalid handle. + static UtlHashFastHandle_t InvalidHandle( void ) { return ( UtlHashFastHandle_t )~0; } + inline bool IsValidHandle( UtlHashFastHandle_t hHash ) const; + + // Initialize. + bool Init( int nBucketCount ); + + // Size not available; count is meaningless for multilists. + // int Count( void ) const; + + // Insertion. + UtlHashFastHandle_t Insert( uintp uiKey, const Data &data ); + UtlHashFastHandle_t FastInsert( uintp uiKey, const Data &data ); + + // Removal. + void Remove( UtlHashFastHandle_t hHash ); + void RemoveAll( void ); + + // Retrieval. + UtlHashFastHandle_t Find( uintp uiKey ) const; + + Data &Element( UtlHashFastHandle_t hHash ); + Data const &Element( UtlHashFastHandle_t hHash ) const; + Data &operator[]( UtlHashFastHandle_t hHash ); + Data const &operator[]( UtlHashFastHandle_t hHash ) const; + + // Iteration + struct UtlHashFastIterator_t + { + int bucket; + UtlHashFastHandle_t handle; + + UtlHashFastIterator_t(int _bucket, const UtlHashFastHandle_t &_handle) + : bucket(_bucket), handle(_handle) {}; + // inline operator UtlHashFastHandle_t() const { return handle; }; + }; + inline UtlHashFastIterator_t First() const; + inline UtlHashFastIterator_t Next( const UtlHashFastIterator_t &hHash ) const; + inline bool IsValidIterator( const UtlHashFastIterator_t &iter ) const; + inline Data &operator[]( const UtlHashFastIterator_t &iter ) { return (*this)[iter.handle]; } + inline Data const &operator[]( const UtlHashFastIterator_t &iter ) const { return (*this)[iter.handle]; } + +//protected: + + // Templatized for memory tracking purposes + template + struct HashFastData_t_ + { + uintp m_uiKey; + HashData m_Data; + }; + + typedef HashFastData_t_ HashFastData_t; + + uintp m_uiBucketMask; + CUtlVector m_aBuckets; + CUtlFixedLinkedList m_aDataPool; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +template CUtlHashFast::CUtlHashFast() +{ + Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Deconstructor +//----------------------------------------------------------------------------- +template CUtlHashFast::~CUtlHashFast() +{ + Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destroy dynamically allocated hash data. +//----------------------------------------------------------------------------- +template inline void CUtlHashFast::Purge( void ) +{ + m_aBuckets.Purge(); + m_aDataPool.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Initialize the hash - set bucket count and hash grow amount. +//----------------------------------------------------------------------------- +template bool CUtlHashFast::Init( int nBucketCount ) +{ + // Verify the bucket count is power of 2. + if ( !IsPowerOfTwo( nBucketCount ) ) + return false; + + // Set the bucket size. + m_aBuckets.SetSize( nBucketCount ); + for ( int iBucket = 0; iBucket < nBucketCount; ++iBucket ) + { + m_aBuckets[iBucket] = m_aDataPool.InvalidIndex(); + } + + // Set the mod mask. + m_uiBucketMask = nBucketCount - 1; + + // Calculate the grow size. + int nGrowSize = UTLHASH_POOL_SCALAR * nBucketCount; + m_aDataPool.SetGrowSize( nGrowSize ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Return the number of elements in the hash. +// Not available because count isn't accurately maintained for multilists. +//----------------------------------------------------------------------------- +/* +template inline int CUtlHashFast::Count( void ) const +{ + return m_aDataPool.Count(); +} +*/ + +//----------------------------------------------------------------------------- +// Purpose: Insert data into the hash table given its key (uintp), with +// a check to see if the element already exists within the tree. +//----------------------------------------------------------------------------- +template inline UtlHashFastHandle_t CUtlHashFast::Insert( uintp uiKey, const Data &data ) +{ + // Check to see if that key already exists in the buckets (should be unique). + UtlHashFastHandle_t hHash = Find( uiKey ); + if( hHash != InvalidHandle() ) + return hHash; + + return FastInsert( uiKey, data ); +} + +//----------------------------------------------------------------------------- +// Purpose: Insert data into the hash table given its key (uintp), +// without a check to see if the element already exists within the tree. +//----------------------------------------------------------------------------- +template inline UtlHashFastHandle_t CUtlHashFast::FastInsert( uintp uiKey, const Data &data ) +{ + // Get a new element from the pool. + intp iHashData = m_aDataPool.Alloc( true ); + HashFastData_t *pHashData = &m_aDataPool[iHashData]; + if ( !pHashData ) + return InvalidHandle(); + + // Add data to new element. + pHashData->m_uiKey = uiKey; + pHashData->m_Data = data; + + // Link element. + int iBucket = HashFuncs::Hash( uiKey, m_uiBucketMask ); + m_aDataPool.LinkBefore( m_aBuckets[iBucket], iHashData ); + m_aBuckets[iBucket] = iHashData; + + return iHashData; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a given element from the hash. +//----------------------------------------------------------------------------- +template inline void CUtlHashFast::Remove( UtlHashFastHandle_t hHash ) +{ + int iBucket = HashFuncs::Hash( m_aDataPool[hHash].m_uiKey, m_uiBucketMask ); + if ( m_aBuckets[iBucket] == hHash ) + { + // It is a bucket head. + m_aBuckets[iBucket] = m_aDataPool.Next( hHash ); + } + else + { + // Not a bucket head. + m_aDataPool.Unlink( hHash ); + } + + // Remove the element. + m_aDataPool.Remove( hHash ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all elements from the hash +//----------------------------------------------------------------------------- +template inline void CUtlHashFast::RemoveAll( void ) +{ + m_aBuckets.RemoveAll(); + m_aDataPool.RemoveAll(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template inline UtlHashFastHandle_t CUtlHashFast::Find( uintp uiKey ) const +{ + // hash the "key" - get the correct hash table "bucket" + int iBucket = HashFuncs::Hash( uiKey, m_uiBucketMask ); + + for ( intp iElement = m_aBuckets[iBucket]; iElement != m_aDataPool.InvalidIndex(); iElement = m_aDataPool.Next( iElement ) ) + { + if ( m_aDataPool[iElement].m_uiKey == uiKey ) + return iElement; + } + + return InvalidHandle(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template inline Data &CUtlHashFast::Element( UtlHashFastHandle_t hHash ) +{ + return ( m_aDataPool[hHash].m_Data ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template inline Data const &CUtlHashFast::Element( UtlHashFastHandle_t hHash ) const +{ + return ( m_aDataPool[hHash].m_Data ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template inline Data &CUtlHashFast::operator[]( UtlHashFastHandle_t hHash ) +{ + return ( m_aDataPool[hHash].m_Data ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template inline Data const &CUtlHashFast::operator[]( UtlHashFastHandle_t hHash ) const +{ + return ( m_aDataPool[hHash].m_Data ); +} + +//----------------------------------------------------------------------------- +// Purpose: For iterating over the whole hash, return the index of the first element +//----------------------------------------------------------------------------- +template + typename CUtlHashFast::UtlHashFastIterator_t + CUtlHashFast::First() const +{ + // walk through the buckets to find the first one that has some data + int bucketCount = m_aBuckets.Count(); + const UtlHashFastHandle_t invalidIndex = m_aDataPool.InvalidIndex(); + for ( int bucket = 0 ; bucket < bucketCount ; ++bucket ) + { + UtlHashFastHandle_t iElement = m_aBuckets[bucket]; // get the head of the bucket + if (iElement != invalidIndex) + return UtlHashFastIterator_t(bucket,iElement); + } + + // if we are down here, the list is empty + return UtlHashFastIterator_t(-1, invalidIndex); +} + +//----------------------------------------------------------------------------- +// Purpose: For iterating over the whole hash, return the next element after +// the param one. Or an invalid iterator. +//----------------------------------------------------------------------------- +template + typename CUtlHashFast::UtlHashFastIterator_t + CUtlHashFast::Next( const typename CUtlHashFast::UtlHashFastIterator_t &iter ) const +{ + // look for the next entry in the current bucket + UtlHashFastHandle_t next = m_aDataPool.Next(iter.handle); + const UtlHashFastHandle_t invalidIndex = m_aDataPool.InvalidIndex(); + if (next != invalidIndex) + { + // this bucket still has more elements in it + return UtlHashFastIterator_t(iter.bucket, next); + } + + // otherwise look for the next bucket with data + int bucketCount = m_aBuckets.Count(); + for ( int bucket = iter.bucket+1 ; bucket < bucketCount ; ++bucket ) + { + UtlHashFastHandle_t next = m_aBuckets[bucket]; // get the head of the bucket + if (next != invalidIndex) + return UtlHashFastIterator_t( bucket, next ); + } + + // if we're here, there's no more data to be had + return UtlHashFastIterator_t(-1, invalidIndex); +} + +template + bool CUtlHashFast::IsValidIterator( const typename CUtlHashFast::UtlHashFastIterator_t &iter ) const +{ + return ( (iter.bucket >= 0) && (m_aDataPool.IsValidIndex(iter.handle)) ); +} + + +template inline bool CUtlHashFast::IsValidHandle( UtlHashFastHandle_t hHash ) const +{ + return m_aDataPool.IsValidIndex(hHash); +} + +//============================================================================= +// +// Fixed Hash +// +// Number of buckets must be a power of 2. +// Key must be pointer-size (uintp). +// +typedef intp UtlHashFixedHandle_t; + +template +class CUtlHashFixedGenericHash +{ +public: + static int Hash( int key, int bucketMask ) + { + int hash = HashIntConventional( key ); + if ( NUM_BUCKETS <= USHRT_MAX ) + { + hash ^= ( hash >> 16 ); + } + if ( NUM_BUCKETS <= UCHAR_MAX ) + { + hash ^= ( hash >> 8 ); + } + return ( hash & bucketMask ); + } +}; + +template +class CUtlHashFixed +{ +public: + + // Constructor/Deconstructor. + CUtlHashFixed(); + ~CUtlHashFixed(); + + // Memory. + void Purge( void ); + + // Invalid handle. + static UtlHashFixedHandle_t InvalidHandle( void ) { return ( UtlHashFixedHandle_t )~0; } + + // Size. + int Count( void ); + + // Insertion. + UtlHashFixedHandle_t Insert( unsigned int uiKey, const Data &data ); + UtlHashFixedHandle_t FastInsert( unsigned int uiKey, const Data &data ); + + // Removal. + void Remove( UtlHashFixedHandle_t hHash ); + void RemoveAll( void ); + + // Retrieval. + UtlHashFixedHandle_t Find( unsigned int uiKey ); + + Data &Element( UtlHashFixedHandle_t hHash ); + Data const &Element( UtlHashFixedHandle_t hHash ) const; + Data &operator[]( UtlHashFixedHandle_t hHash ); + Data const &operator[]( UtlHashFixedHandle_t hHash ) const; + + //protected: + + // Templatized for memory tracking purposes + template + struct HashFixedData_t_ + { + unsigned int m_uiKey; + Data_t m_Data; + }; + + typedef HashFixedData_t_ HashFixedData_t; + + enum + { + BUCKET_MASK = NUM_BUCKETS - 1 + }; + CUtlPtrLinkedList m_aBuckets[NUM_BUCKETS]; + int m_nElements; +}; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +template CUtlHashFixed::CUtlHashFixed() +{ + Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Deconstructor +//----------------------------------------------------------------------------- +template CUtlHashFixed::~CUtlHashFixed() +{ + Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destroy dynamically allocated hash data. +//----------------------------------------------------------------------------- +template inline void CUtlHashFixed::Purge( void ) +{ + RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the number of elements in the hash. +//----------------------------------------------------------------------------- +template inline int CUtlHashFixed::Count( void ) +{ + return m_nElements; +} + +//----------------------------------------------------------------------------- +// Purpose: Insert data into the hash table given its key (unsigned int), with +// a check to see if the element already exists within the tree. +//----------------------------------------------------------------------------- +template inline UtlHashFixedHandle_t CUtlHashFixed::Insert( unsigned int uiKey, const Data &data ) +{ + // Check to see if that key already exists in the buckets (should be unique). + UtlHashFixedHandle_t hHash = Find( uiKey ); + if( hHash != InvalidHandle() ) + return hHash; + + return FastInsert( uiKey, data ); +} + +//----------------------------------------------------------------------------- +// Purpose: Insert data into the hash table given its key (unsigned int), +// without a check to see if the element already exists within the tree. +//----------------------------------------------------------------------------- +template inline UtlHashFixedHandle_t CUtlHashFixed::FastInsert( unsigned int uiKey, const Data &data ) +{ + int iBucket = HashFuncs::Hash( uiKey, NUM_BUCKETS - 1 ); + UtlPtrLinkedListIndex_t iElem = m_aBuckets[iBucket].AddToHead(); + + HashFixedData_t *pHashData = &m_aBuckets[iBucket][iElem]; + + Assert( (UtlPtrLinkedListIndex_t)pHashData == iElem ); + + // Add data to new element. + pHashData->m_uiKey = uiKey; + pHashData->m_Data = data; + + m_nElements++; + return (UtlHashFixedHandle_t)pHashData; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a given element from the hash. +//----------------------------------------------------------------------------- +template inline void CUtlHashFixed::Remove( UtlHashFixedHandle_t hHash ) +{ + HashFixedData_t *pHashData = (HashFixedData_t *)hHash; + Assert( Find(pHashData->m_uiKey) != InvalidHandle() ); + int iBucket = HashFuncs::Hash( pHashData->m_uiKey, NUM_BUCKETS - 1 ); + m_aBuckets[iBucket].Remove( (UtlPtrLinkedListIndex_t)pHashData ); + m_nElements--; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all elements from the hash +//----------------------------------------------------------------------------- +template inline void CUtlHashFixed::RemoveAll( void ) +{ + for ( int i = 0; i < NUM_BUCKETS; i++ ) + { + m_aBuckets[i].RemoveAll(); + } + m_nElements = 0; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template inline UtlHashFixedHandle_t CUtlHashFixed::Find( unsigned int uiKey ) +{ + int iBucket = HashFuncs::Hash( uiKey, NUM_BUCKETS - 1 ); + CUtlPtrLinkedList &bucket = m_aBuckets[iBucket]; + + for ( UtlPtrLinkedListIndex_t iElement = bucket.Head(); iElement != bucket.InvalidIndex(); iElement = bucket.Next( iElement ) ) + { + if ( bucket[iElement].m_uiKey == uiKey ) + return (UtlHashFixedHandle_t)iElement; + } + + return InvalidHandle(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template inline Data &CUtlHashFixed::Element( UtlHashFixedHandle_t hHash ) +{ + return ((HashFixedData_t *)hHash)->m_Data; +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template inline Data const &CUtlHashFixed::Element( UtlHashFixedHandle_t hHash ) const +{ + return ((HashFixedData_t *)hHash)->m_Data; +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template inline Data &CUtlHashFixed::operator[]( UtlHashFixedHandle_t hHash ) +{ + return ((HashFixedData_t *)hHash)->m_Data; +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template inline Data const &CUtlHashFixed::operator[]( UtlHashFixedHandle_t hHash ) const +{ + return ((HashFixedData_t *)hHash)->m_Data; +} + +class CDefaultHash32 +{ +public: + static inline uint32 HashKey32( uint32 nKey ) { return HashIntConventional(nKey); } +}; + +class CPassthroughHash32 +{ +public: + static inline uint32 HashKey32( uint32 nKey ) { return nKey; } +}; + + +// This is a simpler hash for scalar types that stores the entire hash + buckets in a single linear array +// This is much more cache friendly for small (e.g. 32-bit) types stored in the hash +template +class CUtlScalarHash +{ +public: + + // Constructor/Destructor. + CUtlScalarHash(); + ~CUtlScalarHash(); + + // Memory. + // void Purge( void ); + + // Invalid handle. + static const UtlHashFastHandle_t InvalidHandle( void ) { return (unsigned int)~0; } + + // Initialize. + bool Init( int nBucketCount ); + + // Size. + int Count( void ) const { return m_dataCount; } + + // Insertion. + UtlHashFastHandle_t Insert( unsigned int uiKey, const Data &data ); + + // Removal. + void FindAndRemove( unsigned int uiKey, const Data &dataRecord ); + void Remove( UtlHashFastHandle_t hHash ); + void RemoveAll( void ); + void Grow(); + + // Retrieval. Finds by uiKey and then by comparing dataRecord + UtlHashFastHandle_t Find( unsigned int uiKey, const Data &dataRecord ) const; + UtlHashFastHandle_t FindByUniqueKey( unsigned int uiKey ) const; + + Data &Element( UtlHashFastHandle_t hHash ) { Assert(unsigned(hHash)<=m_uiBucketMask); return m_pData[hHash].m_Data; } + Data const &Element( UtlHashFastHandle_t hHash ) const { Assert(unsigned(hHash)<=m_uiBucketMask); return m_pData[hHash].m_Data; } + Data &operator[]( UtlHashFastHandle_t hHash ) { Assert(unsigned(hHash)<=m_uiBucketMask); return m_pData[hHash].m_Data; } + Data const &operator[]( UtlHashFastHandle_t hHash ) const { Assert(unsigned(hHash)<=m_uiBucketMask); return m_pData[hHash].m_Data; } + + unsigned int Key( UtlHashFastHandle_t hHash ) const { Assert(unsigned(hHash)<=m_uiBucketMask); return m_pData[hHash].m_uiKey; } + + UtlHashFastHandle_t FirstInorder() const + { + return NextInorder(-1); + } + UtlHashFastHandle_t NextInorder( UtlHashFastHandle_t nStart ) const + { + int nElementCount = m_maxData * 2; + unsigned int nUnusedListElement = (unsigned int)InvalidHandle(); + for ( int i = nStart+1; i < nElementCount; i++ ) + { + if ( m_pData[i].m_uiKey != nUnusedListElement ) + return i; + } + return nUnusedListElement; + } + + //protected: + + struct HashScalarData_t + { + unsigned int m_uiKey; + Data m_Data; + }; + + unsigned int m_uiBucketMask; + HashScalarData_t *m_pData; + int m_maxData; + int m_dataCount; +}; + +template CUtlScalarHash::CUtlScalarHash() +{ + m_pData = NULL; + m_uiBucketMask = 0; + m_maxData = 0; + m_dataCount = 0; +} + +template CUtlScalarHash::~CUtlScalarHash() +{ + delete[] m_pData; +} + +template bool CUtlScalarHash::Init( int nBucketCount ) +{ + Assert(m_dataCount==0); + m_maxData = SmallestPowerOfTwoGreaterOrEqual(nBucketCount); + int elementCount = m_maxData * 2; + m_pData = new HashScalarData_t[elementCount]; + m_uiBucketMask = elementCount - 1; + RemoveAll(); + return true; +} + +template void CUtlScalarHash::Grow() +{ + ASSERT_NO_REENTRY(); + int oldElementCount = m_maxData * 2; + HashScalarData_t *pOldData = m_pData; + + // Grow to a minimum size of 16 + m_maxData = Max( oldElementCount, 16 ); + int elementCount = m_maxData * 2; + m_pData = new HashScalarData_t[elementCount]; + m_uiBucketMask = elementCount-1; + m_dataCount = 0; + for ( int i = 0; i < elementCount; i++ ) + { + m_pData[i].m_uiKey = InvalidHandle(); + } + for ( int i = 0; i < oldElementCount; i++ ) + { + if ( pOldData[i].m_uiKey != (unsigned)InvalidHandle() ) + { + Insert( pOldData[i].m_uiKey, pOldData[i].m_Data ); + } + } + delete[] pOldData; +} + +template UtlHashFastHandle_t CUtlScalarHash::Insert( unsigned int uiKey, const Data &data ) +{ + if ( m_dataCount >= m_maxData ) + { + Grow(); + } + m_dataCount++; + Assert(uiKey != (uint)InvalidHandle()); // This hash stores less data by assuming uiKey != ~0 + int index = CHashFunction::HashKey32(uiKey) & m_uiBucketMask; + unsigned int endOfList = (unsigned int)InvalidHandle(); + while ( m_pData[index].m_uiKey != endOfList ) + { + index = (index+1) & m_uiBucketMask; + } + m_pData[index].m_uiKey = uiKey; + m_pData[index].m_Data = data; + + return index; +} + +// Removal. +template void CUtlScalarHash::Remove( UtlHashFastHandle_t hHash ) +{ + int mid = (m_uiBucketMask+1) / 2; + int lastRemoveIndex = hHash; + // remove the item + m_pData[lastRemoveIndex].m_uiKey = InvalidHandle(); + m_dataCount--; + + // now search for any items needing to be swapped down + unsigned int endOfList = (unsigned int)InvalidHandle(); + for ( int index = (hHash+1) & m_uiBucketMask; m_pData[index].m_uiKey != endOfList; index = (index+1) & m_uiBucketMask ) + { + int ideal = CHashFunction::HashKey32(m_pData[index].m_uiKey) & m_uiBucketMask; + + // is the ideal index for this element <= (in a wrapped buffer sense) the ideal index of the removed element? + // if so, swap + int diff = ideal - lastRemoveIndex; + if ( diff > mid ) + { + diff -= (m_uiBucketMask+1); + } + if ( diff < -mid ) + { + diff += (m_uiBucketMask+1); + } + + // should I swap this? + if ( diff <= 0 ) + { + m_pData[lastRemoveIndex] = m_pData[index]; + lastRemoveIndex = index; + m_pData[index].m_uiKey = InvalidHandle(); + } + } +} + +template void CUtlScalarHash::FindAndRemove( unsigned int uiKey, const Data &dataRecord ) +{ + int index = CHashFunction::HashKey32(uiKey) & m_uiBucketMask; + unsigned int endOfList = (unsigned int)InvalidHandle(); + while ( m_pData[index].m_uiKey != endOfList ) + { + if ( m_pData[index].m_uiKey == uiKey && m_pData[index].m_Data == dataRecord ) + { + Remove(index); + return; + } + index = (index+1) & m_uiBucketMask; + } +} + +template void CUtlScalarHash::RemoveAll( void ) +{ + int elementCount = m_maxData * 2; + for ( int i = 0; i < elementCount; i++ ) + { + m_pData[i].m_uiKey = (unsigned)InvalidHandle(); + } + m_dataCount = 0; +} + +// Retrieval. +template UtlHashFastHandle_t CUtlScalarHash::Find( unsigned int uiKey, const Data &dataRecord ) const +{ + if ( m_pData == NULL ) + return InvalidHandle(); + int index = CHashFunction::HashKey32(uiKey) & m_uiBucketMask; + unsigned int endOfList = (unsigned int)InvalidHandle(); + while ( m_pData[index].m_uiKey != endOfList ) + { + if ( m_pData[index].m_uiKey == uiKey && m_pData[index].m_Data == dataRecord ) + return index; + index = (index+1) & m_uiBucketMask; + } + return InvalidHandle(); +} + +template UtlHashFastHandle_t CUtlScalarHash::FindByUniqueKey( unsigned int uiKey ) const +{ + if ( m_pData == NULL ) + return InvalidHandle(); + int index = CHashFunction::HashKey32(uiKey) & m_uiBucketMask; + unsigned int endOfList = (unsigned int)InvalidHandle(); + while ( m_pData[index].m_uiKey != endOfList ) + { + if ( m_pData[index].m_uiKey == uiKey ) + return index; + index = (index+1) & m_uiBucketMask; + } + return InvalidHandle(); +} + + +#endif // UTLHASH_H diff --git a/src/public/tier1/utlvector.h b/src/public/tier1/utlvector.h index fb1642e1..0caf449b 100644 --- a/src/public/tier1/utlvector.h +++ b/src/public/tier1/utlvector.h @@ -61,14 +61,6 @@ public: // Copy the array. CUtlVector& operator=(const CUtlVector& other); - // NOTE: - // Do not call after initialization or after adding elements. - // This is added so it could be constructed nicely. Since the - // game executable in monolithic, we couldn't import the malloc - // functions, and thus not construct automatically when using - // the game's memalloc singleton. - void Init(); - // element access T& operator[](int i); const T& operator[](int i) const; @@ -664,15 +656,6 @@ inline CUtlVector& CUtlVector::operator=(const CUtlVector& oth return *this; } -template< typename T, class A > -void CUtlVector::Init() -{ - m_Memory.m_pMemory = nullptr; - m_Memory.m_nAllocationCount = 0; - m_Memory.m_nGrowSize = 0; - m_Size = 0; -} - //----------------------------------------------------------------------------- // element access //----------------------------------------------------------------------------- @@ -1417,24 +1400,23 @@ void CUtlVector::Validate(CValidator& validator, char* pchName) // A vector class for storing pointers, so that the elements pointed to by the pointers are deleted // on exit. -template class CUtlVectorAutoPurge : public CUtlVector< T, CUtlMemory< T, int> > +template class CUtlVectorAutoPurge : public CUtlVector< T > { public: ~CUtlVectorAutoPurge(void) { this->PurgeAndDeleteElements(); } - }; // easy string list class with dynamically allocated strings. For use with V_SplitString, etc. // Frees the dynamic strings in destructor. -class CUtlStringList : public CUtlVectorAutoPurge< char*> +class CUtlStringList : public CUtlVectorAutoPurge { public: void CopyAndAddToTail(char const* pString) // clone the string and add to the end { - char* pNewStr = new char[1 + strlen(pString)]; + char* const pNewStr = new char[strlen(pString) + 1]; strcpy(pNewStr, pString); AddToTail(pNewStr); } @@ -1446,27 +1428,25 @@ public: CUtlStringList() {} - // !TODO: + CUtlStringList(char const* pString, char const* pSeparator) + { + SplitString(pString, pSeparator); + } - //CUtlStringList(char const* pString, char const* pSeparator) - //{ - // SplitString(pString, pSeparator); - //} + CUtlStringList(char const* pString, const char** pSeparators, ssize_t nSeparators) + { + SplitString2(pString, pSeparators, nSeparators); + } - //CUtlStringList(char const* pString, const char** pSeparators, int nSeparators) - //{ - // SplitString2(pString, pSeparators, nSeparators); - //} + void SplitString(char const* pString, char const* pSeparator) + { + V_SplitString(pString, pSeparator, *this); + } - //void SplitString(char const* pString, char const* pSeparator) - //{ - // V_SplitString(pString, pSeparator, *this); - //} - - //void SplitString2(char const* pString, const char** pSeparators, int nSeparators) - //{ - // V_SplitString2(pString, pSeparators, nSeparators, *this); - //} + void SplitString2(char const* pString, const char** pSeparators, ssize_t nSeparators) + { + V_SplitString2(pString, pSeparators, nSeparators, *this); + } private: CUtlStringList(const CUtlStringList& other); // copying directly will cause double-release of the same strings; maybe we need to do a deep copy, but unless and until such need arises, this will guard against double-release }; diff --git a/src/public/tier2/websocket.h b/src/public/tier2/websocket.h new file mode 100644 index 00000000..0a38645f --- /dev/null +++ b/src/public/tier2/websocket.h @@ -0,0 +1,144 @@ +//===========================================================================// +// +// Purpose: WebSocket implementation +// +//===========================================================================// +#ifndef TIER2_WEBSOCKET_H +#define TIER2_WEBSOCKET_H + +#define WEBSOCKET_DEFAULT_BUFFER_SIZE 1024 + +//----------------------------------------------------------------------------- +// forward declarations +//----------------------------------------------------------------------------- +struct ProtoWebSocketRefT; + +class CWebSocket +{ +public: + enum ConnState_e + { + // The socket has to be created and setup + CS_CREATE = 0, + + // The socket connection is established + CS_CONNECTED, + + // The socket is listening for data + CS_LISTENING, + + // The socket is destroyed and deallocated (if retries are set, the + // code will set the state to 'CS_RETRY' and reattempt to establish + // a connection up to ConnParams_s::maxRetries times + CS_DESTROYED, + + // The socket was destroyed and deallocated, and marked for a retry + // attempt + CS_RETRY, + + // The socket was destroyed and deallocated, and is marked unavailable. + // the code will remove this connection from the list and no further + // attempts will be made + CS_UNAVAIL + }; + + //------------------------------------------------------------------------- + // Connection parameters for the system & each individual connection, if + // these are changed, call CWebSocket::UpdateParams() to apply the new + // parameters on the system and each connection + //------------------------------------------------------------------------- + struct ConnParams_s + { + ConnParams_s() + { + bufSize = WEBSOCKET_DEFAULT_BUFFER_SIZE; + retryTime = 0.0f; + maxRetries = 0; + + timeOut = -1; + keepAlive = -1; + laxSSL = 0; + } + + // Total amount of buffer size that could be queued up and sent + int32_t bufSize; + + // Total amount of time between each connection attempt + float retryTime; + + // Maximum number of retries + // NOTE: the initial attempt is not counted as a retry attempt; if this + // field is set to 5, then the code will perform 1 connection attempt + + // 5 retries before giving up and marking this connection as unavailable + int32_t maxRetries; + + // Total amount of time in seconds before the connection is timing out + int32_t timeOut; + + // Time interval in seconds for the periodical keepalive pong message + int32_t keepAlive; + + // Whether to validate the clients certificate, if this is set, no + // validation is performed + int32_t laxSSL; + }; + + //------------------------------------------------------------------------- + // Represents an individual socket connection + //------------------------------------------------------------------------- + struct ConnContext_s + { + ConnContext_s(const char* const addr) + { + webSocket = nullptr; + address = addr; + + state = CS_CREATE; + + tryCount = 0; + lastQueryTime = 0; + } + + bool Connect(const double queryTime, const ConnParams_s& params); + bool Process(const double queryTime); + + void SetParams(const ConnParams_s& params); + + void Disconnect(); + void Reconnect(); + void Destroy(); + + ProtoWebSocketRefT* webSocket; + ConnState_e state; + + int tryCount; // Number of connection attempts + double lastQueryTime; + + CUtlString address; + }; + + CWebSocket(); + + bool Init(const char* const addressList, const ConnParams_s& params, const char*& initError); + void Shutdown(); + + bool UpdateAddressList(const char* const addressList); + void UpdateParams(const ConnParams_s& params); + + void Update(); + void DeleteUnavailable(); + + void DisconnectAll(); + void ReconnectAll(); + void ClearAll(); + + void SendData(const char* const dataBuf, const int32_t dataSize); + bool IsInitialized() const; + +private: + bool m_initialized; + ConnParams_s m_connParams; + CUtlVector m_addressList; +}; + +#endif // TIER2_WEBSOCKET_H diff --git a/src/public/vscript/ivscript.h b/src/public/vscript/ivscript.h index 86a4f6b9..2d9960b8 100644 --- a/src/public/vscript/ivscript.h +++ b/src/public/vscript/ivscript.h @@ -15,17 +15,6 @@ DECLARE_POINTER_HANDLE(HSCRIPT); typedef int ScriptDataType_t; typedef void* ScriptFunctionBindingStorageType_t; -enum ScriptLanguage_t -{ - SL_NONE, - SL_GAMEMONKEY, - SL_SQUIRREL, - SL_LUA, - SL_PYTHON, - - SL_DEFAULT = SL_SQUIRREL -}; - //--------------------------------------------------------- enum ExtendedFieldType diff --git a/src/resource/protobuf/events.proto b/src/resource/protobuf/events.proto new file mode 100644 index 00000000..d52d0231 --- /dev/null +++ b/src/resource/protobuf/events.proto @@ -0,0 +1,781 @@ +////////////////////////////////////////////////////////////////////// +// Apex Legends Live API +// Copyright 2023 Respawn Entertainment +// +// Contains all messages used by LiveAPI with annotations as comments +// See readme.txt for more information on how to consume this file +////////////////////////////////////////////////////////////////////// + +syntax = "proto3"; + +package rtech.liveapi; + + +////////////////////////////////////////////////////////////////////// +// Intermediary messages: +// Not used directly, but as part of other messages +////////////////////////////////////////////////////////////////////// + +message Vector3 +{ + float x = 1; + float y = 2; + float z = 3; +} + +message Player +{ + string name = 1; + uint32 teamId = 2; + Vector3 pos = 3; + Vector3 angles = 4; + + uint32 currentHealth = 5; + uint32 maxHealth = 6; + uint32 shieldHealth = 7; + uint32 shieldMaxHealth = 8; + + string nucleusHash = 9; + string hardwareName = 10; + + string teamName = 11; + uint32 squadIndex = 12; + string character = 13; + string skin = 14; +} + +message CustomMatch_LobbyPlayer +{ + string name = 1; + uint32 teamId = 2; + + string nucleusHash = 3; + string hardwareName = 4; +} + +message Datacenter +{ + uint64 timestamp = 1; + string category = 2; + + string name = 3; +} + +message Version +{ + uint32 major_num = 1; + uint32 minor_num = 2; + uint32 build_stamp = 3; + string revision = 4; +} + +message InventoryItem +{ + int32 quantity = 1; + string item = 2; + + // any mods or additional info on the item + string extraData = 3; +} + +message LoadoutConfiguration +{ + repeated InventoryItem weapons = 1; + repeated InventoryItem equipment = 2; +} + +////////////////////////////////////////////////////////////////////// +// Output messages: +// Game events that describe the ongoing state of the match +// Every message will have a timestamp and category +////////////////////////////////////////////////////////////////////// + +// Traffic initialization +// This message is sent upon successfully connecting over WebSockets +message Init +{ + uint64 timestamp = 1; + string category = 2; + + string gameVersion = 3; + Version apiVersion = 4; + string platform = 5; + + // Named specified by `liveapi_session_name` + string name = 6; +} + +// Response to the CustomMatch_GetLobbyPlayers +// Contains the list of all players in the lobby +message CustomMatch_LobbyPlayers +{ + string playerToken = 1; + repeated CustomMatch_LobbyPlayer players = 2; +} + +///////////////////////////////////////// +// Observer Events +///////////////////////////////////////// + +// Event when the observer camera switches from viewing one player to another +message ObserverSwitched +{ + uint64 timestamp = 1; + string category = 2; + + Player observer = 3; + Player target = 4; + repeated Player targetTeam = 5; +} + +// Used by observers to annotate events uniquely +message ObserverAnnotation +{ + uint64 timestamp = 1; + string category = 2; + + int32 annotationSerial = 3; +} + + +///////////////////////////////////////// +// Match Information +///////////////////////////////////////// + +// Sent during the first phase of a match. This event gives a full description of what match is being played +message MatchSetup +{ + uint64 timestamp = 1; + string category = 2; + + string map = 3; + string playlistName = 4; + string playlistDesc = 5; + Datacenter datacenter = 6; + bool aimAssistOn = 7; + bool anonymousMode = 8; + string serverId = 9; + + LoadoutConfiguration startingLoadout = 10; +} + +// Sent whenever the match changes phases (e.g. prematch, playing) +message GameStateChanged +{ + uint64 timestamp = 1; + string category = 2; + + string state = 3; +} + +// Occurs when any player has locked in a character during legend select +message CharacterSelected +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; +} + +// Event to summarize the match after it has ended +message MatchStateEnd +{ + uint64 timestamp = 1; + string category = 2; + + string state = 3; + repeated Player winners = 4; +} + +// Fired whenever the ring begins moving in a match +message RingStartClosing +{ + uint64 timestamp = 1; + string category = 2; + + uint32 stage = 3; + Vector3 center = 4; + float currentRadius = 5; + float endRadius = 6; + float shrinkDuration= 7; +} + +// Used when the ring has finished moving and prior to it moving again +message RingFinishedClosing +{ + uint64 timestamp = 1; + string category = 2; + + uint32 stage = 3; + Vector3 center = 4; + float currentRadius = 5; + float shrinkDuration= 7; +} + +// Used when a player has connected to the match +message PlayerConnected +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; +} + +// Used when a player has disconnected, even temporarily +// `canReconnect` will indicate if the player is able to reconnect or has forfeited +message PlayerDisconnected +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + bool canReconnect = 4; + bool isAlive = 5; +} + +// Generic event for a change in the player stats +// Common stat names that can come with this event include "knockdowns", "revivesGiven", "kills" +message PlayerStatChanged +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string statName = 4; + + oneof newValue // R5R: formerly of type `uint32` + { + uint32 intValue = 5; + float floatValue= 6; + bool boolValue = 7; + } +} + +// Event used to notify when a player goes above their current tier level +// Tier levels start at 1. Following this event, players may have Upgrades to their legend +// Selection of upgrades will produce a separate `LegendUpgradeSelected` event +message PlayerUpgradeTierChanged +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + int32 level = 4; +} + +///////////////////////////////////////// +// Combat events +///////////////////////////////////////// + +// Event describing a player taking damage +// Details include the attacker, victim, the weapon used and the amount of damage +message PlayerDamaged +{ + uint64 timestamp = 1; + string category = 2; + + Player attacker = 3; + Player victim = 4; + string weapon = 5; + uint32 damageInflicted = 6; +} + +// Sent when a player is killed. Details are similar to PlayerDamaged event +// The `awardedTo` field describes the player that the kill is given to +message PlayerKilled +{ + uint64 timestamp = 1; + string category = 2; + + Player attacker = 3; + Player victim = 4; + Player awardedTo = 5; + string weapon = 6; +} + +// Event describing a player that has been downed after taking sufficient damage +// Similar to PlayerDamaged, but may not be sent in certain game modes (e.g. Control) +message PlayerDowned +{ + uint64 timestamp = 1; + string category = 2; + + Player attacker = 3; + Player victim = 4; + string weapon = 5; +} + +// Sent when a player is killed if there is an assist awarded +// This event may come in rapid succession to the PlayerKilled event with a corresponding `victim` field +message PlayerAssist +{ + uint64 timestamp = 1; + string category = 2; + + Player assistant = 3; + Player victim = 4; + string weapon = 5; +} + +// Occurs when the entire squad in a game has been eliminated +// The event contains all player in said squad. May not occur in certain game modes +message SquadEliminated +{ + uint64 timestamp = 1; + string category = 2; + + repeated Player players = 3; +} + +// Occurs when Gibraltars shield has taken any enemy damage +// The field `damageInflicted` will indicate how much was absorbed by the shield +message GibraltarShieldAbsorbed +{ + uint64 timestamp = 1; + string category = 2; + + Player attacker = 3; + Player victim = 4; + uint32 damageInflicted = 6; +} + +// Occurs when Revenant, while using his Forged Shadows ultimate, takes any enemy damage +// This event is distinct from `PlayerDamaged` since the player may receive no actual damage if the shadow is able to absorb it +// The field `damageInflicted` will indicate how much damage (in total) was dealt +// If there is any leftover damage that goes affects the player, that amount will be what is registered in a different `PlayerDamaged` event +message RevenantForgedShadowDamaged +{ + uint64 timestamp = 1; + string category = 2; + + Player attacker = 3; + Player victim = 4; + uint32 damageInflicted = 6; +} + +///////////////////////////////////////// +// Interaction events +///////////////////////////////////////// + +// Sent when a player is respawned and comes back into game +// For example, when using a respawn beacon +message PlayerRespawnTeam +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + repeated Player respawned = 4; // R5R: formerly of type `string` +} + +// Occurs when a player finishes assisting a downed player +// May not be sent in certain game modes (e.g. Control) +message PlayerRevive +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + Player revived = 4; +} + +// Specific Arenas-only event that occurs when players select an item +message ArenasItemSelected +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string item = 4; + int32 quantity = 5; +} + +// Specific Arenas-only event that occurs when players deselect an item +message ArenasItemDeselected +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string item = 4; + int32 quantity = 5; +} + +// Event that occurs when a player has picked up loot into their inventory +message InventoryPickUp +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string item = 4; + int32 quantity = 5; +} + +// Event that occurs when a player has dropped loot from their inventory +// The item itself may have attachments that will be described in the `extraData` field +message InventoryDrop +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string item = 4; + int32 quantity = 5; + repeated string extraData = 6; +} + +// Used to indicate the player has used a consumable item (e.g. syringe, shield cell) from their inventory +message InventoryUse +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string item = 4; + int32 quantity = 5; +} + +// Event used when a teammate banner has been picked up +message BannerCollected +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + Player collected = 4; +} + +// Used to indicate that the player has activated one of their legend's abilities +// The ability can be a Tactical or an Ultimate and is decribed in the `linkedEntity` field +// For example: `linkedEntity: "Tactical (Eye of the Allfather)"` for Bloodhound's tactical +message PlayerAbilityUsed +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string linkedEntity = 4; +} + +// Signals that a player has selected an upgrade at a particular tier level +// Updates to their tier level will be sent as a PlayerUpgradeTierChanged event +message LegendUpgradeSelected +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string upgradeName = 4; + string upgradeDesc = 5; + int32 level = 6; +} + +// Indicates that a player has started using the zipline +message ZiplineUsed +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string linkedEntity = 4; +} + +// Used to indicate that a player has tossed a grenade +// The `linkedEntity` will describe the grenade in further detail and it may be a legend's Ability +// For example: `linkedEntity: "Ultimate (Rolling Thunder)"` for Bangalore's Ultimate ability +message GrenadeThrown +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string linkedEntity = 4; +} + +// Event specifying that a player has picked up loot from Loba's Black Market +// This event may fire in quick succession to the InventoryPickUp event +message BlackMarketAction +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string item = 4; +} + +// Used to indicate a player has traversed a Wraith Portal +message WraithPortal +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; +} + +// Used to indicate a player has traversed a Warp Gate +message WarpGateUsed +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; +} + +// Used to indicate that a player has used ammo +// This event may not fire immediately and updates may be batched to save bandwidth +message AmmoUsed +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string ammoType = 4; + uint32 amountUsed = 5; + uint32 oldAmmoCount = 6; + uint32 newAmmoCount = 7; +} + +// Used to indicate that a player has switched weapons, either to a weapon in their inventory or swapped with a weapon on the ground +message WeaponSwitched +{ + uint64 timestamp = 1; + string category = 2; + + Player player = 3; + string oldWeapon = 4; + string newWeapon = 5; +} + +///////////////////////////////////////// +// Custom events +///////////////////////////////////////// + +import "google/protobuf/struct.proto"; + +// Event defining custom user data that is otherwise too specific to create dedicated messages for +message CustomEvent +{ + uint64 timestamp = 1; + string category = 2; + + string name = 3; + google.protobuf.Struct data = 4; +} + +////////////////////////////////////////////////////////////////////// +// Input messages: +// Used by observers to programmatically interact with the game +////////////////////////////////////////////////////////////////////// + + +// Enum used to quickly described the target of a ChangeCamera operation +enum PlayerOfInterest +{ + UNSPECIFIED = 0; + + // cycle through known Players in a team + NEXT = 1; + PREVIOUS = 2; + + // Go to an interesting player + KILL_LEADER = 3; + CLOSEST_ENEMY = 4; + CLOSEST_PLAYER = 5; + LATEST_ATTACKER = 6; +} + +// Request to change the observer camera +// If changing by a target's name, be aware that the +// - server may skip the request if the player is not actively in the game (i.e. waiting for reconnect, downed or killed) +// - If the string is longer than 256 characters, the request will fail +message ChangeCamera +{ + oneof target + { + // Set the camera to an interesting player (e.g. the Kill Leader) + PlayerOfInterest poi = 1; + + // Change camera to a player by name + string name = 2; + } +} + +// Request message to toggle pause in a match type that supports it +message PauseToggle +{ + float preTimer = 1; +} + +// Request to create a custom match lobby +message CustomMatch_CreateLobby +{ +} + +// Request to join an existing custom match lobby identified by the `roleToken` +message CustomMatch_JoinLobby +{ + string roleToken = 1; +} + +// Request to leave a custom match lobby +message CustomMatch_LeaveLobby +{ +} + +// Request to programatically change your player's ready state in a custom match lobby +message CustomMatch_SetReady +{ + bool isReady = 1; +} + +// Request to retrieve all connected players in a custom match lobby +message CustomMatch_GetLobbyPlayers +{ +} + +// Request to change the state of matchmaking in a custom match lobby +// When enabled is True, the lobby will attempt to being a match +message CustomMatch_SetMatchmaking +{ + bool enabled = 1; +} + +// Request to assign a particular player to a specific team +// Note that the `targetHardwareName` and `targetNucleusHash` can be obtained from a prior request to CustomMatch_GetLobbyPlayers +// If the parameters do not match any lobby player, the request is ignored +// The `teamId` is across the entire lobby. Meaning, observers have a teamId of 0 and match players will be teamId of 1 and upwards +message CustomMatch_SetTeam +{ + int32 teamId = 1; + string targetHardwareName = 2; + string targetNucleusHash = 3; +} + +// Request to remove a player from the currently connected custom match lobby +message CustomMatch_KickPlayer +{ + string targetHardwareName = 1; + string targetNucleusHash = 2; +} + +// Request to alter the settings of a custom match lobby +// Your request should specify all fields being set with the new value +// For convinience, call `CustomMatch_GetSettings` to get the full state of settings +message CustomMatch_SetSettings +{ + string playlistName = 1; + bool adminChat = 2; + bool teamRename = 3; + bool selfAssign = 4; + bool aimAssist = 5; + bool anonMode = 6; +} + +// Review all the current settings. This request will be replied to with +// `CustomMatch_SetSettings` from which you can modify and reply with any new values for your convenience +message CustomMatch_GetSettings +{ +} + +// Request to set the name of a team in custom match lobby +// Requires special access and is subject to text filtering +message CustomMatch_SetTeamName +{ + int32 teamId = 1; + string teamName = 2; +} + +// Request to programatically send a chat message to the entire custom match lobby +message CustomMatch_SendChat +{ + string text = 1; +} + +// Envelope message for any Live API request +// This allows a single uniform data structure for requests to be made and for the game to receive them +// Specifically, there is only one possible action per request. You can request an acknowledgement of your request by setting `withAck` to true +// Acknowledgements will come in the form of a Response message. More information can be found with that event +// +// A single example to create a CustomMatch_JoinLobby request in python is as follows +// ``` +// req = Request() +// req.customMatch_JoinLobby.roleToken = "" +// req.withAck = True +// ``` +// For more information, consult the Protobuf documentation for your language of choice and look at details regarding the `oneof` field (https://protobuf.dev/programming-guides/proto3/#oneof) +message Request +{ + // Receive an acknowledgement of the request having been received + bool withAck = 1; + + // Preshared key to use with the request. Only necessary if the connecting game has a preshared key specified through `cl_liveapi_requests_psk` + string preSharedKey = 2; + + oneof actions + { + ChangeCamera changeCam = 4; + PauseToggle pauseToggle = 5; + + // Custom Match specific requests (reserved 10 -> 30) + CustomMatch_CreateLobby customMatch_CreateLobby = 10; + CustomMatch_JoinLobby customMatch_JoinLobby = 11; + CustomMatch_LeaveLobby customMatch_LeaveLobby = 12; + CustomMatch_SetReady customMatch_SetReady = 13; + CustomMatch_SetMatchmaking customMatch_SetMatchmaking = 14; + CustomMatch_SetTeam customMatch_SetTeam = 15; + CustomMatch_KickPlayer customMatch_KickPlayer = 16; + CustomMatch_SetSettings customMatch_SetSettings = 17; + CustomMatch_SendChat customMatch_SendChat = 18; + CustomMatch_GetLobbyPlayers customMatch_GetLobbyPlayers = 19; + CustomMatch_SetTeamName customMatch_SetTeamName = 20; + CustomMatch_GetSettings customMatch_GetSettings = 21; + + } +} + +////////////////////////////////////////////////////////////////////// +// Reply messages: +// Used by the game to send data to any connected clients +////////////////////////////////////////////////////////////////////// + +import "google/protobuf/any.proto"; + +// Message used to indicate the status of a request +// Generally, it is used to provide a plain text, detailed response in case of failures or problems +message RequestStatus +{ + string status = 1; +} + +// Message used to indicate the response to a request made to the API +// Only the requesting part will receive this message and this message is only sent if the request required an acknowledgement by setting `withAck` to true in the Request object +// This message is always sent within a LiveAPIEvent and never on its own to allow any applications to have a uniform method of reading events over the wire +// If `success` is true, it does not mean that the Request has finished or that it was completed correctly. In this case, it means that it was successfully received and contains no issues (it is a well-formed request) +// The `result` field may sometimes be populated to give more context around the request, especially in the case of error +// Refer to the LiveAPIEvent message on how to the the Any field +message Response +{ + bool success = 1; + google.protobuf.Any result = 2; +} + +// Envelope for all LiveAPI Events +// Any game events or responses to requests will be sent using this message. The specific event or message is stored in the `gameMessage` field +// Before proceeding, familiarize yourself with the proto3 `Any` field type at: https://protobuf.dev/programming-guides/proto3/#any +// In order to read the message successfully, check the type contained in `gameMessage` and create an instance of that type where you can unpack the data to +// Protobuf has several ways of doing type to instance lookups that will allow you to do this after you've generated bindings +// For example, to read and unpack any LiveAPIEvent in Python, the following can be done (assume `pb_msg` contains the LiveAPIEvent object) +// ``` +// from events_pb2 import * +// from google.protobuf import symbol_database +// [ ... ] +// result_type = pb_msg.gameMessage.TypeName() +// msg_result = symbol_database.Default().GetSymbol(result_type)() +// pb_msg.gameMessage.Unpack(msg_result) # msg_result now holds the actual event you want to read +// ``` +message LiveAPIEvent +{ + fixed32 event_size = 1; + google.protobuf.Any gameMessage = 3; +} diff --git a/src/resource/protobuf/generate.bat b/src/resource/protobuf/generate.bat index 21d31785..e179f451 100644 --- a/src/resource/protobuf/generate.bat +++ b/src/resource/protobuf/generate.bat @@ -1,3 +1,4 @@ protoc64 --cpp_out=. sig_map.proto protoc64 --cpp_out=. sv_rcon.proto protoc64 --cpp_out=. cl_rcon.proto +protoc64 --cpp_out=. events.proto diff --git a/src/rtech/CMakeLists.txt b/src/rtech/CMakeLists.txt index 7dc2d1fd..8be05513 100644 --- a/src/rtech/CMakeLists.txt +++ b/src/rtech/CMakeLists.txt @@ -34,6 +34,11 @@ add_sources( SOURCE_GROUP "Pak" "pak/paktools.h" ) +add_sources( SOURCE_GROUP "LiveAPI" + "liveapi/liveapi.cpp" + "liveapi/liveapi.h" +) + add_sources( SOURCE_GROUP "Public" "${ENGINE_SOURCE_DIR}/public/rtech/iasync.h" "${ENGINE_SOURCE_DIR}/public/rtech/ipakfile.h" @@ -41,6 +46,11 @@ add_sources( SOURCE_GROUP "Public" end_sources() +target_include_directories( ${PROJECT_NAME} PRIVATE + "${THIRDPARTY_SOURCE_DIR}/dirtysdk/include/" + "${THIRDPARTY_SOURCE_DIR}/ea/" +) + add_module( "lib" "rson" "vpc" ${FOLDER_CONTEXT} TRUE TRUE ) start_sources() diff --git a/src/rtech/liveapi/liveapi.cpp b/src/rtech/liveapi/liveapi.cpp new file mode 100644 index 00000000..818b6e3a --- /dev/null +++ b/src/rtech/liveapi/liveapi.cpp @@ -0,0 +1,299 @@ +//===========================================================================// +// +// Purpose: LiveAPI WebSocket implementation +// +//===========================================================================// +#include "liveapi.h" +#include "protobuf/util/json_util.h" + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/proto/protowebsocket.h" + +//----------------------------------------------------------------------------- +// change callbacks +//----------------------------------------------------------------------------- +static void LiveAPI_EnabledChangedCallback(IConVar* var, const char* pOldValue) +{ + LiveAPISystem()->ToggleInit(); +} +static void LiveAPI_WebSocketEnabledChangedCallback(IConVar* var, const char* pOldValue) +{ + LiveAPISystem()->ToggleInitWebSocket(); +} +static void LiveAPI_ParamsChangedCallback(IConVar* var, const char* pOldValue) +{ + LiveAPISystem()->UpdateParams(); +} +static void LiveAPI_AddressChangedCallback(IConVar* var, const char* pOldValue) +{ + LiveAPISystem()->RebootWebSocket(); +} + +//----------------------------------------------------------------------------- +// console variables +//----------------------------------------------------------------------------- +ConVar liveapi_enabled("liveapi_enabled", "0", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Enable LiveAPI functionality", &LiveAPI_EnabledChangedCallback); +ConVar liveapi_session_name("liveapi_session_name", "liveapi_session", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "LiveAPI session name to identify this connection"); +ConVar liveapi_truncate_hash_fields("liveapi_truncate_hash_fields", "1", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Whether to truncate hash fields in LiveAPI events to save on I/O"); + +// WebSocket core +static ConVar liveapi_websocket_enabled("liveapi_websocket_enabled", "0", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Whether to use WebSocket to transmit LiveAPI events", &LiveAPI_WebSocketEnabledChangedCallback); +static ConVar liveapi_servers("liveapi_servers", "", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Comma separated list of addresses to connect to", &LiveAPI_AddressChangedCallback, "ws://domain.suffix:port"); + +// WebSocket connection base parameters +static ConVar liveapi_retry_count("liveapi_retry_count", "5", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Amount of times to retry connecting before marking the connection as unavailable", &LiveAPI_ParamsChangedCallback); +static ConVar liveapi_retry_time("liveapi_retry_time", "30", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Amount of time between each retry", &LiveAPI_ParamsChangedCallback); + +// WebSocket connection context parameters +static ConVar liveapi_timeout("liveapi_timeout", "300", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "WebSocket connection timeout in seconds", &LiveAPI_ParamsChangedCallback); +static ConVar liveapi_keepalive("liveapi_keepalive", "30", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Interval of time to send Pong to any connected server", &LiveAPI_ParamsChangedCallback); +static ConVar liveapi_lax_ssl("liveapi_lax_ssl", "1", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Skip SSL certificate validation for all WSS connections (allows the use of self-signed certificates)", &LiveAPI_ParamsChangedCallback); + +// Print core +static ConVar liveapi_print_enabled("liveapi_print_enabled", "0", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Whether to enable the printing of all events to a LiveAPI JSON file"); + +// Print parameters +static ConVar liveapi_print_pretty("liveapi_print_pretty", "0", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Whether to print events in a formatted manner to the LiveAPI JSON file"); +static ConVar liveapi_print_primitive("liveapi_print_primitive", "0", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Whether to print primitive event fields to the LiveAPI JSON file"); + +//----------------------------------------------------------------------------- +// constructors/destructors +//----------------------------------------------------------------------------- +LiveAPI::LiveAPI() +{ + matchLogCount = 0; + initialLog = false; + initialized = false; +} +LiveAPI::~LiveAPI() +{ +} + +//----------------------------------------------------------------------------- +// Initialization of the LiveAPI system +//----------------------------------------------------------------------------- +void LiveAPI::Init() +{ + if (!liveapi_enabled.GetBool()) + return; + + InitWebSocket(); + initialized = true; +} + +//----------------------------------------------------------------------------- +// Shutdown of the LiveAPI system +//----------------------------------------------------------------------------- +void LiveAPI::Shutdown() +{ + webSocketSystem.Shutdown(); + DestroyLogger(); + initialized = false; +} + +//----------------------------------------------------------------------------- +// Toggle between init or deinit depending on current init state and the value +// of the cvar 'liveapi_enabled' +//----------------------------------------------------------------------------- +void LiveAPI::ToggleInit() +{ + const bool enabled = liveapi_enabled.GetBool(); + + if (enabled && !initialized) + Init(); + else if (!enabled && initialized) + Shutdown(); +} + +//----------------------------------------------------------------------------- +// Populate the connection params structure +//----------------------------------------------------------------------------- +void LiveAPI::CreateParams(CWebSocket::ConnParams_s& params) +{ + params.bufSize = LIVE_API_MAX_FRAME_BUFFER_SIZE; + + params.retryTime = liveapi_retry_time.GetFloat(); + params.maxRetries = liveapi_retry_count.GetInt(); + + params.timeOut = liveapi_timeout.GetInt(); + params.keepAlive = liveapi_keepalive.GetInt(); + params.laxSSL = liveapi_lax_ssl.GetInt(); +} + +//----------------------------------------------------------------------------- +// Update the websocket parameters and apply them on all connections +//----------------------------------------------------------------------------- +void LiveAPI::UpdateParams() +{ + CWebSocket::ConnParams_s connParams; + CreateParams(connParams); + + webSocketSystem.UpdateParams(connParams); +} + +//----------------------------------------------------------------------------- +// Initialize the websocket system +//----------------------------------------------------------------------------- +void LiveAPI::InitWebSocket() +{ + if (!liveapi_websocket_enabled.GetBool()) + return; + + CWebSocket::ConnParams_s connParams; + CreateParams(connParams); + + const char* initError = nullptr; + + if (!webSocketSystem.Init(liveapi_servers.GetString(), connParams, initError)) + { + Error(eDLL_T::RTECH, 0, "LiveAPI: WebSocket initialization failed! [%s]\n", initError); + return; + } +} + +//----------------------------------------------------------------------------- +// Shutdown the websocket system +//----------------------------------------------------------------------------- +void LiveAPI::ShutdownWebSocket() +{ + webSocketSystem.Shutdown(); +} + +//----------------------------------------------------------------------------- +// Toggle between init or deinit depending on current init state and the value +// of the cvar 'liveapi_websocket_enabled' +//----------------------------------------------------------------------------- +void LiveAPI::ToggleInitWebSocket() +{ + const bool enabled = liveapi_websocket_enabled.GetBool(); + + if (enabled && !WebSocketInitialized()) + InitWebSocket(); + else if (!enabled && WebSocketInitialized()) + ShutdownWebSocket(); +} + +//----------------------------------------------------------------------------- +// Reboot the websocket system and reconnect to addresses specified in cvar +// 'liveapi_servers' +//----------------------------------------------------------------------------- +void LiveAPI::RebootWebSocket() +{ + ShutdownWebSocket(); + InitWebSocket(); +} + +//----------------------------------------------------------------------------- +// Create the file logger +//----------------------------------------------------------------------------- +void LiveAPI::CreateLogger() +{ + // Its possible that one was already created but never closed, this is + // possible if the game scripts crashed or something along those lines. + DestroyLogger(); + + if (!liveapi_print_enabled.GetBool()) + return; // Logging is disabled + + matchLogger = spdlog::basic_logger_mt("match_logger", + Format("platform/liveapi/logs/%s/match_%d.json", g_LogSessionUUID.c_str(), matchLogCount++)); + + matchLogger.get()->set_pattern("%v"); + matchLogger.get()->info("[\n"); +} + +//----------------------------------------------------------------------------- +// Destroy the file logger +//----------------------------------------------------------------------------- +void LiveAPI::DestroyLogger() +{ + if (initialLog) + initialLog = false; + + if (!matchLogger) + return; // Nothing to drop + + matchLogger.get()->info("\n]\n"); + matchLogger.reset(); + + spdlog::drop("match_logger"); +} + +//----------------------------------------------------------------------------- +// LiveAPI state machine +//----------------------------------------------------------------------------- +void LiveAPI::RunFrame() +{ + if (!IsEnabled()) + return; + + if (WebSocketInitialized()) + webSocketSystem.Update(); +} + +//----------------------------------------------------------------------------- +// Send an event to all sockets +//----------------------------------------------------------------------------- +void LiveAPI::LogEvent(const google::protobuf::Message* const toTransmit, const google::protobuf::Message* toPrint) +{ + if (!IsEnabled()) + return; + + if (WebSocketInitialized()) + { + const string data = toTransmit->SerializeAsString(); + webSocketSystem.SendData(data.c_str(), (int)data.size()); + } + + // NOTE: we don't check on the cvar 'liveapi_print_enabled' here because if + // this cvar gets disabled on the fly and we check it here, the output will + // be truncated and thus invalid! Log for as long as the SpdLog instance is + // valid. + if (matchLogger) + { + std::string jsonStr(initialLog ? ",\n" : ""); + google::protobuf::util::JsonPrintOptions options; + + options.add_whitespace = liveapi_print_pretty.GetBool(); + options.always_print_primitive_fields = liveapi_print_primitive.GetBool(); + + google::protobuf::util::MessageToJsonString(*toPrint, &jsonStr, options); + + // Remove the trailing newline character + if (options.add_whitespace && !jsonStr.empty()) + jsonStr.pop_back(); + + matchLogger.get()->info(jsonStr); + + if (!initialLog) + initialLog = true; + } +} + +//----------------------------------------------------------------------------- +// Returns whether the system is enabled +//----------------------------------------------------------------------------- +bool LiveAPI::IsEnabled() const +{ + return liveapi_enabled.GetBool(); +} + +//----------------------------------------------------------------------------- +// Returns whether the system is able to run +//----------------------------------------------------------------------------- +bool LiveAPI::IsValidToRun() const +{ + return (IsEnabled() && (WebSocketInitialized() || FileLoggerInitialized())); +} + +static LiveAPI s_liveApi; + +//----------------------------------------------------------------------------- +// Singleton accessor +//----------------------------------------------------------------------------- +LiveAPI* LiveAPISystem() +{ + return &s_liveApi; +} diff --git a/src/rtech/liveapi/liveapi.h b/src/rtech/liveapi/liveapi.h new file mode 100644 index 00000000..94b0f21d --- /dev/null +++ b/src/rtech/liveapi/liveapi.h @@ -0,0 +1,59 @@ +#ifndef RTECH_LIVEAPI_H +#define RTECH_LIVEAPI_H +#include "tier2/websocket.h" +#include "thirdparty/protobuf/message.h" + +#define LIVE_API_MAX_FRAME_BUFFER_SIZE 0x8000 + +extern ConVar liveapi_enabled; +extern ConVar liveapi_session_name; +extern ConVar liveapi_truncate_hash_fields; + +struct ProtoWebSocketRefT; +typedef void (*LiveAPISendCallback_t)(ProtoWebSocketRefT* webSocket); + +class LiveAPI +{ +public: + LiveAPI(); + ~LiveAPI(); + + void Init(); + void Shutdown(); + + void ToggleInit(); + + void CreateParams(CWebSocket::ConnParams_s& params); + void UpdateParams(); + + void InitWebSocket(); + void ShutdownWebSocket(); + + void ToggleInitWebSocket(); + + void RebootWebSocket(); + + void CreateLogger(); + void DestroyLogger(); + + void RunFrame(); + void LogEvent(const google::protobuf::Message* const toTransmit, const google::protobuf::Message* toPrint); + + bool IsEnabled() const; + bool IsValidToRun() const; + + inline bool WebSocketInitialized() const { return webSocketSystem.IsInitialized(); } + inline bool FileLoggerInitialized() const { return matchLogger != nullptr; } + +private: + CWebSocket webSocketSystem; + + std::shared_ptr matchLogger; + int matchLogCount; + bool initialLog; + bool initialized; +}; + +LiveAPI* LiveAPISystem(); + +#endif // RTECH_LIVEAPI_H diff --git a/src/thirdparty/dirtysdk/CMakeLists.txt b/src/thirdparty/dirtysdk/CMakeLists.txt new file mode 100644 index 00000000..6436f9a4 --- /dev/null +++ b/src/thirdparty/dirtysdk/CMakeLists.txt @@ -0,0 +1,191 @@ +cmake_minimum_required( VERSION 3.16 ) +add_module( "lib" "DirtySDK" "" ${FOLDER_CONTEXT} TRUE TRUE ) + +start_sources() + +add_sources( SOURCE_GROUP "Platform" +# Core source files + "source/platform/plat-str.c" + "source/platform/plat-time.c" +) + +add_sources( SOURCE_GROUP "Comm" +# Core source files + "source/comm/commsrp.c" + "source/comm/commudp.c" + "source/comm/commudputil.c" +) + +add_sources( SOURCE_GROUP "Crypto" +# Core source files + "source/crypt/cryptaes.c" + "source/crypt/cryptarc4.c" + "source/crypt/cryptchacha.c" + "source/crypt/cryptcurve.c" + "source/crypt/cryptgcm.c" + "source/crypt/crypthash.c" + "source/crypt/crypthmac.c" + "source/crypt/cryptmd5.c" + "source/crypt/cryptmont.c" + "source/crypt/cryptnist.c" + "source/crypt/cryptrandcommon.c" + "source/crypt/cryptrsa.c" + "source/crypt/cryptsha1.c" + "source/crypt/cryptsha2.c" +# Optional core source files + "source/crypt/cryptbn.c" +# PC plat source files + "source/crypt/cryptrand.c" +) + +add_sources( SOURCE_GROUP "Crypto/Include" +# Core include files + "source/crypt/cryptrandpriv.h" + "source/crypt/cryptrandcommon.h" +) + +add_sources( SOURCE_GROUP "Socket" +# Core source files + "source/dirtysock/dirtyaddr.c" + "source/dirtysock/dirtycert.c" + "source/dirtysock/dirtyerr.c" + "source/dirtysock/dirtylib.c" + "source/dirtysock/dirtylib.cpp" + "source/dirtysock/dirtymem.c" + "source/dirtysock/dirtynames.c" + "source/dirtysock/dirtynet.c" + "source/dirtysock/dirtythread.cpp" + "source/dirtysock/dirtyuser.c" + "source/dirtysock/netconn.c" + "source/dirtysock/netconncommon.c" + #"source/dirtysock/netconnlocaluser.cpp" +# PC plat source files + "source/dirtysock/dirtyaddr.c" + "source/dirtysock/pc/dirtyerrpc.c" + "source/dirtysock/pc/dirtylibwin.c" + "source/dirtysock/pc/dirtynetwin.c" + "source/dirtysock/pc/netconnwin.c" +) + +add_sources( SOURCE_GROUP "Socket/Include" +# Core include files + "source/dirtysock/dirtynetpriv.h" + "source/dirtysock/netconncommon.h" + "source/dirtysock/netconnlocaluser.h" +) + +add_sources( SOURCE_GROUP "Game" +# Core source files + "source/game/connapi.c" + "source/game/netgamedist.c" + "source/game/netgamedistserv.c" + "source/game/netgamelink.c" + "source/game/netgameutil.c" +) + +add_sources( SOURCE_GROUP "Codec" +# Core source files + "source/graph/dirtygif.c" + "source/graph/dirtygraph.c" + "source/graph/dirtyjpg.c" + "source/graph/dirtypng.c" +) + +add_sources( SOURCE_GROUP "Misc" +# Core source files + "source/misc/qosclient.c" + "source/misc/qoscommon.c" + "source/misc/weblog.c" +) + +add_sources( SOURCE_GROUP "Misc/Include" +# Core include files + "source/misc/userapipriv.h" +) + +add_sources( SOURCE_GROUP "Protocol" +# Core source files + "source/proto/protoadvt.c" + "source/proto/protohttpmanager.c" + "source/proto/protohttpserv.c" + "source/proto/protohttputil.c" + "source/proto/protohttp2.c" + "source/proto/protoname.c" + "source/proto/protostream.c" + "source/proto/prototunnel.c" + "source/proto/protoupnp.c" +# PC plat source files + "source/proto/protohttp.c" + "source/proto/protomangle.c" + "source/proto/protossl.c" + "source/proto/protowebsocket.c" +) + +add_sources( SOURCE_GROUP "Utility" +# Core source files + "source/util/aws.c" + "source/util/binary7.c" + "source/util/base64.c" + "source/util/hpack.c" + "source/util/jsonformat.c" + "source/util/jsonparse.c" + "source/util/murmurhash3.c" + "source/util/protobufcommon.c" + "source/util/protobufread.c" + "source/util/protobufwrite.c" + "source/util/utf8.c" +) + +add_sources( SOURCE_GROUP "Voip" +# Core source files + "source/voip/voiptunnel.c" + "source/voip/voipgroup.c" +# PC plat source files + "source/voip/voip.c" + "source/voip/voipblocklist.c" + "source/voip/voipconduit.c" + "source/voip/voipcodec.c" + "source/voip/voipcommon.c" + "source/voip/voipconnection.c" + "source/voip/voipdvi.c" + "source/voip/voipmixer.c" + "source/voip/voippcm.c" + "source/voip/pc/voipheadsetpc.c" + "source/voip/pc/voippc.c" + "source/voip/voiptranscribe.c" + + "source/voip/pc/voipnarratepc.cpp" +) + +add_sources( SOURCE_GROUP "Voip/Include" +# Core include files + "source/voip/voipcommon.h" + "source/voip/voipconduit.h" + "source/voip/voipconnection.h" + "source/voip/voipdvi.h" + "source/voip/voipmixer.h" + "source/voip/voippacket.h" + "source/voip/voippcm.h" + "source/voip/voippriv.h" +) + +add_sources( SOURCE_GROUP "XML" +# Core source files + "source/xml/xmlformat.c" + "source/xml/xmlparse.c" +) + +end_sources() +thirdparty_suppress_warnings() + +target_include_directories( ${PROJECT_NAME} PRIVATE + "${THIRDPARTY_SOURCE_DIR}/dirtysdk/include/" + "${THIRDPARTY_SOURCE_DIR}/ea/" + "${THIRDPARTY_SOURCE_DIR}/ea/EAThread/include/" + + "${THIRDPARTY_SOURCE_DIR}/dirtysdk/source/crypt/" + "${THIRDPARTY_SOURCE_DIR}/dirtysdk/source/dirtysock/" + "${THIRDPARTY_SOURCE_DIR}/dirtysdk/source/voip/" +) + +target_compile_definitions( ${PROJECT_NAME} PRIVATE DIRTYCODE_LOGGING=0 ) diff --git a/src/thirdparty/dirtysdk/changelog.txt b/src/thirdparty/dirtysdk/changelog.txt new file mode 100644 index 00000000..a0098c02 --- /dev/null +++ b/src/thirdparty/dirtysdk/changelog.txt @@ -0,0 +1,6769 @@ +EADP Game Services DirtySDK 15.1.6.0.5 - August 19, 2020 + + Framework 8.04.04 + + Android AndroidSDK 100.00.00-pr1 + AndroidNDK 15.2.4203891 + Capilano CapilanoSDK 180713-proxy + GDK GDK 200602-proxy + Kettle kettlesdk 7.508.021-proxy + PlayStation4AppContentEx 7.008.001 + PlayStation4AudioInEx 7.008.001 + PS5 ps5sdk 1.00.00.40-proxy + NX nx_config 1.00.05 + nxsdk 9.0.0 + Linux/Unix UnixClang 4.0.1 + Stadia StadiaSDK 1.48-proxy + Win32 WindowsSDK 10.0.19041 + +New in this release: + + DirtySDK (All) + DirtyNet + Added new SocketPacketQueueAdd2() which has a new bPartialAllowed parameter allowing for enqueueing of only + a portion of the submitted packet. (Only used from dirtynetnx for now) + + Added new SocketPacketQueueGetHead() that can be used to get the data associated with the queue head, + without consuming the head and dequeueing it. + + Added new SocketPacketQueueTouchHead() that can be used to update queue's head entry such that it no longer + includes successfully consumed portion of data previously queried with SocketPacketQueueGetHead(). + + ProtoSSL + Added the ability to set async receive on the SSL socket. + + DirtySDK (GDK) + NetConnGDK + Added sandbox mapping for CERT.DEBUG to CERT environment which is used by MS internally when debugging + issues. + + DirtySDK (GDK, XboxOne) + VoipHeadsetXboxOne + Added the ability to query the local user's STT preference from the system. + + DirtySDK (NX) + DirtyNetNX + Added send queue and send thread for offloading send operations from main thread. (socket send system call + proved to block unreasonably long even with non-blocking sockets) - feature disabled by default + + Added new 'asnd' global control selector that can be used to enable async send globally or on a per-socket + basis. + + NetConnNX + Added new '-asyncsend' netconn startup param to turn on socket creation defaulting to async send being + used + + Added new '-silent' to disable the display of any first party dialog as a result of handling network request + errors. + + DirtySDK (PS4) + DirtyWebApiPS4 + Added DirtyWebApiUpdate and 'auto' selector to control where the updating of dirtywebapi happens from. + + Added support for queueing event callbacks when the 'qcal' control is set to true. + + DirtySDK (PS5) + DirtyWebApi2PS5 + Added DirtyWebApiUpdate and 'auto' selector to control where the updating of dirtywebapi happens from. + + Added support for queueing event callbacks when the 'qcal' control is set to true. + + NetConnPS5 + Added ability to query play-first trial status via 'pftr' status selector. + + UserApiPS5/UserListApiPS5 + Added rich presence support. On PS5 rich presence returns the most recent unhidden activity record. + * The activity name is returned in strData. For other field it is up to the game parse the raw json. + + DirtySDK (XboxOne) + NetConnXboxOne + Added sandbox mapping for CERT.DEBUG to CERT environment which is used by MS internally when debugging + issues. + +Changes in this release: + + DirtySDK (All) + DirtyNet + Fixed issues with using async receive on our TCP sockets by making several adjustments to our behavior. + The error conversion we were doing in SocketRecvfrom treated all zero error codes as a closed socket. To address this we + simplified the error conversion by changing the error codes when there is no data in the packet queue to fake the codes + the platform socket API would return. For UDP sockets we don't handle errors in the socket receive thread, which + doesn't work well for TCP sockets. To address this we only added open sockets to the poll list for TCP and other socket + types are unchanged. There is a race condition that is created by using the socket recv thread due to the use of the + 'stat' selector. Due to the socket recv thread already handling these cases we just disable the portion of the 'stat' + selector when async receive is enabled. + + DirtySDK (GDK) + Build + Updated to add the dependencies into the modules with in GDK package that give access to the GDK extensions. + + ProtoHttpGDK + Fixed issue getting PROTOHTTP_RECVDONE from ProtoHttpRecv when passing in iBufMax = 0 by not early returning + if SocketRateThrottle returns zero. + + Changed the setting of the init flags for winhttp to be a global control selector instead of setting it + based on if we are in a debug build or not. + + Fixed state transition when calling WinHttpSendRequest by making state transition before the call in case + send completes (on a different thread) before the end of the call. + + UserListApiGDK + Fixed issue with identifying response from the DATA callback for mute and block list queries by adding in + the type and user index into the data. + + Fixed the xuid passed into the fill out profile function which was using the first index into the array + instead of the current index due to a typo. + + DirtySDK (PC, XboxOne, GDK) + DirtyNetWin + Removed the direct recv from the socket when there is no data in the packet queue. In an attempt to align + the implementations of dirtynet, we will remove our attempted optimization in dirtynetwin. + + DirtySDK (PS4, PS5) + NetConnPS4, NetConnPS5 + Fixed an incorrect debug print statement that indicated failure to acquire PPPoE information when 'type' + status selector is used. + + VoipHeadsetPS4 + Changed the block list interval down to the same value we are using on stadia to try to keep the + implementations aligned. + + DirtySDK (PS5) + NetConnPS5 + Fixed duplicate NP platform termination on shutdown. + + UserListApiPS5 + Fixed problems with querying profiles of 100 friends (up to the page limit) + + DirtySDK (Stadia) + UserListApiStadia + Fixed issue with identifying response from the DATA callback for mute and block list queries by adding in + the type and user index into the data. + + VoipHeadsetStadia + Fixed the inability to get the hardware status by calling the status callback when we get state changes. + Using GgpAddMicAudioStateChangedHandler allows us to get the state changes of the microphone state. When the + microphone is not available for use we must convey the mute. DirtySDK already has an API to do this but we + weren't sending the right callbacks down to signal the changes. + + DirtySDK (XboxOne) + VoipNarrateXboxOne + Changed the implementation to add exception handling to the speech api and prevent multiple asynchronous + reads at once. + +EADP Game Services DirtySDK 15.1.6.0.4 - June 3, 2020 + + Framework 8.04.00 + + Android AndroidSDK 100.00.00-pr1 + AndroidNDK 15.2.4203891 + Capilano CapilanoSDK 180712-1-proxy + GDK GDK 200200-proxy + Kettle kettlesdk 7.508.001-proxy + PlayStation4AppContentEx 7.008.001 + PlayStation4AudioInEx 7.008.001 + PS5 ps5sdk 0.95.00.36-proxy + NX nx_config 1.00.05 + nxsdk 9.0.0 + iPhone ios_config 1.14.00 + OSX osx_config 1.20.01 + Linux/Unix UnixClang 4.0.1 + Stadia StadiaSDK dev-proxy + Win32 WindowsSDK 10.0.18362 + +New in this release: + + DirtySDK (All) + Base64 + Added the ability to remove padding from url encoded via a new API Base64EncodeUrl2. Padding isn't required + by the spec in all cases and with JWT we do not wanted padding added. + + DirtySDK (Sony) + VoipHeadsetPS4 + Added support for querying the blocklist to auto-mute players on Sony platforms as required by TRC R5304. + + DirtySDK (Stadia) + NetConnStadia + Added support for the 'chat' selector to check if the local user can use voice chat. This selector will be + used by the voip system like on other platforms to restrict voice chat. + + PrivilegeApiStadia + Added support for checking the voice chat and multiplayer features on stadia allowing for showing the + overlay if necessary. + + VoipHeadsetStadia + Added support for processing the blocklist to auto-mute players. Added tracking for the remote player ids + which is needed for processing the blocklist. + + Added the checking of the voice chat restrictions for the transmission and playback of voice chat. + + Added support for transcription and text chat accessibility features. This implementation is based on PS4 + where we disable 'vdis' for transcription when narration is active. Like on other platforms we provide the + text chat narration features as a way to allow teams to use our voip features for text chat when voice chat + is inactive. + + Added support for text-to-speech using the cloud based providers via voipnarrate. The narration module + supports a sample rate that differs of that in module. This is handled transparently in the codec (via + resampling) but we need to handle the data coming in with the differing sample rate. + + VoipStadia + Added proper support for local input muting. We had preliminary for the local input muting in the voip + module but it was not implemented in the headset module. + +Changes in this release: + + DirtySDK (All) + DirtyNet + Removed the suppressions around the include of winsock2.h in dirtynet.h to fix issues with all warnings + being suppressed for complation units that include this header file. In previous versions of the WindowsSDK + these suppressions were needed due to warnings being generated by the header file. We no longer see this + behavior on current versions of the WindowSDK. + + NetConn + Increased the number of idle handlers to 64 due to game team use cases where more than 32 need to be + registered. The number of idle handlers should not be the limiting factor to the amount of modules we can + have running at once. + + QosClient + Changed the NetCritEnter to NetCritTry to prevent stalling the main thread waiting for the lock. If we can't + the lock then we can wait for the next frame to do the update. + + VoipHeadset + Changed the headset modules to unify on how they detect a change in the first party mute list uisng the + 'fpmu' selector. Previously on xbox we were using the 'fpmc' selector that returns the number of entries in + the first party mute list. Given that certain implementations could make multiple updates in an interval + that could mean no change in the count, we needed to use a different method. The new method will signal if + an update was made to the list which we signal in the places when we know a change has been made. + + VoipNarrate + Updated the module to expose the sample rate that it uses internally. On system's with differing sample + rates it allows us to resample where necessary at the codec level. + + VoipOpus + Refactored the resampler logic to prevent reallocation when switching sampling rate and update the + calculation the sample rate based on input samples. On Stadia when communicating without accessibility we + will be using a sample rate of 48kHz, our accessibility modules depend on 16kHz which we need to resample in + order to support. Given that we might switch the sampling rate we don't want to reallocate the same state + thus we will just remember what sampling rate the resampler is configured to. + + UserList + Increased the size of the gamertag field to support the increases in the gdk and stadia gamertags. + + DirtySDK (GDK) + NetConnGDK + Changed the implementation of the environment determination to default to production on cases where the + sandbox lookup failed or a valid mapping was not found. In these cases we want to ensure that the default + case of running in production still functions. If we would fall into these cases before these changes we + would be stuck in the environment determination state until the user tried to reconnect to the platform + network again. + + UserListApiGDK + Fixed a build error UserListApiGDK. We now also include userlistapixboxone.h in userlistapi.h. + This matches what was done for userapi.h and fixes compliation issues. + + DirtySDK (NX) + NetConnNX + Added 'eusr' selector support on NX. This selector returns the IEAUser interface given a user index. + + DirtySDK (PS4) + DirtySessionManagerPS4 + Removed Play together feature because it is not officially deprecated. All the assciated functionality will + only be avaliable if you are using SDK version lower than 7.5 SDK + + NetConnPS4 + Changed NetConnStartup to update the title id if it is called while the module is already started and + title id is NETCONNPS4_SAMPLE_TITLE_ID. This will allow the title id to be updated to the game title id + for cases where NetConnStartup is called early for BugSentry and NETCONNPS4_SAMPLE_TITLE_ID is used as + a default. + + DirtySDK (PS5) + DirtyWebApi2PS5 + Changed the way how DirtyWebApi callback is called when we encounter sce errors. The http status code is now also + supplied along with the sce error to help differentiate potential buffer size errors. + + Fixed the unintended knock on of sharing dirtywebapi2. When modules sharing dirtywebapi2 they should only be allowed to + abort the requests made by themselves. + + NetConnPS5 + Changed NetConnStartup to update title id if it is called while the module is already started and + title id is NETCONNPS4_SAMPLE_TITLE_ID. This will allow the title id to be updated to the game title id + for cases where NetConnStartup is called early for BugSentry and NETCONNPS4_SAMPLE_TITLE_ID is used as + a default. + + UserListApiPS5 + Fixed Total item friends count not being set for a list end event. + + Fixed callbacks not firing when using type mask with a user without friends. It will now also fire the callback if + the get account ids request returns zero items. + + Removed an unecessary NetCritLeave in _UserListApiProcessRequest (). This extra NetCritLeave was causing strange + deadlocks because unlocking an unlocked mutex is undefined behavior. + + Changed the restriction on the max blocked list size limit to 2000 per request (this is the max number supported by PSN). + Querying Blocked List with filters or mask parameter are also not supported. + + Fixed the _UserListApiAddQueryParamsToRequest() function to ignore adding query parameters for blocked list reqauest + instead of the friends request. + + DirtySDK (Sony) + VoipHeadsetPS4 + Removed the uChatPrivileges field as it is no longer read out of due to changes in CL1329299. + + DirtySDK (Stadia) + NetConnStadia + Changed the 'tick' selector to support retrieveing the raw JWT. Retrieving the JWT was missed during the + initial implementation of JWT support on stadia. This aligns with how the other platforms support retrieving + the JWT. + + Voip + Enabled the speaker callback on stadia. We are following the PC implementation and this was already + supported at the headset level. + + VoipHeadsetStadia + Fixed VoipHeadsetControl to return -1 when unhandled. + + VoipStadia + Fixed a bug in that the correct user indices were not being passed to apply the mute on. + + DirtySDK (XboxOne) + NetConnXboxOne + Fixed for uncaught exception during environment determination when the TMS class is not enabled in the + appxmanifest. If the application was not setup to include the ability to access title storage an exception + will be thrown when we try to create that class. In this case we will catch the exception and default to the + production environment. + + +EADP Game Services DirtySDK 15.1.6.0.3 - April 21, 2020 + + Framework 8.03.02 + + Android AndroidSDK 100.00.00-pr1 + AndroidNDK 15.2.4203891 + android_config 5.00.00-pr1 + Capilano CapilanoSDK 180712-1-proxy + GDK GDK 200200-proxy + Kettle kettlesdk 7.008.001-proxy + PlayStation4AppContentEx 6.508.010 + PlayStation4AudioInEx 6.508.001 + PS5 ps5sdk 0.95.00.36-proxy + NX nx_config 1.00.05 + nxsdk 9.0.0 + iPhone ios_config 1.14.00 + OSX osx_config 1.20.01 + Linux/Unix UnixClang 4.0.1 + Stadia StadiaSDK dev-proxy + Win32 WindowsSDK 10.0.18362 + + *** Important - First release supporting Stadia *** + This is the first DirtySDK release that supports Stadia. Most but not all functionality is supported. + For more detailed information please please contact Gameplay Services. + +New in this release: + + DirtySDK (All) + DirtyGIF, DirtyGraph + Added support for multiframe GIF decoding. Frames may be decoded all at once or one at a time. + + DirtyUser + Added a generic dirtyuser based on uint64_t identifiers and switched all our platforms to using it. Given + that all our platforms use uint64_t ids there is no reason to require the creation of a platform specific + version that is exactly the same. + + ProtoHttp + Added a new error PROTOHTTP_EAGAIN to be used when we cannot make the request because a resource is not + available or not ready to be used. + + ProtoSSL + Added a public API function to expose the pkcs1 verify features internal to protossl. This functionality + will allow us to verify the RSA signatures when used for cryptographic operations outside the context of a + TLS connection. + + Added a public function to generate a PKCSv1.5 signature. + + DirtySDK (PS5) + UserApiPS5, UserListApiPS5 + Added PS5 support for UserApi and UserListApi + + DirtySDK (Stadia) + NetConnStadia + Added initial support for the stadia platform. Other modules use the unix versions which work out of the + box. + + Added a selector 'gpid' to get the player id for the user registered at a given index. + + UserApiStadia, UserListApiStadia + Adding support for userapi and userlistapi on the stadia platform. For the profile data, stadia only + supports getting the gamertag and id. For the presence data, stadia only supports getting the availability + of the user. + + VoipStadia + Added an implementation of the voip module for stadia. This implementation is generic and inspired from the + other platforms. It will be expected to change when we start implementing the headset functionality. + + VoipHeadsetStadia + Added support for the headset module on stadia. Stadia only supports pulling PCM data from the microphone at + 48kHz. For other platforms we normally only support 16kHz but we don't have the option to modify this on + stadia. Given that 20ms of audio data at 48kHz exceeds our MTU we must compress the audio. To do this we + have added support for the opus codec which is supported on other supported platforms. Opus is configured to + use 16kHz by default but for stadia we have modified the build scripts and source to allow bumping that up + to 48kHz. + +Changes in this release: + + DirtySDK (All) + Base64 + Fixed decoding of non-padded base64. when no padding is present treat the data up to the quad as padded. + + NetConn + Fixed the issue of NetConnDisconnect being called while the ref count is not zero. + + NetGameDistServ + Moved some metrics sampling interval validation logic to where it belongs, from gameserveroimetrics.c to + netgamedistserv.c + + Enhanced debug logging around calculation of idpps, odmpps and inputdrops metrics. + + Replaced NetGamDistServT::bPrevFlowEnabled, NetGameDistServT::bPrevNoInputMode and + NetGameDistServT::uPrevStatClientCount with respective local variables in functions where their usage was + limited to. Fixed implementation of NetGameDistControl('rsta') such that it is now limited to reset the + following boolean variables used to flag if events happened during the sampling interval: + NetGameDistServT::bClientCountChanged, NetGameDistServT::bFlowEnabledChanged, + NetGameDistServT::bNoInputModeChanged. + + Fixed implementation of 'ocnt' selector to now return 1 if sampling interval is invalid. + + Fixed implementation of 'drop' and 'icnt' selector to now return 1 if sampling interval is invalid. + + ProtoHttp + Deprecated the PROTOHTTP_NOTHREADS error (used on PS4/PS5) as the PROTOHTTP_EAGAIN can be used instead as a + general purpose error across the platforms. + + ProtoSSL + Fixed an issue where a PSS signature scheme could be chosen when sending a CertificateVerify message for a + PKCS1 certificate. This would cause OpenSSL to abort the connection with an illegal parameter alert. + + Fixed a possible null-pointer exception by aborting the handshake if we can't find a valid matching + signature scheme in RecvCertificateRequest; this situation could only happen with a non-compliant server. + + Fixed SendCertificateRequest() to build the TLS1.2 SignatureAndHashAlgorithms from the available signature + schemes rather than hard-coding a subset of available signature schemes in the function itself. + + Removing support for ECDSA-SHA512 as we are not looking to support SECP521R1 curves at this time. + + VoipConnection + Changed the initial value (from 127 to 0) of the variable used to track the last received seq number for + reliable data messages on a specific voip connection. The previous value was used to make it clear that + the validity range is limited to [1,127] and that the next expected inbound sequence number at connection + start is 127 + 1 = 1. This had a knock on in a scenario where we need to send a ACK before inbound traffic + would have even been received. That scenario seems only possible in server-based connectivity using + the 1x upload Nx download optimiztion on MIC packets. Upon exercising that race condition, one end of + the connection would ACK seq nb 127 when in reality it has not yet received packet resulting in reliable + data traffic flow being broken. The bug fix consists in initializing the variable with 0 instead resulting + in the next expected inbound sequence number at connection start to be 0 + 1 = 1, and the usage of 0 when + ACKING before traffic comes in, where 0 means 'ignore this field, no sequence number acked'. + + DirtySDK (GDK) + ProtoHttpGDK + Fixed the uri truncation by parsing the information directly from the request headers. If you had a uri + longer than 256 characters we would truncate the information causing the formatted request headers to fail + when making the call to WinHttpOpenRequest. + + Changed the behavior on gdk to not automatically redirect before providing the caller with the redirection + header information. When redirects are enabled with WinHttp, you never get a chance to inspect the + intermediate header information. Their are certain customers (webkit) that depend on being able to pull out + the location headers before the redirect is complete. + + Fixed the corruption of the download payload by only resetting the read and recv positions before doing a + new read. If the caller has read the entire available buffer using ProtoHttpRecv while we have a pending + WinHttpReadData call (due to having some available space in the buffer), our payload may get corrupted. The + iInpOff and iInpLen fields get reset to zero when everything is read in ProtoHttpRecv. This causes the + writes to happen to the wrong location. + + Fixed the processing of the redirect from a POST to a GET by making sure the post size is set to zero. In + the GDK version of ProtoHttp to handle the state change to ST_HEAD we wait for all the data to be sent based + on the amount sent and post size. + + Fixed redirect issue when we try to receive the body but a redirect happens by not trying to receive body + when redirecting. If we are redirecting we will be create a new request and by trying to receive the body + we put our request tracking in a bad state. We try to receive the response and then put our query async-op + into the pending state. This puts our async-op in a bad state for the next request that happens. + + ProtoHttpManager + Fixed requests failing on the GDK platform if pipelining is enabled by disabling this pipelining feature at + the httpmanager level. + + DirtySDK (Linux) + DirtyErrUnix + Fixed the parsing of the errorlist not to exit on an error of zero but use the list term as done on other + platforms. + + DirtySDK (PC) + DirtyNetWin + Fixed local address resolution when using a full tunnel VPN by using an address in the public space to do + the routing query. With the work from home directive some teams have started trying to use their work PCs + at home using the full tunneled VPN. On the windows version of dirtynet, the local address resolution tries + to use 192.168.1.1 for routing query. On home networks you are likely to pick a 192.168.1.x address which + is the non-VPN adapter's address which will fail to be used for prototunnel connectivity purposes, even + when the connection does not leave the host. + + DirtySDK (PS5) + DirtyContextManager + Added the cleanup of Http2 contexts during shutdown + + DirtyWebApiPS5 + Fixed a merge error. sceNpWebApi2AddHttpRequestHeader should be using the web request id instead. + + NetConnPS5 + Changed the default behavior of the shared dirtywebapi2ps5 to queue up the callbacks ('qcal' selector) + + DirtySDK (XboxOne) + VoipHeadsetXboxOne + Changed _VoipHeadsetAddLocalTalker()/_VoipHeadsetRemoveLocalTalker() to no longer invoke + _VoipHeadsetDumpChatUsers() directly but to now flag a boolean that will result in the users being + dumped from VoipHeadsetProcess() instead. This is to avoid possible concurrent execution of + _VoipHeadsetDumpChatUsers() from the socket recv thread (not locking ThreadCrit) and other threads locking + ThreadCrit, i.e. voip thread and main thread. + + +EADP Game Services DirtySDK 15.1.6.0.2 - February 14, 2019 + + Framework 8.02.01 + + Android AndroidSDK 100.00.00-pr1 + AndroidNDK 15.2.4203891 + android_config 5.00.00-pr1 + Capilano CapilanoSDK 180709-proxy + GDK GSDK 191100-proxy + Kettle kettlesdk 7.008.001-proxy + PlayStation4AppContentEx 6.508.010 + PlayStation4AudioInEx 6.508.001 + PS5 ps5sdk 0.90.00.24-proxy + NX nx_config 1.00.05 + nxsdk 9.0.0 + iPhone ios_config 1.14.00 + OSX osx_config 1.20.01 + Linux/Unix UnixClang 4.0.1 + Win32 WindowsSDK 10.0.18362 + + *** Important - First Gen5 release supporting PS5 and GDK targets *** + This is the first DirtySDK release that supports Gen5 consoles. Most but not all functionality is supported. + For more detailed information please please contact Gameplay Services. + + *** Important - Required framework version updated to 8.x line in support of Gen5 consoles *** + +New in this release: + DirtySDK (All) + ConnApi + Added a debugging print to explain that we are connecting in passive mode and a comment explaining what that + is. + + NetGameDist + Added support for client side metrics (rpacklost, lpacklost, odpps, idpps, dist wait time, dis proc time) + + DirtySDK (GDK) + DirtyErrGDK + Added the WinHttp specific error mappings to the module. + + Added the errors from xsapi to the file but given that c++ hardcoded the values. + + Added errors that come from winerror that are also used in the xsapi's httpclient. + + NetConnGDK + Added support for checking the trial information. In the GDK, the request is always asynchronous and thus + we needed to introduce a control call to kick off the request. + + Added protoupnp support, strictly for informational purposes (no adding/removing port mapping as we rely on + Microsoft for that). + + Added support for communications check for the 'chat' selector + + PrivilegeApiGDK + Added support for privilege checks on the GDK + + ProtoHttpGDK + Added support for the WinHttp library backed implementation of ProtoHttp. + + Added support for send and recv rate throttles as supported on the socket and other protocols. These + throttles can be queried and controlled via the normal methods ProtoHttpStatus/ProtoHttpControl + respectively. + + ProtoUpnp + Added new macro 'dscp' for Gemini use - discovers/describes and finds port mapping but does not attempt to + add/delete one. + + Added new status flag PROTOUPNP_STATUS_FNDPORTMAP, used to indicate if a port mapping was found on the + device. + + UserApiGDK + Added support for pulling profile(s), presence and rich presence information via the userapi module. + + Added support for posting rich presence data. + + Added support for specifying the size of the avatar urls based on what we do on xboxone. + + UserListApiGDK + Added support for grabbing the social relationships on GDK without pulling any additional information. + + Added support for the avoid and mute list APIs. For this support we are treating avoid list as blocking as + the closest thing available on that platform. + + Added support for profile, presence and rich presence data lookup as part of the userlist. + + Added handling for the rate limiting errors for retry time. + + VoipHeadsetXboxOne + Added support for finding the audio endpoint on GDK builds for narration. + + VoipNarrateGDK + Added support for using XSpeechSynthesizer for doing local narration on GDK builds. + + DirtySDK (PS5) + DirtyWebApiPS5 + Added support for NpWebApi2 calls. + + DirtySDK (NX) + NetConnNX + Added support for setting the dirtysock thread's affinity using the startup param that is used on other + platforms. The 'affn' selector was also added like on other platforms to query our affinity. + + Implemented NetConnQuery based on implementation of other console platforms. Using the information we have + already in the module we can query the index based on nickname, uid or first logged in user. + +Changes in this release: + DirtySDK (All) + ConnApi + Fixed crash when the secure address on xboxone within our dirtyaddr is not properly set by checking the + validity of the getaddr call. + + CryptRand + Changed the module to use a single cryptrand.c source where possible for our various platforms as the + complexity of the solution was unnecessary. + + Changed the definitions of the CryptRandInit and CryptRandShutdown functions to be private + + DirtyThread + Fixed race condition crash / assert when the dirtysock core threads shut down by moving the logging before + the shutdown indicator. + + ProtoHttp2 + Fixed the truncation of the formatted header by ensuring space for the null terminator in our temporary + buffer we use for HPACK compression + + ProtoHttpManager + Fixed a crash in a print, because pHttpManager can be null after a disconnect in rare cases. + + ProtoHttpServ + Fixed boundary issues with chunk headers and trailers not being fully available when receiving a chunked + transfer. + + ProtoSSL + Fixed the behavior of the alerting when no certificate is received and one is required based on what is + specified by the spec. + + VoipDef + Added VoipHeadsetPreferredDefaultDeviceTypeE enum for use with VoipHeadsetStatus() control selector 'odft'. + + Changed comments to be platform agnostic for VOIP_LOCAL_USER_HEADSETOK, VOIP_LOCAL_USER_INPUTDEVICEOK and + VOIP_LOCAL_USER_OUTPUTDEVICEOK. + + VoipCommon + Fixed wrong exit condition for FOR loop that could result in out-of-bound access of + VoipConnectionListT::bTranscribedTextRequested[] array. + + VoipTranscribe + Fixed buffer resetting to be more consistently applied. + + Fixed an issue where SendFinish could fail to send when using IBM Watson and websockets, which could result + in a session timeout from that transaction as the server waits for the close or trailing silence, neither + of which were being sent, to complete the request. + + Fixed some cases that weren't going through ST_FAIL state properly. + + Fixed to reset record buffer on fail; this resolves some issues with a request following a failure. + + Fixed a rare case where the state machine could be stuck in ST_IDLE permanently. + + Fixed STREAM_END for HTTP2 when finishing a submission (Amazon/Google). + + DirtySDK (GDK) + DirtyCert + Fixed the port offset to use the correct one on all xbox platforms (xdk/gdk) + + ProtoHttpGDK + Fixed incorrect copying of data from ProtoHttpRecvAll after back to back requests by making sure we reset + our state to default. + + UserListGDK + Fixed a leak of the xbl context in the case we failed to kick off the async requests. + + VoipHeadsetXboxOne + Fixed the setting of the bitrate for gdk based on the documentation. + game_chat_audio_encoding_type_and_bitrate was changed to game_chat_audio_encoding_bitrate on the GDK. + + Fixed a bug where the users "text to speech conversion preference" was not being honored. + + DirtySDK (GDK, XboxOne) + VoipHeadsetXboxOne + Changed User^ calls to use 'xuid' and 'gtag' for compatibility with both GDK and XDK + + Removed the platform check for xboxone for bApplyRelFromMuting check on the voip thread. + + VoipXboxOne + Changed User^ calls to use 'xuid' and 'gtag' for compatibility with both GDK and XDK + + DirtySDK (NX) + DirtyNetNX + Fixed issues with NetConnStartup failures due no network connection on the switch + + NetConnNX + Removed the to do in _NetConnGetPlatformEnvironment(). Note: On NX there is no way for the client to + determine the environment. + + DirtySDK (PC) + VoipHeadsetPC + Changed VOIP_HEADSET_MAXDEVICES to 16 and updated some comments to no longer refer to only output + devices. + + Changed VoipHeadsetStatus() with control selector 'odft' to take an iValue of enum + VoipHeadsetPreferredDefaultDeviceTypeE for specifying whether to retrieve the default audio device or + the default voice communications device, and whether to set the DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY + flag. + + VoipPC + Changed print statements in voip status callback for consistency with changes in other platforms. + + DirtySDK (PS4) + DirtyErrPS4 + Added more webapi 2 errors specific to PS5. + + DirtyWebApiPS4 + Added DirtyWebApiRequestEx to allow the addition of request headers + + NetConnPS4 + Added mapping for CZECH, HUNGARIAN, GREEK, ROMANIAN, THAI, VIETNAMESE and INDONESIAN from sony + SCE_SYSTEM_PARAM_LANG_* to our LOBBYAPI_LANGUAGE_*. + + VoipHeadsetPS4 + Changed _VoipHeadsetUpdateStatus() to send VOIP_HEADSET_STATUS_INPUT to pStatusCb and changed + headset references to mic in places where only input device was being checked. + + VoipPS4 + Fixed voip status calback to no longer toggle output device status when an input device is inserted + or removed. Output device status is now set when local talker is registered and is not updated. + + DirtySDK (PS5) + NetConnPS5 + Added the 'pscx' and 'pscd' status selector to create/return a user's push context id and to delete a users + push context id. + + Fixed the gating of the chat restriction, the 'chat' selector returns FALSE when there is no restriction + instead of TRUE. + + Added mapping for CZECH, HUNGARIAN, GREEK, ROMANIAN, THAI, VIETNAMESE and INDONESIAN from sony + SCE_SYSTEM_PARAM_LANG_* to our LOBBYAPI_LANGUAGE_*. + + VoipOpus + Removed the hacks for building on PS5 for the speex package after we upgraded to the latest version. + + DirtySDK (XboxOne) + DirtyAddrXboxOne + Modified the DirtyAddrGetInfoXboxOne to validate that the address has been properly set before trying to + retrieve data. + + NetConnXboxOne + Added exception handling when adding a new NetworkStatusChanged event handler + + VoipHeadsetXboxOne + Changed _VoipHeadsetUpdateStatus() to send VOIP_HEADSET_STATUS_INPUT to pStatusCb and changed + headset references to mic in places where only input device was being checked. + + Changed _VoipHeadsetGetChatUserForVoipUser() to use to_wstring instead of snwprintf + + Fixed gamechat 2 audio buffers not being returned when switching between crossplay modes. + + Fixed first party mute to ignore adjuster none. Muting due to adjuster none is technically not a result of + first party settings. + + Fixed the way how user chat status is being handled for crossplay. + + VoipXboxOne + Fixed voip status calback to no longer toggle output device status when an input device is inserted + or removed. Output device status is now set when local talker is registered and is not updated. + +EADP Game Services DirtySDK 15.1.6.0.1 - October 9, 2019 + + EA Config 5.14.04 + Framework 7.19.00 + + Android AndroidSDK 28 + AndroidNDK r15c-1 + android_config 4.06.01 + Capilano CapilanoSDK 180709-proxy + capilano_config 2.07.02 + Kettle kettlesdk 6.508.001-proxy + kettle_config 2.11.00 + PlayStation4AppContentEx 6.508.010 + PlayStation4AudioInEx 6.508.001 + NX nx_config 1.00.05 + nxsdk 9.0.0 + iPhone ios_config 1.14.00 + OSX osx_config 1.20.01 + Linux/Unix UnixClang 4.0.1 + Win32 DotNet 4.0-2 + DotNetSDK 2.0-4 + VisualStudio 15.4.27004.2002-2-proxy + WindowsSDK 10.0.10586-proxy + +New in this release: + DirtySDK (All) + DirtyPNG + Added paletted png decode support. + + NetGameDist + Added new status selector 'drop' to return the total dropped packets + + NetGameDistServ + Added a new NetGameDistServStatus2() function that takes more parameters. + + Added 4 new status selector for reporting stats + 'clnu': number of clients + 'drop': total dropped input packet count + 'idps': total input dist packet count + 'odmp': total output dist multi packet count + + Added new control selector 'rsta' to reset stat variables + + ProtoSSL + Added new ECDSA Gameplay Services Root CA for future use. + + VoipNarrate + Added VoipNarrateControl('ctsm') to clear text to speech metrics in VoipTextToSpeechMetricsT + This is used in a fix where incorrect tts metrics may be reported, to make use of this fix update BlazeSDK + to trama.1. + + VoipTranscribe + Added VoipTranscribeControl('cstm') to clear speech to text metrics in VoipSpeechToTextMetricsT + This is used in a fix where incorrect stt metrics may be reported, to make use of this fix update BlazeSDK + to trama.1. + + DirtySDK (XboxOne) + VoipConnection + Added VoipConnectionlistT::bApplyRelFromMuting. + +Changes in this release: + DirtySDK (All) + DirtyJPG + Fixed a crash when attempting to decode an unsupported JPEG variant; an error is returned instead. + + NetConnCommon + Fixed the external list processing potentially being processed by separate threads which can cause double + destroys by using a crit. + + ProtoSSL + Fixed to add limited unicode support (truncation of 16bit ASCII characters to 8bit ASCII) to ASN.1 + certificate string parsing. + + Fixed bug that prevented an ECDSA root ca from working. + + Voip + Fixed a bunch of status/control selector not retuning proper error codes causing log spam. + + Fixed how local voip users set their crossplay flag, which prevents the receiving side from printing this + incorrect log: + voipconnection: [0] error, platform 2, persona 1000000517837 attempted to join, but cross play is not + enabled remotely. + + VoipBlockList + Improved VoipBlockListIsBlocked, when passed a VOIP_SHARED_USER_INDEX it will check all the local users to + determine if iBlockedAccountId should be blocked. + + Fixed destruction, by caching the memgroup information and use it on destruction instead of re-querying it. + + VoipCodec + Added 'plvl' status selector to support querying of current audio power level as calculated for VAD. + + VoipCommon + Removed superfluous conditions in VoipCommonUpdateRemoteStatus as the same conditions are being checked + in VoipConnectionSetSendMask() and VoipConnectionSetRecvMask() + + Fixed default channel selection for the shared user. The default behavior is to have the shared user + have the most restrictive set of channels between all the local users and blocking communication on the + shared user if there are no channels shared by all of the local users. + + VoipConnection + Changed remote user registration path to now flag that reapplying channel config is necessary + (for join-in-progress only). + + Fixed invalid behavior in the voipconnection due to buffer overrun when making shared user updates by using + the correct shared user index. + + DirtySDK (iOS) + NetConnIos + Fix crash in netconnios when attempting to call into netconn common, but it has no ref to do that. + + DirtySDK (NX) + DirtyErrNX + Changed all the error codes to use Nintendo specific ones. + + DirtyNetNX + Fixed the destruction of the hostname cache to happen after the last _SocketIdle call to ensure we don't + crash on shutdown. + + DirtySDK (PC) + VoipHeadsetPC + Fixed _VoipHeadsetSendEncodedAudio() to use a memcleared voipUser for remoteUser instead of an empty string + when loopback is enabled. + + Fixed 'aloc' selector incorrectly returning error code upon user going from 'participating' to + 'not participating'. + + VoipNarratePC + Fixed crash if CoInitialize failed by setting the failure state and not destroying the ref. + + VoipPC + Removed dead code from _VoipActivateLocalTalker(): inappropriate error handling for 'aloc' selector + because it can't return a negative value. + + DirtySDK (PS4) + DirtyErrPS4 + Removed the errors that no longer exist in SDK7.000 conditionally to maintain backwards compat with + SDK6.500 + + DirtySessionManagerPS4 + Fixed an issue where the meta data on the available invites would not be updated if the invite was received + while we were in the console's REST state, this prevent the user from successfully joining the invite. + Now upon attempting to join an invite from the system software we will no longer assume the list is up to + date. + + ProtoHttpPS4 + Fixed redirects failing due to not being able to parse the location header. + + DirtySDK (XboxOne) + ProtoMangelXboxOne + Fixed crash in the IncomingAssociationEventHandler by validating that the module ref is still valid and + unregistering the event handler like we do in other areas of DirtySock. + + Fixed exception that gets thrown when trying to access a security association that was cleaned up by the + remote peer by not accessing the state. + + Fixed crash when mutating the security association vector between the event handler and main thread by + protecting the writing with a crit. + + Moved the manipulation of the list before we start the destruction and protect with the new global state + crit to ensure the operation has succeeded to prevent invalid access to a killed crit. + + UserApiXboxOne + Added handling of http errors code in _UserApiProcessProfileResponse + + VoipCommon + Fixed setting bApplyRelFromMuting whenever send masks have been altered, this makes it so the communication + relationship is reflected by the change of channels. Prior to this bApplyRelFromMuting was only being set + when a user was activated, which could result in updates for mute/unmute, not working. + + Fixed the 'umic' and 'uspkr' control selector to set bApplyRelFromMuting to reflect changes in title code + mute. + + VoipConnection + Changed remote user registration path to now flag that reapplying communication relationships is + necessary (for both initial connect and join-in-progress). + + VoipConnection/VoipXboxOne + Code reapplying communication relationships was moved from VoipConnectionSetSendMask() and + VoipConnectionSetRecvMask() to the _VoipThread(). + + VoipHeadsetXboxOne + Fixed to capture sttEventCount and sttCharCountRecv metrics. + + Removed the comment block around code dealing with first party mute. First party mute will now work as long + as you are on XDK July QFE 9. + + Fixed to allow enabling/disabling STT functionality with the 'tran' control for crossplay mode, when you + are not yet in crossplay mode. + + VoipXboxOne + Modified local user registration and activation paths to now flag that reapplying communication + relationships is necessary. + +EADP Game Services DirtySDK 15.1.6.0.0 - July 24, 2019 + + EA Config 5.14.04 + Framework 7.19.00 + + Android AndroidSDK 28 + AndroidNDK r15c-1 + android_config 4.06.01 + Capilano CapilanoSDK 180702-proxy + capilano_config 2.07.02 + Kettle kettlesdk 6.508.001-proxy + kettle_config 2.11.00 + PlayStation4AppContentEx 6.508.010 + PlayStation4AudioInEx 6.508.001 + NX nx_config 1.00.04 + nxsdk 7.2.1 + iPhone ios_config 1.14.00 + OSX osx_config 1.20.01 + Linux/Unix UnixClang 4.0.1 + Win32 DotNet 4.0-2 + DotNetSDK 2.0-4 + VisualStudio 15.4.27004.2002-2-proxy + WindowsSDK 10.0.10586-proxy + + *** Important - First crossplay compatible release *** + This is the first DirtySDK release that supports crossplay. For more information please refer to our + crossplay integration guide : https://developer.ea.com/display/TEAMS/Cross+Play+Integration+Guide + https://developer.ea.com/display/dirtysock/VoIP+Tech#VoIPTech-CrossplayConstraints + Crossplay is NOT SHIPPABLE for this release. + +New in this release: + Contrib (ALL) + VoipOpus + Changed VoipOpus to be available on pc, ps4 and xone platforms; for use in cross play mode. + + Added speex resampler, to convert to a standard sample rate when needed (for xone). + + Added the _VoipOpusControl selectors: + 'infl' - Set if the input samples are float via iValue, (default int16) + 'inin' - Set if the input samples are int32_t via iValue, (default int16) + 'insr' - Set the input sample rate (default 16000) + + DirtySDK (All) + NetGameDistServ + Added the ability to configure a logging callback which allows logging using a different mechanism. + This is used in cases where we want to log to a different logger on release builds that don't have debug + logging. + + ProtoHttp + Added an explicit failure code to report a failure due to the maximum number of redirections being + exceeded. + + ProtoSSL + Added the support for enabling and disabling of individual curves. + + Added debug print of from/till times if a certifiicate is determined to be expired. + + Voip + Added the ability to set the thread affinity for the VoIP thread when it differs from DirtySDK. + + VoipBlockList + Added VoipBlockList, when integrated with Social's "Muting Web API" produces a cross platform persistent + muting feature. An example of its usage is found in BlazeSDK\samples\Ignition\Friends.cpp. The + VoipBlockList is created when VoipStartup is called, game code is responsible for calling VoipBlockListAdd, + VoipBlockListRemove, and VoipBlockListClear as needed to keep the DirtySDK representation of the mute list + up to date. It is possible to call VoipBlockListIsBlocked to validate if communication is blocked between + the two users. All of these functions operate on a local user index and an account id of the muted user. + + DirtySDK (PC) + VoipHeadsetPC + Added '-pbk' and '+pbk' selectors which were available on PC and XOne, making muting functionality + consistent. + + DirtySDK (PS4) + VoipHeadsetPS4 + Added cross-play support. + + DirtySDK (XboxOne) + NetConnXboxOne + Added 'tsbx' selector, allowing override of test sandbox name. This obviates the need for hosting a file + to specify the sandbox name, although that capability remains. + + VoipXboxOne, VoipHeadsetXboxOne + Added cross-play support. + +Changes in this release: + DirtySDK (All) + CryptChaCha + Changed the implementation of Encrypt and Decrypt to skip Poly1305 if NULL is passed to pTag. In some cases + we just want to use ChaCha20 to encrypt and decrypt, while not needing to use Poly1305 for MAC. + + Optimized the block operation by using memcpy to eliminate an unnecessary loop. + + CryptNist + Changed the ECDSA signing operations to generate the signatures deterministically by generating k based on + the message hash, private key and curve parameters. + + Decreased the size of the private key state, the largest we will need is 48 bytes. + + CryptRand + Improved our PRNG implementation using CryptChaCha. Since each platform we target has a method to get random + bytes from their entropy pool and our fallback is not secure, we have opted to move to a new system of + generating random data. In this new system we use a small amount of random bytes fro the ChaCha cipher key + and IV (nonce) to generate pseudo random bytes. + + DirtyThread + Suppressed the 4702 warning around our main thread which caused issues when LTCG is enabled. + + Removed the call to ThreadEnd as the EAThread Mutex assert issue came back on newer versions. The call is + not needed for the trouble it has caused us. + + NetConn + Added NetConnAccountInfo struct that stores user's nucleus account id and persona id. 'acid' and 'peid' control + selectors will be used to populate it. + + ProtoHttpUtil + Fixed ProtoHttpUrlDecodeStrParm() to reserve space for terminating null character (preventing possible + overflow of the null). Also fixed to not read past the end of the input buffer if a percent character + was the second-to-last or last character. + + ProtoHttp2 + Enhanced the header formatting to use the available space in the output buffer for formatting. This allows + for formatting much larger headers without extra allocations. + + ProtoSSL + Changed the module to use platform ds_strsplit and removed the _ProtoSSLSplitDelimitedString. + + Removed CA certificates with < SHA-256 hash, with the exception of VeriSign Class 3 Public Primary + Certification Authority - G5 which is still required by C&I. Additionally removed VeriSign Universal Root + CA that never saw use. + + Fixed server to not automatically enable all TLS1.3 ciphers if TLS1.3 is enabled and no TLS1.3 ciphers + are enabled. + + Voip + Removed VoipRemove and VoipConnectionRemove, these functions are no longer called. + + Moved internal functions in voip.h to voipcommon.h which is where our internal/private APIs are located. + + Moved VoipActivateLocalUser to VoipCommon to make sure that customers are calling VoipGroupActivateUser. + + VoipCommon + Removed the controls in voipcommon that are used internally to prevent customers from calling them. Instead + of calling the controls, functions were added that are accessible to the source layer of the voip subsystem. + + Moved the VoIP memory function to be part of VoipCommon and removed the implementations for VoipPriv. + + Removed the NULL check for voipcommon state in _VoipCommonTextDataCb as the state will always be valid. + Headset who calls this callback is owned by VoipCommon, thus it cannot be NULL when getting a callback. This + code trips coverity defects because it assumes if you check here you have to check in the later parts of the + function. + + Removed VoipCommonStatus('ruid') and added 'rcvu', since this no longer returns the remote user id. 'rcvu' + returns the "remote connection voip user" + + VoipConduit + Added support for multiple VoIP mixers. + + Added support for controlling playback (used for muting) through a playback callback. + + VoipConnection + Fixed crash when invalid user index is passed into VoipConnectionSend by early returning in this case. + + Changed the shared user index sent to remote players to VOIP_SHARED_REMOTE_INDEX which -1. + + VoipHeadset + Removed VoipHeadsetStatus('ruid') and added 'ruvu', since it does not operate on id's. 'ruvu' returns (TRUE) + if the "remote user voip user" is registered in voip headset. + + VoipPriv + Removed VoipTimerT as it is no longer used. + + Changed VoipUserT to contain only the NetConnAccountInfo, voip user flags and the platform type of + the voip user. It will no longer contain first party IDs. + + Changed VOIP_NullUser and VOIP_SameUser macros to compare persona IDs. + + DirtySDK (PC) + CryptRand + Switched PC to use BCrypt to align with XboxOne. + + VoipHeadsetPC + Removed the default setting of the volume to 90 on the audio output device. This was interfering with game + settings. + + DirtySDK (PS4) + DirtyWebApiPS4 + Fixed DirtyWebApiAddPushEventListener* so it returns a valid id to be passed back to + DirtyWebApiRemovePushEventListener. + + DirtySDK (NX) + ZFileNX + Removed zfilenx as it is identical to unix outside of one function which we ifdef out on that platform. + + DirtySDK (XboxOne) + ProtoHttpXboxOne + Enhanced implementation of _ProtoHttpXmlHttpWritingToOutstream() for more robustness. Previous + implementation assumed that iBlockingOpsThreadResult would always contain a number of bytes accumulated + equal to the number of bytes originally submitted. After reviewing the implementation of + SequentialStream::Write(), this assumption proved to be luckily correct (thus this CL being on ml only + and not being associated with a critical ticket). But it was decided to change the implementation of + _ProtoHttpXmlHttpWritingToOutstream() to avoid coding pattern based on an "implicit" assumption. + With the new implementation, things will still work in the future if SequentialStream::Write() is + modified and no longer behave the same (i.e. it could result in partial accumulation). + + ProtoMangleXboxOne + Removed entering/leaving MangleCrit in the lambda executed upon completion of the creation of a security + association. It was left over from a previous implementation and it was no longer protecting any specific + thread safety access. + + VoipCommon + Fixed an issue in _VoipCommonReceiveTextDataNativeCb preventing local user from seeing their own Speech To + Text, requires QFE5 of the July 2018 XDK + + VoipDef, VoipGroup + Removed VoipCbTypeE::VOIP_CBTYPE_AMBREVENT (xboxone-only bad rep auto mute event). It had been unused + since rebasing voip xboxone to game chat 2 (i.e. since Shrike.0). + + Removed xboxone-specific fields from VoipSynthesizedSpeechCfgT because the 'voic' control selector is + no longer usable on xboxone since the introduction of MS Game Chat 2 in Shrike.0. + + VoipHeadsetXboxOne + Changed implementation to comply with game chat 2 reference doc (Xbox Live API Compiled HTML Help) and + MS sample code: only call synthesize_text_to_speech() if text_to_speech_conversion_preference_enabled() + is true for the originator. + + Fixed XMA/audio issues experienced by customers by setting the DO_NOT_USE_SHAPE flag when initializing + XAudio2 for use with text narration. Based on the conversation with NHL and the Frostbite Audio team. By not + using DO_NOT_USE_SHAPE we cause the current xma allocations to have bad data. + +EADP Game Services DirtySDK 15.1.5.0.2 - June 20, 2019 + + EA Config 5.14.04 + Framework 7.19.00 + + Android AndroidSDK 28 + AndroidNDK r15c-1 + android_config 4.06.01 + Capilano CapilanoSDK 180702-proxy + capilano_config 2.07.02 + Kettle kettlesdk 6.508.001-proxy + kettle_config 2.11.00 + PlayStation4AppContentEx 6.508.010 + PlayStation4AudioInEx 6.508.001 + NX nx_config 1.00.04 + nxsdk 7.2.1 + iPhone ios_config 1.14.00 + OSX osx_config 1.20.01 + Linux/Unix UnixClang 4.0.1 + Win32 DotNet 4.0-2 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +New in this release: + DirtySDK (All) + DirtyNet + Added the 'poln' control to do a blocking poll specified in nanoseconds. + + Added a SocktetHostnameCacheProcess function to allow us to expire entries from idle to fix entries never + expiring. + + DirtySDK (PS4) + NetConnPS4 + Added play-first trial check using NetConnStatus('pftr') selector. This capability is disabled by default, + use -D:dirtysdk-playfirsttrial=true when generating the solution to enable. Requires the + PlayStation4AppContentEx package. + + Changed 'tria' selector to additionally return TRUE if play-first trial is enabled. + + VoipHeadsetPS4 + Added two new public api to be invoked from voipcommon: VoipHeadsetTranscribedTextPermissionCheck() + and VoipHeadsetSetTranscribedTextReceivedCallback(). + + Added a new callback synopsis: VoipHeadsetTranscribedTextReceivedCbT. + + DirtySDK (XBoxOne) + ProtoHttpXboxOne + Added a period of time before we delete the XmlHttp state to prevent accessing invalid memory from the + HttpCallbacks. There is a small window after the COM memory has been released where callbacks can still + be fired. During this period we want to make sure that both the XmlHttp and HttpRef state is still valid + to prevent crashing. This change will increase the time it takes for the kill list to be cleared. + +Changes in this release: + DirtySDK (All) + Build + Removed the vccorlib includes to the sample program module as this is no longer needed vs2015+. + + ConnApi + Fixed the setup of demangling (and unsetting of addresses) when the game is server hosted but voip is not. + When Xbox Clients setup their dedicated server client in the client list, the address fields in the client + info are set to 0. This causes the connections to the dedicated server to failed because of the invalid IP + addresses. + + ProtoHttpServ + Fixed incorrect linked list manipulations in _ProtoHttServUpdateListen(). Problem description: Upon + inserting a new httpthread entry in the httpthread list, if that list already has more than one entry, + the new entry would incorrectly be inserted after the first entry as the tail of the list... resulting + in the rest of the list being lost. As a knock-on of this bug, CCS-DirtyCast ssl connections would no + longer be cleaned up properly... resulting in stunnel spamming with: + LOG3[1301]: transfer: s_poll_wait: TIMEOUTclose exceeded: closing + + ProtoSSL + Fixed TLS 1.3 client to return illegal_parameter alert instead of handshake_failure when no cipher is + selected. + + Fixed TLS 1.3 client to validate server session id value is empty when the session id sent was empty. + + Fixed TLS 1.3 client to support middlebox compatibility mode for session_id field. + + Fixed TLS 1.3 client to validate Protocol Version or Supported Versions extension. + + Fixed a client bug when a client cert was sent on a previous connection attempt; a subsequent connection + attempt where a client cert was not sent would result in the client trying to send a CertificateVerify + handshake message and crashing with a null-pointer exception. + + Fixed TLS 1.3 client/server to abort on non-null compression value. + + Fixed TLS 1.3 server to return unexpected_message alert instead of no_negotiation when receiving a Hello + Request message. + + Fixed TLS 1.3 server to validate ClientHello version is at most TLS1.2 before parsing supported versions + extension, which previously could overwrite an invalid version before the version was validated. + + Fixed to move bCertSent/bCertRecv from the state ref to the secure state ref, renamed to bSentCert/bRecvCert. + These state booleans make more sense being in the secure state, since they track the status of a specific + session, and also benefit from being automatically reset when a new connection is attempted, which is not + the case with the overall state. + + Fixed to add handshake state transition validation to prevent invalid messages from causing exceptions, + e.g. if a KeyExchange message is sent before ClientHello. + + Fixed to add protection for a server giving us an unsupported elliptic curve (a spec violation). + + Fixed to add protection for a hello extension with an extension size extending past the end of the hello. + + Fixed to remove SSL3_MSG_HELLO_RETRY_REQUEST define, as it is not an explicitly defined handshake message, + and add SSL3_MSG_END_OF_EARLY_DATA handshake define for future reference/use. + + VoipCodec + Fixed VAD to consider all samples in a frame, instead of just the first. Found by inspection. + + VoipGroup + Fixed incorrect low-level conn id to high-level conn id translation logic in + _VoipGroupManagerDisplayTranscribedTextCallback() + + VoipTranscribe + Fixes for some possible sources of metrics issue observed in production. + + DirtySDK (NX) + NetConnNX + Fixed the shutdown by properly clearing the global ref. + + DirtySDK (PC) + VoipNarratePC + Suppressed the warnings from the windows headers which can cause issues on higher warning levels. + + DirtySDK (PC, PS4) + VoipHeadsetPC,VoipHeadsetPS4 + Fixed crash when TTS is enabled but STT is disabled when doing narration by checking for NULL before calling + the 'vdis' control. + + DirtySDK (PS4) + NetConnPS4 + Fixed _NetConnRequestAuthCode() and _NetConnNpAsyncRequest() not returning a negative when an error is + encountered. + + ProtoHttpPS4 + Removed the call to sceHttpAbortRequest which breaks following redirects in certain scenarios. + + VoipCommon (PS4) + Fixed chat restriction not being properly enforced for inbound transcribed text (fix is inspired from + original MS game chat dll implementation). When inbound transcribed text is received from the network, + voipcommon now passes it to VoipHeadset with a call to VoipHeadsetTranscribedTextPermissionCheck(). + If the permission check passes, then VoipHeadset passes the text back to VoipCommon by invoking the + new VoipHeadsetTranscribedTextReceivedCbT callback previously registered with + VoipHeadsetSetTranscribedTextReceivedCallback(). If the permission check fails, VoipHeadset + instead just drops the text and never invokes the VoipHeadsetTranscribedTextReceivedCbT callback. + + DirtySDK (Unix) + DirtyNetUnix + Fixed _SocketPoll(), when processing ppoll result, incorrectly including sockets that were not originally + included in the poll. Potential knock-on: process showing 100% CPU usage caused by ppoll continuously + unblocking immediately. + + VoipUnix + Changed implementation of VoipSetLocalUser() to initialize the local user with user id "dummy_voip_user" + instead of the string returned by NetConnMAC(). This change was done to work around NetConnMAC() not + always returning a valid string when used on a VM because the name of the NIC does not match what + NetConnMAC() is looking for (eth0, eth1 or wlan0). See implementation of _SocketGetMacAddress() in + dirtynetunix. NetConnMAC misbehaving results in VoipSetLocalUser() creating 'empty' voip user id, and + the stress client no longer sending voip traffic over established voip connection because the send mask + is incorrect as a result of the user id being NULL. + + DirtySDK (XboxOne) + ProtoHttpXboxOne + Fixed use after free crash in _ProtoHttpProcessKillList by moving the _ProtoHttpUpdateXmlHttp to be the last + function that is called within the loop. + + UserApiXboxOne + Fixed read access violation when growing the response buffer after the second PROTOHTTP_RECVBUFF by only + copying the exact amount of data in the source buffer. + + VoipHeadsetXboxOne + Enhanced implementation of _VoipHeadsetSetRemotUserOutboundMute() and + _VoipHeadsetSetRemotUserInboundMute() to guard against the possibility of + pRemoteChatUser->custom_user_context() being NULL. + + VoipNarrateXboxOne + Changed the AsyncStatus to be fully scoped which fixes build errors in certain conditions. + +EADP Game Services DirtySDK 15.1.5.0.1 - April 9, 2019 + + EA Config 5.14.04 + Framework 7.19.00 + + Android AndroidSDK 28 + AndroidNDK r15c-1 + android_config 4.06.01 + Capilano CapilanoSDK 180702-proxy + capilano_config 2.07.02 + Kettle kettlesdk 6.008.001-proxy + kettle_config 2.11.00 + PlayStation4AudioInEx 6.008.001 + NX nx_config 1.00.04 + nxsdk 7.2.1 + iPhone ios_config 1.14.00 + OSX osx_config 1.20.01 + Linux/Unix UnixClang 4.0.1 + Win32 DotNet 4.0-2 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + + *** Important - First DirtySDK release with NX Support *** + This is the first DirtySDK release with official NX support. Currently VoIP, UserApi and UserListApi are not + supported on this platform. + + *** Important - API Breaking Change *** + The VoipDisplayTranscribeTextCallback function prototype has been changed to return an int32_t instead of + returning void. This enables the user callback to signal that text has been rendered, and prevent the text + from possibly being rendered again from a different VoipGroup. + +New in this release: + DirtySDK (All) + Aws + Added keyPath to signing info. + + Added reading and writing of signed binary events, used in transcription. + + Voip + Added new VOIP_CBTYPE_TTOSEVENT event type to VoipCallback; this event signals the start and end of TTS. + It can be used to temporarily override a push-to-talk system to allow the audio to be sent to the other + player(s) without requiring push-to-talk to be manually activated by the user. + + VoipPC,VoipPS4 + Added auto-muting of audio input when send mask is zero. This prevents STT from taking place when the user + is muted, e.g. in a push-to-talk system when PTT is not engaged. + + Added polling of TTOS status and updating the VoipCommon status variable that tracks it. + + VoipCommon + Added tracking of ttos status, and triggering of VOIP_CBTYPE_TTOSEVENT when ttos status changes. + + VoipHeadsetPC,VoipHeadsetPS4 + Added 'ttos' status selector to indicate when text-to-speech is active. + + Added 'mute' control and status selector to mute input audio, used to prevent transcription when + a player is not sending any audio. + + Added 'spam' pass-through to VoipTranscribe, to allow setting debug level of that module. + + VoipTranscribe + Added Amazon Transcribe support. + + Added 'vdsc' control selector to enable/disable the short audio discard behavior that is enabled by + default. This is used by the VoipHeadset layer to disable short audio discard during TTS, which + would otherwise prevent STT of short TTS phrases from working. + + DirtySDK (NX) + Build + Added NX support + + DirtySDK (PC) + VoipHeadsetPC + Added support for 'txta' control which is used to control how text chat accessibility functions in the context of loopback. + + DirtySDK (PS4) + VoipHeadsetPS4 + Added support for the 'txta' control to change the behavior when using text chat accessibility features. + + DirtySDK (XboxOne) + VoipNarrateXboxOne + Added the narrate module on xboxone to perform non-voip based narration that can be used for text chat. + +Changes/fixes in this release: + DirtySDK (All) + Aws + Fixed xcode build warnings due to truncation. + + ProtoHttp2 + Changed to increase header cache size from 1k to 2k; this allows sending and receiving larger headers. + + Changed the implemention to ignore frames from untracked (closed) streams. + Per the spec: WINDOW_UPDATE can be sent by a peer that has sent a frame bearing the END_STREAM flag. + + Fixed append header rejects new headers if the previous header values were appended to by switching to + strcmp from strncmp. + + VoipDef + [API BREAKING] Changed VoipDisplayTranscribedTextCallback to return an int32_t instead of void. This + allows the application to indicate when it has displayed transcribed text, and prevent further callbacks + against other voipgroups with the same text. + + VoipGroup + Changed the implementation such that the voipgroup layer registers its 'display transcribed text' + callback with the underlying voip sub-system as soon as the voipgroup manager gets created, and such + that the voipgroup layer only unregisters that callback upon destruction of the voipgroup manager. + This change is inspired from the way the 'event' callback is registered/unregistered and it is necessary + to avoid a possible situation where the 'display transcribed text' callback gets called from the + voip thread after the voipgroup manager got destroyed, which can happen if the voipgroup manager is + destroyed after text is submitted for transcription but before the transcription result is received. + + Removed incorrect usage of critical section from VoipGroupSetDisplayTranscribedTextCallback(). Potential + deadlock before this fix: + --> voip thread locking pVoip->Common.ThreadCrit first from _VoipThread() and then blocking on + NetCritEnter(&pManager->crit) from _VoipGroupManagerEventCallback() + --> main thread locking pManager->crit first from VoipGroupSetDisplayTranscribedTextCallback() + and then blocking on NetCritEnter(&pVoipCommon->ThreadCrit) from + VoipSetDisplayTranscribedTextCallback() + + Changed _VoipGroupManagerDisplayTranscribedTextCallback() to respond to the new + VoipDisplayTranscribedTextCallback return parameter and stop iterating through voipgroups if the callback + returns TRUE. + + VoipNarrate + Fixed an issue formatting the Microsoft request body where the format string and parameter count didn't + match. + + Fixed to release any queued requests on destroy. + + Changed VOIPTRANSCRIBE_INPUT_MAX to VOIPNARRATE_INPUT_MAX based on coding standards. + + VoipTranscribe + Fixed a bug where the STT uDurationMsSent metric was not set to the correct value, often being 0. + + Fixed a bug with google+http encoding where the Base64 request could be terminated too early, resulting in + a 400 server error. + + Fixed an issue parsing google error response. + + Changed to increase the size of the JSON parse buffer to be able to handle large Amazon responses. + + Changed to increase the ref response buffer substantially to be able to handle large Amazon responses. + + Changed Watson parsing to use the ref parse buffer instead of allocating dynamically. + + Fixed to allow specification of a non-encoded Watson apikey, as VoipNarrate does. Pre-encoded Watson + apikeys are still supported, but will be deprecated in the future. + + Fixed Watson/Websockets transcription request failing if the server has timed us out since the last + request. + + Added smart_formatting enable for Watson (e.g. "one two three" becomes "1 2 3"). + + Fixed to eat %HESITATION markers from Watson transcription result, if present. + + Fixed a metrics issue where a buffer could be submitted every VoipTranscribeUpdate() execution while + receiving a response, if a very large untranscribable buffer was previously submitted. The impact of this + issue was that metrics would be ticked every frame and very quickly result in bogus metrics with hundreds + of imaginary transactions spanning thousands of minutes. The fix is to wait to submit until the ongoing + receive operation is complete. In practice, this situation is very hard to trigger and involves many + consecutive empty transaction results and a maximum backoff timer of 60s. + + VoipTunnel + Fixed the voice "squelching" logic to not only release the appropriate rebroadcasting slot(s) when the + voice originator stops talking, but also when the voice originator mutes a subset of the players + (voice consumers) to which he is talking. + + DirtySDK (PC) + VoipDef + Changed PC VOIP_MAXLOCALUSERS from 1 to 4, to match NETCONN_MAXLOCALUSERS + + VoipHeadsetPC + Fixed the 'voic' selector and improved voipnarratepc to support other languages with the voic selector. + + Changed PC voip to allow multiple local users, there is still only one device, so there may only be one + active user at a time. Recording, playback, transcription, STT/TTS metrics only function for the active + user index. + + Changed VoipHeadsetControl 'aloc' which now takes and store the active user index. + + Fixed transcription buffer size to use define instead of being hardcoded at 256. + + Fixed assignment of iDebugLevel to be done only under DIRTYCODE_LOGGING=1 + + VoipPC + Changed so that all users on a PC will have the same headset status. + + Changed VoipSetLocalUser to take user index into account (like other platforms). + + Changed VoipActivateLocalUser to take user index into account (like other platforms) however with the added + limitation that only one user can be active at a time, if the active user becomes deactivated a new user + will be selected if possible. + + DirtySDK (PS4) + VoipHeadsetPS4 + Fixed severe stuttering audio issue with PS4 camera voip, broken in audio input pipeline refactor in + Raven.5. + + DirtySDK (Unix) + DirtyNetUnix + Moved the SocketHost logging behind a verbosity check to reduce spam that is not helpful in most cases. + + DirtySDK (XboxOne) + ConnApi + Fixed the voip server dedicated flow to not perform unnecessary demangling on Xbox One. + + DirtyAuthXboxOne + Fixed potential deadlocks by adding the missing NetCritLeave calls when returning early due to errors. + + ProtoHttpXboxOne + Fixed ProtoHttpSend() with an implicit call to ProtoHttpUpdate() to allow internal state machine to be + pumped when ProtoHttpSend() is called repeatedly without intermediate calls to ProtoHttpUpdate(). + + Changed implementation such that ProtoHttpPost(), ProtoHttpPut() and ProtoHttpPatch() can be used without + a mandatory subsequent call to ProtoHttpSend() as it used to be supported prior to Shrike.0. Fix consists + in buffering request data passed to ProtoHttpRequestCb2() into a new "pre-send" buffer. A new PRESEND state + is also introduced to accumulate the content of the pre-send buffer into the xmlhttp object's outbound + sequential stream before accepting any new data from the user subsequently calling ProtoHttpSend(). + + Fixed a bug introduced in Shrike.0 which included a ProtoHttpXboxone refactor to improve the way + potentially long blocking MS calls are being offloaded to worker threads. The new solution broke an edge + case involving request redirection. Upon some race conditions, a request redirection may be incorrectly failed. + + Fixed the possibility of an xmlhttp req being stalled in state + ProtoHttpXmlReqStateE::REQ_ST_WAITING_RESPONSE when a race condition is exercised: xmlhttp req's event + handler modifying eNextState before the main thread initializes it to REQ_ST_INIT. + + Fixed a crashed in OnError by adding a check for bWaitingForAsyncCompletion in _ProtoHttpProcessKillList(). + bWaitingForAsyncCompletion must be false before xmlHttp can transition states + +EADP Game Services DirtySDK 15.1.5.0.0 - January 23, 2019 + + This SDK release was built using the following compiler packages: + + EA Config 5.14.04 + Framework 7.19.00 + + Android AndroidSDK 26.0.2-3 + AndroidNDK r13b-1 + android_config 4.03.00 + Capilano CapilanoSDK 180702-proxy + capilano_config 2.07.02 + Kettle kettlesdk 6.008.001-proxy + kettle_config 2.11.00 + PlayStation4AudioInEx 6.008.001 + iPhone ios_config 1.14.00 + OSX osx_config 1.20.01 + Linux/Unix UnixClang 4.0.1 + Win32 DotNet 4.0-2 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + + *** Important - PUBLIC API IMPACTING - *** + + Reference: https://developer.ea.com/display/dirtysock/DirtySock+Shrike+Upgrade+Guide + + CryptMD2 has been removed as MD2 support is obsolete. + + Removed VOIP_LOCAL_USER_MICON which has been unused since PS2 era. + + DirtySDK voip for xboxone now use MS Game Chat 2 + * Game chat 2 now needs to be added as a sdkreference in nant build scripts. This step is needed for the + game chat 2 dll to be copied to the deployment directory as a post build step. + + Xbox.Game.Chat.2.Cpp.API, Version=8.0 + + + * Because MS game chat 2 is internally dealing with retrieval of XBL accessibility settings for the user, + DirtySDK XboxOne voip no longer needs to be fed with that info by the game code. + + * DirtySDK XboxOne voip is no longer displaying inbound transcribed text in system UI if + game code does not register a display transcribed text callback. (New behavior resulting from + VoipHeadsetXBoxOne being rebased from from windows::xbox::chat to MS game chat 2) + + * DirtySDK XboxOne voip no longer supports the feature originally requested by the EA accessibility team: a + hearing-impaired person requesting transcribed text from other players also wants to see transcribed text + for his/her own speech. (New behavior resulting from VoipHeadsetXBoxOne being rebased from from + windows::xbox::chat to MS game chat 2) + +New in this release: + DirtySDK (All) + AWS + Added new module to supply various utility functions in support of connecting to AWS services, starting + with AWS Signature Version 4. + + ConnApi + Added the game / voip topology configuration to the module, which allows complexity that existed in the + network adapter to be moved down. + + Added status selectors for the configured timeout values. + + CryptBn + Added the CryptBnBitAnd and CryptBnBitXor which performs bitwise and; bitwise xor operations + + CryptCurve + Introduced cryptcurve, an interface over the multiple curve modules we support. + + CryptEcc + Added a new parameter to CryptEccPointFinal to let us know if we are encoding the shared secret into a + buffer. + + CryptHash + Added the ability to lookup the hash based on the size. + + CryptMont + Added support for X25519 and X448 curves based on RFC7748. + + DirtyLib + Added the ability to set an option for crits via NetCritInit2 and added an option to enable a crit in + single-threaded mode for specific multi-threaded scenarios (SocketLookup). + + Added NetLibCrc32 to calculate CRC32, with optional table parameter. + + DirtyMem + Added new VOIP_PLATFORM_MEMID memory identifier. + + Plat-Str + Added new string functions ds_fmtoctstring_lc, ds_strtolower, ds_strtoupper, ds_strtrim, and + ds_strlistinsert in support of AWSV4 signing. + + Plat-Time + Added TIMETOSTRING_CONVERSION_ISO_8601_BASIC to supported ds_timetostr conversion options. + + ProtoHttp2 + Added the ability to decode the debug data in GOAWAY frames. + + ProtoHttpUtil + Added ProtoHttpUtilParseRequest() for parsing the first line of an HTTP request, functionality refactored + from _ProtoHttpServParseHeader() + + ProtoHttpServ + Added new ProtoHttpServControl2 API to allow setting SSL settings specific to a ProtoHttpServ thread. + + Protomangle + Added the service name to the demangler query for better tracking + + ProtoTunnel + Added control to allow setting the behavior for trying a random port on bind failure. + + Added control selectors for maximum number of receive calls per update and idle callback rate. + + VoipConnection + Added support for "reliable opaque" data type needed for sending/receiving MS game chat 2's reliable data + frames. (MS Game chat 2's unreliable data frames are sent as 'MIC' packets.) + + Added a iConnId parameter to VoipConnectRecvVoiceCbT and VoipConnectRegUserCbT. + + Added VoipConnectRecvOpaqueCbT. + + Added a uSendMask parameter to VoipConnectionSend(). + + VoipHeadset + Added VoipHeadsetReceiveOpaqueDataCb(). + + VoipNarrate + Added Amazon Polly support. + + DirtySDK (Apple) + Build + Added a warning for partial available so the build will fail when the deployment version is too low for the + APIs we reference. + + Created an objectivec build type that imports all our C warnings that we enable. + + ProtoSSLApple + Added support for the ALPN extension on versions of the OS that support it. + + DirtySDK (PC) + VoipNarratePC + Added a new VoipNarrate PC module + *Separated TTS SAPI on PC into it own VoipNarratePC module + *Removed the need of copying the output of SAPI into wav buffers + *Aligned behavior with the generic VoipNarrate module + + DirtySDK (XboxOne) + Build scripts + Added game chat 2 header file directory to include path. Added game chat 2 lib to set of external libs to + be linked with. + + Added game chat 2 lib to set of external libs to be linked with. + + PrivilegeApiXboxOne + Added exception handling around usage of xboxone user object methods. + + UserApiXboxOne + Added exception handling around usage of xboxone user object methods. + + UserListApiXboxOne + Added exception handling around usage of xboxone user object methods. + + VoipHeadset + Added VoipHeadsetTranscribedTextReceivedCbT and VoipHeadsetSetTranscribedTextReceivedCallback(). + + VoipXboxOne + Added _VoipUpdateLocalStatus() in charge of polling VoipHeadsetStatus('talk') to detect if local players + are talking or not. + +Changes/fixes in this release: + DirtySDK (All) + CommUdp + Minor fix to use SockaddrInit instead of ds_memclr to make sure the address family is properly specified, + preventing possible warning in later sockaddr call. + + ConnApi + Reduced the complexity of the implementation of connapi now that we have knowledge of the topologies and + hosts. + + Fixed the initialization of the game / voip host, this needs to be happen before any other initialization + to make sure everything lands in the correct state. + + Replaced usage of ConnApiClientInfoT.uId with uLocalClientId / uRemoteClientId where appropriate. uId is + used for identification purposes, in cases where we are establishing a connection the uLocalClientId and + uRemoteClientId are to be used. + + CryptEcc/CryptNist + Changed the module to split up the DH and DSA states, which allows them to be used independently. + + Changed the implementation to allocate the table on the fly instead of keep it in the state. This work + reduces the sitting memory consumption. + + Changed the initialization of the curve state to use the curve identifier. + + Renamed CryptEcc to CryptNist, CryptNist is more specific to the curves we support in the module. + + CryptHmac + Fixed to work with keys longer than the block size. + + CryptMont + Changed the implementation to return an error if passed an unsupported curve identifier. + + DirtyLib + Changed the NetCritInit functions to return an error if the internal allocation fails. + + DirtyNet + Changed SocketBind to fail if the virtual socket is already bound. + + Improved the thread safety in SocketDestroy by protecting the calls to SocketClose. SocketClose may be + invoked at the same time that the DirtyNet subsystem is being destroyed which we must protect against. + + Replaced the use of uCalllast = 0xFFFFFFFF to the boolean bInCallback to prevent the potential of colliding + with a valid tick value + + DirtyThread + Changed the thread initialization to set the processor to any. + + Hpack + Increased the temporary buffer for header parsing to prevent header value truncation. + + Fixed the encoding of large header values (over 127 bytes) to properly encode the size by offsetting after + the value has been written. + + JsonParse + Fixed an issue in JsonFind where parsing an incomplete object caused by invalid JSON could cause a crash. + + NetGameDist + Fixed compilation errors inside the TIMING_DEBUG defines. + + Platform + Removed unused undefs and defines in this module. + + ProtoHttp + Changed the calling of the custom header callback to pass NULL and 0 to pData and iDataLen in preparation + for these parameters being deprecated. + + ProtoHttp2 + Changed the implementation to validate the frame header and the individual frame types payloads per the + spec. + + Fixed documentation for some internal enumerations and fixed SETTINGS_NUM to use the correct value. + + Enabled huffman coding for HTTP/2 header by default. + + Fixed the content-length header to be all lower case. + + Changed "not enough room" debug print in ProtoHttp2Send use verbosity level 1 so that it is not enabled at + the default logging level. + + ProtoHttpUtil + Removed the call to ProtoHttpGetLocationheader via ProtoHttpGetHeaderValue as the functionality was + deprecated. + + ProtoSSL + Fixed to send of empty renegotiation_info extension in ClientHello; there are a small number of + servers that require this or will fail the connection. + + Fixed to send of empty renegotiation_info extension in ServerHello if the client sent one. + + Changed to add no_renegotiation warning alert response by client if a server attempts to renegotiate. + + Fixed a TLS1.3 issue where receiving a ChangeCipherSpec immediately following a HelloRetryRequest would not + be correctly ignored, because the supported_versions extension in the HRR was not being parsed. + + Changed _RecvCertificate to simplify and clarify the certificate parsing, as well as do a better job + skipping certificates that aren't a part of the chain and might violate some of the other checks which + previously would have resulted in verification failure. + + Removed TLS1.3 draft revision support, and updated all draft RFC links to reference the published final RFC + instead. + + Removed SSLv3 version support (previously disabled by default). + + Removed RC4 cipher suites (previously disabled by default). + + Cleaned up some remaining unused MD2 signature algorithm definitions. + + Reorganized/renamed most functions to provide better and more consistent readability. No functional + change. + + Fixed some out-of-date curve defines that limited the 'crvd' selector to only choosing the first two + curves. + + Added full support for RFC8446 style downgrade detection; added server support for setting random values, + added client support for detecting and aborting a TLS1.2 request being illegally downgraded). + + Added server support for remembering HRR cipher we sent, and making sure it is selected when we send the + following ServerHello. Added client support for remembering HRR cipher we received, and making sure we get + it from the ServerHello, alerting if not. + + Fixed to send an illegal_parameter alert when client parses a supported_versions extension with no versions + we support. + + Changed debug output of connecting hostname to include "using TLSx". + + ProtoStream + Fixed copy of error text to copy only the error text (was also copying leftover data from previous request). + + ProtoWebSocket + Removed the Listen/Accept functions as we do not have plans on adding server functionality at this time. + + Removed ProtoWebSocketConnect2 function as it is not used for anything. Please use ProtoWebSocketConnect + function instead. + + VoipCommon + Enhanced implementation of VoipCommonContro('vcid') to support usage of client id 0. + + VoipConnection + Changed resetting the VOIP_LOCAL_USER_SENDVOICE and VOIP_REMOTE_USER_RECVVOICE to be triggered if we have + not sent or recv a mic packet within the VOIP_PING RATE. + + Enhanced implementation to support scenarios where blaze ends up exercising the solution in such a way + that VoipControl('vcid') gets called with a 0 value. A client id of 0 is now valid and a boolean is now + used to indicate whether the 'vcid' selector was used to set the local user id. + + VoipDef + Removed VOIP_LOCAL_USER_MICON which has been unused since PS2 era. + + Changed VOIP_MAXCONNECT from 30 to 32. + + VoipNarrate + Changed VoipNarrateConfig() to no longer take a VoipNarrateRefT, the supplied configuration is stored + globally and used in all calls to VoipNarrateCreate(). VoipNarrateCreate() will fail if not configured + properly. + + Changed VoipNarrateStatus() to have an additional argument 'iValue' to match the same function signature of + other status functions. + + VoipSpeex + Changed speex codec to use the built-in VoipCodec VAD instead of re-implementing internally. + + VoipTranscribe + Changed VoipTranscribeConfig() to no longer take a VoipTranscribeRefT, the supplied configuration is stored + globally and used by all calls to VoipTranscribeCreate(). VoipTranscribeCreate() will fail if not configured + properly. + + Changed VoipTranscribeSubmit() to no longer take an iUserIndex argument, MLU was implemented above this api. + + Changed VoipTranscribeGet() to no longer take an iUserIndex argument, MLU was implemented above this api. + + Changed VoipTranscribeStatus() to have an additional argument 'iValue' to match the same function signature + of other status functions. + + VoipTunnel + Changed the module to use the VoipPacketHeadT definition from voippacket.h + + DirtySDK (Apple) + ProtoSSLApple + Moved the validation of the peer certificate to its own function and state. The validation of the peer + certificates plus the handshaking causes a performance spike. Moving this into a different state and + function makes it so it gets processed on the next update. + + Removed RC4 cipher suites. + + Fixed a crash when calling ProtoSSLClrCACerts before ProtoSSLShutdown by correctly offsetting the array in + the former function. + + Changed the implementation to redirect our allocation to DirtyMem using a custom allocator. + + DirtySDK (iOS) + NetConnIos + Fixed a strict prototypes build warning by removing prototypes that were not needed. + + DirtySDK (PC) + VoipHeadsetPC + Moved the creation of the voice transcription module until it is needed, this saves memory when not in use. + + Removed 'qual' selector from VoipControl; none of the codecs we support reference it. + + DirtySDK (PS4) + DirtyContextManagerPS4 + Fixed DirtyContextManagerShareNetPool() to properly select the lowest slot ref counted NetContext that has a + context size that is greater or equal to the request iNetPoolSize + + DirtyNetPS4 + Removed the attempted optimization when receiving from the packet queue does not return any data. Given that + the clients and servers don't have a packet rate such that this would be necessary we felt that it is best + to remove it. In observed cases this extra call would result in EAGAIN. + + DirtySessionManagerPS4 + Changed the server driven session to be enabled by default. + + Removed unused parameters from DirtySessionManagerCreate. + + Added multi-threaded protection for global variable _ProtoHttp_State. + + DirtyWebApiPS4 + Changed to deleting the request after sceNpWebApiAbortRequest. This is following Sony guidance in ticket + https://ps4.siedev.net/forums/thread/115651. + + ProtoHttpManager + Compiled out the 'pipe' control call on PS4 because the underlying implementation does not support it. + + ProtoHttpPS4 + Fixed the implementation so that the recv throttling doesn't incorrectly cause the request to finish + prematurely. + + Fixed the header formatting to use the input buffer instead of our header cache to prevent truncation. + + Fixed the parsing of large parsing values by increasing the buffer we used to parse. + + Fixed the request functions to actually send the amount of payload data we sent. + + Refactored the call to sceHttpSendRequest() to optionally be called on its own thread. + + Fixed a truncation error with _ProtoHttpIsPEMFormat() function by changing the return type from a uint8_t to a int32_t. + + VoipHeadsetPS4 + Moved the creation of the voice transcription module until it is needed, this saves memory when not in use. + + Moved the creation of the voice narration module until it is needed, this saves memory when not in use. + + Changed VoipHeadsetStatus('sttm'), MLU is now supported; get VoipSpeechToTextMetricsT via pBuf, user index + is iValue. + + Changed VoipHeadsetStatus('ttsm'), MLU is now supported; get VoipTextToSpeechMetricsT via pBuf, user index + is iValue. + + Fixed the +-pbk to include the shared user + + DirtySDK (Unix) + DirtyNetUnix + Removed the attempted optimization when receiving from the packet queue does not return any data. Given that + the clients and servers don't have a packet rate such that this would be necessary we felt that it is best + to remove it. In observed cases this extra call would result in EAGAIN. + + Changed the implementation to allow 'poll' to be called when not in single-threaded mode. + + Changed implementation of _SocketGetMacAddress() such that additional interfaces can easily be added to the + hardcoded default list of interfaces used for mac addr query attempts. + + NetConnUnix + Fixed the order of operations in shutdown to make sure ProtoSSL is shutdown after its dependents. + + VoipUnix + Fixed VoipActivateLocalUser() not properly forcing re-application of channel config. + + DirtySDK (XboxOne) + ConnApi + Fixed XboxOne attempting to demangle the connection to the dedicated server when the voip topology is set to peer + web. + + Changed the logic for setting up the connection state on XboxOne to always set our gameinfo / voipinfo to be + INIT / Demangled as we never connect to ourselves. + + ProtoMangleXboxOne + Enhanced processing of entries in killlist that are in "listening" state. Because there is no system async + op in progress while in that state, we can immediately proceed with a final clean up as soon as the + association is on the killlist. + + ProtoHttpXboxoOne + Changed the implementation to address the module potentially negatively impacting the performance of the + game thread using it. Refactored the way potentially long blocking MS calls are being offloaded to worker + threads. The new solution is involving a single dedicated thread (same lifetime as request object) to which + we offload as little as possible to minimize potential threading issues. As a result of this refactor, + usage of critical section has significantly been reduced. + + ProtoWebSocketXboxOne + Switched back to the base implementation of ProtoWebSocket on XboxOne. The implementation that is provided on + XboxOne leaves out features that are required for our public API. There are no strict requirements on using + their implementation for WebSockets. + + UserListApiXboxOne + Fixed typo in typdef for struct UserListApiPlatformUserContextT as it was colliding with types in + UserApiXboxOne. + + Changed to be not case sensitive when checking for "retry-after: + + VoipCommon + Changed implementation such that XboxOne is no longer displaying inbound transcribed text in system UI if + game code does not register a display transcribed text callback. + + Changed the implementation such that transcribed text to be displayed is now received with a callback + registered with VoipHeadsetSetTranscribedTextReceivedCallback() instead of a callback registered with + VoipConnectionSetCallbacks(). + + VoipHeadsetXboxOne + Changed the implementation to now use MS game chat 2 instead of windows::xbox::chat. + + Changed VoipHeadsetStatus('sttm') will no longer be able to provide metrics for speech to text, as this + info is hidden within game chat 2. + + Changed VoipHeadsetStatus('ttsm'), MLU is now supported; get VoipTextToSpeechMetricsT via pBuf, user index + is iValue. + + VoipPriv + Removed the specialized VoipUserExT. No longer needd after rebasing VoipHeadsetXboxOne from + windows::xbox::chat to MS game chat 2. DirtySDK XBoxOne voip now uses VoipUserT like all + other platforms. + + VoipSerialization + Removed. No longer needed after rebasing VoipHeadsetXboxOne from windows::xbox::chat to MS game chat 2. + + VoipXboxOne + Fixed the calculation of the length of time the voip thread took to be milliseconds and not microseconds. + + Changed VOIP_THREAD_SLEEP_DURATION from 20 ms to 40 ms to meet MS game chat 2 requirements. + + Removed code for auto muting of bad rep user. This is all handled internally by MS game chat 2 now. + + +EADP Game Services DirtySDK 15.1.4.0.5 - December 20, 2018 + + This SDK release was built using the following compiler packages: + + EA Config 5.07.00 + Framework 7.10.00 + + Android AndroidSDK 26.0.2-2 + AndroidNDK r13b + android_config 4.00.00 + Capilano CapilanoSDK 170608-proxy + capilano_config 2.07.00 + Kettle kettlesdk 5.508.071-proxy + kettle_config 2.09.00 + PlayStation4AudioInEx 5.508.001 + iPhone ios_config 1.09.00 + iphonesdk 10.0-proxy-1 + OSX UnixGCC 0.10.00 + osx_config 1.15.00 + Linux/Unix UnixClang 4.0.1 + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + + *** Important - This release includes full support for VoIP Accessibility on all platforms *** + VoIP Accessibility includes Transcription (Speech to Text or STT) and Narration (Text to Speech or TTS), in order to + fulfill the requirements of the Computer and Video Accessibility Act (CVAA) that will be enforced in the US starting + January 1st, 2019. Previous releases included support of both STT and TTS for the Xbox One and TTS for the PC; this + release adds support for STT on PC and TTS and STT on PS4. + + Also includes feature request from EA accessibility team: a hearing-impaired person requesting transcribed text from + other players also wants to see transcribed text for her/his own speech. VoipDisplayTranscribedTextCallbackT + callback is invoked with iConnId parameter set to -1 when transcribed text is from a local user. + + Integration requires the application to support rendering of STT text to the screen and acquisition of text from the + user for input into TTS. Please see https://developer.ea.com/pages/viewpage.action?pageId=183248884 or email + GS-DirtySock Support for more information. + + *** Important - PUBLIC API IMPACTING - *** + With the addition of full support of PC and PS4 platforms for Accessibility, some API changes have been made to + present a more unified and consistent interface: + + - Changed public STT interface to use a utf8-encoded char string instead of wchar_t string + - Changed synopsis of 'stot' control selector: iValue parameter used to be enable/disable, it is now local + user index; pValue parameter used to be local user index, it is now enable/disable. + - Renamed VoipSetTranscribedTextReceivedCallback() to VoipSetDisplayTranscribedTextCallback() + - Renamed VoipGroupSetTranscribedTextReceivedCallback() to VoipGroupSetDisplayTranscribedTextCallback() + - Renamed VoipTranscribedTextReceivedCallbackT to VoipDisplayTranscribedTextCallbackT + - VoipDisplayTranscribedTextCallbackT callback can now be invoked with iConnId parameter set to -1 when + transcribed text is from a local user + - XoneSynthesizedSpeechCfgT and PCSynthesizedSpeechCfgT have been unified into a single structure with + platform-specific elements and renamed to VoipSynthesizedSpeechCfgT + +New in this release: + DirtySDK (All) + ConnApi + Added usage of the new VoipGroupControl('self') to inform the underlying voipgroup of the local slot + index in the game. + + ProtoStream + Added ProtoStreamOpen2() to allow use of POST requests. + + Added minimum buffer concept for callback; if specified, a callback will never provide less than this + minimum amount of data, unless it is the end of the stream. + + Added cache of HTTP error response body and added 'serr' selector to return it. + + Voip + Added STT/TTS metrics structures, VoipSpeechToTextMetricsT and VoipTextToSpeechMetricsT, which contain + fields used to track usage of the voip accessibility features. + + VoipGroup + Added new VoipGroupControl('self') selector to be used by ConnApi to inform the underlying voipgroup of + the local slot index in the game. + + DirtySDK (PC,PS4) + VoipTranscribe + Added new module to encapsulate cloud-based transcription (Speech to Text) services for the purpose of + transcribing VoIP audio. This module is fully integrated in DirtySDK VoIP and does not need to be accessed + directly. Configuration of this module happens via a Blaze server configuration. This version supports + local user indices but does not currently support multiple local users. + + DirtySDK (PC) + VoipHeadsetPC + Added support for VoipTranscribe module. + + Added VoipHeadsetStatus 'sttm', to get the VoipSpeechToTextMetricsT via the passed in buffer. + + Added VoipHeadsetStatus 'ttsm', to get the VoipTextToSpeechMetricsT via the passed in buffer. + + DirtySDK (PS4) + VoipHeadsetPS4 + Added support for VoipNarrate and VoipTranscribe modules. + + Added call to sceAudioInIsSharedDevice(), used to replace + sceVoiceGetPortAttr(SCE_VOICE_ATTR_AUDIOINPUT_DEVICE_CAMERA), supplied by the new AudioIn_03 library. + + Added silence detection for discontinuous transmission (previously supplied by sceVoice) and transcription. + + Added VoipHeadsetStatus 'sttm', to get the VoipSpeechToTextMetricsT via the passed in buffer. + + Added VoipHeadsetStatus 'ttsm', to get the VoipTextToSpeechMetricsT via the passed in buffer. + + VoipNarrate + Added new module to encapsulate cloud-based narration (Text to Speech) services for the purpose of producing + VoIP audio from text. This module is fully integrated in DirtySDK VoIP and does not need to be accessed + directly. Configuration of this module happens via a Blaze server configuration. This version supports + local user indices but does not currently support multiple local users. + + DirtySDK (XboxOne) + VoipHeadsetXboxOne + Added VoipHeadsetStatus 'sttm', to get the VoipSpeechToTextMetricsT via the passed in buffer. + + Added VoipHeadsetStatus 'ttsm', to get the VoipTextToSpeechMetricsT via the passed in buffer. + +Changes/fixes in this release: + Contrib (PC) + VoipOpus + Changed sampling rate from 8khz to 16khz for improved audio quality, and to match transcription audio input + rate. + + Fixed decoder not accumulating into the output buffer, which broke >2p VoIP. + + DirtySDK (All) + Base64 + Fixed _Base64Encode, which was erroring out if the encoded size plus null terminator would exactly fit the + output buffer size. + + CryptEcc + Changed the definition of the number of iterations to allow teams to override it if they choose. + + NetConn + Fixed the module startup to assign the global ref after the full startup is complete, this makes it so that + 'open' reports correctly. + + NetGameDist + Changed the _ProcessPacketType to prevent setting bRecvMulti on the server. bRecvMulti is only meant for + clients to tell if they are in 2-player peer-to-peer games or server based games. + + ProtoHttp + Fixed to not abort streaming post on header response if the response is successful (in the 200 range). + + ProtoHttp2 + Changed the timer to be updated only when creating a new connection, instead of every time we create a new + request. We don't want to artificially update the timer when we have yet to send data. This could cause us to + get stuck in ST_CONN if we try to create new requests quicker than we can timeout. + + Changed the send behavior to return zero bytes sent if we don't have space but are not sending a zero sized + payload (stream end). This aligns with how other modules handle similar cases. + + ProtoSSL + Fixed an issue processing fragmented handshake packets found when testing TLS1.3 connections to servers + using Facebook's new TLS library ("fizz"). + + Fixed an issue where a private key file passed in without a signature was misidentified as ECDSA due to an + uninitialized variable in _FindPEMCertificateData. This would cause the key file to fail to load and the + subsequent connection request to fail. + + ProtoStream + Changed some debug verbosity levels. + + ProtoUpnp + Fixed the handling in cases that we might send zero bytes in a post. In this situation we need to make calls + to ProtoHttpSend to send the remaining data. + + ProtoWebsocket + Changed to disable "append header is the same" debug spam at default verbosity, matching the behavior of + other protocols. + + Voip + Renamed VoipTranscribedTextReceivedCallbackT to VoipDisplayTranscribedTextCallbackT. + + Renamed VoipSetTranscribedTextReceivedCallback() to VoipSetDisplayTranscribedTextCallback(). + + Changed XoneSynthesizedSpeechCfgT and PCSynthesizedSpeechCfgT to be unified into a single structure with + platform-specific elements and renamed to VoipSynthesizedSpeechCfgT. + + VoipCommon + Changed synopsis of 'stot' control selector: iValue parameter used to be enable/disable, it is now local + user index; pValue parameter used to be local user index, it is now enable/disable. + + Renamed VoipTranscribedTextReceivedCallbackT to VoipDisplayTranscribedTextCallbackT. + + Changed _VoipCommonTextDataCb (callback to handle locally generated transcribed text) to not only send + transcribed text to remote users, but to additionally display it locally if the local user has STT enabled. + + Changed implementation to now enable text transcription locally if any local user is requesting text + transcription from remote users. + + VoipGroup + Changed implementation of _VoipGroupManagerDisplayTranscribedTextCallback() such that it can now surface + up not only transcribed text from remote users, but also locally transcribed text from local users. + + Renamed _VoipGroupManagerTranscribedTextReceivedCallback() to + _VoipGroupManagerDisplayTranscribedTextCallback(). + + Renamed VoipGroupSetTranscribedTextReceivedCallback() to VoipGroupSetDisplayTranscribedTextCallback(). + + VoipHeadset + Changed VoipHeadsetDisplayTranscribedText() to be an xboxone only api because all other platforms + had stubbed implementation for it. Also renamed it from VoipHeadsetDisplayTranscribedText() to + VoipHeadsetDisplayTranscribedTextInSystemUI() to avoid confusion with the new + VoipDisplayTranscribedTextCallbackT callback. + + DirtySDK (PC) + VoipHeadsetPC + Changed sampling rate from 11.025khz to 16khz for improved audio quality, and to match transcription audio + input rate. + + Fixed a crackle and pop issue with PC TTS. + + Renamed VoipHeadsetControl('stot') to VoipHeadsetControl('tran') for enabling local transcription. + + DirtySDK (PS4) + DirtyErrPS4 + Changed to include audio in/out errors. + + Changed audio input path from sceVoice to use sceAudioIn, to gain access to the uncompressed voice audio for + the purpose of transcription. + + VoipHeadsetPs4 + Renamed VoipHeadsetControl('stot') to VoipHeadsetControl('tran') for enabling local transcription. + + Removed deprecated SCE_VOICE_EVENT_AUDIOINPUT_MUTE_STATUS enum. It was never used by Sony. + + DirtySDK (XboxOne) + NetConnXboxOne + Fixed 'xusr' to early return when an error occurs + + ProtoHttpXboxOne + Fixed a memory stomp when terminating our header buffer when the size of the header is larger than that of + the header cache by terminating the source instead of the destination. + + UserApiXboxOne/UserListXboxOne + Fixed the module to correctly handle timeout errors. In CL1332066, we added a new recv error code to inform + users that a timeout had occurred. While doing this there were several modules that were not catching this + case. + + UserApiXboxOne + Fixed the handling in cases that we might send zero bytes in a post. In this situation we need to make calls + to ProtoHttpSend to send the remaining data. + + VoipHeadsetXboxone + Enhanced robustness with new exception handling around the retrieval of the encoder object to be used to + encode locally acquired voice samples. This is added to mitigate the impact of very low-rate prod crashes + reported by NBA. + + Changed the handling of "bystanders". Adding bystanders to the chat session is no longer working with recent + XDK/recoveries. As soon as a "bystander" is added to the chat session all subsequent chat session updates + fail. After reporting the issue to Microsoft, we were told that Windows::Xbox::Chat is completely deprecated, + and that we could stop adding bystanders without any risk of certification failures. + + Changed VoipHeadsetDisplayTranscribedTextInSystemUI() to support both local and remote originators for + transcribed text. + + Renamed VoipHeadsetControl('stot') to VoipHeadsetControl('tran') for enabling local transcription. + +EADP Game Services DirtySDK 15.1.4.0.4 - October 29, 2018 + + This SDK release was built using the following compiler packages: + + EA Config 5.07.00 + Framework 7.10.00 + + Android AndroidSDK 26.0.2-2 + AndroidNDK r13b + android_config 4.00.00 + Capilano CapilanoSDK 170608-proxy + capilano_config 2.07.00 + Kettle kettlesdk 5.508.071-proxy + kettle_config 2.09.00 + iPhone ios_config 1.09.00 + iphonesdk 10.0-proxy-1 + OSX UnixGCC 0.10.00 + osx_config 1.15.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +New in this release: + DirtySDK (All) + ProtoHttp2 + Added support for checking if an idle connection is still active to prevent any unwanted issues if the + server silently goes away. + +Changes/fixes in this release: + DirtySDK (All) + ConnApi + Fixed a bug introduced in the context of GOSREQ-1975 (Refactored the DirtyCast/nonDirtyCast dedicated + server divergent code paths). To align with the DirtyCast implementation, the conn index passed to + VoipGroupConnect() is being decremented by 1 to save the spot unnecessarily used by the voipserver + in the voip sub-system (because there is no voipconnections to the voipserver itself). But the + call to VoipGroupControl('vcid') was not tailored in the same manner. It now is. + + DirtyCert + Fixed deadlock when disconnecting from the redirector and updating at the same time by not waiting on the + module crit when updating. Given that the window that this can occur is rare, we can just try the lock in + the update case to prevent deadlocking in the future. + + DirtyNet + Fixed logic used to decrement pPacketQueue->iPacketTail in SocketPacketQueueAllocUndo(). Upon an underflow + resulting from decrementing the tail when it is 0, the logic proved to only work when + pPacketQueue->iMaxPackets was a power of two. After the fix, the logic works for any value of + pPacketQueue->iMaxPackets. + + ProtoHttpManager + Fixed the update so that we can process idle protohttp refs. ProtoHttp requires that we allow it to do idle + processing to handle connection wide events. + + VoipGroup + Removed an early return in VoipGroupConnStatus and VoipGroupRemoteUserStatus which hid the correct status + information when a suspended connid is queried. + + DirtySDK (PC) + VoipOpus + Fixed the accumulation of the output buffer as required by the voip mixer. Before if we received multiple + voice bundles we would only write one. + + DirtySDK (PS4) + DirtyWebApiPS4 + Fixed a crash if initialization fails by checking for NULL and configuring the thread life correctly. + +EADP Game Services DirtySDK 15.1.4.0.3 - August 24, 2018 + + This SDK release was built using the following compiler packages: + + EA Config 5.06.00 + Framework 7.10.00 + + Android AndroidSDK 26.0.2-2 + AndroidNDK r13b + android_config 4.00.00 + Capilano CapilanoSDK 170608-proxy + capilano_config 2.03.00 + Kettle kettlesdk 5.008.071-proxy + kettle_config 2.06.03 + iPhone ios_config 1.09.00 + iphonesdk 10.0-proxy-1 + OSX UnixGCC 0.10.00 + osx_config 1.15.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +New in this release: + DirtySDK (All) + VoipGroup + Added a selector to get the mute all status from the voipgroup. + + QosClient + Added a new error code QOS_CLIENT_REQUEST_STATUS_ERROR_TIMEOUT_PARTIAL so we can distinguish a qos test + that wasn't successful getting any probes round trip, vs one that was at least partially successful. + +Changes/fixes in this release: + DirtySDK (All) + ConnApi + Fixed a crash if we try to abort demangling on xbox one when we are not currently demangling. + + DirtyNet + Fixed size of internal structure (HostentT::name[]) used during DNS name lookup: increased size from 64 + bytes to 256 bytes to support recent scenarios involving names longer than 64 chars. The maximum DNS name + length is 253 characters + + ProtoHttp2 + Fixed the 31-bit value parsing/decoding that caused incorrect behavior in the module. When decoding the + stream id and window update increments we were masking out the high bit after shifting instead of before, + which caused to get incorrect values. + + QosClient + Fixed a potential issue where resources would not be freed on SocketLookup failure. + + Enhanced error checking, so that received data is at least the size of a probe before processing it. + + VoipCommon + Enhanced error handling around VoipHeadsetCreate() + + VoipGroup + Changed the code to ignore mute all if it has already been done. + + DirtySDK (iPhone/OSX) + ProtoSSLApple + Fixed being stuck in a bad state when an error occurs in setup by cleaning up the secure state in those + instances. + + DirtySDK (PC) + VoipOpus + Changed the default for opus to use the built in voipcodec vad support to help with stuttering. + + DirtySDK (PS4) + NetConnPS4 + Removed the code terminate the UserService as this uninitentially left in. When we still supported + disabling IEAUser, we needed to use the user service to work how our legacy code worked. + + DirtySDK (XboxOne) + ProtoHttpXboxOne + Fixed a crash in the suspend handler by refactoring the code to more careful in how we signal the invalidity + of the xmlhttp requests. + + VoipHeadsetXboxone + Fixed possible crash conditions upon VoipHeadsetDestroy() being called from within VoipHeadsetCreate(). + +EADP Game Services DirtySDK 15.1.4.0.2 - July 12, 2018 + + This SDK release was built using the following compiler packages: + + EA Config 5.06.00 + Framework 7.10.00 + + Android AndroidSDK 26.0.2-2 + AndroidNDK r13b + android_config 4.00.00 + Capilano CapilanoSDK 170608-proxy + capilano_config 2.03.00 + Kettle kettlesdk 5.008.071-proxy + kettle_config 2.06.03 + iPhone ios_config 1.09.00 + iphonesdk 10.0-proxy-1 + OSX UnixGCC 0.10.00 + osx_config 1.15.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +New in this release: + DirtySDK (All) + UserApi + Added a new selector 'rrsp' to disable UserApi from returning First Party raw response. + + DirtySDK (PC) + VoipHeadsetPC + Added Text-to-Speech functionality. You can now use the 'ttos' selector to send text as voice. + +Changes/fixes in this release: + DirtySDK (All) + CommUdp + Fixed the calculation of the packets we can fit in the buffer to account for the encoded subpacket size to + prevent prototunnel rejecting our packets. + + ConnApi + Fixed crashes when creation of the protomangle ref fails on XboxOne by guarding calls to that module. + + NetGameDist + Fixed _SetDataPtrTry to correctly identify uIndexB when we are looking at the server outbound queue. + + ProtoHttp + Fixed failure to invoke the write callbacks with data when we received but have not processed the whole + chunked response. + + ProtoTunnel + Fixed to not rematch on an OUTOFORDER decrypt result, which causes a stream desync and the connection to + fail. + + QosClient + Fixed bug where in some error conditions a protohttp2 stream would be leaked. + + Increased the logging level on protohttp2 used by QoS2.0 to potentially give insight into if the coordinator + is not responding to the client sending results. + + DirtySDK (PS4) + ProtoHttpPs4 + Fixed failure to invoke the write callbacks with data when we received but have not processed the whole + chunked response. + + Fixed no content length downloads to properly calculate the body size when the download is complete. + + DirtySDK (XboxOne) + ProtoHttpXboxOne + Fixed the write callback from not processing due to not correcting assessing if we have read the entire body + by tracking the amount read. + + Fixed the processing of the kill list to allow for killing requests not yet in the in-progress state. + + ProtoMangleXboxOne + Fixed a crash if there is a template load failure by moving the creation of the incoming security + association vector earlier. + + UserApiXboxOne + Fixed the ability to fetch 100 profiles at a time by increasing the json buffer to 2048. + + VoipHeadsetXboxOne + Fixed incorrect usage of the chat session in a scenario where the chat session creation previously failed. + + Fixed incorrect inbound queue management in _VoipHeadsetProcessInboundQueues(). + + Fixed incorrect expection handling in _VoipHeadsetReputationQuery() that might cause a crash. + +EADP Game Services DirtySDK 15.1.4.0.1 - June 1, 2018 + + This SDK release was built using the following compiler packages: + + EA Config 5.06.00 + Framework 7.10.00 + + Android AndroidSDK 26.0.2-2 + AndroidNDK r13b + android_config 4.00.00 + Capilano CapilanoSDK 170608-proxy + capilano_config 2.03.00 + Kettle kettlesdk 5.008.071-proxy + kettle_config 2.06.03 + iPhone ios_config 1.09.00 + iphonesdk 10.0-proxy-1 + OSX UnixGCC 0.10.00 + osx_config 1.15.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +New in this release: + DirtySDK (All) + DirtyNet + Added SocketPacketQueueAllocUndo() to be used to "undo" a recent call to SocketPacketQueueAlloc(). + + Added calls to SocketPacketQueueAllocUndo() in scenarios where the previous call to + SocketPacketQueueAlloc() needs to be undone because the allocated resources ended up not being used, + i.e. when WSARecvFrom() fails and when WSAGetOverlappedResult() fails. + + DirtySDK (PS4) + ProtoHttpPS4 + Added the 'ratr' and 'rats' selectors to the implementations for parity with the stock version of protohttp + + Added ProtoHttpControl('mwrk') selector to control the maximum number of worker threads ProtoHttp will spawn. + Defaults to 0 to that the current behavior will not change unless the user changes this value. + Refactored the call to sceHttpSendRequest() to optionally be called in its own thread. + + DirtySDK (XboxOne) + ProtoHttpXboxOne + Added the 'ratr' and 'rats' selectors to the implementations for parity with the stock version of protohttp + +Changes/fixes in this release: + DirtySDK (All) + ConnApi + Restored the NetGameDistStatus('mplr') selector which will not account for the server index. + + Refactored the ConnApiStatus('gsrv') selector to merge two different implementations. This + will now always return TRUE if the game is server hosted and FALSE otherwise, and if a buffer is + provided it will copy the server's ConnApiClientT there. + + NetConn + Fixed the return code from NetConnCommonStartup when NetConn has already been started. + + NetGameDist + Fixed incorrect "wraparound" logic for the OutBufData queue. This CL is fixing two things with the function + _SetDataPtrTry() which is used to identify where to write data into the respective ring buffers associated + with inbound and outbound queues. + #1 When used with OutBufData, the function was incorrectly initializing uIindexB with "OutBufData.iBeg" + instead of "InpBufData.iBeg+iIOOffset"... thus opening the door for overwriting the "pending entries" + when the queue is close to filling up. + #2 The iPosB variable was being initialized without considering whether uIndexB was pointing to a + "valid" entry or not... potentially resulting in the code executing with a busted iPosB + (specific to empty queue scenarios) + + Refactored NetGameDistMultiSetup to not automatically remove 1 from the index on the clients. This offset + shall be performed by the user. + + ProtoSSL + Fixed a bug introduced in Raven.0 skipping a certificate that is not a part of chain. + + Fixed a bug introduced in Raven.0 where the client did not correctly handle a server downgrade when + formulating the ClientKeyExchange PreMasterSecret, triggering a downgrade detection on the server and + causing the server to fail the connection with a bad record mac alert. + + QosClient + Fixed a bug, when compiling without logging, a timer variable would not be reset resulting in a spam of + callbacks reporting the Qos procedure has timed out. + + Added a way to get module hResults from the callback structure QosCommonProcessedResultsT or via + QosClientStatus('hres'). + + QosCommon + Fixed building qoscommon so that we don't need to keep adding #ifdefs for new platforms to use strtok. + + VoipConnection + Fixed an issue where the local client id was not being treated consistently in _VoipReliableDataUnpackDATA. + This led to Speech-To-Text data not being displayed in some cases. + + VoipTunnel + Fixed the suspend logic so that if we are removing a suspended game at the end of the list we would clear + it out correctly. If we were to remove suspended data at the end and suspend logic hits later at the same + slot, the voiptunnel would incorrectly error because it believes there is already a game suspended there. + + DirtySDK (iPhone/OSX) + ProtoSSLApple + Fixed an issue where we incorrectly don't process the received data when receiving a FIN in the same packet. + How the processing works we can make many read calls off the socket before the data gets returned back to + the caller. In the case that we get data and FIN, we just see this as the peer closing the connection. + Instead of doing it in this way we should always first process any data we received. + + DirtySDK (PC) + DirtyNetWin + Enhanced tracking of pSocket->bRecvInp such that it gets flagged before WSARecvFrom is attempted. The goal + is to eliminate a theoritical race condition identified during code review: bRecvInp is checked in + SocketRecvFrom() which can be invoked from the main thread... and thus that opens the door for a race + condition with the socket recv thread updating that variable too late (after the call to WSARecvFrom(). + + NetConnWin + Fixed the cleanup when socket creation fails during NetConnStartup(). + + DirtySDK (PS4) + ProtoHttpPS4 + Fixed the header parsing for large headers by using the input pointer to the `sceHttpGetAllResponseHeaders`. + In the parse header code we check if we have the entire header to allow us to continue parsing. If the + header is too large, we truncate the headers and the parse header function will always early return, + attempting to wait for more data. + + DirtySDK (XboxOne) + ProtoHttpXboxOne + Fixed overallocating memory when receiving large responses from the server by resetting the offsets when the + entire buffer is read. + + Fixed the logic so that we could kill any requests that we know will no longer receive callbacks. In + conditions such as suspend and disconnects we have observed that no callbacks will be fired which leaves + requests in XMLHTTP_STATE_INPROGRESS. This has the effect of leaving netconn in the ST_CLEANUP state while + waiting for the kill list to be cleared of all requests, which blocks NetConnConnect from connecting to the + first party network. + + ProtoMangleXboxOne + Fixed the crash when template fails to load by failing the creation of the module, it will try to continue + without demangling at the higher levels. + + Removed the concept of secure bypass which is not being used. + + UserApiXboxOne + Fixed a crash when trying to parse the profiles response by ensuring that we received any data. If we didn't + receive any data it could mean that we had something misconfigured or that server just returned an error to + us. We should not have assumed that we would always have data waiting. + +EADP Game Services DirtySDK 15.1.4.0.0 - March 28, 2018 + + This SDK release was built using the following compiler packages: + + EA Config 5.06.00 + Framework 7.10.00 + + Android AndroidSDK 26.0.2-2 + AndroidNDK r13b + android_config 4.00.00 + Capilano CapilanoSDK 170608-proxy + capilano_config 2.03.00 + Kettle kettlesdk 5.008.071-proxy + kettle_config 2.06.03 + iPhone ios_config 1.09.00 + iphonesdk 10.0-proxy-1 + OSX UnixGCC 0.10.00 + osx_config 1.15.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + + *** Important - This release has not been tested with Xbox February XDK and Sony PS4 5.5 SDK *** + Official support for the newer February XDK and PS4 5.5 SDK will come in the next release. + + *** Important - This is the first release of Qos2.0 *** + QosApi has been replaced with QosClient for Qos2.0. E-mail GS-Operations@ea.com to have a custom Qos profile + setup for your title, it specifies which ping sites to use, and what tests it should conducted. + +New in this release: + DirtySDK (All) + CryptBn + Added a debug function to log the big number. + + Added Added CryptBnByteLen to return number of bytes currently used by the big number. + + CryptChaCha + Added ChaCha20-Poly1305 AEAD cipher implementation. + + DirtyCert + Changed redirector endpoint name to gosca18.ea.com + + Changed to add support for using port 44326 for DirtyCert on Xbox One. + + DirtyErr + Added DirtyErrDecodeHResult which can split an hResult value into its component pieces, to better + understand what it represents. + + ProtoSSL + Added TLS1.3 support. Default requested version remains TLS1.2, so TLS1.3 connections are not attempted + without being explicitly enabled. + + Added DigiCert Global Root CA for EA-signed secure public endpoints (used in place of Symantec/VeriSign). + + Added ECDSA support, including private key parsing, cipher suites, server and client certificates, and + signature algorithms for TLS1.2 and 1.3. + + Added support for TLS_CHACHA20_POLY1305_SHA256 (TLS1.3), and TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + and TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (TLS1.2) cipher suites. + + Added support for RSA-PSS client and server certificates. + + Added ability for ProtoSSLControl 'scrt' control selector to pass in full .pem files with signatures not + already stripped. If no signature is present PKCS#1 is assumed, retaining previous functionality. + + Added ProtoSSLStat('ladd'), selector. Is used to return a cached local client address, that was used + for the last connection. The address is copied into pBuffer, which must be at least sizeof(struct sockaddr) + + QosClient + Added the new module QosClient, a part of the Qos2.0 system, it replaces all functionality of previous QosApi. + Qos2.0 manages the whole Qos process from start to finish, this management had been done by BlazeSDK in the + past. The basic usage of the QosClient module would be; call QosClientCreate() to initialize, regularly + call QosClientUpdate(), call QosClientStart() and wait for its callback with results. Look at the in code + documentation for additional details. + + DirtySDK (XboxOne) + UserListApiXboxOne + Added support for 409 (Rate Limited) response code. + +Changes/fixes in this release: + DirtySDK (All) + ConnApi + Fixed size of dynamically allocated module ref. An unused client entry was always unnecessarily allocated + at the very end of the struture. + + Removed the concept of the GameServer in ConnApi. This was only used for DirtyCast servers, which will now + be located at index 0 of the client list the same way it is for nonDirtyCast servers. + + Removed 'mplr' control selector, which was deemed unneeded. + + Removed the XBox One 'phxc' control selector, and replaced it with a generic 'gsrv' selector. This indicates + whether the host is a dedicated server or a console. + + Refactored ConnApiControl('gsrv') usage for the new functionality. + + Removed ConnApiControl('gsv2') . + + Removed the gameflags and associated parameter in ConnApiConnect, this is leftover from Xenon. + + CryptBn + Changed the CryptBnInverseMod function to be implemented using binary extended gcd. This new algorithm is + much faster than extended euclidean. The new algorithm is explained in the Handbook of Applied Cryptography + Chapter 14.4.3 (14.61). + + Changed the CryptBnMod algorithm to skip trying to reduce or left shift until we have at least incremented + the number once. In our profiles we noticed that there was a lot of wasted time in the reduction checking + when the result was still zero. + + CryptEcc + Optimized the point calculations in _CryptEccPointCalculate by multiplying over the modulus. Doing this + makes it so the final redunction happens on a smaller number. + + Fixed the ECDSA sign and validation when the hash's bitsize is larger than the bitsize of the curve. Based + on the algorithm, when the bitsize of the hash is larger than the order of the curve the hash shall be + truncated. + + Changed the ECDSA signing to be done given the new private key parameter. The private key used previously + was the wrong one and caused the validation to fail on the peer's end. + + DirtyCert + Addressed a TODO to close the connection instead of abort when the work is complete. + + DirtyErr + Changed the parameters to DirtyErrGetHResult(), so the sizes of the fields provided more accurately + represent the size of what is internally stored in the hResult. + + DirtyNetUnix + Added the missing thread termination signal + + JsonFormat + Addressed a TODO to use ds_timetostr instead of implementing it inline. + + NetGameDist + Improved readability of code by renaming some variables. + + NetGameDistServ + Fixed some coding standard issues in the module's InputQueryMulti and InputLocalMulti functions. + + ProtoHttp2 + Changed the frame size clamp macro to call DS_CLAMP. + + Removed half-implemented proxy support until a time when we find a h2 proxy that supports CONNECT. + + ProtoTunnel + Fixed a bug where a packet that could not be decrypted and had a negative stream offset could cause the + tunnel connection to be reset, breaking the stream. This fix includes changes to clarify behavior that + was previously difficult to ascertain without a very close inspection of the code: + + ProtoSSL + Changed to disable RC4-MD5 cipher suite and SSLv3 by default. + + Changed to enable ECDHE cipher suites by default. + + Changed the certificate loading to use Base64Decode2. The implementation was assuming that Base64Decode + returned the amount of data needed in the output but in fact it just returns TRUE or FALSE. The + Base64Decode2 function returns the result that it intended to get. + + Changed cipher preference order to be more in-line with current practices. + + Changed signature schemes/algorithms preference order to be more like boringssl. + + Changed execution of TLS1.3 handshake key generation in RecvServerHello, and signature verification in + RecvServerKeyExchange and RecvCertificateVerify to be async (blocking in smaller increments). + + Changes to simplify certificate parsing and make it easier to read and follow, with a fix and two minor + functional improvements; + - fixed issue observed from inspection of skipping a cert that is not part of the chain not working + for tls1.3, due to the extensions field not being skipped + - moved validation bypass to be immediately after certificate parsing, before date validation + - moved leaf cert caching and cached cert bypass into block specific to parsing the leaf certificate, + simplifying main loop + + Changed signature scheme and cipher selection to account for certificate signing algorithm, if a + certificate is available. + + Changed to allocate certificate and private key buffers internally when using 'scrt' and 'skey' control + selectors, meaning these buffers no longer need to be persisted by the caller. + + Changed signature verify buffer from 2048 bytes to SSL_SIG_MAX. + + Changed signature verify failure alert from bad_record_mac to decrypt_error, as OpenSSL does. + + Fixed ECDHE/SHA1 ciphers for TLS1.0/TLS1.1, and enabled them for those versions. + + Fixed an issue with AES decrypt when a padding failure was detected, returning an immediate error instead + of resulting in a bad_record_mac alert as intended. + + Fixed an issue in _ProtoSSLGenerateSecret() where the size of the PreMasterKey buffer (48) was being used + to build the master secret instead of the key length, which is not always 48. + + Fixed a bug in RecvCertificateVerify in TLS1.3, which was dependent on the ECC context being left + initialized with the proper curve by RecvCertificate. + + Fixed to not send signature_algorithms extension for connection attempts with a TLS version less than 1.2. + + Fixed 'rsao' operator to cover new async crypto states, renamed 'crpt', and marked 'rsao' for deprecation. + + Fixed a server issue processing the ChangeCipherSpec message. + + Fixed a server bug from Raven-pr2 where the signature_algorithms extension would be sent in the ServerHello + by a TLS1.2 or earlier server, which is not legal. + + Fixed a server bug that broke resume flow in tls1.2 and prior when tls1.3 server hello session echo was + implemented. + + Fixed the server to select cipher based on session resume cipher if available, and to not attempt resume if + the ssl version or cipher of the previous session don't match those of the current session. + + Fixed a server bug in ProtoSSLUpdateSendServerKeyExchange(), which was leaving bRSAContextInitialized TRUE. + This would cause a subsequent client certificate verify operation to fail. + + Fixed a server bug when receiving a tls1.3 ClientHello but negotiating a tls1.2 or prior connection with an + ECDHE cipher; the key from the key_share extension would be used when sending the server key exchange, + causing the connection to fail. + + Fixed server bug when verifying an ecdsa client certificate; the state was left initialized, which broke + subsequent encryption of Finished message. + + Fixed SendCertificateRequest to transition to sending Certificate message when TLS1.3 is used. + + Fixed RecvCertificateRequest to choose signature scheme in concert with certificate signing algorithm, if a + certificate is available. + + Fixed SendFinished to transition to RECV_HELLO state if TLS1.3+server+client cert flow. + + Fixed SendServerKeyExchange and RecvCertificateVerify for TLS1.1+server+rsa|ecdsa. + + Fixed RecvCertificate to transition to RecvFinished if a TLS1.3 server receives an empty Certificate + message, and the client cert level is not two (client cert required). + + Fixed SendCertificate to transition to SendFinished if a TLS1.3 client sends an empty Certificate message, + skipping CertificateVerify. + + ProtoPing + Moved this module to the LegacyDirtySDK. + + ProtoPingManager + Moved this module to the LegacyDirtySDK. + + ProtoUdp + Moved this module to the LegacyDirtySDK. + + ProtoUpnp + Changed the implementation to switch from using ProtoUdp to DirtyNet for UDP communication. + + QosApi + Moved this module to the LegacyDirtySDK. + + VoipGroup + Changed the VoipGroupManager to contain a variable length array of voipgroups. In multihub mode we require + more voipgroups due to the number of concurrent blazehubs running. + + VoipTunnel + Refactored indexes for voip clients lists were offset by -1 so that the first console user in the list + would appear at index 0 of the voip client list. This helps to not waist one of the maximum 32 voip slots. + + XmlFormat + Removed XmlValidate debug function that was never implemented. This fixes an outstanding TODO we had to + implement this functionality. + + DirtySDK (Android) + CryptRand + Changed the Android implementation to read from /dev/urandom. This addresses an outstanding TODO we had to + add a native implementation for this platform. + + DirtySDK (Linux) + DirtyNetUnix + Fixed the idle procs not being called when running in singlethreaded mode. + + DirtySDK (macOS/iOS) + NetConnIos + Switched the implementation to use the non-deprecated Base64Decode2. + + ProtoSSLApple + Enabled support for ECDSA and CHACHA20-POLY1305 ciphers. + + DirtySDK (PC) + DirtyNetWin + Changed the implementation of 'poll' to fire the socket callback when we have data waiting. The previous + implementation would just notify that is a socket is waiting but would need to wait for the socket idle + thread for the socket callback to be invoked. + + DirtySDK (PS4) + DirtyLibPS4 + Removed support for singlethreaded mode on PS4, this isn't a supported configuration. The implementation was + taken from unix when it was created. + + ProtoHttpPS4 + Fixed a write callback bug when all data is available immediately, in which case the write callback + completes the transition before the data can be read. + + VoipHeadsetPS4 + Fixed size of dynamically allocated module ref. An unused client entry was always unnecessarily allocated + at the very end of the struture. + + DirtySDK (Unix) + NetConnUnix + Fixed bug (opt build only) inadvertently introduced in Raven-pr2. Body of _NetConnUpdateConnStatus() was + incorrectly made "logging only" by conditionnally compiling it in with DIRTYCODE_LOGGING but that broke + the functionality because the line "pRef->uConnStatus = uNewConnStatus" is not only for logging purposes + but is core functionality for internal state transitions. + + VoipUnix + Changed voipunix to assume single-threaded usage: eliminates run-time threading issues (malformed + prototunnel packets) resulting from voipunix being used from the dirtycast stress client which implies + disabled critical sections. + + DirtySDK (XboxOne) + ProtoHttpXboxOne + Fixed the ProtoHttpAbort behavior on XBox One to be similar to other platforms. + + Fixed a write callback bug when all data is available immediately, in which case the write callback + completes the transition before the data can be read. + + ProtoMangleXboxOne + Fixed size of dynamically allocated module ref. An unused client entry was always unnecessarily allocated + at the very end of the struture. + + VoipXboxOne + Fixed copy and paste error, the code was set to set the priority and not the affinity. + +EADP Game Services DirtySDK 15.1.4.0.0-pr2 - February 5, 2018 + + This SDK release was built using the following compiler packages: + + EA Config 5.06.00 + Framework 7.10.00 + + Android androidsdk 24.4.1-1-2 + androidndk r13b + android_config 4.00.00 + Capilano CapilanoSDK 170608-proxy + capilano_config 2.03.00 + Kettle kettlesdk 5.008.051-proxy + kettle_config 2.06.02 + iPhone ios_config 1.09.00 + iphonesdk 10.0-proxy-1 + OSX UnixGCC 0.10.00 + osx_config 1.15.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +New in this release: + DirtySDK (All) + ProtoHttpServ + Added ability to specify ALPN string for ProtoSSL. + + ProtoSSL + Added support for rsassa-pss signed certificates. + + Added support for draft22 and draft23 versions of TLS1.3. + + Added client support for TLS1.3 session resumption. + + Added safe reading for handshake receive functions and extension parsing. + + Added client certificate support for TLS1.3. + + QosApi + Added QosApiControl('stwi') - set the amount of ms before a stall is counted, if a stall is detected test + timeout will be extended (default 500ms). + + UserListApiPS4 + Added order directions for list queries + +Changes/fixes in this release: + DirtySDK (All) + Build + Fixed the build scripts to correctly set the optionset for C++ files when building as a DLL. + + ConnApi + Removed the empty ConnApiMigratePlatformHost function. + + DirtyNet + Changed the implementation of the map address functions to return only -1 in error as negative numbers are + expect for real (non-virtual) IPv4 addresses. We do not want to return error when remapping an IPv4 address. + + Changed the implementation of the remap address function to handle the case an IPv4 address was tried to be + remapped. + + Fixed SockaddrInSetPort to not generate constant value truncation warning on Windows builds. + + Hpack + Fixed crash in _HpackDecodeLiteralField if the indexed entry decode failed by stopping further processing. + + Fixed crash in _HpackHuffmanTreeDecode if the huffman code lookup in the tree failed. + + NetGameDist + Fixed parsing of the stat packet to prevent reading past the end of the buffer. The module reads and writes + to the stat packet based on the number of players. On the client this is a fixed value, while on the server + this is adjusted based on when players join and leave the game. Due to a mismatch between the both sides, + the server will write less than the client expects causing it to potentially read past the end of the + buffer. + + Protobuf + Fixed reading and writing of non-packed repeated fields. When this was originally implemented, how I + understood the encoding documentation is that with proto3 all repeated fields were packed, which was + incorrect. How it actually works is that the primitive types (varint, 32-bit and 64-bit) are packed while + length delimited types are not. Due to this I needed to make a change in the APIs so it can account for the + behavior change in the implementation. + + Refactored the functions dealing with reading packed repeated fields to be a bit more elegant to use. + + Removed the output sizes for the fixed type functions (non-repeated) as this is implicit based on the + function. + + Removed the unnecessary fixed writing functions at align with the reading size. + + Refactored the error checking based on the expectations for each fixed function. + + ProtoHttpUtil + Changed ProtoHttpUrlEncode functions to return the append point rather than always returning zero. Also + changed to mirror C99 vsnprintf() overflow semantics. + + ProtoSSL + Fixed crash when parsing ASN.1 binary objects by preventing a negative size when zero sized data is parsed + from the header. + + Fixed a memory leak when CryptRSAInit fails when verifying the signature and the context is heap allocated. + + Removed support for md2-signed certificates. + + Fixed a bug in sending of TLS1.3 HelloRetryRequest by server. + + Fixed a bug receiving 0/n record split records introduced in Raven-pr1. + + Fixed SendCertificateVerify to work with PSS encoded signatures when using TLS1.2. PSS signature support + was added along with TLS1.3 in Raven-pr1, which exposed the lack of support. + + Fixed to increase ClientHello body format buffer to 2k to accomodate large session tickets in TLS1.3 + resume flow. + + Fixed to prevent ProtoSSL running as a server using DirtyCert. + + Fixed two bugs introduced in DirtySDK 13.3.1.6.0 when code to check AES padding was added. First, in SSLv3 + the block cipher padding value is not specified and therefore should not be checked. This would likely + break any SSLv3 connection negotiated with AES. Second, the padding check itself was biased one character + forward, meaning it missed checking the very first padding byte, instead comparing the last byte (the + length) to itself. + + Fixed to support the full 64 bits of sequence number (previously, only 32 bits were supported). + + QosApi + Changed http and probing timeouts, so they are treated separately and have their own error codes. + + Fixed issue when the client stalls, it was possible to timeout without having had a chance to do the + probing. QosApi will detect client side stalls and will increase the timeout to compensate. + + Changed QosApi to reject latency packets that are well beyond expected ranges, 20 seconds. + + DirtySDK (Apple) + ProtoSSLApple + Changed the code to cleanup the secure state when we detect that the connection was closed. + + DirtySDK (XboxOne) + UserApiXboxOne + Fixed a crash due to the UserApi ref being created before users are added to NetConn by creating + ProtoHttp refs for all possible users. + + VoipHeadsetXboxOne + Changed the LoadLibraryEx input to be wrapped with a TEXT macro to ensure it works on non-UNICODE + environments. + +EADP Game Services DirtySDK 15.1.4.0.0-pr1 - December 7, 2017 + + This SDK release was built using the following compiler packages: + + EA Config 5.06.00 + Framework 7.08.02 + + Android androidsdk 24.4.1-1-2 + androidndk r13b + android_config 4.00.00 + Capilano CapilanoSDK 170608-proxy + capilano_config 2.03.00 + Kettle kettlesdk 5.008.051-proxy + kettle_config 2.06.02 + iPhone ios_config 1.09.00 + iphonesdk 10.0-proxy-1 + OSX UnixGCC 0.10.00 + osx_config 1.15.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + + *** Important - First DirtySDK release with Speech-To-Text (STT) and Text-To-Speech (TTS) support *** + Support available for XboxOne only. PS4 and PC not yet supported + + *** Important - PS4 Random module required *** + With this version loading of the PS4 Random module is required + + *** Important - Public API changes for NetConnXboxOne *** + Removed support for -nologin parameter with NetConnStartup() and NetConnConnect(). + + *** Important - Public API changes for NetGameDistServ *** + Removed the notion of a "started" game as it proved to be completely broken. NetGameDistServStartGame() and + NetGameDistServStopGame() were removed. When a game is started or stopped (like during a blaze match replay), the + game code running on the client is in charge of exercising the netgamedist flow control. + + *** Important - Public API changes for Voip *** + Removed for good 'umic' and 'uspk' control selectors that had been deprecated for a long time already. + +New in this release: + DirtySDK (All) + Base64 + Added support for base64 url string encoding and decoding per RFC 4648 Section 5. Added new + internal functions to do the encoding / decoding to be shared between the various functions. + + Build + Added a target for building full html docs. + + CommUdpUtil + Added as a new file where code shared between commupd and dirtycast is located. Contains new util + functions with explicit names that improve readability and hide bit/byte level inspection in + commudp packets. + + CryptBn + Added functions to left and right shift by more than 1 bit at a time. Defined a macro for right shift by 1 + to mimic how left shift works. + + CryptEcc + Added a function to the module to validate if a point is on the curve. + + Added support for ECDSA signing and verification which can be used instead of RSA for those purposes. + + CryptRSA + Added support for Chinese Remainder Theorem (CRT) algorithm to speed up RSA operations that use the private + key. Since the data and sate used for the different RSA computations are different, I introduced a union + that can represent the data we are working with. + + DirtyNet + Added a minimum rate limit of 1460 bytes/sec to the SocketRate API. This also fixes the issue that + setting a rate lower than this will result in no data going through. + + Added support for IPv6 address parsing from text. + + DirtyThread + Added support for condition variables via the new dirtythread module which is powered by EAThread. + Added support for threads via the new dirtythread module which is powered by EAThread. + + NetGameDistServ + Added a call to _NetGameDistServFlowControl() in NetGameDistServAddClient() such that a player + joining results in DirtyCast announcing "I no longer receive" until the joiner explicitly confirms + "I can receive" with a call to NetGameDistControl('lrcv'). + + NetGameLink + Added the number of naks sent to peer from us to the NetGameLinkStatT structure. + + Moved 'slen' and 'sque' to be a status selector. We are keeping the control selector version of + these but they are now consider deprecated. + + Platform + Added include to to provide access to wchar_t + + Protobuf + Added a reader and writer for the protobuf (version 3) wire format. + + ProtoHttp + Added secure proxy support using HTTP CONNECT. + + Added global proxy setting via new 'gpxy' ProtoHttpControl selector. + + Added an additional request API to specify custom and receive header callbacks, which take priority over the + global callbacks. + + ProtoHttpServ + Added a listen function to allow users to specify their own TCP backlog settings. + + ProtoSSL + Added TLS1.3 protocol support, implementing draft-18 through draft-21 versions at compile-time (defaulting + to draft-18). TLS1.2 remains the default client-requested version. + + Added support for sha512 handshake hash. + + Added debug output of Master Secret in a form suitable for Wireshark decryption via (Pre)-Master Secret log + file. + + ProtoWebSocket + Added message sending (ProtoWebSocketSendMessage) and receiving (ProtoWebSocketRecvMessage). Already + existing Send/Recv functions use a stream-like metaphor. + + QosApi + Added QosApiControl('ncrt') - passed to ProtoHttpControl, if TRUE will proceed even if SSL cert errors + are detected (defaults FALSE), for debug purposes. + + Added QosApiStatus('ncrt') - get the 'ncrt' setting, if TRUE we will ignore SSL errors for setup + communication. + + Added QosApiControl('soli') - passed down to the socket as a SO_LINGER option if its >= 0, a 0 value + will abort the socket on disconnection with no TIME_WAIT state (ideal for stress testing). + + Added QosApiStatus('soli') - get the value passed to ProtoHttp 'soli'. + + Voip + Added VoipSetTranscribedTextReceivedCallback() for voipgroup layer to register a callback used by + lower-level voip to notify about inbound transcribed text over voip connections. + + VoipConnection + Added a per-connection outbound sequence number instead of the global one, needed to support a + scenario where transcribed text is not sent on ALL connections. + + Added a NULL check when setting bPeerIsAcking as based on code before it pConnection might be NULL. + + VoipDef + Added XoneSynthesizedSpeechCfgT to be used with VoipControl('voic'). + + Added VoipTranscribedTextReceivedCallbackT which is the prototype of the callback registered with + VoipSetTranscribedTextReceivedCallback() and VoipGroupSetTranscribedTextReceivedCallback(). + + VoipGroup + Added VoipGroupSetTranscribedTextReceivedCallback() for game code to register a callback used by + DS to notify about inbound transcribed text over voip connections. + + VoipPacket + Added new VOIP_PACKET_STATUS_FLAG_STT, flagging that at least one user on the originating side as requested + transcribed text to be sent. + + Added aRemoteClientId field to ReliableDataInfoT. Required to uniquely identify who the reliable data is + for. + + VoipTunnel + Added new events for adding and deleting games from the VoipTunnel. + + VoipHeadset + Added VoipHeadsetDisplayTranscribedText() to display transcribed text in system overlay. + + DirtySDK (Apple) + DirtyErrUnix + Added the errors from Apple's Security.framework headers SecBase.h and SecureTransport.h + + ProtoSSLApple + Added an implementation of ProtoSSL that is driven by Apple's SecureTransport library. + + DirtySDK (PC) + DirtyNetWin + Added SocketControl('soli') - set SO_LINGER On the specified socket, iData1 is timeout in seconds. + + Added support for IPv4 sockets, required for Windows XP compatibility. Use SocketControl('afam') control + selector to set global address family to use for sockets. + + VoipAux + Added support for the Opus VoIP codec. This codec uses the default settings and 8kHz sampling rate. + + VoipHeadsetPc + Added support for variable length frames for PC codec's which is needed to add Opus support. To allow the + codec to choose if it can support variable length frames, have the headset status fallthrough down to the + codec. Due to the current codecs not supporting variable length frames, I put the unhandled logging message + at a higher verbosity to prevent spamming. Changed the voice data callback to only validate the size of the + frames when not using variable length frames. Changed the calculation of sub-packet size to work for both + variable and fixed frames. + + DirtySDK (PS4) + DirtyNetPS4 + Added support for SO_LINGER socket option via the 'soli' control like on PC/Unix + + NetConnPS4 + Added NetConnStatus('locl') and NetConnStatus('locn') API to fetch locale. + + Added support for -delaySEMD in NetConnStartup to ignore the state of SystemEventMessageDispatcher. + + ProtoHttpPS4 + Added rate limiting. + + DirtySDK (Unix) + DirtyNetUnix + Added SocketControl('soli') - set SO_LINGER On the specified socket, iData1 is timeout in seconds. + + VoipUnix + Added as a new file. Not real voice. Artificial voip data injection on voip connections. Injected + sub-packet rate and sub-packet size are controllable via voip control selectors ('psiz', 'prat'). + + DirtySDK (XboxOne) + UserApiXboxOne + Added support for retrieval of the TTS/STT settings that are a part of the profile data. + + Added Accessibility field to UserApiProfileT. + + VoipHeadsetXboxOne + Added support for leveraging new Text-to-speech (TTS) and Speech-to-text (STT) features in XboxOne XDK. + * Added 'ttos' control selector to be used by the game code to feed in text to be synthesized (from + a speech-impaired user). + * Added 'stot' control selector to be used by the game code to enable local generation of transcribed + text to be sent reliably to all remote peers that requested it (i.e. hearing-impaired user). + * Added 'voic' control selector with platform-specific params used for identifying "voice" used when + producing synthesized speech. + + VoipSerializationXboxOne + Added "game display name" property to the remote user object because we need to feed that into. + Windows::Xbox::UI::Accessibility::SendSpeechToTextString(). So that information is now part of the + user serialization. + +API Breaking Changes in this release: + DirtySDK (All) + Build + Removed support for non-namespaced include imports, you must use the full path. + For example: versus + + CommTcp + Removed CommTCP for good. + + ConnApi + Renamed 'phcc' selector (PC Host Capilano Client) to 'phxc' (Pc Host Xboxone Client). ('phxc' used to + stand for "PC Host Xenon Client" but it was free now that xbox360 is no longer supported.) + + Removed the option of using ConnApi as the owner of voip. Calling VoipStartup/VoipShutdown is now ALWAYS + required before using ConnApi with voip-enabled topologies. + + DirtyLang + Removed LOBBYAPI_LANGUAGE_TEGULU, which was replaced with LOBBYAPI_LANGUAGE_TELUGU long time ago. + + Removed LOBBYAPI_LANGUAGE_UIGUR, which was replaced with LOBBYAPI_LANGUAGE_UIGHUR long time ago. + + Removed LOBBYAPI_LANGUAGE_WELCH, which was replaced with LOBBYAPI_LANGUAGE_WELSH long time ago. + + Removed LOBBYAPI_COUNTRY_SLOVAKIA_, which was replaced with LOBBYAPI_COUNTRY_SLOVAKIA long time ago. + + Removed LOBBYAPI_COUNTRY_ST_HELENA, which was replaced with + LOBBYAPI_COUNTRY_ST_HELENA_ASCENSION_AND_TRISTAN_DA_CUNHA long time ago. + + NetGameDistServ + Removed the notion of a "started" game as it proved to be completely broken. + NetGameDistServStartGame()/NetGameDistServStopGame() were removed. When a game is started or stopped + (like during a blaze match replay), the game code running on the client is in charge of exercising the + netgamedist flow control. + + Updated the stats structure to include more useful information and trimming the sizes down. There was no + need to include ping due to the fact that it can be derived from latency if needed. Added the send queue + length to allow detection when the queue is filling up at the lowest level. To allow us to get this I added + a fallthrough from NetGameDistStatus to NetGameLinkStatus. Added the packet loss and nak information as more + indicators to problems on the client->server connection. + + NetGameLink + Renamed NETGAME_QOS_DURATION_MIN to NETGAMELINK_QOS_DURATION_MIN and moved it from public domain to private + domain. + + Renamed NETGAME_QOS_DURATION_MAX to NETGAMELINK_QOS_DURATION_MAX and moved it from public domain to private + domain. + + Renamed NETGAME_QOS_INTERVAL_MIN to NETGAMELINK_QOS_INTERVAL_MIN and moved it from public domain to private + domain. + + Renamed NETGAME_QOS_PACKETSIZE_MIN to NETGAMELINK_QOS_PACKETSIZE_MIN and moved it from public domain to + private domain. + + Changed the 'mwid' to a status from a control based on a v8 TODO we still had outstanding. + + Voip + Removed for good 'umic' and 'uspk' control selectors that had been deprecated for a long time already. + + Removed VoipDisconnect() and renamed VoipDisconnect2() to VoipDisconnect(). + + Removed the uAddress parameter from the VoipDisconnect() synopsis. + + Removed VoipSpeaker and VoipMicrophone from the public interface. This API can be misused by customers and + given that it used to be the way to deal with muting we want to remove it to prevent incorrect use. + + VoipGroup + Removed VoipGroupConnect() and renamed VoipGroupConnect2() to VoipGroupConnect(). + + Removed VoipGroupResume() and renamed VoipGroupResume2() to VoipGroupResume(). + + DirtySDK (XboxOne) + NetConnXboxOne + Removed support for -nologin parameter with NetConnStartup() and NetConnConnect(). -nologin used to be + exercised by FIFA tools. Instead of using -nologin, tools can just call NetConnStartup() and not + call NetConnConnect(). Implicitly that means tools do not need to wait for netconn to reach +onl state + before doing generic networking activity. + +Changes/fixes in this release: + DirtySDK (All) + Build + Changed the documentation generation to use the headerfiles fileset which is updated to include the correct + files across platform. This allows us to reduce the complexity of our doxygen configuration generation as + it now works similarly to the sourcefiles fileset. + + Fixed hack that gets a property that just equates to ${config-system} with just ${config-system}. + + Changed the build scripts to explicitly build against the newest C standard (c11). We currently are not + supporting the older standards and don't want to assume a default. This fixes some issues with how some + 1st Party APIs are defined. + + Common + Removed the platform specific zlib functionality, instead we call into DirtySDK which implements it already + for all platforms. + + ConnApi + Removed parameter validation from ConnApiControl('sqos') and ConnApiControl('lqos') to eliminate + code duplication with equivalent validation made in NetGameLinkControl('sqos') and + NetGameLinkControl('lqos'). + + Removed the ProtoUpnp code from ConnApi, which is already handled in NetConn. The ConnApi module would be + doing the UPnP operations too late for it to be of value as we want this to be available early so it can be + updated in the user's information. + + Refactored the virtualisation of the VoIP ports to the module that owns the VoIP connection. In a dedicated + server scenario, ConnApi does not establish any VoIP connection as this is handled by the VoipTunnel owned + by the ConnApiVoipManager. + + Refactored ConnApiRemoveClient to validate the index passed in to ensure that it is the valid range. + + Removing code relating to increasing our connection timeout required to create a secure association used for + legacy failover mode (which was removed). + + CommUdp + Enhanced internal implementation with major cosmetic fixes to re-align with coding standard. No functional + impact. + + Removed code duplication between commudp and voipservercomm by moving shared code to a new file called + CommUDPUtil. + + CryptBn + Changed the right shift operation to clear the memory when shrinking the width to match what we do in other + places. + + CryptEcc + Changed the output of the functions CryptEccPublic, CryptEccSecret and CryptEccSign to be optional. + + CryptRSA + Changed the internal exponentiation function so it can take in different data for different needs. + + Refactored the RSA exponentiation code to use a sliding window algorithm as an optimization. + + DirtyAddr + Changed DIRTYADDR_MACHINEADDR_MAXLEN from 127 to 372 for xboxone specifically to fix a rare issue demonstrated + in production where the SecureDeviceAddr blob would be clamped when stored in a DirtyAddrT because too large. + After discussing with MS, 372 was identified as the right size to avoid this kind of issue. + (See comment in the code for more details) + + DirtyCert + Changed the 'prld' to use the correct variable when changing a value based on a control. It was using iControl + which tells us which control we are working on instead of iValue. + + DirtyJpg + Fixed an unusual case where the huffman decoder was reading past the end of the input file. + + DirtyLib + Removed some erroneous extra argument provided to NetPrintf. + + Changed the array printing code to add the opening brace on its own log line. This allows the array to be + copied more easily from the log files. + + Changed NetPrintfVerboseCode to only call ds_vsnprintf when the verbosity is over the check level. + + Removed unused defines from the Gen3 era. + + Refactored the NetCrit system to be driven by EAThread instead of being implemented in place. + + DirtyNames + Removed unneeded extern for function declarations. + + DirtyNet + Changed to refactor address conversion to text and address printing, moving some code from plat-str to + dirtynet, and consolidated and rearranged. Also updated for coding standards, and reordered address + functions to make more sense and align with header. + + Cleaned up the ambiguity about what an INVALID_SOCKET descriptor means. Protect operations that + require a valid socket descriptor or that valid only when the socket has not been added to the kill + list. When shutting down a socket we want to make sure that we protect the socket for the short time + before it is actually released. + + Changed the map translate print to level 3 verbosity based on how often we print this. + + Fixed the code so we have the tick when we finish the receive into allocated packet queue entries. When + looking at the time between adding an entry to the packet queue and removing we were noticing that packets + off the physical socket were reporting misleading times. + + MurmurHash3 + Changed the code to use the EABase little endian define. The current code was not correctly applying the + little endian logic for all the support systems. + + NetConnCommon + Moved implementation of netconn external cleanup mechanism to netconncommon to eliminate code duplication + in netconnxboxone and netconnps4. + _NetConnAddToExternalCleanupList() becomes NetConnCommonAddToExternalCleanupList(). + _NetConnProcessExternalCleanupList() becomes NetConnCommonProcessExternalCleanupList(). + + Moved iRefCount from platform specific implementations to NetConnCommonStartup. Moved the associated printf + on PC and Unix for consistency in the logging. + + Fixed an issue with NetConnStartup, NetConnShutdown and DirtyCert from the recent merging on NetConnCommon + into all platforms. + + NetGameDist + Enhanced internal implementation with major cosmetic fixes to re-align with coding standard. No functional + impact. + + Changed implementation of NetGameDistInputLocalMulti() to improve readability without impacting + functionality: rewrote a very complex if statement in a more explicit manner. + + Removed the unused variable iStrmMaxPktSize, leftover from when dist owned the streams. + + Moved the status selectors to the NetGameDistStatus function from NetGameDistControl to align with the rest + of DirtySDK + + NetGameDistServ + Fixed flow control handling when Join-in-Progress is exercised. + + Fixed usage of _NetGameDistServSanityCheckIn(). It is meant to be used after a call to + NetGameDistInputPeek(), but it was not used like that before this fix. + + Removed unused NetGameDistServT::aInKeepAlive and NetGameDistServT::aOutKeepAlive. + + Adjusted the send and peek timing logging to be a multiple of the configured fixed rate to prevent spamming + the logs when on higher fixed rate configurations. + + NetGameLink + Enhanced internal implementation with major cosmetic fixes to re-align with coding standard. No functional + impact. + + Renamed KEEP_ALIVE_TIME to NETGAMELINK_KEEPALIVE_TIME. + + Enhanced the send packet code to prevent sync packets from appending to unreliable packets. The sync + packets are used to drive the stats we provide at the NetGameLink level, dropping one of these packets would + skew this information. + + Changed NetGameLink to adjust the stream max packet size based on the CommUDP max width. + + Plat-str + Added ds_fmtoctstring(), which formats hexadecimal text from an input binary blob of data. + + ProtoHttp + Fixed a bug where the base url was not correctly processed when using a proxy server. + + Changed ProtoHttpStatus() function to pass down unhandled selectors to ProtoSSLStat(), rather than directly + returning a -1 unhandled result. + + Fixed crash in the instance we call ProtoHttpRecvAll with a NULL buffer but the response has no body. + Normally when a response has a body the use of a NULL buffer and size of zero will prompt ProtoHttp to tell + us that we need to allocate more space to be able to receive. If there is no body and we get a success right + away if will try to null-terminate that buffer which would lead to a crash. + + ProtoHttp2 + Changed ProtoHttp2Status() function to pass down unhandled selectors to ProtoSSLStat(), rather than + directly returning a -1 unhandled result. + + Fixed a missing NetPrintf argument for [%p] print (pState). + + Enhanced the logging for when we get an invalid stream id in a frame that requires one. + + Changed the trigger for incrementing the flow control window to when it will be under the size of the window + to prevent pausing the download. + + Fixed out of bound access when trying to print invalid frame types. + + ProtoHttpManager + Changed the code to prevent a dereference of NULL when logging an error as the pHttpCmd variable might be + NULL. + + ProtoHttpServ + Fixed crash in protohttpserv when a http2 client is used. + + Increased the size of the URL data in the request structure to 128 bytes. + + Fixed a bug with not setting the correct maximum buffer size for the write callback. The maximum needed to + account for the amount of data that was already written. + + ProtoMangle + Removed unnecessary platform-specific code used while generating random port. From now on, a port range of + 6000 is used on all platforms. + + ProtoSSL + Changed TLS_ECDHE_RSA_WITH_AES_(128|256)_CBC_SHA to be TLS1.2+ only after testing revealed issues with + TLS1.1 and prior. + + Fixed issue where _ProtoSSLRecvHandshakeFinish() was processing multiple handshake packets at once, which + could cause the handshake hash to be computed for more handshake packets than was correct. This did not + cause an issue prior to TLS1.3, but resulted in bad handshake hash calculations in the CertificateVerify + and Finished messages received from mozilla servers (which send the Certificate and CertificateVerify in + one SSL frame) when using TLS1.3. + + Fixed _ProtoSSLRecvServerKeyExchange() to validate the RSA-PKCS1.5 signature padding (in addition to adding + RSASSA-PSS validation for TLS1.3). + + Changed signature validation to use generated object comparison rather than parsing the included object. + This is generally understood to be better practice than ASN.1 parsing, which is error-prone. + + Changed to consolidate some other code (debug printing of premaster and master secrets, clearing premaster + secret from memory) in _ProtoSSLBuildKey(). + + Fixed a possible issue in _ProtoSSLHandshakeHashGet() where an unknown hash result from CryptHashGetSize() + could fall through without error. Changed handling of a hash too large for the provided buffer by + truncating the size and continuing rather than returning an error. + + Fixed an issue where a certificate date validation failure would not fail the connection IFF the signing + certificate was not available locally but was successfully installed on-demand by DirtyCert. + + Changed to refactored secure data receive flow, and increased internal receive buffer. This allows + handshake messages that are larger than a single SSL record to be received successfully, as well as in + general simplifying and cleaning up the receive code. While fragmented SSL handshake messages are very + rare they can happen when a very large Certificate is sent by the server, for example. + + Fixed _ProtoSSLUpdateRecvCertificate() to validate outer certificate envelope size. + + Fixed to add validation of ChangeCipherSpec message size and content. + + Changed the signature verification to heap allocate the CryptRSA state. The size of the CryptRSA state can + be rather large due to the number of CryptBnT structures that we use in certain conditions. This is the + only instance of us using a stack allocated CryptRSA state and we rather prevent stack overflow issues. + + Fixed a crash by ensuring we successfully get the Hash ASN object before trying to generate a hash. + + Changed the certification validity expiration prints to only be done when DEBUG_VAL_CERT is enabled. + + Changed ProtoSSLShutdown to clear the stored CAs. + + ProtoTunnel + Changed the send callback to early out when sending on the server socket used for xbox one. This socket + should be treated the same as the normal prototunnel socket, we will not try to match a tunnel. + + Refactored the tunnel flush timeouts to check the last send time on individual tunnels instead of mass + flushing all tunnels regularly regardless of when the last send occurred. This should help improve packet + bundling behavior. + + ProtoWebSocket + Fixed an issue where receiving an empty control frame (e.g. a ping) would cause the receiving to get stuck. + + Added logging when we enforce the minimum keep-alive internal of 30s to let users know what is happening. + + QosApi + Removed an unused function prototype. + + Tools + Removed the mapparse and mpsummary tools as they are no longer used. The source code for these can be found + at //gos/tools/dirtyutils. + + UserListApi + Added a comment to specify that a certain structure is only valid within the UserListApiCallbackT. + + Voip + Changed VoipSetEventCallback() to now enter the ThreadCrit before proceeding. This is to avoid any + potential theoretical race condition where the voipthread would kick in and use the callback while the + main thread is in the middle of altering it. + + VoipAux + Updated the build script's include to make sure that it adds the package.dir to the fromfile attribute. + The basedir only applies to the files in source.txt and not the fromfile call itself. + + VoipCommon + Changed VoipCommonStartup() to now take a new input parameter called pTextDataCb() invoked internally + when the system notifies readiness of newly locally generated transcribed text (ready for transmission + over the network). + + Fixed potential buffer overrun caused by trying to set channel settings on the shared audio input device + on a platform that doesn't support it. + + Changed the logic to make sure that we have a valid index into LocalUsers/RemoteUsers for the 'luid'/'ruid' + status selectors. + + Changed the check for support of the shared user index, checking negative does not work against 0xff when + checking against an int32_t type. Instead of checking for negative we want to check that the shared user + index is not set to the invalid value. When selecting the channel it did not seem as if the original check + was doing what was desired. + + Changed the shared user index checks to compile time instead of runtime to prevent certain compilers + generating unreachable code warnings. + + VoipConnection + Changed implementation to properly initialize the per-connection voice send timers used for sending at + a fixed 10hz rate. Prior to this change, there was a possibility for the first voip MIC packet being sent + to contain more than 100 ms of voice (i.e. the packet would keep filling up to the max sub-packet limit). + + Fixed setting/testing of the VOIP_PACKET_STATUS_FLAG_STT flag. + + Enhanced logics protecting against reliable data packing overflow. + + Changed implementation to now pack and unpack target client id with reliable data. Allows for ignoring + inbound reliable data for which we are not the target when receiving voip MIC packet from the voipserver. + + Fixed the name of the _VoipEncodeFlags and _VoipDecodeFlags function to _VoipEncodeLocalHeadsetStatus and + _VoipDecodeRemoteHeadsetStatus respectively to better reflect their purpose. + + Changed how much channel configuration data we send in the ping packets. Added a channel user indices bitset + to flag which indices the channel configuration corresponds to. This allows us to send less channel + configuration data in the ping packets. + Removed the fixed channel configuration from the ping packet and use the variable data where the reliable + data used to live to write the needed channel configuration. This bumps the VoIP packet version to 'h'. + + VoipGroup + Fixed a cleanup ordering issue in _VoipGroupManagerShutdown() that could result in a crash when + _VoipGroupManagerEVentCallback() is invoked from the voip thread. + + Added support for VOIP_CONNID_ALL in the VoipGroupMuteByXXX methods to affects all users in the VoipGroup + at once. + + Refactored the module ref logging to use %p instead of 0x%08x. + + Added VoipGroupMuteByClientId3. + + Added VoipGroupMuteAll, which stacks with the other VoipGroupMuteByXXX methods. + + VoipHeadset + Changed VoipHeadsetCreate() to now take a new input parameter called pTextDataCb() invoked internally + when the system notifies readiness of newly generated transcribed text. + + VoipMixer + Removed unused parameter iMixBufferUnused from VoipMixerAccumulate() synopsis. + + VoipPacket + Removed VOIP_MAXSUBPKTS_PER_PKT. Had not been needed since _VoipConnectionIsSendPktReady() had + been added. + + Removed the VOIP_PACKET_RELIABLE_FLAG_FIRST flag, no longer needed. + + Changed the value of VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF and VOIP_PACKET_RELIABLE_FLAG_DATA. + + VoipTunnel + Fixed the client flags to properly reset the VOIPTUNNEL_CLIENTFLAG_RECVVOICE when a user leaves the game. + + Enhanced the send logic to no longer call the user callback when send fails. We want to correctly represent + how much data we are sending, this could potentially misrepresent our metrics. This aligns with what we do + for game traffic. + + Fixed missing NetPrintf argument. + + XmlParse + Removed assignment of a variable that gets reassigned on the next line. + + DirtySDK (PC) + CommUdp + Fixed an incorrect sizeof() in PC-specific portion of _CommUDPListen0(). + + DirtyNetWin + Fixed incorrect debug printfs for the 'keep' selector. + + Switched to using a uintptr_t variable to store the temp handle when creating our threads so we don't need + to have a 32-bit / 64-bit check. + + VoipPc + Changed implementation to align with new synopsis of VoipCommonStartup(). + + Fixed the duplicate check by checking the right most condition to use the correct define. + + DirtySDK (PS4) + CryptRand + Refactored the PS4 implementation to use the random library provided by the SDK. Due to only being able to + generate 64 bytes at a time, we use it to seed our random. With this change you are now required to load the + Random module. + + DirtyLibPS4 + Refactored _NetTickCount to use sceKernelGetProcessTimeCounter function which stops while the system is + suspended. + + DirtySessionManagerPs4 + Refactored the control code to align more closely with other DirtySDK modules. We want to get rid of the + large switch statement in the beginning of the function as it is hard to maintain. Instead we will return an + error when we reach the end of the invitation controls if we are doing the sessions via the server. + + NetConnPS4 + Refactored internal implementation. Changed NetConnUserT to be a collection of fields belonging to a single + user instead of a collection of arrays indexed with the user index. + NetConnUserT::aNpid[] renamed to NetConnUserT::NpId. + NetConnUserT::aNpState[] renamed to NetConnUserT::NpState . + NetConnUserT::aUserServiceUserId[] renamed to NetConnUserT::UserServiceUserId. + NetConnUserT::aAuthCode[] renamed to NetConnUserT::AuthCode. + NetConnUserT::aParentalCtrls[] renamed to NetConnUserT::ParentalCtrls. + NetConnUserT::aPsPluss[] renamed to NetConnUserT::PsPlus. + NetConnUserT::aNpAvailability[] renamed to NetConnUserT::NpAvailability. + NetConnRefT::NpUsers renamed to NetConnRefT::aUsers[]. + Created NetConnNpRequestTypeE. Needed for new synopsis of _NetConnUpdateNpRequests(). + + Enhanced internal implementation. + * For consistency with how things are done in other DirtySDK files, local variables declaration made in for + loops were moved to the beginning of the function. + * Renamed some local variables with more explicit names. + * Made debug logging more consistent across the files. + * Uniformized usage of i, iIndex, iUserIndex and iLocalUserIndex --> it is now iLocalUserIndex everywhere. + + Refactored internal implementation: moved implementation of netconn external cleanup mechanism to + netconncommon to eliminate code duplication, and also slightly modified implementation details + such that the new common api is used in a more uniformed manner. + + Changed from using IEAUser to KettleUser to align with the code used in netconnxboxone. + + ProtoHttpPS4 + Fixed a potential crash when running out of system resources. + + Fixed the handling of an error when rate limiting reduced the number of bytes to send to 0. + + Fixed an issue with rate throttling that prevented request with no payload from completing. + + Refactored the timeout behavior to use the our own timeouts rather than the native PS4 timeout system to + better match what we do on other consoles. + + Fixed the return value of the 'data' status selector to return the size of the header plus the currently + received size of the Http body. + + VoipHeadsetPS4 + Modified implementation to now unconditionally apply most restrictive voip settings to comply with Sony TRC + R4061 when there are multiple local users interacting with a game (note: that's different from EA MLU which + implies multiple users authenticated with Blaze). Under such conditions, we used to apply most restrictive + voip settings only if users were sharing output audio via TV and input audio via PS4 cam mic. If both + users were identified to be using dedicated headsets, we used to apply respective privileges on a + per-headset basis. Sony confirmed this being an incorrect strategy + (https://ps4.scedev.net/support/issue/131717/_Need_help_with_interpretation_of_TR_4061) as there is no way + to make sure that the restricted user is not hearing the voice even if headsets are detected because what + is detected as a headset connected to a controller can rather be a speaker. + + VoipPS4 + Changed implementation to align with new synopsis of VoipCommonStartup(). + + DirtySDK (Unix) + DirtyLibUnix + Removed the NetTickSet function which was previously used to workaround an issue with an older version of + the linux kernel. + + Removed the _NetLib_bCritEnabled variable which has been replaced with a NetConn startup parameter. + + DirtyNetUnix (All) + Moved global socket control 'spam' to _SocketControlGlobal while leaving per socket 'spam' control in + SocketControl(). This does not change any behavior but aligns the grouping of global controls and socket + specific controls. + + Aligned with dirtynetwin regarding the logging in SocketConnect, we should be logging the remote address + after translation. + + Changed SocketDestroy to wait for any current socket lookups to finish processing. If there is a current + socket lookup processing and we destroy the hostname cache, after the processing is finish it will segfault. + + DirtyNetUnix (iOS/OSX) + Fixed a bug that broke iOS IPv6 compatibility, introduced in the 15.1.3.0.0 SDK release. + + DirtySDK (XBoxOne) + NetConnXboxOne + Refactored internal implementation: moved implementation of netconn external cleanup mechanism to + netconncommon to eliminate code duplication, and also slightly modified implementation details + such that the new common api is used in a more uniformed manner. + + Fixed an incorrect debug trace in _NetworkStatusThread(). + + Refactored to cache the value of NetConnStatus('tria') so avoid the performance hit from accessing the + 1st party object. + + ProtoHttpXboxOne + Refactored calls to 1st party API which take a long time to resolve so that they are run in a seperate + thread. + + Refactored to move the Open() and initialization of the IXMLHttpRequest2 object into a task to prevent its + long execution time from causing visual glitches when an HTTP request is performed. + + Refactored so that the request header callback would fire on the same thread as the http requestor thread. + This is a requirement for WebKit. + + Fixed a potential crash when cleaning up the XMLHTTP object. + + Fixed an issue for request requiring DirtyCert, the DirtyCert request was not complete at the time we were + trying to query the token which caused these operations to fail. + + Refactored threads so that the custom header callback is triggered on the same thread as the caller of + ProtoHttpUpdate(). + + Refactored the process kill list to accomodate part of the clean up process occuring in a new thread. + + Fixed an error that set the XmlHttp object to NULL incorrectly. + + Fixed a race condition by forcing all XmlHttp objects to be destroyed via the kill list. + + Moved a call to _ProtoHttpRequest on a thread inside the critical section. + + Fixed an occasional Nucleus login issue by working around an error the MS api returns after reaching the + maximum number of redirections. + + Fixed an issue where the protohttp reference can be destroyed while an asynchronous task is in progress. + + Fixed an issue where a critical section was not left before an early return. + + ProtoMangleXboxOne + Removed usage of the PROTOMANGLE_STATE_UNLOCK state for security associations. This state was an + unnecessary state because the "unlocking" step has never really been enforced by Microsoft. + It was a pre-gen4 launch concept that did not become real at gen4 launch. + + ProtoTunnel + Fixed to track up to sixteen different remote server ports for the server socket. + + Changes to status/control selectors: + -changed 'bnds' control selector to not recreate the server socket if it already exists. + -changed 'bnds' status selector to return if the specified port is in the list of server ports. + -changed 'bnds' status and control selectors to use iValue instead of iValue2 for the server port. + -added 'bndr' control selector to remove a mapped server port. + + VoipHeadsetXboxOne + Removed while loop in _VoipHeadsetPollForVoiceData() because there is no good reason to read more + that one sample bundle per voip thread iteration. Moreover, the while loop proved to be problematic + with TTS specifically because the capture source suddenly has a lot of samples ready (... much more + that what typically is delivered in a 20 ms sampling window) and that resulted in the originator + filling up the inbound receive queues of consumers. + + Changed implementation to use NetTickUsec() instead of NetTick() to get usec precision when logging + voice submission activity. + + Fixed a vector by changing it to an immutable vector to prevent a possible crash. + + VoipXboxOne + Changed implementation to align with new synopsis of VoipCommonStartup(). + + Changed implementation to properly support new Text-to-speech (TTS) and Speech-to-text (STT) features + in XboxOne. These changes became unavoidable with STT/TTS because without them it was nearly impossible + to play back successfully a very long input text. The inbound queue on the consumer side would just + continuously overflow. + * Changed VOIP_THREAD_SLEEP_DURATION from 18 ms to 20 ms. It used to be 18 ms because we had + problems pumping the voip thread at a sharp 20 ms rate. This problem was resolved by replacing + calls to NetTick() with calls to NetTickUsec() in _VoipThread(). NetTick() does use the high + res timer internally but it rounds up the result when it returns a ms value. The previous + implementation ended up not being able to create a sharp 20 ms pump rate because it was + subtracting two rounded up value. The new implementation is performing a single usec to + msec conversion at the last minute when it is about to call NetConnSleep(). + * Also, the priority of the _VoipThread() was changed from THREAD_PRIORITY_HIGHEST to + THREAD_PRIORITY_TIME_CRITICAL. The new implementation is much closer to what MS does in the + GameChat DLL. + + +EADP Game Services DirtySDK 15.1.3.0.5 - October 10, 2017 + + This SDK release was built using the following compiler packages: + + EA Config 5.01.00 + Framework 7.01.00 + + Android androidsdk 24.4.1-1-2 + androidndk r13b + android_config 4.00.00 + Capilano CapilanoSDK 170302-proxy + capilano_config 1.05.07 + Kettle kettlesdk 4.508.111-proxy + kettle_config 2.01.00 + iPhone ios_config 1.07.00 + iphonesdk 8.0-proxy-3 + OSX UnixGCC 0.10.00 + osx_config 1.11.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +New in this release: + DirtySDK (All) + ProtoWebSocket + Added APIs to allow sending of text data to the server. Our other APIs only allowed sending binary data + but the protocol also supports text, for more information see: https://tools.ietf.org/html/rfc6455#section-5.6 + +Changes/fixes in this release: + DirtySDK (All) + ConnApi + Fixed negative indexing when updating connection timers for the DirtyCast client. + + MurmurHash3 + Changed the function name of the internal implementation of rotl64 to prevent colliding on other systems + that might provide it. + + ProtoHttp + Fixed a write callback bug when all data is available immediately, in which case the write callback was + completing the transaction before the data could be read. + + ProtoTunnel + Fixed an Xbox One specific issue when a player is in multiple meshes with more than one server, e.g. in a + situation where a user is in a CC-assisted game group while being in a dedicated server game. In such an + instance, the packets for the first server connected to would cease to flow when the second server + connection was established. + + DirtySDK (PS4) + ProtoHttpPS4 + Refactored the timeout behaviour to use our own timeouts rather than the native PS4 timeout system to better + match what we do on other systems. + + DirtySDK (XboxOne) + ConnApi + Changed the size of a local variable used to store a SecureDeviceAddress as it could potentially be not + large enough for some theoretical networking conditions. + + DirtyAddr + Changed DIRTYADDR_MACHINEADDR_MAXLEN from 127 to 372 for xboxone specifically to fix a rare issue + demonstrated in production where the SecureDeviceAddr blob would be clamped when stored in a DirtyAddrT + because too large. After discussing with MS, 372 was identified as the right size to avoid this kind + of issue. (See comment in the code for more details) + + NetConnXboxOne + Fixed an issue with resume where if we didn't set the the status thread connectivity level to none will + cause netconn to skip the xbl_wait state causing connection failures + + ProtoHttpXboxOne + Fixed an issue where a redirect request would never be killed when redirection is disabled due to it never + going into the completed state. + + VoipHeadsetXboxOne + Fixed a potential crash in a race condition to create a chat participant for a bystander. + +EADP Game Services DirtySDK 15.1.3.0.4 - August 30, 2017 + + This SDK release was built using the following compiler packages: + + EA Config 5.01.00 + Framework 7.01.00 + + Android androidsdk 24.4.1-1-2 + androidndk r13b + android_config 4.00.00 + Capilano CapilanoSDK 10.0.14393.2156-proxy + capilano_config 1.05.07 + Kettle kettlesdk 4.508.031-proxy + kettle_config 2.01.00 + iPhone ios_config 1.07.00 + iphonesdk 8.0-proxy-3 + OSX UnixGCC 0.10.00 + osx_config 1.11.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +New in this release: + DirtySDK (All) + ProtoHttp/ProtoHttp2 + Added a timeout error (-7) when our module level timeout hits to allow the caller to differentiate between + errors at the lower protocols currently driven by HRESULT. + + ProtoHttp2 + Added support for HTTP/2 frame padding, see: https://tools.ietf.org/html/rfc7540#section-10.7 + + VoipTunnel + Added VoipTunnelStatus('talk') which returns the current count of "talking" clients, which is a client for + which the VoipTunnel detects inbound voip traffic. + +Changes/fixes in this release: + DirtySDK (All) + Build + Changed the optimized buildtype to correctly derive from what buildtype we are current building (static / + dynamic) using ${dirtysdk-buildtype}. + + CommUDP + Fixed an issue in CommUDPSend() where a full reliable buffer would prevent unreliable packets from being + sent. + + Hpack + Fixed the string decode function to initialize the output to NULL to prevent from freeing stack memory + when there is temporary data still being pointed to from previous functions. + + ProtoWebSocket + Updated the initial handshake receive to be copied into a temporary buffer and changed how it detects we + have the complete handshake header. Previously, if we get the HTTP/1.1 header data and some of the websocket + data combined in a single receive call, we have trouble getting into the open state as expected. + + DirtySDK (XboxOne) + ProtoHttpXboxOne + Fixed the header parsing to replace the final \r\n with NULL characters to align with our other protohttp + implementations. This is required for the headers to be compatible with the ProtoHttpUtil header parsing + functions. + + Removed the memmove in recv that was causing performance issues. On Xbxone the memmove is not really necessary + since we always allocate a buffer big enough for the full response. + + UserListApiXboxOne + Fixed a crash that happens when reading the XboxUserProfile results by adding null checks and exception handling. + +EADP Game Services DirtySDK 15.1.3.0.3 - July 12, 2017 + + This SDK release was built using the following compiler packages: + + EA Config 5.01.00 + Framework 7.01.00 + + Android androidsdk 24.4.1-1-2 + androidndk r13b + android_config 4.00.00 + Capilano CapilanoSDK 10.0.14393.2156-proxy + capilano_config 1.05.07 + Kettle kettlesdk 4.508.001-pr-proxy + kettle_config 2.01.00 + iPhone ios_config 1.07.00 + iphonesdk 8.0-proxy-3 + OSX UnixGCC 0.10.00 + osx_config 1.11.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +Changes/fixes in this release: + DirtySDK (All) + Build + Updated the voipaux sourcefiles includes to make that it adds the package.dir to the fromfile attribute. + The basedir only applies to the files in source.txt and not the fromfile call itself. + + ConnApi + Updated ConnApiRemoveClient to validate the passed in index to ensure it is in the valid range we + support. + + Removed the virtualization of the voip port when voip is disabled. + This allows for virtualization to only be done by modules that own the voip connection. + + DirtyNet + Updated the async receive code to cleanup the packet queue entry when receiving data into it results + in an error, allowing it to be used again. + + NetGameDistServ + Removed the notion of a "started" game as it proved to be completely broken. + When a game is started or stopped, the game code running on the client is in charge of exercising the + netgamedist flow control. + + ProtoHttp2 + Updated the processing of the url when using a proxy server, we needed to update the url after doing our + parsing. + + ProtoHttpServ + Updated the URL length in the request data to 128 bytes. + + DirtySDK (Unix) + DirtyNet + Updated SocketConnect logging to align with dirtynetwin which logs the remote address after translation. + +EADP Game Services DirtySDK 15.1.3.0.2 - June 1, 2017 + + This SDK release was built using the following compiler packages: + + EA Config 5.01.00 + Framework 7.01.00 + + Android androidsdk 24.4.1-1-2 + androidndk r13b + android_config 4.00.00 + Capilano CapilanoSDK 10.0.14393.2156-proxy + capilano_config 1.05.07 + Kettle kettlesdk 4.508.001-pr-proxy + kettle_config 2.01.00 + iPhone ios_config 1.07.00 + iphonesdk 8.0-proxy-3 + OSX UnixGCC 0.10.00 + osx_config 1.11.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +Changes/fixes in this release: + DirtySDK (All) + CommUDP + Added a reliable resend timer separate from our normal send timer to control when we resend reliable + packets. When customers mix reliable and unreliable packets, it didn't play well with the timer that + we had setup within commudp. + + CryptBn + Updated the calculation of the big number bit length to use the correct builtin based on the + representation of uint64_t on our platform. On 32-bit platforms we were trying to calculate using + the unsigned long version when uint64_t is a unsigned long long on that platform. + + CryptEcc + Updated the double and add operations for elliptic curve key generation to use a sliding window. + This algorithm is adapted from the one used for RSA Exponentiation which is discussed in the Handbook of + Applied Cryptography Chapter 14.6. + + ProtoSSL + Updated the server handshake to make sure that we send the server key exchange for ECDHE ciphers + when client certs are enabled. + + Fixed to add PKCS#1 1.5 padding validation to signature verification, added OBJ_sha384 and OBJ_sha512 to + ASN.1 parser table, now required for proper PKCS#1 signature validation. + + Fixed to add overflow checking on ASN.1 length parsing. + + Removed obsolete and unsafe OTG3 and GOS2011 CAs from trusted store. + + DirtySDK (PS4) + ProtoHttpPS4 + Fixed an issue when a POST must be changed to a GET. + + Refactored the creation and destruction of 1st party http contexts objects so that they only exist during requests. + + Refactored the maximum number of times the net context could be shared from a hardcoded constant to a variable + that can be set through ProtoHttpControl('mncs') or DirtyContextManagerControl('mncs'). + + Fixed an issue where a socket is potential left open after a sceEpoll is destroyed without being unset. + + DirtySDK (XboxOne) + NetConnXboxOne + Removed the use of the cached User^ and instead rely upon EAUser to provide the information we + previously relied upon the cached ref for. + +EADP Game Services DirtySDK 15.1.3.0.1 - April 11, 2017 + + This SDK release was built using the following compiler packages: + + EA Config 5.01.00 + Framework 7.01.00 + + Android androidsdk 24.4.1-1-2 + androidndk r13b + android_config 4.00.00 + Capilano CapilanoSDK 10.0.14393.2156-proxy + capilano_config 1.05.07 + Kettle kettlesdk 4.508.001-pr-proxy + kettle_config 2.01.00 + iPhone ios_config 1.07.00 + iphonesdk 8.0-proxy-3 + OSX UnixGCC 0.10.00 + osx_config 1.11.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + +Changes/fixes in this release: + DirtySDK (All) + CommUDP + Changed the #define for ESC_CAUSE_LOSS to not use the define statement as part of the PS4 4.5 SDK changes. + + DirtySDK (PS4) + DirtySessionManagerPS4 + Changed the constant SCE_NP_PLAY_TOGETHER_MAX_ONLINE_ID_LIST_NUM to SCE_NP_PLAY_TOGETHER_MAX_INVITEE_LIST_NUM as part of the PS4 4.5 SDK changes. + +EADP Game Services DirtySDK 15.1.3.0.0 - March 31, 2017 + + This SDK release was built using the following compiler packages: + + EA Config 5.01.00 + Framework 7.01.00 + + Android androidsdk 24.4.1-1-2 + androidndk r13b + android_config 4.00.00 + Capilano CapilanoSDK 10.0.14393.2156-proxy + capilano_config 1.05.07 + Kettle kettlesdk 4.008.131-proxy + kettle_config 1.14.00 + iPhone ios_config 1.07.00 + iphonesdk 8.0-proxy-3 + OSX UnixGCC 0.10.00 + osx_config 1.11.00 + Linux/Unix UnixClang 0.12.02-proxy + Win32 DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 14.0.23107-1-proxy + WindowsSDK 10.0.10586-proxy + + *** Important - DirtyCast Failover Mode has been removed *** + Support of DirtyCast Failover mode has been removed in this release. + This feature has now been replaced by Connection Concierge. + + *** Important - Public API changes UserListApi PS4 *** + The profile mask is now not supported when querying for the block list. + Please specify a type mask of 0 instead. + + *** Important - Public API changes for ConnApi *** + Removed ConnApiStart(). + Removed ConnApiStop(). + Removed ConnApiRematch(). + Removed ConnApiSetPresence(). + + *** Important - PS4 User Identifier Changed *** + As part of Sony's SDK/API 4.0 Sony has deprecated APIs that use OnlineIds, in + favor of replacement versions using the PS4 account ids. All DirtySDK calls to + PSN have been switched to use PS4 account ids instead of OnlineIds. + + *** Important - Public API changes for DirtyNet *** + Renamed SocketInfo('size') to SocketInfo('psiz') + Renamed SocketPacketQueueStatus('size') to SocketPacketQueueStatus('psiz') + Renamed SocketPacketQueueStatus('full') to SocketPacketQueueStatus('pful') + + *** Important - WinRT/WinPRT *** + Removed WinRT and WinPRT support completely. + + *** Important - Modules removed from DirtySDK *** + The following modules have been migrated to the new LegacyDirtySDK package + LobbyLan + Sort + Tagfield + NetResource + WebOffer + + +New in this release: + DirtySDK (ALL) + Build + Added exporting of the buildtype in our module's publicdata, which allowed dependent packages to easily find + our exported libs/dlls. + + Enhanced build scripts to define headerfiles so the headers show correctly in Visual Studio. + + CryptBn + Added a dedicated big number module based on the CryptRSA's implementation, this is used a basis for all + the crypto based math for RSA/ECC operations. + + CryptEcc + Added this module to implement math for short Weierstrass curves using CryptBn. This is the basis for the + support of the P-256/P-384 curves we use within ProtoSSL. + + ConnApi + Added more detail connection metrics which could be retrieved in the ConnApiInfoT struct as a ConnApiConnTimer with the following fields: + * uCreateSATime - time it takes to resolve secure association (xbox one) + * uConnectTime - time it takes for intial connection attempt + * uDemangleTime - time it takes to attempt demangling on the connection + * uDemangleConnectTime - time it takes to attempt a connection after demangling + + Hpack + Added a module to implement HTTP/2 Header compression support based on + https://tools.ietf.org/html/rfc7541. This module is used primarily by the new ProtoHttp2 module. + + JsonParse + Added new JsonParse2() API, which handles allocation of required memory internally. + + Added new getters supporting an error flag (JsonGetString2, JsonGetInteger2, JsonGetDat2, JsonGetBoolean2, + JsonGetEnum2), which can be leveraged to simplify parsing code that wants to validate whether fields were + present or not. JsonGetInteger2 also supports min/max valudation. + + Plat-str + Added new ds_memclr API that encapsulates memsetting memory to zero. In an attempt to prevent incorrect + uses of memsetting, we have introduced an API that encapsulates the more common case for memset. For cases + where memset to zero is not desired a ds_memset function was created so we can easily track conforming code. + + ProtoHttp + Added support for http PATCH requests. + + ProtoHttp2 + Added a module to implement HTTP/2 based on https://tools.ietf.org/html/rfc7540. + + ProtoHttpUtil + Added support for parsing the HTTP/2 style header which name begin with a leading ':' character. + + ProtoSSL + Added support for setting TCP_NODELAY socket option on the ProtoSSL socket. + + Added support for the elliptic curve client hello extension. This extension allows us to negotiate which + elliptic curves we support. + + Added support for ECDHE Key Exchange and the following cipher suites: + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA256 + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA256 + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA + Due to the performance of our configured elliptic curves, these cipher suites are currently disabled by + default. + + ProtoTunnel + Added new status selector 'vers' that can be used to query the prototunnel version negotiated on a specific + tunnel. + + VoipCommon + Enhanced debug logging for voip muting using 'umic' and 'uspk' control selectors. And fixed incorrect + prefix in some other already existing debug traces. + + VoipGroup + Enhanced logging for the low level connectivity id when resuming a connection. + + DirtySDK (iOS) + Build + Added an objective-c fileset / source text for adding multiple files to build as objective-c filetype. + + Added the -Wno-deprecated-declarations flag to disable warnings cause by CHttp functions used in dirtynet. + + DirtySDK (PS4) + DirtyContextManagerPS4 + Added a system to share sceNetPools to make it possible to create more concurrent ProtoHttpRefTs. + + DirtyErrPS4 + Added errors from the Sony SSL/HTTP libraries. + + Added missing errors from the Sony WebApi library. + + DirtyWebApiPS4 + Added server error message reporting when dealing with errors. We previously only reported the SDK side + errors but not the errors reported by the server. The server errors are not defined in a manner that we + can get the data from DirtyErr so we need to rely upon the data returned by the response information + struct. + + NetConnPS4 + Enhanced NetConnShutdown() with implicit IEAUser entries cleanup in case the game team is not calling + NetConnRemoveLocalUser() before calling NetConnShutdown(), or in case recent remove requests are still + pending in the internal request list when calling NetConnShutdown(). + + ProtoHttpPS4 + Added support for http POSTs with no payload. + + Added additional printf messages for debugging http headers. + + Added error logging so we know what the exact Sony errors were when any error occurs. + + VoipPS4 + Added VoipControl('+pri') to be used to by game code to signal that the specified local user shall be + included when internally calculating most restrictive voip privileges + + Added VoipControl('-pri') to be used to by game code to signal that the specified local user shall not be + included when internally calculating most restrictive voip privileges. + + DirtySDK (XboxOne) + NetConnXboxOne + Enhanced NetConnShutdown() with implicit IEAUser entries cleanup in case the game team is not calling + NetConnRemoveLocalUser() before calling NetConnShutdown(), or in case recent remove requests are still + pending in the internal request list when calling NetConnShutdown(). + +Changes/fixes in this release: + DirtySDK (ALL) + Build + Switched over to using Visual Studio 2015 by default. + + Refactored contribs to unflatten the include structure. To prevent collisions with other libraries we + are adding extra directories to our include structure. + common/include -> common/include/libsample + voipaux/include -> voipaux/include/voipaux + + General + Refactored locations that use memcpy/memcpy_s to use ds_memcpy/ds_memcpy_s. + + ConnApi + Removed ConnApiStart(), ConnApiStop(), ConnApiRematch(), ConnApiSetPresence(). Those functions are + leftovers from the xbox360 times... they have been no-op for a long period. They no longer need to exist. + + Fixed the setting of the GameServer's VoIP connection id by setting to VOIP_CONNID_NONE to prevent + incorrect teardowns of any active VoIP Connections. When in a Game Group and a DirtyCast hosted game, + if we leave the game the VoIP connection would have been incorrectly torn down. This causes our VoIP port + to be devirtualized, which caused our subsequent VoIP connections a higher chance of getting into a bad + state. So instead we needed to indicate that the DirtyCast GameServer does not own any VoIP connections and + that we will only devirtualize the VoIP port when tearing down the connection. + + Changed the virtualization of ports to be at the ConnApi level instead of the connection level. When doing + virtualization of ports at a connection level we can quickly run out of available ports. Since we know that + the virtualized ports do not change we can easily move this. With this change I updated our documentation + of how the client level ports are assigned to be more clear of what is happening. + + Removed gameserver mode concept used for fallback. Fallback mode has been replaced with connection concierge. + + DirtyLib + Changed to move platform-specific time code from plat-time to dirtylib. + + Changed NetPrintfCode() to initilize string buffer separately from instantiation. This is an optimization + to prevent the entire array from having to be memset at instantiation on every invocation. + + DirtyMem + Removed these two external MEMIDs: PLAYERSYNCSERVICE_MEMID ('plss') & TELEMETRYAPI_MEMID ('telm') + Any external module using the dirtymem api is now assumed to be internally defining its own mem id. + + Removed unused internal MEMIDS: PROTONAME_MEMID ('pnam') & PROTOARIES_MEMID ('pari') & + FRIENDAPI_MEMID ('frnd') & DIRTYLSP_MEMID ('dlsp') & + COMMSER_MEMID ('cser') + + DirtyNet + Fixed error conversion logic in SockRecvfrom(). Socket type (stream vs dgram vs raw) is now taken + into account to determine what conversion to use. The incorrrect logic proved to be problematic with + TCP sockets using async recv thread and being explicitly polled for recv by the client code instead + of just relying on recv callback invocation by the DS layer. + + Changed (renamed) SocketInfo('size') to SocketInfo('psiz') + + Changed (renamed) SocketPacketQueueStatus('size') to SocketPacketQueueStatus('psiz') + + Changed (renamed) SocketPacketQueueStatus('full') to SocketPacketQueueStatus('pful') + + Fixed SocketPacketQueueStatus 'pdrp' and pmax' when using the SocketPacketQueueAlloc code path. + + Changed SocketAddrMapTranslate to accept addresses of the same type without reporting an error. + + DirtySock + Removed the LobbyLan, Sort, Tagfield, NetResource and WebOffer modules. + These can now be found in the LegacyDirtySDK package. + + Fixed potential threading issues with modules with updates calling through NetConnIdle. + + JsonParse + Fixed JsonFind() to prevent reading outside the parse buffer. + + Fixed JsonParse() to return zero if the parse buffer is too small. + + NetConn + Changed NetConn to track NetConnStartups and Shutdowns with a reference count. Copied the handling on some + NetConnStartup params over to NetConnConnect options. + + NetGameUtil + Fixed NetGameUtilConnect() and NetGameUtilAdvert() to correctly handle failing calls to + _NetGameUtilAdvtConstruct(). + + Fixed local error codes possibly conflictig with COMM_* error codes in NetGameUtilConnect(). + + Changed implementation to eliminate code duplication: advt ref validation code + is now centralized in _NetGameUtilAdvtConstruct(). + + Plat-str + Changed to replace use of strncpy() with inline implementation in ds_strnzcpy(), for improved performance + when copying a string into a large buffer. The standard version will fill any unwritten parts of the + destination buffer with zeros. + + ProtoHttpServ + Changed strLocation (redirection location) to ProtoHttpServeResponseT. + + ProtoMangle + Updated logging to know what ports are bound and what currently module ref that logs belong to + + QosApi + Removed unused tagfield include. + + Changed the default packet queue length to 12 (up from 10), in testing this covers the most extreme case + of _QosApiRecvCB NetCritTry never succeeding. + + Added logging describing packet queue health, a warning will be printed if the queue is close to being + full. + + Fixed a print which indicated both FW probes were sent to the same address, when they in fact have not. + + VoipCommon + Fixed a potential race condition between the main thread and the voip thread when altering the voip channel + config. The bApplyChannelConfig variable was being set to TRUE before propagating the change down to the + voip layer with VoipControl('chan'). Under some timing conditions, that could result in the voip thread + applying the voip channel config with the old values instead of the new values because the data structures + involved were not yet updated. + + VoipGroup + Fixed some coding standard violations, arrays are required to be prefixed with "a" + + VoipTunnel + Changed to using qsort for sorting our internal lookup table. + + Changed suspended data not found logging to be at a higher verbosity as we assume this something that doesn't happen often and can be quite spammy + + DirtySDK (PS4) + DirtyContextManagerPS4 + Fixed a bug which prevented the manager from properly cleaning itself up on shutdown. + + Changed to Sony API using PSN account ids. + + DirtyLibPS4 + Changed mutex creation to attempt to fallback to an unnamed mutex when create returns + SCE_KERNEL_ERROR_EAGAIN. + + DirtyUserPS4 + Removed unused tagfield include. + + Changed to Sony API using PSN account ids. + + DirtyWebApiPS4 + Fixed DIRTYSESSMGR_MEMID being incorrectly used instead of DIRTYWEBAPI_MEMID. + + Changed mutex creation to attempt to fallback to an unnamed mutex when create returns + SCE_KERNEL_ERROR_EAGAIN. + + Removed the SDK 2.5 checks as we are long past the days of titles allowed to ship on those old SDKs. + + Removed deferred delete list. + + Changed to Sony API using PSN account ids. + + NetConnPS4 + Fixed multiple cosmetic issues to improve code readability and alignment with coding convention. + + Fixed an inconsistent error code in NetConnShutdown. + + Changed to Sony API using PSN account ids. + + PrivilegeApiPS4 + Changed to Sony API using PSN account ids. + + ProtoHttpPS4 + Fixed the dumping of the POST data payload, we needed to use the size instead of the result of send. + + Changed the HTTP connection template creation to disable gzip decompression. ProtoHttp does not offer this + type of functionality which causes some isues when trying to read gzipped compressed bodies from the + server. + + Fixed an issue where large headers were not processed properly. + + Fixed a possible truncation of a url when formatting a request. + + Fixed an issue with long urls getting truncated. + + Refactored to deactivate the Sony library's automated handling of redirections and cookies in order to + provide the exact same behavior as other platforms. + + Fixed the handling of binary certs. + + Fixed the http headers to be associated with the sceRequest rather than the sceTemplate to make sure the + headers are cleared at the appropriate times. This prevents the http library from running out of memory + after a few uses of a protohttp reference. + + UserApiPS4 + Changed to Sony API using PSN account ids. + + UserListApiPS4 + Changed to Sony API using PSN account ids. + + VoipPS4 + Changed to Sony API using PSN account ids. + + VoipHeadsetPS4 + Fixed dirtysdk incorrectly requiring a voip user to be a member of a blaze mesh for his privacy settings + to contribute to the calculation of the most restrictive voip setting on the console in scenarios where + multiple users are locally signed-in. DirtySDK now only requires the user to be authenticated with blaz + for his privacy settings to contribute to the calculation of the most restrictive voip setting. The + rational being that a blaze authenticated user is implicitly a user interacting with the game UI and + therefore a "particiapting user" as defined by Sony. + + Changed initialization of Sony voice lib to leverage the new sceVoiceSetThreadsParams() api + (introduced is SDK 3.50) to specify the priority and the affinity of the internal voice and audio port + threads. This replaces the usage of ephemeral high-priority threads for calling sceVoiceStart() and + sceVoiceCreatePort() such that internal threads inherit of the right attributes. + + Changed _VoipHeadsetUpdateChatPrivileges() to now take the new +pri / -pri config into account when + calculating pHeadset->bSharedPrivilege. + + DirtySDK (Unix) + DirtyNetUnix + Fixed to revert address selection to prefer IPv4 addresses on non-Apple platforms. Apple platforms + continue to prefer IPv6 addresses, a fix that was required for iOS10 compatibility. + + NetConnUnix + Fixed an inconsistent error code in NetConnShutdown. + + DirtySDK (XboxOne) + DirtyAddrXboxOne + Removed TagField usage as TagField will be moving to the LegacyDirtySDK. + + Fixed an off-by-1 error for decoding binary7 Xbox One addresses. + + DirtyUserXboxOne + Removed TagField usage as TagField will be moving to the LegacyDirtySDK. + + ProtoHttpXboxone + Refactored implementation to use a request killlist where terminated requests are queued until they + complete for good. That is replacing the former explicit abort operations. + + Changed the implementation with the introduction of a state machine to track asynchronous evolution + of a specific xmlhttp object. + + +EADP Game Services DirtySDK 2015 Winter 2.3.0 - October 19, 2016 + + This SDK release was built using the following compiler packages: + + EA Config 4.03.01 + Framework 6.04.01 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 10.0.14393.1058-proxy + capilano_config 1.05.03 + Kettle kettlesdk 3.508.041-proxy + kettle_config 1.06.00 + iPhone ios_config 1.03.00 + iphonesdk 8.0-proxy-2 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.12.02-proxy + Win32/WinRT/WinPRT + DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 11.0.50727-1-proxy + WindowsSDK 8.0.50727-4-proxy + WindowsPhoneSDK 8.0.9900-proxy + + *** Important: The CommTAPI and CommSer modules have been removed *** + + *** Important: The CryptSSC2 module has been removed, for string encrypt/decrypt please use CryptArc4 *** + + *** Important: The SOCKET_ASYNCRECVTHREAD = 0 support has been removed *** + +New in this release: + DirtySDK (All) + ConnApi + Added a new control selector 'dtif' that can be used to override the default timeout value for XB1 + security association creation or the demangling timeout on other platforms (in a cc-assisted or failover + context). + + DirtyNet + Added support for TCP keep-alive settings using the 'keep' control. In some environments we need to be able + to tweak the keep-alive settings to ensure that firewalls and other external parties do not close our + connections without notifying us. + + Added SocketHostnameCacheDel, which deletes an entry from the hostname cache (internal use only). + + Added debug print when a lookup is refcounted. + + NetConn + Added ability to allow users to override the cpu affinity of our internal threads with a netconn startup + param "-affinity". + + Platform + Added ds_strtoull function to convert strings to uint64_t. + + Added support for using EABase to drive the platform.h defines. + + ProtoSSL + Added support for the ALPN extension (Application Level Protocol Negotiation) on both the client & server + side. This extension helps negotiate a protocol between the client & server; see the spec at + https://tools.ietf.org/html/rfc7301. + + Added ProtoSSLControl() 'resu' selector, to enable/disable SSL session resume (enabled by default, + preserving previous behavior). + + Added support for setting the keep alive settings for the TCP socket. To support this I've added + an 'skep' option and followed the same pattern as the other socket options that we set for this module. + + QosApi + Added ability to configure much of qosapi with QosApiControl, via the BlazeSDK util component this provides + us with many options to tweak QOS behaviors post ship. New selectors are: + 'bpco' - if used overrides config from server, this will be the number of bandwidth probes used for tests + 'bpso' - if used overrides config from server, this will be the size of bandwidth probes used for tests + 'isyt' - sets the maximum number of ms to wait to initially synchronize all latency requests + 'lpco' - if used overrides config from server, this will be the number of latency probes used for tests + 'pque' - passed to SocketControl 'pque' if not 0, otherwise the numpackets will be passed to SocketControl + 'pque' + 'rbuf' - passed to SocketControl 'rbuf' + 'repc' - sets the number of extra probes sent (in addition to those which received no response) on a re-issue + 'sado' - set the override address to send the http requests to when issuing a service request, overrides + parameter passed to QosApiServiceRequest + 'salp' - if TRUE use old logic of sending all latency probes at once (default FALSE) + 'sbuf' - passed to SocketControl 'sbuf' + 'sprt' - set the override port to send the http requests to when issuing a service request, overrides + parameter passed to QosApiServiceRequest + 'tgbp' - sets the minimum number of ms between sending latency probes to the same site. + 'ussl' - if TRUE use https else http for setup communication (defaults TRUE) + + DirtySDK (PS4) + DirtySessionManagerPS4 + Added new status selectors for PlayTogther support. New selectors are: + 'pthe' - poll this selector to check if the client has received the PlayTogether event + 'ptil' - if a PlayTogether event has been received use this selector to retrieve the invite list + + NetConnPS4 + Added a new startup parameter "-checktick" on PS4 to control how we initialize DirtyEventManager. The new + startup parameter will control if we fail our initialization of DirtyEventManager in the case that we have + potentially missed events because the EASystemEventMessageDispatcher has been ticked. By default we will + not check if the dispatcher has been ticked so teams don't need to worry about aligning the startup of the + two systems. + + ProtoHttpPS4 + Added protohttpps4 for native support of HTTP and SSL. + + DirtySDK (XboxOne) + NetConnXboxOne + Added extra mapping from sandbox to environment to prevent having the title download storage figure out the + environment: + * EARW.3 -> CERT + * XDP1.0 -> PROD (EA Access Testing) + +Changes/fixes in this release: + DirtySDK (ALL) + Build + Changed to set the optionset to the C++ build type when building those types of files. With newer packages + we see that our C++ files were trying to be built as C and causing us to generate some compiler errors due + to that. + + ConnApi + Changed to move the virtualization of ports to be handled at the connapi layer instead of having the + network adapter deal with it. + + DirtyCert + Changed to move DirtyCert updating to the internal NetLib idle thread. To make integration easier for our + customers, this makes it possible to use ProtoHttp without the need to poll NetConnIdle. The major + requirement that ProtoHttp depended on was DirtyCert, which needs to be updated in case we needed to pull + down any CA certs. + + DirtyNet + Fixed to no longer refcount HostentT refs that are in the list but are in a done state. + + JsonParse + Fixed crash issue parsing json with leading whitespace. + + ProtoHttp + Fixed a bug in both ProtoHttpGetNextHeader() and ProtoHttpFindHeaderValue(), when dealing with an empty + header. + + ProtoHttpServ + Fixed an issue which could lead an http client to hang indefinately or until timeout when the client + requested a keep-alive. + + Fixed a cert cache issue where it was possible to reconnect to a server with a cached cert using IP address + instead of name. + + ProtoTunnel + Fixed to initialize the sockaddr used in _ProtoTunnelRecvCallback to prevent address translation spam. + + QosApi + Changed to improve latency tests accuracy in poor network conditions; latency is now done in parallel + across all ping sites, preventing random lag spikes from effecting one result. + + Changed to improve logging by making it more configurable with QosApiControl('spam') - set the verbosity of + the module, default 1 (0 errors, 1 debug info, 2 extended debug info, 3 per probe info). + + Changed public APIs QosApiCreate and QosApiServiceRequest to simplify API and better reflect data types + being passed. + + Removed unused defines QOSAPI_STATUS_XB_*, QOSAPI_LISTENFL_*, and QOSAPI_RESPONSE_MAXSIZE. + + Removed and replaced QOSAPI_REQUESTFL_* with QosApiRequestTypeE. + + VoipConnection + Fixed coverity warning about potential out of bound access of pConnection->VoipMicrPacket[] array when + user index is VOIP_SHARED_USER_INDEX (0xFF on PC). + + Fixed a bug in use of memset, which resulted in target buffer not being cleared. + + VoipTunnel + Removed deprecated flags and fields that were set for deprecation since v9 + * VOIPTUNNEL_CLIENTFLAG_BROADCASTING_VOICE + * VOIPTUNNEL_GAMEFLAG_MAX_VOICES_REACHED + * VoipTunnelGameT.iNumClientsBroadcasting + + XmlFormat + Fixed access to unitialized variable coverity warning. + + DirtySDK (PS4) + DirtySessionManagerPS4 + Fixed to try to catch configuration errors with dirtysessionmanager as we want to send failures back to + bring it to the attention of integrators. + + Fixed an issue where the return value was not properly being set because of a bad enclosure of assignment. + + DirtySDK (Unix) + Build + Removed voiptunnel.c from unix specific sources as this is already part of main source list. + + DirtyNetUnix + Fixed _SocketLookupThread to prefer IPv6 addresses, when available. This fixes compatibility with iOS10. + + Fixed SocketConnect to scrub a hostname cache entry when connect() returns an EHOSTUNREACH error. This + type of error happens when switching from an IPv4 to an IPv6 connection or vice versa. + + DirtySDK (XboxOne) + Build + Changed to use sdkreferences task when depending on any of the extension assemblies on XboxOne. + + ProtoMangleXboxOne + Fixed a potential issue where an IPv6 user mapping might get updated instead of added. + + NetConnXboxOne + Fixed to handle the AsyncStatus::Started case when calling DownloadBlobAsync. FIFA was seeing instances + where we would crash when getting a AsyncStatus::Started due to the fact that we don't handle and just kill + the async operation. After discussing this with Microsoft their suggestion was just to ignore this status + as you should expect to get another callback when the operation actually finishes. + + Fixed an issue were a failed environment check would not properly trigger a retry, leaving netconn stuck in + ~env state indefinitely. + + +EADP Game Services DirtySDK 2015 Winter 2.2.3 - September 19, 2016 + + This SDK release was built using the following compiler packages: + + EA Config 3.04.00 + Framework 5.05.00 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 10.0.10586.1203-proxy + capilano_config 1.03.03 + Kettle kettlesdk 3.508.041-proxy + kettle_config 1.06.00 + iPhone ios_config 1.03.00 + iphonesdk 8.0-proxy-2 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.12.01-proxy + Win32/WinRT/WinPRT + DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 11.0.50727-1-proxy + WindowsSDK 8.0.50727-1-proxy + WindowsPhoneSDK 8.0.9900-proxy + +New in this release + DirtySDK (All) + DirtyNet + Added support for TCP keep-alive settings using the 'keep' control + + +EADP Game Services DirtySDK 2015 Winter 2.2.2 - September 06, 2016 + + This SDK release was built using the following compiler packages: + + EA Config 3.04.00 + Framework 5.05.00 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 10.0.10586.1203-proxy + capilano_config 1.03.03 + Kettle kettlesdk 3.508.041-proxy + kettle_config 1.06.00 + iPhone ios_config 1.03.00 + iphonesdk 8.0-proxy-2 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.12.01-proxy + Win32/WinRT/WinPRT + DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 11.0.50727-1-proxy + WindowsSDK 8.0.50727-1-proxy + WindowsPhoneSDK 8.0.9900-proxy + +New in this release: + DirtySDK (All) + ProtoSSL + Added ProtoSSLStat() 'rsao' selector to signal that there is currently an RSA operation ongoing. + +Changes/fixes in this release: + DirtySDK (All) + ProtoTunnel + Fixed a knock-on in cl 1174902 when faced with discarding an out-of-order packet that could not be + recovered. This would cause a stream reset that broke the connection. This fix reverts part of that + change to make the out-of-order packet discard safe and not break the connection. In doing so it undoes + the original fix, meaning some other resolution for that issue will be required moving forward. + + DirtySDK (PC) + VoipPC + Fixed intermittently broken voip on PC caused by bad initialization of the VoIP user identifier. + + DirtySDK (Unix) + NetConnUnix + Fixed to check for NULL terminator when parsing /proc/cpuinfo in the case we have a truncated string + (does not include \n). This caused issues on newer hardware where the flags field grew larger than 512, + which caused a crash from the loop never exiting. + + +EADP Game Services DirtySDK 2015 Winter 2.2.1 - July 13, 2016 + + This SDK release was built using the following compiler packages: + + EA Config 3.04.00 + Framework 5.05.00 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 10.0.10586.1203-proxy + capilano_config 1.03.03 + Kettle kettlesdk 3.508.041-proxy + kettle_config 1.06.00 + iPhone ios_config 1.03.00 + iphonesdk 8.0-proxy-2 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.12.01-proxy + Win32/WinRT/WinPRT + DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 11.0.50727-1-proxy + WindowsSDK 8.0.50727-1-proxy + WindowsPhoneSDK 8.0.9900-proxy + +Changes/fixes in this release: + DirtySDK (PS4) + DirtySessionManagerPS4 + Changed DirtySessionManager to better catch configuration errors and service it back to integrators. + + VoipHeadsetPS4 + Fixed _VoipHeadsetUpdateChatPrivileges() to ignore non-participating user. This fix is necessary to comply + with TRC R4061G in PS4-TestCase_for_TRC1.5_e stating: A non-voip-restricted user in a game should not see + his voip blocked because of a voip-restricted user signed-in on the console but not participating in the + game. + + DirtySDK (XboxOne) + NetConnXboxOne + Fixed potential crash in the lambda expression performed when the DownloadBlobAsync() operation completes + with completion status AsyncStatus::Canceled. The code incorrectly uses a reference that was invalidated + when the operation cancellation was initiated. + + ProtoHttpXboxOne + Fixed a potential external cleanup issue by adding a timeout to stop waiting for the E_ABORT that might + never come. Instead after 5 second time we will fake the E_ABORT Event ourselves. + + DirtySDK (Unix) + DirtyNetUnix + Removed the use of AI_V4MAPPED flag when ai_family is AF_UNSPEC. + + +EADP Game Services DirtySDK 2015 Winter 2.2.0 - June 09, 2016 + + This SDK release was built using the following compiler packages: + + EA Config 3.04.00 + Framework 5.05.00 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 10.0.10586.1203-proxy + capilano_config 1.03.03 + Kettle kettlesdk 3.508.041-proxy + kettle_config 1.06.00 + iPhone ios_config 1.03.00 + iphonesdk 8.0-proxy-2 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.12.01-proxy + Win32/WinRT/WinPRT + DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 11.0.50727-1-proxy + WindowsSDK 8.0.50727-1-proxy + WindowsPhoneSDK 8.0.9900-proxy +New in this release: + DirtySDK (All) + Build + Enabled shadow variable warnings on targets that doesn't set this by default. + + DirtyLib + Added NetPrintRateLimit, implementing automated rate limiting of redundant lines of NetPrintf output. + Identical sequences of lines are suppressed for up to 100ms, or when a new line of text is printed. + The number of suppressions is appended to the line of text. Lines of text over 256 characters are + not rate-limited. + + DirtyNet + Added SocketPacketQueueStatus('full') to query if socket inbound packet queue is full or not. + + Added SocketPacketQueueStatus('size') to query current size (in packets) of socket inbound packet queue. + + Added IPv6 support definitions and includes. + + Added SocketAddressMap functionality, moved here from dirtynetxboxone, for general IPv6 support. IPv6 + addresses are mapped to pseudo-IPv4 addresses for public consumption. + + NetConn + Added standard error code for NetConnStartup. + + ProtoHttpServ + Added function to log via the logging callback at varying verbosity levels. + + ProtoSSL + Added debug output of certificate signature type when validating a certificate. + + QosApi + Added code to leverage the new support for SocketControl('pque') on physical sockets (non-virtual). QosApi + is now using a socket with a 10-deep recv paquet queue to eliminate the possibility of inducing artificial + latency resulting from inbound latency probes remaining in the system socket receive buffer because the + 1-deep queue was full. (Happens when the critical section cannot be entered in the recv callback registered + by qosapi - _QosApiRecvCB()) + + DirtySDK (Android/iOS/PC/Unix/XboxOne) + DirtyNetWin + Added IPv6 support, switched to using IPv6 sockets internally, leveraging dual-protocol stack functionality + to continue to support IPv4. Added IPv6 name resolution support to SocketLookup(). IPv4 names continue + to be preferred when available. + + DirtySDK(WinRT) + DirtyNet + Added new define INADDR_LOOPBACK for WinRT configurations + + DirtySDK(XboxOne) + NetConnXboxOne + Added a new XBLWait state so make sure we wait for the connectivity level to become xbox live. + +Changes/fixes in this release: + DirtySDK (All) + Build + Moved the contents of dirtysock-initialize.xml to Initialize.xml file and tell the package to + initializeself. This makes it so the package will include Initialize.xml automatically without us needing + to do it explicitly. + + Changed implementation to now export the DIRTYCODE_LOGGING defines based on the + dirtysdk_debug_enable / dirtysdk_debug_disable build properties. + + Removed hhc.exe from the SDK and depend on the userdoc package to provide this when building documentation. + + Removed unused macro / defines + + Removed unused release build scripts + + ConnApi + Fixed the GameServer's bAllocated flag to be correctly set TRUE during client initialization. + + CryptGCM + Fixed crash in _CryptGcmDecrypt() when passed a negative length. This condition is rare and has been seen + when an SSL server incorrectly sends an unencrypted alert message when aborting a connection. + + DirtyLib + Changed to move/consolidate NetPrintfCode and NetPrintfHook to dirtylib from platform-specific files. + + Moved DIRTY_TOKEN_PASTE and DIRTY_CONCATENATE_HELPER here from dirtynet.h as a better fit. + + DirtyNet + Fixed implementation to skip empty hostname cache entries before checking for expiration, to eliminate + confusing debug output. + + Refactored recv path for physical sockets (non-virtual): + * Replaced hard-coded 1-deep queue with configurable n-deep queue. + * Performed appropriate changes for all flavors (win, x1, ps4, unix) to be as close as possible + (eliminated unjustified platform specificities) + * Simplified recv path logic in terms of what is done in the context of SocketrecvFrom() vs in the + context of socketrecvthread + + Standardized verbosity tracking and usage (spam levels). + + JsonParse + Added handling for JSON strings inside of JsonSeekObjectEnd. + + Plat-str + Cleanup and better support of IPv6 address printing. + + ProtoAdvt + Fixed a couple of places that didn't initialize a sockaddr struct before use. + + ProtoHttpServ + Changed all the logging to use the logging callback so we get in the applications that run this module. + + ProtoSSL + Fixed a bug when requesting an SSLv3 connection where the requested cipher list would be empty, causing + the connection to fail. This is only relevant to test setups as DirtySDK clients do not request SSLv3 + by default. + + ProtoTunnel + Removed tunnel mode concept from prototunnel. + + DirtySDK (PC) + DirtyNetWin + Removed DirtyNetXboxOne. Consolidated win and xboxone implementations in a single file called DirtyNetWin + to achieve reduction of code duplication. + + VoipConnection + Fixed the voip data to not be processed if there is no listening device attached to prevent TTY spam on PC. + + DirtySDK (PS4) + Build + Removed unsupported defines as part of the PS4 3.500 SDK upgrade. The ORBIS_SDK_VERSION was replaced by + SCE_ORBIS_SDK_VERSION. + + Removed very old version checks that are no longer needed. + + Removed the privilege checks for asynchronous multiplayer as this is not supported on the newer SDK. + + Changed implementation to now set the config-vs-version on PS4 to support the use case of Frostbite titles. + Make sure to only set the VS2015 compiler flags for vc compilers. + + Fixed PS4 compiler warnings introduced in the PS4 3.500 upgrade, reported by a customer (Unreachable code + and checking NULL on arrays). + + DirtyNetPS4 + Added handling of sceNpWebApiDeleteRequest failures. + + DirtySessionManagerPS4 + Fixed potential mem leaks caused by the changeable data never being released when it exists. + + Fixed implementation of DirtySessionManagerControl('gchg') to invoke + _DirtySessionManagerSetChangeableSessionData() instead of _DirtySessionManagerSetSessionData(). + + DirtyWebApiPS4 + Changed where iThreadLife is set to DirtyWebApiCreate (main thread). + + NetConn + Fixed implementation to check the result code of sceAppContentAppParamGetInt() when querying for trial + status. + + UserApiPS4 + Fixed a double counting problem when a first party query fails. + + DirtySDK (XboxOne) + Build + Fixed compiler warnings introduced by upgrading to the March XDK. + * Fixed shadow variable usage in 'poll' selector (dirtynetxboxone) + * Fixed an incorrect implementation of SocketRecvfrom the pBuf parameter is non-const (dirtynetxboxone) + * Remove unused variables on platforms they are not used on (connapi, user, privilege) + * Use a conditional instead of assigning an integer to a boolean in C++ (voipxboxone) + * Remove printing of serialization size as that was the only use of the variable and not very helpful to + logging. + * Suppress warning in T2Host relating to changing of sizes between compilers and alignment attributes + * Assign the enumeration to the correct value instead of treating it as an integer (privilege) + * Switch from while(1) to for (;;) to prevent a constant expression warning (SampleCore) + * Remove unused variable (connapi, SampleCore) + + DirtyLibWin + Changed to statically initialize nettick frequency to a safe value, so any calls to NetTick() on Xbox One + before NetConnStartup() will not crash. + + DirtyNetXboxOne + Switched to using DirtyNet functions for Address Map functionality that were previously internal. + + Removed DirtyNetXboxOne. Consolidated win and xboxone implementations in a single file called DirtyNetWin + to achieve reduction of code duplication. + + VoipHeadsetXboxOne + Refactored bad rep check query to use normal lambda expressions. + + +EADP Game Services DirtySDK 2015 Winter 2.1.0 - March 31, 2016 + + This SDK release was built using the following compiler packages: + + EA Config 3.04.00 + Framework 5.05.00 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 10.0.10586.1100.0-proxy + capilano_config 1.03.03 + Kettle kettlesdk 3.008.211-proxy + kettle_config 1.05.00 + iPhone ios_config 1.03.00 + iphonesdk 8.0-proxy-2 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.12.01-proxy + Win32/WinRT/WinPRT + DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 11.0.50727-1-proxy + WindowsSDK 8.0.50727-1-proxy + WindowsPhoneSDK 8.0.9900-proxy + + *** Important - Xbox One XSAPI 2.0 Support *** + This version of DirtySDK supports XSAPI 2.0 which is not backwards compatible with XSAPI 1.0. + Integrators will need to build against XSAPI 2.0. + + *** Important - ConnApi client uId of 0 is now considered invalid *** + ConnApi client with a uId of 0 will now be consider invalid and will be ignore by ConnApi. + + *** Important - First DirtySDK version compatible with the Connection Concierge feature *** + This version of DirtySDK includes a large amount of changes that were introduced specifically for interoperability + with the Connection Concierge tech (https://docs.developer.ea.com/display/TEAMS/Connection+Concierge+Project). + Regardless of your game leveraging Connection Concierge or not, those changes should be transparent to you. + +New in this release: + DirtySDK (All) + CommUdp + Added backwards-compatible update to protocol to allow versioning, defined previous version as 1.0 and new + version as 1.1. + + Added larger subpacket support (increased from a maximum of 250 bytes to 1530 bytes) for connections + negotiated to commudp protocol version 1.1 or higher. Subpackets requiring 250 bytes or fewer continue to + use one byte of encoding, while larger subpackets require two bytes. This allows packet redundancy, when + combined with raising the default redundancy limit, for larger packets. + + ConnApi + Added the ability to specify a user index when activating the P2P connection forced failure debug + option ('!res'). Setting the index to -1 will affect all users like the old behavior. + + Added support for hosted connections (obtained from Connection Concierge). New code paths were added + to use connection IDs received from Connection Concierge when setting up game connections, voip + connections and tunnels. Related public api changes: + * Added multiple new fields to ConnApiClientInfoT. + * Added CONNAPI_CCMODE_* defines. + * Added a new 'ccmd' control selector to specify the Connection Concierge mode associated with + the connapi instance. + + DirtyNet + Added prints to track virtual port addition and deletion. + + Added packet drop and packet high water to packet queue. + + Added SocketInfo() 'pdrp' and 'pmax' selectors. + + JsonParse + Added support for JSON payloads greater than 64k. + + Added support for JsonParse taking a length of -1, in which case strlen is used to calculate the size of + the input JSON buffer. + + Added support for JsonParse to take a null parse buffer, in which case the size in bytes required for the + parse buffer are calculated and returned. + + ProtoHttpServ + Added support for a 'spam' control to control the amount of logging with do within the module. + + Added a request timeout to cover situations where the remote side stops sending or receiving, defaulting + to 30 seconds. + + Added a ProtoHttpServControl('idle') selector to set the idle/keep-alive timeout. + + Added PROTOHTTPSERV_FLAG_LOOPBACK. + + Added ProtoHttpServCreate2() which has an additional uFlags parameter. + + ProtoTunnel + Added a new 'actv' status selector to get the number of active tunnels on based on a specific prototunnel + version. + + Added a new 'vset' status selector to get a comma delimited list of versions we support to report to the + CCS. + + Added PROTOTUNNEL_VERSION, PROTOTUNNEL_VERSION_MIN, PROTOTUNNEL_VERSION_MAX to the public header file. + + Voip + Added support for one way muting. + + VoipCommon + Added a new 'vcid' control selector to assign a local 32-bit identifier to a specific voip connection. + + VoipConnection + Added support for hosted connections (obtained from Connection Concierge). New code paths were added + to use connection IDs received from Connection Concierge when setting up voip connections. + Related public api changes: + * New uRemoteClientId field added to VoipConnectionT. + * Renamed uClientId field to uLocalClientId in VoipConnectionT. + + VoipGroup + Added support for hosted connections (obtained from Connection Concierge). New code paths were added + to use connection IDs received from Connection Concierge when setting up voip connections. + Related public api changes: + * Added VOIPGROUP_CCMODE_* defines. + * Added a new 'ccmd' control selector to specify the Connection Concierge mode associated with + the voipgroup. + * Added VoipGroupConnect2() which takes an additional bIsConnectivityHosted input parameter and an + additional uLowLevelConnectivityId input parameter. + * Added VoipGroupResume2() which takes an additional uLowLevelConnectivityId input parameter. For + Connection Conncierge support specifically. + + VoipTunnel + Added support for allowing voiptunnel clients to exist in multiple games. This functionality to used to + mimic how Blaze handles clients in Games and Game Groups. Related public api changes: + * Added VOIPTUNNEL_MAXSUSPENDED. + * Added VoipTunnelSuspendInfoT. + * Added iNumSuspended field to VoipTunnelClientT. + + Added support for a unique game identifier that can be specified when creating the game in the + voiptunnel. New API functions were added to allow users to work with this identifier. Related public + api changes: + * Added VoipTunnelGameListAdd2(). + * Added iGameIdx input param to VoipTunnelClientListDel(). + * Added VoipTunnelGameListMatchIndex(). + * Added VoipTunnelGameListMatchId(). + + DirtySDK (PS4) + DirtyNet + Added missing definition for INADDR_LOOPBACK for PS4, needed in protohttpserv. + + VoipHeadsetPS4 + Added the ability to configure voice lib thread attributes. You can override the settings with your + Blazeserver util.cfg. + + DirtySDK (PS4/XboxOne) + NetConn + Added a new 'eusr' selector that returns the IEAUser object at a given index. + + DirtySDK (XboxOne) + DirtyNetXboxOne + Added functionality to remap IPv6 addresses to existing IPv4 virtual addresses. It was demonstrated that + this update is possible in a cable pull scenario. + + ProtoMangleXboxOne + Added processing for updates of secure associations which the are already in a completed state to update + the IPv6 address mappings. + + Voip + Added bad reputation auto muting support in order to better satisfy XR-086. This feature will be on by default. + +Changes/fixes in this release: + DirtySDK (All) + Build + Fixed doc build errors. + + Fixed warnings generated by VS2015 Update 1 + + Changed the build scripts to use the config-vs-version property to better handle changing between versions + of Visual Studio. + + ConnApi + Fixed how we increment the client count. + + Fixed the 'cadr' selector to return the prototunnel address used. + + Removed CONNAPI_STATUS_CLSE as it is no longer used. + + Fixed an issue that caused the previous mute settings not to be properly retained when resuming a voip + connection. + + Fixed an issue where a game group would use the wrong voip port when joining a game session, then a + game group, then leaving the game session. + + Fixed ConnApiControl 'adve' selector, when disabling advertising, to also destroy the NetGameUtil ref + if it has already been created. + + DirtyCert + Fixed to not issue a redundant preload request if a preload request is already in the queue. + + Fixed a bug where a successful preload triggered by a validation failure would result in a failed + transaction, because the subsequent on-demand request would be considered to fail due to adding a + redundant CA. + + DirtyNet + Fixed to include missing return in 'pque' selector that caused misleading debug warning. + + Changed packet queue max size from 127 to 1024. + + JsonFormat + Fixed _JsonUpdateHeader() to prevent buffer offset from exceeding the buffer size during an overflow + condition. This fixes a possible memory stomp on a subsequent call to a JsonFormat method. + + JsonParse + Fixed a bug when executing a JsonFind against an element in an unclosed array; the find would skip past the + end of the parse buffer data and access uninitialized memory. + + Fixed a bug where executing a JsonFind against an unclosed element, containing a name but no value, would + result in returning a pointer to the beginning of the Json buffer. + + Fixed some other cases where the parse buffer could be accessed after the terminator. + + Fixed one issue each in the decode and escape lookup tables. + + Fixed to ensure after calling JsonParse that the parse buffer is always zero terminated. If this is not + possible, zero is returned. + + Changed name of JsonGetListItemEnd to JsonGetObjectEnd to better match JSON naming conventions. A #define + wrapper supporting the old name is included to maintain compatibility with previous code. + + NetConn + Fixed documentation error in header for NetConnIdle. + + Plat-time + Fixed a bug in weekday calculation in ds_secstotime. + + ProtoHttpManager + Fixed to use consistent modulename as prefix to debug output. + + ProtoHttpServ + Removed code in _ProtoHttpServUpdateRecvBody() that set the received body flag true when receiving a + chunked transfer without any initial data. + + Changed the ProtoHttpServControl('time') selector to set the request timeout. + + Fixed a typo in the default idle timeout value, which was supposed to be five minutes but was in fact much + longer. + + Changed size of ProtoHttpServRequestT.strContentType[] from 32 to 64 to match what we use for + ProtoHttpServResponseT + + Changed the code in the _ProtoHttpServParseHeader to skip all the URI / TYPE information that is in the header + This fixes an issue with skipping the first valid header value when parsing + + ProtoTunnel + Fixed two issues when attempting to switch keys. The first issue was on the side receiving a packet + encrypted by the new key, in ProtoTunnelValidatePacket(), which was not using the new crypto state to try + decrypting the packet. The second issue was on the sending side, where a new HMAC was not being calculated + when the new key was chosen. These issues caused failures in scenarios with players in a game and a + gamegroup, when players left either the game or the gamegroup, and could not maintain their p2p + connections. + + Fixed an issue where a large stream offset could cause a rematch to fail due to out-of-order discard. The + send stream offset is now reset to zero when a new key is selected, matching the behavior of the rematch + code on the receive side, which already assumes a stream offset of zero. + + Fixed possible issues with crypto setup by making sure send/recv states are both initialized all of the + time when switching keys. + + Fixed a bug which broke a reconnection scenario where one side of a connection was already active, while + the other was not. The bug was due to an incorrect change of hard-coded return values in + _ProtoTunnelDecryptAndValidatePacket2(), introduced along with the addition of ProtoTunnel 1.1 protocol + support. + + Removed usage of hard-coded values. Replaced with explicit defines for packet decrypt flow. + + Changed _ProtoTunnelDecryptAndValidatePacket() to return a VALIDATE error if an attempt to recover an out + of order packet fails. + + Changed to implementation to simplify error returns from _ProtoTunnelRecvData() and error handling of + values returned by that function in _ProtoTunnelRecv(). + + Fixed a bug in _ProtoTunnelFindTunnel(), where a failed match of an inactive tunnel would print an error + message, but continue on with the tunnel index as if the match had succeeded. This would cause some + parameters (port, addr) to be updated incorrectly before the packet was subsequently rejected, which was + mostly harmless but could cause some confusing temporary side effects particularly in the debug printing. + + Fixed various documentation and debug printing items. + + ProtoWebSocket + Fixed some documentation errors regarding several TODOs in the code. + + VoipCommon + Fixed a potential memory stomp when setting voip channel information for a user index which is out of range. + + Fixed the channel selection functions to properly include attempts to modify the VOIP_SHARED_USER_INDEX. + + VoipGroup + Fixed a bug during the transition from a gamegroup to a game so that the proper mute status from the game + group would transfer over into the game. + + VoipTunnel + Remove support for the 'dcst' control which was used to set a DirtyCast hack. We now support this + functionality by default as there exists no configuration we were disable the hack. + + Fixed a possible memory leak detected by Coverity, in a situation when trying to create a VoipTunnel object + and running out of memory. + + DirtySDK (iOS) + Plat-time + Fixed broken NetPrintf time stamps. + + DirtySDK (PC) + DirtyNetWin + Fixed the usage of the socket select() function to be compatible with Berkley sockets. + + DirtySDK (XboxOne) + Build + Removed support for the XboxOne ADK + + DirtyNetXboxOne + Fixed the usage of the socket select() function to be compatible with Berkley sockets. + + NetConnXboxOne + Fixed an issue where token acquisition did not reset properly after a cable pull and a reconnect, leading + to a report that the token acquisition failed when it had not been attempted. + +EADP Game Services DirtySDK 2015 Winter 2.0.0 - October 22, 2015 + + This SDK release was built using the following compiler packages: + + EA Config 2.25.03 + Framework 3.32.02 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 6.2.13332.0-proxy + capilano_config 1.02.00 + Kettle kettlesdk 3.008.041-proxy + kettle_config 1.03.01 + iPhone ios_config 1.01.05 + iphonesdk 7.0-proxy-5 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.12.01-proxy + Win32/WinRT/WinPRT + DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 11.0.50727-1-proxy + WindowsSDK 8.0.50727-1-proxy + WindowsPhoneSDK 8.0.9900-proxy + + *** Important - DIRTYSDK_IEAUSER_ENABLED compile switch no longer supported *** + Deprecated DIRTYSDK_IEAUSER_ENABLED compile switch such that IEAUser integration can no longer be turned off at + compile time when using DirtySDK NetConn for PS4 or XboxOne. + + *** Important - Gen 3 platform specific code has been removed *** + Gen3 support was deprecated in 15.1.1.1.0. The remaining Gen 3 specific code have now been removed. + +New in this release: + DirtySDK (All) + Build + Added support for structured xml libs-external when Framework is version 3.33.00 or higher. + + Added support for Visual Studio 2015. To enable support the vsversion property needs to be + set to "2015" when building as the new warning suppressions are not backwards compatible. + + Added new build property dirtysdk-contrib that allows enabling the contribs to build Without + samples. If dirtysdk-contrib does not need to be specified when dirtysdk-samples is true. + + CryptSTP1 + Removed CryptSTP1 support. + + Displist + Removed Displist support. + + Hasher + Removed Hasher support. + + NetGameLink + Added verbosity setting to reduce QoS data spam in the logs unless desired. + + Added the reception time in ticks of the packet to NetGamePacketHeadT metadata. + + ProtoHttpServ + Added a new callback for processing the incoming header. This callback allows us to opt out of further + processing if we know that request data is malformed based on header information. + + Added support for handling the 'Expect: 100-continue' header if sent by clients. ProtoHttpServ will always + just send back the 100 status code and will not reject requests based on the expect header. If a payload is + too large it can be rejected using the new header processing callback. + + ProtoAries + Removed ProtoAries support. + + ProtoHttpUtil + Added support for decoding url-encoded strings. + + Added support for parsing query parameters similar to what we use for parsing header information. + + DirtySDK (Android) + Plat-time + Added Android implementation to ds_plattimetotime(). + + DirtySDK (PS4) + DirtySessionManagerPS4 + Added support for ps4 session changeable data. + + Added new server-driven mode (all operations that are executed from a server are now disabled, i.e. + everything except what is necessary for invitation). DirtySessionManagerBinaryHeaderT and + DirtySessionManagerChangeableBinaryHeaderT are now public (PS4 only). This is to enable a compile time + check to ensure the Blaze Server's structs are equivalent to those. C++ static assert are used in the + Blaze client code (game manager) and they rely on those structures being public. + + Added new status selectors: + 'gcda' - to get the active session's changeable binary data size + 'gpcd' - to get the pending session's changeable binary data size + 'gagm' - to get the active changeable data game mode + 'gpgm' - to get the pending changeable data game mode + 'gagt' - to get the active binary data game type + 'gpgt' - to get the pending binary data game type + 'lsts' - to get the last status code returned from the calls to the Sony APIs + + Added new control selectors: + 'gchg' - to queue getting the changeable binary data into the pending session + 'serv' - to turn on server-driven session - in that mode, this client tech no longer honors any + client request for operations that are now owned by the server + 'sess' - to set session identifier (server-driven mode only) + 'spgm' - to set the pending session game mode + 'spgt' - to set the pending session game type + + DirtySDK (PS4/XboxOne) + NetConn + Added a new status selector 'eusr' which will return the IEAUser given a dirtysock user index. + + VoipHeadset + Added new status selector 'sact' to determine if the shared device is active (Kinect/PS Camera). + +Changes/fixes in this release: + DirtySDK (All) + Build + Removed the AddDirtySDKLibsToModule task, external libraries are now added to the + package.DirtySDK.dirtysock.libs.external fileset instead. + + Fixed 'NULL is defined to be 0' warning in platform.h by using #if defined instead of #ifdef. + + Removed the DIRTYCODE_API from typedefs, definitions do not need to be exported for dynamic libraries. + + Fixed an issue with how our C++ filesets are organized which caused files to be built both static and + dynamic instead of based on the build configuration. + + NetGameDist + Fixed NetGameDistInputQueryMulti() and NetGameDistInputCheck() to always poll NetGameDistUpdate() for + new packet data if no remote inputs are available. Previously, NetGameDistUpdate() would not be polled + if there were no local input packets queued for sending, but this behavior had a negative impact in + multi mode, requiring the caller poll NetGameDistUpdate() themselves if they had no inputs to send. To + fix that issue and simplify the code, the (minor) optimization of not calling NetGameDistUpdate() if no + local input packets are queued, when in non-multi mode, was removed. + + Fixed NetGameDistInputQueryMulti() to be safer in the event the caller calling the function following a + queue overrun. In the event of a queue overrun it is possible to lose multi inputs that have a delta + value that is not one (the default) assigned. A delta greater than one that is lost will point the + paired input queue pointer into unallocated buffer space, resulting in an invalid input with a length + of zero. This would result in a memcpy of -1 bytes which would result in a memory stomp and subsequent + crash. This scenario is now detected and handled safely; although the caller should still detect the + overflow error and shut the game down, this condition will no longer cause a crash. + + Fixed NetGameDistInputCheck() to return "ready to receive" in multi mode if remote inputs are + available, regardless of whether there are local inputs queued for sending or not. This issue would + cause titles calling NetGameDistInputCheck() to incorrectly not receive data that was available if they + were not sending a steady stream of inputs themselves. + + ProtoHttpServ + Changed to update the data that is based through the various callbacks for handling the request and + responses. Instead of using the same type, specific request and response types have been created. + + Changed the read/write callbacks to be receive/send to be clearer for integrators. + + Changed the request callback to allow to be called continuously if more processing time is needed based on + the result. + + Changed the logging callback to no longer return a result. + + ProtoMangle + Fixed a Valgrind warning by initializing a variable and preventing the possibility of copying garbage data + to a buffer. + + ProtoTunnel + Removed 1.0 protocol support and remaining Xbox 360 code. + + Removed ability to conditionally compile out HMAC. + + Fixed issue with _ProtoTunnelFindTunnel() not detecting a port change during handshake. + + Voip + Removed Gen3 only parameter for VoipStartup(). + + Changed the way we update voip channel settings. We now update it right when we get a ping and mic packet + with the new channel information instead of in voip idle. This also addresses potential multi-threading + issue with applying channel configs. + + VoipCommon + Fixed shared channel configs not being recalculated after a channel reset. + + Fixed a situation where channel configs are applied before we have any registered user. + + VoipConnection + Fixed a wrong usage of ds_memcpy. + + VoipGroup + Fixed an issue with mute flags being inconsistent or reset when player join both a game group and a game + session. + + Fixed an issues with mute flag consistency across multiple voip groups when a user is a part of more than + one at the same time. + + Fixed an issue where joining a second voip group or leaving an additional voip group would mute or unmute + all players respectively. + + Changed 'umic' and 'uspk' control selectors to deprecated. + + WebOffer + Changed the signature of WebOfferGetBusy2 to take the strDefaultMessage as a const char * instead of char * + to prevent a writable strings warning on PS4. + + DirtySDK (PC) + ProtoPingPC + Fixed an issue on Windows XP where the ProtoPingIcmpResponseT structure was too small (the size required + turns out to not be documented explicitly). Adding a 32 byte buffer to the structure resolved the issue. + + DirtySDK (PS4) + DirtyNetPS4 + Changed implementation such that socket receive thread now runs at high priority. + + DirtySessionManagerPS4 + Changed the dirtysessionmanger callback not to block when we have dirtywebapi queuing enabled. + + DirtyWebApiPS4 + Fixed a race condition where the webapi thread sometimes would miss the signal to wake up. + + NetConnPS4 + Removed internal automatic detection of system users by NetConn. Usage of NetConnAddLocalUser() and + NetConnRemoveLocalUser() is now mandatory. Implicit: Also removed 'cuis' and 'csiu' status selectors + for converting system index into dirtysdk index (or opposite). + + Changed the method to determine what error state NetConn enters to more accurately reflect the reason we + could not go online in a multiple user scenario. + + VoipPS4 + Changed implementation such that voip thread now runs at high priority. + + VoipHeadsetPS4 + Fixed initialization of voice lib thread priorities. sceVoiceStart() and sceVoiceCreatePort() are now + being called from dedicated ephemeral high-priority threads to force internal Sony threads to also + execute as high priority threads. This bug fix is required to eliminate crackling voice during heavy + processing scenarios (game play). + + Changed voice lib initialization to comply with voice lib doc and to mimic what is done in Sony samples. + + Fixed _VoipHeadsetUpdateDecoderConnections() not being called when pHeadset->bSharedPrivilege changes. + + Changed implementation to flush the internal buffer of the decoder port when it is no longer connected to + any speaker port. This is required to avoid "old voice" being played back when the decoder port is being + reconnected to a speaker port. + + DirtySDK (Unix) + DirtyNetUnix + Fixed to prevent the logging of ENOTCONN errors when shutting down the socket to prevent unnecessary spam. + + DirtySDK (XboxOne) + NetConnXboxOne + Removed internal automatic detection of system users by NetConn. Usage of NetConnAddLocalUser() and + NetConnRemoveLocalUser() is now mandatory. Implicit: Also removed 'cuis' and 'csiu' status selectors + for converting system index into dirtysdk index (or opposite). + + Refactored NetConnXboxOne to reduced complexity. + + ProtoHttpXboxOne + Refactored the status function to be consistent with other platforms. Certain selectors are only handled + when the state of the module is ST_BODY or ST_DONE. + + ProtoMangleXboxOne + Fixed _ProtoMangleRemoteHostAddRef() and _ProtoMangleRemoteHostDelRef() such that multi-threaded usage of + refRemoteHostRefCountMap is now guarded by the critical section. + + Fixed implementation to now try-catch around usage of method used to compare two secure device addresses to + gracefully handle rare cases where a COMException is thrown. + + Fixed implementation to now try-catch around SecureDeviceAddress::FromBytes(). + + UserApiXboxOne + Changed to using the AsyncOp refs to track the progress of requests. This prevents an issue where the + previous bitfield might get stomped on in certain conditions and makes the code clearer to understand. + + VoipXboxOne + Changed to move VoIP state destruction to NetConnIdle shutdown callback. This move is because the VoIP + thread can become blocked in Microsoft Chat code for over ten seconds in certain loss-of-network scenarios. + This was causing long stalls when trying to VoipShutdown() upon loss of network. Also added guard on + VoipStartup() to ensure it is not called in a state where the module has not been shut down (VoipShutdown() + already had the corresponding guard on the module having been started). + + +EADP Game Services DirtySDK 2015 Winter 1.1.0 - July 09, 2015 + + This SDK release was built using the following compiler packages: + + EA Config 2.25.01 + Framework 3.32.02 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 6.2.12999.0-1-proxy + capilano_config 1.01.02 + Kettle kettlesdk 2.508.081-proxy + kettle_config 1.03.00 + iPhone ios_config 1.01.05 + iphonesdk 7.0-proxy-5 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.12.01-proxy + Win32/WinRT/WinPRT + DotNet 4.0 + DotNetSDK 2.0-4 + VisualStudio 11.0.50727-1-proxy + WindowsSDK 8.0.50727-1-proxy + WindowsPhoneSDK 8.0.9900-proxy + + *** Important - Gen3 platforms (PlayStation 3 and Xbox 360) no longer supported *** + The Winter 1.0.x stability branch should be used for any further development on these platforms. + +New in this release: + DirtySDK (All) + Build + Removed Gen3 Support. + + ConnAPI + Added support to gate the voip connection based on QoS validation. The 'estv' selector can be used to + ungate the voip connection. + + ProtoHttp + Added returning of http status code as part of the 'hres' status selector. + + ProtoSSL + Added signature_algorithms hello extension support, enabled by default. Some servers require this + extension to be present before accepting a connection request. + + Added validation of supported signature algorithms upon receiving a CertificateRequest handshake message. + + XmlParse + Added support for 64-bit integers via XmlContentGetInteger64 and XmlAttribGetInteger64. + + DirtySDK (PC) + NetConnWin + Added support querying for MAC address with valid IP using the 'ethr'/'macx' when non-zero is passed into + iData. + + DirtySDK (PS4) + DirtyNetPS4 + Added 'spam' selector support to change debug verbosity. + + NetConnPS4 + Added an error message for when PRX is not loaded for the sceNpAuthCreateAsyncRequest call. + + VoipHeadsetPS4 + Added support for the following bitrates: 14400, 16000 and 22533. The default bitrate has been increased + from 7300 to 16000. + +Changes/fixes in this release: + DirtySDK (All) + Build + Changed DirtySDK build scripts to use Framework Structured XML. + + DirtyCert + Changed requesting certificates use format state field with "ST=" instead of "S=", which is the default + OpenSSL nomenclature. + + NetConn + Changed NetConnDisconnect() to also abort UPnP operations. + + ProtoHttp + Changed default PUT/POST behavior to not reuse a previous connection (keep-alive). When making a PUT or + POST request and utilizing a keep-alive connection, it is possible for the server to close the connection + and for that connection close to be "in flight" when issuing the request. This results in the request + being failed when the connection close is received. Because a PUT or POST request can result in modifying + server state, it may not be automatically re-issued. Not using keep-alive avoids this complication. A new + ProtoHttpControl('rput') selector has been added to disable this behavior if the caller is willing to deal + with this complication in trade for getting connection reuse on a PUT/POST. + + ProtoSSL + Fixed incorrect CertificateVerify formatting when using TLS1.2. This fixes client certificate support that + was previously broken when connecting to a server supporting TLS1.2. + + Fixed an error with 'hres' selector not returning the right hresult in some cases. + + ProtoTunnel + Fixed diagnostic output in _ProtoTunnelFindTunnel(), when re-matching addr/port, to include correct tunnel + index instead of the max tunnel index. + + DirtySDK (PC/XboxOne) + DirtyLibWin + Fixed debug print on QueryPerformanceCounter() failure that was comparing against the wrong result code. + + DirtySDK (PS4) + PrivilegeApiPS4 + Fixed a logical error that prevented a dialog from being appropriately terminated when aborted and the + reference is not destroyed. + + DirtyWebApiPS4 + Fixed an issue where we were using the wrong critical section for handling errors which ended up in a + deadlock if another request was made before the response was handled. + + DirtySessionManagerPS4 + Changed 'gses' control selector to now guard against passing in "INVALID". + + VoipHeadsetPS4 + Fixed the wrong user index being set when polling for voice. + + DirtySDK (PS4/XboxOne) + NetConn + Added ability to pass -1 into NetConnRemoveLocalUser(), in which case an internal lookup will be performed. + + Added a new selector 'iusr' than can be used to retrieve the index given the IEAUser UserId. + + VoipHeadset + Fixed voipheadsetDeleteLocalUser() to also clear the last headset status. + + Fixed a situation where channel configs are applied before we have any registered user with voipheadset. + + DirtySDK (PS4/Unix) + DirtyNet + Fixed SocketConnect() to copy the remote address when performing an non-blocking connect + (SCE_NET_EINPROGRESS/SYS_NET_EINPROGRESS/EINPROGRESS) for ps4/ps3/unix. + + DirtySDK (Unix) + DirtyLibUnix + Fixed NetTickUsec() to use proper scale for seconds. + + DirtySDK (XboxOne) + NetConnXboxOne + Changed the environment to be mapped to the sandbox ID. If it can't be mapped it will default back to TMS + RETAIL -> PROD + CERT -> CERT + EARW.1 -> TEST + +EADP Game Services DirtySDK 2015 Winter 1.0.1 - June 05, 2015 + + This SDK release was built using the following compiler packages: + + EA Config 2.19.00 + Framework 3.25.00 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 6.2.12710.0-1-proxy + capilano_config 1.01.00 + Kettle kettlesdk 2.508.051-proxy + kettle_config 1.02.00 + iPhone ios_config 1.01.05 + iphonesdk 7.0-proxy-5 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.07.00 + PS3 ps3sdk 470.001-lite + PlayStation3NpEx 470.001 + Win32/WinRT/WinPRT + DotNetSDK 4.0 + VisualStudio 11.0.50727-proxy + WindowsSDK 8.0.50727-proxy + WindowsPhoneSDK 8.0.9900-proxy + Xenon XenonSDK 2.0.21256.3-proxy + + *** Important - ProtoTunnel format modified - backward compatibility broken *** + Th Winter 1.0 release of DirtySDK introduced a new ProtoTunnel protocol version - 1.1. By default, this + is the version used for all platforms except for Xbox 360. ProtoTunnel v1.1 is not compatible with v1.0, + and Xbox 360 is not compatible with v1.1. Please make sure to rebuild any game servers in addition to + game clients when upgrading to this release from a version older than Winter 1.0. + +New in this release: + DirtySDK (PS4) + NetConnPS4 + Added NetConnRegisterNpStateCallback() to receive callbacks directly from NP State events. + +Changes/fixes in this release: + DirtySDK (All) + NetGameLink + Fixed a rare latency estimation issue at start of connection when local and remote clocks are close to but + not quite synchronized. + + DirtySDK (PC) + VoipPC + Fixed VoipCommonUpdateRemoteStatus() not being invoked from the VoipThread. + + Fixed bPrivileged not defaulting to TRUE. + + VoipCommon + Removed unnecessary PC specificities. + + DirtySDK (PS3) + VoipHeadsetPS3 + Fixed implementation to no longer wait for the user to be in a participating state for acquiring and + playing voice. Without this change, there is some potential for garbled voice at connection establishment. + + DirtySDK (PS4) + VoipHeadsetPS4 + Changed VoIP muting based on privileges back to be the most restrictive between local users. It will + be reverted back to be muted on a per player basis once Sony provides us a way to check whether VoIP + is being played back on the TV or headsets. + + DirtySDK (XboxOne) + ConnApi + Fixed _ConnApiInitClientConnectionState() to not reset ip address if it was already resolved (required for + host migration to succeed with peer hosted topology when p2p voip is active before host migration). + + NetConnXboxOne + Fixed the value of the internet connectivity state to reset after a resume to prevent the user from getting + stuck in an offline state after a NetConnDisconnect()/NetConnConnect(). + + UserListApiXboxOne + Fixed the queueing of UserListApiIsFriendAsync() requests which return success codes but then behaved + strangely when more than one request was active. + + Fixed a possible memory leak on shutdown. + + DirtySDK (PC/PS3/Unix/XboxOne) + DirtyLib + Fixed a rare thread safety issue in SocketLookup around + refcounting + + +EADP Game Services DirtySDK 2015 Winter 1.0.0 - April 9, 2015 + + This SDK release was built using the following compiler packages: + + EA Config 2.19.00 + Framework 3.25.00 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 6.2.12128.0-proxy + capilano_config 1.01.00 + Kettle kettlesdk 2.000.121-proxy + kettle_config 1.02.00 + iPhone ios_config 1.01.05 + iphonesdk 7.0-proxy-5 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.07.00 + PS3 ps3sdk 450.001-lite + PlayStation3NpEx 450.001 + Win32/WinRT/WinPRT + DotNetSDK 4.0 + VisualStudio 11.0.50727-proxy + WindowsSDK 8.0.50727-proxy + WindowsPhoneSDK 8.0.9900-proxy + Xenon XenonSDK 2.0.21256.3-proxy + + *** Important - Please update your QOS servers from your Blaze server configurations *** + The changes to the qosapi requires the new version of the QOS servers in order to function properly. + Contact GOSOPS for information about the most up to date QOS servers. + + *** Important - ProtoTunnel format modified - backward compatibility broken *** + This release of DirtySDK introduces a new ProtoTunnel protocol version - 1.1. By default, this is the + version used for all platforms except for Xbox 360. ProtoTunnel v1.1 is not compatible with v1.0, and + Xbox 360 is not compatible with v1.1. Please make sure to rebuild any game servers in addition to game + clients when upgrading to this release. + +New in this release: + + DirtySDK (All) + ProtoTunnel + Added support for the new 1.1 version of the ProtoTunnel protocol, with the following features: + - Introduces backwards compatibility for future protocol versions. + - Control packet data data used in handshaking is still authenticated, but is now unencrypted. + - Adds fast matching (and re-matching, when the source addr and/or port changes) of packet data to the + correct endpoint. + - Version 1.0 can still be selected and is the default for Xbox 360 operation. Xbox 360 is not + compatible with 1.1 or later versions of the protocol. + - Version 1.0 is compatible with the previous ProtoTunnel protocol. + + CommUDP + Added a packet received flag which can be accessed through NetGameUtilStatus('pkrc'). The flag is stored in + ConnApiClientT GameInfo.uConnFlags. This part of the more specific join error task GOSREQ-184. + + DirtySDK (PS3) + NetConnPS3 + Added 'nper' selector to NetConnStatus() to allow the user to differentiate a user logging out from PSN + from PSN disconnect. + + DirtySDK (XboxOne) + NetConnXboxOne + Added a critical section to protect against NetConnStatus('xusr') being called from the voip thread. + + VoipHeadsetXboxOne + Added support for MS voip bystanders (cert requirement). + + DirtySDK (Xenon) + ConnApiXenon + Added ConnApiControl('xonl') which can be used to tell ConnApiXenon to no longer attempt game connectivity + but to only perform Xsession management. + + Added _ConnApiMoveToNextState() for improved logging of ConnApiXenon state transitions. + +Changes/fixes in this release: + + DirtySDK (All) + CryptRSA + Refactored to make modular exponentiation an iterative process. This allows it to be spread out over + multiple updates, which is useful as the operation can take significant amounts of clock time and is + blocking. As part of this change, roughly 12k of temporary state that used to be on the stack is now + moved to the RSA state. + + Switched to use 64-bit math on Linux and PS4, improving execution time on those platforms. + + DirtyCert + Switched to new gosca.ea.com endpoint using certificate issued against GOS2015 CA. + + DirtySessionManager + Renamed DirtySessionManagerComplete() to DirtySessionManagerGetSecureIp(), and made that new function + xenon-only. + + NetConn + Changed UPnP initialization: if peerport netconn param is not specified, netconn now configures + protoupnp with default port 3659 instead of default port 0. + + NetGameDist + Fixed _strNetGameDistDataType to also cover GMDIST_DATA_NODATA. + + ProtoHttp + Changes to harden protohttp against multi-threaded calls into update, send, and recv functions from + interfering with one another. + + ProtoSSL + Enabled ServerName Hello Extension by default. + + Refactored to take advantage of iteratve modular exponentation for SendClientKeyExchange and + SendCertificateVerify operations. + + Switched CA provider flag from GOS2013 CA to GOS2015 CA. This allows the new DirtyCert endpoint to + provision CA certificates. + + QosApi + Refactored qosapi to improve maintainability, this is extensive changes throughout the + implementation. + + Changed qosapi so that the different phases of the qos process (latency, bandwidth, firewall) + do not have dependencies on one another. In the past firewall could only be done after + bandwidth, and qos phases would have an impact on the error status of other qos phases. + + Changed qosapi to use the new ip reported by the server rather than the IP address discovered + by the DNS lookup. The old behavior prevented XboxOne from successfully querying the NAT type, + due to DNS round robin issues. + + Removed the client to client methods QosApiListen() and QosApiRequest() from the qosapi. + + VoipConnection + Fixed incorrect array size for LocalUsers + + DirtySDK (PS3) + DirtyNetPS3 + Fixed a race condition that causes memory corruption when calling NetConnShutdown() while a + hostname lookup is in progress. NetConnShutdown() now waits until all hostname threads are + finished. + + DirtySDK (PS4) + DirtySessionManagerPS4 + Removed DirtySessionManagerComplete(). + + DirtySDK (XboxOne) + NetConnXboxOne + Fixed a case where the connectivity level can be stuck because of a missing notification from + Microsoft. When logging out after a resume event it seems that we sometimes do not receive an + XboxLiveAccess notification when logging back in after the resume. This fix forces a check of + the network state after 5 seconds. + + DirtySDK (Xenon) + ConnApiXenon + Changed implementation to guarantee that _ConnApiUpdateDemangle() will never be performed before + XSessionCreate() or XSesssionMigrateHost() completes (on both session host and session members). + Failure to enforce such a serialization can lead to 2 potential problems: secure address being + resolved with an invalid session id (if primary session creation is not complete) or connapi + exiting ST_MIGRATE state prematurely before firing SESSEVENT (if secondary session migration is + not complete). + + Changed ConnApiUpdate() such that _ConnApiUpdateDemangle() cannot be called when a session + creation or a session migration is pending. + + Removed usage of ConnApiSessionManagerStatus('idle') at the beginning of _ConnApiUpdateDemangle(): + no longer required now that calls to _ConnApiUpdateDemangle() from ConnApiUpdate() are limited to + states where it is allowed. + + Fixed _ConnApiInitClientList() to properly initialize pConnApi->GameServer.ClientInfo.strTunnelKey + (inspired from connapi.c) This fix is minor because the tunnel key is not used on xenon anyway. + + ConnApiXenonSessionManager + Enhanced implementation of ConnApiXenonSessionManagerComplete() to make it clear that + ConnApiXenonSessionManagerGetSecureIp() CANNOT return "in-progress". + + Fixed implementation of ConnApiXenonSessionManagerComplete() to return the right secure ip address for the + console specified as input. It used to return pRef->iAddrPrimary which is not guaranteed to match the + specified console. + + Renamed ConnApiXenonSessionManagerComplete() to ConnApiXenonSessionManagerGetSecureIp() + + Removed ConnApiXenonSessionManagerControl('conn') which was dead code because the 'conn' selector is no + longer supported in DirtySessionManager. + + DirtySessionManagerXenon + Renamed DirtySessionManagerComplete() to DirtySessionManagerGetSecureIp(). + + VoipXenon + Fixed implementation of VoipSetLocalUser() to properly setup the VoipUserT to be passed to + _VoipRegisterLocalTalker(). Without this fix, wrong xuids are registered with XHV resulting + in calls to pXHVEngine->SubmitIncomingChatData() intermittently failing with ERROR_INVALID_FUNCTION. + + +EADP Game Services DirtySDK 2015 Winter 0.3.0 - March 16, 2015 + + This SDK release was built using the following compiler packages: + + EA Config 2.19.00 + Framework 3.25.00 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.07 + Capilano CapilanoSDK 6.2.12128.0-proxy + capilano_config 1.01.00 + Kettle kettlesdk 2.000.121-proxy + kettle_config 1.02.00 + iPhone ios_config 1.01.05 + iphonesdk 7.0-proxy-5 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.07.00 + PS3 ps3sdk 450.001-lite + PlayStation3NpEx 450.001 + Win32/WinRT/WinPRT + DotNetSDK 4.0 + VisualStudio 11.0.50727-proxy + WindowsSDK 8.0.50727-proxy + WindowsPhoneSDK 8.0.9900-proxy + Xenon XenonSDK 2.0.21256.3-proxy + +New in this release: + + DirtySDK (All) + CommUDP + Added a metric to track packet loss prevented by a redundancy mechanic. + + DirtyLib + Added new NetTickUsec() to return microsecond-precision tick counter for profiling purposes. + + ProtoSSL + Added GOS2015 SHA2 CA. + + Added VeriSign 2008 Universal SHA2 CA. + + VoipConnection + Added support for piggy-backing reliable data over voip traffic (data packets and ping packets) + * Added VoipConnectionReliableBroadcastUser() and VoipConnectionReliableBroadcastOpaque(). + * Added LinkedReliableDataT. + * Extended VoipConnectionT and VoipConnectionlistT for reliable data support. + * Changed synopsis of VoipConnectRecvVoiceCbT. + + VoipHeadset + Added support for piggy-backing reliable data over voip traffic (data packets and ping packets) + * Changed to adapt to change in synopsis of VoipConnectRecvVoiceCbT. + + VoipPacket + Added support for piggy-backing reliable data over voip traffic (data packets and ping packets) + * Added new constants, and ReliableDataInfoT, ReliableDataT, ReliableAckT. + + VoipPriv + Added support for piggy-backing reliable data over voip traffic (data packets and ping packets) + * Changed implementation of VOIP_CopyUser() to use ds_memcpy_s() instead of ds_strnzcpy(). + * Changed VoipUserExT to be conditionally compiled in for XBOXONE only. + + DirtySDK (PS4) + DirtyWebApiPS4 + Added DirtyWebApiUpdate to issue callbacks from same thread as NetConnIdle. This feature can be enabled + or disabled with 'qcal' control selector. By default the feature is disabled. + + DirtySDK (XboxOne) + VoipXboxOne + Added support for piggy-backing reliable data over voip traffic (data packets and ping packets) + * Added call to VoipConnectionReliableBroadcastUser() from VoipActivateLocalUser(). + + DirtySDK (Xenon) + ConnApiXenon + Removed the dependency of the CONNAPI_MAXCLIENT from the function ConnApiUpdateDemangle(). + +Changes/fixes in this release: + + DirtySDK (iOS) + DirtyLib + Fixed incorrect critical section length for iOS64 + + DirtySDK (PC) + Build + Fixed compilation warnings for deprecated winsock2 calls with WindowsSDK 8.1 + + DirtySDK (PS3) + NetConnPS3 + Fixed the processing of the SCE_NP_MANAGER_STATUS_OFFLINE event when we are early on in the connection + process to prevent us from becoming stuck in +con when PSN is down. + + Fixed the handling of cellNetCtlNetStartDialogUnloadAsync() which was being treated as a synchronous + function. + + DirtySDK (PS4) + VoipPS4 + Changed the chat permission to applied on a player to player basis when not using the Playstation Camera + + DirtySDK (WinRT) + DirtyLibWinRT + Fixed incorrect thread priority initialization in NetLibCreate(). + + DirtySDK (XboxOne) + NetConnXboxOne + Fixed a build error that occurs when IEAUSER is not used. + + PrivilegeApiXboxOne + Fixed a memory stomp. + + DirtySDK (Xenon) + Build + Fixed a build break in the post build step of ATGFramework, which is used for the Xenon samples. + + ConnApiXenon + Fixed secure address resolution not being done for new connections to be established after host migration. + + FriendApiXenon + Fixed a potential issue where friend data could be populated in the incorrect slot. + + +EADP Game Services DirtySDK 2015 Winter 0.2.0 - January 28, 2015 + + This SDK release was built using the following compiler packages: + + EA Config 2.19.00 + Framework 3.25.00 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.05-1 + Capilano CapilanoSDK 6.2.11785.0-proxy + capilano_config 1.00.14 + Kettle kettlesdk 2.000.091-proxy + kettle_config 1.01.08 + iPhone ios_config 1.01.05 + iphonesdk 7.0-proxy-5 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.07.00 + PS3 ps3sdk 450.001-lite + PlayStation3NpEx 450.001 + Win32/WinRT/WinPRT + DotNetSDK 4.0 + VisualStudio 11.0.50727-proxy + WindowsSDK 8.0.50727-proxy + WindowsPhoneSDK 8.0.9900-proxy + Xenon XenonSDK 2.0.21256.3-proxy + +New in this release: + + DirtySDK (All) + PrivilegeApi + Added PRIVILEGEAPI_PRIV_INVALID privilege for ease of use. + + DirtySDK (PC) + VoipSpeex + Added the dll export tag to extern const VoipCodecDefT VoipSpeex_CodecDef. + + DirtySDK (PS3) + NetConnPS3 + Added logging for connection status changes. + + DirtySDK (PS4) + DirtySessionMangerPS4 + Added session lock support (prevent user from joining). A PS4 session can not be locked/unlocked + using the 'lock' selector. + + NetConnPS4 + Added logging for connection status changes. + + DirtySDK (XboxOne) + NetConnXboxOne + Removed automatic reset user list support when IEAUSER is used. + + Added logging for connection status changes + + UserApiXboxOne + Added Support for ProfileChanged Event. Please use the new UserApiNotifyDataT::ProfileUpdateData type + and UserApiNotifyTypeE::USERAPI_NOTIFY_PROFILE_UPDATE to listen for the event. + + DirtySDK (Xenon) + NetConnXenon + Added logging for connection status changes + +Changes/fixes in this release: + + DirtySDK (All) + ConnApi + Fixed a bug where ConnApi will tried to initialize clients with NULL machine address. + + DirtyMem + Fixed to export DirtyMemAlloc and DirtyMemFree when building as a DLL. + + Added include of to prevent malloc() and free() calls for default memory allocators compiling + out when building as a DLL. + + DirtyPng + Fixed a wrong usage of ds_strnzcpy that leads to a memory stomp. + + NetConn + Fixed NetConnStatus() not propagating the iData parameter to SocketInfo() for selectors that are passed + through. + + ProtoHttp + Fixed a memory pointer issue when sending chunked http transaction faster than the network can keep up + that could lead to potentially sending parts of uninitialized memory over the network. + + Changed to check status of open keep-alive connection in the DONE state, to detect server-closed + connections when they are closed, rather than waiting until the ProtoHttp ref is used in a new transaction + or destroyed. + + VoipConnection + Fixed an integration error. VoipConnectionStartup() will now have a max player check. If for any reason + voip system is not started properly voip setup will be skipped by Blaze. + + DirtySDK (iOS) + DirtyLib + Fixed wrong critical section length for iOS64. + + DirtySDK (PS4) + DirtyWebApiPS4 + Changed priority and scheduling setting of internal threads in favor of less aggressive threading + patterns to avoid negatively impacting game with unnecessary thread switches during game play. + + NetConnPS4 + Fixed an error which prevents users from going online if PSN does not respond quickly to the parental + settings query. + + PrivilegeApiPS4 + Fixed an integration error, which caused _PrivilegeApiUpdateDialogStatus callback not to be removed properly. + + DirtySDK (XboxOne) + ProtoMangleXboxOne + Fixed a potential crash if a client is removed and re-added while corresponding security association + creation is in progress. + + +EADP Game Services DirtySDK 2015 Winter 0.1.0 - December 4, 2014 + + This SDK release was built using the following compiler packages: + + EA Config 2.19.00 + Framework 3.25.00 + + Android androidsdk 18-2 + androidndk r9c + android_config 1.06.05-1 + Capilano CapilanoSDK 6.2.11785.0-proxy + capilano_config 1.00.14 + Kettle kettlesdk 2.000.071-proxy + kettle_config 1.01.07 + iPhone ios_config 1.01.05 + iphonesdk 7.0-proxy-5 + OSX UnixGCC 0.05.00 + osx_config 1.00.01 + Linux/Unix UnixClang 0.07.00 + PS3 ps3sdk 450.001-lite + PlayStation3NpEx 450.001 + Win32/WinRT/WinPRT + DotNetSDK 4.0 + VisualStudio 11.0.50727-proxy + WindowsSDK 8.0.50727-proxy + WindowsPhoneSDK 8.0.9900-proxy + Xenon XenonSDK 2.0.21256.3-proxy + + *** Important - Complementary integration guidelines available on Confluence *** + DirtySDK 2015 Winter Upgrade available here: + https://docs.developer.ea.com/display/dirtysock/DirtySDK+2015+Winter+Upgrade+Guide + +New in this release: + + DirtySDK (All) + ProtoSSL + Added support for AES-128-GCM-SHA256 and AES-256-GCM-SHA384 cipher suites. + + Added two obsolete alternate definitions for sha1withRSAEncryption for compatibility with some software + that generates certificates using these object IDs, for example makecert.exe in WindowsSDK. + + ProtoTunnel + Enhanced debug logging for the 'bnds' selector. + + Voip + Enhanced API for user registration to better support MLU (Multiple Local Users) scenarios. Every local user + should now be registered with the voip sub-system using VoipSetLocalUser() in order for its headset status + info to be available for queries. To additionally get fully functional voip, local users must be further + activated with VoipActivateLocalUser(), which promotes the user to a "participating" state. Basically any + user being pulled into the game should be a participating user. Associated public api impact: + * New synopsis for VoipSetLocalUser() + * New function to promote local users to "participating" state: VoipActivateLocalUser() + + VoipGroup + Enhanced VoipGroup api. It was re-aligned with changes to the voip api called out above. + * New function to promote local users to "participating" state: VoipGroupActivateLocalUser() + + DirtySDK (PC) + DirtyErrPC + Added DirtyErrPC implementation with pc-specific errors. + + ProtoPingPC + Added a new version of protoping for PC, using ICMPSendEcho2() API. This API allows use of ICMP when an + application is run without administrator privilege, which is required for use of RAW sockets employed with + protoping.c. + + DirtySDK (PS4) + VoipHeadsetPS4 + Enhanced _VoipHeadsetIsLocalVoiceFrameReady() to detect voice readiness quicker. + + Added '-pbk' and '+pbk' selectors to better support our voip channel feature under MLU scenarios. + + DirtySDK (WinRT/WintPRT) + DirtyLibWinRT + Enhanced NetPrintfCode() to use OutputDebugStringA() instead of OutputDebugStringW(). + + DirtySDK (XboxOne) + UserApiXboxOne + Added support for the 'avsz' selector. Now different profile pictures sizes can be fetched. + + VoipXboxOne + Added selectors: + VoipStatus 'pcac' - returns true if party chat is active + VoipStatus 'pcsu' - returns true if party chat is currently suspended + VoipControl 'pcsu' - takes iValue as a bool, TRUE suspend party chat, FALSE resume it + In order for VoipControl 'pcsu' to work you must add to the appxmanifest.xml: + + + + +Changes/fixes in this release: + + DirtySDK (All) + General + Replaced APIs deprecated by Microsoft with generic equivalents. + + ConnApi + Changed synopsis of ConnApiOnline(): it no longer has a pSelfAddr parameter. + + Changed ConnApiUserInfoT.uClientFlags to ConnApiUserInfoT.uFlags. + + Changed ConnApiClientInfoT.uMachineId to ConnApiCLientInfoT.uId. + + Removed multiple virtual machine mode: connapi 'mvtm' status and control selectors no longer exist. + The implementation is now multiple-virtual-machine capable by default. + + Removed ConnApiUserInfoT, ConnApiAddUser() and ConnApiRemoveUser() for all platforms except xbox360. + + Removed ConnApiFindClient(), then renamed ConnApiFindClientById() to ConnApiFindClient(). + + Removed ConnApiClientInfoT.strUniqueId + + Removed _ConnApiDefaultCallback() and made the debug logging client-centric instead of user-centric. + + DirtyNet + Changed all dirtynet files to log unsupported status selectors the same way. + + NetGameLink + Changed implementation to avoid code duplication: moved packet loss calculation logics into a static + function. + + Changed the names of several member variables in the NetGameLinkStatT structure. The changes are listed below: + bps to outbps + rps to outrps + nps to outnps + pps to outpps + rpps to outrpps + + ProtoSSL + Changed to request TLS1.2 instead of TLS1.1. Servers are still allowed to select any version from TLS1.2 + down to SSLv3. + + Changed to use 64-bit time for certificate expiration checking, so certificates created with expiration + times far in the future won't fail validation. + + Changed to harden protossl against multi-threaded use causing obscure secure failures. + + VoipConnection + Removed platform specificities in _VoipConnectionInit(). + + VoipPacket + Changed value of VOIP_PACKET_STATUS_FLAG_METADATA from 2 to 1. + + DirtySDK (PS4) + DirtyWebApiPS4 + Removed DirtyWebApiCreate()/DirtyWebApiCreateEx() and renamed DirtyWebApiCreateEx2() to + DirtyWebApiCreate(). + + Changed WebApi threads to only wakeup when there are jobs queued. + + VoipHeadsetPS4 + Fixed establishment of connections between a decoder port and all local speaker ports when a user is added + after a decoder was created. + + DirtySDK (XboxOne) + DirtyNetXboxOne + Fixed _SocketAddrMapGet() to no longer include the port value when trying to match an input ipv6 address + with an existing mapping. This change is required for multi-hub test bench to be functional on xboxone: + because hubs all bind to different game port, the inbound traffic source port may not be same as the ipv6 + address mapped when resolving secure device address for the local console. + + VoipHeadsetXboxOne + Deprecated support for pre-March2014 XDKs (_XDK_VER < 10698). + + Replaced VOIPHEADSET_VOICELIB_DEBUG with VOIPHEADSET_DEBUG. + + DirtySDK (Xenon) + ConnnApiXenon + Changed internal usage of some control selectors: moved call to ConnApiXenonManagerControl('self') + and VoipGroupControl('lusr') from ConnApiOnline() to _ConnApiInitClientList() + + Fixed connapixenon to properly update the remote tunnel port with the peer's internally bound port + when the internal address (i.e. internal to NAT scope) is used to reach that peer. + + Fixed a logical error forcing the voip connection attempt to fail early. diff --git a/src/thirdparty/dirtysdk/contrib/common/include/libsample/zfile.h b/src/thirdparty/dirtysdk/contrib/common/include/libsample/zfile.h new file mode 100644 index 00000000..a1959c46 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/include/libsample/zfile.h @@ -0,0 +1,143 @@ +/*H********************************************************************************/ +/*! + \File Zfile.h + + \Description + Host file operations. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 02/16/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _Zfile_h +#define _Zfile_h + +/*** Include files ****************************************************************/ + +#if (defined(DIRTYCODE_LINUX)) || (defined(DIRTYCODE_APPLEIOS)) +#include +#endif + +/*** Defines **********************************************************************/ + +#if (defined(DIRTYCODE_LINUX)) || (defined(DIRTYCODE_APPLEIOS)) +#define ZFILE_INVALID (NULL) +#else +#define ZFILE_INVALID (-1) //!< an invalid ZfileT handle +#endif + +#define ZFILE_OPENFLAG_RDONLY (1) +#define ZFILE_OPENFLAG_WRONLY (2) +#define ZFILE_OPENFLAG_RDWR (ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_WRONLY) +#define ZFILE_OPENFLAG_CREATE (4) +#define ZFILE_OPENFLAG_BINARY (8) +#define ZFILE_OPENFLAG_APPEND (16) + +#define ZFILE_SEEKFLAG_CUR (1) +#define ZFILE_SEEKFLAG_END (2) +#define ZFILE_SEEKFLAG_SET (3) + +#define ZFILE_ERROR_NONE (0) //!< no error +#define ZFILE_ERROR_FILEOPEN (-1) //!< generic error opening the file (reading or writing) +#define ZFILE_ERROR_FILECLOSE (-2) //!< generic error closing the file +#define ZFILE_ERROR_FILEWRITE (-3) //!< generic error occurred writing to the file +#define ZFILE_ERROR_FILEDELETE (-4) //!< generic error deleting the file +#define ZFILE_ERROR_FILESTAT (-5) //!< generic error trying to fstat a file +#define ZFILE_ERROR_FILERENAME (-6) //!< generic error renaming a file +#define ZFILE_ERROR_FILENAME (-7) //!< bad filename +#define ZFILE_ERROR_NULLPOINTER (-8) //!< null pointer passed in where data was expected +#define ZFILE_ERROR_NOSUCHFILE (-9) //!< file does not exist +#define ZFILE_ERROR_PERMISSION (-10) //!< permission denied (on opening/writing) + +#define ZFILE_PATHFILE_LENGTHMAX (512) //!< max length of a path/filename string + +/*** Macros ***********************************************************************/ + +#define ZFILESTAT_MODE_READ (0x01) //!< file has read flag set +#define ZFILESTAT_MODE_WRITE (0x02) //!< file has write flag set +#define ZFILESTAT_MODE_EXECUTE (0x04) //!< file has execute flag set +#define ZFILESTAT_MODE_PERMISSIONMASK (0x07) //!< mask for mode flags + +#define ZFILESTAT_MODE_FILE (0x10) //!< item is a file +#define ZFILESTAT_MODE_DIR (0x20) //!< item is a directory +#define ZFILESTAT_MODE_FILETYPEMASK (0x30) //!< mask for file type flags + +/*** Type Definitions *************************************************************/ + +typedef struct ZFileStatT +{ + int64_t iSize; //!< file size in bytes + uint32_t uTimeCreate; //!< file creation time, seconds since epoch + uint32_t uTimeAccess; //!< last access time, seconds since epoch + uint32_t uTimeModify; //!< last modification time, seconds since epoch + uint16_t uMode; //!< file mode (file/dir and R/W/X) + uint16_t uPad1; //!< pad out to even boundary +} ZFileStatT; + +#if !defined(DIRTYCODE_LINUX) && !defined(DIRTYCODE_APPLEIOS) +typedef intptr_t ZFileT; +#else +typedef FILE * ZFileT; +#endif + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Platform-specific implementations (Zfileplatform.c) +*/ + +// open a file +DIRTYCODE_API ZFileT ZFileOpen(const char *pFileName, uint32_t uFlags); + +// close a file +DIRTYCODE_API int32_t ZFileClose(ZFileT iFileId); + +// read from a file +DIRTYCODE_API int32_t ZFileRead(ZFileT iFileId, void *pData, int32_t iSize); + +// write to a file +DIRTYCODE_API int32_t ZFileWrite(ZFileT iFileId, void *pData, int32_t iSize); + +// seek in a file +DIRTYCODE_API int64_t ZFileSeek(ZFileT iFileId, int64_t iOffset, uint32_t uFlags); + +// delete a file +DIRTYCODE_API int32_t ZFileDelete(const char *pFileName); + +// get file status information +DIRTYCODE_API int32_t ZFileStat(const char *pFileName, ZFileStatT *pFileStat); + +// rename a file +DIRTYCODE_API int32_t ZFileRename(const char *pOldname, const char *pNewname); + +// create a directory +DIRTYCODE_API int32_t ZFileMkdir(const char *pPathName); + +/* + Platform-inspecific implementations (Zfile.c) +*/ + +// get file size +DIRTYCODE_API int64_t ZFileSize(ZFileT iFileId); + +// open a file and load it into memory and null-terminate (in case it is a text file) +DIRTYCODE_API char *ZFileLoad(const char *pFileName, int32_t *pFileSize, uint32_t bBinary); + +// save (overwrite) null-terminated data to a file +DIRTYCODE_API int32_t ZFileSave(const char *pFileName, const char *pData, int32_t iSize, uint32_t uFlags); + +#ifdef __cplusplus +}; +#endif + +#endif // _Zfile_h + diff --git a/src/thirdparty/dirtysdk/contrib/common/include/libsample/zlib.h b/src/thirdparty/dirtysdk/contrib/common/include/libsample/zlib.h new file mode 100644 index 00000000..3e1045c2 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/include/libsample/zlib.h @@ -0,0 +1,121 @@ +/*H********************************************************************************/ +/*! + \File zlib.h + + \Description + A simple console style test library that provides a basic + notion of processes, output routines and memory allocation. + Used to implement simple test programs in a unix-style + command-line environment. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 09/15/1999 (gschaefer) Initial Design + \Version 11/08/1999 (gschaefer) Cleanup and revision + \Version 03/17/2005 (jfrank) Cleanup and revision +*/ +/********************************************************************************H*/ + +#ifndef _zlib_h +#define _zlib_h + +/*** Include files ****************************************************************/ + +#include "DirtySDK/dirtysock/dirtylib.h" + +/*** Defines **********************************************************************/ + +#define ZLIB_STATUS_RUNNING ((signed)0x80000000) +#define ZLIB_STATUS_UNKNOWN ((signed)0x80000001) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct ZEnviron ZEnviron; +typedef struct ZContext ZContext; +typedef struct ZConsole ZConsole; +typedef int32_t (ZCommand)(ZContext *pArgz, int32_t iArgc, char **pArgv); + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create a new process environment. Parses command line and gets process ready to run. +DIRTYCODE_API ZEnviron *ZCreate(ZConsole *pConsole, const char *pCmdline); + +// destroy process environment +DIRTYCODE_API void ZDestroy(ZEnviron *pEnv); + +// execute a process within an existing environment. +DIRTYCODE_API void ZInvoke(ZEnviron *pEnv, ZCommand *pCmd); + +// get process id of current process +DIRTYCODE_API int32_t ZGetPid(void); + +// set current environment from context +DIRTYCODE_API void ZSet(ZContext *context); + +// let process signal it wants a callback (has not completed). +DIRTYCODE_API int32_t ZCallback(ZCommand *pCmd, int32_t iDelay); + +// give time to any existing processes that need it. +DIRTYCODE_API int32_t ZTask(void); + +// remove any environments containing complete processes. +DIRTYCODE_API void ZCleanup(void); + +// kill all active processes in preparation for shutdown +DIRTYCODE_API void ZShutdown(void); + +// allow a process to allocate persistant private context. +DIRTYCODE_API ZContext *ZContextCreate(int32_t iSize); + +// return tick count in milliseconds. +DIRTYCODE_API uint32_t ZTick(void); + +// put process to sleep for some period of time +DIRTYCODE_API void ZSleep(uint32_t uMSecs); + +// display output using standard printf semantics. +DIRTYCODE_API void ZPrintf(const char *pFmt, ...); + +// display output using standard printf semantics (no hook) +DIRTYCODE_API void ZPrintf2(const char *pFmt, ...); + +#if DIRTYCODE_LOGGING + #define ZPrintfDbg(_x) ZPrintf _x +#else + #define ZPrintfDbg(_x) { } +#endif + +// set zprint callback +DIRTYCODE_API void ZPrintfHook(int32_t (*pPrintfHook)(void *pParm, const char *pText), void *pParm); + +// show list of all process environments. +DIRTYCODE_API int32_t ZCmdPS(ZContext *pArgz, int32_t iArgc, char **pArgv); + +// kill an existing process. +DIRTYCODE_API int32_t ZCmdKill(ZContext *pArgz, int32_t iArgc, char **pArgv); + +// return status of current env, command. +DIRTYCODE_API int32_t ZGetStatus(ZEnviron *pEnv); + +// return status of process with specified pid +DIRTYCODE_API int32_t ZGetStatusPid(int32_t iPid); + +// get fourcc/integer from command-line argument +DIRTYCODE_API int32_t ZGetIntArg(const char *pArg); + +#ifdef __cplusplus +}; +#endif + +#endif // _zlib_h + + diff --git a/src/thirdparty/dirtysdk/contrib/common/include/libsample/zlist.h b/src/thirdparty/dirtysdk/contrib/common/include/libsample/zlist.h new file mode 100644 index 00000000..aafc2356 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/include/libsample/zlist.h @@ -0,0 +1,63 @@ +/*H********************************************************************************/ +/*! + \File zlist.h + + \Description + Generic list module for samples to use. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/26/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +#ifndef _zlist_h +#define _zlist_h + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +#define ZLIST_ERROR_NONE (0) //!< no error (success) +#define ZLIST_ERROR_NULLPOINTER (-1) //!< a null pointer ref was used +#define ZLIST_ERROR_FULL (-2) //!< sending/receiving list is full (msg dropped) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct ZListT ZListT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create a list object +DIRTYCODE_API ZListT *ZListCreate(int32_t iNumEntries, int32_t iEntrySize); + +// add an entry to the back of a data list +DIRTYCODE_API int32_t ZListPushBack(ZListT *pList, void *pEntry); + +// get an entry off the front of a data list +DIRTYCODE_API int32_t ZListPopFront(ZListT *pList, void *pEntry); + +// examine the front entry of a data list +DIRTYCODE_API void *ZListPeekFront(ZListT *pList); + +// erase and entire list +DIRTYCODE_API void ZListClear(ZListT *pList); + +// destroy a list object +DIRTYCODE_API void ZListDestroy(ZListT *pList); + +#ifdef __cplusplus +}; +#endif + +#endif // _zlist_h + diff --git a/src/thirdparty/dirtysdk/contrib/common/include/libsample/zmem.h b/src/thirdparty/dirtysdk/contrib/common/include/libsample/zmem.h new file mode 100644 index 00000000..c1275be5 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/include/libsample/zmem.h @@ -0,0 +1,48 @@ +/*H********************************************************************************/ +/*! + \File zmem.h + + \Description + ZAlloc (malloc) and ZFree (free) implementations for use on all platforms. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/16/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +#ifndef _zmem_h +#define _zmem_h + +/*** Include files ****************************************************************/ + +// include this so we can use zmemtrack alongside the zmem libraries +#include "zmemtrack.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// allocate some memory, uSize bytes in length +DIRTYCODE_API void *ZMemAlloc(uint32_t uSize); + +// free a previously allocated memory chunk +DIRTYCODE_API uint32_t ZMemFree(void *pMem); + +#ifdef __cplusplus +}; +#endif + +#endif // _zmem_h + diff --git a/src/thirdparty/dirtysdk/contrib/common/include/libsample/zmemtrack.h b/src/thirdparty/dirtysdk/contrib/common/include/libsample/zmemtrack.h new file mode 100644 index 00000000..82e6e75b --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/include/libsample/zmemtrack.h @@ -0,0 +1,62 @@ +/*H********************************************************************************/ +/*! + \File zmemtrack.h + + \Description + Routines for tracking memory allocations. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 02/15/2005 (jbrookes) First Version, based on jfrank's implementation. +*/ +/********************************************************************************H*/ + +#ifndef _zmemtrack_h +#define _zmemtrack_h + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +#define ZMEMTRACK_PRINTFLAG_TRACKING (1) //!< print more verbose output + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! logging function +typedef void (ZMemtrackLogCbT)(const char *pText, void *pUserData); + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// init Zmemtrack module +DIRTYCODE_API void ZMemtrackStartup(void); + +// shut down Zmemtrack module +DIRTYCODE_API void ZMemtrackShutdown(void); + +// set the logging callback +DIRTYCODE_API void ZMemtrackCallback(ZMemtrackLogCbT *pLoggingCb, void *pUserData); + +// track an allocation +DIRTYCODE_API void ZMemtrackAlloc(void *pMem, uint32_t uSize, uint32_t uTag); + +// track a free operation +DIRTYCODE_API void ZMemtrackFree(void *pMem, uint32_t *pSize); + +// print current tracking info +DIRTYCODE_API void ZMemtrackPrint(uint32_t uFlags, uint32_t uTag, const char *pModuleName); + +#ifdef __cplusplus +}; +#endif + +#endif // _zmemtrack_h + diff --git a/src/thirdparty/dirtysdk/contrib/common/source/pc/zfilepc.c b/src/thirdparty/dirtysdk/contrib/common/source/pc/zfilepc.c new file mode 100644 index 00000000..7cb2fb6a --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/source/pc/zfilepc.c @@ -0,0 +1,404 @@ +/*H********************************************************************************/ +/*! + \File zfilepc.c + + \Description + Basic file operations (open, read, write, close, size). + + \Notes + None. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 11/18/1004 (jbrookes) First Version + \Version 1.1 03/16/2005 (jfrank) Updates for common sample libraries +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include // _mkdir + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" + +#include "zfile.h" +#include "zlib.h" +#include "zmem.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function ZFileOpen + + \Description + Open a file. + + \Input *pFileName - file name to open + \Input uFlags - flags dictating how to open the file + + \Output + int32_t - file descriptor, or -1 if there was an error + + \Version 1.0 03/16/2005 (jfrank) First Version +*/ +/********************************************************************************F*/ +ZFileT ZFileOpen(const char *pFileName, uint32_t uFlags) +{ + char strMode[16] = ""; + FILE *pFile; + + if(pFileName == NULL) + return(ZFILE_INVALID); + if(strlen(pFileName) == 0) + return(ZFILE_INVALID); + + ds_memclr(strMode, sizeof(strMode)); + + // map zfile flags to Win32 Mode flags + if (uFlags & ZFILE_OPENFLAG_APPEND) + { + strcat(strMode, "a"); + if (uFlags & ZFILE_OPENFLAG_RDONLY) + strcat(strMode, "+"); + } + else if (uFlags & ZFILE_OPENFLAG_RDONLY) + { + strcat(strMode, "r"); + if (uFlags & ZFILE_OPENFLAG_WRONLY) + strcat(strMode, "+"); + } + else if (uFlags & ZFILE_OPENFLAG_WRONLY) + strcat(strMode, "w"); + if (uFlags & ZFILE_OPENFLAG_BINARY) + strcat(strMode, "b"); + + if ((pFile = fopen(pFileName, strMode)) != NULL) + { + return((ZFileT)pFile); + } + else + { + ZPrintfDbg(("zfilepc: error %d opening file '%s'\n", errno, pFileName)); + return(ZFILE_INVALID); + } +} + +/*F********************************************************************************/ +/*! + \Function ZFileClose + + \Description + Close a file + + \Input iFileId - file descriptor + + \Output int32_t - return value from fclose() + + \Version 1.0 11/18/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileClose(ZFileT iFileId) +{ + if (iFileId != ZFILE_INVALID) + { + return(fclose((FILE *)iFileId)); + } + else + { + return(ZFILE_ERROR_FILECLOSE); + } +} + +/*F********************************************************************************/ +/*! + \Function ZFileRead + + \Description + Read from a file. + + \Input *pData - pointer to buffer to read to + \Input iSize - amount of data to read + \Input iFileId - file descriptor + + \Output + Number of bytes read + + \Version 1.0 11/18/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileRead(ZFileT iFileId, void *pData, int32_t iSize) +{ + int32_t iResult; + + iResult = (int32_t)fread(pData, 1, iSize, (FILE *)iFileId); + + return(ferror((FILE *)iFileId) ? 0 : iResult); +} + +/*F********************************************************************************/ +/*! + \Function ZFileWrite + + \Description + Write to a file. + + \Input *pData - pointer to buffer to write from + \Input iSize - amount of data to write + \Input iFileId - file descriptor + + \Output + Number of bytes written + + \Version 1.0 11/18/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileWrite(ZFileT iFileId, void *pData, int32_t iSize) +{ + if ((iFileId == ZFILE_INVALID) || (pData == NULL) || (iSize == 0)) + { + return(ZFILE_ERROR_FILEWRITE); + } + return((int32_t)fwrite(pData, 1, iSize, (FILE *)iFileId)); +} + +/*F********************************************************************************/ +/*! + \Function ZFileSeek + + \Description + Seek to location in file. + + \Input iFileId - file id to seek + \Input iOffset - offset to seek to + \Input uFlags - seek mode (ZFILE_SEEKFLAG_*) + + \Output + int64_t - resultant seek location, or -1 on error + + \Version 03/16/2005 (jfrank) First Version +*/ +/********************************************************************************F*/ +int64_t ZFileSeek(ZFileT iFileId, int64_t iOffset, uint32_t uFlags) +{ + int64_t iResult; + int32_t iFlags=0; + + if (uFlags == ZFILE_SEEKFLAG_CUR) + iFlags = SEEK_CUR; + else if (uFlags == ZFILE_SEEKFLAG_END) + iFlags = SEEK_END; + else if (uFlags == ZFILE_SEEKFLAG_SET) + iFlags = SEEK_SET; + + iResult = _fseeki64((FILE *)iFileId, iOffset, iFlags); + iResult = _ftelli64((FILE *)iFileId); + return((iResult >= 0) ? iResult : -1); +} + + +/*F********************************************************************************/ +/*! + \Function ZFileDelete + + \Description + Delete a file. + + \Input *pFileName - filename of file to delete + + \Output int32_t - 0=success, error code otherwise + + \Version 03/23/2005 (jfrank) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileDelete(const char *pFileName) +{ + int32_t iResult; + + if(pFileName == NULL) + return(ZFILE_ERROR_FILENAME); + + iResult = remove(pFileName); + if(iResult == 0) + return(ZFILE_ERROR_NONE); + else + return(ZFILE_ERROR_FILEDELETE); +} + + +/*F********************************************************************************/ +/*! + \Function ZFileStat + + \Description + Get File Stat information on a file/dir. + + \Input *pFileName - filename/dir to stat + \Input *pStat + + \Output int32_t - 0=success, error code otherwise + + \Version 03/25/2005 (jfrank) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileStat(const char *pFileName, ZFileStatT *pFileStat) +{ + struct _stat FileStat; + int32_t iResult; + + // check for error conditions + if(pFileName == NULL) + return(ZFILE_ERROR_FILENAME); + if(pFileStat == NULL) + return(ZFILE_ERROR_NULLPOINTER); + + // get file status + iResult = _stat(pFileName, &FileStat); + + // check for some specific errors + if((iResult == -1) && (errno == ENOENT)) + return(ZFILE_ERROR_NOSUCHFILE); + else if(errno == EACCES) + return(ZFILE_ERROR_PERMISSION); + else if(iResult != 0) + return(ZFILE_ERROR_FILESTAT); + + // clear the incoming buffer + ds_memclr(pFileStat, sizeof(ZFileStatT)); + // copy from the PC-specific structures + pFileStat->iSize = FileStat.st_size; + pFileStat->uTimeAccess = FileStat.st_atime; + pFileStat->uTimeCreate = FileStat.st_ctime; + pFileStat->uTimeModify = FileStat.st_mtime; + // get the file modes + if(FileStat.st_mode & _S_IFDIR) + pFileStat->uMode |= ZFILESTAT_MODE_DIR; + if(FileStat.st_mode & _S_IFREG) + pFileStat->uMode |= ZFILESTAT_MODE_FILE; + if(FileStat.st_mode & _S_IREAD) + pFileStat->uMode |= ZFILESTAT_MODE_READ; + if(FileStat.st_mode & _S_IWRITE) + pFileStat->uMode |= ZFILESTAT_MODE_WRITE; + if(FileStat.st_mode & _S_IEXEC) + pFileStat->uMode |= ZFILESTAT_MODE_EXECUTE; + + // done - return no error + return(ZFILE_ERROR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function ZFileRename + + \Description + Rename a file. + + \Input *pOldname - old name + \Input *pNewname - new name + + \Output int32_t - 0=success, error code otherwise + + \Version 03/30/2005 (jfrank) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileRename(const char *pOldname, const char *pNewname) +{ + int32_t iResult; + + // check for error conditions + if((pOldname == NULL) || (pNewname == NULL)) + return(ZFILE_ERROR_NULLPOINTER); + + // rename the file + iResult = rename(pOldname, pNewname); + + if(iResult == 0) + return(ZFILE_ERROR_NONE); + else + return(ZFILE_ERROR_FILERENAME); +} + +/*F********************************************************************************/ +/*! + \Function ZFileMkdir + + \Description + Make a directory, recursively + + \Input *pPathName - directory path to create + + \Output + int32_t - 0=success, error code otherwise + + \Version 01/25/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ZFileMkdir(const char *pPathName) +{ + char strPath[1024], *pPath, cTerm = '\0'; + + // copy pathname + ds_strnzcpy(strPath, pPathName, sizeof(strPath)); + + // translate forward slashes to backward slashes + for (pPath = strPath; *pPath != '\0'; pPath += 1) + { + if (*pPath == '/') + { + *pPath = '\\'; + } + } + + // traverse pathname, making each directory component as we go + for (pPath = strPath; ; pPath += 1) + { + if (*pPath == '\\') + { + cTerm = *pPath; + *pPath = '\0'; + } + if (*pPath == '\0') + { + int32_t iResult; + if ((iResult = _mkdir(strPath)) != 0) + { + if (errno == ENOENT) + { + ZPrintfDbg(("zfilepc: could not create directory '%s'\n", strPath)); + return(-1); + } + if (errno == EEXIST) + { + ZPrintfDbg(("zfilepc: directory %s already exists\n", strPath)); + } + } + } + if (cTerm != '\0') + { + *pPath = cTerm; + cTerm = '\0'; + } + if (*pPath == '\0') + { + break; + } + } + return(0); +} diff --git a/src/thirdparty/dirtysdk/contrib/common/source/unix/zfileunix.c b/src/thirdparty/dirtysdk/contrib/common/source/unix/zfileunix.c new file mode 100644 index 00000000..5296a146 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/source/unix/zfileunix.c @@ -0,0 +1,472 @@ +/*H********************************************************************************/ +/*! + \File zfilepc.c + + \Description + Basic file operations (open, read, write, close, size). + + \Notes + None. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 11/18/1004 (jbrookes) First Version + \Version 1.1 03/16/2005 (jfrank) Updates for common sample libraries +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtylib.h" + +#include "zfile.h" +#include "zlib.h" +#include "zmem.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _ZFilePathTranslate + + \Description + Translate a path from dos-style to unix-style + + \Input *pPathName - path name to translate + \Input *pStrBuf - [out] output for translated path + \Input iBufLen - size of output buffer + + \Output + char * - pointer to translated path + + \Version 10/10/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_ZFilePathTranslate(const char *pPathName, char *pStrBuf, int32_t iBufLen) +{ + char *pPath; + + // copy pathname + ds_strnzcpy(pStrBuf, pPathName, iBufLen); + + // translate any backward slashes to forward slashes + for (pPath = pStrBuf; *pPath != '\0'; pPath += 1) + { + if (*pPath == '\\') + { + *pPath = '/'; + } + } + return(pStrBuf); +} + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function ZFileOpen + + \Description + Open a file. + + \Input *pFileName - file name to open + \Input uFlags - flags dictating how to open the file + + \Output + int32_t - file descriptor, or -1 if there was an error + + \Version 1.0 03/16/2005 (jfrank) First Version +*/ +/********************************************************************************F*/ +ZFileT ZFileOpen(const char *pFileName, uint32_t uFlags) +{ + char strFileName[4096]; + char strMode[16] = ""; + FILE *pFile; + + if ((pFileName == NULL) || (pFileName[0] == '\0')) + { + return(ZFILE_INVALID); + } + + pFileName = _ZFilePathTranslate(pFileName, strFileName, sizeof(strFileName)); + + ds_memclr(strMode, sizeof(strMode)); + + // map zfile flags to Win32 Mode flags + if (uFlags & ZFILE_OPENFLAG_APPEND) + { + strcat(strMode, "a"); + if (uFlags & ZFILE_OPENFLAG_RDONLY) + strcat(strMode, "+"); + } + else if (uFlags & ZFILE_OPENFLAG_RDONLY) + { + strcat(strMode, "r"); + if (uFlags & ZFILE_OPENFLAG_WRONLY) + strcat(strMode, "+"); + } + else if (uFlags & ZFILE_OPENFLAG_WRONLY) + { + strcat(strMode, "w"); + } + if (uFlags & ZFILE_OPENFLAG_BINARY) + { + strcat(strMode, "b"); + } + + if ((pFile = fopen(pFileName, strMode)) != NULL) + { + return((ZFileT)pFile); + } + else + { + return(ZFILE_INVALID); + } +} + +/*F********************************************************************************/ +/*! + \Function ZFileClose + + \Description + Close a file + + \Input iFileId - file descriptor + + \Output int32_t - return value from fclose() + + \Version 1.0 11/18/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileClose(ZFileT iFileId) +{ + return(fclose((FILE *)iFileId)); +} + +/*F********************************************************************************/ +/*! + \Function ZFileRead + + \Description + Read from a file. + + \Input *pData - pointer to buffer to read to + \Input iSize - amount of data to read + \Input iFileId - file descriptor + + \Output + Number of bytes read + + \Version 1.0 11/18/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileRead(ZFileT iFileId, void *pData, int32_t iSize) +{ + int32_t iResult = (int32_t)fread(pData, 1, iSize, (FILE *)iFileId); + return(ferror((FILE *)iFileId) ? -1 : iResult); +} + +/*F********************************************************************************/ +/*! + \Function ZFileWrite + + \Description + Write to a file. + + \Input *pData - pointer to buffer to write from + \Input iSize - amount of data to write + \Input iFileId - file descriptor + + \Output + Number of bytes written + + \Version 1.0 11/18/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileWrite(ZFileT iFileId, void *pData, int32_t iSize) +{ + int32_t iResult; + + if (iFileId == ZFILE_INVALID) + { + return(0); + } + if ((pData == NULL) || (iSize == 0)) + { + return(0); + } + + iResult = (int32_t)fwrite(pData, 1, iSize, (FILE *)iFileId); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ZFileSeek + + \Description + Seek to location in file. + + \Input iFileId - file id to seek + \Input iOffset - offset to seek to + \Input uFlags - seek mode (ZFILE_SEEKFLAG_*) + + \Output + int64_t - resultant seek location, or -1 on error + + \Version 03/16/2005 (jfrank) First Version +*/ +/********************************************************************************F*/ +int64_t ZFileSeek(ZFileT iFileId, int64_t iOffset, uint32_t uFlags) +{ + int64_t iResult; + int32_t iFlags=0; + + if (uFlags == ZFILE_SEEKFLAG_CUR) + { + iFlags = SEEK_CUR; + } + else if (uFlags == ZFILE_SEEKFLAG_END) + { + iFlags = SEEK_END; + } + else if (uFlags == ZFILE_SEEKFLAG_SET) + { + iFlags = SEEK_SET; + } + + fseek((FILE *)iFileId, iOffset, iFlags); + iResult = ftell((FILE *)iFileId); + return((iResult >= 0) ? iResult : -1); +} + +/*F********************************************************************************/ +/*! + \Function ZFileDelete + + \Description + Delete a file. + + \Input *pFileName - filename of file to delete + + \Output int32_t - 0=success, error code otherwise + + \Version 03/23/2005 (jfrank) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileDelete(const char *pFileName) +{ + char strFileName[4096]; + int32_t iResult; + + if ((pFileName == NULL) || (pFileName[0] == '\0')) + { + return(ZFILE_ERROR_FILENAME); + } + + pFileName = _ZFilePathTranslate(pFileName, strFileName, sizeof(strFileName)); + + iResult = remove(pFileName); + if (iResult == 0) + { + return(ZFILE_ERROR_NONE); + } + else + { + return(ZFILE_ERROR_FILEDELETE); + } +} + +/*F********************************************************************************/ +/*! + \Function ZFileStat + + \Description + Get File Stat information on a file/dir. + + \Input *pFileName - filename/dir to stat + \Input *pStat + + \Output int32_t - 0=success, error code otherwise + + \Version 03/25/2005 (jfrank) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileStat(const char *pFileName, ZFileStatT *pFileStat) +{ + #if !defined(DIRTYCODE_NX) + char strFileName[4096]; + struct stat FileStat; + int32_t iResult; + + // check for error conditions + if (pFileName == NULL) + { + return(ZFILE_ERROR_FILENAME); + } + if (pFileStat == NULL) + { + return(ZFILE_ERROR_NULLPOINTER); + } + + pFileName = _ZFilePathTranslate(pFileName, strFileName, sizeof(strFileName)); + + // get file status + iResult = stat(pFileName, &FileStat); + + // check for some specific errors + if ((iResult == -1) && (errno == ENOENT)) + { + return(ZFILE_ERROR_NOSUCHFILE); + } + else if (errno == EACCES) + { + return(ZFILE_ERROR_PERMISSION); + } + else if (iResult != 0) + { + return(ZFILE_ERROR_FILESTAT); + } + + // clear the incoming buffer + ds_memclr(pFileStat, sizeof(ZFileStatT)); + // copy from the PC-specific structures + pFileStat->iSize = FileStat.st_size; + pFileStat->uTimeAccess = (uint32_t)FileStat.st_atime; + pFileStat->uTimeCreate = (uint32_t)FileStat.st_ctime; + pFileStat->uTimeModify = (uint32_t)FileStat.st_mtime; + // get the file modes + if (S_ISDIR(FileStat.st_mode)) + { + pFileStat->uMode |= ZFILESTAT_MODE_DIR; + } + if (S_ISREG(FileStat.st_mode)) + { + pFileStat->uMode |= ZFILESTAT_MODE_FILE; + } + if (FileStat.st_mode & S_IRUSR) + { + pFileStat->uMode |= ZFILESTAT_MODE_READ; + } + if (FileStat.st_mode & S_IWUSR) + { + pFileStat->uMode |= ZFILESTAT_MODE_WRITE; + } + if (FileStat.st_mode & S_IXUSR) + { + pFileStat->uMode |= ZFILESTAT_MODE_EXECUTE; + } + #endif + + // done - return no error + return(ZFILE_ERROR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function ZFileRename + + \Description + Rename a file. + + \Input *pOldname - old name + \Input *pNewname - new name + + \Output int32_t - 0=success, error code otherwise + + \Version 03/30/2005 (jfrank) First Version +*/ +/********************************************************************************F*/ +int32_t ZFileRename(const char *pOldname, const char *pNewname) +{ + int32_t iResult; + + // check for error conditions + if ((pOldname == NULL) || (pNewname == NULL)) + { + return(ZFILE_ERROR_NULLPOINTER); + } + + // rename the file + iResult = rename(pOldname, pNewname); + + return((iResult == 0) ? ZFILE_ERROR_NONE : ZFILE_ERROR_FILERENAME); +} + +/*F********************************************************************************/ +/*! + \Function ZFileMkdir + + \Description + Make a directory, recursively + + \Input *pPathName - directory path to create + + \Output + int32_t - 0=success, error code otherwise + + \Version 01/25/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ZFileMkdir(const char *pPathName) +{ + char strPathName[4096], *pPath, cTerm = '\0'; + + pPath = _ZFilePathTranslate(pPathName, strPathName, sizeof(strPathName)); + + // traverse pathname, making each directory component as we go + for ( ; ; pPath += 1) + { + if (*pPath == '/') + { + cTerm = *pPath; + *pPath = '\0'; + } + if (*pPath == '\0') + { + int32_t iResult; + if ((iResult = mkdir(strPathName, S_IRWXU)) != 0) + { + if (errno == EEXIST) + { + ZPrintfDbg(("zfilepc: directory %s already exists\n", strPathName)); + } + else + { + ZPrintfDbg(("zfileunix: could not create directory '%s' err=%s\n", strPathName, DirtyErrGetName(errno))); + return(-1); + } + } + } + if (cTerm != '\0') + { + *pPath = cTerm; + cTerm = '\0'; + } + if (*pPath == '\0') + { + break; + } + } + return(0); +} + + diff --git a/src/thirdparty/dirtysdk/contrib/common/source/zfile.c b/src/thirdparty/dirtysdk/contrib/common/source/zfile.c new file mode 100644 index 00000000..b589d92b --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/source/zfile.c @@ -0,0 +1,199 @@ +/*H********************************************************************************/ +/*! + \File zfile.c + + \Description + Platform-inspecific host file operations. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 02/16/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "zmem.h" +#include "zlib.h" +#include "zfile.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function ZFileSize + + \Description + Get file size. + + \Input iFileId - file id to get size of + + \Output + int32_t - file size, or -1 on error + + \Version 02/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int64_t ZFileSize(ZFileT iFileId) +{ + int64_t iEnd = ZFileSeek(iFileId, 0, ZFILE_SEEKFLAG_END); + ZFileSeek(iFileId, 0, ZFILE_SEEKFLAG_SET); + return(iEnd); +} + +/*F********************************************************************************/ +/*! + \Function ZFileLoad + + \Description + Open a file and read it into memory. + + \Input *pFilename - pointer to name of file to read + \Input *pFileSize - [out] storage for file size + \Input uOpenFlags - open flags, or zero for default (READONLY) + + \Output + char * - pointer to file data, or NULL + + \Version 02/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +char *ZFileLoad(const char *pFileName, int32_t *pFileSize, uint32_t uOpenFlags) +{ + int64_t iFileSize64; + int32_t iFileSize, iResult; + char *pFileMem; + ZFileT iFileId; + + // determine open flags + if (uOpenFlags == 0) + { + uOpenFlags = ZFILE_OPENFLAG_RDONLY; + } + + // open the file + iFileId = ZFileOpen(pFileName, uOpenFlags); + if (iFileId == ZFILE_INVALID) + { + printf("zfile: unable to open file '%s'\n", pFileName); + return(NULL); + } + + // get the file size + iFileSize64 = ZFileSize(iFileId); + if (iFileSize64 < 0) + { + printf("zfile: unable to get size of file '%s'\n", pFileName); + return(NULL); + } + iFileSize = (int32_t)iFileSize64; // this function does not support files >2GB + + // allocate and clear memory for the file + pFileMem = (char *) ZMemAlloc(iFileSize+1); + if (pFileMem == NULL) + { + printf("zfile: unable to allocate %d bytes to load file '%s'\n", iFileSize+1, pFileName); + return(NULL); + } + ds_memclr(pFileMem, iFileSize+1); + + // read file into memory + iResult = ZFileRead(iFileId, pFileMem, iFileSize); + if (iResult <= 0) + { + printf("zfile: unable to read file '%s'\n", pFileName); + return(NULL); + } + /* null-terminate; we do this in addition to the memset up above because + under windows the size of a text file on disk may be larger than the + file in memory */ + pFileMem[iResult] = '\0'; + + // if size parameter is not null, set it + if (pFileSize != NULL) + { + *pFileSize = iResult; + } + + // close the file + iResult = ZFileClose(iFileId); + if (iResult < 0) + { + printf("zfile: error closing file '%s'\n", pFileName); + } + + // return pointer to memory + return(pFileMem); +} + + +/*F********************************************************************************/ +/*! + \Function ZFileSave + + \Description + Save data to a file. (OVERWRITE and CREATE by default) + + \Input *pFilename - pointer to name of file to read + \Input *pData - pointer to data to save to a file + \Input iSize - amount of data to save to the file + \Input uOpenFlags - open flags, or zero for default (WRONLY|CREATE) + + \Output + int32_t - 0 if success, error code otherwise + + \Version 03/18/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t ZFileSave(const char *pFileName, const char *pData, int32_t iSize, uint32_t uOpenFlags) +{ + int32_t iResult; + ZFileT iFileId; + + // determine what flags to use + if (uOpenFlags == 0) + { + uOpenFlags = ZFILE_OPENFLAG_WRONLY | ZFILE_OPENFLAG_CREATE; + } + + // open the file + iFileId = ZFileOpen(pFileName, uOpenFlags); + if (iFileId == ZFILE_INVALID) + { + ZPrintf("zfile: unable to open file '%s' with flags 0x%X\n", pFileName, uOpenFlags); + return(ZFILE_ERROR_FILEOPEN); + } + + // now write the data + iResult = ZFileWrite(iFileId, (char *)pData, iSize); + if(iResult != iSize) + { + ZPrintf("zfile: write error - size to write [%d] size written [%d]\n", iSize, iResult); + return(ZFILE_ERROR_FILEWRITE); + } + + // and close the file + iResult = ZFileClose(iFileId); + if(iResult != 0) + { + ZPrintf("zfile: close error [%d=0x%X]\n", iResult, iResult); + return(ZFILE_ERROR_FILECLOSE); + } + + // else successful save occurred + return(ZFILE_ERROR_NONE); +} + diff --git a/src/thirdparty/dirtysdk/contrib/common/source/zlib.c b/src/thirdparty/dirtysdk/contrib/common/source/zlib.c new file mode 100644 index 00000000..59b73927 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/source/zlib.c @@ -0,0 +1,805 @@ +/*H********************************************************************************/ +/*! + \File zlib.c + + \Description + A simple console style test library that provides a basic + notion of processes, output routines and memory allocation. + Used to implement simple test programs in a unix-style + command-line environment. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 09/15/1999 (gschaefer) Initial Design + \Version 11/08/1999 (gschaefer) Cleanup and revision + \Version 03/17/2005 (jfrank) Cleanup and revision +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#if defined(_WIN32) && !defined(_XBOX) +#define WIN32_LEAN_AND_MEAN 1 + +#pragma warning(push,0) +#include +#pragma warning(pop) +#endif + +#include +#include +#include +#include +#include + +#include "DirtySDK/platform.h" + +#include "DirtySDK/dirtydefs.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "zlib.h" +#include "zmem.h" + +/*** Defines **********************************************************************/ + +#if !defined(_WIN32) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) +#define wsprintf sprintf +#endif + +/*** Type Definitions *************************************************************/ + +struct ZEnviron +{ + ZEnviron *pNext; //!< list of active process contexts + ZContext *pContext; //!< process private context + ZConsole *pConsole; //!< console output pointer + ZCommand *pCommand; //!< pointer to process code + + uint32_t uRuntime; //!< number of ms process has run + uint32_t uRuncount; //!< number of time slices + + int32_t iPID; //!< process identifier + int32_t iStatus; //!< exit status + uint32_t uSchedule; //!< next scheduled tick to run at + + int32_t iArgc; //!< arg count for program + char *pArgv[200]; //!< pointer to entry parms + char strArgb[16*1024]; //!< buffer to hold entry parms +}; + +/*** Variables ********************************************************************/ + +static ZEnviron *_pEnvCurr = NULL; //!< current process +static ZEnviron *_pEnvList = NULL; //!< list of processes + +static void *_Zlib_pPrintfParm = NULL; +static int32_t (*_Zlib_pPrintfHook)(void *pParm, const char *pText) = NULL; + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ZCreate + + \Description + Create a new process environment. Parses command line + and gets process ready to run. + + \Input console - default output console for process + \Input cmdline - the entry command line params + + \Output ZEnviron - new process environment + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +ZEnviron *ZCreate(ZConsole *console, const char *cmdline) +{ + char term; + char *s; + ZEnviron *env; + static uint32_t pid = 1; + + // get new environment + env = (ZEnviron *)ZMemAlloc(sizeof(*env)); + ds_memclr(env, sizeof(*env)); + + // assign unique pid + env->iPID = pid++; + + // save console ref + env->pConsole = console; + + // copy command line + ds_strnzcpy(env->strArgb, cmdline, sizeof(env->strArgb)); + s = env->strArgb; + + // parse command line + for (env->iArgc = 0; env->iArgc < (signed)(sizeof(env->pArgv)/sizeof(env->pArgv[0])); env->iArgc += 1) + { + // skip to next token + while ((*s != 0) && (*s <= ' ')) + ++s; + // see if anything to save + if (*s == 0) + break; + // see if there is a terminator + term = ((*s == '"') || (*s == '\'') ? *s++ : 0); + // record start of token + env->pArgv[env->iArgc] = s; + // find end of token + while ((*s != 0) && (((term == 0) && (*s > ' ')) || ((term != 0) && (*s != term)))) + ++s; + // terminate token + if (*s != 0) + *s++ = 0; + } + + // set status to terminated + env->iStatus = 0; + env->uSchedule = (uint32_t)(-1); + + // put into process list + env->pNext = _pEnvList; + _pEnvList = env; + + // return new environment + return(env); +} + +/*F********************************************************************************/ +/*! + \Function ZDestroy + + \Description + Destroy a process environment + + \Input env - existing process environment + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +void ZDestroy(ZEnviron *env) +{ + ZEnviron **scan; + + // remove from environment list + for (scan = &_pEnvList; *scan != NULL; scan = &(*scan)->pNext) + { + if (*scan == env) + { + *scan = env->pNext; + break; + } + } + + // remove from active + if (_pEnvCurr == env) + _pEnvCurr = NULL; + + // destroy any attached context + if (env->pContext != NULL) + ZMemFree(env->pContext); + + // destroy the environment + ZMemFree(env); + return; +} + +/*F********************************************************************************/ +/*! + \Function ZInvoke + + \Description + Execute a process within an existing environment + + \Input env - existing environment + \Input cmd - process code pointer + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +void ZInvoke(ZEnviron *env, ZCommand *cmd) +{ + uint32_t tick1, tick2; + + // make sure there is a current environment + if (env == NULL) + return; + _pEnvCurr = env; + + // if this is first run + if (cmd != NULL) + { + // save the command + _pEnvCurr->pCommand = cmd; + // remove any existing context + ZContextCreate(0); + } + else if (_pEnvCurr->pCommand == NULL) + { + return; + } + + // invoke the command + tick1 = ZTick(); + _pEnvCurr->uSchedule = (uint32_t)(-1); + _pEnvCurr->uRuncount += 1; + _pEnvCurr->iStatus = _pEnvCurr->pCommand(_pEnvCurr->pContext, _pEnvCurr->iArgc, _pEnvCurr->pArgv); + tick2 = ZTick(); + _pEnvCurr->uRuntime += tick2-tick1; + + // handle callback request + if ((_pEnvCurr->uSchedule != (uint32_t)(-1)) && (_pEnvCurr->iStatus == ZLIB_STATUS_RUNNING)) + _pEnvCurr->uSchedule += tick2; +} + +/*F********************************************************************************/ +/*! + \Function ZSet + + \Description + Set current environment, based on specified context + + \Input context - context; environment that owns this context will be made current + + \Version 10/10/2012 (jbrookes) +*/ +/********************************************************************************F*/ +void ZSet(ZContext *context) +{ + ZEnviron *env; + + // find context + for (env = _pEnvCurr; env != NULL && env->pContext != context; env = env->pNext) + ; + + // found it? make it current + if (env != NULL) + { + _pEnvCurr = env; + } +} + +/*F********************************************************************************/ +/*! + \Function ZGetPid + + \Description + Get process id of current environment + + \Output + int32_t - pid, or zero if no current environment + + \Version 10/10/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ZGetPid(void) +{ + int32_t iPID = 0; + if (_pEnvCurr != NULL) + { + iPID = _pEnvCurr->iPID; + } + return(iPID); +} + +/*F********************************************************************************/ +/*! + \Function ZCallback + + \Description + Let process signal it wants a callback (has not completed) + + \Input cmd - pointer to callback code + \Input delay - milliseconds until callback + + \Output int32_t - special return value that process returns with + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ZCallback(ZCommand *cmd, int32_t delay) +{ + _pEnvCurr->pCommand = cmd; + _pEnvCurr->uSchedule = delay; + return(ZLIB_STATUS_RUNNING); +} + +/*F********************************************************************************/ +/*! + \Function ZTask + + \Description + Give time to any existing processes that need it. + + \Output int32_t - number of ticks until the next requested update + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ZTask(void) +{ + ZEnviron *env; + uint32_t tick = ZTick(); + uint32_t next = tick+1000; + + // walk environ list and execute processes which are scheduled + for (env = _pEnvList; env != NULL; env = env->pNext) + { + if (tick > env->uSchedule) + ZInvoke(env, NULL); + if (next > env->uSchedule) + next = env->uSchedule; + } + + // figure time until next tick + tick = ZTick(); + return((tick > next) ? 0 : next-tick); +} + +/*F********************************************************************************/ +/*! + \Function ZCleanup + + \Description + Remove any environments containing complete processes. + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +void ZCleanup(void) +{ + ZEnviron *env; + + for (env = _pEnvList; env != NULL;) + { + if (env->uSchedule == (uint32_t)(-1)) + { + ZEnviron *kill = env; + env = env->pNext; + ZDestroy(kill); + } + else + { + env = env->pNext; + } + } +} + +/*F********************************************************************************/ +/*! + \Function ZShutdown + + \Description + Kill all active processes in preparation for shutdown. + + \Version 02/18/03 (jbrookes) +*/ +/********************************************************************************F*/ +void ZShutdown(void) +{ + ZEnviron *env; + + for (env = _pEnvList; env != NULL; env = env->pNext) + { + // kill the process + env->uSchedule = (uint32_t)(-1); + env->iStatus = (env->pCommand)(env->pContext, 0, env->pArgv); + env->uSchedule = (uint32_t)(-1); + } + + while(_pEnvList != NULL) + { + ZTask(); + ZCleanup(); + } +} + +/*F********************************************************************************/ +/*! + \Function ZCreateContext + + \Description + Allow a process to allocate persistent private context. + + \Input size - size of needed context + + \Output ZContext * - context of requested size + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +ZContext *ZContextCreate(int32_t size) +{ + if (_pEnvCurr == NULL) + return(NULL); + + if (_pEnvCurr->pContext != NULL) + ZMemFree(_pEnvCurr->pContext); + + _pEnvCurr->pContext = ((size > 0) ? (ZContext *)ZMemAlloc(size) : NULL); + return(_pEnvCurr->pContext); +} + +/*F********************************************************************************/ +/*! + \Function ZPrintf + + \Description + Display output using standard printf semantics. + + \Input Standard printf inputs. + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +void ZPrintf(const char *fmt, ...) +{ + char text[4096]; + va_list args; + int32_t iOutput=1; + + // parse the data + va_start(args, fmt); + ds_vsnprintf(text, sizeof(text), fmt, args); + va_end(args); + + // send to debug hook if set + if (_Zlib_pPrintfHook != NULL) + { + iOutput = _Zlib_pPrintfHook(_Zlib_pPrintfParm, text); + } + + // if debug hook didn't override output, print here + if (iOutput != 0) + { + #if defined(DIRTYCODE_PC) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + OutputDebugStringA(text); + #else + printf("%s", text); + #endif + } +} + +/*F********************************************************************************/ +/*! + \Function ZPrintf2 + + \Description + Display output using standard printf semantics (no hook). + + \Input Standard printf inputs. + + \Version 10/18/2011 (jbrookes) +*/ +/********************************************************************************F*/ +void ZPrintf2(const char *fmt, ...) +{ + char text[4096]; + va_list args; + + // parse the data + va_start(args, fmt); + ds_vsnprintf(text, sizeof(text), fmt, args); + va_end(args); + + // print here + #if defined(DIRTYCODE_PC) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + OutputDebugStringA(text); + #else + printf("%s", text); + #endif +} + +/*F********************************************************************************/ +/*! + \Function ZPrintfHook + + \Description + Hook into output. + + \Input *pPrintfHook - pointer to function to call with output + \Input *pParm - user parameter + + \Output + None. + + \Version 03/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void ZPrintfHook(int32_t (*pPrintfHook)(void *pParm, const char *pText), void *pParm) +{ + _Zlib_pPrintfHook = pPrintfHook; + _Zlib_pPrintfParm = pParm; +} + +/*F********************************************************************************/ +/*! + \Function ZCmdPS + + \Description + Show list of all process environments. + + \Input *argz - context + \Input argc - arg count + \Input argz - arg strings + + \Output int32_t - exit code + + \Version 10/04/1999 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ZCmdPS(ZContext *argz, int32_t argc, char **pArgv) +{ + ZEnviron *env; + uint32_t tick = ZTick(); + + // handle help + if (argc == 0) { + ZPrintf("%s - show process status list\r\n", pArgv[0]); + return(0); + } + + // show process status list + ZPrintf(" PID STATUS ITER TIME COMMAND\r\n"); + for (env = _pEnvList; env != NULL; env = env->pNext) { + int32_t i; + char text[256]; + char *s = text; + + // dont show ourselves + if (env == _pEnvCurr) + continue; + + // put in pid + s += wsprintf(s, "%5d", env->iPID); + + // add in state/time till next + if (env->uSchedule == (uint32_t)(-1)) + { + s += wsprintf(s, " F %4d", env->iStatus); + } + else + { + int32_t timeout = env->uSchedule-tick; + if (timeout < 0) + timeout = 0; + if (timeout > 9999) + timeout = 9999; + s += wsprintf(s, " S %4d", timeout); + } + + // show run count + s += wsprintf(s, " %4d", (env->uRuncount < 9999 ? env->uRuncount : 9999)); + + // show time used + s += wsprintf(s, " %2d.%03d", env->uRuntime/1000, env->uRuntime%1000); + + // show command name + s += wsprintf(s, " %s", env->pArgv[0]); + + // show command parms + for (i = 1; i < env->iArgc; ++i) + s += wsprintf(s, " '%s'", env->pArgv[i]); + + // end of line + *s++ = '\r'; + *s++ = '\n'; + *s = 0; + ZPrintf("%s", text); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function ZCmdKill + + \Description + Kill an existing process. + + \Input *argz - context + \Input argc - arg count + \Input argz - arg strings + + \Output int32_t - exit code + + \Version 10/04/1999 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ZCmdKill(ZContext *argz, int32_t argc, char **pArgv) +{ + int32_t pid; + char *s, *d; + ZEnviron *env; + + // handle help + if (argc == 0) { + ZPrintf("%s pid|name - kill a running command\r\n", pArgv[0]); + return(0); + } + + // check usage + if (argc != 2) { + ZPrintf("usage: %s pid|name\r\n", pArgv[0]); + return(-1); + } + + // get the pid + pid = 0; + for (s = pArgv[1]; (*s >= '0') && (*s <= '9'); ++s) + pid = (pid * 10) + (*s & 15); + + // if its zero, see if the name matches + for (env = _pEnvList; env != NULL; env = env->pNext) + { + for (s = pArgv[1], d = env->pArgv[0]; *s != 0; ++s, ++d) + { + if (*s != *d) + break; + } + if (*s == 0) + { + pid = env->iPID; + break; + } + } + + // make sure we got something + if (pid <= 0) + { + ZPrintf("%s: invalid pid: %s\r\n", pArgv[0], pArgv[1]); + return(-2); + } + + // search process list for match + for (env = _pEnvList; env != NULL; env = env->pNext) { + if ((env != _pEnvCurr) && (env->iPID == pid)) + break; + } + + // error if no matching process + if (env == NULL) + { + ZPrintf("%s: no such process %d\r\n", pArgv[0], pid); + return(-3); + } + + // if already dead + if (env->uSchedule == (uint32_t)(-1)) + { + ZPrintf("%s: process %d already dead\r\n", pArgv[0], pid); + return(-4); + } + + // kill the process + env->uSchedule = (uint32_t)(-1); + env->iStatus = (env->pCommand)(env->pContext, 0, env->pArgv); + env->uSchedule = (uint32_t)(-1); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function ZGetStatus + + \Description + Return status of current command. Returns -1 if pEnv is Null. + + \Input pEnv -pointer to current env, command. + + \Output Status of current command. Returns -1 if pEnv is Null. + + \Version 29/11/2005 (TE) +*/ +/********************************************************************************F*/ +int32_t ZGetStatus(ZEnviron *pEnv) +{ + return(pEnv ? pEnv->iStatus :-1); +} + +/*F********************************************************************************/ +/*! + \Function ZGetStatusPid + + \Description + Get status of the process specified by pid + + \Output + int32_t - status, or ZLIB_STATUS_UNKNOWN if process is not found + + \Version 10/10/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ZGetStatusPid(int32_t iPID) +{ + ZEnviron *env; + + // show process status list + for (env = _pEnvList; env != NULL; env = env->pNext) + { + if (env->iPID == iPID) + { + return(env->iStatus); + } + } + return(ZLIB_STATUS_UNKNOWN); +} + +/*F********************************************************************************/ +/*! + \Function ZGetIntArg + + \Description + Get fourcc/integer from command-line argument + + \Input *pArg - pointer to argument + + \Version 11/26/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ZGetIntArg(const char *pArg) +{ + int32_t iValue; + + // check for possible fourcc value + if ((strlen(pArg) == 4) && (isalpha(pArg[0]) || isalpha(pArg[1]) || isalpha(pArg[2]) || isalpha(pArg[3]))) + { + iValue = pArg[0] << 24; + iValue |= pArg[1] << 16; + iValue |= pArg[2] << 8; + iValue |= pArg[3]; + } + else + { + iValue = (signed)strtol(pArg, NULL, 10); + } + return(iValue); +} + +/*F*************************************************************************************/ +/*! + \Function ZTick + + \Description + Return some kind of increasing tick count with millisecond scale (does + not need to have millisecond precision, but higher precision is better). + + \Output + uint32_t - millisecond tick count + + \Version 1.0 05/06/2005 (jfrank) First Version +*/ +/*************************************************************************************F*/ +uint32_t ZTick(void) +{ + return(NetTick()); +} + + +/*F********************************************************************************/ +/*! + \Function ZSleep + + \Description + Put process to sleep for some period of time + + \Input uMSecs - Number of milliseconds to sleep for. + + \Version 05/06/2005 (jfrank) +*/ +/********************************************************************************F*/ +void ZSleep(uint32_t uMSecs) +{ + NetConnSleep(uMSecs); +} + + + diff --git a/src/thirdparty/dirtysdk/contrib/common/source/zlist.c b/src/thirdparty/dirtysdk/contrib/common/source/zlist.c new file mode 100644 index 00000000..b8b0085d --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/source/zlist.c @@ -0,0 +1,270 @@ +/*H********************************************************************************/ +/*! + \File zlist.h + + \Description + Generic list module for samples to use. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/26/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/dirtysock.h" +#include "zmem.h" +#include "zlib.h" +#include "zlist.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +struct ZListT +{ + uint32_t iNumEntries; //!< number of entries in the list + uint32_t iEntrySize; //!< size of each entry + uint32_t uHead; //!< index of input list head entry + uint32_t uTail; //!< index of input list tail entry + void *pData; //!< data +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function ZListCreate + + \Description + Create a list object + + \Input iMaxEntries - maximum number of entries in the list + \Input iEntrySize - maximum size of each entry in the list + + \Output ZListT * - pointer to the created list + + \Version 04/26/2005 (jfrank) +*/ +/********************************************************************************F*/ +ZListT *ZListCreate(int32_t iNumEntries, int32_t iEntrySize) +{ + ZListT *pList; + + // check for errors + if((iNumEntries <= 0) || (iEntrySize <= 0)) + { + ZPrintf("Could not create list with [%d] entries of size [%d] total size [%d]\n", + iNumEntries, iEntrySize, iNumEntries * iEntrySize); + return(NULL); + } + + // create the list + pList = (ZListT *)ZMemAlloc(sizeof(ZListT)); + ds_memclr(pList, sizeof(*pList)); + pList->iEntrySize = iEntrySize; + pList->iNumEntries = iNumEntries; + pList->pData = (ZListT *)ZMemAlloc(iNumEntries * iEntrySize); + return(pList); +} + + +/*F********************************************************************************/ +/*! + \Function ZListPushBack + + \Description + Add and entry to the back of a data list + + \Input pList - pointer to the list to use + \Input pEntry - entry to add (will be copied in) + + \Output int32_t - 0 for success, error code otherwise + + \TODO + The current implementation doesn't wrap the queue, so if the head + starts chasing the tail but doesn't catch up, a large amount of the + buffer space can end up being wasted. Either the queue needs to be + modified to wrap, or the buffer memory shifted to allow the head to + be reset to zero without catching the tail. + + \Version 04/26/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t ZListPushBack(ZListT *pList, void *pEntry) +{ + char *pDest; + + // check to make sure we got an entry + if ((pEntry == NULL) || (pList == NULL)) + { + return(ZLIST_ERROR_NULLPOINTER); + } + + // see if the list is full + if (pList->uTail >= pList->iNumEntries) + { + return(ZLIST_ERROR_FULL); + } + + // insert into the list + pDest = (char *)pList->pData + (pList->uTail * pList->iEntrySize); + ds_memcpy(pDest, pEntry, pList->iEntrySize); + // increment the tail pointer + pList->uTail++; + + // done + return(0); +} + +/*F********************************************************************************/ +/*! + \Function ZListPopFront + + \Description + Get an entry off the front of a data list + + \Input pList - pointer to the list to destroy + \Input pEntry - destination for the entry to get (will be copied in), NULL to discard data + + \Output int32_t - <0 for error, 0 for no data left, >0 for amount of data left + + \Version 04/26/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t ZListPopFront(ZListT *pList, void *pEntry) +{ + uint32_t uAmtLeft; + char *pSrc; + + // check to make sure we got an entry + if (pList == NULL) + { + return(ZLIST_ERROR_NULLPOINTER); + } + + // see if the list is empty + uAmtLeft = pList->uTail - pList->uHead; + if (uAmtLeft == 0) + { + // no error - list is just empty + return(0); + } + else + { + // only copy data if we have a container for it + if (pEntry) + { + // copy from the list into the new location + pSrc = (char *)pList->pData + (pList->uHead * pList->iEntrySize); + ds_memcpy(pEntry, (void *)pSrc, pList->iEntrySize); + } + pList->uHead++; + // test for empty list situation + if (pList->uHead == pList->uTail) + { + // empty list - reset + pList->uHead = 0; + pList->uTail = 0; + } + } + + // done + return(uAmtLeft); +} + + +/*F********************************************************************************/ +/*! + \Function ZListPeekFront + + \Description + Examine the front entry of a data list + + \Input pList - pointer to the list to destroy + + \Output void * - pointer to the first entry, NULL if empty + + \Version 04/26/2005 (jfrank) +*/ +/********************************************************************************F*/ +void *ZListPeekFront(ZListT *pList) +{ + char *pSrc; + + // check for errors + if(pList == NULL) + { + return(NULL); + } + + // if list is empty, return NULL + if (pList->uHead == pList->uTail) + { + return(NULL); + } + + // otherwise return a pointer to the data in question + pSrc = (char *)pList->pData + (pList->uHead * pList->iEntrySize); + return(pSrc); +} + + +/*F********************************************************************************/ +/*! + \Function ZListClear + + \Description + Clear the entire list + + \Input pList - pointer to the list to destroy + + \Output None + + \Version 04/26/2005 (jfrank) +*/ +/********************************************************************************F*/ +void ZListClear(ZListT *pList) +{ + if(pList) + { + ds_memclr(pList->pData, pList->iNumEntries * pList->iEntrySize); + pList->uHead = 0; + pList->uTail = 0; + } +} + + +/*F********************************************************************************/ +/*! + \Function ZListDestroy + + \Description + Destroy a list object and free all associated memory + + \Input pList - pointer to the list to destroy + + \Output None + + \Version 04/26/2005 (jfrank) +*/ +/********************************************************************************F*/ +void ZListDestroy(ZListT *pList) +{ + if (pList) + { + if (pList->pData) + { + ZMemFree(pList->pData); + } + + ZMemFree(pList); + } +} + diff --git a/src/thirdparty/dirtysdk/contrib/common/source/zmem.c b/src/thirdparty/dirtysdk/contrib/common/source/zmem.c new file mode 100644 index 00000000..578ade24 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/source/zmem.c @@ -0,0 +1,83 @@ +/*H********************************************************************************/ +/*! + \File zmem.c + + \Description + Memory implementations for use on all platforms. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/16/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" + +#include "zmemtrack.h" +#include "zmem.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function ZMemAlloc + + \Description + Allocate some memory + + \Input uSize - amount of memory, in bytes, to allocate + + \Output void * - pointer to an allocated memory chunk + + \Version 03/16/2005 (jfrank) +*/ +/********************************************************************************F*/ +void *ZMemAlloc(uint32_t uSize) +{ + void *pMem; + if ((pMem = (void *)malloc(uSize)) != NULL) + { + ds_memset(pMem, 0xCD, uSize); + ZMemtrackAlloc(pMem, uSize, 0); + } + return(pMem); +} + + +/*F********************************************************************************/ +/*! + \Function ZMemFree + + \Description + Free a previously allocated chunk of memory + + \Input void *pMem - pointer to an allocated memory chunk + + \Output None + + \Version 03/16/2005 (jfrank) +*/ +/********************************************************************************F*/ +uint32_t ZMemFree(void *pMem) +{ + uint32_t uSize; + ZMemtrackFree(pMem, &uSize); + ds_memset(pMem, 0xEF, uSize); + free(pMem); + return(uSize); +} diff --git a/src/thirdparty/dirtysdk/contrib/common/source/zmemtrack.c b/src/thirdparty/dirtysdk/contrib/common/source/zmemtrack.c new file mode 100644 index 00000000..46fdd4c2 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/common/source/zmemtrack.c @@ -0,0 +1,371 @@ +/*H********************************************************************************/ +/*! + \File zmemtrack.c + + \Description + Routines for tracking memory allocations. + + \Copyright + Copyright (c) 2005-2017 Electronic Arts Inc. + + \Version 02/15/2005 (jbrookes) First Version, based on jfrank's implementation. +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "zlib.h" +#include "zmem.h" +#include "zmemtrack.h" + +/*** Defines **********************************************************************/ + +#define ZMEMTRACK_MEMDUMPBYTES (64) //!< number of bytes to print around the leak +#define ZMEMTRACK_MAXALLOCATIONS (1024*8) //!< maximum list allocation size + +/*** Type Definitions *************************************************************/ + +typedef struct ZMemtrackElemT +{ + void *pMem; + uint32_t uMemSize; + uint32_t uTag; +} ZMemtrackElemT; + +typedef struct ZMemtrackRefT +{ + uint32_t uNumAllocations; + uint32_t uMaxAllocations; + uint32_t uTotalAllocations; + uint32_t uTotalMemory; + uint32_t uMaxMemory; + uint8_t bOverflow; + uint8_t bStarted; + uint8_t _pad[2]; + + ZMemtrackLogCbT *pLoggingCb; + void *pUserData; + + ZMemtrackElemT MemList[ZMEMTRACK_MAXALLOCATIONS]; +} ZMemtrackRefT; + +/*** Variables ********************************************************************/ + +static ZMemtrackRefT _ZMemtrack_Ref; + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _ZMemtrackLogPrintf + + \Description + Logs the information from the module + + \Input *pFormat - information to log + \Input ... - additional parameters + + \Version 09/18/2017 (eesponda) +*/ +/********************************************************************************F*/ +static void _ZMemtrackLogPrintf(const char *pFormat, ...) +{ + char strText[2048]; + int32_t iOffset = 0; + va_list Args; + ZMemtrackRefT *pRef = &_ZMemtrack_Ref; + + // format output + va_start(Args, pFormat); + iOffset += ds_vsnprintf(strText+iOffset, sizeof(strText)-iOffset, pFormat, Args); + va_end(Args); + + // forward to callback, or print if not callback installed + if (pRef->pLoggingCb != NULL) + { + pRef->pLoggingCb(strText, pRef->pUserData); + } + else + { + ZPrintf("zmemtrack: %s", strText); + } +} + +/*F********************************************************************************/ +/*! + \Function _ZMemtrackPrintLeak + + \Description + Print a memory leak to debug output. + + \Input *pElem - pointer to allocation that was leaked + + \Version 02/15/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ZMemtrackPrintLeak(ZMemtrackElemT *pElem) +{ + static const char _hex[] = "0123456789ABCDEF"; + uint32_t uBytes; + char strOutput[128]; + int32_t iOutput = 2; + + _ZMemtrackLogPrintf("allocated: [%d] bytes at [%p] with tag '%c%c%c%c'\n", pElem->uMemSize, pElem->pMem, + (uint8_t)(pElem->uTag>>24), (uint8_t)(pElem->uTag>>16), (uint8_t)(pElem->uTag>>8), (uint8_t)(pElem->uTag)); + + ds_memset(strOutput, ' ', sizeof(strOutput)-1); + strOutput[sizeof(strOutput)-1] = '\0'; + + for (uBytes = 0; (uBytes < pElem->uMemSize) && (uBytes < ZMEMTRACK_MEMDUMPBYTES); uBytes++, iOutput += 2) + { + unsigned char cByte = ((unsigned char *)(pElem->pMem))[uBytes]; + strOutput[iOutput] = _hex[cByte>>4]; + strOutput[iOutput+1] = _hex[cByte&0xf]; + strOutput[(iOutput/2)+40] = isprint(cByte) ? cByte : '.'; + if (uBytes > 0) + { + if (((uBytes+1) % 16) == 0) + { + strOutput[(iOutput/2)+40+1] = '\0'; + _ZMemtrackLogPrintf("%s\n", strOutput); + ds_memset(strOutput, ' ', sizeof(strOutput)-1); + strOutput[sizeof(strOutput)-1] = '\0'; + iOutput = 0; + } + else if (((uBytes+1) % 4) == 0) + { + iOutput++; + } + } + } + + if (((uBytes > ZMEMTRACK_MEMDUMPBYTES) && (uBytes % ZMEMTRACK_MEMDUMPBYTES) != 0) || (pElem->uMemSize < 16)) + { + strOutput[(iOutput/2)+40+1] = '\0'; + _ZMemtrackLogPrintf("%s\n", strOutput); + } +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ZMemtrackStartup + + \Description + Start up the ZMemtracking module. + + \Version 02/15/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ZMemtrackStartup(void) +{ + ZMemtrackRefT *pRef = &_ZMemtrack_Ref; + ds_memclr(pRef, sizeof(*pRef)); + pRef->bStarted = TRUE; +} + + +/*F********************************************************************************/ +/*! + \Function ZMemtrackShutdown + + \Description + Shut down the ZMemtracking module. + + \Version 02/15/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ZMemtrackShutdown(void) +{ + // dump the current status of the entire module + ZMemtrackPrint(ZMEMTRACK_PRINTFLAG_TRACKING, 0, NULL); + _ZMemtrack_Ref.bStarted = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function ZMemtrackCallback + + \Description + Set the logging callback + + \Input *pLoggingCb - logging function pointer + \Input *pUserData - additional data to pass along + + \Version 09/18/2017 (eesponda) +*/ +/********************************************************************************F*/ +void ZMemtrackCallback(ZMemtrackLogCbT *pLoggingCb, void *pUserData) +{ + ZMemtrackRefT *pRef = &_ZMemtrack_Ref; + pRef->pLoggingCb = pLoggingCb; + pRef->pUserData = pUserData; +} + +/*F********************************************************************************/ +/*! + \Function ZMemtrackAlloc + + \Description + Track an allocation. + + \Input *pMem - pointer to allocated memory block + \Input uSize - size of allocated memory block + \Input uTag - allocation tag + \Version 02/15/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ZMemtrackAlloc(void *pMem, uint32_t uSize, uint32_t uTag) +{ + ZMemtrackRefT *pRef = &_ZMemtrack_Ref; + uint32_t uMemEntry; + + // now if we got the memory, add to the list + if ((pMem == NULL) || (pRef->bStarted == FALSE)) + { + return; + } + + // find a clear spot + for (uMemEntry = 0; uMemEntry < ZMEMTRACK_MAXALLOCATIONS; uMemEntry++) + { + if (pRef->MemList[uMemEntry].pMem == NULL) + { + // get the memory location + pRef->uTotalMemory += uSize; + pRef->uNumAllocations += 1; + pRef->uTotalAllocations += 1; + + // update high-water tracking + if (pRef->uMaxAllocations < pRef->uNumAllocations) + { + pRef->uMaxAllocations = pRef->uNumAllocations; + } + if (pRef->uMaxMemory < pRef->uTotalMemory) + { + pRef->uMaxMemory = pRef->uTotalMemory; + } + + // store the info + pRef->MemList[uMemEntry].pMem = pMem; + pRef->MemList[uMemEntry].uMemSize = uSize; + pRef->MemList[uMemEntry].uTag = uTag; + + break; + } + } + + // check to see if we ran out of room to store this stuff + if (uMemEntry == ZMEMTRACK_MAXALLOCATIONS) + { + pRef->bOverflow = 1; + } +} + + +/*F********************************************************************************/ +/*! + \Function ZMemtrackFree + + \Description + Track a free operation. + + \Input *pMem - pointer to allocated memory block + \Input *pSize - [out] storage for memory block size + + \Version 02/15/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ZMemtrackFree(void *pMem, uint32_t *pSize) +{ + ZMemtrackRefT *pRef = &_ZMemtrack_Ref; + uint32_t uMemEntry; + + if ((pMem == NULL) || (pRef->bStarted == FALSE)) + { + *pSize = 0; + return; + } + + for (uMemEntry = 0, *pSize = 0; uMemEntry < ZMEMTRACK_MAXALLOCATIONS; uMemEntry++) + { + if (pRef->MemList[uMemEntry].pMem == pMem) + { + pRef->uTotalMemory -= pRef->MemList[uMemEntry].uMemSize; + pRef->uNumAllocations -= 1; + *pSize = pRef->MemList[uMemEntry].uMemSize; + ds_memclr(&pRef->MemList[uMemEntry], sizeof(pRef->MemList[uMemEntry])); + break; + } + } +} + + +/*F********************************************************************************/ +/*! + \Function ZMemtrackPrint + + \Description + Print overall memory info. + + \Input uFlags - ZMemtrack_PRINTFLAG_* + \Input uTag - [optional] if non-zero, only display memory leaks stamped with this tag + \Input *pModuleName - [optional] pointer to module name + + \Version 02/15/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ZMemtrackPrint(uint32_t uFlags, uint32_t uTag, const char *pModuleName) +{ + ZMemtrackRefT *pRef = &_ZMemtrack_Ref; + uint32_t uMemEntry; + + if (uFlags & ZMEMTRACK_PRINTFLAG_TRACKING) + { + _ZMemtrackLogPrintf("memory report\n"); + _ZMemtrackLogPrintf(" maximum number of allocations at once: [%u]\n", pRef->uMaxAllocations); + _ZMemtrackLogPrintf(" current number of allocations : [%u]\n", pRef->uNumAllocations); + _ZMemtrackLogPrintf(" total number of allocations ever : [%u]\n", pRef->uTotalAllocations); + _ZMemtrackLogPrintf(" maximum memory allocated : [%u] bytes\n", pRef->uMaxMemory); + _ZMemtrackLogPrintf(" current memory allocated : [%u] bytes\n", pRef->uTotalMemory); + _ZMemtrackLogPrintf("\n"); + } + + if (pRef->bOverflow) + { + _ZMemtrackLogPrintf("WARNING: Allocation watcher overflowed!"); + } + + // see if there were any leaks + for (uMemEntry = 0; uMemEntry < ZMEMTRACK_MAXALLOCATIONS; uMemEntry++) + { + ZMemtrackElemT *pElem = &pRef->MemList[uMemEntry]; + if ((pElem->pMem != NULL) && ((uTag == 0) || (pElem->uTag == uTag))) + { + break; + } + } + + // if there were leaks, display them + if (uMemEntry != ZMEMTRACK_MAXALLOCATIONS) + { + _ZMemtrackLogPrintf("detected %s memory leaks!\n", pModuleName != NULL ? pModuleName : ""); + for ( ; uMemEntry < ZMEMTRACK_MAXALLOCATIONS; uMemEntry++) + { + ZMemtrackElemT *pElem = &pRef->MemList[uMemEntry]; + if ((pElem->pMem != NULL) && ((uTag == 0) || (pElem->uTag == uTag))) + { + _ZMemtrackPrintLeak(pElem); + } + } + } + +} diff --git a/src/thirdparty/dirtysdk/contrib/voipaux/include/voipaux/voipopus.h b/src/thirdparty/dirtysdk/contrib/voipaux/include/voipaux/voipopus.h new file mode 100644 index 00000000..f54e6b31 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/voipaux/include/voipaux/voipopus.h @@ -0,0 +1,36 @@ +/*H*************************************************************************************/ +/*! + \File voipopus.c + + \Description + PC Audio Encoder / Decoder using Opus + + \Copyright + Copyright (c) Electronic Arts 2017. ALL RIGHTS RESERVED. + + \Version 07/03/2017 (eesponda) +*/ +/*************************************************************************************H*/ + +#ifndef _voipopus_h +#define _voipopus_h + +/*** Includes **************************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/voip/voipcodec.h" + +/*** Variables *************************************************************************/ + +#if defined(__cplusplus) +extern "C" { +#endif + +// opus codec definition +DIRTYCODE_API extern const VoipCodecDefT VoipOpus_CodecDef; + +#if defined(__cplusplus) +} +#endif + +#endif // _voipopus_h diff --git a/src/thirdparty/dirtysdk/contrib/voipaux/include/voipaux/voipspeex.h b/src/thirdparty/dirtysdk/contrib/voipaux/include/voipaux/voipspeex.h new file mode 100644 index 00000000..7f54f9b6 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/voipaux/include/voipaux/voipspeex.h @@ -0,0 +1,46 @@ +/*H********************************************************************************/ +/*! + \File voipspeex.h + + \Description + PC Audio Encoder / Decoder using Speex + + \Copyright + Copyright (c) Electronic Arts 2007. ALL RIGHTS RESERVED. + + \Version 1.0 04/02/2007 (cadam) First version +*/ +/********************************************************************************H*/ + +#ifndef _voipspeex_h +#define _voipspeex_h + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/voip/voipcodec.h" +#include "DirtySDK/voip/voip.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// speex codec definition +DIRTYCODE_API extern const VoipCodecDefT VoipSpeex_CodecDef; + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif // _voipspeex_h diff --git a/src/thirdparty/dirtysdk/contrib/voipaux/source/voipopus.c b/src/thirdparty/dirtysdk/contrib/voipaux/source/voipopus.c new file mode 100644 index 00000000..b7549c8e --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/voipaux/source/voipopus.c @@ -0,0 +1,582 @@ +/*H*************************************************************************************/ +/*! + \File voipopus.c + + \Description + PC Audio Encoder / Decoder using Opus + + \Copyright + Copyright (c) Electronic Arts 2017. ALL RIGHTS RESERVED. + + \Notes + We depend on the Speex resampler for resampling (recommended by Opus) + + \Version 07/03/2017 (eesponda) +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/dirtysock.h" + +#include +#include + +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/voip/voip.h" +#include "voipopus.h" + +/*** Defines ***************************************************************************/ + +//! maximum duration frame +#define VOIPOPUS_MAX_FRAME (5760) + +//! sampling rate we support in Hz +#if !defined(VOIPOPUS_DEFAULT_SAMPLING_RATE) + #define VOIPOPUS_DEFAULT_SAMPLING_RATE (16000) +#endif + +//! duration of the frame in milliseconds; 20ms +#define VOIPOPUS_FRAMEDURATION (20) + +//! number of channels we support (mono or stereo) +#define VOIPOPUS_DEFAULT_CHANNELS (1) + +//! hard-coded maximum output used when encoding, this is taken from value in voippacket.h (VOIP_MAXMICRPKTSIZE) +#define VOIPOPUS_MAX_OUTPUT (1238) + +//! speex resampler quality value (it is a number from 1 - 10) +#define VOIPOPUS_RESAMPLER_QUALITY (3) + +//! this much space will be needed to resample 20ms of audio +#define VOIPOPUS_RESAMPLE_BUFFER_SIZE ((VOIPOPUS_FRAMEDURATION * VOIPOPUS_DEFAULT_SAMPLING_RATE * sizeof(float)) / 1000) + +/*** Macros ****************************************************************************/ + +//! calculate the sample rate based on number of samples +#define VOIPOPUS_GetSampleRate(uNumSamples) (((uNumSamples) * 1000) / VOIPOPUS_FRAMEDURATION) + +/*** Type Definition *******************************************************************/ + +//! voipopus module state +typedef struct VoipOpusRefT +{ + VoipCodecRefT CodecState; + + int32_t iMemGroup; //!< memgroup id + void *pMemGroupUserData; //!< memgroup userdata + + int32_t iVerbose; //!< logging verbosity level + int32_t iOutputVolume; //!< volumn configuration + + uint32_t uSampleRateIn; //!< what is the sample rate of the data being passed to encode + uint32_t uResamplerRate; //!< sample rate that our resampler is configured to. this allows us to switch the resampler on and off without reallocation + uint8_t bInt32Input; //!< is the input data coming as 32 bit integers + uint8_t bFloatInput; //!< is the input data coming as 32 bit floats + uint8_t _pad[2]; + + SpeexResamplerState *pSpeexResampler; //!< resampler used if sample rate != VOIPOPUS_DEFAULT_SAMPLING_RATE + OpusEncoder *pEncoder; //!< opus encoder + OpusDecoder *aDecoders[1]; //!< opus variable decoders (must come last!) +} VoipOpusRefT; + +/*** Function Prototypes ***************************************************************/ + +static VoipCodecRefT *_VoipOpusCreate(int32_t iNumDecoders); +static void _VoipOpusDestroy(VoipCodecRefT *pState); +static int32_t _VoipOpusEncodeBlock(VoipCodecRefT *pState, uint8_t *pOutput, const int16_t *pInput, int32_t iNumSamples); +static int32_t _VoipOpusDecodeBlock(VoipCodecRefT *pState, int32_t *pOutput, const uint8_t *pInput, int32_t iInputBytes, int32_t iChannel); +static void _VoipOpusReset(VoipCodecRefT *pState); +static int32_t _VoipOpusControl(VoipCodecRefT *pState, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); +static int32_t _VoipOpusStatus(VoipCodecRefT *pState, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize); + +/*** Variables *************************************************************************/ + +//! voipopus codec definition +const VoipCodecDefT VoipOpus_CodecDef = +{ + _VoipOpusCreate, + _VoipOpusDestroy, + _VoipOpusEncodeBlock, + _VoipOpusDecodeBlock, + _VoipOpusReset, + _VoipOpusControl, + _VoipOpusStatus +}; + +#if DIRTYSOCK_ERRORNAMES +//! errors returned from the opus api +static const DirtyErrT _VoipOpus_aErrList[] = +{ + DIRTYSOCK_ErrorName(OPUS_OK), // 0; No Error + DIRTYSOCK_ErrorName(OPUS_BAD_ARG), // -1; One of more invalid/out of range arguments + DIRTYSOCK_ErrorName(OPUS_BUFFER_TOO_SMALL), // -2; The mode struct passed is invalid + DIRTYSOCK_ErrorName(OPUS_INTERNAL_ERROR), // -3; An internal error was detected + DIRTYSOCK_ErrorName(OPUS_INVALID_PACKET), // -4; The compressed data passed is corrupted + DIRTYSOCK_ErrorName(OPUS_UNIMPLEMENTED), // -5; Invalid/unsupported request number + DIRTYSOCK_ErrorName(OPUS_INVALID_STATE), // -6; An encoder or decoder structure is invalid or already freed + DIRTYSOCK_ErrorName(OPUS_ALLOC_FAIL), // -7; Memory allocation has failed + DIRTYSOCK_ListEnd() +}; +#endif + +/*** Private Functions *****************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function _VoipOpusCreate + + \Description + Create a Opus codec state. + + \Input iNumDecoders - number of decoder channels + + \Output + VoipCodecStateT * - pointer to opus codec state + + \Version 07/03/2017 (eesponda) +*/ +/*************************************************************************************F*/ +static VoipCodecRefT *_VoipOpusCreate(int32_t iNumDecoders) +{ + VoipOpusRefT *pState; + int32_t iResult, iMemGroup, iDecoder, iMemSize; + void *pMemGroupUserData; + + // query memgroup information + iMemGroup = VoipStatus(NULL, 'mgrp', 0, NULL, 0); + VoipStatus(NULL, 'mgud', 0, &pMemGroupUserData, sizeof(pMemGroupUserData)); + + // allocate and initialize module state + iMemSize = sizeof(*pState) + (sizeof(OpusDecoder *) * (iNumDecoders - 1)); + if ((pState = (VoipOpusRefT *)DirtyMemAlloc(iMemSize, VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipopus: unable to allocate module state\n")); + return(NULL); + } + ds_memclr(pState, iMemSize); + pState->CodecState.pCodecDef = &VoipOpus_CodecDef; + pState->CodecState.iDecodeChannels = iNumDecoders; + pState->CodecState.bVadEnabled = TRUE; + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + pState->iVerbose = 2; + pState->iOutputVolume = 1 << VOIP_CODEC_OUTPUT_FRACTIONAL; + + // allocate and initialize the encoder + if ((iMemSize = opus_encoder_get_size(VOIPOPUS_DEFAULT_CHANNELS)) <= 0) + { + NetPrintf(("voipopus: unable to get encoder size for allocation\n")); + _VoipOpusDestroy(&pState->CodecState); + return(NULL); + } + if ((pState->pEncoder = (OpusEncoder *)DirtyMemAlloc(iMemSize, VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipopus: unable to allocate the encoder\n")); + _VoipOpusDestroy(&pState->CodecState); + return(NULL); + } + if ((iResult = opus_encoder_init(pState->pEncoder, VOIPOPUS_DEFAULT_SAMPLING_RATE, VOIPOPUS_DEFAULT_CHANNELS, OPUS_APPLICATION_VOIP)) != OPUS_OK) + { + NetPrintf(("voipopus: unable to initialize the encoder (err=%s)\n", DirtyErrGetNameList(iResult, _VoipOpus_aErrList))); + + _VoipOpusDestroy(&pState->CodecState); + return(NULL); + } + // allocate and initialize the decoders + if ((iMemSize = opus_decoder_get_size(VOIPOPUS_DEFAULT_CHANNELS)) <= 0) + { + NetPrintf(("voipopus: unable to get decoder size for allocation\n")); + _VoipOpusDestroy(&pState->CodecState); + return(NULL); + } + for (iDecoder = 0; iDecoder < iNumDecoders; iDecoder += 1) + { + if ((pState->aDecoders[iDecoder] = (OpusDecoder *)DirtyMemAlloc(iMemSize, VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipopus: unable to allocate the decoder\n")); + _VoipOpusDestroy(&pState->CodecState); + return(NULL); + } + if ((iResult = opus_decoder_init(pState->aDecoders[iDecoder], VOIPOPUS_DEFAULT_SAMPLING_RATE, VOIPOPUS_DEFAULT_CHANNELS)) != OPUS_OK) + { + NetPrintf(("voipopus: unable to initialize the decoder (err=%s)\n", DirtyErrGetNameList(iResult, _VoipOpus_aErrList))); + _VoipOpusDestroy(&pState->CodecState); + return(NULL); + } + } + return(&pState->CodecState); +} + +/*F*************************************************************************************/ +/*! + \Function _VoipOpusDestroy + + \Description + Destroy the Opus codec state + + \Input *pState - codec state + + \Version 07/03/2017 (eesponda) +*/ +/*************************************************************************************F*/ +static void _VoipOpusDestroy(VoipCodecRefT *pState) +{ + OpusDecoder **pDecoder; + int32_t iDecoder; + VoipOpusRefT *pOpus = (VoipOpusRefT *)pState; + + for (iDecoder = 0; iDecoder < pOpus->CodecState.iDecodeChannels; iDecoder += 1) + { + if ((pDecoder = &pOpus->aDecoders[iDecoder]) != NULL) + { + DirtyMemFree(*pDecoder, VOIP_MEMID, pOpus->iMemGroup, pOpus->pMemGroupUserData); + *pDecoder = NULL; + } + } + if (pOpus->pEncoder != NULL) + { + DirtyMemFree(pOpus->pEncoder, VOIP_MEMID, pOpus->iMemGroup, pOpus->pMemGroupUserData); + pOpus->pEncoder = NULL; + } + + if (pOpus->pSpeexResampler != NULL) + { + speex_resampler_destroy(pOpus->pSpeexResampler); + pOpus->pSpeexResampler = NULL; + } + + DirtyMemFree(pOpus, VOIP_MEMID, pOpus->iMemGroup, pOpus->pMemGroupUserData); +} + +/*F*************************************************************************************/ +/*! + \Function _VoipOpusConvertInt32ToFloat + + \Description + Convert int32_t samples into floats, within the same buffer passed in + + \Input *pState - codec state + \Input *pInSamples - input int32_t samples + \Input iNumSamples - number of samples in pInSamples + + \Output + int32_t - number of samples converted + + \Version 04/09/2019 (cvienneau) +*/ +/*************************************************************************************F*/ +static int32_t _VoipOpusConvertInt32ToFloat(VoipCodecRefT *pState, uint8_t *pInBytes, int32_t iNumSamples) +{ + VoipOpusRefT *pOpus = (VoipOpusRefT *)pState; + + if (pOpus->bInt32Input) + { + int32_t *pInput = (int32_t*)pInBytes; + float *pOutput = (float*)pInBytes; + + int32_t iBufferIndex; + for (iBufferIndex = 0; iBufferIndex < iNumSamples; ++iBufferIndex) + { + pOutput[iBufferIndex] = (float)pInput[iBufferIndex] / INT32_MAX; + } + return (iBufferIndex); + } + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function _VoipOpusResample + + \Description + Resample in coming samples to the rate of VOIPOPUS_DEFAULT_SAMPLING_RATE. + If VOIPOPUS_DEFAULT_SAMPLING_RATE equal iNumSamples no resampling is done. + + \Input *pState - codec state + \Input *pInBytes - input samples in byte array + \Input iNumSamples - number of samples in pInBytes + \Input *pOutBytes - output samples written + \Input uOutBuffSize - size of pOutBytes + + \Output + int32_t - number of samples written + + \Version 03/26/2019 (tcho) +*/ +/*************************************************************************************F*/ +static int32_t _VoipOpusResample(VoipCodecRefT *pState, const uint8_t *pInBytes, int32_t iNumSamples, uint8_t *pOutBytes, uint32_t uOutBuffSize) +{ + VoipOpusRefT *pOpus = (VoipOpusRefT *)pState; + int32_t iOutputSamples = iNumSamples; //default skipped resample + int32_t iError; + + if (pOpus->bFloatInput) + { + iOutputSamples = uOutBuffSize / sizeof(float); //goes into speex_resampler_process_float as buffer size, comes out as samples written + if ((iError = speex_resampler_process_float(pOpus->pSpeexResampler, 0, (const float *)pInBytes, (uint32_t *)&iNumSamples, (float *)pOutBytes, (uint32_t *)&iOutputSamples)) != 0) + { + NetPrintf(("voipopus: error resampling float, %d input samples (Error = %d).\n", iNumSamples, iError)); + } + } + else + { + iOutputSamples = uOutBuffSize / sizeof(int16_t); //goes into speex_resampler_process_int as buffer size, comes out as samples written + if ((iError = speex_resampler_process_int(pOpus->pSpeexResampler, 0, (const int16_t *)pInBytes, (uint32_t *)&iNumSamples, (int16_t *)pOutBytes, (uint32_t *)&iOutputSamples)) != 0) + { + NetPrintf(("voipopus: error resampling int16, %d input samples, %d (Error = %d).\n", iNumSamples, iError)); + } + } + + return(iOutputSamples); +} + + +/*F*************************************************************************************/ +/*! + \Function _VoipOpusEncodeBlock + + \Description + Encode a buffer 16-bit audio or float sample using the Opus encoder + + \Input *pState - codec state + \Input *pOutput - [out] outbut buffer + \Input *pInput - input buffer + \Input iNumSamples - the number of samples to encode + + \Output + int32_t - positive=number of encoded bytes, negative=error + + \Version 07/03/2017 (eesponda) +*/ +/*************************************************************************************F*/ +static int32_t _VoipOpusEncodeBlock(VoipCodecRefT *pState, uint8_t *pOutput, const int16_t *pInput, int32_t iNumSamples) +{ + VoipOpusRefT *pOpus = (VoipOpusRefT *)pState; + int32_t iResult = -1; + uint32_t uSampleRateIn; + uint8_t aResampledData[VOIPOPUS_RESAMPLE_BUFFER_SIZE]; + + /* if we haven't set a sample rate manually via the 'insr' control calculate the sample rate based on number of samples. this is with the assumption of 20ms audio + $$todo$$ investigate removing the manual calls of 'insr' on xbox if we can confirm that this calculation works */ + uSampleRateIn = (pOpus->uSampleRateIn == 0) ? VOIPOPUS_GetSampleRate(iNumSamples) : pOpus->uSampleRateIn; + + // convert the data to a useable format if needed + iResult = _VoipOpusConvertInt32ToFloat(pState, (uint8_t*)pInput, iNumSamples); + + if (uSampleRateIn != VOIPOPUS_DEFAULT_SAMPLING_RATE) + { + // re-create the resampler if needed + if (uSampleRateIn != pOpus->uResamplerRate) + { + if (pOpus->pSpeexResampler != NULL) + { + speex_resampler_destroy(pOpus->pSpeexResampler); + } + if ((pOpus->pSpeexResampler = speex_resampler_init_frac(VOIPOPUS_DEFAULT_CHANNELS, uSampleRateIn, VOIPOPUS_DEFAULT_SAMPLING_RATE, uSampleRateIn, VOIPOPUS_DEFAULT_SAMPLING_RATE, VOIPOPUS_RESAMPLER_QUALITY, &iResult)) == NULL) + { + NetPrintf(("voipopus: unable to allocate resampler (Error = %d).\n", iResult)); + return(iResult); + } + pOpus->uResamplerRate = uSampleRateIn; + } + + // resample the data if needed + iResult = _VoipOpusResample(pState, (uint8_t*)pInput, iNumSamples, aResampledData, sizeof(aResampledData)); + if (iResult != iNumSamples) + { + pInput = (const int16_t*)aResampledData; + iNumSamples = iResult; + } + } + + // encode as float or int16 + if (pOpus->bFloatInput) + { + if ((iResult = opus_encode_float(pOpus->pEncoder, (float*)pInput, iNumSamples, pOutput, VOIPOPUS_MAX_OUTPUT)) < 0) + { + NetPrintf(("voipopus: unable to encode float (err=%s)\n", DirtyErrGetNameList(iResult, _VoipOpus_aErrList))); + } + } + else + { + if ((iResult = opus_encode(pOpus->pEncoder, pInput, iNumSamples, pOutput, VOIPOPUS_MAX_OUTPUT)) < 0) + { + NetPrintf(("voipopus: unable to encode int16_t (err=%s)\n", DirtyErrGetNameList(iResult, _VoipOpus_aErrList))); + } + } + return(iResult); +} + +/*F*************************************************************************************/ +/*! + \Function _VoipOpusDecodeBlock + + \Description + Decode a Opus encoded input to 16-bit linear PCM samples + + \Input *pState - codec state + \Input *pOutput - [out] outbut buffer + \Input *pInput - input buffer + \Input iInputBytes - size of the input buffer + \Input iChannel - the decode channel for which we are decoding data + + \Output + int32_t - positive=number of decoded samples, negative=error + + \Version 07/03/2017 (eesponda) +*/ +/*************************************************************************************F*/ +static int32_t _VoipOpusDecodeBlock(VoipCodecRefT *pState, int32_t *pOutput, const uint8_t *pInput, int32_t iInputBytes, int32_t iChannel) +{ + VoipOpusRefT *pOpus = (VoipOpusRefT *)pState; + int32_t iResult, iSample; + int16_t aOutput[VOIPOPUS_MAX_FRAME]; + + if ((iChannel < 0) || (iChannel > pOpus->CodecState.iDecodeChannels)) + { + NetPrintf(("voipopus: trying to decode with invalid decoder channel\n")); + return(-1); + } + + if ((iResult = opus_decode(pOpus->aDecoders[iChannel], pInput, iInputBytes, aOutput, VOIPOPUS_MAX_FRAME, 0)) < 0) + { + NetPrintf(("voipopus: unable to decode (err=%s)\n", DirtyErrGetNameList(iResult, _VoipOpus_aErrList))); + } + + // accumulate output in the expected format + for (iSample = 0; iSample < iResult; iSample += 1) + { + pOutput[iSample] += (aOutput[iSample] * pOpus->iOutputVolume) >> VOIP_CODEC_OUTPUT_FRACTIONAL; + } + return(iResult); +} + +/*F*************************************************************************************/ +/*! + \Function _VoipOpusReset + + \Description + Reset the codec state + + \Input *pState - codec state + + \Version 07/03/2017 (eesponda) +*/ +/*************************************************************************************F*/ +static void _VoipOpusReset(VoipCodecRefT *pState) +{ + int32_t iChannel; + VoipOpusRefT *pOpus = (VoipOpusRefT *)pState; + + opus_encoder_ctl(pOpus->pEncoder, OPUS_RESET_STATE); + + for (iChannel = 0; iChannel < pOpus->CodecState.iDecodeChannels; iChannel += 1) + { + opus_decoder_ctl(pOpus->aDecoders[iChannel], OPUS_RESET_STATE); + } +} + +/*F*************************************************************************************/ +/*! + \Function _VoipOpusControl + + \Description + Set control options + + \Input *pState - codec state + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + 'plvl' - Set the output volumn + 'infl' - Set if the input samples are float via iValue, (default int16) + 'inin' - Set if the input samples are int32_t via iValue, (default int16) + 'insr' - Set the input sample rate + 'spam' - Set debug output verbosity + \endverbatim + + \Version 07/03/2017 (eesponda) +*/ +/*************************************************************************************F*/ +static int32_t _VoipOpusControl(VoipCodecRefT *pState, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + VoipOpusRefT *pOpus = (VoipOpusRefT *)pState; + + if (iControl == 'plvl') + { + pOpus->iOutputVolume = iValue; + return(0); + } + if (iControl == 'infl') + { + pOpus->bInt32Input = FALSE; + pOpus->bFloatInput = iValue; + return(0); + } + if (iControl == 'inin') + { + pOpus->bInt32Input = iValue; //int32s are converted to float, so we set both + pOpus->bFloatInput = iValue; + return(0); + } + + if (iControl == 'insr') + { + pOpus->uSampleRateIn = iValue; + return(0); + } + if (iControl == 'spam') + { + pOpus->iVerbose = iValue; + return(0); + } + // unhandled control + return(-1); +} + +/*F*************************************************************************************/ +/*! + \Function _VoipOpusStatus + + \Description + Get codec status + + \Input *pState - codec state + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuffer - [out] storage for selector output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iSelect can be one of the following: + + \verbatim + 'vlen' - returns TRUE to indicate we are using variable length frames + \endverbatim + + \Version 07/03/2017 (eesponda) +*/ +/*************************************************************************************F*/ +static int32_t _VoipOpusStatus(VoipCodecRefT *pState, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize) +{ + if (iSelect == 'vlen') + { + *(uint8_t *)pBuffer = TRUE; + return(0); + } + // unhandle selector + return(-1); +} diff --git a/src/thirdparty/dirtysdk/contrib/voipaux/source/voipspeex.c b/src/thirdparty/dirtysdk/contrib/voipaux/source/voipspeex.c new file mode 100644 index 00000000..1086aa64 --- /dev/null +++ b/src/thirdparty/dirtysdk/contrib/voipaux/source/voipspeex.c @@ -0,0 +1,663 @@ +/*H*************************************************************************************************/ +/*! + \File voipspeex.c + + \Description + PC Audio Encoder / Decoder using Speex + + \Copyright + Copyright (c) Electronic Arts 2007. ALL RIGHTS RESERVED. + + \Version 1.0 04/02/2007 (cadam) First version +*/ +/*************************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include +#include +#include + +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "voipspeex.h" + +#include +#include + +/*** Defines ***************************************************************************/ + +#define VOIPSPEEX_ENABLED (TRUE) // enable speex codec + +#ifndef VOIPSPEEX_PREPROCESS +#define VOIPSPEEX_PREPROCESS (0) // Speex preprocessor +#endif + +// encode/decode information +#define VOIPSPEEX_FRAME_SIZE (160) +#define VOIPSPEEX_MAX_BYTES (200) +#define VOIPSPEEX_COMP_SIZE (38) + +// speex settings +#define VOIPSPEEX_COMPLEXITY (1) +#define VOIPSPEEX_QUALITY (8) +#define VOIPSPEEX_PERCEPTUAL (1) + +// speex preprocessor settings +#define VOIPSPEEX_DENOISE (1) +#define VOIPSPEEX_AGC (1) +#define VOIPSPEEX_PROB_START (30) +#define VOIPSPEEX_PROB_CONTINUE (7) + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! voipspeex decoder ref +typedef struct VoipSpeexDecoderT +{ + int32_t iChannel; + void *pDecodeState; +} VoipSpeexDecoderT; + +//! voipspeex module state +typedef struct VoipSpeexStateT +{ + VoipCodecRefT CodecState; + + // module memory group + int32_t iMemGroup; + void *pMemGroupUserData; + + int32_t iNumChannels; + + int32_t iOutputVolume; + + SpeexBits sEncodeBits; + SpeexBits *pDecodeBits; + + void *pEncodeState; + VoipSpeexDecoderT *pDecoders; + SpeexPreprocessState *pPreprocessor; + + #if DIRTYCODE_LOGGING + int32_t iDebugLevel; + #endif +} VoipSpeexStateT; + + +/*** Function Prototypes ***************************************************************/ + +#if VOIPSPEEX_ENABLED +static VoipCodecRefT *_VoipSpeexCreate(int32_t iDecodeChannels); +static void _VoipSpeexDestroy(VoipCodecRefT *pState); +static int32_t _VoipSpeexEncodeBlock(VoipCodecRefT *pState, uint8_t *pOut, const int16_t *pInp, int32_t iNumSamples); +static int32_t _VoipSpeexDecodeBlock(VoipCodecRefT *pState, int32_t *pOut, const uint8_t *pInp, int32_t iInputBytes, int32_t iChannel); +static void _VoipSpeexReset(VoipCodecRefT *pState); +static int32_t _VoipSpeexControl(VoipCodecRefT *pState, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); +static int32_t _VoipSpeexStatus(VoipCodecRefT *pState, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize); +#else +static VoipCodecRefT *_VoipSpeexCreate(int32_t iDecodeChannels) { return NULL; } +static void _VoipSpeexDestroy(VoipCodecRefT *pState) { } +static int32_t _VoipSpeexEncodeBlock(VoipCodecRefT *pState, uint8_t *pOut, const int16_t *pInp, int32_t iNumSamples) { return -1; } +static int32_t _VoipSpeexDecodeBlock(VoipCodecRefT *pState, int32_t *pOut, const uint8_t *pInp, int32_t iInputBytes, int32_t iChannel) { return -1; } +static void _VoipSpeexReset(VoipCodecRefT *pState) { } +static int32_t _VoipSpeexControl(VoipCodecRefT *pState, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) { return -1; } +static int32_t _VoipSpeexStatus(VoipCodecRefT *pState, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize) { return -1; } +#endif + +/*** Variables *************************************************************************/ + +//! voipspeex codec block +const VoipCodecDefT VoipSpeex_CodecDef = +{ + _VoipSpeexCreate, + _VoipSpeexDestroy, + _VoipSpeexEncodeBlock, + _VoipSpeexDecodeBlock, + _VoipSpeexReset, + _VoipSpeexControl, + _VoipSpeexStatus, +}; + +#if VOIPSPEEX_ENABLED + +/*** Private Functions *****************************************************************/ + +/*F*************************************************************************************************/ +/*! + \Function _VoipSpeexSetControls + + \Description + Sets all the controls on the codec + + \Input pCodecState - codec state + + \Version 01/12/08 (grouse) +*/ +/*************************************************************************************************F*/ +static void _VoipSpeexSetControls( VoipCodecRefT *pCodecState ) +{ + VoipSpeexStateT *pState = (VoipSpeexStateT *)pCodecState; + + int32_t iChannel; + + int32_t iComplexity = VOIPSPEEX_COMPLEXITY; + int32_t iQuality = VOIPSPEEX_QUALITY; + int32_t iPerceptual = VOIPSPEEX_PERCEPTUAL; + +#if VOIPSPEEX_PREPROCESS + int32_t iDenoise = VOIPSPEEX_DENOISE; + int32_t iAGC = VOIPSPEEX_AGC; + int32_t iProbStart = VOIPSPEEX_PROB_START; + int32_t iProbContinue = VOIPSPEEX_PROB_CONTINUE; +#endif + + int32_t iReturnVal = 0; + + iReturnVal = speex_encoder_ctl(pState->pEncodeState, SPEEX_SET_COMPLEXITY, &iComplexity); + if( iReturnVal != 0 ) + { + NetPrintf(("voipspeex: error setting encoder control SPEEX_SET_COMPLEXITY with code %d\n", iReturnVal)); + } + + iReturnVal = speex_encoder_ctl(pState->pEncodeState, SPEEX_SET_QUALITY, &iQuality); + if( iReturnVal != 0 ) + { + NetPrintf(("voipspeex: error setting encoder control SPEEX_SET_QUALITY with code %d\n", iReturnVal)); + } + + for (iChannel=0; iChannel < pState->CodecState.iDecodeChannels; iChannel++) + { + iReturnVal = speex_decoder_ctl(pState->pDecoders[iChannel].pDecodeState, SPEEX_SET_ENH, &iPerceptual); + if( iReturnVal != 0 ) + { + NetPrintf(("voipspeex: error setting decoder control SPEEX_SET_ENH for decoder %d with code %d\n", iChannel, iReturnVal)); + } + } + +#if VOIPSPEEX_PREPROCESS + iReturnVal = speex_preprocess_ctl(pState->pPreprocessor, SPEEX_PREPROCESS_SET_DENOISE, &iDenoise); + if( iReturnVal != 0 ) + { + NetPrintf(("voipspeex: error setting preprocessor control SPEEX_PREPROCESS_SET_DENOISE with code %d\n", iReturnVal)); + } + + iReturnVal = speex_preprocess_ctl(pState->pPreprocessor, SPEEX_PREPROCESS_SET_AGC, &iAGC); + if( iReturnVal != 0 ) + { + NetPrintf(("voipspeex: error setting preprocessor control SPEEX_PREPROCESS_SET_AGC with code %d\n", iReturnVal)); + } + + iReturnVal = speex_preprocess_ctl(pState->pPreprocessor, SPEEX_PREPROCESS_SET_PROB_START, &iProbStart); + if( iReturnVal != 0 ) + { + NetPrintf(("voipspeex: error re-setting preprocessor control SPEEX_PREPROCESS_SET_PROB_START with code %d\n", iReturnVal)); + } + + iReturnVal = speex_preprocess_ctl(pState->pPreprocessor, SPEEX_PREPROCESS_SET_PROB_CONTINUE, &iProbContinue); + if( iReturnVal != 0 ) + { + NetPrintf(("voipspeex: error re-setting preprocessor control SPEEX_PREPROCESS_SET_PROB_CONTINUE with code %d\n", iReturnVal)); + } +#endif +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipSpeexCreate + + \Description + Create a Speex codec state. + + \Input iDecodeChannels - number of decoder channels + + \Output + VoipCodecStateT * - pointer to speex codec state + + \Version 04/02/2007 (cadam) +*/ +/*************************************************************************************************F*/ +static VoipCodecRefT *_VoipSpeexCreate(int32_t iDecodeChannels) +{ + VoipSpeexStateT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + int32_t iChannel; + + // Query mem group id + iMemGroup = VoipStatus(NULL, 'mgrp', 0, NULL, 0); + + // Query mem group user data + VoipStatus(NULL, 'mgud', 0, &pMemGroupUserData, sizeof(pMemGroupUserData)); + + // allocate memory for the state structure and initialize the variables + pState = (VoipSpeexStateT *)DirtyMemAlloc(sizeof(VoipSpeexStateT), VOIP_MEMID, iMemGroup, pMemGroupUserData); + ds_memclr(pState, sizeof(VoipSpeexStateT)); + NetPrintfVerbose((pState->iDebugLevel, 0, "voipspeex: allocated module reference at %p\n", pState)); + + // initialize the state variables + pState->CodecState.pCodecDef = &VoipSpeex_CodecDef; + pState->CodecState.iDecodeChannels = iDecodeChannels; + pState->CodecState.bVadEnabled = TRUE; + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + pState->iNumChannels = iDecodeChannels; + + // set the power threshold and output level + pState->iOutputVolume = 1 << VOIP_CODEC_OUTPUT_FRACTIONAL; + + // initialize the speex bits + speex_bits_init(&pState->sEncodeBits); + + // create and initialize the encoder + pState->pEncodeState = speex_encoder_init(&speex_nb_mode); + if (pState->pEncodeState == NULL) + { + NetPrintf(("voipspeex: failed to create encoder\n")); + speex_bits_destroy(&pState->sEncodeBits); + DirtyMemFree(pState, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + return(NULL); + } + + // allocate an array of pointers to hold pointers to one decoder per channel + pState->pDecoders = DirtyMemAlloc(sizeof(VoipSpeexDecoderT) * iDecodeChannels, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + NetPrintfVerbose((pState->iDebugLevel, 0, "voipspeex: decoder array allocated %d bytes at %p\n", sizeof(VoipSpeexDecoderT) * iDecodeChannels, pState->pDecoders)); + pState->pDecodeBits = DirtyMemAlloc(sizeof(SpeexBits) * iDecodeChannels, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + NetPrintfVerbose((pState->iDebugLevel, 0, "voipspeex: decode bits array allocated %d bytes at %p\n", sizeof(SpeexBits) * iDecodeChannels, pState->pDecodeBits)); + + // initialize the decoders + for (iChannel = 0; iChannel < pState->iNumChannels; iChannel++) + { + pState->pDecoders[iChannel].iChannel = iChannel; + pState->pDecoders[iChannel].pDecodeState = speex_decoder_init(&speex_nb_mode); + + if (pState->pDecoders[iChannel].pDecodeState == NULL) + { + // we only need to destroy the ones before the failure + pState->iNumChannels = iChannel; + + NetPrintf(("voipspeex: failed to create decoder %d\n", iChannel)); + for (iChannel = 0; iChannel < pState->iNumChannels; iChannel++) + { + speex_decoder_destroy(pState->pDecoders[iChannel].pDecodeState); + speex_bits_destroy(&pState->pDecodeBits[iChannel]); + } + + DirtyMemFree(pState->pDecoders, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + DirtyMemFree(pState->pDecodeBits, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + speex_encoder_destroy(pState->pEncodeState); + DirtyMemFree(pState, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + + return(NULL); + } + + speex_bits_init(&pState->pDecodeBits[iChannel]); + } + +#if VOIPSPEEX_PREPROCESS + pState->pPreprocessor = speex_preprocess_state_init(VOIPSPEEX_FRAME_SIZE, 8000); + if (pState->pPreprocessor == NULL) + { + NetPrintf(("voipspeex: Failed to create pre-processor\n")); + + for (iChannel = 0; iChannel < pState->iNumChannels; iChannel++) + { + if (pState->pDecoders[iChannel].pDecodeState) + { + speex_decoder_destroy(pState->pDecoders[iChannel].pDecodeState); + speex_bits_destroy(&pState->pDecodeBits[iChannel]); + } + } + + DirtyMemFree(pState->pDecoders, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + DirtyMemFree(pState->pDecodeBits, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + speex_encoder_destroy(pState->pEncodeState); + DirtyMemFree(pState, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + + return(NULL); + } +#else + pState->pPreprocessor = NULL; +#endif + + _VoipSpeexSetControls(&pState->CodecState); + + NetPrintfVerbose((pState->iDebugLevel, 0, "voipspeex: returning %p\n", &pState->CodecState)); + return(&pState->CodecState); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipSpeexDestroy + + \Description + Destroy a Speex codec state. + + \Input *pState - state to destroy + + \Version 04/02/2007 (cadam) +*/ +/*************************************************************************************************F*/ +static void _VoipSpeexDestroy(VoipCodecRefT *pCodecState) +{ + int32_t i; + + VoipSpeexStateT *pState = (VoipSpeexStateT *)pCodecState; + +#if VOIPSPEEX_PREPROCESS + speex_preprocess_state_destroy(pState->pPreprocessor); +#endif + + // destroy the decoders then free the memory that was allocated for them + for (i = 0; i < pState->iNumChannels; i++) + { + speex_decoder_destroy(pState->pDecoders[i].pDecodeState); + speex_bits_destroy(&pState->pDecodeBits[i]); + } + + // free the array that held the decoders + DirtyMemFree(pState->pDecoders, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + + // free the array that held the decode bits + DirtyMemFree(pState->pDecodeBits, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + + // destroy the encoder + speex_encoder_destroy(pState->pEncodeState); + + // destroy the SpeexBits + speex_bits_destroy(&pState->sEncodeBits); + + // free the state + DirtyMemFree(pState, VOIP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipSpeexEncodeBlock + + \Description + Encode a buffer 16-bit audio samples using the Speex encoder. + + \Input *pState - pointer to encode state + \Input *pOut - pointer to output buffer + \Input *pInp - pointer to input buffer + \Input iNumSamples - number of samples to encode + + \Output + int32_t - size of compressed data in bytes + + \Version 04/05/2007 (cadam) +*/ +/*************************************************************************************************F*/ +static int32_t _VoipSpeexEncodeBlock(VoipCodecRefT *pCodecState, unsigned char *pOut, const int16_t *pInp, int32_t iNumSamples) +{ + int32_t iFrame, iNumFrames, iSample; + VoipSpeexStateT *pState = (VoipSpeexStateT *)pCodecState; + unsigned char *pOutput = pOut; + const int16_t *pInput = pInp; + spx_int16_t iFrameBuffer[VOIPSPEEX_FRAME_SIZE]; + int32_t iNumBytes, iNumBytesEncoded = 0; + + iNumFrames = iNumSamples/VOIPSPEEX_FRAME_SIZE; + + NetPrintfVerbose((pState->iDebugLevel, 2, "voipspeex: encoding %d samples (%d frames)\n", iNumSamples, iNumFrames)); + + if ((iNumSamples % VOIPSPEEX_FRAME_SIZE) != 0) + { + NetPrintf(("voipspeex: error - speex encoder can only encode multiples of %d samples.\n", VOIPSPEEX_FRAME_SIZE)); + return(0); + } + + for (iFrame = 0; iFrame < iNumFrames; iFrame++) + { + ds_memclr(iFrameBuffer, sizeof(iFrameBuffer)); + + // convert the 16 bit input values to a float buffer for Speex + for (iSample = 0; iSample < VOIPSPEEX_FRAME_SIZE; iSample++) + { + iFrameBuffer[iSample] = pInput[iSample]; + } + + // reset the SpeexBits + speex_bits_reset(&pState->sEncodeBits); + +#if VOIPSPEEX_PREPROCESS + speex_preprocess(pState->pPreprocessor, iFrameBuffer, NULL); +#endif + + // encode the frame + speex_encode_int(pState->pEncodeState, iFrameBuffer, &pState->sEncodeBits); + + // write the result to the output and get the number of bytes written + iNumBytes = speex_bits_write(&pState->sEncodeBits, (char *)pOutput, VOIPSPEEX_MAX_BYTES); + + // increment the pointers and the total number of bytes written + pInput += VOIPSPEEX_FRAME_SIZE; + pOutput += iNumBytes; + iNumBytesEncoded += iNumBytes; + } + + return(iNumBytesEncoded); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipSpeexDecodeBlock + + \Description + Decode Speex-encoded input to 16-bit linear PCM samples, and accumulate in the given output buffer. + + \Input *pState - pointer to decode state + \Input *pOut - pointer to output buffer + \Input *pInp - pointer to input buffer + \Input iInputBytes - size of input data + \Input iChannel - the decode channel for which we are decoding data + + \Output + int32_t - number of samples decoded + + \Version 04/05/2007 (cadam) +*/ +/*************************************************************************************************F*/ +static int32_t _VoipSpeexDecodeBlock(VoipCodecRefT *pCodecState, int32_t *pOut, const unsigned char *pInp, int32_t iInputBytes, int32_t iChannel) +{ + int32_t iFrame, iFrameSize, iNumFrames, iSample; + VoipSpeexStateT *pState = (VoipSpeexStateT *)pCodecState; + int32_t *pOutput = pOut; + unsigned char *pInput = (unsigned char *)pInp; + spx_int16_t iFrameBuffer[VOIPSPEEX_FRAME_SIZE]; + + iFrameSize = VOIPSPEEX_COMP_SIZE; + iNumFrames = iInputBytes/iFrameSize; + if (iInputBytes == 0) + { + NetPrintf(("voipspeex: no data to decode\n")); + return(0); + } + if ((iInputBytes % iFrameSize) != 0) + { + NetPrintf(("voipspeex: speex decoder can only decode multiples of %d bytes (%d submitted)\n", iFrameSize, iInputBytes)); + return(0); + } + + NetPrintfVerbose((pState->iDebugLevel, 2, "voipspeex: decoding %d bytes (%d frames)\n", iInputBytes, iNumFrames)); + + for (iFrame = 0; iFrame < iNumFrames; iFrame++) + { + ds_memclr(iFrameBuffer, sizeof(iFrameBuffer)); + + // reset the SpeexBits + speex_bits_reset(&pState->pDecodeBits[iChannel]); + + // read the bits from the input + speex_bits_read_from(&pState->pDecodeBits[iChannel], (char *)pInput, iFrameSize); + + // decode the bits + speex_decode_int(pState->pDecoders[iChannel].pDecodeState, &pState->pDecodeBits[iChannel], iFrameBuffer); + + // convert the float buffer to an int32_t array for Speex + for (iSample = 0; iSample < VOIPSPEEX_FRAME_SIZE; iSample++) + { + pOutput[iSample] += (((int32_t)iFrameBuffer[iSample] * pState->iOutputVolume) >> VOIP_CODEC_OUTPUT_FRACTIONAL); + } + + // increment the pointers and the total number of bytes written + pInput += iFrameSize; + pOutput += VOIPSPEEX_FRAME_SIZE; + } + + return(iNumFrames * VOIPSPEEX_FRAME_SIZE); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipSpeexReset + + \Description + Resets codec state. + + \Input *pState - pointer to decode state + + \Version 04/05/2007 (cadam) +*/ +/*************************************************************************************************F*/ +static void _VoipSpeexReset(VoipCodecRefT *pCodecState) +{ + int32_t i; + + VoipSpeexStateT *pState = (VoipSpeexStateT *)pCodecState; + + // reset the SpeexBits + speex_bits_reset(&pState->sEncodeBits); + + // reset the encoder + speex_encoder_ctl(pState->pEncodeState, SPEEX_RESET_STATE, NULL); + + // reset the decoders + for (i = 0; i < pState->iNumChannels; i++) + { + speex_decoder_ctl(pState->pDecoders[i].pDecodeState, SPEEX_RESET_STATE, NULL); + } + + _VoipSpeexSetControls(&pState->CodecState); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipSpeexControl + + \Description + Modifies parameters of the codec + + \Input *pState - pointer to decode state + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + 'plvl' - Set the output power level + 'spam' - Set debug output verbosity (debug only) + \endverbatim + + \Version 03/12/2008 (grouse) +*/ +/*************************************************************************************************F*/ +static int32_t _VoipSpeexControl(VoipCodecRefT *pCodecState, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + VoipSpeexStateT *pState = (VoipSpeexStateT *)pCodecState; + + if ((iControl == 'plvl') && (pState != NULL)) + { + pState->iOutputVolume = iValue; + return(0); + } + #if DIRTYCODE_LOGGING + if ((iControl == 'spam') && (pState != NULL)) + { + pState->iDebugLevel = iValue; + return(0); + } + #endif + NetPrintf(("voipspeex: unhandled control selector '%C'\n", iControl)); + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipSpeexStatus + + \Description + Get codec status + + \Input *pCodecState - pointer to decode state + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuffer - [out] storage for selector output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iSelect can be one of the following: + + \verbatim + 'fsiz' - size of encoder output / decoder input in bytes (iValue=samples per frame) + 'fsmp' - frame sample size for current codec + \endverbatim + + \Version 10/11/2011 (jbrookes) +*/ +/*************************************************************************************************F*/ +static int32_t _VoipSpeexStatus(VoipCodecRefT *pCodecState, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize) +{ + VoipSpeexStateT *pModuleState = (VoipSpeexStateT *)pCodecState; + + // these options require module state + if (pModuleState != NULL) + { + if (iSelect == 'fsiz') + { + return((iValue/VOIPSPEEX_FRAME_SIZE) * VOIPSPEEX_COMP_SIZE); + } + if (iSelect == 'fsmp') + { + return(VOIPSPEEX_FRAME_SIZE); + } + } + NetPrintfVerbose((pModuleState->iDebugLevel, 1, "voipspeex: unhandled status selector '%C'\n", iSelect)); + return(-1); +} + +// speex function overrides +void _speex_fatal(const char *str, const char *file, int line) +{ + NetPrintf(("Fatal (internal) error in %s, line %d: %s\n", file, line, str )); +} + +void speex_warning(const char *str) +{ + NetPrintf(("libspeex: warning: %s\n", str)); +} + +void speex_warning_int(const char *str, int val) +{ + NetPrintf(("libspeex: warning: %s %d\n", str, val)); +} + +void speex_notify(const char *str) +{ + NetPrintf(("libspeex: notification: %s\n", str)); +} + +#endif // VOIPSPEEX_ENABLED + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/comm/commall.h b/src/thirdparty/dirtysdk/include/DirtySDK/comm/commall.h new file mode 100644 index 00000000..2bf646b1 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/comm/commall.h @@ -0,0 +1,337 @@ +/*H*************************************************************************************/ +/*! + \File commall.h + + \Description + This is the common communication header required for use with any + of the CommXXXX routines. It provides a unified calling structure + as well as unified status and error values. + + \Copyright + Copyright (c) Electronic Arts 2002 + + \Version 0.5 02/23/1999 (gschaefer) First Version + \Version 1.0 02/25/1999 (gschaefer) Alpha release + \Version 1.1 11/20/2002 (jbrookes) Added Send() flags parameter, protocol definitions. +*/ +/*************************************************************************************H*/ + +#ifndef _commall_h +#define _commall_h + +/*! +\Moduledef CommAll CommAll +\Modulemember Comm +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +#define COMM_OFFLINE 1 //!< status - offline +#define COMM_CONNECTING 2 //!< status - connecting +#define COMM_ONLINE 3 //!< status - online +#define COMM_FAILURE 4 //!< status - failure + +#define COMM_PENDING 1 //!< pending +#define COMM_NOERROR 0 //!< no error +#define COMM_BADPARM -1 //!< invalid parameter +#define COMM_BADSTATE -2 //!< invalid state +#define COMM_BADADDRESS -3 //!< invalid address +#define COMM_NORESOURCE -4 //!< no resources available +#define COMM_UNEXPECTED -5 //!< unexpected +#define COMM_MINBUFFER -6 //!< minbuffer +#define COMM_NODATA -7 //!< no data available +#define COMM_INPROGRESS -8 //!< operation in progress +#define COMM_PORTBOUND -9 //!< requested local port is already bound + +// start of protocol numbering +#define COMM_PROTO_TCP (1) //!< TCP protocol +#define COMM_PROTO_UDP (2) //!< UDP protocol +#define COMM_PROTO_SRP (3) //!< SRP protocol + +#define COMM_FLAGS_RELIABLE 0 //!< CommRef->Send() flag - use reliable transmission +#define COMM_FLAGS_UNRELIABLE 1 //!< CommRef->Send() flag - use unreliable transmission +#define COMM_FLAGS_BROADCAST 2 //!< CommRef->Send() flag - use unreliable broadcast transmission + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct CommRef CommRef; + +//! common reference type +struct CommRef +{ + //! construct the class + /*! + * note: initialized winsock for first class. also creates linked + * list of all current instances of the class and worker thread + * to do most udp stuff. + * + * entry: maxwid=max record width, maxinp/maxout=input/output packet buffer size + * exit: none + */ + CommRef* (*Construct)(int32_t maxwid, int32_t maxinp, int32_t maxout); + + //! destruct the class + /*! + * entry: none + * exit: none + */ + void (*Destroy)(CommRef *what); + + //! resolve an address + /*! + * entry: what=endpoint ref, addr=address, buffer=target buffer, buflen=length + * exit: <0=error, 0=in progress, >0=complete + */ + int32_t (*Resolve)(struct CommRef *what, const char *addr, char *buf, int32_t len, char iDiv); + + //! stop the resolver + /*! + * entry: what=endpoint ref + * exit: none + */ + void (*Unresolve)(CommRef *what); + + //! listen for a connection + /*! + * entry: addr=port to listen on (only :port portion used) + * exit: negative=error, zero=ok + */ + int32_t (*Listen)(CommRef *what, const char *addr); + + //! stop listening + /*! + * entry: none + * exit: negative=error, zero=ok + */ + int32_t (*Unlisten)(CommRef *what); + + //! initiate a connection to a peer + /*! + * note: does not currently perform dns translation + * + * entry: addr=address in ip-address:port form + * exit: negative=error, zero=ok + */ + int32_t (*Connect)(CommRef *what, const char *addr); + + //! terminate a connection + /*! + * entry: none + * exit: negative=error, zero=ok + */ + int32_t (*Unconnect)(CommRef *what); + + //! set event callback hook + /*! + * Note1: this is an optional callback which is called after new data has been + * received and buffered or at other times where the protocol state has + * significantly changed. It is called by a private thread so the routines it + * uses must be thread safe. It can be used to provide "life" to servers or + * other modules which use comm services. The code must not take too long to + * execute since it will impact comm performance if it does. + * + * Note2: By disabling and enabling this callback at specific times, it is + * possible to avoid threading concerns in the upper layer (i.e., if you + * disable the callback while the callback is executing, this call will block + * until the callback completes). + * + * entry: comm reference + * exit: none + */ + void (*Callback)(CommRef *what, void (*callback)(CommRef *ref, int32_t event)); + + //! return current stream status + /*! + * entry: none + * exit: CONNECTING, OFFLINE, ONLINE or FAILURE + */ + int32_t (*Status)(CommRef *what); + + //! control connection behavior (optional) + /*! + * see specific implementation for entry and exit parameter descriptions + */ + int32_t (*Control)(CommRef *what, int32_t iControl, int32_t iValue, void *pValue); + + //! return current clock tick + /*! + * entry: none + * exit: elapsed millisecs from some epoch + */ + uint32_t (*Tick)(CommRef *what); + + //! send a packet + /*! + * note: zero length packets may not be sent (they are ignored) + * + * entry: buffer=pointer to data, length=length of data, flags=COMM_FLAGS_* (see defines above) + * exit: negative=error, zero=ok + */ + int32_t (*Send)(CommRef *what, const void *buffer, int32_t length, uint32_t flags); + + //! peek at waiting packet + /*! + * entry: target=target buffer, length=buffer length, when=tick received at + * exit: negative=nothing pending, else packet length + */ + int32_t (*Peek)(CommRef *what, void *target, int32_t length, uint32_t *when); + + //! receive a packet from the buffer + /*! + * entry: target=target buffer, length=buffer length, what=tick received at + * exit: negative=error, else packet length + */ + int32_t (*Recv)(CommRef *what, void *target, int32_t length, uint32_t *when); + + //! send data callback hook + /*! + * Note: this is an optional callback which is called immediately before + * a packet is transmitted. Due to error handling, it may get called more + * than once for the same packet if the packet is sent more than once. + * + * entry: same as Send + * exit: none + */ + void (*SendCallback)(CommRef *what, void *buffer, int32_t length, uint32_t when); + + //! receive data callback hook + /*! + * Note: this is an optional callback which is called immediately after + * a packet is received. By the time this function is called, the packet + * is available for input via the Recv function. + * + * entry: same as Recv + * exit: none + */ + void (*RecvCallback)(CommRef *what, void *target, int32_t length, uint32_t when); + + //! module memory group + int32_t memgroup; + void *memgrpusrdata; + + //! user definable storage + int32_t refnum; + + //! user definable reference + void *refptr; + + //! socket ref + SocketT *sockptr; + + //! maximum packet width (read only) + uint16_t maxwid; + + //! maximum number of input packets buffered + uint8_t maxinp; + + //! maximum number of output packets buffered + uint8_t maxout; + + //! data transfer statistics (read only) + int32_t datasent; + + //! data transfer statistics (read only) + int32_t datarcvd; + + //! data transfer statistics (read only) + int32_t packsent; + + //! data transfer statistics (read only) + int32_t packrcvd; + + //! data transfer statistics (read only) + int32_t packlost; + + //! data tranfer statictics (read only) + uint32_t packsaved; // tracks the number of packers recovered by commudp redundancy mechanism + + //! data transfer statistics (read only) + int32_t naksent; + + //! data transfer statistics (read only) + int32_t overhead; // tracks the sent overhead including IP4 and UDP header + + //! data transfer statistics (read only) + int32_t rcvoverhead; // tracks the receive overhead including IP4 and UDP header + + //!< host ip address (read only) + uint32_t hostip; + + //!< peer ip address (read only) + uint32_t peerip; + + //!< host port (read only) + uint16_t hostport; + + //!< peer port (read only) + uint16_t peerport; + + //!< if packet was received (read only) + uint8_t bpackrcvd; + + uint8_t _pad[3]; +}; + +// typedef versions for discrete declarations +typedef CommRef *(CommAllConstructT)(int32_t maxwid, int32_t maxinp, int32_t maxout); +typedef void (CommAllDestroyT)(CommRef *what); +typedef int32_t (CommAllResolveT)(struct CommRef *what, const char *addr, char *buf, int32_t len, char iDiv); +typedef void (CommAllUnresolveT)(CommRef *what); +typedef int32_t (CommAllListenT)(CommRef *what, const char *addr); +typedef int32_t (CommAllUnlistenT)(CommRef *what); +typedef int32_t (CommAllConnectT)(CommRef *what, const char *addr); +typedef int32_t (CommAllUnconnectT)(CommRef *what); +typedef void (CommAllCallbackT)(CommRef *what, void (*callback)(CommRef *ref, int32_t event)); +typedef int32_t (CommAllStatusT)(CommRef *what); +typedef int32_t (CommAllControlT)(CommRef *what, int32_t iControl, int32_t iValue, void *pValue); +typedef uint32_t (CommAllTickT)(CommRef *what); +typedef int32_t (CommAllSendT)(CommRef *what, const void *buffer, int32_t length, uint32_t flags); +typedef int32_t (CommAllPeekT)(CommRef *what, void *target, int32_t length, uint32_t *when); +typedef int32_t (CommAllRecvT)(CommRef *what, void *target, int32_t length, uint32_t *when); +typedef void (CommAllSendCallbackT)(CommRef *what, const void *, int32_t length, uint32_t when); +typedef void (CommAllRecvCallbackT)(CommRef *what, void *target, int32_t length, uint32_t when); +typedef void (CommAllEventCallbackT)(CommRef *what, int32_t event); + + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +// known protocol constructors (these also appear in the .h file for the +// protocol in question, but are included here for convenience) -- see +// protocol .h file for more details + +/* + * Construct the class + * + * entry: maxwid=max record width, maxinp/maxout=input/output packet buffer size + * exit: none + */ + +#if 0 +//typedef struct CommUDPRef CommUDPRef; +CommRef *CommUDPConstruct(int32_t maxwid, int32_t maxinp, int32_t maxout); + +//typedef struct CommSRPRef CommSRPRef; +CommRef *CommSRPConstruct(int32_t maxwid, int32_t maxinp, int32_t maxout); +#endif + +//@} + +#endif // _commall_h + + + + + + + + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/comm/commsrp.h b/src/thirdparty/dirtysdk/include/DirtySDK/comm/commsrp.h new file mode 100644 index 00000000..0922d3d4 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/comm/commsrp.h @@ -0,0 +1,103 @@ +/*H*************************************************************************************/ +/*! + \File commsrp.h + + \Description + This is CommSRP (Selectively Reliable Protocol), a datagram packet-based + transport class. + + \Copyright + Copyright Electronic Arts 1999-2003 + + \Version 0.5 01/03/03 (jbrookes) Initial Version, based on CommTCP + \Version 0.7 01/07/03 (jbrookes) Working unreliable transport, based on CommUDP + \Version 0.8 01/08/03 (jbrookes) Working reliable transport. + \Version 0.9 02/09/03 (jbrookes) Added support for sending zero-byte packets, and fixed PS2 alignment issue. +*/ +/*************************************************************************************H*/ + +#ifndef _commsrp_h +#define _commsrp_h + +/*! +\Moduledef CommSRP CommSRP +\Modulemember Comm +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +// basic reference returned/used by all routines +typedef struct CommSRPRef CommSRPRef; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// construct the class +DIRTYCODE_API CommSRPRef *CommSRPConstruct(int32_t maxwid, int32_t maxinp, int32_t maxout); + +// destruct the class +DIRTYCODE_API void CommSRPDestroy(CommSRPRef *what); + +// resolve an address +DIRTYCODE_API int32_t CommSRPResolve(CommSRPRef *what, const char *addr, char *buf, int32_t len, char div); + +// stop the resolver +DIRTYCODE_API void CommSRPUnresolve(CommSRPRef *what); + +// listen for a connection +DIRTYCODE_API int32_t CommSRPListen(CommSRPRef *what, const char *addr); + +// stop listening +DIRTYCODE_API int32_t CommSRPUnlisten(CommSRPRef *what); + +// initiate a connection to a peer +DIRTYCODE_API int32_t CommSRPConnect(CommSRPRef *what, const char *addr); + +// terminate a connection +DIRTYCODE_API int32_t CommSRPUnconnect(CommSRPRef *what); + +// set event callback hook +DIRTYCODE_API void CommSRPCallback(CommSRPRef *what, void (*callback)(CommRef *ref, int32_t event)); + +// return current stream status +DIRTYCODE_API int32_t CommSRPStatus(CommSRPRef *what); + +// return current clock tick +DIRTYCODE_API uint32_t CommSRPTick(CommSRPRef *what); + +// send a packet +DIRTYCODE_API int32_t CommSRPSend(CommSRPRef *what, const void *buffer, int32_t length, uint32_t flags); + +// peek at waiting packet +DIRTYCODE_API int32_t CommSRPPeek(CommSRPRef *what, void *target, int32_t length, uint32_t *when); + +// receive a packet from the buffer +DIRTYCODE_API int32_t CommSRPRecv(CommSRPRef *what, void *target, int32_t length, uint32_t *when); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _commsrp_h + + + + + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/comm/commudp.h b/src/thirdparty/dirtysdk/include/DirtySDK/comm/commudp.h new file mode 100644 index 00000000..85119f15 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/comm/commudp.h @@ -0,0 +1,120 @@ +/*H*************************************************************************************************/ +/*! + + \File commudp.h + + \Description + This is a simple UDP transport class which incorporations the notions of virtual + connections and error free transfer. The protocol is optimized for use in a real-time + environment where latency is more important than bandwidth. Overhead is low with + the protocol adding only 8 bytes per packet on top of that required by UDP itself. + + \Notes + None. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 1999-2003. ALL RIGHTS RESERVED. + + \Version 0.1 02/09/99 (GWS) First Version + \Version 0.2 02/14/99 (GWS) Revised and enhanced + \Version 0.5 02/14/99 (GWS) Alpha release + \Version 1.0 07/30/99 (GWS) Final release + \Version 2.0 10/27/99 (GWS) Revised to use winsock 1.1/2.0 + \Version 2.1 12/04/99 (GWS) Removed winsock 1.1 support + \Version 2.2 01/12/00 (GWS) Fixed receive tick bug + \Version 2.3 06/12/00 (GWS) Added fastack for low-latency nets + \Version 2.4 07/07/00 (GWS) Added firewall penetrator + \Version 3.0 12/04/00 (GWS) Reported to dirtysock + \Version 3.1 11/20/02 (JLB) Added Send() flags parameter + \Version 3.2 02/18/03 (JLB) Fixes for multiple connection support + \Version 3.3 05/06/03 (GWS) Allowed poke to come from any IP + \Version 3.4 09/02/03 (JLB) Added unreliable packet type + \Version 4.0 09/12/03 (JLB) Per-send optional unreliable transport + +*/ +/*************************************************************************************************H*/ + + +#ifndef _commudp_h +#define _commudp_h + +/*! +\Moduledef CommUDP CommUDP +\Modulemember Comm +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +// basic reference returned/used by all routines +typedef struct CommUDPRef CommUDPRef; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// construct the class +DIRTYCODE_API CommUDPRef *CommUDPConstruct(int32_t iMaxWid, int32_t iMaxInp, int32_t iMaxOut); + +// destruct the class +DIRTYCODE_API void CommUDPDestroy(CommUDPRef *pRef); + +// resolve an address +DIRTYCODE_API int32_t CommUDPResolve(CommUDPRef *pRef, const char *pAddr, char *pBuf, int32_t iLen, char cDiv); + +// resolve an address +DIRTYCODE_API void CommUDPUnresolve(CommUDPRef *pRef); + +// listen for a connection +DIRTYCODE_API int32_t CommUDPListen(CommUDPRef *pRef, const char *pAddr); + +// stop listening +DIRTYCODE_API int32_t CommUDPUnlisten(CommUDPRef *pRef); + +// initiate a connection to a peer +DIRTYCODE_API int32_t CommUDPConnect(CommUDPRef *pRef, const char *pAddr); + +// terminate a connection +DIRTYCODE_API int32_t CommUDPUnconnect(CommUDPRef *pRef); + +// set event callback hook +DIRTYCODE_API void CommUDPCallback(CommUDPRef *pRef, void (*pCallback)(void *pRef, int32_t iEvent)); + +// return current stream status +DIRTYCODE_API int32_t CommUDPStatus(CommUDPRef *pRef); + +// control connection behavior +DIRTYCODE_API int32_t CommUDPControl(CommUDPRef *pRef, int32_t iControl, int32_t iValue, void *pValue); + +// return current clock tick +DIRTYCODE_API uint32_t CommUDPTick(CommUDPRef *pRef); + +// send a packet +DIRTYCODE_API int32_t CommUDPSend(CommUDPRef *pRef, const void *pBuffer, int32_t iLength, uint32_t uFlags); + +// peek at waiting packet +DIRTYCODE_API int32_t CommUDPPeek(CommUDPRef *pRef, void *pTarget, int32_t iLength, uint32_t *pWhen); + +// receive a packet from the buffer +DIRTYCODE_API int32_t CommUDPRecv(CommUDPRef *pRef, void *pTarget, int32_t iLength, uint32_t *pWhen); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _commudp_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/comm/commudputil.h b/src/thirdparty/dirtysdk/include/DirtySDK/comm/commudputil.h new file mode 100644 index 00000000..273ac1fe --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/comm/commudputil.h @@ -0,0 +1,92 @@ +/*H*************************************************************************************************/ +/*! + + \File commudputil.h + + \Description + CommUdp knowledge to be shared with gameserver implementation. + + \Copyright + Copyright (c) 2006-2017 Electronic Arts Inc. + + \Version 1.0 09/01/2017 (mclouatre) First Version + +*/ +/*************************************************************************************************H*/ + + +#ifndef _commudputil_h +#define _commudputil_h + +/*! +\Moduledef CommUDP CommUDP +\Modulemember Comm +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +//! define protocol packet types +enum { + COMMUDP_RAW_PACKET_INIT = 1, // initiate a connection + COMMUDP_RAW_PACKET_CONN, // confirm a connection + COMMUDP_RAW_PACKET_DISC, // terminate a connection + COMMUDP_RAW_PACKET_NAK, // force resend of lost data + COMMUDP_RAW_PACKET_POKE, // try and poke through firewall + + COMMUDP_RAW_PACKET_UNREL = 128, // unreliable packet send (must be power of two) + // 128-255 reserved for unreliable packet sequence + COMMUDP_RAW_PACKET_DATA = 256, // initial data packet sequence number (must be power of two) + + /* Width of the sequence window, can be anything provided + RAW_PACKET_DATA + RAW_PACKET_DATA_WINDOW + doesn't overlap the meta-data bits. */ + COMMUDP_RAW_PACKET_DATA_WINDOW = (1 << 24) - 256 +}; + +#define COMMUDP_RAW_METATYPE1_SIZE (8) +#define COMMUDP_SEQ_META_SHIFT (28 - 4) +#define COMMUDP_SEQ_MULTI_SHIFT (28) +#define COMMUDP_SEQ_MULTI_INC (1 << COMMUDP_SEQ_MULTI_SHIFT) +#define COMMUDP_SEQ_MASK (0x00ffffff) + + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// Encodes a subpacket size field. +DIRTYCODE_API uint32_t CommUDPUtilEncodeSubpacketSize(uint8_t *pBuf, uint32_t uVal); + +// Decodes a subpacket size field (see _CommUDPEncodeSubpacketSize for notes) +DIRTYCODE_API uint32_t CommUDPUtilDecodeSubpacketSize(const uint8_t *pBuf, int32_t *pVal); + +// Extracts metatype from host-ordered commupd header seq field (RawUDPPacketT.body.uSeq) +DIRTYCODE_API uint32_t CommUDPUtilGetMetaTypeFromSeq(uint32_t uSeq); + +// Returns the metadata size for a given metadata type. +DIRTYCODE_API uint32_t CommUDPUtilGetMetaSize(uint32_t uMetaType); + +// Extracts the number of extra subpackets (excluding the main subpacket) from host - ordered commupd header seq field(RawUDPPacketT.body.uSeq) +DIRTYCODE_API uint32_t CommUDPUtilGetExtraSubPktCountFromSeq(uint32_t uSeq); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _commudputil_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptaes.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptaes.h new file mode 100644 index 00000000..0d530cce --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptaes.h @@ -0,0 +1,97 @@ +/*H********************************************************************************/ +/*! + \File cryptaes.h + + \Description + An implementation of the AES-128 and AES-256 cipher, based on the FIPS + standard, intended for use with the TLS AES cipher suites. + + This implementation deliberately uses the naming conventions from the + standard where possible in order to aid comprehension. + + \Notes + References: + FIPS197 standard: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + + \Copyright + Copyright (c) 2011 Electronic Arts + + \Version 01/20/2011 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _cryptaes_h +#define _cryptaes_h + +/*! +\Moduledef CryptAes CryptAes +\Modulemember Crypt +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +#define CRYPTAES_MAXROUNDS (14) + +#define CRYPTAES_KEYTYPE_ENCRYPT (0) +#define CRYPTAES_KEYTYPE_DECRYPT (1) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct CryptAesKeyScheduleT +{ + uint16_t uNumRounds; + uint16_t uKeyWords; + uint32_t aKeySchedule[(CRYPTAES_MAXROUNDS+1)*8]; +} CryptAesKeyScheduleT; + +// opaque module state +typedef struct CryptAesT CryptAesT; + +//! all fields are PRIVATE +struct CryptAesT +{ + CryptAesKeyScheduleT KeySchedule; + uint8_t aInitVec[16]; //!< initialization vector/CBC state +}; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// initialize state for AES encryption/decryption module +DIRTYCODE_API void CryptAesInit(CryptAesT *pAes, const uint8_t *pKeyBuf, int32_t iKeyLen, uint32_t uKeyType, const uint8_t *pInitVec); + +// encrypt data with the AES cipher in CBC mode +DIRTYCODE_API void CryptAesEncrypt(CryptAesT *pAes, uint8_t *pBuffer, int32_t iLength); + +// decrypt data with the AES cipher in CBC mode +DIRTYCODE_API void CryptAesDecrypt(CryptAesT *pAes, uint8_t *pBuffer, int32_t iLength); + +// encrypt a 16 byte data block with AES cipher +DIRTYCODE_API void CryptAesEncryptBlock(CryptAesKeyScheduleT *pKeySchedule, const uint8_t *pInput, uint8_t *pOutput); + +// decrypt a 16 byte data block with AES cipher +DIRTYCODE_API void CryptAesDecryptBlock(CryptAesKeyScheduleT *pKeySchedule, const uint8_t *pInput, uint8_t *pOutput); + +// initialize AES key schedule +DIRTYCODE_API void CryptAesInitKeySchedule(CryptAesKeyScheduleT *pKeySchedule, const uint8_t *pKeyBuf, int32_t iKeyLen, uint32_t uKeyType); + + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptaes_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptarc4.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptarc4.h new file mode 100644 index 00000000..f9bfb24f --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptarc4.h @@ -0,0 +1,102 @@ +/*H*************************************************************************************/ +/*! + \File cryptarc4.h + + \Description + This module is a from-scratch ARC4 implementation designed to avoid + any intellectual property complications. The ARC4 stream cipher is + known to produce output that is compatible with the RC4 stream cipher. + + \Notes + This algorithm from this cypher was taken from the web site: ciphersaber.gurus.com + It is based on the RC4 compatible algorithm that was published in the 2nd ed of + the book Applied Cryptography by Bruce Schneier. This is a private-key stream + cipher which means that some other secure way of exchanging cipher keys prior + to algorithm use is required. Its strength is directly related to the key exchange + algorithm strengh. In operation, each individual stream message must use a unique + key. This is handled by appending on 10 byte random value onto the private key. + This 10-byte data can be sent by public means to the receptor (or included at the + start of a file or whatever). When the private key is combined with this public + key, it essentially puts the cypher into a random starting state (it is like + using a message digest routine to generate a random hash for password comparison). + The code below was written from scratch using only a textual algorithm description. + + \Copyright + Copyright (c) Electronic Arts 2000-2002 + + \Version 1.0 02/25/2000 (gschaefer) First Version + \Version 1.1 11/06/2002 (jbrookes) Removed Create()/Destroy() to eliminate mem alloc dependencies. +*/ +/*************************************************************************************H*/ + +#ifndef _cryptarc4_h +#define _cryptarc4_h + +/*! +\Moduledef CryptArc4 CryptArc4 +\Modulemember Crypt +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct CryptArc4T CryptArc4T; + +//! all fields are PRIVATE +struct CryptArc4T +{ + uint8_t state[256]; + uint8_t walk; + uint8_t swap; +}; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create state for ARC4 encryption/decryption module. +DIRTYCODE_API void CryptArc4Init(CryptArc4T *pState, const unsigned char *pKeyBuf, int32_t iKeyLen, int32_t iIter); + +// apply the cipher to the data. calling twice undoes the uncryption +DIRTYCODE_API void CryptArc4Apply(CryptArc4T *pState, unsigned char *pBuffer, int32_t iLength); + +// advance the cipher state by iLength bytes +DIRTYCODE_API void CryptArc4Advance(CryptArc4T *pState, int32_t iLength); + +// encrypt an asciiz string, with a 7-bit asciiz string result +DIRTYCODE_API void CryptArc4StringEncrypt(char *pDst, int32_t iLen, const char *pSrc, const uint8_t *pKey, int32_t iKey, int32_t iIter); + +// decrypt an asciiz string, from a 7-bit asciiz encrypted string +DIRTYCODE_API void CryptArc4StringDecrypt(char *pDst, int32_t iLen, const char *pSrc, const uint8_t *pKey, int32_t iKey, int32_t iIter); + +#if DIRTYCODE_DEBUG + // encryption/decryption helper intended for use with static strings that need to be encrypted in the binary + #define CryptArc4StringEncryptStatic(_pStr, _iStrSize, _pKey, _iKeySize, _pStrSrc) CryptArc4StringEncryptStaticCode(_pStr, _iStrSize, _pKey, _iKeySize, _pStrSrc) +#else + // release version of string encrypt; does not pass source (plaintext) string + #define CryptArc4StringEncryptStatic(_pStr, _iStrSize, _pKey, _iKeySize, _pStrSrc) CryptArc4StringEncryptStaticCode(_pStr, _iStrSize, _pKey, _iKeySize, NULL) +#endif + +// encryption/decryption helper intended for use with static strings that need to be encrypted in the binary (do not call directly; use CryptArc4StringEncryptStatic() wrapper) +DIRTYCODE_API int32_t CryptArc4StringEncryptStaticCode(char *pStr, int32_t iStrSize, const uint8_t *pKey, int32_t iKeySize, const char *pStrSrc); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptarc4_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptbn.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptbn.h new file mode 100644 index 00000000..0000430b --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptbn.h @@ -0,0 +1,156 @@ +/*H*************************************************************************************/ +/*! + \File cryptbn.h + + \Description + This module is implements big integer math needed for our cryptography + + \Copyright + Copyright (c) Electronic Arts 2017. ALL RIGHTS RESERVED. + + \Version 01/18/2017 (eesponda) First version split from CryptRSA +*/ +/*************************************************************************************H*/ + +#ifndef _cryptbn_h +#define _cryptbn_h + +/*! +\Moduledef CryptBn CryptBn +\Modulemember Crypt +*/ +//@{ + +/*** Include files ********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **************************************************************************/ + +// size of a each unit in the large number +#if defined(DIRTYCODE_PC) && DIRTYCODE_64BITPTR == 0 +#define UCRYPT_SIZE (4) +#else +#define UCRYPT_SIZE (8) +#endif +// bits per word +#define UCRYPT_BITSIZE (UCRYPT_SIZE*8) +// maximum bits of number (add one unit of wiggle room) +#define CRYPTBN_MAX_BITS (4096+UCRYPT_BITSIZE) +// maximum width of number +#define CRYPTBN_MAX_WIDTH (CRYPTBN_MAX_BITS/UCRYPT_BITSIZE) + +/*** Type Definitions *****************************************************************/ + +// define crypt types based on UCRYPT_SIZE +#if (UCRYPT_SIZE == 4) +typedef uint32_t ucrypt_t; +#else +typedef uint64_t ucrypt_t; +#endif + +//! big number state +typedef struct CryptBnT +{ + ucrypt_t aData[CRYPTBN_MAX_WIDTH]; + int32_t iWidth; + uint32_t uSign; +} CryptBnT; + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// init big number state at specific width +DIRTYCODE_API void CryptBnInit(CryptBnT *pState, int32_t iWidth); + +// init big number state with an unsigned number +DIRTYCODE_API void CryptBnInitSet(CryptBnT *pState, uint32_t uValue); + +// init big number state from a buffer +DIRTYCODE_API int32_t CryptBnInitFrom(CryptBnT *pState, int32_t iWidth, const uint8_t *pSource, int32_t iLength); + +// init big number state from a buffer as little-endian +DIRTYCODE_API int32_t CryptBnInitLeFrom(CryptBnT *pState, const uint8_t *pSource, int32_t iLength); + +// left shift the big number by a bit +DIRTYCODE_API void CryptBnLeftShift(CryptBnT *pState); + +// left shift the big number by multiple bits +DIRTYCODE_API void CryptBnLeftShift2(CryptBnT *pState, int32_t iBits); + +// right shift the big number by a bit +#define CryptBnRightShift(pState) CryptBnRightShift2(pState, 1) + +// right shift the big number by multiple bits +DIRTYCODE_API void CryptBnRightShift2(CryptBnT *pState, int32_t iBits); + +// tests a bit in the big number and returns if it is set +DIRTYCODE_API uint8_t CryptBnBitTest(const CryptBnT *pState, int32_t iBit); + +// set a bit in the big number +DIRTYCODE_API void CryptBnBitSet(CryptBnT *pState, int32_t iBit); + +// bitwise and two big numbers +DIRTYCODE_API void CryptBnBitAnd(CryptBnT *pState, const CryptBnT *pLhs, const CryptBnT *pRhs); + +// bitwise xor two big numbers +DIRTYCODE_API void CryptBnBitXor(CryptBnT *pState, const CryptBnT *pLhs, const CryptBnT *pRhs); + +// returns bit length +DIRTYCODE_API int32_t CryptBnBitLen(const CryptBnT *pState); + +// returns byte length +DIRTYCODE_API int32_t CryptBnByteLen(const CryptBnT *pState); + +// add two big numbers over modulus +DIRTYCODE_API void CryptBnModAdd(CryptBnT *pState, const CryptBnT *pAdd1, const CryptBnT *pAdd2, const CryptBnT *pMod); + +// accumulate one big number into another +DIRTYCODE_API void CryptBnAccumulate(CryptBnT *pState, const CryptBnT *pAdd); + +// increment the number by 1 +DIRTYCODE_API void CryptBnIncrement(CryptBnT *pState); + +// subtract two big numbers +DIRTYCODE_API void CryptBnSubtract(CryptBnT *pState, const CryptBnT *pSub1, const CryptBnT *pSub2); + +// subtract the number by 1 +DIRTYCODE_API void CryptBnDecrement(CryptBnT *pState); + +// perform modular multiply operation +DIRTYCODE_API void CryptBnModMultiply(CryptBnT *pState, const CryptBnT *pMul1, const CryptBnT *pMul2, const CryptBnT *pMod); + +// multiplication using classical algorithm +DIRTYCODE_API void CryptBnMultiply(CryptBnT *pState, const CryptBnT *pMul1, const CryptBnT *pMul2); + +// does a modulus/divide operation using the classical method +DIRTYCODE_API void CryptBnMod(const CryptBnT *pDividend, const CryptBnT *pDivisor, CryptBnT *pRemainder, CryptBnT *pQuotient); + +// perform inverse modulo operation +DIRTYCODE_API void CryptBnInverseMod(CryptBnT *pState, const CryptBnT *pMod); + +// copy the big number data into output buffer +DIRTYCODE_API void CryptBnFinal(const CryptBnT *pState, uint8_t *pResult, int32_t iLength); + +// copy the bit number data into output buffer as little endian +DIRTYCODE_API void CryptBnFinalLe(const CryptBnT *pState, uint8_t *pResult, int32_t iLength); + +// copy a big number +DIRTYCODE_API void CryptBnClone(CryptBnT *pDst, const CryptBnT *pSrc); + +// print the big number to the log +DIRTYCODE_API void CryptBnPrint(const CryptBnT *pState, const char *pTitle); + +// compare two big numbers +DIRTYCODE_API int32_t CryptBnCompare(const CryptBnT *pLhs, const CryptBnT *pRhs); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptbn_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptchacha.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptchacha.h new file mode 100644 index 00000000..41cfedbd --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptchacha.h @@ -0,0 +1,65 @@ +/*H********************************************************************************/ +/*! + \File cryptchacha.h + + \Description + + \Copyright + Copyright (c) 2018 Electronic Arts + + \Version 02/12/2018 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _cryptchacha_h +#define _cryptchacha_h + +/*! +\Moduledef CryptChaCha CryptChaCha +\Modulemember Crypt +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// opaque module state +typedef struct CryptChaChaT CryptChaChaT; + +//! all fields are PRIVATE +struct CryptChaChaT +{ + uint32_t aInput[16]; +}; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// initialize state for ChaCha encryption/decryption module +DIRTYCODE_API void CryptChaChaInit(CryptChaChaT *pChaCha, const uint8_t *pKeyBuf, int32_t iKeyLen); + +// encrypt data with the ChaCha cipher +DIRTYCODE_API int32_t CryptChaChaEncrypt(CryptChaChaT *pChaCha, uint8_t *pBuffer, int32_t iLength, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen, uint8_t *pTag, int32_t iTagLen); + +// decrypt data with the ChaCha cipher +DIRTYCODE_API int32_t CryptChaChaDecrypt(CryptChaChaT *pChaCha, uint8_t *pBuffer, int32_t iLength, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen, const uint8_t *pTag, int32_t iTagLen); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptgcm_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptcurve.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptcurve.h new file mode 100644 index 00000000..07469096 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptcurve.h @@ -0,0 +1,98 @@ +/*H********************************************************************************/ +/*! + \File cryptcurve.h + + \Description + This module implements an interface over our different curve crypto APIs + + \Copyright + Copyright (c) Electronic Arts 2018. ALL RIGHTS RESERVED. +*/ +/********************************************************************************H*/ + +#ifndef _cryptcurve_h +#define _cryptcurve_h + +/*! +\Moduledef CryptCurve CryptCurve +\Modulemember Crypt +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptbn.h" +#include "DirtySDK/crypt/cryptdef.h" +#include "DirtySDK/crypt/cryptnist.h" + +/*** Defines **********************************************************************/ + +//! maximum state storage we need for diffie hellman +#define CRYPTCURVE_DH_MAXSTATE (sizeof(CryptNistDhT)) +//! maximum state storage we need for digital signature algorithm +#define CRYPTCURVE_DSA_MAXSTATE (sizeof(CryptNistDsaT)) + +/*** Type Definitions *************************************************************/ + +//! curve init function +typedef int32_t (CryptCurveInitT)(void *pState, int32_t iCurveType); + +//! curve generate public function +typedef int32_t (CryptCurvePublicT)(void *pState, CryptEccPointT *pResult, uint32_t *pCryptUsecs); + +//! curve generate secret function +typedef int32_t (CryptCurveSecretT)(void *pState, CryptEccPointT *pPublicKey, CryptEccPointT *pResult, uint32_t *pCryptUsecs); + +//! curve init point +typedef int32_t (CryptCurvePointInitT)(CryptEccPointT *pPoint, const uint8_t *pBuffer, int32_t iBufLen); + +//! curve output point to buffer (for dh cases) +typedef int32_t (CryptCurvePointFinalT)(void *pState, const CryptEccPointT *pPoint, uint8_t bSecret, uint8_t *pBuffer, int32_t iBufLen); + +//! curve generate dsa signature +typedef int32_t (CryptCurveSignT)(void *pState, const CryptBnT *pPrivateKey, const uint8_t *pHash, int32_t iHashSize, CryptEccPointT *pSignature, uint32_t *pCryptUsecs); + +//! curve verify dsa signature +typedef int32_t (CryptCurveVerifyT)(void *pState, CryptEccPointT *pPublicKey, const uint8_t *pHash, int32_t iHashSize, CryptEccPointT *pSignature, uint32_t *pCryptUsecs); + +//! interface for dh functionality +typedef struct CryptCurveDhT +{ + CryptCurveInitT *Init; + CryptCurvePublicT *Public; + CryptCurveSecretT *Secret; + CryptCurvePointInitT *PointInit; + CryptCurvePointFinalT *PointFinal; + int32_t iCurveType; +} CryptCurveDhT; + +//! interface for dsa functionality +typedef struct CryptCurveDsaT +{ + CryptCurveInitT *Init; + CryptCurveSignT *Sign; + CryptCurveVerifyT *Verify; + CryptCurvePointInitT *PointInit; + int32_t iCurveType; +} CryptCurveDsaT; + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// get dh reference and initializes based on curve type +DIRTYCODE_API const CryptCurveDhT *CryptCurveGetDh(int32_t iCurveType); + +// get dsa reference and initializes based on curve type +DIRTYCODE_API const CryptCurveDsaT *CryptCurveGetDsa(int32_t iCurveType); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptcurve_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptdef.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptdef.h new file mode 100644 index 00000000..b63f56ee --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptdef.h @@ -0,0 +1,89 @@ +/*H*******************************************************************/ +/*! + \File cryptdef.h + + \Description + Common definitions for Crypt modules + + \Copyright + Copyright (c) Electronic Arts 2013 + + \Version 1.0 11/19/2013 (jbrookes) First Version +*/ +/*******************************************************************H*/ + +#ifndef _cryptdef_h +#define _cryptdef_h + +/*! +\Moduledef CryptDef CryptDef +\Modulemember Crypt +*/ +//@{ + +/*** Include files ***************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptbn.h" + +/*** Defines *********************************************************/ + +/*** Macros **********************************************************/ + +/*** Type Definitions ************************************************/ + +//! a binary large object, such as a modulus or exponent +typedef struct CryptBinaryObjT +{ + uint8_t *pObjData; + int32_t iObjSize; +} CryptBinaryObjT; + +//! used for the point calculations that are used for elliptic curve crypto +typedef struct CryptEccPointT +{ + CryptBnT X; + CryptBnT Y; +} CryptEccPointT; + +//! curve supported groups as per: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 +typedef enum CryptCurveE +{ + CRYPTCURVE_UNASSIGNED, + CRYPTCURVE_SECT163K1, + CRYPTCURVE_SECT163R1, + CRYPTCURVE_SECT163R2, + CRYPTCURVE_SECT193R1, + CRYPTCURVE_SECT193R2, + CRYPTCURVE_SECT233K1, + CRYPTCURVE_SECT233R1, + CRYPTCURVE_SECT239K1, + CRYPTCURVE_SECT283K1, + CRYPTCURVE_SECT283R1, + CRYPTCURVE_SECT409K1, + CRYPTCURVE_SECT409R1, + CRYPTCURVE_SECT571K1, + CRYPTCURVE_SECT571R1, + CRYPTCURVE_SECP160K1, + CRYPTCURVE_SECP160R1, + CRYPTCURVE_SECP160R2, + CRYPTCURVE_SECP192K1, + CRYPTCURVE_SECP192R1, + CRYPTCURVE_SECP224K1, + CRYPTCURVE_SECP224R1, + CRYPTCURVE_SECP256K1, + CRYPTCURVE_SECP256R1, + CRYPTCURVE_SECP384R1, + CRYPTCURVE_SECP521R1, + CRYPTCURVE_BRAINPOOLP256R1, + CRYPTCURVE_BRAINPOOLP384R1, + CRYPTCURVE_BRAINPOOLP512R1, + CRYPTCURVE_X25519, + CRYPTCURVE_X448, + CRYPTCURVE_MAX = CRYPTCURVE_X448 +} CryptCurveE; + +//@} + +#endif // _cryptdef_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptgcm.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptgcm.h new file mode 100644 index 00000000..6b65d2c3 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptgcm.h @@ -0,0 +1,81 @@ +/*H********************************************************************************/ +/*! + \File cryptgcm.h + + \Description + An implementation of the GCM-128 and GCM-256 cipher, based on the NIST + standard, intended for use in implementation of TLS1.1 GCM cipher suites. + + This implementation uses Shoup's method utilizing 4-bit tables as described + in the GCM specifications. While not particularly fast, table generation + is quick and memory usage required small. This implementation is restricted + to a feature set suitable for implementation of TLS1.1 GCM cipher suites. + + \Copyright + Copyright (c) 2014 Electronic Arts + + \Version 07/01/2014 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _cryptgcm_h +#define _cryptgcm_h + +/*! +\Moduledef CryptGcm CryptGcm +\Modulemember Crypt +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptaes.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// opaque module state +typedef struct CryptGcmT CryptGcmT; + +//! all fields are PRIVATE +struct CryptGcmT +{ + uint64_t HL[16]; //!< precalculated htable, low + uint64_t HH[16]; //!< precalculated htable, high + uint64_t uLen; //!< data length + uint64_t uAddLen; //!< additional data length + uint8_t aBaseEctr[16]; //!< first ECTR for tag + uint8_t aY[16]; //!< y scratch buffer + uint8_t aBuf[16]; //!< I/O scratch buffer + uint32_t uMode; //!< CRYPTAES_KETYPE_ENCRYPT or CRYPTAES_KEYTYPE_DECRYPT + CryptAesKeyScheduleT KeySchedule; //!< AES key schedule +}; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// initialize state for GCM encryption/decryption module +DIRTYCODE_API void CryptGcmInit(CryptGcmT *pGcm, const uint8_t *pKeyBuf, int32_t iKeyLen); + +// encrypt data with the GCM cipher +DIRTYCODE_API int32_t CryptGcmEncrypt(CryptGcmT *pGcm, uint8_t *pBuffer, int32_t iLength, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen, uint8_t *pTag, int32_t iTagLen); + +// decrypt data with the GCM cipher +DIRTYCODE_API int32_t CryptGcmDecrypt(CryptGcmT *pGcm, uint8_t *pBuffer, int32_t iLength, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen, uint8_t *pTag, int32_t iTagLen); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptgcm_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/crypthash.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/crypthash.h new file mode 100644 index 00000000..cb9a66e6 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/crypthash.h @@ -0,0 +1,98 @@ +/*H*******************************************************************/ +/*! + \File crypthash.h + + \Description + This module implements a generic wrapper for all cryptograph + hash modules. + + \Copyright + Copyright (c) Electronic Arts 2013 + + \Version 1.0 11/05/2013 (jbrookes) First Version +*/ +/*******************************************************************H*/ + +#ifndef _crypthash_h +#define _crypthash_h + +/*! +\Moduledef CryptHash CryptHash +\Modulemember Crypt +*/ +//@{ + +/*** Include files ***************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptmd5.h" +#include "DirtySDK/crypt/cryptsha1.h" +#include "DirtySDK/crypt/cryptsha2.h" +#include "DirtySDK/util/murmurhash3.h" + +/*** Defines *********************************************************/ + +typedef enum CryptHashTypeE +{ + CRYPTHASH_NULL, + CRYPTHASH_MURMUR3, //!< murmerhash3; NOT cryto grade but VERY fast, use with caution + CRYPTHASH_MD5, //!< MD5; semi-obsolete, use with caution + CRYPTHASH_SHA1, //!< SHA1 + CRYPTHASH_SHA224, //!< SHA2-224 + CRYPTHASH_SHA256, //!< SHA2-256 + CRYPTHASH_SHA384, //!< SHA2-384 + CRYPTHASH_SHA512, //!< SHA2-512 + CRYPTHASH_NUMHASHES +} CryptHashTypeE; + +#define CRYPTHASH_MAXDIGEST (CRYPTSHA2_HASHSIZE_MAX) //!< SHA2 has the largest digest size +#define CRYPTHASH_MAXSTATE (sizeof(CryptSha2T)) //!< SHA2 has the largest state + +/*** Macros **********************************************************/ + +/*** Type Definitions ************************************************/ + +//! crypthash init func +typedef void (CryptHashInitT)(void *pState, int32_t iHashSize); + +//! crypthash update func +typedef void (CryptHashUpdateT)(void *pState, const uint8_t *pInput, uint32_t uInputLength); + +//! crypthash final func +typedef void (CryptHashFinalT)(void *pState, void *pBuffer, uint32_t uLength); + +//! crypthash function block +typedef struct CryptHashT +{ + CryptHashInitT *Init; + CryptHashUpdateT *Update; + CryptHashFinalT *Final; + CryptHashTypeE eHashType; + int32_t iHashSize; +} CryptHashT; + +/*** Variables *******************************************************/ + +/*** Functions *******************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +//!< get reference to hash function +DIRTYCODE_API const CryptHashT *CryptHashGet(CryptHashTypeE eHashType); + +//!< get hash size of specified hash function +DIRTYCODE_API int32_t CryptHashGetSize(CryptHashTypeE eHashType); + +//!< get hash type based on size of hash (excludes murmurhash3) +DIRTYCODE_API CryptHashTypeE CryptHashGetBySize(int32_t iHashSize); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _crypthash_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/crypthmac.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/crypthmac.h new file mode 100644 index 00000000..82b08e11 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/crypthmac.h @@ -0,0 +1,62 @@ +/*H********************************************************************************/ +/*! + \File crypthmac.h + + \Description + + \Notes + References: + + \Copyright + Copyright (c) 2013 Electronic Arts Inc. + + \Version 01/14/2013 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _crypthmac_h +#define _crypthmac_h + +/*! +\Moduledef CryptHmac CryptHmac +\Modulemember Crypt +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct CryptHmacMsgT +{ + const uint8_t *pMessage; + int32_t iMessageLen; +} CryptHmacMsgT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// calculate HMAC message digest algorithm +DIRTYCODE_API int32_t CryptHmacCalc(uint8_t *pBuffer, int32_t iBufLen, const uint8_t *pMessage, int32_t iMessageLen, const uint8_t *pKey, int32_t iKeyLen, CryptHashTypeE eHashType); + +// calculate HMAC message digest algorithm; this version allows multiple buffers to allow for easy hashing of sparse messages +DIRTYCODE_API int32_t CryptHmacCalcMulti(uint8_t *pBuffer, int32_t iBufLen, const CryptHmacMsgT *pMessageList, int32_t iNumMessages, const uint8_t *pKey, int32_t iKeyLen, CryptHashTypeE eHashType); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptmd5.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptmd5.h new file mode 100644 index 00000000..6be3c90d --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptmd5.h @@ -0,0 +1,137 @@ +/*H*************************************************************************************************/ +/*! + + \File cryptmd5.h + + \Description + The MD5 message digest algorithm developed by Ron Rivest and documented + in RFC1321. This implementation is based on the RFC but does not use the + sample code.. It should be free from intellectual property concerns and + a reference is included below which further clarifies this point. + + Note that this implementation is limited to hashing no more than 2^32 + bytes after which its results would be impatible with a fully compliant + implementation. + + \Notes + http://www.ietf.org/ietf/IPR/RSA-MD-all + + The following was recevied Fenbruary 23,2000 + From: "Linn, John" + + February 19, 2000 + + The purpose of this memo is to clarify the status of intellectual + property rights asserted by RSA Security Inc. ("RSA") in the MD2, MD4 and + MD5 message-digest algorithms, which are documented in RFC-1319, RFC-1320, + and RFC-1321 respectively. + + Implementations of these message-digest algorithms, including + implementations derived from the reference C code in RFC-1319, RFC-1320, and + RFC-1321, may be made, used, and sold without license from RSA for any + purpose. + + No rights other than the ones explicitly set forth above are + granted. Further, although RSA grants rights to implement certain + algorithms as defined by identified RFCs, including implementations derived + from the reference C code in those RFCs, no right to use, copy, sell, or + distribute any other implementations of the MD2, MD4, or MD5 message-digest + algorithms created, implemented, or distributed by RSA is hereby granted by + implication, estoppel, or otherwise. Parties interested in licensing + security components and toolkits written by RSA should contact the company + to discuss receiving a license. All other questions should be directed to + Margaret K. Seif, General Counsel, RSA Security Inc., 36 Crosby Drive, + Bedford, Massachusetts 01730. + + Implementations of the MD2, MD4, or MD5 algorithms may be subject to + United States laws and regulations controlling the export of technical data, + computer software, laboratory prototypes and other commodities (including + the Arms Export Control Act, as amended, and the Export Administration Act + of 1970). The transfer of certain technical data and commodities may + require a license from the cognizant agency of the United States Government. + RSA neither represents that a license shall not be required for a particular + implementation nor that, if required, one shall be issued. + + + DISCLAIMER: RSA MAKES NO REPRESENTATIONS AND EXTENDS NO WARRANTIES + OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, VALIDITY OF + INTELLECTUAL PROPERTY RIGHTS, ISSUED OR PENDING, OR THE ABSENCE OF LATENT OR + OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE, IN CONNECTION WITH THE MD2, MD4, + OR MD5 ALGORITHMS. NOTHING IN THIS GRANT OF RIGHTS SHALL BE CONSTRUED AS A + REPRESENTATION OR WARRANTY GIVEN BY RSA THAT THE IMPLEMENTATION OF THE + ALGORITHM WILL NOT INFRINGE THE INTELLECTUAL PROPERTY RIGHTS OF ANY THIRD + PARTY. IN NO EVENT SHALL RSA, ITS TRUSTEES, DIRECTORS, OFFICERS, EMPLOYEES, + PARENTS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF + ANY KIND RESULTING FROM IMPLEMENTATION OF THIS ALGORITHM, INCLUDING ECONOMIC + DAMAGE OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER RSA + SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 1.0 03/16/2001 (GWS) First Version +*/ +/*************************************************************************************************H*/ + +#ifndef _cryptmd5_h +#define _cryptmd5_h + +/*! +\Moduledef CryptMD5 CryptMD5 +\Modulemember Crypt +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +#define MD5_BINARY_OUT 16 //!< length of binary output +#define MD5_STRING_OUT 33 //!< length of string output + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct CryptMD5T CryptMD5T; + +//! all fields are PRIVATE +struct CryptMD5T +{ + unsigned char strData[64+8];//!< partial data block (8 for padding) + uint32_t uCount; //!< total byte count + uint32_t uRegs[4]; //!< the digest registers +}; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// init the MD5 context. +DIRTYCODE_API void CryptMD5Init(CryptMD5T *pContext); + +// init the MD5 context (alternate form) +DIRTYCODE_API void CryptMD5Init2(CryptMD5T *pContext, int32_t iHashSize); + +// add data to the MD5 context (hash the data). +DIRTYCODE_API void CryptMD5Update(CryptMD5T *pContext, const void *pBuffer, int32_t iLength); + +// convert MD5 state into final output form +DIRTYCODE_API void CryptMD5Final(CryptMD5T *pContext, void *pBuffer, int32_t iLength); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptmd5_h + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptmont.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptmont.h new file mode 100644 index 00000000..2b899232 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptmont.h @@ -0,0 +1,91 @@ +/*H********************************************************************************/ +/*! + \File cryptmont.h + + \Description + This module implements the math for elliptic curve cryptography + using montgomery curves + + \Copyright + Copyright (c) Electronic Arts 2018. ALL RIGHTS RESERVED. +*/ +/********************************************************************************H*/ + +#ifndef _cryptmont_h +#define _cryptmont_h + +/*! +\Moduledef CryptMont CryptMont +\Modulemember Crypt +*/ +//@{ + +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptbn.h" +#include "DirtySDK/crypt/cryptdef.h" + +/*** Type Definitions **************************************************************/ + +typedef struct CryptMontT +{ + CryptBnT Prime; //!< curve prime + CryptBnT PrimeMin2; //!< curve prime - 2 used for exponentiation + CryptBnT BasePoint; //!< curve base point (only u) + CryptBnT A24; //!< constant used for calculations + CryptBnT PrivateKey; //!< private key + + //! working state + CryptBnT X_2; + CryptBnT X_3; + + CryptEccPointT Result; //!< cached result and z_2/z_3 during the point state + + int32_t iMemGroup; //!< memgroup id + void *pMemGroupUserdata; //!< memgroup userdata + CryptBnT *pTable; //!< table used for sliding window in exponentiation + + enum + { + CRYPTMONT_COMPUTE_POINT,//!< calculate the point + CRYPTMONT_COMPUTE_EXP //!< calculate the final result + } eState; //!< current state of the calculation + + int16_t iBitIndex; //!< bit index for the calculation (private key or exponent) + uint8_t bAccumulOne; //!< used to skip the first multiply + uint8_t uSwap; //!< current swap state + uint32_t uCryptUsecs; //!< total number of usecs the operation took + int32_t iCurveType; //!< what curve were we initialized with +} CryptMontT; + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// init the curve state +DIRTYCODE_API int32_t CryptMontInit(CryptMontT *pState, int32_t iCurveType); + +// for testing-purposes; set the private key +DIRTYCODE_API void CryptMontSetPrivateKey(CryptMontT *pState, const uint8_t *pKey); + +// generates a public key based on the curve parameters: BasePoint * Secret +DIRTYCODE_API int32_t CryptMontPublic(CryptMontT *pState, CryptEccPointT *pResult, uint32_t *pCryptUsecs); + +// generate a shared secret based on the curve parameters and other parties public key: PublicKey * Secret +DIRTYCODE_API int32_t CryptMontSecret(CryptMontT *pState, CryptEccPointT *pPublicKey, CryptEccPointT *pResult, uint32_t *pCryptUsecs); + +// initialize a point on the curve from a buffer +DIRTYCODE_API int32_t CryptMontPointInitFrom(CryptEccPointT *pPoint, const uint8_t *pBuffer, int32_t iBufSize); + +// output a point to a buffer +DIRTYCODE_API int32_t CryptMontPointFinal(const CryptMontT *pState, const CryptEccPointT *pPoint, uint8_t bSecret, uint8_t *pBuffer, int32_t iBufSize); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptmont_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptnist.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptnist.h new file mode 100644 index 00000000..ba9d4508 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptnist.h @@ -0,0 +1,116 @@ +/*H********************************************************************************/ +/*! + \File cryptnist.h + + \Description + This module implements the math for elliptic curve cryptography + using curves in short Weierstrass form (NIST curves) + + \Copyright + Copyright (c) Electronic Arts 2017. ALL RIGHTS RESERVED. +*/ +/********************************************************************************H*/ + +#ifndef _cryptnist_h +#define _cryptnist_h + +/*! +\Moduledef CryptNist CryptNist +\Modulemember Crypt +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptbn.h" +#include "DirtySDK/crypt/cryptdef.h" + +/*** Defines **********************************************************************/ + +//! window size used for sliding window +#define CRYPTNIST_WINDOW_SIZE (5) + +//! precomputed table size based on the window size +#define CRYPTNIST_TABLE_SIZE (1 << (CRYPTNIST_WINDOW_SIZE - 1)) + +/*** Type Definitions *************************************************************/ + +//! private state that defines the curve +typedef struct CryptNistT +{ + CryptBnT Prime; //!< the finite field that the curve is defined over + CryptBnT CoefficientA; //!< cofficient that defines the curve + CryptBnT CoefficientB; //!< cofficient that defines the curve + CryptEccPointT BasePoint; //!< generator point on the curve + CryptBnT Order; //!< the number of point operations on the curve until the resultant line is vertical + + int32_t iMemGroup; //!< memgroup id + void *pMemGroupUserdata; //!< memgroup userdata + CryptEccPointT *pTable; //!< precomputed values used for for computation + int32_t iKeyIndex; //!< current bit index state when generating + int32_t iSize; //!< size of curve parameters + + uint32_t uCryptUSecs; //!< number of total usecs the operation took +} CryptNistT; + +//! state dealing with diffie hellmen key exchange +typedef struct CryptNistDhT +{ + CryptNistT Ecc; //!< base ecc state + + CryptBnT PrivateKey; //!< private key used to calculate public key and shared secret + CryptEccPointT Result; //!< working state +} CryptNistDhT; + +//! state dealing with dsa sign and verify +typedef struct CryptNistDsaT +{ + CryptNistT Ecc; //!< base ecc state + + CryptBnT K; //!< secret used for signing operations + CryptBnT U1; //!< used for verification operations + CryptBnT U2; //!< used for verification operations + CryptEccPointT Result; //!< working state +} CryptNistDsaT; + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// initializes the crypt state to perform diffie hellmen key exchange +DIRTYCODE_API int32_t CryptNistInitDh(CryptNistDhT *pState, int32_t iCurveType); + +// initializes the crypt state to perform dsa sign and verify +DIRTYCODE_API int32_t CryptNistInitDsa(CryptNistDsaT *pState, int32_t iCurveType); + +// generates a public key based on the curve parameters: BasePoint * Secret +DIRTYCODE_API int32_t CryptNistPublic(CryptNistDhT *pState, CryptEccPointT *pResult, uint32_t *pCryptUsecs); + +// generate a shared secret based on the curve parameters and other parties public key: PublicKey * Secret +DIRTYCODE_API int32_t CryptNistSecret(CryptNistDhT *pState, CryptEccPointT *pPublicKey, CryptEccPointT *pResult, uint32_t *pCryptUsecs); + +// generate a ecdsa signature +DIRTYCODE_API int32_t CryptNistSign(CryptNistDsaT *pState, const CryptBnT *pPrivateKey, const uint8_t *pHash, int32_t iHashSize, CryptEccPointT *pSignature, uint32_t *pCryptUsecs); + +// verify a ecdsa signature +DIRTYCODE_API int32_t CryptNistVerify(CryptNistDsaT *pState, CryptEccPointT *pPublicKey, const uint8_t *pHash, int32_t iHashSize, CryptEccPointT *pSignature, uint32_t *pCryptUsecs); + +// check if the point is on the curve +DIRTYCODE_API uint8_t CryptNistPointValidate(const CryptNistDhT *pState, const CryptEccPointT *pPoint); + +// initialize a point on the curve from a buffer +DIRTYCODE_API int32_t CryptNistPointInitFrom(CryptEccPointT *pPoint, const uint8_t *pBuffer, int32_t iBufLen); + +// output a point to a buffer (for dh, dsa encoding looks different which we do in protossl) +DIRTYCODE_API int32_t CryptNistPointFinal(const CryptNistDhT *pState, const CryptEccPointT *pPoint, uint8_t bSecret, uint8_t *pBuffer, int32_t iBufLen); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptnist_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptrand.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptrand.h new file mode 100644 index 00000000..63829811 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptrand.h @@ -0,0 +1,53 @@ +/*H********************************************************************************/ +/*! + \File cryptrand.h + + \Description + Cryptographic random number generator. Uses system-defined rng where + available. + + \Copyright + Copyright (c) 2012 Electronic Arts Inc. + + \Version 12/05/2012 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _cryptrand_h +#define _cryptrand_h + +/*! +\Moduledef CryptRand CryptRand +\Modulemember Crypt +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// get random data +DIRTYCODE_API void CryptRandGet(uint8_t *pBuffer, int32_t iBufSize); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptrand_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptrsa.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptrsa.h new file mode 100644 index 00000000..7eb9e001 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptrsa.h @@ -0,0 +1,138 @@ +/*H*************************************************************************************/ +/*! + \File cryptrsa.h + + \Description + This module is a from-scratch RSA implementation in order to avoid any + intellectual property issues. The 1024 bit RSA public key encryption algorithm + was implemented from a specification provided by Netscape for SSL implementation + (see protossl.h). + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 03/08/2002 (gschaefer) First Version (protossl) + \Version 11/18/2002 (jbrookes) Names changed to include "Proto" + \Version 03/05/2003 (jbrookes) Split RSA encryption from protossl +*/ +/*************************************************************************************H*/ + +#ifndef _cryptrsa_h +#define _cryptrsa_h + +/*! +\Moduledef CryptRSA CryptRSA +\Modulemember Crypt +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptbn.h" +#include "DirtySDK/crypt/cryptdef.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! union of our working state depending on the type of operations +typedef union CryptRSAWorkingT +{ + struct + { + CryptBnT Modulus; //!< public key modulus + CryptBnT Exponent; //!< public key exponent + + // working memory for modular exponentiation + CryptBnT Powerof; + CryptBnT Accumul; + } PublicKey; + + struct + { + /* selected private key state we use for chinese remainder theorum computation + we have access to the other data (modulus, exponent) but it is not used so + not worth storing */ + CryptBnT PrimeP; //!< prime factor p of modulus + CryptBnT PrimeQ; //!< prime factor q of modulus + CryptBnT ExponentP; //!< exponent dP mod p-1 + CryptBnT ExponentQ; //!< exponent dQ mod q-1 + CryptBnT Coeffecient; //!< inverse of q mod p + + // working memory for modular exponentiation + CryptBnT PowerofP; + CryptBnT PowerofQ; + CryptBnT M1; + CryptBnT M2; + + // current state of computation + enum + { + CRT_COMPUTE_M1, + CRT_COMPUTE_M2 + } eState; + } PrivateKey; +} CryptRSAWorkingT; + +//! cryptrsa state +typedef struct CryptRSAT +{ + CryptRSAWorkingT Working; //!< working state & data for doing modular exponentiation + + CryptBnT *pTable; //!< table used in sliding window for precomputed powers + CryptBnT aTable[1]; //!< fixed sized table used for public key operations which is our base case + + // memory allocation data + int32_t iMemGroup; + void *pMemGroupUserData; + + int32_t iKeyModSize; //!< size of public key modulus + uint8_t EncryptBlock[1024]; //!< input/output data + + int16_t iExpBitIndex; //!< bit index into the current exponent we are working on + uint8_t bAccumulOne; //!< can we skip the first multiply? + uint8_t bPrivate; //!< are we public or private key operation? + + // rsa profiling info + uint32_t uCryptMsecs; + uint32_t uCryptUsecs; + uint32_t uNumExpCalls; +} CryptRSAT; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// init rsa state +DIRTYCODE_API int32_t CryptRSAInit(CryptRSAT *pState, const uint8_t *pModulus, int32_t iModSize, const uint8_t *pExponent, int32_t iExpSize); + +// init rsa state for private key data +DIRTYCODE_API int32_t CryptRSAInit2(CryptRSAT *pState, int32_t iModSize, const CryptBinaryObjT *pPrimeP, const CryptBinaryObjT *pPrimeQ, const CryptBinaryObjT *pExponentP, const CryptBinaryObjT *pExponentQ, const CryptBinaryObjT *pCoeffecient); + +// setup the master shared secret for encryption +DIRTYCODE_API void CryptRSAInitMaster(CryptRSAT *pState, const uint8_t *pMaster, int32_t iMasterLen); + +// setup the master shared secret for encryption using PKCS1.5 +DIRTYCODE_API void CryptRSAInitPrivate(CryptRSAT *pState, const uint8_t *pMaster, int32_t iMasterLen); + +// setup the encrypted signature for decryption. +DIRTYCODE_API void CryptRSAInitSignature(CryptRSAT *pState, const uint8_t *pSig, int32_t iSigLen); + +// do the encryption/decryption +DIRTYCODE_API int32_t CryptRSAEncrypt(CryptRSAT *pState, int32_t iIter); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptrsa_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptsha1.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptsha1.h new file mode 100644 index 00000000..1d7bd0e0 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptsha1.h @@ -0,0 +1,84 @@ +/*H*******************************************************************/ +/*! + \File cryptsha1.h + + \Description + This module implements SHA1 as defined in RFC 3174. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Notes + The implementation is based on the algorithm description in sections + 3 through 6 of RFC 3174 and not on the C code in section 7. + + The code deliberately uses some of the naming conventions from + the RFC to in order to aid comprehension. + + This implementation is limited to hashing no more than 2^32-9 bytes. + It will silently produce the wrong result if an attempt is made to + hash more data. + + \Version 1.0 06/06/2004 (sbevan) First Version +*/ +/*******************************************************************H*/ + +#ifndef _cryptsha1_h +#define _cryptsha1_h + +/*! +\Moduledef CryptSha1 CryptSha1 +\Modulemember Crypt +*/ +//@{ + +/*** Include files ***************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines *********************************************************/ + +#define CRYPTSHA1_HASHSIZE (160/8) //!< length of SHA1 hash + +/*** Macros **********************************************************/ + +/*** Type Definitions ************************************************/ + +typedef struct CryptSha1T CryptSha1T; + +//! all fields are PRIVATE +struct CryptSha1T +{ + uint32_t uCount; //!< total length of hash data in bytes + uint32_t uPartialCount; //!< # bytes in the strData + uint32_t H[5]; //!< message digest + uint8_t strData[16*4]; //!< partial data block +}; + +/*** Variables *******************************************************/ + +/*** Functions *******************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// init the SHA1 context. +DIRTYCODE_API void CryptSha1Init(CryptSha1T *pSha1); + +// init the SHA1 context (alternate form) +DIRTYCODE_API void CryptSha1Init2(CryptSha1T *pSha1, int32_t iHashSize); + +// add data to the SHA1 context (hash the data). +DIRTYCODE_API void CryptSha1Update(CryptSha1T *pSha1, const uint8_t *pInput, uint32_t uInputLength); + +// convert SHA1 state into final output form +DIRTYCODE_API void CryptSha1Final(CryptSha1T *pSha1, void *pBuffer, uint32_t uLength); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptsha2.h b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptsha2.h new file mode 100644 index 00000000..a69a81c4 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/crypt/cryptsha2.h @@ -0,0 +1,87 @@ +/*H*******************************************************************/ +/*! + \File cryptsha2.h + + \Description + This module implements SHA2 as defined in RFC 6234, which is + itself based on FIPS 180-2. + + \Notes + This implementation is limited to hashing no more than 2^32-9 + bytes. It will silently produce the wrong result if an attempt + is made to hash more data. + + \Copyright + Copyright (c) Electronic Arts 2013 + + \Version 1.0 11/04/2013 (jbrookes) First Version +*/ +/*******************************************************************H*/ + +#ifndef _cryptsha2_h +#define _cryptsha2_h + +/*! +\Moduledef CryptSha2 CryptSha2 +\Modulemember Crypt +*/ +//@{ + +/*** Include files ***************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines *********************************************************/ + +//! maximum hash result +#define CRYPTSHA224_HASHSIZE (28) +#define CRYPTSHA256_HASHSIZE (32) +#define CRYPTSHA384_HASHSIZE (48) +#define CRYPTSHA512_HASHSIZE (64) +#define CRYPTSHA2_HASHSIZE_MAX (CRYPTSHA512_HASHSIZE) + +/*** Macros **********************************************************/ + +/*** Type Definitions ************************************************/ + +//! private SHA2 state +typedef struct CryptSha2T +{ + uint32_t uCount; //!< total length of hash data in bytes + uint8_t uHashSize; //!< hash size + uint8_t uBlockSize; //!< 64/128 depending on mode + uint8_t uPartialCount; //!< # bytes in the partial data block + uint8_t _pad; + union + { + uint32_t H_32[8]; + uint64_t H_64[8]; + }TempHash; //!< temporary hash state + uint8_t strData[128]; //!< partial data block +} CryptSha2T; + +/*** Variables *******************************************************/ + +/*** Functions *******************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// init the SHA2 context. +DIRTYCODE_API void CryptSha2Init(CryptSha2T *pSha2, int32_t iHashSize); + +// add data to the SHA2 context (hash the data). +DIRTYCODE_API void CryptSha2Update(CryptSha2T *pSha2, const uint8_t *pInput, uint32_t uInputLength); + +// convert SHA2 state into final output form +DIRTYCODE_API void CryptSha2Final(CryptSha2T *pSha2, uint8_t *pBuffer, uint32_t uLength); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _cryptsha2_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtydefs.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtydefs.h new file mode 100644 index 00000000..4b50f561 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtydefs.h @@ -0,0 +1,49 @@ +/*H********************************************************************************/ +/*! + + \File dirtydefs.h + + \Description: + DirtySock platform independent definitions and enumerations. + + [DEPRECATE] + + \Copyright + Copyright (c) Electronic Arts 1999-2005 + + \Version 1.0 02/03/99 (JLB) First Version + \Version 1.1 04/01/99 (MDB) Added Endian types + \Version 1.2 03/27/02 (GWS) Made NULL C++ friendly, added DIRTYCODE_IOP, CODE_UNIX + \Version 1.3 02/22/05 (JEF) Moved CODE_XXX to DIRTYCODE_XXX to avoid conflicts + \Version 1.4 03/22/05 (GWS) Replaced with include of platform.h +*/ +/********************************************************************************H*/ + +#ifndef _dirtydefs_h +#define _dirtydefs_h + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +// Microsoft Facilty codes when building a HRESTULT go up to 81, so we'll just start at 128 +// we have 11 bits to work with giving us a max value of 2047 +#define DIRTYAPI_SOCKET (128) +#define DIRTYAPI_PROTO_HTTP (129) +#define DIRTYAPI_PROTO_SSL (130) +#define DIRTYAPI_QOS (131) +#define DIRTYAPI_MAX (2047) + +#define DIRTYAPI_SOCKET_ERR_ALREADY_ACTIVE (-1) +#define DIRTYAPI_SOCKET_ERR_NO_MEMORY (-2) +#define DIRTYAPI_SOCKET_ERR_HOST_NAME_CACHE (-3) +#define DIRTYAPI_SOCKET_ERR_PLATFORM_SPECIFIC (-4) +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +#endif // _dirtydefs_h + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtylang.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtylang.h new file mode 100644 index 00000000..0799c438 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtylang.h @@ -0,0 +1,746 @@ +/*H********************************************************************************/ +/*! + \File dirtylang.h + + \Description + Country and Language Code Information + + \Notes + This module provides country and language codes for both client and + server code in DirtySDK. + 2-letter language codes are currently available here: + http://en.wikipedia.org/wiki/ISO_639 + 2-letter country codes are currently available here: + http://en.wikipedia.org/wiki/ISO_3166 + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 11/30/2004 (jfrank) First Version +*/ +/********************************************************************************H*/ + +#ifndef _dirtylang_h +#define _dirtylang_h + +/*! +\Moduledef DirtyLang DirtyLang +\Modulemember DirtySock +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +//! Commonly used locality values +#define LOBBYAPI_LOCALITY_UNKNOWN ('zzZZ') +#define LOBBYAPI_LOCALITY_EN_US ('enUS') +#define LOBBYAPI_LOCALITY_BLANK ('\0\0\0\0') +#define LOBBYAPI_LOCALITY_WILDCARD ('****') + +#define LOBBYAPI_LOCALITY_DEFAULT LOBBYAPI_LOCALITY_EN_US +#define LOBBYAPI_LOCALITY_DEFAULT_STR "enUS" +#define LOBBYAPI_LOCALITY_UNKNOWN_STR "zzZZ" + +//! Non-specific, commonly used country and language codes +#define LOBBYAPI_LANGUAGE_UNKNOWN ('zz') +#define LOBBYAPI_COUNTRY_UNKNOWN ('ZZ') +#define LOBBYAPI_LANGUAGE_WILDCARD ('**') +#define LOBBYAPI_COUNTRY_WILDCARD ('**') +#define LOBBYAPI_LANGUAGE_UNKNOWN_STR ("zz") +#define LOBBYAPI_COUNTRY_UNKNOWN_STR ("ZZ") + +//! Languages +#define LOBBYAPI_LANGUAGE_AFAN_OROMO ('om') +#define LOBBYAPI_LANGUAGE_ABKHAZIAN ('ab') +#define LOBBYAPI_LANGUAGE_AFAR ('aa') +#define LOBBYAPI_LANGUAGE_AFRIKAANS ('af') +#define LOBBYAPI_LANGUAGE_ALBANIAN ('sq') +#define LOBBYAPI_LANGUAGE_AMHARIC ('am') +#define LOBBYAPI_LANGUAGE_ARABIC ('ar') +#define LOBBYAPI_LANGUAGE_ARMENIAN ('hy') +#define LOBBYAPI_LANGUAGE_ASSAMESE ('as') +#define LOBBYAPI_LANGUAGE_AYMARA ('ay') +#define LOBBYAPI_LANGUAGE_AZERBAIJANI ('az') +#define LOBBYAPI_LANGUAGE_BASHKIR ('ba') +#define LOBBYAPI_LANGUAGE_BASQUE ('eu') +#define LOBBYAPI_LANGUAGE_BENGALI ('bn') +#define LOBBYAPI_LANGUAGE_BHUTANI ('dz') +#define LOBBYAPI_LANGUAGE_BIHARI ('bh') +#define LOBBYAPI_LANGUAGE_BISLAMA ('bi') +#define LOBBYAPI_LANGUAGE_BRETON ('br') +#define LOBBYAPI_LANGUAGE_BULGARIAN ('bg') +#define LOBBYAPI_LANGUAGE_BURMESE ('my') +#define LOBBYAPI_LANGUAGE_BYELORUSSIAN ('be') +#define LOBBYAPI_LANGUAGE_CAMBODIAN ('km') +#define LOBBYAPI_LANGUAGE_CATALAN ('ca') +#define LOBBYAPI_LANGUAGE_CHINESE ('zh') +#define LOBBYAPI_LANGUAGE_CORSICAN ('co') +#define LOBBYAPI_LANGUAGE_CROATIAN ('hr') +#define LOBBYAPI_LANGUAGE_CZECH ('cs') +#define LOBBYAPI_LANGUAGE_DANISH ('da') +#define LOBBYAPI_LANGUAGE_DUTCH ('nl') +#define LOBBYAPI_LANGUAGE_ENGLISH ('en') +#define LOBBYAPI_LANGUAGE_ESPERANTO ('eo') +#define LOBBYAPI_LANGUAGE_ESTONIAN ('et') +#define LOBBYAPI_LANGUAGE_FAEROESE ('fo') +#define LOBBYAPI_LANGUAGE_FIJI ('fj') +#define LOBBYAPI_LANGUAGE_FINNISH ('fi') +#define LOBBYAPI_LANGUAGE_FRENCH ('fr') +#define LOBBYAPI_LANGUAGE_FRISIAN ('fy') +#define LOBBYAPI_LANGUAGE_GALICIAN ('gl') +#define LOBBYAPI_LANGUAGE_GEORGIAN ('ka') +#define LOBBYAPI_LANGUAGE_GERMAN ('de') +#define LOBBYAPI_LANGUAGE_GREEK ('el') +#define LOBBYAPI_LANGUAGE_GREENLANDIC ('kl') +#define LOBBYAPI_LANGUAGE_GUARANI ('gn') +#define LOBBYAPI_LANGUAGE_GUJARATI ('gu') +#define LOBBYAPI_LANGUAGE_HAUSA ('ha') +#define LOBBYAPI_LANGUAGE_HEBREW ('he') +#define LOBBYAPI_LANGUAGE_HINDI ('hi') +#define LOBBYAPI_LANGUAGE_HUNGARIAN ('hu') +#define LOBBYAPI_LANGUAGE_ICELANDIC ('is') +#define LOBBYAPI_LANGUAGE_INDONESIAN ('id') +#define LOBBYAPI_LANGUAGE_INTERLINGUA ('ia') +#define LOBBYAPI_LANGUAGE_INTERLINGUE ('ie') +#define LOBBYAPI_LANGUAGE_INUPIAK ('ik') +#define LOBBYAPI_LANGUAGE_INUKTITUT ('iu') +#define LOBBYAPI_LANGUAGE_IRISH ('ga') +#define LOBBYAPI_LANGUAGE_ITALIAN ('it') +#define LOBBYAPI_LANGUAGE_JAPANESE ('ja') +#define LOBBYAPI_LANGUAGE_JAVANESE ('jw') +#define LOBBYAPI_LANGUAGE_KANNADA ('kn') +#define LOBBYAPI_LANGUAGE_KASHMIRI ('ks') +#define LOBBYAPI_LANGUAGE_KAZAKH ('kk') +#define LOBBYAPI_LANGUAGE_KINYARWANDA ('rw') +#define LOBBYAPI_LANGUAGE_KIRGHIZ ('ky') +#define LOBBYAPI_LANGUAGE_KIRUNDI ('rn') +#define LOBBYAPI_LANGUAGE_KOREAN ('ko') +#define LOBBYAPI_LANGUAGE_KURDISH ('ku') +#define LOBBYAPI_LANGUAGE_LAOTHIAN ('lo') +#define LOBBYAPI_LANGUAGE_LATIN ('la') +#define LOBBYAPI_LANGUAGE_LATVIAN_LETTISH ('lv') +#define LOBBYAPI_LANGUAGE_LINGALA ('ln') +#define LOBBYAPI_LANGUAGE_LITHUANIAN ('lt') +#define LOBBYAPI_LANGUAGE_MACEDONIAN ('mk') +#define LOBBYAPI_LANGUAGE_MALAGASY ('mg') +#define LOBBYAPI_LANGUAGE_MALAY ('ms') +#define LOBBYAPI_LANGUAGE_MALAYALAM ('ml') +#define LOBBYAPI_LANGUAGE_MALTESE ('mt') +#define LOBBYAPI_LANGUAGE_MAORI ('mi') +#define LOBBYAPI_LANGUAGE_MARATHI ('mr') +#define LOBBYAPI_LANGUAGE_MOLDAVIAN ('mo') +#define LOBBYAPI_LANGUAGE_MONGOLIAN ('mn') +#define LOBBYAPI_LANGUAGE_NAURU ('na') +#define LOBBYAPI_LANGUAGE_NEPALI ('ne') +#define LOBBYAPI_LANGUAGE_NORWEGIAN ('no') +#define LOBBYAPI_LANGUAGE_OCCITAN ('oc') +#define LOBBYAPI_LANGUAGE_ORIYA ('or') +#define LOBBYAPI_LANGUAGE_PASHTO_PUSHTO ('ps') +#define LOBBYAPI_LANGUAGE_PERSIAN ('fa') +#define LOBBYAPI_LANGUAGE_POLISH ('pl') +#define LOBBYAPI_LANGUAGE_PORTUGUESE ('pt') +#define LOBBYAPI_LANGUAGE_PUNJABI ('pa') +#define LOBBYAPI_LANGUAGE_QUECHUA ('qu') +#define LOBBYAPI_LANGUAGE_RHAETO_ROMANCE ('rm') +#define LOBBYAPI_LANGUAGE_ROMANIAN ('ro') +#define LOBBYAPI_LANGUAGE_RUSSIAN ('ru') +#define LOBBYAPI_LANGUAGE_SAMOAN ('sm') +#define LOBBYAPI_LANGUAGE_SANGRO ('sg') +#define LOBBYAPI_LANGUAGE_SANSKRIT ('sa') +#define LOBBYAPI_LANGUAGE_SCOTS_GAELIC ('gd') +#define LOBBYAPI_LANGUAGE_SERBIAN ('sr') +#define LOBBYAPI_LANGUAGE_SERBO_CROATIAN ('sh') +#define LOBBYAPI_LANGUAGE_SESOTHO ('st') +#define LOBBYAPI_LANGUAGE_SETSWANA ('tn') +#define LOBBYAPI_LANGUAGE_SHONA ('sn') +#define LOBBYAPI_LANGUAGE_SINDHI ('sd') +#define LOBBYAPI_LANGUAGE_SINGHALESE ('si') +#define LOBBYAPI_LANGUAGE_SISWATI ('ss') +#define LOBBYAPI_LANGUAGE_SLOVAK ('sk') +#define LOBBYAPI_LANGUAGE_SLOVENIAN ('sl') +#define LOBBYAPI_LANGUAGE_SOMALI ('so') +#define LOBBYAPI_LANGUAGE_SPANISH ('es') +#define LOBBYAPI_LANGUAGE_SUDANESE ('su') +#define LOBBYAPI_LANGUAGE_SWAHILI ('sw') +#define LOBBYAPI_LANGUAGE_SWEDISH ('sv') +#define LOBBYAPI_LANGUAGE_TAGALOG ('tl') +#define LOBBYAPI_LANGUAGE_TAJIK ('tg') +#define LOBBYAPI_LANGUAGE_TAMIL ('ta') +#define LOBBYAPI_LANGUAGE_TATAR ('tt') +#define LOBBYAPI_LANGUAGE_TELUGU ('te') +#define LOBBYAPI_LANGUAGE_THAI ('th') +#define LOBBYAPI_LANGUAGE_TIBETAN ('bo') +#define LOBBYAPI_LANGUAGE_TIGRINYA ('ti') +#define LOBBYAPI_LANGUAGE_TONGA ('to') +#define LOBBYAPI_LANGUAGE_TSONGA ('ts') +#define LOBBYAPI_LANGUAGE_TURKISH ('tr') +#define LOBBYAPI_LANGUAGE_TURKMEN ('tk') +#define LOBBYAPI_LANGUAGE_TWI ('tw') +#define LOBBYAPI_LANGUAGE_UIGHUR ('ug') +#define LOBBYAPI_LANGUAGE_UKRAINIAN ('uk') +#define LOBBYAPI_LANGUAGE_URDU ('ur') +#define LOBBYAPI_LANGUAGE_UZBEK ('uz') +#define LOBBYAPI_LANGUAGE_VIETNAMESE ('vi') +#define LOBBYAPI_LANGUAGE_VOLAPUK ('vo') +#define LOBBYAPI_LANGUAGE_WELSH ('cy') +#define LOBBYAPI_LANGUAGE_WOLOF ('wo') +#define LOBBYAPI_LANGUAGE_XHOSA ('xh') +#define LOBBYAPI_LANGUAGE_YIDDISH ('yi') +#define LOBBYAPI_LANGUAGE_YORUBA ('yo') +#define LOBBYAPI_LANGUAGE_ZHUANG ('za') +#define LOBBYAPI_LANGUAGE_ZULU ('zu') + +// Languages: added on Mar-25-2011 according to ISO 639-1 +#define LOBBYAPI_LANGUAGE_BOSNIAN ('bs') +#define LOBBYAPI_LANGUAGE_DIVEHI ('dv') +#define LOBBYAPI_LANGUAGE_IGBO ('ig') +#define LOBBYAPI_LANGUAGE_LUXEMBOURGISH ('lb') +#define LOBBYAPI_LANGUAGE_YI ('ii') +#define LOBBYAPI_LANGUAGE_NORWEGIAN_BOKMAL ('nb') +#define LOBBYAPI_LANGUAGE_NORWEGIAN_NYNORSK ('nn') +#define LOBBYAPI_LANGUAGE_SAMI ('se') + +// Default language +#define LOBBYAPI_LANGUAGE_DEFAULT LOBBYAPI_LANGUAGE_ENGLISH +#define LOBBYAPI_LANGUAGE_DEFAULT_STR "en" + +//! Countries +#define LOBBYAPI_COUNTRY_AFGHANISTAN ('AF') +#define LOBBYAPI_COUNTRY_ALBANIA ('AL') +#define LOBBYAPI_COUNTRY_ALGERIA ('DZ') +#define LOBBYAPI_COUNTRY_AMERICAN_SAMOA ('AS') +#define LOBBYAPI_COUNTRY_ANDORRA ('AD') +#define LOBBYAPI_COUNTRY_ANGOLA ('AO') +#define LOBBYAPI_COUNTRY_ANGUILLA ('AI') +#define LOBBYAPI_COUNTRY_ANTARCTICA ('AQ') +#define LOBBYAPI_COUNTRY_ANTIGUA_BARBUDA ('AG') +#define LOBBYAPI_COUNTRY_ARGENTINA ('AR') +#define LOBBYAPI_COUNTRY_ARMENIA ('AM') +#define LOBBYAPI_COUNTRY_ARUBA ('AW') +#define LOBBYAPI_COUNTRY_AUSTRALIA ('AU') +#define LOBBYAPI_COUNTRY_AUSTRIA ('AT') +#define LOBBYAPI_COUNTRY_AZERBAIJAN ('AZ') +#define LOBBYAPI_COUNTRY_BAHAMAS ('BS') +#define LOBBYAPI_COUNTRY_BAHRAIN ('BH') +#define LOBBYAPI_COUNTRY_BANGLADESH ('BD') +#define LOBBYAPI_COUNTRY_BARBADOS ('BB') +#define LOBBYAPI_COUNTRY_BELARUS ('BY') +#define LOBBYAPI_COUNTRY_BELGIUM ('BE') +#define LOBBYAPI_COUNTRY_BELIZE ('BZ') +#define LOBBYAPI_COUNTRY_BENIN ('BJ') +#define LOBBYAPI_COUNTRY_BERMUDA ('BM') +#define LOBBYAPI_COUNTRY_BHUTAN ('BT') +#define LOBBYAPI_COUNTRY_BOLIVIA ('BO') +#define LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA ('BA') +#define LOBBYAPI_COUNTRY_BOTSWANA ('BW') +#define LOBBYAPI_COUNTRY_BOUVET_ISLAND ('BV') +#define LOBBYAPI_COUNTRY_BRAZIL ('BR') +#define LOBBYAPI_COUNTRY_BRITISH_INDIAN_OCEAN_TERRITORY ('IO') +#define LOBBYAPI_COUNTRY_BRUNEI_DARUSSALAM ('BN') +#define LOBBYAPI_COUNTRY_BULGARIA ('BG') +#define LOBBYAPI_COUNTRY_BURKINA_FASO ('BF') +#define LOBBYAPI_COUNTRY_BURUNDI ('BI') +#define LOBBYAPI_COUNTRY_CAMBODIA ('KH') +#define LOBBYAPI_COUNTRY_CAMEROON ('CM') +#define LOBBYAPI_COUNTRY_CANADA ('CA') +#define LOBBYAPI_COUNTRY_CAPE_VERDE ('CV') +#define LOBBYAPI_COUNTRY_CAYMAN_ISLANDS ('KY') +#define LOBBYAPI_COUNTRY_CENTRAL_AFRICAN_REPUBLIC ('CF') +#define LOBBYAPI_COUNTRY_CHAD ('TD') +#define LOBBYAPI_COUNTRY_CHILE ('CL') +#define LOBBYAPI_COUNTRY_CHINA ('CN') +#define LOBBYAPI_COUNTRY_CHRISTMAS_ISLAND ('CX') +#define LOBBYAPI_COUNTRY_COCOS_KEELING_ISLANDS ('CC') +#define LOBBYAPI_COUNTRY_COLOMBIA ('CO') +#define LOBBYAPI_COUNTRY_COMOROS ('KM') +#define LOBBYAPI_COUNTRY_CONGO ('CG') +#define LOBBYAPI_COUNTRY_COOK_ISLANDS ('CK') +#define LOBBYAPI_COUNTRY_COSTA_RICA ('CR') +#define LOBBYAPI_COUNTRY_COTE_DIVOIRE ('CI') +#define LOBBYAPI_COUNTRY_CROATIA ('HR') +#define LOBBYAPI_COUNTRY_CUBA ('CU') +#define LOBBYAPI_COUNTRY_CYPRUS ('CY') +#define LOBBYAPI_COUNTRY_CZECH_REPUBLIC ('CZ') +#define LOBBYAPI_COUNTRY_DENMARK ('DK') +#define LOBBYAPI_COUNTRY_DJIBOUTI ('DJ') +#define LOBBYAPI_COUNTRY_DOMINICA ('DM') +#define LOBBYAPI_COUNTRY_DOMINICAN_REPUBLIC ('DO') +#define LOBBYAPI_COUNTRY_EAST_TIMOR ('TP') +#define LOBBYAPI_COUNTRY_ECUADOR ('EC') +#define LOBBYAPI_COUNTRY_EGYPT ('EG') +#define LOBBYAPI_COUNTRY_EL_SALVADOR ('SV') +#define LOBBYAPI_COUNTRY_EQUATORIAL_GUINEA ('GQ') +#define LOBBYAPI_COUNTRY_ERITREA ('ER') +#define LOBBYAPI_COUNTRY_ESTONIA ('EE') +#define LOBBYAPI_COUNTRY_ETHIOPIA ('ET') +#define LOBBYAPI_COUNTRY_EUROPE_SSGFI_ONLY ('EU') +#define LOBBYAPI_COUNTRY_FALKLAND_ISLANDS ('FK') +#define LOBBYAPI_COUNTRY_FAEROE_ISLANDS ('FO') +#define LOBBYAPI_COUNTRY_FIJI ('FJ') +#define LOBBYAPI_COUNTRY_FINLAND ('FI') +#define LOBBYAPI_COUNTRY_FRANCE ('FR') +#define LOBBYAPI_COUNTRY_FRANCE_METROPOLITAN ('FX') +#define LOBBYAPI_COUNTRY_FRENCH_GUIANA ('GF') +#define LOBBYAPI_COUNTRY_FRENCH_POLYNESIA ('PF') +#define LOBBYAPI_COUNTRY_FRENCH_SOUTHERN_TERRITORIES ('TF') +#define LOBBYAPI_COUNTRY_GABON ('GA') +#define LOBBYAPI_COUNTRY_GAMBIA ('GM') +#define LOBBYAPI_COUNTRY_GEORGIA ('GE') +#define LOBBYAPI_COUNTRY_GERMANY ('DE') +#define LOBBYAPI_COUNTRY_GHANA ('GH') +#define LOBBYAPI_COUNTRY_GIBRALTAR ('GI') +#define LOBBYAPI_COUNTRY_GREECE ('GR') +#define LOBBYAPI_COUNTRY_GREENLAND ('GL') +#define LOBBYAPI_COUNTRY_GRENADA ('GD') +#define LOBBYAPI_COUNTRY_GUADELOUPE ('GP') +#define LOBBYAPI_COUNTRY_GUAM ('GU') +#define LOBBYAPI_COUNTRY_GUATEMALA ('GT') +#define LOBBYAPI_COUNTRY_GUINEA ('GN') +#define LOBBYAPI_COUNTRY_GUINEA_BISSAU ('GW') +#define LOBBYAPI_COUNTRY_GUYANA ('GY') +#define LOBBYAPI_COUNTRY_HAITI ('HT') +#define LOBBYAPI_COUNTRY_HEARD_AND_MC_DONALD_ISLANDS ('HM') +#define LOBBYAPI_COUNTRY_HONDURAS ('HN') +#define LOBBYAPI_COUNTRY_HONG_KONG ('HK') +#define LOBBYAPI_COUNTRY_HUNGARY ('HU') +#define LOBBYAPI_COUNTRY_ICELAND ('IS') +#define LOBBYAPI_COUNTRY_INDIA ('IN') +#define LOBBYAPI_COUNTRY_INDONESIA ('ID') +#define LOBBYAPI_COUNTRY_INTERNATIONAL_SSGFI_ONLY ('II') +#define LOBBYAPI_COUNTRY_IRAN ('IR') +#define LOBBYAPI_COUNTRY_IRAQ ('IQ') +#define LOBBYAPI_COUNTRY_IRELAND ('IE') +#define LOBBYAPI_COUNTRY_ISRAEL ('IL') +#define LOBBYAPI_COUNTRY_ITALY ('IT') +#define LOBBYAPI_COUNTRY_JAMAICA ('JM') +#define LOBBYAPI_COUNTRY_JAPAN ('JP') +#define LOBBYAPI_COUNTRY_JORDAN ('JO') +#define LOBBYAPI_COUNTRY_KAZAKHSTAN ('KZ') +#define LOBBYAPI_COUNTRY_KENYA ('KE') +#define LOBBYAPI_COUNTRY_KIRIBATI ('KI') +#define LOBBYAPI_COUNTRY_KOREA_DEMOCRATIC_PEOPLES_REPUBLIC_OF ('KP') +#define LOBBYAPI_COUNTRY_KOREA_REPUBLIC_OF ('KR') +#define LOBBYAPI_COUNTRY_KUWAIT ('KW') +#define LOBBYAPI_COUNTRY_KYRGYZSTAN ('KG') +#define LOBBYAPI_COUNTRY_LAO_PEOPLES_DEMOCRATIC_REPUBLIC ('LA') +#define LOBBYAPI_COUNTRY_LATVIA ('LV') +#define LOBBYAPI_COUNTRY_LEBANON ('LB') +#define LOBBYAPI_COUNTRY_LESOTHO ('LS') +#define LOBBYAPI_COUNTRY_LIBERIA ('LR') +#define LOBBYAPI_COUNTRY_LIBYAN_ARAB_JAMAHIRIYA ('LY') +#define LOBBYAPI_COUNTRY_LIECHTENSTEIN ('LI') +#define LOBBYAPI_COUNTRY_LITHUANIA ('LT') +#define LOBBYAPI_COUNTRY_LUXEMBOURG ('LU') +#define LOBBYAPI_COUNTRY_MACAU ('MO') +#define LOBBYAPI_COUNTRY_MACEDONIA_THE_FORMER_YUGOSLAV_REPUBLIC_OF ('MK') +#define LOBBYAPI_COUNTRY_MADAGASCAR ('MG') +#define LOBBYAPI_COUNTRY_MALAWI ('MW') +#define LOBBYAPI_COUNTRY_MALAYSIA ('MY') +#define LOBBYAPI_COUNTRY_MALDIVES ('MV') +#define LOBBYAPI_COUNTRY_MALI ('ML') +#define LOBBYAPI_COUNTRY_MALTA ('MT') +#define LOBBYAPI_COUNTRY_MARSHALL_ISLANDS ('MH') +#define LOBBYAPI_COUNTRY_MARTINIQUE ('MQ') +#define LOBBYAPI_COUNTRY_MAURITANIA ('MR') +#define LOBBYAPI_COUNTRY_MAURITIUS ('MU') +#define LOBBYAPI_COUNTRY_MAYOTTE ('YT') +#define LOBBYAPI_COUNTRY_MEXICO ('MX') +#define LOBBYAPI_COUNTRY_MICRONESIA_FEDERATED_STATES_OF ('FM') +#define LOBBYAPI_COUNTRY_MOLDOVA_REPUBLIC_OF ('MD') +#define LOBBYAPI_COUNTRY_MONACO ('MC') +#define LOBBYAPI_COUNTRY_MONGOLIA ('MN') +#define LOBBYAPI_COUNTRY_MONTSERRAT ('MS') +#define LOBBYAPI_COUNTRY_MOROCCO ('MA') +#define LOBBYAPI_COUNTRY_MOZAMBIQUE ('MZ') +#define LOBBYAPI_COUNTRY_MYANMAR ('MM') +#define LOBBYAPI_COUNTRY_NAMIBIA ('NA') +#define LOBBYAPI_COUNTRY_NAURU ('NR') +#define LOBBYAPI_COUNTRY_NEPAL ('NP') +#define LOBBYAPI_COUNTRY_NETHERLANDS ('NL') +#define LOBBYAPI_COUNTRY_NETHERLANDS_ANTILLES ('AN') +#define LOBBYAPI_COUNTRY_NEW_CALEDONIA ('NC') +#define LOBBYAPI_COUNTRY_NEW_ZEALAND ('NZ') +#define LOBBYAPI_COUNTRY_NICARAGUA ('NI') +#define LOBBYAPI_COUNTRY_NIGER ('NE') +#define LOBBYAPI_COUNTRY_NIGERIA ('NG') +#define LOBBYAPI_COUNTRY_NIUE ('NU') +#define LOBBYAPI_COUNTRY_NORFOLK_ISLAND ('NF') +#define LOBBYAPI_COUNTRY_NORTHERN_MARIANA_ISLANDS ('MP') +#define LOBBYAPI_COUNTRY_NORWAY ('NO') +#define LOBBYAPI_COUNTRY_OMAN ('OM') +#define LOBBYAPI_COUNTRY_PAKISTAN ('PK') +#define LOBBYAPI_COUNTRY_PALAU ('PW') +#define LOBBYAPI_COUNTRY_PANAMA ('PA') +#define LOBBYAPI_COUNTRY_PAPUA_NEW_GUINEA ('PG') +#define LOBBYAPI_COUNTRY_PARAGUAY ('PY') +#define LOBBYAPI_COUNTRY_PERU ('PE') +#define LOBBYAPI_COUNTRY_PHILIPPINES ('PH') +#define LOBBYAPI_COUNTRY_PITCAIRN ('PN') +#define LOBBYAPI_COUNTRY_POLAND ('PL') +#define LOBBYAPI_COUNTRY_PORTUGAL ('PT') +#define LOBBYAPI_COUNTRY_PUERTO_RICO ('PR') +#define LOBBYAPI_COUNTRY_QATAR ('QA') +#define LOBBYAPI_COUNTRY_REUNION ('RE') +#define LOBBYAPI_COUNTRY_ROMANIA ('RO') +#define LOBBYAPI_COUNTRY_RUSSIAN_FEDERATION ('RU') +#define LOBBYAPI_COUNTRY_RWANDA ('RW') +#define LOBBYAPI_COUNTRY_SAINT_KITTS_AND_NEVIS ('KN') +#define LOBBYAPI_COUNTRY_SAINT_LUCIA ('LC') +#define LOBBYAPI_COUNTRY_SAINT_VINCENT_AND_THE_GRENADINES ('VC') +#define LOBBYAPI_COUNTRY_SAMOA ('WS') +#define LOBBYAPI_COUNTRY_SAN_MARINO ('SM') +#define LOBBYAPI_COUNTRY_SAO_TOME_AND_PRINCIPE ('ST') +#define LOBBYAPI_COUNTRY_SAUDI_ARABIA ('SA') +#define LOBBYAPI_COUNTRY_SENEGAL ('SN') +#define LOBBYAPI_COUNTRY_SEYCHELLES ('SC') +#define LOBBYAPI_COUNTRY_SIERRA_LEONE ('SL') +#define LOBBYAPI_COUNTRY_SINGAPORE ('SG') +#define LOBBYAPI_COUNTRY_SLOVAKIA ('SK') +#define LOBBYAPI_COUNTRY_SLOVENIA ('SI') +#define LOBBYAPI_COUNTRY_SOLOMON_ISLANDS ('SB') +#define LOBBYAPI_COUNTRY_SOMALIA ('SO') +#define LOBBYAPI_COUNTRY_SOUTH_AFRICA ('ZA') +#define LOBBYAPI_COUNTRY_SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS ('GS') +#define LOBBYAPI_COUNTRY_SPAIN ('ES') +#define LOBBYAPI_COUNTRY_SRI_LANKA ('LK') +#define LOBBYAPI_COUNTRY_ST_HELENA_ASCENSION_AND_TRISTAN_DA_CUNHA ('SH') +#define LOBBYAPI_COUNTRY_ST_PIERRE_AND_MIQUELON ('PM') +#define LOBBYAPI_COUNTRY_SUDAN ('SD') +#define LOBBYAPI_COUNTRY_SURINAME ('SR') +#define LOBBYAPI_COUNTRY_SVALBARD_AND_JAN_MAYEN_ISLANDS ('SJ') +#define LOBBYAPI_COUNTRY_SWAZILAND ('SZ') +#define LOBBYAPI_COUNTRY_SWEDEN ('SE') +#define LOBBYAPI_COUNTRY_SWITZERLAND ('CH') +#define LOBBYAPI_COUNTRY_SYRIAN_ARAB_REPUBLIC ('SY') +#define LOBBYAPI_COUNTRY_TAIWAN ('TW') +#define LOBBYAPI_COUNTRY_TAJIKISTAN ('TJ') +#define LOBBYAPI_COUNTRY_TANZANIA_UNITED_REPUBLIC_OF ('TZ') +#define LOBBYAPI_COUNTRY_THAILAND ('TH') +#define LOBBYAPI_COUNTRY_TOGO ('TG') +#define LOBBYAPI_COUNTRY_TOKELAU ('TK') +#define LOBBYAPI_COUNTRY_TONGA ('TO') +#define LOBBYAPI_COUNTRY_TRINIDAD_AND_TOBAGO ('TT') +#define LOBBYAPI_COUNTRY_TUNISIA ('TN') +#define LOBBYAPI_COUNTRY_TURKEY ('TR') +#define LOBBYAPI_COUNTRY_TURKMENISTAN ('TM') +#define LOBBYAPI_COUNTRY_TURKS_AND_CAICOS_ISLANDS ('TC') +#define LOBBYAPI_COUNTRY_TUVALU ('TV') +#define LOBBYAPI_COUNTRY_UGANDA ('UG') +#define LOBBYAPI_COUNTRY_UKRAINE ('UA') +#define LOBBYAPI_COUNTRY_UNITED_ARAB_EMIRATES ('AE') +#define LOBBYAPI_COUNTRY_UNITED_KINGDOM ('GB') +#define LOBBYAPI_COUNTRY_UNITED_STATES ('US') +#define LOBBYAPI_COUNTRY_UNITED_STATES_MINOR_OUTLYING_ISLANDS ('UM') +#define LOBBYAPI_COUNTRY_URUGUAY ('UY') +#define LOBBYAPI_COUNTRY_UZBEKISTAN ('UZ') +#define LOBBYAPI_COUNTRY_VANUATU ('VU') +#define LOBBYAPI_COUNTRY_VATICAN_CITY_STATE ('VA') +#define LOBBYAPI_COUNTRY_VENEZUELA ('VE') +#define LOBBYAPI_COUNTRY_VIETNAM ('VN') +#define LOBBYAPI_COUNTRY_VIRGIN_ISLANDS_BRITISH ('VG') +#define LOBBYAPI_COUNTRY_VIRGIN_ISLANDS_US ('VI') +#define LOBBYAPI_COUNTRY_WALLIS_AND_FUTUNA_ISLANDS ('WF') +#define LOBBYAPI_COUNTRY_WESTERN_SAHARA ('EH') +#define LOBBYAPI_COUNTRY_YEMEN ('YE') +#define LOBBYAPI_COUNTRY_YUGOSLAVIA ('YU') +#define LOBBYAPI_COUNTRY_ZAIRE ('ZR') +#define LOBBYAPI_COUNTRY_ZAMBIA ('ZM') +#define LOBBYAPI_COUNTRY_ZIMBABWE ('ZW') + +// Countries: added on Mar-25-2011 +#define LOBBYAPI_COUNTRY_SERBIA_AND_MONTENEGRO ('CS') +#define LOBBYAPI_COUNTRY_MONTENEGRO ('ME') +#define LOBBYAPI_COUNTRY_SERBIA ('RS') +#define LOBBYAPI_COUNTRY_CONGO_DRC ('CD') +#define LOBBYAPI_COUNTRY_PALESTINIAN_TERRITORY ('PS') +#define LOBBYAPI_COUNTRY_GUERNSEY ('GG') +#define LOBBYAPI_COUNTRY_JERSEY ('JE') +#define LOBBYAPI_COUNTRY_ISLE_OF_MAN ('IM') +#define LOBBYAPI_COUNTRY_TIMOR_LESTE ('TL') + +// Default country +#define LOBBYAPI_COUNTRY_DEFAULT LOBBYAPI_COUNTRY_UNITED_STATES +#define LOBBYAPI_COUNTRY_DEFAULT_STR "US" + + +//! Currencies (ISO-4217) +#define LOBBYAPI_CURRENCY_UNITED_ARAB_EMIRATS_DIRHAM ('AED') +#define LOBBYAPI_CURRENCY_AFGHAN_AFGHANI ('AFN') +#define LOBBYAPI_CURRENCY_ALBANIAN_LEK ('ALL') +#define LOBBYAPI_CURRENCY_ARMENIAN_DRAM ('AMD') +#define LOBBYAPI_CURRENCY_NETHERLANDS_ANTILLEAN_GUILDER ('ANG') +#define LOBBYAPI_CURRENCY_ANGOLAN_KWANZA ('AOA') +#define LOBBYAPI_CURRENCY_ARGENTINE_PESO ('ARS') +#define LOBBYAPI_CURRENCY_AUSTRALIAN_DOLLAR ('AUD') +#define LOBBYAPI_CURRENCY_ARUBAN_FLORIN ('AWG') +#define LOBBYAPI_CURRENCY_AZERBAIJANI_PMANAT ('AZN') +#define LOBBYAPI_CURRENCY_BOSNIA_AND_HERZEGOVINA_CONVERTIBLE_MARK ('BAM') +#define LOBBYAPI_CURRENCY_BARBADOS_DOLLAR ('BBD') +#define LOBBYAPI_CURRENCY_BANGLADESHI_TAKA ('BDT') +#define LOBBYAPI_CURRENCY_BULGARIAN_LEV ('BGN') +#define LOBBYAPI_CURRENCY_BAHRAINI_DINAR ('BHD') +#define LOBBYAPI_CURRENCY_BURUNDIAN_FRANC ('BIF') +#define LOBBYAPI_CURRENCY_BERMUDIAN_DOLLAR ('BMD') +#define LOBBYAPI_CURRENCY_BRUNEI_DOLLAR ('BND') +#define LOBBYAPI_CURRENCY_BOLIVIANO ('BOB') +#define LOBBYAPI_CURRENCY_BOLIVIAN_MVDOL ('BOV') +#define LOBBYAPI_CURRENCY_BRAZILIAN_REAL ('BRL') +#define LOBBYAPI_CURRENCY_BAHAMIAN_DOLLAR ('BSD') +#define LOBBYAPI_CURRENCY_BHUTANESE_NGULTRUM ('BTN') +#define LOBBYAPI_CURRENCY_BOTSWANA_PULA ('BWP') +#define LOBBYAPI_CURRENCY_BELARUSIAN_RUBLE ('BYR') +#define LOBBYAPI_CURRENCY_BELIZE_DOLLAR ('BZD') +#define LOBBYAPI_CURRENCY_CANADIAN_DOLLAR ('CAD') +#define LOBBYAPI_CURRENCY_CONGOLESE_FRANC ('CDF') +#define LOBBYAPI_CURRENCY_WIR_EURO ('CHE') +#define LOBBYAPI_CURRENCY_SWISS_FRANC ('CHF') +#define LOBBYAPI_CURRENCY_WIR_FRANC ('CHW') +#define LOBBYAPI_CURRENCY_UNIDAD_DE_FOMENTO ('CLF') +#define LOBBYAPI_CURRENCY_CHILEAN_PESO ('CLP') +#define LOBBYAPI_CURRENCY_CHINESE_YUAN ('CNY') +#define LOBBYAPI_CURRENCY_COLOBIAN_PESO ('COP') +#define LOBBYAPI_CURRENCY_UNIDAD_DE_VALOR_REAL ('COU') +#define LOBBYAPI_CURRENCY_COSTA_RICAN_COLON ('CRC') +#define LOBBYAPI_CURRENCY_CUBAN_CONVERTIBLE_PESO ('CUC') +#define LOBBYAPI_CURRENCY_CUBAN_PESO ('CUP') +#define LOBBYAPI_CURRENCY_CAP_VERDE_ESCUDO ('CVE') +#define LOBBYAPI_CURRENCY_CZECH_KORUNA ('CZK') +#define LOBBYAPI_CURRENCY_DJIBOUTIAN_FRANC ('DJF') +#define LOBBYAPI_CURRENCY_DANISH_KRONE ('DKK') +#define LOBBYAPI_CURRENCY_DOMINICAN_PESO ('DOP') +#define LOBBYAPI_CURRENCY_ALGERIAN_DINAR ('DZD') +#define LOBBYAPI_CURRENCY_EGYPTIAN_POUND ('EGP') +#define LOBBYAPI_CURRENCY_ERITREAN_NAKFA ('ERN') +#define LOBBYAPI_CURRENCY_ETHIOPIAN_BIRR ('ETB') +#define LOBBYAPI_CURRENCY_EURO ('EUR') +#define LOBBYAPI_CURRENCY_FIJI_DOLLAR ('FJD') +#define LOBBYAPI_CURRENCY_FALKLAND_ISLANDS_POUND ('FKP') +#define LOBBYAPI_CURRENCY_POUND_STERLING ('GBP') +#define LOBBYAPI_CURRENCY_GEORGIAN_LARI ('GEL') +#define LOBBYAPI_CURRENCY_GHANAIAN_CEDI ('GHS') +#define LOBBYAPI_CURRENCY_GIBRALTAR_POUND ('GIP') +#define LOBBYAPI_CURRENCY_GAMBIAN_DALASI ('GMD') +#define LOBBYAPI_CURRENCY_GUINEAN_FRANC ('GNF') +#define LOBBYAPI_CURRENCY_GUATEMALAN_QUETZAL ('GTQ') +#define LOBBYAPI_CURRENCY_GUYANESE_DOLLAR ('GYD') +#define LOBBYAPI_CURRENCY_HONG_KONG_DOLLAR ('HKD') +#define LOBBYAPI_CURRENCY_HONDURAN_LEMPIRA ('HNL') +#define LOBBYAPI_CURRENCY_CROATIAN_KUNA ('HRK') +#define LOBBYAPI_CURRENCY_HAITIAN_GOURDE ('HTG') +#define LOBBYAPI_CURRENCY_HUNGARIAN_FORINT ('HUF') +#define LOBBYAPI_CURRENCY_INDONESIAN_RUPIAH ('IDR') +#define LOBBYAPI_CURRENCY_ISRAELI_NEW_SHEQEL ('ILS') +#define LOBBYAPI_CURRENCY_INDIAN_RUPEE ('INR') +#define LOBBYAPI_CURRENCY_IRAQI_DINAR ('IQD') +#define LOBBYAPI_CURRENCY_IRANIAN_RIAL ('IRR') +#define LOBBYAPI_CURRENCY_ICELANDIC_KRONA ('ISK') +#define LOBBYAPI_CURRENCY_JAMAICAN_DOLLAR ('JMD') +#define LOBBYAPI_CURRENCY_JORDANIAN_DINAR ('JOD') +#define LOBBYAPI_CURRENCY_JAPANESE_YEN ('JPY') +#define LOBBYAPI_CURRENCY_KENYAN_SHILLING ('KES') +#define LOBBYAPI_CURRENCY_KYRGYZSTANI_SOM ('KGS') +#define LOBBYAPI_CURRENCY_CAMBODIAN_RIEL ('KHR') +#define LOBBYAPI_CURRENCY_COMORO_FRANC ('KMF') +#define LOBBYAPI_CURRENCY_NORTH_KOREAN_WON ('KPW') +#define LOBBYAPI_CURRENCY_SOUTH_KOREAN_WON ('KRW') +#define LOBBYAPI_CURRENCY_KUWAITI_DINAR ('KWD') +#define LOBBYAPI_CURRENCY_CAYMAN_ISLANDS_DOLLAR ('KYD') +#define LOBBYAPI_CURRENCY_KAZAKHSTANI_TENGE ('KZT') +#define LOBBYAPI_CURRENCY_LAO_KIP ('LAK') +#define LOBBYAPI_CURRENCY_LEBANESE_POUND ('LBP') +#define LOBBYAPI_CURRENCY_SRI_LANKAN_RUPEE ('LKR') +#define LOBBYAPI_CURRENCY_LIBERIAN_DOLLAR ('LRD') +#define LOBBYAPI_CURRENCY_LESOTHO_LOTI ('LSL') +#define LOBBYAPI_CURRENCY_LITHUANIAN_LITAS ('LTL') +#define LOBBYAPI_CURRENCY_LATVIAN_LATS ('LVL') +#define LOBBYAPI_CURRENCY_LYBIAN_DINAR ('LYD') +#define LOBBYAPI_CURRENCY_MOROCCAN_DIRHAM ('MAD') +#define LOBBYAPI_CURRENCY_MOLDOVAN_LEU ('MDL') +#define LOBBYAPI_CURRENCY_MALAGASY_ARIARY ('MGA') +#define LOBBYAPI_CURRENCY_MACEDONIAN_DENAR ('MKD') +#define LOBBYAPI_CURRENCY_MYANMA_KYAT ('MMK') +#define LOBBYAPI_CURRENCY_MONGOLIAN_TUGRIK ('MNT') +#define LOBBYAPI_CURRENCY_MACANESE_PATACA ('MOP') +#define LOBBYAPI_CURRENCY_MAURITANIAN_OUGUIYA ('MRO') +#define LOBBYAPI_CURRENCY_MAURITIAN_RUPEE ('MUR') +#define LOBBYAPI_CURRENCY_MALDIVIAN_RUFIYAA ('MVR') +#define LOBBYAPI_CURRENCY_MALAWAIAN_KWACHA ('MWK') +#define LOBBYAPI_CURRENCY_MEXICAN_PESO ('MXN') +#define LOBBYAPI_CURRENCY_MEXICAN_UNIDAD_DE_INVERSION ('MXV') +#define LOBBYAPI_CURRENCY_MALAYSIAN_RINGGIT ('MYR') +#define LOBBYAPI_CURRENCY_MOZAMBICAN_METICAL ('MZN') +#define LOBBYAPI_CURRENCY_NAMIBIAN_DOLLAR ('NAD') +#define LOBBYAPI_CURRENCY_NIGERIAN_NAIRA ('NGN') +#define LOBBYAPI_CURRENCY_NICARAGUAN_CORDOBA ('NIO') +#define LOBBYAPI_CURRENCY_NORVEGIAN_KRONE ('NOK') +#define LOBBYAPI_CURRENCY_NEPALESE_RUPEE ('NPR') +#define LOBBYAPI_CURRENCY_NEW_ZEALAND_DOLLAR ('NZD') +#define LOBBYAPI_CURRENCY_OMANI_RIAL ('OMR') +#define LOBBYAPI_CURRENCY_PANAMANIAN_BALBOA ('PAB') +#define LOBBYAPI_CURRENCY_PERUVIAN_NUEVO_SOL ('PEN') +#define LOBBYAPI_CURRENCY_PAPUA_NEW_GUINEAN_KINA ('PGK') +#define LOBBYAPI_CURRENCY_PHILIPPINE_PESO ('PHP') +#define LOBBYAPI_CURRENCY_PAKISTANI_RUPEE ('PKR') +#define LOBBYAPI_CURRENCY_POLISH_ZLOTY ('PLN') +#define LOBBYAPI_CURRENCY_PARAGUAYAN_GUARANI ('PYG') +#define LOBBYAPI_CURRENCY_QATARI_RIAL ('QAR') +#define LOBBYAPI_CURRENCY_ROMANIAN_NEW_LEU ('RON') +#define LOBBYAPI_CURRENCY_SERBIAN_DINAR ('RSD') +#define LOBBYAPI_CURRENCY_RUSSIAN_RUBLE ('RUB') +#define LOBBYAPI_CURRENCY_RWANDAN_FRANC ('RWF') +#define LOBBYAPI_CURRENCY_SAUDI_RIYAL ('SAR') +#define LOBBYAPI_CURRENCY_SOLOMON_ISLANDS_DOLLAR ('SBD') +#define LOBBYAPI_CURRENCY_SEYCHELLES_RUPEE ('SRC') +#define LOBBYAPI_CURRENCY_SUDANESE_POUND ('SDG') +#define LOBBYAPI_CURRENCY_SWEDISH_KRONA ('SEK') +#define LOBBYAPI_CURRENCY_SINGAPORE_DOLLAR ('SGD') +#define LOBBYAPI_CURRENCY_SAINT_HELENA_POUND ('SHP') +#define LOBBYAPI_CURRENCY_SIERRA_LEONEAN_LEONE ('SLL') +#define LOBBYAPI_CURRENCY_SOMALI_SHILLING ('SOS') +#define LOBBYAPI_CURRENCY_SURINAMESE_DOLLAR ('SRD') +#define LOBBYAPI_CURRENCY_SOUTH_SUDANESE_POUND ('SSP') +#define LOBBYAPI_CURRENCY_SAO_TOME_AND_PRINCIPE_DOBRA ('STD') +#define LOBBYAPI_CURRENCY_SYRIAN_POUND ('SYP') +#define LOBBYAPI_CURRENCY_SWAZI_LILANGENI ('SZL') +#define LOBBYAPI_CURRENCY_THAI_BAHT ('THB') +#define LOBBYAPI_CURRENCY_TAJIKISTANI_SOMONI ('TJS') +#define LOBBYAPI_CURRENCY_TURKMENISTANI_MANAT ('TMT') +#define LOBBYAPI_CURRENCY_TUNISIAN_DINAR ('TND') +#define LOBBYAPI_CURRENCY_TONGAN_PAANGA ('TOP') +#define LOBBYAPI_CURRENCY_TURKISH_LIRA ('TRY') +#define LOBBYAPI_CURRENCY_TRINIDAD_AND_TOBAGO_DOLLAR ('TTD') +#define LOBBYAPI_CURRENCY_NEW_TAIWAN_DOLLAR ('TWD') +#define LOBBYAPI_CURRENCY_TANZANIAN_SHILLING ('TZS') +#define LOBBYAPI_CURRENCY_UKRAINIAN_HRYVNIA ('UAH') +#define LOBBYAPI_CURRENCY_UGANDAN_SHILLING ('UGX') +#define LOBBYAPI_CURRENCY_UNITED_STATES_DOLLAR ('USD') +#define LOBBYAPI_CURRENCY_UNITED_STATES_DOLLAR_NEXT_DAY ('USN') +#define LOBBYAPI_CURRENCY_UNITED_STATES_DOLLAR_SAME_DAY ('USS') +#define LOBBYAPI_CURRENCY_URUGUAY_PESO_EN_UNIDADES_INDEXADAS ('UYI') +#define LOBBYAPI_CURRENCY_URUGUAYAN_PESO ('UYU') +#define LOBBYAPI_CURRENCY_UZBEKISTAN_SOM ('UZS') +#define LOBBYAPI_CURRENCY_VENEZUELAN_BOLIVAR_FUERTE ('VEF') +#define LOBBYAPI_CURRENCY_VIETNAMESE_DONG ('VND') +#define LOBBYAPI_CURRENCY_VANUATU_VATU ('VUV') +#define LOBBYAPI_CURRENCY_SAMOAN_TALA ('WST') +#define LOBBYAPI_CURRENCY_CFA_FRANC_BEAC ('XAF') +#define LOBBYAPI_CURRENCY_EAST_CARABBEAN_DOLLAR ('XCD') +#define LOBBYAPI_CURRENCY_CFA_FRANC_BCEAO ('XOF') +#define LOBBYAPI_CURRENCY_CFP_FRANC ('XPF') +#define LOBBYAPI_CURRENCY_YEMENI_RIAL ('YER') +#define LOBBYAPI_CURRENCY_SOUTH_AFRICAN_RAND ('ZAR') +#define LOBBYAPI_CURRENCY_ZAMBIAN_KWACHA ('ZMK') +#define LOBBYAPI_CURRENCY_ZIMBABWE_DOLLAR ('ZWL') + +// Default currency +#define LOBBYAPI_CURRENCY_DEFAULT LOBBYAPI_CURRENCY_UNITED_STATES_DOLLAR +#define LOBBYAPI_CURRENCY_DEFAULT_STR "USD" + + +/*** Macros ***********************************************************************/ + +//! Write the currency code to a character string +#define LOBBYAPI_CreateCurrencyString(strOutstring, uCurrency) \ + { \ + (strOutstring)[0] = (char)(((uCurrency) >> 16 ) & 0xFF); \ + (strOutstring)[1] = (char)(((uCurrency) >> 8) & 0xFF); \ + (strOutstring)[2] = (char)((uCurrency) & 0xFF); \ + (strOutstring)[3]='\0'; \ + } + +//! toupper replacement +#define LOBBYAPI_LocalizerTokenToUpper(uCharToModify) \ + ((((unsigned char)(uCharToModify) >= 'a') && ((unsigned char)(uCharToModify) <= 'z')) ? \ + (((unsigned char)(uCharToModify)) & (~32)) : \ + (uCharToModify)) + +//! tolower replacement +#define LOBBYAPI_LocalizerTokenToLower(uCharToModify) \ + ((((unsigned char)(uCharToModify) >= 'A') && ((unsigned char)(uCharToModify) <= 'Z')) ? \ + (((unsigned char)(uCharToModify)) | (32)) : \ + (uCharToModify)) + +//! Create a localizer token from shorts representing country and language +#define LOBBYAPI_LocalizerTokenCreate(uLanguage, uCountry) \ + (((LOBBYAPI_LocalizerTokenShortToLower(uLanguage)) << 16) + (LOBBYAPI_LocalizerTokenShortToUpper(uCountry))) + +//! Create a localizer token from strings containing country and language +#define LOBBYAPI_LocalizerTokenCreateFromStrings(strLanguage, strCountry) \ + (LOBBYAPI_LocalizerTokenCreate(LOBBYAPI_LocalizerTokenGetShortFromString(strLanguage),LOBBYAPI_LocalizerTokenGetShortFromString(strCountry))) + +//! Create a localizer token from a single string (language + country - ex: "enUS") +#define LOBBYAPI_LocalizerTokenCreateFromString(strLocality) \ + (LOBBYAPI_LocalizerTokenCreateFromStrings(&(strLocality)[0], &(strLocality)[2])) + +//! Get a int16_t integer from a string +#define LOBBYAPI_LocalizerTokenGetShortFromString(strInstring) (( (((unsigned char*)(strInstring) == NULL) || ((unsigned char*)strInstring)[0] == '\0')) ? \ + ((uint16_t)(0)) : \ + ((uint16_t)((((unsigned char*)strInstring)[0] << 8) | ((unsigned char*)strInstring)[1]))) + +//! Pull the country (int16_t) from a localizer token (int32_t) +#define LOBBYAPI_LocalizerTokenGetCountry(uToken) ((uint16_t)((uToken) & 0xFFFF)) + +//! Pull the language (int16_t) from a localizer token (int32_t) +#define LOBBYAPI_LocalizerTokenGetLanguage(uToken) ((uint16_t)(((uToken) >> 16) & 0xFFFF)) + +//! Replace the country in a locality value +#define LOBBYAPI_LocalizerTokenSetCountry(uToken, uCountry) (uToken) = (((uToken) & 0xFFFF0000) | (uCountry)); + +//! Replace the language in a locality value +#define LOBBYAPI_LocalizerTokenSetLanguage(uToken, uLanguage) (uToken) = (((uToken) & 0x0000FFFF) | ((uLanguage) << 16)); + +//! Write the country contained in a localizer token to a character string +#define LOBBYAPI_LocalizerTokenCreateCountryString(strOutstring, uToken) \ + { \ + (strOutstring)[0] = (char)(((uToken) >> 8) & 0xFF); \ + (strOutstring)[1] = (char)((uToken) & 0xFF); \ + (strOutstring)[2]='\0'; \ + } + + +//! Write the language contained in a localizer token to a character string +#define LOBBYAPI_LocalizerTokenCreateLanguageString(strOutstring, uToken) \ + { \ + (strOutstring)[0]=(char)(((uToken) >> 24) & 0xFF); \ + (strOutstring)[1]=(char)(((uToken) >> 16) & 0xFF); \ + (strOutstring)[2]='\0'; \ + } + +//! Write the entire locality string to a character string +#define LOBBYAPI_LocalizerTokenCreateLocalityString(strOutstring, uToken) \ + { \ + (strOutstring)[0]=(char)(((uToken) >> 24) & 0xFF); \ + (strOutstring)[1]=(char)(((uToken) >> 16) & 0xFF); \ + (strOutstring)[2]=(char)(((uToken) >> 8) & 0xFF); \ + (strOutstring)[3]=(char)((uToken) & 0xFF); \ + (strOutstring)[4]='\0'; \ + } + +//! Macro to provide an easy way to display the token in character format +#define LOBBYAPI_LocalizerTokenPrintCharArray(uToken) \ + (char)(((uToken)>>24)&0xFF), (char)(((uToken)>>16)&0xFF), (char)(((uToken)>>8)&0xFF), (char)((uToken)&0xFF) + +//! Provide a way to capitalize the elements in a int16_t +#define LOBBYAPI_LocalizerTokenShortToUpper(uShort) \ + ((uint16_t)(((LOBBYAPI_LocalizerTokenToUpper(((uShort) >> 8) & 0xFF)) << 8) + \ + (LOBBYAPI_LocalizerTokenToUpper((uShort) & 0xFF)))) + +//! Provide a way to lowercase the elements in a int16_t +#define LOBBYAPI_LocalizerTokenShortToLower(uShort) \ + ((uint16_t)(((LOBBYAPI_LocalizerTokenToLower(((uShort) >> 8) & 0xFF)) << 8) + \ + (LOBBYAPI_LocalizerTokenToLower((uShort) & 0xFF)))) + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +//@} + +#endif // _dirtylang_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock.h new file mode 100644 index 00000000..e844e870 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock.h @@ -0,0 +1,44 @@ +/*H*************************************************************************************/ +/*! + \File dirtysock.h + + \Description + Platform independent interface to network layers. Based on + BSD sockets, but with performance modifications. Allows truly + portable modules to be written and moved to different platforms + needing only different support wrappers (no change to actual + network modes). + + \Copyright + Copyright (c) Electronic Arts 2001-2014 + + \Version 1.0 08/01/2001 (gschaefer) First Version +*/ +/*************************************************************************************H*/ + +#ifndef _dirtysock_h +#define _dirtysock_h + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +#ifndef DIRTYSOCK +#define DIRTYSOCK (TRUE) +#include "DirtySDK/dirtydefs.h" +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#endif + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#endif // _dirtysock_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtyaddr.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtyaddr.h new file mode 100644 index 00000000..66c88cd4 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtyaddr.h @@ -0,0 +1,98 @@ +/*H********************************************************************************/ +/*! + \File dirtyaddr.h + + \Description + Definition for portable address type. + + \Copyright + Copyright (c) Electronic Arts 2004 + + \Version 1.0 04/07/2004 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _dirtyaddr_h +#define _dirtyaddr_h + +/*! +\Moduledef DirtyAddr DirtyAddr +\Modulemember DirtySock +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +#if defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) +/* In production, when an xboxone is located behind an OpenWRT based router that has + an IPv6 connection, the router defaults to assigning ULA prefixes via SLAAC and + DHCPv6. This results in the console having a global IPv6 address, a link local + IPv6 address, 2 ULA IPv6 addresses, and an IPv4 address. In such a scenario, + the Secure Device Address of the console is large enough that it cannot fit in a + 127-byte DirtyAddrT (size used on other platforms). + + After checking with MS, we got a confirmation that the size of a + SecureDevicAddress will never exceed 300 bytes. (enforced both in the + Windows::Networking::XboxLive and the Windows::Xbox::Networking namespaces). + + A call to DirtyAddrSetInfoXboxOne() for a 300-byte SecureDeviceAddress blob + results in 370 bytes being written in the DirtyAddrT. Consequently, it is safe + to make the size DIRTYADDR_MACHINEADDR_MAXLEN 372 on xboxone. +*/ +#define DIRTYADDR_MACHINEADDR_MAXLEN (372) +#else +#define DIRTYADDR_MACHINEADDR_MAXLEN (127) +#endif +#define DIRTYADDR_MACHINEADDR_MAXSIZE (DIRTYADDR_MACHINEADDR_MAXLEN + 1) + +/*** Macros ***********************************************************************/ + +//! compare two opaque addresses for equality (same=TRUE, different=FALSE) +#define DirtyAddrCompare(_pAddr1, _pAddr2) (!strcmp((_pAddr1)->strMachineAddr, (_pAddr2)->strMachineAddr)) + +/*** Type Definitions *************************************************************/ + +//! opaque address type +typedef struct DirtyAddrT +{ + char strMachineAddr[DIRTYADDR_MACHINEADDR_MAXSIZE]; +} DirtyAddrT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +//! convert a DirtyAddrT to native format +DIRTYCODE_API uint32_t DirtyAddrToHostAddr(void *pOutput, int32_t iBufLen, const DirtyAddrT *pAddr); + +//! convert a host-format address to native format +DIRTYCODE_API uint32_t DirtyAddrFromHostAddr(DirtyAddrT *pAddr, const void *pInput); + +//! get local address in DirtyAddr form +DIRTYCODE_API uint32_t DirtyAddrGetLocalAddr(DirtyAddrT *pAddr); + +#if defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) +//! get Xbox One extended info into dirtyaddr +DIRTYCODE_API uint8_t DirtyAddrGetInfoXboxOne(const DirtyAddrT *pDirtyAddr, void *pXuid, void *pSecureDeviceAddressBlob, int32_t *pBlobSize); + +//! set Xbox One extended info into dirtyaddr +DIRTYCODE_API void DirtyAddrSetInfoXboxOne(DirtyAddrT *pDirtyAddr, const void *pXuid, const void *pSecureDeviceAddressBlob, int32_t iBlobSize); + +#endif + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtyaddr_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtycert.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtycert.h new file mode 100644 index 00000000..840ff490 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtycert.h @@ -0,0 +1,77 @@ +/*H********************************************************************************/ +/*! + \File dirtycert.h + + \Description + This module defines the CA fallback mechanism which is used by ProtoSSL. + + \Copyright + Copyright (c) 2012 Electronic Arts Inc. + + \Version 01/23/2012 (szhu) +*/ +/********************************************************************************H*/ + +#ifndef _dirtycert_h +#define _dirtycert_h + +/*! +\Moduledef NetConnDefs NetConnDefs +\Modulemember DirtySock +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/proto/protossl.h" + +/*** Defines **********************************************************************/ +#define DIRTYCERT_SERVICENAME_SIZE (128) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// opaque module state ref +typedef struct DirtyCertRefT DirtyCertRefT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create dirtycert module +DIRTYCODE_API int32_t DirtyCertCreate(void); + +// release resources and destroy module +DIRTYCODE_API int32_t DirtyCertDestroy(void); + +// initiate a CA fetch request +DIRTYCODE_API int32_t DirtyCertCARequestCert(const ProtoSSLCertInfoT *pCertInfo, const char *pHost, int32_t iPort); + +// initiate a CA prefetch request +DIRTYCODE_API void DirtyCertCAPreloadCerts(const char *pServiceName); + +// if a CA fetch request is complete +DIRTYCODE_API int32_t DirtyCertCARequestDone(int32_t iRequestId); + +// release resources used by a CA fetch request +DIRTYCODE_API int32_t DirtyCertCARequestFree(int32_t iRequestId); + +// control module behavior +DIRTYCODE_API int32_t DirtyCertControl(int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); + +// get module status +DIRTYCODE_API int32_t DirtyCertStatus(int32_t iStatus, void *pBuffer, int32_t iBufSize); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtycert_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtyerr.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtyerr.h new file mode 100644 index 00000000..59f39a1c --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtyerr.h @@ -0,0 +1,97 @@ +/*H********************************************************************************/ +/*! + \File dirtyerr.h + + \Description + Dirtysock debug error routines. + + \Copyright + Copyright (c) 2005 Electronic Arts + + \Version 06/13/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _dirtyerr_h +#define _dirtyerr_h + +/*! +\Moduledef DirtyErr DirtyErr +\Modulemember DirtySock +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +#define DIRTYSOCK_ERRORNAMES (DIRTYCODE_LOGGING && TRUE) +#define DIRTYSOCK_LISTTERM (0x45454545) + +/*** Macros ***********************************************************************/ + +#if DIRTYSOCK_ERRORNAMES +#define DIRTYSOCK_ErrorName(_iError) { (uint32_t)_iError, #_iError } +#define DIRTYSOCK_ListEnd() { DIRTYSOCK_LISTTERM, "" } +#endif + +/*** Type Definitions *************************************************************/ + +typedef struct DirtyErrT +{ + uint32_t uError; + const char *pErrorName; +} DirtyErrT; + +#ifdef DIRTYCODE_PS4 + +typedef void (*DirtySockAppErrorCallback)(int32_t errorCode); + +#endif + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DIRTYCODE_PS4 + +//set Application Error Callback +void DirtyErrAppCallbackSet(DirtySockAppErrorCallback pCallback); + +//Inovke App Error Callback if set to report sony error code back to application layer +void DirtyErrAppReport(int32_t iError); + +#endif + +// take a system-specific error code, and either resolve it to its define name or format it as a hex number +DIRTYCODE_API void DirtyErrName(char *pBuffer, int32_t iBufSize, uint32_t uError); + +// same as DirtyErrName, but references the specified list +DIRTYCODE_API void DirtyErrNameList(char *pBuffer, int32_t iBufSize, uint32_t uError, const DirtyErrT *pList); + +// same as DirtyErrName, except a pointer is returned +DIRTYCODE_API const char *DirtyErrGetName(uint32_t uError); + +// same as DirtyErrGetName, but references the specified list +DIRTYCODE_API const char *DirtyErrGetNameList(uint32_t uError, const DirtyErrT *pList); + +// create a unique error code for use accross DirtySDK +DIRTYCODE_API uint32_t DirtyErrGetHResult(uint16_t uFacility, int16_t iCode, uint8_t bFailure); + +// break a hresult back into its components +DIRTYCODE_API void DirtyErrDecodeHResult(uint32_t hResult, uint16_t* uFacility, int16_t* iCode, uint8_t* bCustomer, uint8_t* bFailure); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtyerr_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtylib.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtylib.h new file mode 100644 index 00000000..f117c6b1 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtylib.h @@ -0,0 +1,234 @@ +/*H**************************************************************************************/ +/*! + \File dirtylib.h + + \Description + Provide basic library functions for use by network layer code. + This is needed because the network code is platform/project + independent and needs to rely on a certain set of basic + functions. + + \Copyright + Copyright (c) Electronic Arts 2001-2018 + + \Version 0.5 08/01/01 (GWS) First Version + \Version 1.0 12/31/01 (GWS) Redesigned for Tiburon environment +*/ +/**************************************************************************************H*/ + +#ifndef _dirtylib_h +#define _dirtylib_h + +/*! +\Moduledef DirtyLib DirtyLib +\Modulemember DirtySock +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +// define platform-specific options +#ifndef DIRTYCODE_LOGGING + #if DIRTYCODE_DEBUG + //in debug mode logging is defaulted to on + #define DIRTYCODE_LOGGING (1) + #else + //if its not specified then turn it off + #define DIRTYCODE_LOGGING (0) + #endif +#endif + +//! define NetCrit options +#define NETCRIT_OPTION_NONE (0) //!< default settings +#define NETCRIT_OPTION_SINGLETHREADENABLE (1) //!< enable the crit even when in single-threaded mode + +// debug printing routines +#if DIRTYCODE_LOGGING + #define NetPrintf(_x) NetPrintfCode _x + #define NetPrintfVerbose(_x) NetPrintfVerboseCode _x + #define NetPrintArray(_pMem, _iSize, _pTitle) NetPrintArrayCode(_pMem, _iSize, _pTitle) + #define NetPrintMem(_pMem, _iSize, _pTitle) NetPrintMemCode(_pMem, _iSize, _pTitle) + #define NetPrintWrap(_pString, _iWrapCol) NetPrintWrapCode(_pString, _iWrapCol) + #define NetTimeStampEnable(_bEnableTimeStamp) NetTimeStampEnableCode(_bEnableTimeStamp) +#else + #define NetPrintf(_x) { } + #define NetPrintfVerbose(_x) { } + #define NetPrintArray(_pMem, _iSize, _pTitle) { } + #define NetPrintMem(_pMem, _iSize, _pTitle) { } + #define NetPrintWrap(_pString, _iWrapCol) { } + #define NetTimeStampEnable(_bEnableTimeStamp) { } +#endif + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct NetCritPrivT NetCritPrivT; + +//! critical section definition +typedef struct NetCritT +{ + NetCritPrivT *pData; +} NetCritT; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Portable routines implemented in dirtynet.c +*/ + +// platform-common create (called internally by NetLibCreate) +DIRTYCODE_API void NetLibCommonInit(void); + +// platform-common shutdown (called internally by NetLibDestroy) +DIRTYCODE_API void NetLibCommonShutdown(void); + +// reset net idle list +DIRTYCODE_API void NetIdleReset(void); + +// remove a function to the idle callback list. +DIRTYCODE_API void NetIdleAdd(void (*proc)(void *ref), void *ref); + +// call all the functions in the idle list. +DIRTYCODE_API void NetIdleDel(void (*proc)(void *ref), void *ref); + +// make sure all idle calls have completed +DIRTYCODE_API void NetIdleDone(void); + +// add a function to the idle callback list +DIRTYCODE_API void NetIdleCall(void); + +// return 32-bit hash from given input string +DIRTYCODE_API int32_t NetHash(const char *pString); + +// return 32-bit hash from given buffer +DIRTYCODE_API int32_t NetHashBin(const void *pBuffer, uint32_t uLength); + +// return 32-bit CRC from given buffer +DIRTYCODE_API int32_t NetCrc32(const uint8_t *pBuffer, int32_t iBufLen, const uint32_t *pCrcTable); + +// A simple psuedo-random sequence generator +DIRTYCODE_API uint32_t NetRand(uint32_t uLimit); + +// return time +DIRTYCODE_API uint64_t NetTime(void); + +// enable logging time stamp +DIRTYCODE_API void NetTimeStampEnableCode(uint8_t bEnableTimeStamp); + +// hook into debug output +DIRTYCODE_API void NetPrintfHook(int32_t (*pPrintfDebugHook)(void *pParm, const char *pText), void *pParm); + +// diagnostic output routine (do not call directly, use NetPrintf() wrapper +DIRTYCODE_API int32_t NetPrintfCode(const char *fmt, ...); + +// diagnostic output routine (do not call directly, use NetPrintf() wrapper +DIRTYCODE_API void NetPrintfVerboseCode(int32_t iVerbosityLevel, int32_t iCheckLevel, const char *pFormat, ...); + +// print input buffer with wrapping (do not call directly; use NetPrintWrap() wrapper) +DIRTYCODE_API void NetPrintWrapCode(const char *pData, int32_t iWrapCol); + +// print memory as hex (do not call directly; use NetPrintMem() wrapper) +DIRTYCODE_API void NetPrintMemCode(const void *pMem, int32_t iSize, const char *pTitle); + +// print memory as a c-style array (do not call directly; use NetPrintArray() wrapper) +DIRTYCODE_API void NetPrintArrayCode(const void *pMem, int32_t iSize, const char *pTitle); + +/* + Platform-specific routines implemented in dirtynet.c +*/ + +// initialize the network library functions. +DIRTYCODE_API void NetLibCreate(int32_t iThreadPrio, int32_t iThreadStackSize, int32_t iThreadCpuAffinity); + +// shutdown the network library. +DIRTYCODE_API void NetLibDestroy(uint32_t uShutdownFlags); + +// return an increasing tick count with millisecond scale +DIRTYCODE_API uint32_t NetTick(void); + +// return microsecond timer, intended for debug timing purposes only +DIRTYCODE_API uint64_t NetTickUsec(void); + + +/* +The NetTickDiff() macro implies 2 steps. + +The first step consists in substracting 2 unsigned values. When working with unsigned +types, modular arithmetic (aka "wrap around" behavior) is taking place. It is similar +to reading a clock. + Adding clockwise: 9 + 4 = 1 (13 mod 12) + Substracting counterclockwise: 1 - 4 = 9 (-3 mod 12) +Obviously the value range here is [0,0xFFFFFFFF] and not [0,11]. +By the virtue of modular arithmetic, the difference between _uNewTime and _uOldTime is +always valid, even in scenarios where one (or both) of the two values has just +"wrapped around". + +The second step consists in casting the unsigned result of step 1 into a signed +integer. The result of that second step is the final outcome of the macro, i.e. a +value ranging between + -2147483648 (2's complement notation: 0x80000000) and + 2147483647 (2's complement notation: 0x7FFFFFFF) + +Consequently, the maximum time difference (positive or negative) that can be calculated +between _uNewTime and _uOldTime is 2147483647 ms, i.e. approximately 596,8 hours (24,9 days). + +Any longer period of time captured with an initial call to NetTick() and a final +call to NetTick() and then calculated by feeding both values to NetTickDiff() would +incorrectly result in a time difference much shorter than reality. + +If _uNewTime is more recent than _uOldTime (by not more than 596,8 hours), then +the returned time difference will be positive. + +If _uOldTime is more recent than _uNewTime (by not more than 596,8 hours), then +the returned time difference will be negative. +*/ + +// return signed difference between new tick count and old tick count (new - old) +#define NetTickDiff(_uNewTime, _uOldTime) ((signed)((_uNewTime) - (_uOldTime))) + +// return localtime +DIRTYCODE_API struct tm *NetLocalTime(struct tm *pTm, uint64_t uElap); + +// convert a platform-specific time format to generic time format +DIRTYCODE_API struct tm *NetPlattimeToTime(struct tm *pTm, void *pPlatTime); + +// convert a platform-specific time format to generic time format, with milliseconds +DIRTYCODE_API struct tm *NetPlattimeToTimeMs(struct tm *pTm, int32_t *pImSec); + +// initialize a critical section for use -- includes name for verbose debugging on some platforms +DIRTYCODE_API int32_t NetCritInit(NetCritT *pCrit, const char *pCritName); + +// initialize a critical section with the ability to set options (NETCRIT_OPTION_*) +DIRTYCODE_API int32_t NetCritInit2(NetCritT *pCrit, const char *pCritName, uint32_t uFlags); + +// release resources and destroy critical section +DIRTYCODE_API void NetCritKill(NetCritT *pCrit); + +// attempt to gain access to critical section +DIRTYCODE_API int32_t NetCritTry(NetCritT *pCrit); + +// enter a critical section, blocking if needed +DIRTYCODE_API void NetCritEnter(NetCritT *pCrit); + +// leave a critical section +DIRTYCODE_API void NetCritLeave(NetCritT *pCrit); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtylib_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtymem.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtymem.h new file mode 100644 index 00000000..9fa4ebc5 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtymem.h @@ -0,0 +1,180 @@ +/*H********************************************************************************/ +/*! + \File dirtymem.h + + \Description + DirtySock memory allocation routines. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 10/12/2005 (jbrookes) First Version + \Version 11/19/2008 (mclouatre) Adding pMemGroupUserData to mem groups +*/ +/********************************************************************************H*/ + +#ifndef _dirtymem_h +#define _dirtymem_h + +/*! +\Moduledef DirtyMem DirtyMem +\Modulemember DirtySock +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +/*! + All DirtySock modules have their memory identifiers defined here. +*/ +// comm modules +#define COMMSRP_MEMID ('csrp') +#define COMMUDP_MEMID ('cudp') + +// crypt modules +#define CRYPTRSA_MEMID ('crsa') +#define CRYPTRAND_MEMID ('rand') + +// dirtysock modules +#define DIRTYAUTH_MEMID ('dath') +#define DIRTYCERT_MEMID ('dcrt') +#define DIRTYCM_MEMID ('dhcm') +#define DIRTYSESSMGR_MEMID ('dsmg') +#define DIRTYWEBAPI_MEMID ('weba') +#define DIRTYEVENT_DISP_MEMID ('semd') +#define SOCKET_MEMID ('dsoc') +#define NETCONN_MEMID ('ncon') +#define DIRTYTHREAD_MEMID ('dthr') + +// game modules +#define CONNAPI_MEMID ('conn') +#define NETGAMEDIST_MEMID ('ngdt') +#define NETGAMEDISTSERV_MEMID ('ngds') +#define NETGAMELINK_MEMID ('nglk') +#define NETGAMEUTIL_MEMID ('ngut') + +// graph modules +#define DIRTYGRAPH_MEMID ('dgph') +#define DIRTYJPG_MEMID ('djpg') +#define DIRTYPNG_MEMID ('dpng') + +// misc modules +#define LOBBYLAN_MEMID ('llan') +#define USERAPI_MEMID ('uapi') +#define USERLISTAPI_MEMID ('ulst') +#define WEBLOG_MEMID ('wlog') +#define PRIVILEGEAPI_MEMID ('priv') + +// proto modules +#define PROTOADVT_MEMID ('padv') +#define PROTOHTTP_MEMID ('phtp') +#define HTTPSERV_MEMID ('hsrv') +#define HTTPMGR_MEMID ('hmgr') +#define PROTOMANGLE_MEMID ('pmgl') +#define PROTOPING_MEMID ('ppng') +#define PINGMGR_MEMID ('lpmg') +#define PROTOSSL_MEMID ('pssl') +#define PROTOSTREAM_MEMID ('pstr') +#define PROTOTUNNEL_MEMID ('ptun') +#define PROTOUDP_MEMID ('pudp') +#define PROTOUPNP_MEMID ('pupp') +#define PROTOWEBSOCKET_MEMID ('webs') + +// util modules +#define DISPLIST_MEMID ('ldsp') +#define HASHER_MEMID ('lhsh') +#define SORT_MEMID ('lsor') +#define HPACK_MEMID ('hpak') +#define PROTOBUF_MEMID ('pbuf') + +// qos module +#define QOSAPI_MEMID ('dqos') +#define QOS_CLIENT_MEMID ('qosc') +#define QOS_COMMON_MEMID ('qcom') + +// voip module +#define VOIP_MEMID ('voip') +#define VOIPNARRATE_MEMID ('vnar') +#define VOIPTRANSCRIBE_MEMID ('vscr') +#define VOIP_PLATFORM_MEMID ('vplt') // +#include +#include +#include + +#elif !defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_PC) && !defined(DIRTYCODE_GDK) + +#include +#include /* struct sockaddr_in */ +#include /* struct in_addr */ + +#if defined(DIRTYCODE_PS4) +#define IPPROTO_IPV4 4 +#ifdef INADDR_BROADCAST + #undef INADDR_BROADCAST +#endif +#ifdef INADDR_ANY + #undef INADDR_ANY + #define INADDR_ANY 0x00000000 +#endif +#ifdef INADDR_LOOPBACK + #undef INADDR_LOOPBACK + #define INADDR_LOOPBACK 0x7f000001 +#endif + +#endif // defined(DIRTYCODE_PS4) + +#else // DIRTYCODE_PC or DIRTYCODE_XBOXONE + +#if !defined(_WINSOCKAPI_) || defined(EA_FORCE_WINSOCK2_INCLUDE) // if was included, skip, unless we want to force include it +#include +#include +#endif // _WINSOCKAPI_ + +#endif // including system socket/inet headers + + +/* + IPv6 definitions for common code for platforms that don't have them, + until if/when they are available natively +*/ + +#if defined(DIRTYCODE_PS4) +#if !defined(s6_addr) // not sure what ipv6 definitions will look like but this is a likely definition we can check for +struct in6_addr { + uint8_t s6_addr[16]; /* IPv6 address */ +}; + +struct sockaddr_in6 { + uint16_t sin6_family; /* AF_INET6 */ + uint16_t sin6_port; /* port number */ + uint32_t sin6_flowinfo; /* IPv6 flow information */ + struct in6_addr sin6_addr; /* IPv6 address */ + uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ +}; +#endif // !defined(s6_addr) +#endif // DIRTYCODE_PS4 + + +/* + DirtyNet specific defines +*/ + +#define SOCK_NORECV 1 //!< caller does not want to receive more data +#define SOCK_NOSEND 2 //!< caller does not want to send more data + +#if !defined (DIRTYCODE_NX) +#ifndef INADDR_BROADCAST +#define INADDR_BROADCAST 0xffffffff +#endif +#endif + +#define CALLB_NONE 0 //!< no callback +#define CALLB_SEND 1 //!< call when we can send +#define CALLB_RECV 2 //!< call when we can receive + +#define SOCKLOOK_LOCALADDR "*" + +#define SOCKLOOK_FLAGS_ALLOWXDNS (1) + +//! maximum udp packet size we can receive (constrained by Xbox 360 max VDP packet size) +#define SOCKET_MAXUDPRECV (1264) + +//! maximum number of virtual ports that can be specified +#define SOCKET_MAXVIRTUALPORTS (32) + +// Errors + +#define SOCKERR_NONE 0 //!< no error +#define SOCKERR_CLOSED -1 //!< the socket is closed +#define SOCKERR_NOTCONN -2 //!< the socket is not connected +#define SOCKERR_BLOCKED -3 //!< operation would result in blocking +#define SOCKERR_ADDRESS -4 //!< the address is invalid +#define SOCKERR_UNREACH -5 //!< network cannot be accessed by this host +#define SOCKERR_REFUSED -6 //!< connection refused by the recipient +#define SOCKERR_OTHER -7 //!< unclassified error +#define SOCKERR_NOMEM -8 //!< out of memory +#define SOCKERR_NORSRC -9 //!< out of resources +#define SOCKERR_UNSUPPORT -10 //!< unsupported operation +#define SOCKERR_INVALID -11 //!< resource or operation is invalid +#define SOCKERR_ADDRINUSE -12 //!< address already in use +#define SOCKERR_CONNRESET -13 //!< connection has been reset +#define SOCKERR_BADPIPE -14 //!< EBADF or EPIPE + +//! error occured when trying to map address using SocketAddrMapAddress (+ip6)/SocketAddrRemapAddress (~ip6) +#define SOCKMAP_ERROR (-1) + +//! maximum number of send callbacks supported (internal use only - needs to be public for DirtyCast stress tester) +#define SOCKET_MAXSENDCALLBACKS (8) + + +/*** Macros ****************************************************************************/ + +/* +Macros used to write into a sockaddr structure take into account the fact that the sa_data field is defined +as an array of signed char on Windows and an array of unsigned char on other platforms. Doing this is required +to avoid C4365 warning occurring when these macros are used in .cpp files. (The same warning would not occur +when using these macros in .c files. The windows compiler (cl.exe) only warns about C4365 with .cpp files. +*/ +#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_PC) +#define SA_DATA_TYPE char +#else +#define SA_DATA_TYPE unsigned char +#endif + +//! init a sockaddr to zero and set its family type +#define SockaddrInit(addr,fam) { (addr)->sa_family = (fam); ds_memclr((addr)->sa_data, sizeof((addr)->sa_data)); } + +//! get the port in host format from sockaddr +#define SockaddrInGetPort(addr) ((((unsigned char)(addr)->sa_data[0])<<8)|(((unsigned char)(addr)->sa_data[1])<<0)) + +//! set the port in host format in a sockaddr +#define SockaddrInSetPort(addr,val) { (addr)->sa_data[0] = (SA_DATA_TYPE)(((val)>>8)&0xff); (addr)->sa_data[1] = (SA_DATA_TYPE)((val)&0xff); } + +//! get the address in host format from sockaddr +#define SockaddrInGetAddr(addr) (((((((unsigned char)((addr)->sa_data[2])<<8)|(unsigned char)((addr)->sa_data[3]))<<8)|(unsigned char)((addr)->sa_data[4]))<<8)|(unsigned char)((addr)->sa_data[5])) + +//! set the address in host format in a sockaddr +#define SockaddrInSetAddr(addr,val) { uint32_t val2 = (val); (addr)->sa_data[5] = (SA_DATA_TYPE)val2; val2 >>= 8; (addr)->sa_data[4] = (SA_DATA_TYPE)val2; val2 >>= 8; (addr)->sa_data[3] = (SA_DATA_TYPE)val2; val2 >>= 8; (addr)->sa_data[2] = (SA_DATA_TYPE)val2; } + +//! get the misc field in host format from sockaddr +#define SockaddrInGetMisc(addr) (((((((unsigned char)((addr)->sa_data[6])<<8)|(unsigned char)((addr)->sa_data[7]))<<8)|(unsigned char)((addr)->sa_data[8]))<<8)|(unsigned char)((addr)->sa_data[9])) + +//! set the misc field in host format in a sockaddr +#define SockaddrInSetMisc(addr,val) { uint32_t val2 = (val); (addr)->sa_data[9] = (SA_DATA_TYPE)val2; val2 >>= 8; (addr)->sa_data[8] = (SA_DATA_TYPE)val2; val2 >>= 8; (addr)->sa_data[7] = (SA_DATA_TYPE)val2; val2 >>= 8; (addr)->sa_data[6] = (SA_DATA_TYPE)val2; } + +//! detect loopback address (family independent) +#define SockaddrIsLoopback(addr) (( ((addr)->sa_family == AF_INET) && ((addr)->sa_data[0] == 127) && ((addr)->sa_data[1] == 0) && ((addr)->sa_data[2] == 0) && ((addr)->sa_data[3] == 1) )) + +/* + sockaddr_v6 helpers +*/ + +//! init a sockaddr6 to zero and set its family type +#define SockaddrInit6(addr,fam) { ds_memclr(addr, sizeof(*(addr))); (addr)->sin6_family = (fam); } + +//! get IPv6 address from IPv4-mapped IPv6 address, also works for NAT64 +#define SockaddrIn6GetAddr4(addr) (((((((uint8_t)((addr)->sin6_addr.s6_addr[12])<<8)|(uint8_t)((addr)->sin6_addr.s6_addr[13]))<<8)|(uint8_t)((addr)->sin6_addr.s6_addr[14]))<<8)|(uint8_t)((addr)->sin6_addr.s6_addr[15])) + +/*** Type Definitions ******************************************************************/ + + +// basic socket type is a pointer +typedef struct SocketT SocketT; + +//! a host lookup structure -- uses a callback +//! system to determine when lookup has finished +typedef struct HostentT +{ + int32_t done; //!< public: indicates when lookup is complete + uint32_t addr; //!< public: resolved host address + + int32_t (*Done)(struct HostentT *); //!< public: callback to indicate completion status + void (*Free)(struct HostentT *); //!< public: callback to release Hostent structure + + char name[256]; //!< private (the maximum DNS name length is 253 characters) + int32_t sema; //!< private + int32_t thread; //!< private + void* pData; //!< private + uint32_t timeout; //!< private + struct HostentT *pNext; //!< private + int32_t refcount; //!< private +} HostentT; + +//! global socket send callback +typedef int32_t (SocketSendCallbackT)(SocketT *pSocket, int32_t iType, const uint8_t *pData, int32_t iDataSize, const struct sockaddr *pTo, void *pCallref); + +//! socket send callback entry (internal use only - needs to be public for DirtyCast stress tester) +typedef struct SocketSendCallbackEntryT +{ + SocketSendCallbackT *pSendCallback; //!< global send callback + void *pSendCallref; //!< user callback data +} SocketSendCallbackEntryT; + + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + platform independent functions, implemented in dirtynet.c +*/ + +// compare two sockaddr structures +DIRTYCODE_API int32_t SockaddrCompare(const struct sockaddr *pAddr1, const struct sockaddr *pAddr2); + +// set the address in text format in a sockaddr +DIRTYCODE_API int32_t SockaddrInSetAddrText(struct sockaddr *pAddr, const char *pStr); + +// get the address in text format from sockaddr +DIRTYCODE_API char *SockaddrInGetAddrText(const struct sockaddr *pAddr, char *pStr, int32_t iLen); + +// parse address:port combination +DIRTYCODE_API int32_t SockaddrInParse(struct sockaddr *pAddr, const char *pParse); + +// parse address:port:port combination into separate components +DIRTYCODE_API int32_t SockaddrInParse2(uint32_t *pAddr, int32_t *pPort, int32_t *pPort2, const char *pParse); + +// convert 32-bit internet address into textual form +DIRTYCODE_API char *SocketInAddrGetText(uint32_t uAddr, char *pStr, int32_t iLen); + +// convert textual internet address into 32-bit integer form +DIRTYCODE_API int32_t SocketInTextGetAddr(const char *pAddrText); + +// convert int16_t from host to network byte order +DIRTYCODE_API uint16_t SocketHtons(uint16_t uAddr); + +// convert int32_t from host to network byte order +DIRTYCODE_API uint32_t SocketHtonl(uint32_t uAddr); + +// convert int16_t from network to host byte order +DIRTYCODE_API uint16_t SocketNtohs(uint16_t uAddr); + +// convert int32_t from network to host byte order. +DIRTYCODE_API uint32_t SocketNtohl(uint32_t uAddr); + +/* + platform dependent functions, implemented in dirtynet.c +*/ + +// create new instance of socket interface module +DIRTYCODE_API int32_t SocketCreate(int32_t iThreadPrio, int32_t iThreadStackSize, int32_t iThreadCpuAffinity); + +// release resources and destroy module. +DIRTYCODE_API int32_t SocketDestroy(uint32_t uShutdownFlags); + +// create a new transfer endpoint +DIRTYCODE_API SocketT *SocketOpen(int32_t af, int32_t type, int32_t protocol); + +// perform partial/complete shutdown of socket +DIRTYCODE_API int32_t SocketShutdown(SocketT *pSocket, int32_t how); + +// close a socket +DIRTYCODE_API int32_t SocketClose(SocketT *pSocket); + +// import a socket - may be SocketT pointer or sony socket ref +DIRTYCODE_API SocketT *SocketImport(intptr_t uSockRef); + +// release an imported socket +DIRTYCODE_API void SocketRelease(SocketT *pSocket); + +// bind a local address/port to a socket +DIRTYCODE_API int32_t SocketBind(SocketT *pSocket, const struct sockaddr *name, int32_t namelen); + +// return information about an existing socket. +DIRTYCODE_API int32_t SocketInfo(SocketT *pSocket, int32_t iInfo, int32_t iData, void *pBuf, int32_t iLen); + +// send a control message to the dirtysock layer +DIRTYCODE_API int32_t SocketControl(SocketT *pSocket, int32_t option, int32_t data1, void *data2, void *data3); + +// start listening for an incoming connection on the socket +DIRTYCODE_API int32_t SocketListen(SocketT *pSocket, int32_t backlog); + +// attempt to accept an incoming connection from a +DIRTYCODE_API SocketT *SocketAccept(SocketT *pSocket, struct sockaddr *addr, int32_t *addrlen); + +// initiate a connection attempt to a remote host +DIRTYCODE_API int32_t SocketConnect(SocketT *pSocket, struct sockaddr *name, int32_t namelen); + +// send data to a remote host +DIRTYCODE_API int32_t SocketSendto(SocketT *pSocket, const char *buf, int32_t len, int32_t flags, const struct sockaddr *to, int32_t tolen); + +// same as SocketSendto() with "to" set to NULL +#define SocketSend(_pSocket, _pBuf, iLen, iFlags) SocketSendto(_pSocket, _pBuf, iLen, iFlags, NULL, 0) + +// receive data from a remote host +DIRTYCODE_API int32_t SocketRecvfrom(SocketT *pSocket, char *buf, int32_t len, int32_t flags, struct sockaddr *from, int32_t *fromlen); + +// same as SocketRecvfrom() with "from" set to NULL. +#define SocketRecv(_pSocket, pBuf, iLen, iFlags) SocketRecvfrom(_pSocket, pBuf, iLen, iFlags, NULL, 0) + +// register a callback routine for notification of socket events +DIRTYCODE_API int32_t SocketCallback(SocketT *pSocket, int32_t flags, int32_t timeout, void *ref, int32_t (*proc)(SocketT *pSocket, int32_t flags, void *ref)); + +// return the host address that would be used in order to communicate with the given destination address. +DIRTYCODE_API int32_t SocketHost(struct sockaddr *host, int32_t hostlen, const struct sockaddr *dest, int32_t destlen); + +// lookup a host by name and return the corresponding Internet address +DIRTYCODE_API HostentT *SocketLookup(const char *text, int32_t timeout); + +// lookup a host by name and return the corresponding Internet address, with flags +#define SocketLookup2(_text, _timeout, _flags) SocketLookup(_text, _timeout) + +// invoke all registered send callbacks (internal use only - needs to be public for DirtyCast stress tester) +int32_t SocketSendCallbackInvoke(SocketSendCallbackEntryT aCbEntries[], SocketT *pSocket, int32_t iType, const char *pBuf, int32_t iLen, const struct sockaddr *pTo); + + +// return "external" local address +DIRTYCODE_API uint32_t SocketGetLocalAddr(void); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtynet_h + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtysessionmanager.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtysessionmanager.h new file mode 100644 index 00000000..8004fc73 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtysessionmanager.h @@ -0,0 +1,120 @@ +/*H*************************************************************************************/ +/*! + \File dirtysessionmanager.h + + \Description + DirtySessionManager handles the creation, joinning and leaving of + a session. Offers mechanism to encode and decode the session, and does some + session flags management + + \Notes + + \Copyright + Copyright (c) Electronic Arts 2003-2007 + + \Version 1.0 11/03/2003 (jbrookes) First Version + \Version 2.0 11/04/2007 (jbrookes) Removed from ProtoMangle namespace, cleanup + \Version 2.2 10/26/2009 (mclouatre) Renamed from core/include/dirtysessionmanager.h to core/include/xenon/dirtysessionmanagerxenon.h + \Version 2.3 03/26/2013 (cvienneau) Renamed from core/include/xenon/dirtysessionmanagerxenon.h to core/include/dirtysessionmanager.h +*/ +/*************************************************************************************H*/ + +#ifndef _dirtysessionmanager_h +#define _dirtysessionmanager_h + +/*! +\Moduledef DirtySessionManager DirtySessionManager +\Modulemember DirtySock +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtyaddr.h" + +/*** Defines ***************************************************************************/ + +#define DIRTYSESSIONMANAGER_FLAG_PUBLICSLOT (1) +#define DIRTYSESSIONMANAGER_FLAG_PRIVATESLOT (2) + +#if defined(DIRTYCODE_PS4) +#define DIRTY_SESSION_GAME_MODE_LENGTH 257 +#endif + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct DirtySessionManagerRefT DirtySessionManagerRefT; +#if DIRTYCODE_DEBUG +//! true skill values struct +typedef struct DirtySessionManagerTrueSkillRefT //!< true skill struct used for debugging +{ + double dMu; + double dSigma; +} DirtySessionManagerTrueSkillRefT; +#endif + +#if defined(DIRTYCODE_PS4) +//!< data that we control within the binary blob portion of the session +typedef struct DirtySessionManagerBinaryHeaderT +{ + int64_t iLobbyId; + int64_t iGameType; +} DirtySessionManagerBinaryHeaderT; + +//!< data that we control within the changeable binary blob portion of the session +typedef struct DirtySessionManagerChangeableBinaryHeaderT +{ + char strGameMode[DIRTY_SESSION_GAME_MODE_LENGTH]; +} DirtySessionManagerChangeableBinaryHeaderT; +#endif + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// allocate module state and prepare for use +DirtySessionManagerRefT *DirtySessionManagerCreate(void); + +// destroy the module and release its state +void DirtySessionManagerDestroy(DirtySessionManagerRefT *pRef); + +// give time to module to do its thing (should be called periodically to allow module to perform work) +void DirtySessionManagerUpdate(DirtySessionManagerRefT *pRef); + +// join one (or many) remote player(s) by specifying the session and type of slot to use +void DirtySessionManagerConnect(DirtySessionManagerRefT *pRef, const char **pSessID, uint32_t *pSlot, uint32_t uCount); + +// dirtysessionmanager control +int32_t DirtySessionManagerControl(DirtySessionManagerRefT *pRef, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue); + +// get module status based on selector +int32_t DirtySessionManagerStatus(DirtySessionManagerRefT *pRef, int32_t iSelect, void *pBuf, int32_t iBufSize); + +// get module status based on selector +int32_t DirtySessionManagerStatus2(DirtySessionManagerRefT *pRef, int32_t iSelect, int32_t iValue, int32_t iValue2, int32_t iValue3, void *pBuf, int32_t iBufSize); + +// encode +void DirtySessionManagerEncodeSession(char *pBuffer, int32_t iBufSize, const void *pSessionInfo); + +// decode +void DirtySessionManagerDecodeSession(void *pSessionInfo, const char *pBuffer); + +// create the session (previously the 'sess' control selector) +int32_t DirtySessionManagerCreateSess(DirtySessionManagerRefT *pRef, uint32_t bRanked, uint32_t *uUserFlags, const char *pSession, DirtyAddrT *pLocalAddrs); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtysessionmanager_h + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtythread.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtythread.h new file mode 100644 index 00000000..2eb96a3a --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtythread.h @@ -0,0 +1,78 @@ +/*H**************************************************************************************/ +/*! + \File dirtythread.h + + \Description + Provide threading library functions for use by network layer code. + + \Copyright + Copyright (c) Electronic Arts 2017 + + \Version 09/27/17 (eesponda) +*/ +/**************************************************************************************H*/ + +#ifndef _dirtythread_h +#define _dirtythread_h + +/*! +\Moduledef DirtyThread DirtyThread +\Modulemember DirtySock +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Type Definitions ******************************************************************/ + +// configuration for the thread +typedef struct DirtyThreadConfigT +{ + int32_t iPriority; //!< priority of the thread, platform dependent + int32_t iAffinity; //!< affinity mask + const char *pName; //!< name of the thread + int32_t iVerbosity; //!< verbosity of logging information (deprecated) +} DirtyThreadConfigT; + +// function that gets run on the thread +typedef void (DirtyRunnableFunctionT)(void* pUserData); + +// opaque module ref +typedef struct DirtyConditionRefT DirtyConditionRefT; + +// forward declaration +typedef struct NetCritT NetCritT; + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// start the thread +DIRTYCODE_API int32_t DirtyThreadCreate(DirtyRunnableFunctionT *pFunction, void *pUserData, const DirtyThreadConfigT *pConfig); + +// get thread id +DIRTYCODE_API const char *DirtyThreadGetThreadId(char *pBuffer, int32_t iBufSize); + +// create a condition variable with name +DIRTYCODE_API DirtyConditionRefT *DirtyConditionCreate(const char *pName); + +// destroy the condition +DIRTYCODE_API void DirtyConditionDestroy(DirtyConditionRefT *pCondition); + +// wait for a condition +DIRTYCODE_API void DirtyConditionWait(DirtyConditionRefT *pCondition, NetCritT *pCrit); + +// signal the condition +DIRTYCODE_API uint8_t DirtyConditionSignal(DirtyConditionRefT *pCondition); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtythread_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtyuser.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtyuser.h new file mode 100644 index 00000000..5c92d799 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/dirtyuser.h @@ -0,0 +1,66 @@ +/*H********************************************************************************/ +/*! + \File dirtyuser.h + + \Description + Definition for portable user type. + + \Copyright + Copyright (c) Electronic Arts 2013 + + \Version 04/25/13 (mclouatre) First Version +*/ +/********************************************************************************H*/ + +#ifndef _dirtyuser_h +#define _dirtyuser_h + +/*! +\Moduledef DirtyUser DirtyUser +\Modulemember DirtySock +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +#define DIRTYUSER_NATIVEUSER_MAXLEN (64) +#define DIRTYUSER_NATIVEUSER_MAXSIZE (DIRTYUSER_NATIVEUSER_MAXLEN + 1) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! opaque user type +typedef struct DirtyUserT +{ + char strNativeUser[DIRTYUSER_NATIVEUSER_MAXSIZE]; +} DirtyUserT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +//! convert a DirtyUserT to native format +DIRTYCODE_API uint32_t DirtyUserToNativeUser(void *pOutput, int32_t iBufLen, const DirtyUserT *pUser); + +//! convert a native format to a DirtyUserT +DIRTYCODE_API uint32_t DirtyUserFromNativeUser(DirtyUserT *pUser, const void *pInput); + +//! compare two opaque users for equality (same=TRUE, different=FALSE) +DIRTYCODE_API int32_t DirtyUserCompare(DirtyUserT *pUser1, DirtyUserT *pUser2); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtyuser_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/netconn.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/netconn.h new file mode 100644 index 00000000..43ccc167 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/netconn.h @@ -0,0 +1,160 @@ +/*H*************************************************************************************/ +/*! + \File netconn.h + + \Description + Provides network setup and teardown support. Does not actually create any + kind of network connections. + + \Copyright + Copyright (c) Electronic Arts 2001-2009 + + \Version 03/12/2001 (gschaefer) First Version +*/ +/*************************************************************************************H*/ + +#ifndef _netconn_h +#define _netconn_h + +/*! +\Moduledef NetConnDefs NetConnDefs +\Modulemember DirtySock +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/netconndefs.h" + +/*** Defines ***************************************************************************/ + +//! maximum number of local users +#if defined(DIRTYCODE_XBOXONE) + #define NETCONN_MAXLOCALUSERS (16) +#elif defined(DIRTYCODE_NX) + #define NETCONN_MAXLOCALUSERS (8) +#else + #define NETCONN_MAXLOCALUSERS (4) +#endif + + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct NetConnUserDataT +{ + char strName[16]; + void *pRawData1; + void *pRawData2; +} NetConnUserDataT; + +//! account structure +typedef struct NetConnAccountInfoT +{ + int64_t iAccountId; //!< the EA account Id of the user + int64_t iPersonaId; //!< the EA persona Id of the user +} NetConnAccountInfoT; + +#if defined(DIRTYCODE_PS4) +typedef void (NetConnNpStateCallbackT)(int32_t /* SceUserServiceUserId */ userId, int32_t /* SceNpState */ state, void* pUserData); +#endif + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// bring the network connection module to life +DIRTYCODE_API int32_t NetConnStartup(const char *pParams); + +// query the list of available connection configurations +DIRTYCODE_API int32_t NetConnQuery(const char *pDevice, NetConfigRecT *pList, int32_t iSize); + +// bring the networking online with a specific configuration +DIRTYCODE_API int32_t NetConnConnect(const NetConfigRecT *pConfig, const char *pParms, int32_t iData); + +// set module behavior based on input selector +DIRTYCODE_API int32_t NetConnControl(int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue, void *pValue2); + +// check general network connection status (added param) +DIRTYCODE_API int32_t NetConnStatus(int32_t iKind, int32_t iData, void *pBuf, int32_t iBufSize); + +// return MAC address in textual form +DIRTYCODE_API const char *NetConnMAC(void); + +// take down the network connection +DIRTYCODE_API int32_t NetConnDisconnect(void); + +// shutdown the network code and return to idle state +DIRTYCODE_API int32_t NetConnShutdown(uint32_t uShutdownFlags); + +// return elapsed time in milliseconds +DIRTYCODE_API uint32_t NetConnElapsed(void); + +// sleep the application (burn cycles) for some number of milliseconds +DIRTYCODE_API void NetConnSleep(int32_t iMilliSecs); + +// add an idle handler that will get called periodically +DIRTYCODE_API int32_t NetConnIdleAdd(void (*proc)(void *data, uint32_t tick), void *data); + +// remove a previously added idle handler +DIRTYCODE_API int32_t NetConnIdleDel(void (*proc)(void *data, uint32_t tick), void *data); + +// provide "life" to the network code +DIRTYCODE_API void NetConnIdle(void); + +// shut down netconn idle handler +// NOTE: this is meant for internal use only, and should not be called by applications directly +DIRTYCODE_API void NetConnIdleShutdown(void); + +// Enable or disable the timing of netconnidles +DIRTYCODE_API void NetConnTiming(uint8_t uEnableTiming); + +typedef void (UserInfoCallbackT)(NetConnUserDataT *pUserDataT, void *pData); + +// get the unique machine id +DIRTYCODE_API uint32_t NetConnMachineId(void); + +// set the unique machine id +DIRTYCODE_API void NetConnSetMachineId(uint32_t uMachineId); + +#if DIRTYCODE_LOGGING +DIRTYCODE_API void NetConnMonitorValue(const char* pName, int32_t iValue); +#endif + +// copy a startup parameter +DIRTYCODE_API int32_t NetConnCopyParam(char *pDst, int32_t iDstLen, const char *pParamName, const char *pSrc, const char *pDefault); + +// create dirtycert module +DIRTYCODE_API int32_t NetConnDirtyCertCreate(const char *pParams); + +// translate netconn environment to string +DIRTYCODE_API const char *NetConnGetEnvStr(void); + +#if defined(DIRTYCODE_PS4) +DIRTYCODE_API void NetConnRegisterNpStateCallback(NetConnNpStateCallbackT *pCallback, void *pUserData); +#endif + +#ifdef __cplusplus +} + +// forward declare IEAUser so as not to create a dependency +namespace EA { namespace User { class IEAUser; } } + +// use this function to tell netconn about a newly detected local user on the local console +DIRTYCODE_API int32_t NetConnAddLocalUser(int32_t iLocalUserIndex, const EA::User::IEAUser *pLocalUser); + +// use this function to tell netconn about a removed local user on the local console +// in cases where the index is unknown, pass -1 and we will do an internal query +DIRTYCODE_API int32_t NetConnRemoveLocalUser(int32_t iLocalUserIndex, const EA::User::IEAUser *pLocalUser); +#endif // __cplusplus + +//@} + +#endif // _netconn_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/netconndefs.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/netconndefs.h new file mode 100644 index 00000000..6c7780ad --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtysock/netconndefs.h @@ -0,0 +1,75 @@ +/*H********************************************************************************/ +/*! + \File netconndefs.h + + \Description + Definitions for the netconn module. + + \Copyright + Copyright (c) 2005-2009 Electronic Arts Inc. + + \Version 09/29/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _netconndefs_h +#define _netconndefs_h + +/*! +\Moduledef NetConnDefs NetConnDefs +\Modulemember DirtySock +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +// interface types, returned by NetConnStatus('type') +#define NETCONN_IFTYPE_NONE (1) //!< indeterminate interface type +#define NETCONN_IFTYPE_MODEM (2) //!< interface is a modem +#define NETCONN_IFTYPE_ETHER (4) //!< interface is ethernet +#define NETCONN_IFTYPE_USB (8) //!< interface bus type is USB +#define NETCONN_IFTYPE_PPPOE (16) //!< interface is PPPoE +#define NETCONN_IFTYPE_WIRELESS (32) //!< interface is wireless (wifi) +#define NETCONN_IFTYPE_CELL (64) //!< interface is cellular + +// EA back-end environment types, returned by NetConnStatus('envi') +#define NETCONN_PLATENV_DEV (8) //!< Dev environment - Note (0) is used by the 'envi' NetConnStatus selector to indicate ~inp/try again +#define NETCONN_PLATENV_TEST (1) //!< Test environment +#define NETCONN_PLATENV_CERT (2) //!< Certification environment +#define NETCONN_PLATENV_PROD (4) //!< Production environment + +// generic netconn error responses +#define NETCONN_ERROR_ISACTIVE (-1) //!< the module is currently active +#define NETCONN_ERROR_NOTACTIVE (-2) //!< the module isn't currently active + + +// generic NetConnStartup errors +#define NETCONN_ERROR_NO_MEMORY (-2) +#define NETCONN_ERROR_SOCKET_CREATE (-3) +#define NETCONN_ERROR_DIRTYCERT_CREATE (-4) +#define NETCONN_ERROR_PROTOSSL_CREATE (-5) +#define NETCONN_ERROR_PROTOUPNP_CREATE (-6) +#define NETCONN_ERROR_INTERNAL (-7) +#define NETCONN_ERROR_PLATFORM_SPECIFIC (-8) +#define NETCONN_ERROR_ALREADY_STARTED (-9) +#define NETCONN_ERROR_RETRY (-10) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! network configuration entry +typedef void * NetConfigRecT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +//@} + +#endif // _netconndefs_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/dirtyvers.h b/src/thirdparty/dirtysdk/include/DirtySDK/dirtyvers.h new file mode 100644 index 00000000..3f1fc5dc --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/dirtyvers.h @@ -0,0 +1,52 @@ +/*H********************************************************************************/ +/*! + \File dirtyvers.h + + \Description + DirtySock SDK version. + + \Copyright + Copyright (c) 2003-2005 Electronic Arts + + \Version 06/13/2003 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _dirtyvers_h +#define _dirtyvers_h + +/*! +\Moduledef DirtyVers DirtyVers +\Modulemember DirtySock +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + + +/*** Defines **********************************************************************/ + +#define DIRTYSDK_VERSION_MAKE(year, season, major, minor, patch) (((year) * 100000000) + ((season) * 1000000) + ((major) * 10000) + ((minor) * 100) + (patch)) + +#define DIRTYSDK_VERSION_YEAR (15) +#define DIRTYSDK_VERSION_SEASON (1) +#define DIRTYSDK_VERSION_MAJOR (6) +#define DIRTYSDK_VERSION_MINOR (0) +#define DIRTYSDK_VERSION_PATCH (5) +#define DIRTYSDK_VERSION (DIRTYSDK_VERSION_MAKE(DIRTYSDK_VERSION_YEAR, DIRTYSDK_VERSION_SEASON, DIRTYSDK_VERSION_MAJOR, DIRTYSDK_VERSION_MINOR, DIRTYSDK_VERSION_PATCH)) +#define DIRTYVERS (DIRTYSDK_VERSION) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +//@} + +#endif // _dirtyvers_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/game/connapi.h b/src/thirdparty/dirtysdk/include/DirtySDK/game/connapi.h new file mode 100644 index 00000000..e943a171 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/game/connapi.h @@ -0,0 +1,266 @@ +/*H********************************************************************************/ +/*! + \File connapi.h + + \Description + ConnApi is a high-level connection manager, that packages the "connect to + peer" process into a single module. Both game connections and voice + connections can be managed. Multiple peers are supported in a host/client + model for the game connection, and a peer/peer model for the voice + connections. + + \Copyright + Copyright (c) Electronic Arts 2005. ALL RIGHTS RESERVED. + + \Version 01/04/2005 (jbrookes) first version +*/ +/********************************************************************************H*/ + +#ifndef _connapi_h +#define _connapi_h + +/*! +\Moduledef ConnApi ConnApi +\Modulemember Game +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtyaddr.h" +#include "DirtySDK/game/netgameutil.h" +#include "DirtySDK/game/netgamelink.h" +#include "DirtySDK/game/netgamedist.h" +#include "DirtySDK/comm/commudp.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/prototunnel.h" + +/*** Defines **********************************************************************/ + + +// connection flags +#define CONNAPI_CONNFLAG_GAMECONN (1) //!< game connection supported +#define CONNAPI_CONNFLAG_VOIPCONN (2) //!< voip connection supported +#define CONNAPI_CONNFLAG_GAMEVOIP (3) //!< game and voip connections supported + +// connection status flags +#define CONNAPI_CONNFLAG_CONNECTED (4) //!< set if connection succeeded +#define CONNAPI_CONNFLAG_DEMANGLED (16) //!< set if demangler was attempted +#define CONNAPI_CONNFLAG_PKTRECEIVED (32) //!< set if packets are received (for game connection only) + +// error codes +#define CONNAPI_ERROR_INVALID_STATE (-1) //!< connapi is in an invalid state +#define CONNAPI_ERROR_CLIENTLIST_FULL (-2) //!< client list is full +#define CONNAPI_ERROR_SLOT_USED (-3) //!< selected slot already in use +#define CONNAPI_ERROR_SLOT_OUT_OF_RANGE (-4) //!< selected slot is not an valid index into the client array +#define CONNAPI_CALLBACKS_FULL (-5) //!< maximum number of callbacks registered +#define CONNAPI_CALLBACK_NOT_FOUND (-6) //!< callback not found + +// supported connection concierge mode (to be used with 'ccmd' control selector) +#define CONNAPI_CCMODE_PEERONLY (0) //!< peer connections only +#define CONNAPI_CCMODE_HOSTEDONLY (1) //!< ccs connections only +#define CONNAPI_CCMODE_HOSTEDFALLBACK (2) //!< peer connections fallback to ccs on failure + +//! ConnApi callback types +typedef enum ConnApiCbTypeE +{ + CONNAPI_CBTYPE_GAMEEVENT, //!< a game event occurred + CONNAPI_CBTYPE_DESTEVENT, //!< link/util ref destruction event + CONNAPI_CBTYPE_VOIPEVENT, //!< a voip event occurred + CONNAPI_NUMCBTYPES //!< number of callback types +} ConnApiCbTypeE; + +//! connection status +typedef enum ConnApiConnStatusE +{ + CONNAPI_STATUS_INIT, //!< initialization state + CONNAPI_STATUS_CONN, //!< connecting to peer + CONNAPI_STATUS_MNGL, //!< demangling + CONNAPI_STATUS_ACTV, //!< connection established + CONNAPI_STATUS_DISC, //!< disconnected + CONNAPI_NUMSTATUSTYPES //!< max number of status types +} ConnApiConnStatusE; + +//! game topology types +typedef enum ConnApiGameTopologyE +{ + CONNAPI_GAMETOPOLOGY_DISABLED, //!< no game traffic + CONNAPI_GAMETOPOLOGY_PEERWEB, //!< peer to peer full mesh + CONNAPI_GAMETOPOLOGY_PEERHOSTED, //!< hosted by peer + CONNAPI_GAMETOPOLOGY_SERVERHOSTED //!< hosted by server +} ConnApiGameTopologyE; + +//! voip topology types +typedef enum ConnApiVoipTopologyE +{ + CONNAPI_VOIPTOPOLOGY_DISABLED, //!< no voip traffic + CONNAPI_VOIPTOPOLOGY_PEERWEB, //!< peer to peer full mesh + CONNAPI_VOIPTOPOLOGY_SERVERHOSTED //!< peer to peer routed via server +} ConnApiVoipTopologyE; + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ +//! connection timers +typedef struct ConnApiConnTimersT +{ + uint32_t uCreateSATime; //!< time it takes to resolve secure association (xbox one) + uint32_t uConnectTime; //!< time it takes for intial connection attempt + uint32_t uDemangleTime; //!< time it takes to attempt demangling on the connection + uint32_t uDemangleConnectTime; //!< time it takes to attempt a connection after demangling +} ConnApiConnTimersT; + +//! connection info +typedef struct ConnApiConnInfoT +{ + uint16_t uLocalPort; //!< local (bind) port + uint16_t uMnglPort; //!< demangled port, if any + uint8_t bDemangling; //!< is demangling process ongoing? (internal use only - see notes in connapi.c file header) + uint8_t uConnFlags; //!< connection status flags (CONNAPI_CONNSTAT_*) + uint8_t _pad[2]; + ConnApiConnStatusE eStatus; //!< status of connection (CONNAPI_STATUS_*) + uint32_t iConnStart; //!< NetTick() recorded at connection or demangling start (internal use only) + ConnApiConnTimersT ConnTimers; //!< connection timers +} ConnApiConnInfoT; + +//! client info +typedef struct ConnApiClientInfoT +{ + uint32_t uId; //!< unique connapi client id, uId should never be 0. A client with uId of 0 will be consider invalid. + uint32_t uAddr; //!< external internet address of user + uint8_t bIsConnectivityHosted; //!< flag for whether the connectivity to this client is direct or server-hosted + uint32_t uRemoteClientId; //!< remote client id (used when setting up tunnel, game connection and voip connection with this client) + uint32_t uLocalClientId; //!< local client id (used when setting up tunnel, game connection and voip connection with this client) + uint32_t uHostingServerId; //!< id of the hosting server or 0 if none + uint32_t uLocalAddr; //!< internal address of user + uint16_t uGamePort; //!< external (send) port to use for game connection, or zero to use global port + uint16_t uVoipPort; //!< external (send) port to use for voip connection, or zero to use global port + uint16_t uLocalGamePort; //!< local (bind) port to use for game connection, or zero to use global port + uint16_t uLocalVoipPort; //!< local (bind) port to use for voip connection, or zero to use global port + uint16_t uTunnelPort; //!< user's tunnel port, or zero to use default + uint16_t uLocalTunnelPort; //!< user's local tunnel port + uint8_t bEnableQos; //!< enable QoS for this client, call ConnApiControl() with 'sqos' and 'lqos' to configure QoS settings + uint8_t _pad[3]; + DirtyAddrT DirtyAddr; //!< dirtyaddr address of client + char strTunnelKey[PROTOTUNNEL_MAXKEYLEN]; //!< tunnel key +} ConnApiClientInfoT; + +//! connection type +typedef struct ConnApiClientT +{ + ConnApiClientInfoT ClientInfo; + ConnApiConnInfoT GameInfo; //!< info about game connection + ConnApiConnInfoT VoipInfo; //!< info about voip connection + NetGameUtilRefT *pGameUtilRef; //!< util ref for connection + NetGameLinkRefT *pGameLinkRef; //!< link ref for connection + NetGameDistRefT *pGameDistRef; //!< dist ref for connection (for app use; not managed by ConnApi) + int32_t iTunnelId; //!< tunnel identifier (if any) + uint16_t uConnFlags; //!< CONNAPI_CONNFLAG_* describing the connection type (read-only) + int16_t iVoipConnId; //!< voip connection identifier + uint16_t uFlags; //!< internal client flags + uint8_t bAllocated; //!< TRUE if this slot is allocated, else FALSE + uint8_t bEstablishVoip; //!< used to establish voip when enable QoS is set (after QoS has been validated), call ConnApiControl() with 'estv' to configure +} ConnApiClientT; + +//! connection list type +typedef struct ConnApiClientListT +{ + int32_t iNumClients; //!< number of clients in list + int32_t iMaxClients; //!< max number of clients + ConnApiClientT Clients[1]; //!< client array (variable length) +} ConnApiClientListT; + +//! callback info +typedef struct ConnApiCbInfoT +{ + int32_t iClientIndex; //!< index of client event is for + uint32_t eType; //!< type of event (CONNAPI_CBTYPE_*) + uint32_t eOldStatus; //!< old status (CONNAPI_STATUS_*) + uint32_t eNewStatus; //!< new status (CONNAPI_STATUS_*) + const ConnApiClientT* pClient; //!< pointer to the corresponding client structure +} ConnApiCbInfoT; + +//! opaque module ref +typedef struct ConnApiRefT ConnApiRefT; + +/*! + \Callback ConnApiCallbackT + + \Description + Callback fired when a connection event happens identified by the information + in pCbInfo + + \Input *pConnApi - module state + \Input *pCbInfo - callback information + \Input *pUserData - user information passed along with the callback +*/ +typedef void (ConnApiCallbackT)(ConnApiRefT *pConnApi, ConnApiCbInfoT *pCbInfo, void *pUserData); + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +//! create the module state +#define ConnApiCreate(_iGamePort, _iMaxClients, _pCallback, _pUserData) ConnApiCreate2(_iGamePort, _iMaxClients, _pCallback, _pUserData, (CommAllConstructT *)CommUDPConstruct) + +// create the module state +DIRTYCODE_API ConnApiRefT *ConnApiCreate2(int32_t iGamePort, int32_t iMaxClients, ConnApiCallbackT *pCallback, void *pUserData, CommAllConstructT *pConstruct); + +// this function should be called once the user has logged on and the input parameters are available +DIRTYCODE_API void ConnApiOnline(ConnApiRefT *pConnApi, const char *pGameName, uint32_t uSelfId, ConnApiGameTopologyE eGameTopology, ConnApiVoipTopologyE eVoipTopology); + +// destroy the module state +DIRTYCODE_API void ConnApiDestroy(ConnApiRefT *pConnApi); + +// host or connect to a game / voip, with the possibility to import connection. +DIRTYCODE_API void ConnApiConnect(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientList, int32_t iClientListSize, int32_t iGameHostIndex, int32_t iVoipHostIndex, int32_t iSessId); + +// add a new client to a pre-existing game in the specified index. +DIRTYCODE_API int32_t ConnApiAddClient(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientInfo, int32_t iClientIdx); + +// return the ConnApiClientT for the specified client (by id) +DIRTYCODE_API uint8_t ConnApiFindClient(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientInfo, ConnApiClientT *pOutClient); + +// remove a current client from a game +DIRTYCODE_API void ConnApiRemoveClient(ConnApiRefT *pConnApi, int32_t iClientIdx); + +// redo all connections, using the host specified +DIRTYCODE_API void ConnApiMigrateGameHost(ConnApiRefT *pConnApi, int32_t iNewGameHostIndex); + +// disconnect from game +DIRTYCODE_API void ConnApiDisconnect(ConnApiRefT *pConnApi); + +// get list of current connections +DIRTYCODE_API const ConnApiClientListT *ConnApiGetClientList(ConnApiRefT *pConnApi); + +// connapi status +DIRTYCODE_API int32_t ConnApiStatus(ConnApiRefT *pConnApi, int32_t iSelect, void *pBuf, int32_t iBufSize); + +// connapi status (version 2) +DIRTYCODE_API int32_t ConnApiStatus2(ConnApiRefT *pConnApi, int32_t iSelect, void *pData, void *pBuf, int32_t iBufSize); + +// connapi control +DIRTYCODE_API int32_t ConnApiControl(ConnApiRefT *pConnApi, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); + +// update connapi module (must be called directly if auto-update is disabled) +DIRTYCODE_API void ConnApiUpdate(ConnApiRefT *pConnApi); + +// Register a new callback +DIRTYCODE_API int32_t ConnApiAddCallback(ConnApiRefT *pConnApi, ConnApiCallbackT *pCallback, void *pUserData); + +// Removes a callback previously registered +DIRTYCODE_API int32_t ConnApiRemoveCallback(ConnApiRefT *pConnApi, ConnApiCallbackT *pCallback, void *pUserData); + +#ifdef __cplusplus +}; +#endif + +//@} + +#endif // _connapi_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/game/netgamedist.h b/src/thirdparty/dirtysdk/include/DirtySDK/game/netgamedist.h new file mode 100644 index 00000000..b2b617d5 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/game/netgamedist.h @@ -0,0 +1,177 @@ +/*H*************************************************************************************/ +/*! + \File netgamedist.h + + \Description + The GameDist class provides the main control interface between the + individual game controllers and the distributed game-input engine. + The GameInput and GameComm classes provided the interfaces used by + this class for real-world communication. + + \Copyright + Copyright (c) Electronic Arts 1998-2007. + + \Version 1.0 12/15/1998 (gschaefer) First Version + \Version 1.1 12/26/1999 (gschaefer) Revised to add external stats interface + \Version 1.2 11/18/2002 (jbrookes) Moved to NetGame hierarchy +*/ +/*************************************************************************************H*/ + +#ifndef _netgamedist_h +#define _netgamedist_h + +/*! +\Moduledef NetGameDist NetGameDist +\Modulemember Game +*/ +//@{ + + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/game/netgameerr.h" +#include "DirtySDK/game/netgamepkt.h" + +/*** Defines ***************************************************************************/ + +#define GMDIST_DEFAULT_BUFFERSIZE_IN (16384) //local packets lost: number of packets (from peer) lost + uint8_t naksent; //!< number of NAKs received by peer (from us) +} NetGameDistStatT; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// construct the game client +DIRTYCODE_API NetGameDistRefT *NetGameDistCreate(void *pLinkRef, NetGameDistStatProc *pStatProc, NetGameDistSendProc *pSendProc, NetGameDistRecvProc *pRecvProc, uint32_t uInBufferSize, uint32_t uOutBufferSize ); + +// destruct the game client +DIRTYCODE_API void NetGameDistDestroy(NetGameDistRefT *pRef); + +// status function +DIRTYCODE_API int32_t NetGameDistStatus(NetGameDistRefT *pRef, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize); + +// for game-server usage, make NetGameDist send multi-packet +DIRTYCODE_API void NetGameDistSetServer(NetGameDistRefT *pRef, uint8_t bActAsServer); + +// on the server: iDist index will be the index of the player that owns the dist +// on the client: iDist index will be the index of the local player that owns the dist +// iTotplayers will always be the total players in game +DIRTYCODE_API void NetGameDistMultiSetup(NetGameDistRefT *pRef, int32_t iDistIndex, int32_t iTotPlrs ); + +// set whether to enable meta-information sending, the mask and the version number +DIRTYCODE_API void NetGameDistMetaSetup(NetGameDistRefT *pRef, uint8_t bSparse, uint32_t uMask, uint32_t uVersion); + +// peek input from peer +DIRTYCODE_API int32_t NetGameDistInputPeek(NetGameDistRefT *pRef, uint8_t *pType, void *pPeer, int32_t *pPlen); + +// return completed input from client +DIRTYCODE_API int32_t NetGameDistInputQuery(NetGameDistRefT *pRef, void *pOurs, int32_t *pOlen, void *pPeer, int32_t *pPlen); + +// return completed input from multiple clients +DIRTYCODE_API int32_t NetGameDistInputQueryMulti(NetGameDistRefT *pRef, uint8_t *pDataTypes, void **pInputs, int32_t *pLen); + +// provide local input data +DIRTYCODE_API int32_t NetGameDistInputLocal(NetGameDistRefT *pRef, void *pBuffer, int32_t iLen); + +// provide local input data (n-consoles). Client usage is to provide one-long arrays. Type can be GMDIST_DATA_INPUT, GMDIST_DATA_INPUT_DROPPABLE +DIRTYCODE_API int32_t NetGameDistInputLocalMulti(NetGameDistRefT *pRef, uint8_t *pTypesArray, void **ppBuffer, int32_t *pLengthsArray, int32_t iDelta); + +// check input status (see how long till next) +DIRTYCODE_API void NetGameDistInputCheck(NetGameDistRefT *pRef, int32_t *pSend, int32_t *pRecv); + +// set new input exchange rate +DIRTYCODE_API void NetGameDistInputRate(NetGameDistRefT *pRef, int32_t iRate); + +// flush the input queue (note that this must be done with +DIRTYCODE_API void NetGameDistInputClear(NetGameDistRefT *pRef); + +// set NetGameDist operation parameters +DIRTYCODE_API int32_t NetGameDistControl(NetGameDistRefT *pRef, int32_t iSelect, int32_t iValue, void *pValue); + +// dispatch to appropriate updaters +DIRTYCODE_API uint32_t NetGameDistUpdate(NetGameDistRefT *pRef); + +// override the send,recv,stats procs or set the drop proc +DIRTYCODE_API void NetGameDistSetProc(NetGameDistRefT *pRef, int32_t iKind, void *pProc); + +// For the game server to provides stats, the array must be sized according to what was passed to NetGameDistMultiSetup +DIRTYCODE_API void NetGameDistSendStats(NetGameDistRefT *pRef, NetGameDistStatT *pStats); + +// return non-zero if there ever was an overflow +DIRTYCODE_API int32_t NetGameDistGetError(NetGameDistRefT *pRef); + +// copies into the passed buffer the last error text. +DIRTYCODE_API void NetGameDistGetErrorText(NetGameDistRefT *pRef, char *pErrorBuffer, int32_t iBufSize); + +// reset the error count +DIRTYCODE_API void NetGameDistResetError(NetGameDistRefT *pRef); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _netgamedist_h + + + + + + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/game/netgamedistserv.h b/src/thirdparty/dirtysdk/include/DirtySDK/game/netgamedistserv.h new file mode 100644 index 00000000..3da9835f --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/game/netgamedistserv.h @@ -0,0 +1,97 @@ +/*H********************************************************************************/ +/*! + \File netgamedistserv.h + + \Description + Server module to handle 2+ NetGameDist connections in a client/server + architecture. + + \Copyright + Copyright (c) 2007 Electronic Arts + + \Version 02/01/2007 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _netgamedistserv_h +#define _netgamedistserv_h + +/*! +\Moduledef NetGameDistServ NetGameDistServ +\Modulemember Game +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/game/netgamelink.h" +#include "DirtySDK/game/netgamedist.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! opaque module state +typedef struct NetGameDistServT NetGameDistServT; + +//! logging function type +typedef int32_t (NetGameDistServLoggingCbT)(const char *pText, void *pUserData); + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create the module state +DIRTYCODE_API NetGameDistServT *NetGameDistServCreate(int32_t iMaxClients, int32_t iVerbosity); + +// destroy the module state +DIRTYCODE_API void NetGameDistServDestroy(NetGameDistServT *pDistServ); + +// add a client to module +DIRTYCODE_API int32_t NetGameDistServAddClient(NetGameDistServT *pDistServ, int32_t iClient, NetGameLinkRefT *pLinkRef, const char *pClientName); + +// del a client from module +DIRTYCODE_API int32_t NetGameDistServDelClient(NetGameDistServT *pDistServ, int32_t iClient); + +// notify that a client disconnected +DIRTYCODE_API int32_t NetGameDistServDiscClient(NetGameDistServT *pDistServ, int32_t iClient); + +// update a client +DIRTYCODE_API int32_t NetGameDistServUpdateClient(NetGameDistServT *pDistServ, int32_t iClient); + +// update the module (must be called at fixed rate) +DIRTYCODE_API void NetGameDistServUpdate(NetGameDistServT *pDistServ); + +// whether the highwater mark changed, and the current highwater values. +DIRTYCODE_API uint8_t NetGameDistServHighWaterChanged(NetGameDistServT *pDistServ, int32_t* pHighWaterInputQueue, int32_t* pHighWaterOutputQueue); + +// return the lastest error reported by netgamedist, for client iClient +DIRTYCODE_API char* NetGameDistServExplainError(NetGameDistServT *pDistServ, int32_t iClient); + +// netgamedistserv control +DIRTYCODE_API int32_t NetGameDistServControl(NetGameDistServT *pDistServ, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); + +// netgamedistserv status +DIRTYCODE_API int32_t NetGameDistServStatus(NetGameDistServT *pDistServ, int32_t iSelect, void *pBuf, int32_t iBufSize); + +// netgamedistserv2 status +DIRTYCODE_API int32_t NetGameDistServStatus2(NetGameDistServT *pDistServ, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize); + +// set logging callback +DIRTYCODE_API void NetGameDistServSetLoggingCallback(NetGameDistServT *pDistServ, NetGameDistServLoggingCbT *pLoggingCb, void *pUserData); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _netgamedistserv_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/game/netgameerr.h b/src/thirdparty/dirtysdk/include/DirtySDK/game/netgameerr.h new file mode 100644 index 00000000..287c3844 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/game/netgameerr.h @@ -0,0 +1,67 @@ +/*H********************************************************************************/ +/*! + \File netgameerr.h + + \Description + Error codes for GameDist functions (errors always negative) + + \Copyright + Copyright (c) Electronic Arts 1998-2007 + + \Version 1.0 23/06/2009 (jrainy) First Version +*/ +/********************************************************************************H*/ + +#ifndef _netgameerr_h +#define _netgameerr_h + +/*! +\Moduledef NetGameErr NetGameErr +\Modulemember Game +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +#define GMDIST_ORPHAN (-1) //local packets lost: number of packets (from peer) lost since start - not always equal to (rpacksent - lpackrcvd) + uint32_t lpacksaved; //!< number of packets recovered by underlying commudp redundancy mechanism + uint32_t rnaksent; //!< number of NAKs sent by peer (to us) since start + uint32_t rpacksent; //!< number of packets sent by peer (to us) since start + uint32_t rpackrcvd; //!< number of packets received by peer (from us) since start + uint32_t rpacklost; //!< local->remote packets lost: number of packets (from us) lost by peer since start - not always equal to (lpacksent - rpackrcvd) + /* Notes + Attempting to calculate the number of local->remote packets lost with (lpacksent - rpackrcvd) sometimes + lead to a boosted packet loss result because some in-flight packets are accounted in the sent counter but not + in the rcved counter. For a precise local->remote packet loss value, rpacklost is better because + it is obtained from the remote end directly (from the inbound NetGameLinkSyncT packet) + + Attempting to calculate the number of remote->local packets lost with (rpacksent - lpackrcvd) sometimes + lead to reduced (or even negative) packet loss result because additional inbound packets are + received when commudp retransmission mechanisms quick in to guarantee transport reliability. Those + packet are accounted for in the rcved counter but not in the sent counter. For a precise + remote->local packet loss value, lpacklost is better because it takes the above phenomenon + into account. */ + + uint8_t isconn; //!< listening or connecting \Deprecated + uint8_t isopen; //!< listening, connecting, or connected + uint8_t pad0[2]; //!< pad to four-byte alignment + + uint32_t pingtick; //!< last tick at which ping stuff updated + uint32_t pingslot; //!< the index containing the current ping + int32_t pingdev; //!< ping deviation + int32_t pingavg; //!< ping average + + NetGameLinkHistT pinghist[PING_HISTORY]; +} NetGameLinkStatT; + +// stream structure +typedef struct NetGameLinkStreamT NetGameLinkStreamT; + +//! dist stream send proc +typedef int32_t (NetGameLinkStreamSendProc)(NetGameLinkStreamT *pStream, int32_t iSubchan, int32_t iKind, void *pBuffer, int32_t iLen); +//! dist stream recv proc +typedef void (NetGameLinkStreamRecvProc)(NetGameLinkStreamT *pStream, int32_t iSubchan, int32_t iKind, void *pBuffer, int32_t iLen); + +typedef struct NetGameLinkStreamInpT +{ + char *pInpData; //!< private + int32_t iInpSize; //!< private + int32_t iInpProg; //!< private + int32_t iInpKind; //!< private +} NetGameLinkStreamInpT; + +//! structure to access network stream with +struct NetGameLinkStreamT +{ + NetGameLinkStreamT *pNext; //!< private + NetGameLinkRefT *pClient; //!< private + + int32_t iIdent; //!< private (can read -- stream identifier) + int32_t iSubchan; //!< number of subchannels + int32_t iRefNum; //!< public (for callers use) + void *pRefPtr; //!< public (for callers use) + NetGameLinkStreamSendProc *Send;//!< public + NetGameLinkStreamRecvProc *Recv;//!< public + int32_t iQueueDepth; //!< public read only (total bytes in output queue) + int32_t iQueueLimit; //!< public read/write (maximum outgoing queue limit) + + int32_t iHighWaterUsed; //!< public read only + int32_t iHighWaterNeeded; //!< public read only + + int32_t iInpMaxm; //!< private + NetGameLinkStreamInpT *pInp; //!< private + + char *pOutData; //!< private + int32_t iOutMaxm; //!< private + int32_t iOutSize; //!< private + int32_t iOutProg; //!< private + + char *pSynData; //!< private + int32_t iSynMaxm; //!< private + int32_t iSynSize; //!< private +}; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// construct the game client +DIRTYCODE_API NetGameLinkRefT *NetGameLinkCreate(void *pCommRef, int32_t iOwner, int32_t iBufLen); + +// destruct the game client +DIRTYCODE_API void NetGameLinkDestroy(NetGameLinkRefT *pRef); + +// register a callback function +DIRTYCODE_API void NetGameLinkCallback(NetGameLinkRefT *pRef, void *pCallData, void (*pCallProc)(void *pCallData, int32_t iKind)); + +// status function +DIRTYCODE_API int32_t NetGameLinkStatus(NetGameLinkRefT *pRef, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize); + +// incoming packet stream from upper layer +DIRTYCODE_API int32_t NetGameLinkSend(NetGameLinkRefT *pRef, NetGamePacketT *pPkt, int32_t iLen); + +// peek into the buffer +DIRTYCODE_API int32_t NetGameLinkPeek(NetGameLinkRefT *pRef, NetGamePacketT **ppPkt); + +// peek into the buffer for specific packet types +DIRTYCODE_API int32_t NetGameLinkPeek2(NetGameLinkRefT *pRef, NetGamePacketT **ppPkt, uint32_t uMask); + +// outgoing packet stream to upper layer +DIRTYCODE_API int32_t NetGameLinkRecv(NetGameLinkRefT *pRef, NetGamePacketT *pBuf, int32_t iLen, uint8_t bDist); + +// same as NetGameLinkRecv, but takes a mask of which GAME_PACKET types we want +DIRTYCODE_API int32_t NetGameLinkRecv2(NetGameLinkRefT *pRef, NetGamePacketT *pBuf, int32_t iLen, uint32_t uMask); + +// control behavior +DIRTYCODE_API int32_t NetGameLinkControl(NetGameLinkRefT *pRef, int32_t iSelect, int32_t iValue, void *pValue); + +// dispatch to appropriate updaters +DIRTYCODE_API uint32_t NetGameLinkUpdate(NetGameLinkRefT *pRef); + +// create a new network stream +DIRTYCODE_API NetGameLinkStreamT *NetGameLinkCreateStream(NetGameLinkRefT *pRef, int32_t iSubChan, int32_t iIdent, int32_t iInpLen, int32_t iOutLen, int32_t iSynLen); + +// destroy an existing network stream +DIRTYCODE_API void NetGameLinkDestroyStream(NetGameLinkRefT *pRef, NetGameLinkStreamT *pStream); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _netgamelink_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/game/netgamepkt.h b/src/thirdparty/dirtysdk/include/DirtySDK/game/netgamepkt.h new file mode 100644 index 00000000..72d0bdfb --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/game/netgamepkt.h @@ -0,0 +1,159 @@ +/*H*************************************************************************************/ +/*! + \File netgamepkt.h + + \Description + Defines netgame packet constants and types. + + \Copyright + Copyright (c) Electronic Arts 1998-2007. + + \Version 1.0 12/12/1998 (gschaefer) First Version + \Version 1.1 11/18/2002 (jbrookes) Moved to NetGame hierarchy + \Version 1.2 02/04/2007 (jbrookes) Cleanup, added NetGameMaxPacketT +*/ +/*************************************************************************************H*/ + +#ifndef _netgamepkt_h +#define _netgamepkt_h + +/*! +\Moduledef NetGamePkt NetGamePkt +\Modulemember Game +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +/* sampling period constants + dont enum these or you will regret it + (the compiler translates divides by these into logical shifts) */ +#define RTT_SAMPLE_PERIOD 1024 +#define LATE_SAMPLE_PERIOD 512 + +//! identifiers of client/server packets +enum +{ + GAME_PACKET_ONE_BEFORE_FIRST =1,//!< enum with value 1 available for future use. If needed just lower ONE_BEFORE_FIRST to zero + GAME_PACKET_INPUT, //!< data exchanged by machines using dist, usually controller inputs + GAME_PACKET_INPUT_MULTI, //!< same as INPUT but from the game server. + GAME_PACKET_STATS, //!< stats sent by the game server. + GAME_PACKET_USER, //!< user packet + GAME_PACKET_USER_UNRELIABLE, //!< user packet, to be sent unreliably + GAME_PACKET_USER_BROADCAST, //!< user packet, to be sent unreliably to broadcast address + GAME_PACKET_INPUT_FLOW, //!< dist flow control, sent to indicate readiness + GAME_PACKET_INPUT_MULTI_FAT, //!< same as INPUT_MULTI but 16-bit individual packet sizes (to allow 256-1200 sizes). + GAME_PACKET_INPUT_META, //!< meta-information on the packet format + GAME_PACKET_LINK_STRM, //!< netgamelink stream packet + GAME_PACKET_ONE_PAST_LAST, //!< used to mark the valid range. Insert new kinds before + GAME_PACKET_SYNC = 64 //!< OR this value with packet kind to signal that packet conveys sync info +}; + +//! default max raw data packet size +#define NETGAME_DATAPKT_DEFSIZE (240) + +//! max raw data packet size +#define NETGAME_DATAPKT_MAXSIZE (1200) + +//! max size of packet tail (sync+kind) +#define NETGAME_DATAPKT_MAXTAIL (sizeof(NetGamePacketSyncT)+1) + +//! max stream packet size +#define NETGAME_STRMPKT_DEFSIZE (200) +#define NETGAME_STRMPKT_MAXSIZE (NETGAME_DATAPKT_MAXSIZE - 12) + +//! default max input/output buffers, in packets +#define NETGAME_DATABUF_MAXSIZE (32) + +// sync packet flags + +//!< indicates repl field is not yet valid (remote side has not received our first sync packet yet) +#define NETGAME_SYNCFLAG_REPLINVALID (0x01) + + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! packet header (common for every packet) +typedef struct NetGamePacketHeadT +{ + int32_t size; //!< total size (including header, 4-byte aligned) + uint32_t when; //!< reception time in ticks + uint16_t len; //!< length of packet body + uint8_t kind; //!< packet type (see enum above) + uint8_t pad; //!< unused (alignment) +} NetGamePacketHeadT; + +//! format of a sync packet (piggybacks onto regular packets) -- FOR INTERNAL USE ONLY +typedef struct NetGamePacketSyncT +{ + uint32_t repl; + uint32_t echo; + int16_t late; + uint8_t psnt; //!< number of packets sent since last update + uint8_t prcvd; //!< number of packets received since last update + uint8_t plost; //!< number of inbound packets lost since last update + uint8_t nsnt; //!< number of NAKs sent since last update + uint8_t flags; //!< sync packet flags (NETGAME_SYNCFLAG_*) + uint8_t size; //!< size of sync packet (MUST COME LAST) +} NetGamePacketSyncT; + +//! format of packets which are exchanged by client/server +typedef struct NetGamePacketT +{ + //! packet header (not sent) + NetGamePacketHeadT head; + + //! game packet body + union { + //! stream data + struct { + int32_t ident; //!< stream ident + int32_t kind; //!< token + int32_t size; //!< packet size + uint8_t data[NETGAME_STRMPKT_DEFSIZE];//!< binary data + } strm; + //! raw data packet + uint8_t data[NETGAME_DATAPKT_DEFSIZE]; + } body; + + //! space for game packet tail + uint8_t tail[NETGAME_DATAPKT_MAXTAIL]; +} NetGamePacketT; + +//! format of a max-sized link packet (may be substituted for a NetGamePacketT) +typedef struct NetGameMaxPacketT +{ + //! packet header (not sent) + NetGamePacketHeadT head; + + //! game packet body + union { + //! stream data + struct { + int32_t ident; //!< stream ident + int32_t kind; //!< token + int32_t size; //!< packet size + uint8_t data[NETGAME_STRMPKT_MAXSIZE];//!< binary data + } strm; + //! raw data packet + uint8_t data[NETGAME_DATAPKT_MAXSIZE]; + } body; + + //! space for game packet tail + uint8_t tail[NETGAME_DATAPKT_MAXTAIL]; +} NetGameMaxPacketT; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +//@} + +#endif // _netgamepkt_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/game/netgameutil.h b/src/thirdparty/dirtysdk/include/DirtySDK/game/netgameutil.h new file mode 100644 index 00000000..12cd5ae1 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/game/netgameutil.h @@ -0,0 +1,91 @@ +/*H*************************************************************************************/ +/*! + \File netgameutil.h + + \Description + Group of functions to help setup network games + + \Copyright + Copyright (c) Electronic Arts 2001-2002 + + \Version 1.0 01/09/2001 (gschaefer) First Version + \Version 1.1 11/18/2002 (jbrookes) Moved to NetGame hierarchy +*/ +/*************************************************************************************H*/ + +#ifndef _netgameutil_h +#define _netgameutil_h + +/*! +\Moduledef NetGameUtil NetGameUtil +\Modulemember Game +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/comm/commall.h" + +/*** Defines ***************************************************************************/ + +#define NETGAME_CONN_LISTEN (1) //!< listen for connect +#define NETGAME_CONN_CONNECT (2) //!< connect to peer +#define NETGAME_CONN_AUTO (NETGAME_CONN_LISTEN|NETGAME_CONN_CONNECT) //!< (debug only) autoconnect + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct NetGameUtilRefT NetGameUtilRefT; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// construct the game setup module +DIRTYCODE_API NetGameUtilRefT *NetGameUtilCreate(void); + +// destroy the game setup module +DIRTYCODE_API void NetGameUtilDestroy(NetGameUtilRefT *ref); + +// reset the game setup module +DIRTYCODE_API void NetGameUtilReset(NetGameUtilRefT *ref); + +// set internal GameUtil parameters +DIRTYCODE_API void NetGameUtilControl(NetGameUtilRefT *pRef, int32_t iKind, int32_t iValue); + +// get a connection (connect/listen) +DIRTYCODE_API int32_t NetGameUtilConnect(NetGameUtilRefT *ref, int32_t conn, const char *addr, CommAllConstructT *pConstruct); + +// check connection status +DIRTYCODE_API void *NetGameUtilComplete(NetGameUtilRefT *ref); + +// return status info +DIRTYCODE_API int32_t NetGameUtilStatus(NetGameUtilRefT *ref, int32_t iSelect, void *pBuf, int32_t iBufSize); + +// send out an advertisement +DIRTYCODE_API void NetGameUtilAdvert(NetGameUtilRefT *ref, const char *kind, const char *name, const char *note); + +// cancel current advertisement +DIRTYCODE_API void NetGameUtilWithdraw(NetGameUtilRefT *ref, const char *kind, const char *name); + +// find ip address of a specific advertisement +DIRTYCODE_API uint32_t NetGameUtilLocate(NetGameUtilRefT *ref, const char *kind, const char *name); + +// return a list of all advertisements +DIRTYCODE_API int32_t NetGameUtilQuery(NetGameUtilRefT *ref, const char *kind, char *buf, int32_t max); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _netgameutil_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtygif.h b/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtygif.h new file mode 100644 index 00000000..0bec40ba --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtygif.h @@ -0,0 +1,97 @@ +/*H********************************************************************************/ +/*! + \File dirtygif.h + + \Description + Routines to parse and decode a GIF file. + + \Copyright + Copyright (c) 2003-2020 Electronic Arts Inc. + + \Version 11/13/2003 (jbrookes) First Version + \Version 01/28/2020 (jbrookes) Added multiframe (animated) support +*/ +/********************************************************************************H*/ + +#ifndef _dirtygif_h +#define _dirtygif_h + +/*! +\Moduledef DirtyGif DirtyGif +\Modulemember Graph +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! structure to represent a parsed gif/gif frame +typedef struct DirtyGifHdrT +{ + // image info + const uint8_t *pImageData; //!< pointer to start of gif bits + const uint8_t *pImageEnd; //!< pointer to end of gif bits + const uint8_t *pColorTable; //!< pointer to gif color table + + uint32_t uNumFrames; //!< number of image frames + + uint16_t uTop; //!< top offset of image frame + uint16_t uLeft; //!< left offset of image frame + uint16_t uWidth; //!< image width + uint16_t uHeight; //!< image height + uint16_t uNumColors; //!< number of color table entries + uint16_t uDelay; //!< time delay after displaying current frame before moving to next in 1/100ths of a second + + // misc info + uint8_t bInterlaced; //!< flag indicating an interlaced image + uint8_t bHasAlpha; //!< flag indicating transparent color present + uint8_t uTransColor; //!< which color index is transparent + uint8_t pad; +} DirtyGifHdrT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// identify if image is a gif or not +DIRTYCODE_API int32_t DirtyGifIdentify(const uint8_t *pImageData, uint32_t uImageLen); + +// parse GIF into something we can use +DIRTYCODE_API int32_t DirtyGifParse(DirtyGifHdrT *pGifHdr, const uint8_t *pGifData, const uint8_t *pGifEnd); + +// parse GIF with extended info +DIRTYCODE_API int32_t DirtyGifParseEx(DirtyGifHdrT *pGifHdr, const uint8_t *pGifData, const uint8_t *pGifEnd, DirtyGifHdrT *pFrames, uint32_t uNumFrames); + +// decode a GIF palette to 32bit color table +DIRTYCODE_API int32_t DirtyGifDecodePalette(DirtyGifHdrT *pGifHdr, uint8_t *pPalette, uint8_t *pPaletteEnd, uint8_t uAlpha); + +// decode a GIF image to 8bit paletted image +DIRTYCODE_API int32_t DirtyGifDecodeImage(DirtyGifHdrT *pGifHdr, uint8_t *pImageData, int32_t iBufWidth, int32_t iBufHeight, uint32_t bVflip); + +// decode a GIF image to 32bit direct-color image +DIRTYCODE_API int32_t DirtyGifDecodeImage32(DirtyGifHdrT *pGifHdr, uint8_t *pImageData, int32_t iBufWidth, int32_t iBufHeight, uint32_t bVflip); + +// decode a GIF image to one or more 32bit direct-color images +DIRTYCODE_API int32_t DirtyGifDecodeImage32Multi(DirtyGifHdrT *pGifHdr, DirtyGifHdrT *pFrameInfo, uint8_t *pImageData, int32_t iBufWidth, int32_t iBufHeight, int32_t iNumFrames, uint32_t bVflip); + +// decode a specific frame of a GIF image into a single 32bit ARGB direct-color bitmap. +DIRTYCODE_API int32_t DirtyGifDecodeImage32Frame(DirtyGifHdrT *pGifHdr, DirtyGifHdrT *pFrameInfo, uint8_t *pImageData, uint8_t *p8BitImage, int32_t iBufWidth, int32_t iBufHeight, int32_t iFrame, int32_t iNumFrames, uint32_t bVflip); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtygif_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtygraph.h b/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtygraph.h new file mode 100644 index 00000000..68b3948b --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtygraph.h @@ -0,0 +1,93 @@ +/*H********************************************************************************/ +/*! + \File dirtygraph.h + + \Description + Routines for decoding an encoded graphics image. + + \Copyright + Copyright (c) 2006-2020 Electronic Arts Inc. + + \Version 03/09/2006 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _dirtygraph_h +#define _dirtygraph_h + +/*! +\Moduledef DirtyGraph DirtyGraph +\Modulemember Graph +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +//! supported image types +typedef enum DirtyGraphImageTypeE +{ + DIRTYGRAPH_IMAGETYPE_UNKNOWN = 0, + DIRTYGRAPH_IMAGETYPE_GIF, + DIRTYGRAPH_IMAGETYPE_JPG, + DIRTYGRAPH_IMAGETYPE_PNG +} DirtyGraphImageTypeE; + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! structure representing parsed image info +typedef struct DirtyGraphInfoT +{ + const uint8_t *pImageData; //!< pointer to start of data + uint32_t uLength; //!< length of file + int16_t iWidth; //!< image width + int16_t iHeight; //!< image height + uint16_t uNumFrames; //!< number of image frames + uint8_t uType; //!< image type + uint8_t _pad; +} DirtyGraphInfoT; + +//! opaque module state +typedef struct DirtyGraphRefT DirtyGraphRefT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create module state +DIRTYCODE_API DirtyGraphRefT *DirtyGraphCreate(void); + +// done with module state +DIRTYCODE_API void DirtyGraphDestroy(DirtyGraphRefT *pRef); + +// parse the header +DIRTYCODE_API int32_t DirtyGraphDecodeHeader(DirtyGraphRefT *pRef, DirtyGraphInfoT *pInfo, const uint8_t *pImageData, uint32_t uImageLen); + +// get extra image info +DIRTYCODE_API int32_t DirtyGraphGetImageInfo(DirtyGraphRefT *pRef, DirtyGraphInfoT *pInfo, int32_t iSelect, void *pBuffer, int32_t iBufSize); + +// decode an image +DIRTYCODE_API int32_t DirtyGraphDecodeImage(DirtyGraphRefT *pRef, DirtyGraphInfoT *pInfo, uint8_t *pImageBuf, int32_t iBufWidth, int32_t iBufHeight); + +// decode a multiframe image +DIRTYCODE_API int32_t DirtyGraphDecodeImageMulti(DirtyGraphRefT *pRef, DirtyGraphInfoT *pInfo, uint8_t *pImageBuf, int32_t iBufWidth, int32_t iBufHeight); + +// decode a single frame of a multiframe image +DIRTYCODE_API int32_t DirtyGraphDecodeImageFrame(DirtyGraphRefT *pDirtyGraph, DirtyGraphInfoT *pInfo, uint8_t *pImageBuf, int32_t iBufWidth, int32_t iBufHeight, int32_t iFrame); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtygraph_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtyjpg.h b/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtyjpg.h new file mode 100644 index 00000000..b948f553 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtyjpg.h @@ -0,0 +1,95 @@ +/*H********************************************************************************/ +/*! + \File dirtyjpg.h + + \Description + Routines to parse and decode a JPEG image file. JFIF and EXIF file formats + are supported. + + \Copyright + Copyright (c) 2006 Electronic Arts Inc. + + \Version 02/23/2006 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _dirtyjpg_h +#define _dirtyjpg_h + +/*! +\Moduledef DirtyJpg DirtyJpg +\Modulemember Graph +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +#define DIRTYJPG_ERR_NONE (0) +#define DIRTYJPG_ERR_NOMAGIC (-1) +#define DIRTYJPG_ERR_TOOSHORT (-2) +#define DIRTYJPG_ERR_NOBUFFER (-3) +#define DIRTYJPG_ERR_BADSTATE (-4) +#define DIRTYJPG_ERR_BADFRAME (-5) +#define DIRTYJPG_ERR_BADVERS (-6) +#define DIRTYJPG_ERR_ENDOFDATA (-7) +#define DIRTYJPG_ERR_BADDQT (-8) +#define DIRTYJPG_ERR_BADDHT (-9) +#define DIRTYJPG_ERR_BADSOF0 (-10) +#define DIRTYJPG_ERR_BITDEPTH (-11) // unsupported bitdepth +#define DIRTYJPG_ERR_DECODER (-12) // decoding error +#define DIRTYJPG_ERR_NOSUPPORT (-13) // unsupported feature +#define DIRTYJPG_ERR_NOFORMAT (-14) // unsupported format (not JFIF or EXIF) + +typedef struct DirtyJpgStateT DirtyJpgStateT; + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! structure to represent a parsed gif +typedef struct DirtyJpgHdrT +{ + // image info + const uint8_t *pImageData; //!< pointer to start of data + uint32_t uLength; //!< length of file + uint32_t uWidth; //!< image width + uint32_t uHeight; //!< image height +} DirtyJpgHdrT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create module state +DIRTYCODE_API DirtyJpgStateT *DirtyJpgCreate(void); + +// reset to default +DIRTYCODE_API void DirtyJpgReset(DirtyJpgStateT *pState); + +// done with module state +DIRTYCODE_API void DirtyJpgDestroy(DirtyJpgStateT *pState); + +// identify if image is a jpg or not +DIRTYCODE_API int32_t DirtyJpgIdentify(DirtyJpgStateT *pState, const uint8_t *pImage, uint32_t uLength); + +// parse the header +DIRTYCODE_API int32_t DirtyJpgDecodeHeader(DirtyJpgStateT *pState, DirtyJpgHdrT *pJpgHdr, const uint8_t *pImage, uint32_t uLength); + +// parse the image +DIRTYCODE_API int32_t DirtyJpgDecodeImage(DirtyJpgStateT *pState, DirtyJpgHdrT *pJpgHdr, uint8_t *pImageData, int32_t iBufWidth, int32_t iBufHeight); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtyjpg_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtypng.h b/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtypng.h new file mode 100644 index 00000000..615761f4 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/graph/dirtypng.h @@ -0,0 +1,134 @@ +/*H********************************************************************************/ +/*! + \File dirtypng.h + + \Description + Routines to decode a PNG into a raw image and palette. These routines + were written from scratch based on the published specifications. The + functions and style were heavily based on the dirtygif.c and dirtyjpeg.c + files coded by James Brookes. + + Supported features + Bit Depth of 1, 2, 4, and 8 + Colour Type 0, 2, 4, 6 (grayscale, truecolor, grayscale with alpha, truecolor with alpha) + Noncompressed blocks, statically compressed blocks, dynamically compressed blocks + Zlib compression levels 0, 1, 2 and 9 (none through maximum) + Interlace method Adam7 + Filter types 0-4 (none, sub, up, average, paeth) + + Unsupported features + Bit Depth of 16 support + Colour Type 3 (Indexed Color support) + PLTE chunk support + Ancillary chunk support + + \Notes + References: + [1] http://www.w3.org/TR/PNG/ + [2] http://www.gzip.org/zlib/rfc1950.pdf + [3] http://www.gzip.org/zlib/rfc1951.pdf + [4] http://www.zlib.net/feldspar.html + [5] http://www.schaik.com/pngsuite/#basic + + \Copyright + Copyright (c) 2007 Electronic Arts Inc. + + \Version 02/05/2007 (christianadam) First Version +*/ +/********************************************************************************H*/ + +#ifndef _dirtypng_h +#define _dirtypng_h + +/*! +\Moduledef DirtyPng DirtyPng +\Modulemember Graph +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +#define DIRTYPNG_ERR_NONE (0) +#define DIRTYPNG_ERR_TOOSHORT (-1) +#define DIRTYPNG_ERR_BADCRC (-2) +#define DIRTYPNG_ERR_BADTYPE (-3) +#define DIRTYPNG_ERR_BADCOLOR (-4) +#define DIRTYPNG_ERR_BADDEPTH (-5) +#define DIRTYPNG_ERR_BADCOMPR (-6) +#define DIRTYPNG_ERR_BADFILTR (-7) +#define DIRTYPNG_ERR_BADINTRL (-8) +#define DIRTYPNG_ERR_ALLOCFAIL (-9) +#define DIRTYPNG_ERR_TYPEMISS (-10) +#define DIRTYPNG_ERR_BADORDER (-11) +#define DIRTYPNG_ERR_TYPEDUPL (-12) +#define DIRTYPNG_ERR_UNKNCRIT (-13) +#define DIRTYPNG_ERR_BADCM (-14) +#define DIRTYPNG_ERR_BADCI (-15) +#define DIRTYPNG_ERR_BADFLG (-16) +#define DIRTYPNG_ERR_FDICTSET (-17) +#define DIRTYPNG_ERR_BADBTYPE (-18) +#define DIRTYPNG_ERR_NOBLKEND (-19) +#define DIRTYPNG_ERR_BADBLKLEN (-20) +#define DIRTYPNG_ERR_MAXCODES (-21) +#define DIRTYPNG_ERR_NOCODES (-22) +#define DIRTYPNG_ERR_BADCODE (-23) +#define DIRTYPNG_ERR_INVFILE (-24) +#define DIRTYPNG_ERR_UNKNOWN (-25) + +typedef struct DirtyPngStateT DirtyPngStateT; + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! structure to represent a parsed png +typedef struct DirtyPngHdrT +{ + // image info + const uint8_t *pImageData; //!< pointer to start of png bits + const uint8_t *pImageEnd; //!< pointer to end of png bits + uint32_t uWidth; //!< image width + uint32_t uHeight; //!< image height + + // misc info + int8_t iBitDepth; //!< bit depth of the image + int8_t iColourType; //!< colour type of the image + int8_t iCompression; //!< compression method of the image + int8_t iFilter; //!< filter method of the image + int8_t iInterlace; //!< interlace method of the image +} DirtyPngHdrT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create module state +DIRTYCODE_API DirtyPngStateT *DirtyPngCreate(void); + +// done with module state +DIRTYCODE_API void DirtyPngDestroy(DirtyPngStateT *pState); + +// identify if image is a png or not +DIRTYCODE_API int32_t DirtyPngIdentify(const uint8_t *pImageData, uint32_t uImageLen); + +// parse PNG into something we can use +DIRTYCODE_API int32_t DirtyPngParse(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr, const uint8_t *pImageData, const uint8_t *pImageEnd); + +// decode a PNG image +DIRTYCODE_API int32_t DirtyPngDecodeImage(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr, uint8_t *pImageData, int32_t iBufWidth, int32_t iBufHeight); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtypng_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/misc/privilegeapi.h b/src/thirdparty/dirtysdk/include/DirtySDK/misc/privilegeapi.h new file mode 100644 index 00000000..06edcf2a --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/misc/privilegeapi.h @@ -0,0 +1,118 @@ +/*H*************************************************************************************/ +/*! + + \File privilegeapi.h + + \Description + Check first party privileges and parental controls + + \Copyright + Copyright (c) Electronic Arts 2013. + + \Version 09/02/13 (mcorcoran) First Version +*/ +/*************************************************************************************H*/ + +#ifndef _privilegeapi_h +#define _privilegeapi_h + +/*! +\Moduledef PrivilegeApi PrivilegeApi +\Modulemember User +*/ +//@{ + + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct PrivilegeApiRefT PrivilegeApiRefT; + +/*** Variables *************************************************************************/ + +typedef enum PrivilegeApiPrivE +{ + PRIVILEGEAPI_PRIV_INVALID = (0), + PRIVILEGEAPI_PRIV_COMMUNICATION_VOICE_INGAME = (-1), + PRIVILEGEAPI_PRIV_COMMUNICATION_VOICE_SKYPE = (-2), + PRIVILEGEAPI_PRIV_VIDEO_COMMUNICATIONS = (-3), + PRIVILEGEAPI_PRIV_COMMUNICATIONS = (-4), + PRIVILEGEAPI_PRIV_USER_CREATED_CONTENT = (-5), + PRIVILEGEAPI_PRIV_MULTIPLAYER_SESSIONS_REALTIME = (-6), + PRIVILEGEAPI_PRIV_DOWNLOAD_FREE_CONTENT = (-7), + PRIVILEGEAPI_PRIV_FITNESS_UPLOAD = (-8), + PRIVILEGEAPI_PRIV_VIEW_FRIENDS_LIST = (-9), + PRIVILEGEAPI_PRIV_SHARE_KINECT_CONTENT = (-10), + PRIVILEGEAPI_PRIV_MULTIPLAYER_PARTIES = (-11), + PRIVILEGEAPI_PRIV_CLOUD_GAMING_MANAGE_SESSION = (-12), + PRIVILEGEAPI_PRIV_CLOUD_GAMING_JOIN_SESSION = (-13), + PRIVILEGEAPI_PRIV_CLOUD_SAVED_GAMES = (-14), + PRIVILEGEAPI_PRIV_PREMIUM_CONTENT = (-15), + PRIVILEGEAPI_PRIV_INTERNET_BROWSER = (-16), + PRIVILEGEAPI_PRIV_SUBSCRIPTION_CONTENT = (-17), + PRIVILEGEAPI_PRIV_PREMIUM_VIDEO = (-18), + PRIVILEGEAPI_PRIV_GAME_DVR = (-19), + PRIVILEGEAPI_PRIV_SOCIAL_NETWORK_SHARING = (-20), + PRIVILEGEAPI_PRIV_PURCHASE_CONTENT = (-21), + PRIVILEGEAPI_PRIV_PROFILE_VIEWING = (-22), + PRIVILEGEAPI_PRIV_PRESENCE = (-23), + PRIVILEGEAPI_PRIV_CONTENT_AUTHOR = (-24), + PRIVILEGEAPI_PRIV_UNSAFE_PROGRAMMING = (-25), + PRIVILEGEAPI_PRIV_MULTIPLAYER_SESSIONS_ASYNC = (-26), + PRIVILEGEAPI_PRIV_ONLINE_ACCESS = (-27), + PRIVILEGEAPI_PRIV_EA_ACCESS = (256) +} PrivilegeApiPrivE; + +#define PRIVILEGEAPI_STATUS_NONE (0x0000) +#define PRIVILEGEAPI_STATUS_IN_PROGRESS (0x0001) +#define PRIVILEGEAPI_STATUS_GRANTED (0x0002) +#define PRIVILEGEAPI_STATUS_GRANTED_FRIENDS_ONLY (0x0004) +#define PRIVILEGEAPI_STATUS_RESTRICTED (0x0008) +#define PRIVILEGEAPI_STATUS_BANNED (0x0010) +#define PRIVILEGEAPI_STATUS_PURCHASE_REQUIRED (0x0020) +#define PRIVILEGEAPI_STATUS_ABORTED (0x0040) +#define PRIVILEGEAPI_STATUS_ERROR (0x0080) +#define PRIVILEGEAPI_STATUS_ADDITIONAL_CHECKS_REQUIRED (0x0100) +#define PRIVILEGEAPI_STATUS_UNDERAGE (0x0200) + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// creates the module +DIRTYCODE_API PrivilegeApiRefT *PrivilegeApiCreate(void); + +// destroys the module +DIRTYCODE_API int32_t PrivilegeApiDestroy(PrivilegeApiRefT *pRef); + +// checks for the existance of 1 or more privileges for a given user +DIRTYCODE_API int32_t PrivilegeApiCheckPrivilegesAsync(PrivilegeApiRefT *pRef, int32_t iUserIndex, const PrivilegeApiPrivE *pPrivileges, int32_t iPrivilegeCount, int32_t *pHint); + +// checks for the existance of 1 or more privileges for a given user, and show the user a system provided UI in the even tthe user does not have the requested privileges +DIRTYCODE_API int32_t PrivilegeApiCheckPrivilegesAsyncWithUi(PrivilegeApiRefT *pRef, int32_t iUserIndex, const PrivilegeApiPrivE *pPrivileges, int32_t iPrivilegeCount, int32_t *pHint, const char *pUiMessage); + +// checks/polls for the result of a given request id returned from the PrivilegeApiCheckPrivilegesAsync() functions +DIRTYCODE_API int32_t PrivilegeApiCheckResult(PrivilegeApiRefT *pRef, int32_t iRequestId); + +// frees up a request id once you are finished with it +DIRTYCODE_API int32_t PrivilegeApiReleaseRequest(PrivilegeApiRefT *pRef, int32_t iRequestId); + +// aborts a currently executing async request +DIRTYCODE_API int32_t PrivilegeApiAbort(PrivilegeApiRefT *pRef, int32_t iRequestId); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/misc/qosclient.h b/src/thirdparty/dirtysdk/include/DirtySDK/misc/qosclient.h new file mode 100644 index 00000000..53475883 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/misc/qosclient.h @@ -0,0 +1,76 @@ +/*H********************************************************************************/ +/*! + \File qosclient.h + + \Description + Main include for the Quality of Service module. + + \Copyright + Copyright (c) 2017 Electronic Arts Inc. + + \Version 1.0 04/07/2017 (cvienneau) 2.0 +*/ +/********************************************************************************H*/ + +#ifndef _qosclient_h +#define _qosclient_h + +/*! +\Moduledef QosClient QosClient +\Modulemember Misc +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/misc/qoscommon.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Forward Declarations *********************************************************/ + +/*** Type Definitions *************************************************************/ + +//! opaque module ref +typedef struct QosClientRefT QosClientRefT; + +//! event callback function prototype +typedef void (QosClientCallbackT)(QosClientRefT *pQosClient, QosCommonProcessedResultsT *pResults, void *pUserData); + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create the module state +DIRTYCODE_API QosClientRefT *QosClientCreate(QosClientCallbackT *pCallback, void *pUserData, uint16_t uListenPort); + +// let the module do processing +DIRTYCODE_API void QosClientUpdate(QosClientRefT *pQosClient); + +// begin QOS process managed by the QOS coordinator +DIRTYCODE_API void QosClientStart(QosClientRefT *pQosClient, const char *pStrCoordinatorAddr, uint16_t uPort, const char * strQosProfile); + +// change the behavior of the QOS system +DIRTYCODE_API int32_t QosClientControl(QosClientRefT *pQosClient, int32_t iControl, int32_t iValue, void *pValue); + +// get the status, of the QOS system +DIRTYCODE_API int32_t QosClientStatus(QosClientRefT *pQosClient, int32_t iSelect, int32_t iData, void *pBuf, int32_t iBufSize); + +// destroy the module state +DIRTYCODE_API void QosClientDestroy(QosClientRefT *pQosClient); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _filename_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/misc/qoscommon.h b/src/thirdparty/dirtysdk/include/DirtySDK/misc/qoscommon.h new file mode 100644 index 00000000..2246cfe0 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/misc/qoscommon.h @@ -0,0 +1,297 @@ +/*H********************************************************************************/ +/*! + \File qoscommon.h + + \Description + This code declares shared items between client and server of the QOS system. + + \Copyright + Copyright (c) 2017 Electronic Arts Inc. + +*/ +/********************************************************************************H*/ + +#ifndef _qoscommon_h +#define _qoscommon_h + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtyaddr.h" +#include "DirtySDK/dirtysock/dirtynet.h" + +/*** Defines **********************************************************************/ +#define QOS_COMMON_HMAC_TYPE (CRYPTHASH_SHA256) //!< a secure strong hashing algorithm +#define QOS_COMMON_HMAC_SIZE (32) //!< CRYPTSHA256_HASHSIZE +#define QOS_COMMON_SECURE_KEY_LENGTH (16) //!< length of random data as key, in generation of hmac +#define QOS_COMMON_SIZEOF_PROBE_DATA (45) //!< total byte count of probe, excluding hmac +#define QOS_COMMON_PROBE_PROTOCOL_ID ('qos2') //!< probe packets will all start with this to identify them as being the QOS 2 protocol +#define QOS_COMMON_PROBE_VERSION_MAJOR (2) //!< identifies changes to QosCommonProbePacketT, 2 bytes, first byte indicates api breaking +#define QOS_COMMON_PROBE_VERSION_MINOR (0) //!< second byte compatible bug fixes. +#define QOS_COMMON_PROBE_VERSION ((QOS_COMMON_PROBE_VERSION_MAJOR << 8) | QOS_COMMON_PROBE_VERSION_MINOR) //!< the combined major, minor version bytes. +#define QOS_COMMON_MIN_PACKET_SIZE (QOS_COMMON_SIZEOF_PROBE_DATA + QOS_COMMON_HMAC_SIZE) //!< size of on wire representation of the probe packet +#define QOS_COMMON_MAX_PACKET_SIZE (SOCKET_MAXUDPRECV) //!< maximum probe/packet length +#define QOS_COMMON_MAX_PROBE_COUNT (32) //!< maximum probes per request +#define QOS_COMMON_MAX_SITES (32) //!< maximum sites we can test against at one time +#define QOS_COMMON_MAX_CONTROL_CONFIGS (6) //!< maximum number of QosClientControl selectors we can include in the request +#define QOS_COMMON_MAX_TESTS (4) //!< latency, bandwidthUP, bandwidthDown; and extra room for something we haven't considered +#define QOS_COMMON_MAX_RESULTS (QOS_COMMON_MAX_SITES * QOS_COMMON_MAX_TESTS) //!< highest number of possible results we can generate +#define QOS_COMMON_MAX_URL_LENGTH (256) //!< size used for URL strings +#define QOS_COMMON_MAX_RPC_STRING (128) //!< size used for typical strings +#define QOS_COMMON_MAX_RPC_STRING_SHORT (16) //!< size used for strings we expect to be very short +#define QOS_COMMON_MAX_RPC_BODY_SIZE (1024 * 200) //!< rpc messages should never exceed this size, raw results are our biggest message +#define QOS_COMMON_DEFAULT_RPC_BODY_SIZE (QOS_COMMON_MAX_RPC_BODY_SIZE / 10) //!< An amount of space that will usually be enough to receive an rpc message into +#define QOS_COMMON_CONTENT_TYPE ("application/grpc") //!< http content type, everything is protobuff +#define QOS_COMMON_SERVER_URL ("/eadp.qoscoordinator.QOSCoordinatorRegistration/registerServer") //!< used by server to register to the QOS server, and heartbeat to refresh its availability +#define QOS_COMMON_CLIENT_URL ("/eadp.qoscoordinator.QOSCoordinator/ClientCall") //!< used by client to request config and push back final results + + +// flags contained in probe packets +#define QOS_COMMON_PACKET_FLAG_LAST_PROBE (1) //!< set if the client expects this is the last packet in this test + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! just enough space to hold a address family, port, and an v4 or v6 address. Used for storage or communicating address on the wire, never used to actually send/recv. +typedef struct QosCommonAddrT +{ + union + { + union + { + uint8_t aBytes[16]; + uint16_t aWords[8]; + uint32_t aDwords[4]; + }v6; + uint32_t v4; + }addr; + uint16_t uFamily; + uint16_t uPort; +} QosCommonAddrT; + +//! describes the configuration for a particular QOS test +typedef struct QosCommonTestT +{ + char strTestName[QOS_COMMON_MAX_RPC_STRING_SHORT]; //!< name of the test + char strSiteName[QOS_COMMON_MAX_RPC_STRING_SHORT]; //!< alias of target QOS server + uint16_t uProbeSizeUp; //!< minimum size of the probe, the probe will be padded with extra data to reach this size + uint16_t uProbeSizeDown; //!< minimum size of the probe, the probe will be padded with extra data to reach this size + uint16_t uTimeout; //!< maximum amount of time before giving up on completing the test + uint16_t uMinTimeBetwenProbes; //!< minimum amount of time between each probe sent from the client + uint16_t uTimeTillResend; //!< if we haven't got the expected number of responses back in this much time, we will send more probes + uint16_t uInitSyncTimeout; //!< the amount of time we might wait to synchronize all requests together + uint8_t uProbeCountUp; //!< number of probes to send to the server + uint8_t uProbeCountDown; //!< number of probes there server should respond with for each probe received + uint8_t uResendExtraProbeCount; //!< in the case of finding packet loss, we request the lost packets again with a few extras, to reduce the chance that those are lost too + uint8_t uAcceptableLostProbeCount; //!< typically we would receive (uProbeCountUp * uProbeCountDown) probes, this indicates how many less probes we can receive without triggering a re-send +} QosCommonTestT; + +//! describes QosClientControl overrides that should be executed before doing any QosTests +typedef struct QosCommonControlConfigT +{ + char strValue[QOS_COMMON_MAX_RPC_STRING]; //!< string value, such as QosClientControl(mQosClient, 'sprt', 0, strValue); + char strControl[5]; //!< dirty sdks four character code identifier, usually in the form of 'wxzy' + int32_t iValue; //!< integer value, such as QosClientControl(mQosClient, 'lprt', iValue, NULL); +} QosCommonControlConfigT; + +//! describes a site, QOS Tests are performed against a QOS/ping Site +typedef struct QosCommonSiteT +{ + char strSiteName[QOS_COMMON_MAX_RPC_STRING_SHORT]; //!< identifies where a site is, such as bio-sjc + char strProbeAddr[QOS_COMMON_MAX_RPC_STRING]; //!< url to communicate with the server + uint8_t aSecureKey[QOS_COMMON_SECURE_KEY_LENGTH]; //!< key used in generating hmac + uint16_t uProbePort; //!< port to use when communicating + uint16_t uProbeVersion; //!< the version the site is operating with +} QosCommonSiteT; + +//! a collection of sites, control configs and tests to describe what actions the client should be taking +typedef struct QosCommonClientConfigT +{ + QosCommonSiteT aSites[QOS_COMMON_MAX_SITES]; //!< list of sites that are potential targets + QosCommonControlConfigT aControlConfigs[QOS_COMMON_MAX_CONTROL_CONFIGS]; //!< list of control settings to be run before doing tests + QosCommonTestT aQosTests[QOS_COMMON_MAX_TESTS]; //!< list tests to be done + QosCommonAddrT clientAddressFromCoordinator; //!< the client must report its external address to the QOS server, the coordinator will initialize this to what it sees the clients address as + uint8_t uNumSites; //!< number of QosCommonSiteT contained in aSites + uint8_t uNumControlConfigs; //!< number of QosCommonControlConfigT contained in aControlConfigs + uint8_t uNumQosTests; //!< number of QosCommonTestT contained aQosTests +} QosCommonClientConfigT; + +//!< a firewall or NAT (network address translation) indicates how difficult it is to make a connection with this location +typedef enum QosCommonFirewallTypeE +{ + QOS_COMMON_FIREWALL_UNKNOWN, + QOS_COMMON_FIREWALL_OPEN, + QOS_COMMON_FIREWALL_MODERATE, + QOS_COMMON_FIREWALL_STRICT, + QOS_COMMON_FIREWALL_NUMNATTYPES //!< max number of NAT types, must be at the end +} QosCommonFirewallTypeE; + +//! contains latency and bandwidth results for a given site +typedef struct QosCommonTestResultT +{ + char strSiteName[QOS_COMMON_MAX_RPC_STRING_SHORT]; + uint32_t uMinRTT; + uint32_t uUpbps; + uint32_t uDownbps; + uint32_t hResult; +} QosCommonTestResultT; + +//! the coordinator will examine a QosCommonRawResultsT and produce usable QosCommonProcessedResultsT +typedef struct QosCommonProcessedResultsT +{ + QosCommonTestResultT aTestResults[QOS_COMMON_MAX_SITES]; //!< an ordered list (by "best" site, coordinator's decision) of all test results + QosCommonAddrT clientExternalAddress; //!< this is the address the coordinator recommends using for communication to this client + QosCommonFirewallTypeE eFirewallType; //!< this is what the coordinator classifies the firewall type to be + uint32_t uNumResults; //!< number of results found in aTestResults + uint32_t uTimeTillRetry; //!< if this is != 0, the QOS server wants us to check back in this many ms for a new test + uint32_t hResult; //!< holds the error/status code of the request +} QosCommonProcessedResultsT; + +//! the coordinator responds with configuration or results +typedef struct QosCommonCoordinatorToClientResponseT +{ + QosCommonClientConfigT configuration; //!< what does the coordinator want the client to do + QosCommonProcessedResultsT results; //!< contains the processed data from the coordinator, this will be provided if there coordinator doesn't have more tests for the client to do + uint32_t uServiceRequestID; //!< number uniquely identifies this transaction taking place between client and coordinator +} QosCommonCoordinatorToClientResponseT; + +//! sent to and from the QOS server as a udp probe, note QosCommonSerializeProbePacket and QosCommonDeserializeProbePacket determines on wire packing and order +typedef struct QosCommonProbePacketT +{ + QosCommonAddrT clientAddressFromService; //!< initially the client address from coordinator, however address from server prospective takes precedence, used to authenticate the packet is coming from the address that generated it + uint32_t uProtocol; //!< QOS 2.0 packets will always contain 'qos2' for easy identification. + uint32_t uServiceRequestId; //!< provided by the QosCoordinator, unique to the client doing multiple QOS actions, used to identify which server resources this client is using + uint32_t uServerReceiveTime; //!< time the server received this probe from the client + uint16_t uServerSendDelta; //!< duration the server held onto the packet before sending the response probe, this is latency added by the server process + uint16_t uVersion; //!< uniquely identifies protocol + uint16_t uProbeSizeUp; //!< indicates how big this probe is, including any padding for bandwidth + uint16_t uProbeSizeDown; //!< indicates how big this probe is, including any padding for bandwidth + uint16_t uClientRequestId; //!< provided by the client, unique to a particular QOS action, used to pair request and responses together + uint8_t uProbeCountUp; //!< count index of this probe + uint8_t uProbeCountDown; //!< from client (number of probes there server should respond with for each probe received) from server (count index of this probe) + uint8_t uExpectedProbeCountUp; //!< number of probes the client expects to send to the sever in this test (assuming no packet loss) +} QosCommonProbePacketT; + +//! a single QOS probe result information +typedef struct QosCommonProbeResultT +{ + uint32_t uClientSendTime; //!< time the client initially sent this QOS probe + uint32_t uServerReceiveTime; //!< time the server received this probe from the client (note that server and client time are not in sync) + uint16_t uServerSendDelta; //!< duration the server held onto the packet before sending the response probe, this is latency added by the server process + uint16_t uClientReceiveDelta; //!< duration since sending before the client received response from the QOS server for this probe +} QosCommonProbeResultT; + +//!< after the client performs the tests, raw results are provided to the coordinator for analysis +//! Its expected each QosCommonRawResultsT can be used to generate latency values, bandwidth values, with two QosCommonRawResultsT firewall values can be calculated +typedef struct QosCommonRawResultsT +{ + QosCommonProbeResultT aProbeResult[QOS_COMMON_MAX_PROBE_COUNT]; //!< a list of individual probe timings + QosCommonAddrT clientAddressFromServer; //!< clients address from prospective of the QOS server + char strSiteName[QOS_COMMON_MAX_RPC_STRING_SHORT]; //!< identifies where a site is, such as bio-sjc + char strTestName[QOS_COMMON_MAX_RPC_STRING_SHORT]; //!< identifies which test, such as latency, bandwidthUp, bandwidthDown + uint32_t hResult; //!< holds the error/status code of the request + uint8_t uProbeCountUp; //!< number of probes the request sent when all retrying and everything is done (usually same as uProbeCountRequestedUp if no probes were lost, otherwise we send more) + uint8_t uProbeCountDown; //!< number of valid probes from this request that came back from the server (usually same as uProbeCountRequestedDown at the end unless an error or probes lost) + uint8_t uProbeCountHighWater; //!< the highest count of probe we receive back + //note, uProbeCountHighWater, uProbeCountDown, and uProbeCountUp aren't shared with the coordinator +} QosCommonRawResultsT ; + +//! data the client will tell the coordinator about itself (aside from test results) to help it make its decisions +typedef struct QosCommonClientModifiersT +{ + QosCommonAddrT clientAddressInternal; //!< address the client believes it is using + char strPlatform[QOS_COMMON_MAX_RPC_STRING_SHORT]; //!< a string describing which platform the client is + QosCommonFirewallTypeE eOSFirewallType; //!< the firewall type that the client OS suggests it might be + uint32_t uPacketQueueRemaining; //!< number of spots left in the packet queue after the client has done its tests, a 0 value indicates a problem + uint32_t uStallCountCoordinator; //!< number of stall events encountered when attempting to communicate with the coordinator + uint32_t uStallDurationCoordinator; //!< total duration of time spent stalled while communicating with the coordinator + uint32_t uStallCountProbe; //!< number of stall events encountered when attempting to send probes to the qos server + uint32_t uStallDurationProbe; //!< total duration of time spent stalled while communicating with the qos server + uint32_t hResult; //!< current error status of the QosClient module, can be used to report errors to the coordinator +} QosCommonClientModifiersT; + +//! the client informs the coordinator of who it is and any results it has gathered in order to get configuration or processed "final" results +typedef struct QosCommonClientToCoordinatorRequestT +{ + QosCommonRawResultsT aResults[QOS_COMMON_MAX_RESULTS]; //!< a set of raw results for each site, for each test performed + char strQosProfile[QOS_COMMON_MAX_RPC_STRING]; //!< a string describing which setting the coordinator should use for this title. (likely service name, like fifa-pc-2017) + uint32_t uServiceRequestID; //!< number uniquely identifies this transaction taking place between client and coordinator + uint32_t uNumResults; //!< number results in aResults, if the client has results to report + uint16_t uProbeVersion; //!< uniquely identifies protocol + QosCommonClientModifiersT clientModifiers; //!< a set of fields the client wants to share with the coordinator such as health information to use in decision process +} QosCommonClientToCoordinatorRequestT; + +//! the QOS server will request to be registered with the coordinator +typedef struct QosCommonServerToCoordinatorRequestT +{ + char strAddr[QOS_COMMON_MAX_RPC_STRING]; //!< url to communicate with the server + char strSiteName[QOS_COMMON_MAX_RPC_STRING_SHORT]; //!< location where this server resides ie bio-sjc + char strPool[QOS_COMMON_MAX_RPC_STRING]; //!< segregate servers at a given location, such as only fifa is using this server or only for special qa tests + uint8_t aSecureKey[QOS_COMMON_SECURE_KEY_LENGTH]; //!< key used to generate hmac + uint16_t uPort; //!< port to use when communicating + uint16_t uCapacityPerSec; //!< maximum number of clients the coordinator should send to the server per second + uint16_t uLastLoadPerSec; //!< number of qos tests started last second + uint16_t uProbeVersion; //!< the probe packet version this server will operate with + uint16_t uUpdateInterval; //!< amount of time in ms that the coordinator should wait before expecting the next heartbeat + uint8_t bShuttingDown; //!< indicates if this server has begun to shutdown, if so no more clients should be sent to this server +} QosCommonServerToCoordinatorRequestT; + +//! registration message is for debug purpose only +typedef struct QosCommonCoordinatorToServerResponseT +{ + char strRegistrationMessage[QOS_COMMON_MAX_RPC_STRING]; //!< message provided by the coordinator for this server, likely explaining any error status + uint32_t uMinServiceRequestID; //!< don't accept service request id's older than this +} QosCommonCoordinatorToServerResponseT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// QosCommonClientToCoordinatorRequestT helpers +DIRTYCODE_API uint8_t* QosCommonClientToCoordinatorRequestEncode(const QosCommonClientToCoordinatorRequestT *pClientToCoordinatorRequest, uint8_t *pBuffer, uint32_t uBuffSize, uint32_t *pOutSize); +DIRTYCODE_API int32_t QosCommonClientToCoordinatorEstimateEncodedSize(const QosCommonClientToCoordinatorRequestT *pClientToCoordinatorRequest); +DIRTYCODE_API void QosCommonClientToCoordinatorPrint(const QosCommonClientToCoordinatorRequestT *pClientToCoordinatorRequest, uint32_t uLogLevel); + +// QosCommonCoordinatorToClientResponseT helpers +DIRTYCODE_API int32_t QosCommonCoordinatorToClientResponseDecode(QosCommonCoordinatorToClientResponseT *pResponse, const uint8_t *pBuffer, uint32_t uBuffSize); +DIRTYCODE_API void QosCommonCoordinatorToClientPrint(const QosCommonCoordinatorToClientResponseT *pResponse, uint32_t uLogLevel); + +// QosCommonServerToCoordinatorRequestT helpers +DIRTYCODE_API uint8_t* QosCommonServerToCoordinatorRequestEncode(const QosCommonServerToCoordinatorRequestT *pServerRegistrationRequest, uint8_t *pBuffer, uint32_t uBuffSize, uint32_t *pOutSize); + +// QosCommonCoordinatorToServerResponseT helpers +DIRTYCODE_API int32_t QosCommonCoordinatorToServerResponseDecode(QosCommonCoordinatorToServerResponseT *pServerRegistrationResponse, const uint8_t *pBuffer, uint32_t uBuffSize); + +// helpers for address +DIRTYCODE_API char* QosCommonAddrToString(const QosCommonAddrT *pAddr, char *pBuffer, int32_t iBufSize); +DIRTYCODE_API int32_t QosCommonStringToAddr(char *pStrIn, QosCommonAddrT *pOutAddr); +DIRTYCODE_API uint8_t QosCommonIsAddrEqual(QosCommonAddrT *pAddr1, QosCommonAddrT *pAddr2); +DIRTYCODE_API uint8_t QosCommonIsRemappedAddrEqual(QosCommonAddrT *pAddr1, struct sockaddr *pAddr2); +DIRTYCODE_API void QosCommonConvertAddr(QosCommonAddrT *pTargetAddr, struct sockaddr *pSourceAddr); + +// helpers for serializing and deserializing packets +DIRTYCODE_API uint8_t QosCommonSerializeProbePacket(uint8_t *pOutBuff, uint32_t uBuffSize, const QosCommonProbePacketT *pInPacket, uint8_t *pSecureKey); +DIRTYCODE_API uint8_t QosCommonDeserializeProbePacket(QosCommonProbePacketT *pOutPacket, uint8_t *pInBuff, uint8_t *pSecureKey1, uint8_t *pSecureKey2); +DIRTYCODE_API void QosCommonDeserializeProbePacketInsecure(QosCommonProbePacketT *pOutPacket, uint8_t *pInBuff); +DIRTYCODE_API uint16_t QosCommonDeserializeClientRequestId(uint8_t *pInBuff); +DIRTYCODE_API uint32_t QosCommonDeserializeServiceRequestId(uint8_t *pInBuff); + +// helpers for version numbering +DIRTYCODE_API uint16_t QosCommonMakeVersion(uint8_t uMajor, uint8_t uMinor); +DIRTYCODE_API void QosCommonGetVersion(uint16_t uVersion, uint8_t *uMajor, uint8_t *uMinor); +DIRTYCODE_API uint8_t QosCommonIsCompatibleVersion(uint16_t uVersion1, uint16_t uVersion2); +DIRTYCODE_API uint8_t QosCommonIsCompatibleProbeVersion(uint16_t uVersion); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _qoscommon_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/misc/userapi.h b/src/thirdparty/dirtysdk/include/DirtySDK/misc/userapi.h new file mode 100644 index 00000000..30528890 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/misc/userapi.h @@ -0,0 +1,280 @@ +/*H*************************************************************************************************/ +/*! + \File userapi.h + + \Description + Expose first party player information + + \Notes + This API currently only supports Gen 4 platforms (XBox One and PS4). + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2001-2013. ALL RIGHTS RESERVED. + + \Version 05/12/13 (mcorcoran) First Version + \Version 12/02/14 (amakoukji) API revamped +*/ +/*************************************************************************************************H*/ + +#ifndef _userapi_h +#define _userapi_h + +/*! +\Moduledef UserApi UserApi +\Modulemember User +*/ +//@{ + +/*** Include files ********************************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtyuser.h" + +#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) +#include "DirtySDK/misc/xboxone/userapixboxone.h" +#endif + +/*** Defines **************************************************************************************/ + +//!< Maximum sizes of various strings +#define USERAPI_NOTIFY_LIST_MAX_SIZE (50) +#define USERAPI_GAMERTAG_MAXLEN (128) +#define USERAPI_AVATAR_URL_MAXLEN (256) +#define USERAPI_DISPLAYNAME_MAXLEN (256) +#define USERAPI_PLATFORM_NAME_MAXLEN (64) +#define USERAPI_TITLE_NAME_MAXLEN (128) +#define USERAPI_TITLE_ID_MAXLEN (64) +#define USERAPI_RICH_PRES_MAXLEN (512) +#define USERAPI_GAME_DATA_MAXLEN (172) + +#define USERAPI_MASK_PROFILE (0x01) +#define USERAPI_MASK_PRESENCE (0x02) +#define USERAPI_MASK_RICH_PRESENCE (0x04) +#define USERAPI_MASK_PROFILES (0x08) + +/*** Macros ***************************************************************************************/ + +/*** Definitions *****************************************************************************/ + +//!< UserApi Error types +typedef enum UserApiEventErrorE +{ + USERAPI_ERROR_OK = 0, // Success code + + USERAPI_ERROR_REQUEST_FAILED = -100, // Failed to retrieve id + USERAPI_ERROR_REQUEST_NOT_FOUND, // User wasn't found + USERAPI_ERROR_REQUEST_FORBIDDEN, // local user does not have access to requested user information + USERAPI_ERROR_REQUEST_SERVER_ERROR, // Internal server error + USERAPI_ERROR_REQUEST_UNAVAILABLE, // Service unavailable + USERAPI_ERROR_REQUEST_TIMEOUT, // Profile response was not received on time + USERAPI_ERROR_UNSUPPORTED, + USERAPI_ERROR_INPROGRESS, + USERAPI_ERROR_REQUEST_PARSE_FAILED, + USERAPI_ERROR_NO_USER, + USERAPI_ERROR_FULL +} UserApiEventErrorE; + +//!< Type of first party notifications +// Note that minimal information is returned by the notifications themselves and a follow up query to the first party will often be necessary to update the internal state +typedef enum UserApiNotifyTypeE +{ + USERAPI_NOTIFY_PRESENCE_UPDATE = 0, + USERAPI_NOTIFY_TITLE_UPDATE, + USERAPI_NOTIFY_RICH_PRESENCE_UPDATE, + USERAPI_NOTIFY_PROFILE_UPDATE +} UserApiNotifyTypeE; + +//!< Categories of data for UserApi callbacks +typedef enum UserApiEventTypeE +{ + USERAPI_EVENT_DATA = 0, // The current event has user data available + USERAPI_EVENT_END_OF_LIST // The current event has completion data available through EventDetails.EndOfList +} UserApiEventTypeE; + +//!< List of possible online statuses +typedef enum UserApiOnlineStatusE +{ + USERAPI_PRESENCE_UNKNOWN = 0, + USERAPI_PRESENCE_OFFLINE, + USERAPI_PRESENCE_ONLINE, + USERAPI_PRESENCE_AWAY +} UserApiOnlineStatusE; + +//!< Rich presence response data structure +typedef struct UserApiRichPresenceT +{ + char strData[USERAPI_RICH_PRES_MAXLEN + 1]; // Rich presence string + char strGameData[USERAPI_GAME_DATA_MAXLEN + 1]; // GameData Base64 string + void *pRawData; +} UserApiRichPresenceT; + +//!< Presence data response structure +typedef struct UserApiPresenceT +{ + UserApiOnlineStatusE ePresenceStatus; // online, offline, away or unknown + char strPlatform[USERAPI_PLATFORM_NAME_MAXLEN+1]; // name of the console the user is on + char strTitleName[USERAPI_TITLE_NAME_MAXLEN+1]; // title name + char strTitleId[USERAPI_TITLE_ID_MAXLEN+1]; // title identifier + uint8_t bIsPlayingSameTitle; + void *pRawData; +} UserApiPresenceT; + +//!< Profile data response structure +typedef struct UserApiProfileT +{ + char strDisplayName[USERAPI_DISPLAYNAME_MAXLEN + 1]; // The users real name. This string will contain strGamerTag if the real name is not available + char strGamertag [USERAPI_GAMERTAG_MAXLEN + 1]; // The users gamertag for XBox One or onlineId for PS4 + char strAvatarUrl [USERAPI_AVATAR_URL_MAXLEN + 1]; // URI pointing to the avatar image for XBox One and PS4 + uint32_t uLocale; // The users location and language encoded using the LOBBYAPI macros in dirtylang.h + #if defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) + UserApiAccessibilityT Accessibility; // Contains the accessibility settings + #endif + void *pRawData; // pointer to object that contain all first part data received +} UserApiProfileT; + +//!< General response structure +typedef struct UserApiUserDataT +{ + DirtyUserT DirtyUser; // A DirtySDK representation or the native user on the current platform + uint32_t uUserDataMask; // A mask value to determine which values below are valid + UserApiProfileT Profile; // Valid when uUserDataMask & USERAPI_MASK_PROFILE > 0 + UserApiPresenceT Presence; // Valid when uUserDataMask & USERAPI_MASK_PRESENCE > 0 + UserApiRichPresenceT RichPresence; // Valid when uUserDataMask & USERAPI_MASK_RICH_PRESENCE > 0 +} UserApiUserDataT; + +//!< Request metadata response structure +typedef struct UserApiEndOfListT +{ + int32_t iTotalRequested; + int32_t iTotalReceived; + int32_t iTotalErrors; +} UserApiEndOfListT; + +//!< Container union for responses +typedef union UserApiEventDetailsT +{ + UserApiUserDataT UserData; // Valid when eEventType == USERAPI_EVENT_DATA + UserApiEndOfListT EndOfList; // Valid when eEventType == USERAPI_EVENT_END_OF_LIST +} UserApiEventDetailsT; + +//!< User metadata fpr USERAPI_EVENT_DATA +typedef struct UserApiEventDataT +{ + UserApiEventErrorE eError; // Error code associated with this event + UserApiEventTypeE eEventType; // Event type used to specify the type of data in EventDetails + uint32_t uUserIndex; // Index of the user that made the request + UserApiEventDetailsT EventDetails; // Contains event details specific for the eEventType event type +} UserApiEventDataT; + +//!< Response from data post style request +typedef struct UserApiPostResponseT +{ + uint32_t uUserIndex; // Index of the user that made the request + UserApiEventErrorE eError; // HTTP Error code associated with this event + const char *pMessage; // Message if applicable +} UserApiPostResponseT; + +//!< opaque module ref +typedef struct UserApiRefT UserApiRefT; + +//!< Title change notification data +typedef struct UserApiNotifyTitleDataT +{ + DirtyUserT DirtyUser; // user who's presence was updated + uint32_t uTitleId; // title that was updated + uint32_t uUserIndex; // user the notification is for +} UserApiNotifyTitleDataT; + +//!< Presence change notification data, note that this remains incomplete since minimal data is provided by first parties +typedef struct UserApiNotifyPresenceDataT +{ + DirtyUserT DirtyUser; // user who's presence was updated + uint32_t uUserIndex; // user the notification is for +} UserApiNotifyPresenceDataT; + +//!< Rich presence change notification data, note that this remains incomplete since minimal data is provided by first parties +typedef struct UserApiNotifyRichPresenceDataT +{ + DirtyUserT DirtyUser; // user who's presence was updated + uint32_t uUserIndex; // user the notification is for +} UserApiNotifyRichPresenceDataT; + +//!< Rich presence change notification data, note that this remains incomplete since minimal data is provided by first parties +typedef struct UserApiNotifyProfileUpdateDataT +{ + DirtyUserT DirtyUser; // user who's presence was updated + uint32_t uUserIndex; // user the notification is for +} UserApiNotifyProfileUpdateDataT; + +//!< container union for notification data +typedef union UserApiNotifyDataT +{ + UserApiNotifyTitleDataT TitleData; // Valid when eNotifyType == USERAPI_NOTIFY_TITLE_UPDATE + UserApiNotifyPresenceDataT PresenceData; // Valid when eNotifyType == USERAPI_NOTIFY_PRESENCE + UserApiNotifyRichPresenceDataT RichPresenceData; // Valid when eNotifyType == USERAPI_NOTIFY_RICH_PRESENCE + UserApiNotifyProfileUpdateDataT ProfileUpdateData; // Valid When eNotifyType == USERAPI_NOTIFY_PROFILE_UPDATE +} UserApiNotifyDataT; + +//!< callback types +typedef void (UserApiCallbackT) (UserApiRefT *pRef, UserApiEventDataT *pUserApiEventData, void *pUserData); +typedef void (UserApiPostCallbackT) (UserApiRefT *pRef, UserApiPostResponseT *pResponse, void *pUserData); +typedef void (UserApiUpdateCallbackT)(UserApiRefT *pRef, UserApiNotifyTypeE eNotifyType, UserApiNotifyDataT *pData, void *pUserData); + +/*** Function Prototypes **************************************************************************/ + +/*** Variables ************************************************************************************/ + +/*** Private Functions ****************************************************************************/ + +/*** Public Functions *****************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// Starts UserApi. +DIRTYCODE_API UserApiRefT *UserApiCreate(void); + +// Starts shutting down module. Module will not accept new requests, and will abort ongoing ones. Also, registered UserApiCallbackT callback will not be called even if there is data available for processing +DIRTYCODE_API int32_t UserApiDestroy(UserApiRefT *pRef); + +// Get status information. +DIRTYCODE_API int32_t UserApiStatus(UserApiRefT *pRef, int32_t iSelect, void *pBuf, int32_t iBufSize); + +// Control behavior of module +DIRTYCODE_API int32_t UserApiControl(UserApiRefT *pRef, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); + +// Starts the process to retrieve a batch of user information for players. pLookupUsers is a pointer to the first DirtyUserT, and iLookupUsersLength is the number of DirtyUserTs. +DIRTYCODE_API int32_t UserApiRequestProfilesAsync(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUsers, int32_t iLookupUsersLength, UserApiCallbackT *pCallback, void *pUserData); + +// Starts the process to retrieve user information of a single user. pLookupUsers is a pointer to the DirtyUserT +DIRTYCODE_API int32_t UserApiRequestProfileAsync(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUser, UserApiCallbackT *pCallback, uint32_t uUserDataMask, void *pUserData); + +// Starts the process to retrieve Presence information of a single user. pLookupUsers is a pointer to the DirtyUserT +DIRTYCODE_API int32_t UserApiRequestPresenceAsync(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUser, UserApiCallbackT *pCallback, uint32_t uUserDataMask, void *pUserData); + +// Starts the process to retrieve Rich Presence information of a single user. pLookupUsers is a pointer to the DirtyUserT +DIRTYCODE_API int32_t UserApiRequestRichPresenceAsync(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUser, UserApiCallbackT *pCallback, uint32_t uUserDataMask, void *pUserData); + +// Log that a local user recently interacted with another +DIRTYCODE_API int32_t UserApiPostRecentlyMetAsync(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pPlayerMet, void *pAdditionalInfo, UserApiPostCallbackT *pCallback, void *pUserData); + +// Log new rich presence info; usage of this is very dependant on the first party's method of rich presence +DIRTYCODE_API int32_t UserApiPostRichPresenceAsync(UserApiRefT *pRef, uint32_t uUserIndex, UserApiRichPresenceT *pData, UserApiPostCallbackT *pCallback, void *pUserData); + +// Register for callbacks +DIRTYCODE_API int32_t UserApiRegisterUpdateEvent(UserApiRefT *pRef, uint32_t uUserIndex, UserApiNotifyTypeE eType, UserApiUpdateCallbackT *pNotifyCb, void *pUserData); + +// Update the internal state of the module, and call registered UserApiCallbackT callback if there are GamerCard/Profile responses available. This function should be called periodically. +DIRTYCODE_API void UserApiUpdate(UserApiRefT *pRef); + +// cancels all pending transactions +DIRTYCODE_API int32_t UserApiCancel(UserApiRefT *pRef, uint32_t uUserIndex); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _userapi_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/misc/userlistapi.h b/src/thirdparty/dirtysdk/include/DirtySDK/misc/userlistapi.h new file mode 100644 index 00000000..04bfd537 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/misc/userlistapi.h @@ -0,0 +1,251 @@ +/*H*************************************************************************************************/ +/*! + \File userlistapi.h + + \Description + The UserListApi module allow the user to fetche friends, blocked users and + muted users from first parties. + + \Notes + This API currently only supports Gen 4 platforms (XBox One and PS4). + + \Copyright + Copyright (c) Electronic Arts 2013. + + \Version 04/17/13 (amakoukji) + \Version 12/02/14 (amakoukji) API revamped +*/ +/*************************************************************************************************H*/ + +#ifndef _userlistapi_h +#define _userlistapi_h + +/*! +\Moduledef UserList UserList +\Modulemember User +*/ +//@{ + +/*** Include files ********************************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtyuser.h" +#include "DirtySDK/misc/userapi.h" + +#if defined (DIRTYCODE_XBOXONE) || defined (DIRTYCODE_GDK) +#include "DirtySDK/misc/xboxone/userlistapixboxone.h" +#elif defined (DIRTYCODE_PS5) +#include "DirtySDK/misc/ps5/userlistapips5.h" +#elif defined (DIRTYCODE_PS4) +#include "DirtySDK/misc/ps4/userlistapips4.h" +#endif + +/*** Defines **************************************************************************************/ + +#define USERLISTAPI_ERROR_UNSUPPORTED (-1) +#define USERLISTAPI_ERROR_INPROGRESS (-2) +#define USERLISTAPI_ERROR_BAD_PARAM (-3) +#define USERLISTAPI_ERROR_NO_USER (-4) +#define USERLISTAPI_ERROR_TIMEOUT (-5) +#define USERLISTAPI_ERROR_GUEST (-6) +#define USERLISTAPI_ERROR_INVALID_USER (-7) +#define USERLISTAPI_ERROR_USER_INDEX_RANGE (-8) +#define USERLISTAPI_ERROR_RATE_LIMITED (-9) +#define USERLISTAPI_ERROR_UNKNOWN (-99) + +#define USERLISTAPI_MASK_PROFILE (0x01) +#define USERLISTAPI_MASK_PRESENCE (0x02) +#define USERLISTAPI_MASK_RICH_PRESENCE (0x04) + +#define USERLISTAPI_NOTIFY_LIST_MAX_SIZE (50) + +typedef enum UserListApiTypeE +{ + USERLISTAPI_TYPE_FRIENDS, + USERLISTAPI_TYPE_BLOCKED, + USERLISTAPI_TYPE_FRIENDS_OF_FRIEND, + USERLISTAPI_TYPE_MUTED +} UserListApiTypeE; + +typedef enum UserListApiIfATypeE +{ + USERLISTAPI_IS_FRIENDS, + USERLISTAPI_IS_BLOCKED +} UserListApiIfATypeE; + +typedef enum UserListApiReturnTypeE +{ + TYPE_USER_DATA, + TYPE_LIST_END +} UserListApiReturnTypeE; + +typedef enum UserListApiIsAReturnTypeE +{ + USERLISTAPI_IS_OF_TYPE, + USERLISTAPI_IS_NOT_OF_TYPE, + USERLISTAPI_IS_MYSELF, + USERLISTAPI_IS_TYPE_UNKNOWN +} UserListApiIsAReturnTypeE; + +typedef enum UserListApiNotifyTypeE +{ + USERLISTAPI_NOTIFY_FRIENDLIST_UPDATE = 0, + USERLISTAPI_NOTIFY_BLOCKEDLIST_UPDATE +} +UserListApiNotifyTypeE; + +typedef enum UserListApiUpdateTypeE +{ + USERLISTAPI_UPDATE_TYPE_UNKNOWN = 0, + USERLISTAPI_UPDATE_TYPE_REMOVED, + USERLISTAPI_UPDATE_TYPE_ADD +} UserListApiUpdateTypeE; + +/*** Macros ***************************************************************************************/ + +/*** Type Definitions *****************************************************************************/ + +//!< opaque module ref +typedef struct UserListApiRefT UserListApiRefT; + +//!< search filters ref, this will be further defined in platforms specific headers +typedef struct UserListApiFiltersT UserListApiFiltersT; + +//!< response structure for user data +typedef struct UserListApiUserDataT +{ + DirtyUserT DirtyUser; // user identifier + uint8_t bIsMutualFriend; // TRUE if the user is a mutual friend, FALSE otherwise + uint8_t bIsFavorite; // TRUE if the user is a favorite friend, FALSE otherwise + int32_t iUserProfileError; // >= 0 means the profile is valid, otherwise a meaningful error code + UserApiUserDataT ExtendedUserData; // user details containing profile, presence and rich presence + UserListApiTypeE eRequestType; // type of user lists, friends, recently met, etc + uint32_t uUserIndex; // index of local user index + UserListApiFiltersT *pUserListFilters; // filters used for this query, first Party Specific, only valid inside the UserListApiCallbackT + void *pRawData; // pointer to 1st party friend data +} UserListApiUserDataT; + +//!< response structure for metadata +typedef struct UserListApiEndDataT +{ + UserListApiTypeE eRequestType; // type of user lists, friends, recently met, etc + UserListApiFiltersT *pUserListFilters; // filters used for this query, first Party Specific, only valid inside the UserListApiCallbackT + uint32_t uUserIndex; // index of local user index + uint32_t uTypeMask; // type mask + int64_t iLimit; // maximum number of users to return + int64_t iOffset; // index of first user from 1st party query + int64_t iTotalFriendCount; // total number of users that match the query, will become valid + int32_t Error; // error code returned by the function + int32_t iRateLimRetrySecs; // if Error is USERLISTAPI_ERROR_RATE_LIMITED then this is the number of seconds to wait before retrying +} UserListApiEndDataT; + +//!< IsA style request response +typedef struct UserListApiIsADataT +{ + DirtyUserT DirtyUser; // user identifier + uint32_t uUserIndex; // index of local user index + UserListApiIsAReturnTypeE eIsaType; // result (yes, no, unknown or myself) + uint8_t bIsMutualFriend; // TRUE if the user is a mutual friend, FALSE otherwise + uint8_t bIsFavorite; // TRUE if the user is a favorite friend, FALSE otherwise + int32_t Error; // error code returned by the function + int32_t iRateLimRetrySecs; // if Error is USERLISTAPI_ERROR_RATE_LIMITED then this is the number of seconds to wait before retrying +} UserListApiIsADataT; + +//!< container union for generic response +typedef union UserListApiEventDataT +{ + struct UserListApiUserDataT UserData; // user data for eResponseType = TYPE_USER_DATA + struct UserListApiEndDataT ListEndData; // meta data for eResponseType = TYPE_LIST_END +} UserListApiEventDataT; + +//!< presence update notification data +typedef struct UserListApiNotifyPresenceUpdateData +{ + DirtyUserT DirtyUser; // user identifier +} UserListApiNotifyPresenceUpdateData; + +//!< rich presence update notification data +typedef struct UserListApiNotifyRichPresenceUpdateData +{ + DirtyUserT DirtyUser; // user identifier +} UserListApiNotifyRichPresenceUpdateData; + +//!< friend list update notification data +typedef struct UserListApiNotifyFriendListUpdate +{ + DirtyUserT DirtyUser; // user identifier + UserListApiUpdateTypeE eType; + uint32_t uUserIndex; // user index for whom this notification belongs +} UserListApiNotifyFriendListUpdate; + +//!< blocked list update notification data +typedef struct UserListApiNotifyBlockedListUpdate +{ + DirtyUserT DirtyUser; // user identifier + UserListApiUpdateTypeE eType; + uint32_t uUserIndex; // user index for whom this notification belongs +} UserListApiNotifyBlockedListUpdate; + + +//!< container union for notification results +typedef union UserListApiNotifyDataT +{ + UserListApiNotifyFriendListUpdate FriendListData; + UserListApiNotifyBlockedListUpdate BlockedListData; +} UserListApiNotifyDataT; + +// + +/*** Defines **********************************************************************/ + +// identify platform if not yet known +#if defined(DIRTYCODE_PC) || defined(DIRTYCODE_LINUX) +// do nothing +#elif defined(EA_PLATFORM_XBSX) + #define DIRTYCODE_GDK 1 + #define DIRTYCODE_XBSX 1 +#elif defined(EA_PLATFORM_XBOX_GDK) + #define DIRTYCODE_GDK 1 + #define DIRTYCODE_XBOXONE 1 +#elif defined(EA_PLATFORM_CAPILANO) + #define DIRTYCODE_XBOXONE 1 +#elif defined(EA_PLATFORM_MICROSOFT) + #define DIRTYCODE_PC 1 +#elif defined(EA_PLATFORM_PS4) + #define DIRTYCODE_PS4 1 +#elif defined(EA_PLATFORM_PS5) + #define DIRTYCODE_PS5 1 + #define DIRTYCODE_PS4 1 +#elif defined(EA_PLATFORM_IPHONE) || defined(EA_PLATFORM_IPHONE_SIMULATOR) + #define DIRTYCODE_APPLEIOS 1 +#elif defined(EA_PLATFORM_OSX) + #define DIRTYCODE_APPLEOSX 1 +#elif defined(EA_PLATFORM_ANDROID) + #define DIRTYCODE_ANDROID 1 +#elif defined(EA_PLATFORM_STADIA) + #define DIRTYCODE_LINUX 1 + #define DIRTYCODE_STADIA 1 +#elif defined(EA_PLATFORM_LINUX) + #define DIRTYCODE_LINUX 1 +#elif defined(EA_PLATFORM_NX) + #define DIRTYCODE_NX 1 +#endif + +// define platform name +#if defined(DIRTYCODE_ANDROID) +#define DIRTYCODE_PLATNAME "Android" +#define DIRTYCODE_PLATNAME_SHORT "android" +#elif defined(DIRTYCODE_APPLEIOS) +#define DIRTYCODE_PLATNAME "AppleIOS" +#define DIRTYCODE_PLATNAME_SHORT "ios" +#elif defined(DIRTYCODE_APPLEOSX) +#define DIRTYCODE_PLATNAME "AppleOSX" +#define DIRTYCODE_PLATNAME_SHORT "osx" +#elif defined(DIRTYCODE_XBSX) +#define DIRTYCODE_PLATNAME "XboxSeriesX" +#define DIRTYCODE_PLATNAME_SHORT "xbsx" +#elif defined(DIRTYCODE_XBOXONE) +#define DIRTYCODE_PLATNAME "XboxOne" +#define DIRTYCODE_PLATNAME_SHORT "xone" +#elif defined(DIRTYCODE_PS5) +#define DIRTYCODE_PLATNAME "PlayStation5" +#define DIRTYCODE_PLATNAME_SHORT "ps5" +#elif defined(DIRTYCODE_PS4) +#define DIRTYCODE_PLATNAME "PlayStation4" +#define DIRTYCODE_PLATNAME_SHORT "ps4" +#elif defined(DIRTYCODE_STADIA) +#define DIRTYCODE_PLATNAME "Stadia" +#define DIRTYCODE_PLATNAME_SHORT "stadia" +#elif defined(DIRTYCODE_LINUX) +#define DIRTYCODE_PLATNAME "Linux" +#define DIRTYCODE_PLATNAME_SHORT "linux" +#elif defined(DIRTYCODE_PC) +#define DIRTYCODE_PLATNAME "Windows" +#define DIRTYCODE_PLATNAME_SHORT "pc" +#elif defined(DIRTYCODE_NX) +#define DIRTYCODE_PLATNAME "Switch" +#define DIRTYCODE_PLATNAME_SHORT "nx" +#else +#error The platform was not predefined and could not be auto-determined! +#endif + +//For Building as a dll +#if defined(EA_DLL) + #ifndef DIRTYCODE_DLL + #define DIRTYCODE_DLL + #endif + #ifndef DIRTYCODE_API + #define DIRTYCODE_API __declspec(dllimport) + #endif +#else + #define DIRTYCODE_API +#endif + +// define 32-bit or 64-bit pointers +#if (EA_PLATFORM_PTR_SIZE == 8) + #define DIRTYCODE_64BITPTR (1) +#else + #define DIRTYCODE_64BITPTR (0) +#endif + +// we need va_list to be a universal type +#include +#include +#include + +/*** Defines **********************************************************************/ + +// force our definition of TRUE/FALSE +#ifdef TRUE +#undef TRUE +#undef FALSE +#endif + +#define FALSE (0) +#define TRUE (1) + +// map common debug code definitions to our debug code definition +#ifndef DIRTYCODE_DEBUG + #if defined(EA_DEBUG) + #define DIRTYCODE_DEBUG (1) + #else + #define DIRTYCODE_DEBUG (0) + #endif +#endif + +/*** Macros ***********************************************************************/ + +//! min of _x and _y +#define DS_MIN(_x, _y) (((_x) < (_y)) ? (_x) : (_y)) + +//! max of _x and _y +#define DS_MAX(_x, _y) (((_x) > (_y)) ? (_x) : (_y)) + +//! abs of _x +#define DS_ABS(_x) (((_x) < 0) ? -(_x) : (_x)) + +//! clamp _x between _min and _max; if _low < _high result is undefined +#define DS_CLAMP(_x, _min, _max) (((_x) > (_max)) ? (_max) : (((_x) < (_min)) ? (_min) : (_x))) + +/*** Type Definitions *************************************************************/ + +//! time-to-string conversion type +typedef enum TimeToStringConversionTypeE +{ + TIMETOSTRING_CONVERSION_ISO_8601, //!< ISO 8601 standard: yyyy-MM-ddTHH:mmZ where Z means 0 UTC offset, and no Z means local time zone + TIMETOSTRING_CONVERSION_ISO_8601_BASIC, //!< ISO 8601 basic format (no hyphens or colon) : https://en.wikipedia.org/wiki/ISO_8601#cite_ref-15 + TIMETOSTRING_CONVERSION_RFC_0822, //!< RFC 0822 format, updated by RFC1123: day, dd mon yyyy hh:mm:ss zzz + TIMETOSTRING_CONVERSION_ASN1_UTCTIME, //!< ASN.1 UTCTime format + TIMETOSTRING_CONVERSION_ASN1_GENTIME, //!< ASN.1 GeneralizedTime format + TIMETOSTRING_CONVERSION_UNKNOWN //!< unknown source format type; try to auto-determine if possible +} TimeToStringConversionTypeE; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// replacement time functions +DIRTYCODE_API uint64_t ds_timeinsecs(void); +DIRTYCODE_API int32_t ds_timezone(void); +DIRTYCODE_API struct tm *ds_localtime(struct tm *pTm, uint64_t elap); +DIRTYCODE_API struct tm *ds_secstotime(struct tm *pTm, uint64_t elap); +DIRTYCODE_API uint64_t ds_timetosecs(const struct tm *pTm); +DIRTYCODE_API struct tm *ds_plattimetotime(struct tm *pTm, void *pPlatTime); +DIRTYCODE_API struct tm *ds_plattimetotimems(struct tm *pTm, int32_t *pImSec); +DIRTYCODE_API char *ds_timetostr(const struct tm *pTm, TimeToStringConversionTypeE eConvType, uint8_t bLocalTime, char *pStr, int32_t iBufSize); +DIRTYCODE_API char *ds_secstostr(uint64_t elap, TimeToStringConversionTypeE eConvType, uint8_t bLocalTime, char *pStr, int32_t iBufSize); +DIRTYCODE_API uint64_t ds_strtotime(const char *str); +DIRTYCODE_API uint64_t ds_strtotime2(const char *pStr, TimeToStringConversionTypeE eConvType); + +// replacement string functions +DIRTYCODE_API int32_t ds_strnlen(const char *pBuffer, int32_t iLength); +DIRTYCODE_API int32_t ds_vsnprintf(char *pBuffer, int32_t iLength, const char *pFormat, va_list Args); +DIRTYCODE_API int32_t ds_vsnzprintf(char *pBuffer, int32_t iLength, const char *pFormat, va_list Args); +DIRTYCODE_API int32_t ds_snzprintf(char *pBuffer, int32_t iLength, const char *pFormat, ...); +DIRTYCODE_API char *ds_strnzcpy(char *pDest, const char *pSource, int32_t iCount); +DIRTYCODE_API int32_t ds_strnzcat(char *pDst, const char *pSrc, int32_t iDstLen); +DIRTYCODE_API int32_t ds_stricmp(const char *pString1, const char *pString2); +DIRTYCODE_API int32_t ds_strnicmp(const char *pString1, const char *pString2, uint32_t uCount); +DIRTYCODE_API char *ds_stristr(const char *pHaystack, const char *pNeedle); +DIRTYCODE_API char* ds_strtok_r(char *pStr, const char *pDelim, char **ppSavePtr); + +#if defined(_WIN32) || defined(_WIN64) + #define ds_strtoll(_buf, _outp, _radix) _strtoi64(_buf, _outp, _radix) + #define ds_strtoull(_buf, _outp, _radix) _strtoui64(_buf, _outp, _radix) +#else + #define ds_strtoll(_buf, _outp, _radix) strtoll(_buf, _outp, _radix) + #define ds_strtoull(_buf, _outp, _radix) strtoull(_buf, _outp, _radix) +#endif + +// 'original' string functions +DIRTYCODE_API char *ds_fmtoctstring(char *pOutput, int32_t iOutLen, const uint8_t *pInput, int32_t iInpLen); +DIRTYCODE_API char *ds_fmtoctstring_lc(char *pOutput, int32_t iOutLen, const uint8_t *pInput, int32_t iInpLen); +DIRTYCODE_API char *ds_strtolower(char *pString); +DIRTYCODE_API char *ds_strtoupper(char *pString); +DIRTYCODE_API char *ds_strtrim(char *pString); +DIRTYCODE_API int32_t ds_strlistinsert(char *pStrList, int32_t iListBufLen, const char *pString, char cTerm); +DIRTYCODE_API int32_t ds_strsubzcpy(char *pDst, int32_t iDstLen, const char *pSrc, int32_t iSrcLen); +DIRTYCODE_API int32_t ds_strsubzcat(char *pDst, int32_t iDstLen, const char *pSrc, int32_t iSrcLen); +DIRTYCODE_API int32_t ds_strcmpwc(const char *pString1, const char *pStrWild); +DIRTYCODE_API int32_t ds_stricmpwc(const char *pString1, const char *pStrWild); +DIRTYCODE_API int32_t ds_strsplit(const char *pSrc, char cDelimiter, char *pDst, int32_t iDstSize, const char **pNewSrc); + +// mem functions +DIRTYCODE_API void ds_memcpy(void *pDst, const void *pSrc, int32_t iDstLen); +DIRTYCODE_API void ds_memcpy_s(void *pDst, int32_t iDstLen, const void *pSrc, int32_t iSrcLen); +DIRTYCODE_API void ds_memclr(void *pMem, int32_t iCount); +DIRTYCODE_API void ds_memset(void *pMem, int32_t iValue, int32_t iCount); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _platform_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protoadvt.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protoadvt.h new file mode 100644 index 00000000..8c28b56a --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protoadvt.h @@ -0,0 +1,99 @@ +/*H*************************************************************************************/ +/*! + + \File protoadvt.h + + \Description + This advertising module provides a relatively simple multi-protocol + distributed name server architecture utilizing the broadcast capabilities + of UDP and IPX. Once the module is instantiated, it can be used as both + an advertiser (server) and watcher (client) simultaneously. + + \Notes + None. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 1.0 02/17/99 (GWS) Original version + \Version 1.1 02/25/99 (GWS) Alpha release + \Version 1.2 08/10/99 (GWS) Final release + \Version 1.3 12/04/00 (GWS) Revised for Dirtysock + +*/ +/*************************************************************************************H*/ + +#ifndef _protoadvt_h +#define _protoadvt_h + +/*! +\Moduledef ProtoAdvt ProtoAdvt +\Modulemember Proto +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +//! define the ports used for service broadcasts +#define ADVERT_BROADCAST_PORT_UDP 9999 +#define ADVERT_BROADCAST_PORT_IPX 9999 + +//! define the static packet header identifier +#define ADVERT_PACKET_IDENTIFIER "gEA" + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! module reference -- passed as first arg to all module functions +typedef struct ProtoAdvtRef ProtoAdvtRef; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// construct an advertising agent +DIRTYCODE_API ProtoAdvtRef *ProtoAdvtConstruct(int32_t buffer); + +// destruct an advertising agent +DIRTYCODE_API void ProtoAdvtDestroy(ProtoAdvtRef *what); + +// query for available services +DIRTYCODE_API int32_t ProtoAdvtQuery(ProtoAdvtRef *what, const char *kind, const char *proto, + char *buffer, int32_t buflen, int32_t local); + +// locate a specific advertisement and return advertisers address (UDP only) +DIRTYCODE_API uint32_t ProtoAdvtLocate(ProtoAdvtRef *ref, const char *kind, const char *name, + uint32_t *host, uint32_t defval); + +// advertise a service as available +DIRTYCODE_API int32_t ProtoAdvtAnnounce(ProtoAdvtRef *what, const char *kind, const char *name, + const char *addr, const char *note, int32_t freq); + +// cancel server advertisement +DIRTYCODE_API int32_t ProtoAdvtCancel(ProtoAdvtRef *what, const char *kind, const char *name); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _protoadvt_h + + + + + + + + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttp.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttp.h new file mode 100644 index 00000000..3a763f50 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttp.h @@ -0,0 +1,229 @@ +/*H********************************************************************************/ +/*! + \File protohttp.h + + \Description + This module implements an HTTP client that can perform basic transactions + (get/put) with an HTTP server. It conforms to but does not fully implement + the 1.1 HTTP spec (http://www.w3.org/Protocols/rfc2616/rfc2616.html), and + allows for secure HTTP transactions as well as insecure transactions. + + \Copyright + Copyright (c) Electronic Arts 2000-2018. ALL RIGHTS RESERVED. + + \Version 0.5 02/21/2000 (gschaefer) First Version + \Version 1.0 12/07/2000 (gschaefer) Added PS2/Dirtysock support + \Version 1.1 03/03/2004 (sbevan) ProtoSSL/https rewrite, added limited Post support. + \Version 1.2 11/18/2004 (jbrookes) Refactored, updated to HTTP 1.1, added full Post support. +*/ +/********************************************************************************H*/ + +#ifndef _protohttp_h +#define _protohttp_h + +/*! +\Moduledef ProtoHttp ProtoHttp +\Modulemember Proto +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/proto/protohttputil.h" + +/*** Defines **********************************************************************/ + +// defines for ProtoHttpPost's bDoPut parameter +#define PROTOHTTP_POST (0) //!< execute a POST when calling ProtoHttpPost +#define PROTOHTTP_PUT (1) //!< execute a PUT when calling ProtoHttpPost + +// define for ProtoHttpPost's iDataSize parameter +#define PROTOHTTP_STREAM_BEGIN (-1) //!< start streaming upload (size unknown) + +// define for ProtoHttpSend's iDataSize parameter +#define PROTOHTTP_STREAM_END (0) //!< streaming upload operation is complete + +// defines for ProtoHttpGet's bHeadOnly parameter +#define PROTOHTTP_HEADBODY (0) //!< get head and body when calling ProtoHttpGet +#define PROTOHTTP_HEADONLY (1) //!< only get head, not body, when calling ProtoHttpGet + +// defines for ProtoHttpRecv's return result +#define PROTOHTTP_RECVDONE (-1) //!< receive operation complete, and all data has been read +#define PROTOHTTP_RECVFAIL (-2) //!< receive operation failed +#define PROTOHTTP_RECVWAIT (-3) //!< waiting for body data +#define PROTOHTTP_RECVHEAD (-4) //!< in headonly mode and header has been received +#define PROTOHTTP_RECVBUFF (-5) //!< recvall did not have enough space in the provided buffer +#define PROTOHTTP_RECVRDIR (-6) //!< maximum number of redirects exceeded + +// generic protohttp errors (do not overlap with RECV* codes) +#define PROTOHTTP_MINBUFF (-16) //!< not enough input buffer space for operation +#define PROTOHTTP_TIMEOUT (-17) //!< did not receive response from server in configured time + +#define PROTOHTTP_NOTHREADS (-18) //!< not enough worker threads can be spawned (deprecated) +#define PROTOHTTP_EAGAIN (-18) //!< resource not available + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! opaque module ref +typedef struct ProtoHttpRefT ProtoHttpRefT; + +//! write callback info +#ifndef _PROTOHTTP_WRITECBINFO_DEFINED +typedef struct ProtoHttpWriteCbInfoT +{ + ProtoHttpRequestTypeE eRequestType; + ProtoHttpResponseE eRequestResponse; +} ProtoHttpWriteCbInfoT; + +/*! + \Callback ProtoHttpWriteCbT + + \Description + Write data callback + + \Input *pState - module state + \Input *pCbInfo - information about about the request this data is for + \Input *pData - data we received from the server + \Input iDataSize - size of the data + \Input *pUserData - user data pointer passed along in the callback + + \Output + int32_t - unused + */ +typedef int32_t (ProtoHttpWriteCbT)(ProtoHttpRefT *pState, const ProtoHttpWriteCbInfoT *pCbInfo, const char *pData, int32_t iDataSize, void *pUserData); + +#define _PROTOHTTP_WRITECBINFO_DEFINED +#endif + +/*! + \Callback ProtoHttpCustomHeaderCbT + + \Description + Callback that may be used to customize request header before sending + + \Input *pState - module state + \Input *pHeader - [out] request headers that we are going to send + \Input uHeaderSize - size of the request headers + \Input *pData - pointer to data sent in the request. deprecated, will always be NULL + \Input iDataLen - size of the data sent in the request. deprecated, will always be 0 + \Input *pUserRef - user data pointer passed along in the callback + + \Output + int32_t - negative=error, size of the header=success + + \Deprecated + pData and iDataLen are deprecated. We will pass NULL and 0 to them respectively. +*/ +typedef int32_t (ProtoHttpCustomHeaderCbT)(ProtoHttpRefT *pState, char *pHeader, uint32_t uHeaderSize, const char *pData, int64_t iDataLen, void *pUserRef); + +/*! + \Callback ProtoHttpReceiveHeaderCbT + + \Description + Callback that may be used to implement custom header parsing on header receipt + + \Input *pState - module state + \Input *pHeader - header we received from the server + \Input uHeaderSize - size of the response header + \Input *pUserRef - user data pointer passed along in the callback + + \Output + int32_t - unused +*/ +//! callback that may be used to implement custom header parsing on header receipt +typedef int32_t (ProtoHttpReceiveHeaderCbT)(ProtoHttpRefT *pState, const char *pHeader, uint32_t uHeaderSize, void *pUserRef); + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// allocate module state and prepare for use +DIRTYCODE_API ProtoHttpRefT *ProtoHttpCreate(int32_t iRcvBuf); + +// set custom header callback +DIRTYCODE_API void ProtoHttpCallback(ProtoHttpRefT *pState, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pUserRef); + +// destroy the module and release its state +DIRTYCODE_API void ProtoHttpDestroy(ProtoHttpRefT *pState); + +// initiate an HTTP transfer +DIRTYCODE_API int32_t ProtoHttpGet(ProtoHttpRefT *pState, const char *pUrl, uint32_t bHeadOnly); + +// return the actual url data +DIRTYCODE_API int32_t ProtoHttpRecv(ProtoHttpRefT *pState, char *pBuffer, int32_t iBufMin, int32_t iBufMax); + +// receive all of the response data +DIRTYCODE_API int32_t ProtoHttpRecvAll(ProtoHttpRefT *pState, char *pBuffer, int32_t iBufSize); + +// initiate transfer of data to to the server via an HTTP POST command +DIRTYCODE_API int32_t ProtoHttpPost(ProtoHttpRefT *pState, const char *pUrl, const char *pData, int64_t iDataSize, uint32_t bDoPut); + +// send the actual url data +DIRTYCODE_API int32_t ProtoHttpSend(ProtoHttpRefT *pState, const char *pData, int32_t iDataSize); + +// request deletion of a server-based resource +DIRTYCODE_API int32_t ProtoHttpDelete(ProtoHttpRefT *pState, const char *pUrl); + +// get server options for specified resource +DIRTYCODE_API int32_t ProtoHttpOptions(ProtoHttpRefT *pState, const char *pUrl); + +// initiate transfer of data to to the server via an HTTP PATCH command +DIRTYCODE_API int32_t ProtoHttpPatch(ProtoHttpRefT *pState, const char *pUrl, const char *pData, int64_t iDataSize); + +// make an HTTP request +#define ProtoHttpRequest(_pState, _pUrl, _pData, _iDataSize, _eRequestType) ProtoHttpRequestCb2(_pState, _pUrl, _pData, _iDataSize, _eRequestType, NULL, NULL, NULL, NULL) + +// make an HTTP request with write callback +#define ProtoHttpRequestCb(_pState, _pUrl, _pData, _iDataSize, _eRequestType, _pWriteCb, _pWriteCbUserData) ProtoHttpRequestCb2(_pState, _pUrl, _pData, _iDataSize, _eRequestType, _pWriteCb, NULL, NULL, _pWriteCbUserData) + +// make an HTTP request with write, custom header and receive header callback +DIRTYCODE_API int32_t ProtoHttpRequestCb2(ProtoHttpRefT *pState, const char *pUrl, const char *pData, int64_t iDataSize, ProtoHttpRequestTypeE eRequestType, ProtoHttpWriteCbT *pWriteCb, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData); + +// abort current transaction +DIRTYCODE_API void ProtoHttpAbort(ProtoHttpRefT *pState); + +// set base url (used in relative url support) +DIRTYCODE_API void ProtoHttpSetBaseUrl(ProtoHttpRefT *pState, const char *pUrl); + +// get location header (requires state and special processing for relative urls) +DIRTYCODE_API int32_t ProtoHttpGetLocationHeader(ProtoHttpRefT *pState, const char *pInpBuf, char *pBuffer, int32_t iBufSize, const char **pHdrEnd); + +// control function; functionality based on input selector +DIRTYCODE_API int32_t ProtoHttpControl(ProtoHttpRefT *pState, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue); + +// return module status based on input selector +DIRTYCODE_API int32_t ProtoHttpStatus(ProtoHttpRefT *pState, int32_t iSelect, void *pBuffer, int32_t iBufSize); + +// check whether a request for the given Url would be a keep-alive transaction +DIRTYCODE_API int32_t ProtoHttpCheckKeepAlive(ProtoHttpRefT *pState, const char *pUrl); + +// give time to module to do its thing (should be called periodically to allow module to perform work) +DIRTYCODE_API void ProtoHttpUpdate(ProtoHttpRefT *pState); + +// add an X.509 CA certificate that will be recognized in future transactions +DIRTYCODE_API int32_t ProtoHttpSetCACert(const uint8_t *pCACert, int32_t iCertSize); + +// same as ProtoHttpSetCACert(), but .pem certificates multiply bundled are parsed bottom to top +DIRTYCODE_API int32_t ProtoHttpSetCACert2(const uint8_t *pCACert, int32_t iCertSize); + +// validate all CAs that have not already been validated +DIRTYCODE_API int32_t ProtoHttpValidateAllCA(void); + +// clear all CA certs +DIRTYCODE_API void ProtoHttpClrCACerts(void); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _protohttp_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttp2.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttp2.h new file mode 100644 index 00000000..d424379e --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttp2.h @@ -0,0 +1,193 @@ +/*H********************************************************************************/ +/*! + \File protohttp2.h + + \Description + This module implements an HTTP/2 client that can perform basic transactions + with an HTTP/2 server. It conforms to but does not fully implement + the 2.0 HTTP spec (https://tools.ietf.org/html/rfc7540), and + allows for secure HTTP transactions as well as insecure transactions. + + \Copyright + Copyright (c) Electronic Arts 2016-2018. ALL RIGHTS RESERVED. +*/ +/********************************************************************************H*/ + +#ifndef _protohttp2_h +#define _protohttp2_h + +/*! +\Moduledef ProtoHttp2 ProtoHttp2 +\Modulemember Proto +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/proto/protohttputil.h" + +/*** Defines **********************************************************************/ + +// define for ProtoHttp2Request iDataSize parameter +#define PROTOHTTP2_STREAM_BEGIN (-1) //!< start streaming upload (size unknown) + +// define for ProtoHttp2Send's iDataSize parameter +#define PROTOHTTP2_STREAM_END (0) //!< streaming upload operation is complete + +// define for ProtoHttp2Status/ProtoHttp2Control iStreamId parameter +#define PROTOHTTP2_INVALID_STREAMID (-1) //!< invalid stream for the functions that allow for invalid + +// defines for ProtoHttpRecv's return result +#define PROTOHTTP2_RECVDONE (-1) //!< receive operation complete, and all data has been read +#define PROTOHTTP2_RECVFAIL (-2) //!< receive operation failed +#define PROTOHTTP2_RECVWAIT (-3) //!< waiting for body data +#define PROTOHTTP2_RECVHEAD (-4) //!< in headonly mode and header has been received +#define PROTOHTTP2_RECVBUFF (-5) //!< recvall did not have enough space in the provided buffer + +// generic protohttp errors (do not overlap with RECV* codes) +#define PROTOHTTP2_MINBUFF (-6) //!< not enough input buffer space for operation +#define PROTOHTTP2_TIMEOUT (-7) //!< did not receive response from server in configured time + +/*** Type Definitions *************************************************************/ + +//! opaque module ref +typedef struct ProtoHttp2RefT ProtoHttp2RefT; + +//! write callback info +typedef struct ProtoHttp2WriteCbInfoT +{ + ProtoHttpRequestTypeE eRequestType; + ProtoHttpResponseE eRequestResponse; + int32_t iStreamId; +} ProtoHttp2WriteCbInfoT; + +/*! + \Callback ProtoHttp2WriteCbT + + \Description + Write data callback + + \Input *pState - module state + \Input *pCbInfo - information about about the request this data is for + \Input *pData - data we received from the server + \Input iDataSize - size of the data + \Input *pUserData - user data pointer passed along in the callback + + \Output + int32_t - unused +*/ +typedef int32_t (ProtoHttp2WriteCbT)(ProtoHttp2RefT *pState, const ProtoHttp2WriteCbInfoT *pCbInfo, const uint8_t *pData, int32_t iDataSize, void *pUserData); + +/*! + \Callback ProtoHttp2CustomHeaderCbT + + \Description + Callback that may be used to customize request header before sending + + \Input *pState - module state + \Input *pHeader - [out] request headers that we are going to send + \Input uHeaderSize - size of the request headers + \Input *pData - pointer to data sent in the request. deprecated, will always be NULL + \Input iDataLen - size of the data sent in the request. deprecated, will always be 0 + \Input *pUserData - user data pointer passed along in the callback + + \Output + int32_t - negative=error, size of the header=success + + \Deprecated + pData and iDataLen are deprecated. We will pass NULL and 0 to them respectively. +*/ +typedef int32_t (ProtoHttp2CustomHeaderCbT)(ProtoHttp2RefT *pState, char *pHeader, uint32_t uHeaderSize, const uint8_t *pData, int64_t iDataLen, void *pUserData); + +/*! + \Callback ProtoHttp2ReceiveHeaderCbT + + \Description + Callback that may be used to implement custom header parsing on header receipt + + \Input *pState - module state + \Input iStreamId - stream identifier for request + \Input *pHeader - header we received from the server + \Input uHeaderSize - size of the response header + \Input *pUserData - user data pointer passed along in the callback + + \Output + int32_t - unused +*/ +typedef int32_t (ProtoHttp2ReceiveHeaderCbT)(ProtoHttp2RefT *pState, int32_t iStreamId, const char *pHeader, uint32_t uHeaderSize, void *pUserData); + +//! state of the stream (see https://tools.ietf.org/html/rfc7540#section-5.1 for reference) +typedef enum ProtoHttp2StreamStateE +{ + STREAMSTATE_IDLE, + STREAMSTATE_OPEN, + STREAMSTATE_HALF_CLOSED_LOCAL, + STREAMSTATE_HALF_CLOSED_REMOTE, + STREAMSTATE_CLOSED +} ProtoHttp2StreamStateE; + +/*** Functions ********************************************************************/ + +#if defined(__cplusplus) +extern "C" { +#endif + +// allocate the module state and prepare for use +DIRTYCODE_API ProtoHttp2RefT *ProtoHttp2Create(int32_t iRecvBuf); + +// give time to module to do its thing +DIRTYCODE_API void ProtoHttp2Update(ProtoHttp2RefT *pState); + +// destroy the module and release its state +DIRTYCODE_API void ProtoHttp2Destroy(ProtoHttp2RefT *pState); + +// set the custom header callbacks +DIRTYCODE_API void ProtoHttp2Callback(ProtoHttp2RefT *pState, ProtoHttp2CustomHeaderCbT *pCustomHeaderCb, ProtoHttp2ReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData); + +// initiate an HTTP request +DIRTYCODE_API int32_t ProtoHttp2Request(ProtoHttp2RefT *pState, const char *pUrl, const uint8_t *pData, int32_t iDataSize, ProtoHttpRequestTypeE eRequestType, int32_t *pStreamId); + +// initiate an HTTP request with a write callback +DIRTYCODE_API int32_t ProtoHttp2RequestCb(ProtoHttp2RefT *pState, const char *pUrl, const uint8_t *pData, int32_t iDataSize, ProtoHttpRequestTypeE eRequestType, int32_t *pStreamId, ProtoHttp2WriteCbT *pWriteCb, void *pUserData); + +// initiate an HTTP request with a write, custom header and receive header callback +DIRTYCODE_API int32_t ProtoHttp2RequestCb2(ProtoHttp2RefT *pState, const char *pUrl, const uint8_t *pData, int32_t iDataSize, ProtoHttpRequestTypeE eRequestType, int32_t *pStreamId, ProtoHttp2WriteCbT *pWriteCb, ProtoHttp2CustomHeaderCbT *pCustomHeaderCb, ProtoHttp2ReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData); + +// send data for a specific stream, NULL data ends the stream +DIRTYCODE_API int32_t ProtoHttp2Send(ProtoHttp2RefT *pState, int32_t iStreamId, const uint8_t *pData, int32_t iDataSize); + +// receive the actual url data +DIRTYCODE_API int32_t ProtoHttp2Recv(ProtoHttp2RefT *pState, int32_t iStreamId, uint8_t *pBuffer, int32_t iBufMin, int32_t iBufMax); + +// receive all of the response data for the given stream +DIRTYCODE_API int32_t ProtoHttp2RecvAll(ProtoHttp2RefT *pState, int32_t iStreamId, uint8_t *pBuffer, int32_t iBufSize); + +// abort the passed in stream's transaction +DIRTYCODE_API void ProtoHttp2Abort(ProtoHttp2RefT *pState, int32_t iStreamId); + +// close the connection to the server +DIRTYCODE_API void ProtoHttp2Close(ProtoHttp2RefT *pState); + +// return module status based on input selector +DIRTYCODE_API int32_t ProtoHttp2Status(ProtoHttp2RefT *pState, int32_t iStreamId, int32_t iSelect, void *pBuffer, int32_t iBufSize); + +// control function; functionality based on input selector +DIRTYCODE_API int32_t ProtoHttp2Control(ProtoHttp2RefT *pState, int32_t iStreamId, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); + +// set base url (used in relative url support) +DIRTYCODE_API void ProtoHttp2SetBaseUrl(ProtoHttp2RefT *pState, const char *pUrl); + +// get location header (requires state and special processing for relative urls) +DIRTYCODE_API int32_t ProtoHttp2GetLocationHeader(ProtoHttp2RefT *pState, const char *pInpBuf, char *pBuffer, int32_t iBufSize, const char **pHdrEnd); + +// release resources for the stream identified by id +DIRTYCODE_API void ProtoHttp2StreamFree(ProtoHttp2RefT *pState, int32_t iStreamId); + +#if defined(__cplusplus) +} +#endif + +//@} + +#endif // _protohttp2_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttpmanager.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttpmanager.h new file mode 100644 index 00000000..d7815a27 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttpmanager.h @@ -0,0 +1,131 @@ +/*H********************************************************************************/ +/*! + \File protohttpmanager.h + + \Description + High-level module designed to create and manage a pool of ProtoHttp refs. A + client application can submit rapid-fire http requests and ProtoHttpManager + will distribute them efficiently across the ref pool internally, queuing + them for efficient use of keep-alive and pipelining requests where possible. + + \Notes + None. + + \Todo + Pipelining + + \Copyright + Copyright (c) Electronic Arts 2009. + + \Version 1.0 05/20/2009 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _protohttpmanager_h +#define _protohttpmanager_h + +/*! +\Moduledef ProtoHttpManager ProtoHttpManager +\Modulemember Proto +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/proto/protohttp.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! httpmanager stats +typedef struct HttpManagerStatT +{ + uint32_t uNumActiveTransactions; //!< current number of active transactions + uint32_t uMaxActiveTransactions; //!< maximum (highwater) number of active transactions + uint32_t uNumQueuedTransactions; //!< current number of queued transactions + uint32_t uMaxQueuedTransactions; //!< maximum (highwater) number of queued transactions + uint32_t uNumTransactions; //!< total number of transactions issued + uint32_t uNumKeepAliveTransactions; //!< total number of keep-alive transactions issued + uint32_t uNumPipelinedTransactions; //!< total number of pipelined transactions issued + uint32_t uSumQueueWaitLatency; //!< total amount of time spent waiting in queue + uint32_t uMaxQueueWaitLatency; //!< max time any single request had to wait + uint32_t uSumQueueFreeLatency; //!< total amount of time spent waiting for transaction to freed + uint32_t uMaxQueueFreeLatency; //!< max time any single transaction waited to be freed + uint64_t uTransactionBytes; //!< total bytes xferred + uint32_t uTransactionTime; //!< total transaction time +} HttpManagerStatT; + +//! opaque module ref +typedef struct HttpManagerRefT HttpManagerRefT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// allocate module state and prepare for use +DIRTYCODE_API HttpManagerRefT *HttpManagerCreate(int32_t iHttpBufSize, int32_t iHttpNumRefs); + +// set custom header callback +DIRTYCODE_API void HttpManagerCallback(HttpManagerRefT *pHttpManager, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb); + +// destroy the module and release its state +DIRTYCODE_API void HttpManagerDestroy(HttpManagerRefT *pHttpManager); + +// allocate a new transaction handle +DIRTYCODE_API int32_t HttpManagerAlloc(HttpManagerRefT *pHttpManager); + +// release a transaction handle +DIRTYCODE_API void HttpManagerFree(HttpManagerRefT *pHttpManager, int32_t iHandle); + +// initiate an HTTP transfer +DIRTYCODE_API int32_t HttpManagerGet(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, uint32_t bHeadOnly); + +// return the actual url data +DIRTYCODE_API int32_t HttpManagerRecv(HttpManagerRefT *pHttpManager, int32_t iHandle, char *pBuffer, int32_t iBufMin, int32_t iBufMax); + +// receive all of the response data +DIRTYCODE_API int32_t HttpManagerRecvAll(HttpManagerRefT *pHttpManager, int32_t iHandle, char *pBuffer, int32_t iBufSize); + +// initiate transfer of data to to the server via an HTTP POST command +DIRTYCODE_API int32_t HttpManagerPost(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, const char *pData, int64_t iDataSize, uint32_t bDoPut); + +// send the actual url data +DIRTYCODE_API int32_t HttpManagerSend(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pData, int32_t iDataSize); + +// make an http request +#define HttpManagerRequest(_pHttpManager, _iHandle, _pUrl, _pData, _iDataSize, _eRequestType) HttpManagerRequestCb2(_pHttpManager, _iHandle, _pUrl, _pData, _iDataSize, _eRequestType, NULL, NULL, NULL, NULL) + +// make an http request with write callback +#define HttpManagerRequestCb(_pHttpManager, _iHandle, _pUrl, _pData, _iDataSize, _eRequestType, _pWriteCb, _pWriteCbUserData) HttpManagerRequestCb2(_pHttpManager, _iHandle, _pUrl, _pData, _iDataSize, _eRequestType, _pWriteCb, NULL, NULL, _pWriteCbUserData) + +// make an http request with write, custom header and receive header callback +DIRTYCODE_API int32_t HttpManagerRequestCb2(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, const char *pData, int64_t iDataSize, ProtoHttpRequestTypeE eRequestType, ProtoHttpWriteCbT *pWriteCb, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData); + +// set base url +DIRTYCODE_API void HttpManagerSetBaseUrl(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl); + +// control function; functionality based on input selector (-1 to apply to all refs) +DIRTYCODE_API int32_t HttpManagerControl(HttpManagerRefT *pHttpManager, int32_t iHandle, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue); + +// return module status based on input selector +DIRTYCODE_API int32_t HttpManagerStatus(HttpManagerRefT *pHttpManager, int32_t iHandle, int32_t iSelect, void *pBuffer, int32_t iBufSize); + +// give time to module to do its thing (should be called periodically to allow module to perform work) +DIRTYCODE_API void HttpManagerUpdate(HttpManagerRefT *pHttpManager); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _protohttpmanager_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttpserv.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttpserv.h new file mode 100644 index 00000000..f5c6f97a --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttpserv.h @@ -0,0 +1,177 @@ +/*H********************************************************************************/ +/*! + \File protohttpserv.h + + \Description + This module implements an HTTP server that can perform basic transactions + (get/put) with an HTTP client. It conforms to but does not fully implement + the 1.1 HTTP spec (http://www.w3.org/Protocols/rfc2616/rfc2616.html), and + allows for secure HTTP transactions as well as insecure transactions. + + \Copyright + Copyright (c) Electronic Arts 2013 + + \Version 1.0 12/11/2013 (jbrookes) Initial version, based on HttpServ tester2 module +*/ +/********************************************************************************H*/ + +#ifndef _protohttpserv_h +#define _protohttpserv_h + +/*! +\Moduledef ProtoHttpServ ProtoHttpServ +\Modulemember Proto +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/proto/protohttputil.h" + +/*** Defines **********************************************************************/ + +#define PROTOHTTPSERV_FLAG_LOOPBACK (0x01) //!< when set, only traffic from the same host will be accepted on the listen port + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct ProtoHttpServRequestT +{ + ProtoHttpRequestTypeE eRequestType; //!< The HTTP method + uint32_t uAddr; //!< Address of where the request originated from + int64_t iContentLength; //!< `Content-Length` header value, how big is the request + int64_t iContentRecv; //!< Amount of data received when processing request + char strContentType[64]; //!< `Content-Type` header value, what type of data is being received + char strUrl[128]; //!< The path / resource (when a RESTful API) that is being accessed. Example: /person/{id} + char strQuery[128]; //!< Query strings passed into the request. Example: ?name="foobar" + char strHeader[16*1024]; //!< HTTP headers sent with the request. Use ProtoHttpUtil functions for parsing + void *pData; //!< User Specific data (store any data to help handle the request) +} ProtoHttpServRequestT; + +typedef struct ProtoHttpServResponseT +{ + ProtoHttpResponseE eResponseCode; //!< The HTTP code (200, 404, etc) + char strContentType[64]; //!< `Content-Type` header value, what type of data is being sent + int64_t iContentLength; //!< `Content-Length` header value, how much data is being sent. + int64_t iContentRead; //!< Amount of data read that is being sent + char strHeader[1*1024]; //!< Custom headers being sent + int32_t iHeaderLen; //!< Size of the custom headers + int32_t iChunkLength; //!< Use `Transfer-Encoding: chunked` if greater than zero + char strLocation[1 * 1024]; //!< Redirect Location + void *pData; //!< User Specific data (store any data to help respond) +} ProtoHttpServResponseT; + +/*! + \Callback ProtoHttpServRequestCbT + + \Description + Invoked when a request is ready for processing + + \Input *pRequest - Information that allows for processing of request + \Input *pResponse - [out] Information that ProtoHttpServ used in responding to the request + \Input *pUserData - User Specific Data that was passed into ProtoHttpServCallback + + \Output + int32_t - zero=complete, positive=in-progress, negative=error +*/ +typedef int32_t (ProtoHttpServRequestCbT)(ProtoHttpServRequestT *pRequest, ProtoHttpServResponseT *pResponse, void *pUserData); + +/*! + \Callback ProtoHttpServReceiveCbT + + \Description + Invoked when new incoming data is received for a request + + \Input *pRequest - [in/out] Information about the request + \Input *pBuffer - The data that was received (NULL when complete) + \Input iBufSize - The size of the data received (zero when complete) + \Input *pUserData - User Specific Data that was passed into ProtoHttpServCallback + + \Output + int32_t - zero=no data processed, positive=amount of data processed, negative=error +*/ +typedef int32_t (ProtoHttpServReceiveCbT)(ProtoHttpServRequestT *pRequest, const char *pBuffer, int32_t iBufSize, void *pUserData); + +/*! + \Callback ProtoHttpServSendCbT + + \Description + Invoked when outgoing data needs to be sent for the response + + \Input *pResponse - Information about the response we are sending + \Input *pBuffer - The data we are writting to for the response (NULL when complete) + \Input iBufSize - The size of the outgoing buffer (zero when complete) + \Input *pUserData - User Specific Data that was passed into ProtoHttpServCallback + + \Output + int32_t - positive=amount of data written, other=error +*/ +typedef int32_t (ProtoHttpServSendCbT)(ProtoHttpServResponseT *pResponse, char *pBuffer, int32_t iBufSize, void *pUserData); + +/*! + \Callback ProtoHttpServHeaderCbT + + \Description + Invoked when a new request header is received + + \Input *pRequest - [in/out] Information about the request we are going to process + \Input *pResponse - [out] Used for handling the response, this allows us to error if header is malformed + \Input *pUserData - User Specific Data that was passed into ProtoHttpServCallback + + \Output + int32_t - positive=success, negative=error +*/ +typedef int32_t (ProtoHttpServHeaderCbT)(ProtoHttpServRequestT *pRequest, ProtoHttpServResponseT *pResponse, void *pUserData); + +//! logging function type +typedef void (ProtoHttpServLogCbT)(const char *pText, void *pUserData); + +//! opaque module ref +typedef struct ProtoHttpServRefT ProtoHttpServRefT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create an httpserv module +DIRTYCODE_API ProtoHttpServRefT *ProtoHttpServCreate(int32_t iPort, int32_t iSecure, const char *pName); + +// create an httpserv module +DIRTYCODE_API ProtoHttpServRefT *ProtoHttpServCreate2(int32_t iPort, int32_t iSecure, const char *pName, uint32_t uFlags); + +// destroy an httpserv module +DIRTYCODE_API void ProtoHttpServDestroy(ProtoHttpServRefT *pHttpServ); + +// set httpserv callback function handlers (required) +DIRTYCODE_API void ProtoHttpServCallback(ProtoHttpServRefT *pHttpServ, ProtoHttpServRequestCbT *pRequestCb, ProtoHttpServReceiveCbT *pReceiveCb, ProtoHttpServSendCbT *pSendCb, ProtoHttpServHeaderCbT *pHeaderCb, ProtoHttpServLogCbT *pLogCb, void *pUserData); + +// control function; functionality based on input selector +DIRTYCODE_API int32_t ProtoHttpServControl(ProtoHttpServRefT *pHttpServ, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue); + +// control function allowing thread-specific control; functionality based on input selector +DIRTYCODE_API int32_t ProtoHttpServControl2(ProtoHttpServRefT *pHttpServ, int32_t iThread, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue); + +// return module status based on input selector +DIRTYCODE_API int32_t ProtoHttpServStatus(ProtoHttpServRefT *pHttpServ, int32_t iSelect, void *pBuffer, int32_t iBufSize); + +// update the module +DIRTYCODE_API void ProtoHttpServUpdate(ProtoHttpServRefT *pHttpServ); + +// updates the backlog setting on the listen socket, not required to be called unless you need to update the backlog +DIRTYCODE_API int32_t ProtoHttpServListen(ProtoHttpServRefT *pHttpServ, int32_t iBacklog); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _protohttpserv_h + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttputil.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttputil.h new file mode 100644 index 00000000..df7a8735 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protohttputil.h @@ -0,0 +1,176 @@ +/*H********************************************************************************/ +/*! + \File protohttputil.h + + \Description + This module implements HTTP support routines such as URL processing, header + parsing, etc. + + \Copyright + Copyright (c) Electronic Arts 2000-2012. + + \Version 1.0 12/18/2012 (jbrookes) Refactored into separate module from protohttp. +*/ +/********************************************************************************H*/ + +#ifndef _protohttputil_h +#define _protohttputil_h + +/*! +\Moduledef ProtoHttpUtil ProtoHttpUtil +\Modulemember Proto +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +//! max supported protocol scheme length +#define PROTOHTTPUTIL_SCHEME_MAX (32) + +/*** Macros ***********************************************************************/ + +//! get class of response code +#define PROTOHTTP_GetResponseClass(_eError) (((_eError)/100) * 100) + +/*** Type Definitions *************************************************************/ + +//! request types +typedef enum ProtoHttpRequestTypeE +{ + PROTOHTTP_REQUESTTYPE_HEAD = 0, //!< HEAD request - does not return any data + PROTOHTTP_REQUESTTYPE_GET, //!< GET request - get data from a server + PROTOHTTP_REQUESTTYPE_POST, //!< POST request - post data to a server + PROTOHTTP_REQUESTTYPE_PUT, //!< PUT request - put data on a server + PROTOHTTP_REQUESTTYPE_DELETE, //!< DELETE request - delete resource from a server + PROTOHTTP_REQUESTTYPE_OPTIONS, //!< OPTIONS request - get server options for specified resource + PROTOHTTP_REQUESTTYPE_PATCH, //!< PATCH request - like PUT but updates the ressource instead of overwriting + PROTOHTTP_REQUESTTYPE_CONNECT, //!< CONNECT request - connect to a proxy that can tunnel (internal use only) + + PROTOHTTP_NUMREQUESTTYPES +} ProtoHttpRequestTypeE; + +/*! HTTP response codes - see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 + for more detailed information. These response codes may be retrieved from ProtoHttp + by calling ProtoHttpStatus('code'). Note that response codes are not available until + a response has been received from the server and parsed by ProtoHttp. If a response + code is not yet available, -1 will be returned. */ +typedef enum ProtoHttpResponseE +{ + PROTOHTTP_RESPONSE_PENDING = -1, //!< response has not yet been received + + // 1xx - informational reponse + PROTOHTTP_RESPONSE_INFORMATIONAL = 100, //!< generic name for a 1xx class response + PROTOHTTP_RESPONSE_CONTINUE = 100, //!< continue with request, generally ignored + PROTOHTTP_RESPONSE_SWITCHPROTO, //!< 101 - OK response to client switch protocol request + + // 2xx - success response + PROTOHTTP_RESPONSE_SUCCESSFUL = 200, //!< generic name for a 2xx class response + PROTOHTTP_RESPONSE_OK = 200, //!< client's request was successfully received, understood, and accepted + PROTOHTTP_RESPONSE_CREATED, //!< new resource has been created + PROTOHTTP_RESPONSE_ACCEPTED, //!< request accepted but not complete + PROTOHTTP_RESPONSE_NONAUTH, //!< non-authoritative info (ok) + PROTOHTTP_RESPONSE_NOCONTENT, //!< request fulfilled, no message body + PROTOHTTP_RESPONSE_RESETCONTENT, //!< request success, reset document view + PROTOHTTP_RESPONSE_PARTIALCONTENT, //!< server has fulfilled partial GET request + + // 3xx - redirection response + PROTOHTTP_RESPONSE_REDIRECTION = 300, //!< generic name for a 3xx class response + PROTOHTTP_RESPONSE_MULTIPLECHOICES = 300, //!< requested resource corresponds to a set of representations + PROTOHTTP_RESPONSE_MOVEDPERMANENTLY, //!< requested resource has been moved permanently to new URI + PROTOHTTP_RESPONSE_FOUND, //!< requested resources has been moved temporarily to a new URI + PROTOHTTP_RESPONSE_SEEOTHER, //!< response can be found under a different URI + PROTOHTTP_RESPONSE_NOTMODIFIED, //!< response to conditional get when document has not changed + PROTOHTTP_RESPONSE_USEPROXY, //!< requested resource must be accessed through proxy + PROTOHTTP_RESPONSE_RESERVED, //!< old response code - reserved + PROTOHTTP_RESPONSE_TEMPREDIRECT, //!< requested resource resides temporarily under a different URI + + // 4xx - client error response + PROTOHTTP_RESPONSE_CLIENTERROR = 400, //!< generic name for a 4xx class response + PROTOHTTP_RESPONSE_BADREQUEST = 400, //!< request could not be understood by server due to malformed syntax + PROTOHTTP_RESPONSE_UNAUTHORIZED, //!< request requires user authorization + PROTOHTTP_RESPONSE_PAYMENTREQUIRED, //!< reserved for future user + PROTOHTTP_RESPONSE_FORBIDDEN, //!< request understood, but server will not fulfill it + PROTOHTTP_RESPONSE_NOTFOUND, //!< Request-URI not found + PROTOHTTP_RESPONSE_METHODNOTALLOWED, //!< method specified in the Request-Line is not allowed + PROTOHTTP_RESPONSE_NOTACCEPTABLE, //!< resource incapable of generating content acceptable according to accept headers in request + PROTOHTTP_RESPONSE_PROXYAUTHREQ, //!< client must first authenticate with proxy + PROTOHTTP_RESPONSE_REQUESTTIMEOUT, //!< client did not produce response within server timeout + PROTOHTTP_RESPONSE_CONFLICT, //!< request could not be completed due to a conflict with current state of the resource + PROTOHTTP_RESPONSE_GONE, //!< requested resource is no longer available and no forwarding address is known + PROTOHTTP_RESPONSE_LENGTHREQUIRED, //!< a Content-Length header was not specified and is required + PROTOHTTP_RESPONSE_PRECONFAILED, //!< precondition given in request-header field(s) failed + PROTOHTTP_RESPONSE_REQENTITYTOOLARGE, //!< request entity is larger than the server is able or willing to process + PROTOHTTP_RESPONSE_REQURITOOLONG, //!< Request-URI is longer than the server is willing to interpret + PROTOHTTP_RESPONSE_UNSUPPORTEDMEDIA, //!< request entity is in unsupported format + PROTOHTTP_RESPONSE_REQUESTRANGE, //!< invalid range in Range request header + PROTOHTTP_RESPONSE_EXPECTATIONFAILED, //!< expectation in Expect request-header field could not be met by server + + // 5xx - server error response + PROTOHTTP_RESPONSE_SERVERERROR = 500, //!< generic name for a 5xx class response + PROTOHTTP_RESPONSE_INTERNALSERVERERROR = 500, //!< an unexpected condition prevented the server from fulfilling the request + PROTOHTTP_RESPONSE_NOTIMPLEMENTED, //!< the server does not support the functionality required to fulfill the request + PROTOHTTP_RESPONSE_BADGATEWAY, //!< invalid response from gateway server + PROTOHTTP_RESPONSE_SERVICEUNAVAILABLE, //!< unable to handle request due to a temporary overloading or maintainance + PROTOHTTP_RESPONSE_GATEWAYTIMEOUT, //!< gateway or DNS server timeout + PROTOHTTP_RESPONSE_HTTPVERSUNSUPPORTED //!< the server does not support the HTTP protocol version that was used in the request +} ProtoHttpResponseE; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// parse HTTP request line from the format as sent on the wire +DIRTYCODE_API const char *ProtoHttpUtilParseRequest(const char *pStrHeader, char *pStrMethod, int32_t iMethodLen, char *pStrUri, int32_t iUriLen, char *pStrQuery, int32_t iQueryLen, ProtoHttpRequestTypeE *pRequestType, uint8_t *pHttp1_0); + +// finds the header field value for the specified header field +DIRTYCODE_API const char *ProtoHttpFindHeaderValue(const char *pHdrBuf, const char *pHeaderText); + +// extracts the header field value for the specified header field entry +DIRTYCODE_API int32_t ProtoHttpExtractHeaderValue(const char *pHdrField, char *pOutBuf, int32_t iOutLen, const char **ppHdrEnd); + +// parse header response code from HTTP header +DIRTYCODE_API int32_t ProtoHttpParseHeaderCode(const char *pHdrBuf); + +// extract a header value from the specified header text +DIRTYCODE_API int32_t ProtoHttpGetHeaderValue(void *pState, const char *pHdrBuf, const char *pName, char *pBuffer, int32_t iBufSize, const char **pHdrEnd); + +// get next header name and value from header buffer +DIRTYCODE_API int32_t ProtoHttpGetNextHeader(void *pState, const char *pHdrBuf, char *pName, int32_t iNameSize, char *pValue, int32_t iValSize, const char **pHdrEnd); + +// url-encode a integer parameter +DIRTYCODE_API int32_t ProtoHttpUrlEncodeIntParm(char *pBuffer, int32_t iLength, const char *pParm, int32_t iValue); + +// url-encode a string parameter +DIRTYCODE_API int32_t ProtoHttpUrlEncodeStrParm(char *pBuffer, int32_t iLength, const char *pParm, const char *pData); + +// url-encode a string parameter with custom safe table +DIRTYCODE_API int32_t ProtoHttpUrlEncodeStrParm2(char *pBuffer, int32_t iLength, const char *pParm, const char *pData, const char *pStrSafe); + +// url-decode a string parameter +DIRTYCODE_API int32_t ProtoHttpUrlDecodeStrParm(const char *pBuffer, char *pData, int32_t iDataSize); + +// parse a url into component parts +DIRTYCODE_API const char *ProtoHttpUrlParse(const char *pUrl, char *pKind, int32_t iKindSize, char *pHost, int32_t iHostSize, int32_t *pPort, int32_t *pSecure); + +// parse a url into component parts +DIRTYCODE_API const char *ProtoHttpUrlParse2(const char *pUrl, char *pKind, int32_t iKindSize, char *pHost, int32_t iHostSize, int32_t *pPort, int32_t *pSecure, uint8_t *bPortSpecified); + +// get next param name and value from URI buffer +DIRTYCODE_API int32_t ProtoHttpGetNextParam(void *pState, const char *pUriBuf, char *pName, int32_t iNameSize, char *pValue, int32_t iValSize, const char **pUriEnd); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _protohttp_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protomangle.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protomangle.h new file mode 100644 index 00000000..7edd9390 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protomangle.h @@ -0,0 +1,139 @@ +/*H*************************************************************************************************/ +/*! + + \File protomangle.h + + \Description + This module encapsulates use of the EA.Com Demangler service. + + \Notes + The ProtoMangle client was developed from version 1.1 of the Peer Connection Service + Protocol Specification: + + http://docs.online.ea.com/infrastructure/demangler/peer-connection-service-protocol.doc + + Please see the following documentation for information on the Demangler service: + + http://docs.online.ea.com/infrastructure/demangler/demangler.html + http://docs.online.ea.com/infrastructure/demangler/peer-connection-service-reqs.doc + http://docs.online.ea.com/infrastructure/demangler/peer-connection-service-engineering-spec.doc + http://docs.online.ea.com/infrastructure/demangler/Peer-Connection-Service-Architecture.doc + + Original work done by C&C: Red Alert 2, on which the Demangler service is based: + + http://www.worldwide.ea.com/articles/render/attachment.aspx?id=919 + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2003. ALL RIGHTS RESERVED. + + \Version 1.0 04/03/2003 (JLB) First Version +*/ +/*************************************************************************************************H*/ + +#ifndef _protomangle_h +#define _protomangle_h + +/*! +\Moduledef ProtoMangle ProtoMangle +\Modulemember Proto +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +//! max cookie length, including terminator +#define PROTOMANGLE_STRCOOKIE_MAX (64) + +//! max gameFeatureID length, including terminator +#define PROTOMANGLE_STRGAMEID_MAX (32) + +//! max LKey length, including terminator +#define PROTOMANGLE_STRLKEY_MAX (64) + +//! max server name length, including terminator +#define PROTOMANGLE_STRSERVER_MAX (32) + +/* + Default server name/port +*/ + +//! default server name +#define PROTOMANGLE_SERVER ("demangler.ea.com") + +//! development server +#define PROTOMANGLE_TEST_SERVER ("peach.online.ea.com") + +//! default server port +#define PROTOMANGLE_PORT (3658) + +//! response codes +typedef enum ProtoMangleStatusE +{ + PROTOMANGLE_STATUS_CONNECTED, + PROTOMANGLE_STATUS_FAILED, + + PROTOMANGLE_NUMSTATUS +} ProtoMangleStatusE; + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct ProtoMangleRefT ProtoMangleRefT; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// allocate module state and prepare for use +DIRTYCODE_API ProtoMangleRefT *ProtoMangleCreate(const char *pServer, int32_t iPort, const char *pGameID, const char *pLKey); + +// destroy the module and release its state +DIRTYCODE_API void ProtoMangleDestroy(ProtoMangleRefT *pRef); + +// give time to module to do its thing (should be called periodically to allow module to perform work) +DIRTYCODE_API void ProtoMangleUpdate(ProtoMangleRefT *pRef); + +// connect to demangler server +DIRTYCODE_API void ProtoMangleConnect(ProtoMangleRefT *pRef, int32_t iGamePort, const char *pSessID); + +// connect to demangler server (extended) +DIRTYCODE_API void ProtoMangleConnect2(ProtoMangleRefT *pRef, int32_t iGamePort, const char *pSessID, int32_t iSessIDLen); + +// connect to demangler server, using the given socket ref to issue UDP probes instead of creating one +DIRTYCODE_API void ProtoMangleConnectSocket(ProtoMangleRefT *pRef, intptr_t uSocketRef, const char *pSessID); + +// get result +DIRTYCODE_API int32_t ProtoMangleComplete(ProtoMangleRefT *pRef, int32_t *pAddr, int32_t *pPort); + +// submit result to server +DIRTYCODE_API int32_t ProtoMangleReport(ProtoMangleRefT *pRef, ProtoMangleStatusE eStatus, int32_t iLatency); + +// protomangle control +DIRTYCODE_API int32_t ProtoMangleControl(ProtoMangleRefT *pRef, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue); + +// get module status based on selector +DIRTYCODE_API int32_t ProtoMangleStatus(ProtoMangleRefT *pRef, int32_t iSelect, void *pBuf, int32_t iBufSize); + +// find a secure template for given network parameters +#if defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) +const char *ProtoMangleFindTemplate(char *pStrTemplateName, int32_t iTemplateNameSize, int32_t iLocalPort, int32_t iRemotePort, uint8_t bTcp, int32_t iVerbose); +#endif + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _protomangle_h + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protoname.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protoname.h new file mode 100644 index 00000000..36778254 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protoname.h @@ -0,0 +1,63 @@ +/*H*************************************************************************************************/ +/*! + + \File protoname.h + + \Description + This module provides name lookup services via DNS. It is platform indepedent + and can be used to resolve names for use with other protocol modules. At this + point, name support is being removed from other protocol modules so it can be + centralized here. + + \Notes + None. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 1.0 03/19/02 (GWS) First Version + +*/ +/*************************************************************************************************H*/ + +#ifndef _protoname_h +#define _protoname_h + +/*! +\Moduledef ProtoName ProtoName +\Modulemember Proto +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtynet.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// async lookup of a domain name +DIRTYCODE_API HostentT *ProtoNameAsync(const char *pName, int32_t iTimeout); + +// synchronous (blocking) lookup of a domain name. +DIRTYCODE_API uint32_t ProtoNameSync(const char *pName, int32_t iTimeout); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _protoname_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protossl.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protossl.h new file mode 100644 index 00000000..f6e7179d --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protossl.h @@ -0,0 +1,277 @@ +/*H*************************************************************************************************/ +/*! + \File protossl.h + + \Description + This module is a from-scratch TLS implementation. It does not use any + third-party code of any kind and was developed entirely by EA. + + \Notes + References: + TLS1.0 RFC: https://tools.ietf.org/html/rfc2246 + TLS1.1 RFC: https://tools.ietf.org/html/rfc4346 + TLS1.2 RFC: https://tools.ietf.org/html/rfc5246 + TLS1.3 RFC: https://tools.ietf.org/html/rfc8446 + ASN.1 encoding rules: https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf + + \Copyright + Copyright (c) Electronic Arts 2002-2018 + + \Version 03/08/2002 (gschaefer) Initial SSL 2.0 implementation + \Version 03/03/2004 (sbevan) Added certificate validation + \Version 11/05/2005 (gschaefer) Rewritten to follow SSL 3.0 specification + \Version 10/12/2012 (jbrookes) Added support for TLS1.0 & TLS1.1 + \Version 10/20/2013 (jbrookes) Added server handshake, client cert support + \Version 11/06/2013 (jbrookes) Added support for TLS1.2 + \Version 03/31/2017 (eesponda) Added support for EC ciphers + \Version 03/28/2018 (jbrookes) Added support for TLS1.3 + \Version 08/15/2018 (jbrookes) Removed SSLv3 & RC4 ciphers +*/ +/*************************************************************************************************H*/ + +#ifndef _protossl_h +#define _protossl_h + +/*! +\Moduledef ProtoSSL ProtoSSL +\Modulemember Proto +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptdef.h" +#include "DirtySDK/crypt/cryptrsa.h" + +/*** Defines ***************************************************************************/ + +// supported TLS versions +#define PROTOSSL_VERSION_TLS1_0 (0x0301) +#define PROTOSSL_VERSION_TLS1_1 (0x0302) +#define PROTOSSL_VERSION_TLS1_2 (0x0303) +#define PROTOSSL_VERSION_TLS1_3 (0x0304) + +// protossl failure codes (retrieve with ProtoSSLStat('fail') +#define PROTOSSL_ERROR_NONE ( 0) //!< no error +#define PROTOSSL_ERROR_DNS (-1) //!< DNS failure +#define PROTOSSL_ERROR_CONN (-10) //!< TCP connection failure +#define PROTOSSL_ERROR_CONN_SSL2 (-11) //!< connection attempt was using unsupported SSLv2 record format +#define PROTOSSL_ERROR_CONN_NOTSSL (-12) //!< connection attempt was not recognized as SSL +#define PROTOSSL_ERROR_CONN_MINVERS (-13) //!< request failed minimum protocol version restriction +#define PROTOSSL_ERROR_CONN_MAXVERS (-14) //!< request failed maximum protocol version restriction +#define PROTOSSL_ERROR_CONN_NOCIPHER (-15) //!< no supported cipher +#define PROTOSSL_ERROR_CONN_NOCURVE (-16) //!< no supported curve +#define PROTOSSL_ERROR_CERT_INVALID (-20) //!< certificate invalid +#define PROTOSSL_ERROR_CERT_HOST (-21) //!< certificate not issued to this host +#define PROTOSSL_ERROR_CERT_NOTRUST (-22) //!< certificate is not trusted (recognized) +#define PROTOSSL_ERROR_CERT_MISSING (-23) //!< certificate not provided in certificate message +#define PROTOSSL_ERROR_CERT_BADDATE (-24) //!< certificate date range validity check failed +#define PROTOSSL_ERROR_CERT_REQUEST (-25) //!< CA fetch request failed +#define PROTOSSL_ERROR_SETUP (-30) //!< failure in secure setup +#define PROTOSSL_ERROR_SECURE (-31) //!< failure in secure connection after setup +#define PROTOSSL_ERROR_UNKNOWN (-32) //!< unknown failure + +// SSLv3 cipher suites (available for TLS1.0+ although SSLv3 is no longer supported) +#define PROTOSSL_CIPHER_RSA_WITH_AES_128_CBC_SHA (1<<0) +#define PROTOSSL_CIPHER_RSA_WITH_AES_256_CBC_SHA (1<<1) +// TLS1.0 cipher suites +#define PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA (1<<2) +#define PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA (1<<3) +#define PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (1<<4) +#define PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (1<<5) +// TLS1.2 cipher suites +#define PROTOSSL_CIPHER_RSA_WITH_AES_128_CBC_SHA256 (1<<6) +#define PROTOSSL_CIPHER_RSA_WITH_AES_256_CBC_SHA256 (1<<7) +#define PROTOSSL_CIPHER_RSA_WITH_AES_128_GCM_SHA256 (1<<8) +#define PROTOSSL_CIPHER_RSA_WITH_AES_256_GCM_SHA384 (1<<9) +#define PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (1<<10) +#define PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (1<<11) +#define PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (1<<12) +#define PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (1<<13) +#define PROTOSSL_CIPHER_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (1<<14) +#define PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 (1<<15) +#define PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (1<<16) +#define PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (1<<17) +#define PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (1<<18) +#define PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (1<<19) +// TLS1.3 cipher suites +#define PROTOSSL_CIPHER_AES_128_GCM_SHA256 (1<<20) +#define PROTOSSL_CIPHER_AES_256_GCM_SHA384 (1<<21) +#define PROTOSSL_CIPHER_CHACHA20_POLY1305_SHA256 (1<<22) + +//! all rsa cipher suites (minus disabled rc4-md5) +#define PROTOSSL_CIPHER_RSA (\ + PROTOSSL_CIPHER_RSA_WITH_AES_128_CBC_SHA|PROTOSSL_CIPHER_RSA_WITH_AES_256_CBC_SHA|\ + PROTOSSL_CIPHER_RSA_WITH_AES_128_CBC_SHA256|PROTOSSL_CIPHER_RSA_WITH_AES_128_GCM_SHA256|\ + PROTOSSL_CIPHER_RSA_WITH_AES_256_CBC_SHA256|PROTOSSL_CIPHER_RSA_WITH_AES_256_GCM_SHA384) + +//! all ecc cipher suites +#define PROTOSSL_CIPHER_ECC (\ + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA|PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA256|\ + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA|PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA384|\ + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_GCM_SHA256|PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA|\ + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_GCM_SHA384|PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA|\ + PROTOSSL_CIPHER_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256|\ + PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256|PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256|\ + PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384|PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384|\ + PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256) + +//! all tls1.3 cipher suites +#define PROTOSSL_CIPHER_ALL_13 (PROTOSSL_CIPHER_AES_128_GCM_SHA256|PROTOSSL_CIPHER_AES_256_GCM_SHA384|PROTOSSL_CIPHER_CHACHA20_POLY1305_SHA256) + +//! default cipher suites +#define PROTOSSL_CIPHER_ALL (PROTOSSL_CIPHER_RSA|PROTOSSL_CIPHER_ECC|PROTOSSL_CIPHER_ALL_13) + +// client cert flags (ProtoSSLControl() 'ccrt' selector) +#define PROTOSSL_CLIENTCERT_NONE (0) +#define PROTOSSL_CLIENTCERT_OPTIONAL (1) +#define PROTOSSL_CLIENTCERT_REQUIRED (2) + +// clienthello extensions +#define PROTOSSL_HELLOEXTN_NONE (0) +#define PROTOSSL_HELLOEXTN_SERVERNAME (1) +#define PROTOSSL_HELLOEXTN_SIGALGS (2) +#define PROTOSSL_HELLOEXTN_ALPN (4) +#define PROTOSSL_HELLOEXTN_ELLIPTIC_CURVES (8) +// all extensions +#define PROTOSSL_HELLOEXTN_ALL (\ + PROTOSSL_HELLOEXTN_SERVERNAME|PROTOSSL_HELLOEXTN_SIGALGS|PROTOSSL_HELLOEXTN_ALPN) +// default extensions +#define PROTOSSL_HELLOEXTN_DEFAULT (PROTOSSL_HELLOEXTN_ALL) + +//! elliptic curves +#define PROTOSSL_CURVE_SECP256R1 (1 << 0) +#define PROTOSSL_CURVE_SECP384R1 (1 << 1) +#define PROTOSSL_CURVE_X25519 (1 << 2) +#define PROTOSSL_CURVE_X448 (1 << 3) + +//! all curves +#define PROTOSSL_CURVE_ALL (\ + PROTOSSL_CURVE_SECP256R1|PROTOSSL_CURVE_SECP384R1|PROTOSSL_CURVE_X25519|PROTOSSL_CURVE_X448) +//! default curves +#define PROTOSSL_CURVE_DEFAULT (PROTOSSL_CURVE_ALL) + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! identity fields for X509 issuer/subject +typedef struct ProtoSSLCertIdentT +{ + char strCountry[32]; + char strState[32]; + char strCity[32]; + char strOrg[32]; + char strUnit[256]; + char strCommon[64]; +} ProtoSSLCertIdentT; + +//! alert info, returned by ProtoSSLStat() 'alrt' selector after an SSL alert has been received +typedef struct ProtoSSLAlertDescT +{ + int32_t iAlertType; + const char *pAlertDesc; +} ProtoSSLAlertDescT; + +//! cert info, returned by ProtoSSLStat() 'cert' selector after certificate failure +typedef struct ProtoSSLCertInfoT +{ + ProtoSSLCertIdentT Ident; + int32_t iKeyModSize; +} ProtoSSLCertInfoT; + +//! state for pkcs1 operations +typedef struct ProtoSSLPkcs1T +{ + CryptRSAT RSAContext; //!< context used for rsa operations +} ProtoSSLPkcs1T; + +// opaque module state ref +typedef struct ProtoSSLRefT ProtoSSLRefT; + +// forward declaration of sockaddr +struct sockaddr; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// protossl startup +DIRTYCODE_API int32_t ProtoSSLStartup(void); + +// protossl shutdown +DIRTYCODE_API void ProtoSSLShutdown(void); + +// allocate an SSL connection and prepare for use +DIRTYCODE_API ProtoSSLRefT *ProtoSSLCreate(void); + +// reset connection back to base state. +DIRTYCODE_API void ProtoSSLReset(ProtoSSLRefT *pState); + +// destroy the module and release its state +DIRTYCODE_API void ProtoSSLDestroy(ProtoSSLRefT *pState); + +// give time to module to do its thing (should be called periodically to allow module to perform work) +DIRTYCODE_API void ProtoSSLUpdate(ProtoSSLRefT *pState); + +// Accept an incoming connection. +DIRTYCODE_API ProtoSSLRefT* ProtoSSLAccept(ProtoSSLRefT *pState, int32_t iSecure, struct sockaddr *pAddr, int32_t *pAddrlen); + +// Create a socket bound to the given address. +DIRTYCODE_API int32_t ProtoSSLBind(ProtoSSLRefT *pState, const struct sockaddr *pAddr, int32_t pAddrlen); + +// make a secure connection to a server. +DIRTYCODE_API int32_t ProtoSSLConnect(ProtoSSLRefT *pState, int32_t iSecure, const char *pAddr, uint32_t uAddr, int32_t iPort); + +// disconnect from the server. +DIRTYCODE_API int32_t ProtoSSLDisconnect(ProtoSSLRefT *pState); + +// Start listening for an incoming connection. +DIRTYCODE_API int32_t ProtoSSLListen(ProtoSSLRefT *pState, int32_t iBacklog); + +// send secure data to the server. +DIRTYCODE_API int32_t ProtoSSLSend(ProtoSSLRefT *pState, const char *pBuffer, int32_t iLength); + +// receive secure data from the server. +DIRTYCODE_API int32_t ProtoSSLRecv(ProtoSSLRefT *pState, char *pBuffer, int32_t iLength); + +// return the current module status (according to selector) +DIRTYCODE_API int32_t ProtoSSLStat(ProtoSSLRefT *pState, int32_t iSelect, void *pBuffer, int32_t iLength); + +// control module behavior +DIRTYCODE_API int32_t ProtoSSLControl(ProtoSSLRefT *pState, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue); + +// add an X.509 CA certificate that will be recognized in future transactions +DIRTYCODE_API int32_t ProtoSSLSetCACert(const uint8_t *pCACert, int32_t iCertSize); + +// same as ProtoSSLSetCACert(), but certs are not validated at load time +DIRTYCODE_API int32_t ProtoSSLSetCACert2(const uint8_t *pCACert, int32_t iCertSize); + +// validate all CAs that have not already been validated +DIRTYCODE_API int32_t ProtoSSLValidateAllCA(void); + +// clear all CA certs +DIRTYCODE_API void ProtoSSLClrCACerts(void); + +// generate a pkcs1.5 rsa signature - init +DIRTYCODE_API int32_t ProtoSSLPkcs1GenerateInit(ProtoSSLPkcs1T *pPkcs1, const uint8_t *pHashData, int32_t iHashLen, int32_t iHashType, int32_t iModSize, const CryptBinaryObjT *pPrimeP, const CryptBinaryObjT *pPrimeQ, const CryptBinaryObjT *pExponentP, const CryptBinaryObjT *pExponentQ, const CryptBinaryObjT *pCoefficient); + +// generate a pkcs1.5 rsa signature +DIRTYCODE_API int32_t ProtoSSLPkcs1GenerateUpdate(ProtoSSLPkcs1T *pPkcs1, int32_t iNumIterations, uint8_t *pSigData, int32_t iSigSize); + +// verify the pkcs1.5 rsa signature +DIRTYCODE_API int32_t ProtoSSLPkcs1Verify(const uint8_t *pSignature, int32_t iSigLen, const uint8_t *pHashData, int32_t iHashLen, int32_t iHashType, const uint8_t *pMod, int32_t iModSize, const uint8_t *pExp, int32_t iExpSize); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _protossl_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protostream.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protostream.h new file mode 100644 index 00000000..bd2edb71 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protostream.h @@ -0,0 +1,107 @@ +/*H********************************************************************************/ +/*! + \File protostream.h + + \Description + Manage streaming of an Internet media resource. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 11/16/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _protostream_h +#define _protostream_h + +/*! +\Moduledef ProtoStream ProtoStream +\Modulemember Proto +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/proto/protohttp.h" + +/*** Defines **********************************************************************/ + +/* when a stream is opened, the repeat frequency is specified in seconds. these + constants handle special cases of "one time play" (once) and "immediate + restart" (immed). */ +#define PROTOSTREAM_FREQ_ONCE (-1) //!< one time play +#define PROTOSTREAM_FREQ_IMMED (0) //!< immediate restart + +//! callback status indicating state of the stream +typedef enum ProtoStreamStatusE +{ + PROTOSTREAM_STATUS_BEGIN = 0, //!< first buffer of data (start of stream) + PROTOSTREAM_STATUS_DATA, //!< data has been received + PROTOSTREAM_STATUS_SYNC, //!< data was dropped from the stream + PROTOSTREAM_STATUS_DONE //!< end of stream (no data) +} ProtoStreamStatusE; + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! opaque module state +typedef struct ProtoStreamRefT ProtoStreamRefT; + +//! data callback definition +typedef int32_t (ProtoStreamCallbackT)(ProtoStreamRefT *pProtoStream, ProtoStreamStatusE eStatus, const uint8_t *pBuffer, int32_t iBufSize, void *pUserData); + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create the module +DIRTYCODE_API ProtoStreamRefT *ProtoStreamCreate(int32_t iBufSize); + +// destroy the module +DIRTYCODE_API void ProtoStreamDestroy(ProtoStreamRefT *pProtoStream); + +// set recurring callback +DIRTYCODE_API void ProtoStreamSetCallback(ProtoStreamRefT *pProtoStream, int32_t iRate, ProtoStreamCallbackT *pCallback, void *pUserData); + +// set http custom header send/recv callbacks +DIRTYCODE_API void ProtoStreamSetHttpCallback(ProtoStreamRefT *pProtoStream, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData); + +// begin streaming an internet media resource +DIRTYCODE_API int32_t ProtoStreamOpen(ProtoStreamRefT *pProtoStream, const char *pUrl, int32_t iFreq); + +// begin streaming an internet media resource, with a request body (using POST) +DIRTYCODE_API int32_t ProtoStreamOpen2(ProtoStreamRefT *pProtoStream, const char *pUrl, const char *pReq, int32_t iFreq); + +// read at least iMinLen bytes from stream into user buffer +DIRTYCODE_API int32_t ProtoStreamRead(ProtoStreamRefT *pProtoStream, char *pBuffer, int32_t iBufLen, int32_t iMinLen); + +// pause stream +DIRTYCODE_API void ProtoStreamPause(ProtoStreamRefT *pProtoStream, uint8_t bPause); + +// stop streaming +DIRTYCODE_API void ProtoStreamClose(ProtoStreamRefT *pProtoStream); + +// get module status +DIRTYCODE_API int32_t ProtoStreamStatus(ProtoStreamRefT *pProtoStream, int32_t iStatus, void *pBuffer, int32_t iBufSize); + +// set control options +DIRTYCODE_API int32_t ProtoStreamControl(ProtoStreamRefT *pProtoStream, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); + +// update module +DIRTYCODE_API void ProtoStreamUpdate(ProtoStreamRefT *pProtoStream); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _protostream_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/prototunnel.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/prototunnel.h new file mode 100644 index 00000000..8dbf867d --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/prototunnel.h @@ -0,0 +1,148 @@ +/*H********************************************************************************/ +/*! + \File prototunnel.h + + \Description + ProtoTunnel creates and manages a Virtual DirtySock Tunnel (VDST) + connection. The tunnel transparently bundles data sent from multiple ports + to a specific remote host into a single send, and optionally encrypts + portions of that packet. Only data sent over a UDP socket may be + tunneled. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 12/02/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _prototunnel_h +#define _prototunnel_h + +/*! +\Moduledef ProtoTunnel ProtoTunnel +\Modulemember Proto +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +// prototunnel protocol versions (maj.min) +#define PROTOTUNNEL_VERSION_1_1 (0x0101) +#define PROTOTUNNEL_VERSION (PROTOTUNNEL_VERSION_1_1) //!< default protocol version +#define PROTOTUNNEL_VERSION_MIN (PROTOTUNNEL_VERSION_1_1) //!< minimum protocol version +#define PROTOTUNNEL_VERSION_MAX (PROTOTUNNEL_VERSION_1_1) //!< maximum protocol version + +//! maximum number of ports that can be mapped to a tunnel +#define PROTOTUNNEL_MAXPORTS (8) + +//! port flag indicating data is encrypted +#define PROTOTUNNEL_PORTFLAG_ENCRYPTED (1) +#define PROTOTUNNEL_PORTFLAG_AUTOFLUSH (2) + +//! max key length +#define PROTOTUNNEL_MAXKEYLEN (128) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! tunnel event types +typedef enum ProtoTunnelEventE +{ + PROTOTUNNEL_EVENT_RECVNOMATCH, //!< data received from source with no tunnel mapping + + PROTOTUNNEL_NUMEVENTS //!< total number of events +} ProtoTunnelEventE; + +//! map description +typedef struct ProtoTunnelInfoT +{ + uint32_t uRemoteClientId; //!< unique client ID of remote client + uint32_t uRemoteAddr; //!< remote tunnel address + uint16_t uRemotePort; //!< remote tunnel port (if zero, use local port) + uint16_t aRemotePortList[PROTOTUNNEL_MAXPORTS]; //!< port(s) to map + uint8_t aPortFlags[PROTOTUNNEL_MAXPORTS]; //!< port flags + uint16_t uTunnelVers; //!< tunnel protocol version + uint8_t _pad[2]; +} ProtoTunnelInfoT; + +//! tunnel statistics +typedef struct ProtoTunnelStatT +{ + uint32_t uLastPacketTime; //!< last time packet was snet or received + uint32_t uUpdateTime; //!< time when ProtoTunnelStatT was updated + uint32_t uPrevUpdateTime; //!< last time ProtoTunnelStatT was updated (can be used to keep track of update interval) + uint32_t uEfficiency; //!< send/receive efficiency for the last update interval + uint32_t uRawBytesPerSecond; //!< raw byte per second for the last update interval + uint32_t uBytePerSecond; //!< data bytes (user data) per second for the last update interval + uint32_t uNumBytes; //!< total number of bytes + uint32_t uNumSubpacketBytes; //!< total number of subpacket bytes (user data) + uint16_t uNumPackets; //!< total number of packets + uint16_t uNumSubpackets; //!< total number of subpackets + uint16_t uNumDiscards; //!< total number of out-of-order packet discards + uint16_t _pad; +} ProtoTunnelStatT; + +//! opaque module ref +typedef struct ProtoTunnelRefT ProtoTunnelRefT; + +//! prototunnel event callback +typedef void (ProtoTunnelCallbackT)(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelEventE eEvent, const char *pData, int32_t iDataSize, struct sockaddr *pRecvAddr, void *pUserData); + +//! raw inbound data receive callback (shall return 1 if it swallows the data, 0 otherwise) +typedef int32_t (RawRecvCallbackT)(SocketT *pSocket, uint8_t *pData, int32_t iRecvLen, const struct sockaddr *pFrom, int32_t iFromLen, void *pUserData); + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create the module +DIRTYCODE_API ProtoTunnelRefT *ProtoTunnelCreate(int32_t iMaxTunnels, int32_t iTunnelPort); + +// destroy the module +DIRTYCODE_API void ProtoTunnelDestroy(ProtoTunnelRefT *pProtoTunnel); + +// set event callback +DIRTYCODE_API void ProtoTunnelCallback(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelCallbackT *pCallback, void *pUserData); + +// allocate a tunnel +DIRTYCODE_API int32_t ProtoTunnelAlloc(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelInfoT *pInfo, const char *pKey); + +// free a tunnel +DIRTYCODE_API uint32_t ProtoTunnelFree(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId, const char *pKey); + +// free a tunnel +DIRTYCODE_API uint32_t ProtoTunnelFree2(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId, const char *pKey, uint32_t uAddr); + +// update port mapping for specified tunnel +DIRTYCODE_API int32_t ProtoTunnelUpdatePortList(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId, ProtoTunnelInfoT *pInfo); + +// get module status +DIRTYCODE_API int32_t ProtoTunnelStatus(ProtoTunnelRefT *pProtoTunnel, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize); + +// control the module +DIRTYCODE_API int32_t ProtoTunnelControl(ProtoTunnelRefT *pProtoTunnel, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue); + +// update the module +DIRTYCODE_API void ProtoTunnelUpdate(ProtoTunnelRefT *pProtoTunnel); + +// use this function to send raw data to a remote host from the prototunnel socket +DIRTYCODE_API int32_t ProtoTunnelRawSendto(ProtoTunnelRefT *pProtoTunnel, const char *pBuf, int32_t iLen, const struct sockaddr *pTo, int32_t iToLen); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _prototunnel_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protoupnp.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protoupnp.h new file mode 100644 index 00000000..cba29ae9 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protoupnp.h @@ -0,0 +1,90 @@ +/*H********************************************************************************/ +/*! + \File protoupnp.h + + \Description + Implements a simple UPnP client, designed specifically to talk to a UPnP + router and open up a firewall port for peer-peer communication with a + remote client. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/23/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _protoupnp_h +#define _protoupnp_h + +/*! +\Moduledef ProtoUpnp ProtoUpnp +\Modulemember Proto +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +// status bits for ProtoUpnpStatus('stat') + +#define PROTOUPNP_STATUS_DISCOVERED (1) //!< discovered a upnp device +#define PROTOUPNP_STATUS_DESCRIBED (2) //!< described a upnp device +#define PROTOUPNP_STATUS_GOTEXTADDR (4) //!< got external address for device +#define PROTOUPNP_STATUS_ADDPORTMAP (8) //!< successfully added port mapping +#define PROTOUPNP_STATUS_DELPORTMAP (16) //!< successfully deleted port mapping +#define PROTOUPNP_STATUS_FNDPORTMAP (32) //!< found existing port mapping + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! protoupnp macro element definition +typedef struct ProtoUpnpMacroT +{ + int32_t iControl; + int32_t iValue; + int32_t iValue2; + void *pValue; +} ProtoUpnpMacroT; + +//! opaque module ref +typedef struct ProtoUpnpRefT ProtoUpnpRefT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create the module +DIRTYCODE_API ProtoUpnpRefT *ProtoUpnpCreate(void); + +// get module ref +DIRTYCODE_API ProtoUpnpRefT *ProtoUpnpGetRef(void); + +// destroy the module +DIRTYCODE_API void ProtoUpnpDestroy(ProtoUpnpRefT *pProtoUpnp); + +// get module status +DIRTYCODE_API int32_t ProtoUpnpStatus(ProtoUpnpRefT *pProtoUpnp, int32_t iSelect, void *pBuf, int32_t iBufSize); + +// protoupnp control +DIRTYCODE_API int32_t ProtoUpnpControl(ProtoUpnpRefT *pProtoUpnp, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue); + +// update the module +DIRTYCODE_API void ProtoUpnpUpdate(ProtoUpnpRefT *pProtoUpnp); + +#ifdef __cplusplus +}; +#endif + +//@} + +#endif // _protoupnp_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/proto/protowebsocket.h b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protowebsocket.h new file mode 100644 index 00000000..86a18543 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/proto/protowebsocket.h @@ -0,0 +1,122 @@ +/*H********************************************************************************/ +/*! + \File protowebsocket.h + + \Description + This module implements a WebSocket client as documented in RFC 6455 + (http://tools.ietf.org/html/rfc6455). Note that implementation details + may vary when a platform-specific implementation is required. The API + as currently implemented offers stream-like operations; message-based + operations are currently not supported. + + \Todo + - Message-based APIs + + \Copyright + Copyright (c) 2012 Electronic Arts Inc. + + \Version 11/26/2012 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _protowebsocket_h +#define _protowebsocket_h + +/*! +\Moduledef ProtoWebSocket ProtoWebSocket +\Modulemember Proto +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +// close reasons +#define PROTOWEBSOCKET_CLOSEREASON_BASE (1000) +#define PROTOWEBSOCKET_CLOSEREASON_NORMAL (PROTOWEBSOCKET_CLOSEREASON_BASE+0) +#define PROTOWEBSOCKET_CLOSEREASON_GOINGAWAY (PROTOWEBSOCKET_CLOSEREASON_BASE+1) +#define PROTOWEBSOCKET_CLOSEREASON_PROTOCOLERROR (PROTOWEBSOCKET_CLOSEREASON_BASE+2) +#define PROTOWEBSOCKET_CLOSEREASON_UNSUPPORTEDDATA (PROTOWEBSOCKET_CLOSEREASON_BASE+3) +#define PROTOWEBSOCKET_CLOSEREASON_NOSTATUSRECVD (PROTOWEBSOCKET_CLOSEREASON_BASE+5) +#define PROTOWEBSOCKET_CLOSEREASON_ABNORMALCLOSURE (PROTOWEBSOCKET_CLOSEREASON_BASE+6) +#define PROTOWEBSOCKET_CLOSEREASON_INVALIDFRAMEDATA (PROTOWEBSOCKET_CLOSEREASON_BASE+7) +#define PROTOWEBSOCKET_CLOSEREASON_POLICYVIOLATION (PROTOWEBSOCKET_CLOSEREASON_BASE+8) +#define PROTOWEBSOCKET_CLOSEREASON_MESSAGETOOBIG (PROTOWEBSOCKET_CLOSEREASON_BASE+9) +#define PROTOWEBSOCKET_CLOSEREASON_MANDATORYEXT (PROTOWEBSOCKET_CLOSEREASON_BASE+10) +#define PROTOWEBSOCKET_CLOSEREASON_INTERNALERR (PROTOWEBSOCKET_CLOSEREASON_BASE+11) +#define PROTOWEBSOCKET_CLOSEREASON_SERVICERESTART (PROTOWEBSOCKET_CLOSEREASON_BASE+12) +#define PROTOWEBSOCKET_CLOSEREASON_TRYAGAINLATER (PROTOWEBSOCKET_CLOSEREASON_BASE+13) +#define PROTOWEBSOCKET_CLOSEREASON_TLSHANDSHAKE (PROTOWEBSOCKET_CLOSEREASON_BASE+15) +#define PROTOWEBSOCKET_CLOSEREASON_MAX (PROTOWEBSOCKET_CLOSEREASON_TLSHANDSHAKE) +#define PROTOWEBSOCKET_CLOSEREASON_COUNT (PROTOWEBSOCKET_CLOSEREASON_MAX-PROTOWEBSOCKET_CLOSEREASON_BASE+1) +// unidentified close reason +#define PROTOWEBSOCKET_CLOSEREASON_UNKNOWN (PROTOWEBSOCKET_CLOSEREASON_BASE+100) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! opaque module state definition +typedef struct ProtoWebSocketRefT ProtoWebSocketRefT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// allocate a websocket connection and prepare for use +DIRTYCODE_API ProtoWebSocketRefT *ProtoWebSocketCreate(int32_t iBufSize); + +// allocate a websocket connection and prepare for use (protossl function signature compatible) +DIRTYCODE_API ProtoWebSocketRefT *ProtoWebSocketCreate2(void); + +// destroy a connection and release state +DIRTYCODE_API void ProtoWebSocketDestroy(ProtoWebSocketRefT *pWebSocket); + +// initiate a connection to a server +DIRTYCODE_API int32_t ProtoWebSocketConnect(ProtoWebSocketRefT *pWebSocket, const char *pUrl); + +// disconnect from the server +DIRTYCODE_API int32_t ProtoWebSocketDisconnect(ProtoWebSocketRefT *pWebSocket); + +// send data to a server over a websocket connection +DIRTYCODE_API int32_t ProtoWebSocketSend(ProtoWebSocketRefT *pWebSocket, const char *pBuffer, int32_t iLength); + +// send text data to a server over a websocket connection +DIRTYCODE_API int32_t ProtoWebSocketSendText(ProtoWebSocketRefT *pWebSocket, const char *pBuffer); + +// send a message to a server over a websocket connection +DIRTYCODE_API int32_t ProtoWebSocketSendMessage(ProtoWebSocketRefT *pWebSocket, const char *pBuffer, int32_t iLength); + +// send a text message to a server over a websocket connection +DIRTYCODE_API int32_t ProtoWebSocketSendMessageText(ProtoWebSocketRefT *pWebSocket, const char *pBuffer); + +// recv data from a server over a websocket connection +DIRTYCODE_API int32_t ProtoWebSocketRecv(ProtoWebSocketRefT *pWebSocket, char *pBuffer, int32_t iLength); + +// recv a message from a server over a websocket connection +DIRTYCODE_API int32_t ProtoWebSocketRecvMessage(ProtoWebSocketRefT *pWebSocket, char *pBuffer, int32_t iLength); + +// get module status +DIRTYCODE_API int32_t ProtoWebSocketStatus(ProtoWebSocketRefT *pWebSocket, int32_t iSelect, void *pBuffer, int32_t iBufSize); + +// control module behavior +DIRTYCODE_API int32_t ProtoWebSocketControl(ProtoWebSocketRefT *pWebSocket, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue); + +// give time to module to do its thing (should be called periodically to allow module to perform work) +DIRTYCODE_API void ProtoWebSocketUpdate(ProtoWebSocketRefT *pWebSocket); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _protowebsocket_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/aws.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/aws.h new file mode 100644 index 00000000..74defc6a --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/aws.h @@ -0,0 +1,70 @@ +/*H********************************************************************************/ +/*! + \File aws.h + + \Description + Implements AWS utility functions, including SigV4 signing and signed binary + event reading and writing. + + \Copyright + Copyright 2018 Electronic Arts + + \Version 12/26/2018 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _aws_h +#define _aws_h + +/*! +\Moduledef AWS AWS +\Modulemember Util +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! AWS signing info +typedef struct AWSSignInfoT +{ + char strRegion[32]; //!< region request is being made in + char strService[32]; //!< name of service for request + char strKeyPath[64]; //!< keypath for request + char strSignature[65]; //!< latest signature, hex encoded + char strKey[64]; //!< secret key used to sign request +} AWSSignInfoT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// sign given header with AWS Signature Version 4 signing process +DIRTYCODE_API int32_t AWSSignSigV4(char *pHeader, int32_t iHeaderSize, const char *pRequest, const char *pKeyInfo, const char *pService, AWSSignInfoT *pSignInfo); + +// write a signed binary event +DIRTYCODE_API int32_t AWSWriteEvent(uint8_t *pBuffer, int32_t iBufSize, const uint8_t *pData, int32_t *pDataSize, const char *pEvent, AWSSignInfoT *pSignInfo); + +// read a signed binary event +DIRTYCODE_API int32_t AWSReadEvent(const uint8_t *pBuffer, int32_t iBufLen, char *pEventType, int32_t iEventSize, char *pMessage, int32_t *pMessageSize); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _aws_h + + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/base64.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/base64.h new file mode 100644 index 00000000..febd280b --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/base64.h @@ -0,0 +1,88 @@ +/*H*******************************************************************/ +/*! + \File base64.h + + \Description + This module Base-64 encoding/decoding as defined in RFC + 989, 1040, 1113, 1421, 1521 and 2045. + + \Copyright + Copyright (c) Electronic Arts 2003. ALL RIGHTS RESERVED. + + \Version 1.0 12/11/2003 (SJB) First Version +*/ +/*******************************************************************H*/ + +#ifndef _base64_h +#define _base64_h + +/*! +\Moduledef Base64 Base64 +\Modulemember Util +*/ +//@{ + +/*** Include files ***************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines *********************************************************/ + +/*** Macros **********************************************************/ + +/*! The number of bytes it takes to Base-64 encode the given number + of bytes. The result includes any required padding but not a '\0' + terminator. */ +#define Base64EncodedSize(x) ((((x)+2)/3)*4) + +/*! The maximum number of bytes it takes to hold the decoded version + of a Base-64 encoded string that is 'x' bytes long. + + In this version of Base-64, 'x' is always a multiple of 4 and the + result is always a multiple of 3 (i.e. there may be up to 2 padding + bytes at the end of the decoded value). It is assumed that the + exact length of the string (minus any padding) is either encoded in + the string or is external to the string. */ +#define Base64DecodedSize(x) (((x)/4)*3) + +/*** Type Definitions ************************************************/ + +/*** Variables *******************************************************/ + +/*** Functions *******************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// Base64 encode a string +DIRTYCODE_API void Base64Encode(int32_t iInputLen, const char *pInput, char *pOutput); + +// Base64 encode a string, buffer-safe version +DIRTYCODE_API int32_t Base64Encode2(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen); + +// decode a Base64-encoded string +DIRTYCODE_API int32_t Base64Decode(int32_t iInputLen, const char *pInput, char *pOutput); + +// decode a Base64-encoded string, return decoded size +DIRTYCODE_API int32_t Base64Decode2(int32_t iInputLen, const char *pInput, char *pOutput); + +// decode a Base64-encoded string, return decoded size, buffer-safe version +DIRTYCODE_API int32_t Base64Decode3(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen); + +// Base64 encode a url string +DIRTYCODE_API int32_t Base64EncodeUrl(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen); + +// Base64 encode a url string with more options +DIRTYCODE_API int32_t Base64EncodeUrl2(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen, uint8_t bPadded); + +// decode a Base64-encode url string +DIRTYCODE_API int32_t Base64DecodeUrl(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/binary7.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/binary7.h new file mode 100644 index 00000000..288708ae --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/binary7.h @@ -0,0 +1,55 @@ +/*H*************************************************************************************/ +/*! + \File binary7.h + + \Description + This module provides routines to encode/decode binary7 data to/from a buffer. + + \Copyright + Copyright (c) Electronic Arts 2009. ALL RIGHTS RESERVED. + + \Version 1.0 11/02/2009 (cadam) First version +*/ +/*************************************************************************************H*/ + +#ifndef _binary7_h +#define _binary7_h + +/*! +\Moduledef Binary7 Binary7 +\Modulemember Util +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// set a binary field using more efficient encoding +DIRTYCODE_API char *Binary7Encode(unsigned char *pDst, int32_t iDstLen, unsigned const char *pSrc, int32_t iSrcLen, uint32_t bTerminate); + +// get binary field contents +DIRTYCODE_API unsigned const char *Binary7Decode(unsigned char *pDst, int32_t iDstLen, unsigned const char *pSrc); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _binary7_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/hpack.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/hpack.h new file mode 100644 index 00000000..c59fb602 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/hpack.h @@ -0,0 +1,63 @@ +/*H********************************************************************************/ +/*! + \File hpack.h + + \Description + This module implements a decode/encoder based on the HPACK spec + (https://tools.ietf.org/html/rfc7541). Which is used for encoding/decoding + the HEADERS frame in the HTTP/2 protocol. + + \Copyright + Copyright (c) Electronic Arts 2016. ALL RIGHTS RESERVED. +*/ +/********************************************************************************H*/ + +#ifndef _hpack_h +#define _hpack_h + +/*! +\Moduledef Hpack Hpack +\Modulemember Util +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Type Definitions *************************************************************/ + +//! opaque module ref +typedef struct HpackRefT HpackRefT; + +/*** Functions ********************************************************************/ + +#if defined(__cplusplus) +extern "C" { +#endif + +// create the module +DIRTYCODE_API HpackRefT *HpackCreate(uint32_t uTableMax, uint8_t bDecoder); + +// destroy the module +DIRTYCODE_API void HpackDestroy(HpackRefT *pRef); + +// unpack the header +DIRTYCODE_API int32_t HpackDecode(HpackRefT *pRef, const uint8_t *pInput, int32_t iInpSize, char *pOutput, int32_t iOutSize); + +// encode the header +DIRTYCODE_API int32_t HpackEncode(HpackRefT *pRef, const char *pInput, uint8_t *pOutput, int32_t iOutSize, uint8_t bHuffman); + +// clear the dynamic table +DIRTYCODE_API void HpackClear(HpackRefT *pRef); + +// resize the dynamic table +DIRTYCODE_API void HpackResize(HpackRefT *pRef, uint32_t uTableSize, uint8_t bSendUpdate); + +#if defined(__cplusplus) +} +#endif + +//@} + +#endif // _hpack_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/jsonformat.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/jsonformat.h new file mode 100644 index 00000000..5b2ae4c7 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/jsonformat.h @@ -0,0 +1,98 @@ +/*H*************************************************************************************/ +/*! + \File jsonformat.h + + \Description + This module formats simple Json, in a linear fashion, using a character buffer + that the client provides. + + \Notes + References: + JSON RFC: http://tools.ietf.org/html/rfc4627 + + \Copyright + Copyright (c) Electronic Arts 2012. + + \Version 12/11/2012 (jbrookes) First Version +*/ +/*************************************************************************************H*/ + +#ifndef _jsonformat_h +#define _jsonformat_h + +/*! +\Moduledef JsonFormat JsonFormat +\Modulemember Util +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +#define JSON_ERR_NONE (0) //!< no error +#define JSON_ERR_FULL (-1) //!< buffer is full, no space to add attribute or element. +#define JSON_ERR_UNINIT (-2) //!< did not call JsonInit() prior to writing. +#define JSON_ERR_NOT_OPEN (-3) //!< attempt to set elem or attrib, but, no tag opened. +#define JSON_ERR_ATTR_POSITION (-4) //!< attempt to set an attrib, but, child element already added to tag. +#define JSON_ERR_INVALID_PARAM (-5) //!< invalid parameter passed to function + +// Encoding control flags +#define JSON_FL_WHITESPACE (1) //!< include formatting whitespace + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// init the API -- pass in the character buffer. MUST be called first. +DIRTYCODE_API void JsonInit(char *pBuffer, int32_t iBufLen, uint8_t uFlags); + +// notify the API that the buf size was increased +DIRTYCODE_API void JsonBufSizeIncrease(char *pBuffer, int32_t iNewBufLen); + +// finish JSON output to this buffer; use returned pointer ref +DIRTYCODE_API char *JsonFinish(char *pBuffer); + +// start an object +DIRTYCODE_API int32_t JsonObjectStart(char *pBuffer, const char *pName); + +// end the current object -- must have an outstanding open object +DIRTYCODE_API int32_t JsonObjectEnd(char *pBuffer); + +// start an array +DIRTYCODE_API int32_t JsonArrayStart(char *pBuffer, const char *pName); + +// end the current array -- must have an outstanding open array +DIRTYCODE_API int32_t JsonArrayEnd(char *pBuffer); + +// add a string element +DIRTYCODE_API int32_t JsonAddStr(char *pBuffer, const char *pElemName, const char *pValue); + +// add a number +DIRTYCODE_API int32_t JsonAddNum(char *pBuffer, const char *pElemName, const char *pFormatSpec, float fValue); + +// add an integer element +DIRTYCODE_API int32_t JsonAddInt(char *pBuffer, const char *pElemName, int64_t iValue); + +// add a date (will be encoded as a string; use JsonAddInteger for integer encoding) +DIRTYCODE_API int32_t JsonAddDate(char *pBuffer, const char *pElemName, uint32_t uEpochDate); + +// build a JSON output string from a formatted pattern (like vprintf) $$TODO +#ifdef va_start +DIRTYCODE_API char *JsonFormatVPrintf(char *pJsonBuff, int32_t iBufLen, const char *pFormat, va_list pFmtArgs); +#endif + +// build a JSON output string from a formatted pattern (like printf) $$TODO +DIRTYCODE_API char *JsonFormatPrintf(char *pJsonBuff, int32_t iBufLen, const char *pFormat, ...); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _jsonformat_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/jsonparse.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/jsonparse.h new file mode 100644 index 00000000..bf6211b3 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/jsonparse.h @@ -0,0 +1,98 @@ +/*H*************************************************************************************/ +/*! + \File jsonparse.h + + \Description + A simple JSON parser. + + \Notes + Written by Greg Schaefer outside of EA for a personal project, but donated + back for a good cause. + + \Copyright + Copyright (c) Electronic Arts 2012. + + \Version 12/11/2012 (jbrookes) Added to DirtySDK, added some new functionality +*/ +/*************************************************************************************H*/ + +#ifndef _jsonparse_h +#define _jsonparse_h + +/*! +\Moduledef JsonParse JsonParse +\Modulemember Util +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +//! alternate name for JsonSeekObjectEnd (DEPRECATED) +#define JsonGetListItemEnd(__pObject) JsonSeekObjectEnd(__pObject) + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// parse json, preparing lookup for fast dereference +DIRTYCODE_API int32_t JsonParse(uint16_t *pDst, int32_t iMax, const char *pSrc, int32_t iLen); + +// parse json, allocate required memory for parse buffer internally +DIRTYCODE_API uint16_t *JsonParse2(const char *pSrc, int32_t iLen, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData); + +// locate a json element +DIRTYCODE_API const char *JsonFind(const uint16_t *pParse, const char *pName); + +// locate a json element, starting from an offset, with an optional array index +DIRTYCODE_API const char *JsonFind2(const uint16_t *pParse, const char *pJson, const char *pName, int32_t iIndex); + +// get a string element +DIRTYCODE_API int32_t JsonGetString(const char *pJson, char *pBuffer, int32_t iLength, const char *pDefault); + +// get a string element, with accumulating error +DIRTYCODE_API int32_t JsonGetString2(const char *pJson, char *pBuffer, int32_t iLength, const char *pDefault, uint8_t *pError); + +// get an integer element +DIRTYCODE_API int64_t JsonGetInteger(const char *pJson, int64_t iDefault); + +// get an integer element, with range validation and accumulating error +DIRTYCODE_API int64_t JsonGetInteger2(const char *pJson, int64_t iDefault, int64_t iMin, int64_t iMax, uint8_t *pError); + +// get a date in ISO_8601 format +DIRTYCODE_API uint32_t JsonGetDate(const char *pJson, uint32_t uDefault); + +// get a date in ISO_8601 format, with accumulating error +DIRTYCODE_API uint32_t JsonGetDate2(const char *pJson, uint32_t uDefault, uint8_t *pError); + +// get a boolean element +DIRTYCODE_API uint8_t JsonGetBoolean(const char *pJson, uint8_t bDefault); + +// get a boolean element, with accumulating error +DIRTYCODE_API uint8_t JsonGetBoolean2(const char *pJson, uint8_t bDefault, uint8_t *pError); + +// get an enum element +DIRTYCODE_API int32_t JsonGetEnum(const char *pJson, const char *pEnumArray[], int32_t iDefault); + +// get an enum element, with accumulating error +DIRTYCODE_API int32_t JsonGetEnum2(const char *pJson, const char *pEnumArray[], int32_t iDefault, uint8_t *pError); + +// seek to the end of an object within the JSON text buffer +DIRTYCODE_API const char *JsonSeekObjectEnd(const char *pObject); + +// seek to the start of the value of a key / value pair within the JSON text buffer +DIRTYCODE_API const char *JsonSeekValue(const char *pKey); + + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _jsonparse_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/murmurhash3.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/murmurhash3.h new file mode 100644 index 00000000..29e32577 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/murmurhash3.h @@ -0,0 +1,79 @@ +/*H********************************************************************************/ +/*! + \File murmurhash3.h + + \Description + An implementation of MurmurHash3, based heavily on the x64 128bit output + implementation. MurmurHash3 is a public domain hashing algorithm written + by Austin Appleby, ref http://code.google.com/p/smhasher/wiki/MurmurHash3. + + MurmurHash3 is not cryptographically secure, however it has excellent + collision resistance and is orders of magnitude faster than the fastest + secure cryptographic hash. + + \Copyright + Copyright (c) 2014 Electronic Arts + + \Version 03/07/2014 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _murmurhash3_h +#define _murmurhash3_h + +/*! +\Moduledef MurmurHash3 MurmurHash3 +\Modulemember Util +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +#define MURMURHASH_HASHSIZE (16) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct MurmurHash3T +{ + uint64_t aState[2]; //!< hash state + int32_t iCount; //!< total byte count + uint8_t aData[16]; //!< partial data block +} MurmurHash3T; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// init the MurmurHash3 context +DIRTYCODE_API void MurmurHash3Init(MurmurHash3T *pContext); + +// init the MurmurHash3 context, with hash size +DIRTYCODE_API void MurmurHash3Init2(MurmurHash3T *pContext, int32_t iHashSize); + +// add data to the MurmurHash3 context (hash the data) +DIRTYCODE_API void MurmurHash3Update(MurmurHash3T *pContext, const void *pBuffer, int32_t iLength); + +// convert MurmurHash3 state into final output form +DIRTYCODE_API void MurmurHash3Final(MurmurHash3T *pContext, void *pBuffer, int32_t iLength); + +// hash the data, all-in-one version +DIRTYCODE_API void MurmurHash3(void *_pOutput, int32_t iOutLen, const void *_pInput, int32_t iInpLen, const void *pKey, int32_t iKeyLen); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _murmurhash3_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/protobufcommon.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/protobufcommon.h new file mode 100644 index 00000000..25227469 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/protobufcommon.h @@ -0,0 +1,59 @@ +/*H*************************************************************************************/ +/*! + \File protobufcommon.h + + \Description + Shared definitions and functionality for the protobuf encoder / decoder + + \Copyright + Copyright (c) Electronic Arts 2017-2018. ALL RIGHTS RESERVED. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************H*/ + +#ifndef _protobufcommon_h +#define _protobufcommon_h + +/*! +\Moduledef ProtobufCommon ProtobufCommon +\Modulemember Util +*/ +//@{ + +/*** Include Files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Type Definitions ******************************************************************/ + +//! different field types that are supported by protobuf. note, the order is needed to follow the defined values of the protocol +typedef enum ProtobufTypeE +{ + PROTOBUF_TYPE_VARINT, //!< int32, int64, uint32, uint64, sint32, sint64, bool, enum + PROTOBUF_TYPE_64BIT, //!< fixed64 (uint64_t), sfixed64 (int64_t), double + PROTOBUF_TYPE_LENGTH_DELIMITED, //!< string, bytes, embedded messages, packed repeated fields + PROTOBUF_TYPE_START_GROUP, //!< groups (deprecated) + PROTOBUF_TYPE_END_GROUP, //!< groups (deprecated) + PROTOBUF_TYPE_32BIT //!< fixed32 (uint32_t), sfixed32 (int32_t), float +} ProtobufTypeE; + +/*** Functions *************************************************************************/ + +#if defined(__cplusplus) +extern "C" { +#endif + +// read the message size out of the payload +DIRTYCODE_API const uint8_t *ProtobufCommonReadSize(const uint8_t *pBuffer, int32_t iBufLen, int32_t *pResult); + +// get the string representation of a type +DIRTYCODE_API const char *ProtobufCommonGetType(ProtobufTypeE eType); + +#if defined(__cplusplus) +} +#endif + +//@} + +#endif // _protobufcommon_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/protobufread.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/protobufread.h new file mode 100644 index 00000000..0f8e0700 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/protobufread.h @@ -0,0 +1,113 @@ +/*H*************************************************************************************/ +/*! + \File protobufread.h + + \Description + Interface of decoder for the Google Protobuf wire format + See: https://developers.google.com/protocol-buffers/docs/encoding + + This module only supports protobuf version 3, if any lesser versions are used + the result is undefined. + + \Copyright + Copyright (c) Electronic Arts 2017-2018. ALL RIGHTS RESERVED. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************H*/ + +#ifndef _protobufread_h +#define _protobufread_h + +/*! +\Moduledef ProtobufRead ProtobufRead +\Modulemember Util +*/ +//@{ + +/*** Includes **************************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Type Definitions ******************************************************************/ + +//! struct for ease of use for the API +typedef struct ProtobufReadT +{ + const uint8_t *pBuffer; //!< pointer to the start of the buffer + const uint8_t *pBufEnd; //!< pointer to the end of the buffer +} ProtobufReadT; + +/*** Functions *************************************************************************/ + +#if defined(__cplusplus) +extern "C" { +#endif + +// initialize the state +DIRTYCODE_API void ProtobufReadInit(ProtobufReadT *pState, const uint8_t *pBuffer, int32_t iBufLen); + +// find a field by identifier +DIRTYCODE_API const uint8_t *ProtobufReadFind(const ProtobufReadT *pState, uint32_t uField); + +// find a field by identifier that allows continuation for non-packed repeated fields +DIRTYCODE_API const uint8_t *ProtobufReadFind2(const ProtobufReadT *pState, uint32_t uField, const uint8_t *pBuffer); + +// read the current field header +DIRTYCODE_API const uint8_t *ProtobufReadHeader(const ProtobufReadT *pState, const uint8_t *pCurrent, uint32_t *pField, uint8_t *pType); + +// skip over a field +DIRTYCODE_API const uint8_t *ProtobufReadSkip(const ProtobufReadT *pState, const uint8_t *pCurrent, uint8_t uType); + +// read the varint int32, int64, uint32, uint64, bool or enum from the buffer +DIRTYCODE_API uint64_t ProtobufReadVarint(const ProtobufReadT *pState, const uint8_t *pCurrent); + +// alternative version used in conjunction with repeated field reading +DIRTYCODE_API const uint8_t *ProtobufReadVarint2(const ProtobufReadT *pState, const uint8_t *pCurrent, uint64_t *pResult); + +// read the repeated varint int32, int64, uint32, uint64, bool or enum from the buffer +DIRTYCODE_API const uint8_t *ProtobufReadRepeatedVarint(const ProtobufReadT *pState, const uint8_t *pCurrent, uint64_t *pResult, int32_t iCount); + +// read the varint sint32 from the buffer (zigzag decoded) +DIRTYCODE_API const uint8_t *ProtobufReadSint32(const ProtobufReadT *pState, const uint8_t *pCurrent, int32_t *pResult); + +// read the repeated varint sint32 from the buffer (zigzag decoded) +DIRTYCODE_API const uint8_t *ProtobufReadRepeatedSint32(const ProtobufReadT *pState, const uint8_t *pCurrent, int32_t *pResult, int32_t iCount); + +// read the varint sint64 from the buffer (zigzag decoded) +DIRTYCODE_API const uint8_t *ProtobufReadSint64(const ProtobufReadT *pState, const uint8_t *pCurrent, int64_t *pResult); + +// read the repeated varint sint64 from the buffer (zigzag decoded) +DIRTYCODE_API const uint8_t *ProtobufReadRepeatedSint64(const ProtobufReadT *pState, const uint8_t *pCurrent, int64_t *pResult, int32_t iCount); + +// read fixed32, float or sfixed32 from the buffer +DIRTYCODE_API const uint8_t *ProtobufReadFixed32(const ProtobufReadT *pState, const uint8_t *pCurrent, void *pOutput); + +// read repeated fixed32, float or sfixed32 from the buffer +DIRTYCODE_API const uint8_t *ProtobufReadRepeatedFixed32(const ProtobufReadT *pState, const uint8_t *pCurrent, void *pOutput, int32_t iOutLen); + +// read fixed64, double or sfixed64 from the buffer +DIRTYCODE_API const uint8_t *ProtobufReadFixed64(const ProtobufReadT *pState, const uint8_t *pCurrent, void *pOutput); + +// read repeated fixed64, double or sfixed64 from the buffer +DIRTYCODE_API const uint8_t *ProtobufReadRepeatedFixed64(const ProtobufReadT *pState, const uint8_t *pCurrent, void *pOutput, int32_t iOutLen); + +// read the bytes from the buffer +DIRTYCODE_API const uint8_t *ProtobufReadBytes(const ProtobufReadT *pState, const uint8_t *pCurrent, uint8_t *pOutput, int32_t iOutLen); + +// read the string from the buffer +DIRTYCODE_API const uint8_t *ProtobufReadString(const ProtobufReadT *pState, const uint8_t *pCurrent, char *pOutput, int32_t iOutLen); + +// read an embedded message information from the buffer +DIRTYCODE_API const uint8_t *ProtobufReadMessage(const ProtobufReadT *pState, const uint8_t *pCurrent, ProtobufReadT *pMsg); + +// read the number of elements in the repeated field +DIRTYCODE_API int32_t ProtobufReadNumRepeatedElements(const ProtobufReadT *pState, uint32_t uField, uint8_t uType); + +#if defined(__cplusplus) +} +#endif + +//@} + +#endif // _protobufread_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/protobufwrite.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/protobufwrite.h new file mode 100644 index 00000000..8b4204fc --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/protobufwrite.h @@ -0,0 +1,105 @@ +/*H*************************************************************************************/ +/*! + \File protobufwrite.h + + \Description + Interface of encoder for the Google Protobuf wire format + See: https://developers.google.com/protocol-buffers/docs/encoding + + \Copyright + Copyright (c) Electronic Arts 2017-2018. ALL RIGHTS RESERVED. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************H*/ + +#ifndef _protobufwrite_h +#define _protobufwrite_h + +/*! +\Moduledef ProtobufWrite ProtobufWrite +\Modulemember Util +*/ +//@{ + +/*** Includes **************************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Type Definitions ******************************************************************/ + +//! used for result for the write functions +typedef enum ProtobufWriteErrorE +{ + PROTOBUFWRITE_ERROR_OK, //!< successful operation + PROTOBUFWRITE_ERROR_FULL, //!< the buffer is full + PROTOBUFWRITE_ERROR_EMBED, //!< called begin when out of slots or calling end when begin hasn't been called +} ProtobufWriteErrorE; + +//! opaque module ref +typedef struct ProtobufWriteRefT ProtobufWriteRefT; + +/*** Functions *************************************************************************/ + +#if defined(__cplusplus) +extern "C" { +#endif + +// initialize the encoder, must be called first +DIRTYCODE_API ProtobufWriteRefT *ProtobufWriteCreate(uint8_t *pBuffer, int32_t iBufLen, uint8_t bEncodeSize); + +// write the varint int32, int64, uint32, uint64, bool or enum to the buffer +DIRTYCODE_API int32_t ProtobufWriteVarint(ProtobufWriteRefT *pState, uint64_t uValue, uint32_t uField); + +// write repeated varint to the buffer +DIRTYCODE_API int32_t ProtobufWriteRepeatedVarint(ProtobufWriteRefT *pState, uint64_t *pValues, int32_t iCount, uint32_t uField); + +// write the varint sint32 to the buffer (zigzag encoded) +DIRTYCODE_API int32_t ProtobufWriteSint32(ProtobufWriteRefT *pState, int32_t iValue, uint32_t uField); + +// write repeated varint sint32 to the buffer (zigzag encoded) +DIRTYCODE_API int32_t ProtobufWriteRepeatedSint32(ProtobufWriteRefT *pState, int32_t *pValues, int32_t iCount, uint32_t uField); + +// write the varint sint64 to the buffer (zigzag encoded) +DIRTYCODE_API int32_t ProtobufWriteSint64(ProtobufWriteRefT *pState, int64_t iValue, uint32_t uField); + +// write repeated varint sint64 to the buffer (zigzag encoded) +DIRTYCODE_API int32_t ProtobufWriteRepeatedSint64(ProtobufWriteRefT *pState, int64_t *pValues, int32_t iCount, uint32_t uField); + +// write fixed32, float or sfixed32 to the buffer +DIRTYCODE_API int32_t ProtobufWriteFixed32(ProtobufWriteRefT *pState, const void *pValue, uint32_t uField); + +// write repeated fixed32, float or sfixed32 to the buffer +DIRTYCODE_API int32_t ProtobufWriteRepeatedFixed32(ProtobufWriteRefT *pState, const void *pInput, int32_t iInpLen, uint32_t uField); + +// write fixed64, double or sfixed64 to the buffer +DIRTYCODE_API int32_t ProtobufWriteFixed64(ProtobufWriteRefT *pState, const void *pValue, uint32_t uField); + +// write repeated fixed64, double or sfixed64 to the buffer +DIRTYCODE_API int32_t ProtobufWriteRepeatedFixed64(ProtobufWriteRefT *pState, const void *pInput, int32_t iInpLen, uint32_t uField); + +// write the length delimited string, bytes or embedded message/packed repeated fields (if already encoded in pValue) to the buffer +DIRTYCODE_API int32_t ProtobufWriteLengthDelimited(ProtobufWriteRefT *pState, const void *pValue, int32_t iLength, uint32_t uField); + +// alias function to write string +#define ProtobufWriteString(pState, pValue, iLength, uField) ProtobufWriteLengthDelimited(pState, pValue, iLength, uField) + +// alias function to write bytes +#define ProtobufWriteBytes(pState, pValue, iLength, uField) ProtobufWriteLengthDelimited(pState, pValue, iLength, uField) + +// begin an embedded message/packed repeated fields +DIRTYCODE_API int32_t ProtobufWriteMessageBegin(ProtobufWriteRefT *pState, uint32_t uField); + +// end an embedded message/packed repeated fields +DIRTYCODE_API int32_t ProtobufWriteMessageEnd(ProtobufWriteRefT *pState); + +// finish the writing of the message, cleanup the module state and return the size of the payload +DIRTYCODE_API int32_t ProtobufWriteDestroy(ProtobufWriteRefT *pState); + +#if defined(__cplusplus) +} +#endif + +//@} + +#endif // _protobufwrite_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/util/utf8.h b/src/thirdparty/dirtysdk/include/DirtySDK/util/utf8.h new file mode 100644 index 00000000..08626745 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/util/utf8.h @@ -0,0 +1,101 @@ +/*H*************************************************************************************************/ +/*! + + \File utf8.h + + \Description + This module implements routines for converting to and from UTF-8. + + \Notes + This code only decodes the first three octets of UTF-8, thus it only handles UCS-2 codes, + not UCS-4 codes. It also does not handle UTF-16 (and surrogate pairs), and is therefore + limited to encoding to/decoding from the basic reference plane. + + Helpful references: + + http://www.utf-8.com/ - links + http://www.cis.ohio-state.edu/cgi-bin/rfc/rfc2279.html - RFC 2279 + http://www.unicode.org/charts/ - UNICODE character charts + http://www-106.ibm.com/developerworks/library/utfencodingforms/ - UNICODE primer + http://www.columbia.edu/kermit/utf8.html - UTF-8 samples + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2003. ALL RIGHTS RESERVED. + + \Version 1.0 03/25/03 (JLB) First version. + +*/ +/*************************************************************************************************H*/ + +#ifndef _utf8_h +#define _utf8_h + +/*! +\Moduledef Utf8 Utf8 +\Modulemember Util +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! UTF-8 to 8bit translation table +typedef struct Utf8TransTblT +{ + uint32_t uRangeBegin; + uint32_t uRangeEnd; + unsigned char *pCodeTbl; +} Utf8TransTblT; + +//! 8bit to UTF-8 translation table +typedef struct Utf8EncodeTblT +{ + uint16_t uCodeTbl[256]; +} Utf8EncodeTblT; + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// strip non-ASCII characters from a UTF-8 encoded string +DIRTYCODE_API int32_t Utf8Strip(char *pOutStr, int32_t iBufSize, const char *pInStr); + +// replace non-ASCII characters in a UTF-8 encoded string with 'cReplace' +DIRTYCODE_API int32_t Utf8Replace(char *pOutStr, int32_t iBufSize, const char *pInStr, char cReplace); + +// get code point length of UTF-8 encoded string +DIRTYCODE_API int32_t Utf8StrLen(const char *pStr); + +// encode a UCS-2 string to UTF-8 +DIRTYCODE_API int32_t Utf8EncodeFromUCS2(char *pOutStr, int32_t iBufLen, const uint16_t *pInStr); + +// encode a single UCS-2 "char" to UTF-8 string. +DIRTYCODE_API int32_t Utf8EncodeFromUCS2CodePt(char *pOutPtr, uint16_t uCodePt); + +// decode a UTF-8 encoded string into UCS-2 +DIRTYCODE_API int32_t Utf8DecodeToUCS2(uint16_t *pOutStr, int32_t iBufLen, const char *pInStr); + +// encode the given 8bit input string to UTF-8, based on the input translation table +DIRTYCODE_API int32_t Utf8EncodeFrom8Bit(char *pOutStr, int32_t iBufLen, const char *pInStr, const Utf8EncodeTblT *pEncodeTbl); + +// translate the given UTF-8 sequence based on the NULL-terminated array of given tables +DIRTYCODE_API int32_t Utf8TranslateTo8Bit(char *pOutStr, int32_t iBufLen, const char *pInStr, char cReplace, const Utf8TransTblT *pTransTbl); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _utf8_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/voip/voip.h b/src/thirdparty/dirtysdk/include/DirtySDK/voip/voip.h new file mode 100644 index 00000000..a61a99ba --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/voip/voip.h @@ -0,0 +1,100 @@ +/*H*************************************************************************************************/ +/*! + + \File voip.h + + \Description + Main include for Voice Over IP module. + + \Notes + MLU: + Every Local User should be registered with the voip subsytem in order to get headset status info with VoipSetLocalUser(). + In order to get fully functional voip a user must be further activated which enrolls them as a participating user with VoipGroupActivateLocalUser(). + + Basically any users that are being pulled into the game should be a participating user. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002-2004. ALL RIGHTS RESERVED. + + \Version 1.0 11/19/02 (IRS) First Version + \Version 2.0 05/13/03 (GWS) Rewrite to fix misc bugs and improve network connection. + \Version 3.0 11/03/03 (JLB) Implemented unloadable support, major code cleanup and reorganization. + \Version 3.5 03/02/04 (JLB) VoIP 2.0 - API changes for future multiple channel support. + \Version 3.6 11/19/08 (mclouatre) Modified synopsis of VoipStatus() +*/ +/*************************************************************************************************H*/ + +#ifndef _voip_h +#define _voip_h + +/*! +\Moduledef VoipApi VoipApi +\Modulemember Voip +*/ +//@{ + +/*** Include files ********************************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/voip/voipdef.h" + +/*** Defines **************************************************************************************/ + +/*** Macros ***************************************************************************************/ + +/*** Type Definitions *****************************************************************************/ + +/*** Variables ************************************************************************************/ + +/*** Functions ************************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// prepare voip for use +DIRTYCODE_API VoipRefT *VoipStartup(int32_t iMaxPeers, int32_t iData); + +// return pointer to current module state +DIRTYCODE_API VoipRefT *VoipGetRef(void); + +// release all voip resources +DIRTYCODE_API void VoipShutdown(VoipRefT *pVoip, uint32_t uShutdownFlags); + +// register/unregister specified local user with the voip sub-system (allows for local headset status query, but not voice acquisition/playback) +DIRTYCODE_API void VoipSetLocalUser(VoipRefT *pVoip, int32_t iLocalUserIndex, uint32_t bRegister); + +// return information about local hardware state on a per-user basis +DIRTYCODE_API int32_t VoipLocalUserStatus(VoipRefT *pVoip, int32_t iLocalUserIndex); + +// return status +DIRTYCODE_API int32_t VoipStatus(VoipRefT *pVoip, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize); //do any of the controls need user index? + +// set control options +DIRTYCODE_API int32_t VoipControl(VoipRefT *pVoip, int32_t iControl, int32_t iValue, void *pValue); //do any of the controls need user index? + +// set speaker output callback (only available on some platforms) +DIRTYCODE_API void VoipSpkrCallback(VoipRefT *pVoip, VoipSpkrCallbackT *pCallback, void *pUserData); + +// pick a channel +DIRTYCODE_API int32_t VoipSelectChannel(VoipRefT *pVoip, int32_t iUserIndex, int32_t iChannel, VoipChanModeE eMode); + +// go back to not using channels +DIRTYCODE_API void VoipResetChannels(VoipRefT *pVoip, int32_t iUserIndex); + +// config voice transcriptions (for default config set uProfile to -1) +DIRTYCODE_API void VoipConfigTranscription(VoipRefT *pVoip, uint32_t uProfile, const char *pUrl, const char *pKey); + +// config text narration (for the default config set uProvider to -1) +DIRTYCODE_API void VoipConfigNarration(VoipRefT *pVoip, uint32_t uProvider, const char *pUrl, const char *pKey); + +// set first party id callback +DIRTYCODE_API void VoipRegisterFirstPartyIdCallback(VoipRefT *pVoip, VoipFirstPartyIdCallbackCbT *pCallback, void *pUserData); +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _voip_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/voip/voipblocklist.h b/src/thirdparty/dirtysdk/include/DirtySDK/voip/voipblocklist.h new file mode 100644 index 00000000..d2089e8c --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/voip/voipblocklist.h @@ -0,0 +1,62 @@ + +/*H*************************************************************************************************/ +/*! + \File voipblocklist.h + + \Description + Allow blocking of voip communication based on account id. + + \Copyright + Copyright (c) 2019 Electronic Arts Inc. + + \Version 07/03/2019 (cvienneau) First Version +*/ +/*************************************************************************************************H*/ + +#ifndef _voipblocklist_h +#define _voipblocklist_h + +/*** Include files ********************************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/voip/voipdef.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ +typedef struct VoipBlockListT VoipBlockListT; + +/*** Macros ***************************************************************************************/ + +/*** Variables ************************************************************************************/ + +/*** Functions ************************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// creates the VoipBlockListT (internal use only by voipcommon) +DIRTYCODE_API VoipBlockListT *VoipBlockListCreate(void); + +// destroys the VoipBlockListT (internal use only by voipcommon) +DIRTYCODE_API void VoipBlockListDestroy(VoipRefT *pVoip); + +// add a user to be blocked by the local user +DIRTYCODE_API uint8_t VoipBlockListAdd(VoipRefT *pVoip, int32_t iLocalUserIndex, int64_t iMutedAccountId); + +// remove a user that was blocked by the local user +DIRTYCODE_API uint8_t VoipBlockListRemove(VoipRefT *pVoip, int32_t iLocalUserIndex, int64_t iMutedAccountId); + +// check if a user is blocked by the local user +DIRTYCODE_API uint8_t VoipBlockListIsBlocked(VoipRefT *pVoip, int32_t iLocalUserIndex, int64_t iMutedAccountId); + +// clear the blocked list for the local user (-1 for all users) +DIRTYCODE_API uint8_t VoipBlockListClear(VoipRefT *pVoip, int32_t iLocalUserIndex); + +#ifdef __cplusplus +} +#endif + +#endif // _voipblocklist_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/voip/voipcodec.h b/src/thirdparty/dirtysdk/include/DirtySDK/voip/voipcodec.h new file mode 100644 index 00000000..148494a4 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/voip/voipcodec.h @@ -0,0 +1,137 @@ +/*H********************************************************************************/ +/*! + \File voipcodec.h + + \Description + Defines an interface for Voip codec modules, and a singleton dispatcher + used to manage codec instances. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 08/04/2004 (jbrookes) First version +*/ +/********************************************************************************H*/ + +#ifndef _voipcodec_h +#define _voipcodec_h + +/*! +\Moduledef VoipCodec VoipCodec +\Modulemember Voip +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/voip/voiptranscribe.h" + +/*** Defines **********************************************************************/ + +//! used by some of the fractional integer codes +#define VOIP_CODEC_OUTPUT_FRACTIONAL (12) + +//! operate on active codec (passed for ident, valid for VoipDestroy, VoipControl) +#define VOIP_CODEC_ACTIVE (-1) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! opaque module ref +typedef struct VoipCodecRefT VoipCodecRefT; + +//! create function type +typedef VoipCodecRefT *(VoipCodecCreateT)(int32_t iNumDecoders); + +//! destroy function type +typedef void (VoipCodecDestroyT)(VoipCodecRefT *pState); + +//! encode function type +typedef int32_t (VoipCodecEncodeT)(VoipCodecRefT *pState, uint8_t *pOutput, const int16_t *pInput, int32_t iNumSamples); + +//! decode function type +typedef int32_t (VoipCodecDecodeT)(VoipCodecRefT *pState, int32_t *pOutput, const uint8_t *pInput, int32_t iInputBytes, int32_t iChannel); + +//! reset function type +typedef void (VoipCodecResetT)(VoipCodecRefT *pState); + +//! control function type +typedef int32_t (VoipCodecControlT)(VoipCodecRefT *pState, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue); + +//! status function type +typedef int32_t (VoipCodecStatusT)(VoipCodecRefT *pState, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize); + +//! codec function block +typedef struct VoipCodecDefT +{ + VoipCodecCreateT *pCreate; + VoipCodecDestroyT *pDestroy; + VoipCodecEncodeT *pEncode; + VoipCodecDecodeT *pDecode; + VoipCodecResetT *pReset; + VoipCodecControlT *pControl; + VoipCodecStatusT *pStatus; +} VoipCodecDefT; + +//! codec state structure +struct VoipCodecRefT +{ + const VoipCodecDefT *pCodecDef; + int32_t iDecodeChannels; + + #if DIRTYCODE_LOGGING + int32_t iDebugLevel; //>16)&0xff) +#define VOIPTRANSCRIBE_PROFILE_FORMAT(_uProfile) (VoipTranscribeFormatE)(((_uProfile)>>8)&0xff) +#define VOIPTRANSCRIBE_PROFILE_TRANSPORT(_uProfile) (VoipTranscribeTransportE)((_uProfile)&0xff) + +// disabled profile +#define VOIPTRANSCRIBE_PROFILE_DISABLED VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_NONE, VOIPTRANSCRIBE_FORMAT_NONE, VOIPTRANSCRIBE_TRANSPORT_NONE) +// watson profiles +#define VOIPTRANSCRIBE_PROFILE_IBMWATSON_HTTP_LI16 VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_IBMWATSON, VOIPTRANSCRIBE_FORMAT_LI16, VOIPTRANSCRIBE_TRANSPORT_HTTP) +#define VOIPTRANSCRIBE_PROFILE_IBMWATSON_HTTP_WAV16 VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_IBMWATSON, VOIPTRANSCRIBE_FORMAT_WAV16, VOIPTRANSCRIBE_TRANSPORT_HTTP) +#define VOIPTRANSCRIBE_PROFILE_IBMWATSON_HTTP_OPUS VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_IBMWATSON, VOIPTRANSCRIBE_FORMAT_OPUS, VOIPTRANSCRIBE_TRANSPORT_HTTP) +#define VOIPTRANSCRIBE_PROFILE_IBMWATSON_WEBSOCKETS_LI16 VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_IBMWATSON, VOIPTRANSCRIBE_FORMAT_LI16, VOIPTRANSCRIBE_TRANSPORT_WEBSOCKETS) +#define VOIPTRANSCRIBE_PROFILE_IBMWATSON_WEBSOCKETS_WAV16 VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_IBMWATSON, VOIPTRANSCRIBE_FORMAT_WAV16, VOIPTRANSCRIBE_TRANSPORT_WEBSOCKETS) +#define VOIPTRANSCRIBE_PROFILE_IBMWATSON_WEBSOCKETS_OPUS VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_IBMWATSON, VOIPTRANSCRIBE_FORMAT_OPUS, VOIPTRANSCRIBE_TRANSPORT_WEBSOCKETS) +// bing profiles +#define VOIPTRANSCRIBE_PROFILE_MICROSOFT_HTTP_WAV16 VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_MICROSOFT, VOIPTRANSCRIBE_FORMAT_WAV16, VOIPTRANSCRIBE_TRANSPORT_HTTP) +#define VOIPTRANSCRIBE_PROFILE_MICROSOFT_HTTP_OPUS VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_MICROSOFT, VOIPTRANSCRIBE_FORMAT_OPUS, VOIPTRANSCRIBE_TRANSPORT_HTTP) +// google profiles +#define VOIPTRANSCRIBE_PROFILE_GOOGLE_HTTP_LI16 VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_GOOGLE, VOIPTRANSCRIBE_FORMAT_LI16, VOIPTRANSCRIBE_TRANSPORT_HTTP) +#define VOIPTRANSCRIBE_PROFILE_GOOGLE_HTTP_OPUS VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_GOOGLE, VOIPTRANSCRIBE_FORMAT_OPUS, VOIPTRANSCRIBE_TRANSPORT_HTTP) +#define VOIPTRANSCRIBE_PROFILE_GOOGLE_HTTP2_LI16 VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_GOOGLE, VOIPTRANSCRIBE_FORMAT_LI16, VOIPTRANSCRIBE_TRANSPORT_HTTP2) +#define VOIPTRANSCRIBE_PROFILE_GOOGLE_HTTP2_OPUS VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_GOOGLE, VOIPTRANSCRIBE_FORMAT_OPUS, VOIPTRANSCRIBE_TRANSPORT_HTTP2) +// amazon profiles +#define VOIPTRANSCRIBE_PROFILE_AMAZON_HTTP2 VOIPTRANSCRIBE_PROFILE_CONSTRUCT(VOIPTRANSCRIBE_PROVIDER_AMAZON, VOIPTRANSCRIBE_FORMAT_LI16, VOIPTRANSCRIBE_TRANSPORT_HTTP2) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! possible transcription providers +typedef enum VoipTranscribeProviderE +{ + VOIPTRANSCRIBE_PROVIDER_NONE, + VOIPTRANSCRIBE_PROVIDER_IBMWATSON, //!< IBM Watson + VOIPTRANSCRIBE_PROVIDER_MICROSOFT, //!< Microsoft Cognitive Services + VOIPTRANSCRIBE_PROVIDER_GOOGLE, //!< Google Speech + VOIPTRANSCRIBE_PROVIDER_AMAZON, //!< Amazon Transcribe + VOIPTRANSCRIBE_NUMPROVIDERS +} VoipTranscribeProviderE; + +//! possible transcription audio formats +typedef enum VoipTranscribeFormatE +{ + VOIPTRANSCRIBE_FORMAT_NONE, + VOIPTRANSCRIBE_FORMAT_LI16, //!< 16bit linear PCM audio with no framing + VOIPTRANSCRIBE_FORMAT_WAV16, //!< 16bit linear PCM in a WAV wrapper + VOIPTRANSCRIBE_FORMAT_OPUS, //!< Opus codec + VOIPTRANSCRIBE_NUMFORMATS +} VoipTranscribeFormatE; + +//! possible transport protocols +typedef enum VoipTranscribeTransportE +{ + VOIPTRANSCRIBE_TRANSPORT_NONE, + VOIPTRANSCRIBE_TRANSPORT_HTTP, //!< HTTPS + VOIPTRANSCRIBE_TRANSPORT_HTTP2, //!< HTTP2 + VOIPTRANSCRIBE_TRANSPORT_WEBSOCKETS, //!< WebSockets + VOIPTRANSCRIBE_NUMTRANSPORTS +} VoipTranscribeTransportE; + +//! opaque module state +typedef struct VoipTranscribeRefT VoipTranscribeRefT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create the module +DIRTYCODE_API VoipTranscribeRefT *VoipTranscribeCreate(int32_t iBufSize); + +// configure the transcribe module +DIRTYCODE_API void VoipTranscribeConfig(uint32_t uProfile, const char *pUrl, const char *pKey); + +// destroy the module +DIRTYCODE_API void VoipTranscribeDestroy(VoipTranscribeRefT *pVoipTranscribe); + +// submit voice data to be transcribed +DIRTYCODE_API int32_t VoipTranscribeSubmit(VoipTranscribeRefT *pVoipTranscribe, const uint8_t *pBuffer, int32_t iBufLen); + +// get a transcription +DIRTYCODE_API int32_t VoipTranscribeGet(VoipTranscribeRefT *pVoipTranscribe, char *pBuffer, int32_t iBufLen); + +// get module status +DIRTYCODE_API int32_t VoipTranscribeStatus(VoipTranscribeRefT *pVoipTranscribe, int32_t iStatus, int32_t iValue, void *pBuffer, int32_t iBufSize); + +// set control options +DIRTYCODE_API int32_t VoipTranscribeControl(VoipTranscribeRefT *pVoipTranscribe, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); + +// update module +DIRTYCODE_API void VoipTranscribeUpdate(VoipTranscribeRefT *pVoipTranscribe); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _voiptranscribe_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/voip/voiptunnel.h b/src/thirdparty/dirtysdk/include/DirtySDK/voip/voiptunnel.h new file mode 100644 index 00000000..07365718 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/voip/voiptunnel.h @@ -0,0 +1,208 @@ +/*H********************************************************************************/ +/*! + \File voiptunnel.h + + \Description + This module implements the main logic for the VoipTunnel server. + + Description forthcoming. + + \Copyright + Copyright (c) 2006 Electronic Arts Inc. + + \Version 03/24/2006 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _voiptunnel_h +#define _voiptunnel_h + +/*! +\Moduledef VoipTunnel VoipTunnel +\Modulemember Voip +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/proto/prototunnel.h" + +/*** Defines **********************************************************************/ + +//! max number of clients that can appear in a group +#define VOIPTUNNEL_MAXGROUPSIZE (32) + +//! max number of games that we can have suspended +#define VOIPTUNNEL_MAXSUSPENDED (4) + +//! default amount of time elapsed before incoming voice is assumed to be stopped, in milliseconds +#define VOIPTUNNEL_RECVVOICE_TIMEOUT_DEFAULT (1 * 1000) + +//! default maximum number of players can be talking at once in a single game +#define VOIPTUNNEL_MAX_BROADCASTING_VOICES_DEFAULT 4 + +// client flags +#define VOIPTUNNEL_CLIENTFLAG_RECVVOICE (1<<0) //!< voiptunnel is currently receiving voice from this client +#define VOIPTUNNEL_CLIENTFLAG_MAX_VOICES_REACHED (1<<1) //!< this client has reached its max simultaneous talkers + +/*** Type Definitions *************************************************************/ + +//! voiptunnel event types +typedef enum VoipTunnelEventE +{ + VOIPTUNNEL_EVENT_ADDCLIENT, //!< client added to client list + VOIPTUNNEL_EVENT_DELCLIENT, //!< client removed from client list + VOIPTUNNEL_EVENT_MATCHADDR, //!< client matched addr + VOIPTUNNEL_EVENT_MATCHPORT, //!< client matched port + VOIPTUNNEL_EVENT_RECVVOICE, //!< received voice data from client + VOIPTUNNEL_EVENT_SENDVOICE, //!< sending voice data to client + VOIPTUNNEL_EVENT_DEADVOICE, //!< voice connection has gone dead + VOIPTUNNEL_EVENT_MAXDVOICE, //!< additional clients attempting to send voice will not be rebroadcast + VOIPTUNNEL_EVENT_AVLBVOICE, //!< additional clients may once again send voice + VOIPTUNNEL_EVENT_ADDGAME, //!< game added to game list + VOIPTUNNEL_EVENT_DELGAME, //!< game removed from game list + + VOIPTUNNEL_NUMEVENTS //!< total number of events +} VoipTunnelEventE; + +//! client specific game we save when suspending +typedef struct VoipTunnelSuspendInfoT +{ + int16_t iGameIdx; + uint8_t _pad[2]; + int32_t iNumClients; + int32_t aClientIds[VOIPTUNNEL_MAXGROUPSIZE]; + char strTunnelKey[PROTOTUNNEL_MAXKEYLEN]; +} VoipTunnelSuspendInfoT; + +//! voiptunnel client info +typedef struct VoipTunnelClientT +{ + uint32_t uRemoteAddr; //!< remote address + uint16_t uRemoteGamePort; //!< remote game port + uint16_t uRemoteVoipPort; //!< remote voip port + int16_t iGameIdx; //!< game index client is associated with (active) + int16_t iNumSuspended; //!< number of games this client has suspended + uint8_t uFlags; //!< client flags + uint8_t uPacketVersion; //!< version of voip protocol as identified by connection packets + uint8_t _pad[2]; + uint32_t uClientId; //!< unique client identifier + uint32_t uLastUpdate; //!< last update time in milliseconds + uint32_t uLastRecvVoice; //!< last tick voice mic data was received from this client + uint32_t uLastRecvGame; //!< last tick game data was received from this client (not managed by voiptunnel; for user convenience) + uint32_t uSendMask; //!< current voice send mask (from client) + uint32_t uGameSendMask; //!< current voice send mask (from game) + uint32_t uTunnelId; //!< tunnel id -- for (optional) use by application + int32_t iNumClients; //!< number of clients in client list + int32_t iNumTalker; //!< number of other clients talking to this client + int32_t iUserData0; //!< storage for user-specified data (not managed by voiptunnel; for user convenience) + int32_t aClientIds[VOIPTUNNEL_MAXGROUPSIZE]; //!< list of other clients in send group (GAMEFLAG_SEND_MULTI) + char strTunnelKey[PROTOTUNNEL_MAXKEYLEN]; //!< this client's portion of the tunnel key + + //! keeps track of the game's the client belongs to + VoipTunnelSuspendInfoT aSuspendedData[VOIPTUNNEL_MAXSUSPENDED]; +} VoipTunnelClientT; + +//! game structure +typedef struct VoipTunnelGameT +{ + int32_t iNumClients; //!< current number of clients + int32_t iNumTalkingClients; //!< number of "talking" clients (i.e. client with VOIPTUNNEL_CLIENTFLAG_RECVVOICE flag set) + uint32_t uVoiceDataDropMetric; //!< the number of voice packets that were not rebroadcast in this game due to max broadcasters constraint + uint32_t uGameId; //!< unique game identifier + uint32_t aClientList[VOIPTUNNEL_MAXGROUPSIZE]; + uint8_t bClientActive[VOIPTUNNEL_MAXGROUPSIZE]; //!< active clients for the aClientList +} VoipTunnelGameT; + +//! opaque module state +typedef struct VoipTunnelRefT VoipTunnelRefT; + +//! voiptunnel callback data +typedef struct VoipTunnelEventDataT +{ + VoipTunnelEventE eEvent; + VoipTunnelClientT *pClient; + int32_t iDataSize; +} VoipTunnelEventDataT; + +//! voiptunnel event callback +typedef void (VoipTunnelCallbackT)(VoipTunnelRefT *pVoipTunnel, const VoipTunnelEventDataT *pEventData, void *pUserData); + +//! voiptunnel match function +typedef int32_t (VoipTunnelMatchFuncT)(VoipTunnelClientT *pClient, void *pUserData); + +/*** Function Prototypes **********************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create the module state +DIRTYCODE_API VoipTunnelRefT *VoipTunnelCreate(uint32_t uVoipPort, int32_t iMaxClients, int32_t iMaxGames); + +// set optional voiptunnel event callback +DIRTYCODE_API void VoipTunnelCallback(VoipTunnelRefT *pVoipTunnel, VoipTunnelCallbackT *pCallback, void *pUserData); + +// destroy the module state +DIRTYCODE_API void VoipTunnelDestroy(VoipTunnelRefT *pVoipTunnel); + +// add a client to the client list +DIRTYCODE_API int32_t VoipTunnelClientListAdd(VoipTunnelRefT *pVoipTunnel, const VoipTunnelClientT *pClientInfo, VoipTunnelClientT **ppNewClient); + +// add a client to the client list, at a specific index +DIRTYCODE_API int32_t VoipTunnelClientListAdd2(VoipTunnelRefT *pVoipTunnel, const VoipTunnelClientT *pClientInfo, VoipTunnelClientT **ppNewClient, int32_t iIndex); + +// remove a client from the client list +DIRTYCODE_API void VoipTunnelClientListDel(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pClient, int32_t iGameIdx); + +// add a game to game list +DIRTYCODE_API int32_t VoipTunnelGameListAdd(VoipTunnelRefT *pVoipTunnel, int32_t iGameIdx); + +// add a game to game list with unique game identifier +DIRTYCODE_API int32_t VoipTunnelGameListAdd2(VoipTunnelRefT *pVoipTunnel, int32_t iGameIdx, uint32_t uGameId); + +// remove a game from game list +DIRTYCODE_API int32_t VoipTunnelGameListDel(VoipTunnelRefT *pVoipTunnel, int32_t iGameIdx); + +// return client matching given address +DIRTYCODE_API VoipTunnelClientT *VoipTunnelClientListMatchAddr(VoipTunnelRefT *pVoipTunnel, uint32_t uRemoteAddr); + +// return client matching given client identifier +DIRTYCODE_API VoipTunnelClientT *VoipTunnelClientListMatchId(VoipTunnelRefT *pVoipTunnel, uint32_t uClientId); + +// return client at given client index +DIRTYCODE_API VoipTunnelClientT *VoipTunnelClientListMatchIndex(VoipTunnelRefT *pVoipTunnel, uint32_t uClientIndex); + +// return client matching given address and port +DIRTYCODE_API VoipTunnelClientT *VoipTunnelClientListMatchSockaddr(VoipTunnelRefT *pVoipTunnel, struct sockaddr *pSockaddr); + +// return client using user-supplied match function +DIRTYCODE_API VoipTunnelClientT *VoipTunnelClientListMatchFunc(VoipTunnelRefT *pVoipTunnel, VoipTunnelMatchFuncT *pMatchFunc, void *pUserData); + +// return game at given game index +DIRTYCODE_API VoipTunnelGameT *VoipTunnelGameListMatchIndex(VoipTunnelRefT *pVoipTunnel, int32_t iGameIdx); + +// return game matching given game identifier +DIRTYCODE_API VoipTunnelGameT *VoipTunnelGameListMatchId(VoipTunnelRefT *pVoipTunnel, uint32_t uGameId, int32_t *pGameIdx); + +// let api know client's send mask should be updated +DIRTYCODE_API void VoipTunnelClientRefreshSendMask(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pClient); + +// get module status +DIRTYCODE_API int32_t VoipTunnelStatus(VoipTunnelRefT *pVoipTunnel, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize); + +// control the module +DIRTYCODE_API int32_t VoipTunnelControl(VoipTunnelRefT *pVoipTunnel, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue); + +// update voiptunnel state +DIRTYCODE_API void VoipTunnelUpdate(VoipTunnelRefT *pVoipTunnel); + +#ifdef __cplusplus +}; +#endif + +//@} + +#endif // _voiptunnel_h + diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/xml/xmlformat.h b/src/thirdparty/dirtysdk/include/DirtySDK/xml/xmlformat.h new file mode 100644 index 00000000..ef55fce6 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/xml/xmlformat.h @@ -0,0 +1,141 @@ +/*H*************************************************************************************/ +/*! + \File xmlformat.h + + \Description + This module formats simple Xml, in a linear fashion, using a character buffer + that the client provides. + + \Notes + \verbatim + Since this is a simple linear buffer write, without support for a DOM, the following + rules apply: + - You cannot set attributes or element values if an element (tag) is not started. + - If you start an element, (tag), you have to set the attributes before any + child elements are added. + - The XmlElemAddXXX calls will create a complete element, complete with start + and end tags. + - XmlInit MUST be called on the client buffer, before any api functions are + called. + - Once XML output is complete, the client should call XmlFinish(). There is + some hidden data in the buffer which must be cleared prior to output. Client + should account for 24 extra bytes in buffer for use by Xml API. + + References: + 1) XML Core: http://www.w3.org/XML/Core/ + 2) XML 1.0 4th edition specifications: http://www.w3.org/TR/xml/ + \endverbatim + + \Copyright + Copyright (c) Electronic Arts 2004-2008. ALL RIGHTS RESERVED. + + \Version 01/30/2004 (jbertrand) First Version +*/ +/*************************************************************************************H*/ + +#ifndef _xmlformat_h +#define _xmlformat_h + +/*! +\Moduledef XmlFormat XmlFormat +\Modulemember Util +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +#define XML_ERR_NONE 0 +#define XML_ERR_FULL -1 //Buffer is full, no space to add attribute or element. +#define XML_ERR_UNINIT -2 //Did not call XmlInit() prior to writing. +#define XML_ERR_NOT_OPEN -3 //Attempt to set elem or attrib, but, no tag opened. +#define XML_ERR_ATTR_POSITION -4 //Attempt to set an attrib, but, child element already added to tag. +#define XML_ERR_INVALID_PARAM -5 //Invalid parameter passed to function + +// Encoding control flags +#define XML_FL_WHITESPACE 1 //!< Include formatting whitespace + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// Init the API -- pass in the character buffer. MUST be called first. +DIRTYCODE_API void XmlInit(char *pBuffer, int32_t iBufLen, uint8_t uFlags); + +// Notify the xmlformat API that the buf size was increased +DIRTYCODE_API void XmlBufSizeIncrease(char *pBuffer, int32_t iNewBufLen); + +// Finish XML output to this buffer. +DIRTYCODE_API char *XmlFinish(char *pBuffer); + +// Start an element to which other elements or attributes may be added. +DIRTYCODE_API int32_t XmlTagStart(char *pBuffer, const char *pName); + +// End the current element or tag-- must have an outstanding open tag. +DIRTYCODE_API int32_t XmlTagEnd(char *pBuffer); + +// Add a complete, contained text element. Builds start and end tag. Nothing can be appended to this node. +DIRTYCODE_API int32_t XmlElemAddString(char *pBuffer, const char *pElemName, const char *pValue); + +// Add a complete, contained integer element. Builds start and end tag. Nothing can be appended to this node. +DIRTYCODE_API int32_t XmlElemAddInt(char *pBuffer, const char *pElemName, int32_t iValue); + +// Add a complete, contained decimal element. Format it using formatSpec. Builds start and end tag. Nothing can be appended to this node. +DIRTYCODE_API int32_t XmlElemAddFloat(char *pBuffer, const char *pElemName, const char *pFormatSpec, float fValue); + +// Add a complete, contained Date elem, using epoch date. Builds start and end tag. Nothing can be appended to this node. +DIRTYCODE_API int32_t XmlElemAddDate(char *pBuffer, const char *pElemName, uint32_t uEpochDate); + +// Set a text attribute for an open element (started tag). Must be called before element text is set. +DIRTYCODE_API int32_t XmlAttrSetString(char *pBuffer, const char *pAttrName, const char *pValue); + +// Set a text attribute for an open element (started tag), with no text encoding being performed on the input string. Must be called before element text is set. +DIRTYCODE_API int32_t XmlAttrSetStringRaw(char *pBuffer, const char *pAttr); + +// Set an integer attribute for an open element (started tag). Must be called before element text is set. +DIRTYCODE_API int32_t XmlAttrSetInt(char *pBuffer, const char *pAttrName, int32_t iValue); + +// Set an IP address attribute (in dot notation) +DIRTYCODE_API int32_t XmlAttrSetAddr(char *pBuffer, const char *pAttrName, uint32_t uAddr); + +// Set a decimal attribute for an open element (started tag). Format using formatSpec. Must be called before element text is set. +DIRTYCODE_API int32_t XmlAttrSetFloat(char *pBuffer, const char *pAttrName, const char *pFormatSpec, float fValue); + +// Set a date attribute for an open element (started tag). Must be called before element text is set. +DIRTYCODE_API int32_t XmlAttrSetDate(char *pBuffer, const char *pAttrName, uint32_t uEpochDate); + +// Set a text value for an open element. Must have an open element (started tag). +DIRTYCODE_API int32_t XmlElemSetString(char *pBuffer, const char *pValue); + +// Set a text value for an open element, with no text encoding being performed on the input string. Must have an open element (started tag). +DIRTYCODE_API int32_t XmlElemSetStringRaw(char *pBuffer, const char *pValue); + +// Set an integer value for an open element. Must have an open element (started tag). +DIRTYCODE_API int32_t XmlElemSetInt(char *pBuffer, int32_t iValue); + +// Set an IP address value for an open element (in dot notation). Must have an open element (started tag). +DIRTYCODE_API int32_t XmlElemSetAddr(char *pBuffer, uint32_t uAddr); + +// Set a date for an open element. Must have an open element (started tag). +DIRTYCODE_API int32_t XmlElemSetDate(char *pBuffer, uint32_t uEpoch); + +// Build an XML output string from a formatted pattern (like vprintf) +#ifdef va_start +DIRTYCODE_API char *XmlFormatVPrintf(char *pXmlBuff, int32_t iBufLen, const char *pFormat, va_list pFmtArgs); +#endif + +// Build an XML output string from a formatted pattern (like printf) +DIRTYCODE_API char *XmlFormatPrintf(char *pXmlBuff, int32_t iBufLen, const char *pFormat, ...); + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _xmlformat_h diff --git a/src/thirdparty/dirtysdk/include/DirtySDK/xml/xmlparse.h b/src/thirdparty/dirtysdk/include/DirtySDK/xml/xmlparse.h new file mode 100644 index 00000000..a11b40d0 --- /dev/null +++ b/src/thirdparty/dirtysdk/include/DirtySDK/xml/xmlparse.h @@ -0,0 +1,130 @@ +/*H*************************************************************************************************/ +/*! + + \File xmlparse.h + + \Description + \verbatim + This is a simple XML parser for use in controlled situations. It should not + be used with arbitrary XML from uncontrolled hosts (i.e., test its parsing against + a particular host before using it). This only implement a small subset of full + XML and while suitable for many applications, it is easily confused by badly + formed XML or by some of the ligitimate but convoluated XML conventions. + + In particular, this parser cannot handle degenerate empty elements (i.e.,

+ will confuse it). Empty elements must be of proper XML form

. Only the + predefined entity types (< > & ' ") are supported. This module + does not support unicode values. + \endverbatim + + \Notes + None. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 1.0 01/30/2002 (GWS) First Version + +*/ +/*************************************************************************************************H*/ + +#ifndef _xmlparse_h +#define _xmlparse_h + +/*! +\Moduledef XmlParse XmlParse +\Modulemember Util +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +// debug printing routines +#if DIRTYCODE_LOGGING + #define XmlPrintFmt(_x) XmlPrintFmtCode _x +#else + #define XmlPrintFmt(_x) { } +#endif + +/*** Type Definitions ******************************************************************/ + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// skip to the next xml entity (used to enumerate lists) +DIRTYCODE_API const char *XmlSkip(const char *strXml); + +// find an entity within an xml document +DIRTYCODE_API const char *XmlFind(const char *strXml, const char *strName); + +// skip to next entity with same name as current +DIRTYCODE_API const char *XmlNext(const char *strXml); + +// step to next xml element +DIRTYCODE_API const char *XmlStep(const char *strXml); + +// determines if the xml entity pointed to is complete +DIRTYCODE_API uint32_t XmlComplete(const char *pXml); + +// return entity contents as a string +DIRTYCODE_API int32_t XmlContentGetString(const char *strXml, char *strBuffer, int32_t iLength, const char *strDefault); + +// return entity contents as an integer +DIRTYCODE_API int32_t XmlContentGetInteger(const char *strXml, int32_t iDefault); + +// return entity contents as a 64-bit integer +DIRTYCODE_API int64_t XmlContentGetInteger64(const char *strXml, int64_t iDefault); + +// return entity contents as a token (a packed sequence of characters) +DIRTYCODE_API int32_t XmlContentGetToken(const char *pXml, int32_t iDefault); + +// return epoch seconds for a date +DIRTYCODE_API uint32_t XmlContentGetDate(const char *pXml, uint32_t uDefault); + +// parse entity contents as an internet dot-notation address +DIRTYCODE_API int32_t XmlContentGetAddress(const char *pXml, int32_t iDefault); + +// return binary encoded data +DIRTYCODE_API int32_t XmlContentGetBinary(const char *pXml, char *pBuffer, int32_t iLength); + +// return entity attribute as a string +DIRTYCODE_API int32_t XmlAttribGetString(const char *strXml, const char *strAttrib, char *strBuffer, int32_t iLength, const char *strDefault); + +// return entity attribute as an integer +DIRTYCODE_API int32_t XmlAttribGetInteger(const char *strXml, const char *strAttrib, int32_t iDefault); + +// return entity attribute as an 64-bit integer +DIRTYCODE_API int64_t XmlAttribGetInteger64(const char *strXml, const char *strAttrib, int64_t iDefault); + +// return entity attribute as a token +DIRTYCODE_API int32_t XmlAttribGetToken(const char *pXml, const char *pAttrib, int32_t iDefault); + +// return epoch seconds for a date +DIRTYCODE_API uint32_t XmlAttribGetDate(const char *pXml, const char *pAttrib, uint32_t uDefault); + +// convert epoch to date components +DIRTYCODE_API int32_t XmlConvEpoch2Date(uint32_t uEpoch, int32_t *pYear, int32_t *pMonth, int32_t *pDay, int32_t *pHour, int32_t *pMinute, int32_t *pSecond); + +// debug output of XML reformatted to look 'nicer' - use macro wrapper XmlPrintFmt(), do not call directly +#if DIRTYCODE_LOGGING +DIRTYCODE_API void XmlPrintFmtCode(const char *pXml, const char *pFormat, ...); +#endif + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _xmlparse_h diff --git a/src/thirdparty/dirtysdk/sample/dirtysim/example.txt b/src/thirdparty/dirtysdk/sample/dirtysim/example.txt new file mode 100644 index 00000000..ee1420ac --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/dirtysim/example.txt @@ -0,0 +1,31 @@ +Example of using DirtySim with Tester2 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +DirtySim creates a "bridge" between two clients. It servers as a middle-man that can introduce packet loss, packet reordering, or packet corruption. Here's an example setup and some Tester2 commands that are used to drive the clients that connect through the bridge for both game and voice. In both cases, the three options are given; direct connection, tunneled connection, and tunneled connection through DirtySim. + +ClientA DirtySim ClientB +10.8.13.194:8010 10.8.13.194:8000 10.8.13.194:8020 + +; Voice connnection, direct +t2host voice create -nodevselect 8; voice connect 10.8.13.194:6000:6001 +t2host voice create -nodevselect 8; voice connect 10.8.13.194:6001:6000 + +; Voice connection, tunneled, direct +t2host tunnel create 43214321 8010; tunnel alloc 12341234 10.8.13.194 8020 abcd; net ctrl vadd 6000; voice create -nodevselect 8; voice ctrl clid 16777216; voice connect 1.0.0.0 01000001 +t2host tunnel create 12341234 8020; tunnel alloc 43214321 10.8.13.194 8010 abcd; net ctrl vadd 6000; voice create -nodevselect 8; voice ctrl clid 16777217; voice connect 1.0.0.0 01000000 + +; Voice connection, tunneled, dirtysim +t2host tunnel create 43214321 8010; tunnel alloc 12341234 10.8.13.194 8000 abcd; net ctrl vadd 6000; voice create -nodevselect 8; voice ctrl clid 16777216; voice connect 1.0.0.0 01000001 +t2host tunnel create 12341234 8020; tunnel alloc 43214321 10.8.13.194 8000 abcd; net ctrl vadd 6000; voice create -nodevselect 8; voice ctrl clid 16777217; voice connect 1.0.0.0 01000000 + +; Game connection, direct +t2host gamelink connect 10.8.13.194:3658:3659 +t2host gamelink connect 10.8.13.194:3659:3658 + +; Game connection, tunneled, direct +t2host tunnel create 43214321 8010; tunnel alloc 12341234 10.8.13.194 8020 abcd; net ctrl vadd 3658; gamelink connect 1.0.0.0:3658 +t2host tunnel create 12341234 8020; tunnel alloc 43214321 10.8.13.194 8010 abcd; net ctrl vadd 3658; gamelink connect 1.0.0.0:3658 + +; Game connection, tunneled, dirtysim +t2host tunnel create 43214321 8010; tunnel alloc 12341234 10.8.13.194 8000 abcd; net ctrl vadd 3658; gamelink connect 1.0.0.0:3658 +t2host tunnel create 12341234 8020; tunnel alloc 43214321 10.8.13.194 8000 abcd; net ctrl vadd 3658; gamelink connect 1.0.0.0:3658 \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/sample/dirtysim/source/dirtysim.c b/src/thirdparty/dirtysdk/sample/dirtysim/source/dirtysim.c new file mode 100644 index 00000000..9dc9b5c6 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/dirtysim/source/dirtysim.c @@ -0,0 +1,489 @@ +/*H*************************************************************************************/ +/*! + \File dirtysim.c + + \Description + Lightweight network simulator intended to allow exercise of various codepaths + in network code that handle adverse network conditions (latency, packet loss, + out of order packets). Is *not* intended to accurately simulate real network + conditions. + + \Copyright + Copyright (c) Electronic Arts 2018. + + \Version 01/25/2018 (jbrookes) First Version +*/ +/*************************************************************************************H*/ + +/*** Example Usage *********************************************************************/ + +/*** Include files *********************************************************************/ + +#include +#include +#include + +// dirtysock includes +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protohttp.h" +#include "DirtySDK/proto/protossl.h" + +// zlib includes +#include "libsample/zlib.h" +#include "libsample/zmem.h" +#include "libsample/zfile.h" + +/*** Defines ***************************************************************************/ + +// PACKET_CHANCE percentage chance packet is lost, out of order, or corrupted +#define PACKET_CHANCE_PCT (10) +#define PACKET_CHANCE ((65536*PACKET_CHANCE_PCT)/100) + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct DirtyPacketT +{ + uint32_t uPacketSize; + uint32_t uPacketTick; + uint8_t aPacketData[SOCKET_MAXUDPRECV]; +} DirtyPacketT; + +typedef struct DirtyPacketQueueT +{ + uint32_t uNumPackets; + uint32_t uFirstPacket; + uint32_t uLastPacket; + DirtyPacketT PacketBuf[128]; +} DirtyPacketQueueT; + +typedef struct DirtyBridgeT +{ + struct sockaddr ClientAddrA; + DirtyPacketQueueT ClientQueueA; + struct sockaddr ClientAddrB; + DirtyPacketQueueT ClientQueueB; +} DirtyBridgeT; + +typedef struct DirtysimOptionsT +{ + uint32_t uTemp; +} DirtysimOptionsT; + +typedef struct DirtysimStateT +{ + SocketT *pSocket; + DirtyBridgeT Bridge; +} DirtysimStateT; + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +// Public variables + + +/*** Private Functions ******************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function dirtysim_zprintf_hook + + \Description + Hook up debug output for ZPrintf where appropriate + + \Input *pParm - unused + \Input *pText - text to print + + \Output + int32_t - one + + \Version 01/25/2018 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t dirtysim_zprintf_hook(void *pParm, const char *pText) +{ + // on PC we want output to console in addition to debug output + #if defined(DIRTYCODE_PC) + printf("%s", pText); + #endif + // don't suppress debug output + return(1); +} + +/*F*************************************************************************************/ +/*! + \Function network_startup + + \Description + Start up required DirtySDK networking + + \Input *pParm - unused + \Input *pText - text to print + + \Output + int32_t - one + + \Version 01/25/2018 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t network_startup(void) +{ + int32_t iResult=0, iStatus, iTimeout; + + // start network + NetConnStartup("-servicename=dirtysim"); + + // bring up the interface + NetConnConnect(NULL, NULL, 0); + + // wait for network interface activation + for (iTimeout = NetTick() + 15*1000; ; ) + { + // update network + NetConnIdle(); + + // get current status + iStatus = NetConnStatus('conn', 0, NULL, 0); + if ((iStatus == '+onl') || ((iStatus >> 24) == '-')) + { + break; + } + + // check for timeout + if (iTimeout < (signed)NetTick()) + { + ZPrintf("dirtysim: timeout waiting for interface activation\n"); + break; + } + + // give time to other threads + NetConnSleep(500); + } + + // check result code + if ((iStatus = NetConnStatus('conn', 0, NULL, 0)) == '+onl') + { + ZPrintf("dirtysim: interface active\n"); + iResult = 1; + } + else if ((iStatus >> 24) == '-') + { + ZPrintf("dirtysim: error %C bringing up interface\n", iStatus); + } + + // return result to caller + return(iResult); +} + +/*F*************************************************************************************/ +/*! + \Function process_args + + \Description + Process command-line arguments + + \Input iArgc - arg count + \Input *pArgv[] - arg list + \Input *pOptions - [out] parsed options + + \Version 01/25/2018 (jbrookes) +*/ +/*************************************************************************************F*/ +static void process_args(int32_t iArgc, const char *pArgv[], DirtysimOptionsT *pOptions) +{ + int32_t iArg; + + // echo options + for (iArg = 0; iArg < iArgc; iArg += 1) + { + ZPrintf("%s ", pArgv[iArg]); + } + ZPrintf("\n"); + + ds_memclr(pOptions, sizeof(*pOptions)); + + // init default options + + // pick off command-line options + for (iArg = 1; (iArg < iArgc) && (pArgv[iArg][0] == '-'); iArg += 1) + { + #if 0 // example processing + if (!strcmp(pArgv[iArg], "-someopt") && ((iArg+1) < iArgc)) + { + // process option, skip additional value if present (e.g. -someopt value) + iArg += 1; + } + #endif + } +} + +/*F*************************************************************************************/ +/*! + \Function add_packet_to_queue + + \Description + Add a packet to packet queue + + \Input *pPacketQueue - packet queue to add to + \Input iPacketData - packet data to add + \Input iPacketSize - size of packet data + + \Version 01/25/2018 (jbrookes) +*/ +/*************************************************************************************F*/ +static void add_packet_to_queue(DirtyPacketQueueT *pPacketQueue, const uint8_t *pPacketData, int32_t iPacketSize) +{ + const uint32_t uQueueSize = sizeof(pPacketQueue->PacketBuf)/sizeof(pPacketQueue->PacketBuf[0]); + DirtyPacketT *pPacket; + + if (pPacketQueue->uNumPackets == uQueueSize) + { + ZPrintf("dirtysim: packet queue full\n"); + return; + } + + // allocate packet + pPacket = &pPacketQueue->PacketBuf[pPacketQueue->uNumPackets++]; + // copy data + ds_memcpy_s(pPacket->aPacketData, sizeof(pPacket->aPacketData), pPacketData, iPacketSize); + pPacket->uPacketSize = iPacketSize; + // timestamp receive + pPacket->uPacketTick = NetTick(); +} + +/*F*************************************************************************************/ +/*! + \Function get_packet_from_queue + + \Description + Get a packet from packet queue + + \Input *pPacketQueue - packet queue to get packet from + \Input iPacketData - [out] packet buffer + \Input iPacketSize - size of packet buffer + \Input uMinQueueSize - min queue size to allow packet reordering + + \Version 01/25/2018 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t get_packet_from_queue(DirtyPacketQueueT *pPacketQueue, uint8_t *pPacketData, int32_t iPacketSize, uint32_t uMinQueueSize) +{ + DirtyPacketT *pPacket; + + // make sure we buffer at least one packet to be able to do out-of-order + if (pPacketQueue->uNumPackets <= uMinQueueSize) + { + return(0); + } + + // deallocate packet + pPacket = &pPacketQueue->PacketBuf[--pPacketQueue->uNumPackets]; + // copy data + ds_memcpy_s(pPacketData, iPacketSize, pPacket->aPacketData, pPacket->uPacketSize); + // return + return(pPacket->uPacketSize); +} + +/*F*************************************************************************************/ +/*! + \Function packet_queue_process + + \Description + Process packet queue, introducing possible packet loss, out of order, + or corruption. + + \Input *pState - application state + \Input *pPacketQueue - packet queue to process + \Input *pSrcAddr - bridge src addr + \Input *pDstAddr - bridge dst addr + + \Version 01/25/2018 (jbrookes) +*/ +/*************************************************************************************F*/ +static void packet_queue_process(DirtysimStateT *pState, DirtyPacketQueueT *pPacketQueue, struct sockaddr *pSrcAddr, struct sockaddr *pDstAddr) +{ + uint8_t aPacketBuf[SOCKET_MAXUDPRECV]; + int32_t iAddrLen = sizeof(*pDstAddr), iPacketLen; + uint32_t uRandom; + + // get packet; give us a buffer to allow out-of-order packets + if ((iPacketLen = get_packet_from_queue(pPacketQueue, aPacketBuf, sizeof(aPacketBuf), 2)) == 0) + { + return; + } + + // get a random 16bit number + uRandom = NetRand(0xffff); + + // see if we should do something + if (uRandom < PACKET_CHANCE) + { + ZPrintf("dirtysim: %A->%A (%d bytes) (depth=%d) [LOST]\n", pSrcAddr, pDstAddr, iPacketLen, pPacketQueue->uNumPackets); + // packet lost; don't forward + return; + } + else if (uRandom < (PACKET_CHANCE*2)) + { + uint8_t aPacketBuf2[SOCKET_MAXUDPRECV]; + int32_t iPacketLen2; + + // packet out of order - get next packet and send it first + if ((iPacketLen2 = get_packet_from_queue(pPacketQueue, aPacketBuf2, sizeof(aPacketBuf2), 1)) > 0) + { + ZPrintf("dirtysim: %A->%A (%d bytes) (depth=%d) [OUTOFORDER]\n", pSrcAddr, pDstAddr, iPacketLen2, pPacketQueue->uNumPackets); + SocketSendto(pState->pSocket, (char *)aPacketBuf2, iPacketLen2, 0, pDstAddr, iAddrLen); + } + } + else if (uRandom < (PACKET_CHANCE*3)) + { + // corrupt the packet; we pick a random byte and set it to the index value + uRandom = NetRand(iPacketLen-1); + aPacketBuf[uRandom] = (uint8_t)(uRandom&0xff); + ZPrintf("dirtysim: %A->%A (%d bytes) (depth=%d) [CORRUPTED]\n", pSrcAddr, pDstAddr, iPacketLen, pPacketQueue->uNumPackets); + } + else + { + ZPrintf("dirtysim: %A->%A (%d bytes) (depth=%d)\n", pSrcAddr, pDstAddr, iPacketLen, pPacketQueue->uNumPackets); + } + + // forward the packet + SocketSendto(pState->pSocket, (char *)aPacketBuf, iPacketLen, 0, pDstAddr, iAddrLen); +} + + +/*** Public Functions ******************************************************************/ + +// dll-friendly DirtyMemAlloc +#if !defined(DIRTYCODE_DLL) +void *DirtyMemAlloc(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#else +void *DirtyMemAlloc2(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#endif +{ + return(malloc(iSize)); +} + +// dll-friendly DirtyMemFree +#if !defined(DIRTYCODE_DLL) +void DirtyMemFree(void *pMem, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#else +void DirtyMemFree2(void *pMem, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#endif +{ + free(pMem); +} + + +/*F*************************************************************************************/ +/*! + \Function main + + \Description + Main processing for DirtySim + + \Input argc - input argument count + \Input *argv[] - input argument list + + \Output + int32_t - negative on failure, else zero + + \Version 01/25/2018 (jbrookes) +*/ +/*************************************************************************************F*/ +int main(int32_t argc, const char *argv[]) +{ + int32_t iCount, iLen, iTimeout; + DirtysimOptionsT DirtysimOptions; + DirtysimStateT DirtysimState; + uint8_t aPacketBuf[SOCKET_MAXUDPRECV]; + struct sockaddr BindAddr, RecvAddr; + int32_t iAddrLen = sizeof(RecvAddr), iRecvLen; + + #if defined(DIRTYCODE_DLL) + DirtyMemFuncSet(&DirtyMemAlloc2, &DirtyMemFree2); + #endif + + // hook into zprintf output + ZPrintfHook(dirtysim_zprintf_hook, NULL); + + // get options + process_args(argc, argv, &DirtysimOptions); + + // init state + + // start dirtysock + if (!network_startup()) + { + return(-1); + } + + // init bridge + ds_memclr(&DirtysimState.Bridge, sizeof(DirtysimState.Bridge)); + + // client a + SockaddrInit(&DirtysimState.Bridge.ClientAddrA, AF_INET); + SockaddrInSetAddr(&DirtysimState.Bridge.ClientAddrA, SocketInTextGetAddr("10.8.13.194")); + SockaddrInSetPort(&DirtysimState.Bridge.ClientAddrA, 8010); + // client b + SockaddrInit(&DirtysimState.Bridge.ClientAddrB, AF_INET); + SockaddrInSetAddr(&DirtysimState.Bridge.ClientAddrB, SocketInTextGetAddr("10.8.13.194")); + SockaddrInSetPort(&DirtysimState.Bridge.ClientAddrB, 8020); + + // create & bind DirtySim socket + DirtysimState.pSocket = SocketOpen(AF_INET, SOCK_DGRAM, 0); + SockaddrInit(&BindAddr, AF_INET); + SockaddrInSetPort(&BindAddr, 8000); + SocketBind(DirtysimState.pSocket, &BindAddr, iAddrLen); + + // just keep working + for (iCount = 0, iLen = -1, iTimeout = NetTick()-1; ; ) + { + // receive a packet + iRecvLen = SocketRecvfrom(DirtysimState.pSocket, (char *)aPacketBuf, sizeof(aPacketBuf), 0, &RecvAddr, &iAddrLen); + + // if we got something, add it to the packet queue + if (iRecvLen > 0) + { + //ZPrintf("dirtysim: read %d byte packet from %A\n", iRecvLen, &RecvAddr); + // ClientA->ClientB + if (!SockaddrCompare(&RecvAddr, &DirtysimState.Bridge.ClientAddrA)) + { + add_packet_to_queue(&DirtysimState.Bridge.ClientQueueA, aPacketBuf, iRecvLen); + } + // ClientB->ClientA + else if (!SockaddrCompare(&RecvAddr, &DirtysimState.Bridge.ClientAddrB)) + { + add_packet_to_queue(&DirtysimState.Bridge.ClientQueueB, aPacketBuf, iRecvLen); + } + } + + // process the packet queue; if there's a packet to send, send it + packet_queue_process(&DirtysimState, &DirtysimState.Bridge.ClientQueueA, &DirtysimState.Bridge.ClientAddrA, &DirtysimState.Bridge.ClientAddrB); + packet_queue_process(&DirtysimState, &DirtysimState.Bridge.ClientQueueB, &DirtysimState.Bridge.ClientAddrB, &DirtysimState.Bridge.ClientAddrA); + + // if we got something, try again + if (iRecvLen > 0) + { + continue; + } + + // sleep a bit + NetConnSleep(10); + } + + //ZPrintf("dirtysim: done\n"); + + // disconnect from the network% + //NetConnDisconnect(); + + // shutdown the network connections && destroy the dirtysock code + //NetConnShutdown(FALSE); + //return(0); +} \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/sample/libstat/source/libstat.c b/src/thirdparty/dirtysdk/sample/libstat/source/libstat.c new file mode 100644 index 00000000..38f57541 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/libstat/source/libstat.c @@ -0,0 +1,240 @@ +/*H*************************************************************************************************/ +/*! + + \File libstat.c + + \Description + This sample links in all DirtySock EE code, so that the linker-generated map file may + be used to estimate DirtySock EE code size. + + \Notes + None. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 02/02/04 (JLB) First Version + +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" + +// dirtysock includes +#include "DirtySDK/game/connapi.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/graph/dirtygif.h" +#include "DirtySDK/graph/dirtygraph.h" +#include "DirtySDK/graph/dirtyjpg.h" + +// crypt includes +#include "DirtySDK/crypt/cryptarc4.h" +#include "DirtySDK/crypt/cryptmd5.h" +#include "DirtySDK/crypt/cryptrsa.h" +#include "DirtySDK/crypt/cryptsha1.h" + +// lobby includes +#include "DirtySDK/util/base64.h" +#include "DirtySDK/util/utf8.h" + +// netgame includes +#include "DirtySDK/game/netgamedist.h" +#include "DirtySDK/game/netgamedistserv.h" +#include "DirtySDK/game/netgamelink.h" +#include "DirtySDK/game/netgameutil.h" + +// proto includes +#include "DirtySDK/proto/protohttp.h" +#include "DirtySDK/proto/protomangle.h" +#include "DirtySDK/proto/protoname.h" +#include "DirtySDK/proto/protostream.h" +#include "DirtySDK/proto/protoupnp.h" + +// voip includes +#include "DirtySDK/voip/voip.h" +#include "DirtySDK/voip/voiptunnel.h" + +// xml includes +#include "DirtySDK/xml/xmlformat.h" +#include "DirtySDK/xml/xmlparse.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + + +// Private variables + +// Public variables + + +/*** Public Functions ******************************************************************/ + + +/* + +Required alloc/free functions + +*/ +#ifndef DIRTYCODE_DLL +void *DirtyMemAlloc(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +{ + return(malloc(iSize)); +} + +void DirtyMemFree(void *pMem, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +{ + free(pMem); +} +#endif + +/* + + init routines - the following routines are provided to make sure the modules + link into the application + +*/ + + +/* + + main routine + +*/ + +static void _CryptModules(void) +{ + // cryptarc4 + CryptArc4Init(NULL, NULL, 0, 0); + + // cryptmd5 + CryptMD5Init(NULL); + + // cryptrsa + CryptRSAInit(NULL, NULL, 0, NULL, 0); + + // cryptsha1 + CryptSha1Init(NULL); +} + +static void _DirtysockModules(void) +{ + // dirtylibps2 + NetTick(); + + //dirtygif + DirtyGifIdentify(NULL, 0); + + //dirtygraph + DirtyGraphCreate(); + + //dirtyjpg + DirtyJpgCreate(); + + // netconn/netconnps2 + NetConnMAC(); + + // Proto Includes + // protohttp + ProtoHttpCreate(0); + + // protomangle + ProtoMangleCreate("", 0, "", ""); + + // protoname + ProtoNameAsync("", 0); + + // protostream + ProtoStreamCreate(0); + + // protoupnp + ProtoUpnpStatus(NULL, 0, NULL, 0); +} + +static void _NetgameModules(void) +{ + // netgamelink-e + NetGameLinkCreate(NULL, 0, 0); + + // netgameutil-e + NetGameUtilCreate(); + + // netgamedist + NetGameDistCreate(NULL, NULL, NULL, NULL, 0, 0); + + // netgamedistserv + NetGameDistServCreate(0, 0); + + //connapi + ConnApiGetClientList(NULL); +} + +static void _LobbyModules(void) +{ + // base64 + Base64Encode(0, NULL, NULL); + + // lobbyutf8 + Utf8Strip(NULL, 0, NULL); +} + +static void _XmlModules(void) +{ + // xmlformat + XmlInit(NULL, 0, 0); + + // xmlparse + XmlSkip(NULL); +} + +static void _ContribModules(void) +{ + // voip + VoipStartup(1, 0); + + //voiptunnel + VoipTunnelCreate(0, 0, 0); +} + +int main(int32_t argc, char *argv[]) +{ + // pull in crypt modules + _CryptModules(); + + // pull in DirtySock modules + _DirtysockModules(); + + // pull in lobby modules + _LobbyModules(); + + // pull in netgame modules + _NetgameModules(); + + // pull in xml modules + _XmlModules(); + + // pull in contrib modules + _ContribModules(); + + // done + return(0); +} + + + + diff --git a/src/thirdparty/dirtysdk/sample/ssltest/source/ssltest.c b/src/thirdparty/dirtysdk/sample/ssltest/source/ssltest.c new file mode 100644 index 00000000..f137ad4c --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/ssltest/source/ssltest.c @@ -0,0 +1,768 @@ +/*H*************************************************************************************/ +/*! + \File sslstress.c + + \Description + A smoke tester for TLS/SSL, designed to connect to a series of servers while + exercising options (version, cipher) to validate basic connectability across + the protocol feature set supported by ProtoSSL. + + \Copyright + Copyright (c) Electronic Arts 2017. + + \Version 08/03/2017 (jbrookes) First Version +*/ +/*************************************************************************************H*/ + +/*** Example Usage *********************************************************************/ + +/* + Test TLS1.3 servers: + -cert e:\temp\testcerts\DSTRootCAX3.crt -minvers 4 -minciph 14 https://enabled.tls13.com https://tls13.crypto.mozilla.org/ https://tls.ctf.network/ https://rustls.jbp.io/ https://h2o.examp1e.net https://www.mew.org/ + Test TLS1.2 servers: + https://www.google.com https://www.yahoo.com https://www.amazon.com https://facebook.com https://badssl.com +*/ + +/*** Include files *********************************************************************/ + +#include +#include +#include + +// dirtysock includes +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protohttp.h" +#include "DirtySDK/proto/protossl.h" + +// zlib includes +#include "libsample/zlib.h" +#include "libsample/zmem.h" +#include "libsample/zfile.h" + +/*** Defines ***************************************************************************/ + +#define SSLTEST_MAXCIPHER_PRE10 1 // index of max cipher supported prior to tls1.0 +#define SSLTEST_MAXCIPHER_PRE12 5 // index of max cipher supported prior to tls1.2 +#define SSLTEST_MAXCIPHER_PRE13 19 // index of max cipher supported prior to tls1.3 +#define SSLTEST_CIPHER_ALL 23 // index of entry supporting all ciphers + +enum +{ + SSLTEST_STAGE_SETUP = 0, + SSLTEST_STAGE_RUNNING, + SSLTEST_STAGE_RESULT, + SSLTEST_STAGE_COMPLETE +}; + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct SsltestOptionsT +{ + int32_t iMinTest; + int32_t iMaxTest; + int32_t iMinServ; + int32_t iMaxServ; + int32_t iMinVers; + int32_t iMaxVers; + int32_t iMinCiph; + int32_t iMaxCiph; + uint8_t bNcrt; + uint8_t bSkip; + uint8_t _pad[2]; +} SsltestOptionsT; + + +typedef struct SsltestStateT +{ + int32_t iTest; + int32_t iServ; + int32_t iVers; + int32_t iCiph; + uint8_t *pClientCert; + int32_t iCertSize; + uint8_t *pClientKey; + int32_t iKeySize; + char strResult[128]; +} SsltestStateT; + +// test function type +typedef void(SsltestTestFuncT)(ProtoHttpRefT *pHttpRef, SsltestOptionsT *pOptions, SsltestStateT *pState, int32_t iStage); + +// test type +typedef struct SsltestTestT +{ + SsltestTestFuncT *pTestFunc; + char *pTestName; +} SsltestTestT; + +/*** Function Prototypes ***************************************************************/ + +static void test_cipher_and_version(ProtoHttpRefT *pHttpRef, SsltestOptionsT *pOptions, SsltestStateT *pState, int32_t iStage); +static void test_resume(ProtoHttpRefT *pHttpRef, SsltestOptionsT *pOptions, SsltestStateT *pState, int32_t iStage); +static void test_basic(ProtoHttpRefT *pHttpRef, SsltestOptionsT *pOptions, SsltestStateT *pState, int32_t iStage); + +/*** Variables *************************************************************************/ + +// Private variables + +static uint16_t _ssltest_versions[] = +{ + PROTOSSL_VERSION_TLS1_0, + PROTOSSL_VERSION_TLS1_1, + PROTOSSL_VERSION_TLS1_2, + PROTOSSL_VERSION_TLS1_3 +}; + +static const char *_ssltest_tlsnames[] = +{ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3", +}; + +// protossl ciphers - matches list order in protossl.h +static uint32_t _ssltest_ciphers[] = +{ + // SSLv3 cipher suites + PROTOSSL_CIPHER_RSA_WITH_AES_128_CBC_SHA, + PROTOSSL_CIPHER_RSA_WITH_AES_256_CBC_SHA, + // TLS1.0 cipher suites + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA, + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA, + PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + // the following cipher suites are TLS1.2+ only + PROTOSSL_CIPHER_RSA_WITH_AES_128_CBC_SHA256, + PROTOSSL_CIPHER_RSA_WITH_AES_256_CBC_SHA256, + PROTOSSL_CIPHER_RSA_WITH_AES_128_GCM_SHA256, + PROTOSSL_CIPHER_RSA_WITH_AES_256_GCM_SHA384, + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + PROTOSSL_CIPHER_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + // the following cipher suites are TLS1.3+ only + PROTOSSL_CIPHER_AES_128_GCM_SHA256, + PROTOSSL_CIPHER_AES_256_GCM_SHA384, + PROTOSSL_CIPHER_CHACHA20_POLY1305_SHA256, + // all ciphers + PROTOSSL_CIPHER_ALL|PROTOSSL_CIPHER_ALL_13 +}; + +static const char *_ssltest_ciphernames[] = +{ + // SSLv3 cipher suites + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + // TLS1.0 cipher suites + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + // TLS1.2 cipher suites + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + // TLS1.3 cipher suites + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + // a synthetic for all ciphers + "TLS_ALL_CIPHERS" +}; + +static SsltestTestT _ssltest_tests[] = +{ + { test_resume, "resume" }, + { test_cipher_and_version, "cipher and version" }, + { test_basic, "basic" } +}; + +// Public variables + + +/*** Private Functions ******************************************************************/ + +static int32_t ssltest_zprintf_hook(void *pParm, const char *pText) +{ + // on PC we want output to console in addition to debug output + #if defined(DIRTYCODE_PC) + printf("%s", pText); + #endif + // don't suppress debug output + return(1); +} + +static int32_t network_startup(void) +{ + int32_t iResult=0, iStatus, iTimeout; + + // start network + NetConnStartup("-servicename=sslstress"); + + // bring up the interface + NetConnConnect(NULL, NULL, 0); + + // wait for network interface activation + for (iTimeout = NetTick() + 15*1000; ; ) + { + // update network + NetConnIdle(); + + // get current status + iStatus = NetConnStatus('conn', 0, NULL, 0); + if ((iStatus == '+onl') || ((iStatus >> 24) == '-')) + { + break; + } + + // check for timeout + if (iTimeout < (signed)NetTick()) + { + ZPrintf("ssltest: timeout waiting for interface activation\n"); + break; + } + + // give time to other threads + NetConnSleep(500); + } + + // check result code + if ((iStatus = NetConnStatus('conn', 0, NULL, 0)) == '+onl') + { + ZPrintf("ssltest: interface active\n"); + iResult = 1; + } + else if ((iStatus >> 24) == '-') + { + ZPrintf("ssltest: error %C bringing up interface\n", iStatus); + } + + // return result to caller + return(iResult); +} + +static uint8_t *load_pem(const char *pFilename, int32_t *pCertSize) +{ + char *pCertBuf; + int32_t iFileSize; + + // load certificate file + if ((pCertBuf = (char *)ZFileLoad(pFilename, &iFileSize, ZFILE_OPENFLAG_RDONLY)) == NULL) + { + return(NULL); + } + + // calculate length + *pCertSize = iFileSize; + // return to caller + return((uint8_t *)pCertBuf); +} + +static void load_ca(const char *pCertPath) +{ + const uint8_t *pFileData; + int32_t iFileSize, iResult = 0; + + // try and open file + if ((pFileData = (const uint8_t *)ZFileLoad(pCertPath, &iFileSize, ZFILE_OPENFLAG_RDONLY | ZFILE_OPENFLAG_BINARY)) != NULL) + { + iResult = ProtoHttpSetCACert(pFileData, iFileSize); + ZMemFree((void *)pFileData); + } + ZPrintf("ssltest: load of certificate '%s' %s\n", pCertPath, (iResult > 0) ? "succeeded" : "failed"); +} + +static void load_cert(SsltestStateT *pState, const char *pCertPath) +{ + // load the cert + pState->pClientCert = load_pem(pCertPath, &pState->iCertSize); + // output result + ZPrintf("ssltest: load of client certificate '%s' %s\n", pCertPath, (pState->pClientCert != NULL) ? "succeeded" : "failed"); +} + +static void load_key(SsltestStateT *pState, const char *pCertPath) +{ + // load the key + pState->pClientKey = load_pem(pCertPath, &pState->iKeySize); + // output result + ZPrintf("ssltest: load of client key '%s' %s\n", pCertPath, (pState->pClientKey != NULL) ? "succeeded" : "failed"); +} + +// test all ciphers and tls versions between min and max +static void test_cipher_and_version(ProtoHttpRefT *pHttpRef, SsltestOptionsT *pOptions, SsltestStateT *pState, int32_t iStage) +{ + if (iStage == SSLTEST_STAGE_SETUP) + { + // disable session resumption to force a new connection every attempt + ProtoHttpControl(pHttpRef, 'resu', 0, 0, NULL); + } + else if (iStage == SSLTEST_STAGE_RUNNING) + { + // select next cipher/version + if (++pState->iCiph >= pOptions->iMaxCiph) + { + pState->iVers += 1; + pState->iCiph = pOptions->iMinCiph; + } + // select next server + if (pState->iVers >= pOptions->iMaxVers) + { + pState->iServ += 1; + pState->iVers = pOptions->iMinVers; + } + } + + if (iStage < SSLTEST_STAGE_RESULT) + { + // select specific cipher + ProtoHttpControl(pHttpRef, 'ciph', _ssltest_ciphers[pState->iCiph], 0, NULL); + } + + // format result text + if (iStage == SSLTEST_STAGE_RESULT) + { + ds_snzprintf(pState->strResult, sizeof(pState->strResult), "vers=%x", ProtoHttpStatus(pHttpRef, 'vers', NULL, 0)); + } +} + +// test session resumption in all tls versions +static void test_resume(ProtoHttpRefT *pHttpRef, SsltestOptionsT *pOptions, SsltestStateT *pState, int32_t iStage) +{ + static int32_t _iTest; + + if (iStage == SSLTEST_STAGE_SETUP) + { + // enable session resumption + ProtoHttpControl(pHttpRef, 'resu', 1, 0, NULL); + + // enable all ciphers + pState->iCiph = SSLTEST_CIPHER_ALL; + ProtoHttpControl(pHttpRef, 'ciph', _ssltest_ciphers[pState->iCiph], 0, NULL); + + // since we want to do tests twice, track that here + _iTest = -1; + } + + // since we're testing resume, we want to connect twice (first non-resuming, second resuming) + if (iStage < SSLTEST_STAGE_RESULT) + { + if (++_iTest == 2) + { + // select next version + pState->iVers += 1; + // if past max, select next server + if (pState->iVers >= pOptions->iMaxVers) + { + pState->iServ += 1; + pState->iVers = pOptions->iMinVers; + } + _iTest = 0; + } + } + + // format result text + if (iStage == SSLTEST_STAGE_RESULT) + { + char strCipher[64] = ""; + ProtoHttpStatus(pHttpRef, 'ciph', strCipher, sizeof(strCipher)); + ds_snzprintf(pState->strResult, sizeof(pState->strResult), "vers=%x, ciph=%s, resume=%s", ProtoHttpStatus(pHttpRef, 'vers', NULL, 0), strCipher, + ProtoHttpStatus(pHttpRef, 'resu', NULL, 0) ? "true" : "false"); + } +} + +// test a single basic connection with all ciphers enabled +static void test_basic(ProtoHttpRefT *pHttpRef, SsltestOptionsT *pOptions, SsltestStateT *pState, int32_t iStage) +{ + if (iStage == SSLTEST_STAGE_SETUP) + { + // enable all ciphers + pState->iCiph = SSLTEST_CIPHER_ALL; + ProtoHttpControl(pHttpRef, 'ciph', _ssltest_ciphers[pState->iCiph], 0, NULL); + //ProtoHttpControl(pHttpRef, 'crvd', 2, 0, NULL); + } + + if (iStage == SSLTEST_STAGE_RUNNING) + { + // select next server + if (pState->iVers >= pOptions->iMaxVers) + { + pState->iServ += 1; + pState->iVers = pOptions->iMinVers; + } + } + + // handle results formatting + if (iStage == SSLTEST_STAGE_RESULT) + { + // format result text + char strCipher[64] = "", strSigAlg[32] = "salg="; + int32_t iSigAlg; + ProtoHttpStatus(pHttpRef, 'ciph', strCipher, sizeof(strCipher)); + iSigAlg = ProtoHttpStatus(pHttpRef, 'salg', strSigAlg+5, sizeof(strSigAlg)-5); + ds_snzprintf(pState->strResult, sizeof(pState->strResult), "vers=%x, ciph=%s %s", ProtoHttpStatus(pHttpRef, 'vers', NULL, 0), strCipher, iSigAlg ? strSigAlg : ""); + } + + // handle completion + if (iStage == SSLTEST_STAGE_COMPLETE) + { + // select next version + pState->iVers += 1; + } +} + +static const char **process_args(SsltestStateT *pState, SsltestOptionsT *pOptions, int32_t iArgc, const char *pArgv[]) +{ + static const char *strServerDefault[] = { "https://www.google.com" }, **pServers; + int32_t iArg; + + // echo options + for (iArg = 0; iArg < iArgc; iArg += 1) + { + ZPrintf("%s ", pArgv[iArg]); + } + ZPrintf("\n"); + + ds_memclr(pOptions, sizeof(*pOptions)); + + // init default options + pOptions->iMinVers = 0; + pOptions->iMaxVers = (int32_t)sizeof(_ssltest_versions)/sizeof(_ssltest_versions[0]); + pOptions->iMinCiph = 0; + pOptions->iMaxCiph = (int32_t)sizeof(_ssltest_ciphers)/sizeof(_ssltest_ciphers[0]) - 1; // remove SSLTEST_CIPHER_ALL + pOptions->iMinTest = 0; + pOptions->iMaxTest = (int32_t)sizeof(_ssltest_tests)/sizeof(_ssltest_tests[0]); + pOptions->bSkip = FALSE; + + // pick off command-line options + for (iArg = 1; (iArg < iArgc) && (pArgv[iArg][0] == '-'); iArg += 1) + { + if (!strcmp(pArgv[iArg], "-cert") && ((iArg+1) < iArgc)) + { + load_ca(pArgv[iArg+1]); + iArg += 1; + } + if (!strcmp(pArgv[iArg], "-scrt") && ((iArg+1) < iArgc)) + { + load_cert(pState, pArgv[iArg+1]); + iArg += 1; + } + if (!strcmp(pArgv[iArg], "-skey") && ((iArg+1) < iArgc)) + { + load_key(pState, pArgv[iArg+1]); + iArg += 1; + } + if (!strcmp(pArgv[iArg], "-skip")) + { + pOptions->bSkip = TRUE; + } + if (!strcmp(pArgv[iArg], "-ncrt")) + { + pOptions->bNcrt = TRUE; + } + if (!strcmp(pArgv[iArg], "-mintest") && ((iArg+1) < iArgc)) + { + pOptions->iMinTest = strtol(pArgv[iArg+1], NULL, 10); + ZPrintf("ssltest: mintest=%d\n", pOptions->iMinTest); + iArg += 1; + } + if (!strcmp(pArgv[iArg], "-maxtest") && ((iArg+1) < iArgc)) + { + pOptions->iMaxTest = strtol(pArgv[iArg+1], NULL, 10); + ZPrintf("ssltest: maxtest=%d\n", pOptions->iMaxTest); + iArg += 1; + } + if (!strcmp(pArgv[iArg], "-minvers") && ((iArg+1) < iArgc)) + { + pOptions->iMinVers = strtol(pArgv[iArg+1], NULL, 10); + ZPrintf("ssltest: minvers=%d\n", pOptions->iMinVers); + iArg += 1; + } + if (!strcmp(pArgv[iArg], "-maxvers") && ((iArg+1) < iArgc)) + { + pOptions->iMaxVers = strtol(pArgv[iArg+1], NULL, 10); + ZPrintf("ssltest: maxvers=%d\n", pOptions->iMaxVers); + iArg += 1; + } + if (!strcmp(pArgv[iArg], "-minciph") && ((iArg+1) < iArgc)) + { + pOptions->iMinCiph = strtol(pArgv[iArg+1], NULL, 10); + ZPrintf("ssltest: minciph=%d\n", pOptions->iMinCiph); + iArg += 1; + } + if (!strcmp(pArgv[iArg], "-maxciph") && ((iArg+1) < iArgc)) + { + pOptions->iMaxCiph = strtol(pArgv[iArg+1], NULL, 10); + ZPrintf("ssltest: maxciph=%d\n", pOptions->iMaxCiph); + iArg += 1; + } + } + + // find servers in argument list + if (iArg < iArgc) + { + pServers = pArgv; + pOptions->iMinServ = iArg; + pOptions->iMaxServ = iArgc; + } + else // if no servers specified, use default + { + pServers = strServerDefault; + pOptions->iMinServ = 0; + pOptions->iMaxServ = 1; + } + + // log params + ZPrintf("ssltest: params(vers[%d,%d] ciph[%d,%d] test[%d,%d]\n", pOptions->iMinVers, pOptions->iMaxVers, pOptions->iMinCiph, pOptions->iMaxCiph, pOptions->iMinTest, pOptions->iMaxTest); + + // return servers reference to caller + return(pServers); +} + +static void fail_check(ProtoHttpRefT *pHttpRef, int32_t iLen, char *pResultText, int32_t iResultLen) +{ + ProtoSSLAlertDescT AlertDesc; + int32_t iSockErr = ProtoHttpStatus(pHttpRef, 'serr', NULL, 0); + int32_t iSslFail = ProtoHttpStatus(pHttpRef, 'essl', NULL, 0); + int32_t iAlert = ProtoHttpStatus(pHttpRef, 'alrt', &AlertDesc, sizeof(AlertDesc)); + int32_t iOffset; + + if (iAlert > 0) + { + iOffset = ds_snzprintf(pResultText, iResultLen, "%s ssl alert %s (%d)", (iAlert == 1) ? "recv" : "sent", AlertDesc.pAlertDesc, AlertDesc.iAlertType); + } + else + { + iOffset = ds_snzprintf(pResultText, iResultLen, "download failed (err=%d, sockerr=%d sslerr=%d)", iLen, iSockErr, iSslFail); + } + + if ((iSslFail == PROTOSSL_ERROR_CERT_INVALID) || (iSslFail == PROTOSSL_ERROR_CERT_HOST) || + (iSslFail == PROTOSSL_ERROR_CERT_NOTRUST) || (iSslFail == PROTOSSL_ERROR_CERT_REQUEST)) + { + ProtoSSLCertInfoT CertInfo; + if (ProtoHttpStatus(pHttpRef, 'cert', &CertInfo, sizeof(CertInfo)) == 0) + { + ds_snzprintf(pResultText+iOffset, iResultLen-iOffset, " cert failure (%d): (C=%s, ST=%s, L=%s, O=%s, OU=%s, CN=%s)", iSslFail, + CertInfo.Ident.strCountry, CertInfo.Ident.strState, CertInfo.Ident.strCity, + CertInfo.Ident.strOrg, CertInfo.Ident.strUnit, CertInfo.Ident.strCommon); + } + else + { + ds_snzprintf(pResultText+iOffset, iResultLen-iOffset, " could not get cert info"); + } + } +} + + +/*** Public Functions ******************************************************************/ + +// dll-friendly DirtyMemAlloc +#if !defined(DIRTYCODE_DLL) +void *DirtyMemAlloc(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#else +void *DirtyMemAlloc2(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#endif +{ + return(malloc(iSize)); +} + +// dll-friendly DirtyMemFree +#if !defined(DIRTYCODE_DLL) +void DirtyMemFree(void *pMem, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#else +void DirtyMemFree2(void *pMem, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#endif +{ + free(pMem); +} + +// usage: ssltest [options] +int main(int32_t argc, const char *argv[]) +{ + int32_t iCount, iLen, iState, iTimeout; + ProtoHttpRefT *pHttpRef; + char strBuffer[16*1024]; + const char **pServers; + SsltestOptionsT SsltestOptions, *pOptions = &SsltestOptions; + SsltestStateT SsltestState, *pState = &SsltestState; + + #if defined(DIRTYCODE_DLL) + DirtyMemFuncSet(&DirtyMemAlloc2, &DirtyMemFree2); + #endif + + // hook into zprintf output + ZPrintfHook(ssltest_zprintf_hook, NULL); + + // clear state and options settings + ds_memclr(pState, sizeof(*pState)); + ds_memclr(pOptions, sizeof(*pOptions)); + + // get options + pServers = process_args(pState, pOptions, argc, argv); + + // init test state + pState->iTest = pOptions->iMinTest; + pState->iServ = pOptions->iMinServ; + pState->iVers = pOptions->iMinVers; + pState->iCiph = pOptions->iMinCiph; + + // start dirtysock + if (!network_startup()) + { + return(-1); + } + + // setup http module + pHttpRef = ProtoHttpCreate(4096); + + // just keep working + for (iCount = 0, iState = SSLTEST_STAGE_SETUP, iLen = -1, iTimeout = NetTick()-1; ; ) + { + const SsltestTestT *pTest = &_ssltest_tests[pState->iTest]; + + // see if its time to query + if ((iTimeout != 0) && (NetTickDiff(NetTick(), iTimeout) >= 0)) + { + // set up for test + if (iState == SSLTEST_STAGE_SETUP) + { + ZPrintf("ssltest: running %s test\n", pTest->pTestName); + } + pTest->pTestFunc(pHttpRef, &SsltestOptions, pState, iState); + + // see if we're at the end of our test + if (pState->iServ >= pOptions->iMaxServ) + { + // go to next test + pTest = &_ssltest_tests[++pState->iTest]; + // see if we're done with our tests + if (pState->iTest >= pOptions->iMaxTest) + { + break; + } + // reset test state + pState->iServ = pOptions->iMinServ; + pState->iVers = pOptions->iMinVers; + pState->iCiph = pOptions->iMinCiph; + // move to setup + iState = SSLTEST_STAGE_SETUP; + continue; + } + else + { + iState = SSLTEST_STAGE_RUNNING; + } + + // if TLS1.0 or TLS1.1, exclude ciphers that were introduced after TLS1.1 + if ((_ssltest_versions[pState->iVers] < PROTOSSL_VERSION_TLS1_2) && (pState->iCiph > SSLTEST_MAXCIPHER_PRE12) && (pState->iCiph != SSLTEST_CIPHER_ALL)) + { + continue; + } + // if TLS1.2, exclude ciphers that were introduced for TLS1.3 + if ((_ssltest_versions[pState->iVers] < PROTOSSL_VERSION_TLS1_3) && (pState->iCiph > SSLTEST_MAXCIPHER_PRE13) && (pState->iCiph != SSLTEST_CIPHER_ALL)) + { + continue; + } + // if TLS1.3, exclude ciphers that predate TLS1.3 + if ((_ssltest_versions[pState->iVers] == PROTOSSL_VERSION_TLS1_3) && (pState->iCiph <= SSLTEST_MAXCIPHER_PRE13) && (pState->iCiph != SSLTEST_CIPHER_ALL)) + { + continue; + } + + // set ssl options + ProtoHttpControl(pHttpRef, 'vers', _ssltest_versions[pState->iVers], 0, NULL); + ProtoHttpControl(pHttpRef, 'ncrt', pOptions->bNcrt, 0, NULL); + + // set client cert if specified + if (pState->pClientCert != NULL) + { + ProtoHttpControl(pHttpRef, 'scrt', pState->iCertSize, 0, pState->pClientCert); + } + // set client key if specified + if (pState->pClientKey != NULL) + { + ProtoHttpControl(pHttpRef, 'skey', pState->iKeySize, 0, pState->pClientKey); + } + + // set http options + ProtoHttpControl(pHttpRef, 'keep', 0, 0, NULL); + + // run the test + iTimeout = NetTick(); + if (!pOptions->bSkip) + { + ProtoHttpGet(pHttpRef, pServers[pState->iServ], FALSE); + iTimeout += 30*1000; + } + iLen = 0; + iCount += 1; // count the attempt + } + + if (!pOptions->bSkip) + { + // update protohttp + ProtoHttpUpdate(pHttpRef); + + // read incoming data into buffer + if ((iLen = ProtoHttpRecvAll(pHttpRef, strBuffer, sizeof(strBuffer) - 1)) != PROTOHTTP_RECVWAIT) + { + char strFailInfo[256] = ""; + int32_t iVers = pState->iVers; + if ((iLen > 0) || (iLen == PROTOHTTP_RECVBUFF)) + { + pTest->pTestFunc(pHttpRef, &SsltestOptions, &SsltestState, SSLTEST_STAGE_RESULT); + ZPrintf("ssltest: [success] version=%s cipher=%s server=%s (%s)\n", _ssltest_tlsnames[iVers], _ssltest_ciphernames[pState->iCiph], pServers[pState->iServ], pState->strResult); + } + else + { + fail_check(pHttpRef, iLen, strFailInfo, sizeof(strFailInfo)); + ZPrintf("ssltest: [failure] version=%s cipher=%s server=%s (%s)\n", _ssltest_tlsnames[iVers], _ssltest_ciphernames[pState->iCiph], pServers[pState->iServ], strFailInfo); + } + pTest->pTestFunc(pHttpRef, &SsltestOptions, &SsltestState, SSLTEST_STAGE_COMPLETE); + iTimeout = NetTick() - 1; + } + } + else + { + ZPrintf("ssltest: [skipped] version=%s cipher=%s server=%s\n", _ssltest_tlsnames[pState->iVers], _ssltest_ciphernames[pState->iCiph], pServers[pState->iServ]); + pTest->pTestFunc(pHttpRef, &SsltestOptions, &SsltestState, SSLTEST_STAGE_COMPLETE); + } + + // sleep a bit + NetConnSleep(10); + } + + ZPrintf("ssltest: done (%d tests)\n", iCount); + + // shut down HTTP + ProtoHttpDestroy(pHttpRef); + + // disconnect from the network + NetConnDisconnect(); + + // shutdown the network connections && destroy the dirtysock code + NetConnShutdown(FALSE); + return(0); +} \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/sample/tester2/data/club/club1.txt b/src/thirdparty/dirtysdk/sample/tester2/data/club/club1.txt new file mode 100644 index 00000000..cb3d5dfa --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/data/club/club1.txt @@ -0,0 +1,89 @@ +#sign on an create a club +club connect sdevlobby01.online.ea.com 11900 csoc030 csotest csoc030 +club create fooclub tag30 UK 1 2 3 +club recruit csoc031 +club recruit csoc032 +club recruit csoc033 +club recruit csoc034 +club recruit csoc035 +club recruit csoc036 + +sleep 30 + +#recruitee accepts +club connect sdevlobby01.online.ea.com 11900 csoc031 csotest +sleep 10 +# Find with no params, will find my recruited clubs.. +club find +#note: accept also takes a clubid param, but, to make it easy, tester2 stores id from find above. +club accept +sleep 30 + +#another dude accepts +club connect sdevlobby01.online.ea.com 11900 csoc032 csotest +sleep 10 +club find +club accept +sleep 30 + +#just say no +club connect sdevlobby01.online.ea.com 11900 csoc033 csotest +sleep 10 +club find +club reject +sleep 30 + +#another dude accepts +club connect sdevlobby01.online.ea.com 11900 csoc034 csotest +sleep 10 +club find +club accept +sleep 30 + +# now, GM can transfer to member, who accepted was recruit above.. +club connect sdevlobby01.online.ea.com 11900 csoc031 csotest +sleep 10 +club get +club transfer csoc032 + +# now, transferee can accept transfer .. +club connect sdevlobby01.online.ea.com 11900 csoc031 csotest +sleep 10 +club get +club acceptX + +#new gm should be able to recruit.. +club recruit csoc038 + +#report some scores.. NOTE fiso001 must belong to another club to see stats update.. +club score csoc032 1 fiso001 3 +club get + + + +club connect sdevlobby01.online.ea.com 11900 csoc035 csotest +sleep 10 +club find +club accept +sleep 30 + +club connect sdevlobby01.online.ea.com 11900 csoc038 csotest +sleep 10 +club find +club accept +sleep 30 + +#this will fail --not the gm +club connect sdevlobby01.online.ea.com 11900 csoc030 csotest +sleep 10 +club find tag30 T +club disband +sleep 30 + + + + + + + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/data/http/rsa1000bitCA.pem b/src/thirdparty/dirtysdk/sample/tester2/data/http/rsa1000bitCA.pem new file mode 100644 index 00000000..8125dc28 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/data/http/rsa1000bitCA.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD +VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0 +MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV +BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy +dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ +ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII +0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI +uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI +hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3 +YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc +1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA== +-----END CERTIFICATE----- + diff --git a/src/thirdparty/dirtysdk/sample/tester2/data/http/thawteCp.pem b/src/thirdparty/dirtysdk/sample/tester2/data/http/thawteCp.pem new file mode 100644 index 00000000..51285e33 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/data/http/thawteCp.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy +dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t +MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB +MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG +A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp +b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl +cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv +bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE +VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ +ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR +uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI +hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM +pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- diff --git a/src/thirdparty/dirtysdk/sample/tester2/data/utf8/readme.txt b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/readme.txt new file mode 100644 index 00000000..cd932674 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/readme.txt @@ -0,0 +1,9 @@ +Files in this directory are input for the 'utf8' command in the tester application. + +Filename Format Description +~~~~~~~~~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +utf8test00.txt UTF-8 Middle English text +utf8test01.txt UTF-8 Middle High German +utf8test02.txt UTF-8 "I can eat glass and it doesn't hurt me" in 29 languages +ucs2test00.txt UCS-2 "You have been ejected from the game" in FIFA's supported languages +ucs2test01.txt UCS-2 More FIFA text \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/sample/tester2/data/utf8/ucs2test00.txt b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/ucs2test00.txt new file mode 100644 index 00000000..876c5c8a Binary files /dev/null and b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/ucs2test00.txt differ diff --git a/src/thirdparty/dirtysdk/sample/tester2/data/utf8/ucs2test01.txt b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/ucs2test01.txt new file mode 100644 index 00000000..fad08cab Binary files /dev/null and b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/ucs2test01.txt differ diff --git a/src/thirdparty/dirtysdk/sample/tester2/data/utf8/utf8test00.txt b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/utf8test00.txt new file mode 100644 index 00000000..d348f546 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/utf8test00.txt @@ -0,0 +1,7 @@ +From Laȝamon's The Chronicles of England, Middle English, West Midlands: + +An preost wes on leoden, Laȝamon was ihoten +He wes Leovenaðes sone -- liðe him be Drihten. +He wonede at Ernleȝe at æðelen are chirechen, +Uppen Sevarne staþe, sel þar him þuhte, +Onfest Radestone, þer he bock radde. \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/sample/tester2/data/utf8/utf8test01.txt b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/utf8test01.txt new file mode 100644 index 00000000..6acb8489 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/utf8test01.txt @@ -0,0 +1,9 @@ +From the Tagelied of Wolfram von Eschenbach (Middle High German): +Sîne klâwen durh die wolken sint geslagen, +er stîget ûf mit grôzer kraft, +ich sih in grâwen tägelîch als er wil tagen, +den tac, der im geselleschaft +erwenden wil, dem werden man, +den ich mit sorgen în verliez. +ich bringe in hinnen, ob ich kan. +sîn vil manegiu tugent michz leisten hiez. diff --git a/src/thirdparty/dirtysdk/sample/tester2/data/utf8/utf8test02.txt b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/utf8test02.txt new file mode 100644 index 00000000..4928614d --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/data/utf8/utf8test02.txt @@ -0,0 +1,29 @@ +English: I can eat glass and it doesn't hurt me. +Latin: Vitrum edere possum; mihi non nocet. +Esperanto: Mi povas manĝi vitron, ĝi ne damaĝas min. +French: Je peux manger du verre, ça ne me fait pas de mal. +Provençal / Occitan: Pòdi manjar de veire, me nafrariá pas. +Québécois: J'peux manger d'la vitre, ça m'fa pas mal. +Walloon: Dji pou magnî do vêre, çoula m' freut nén må. +Basque: Kristala jan dezaket, ez dit minik ematen. +Catalan: Puc menjar vidre que no em fa mal. +Spanish: Puedo comer vidrio, no me hace daño. +Aragones: Puedo minchar beire, no me'n fa mal . +Galician: Eu podo xantar cristais e non cortarme. +Portuguese: Posso comer vidro, não me faz mal. +Brazilian Portuguese: Consigo comer vidro. Não me machuca. +Cabo Verde Creole: M' podê cumê vidru, ca ta maguâ-m'. +Italian: Posso mangiare il vetro e non mi fa male. +Roman: Me posso magna' er vetro, e nun me fa male. +Sicilian: Puotsu mangiari u vitru, nun mi fa mali. +Milanese: Sôn bôn de magnà el véder, el me fa minga mal. +Venetian: Mi posso magnare el vetro, no'l me fa mae. +Romanian: Pot să mănânc sticlă și ea nu mă rănește. +Cornish: Mý a yl dybry gwéder hag éf ny wra ow ankenya. +Welsh: Dw i'n gallu bwyta gwydr, 'dyw e ddim yn gwneud dolur i mi. +Manx Gaelic: Foddym gee glonney agh cha jean eh gortaghey mee. +Old Irish (Latin): Con·iccim ithi nglano. Ním·géna. +Irish: Is féidir liom gloinne a ithe. Ní dhéanann sí dochar ar bith dom. +Scottish Gaelic: S urrainn dhomh gloinne ithe; cha ghoirtich i mi. +Anglo-Saxon (Latin): Ic mæg glæs eotan ond hit ne hearmiað me. +Middle English: Ich canne glas eten and hit hirtiþ me nouȝt. diff --git a/src/thirdparty/dirtysdk/sample/tester2/scripts/testhttpsrv/readme.txt b/src/thirdparty/dirtysdk/sample/tester2/scripts/testhttpsrv/readme.txt new file mode 100644 index 00000000..481bb8a6 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/scripts/testhttpsrv/readme.txt @@ -0,0 +1,22 @@ +tw.py is a python script to act as a test server to simulate the conditions below. The syntax is python tw.py --ip --handler . +By default the server will listen on port 9000. + +1. Server responds normally to request. +python tw.py --ip 10.30.80.175 --handler normal + +2. Server is not responding to requests. +python tw.py --ip 10.30.80.175 --handler noresponse + +The server will not respond with a proper HTTP response. + +3. Server is responding slowly to requests. +python tw.py --ip 10.30.80.175 --handler slow + +Server will respond in 30s + +4. Server is responding with a certain HTTP error code. +python tw.py --ip 10.30.80.175 --handler error --error 403 + +5. Server is not listening on a port at all. +Don't start the server. + diff --git a/src/thirdparty/dirtysdk/sample/tester2/scripts/testhttpsrv/tw.py b/src/thirdparty/dirtysdk/sample/tester2/scripts/testhttpsrv/tw.py new file mode 100644 index 00000000..f96c11b3 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/scripts/testhttpsrv/tw.py @@ -0,0 +1,113 @@ +from SocketServer import ThreadingMixIn +from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler +import sys +import time +import optparse +import os +import uuid + +errorcode = 404 +tmp_dir = 'twtmp' + +class NormalHandler(BaseHTTPRequestHandler): + def do_GET(self): + self.do_all() + + def do_POST(self): + global tmp_dir + length = long(self.headers.getheader('content-length')) + chunk = self.rfile.read(length) + + fname = os.path.join(tmp_dir, str(uuid.uuid1())) + fout = file(fname, 'wb') + fout.write(chunk) + fout.close() + + fsize = os.path.getsize(fname) + + self.do_all([fname, str(fsize)]) + + def do_HEAD(self): + self.do_all() + + def do_PUT(self): + self.do_all(); + + def do_DELETE(self): + self.do_all(); + + def do_all(self, response=[]): + self.send_response(200) + self.send_header('Content-type','text/plain') + self.end_headers() + self.wfile.write(self.path+'\n') + for r in response: + self.wfile.write(r+'\n') + +class NoResponseHandler(BaseHTTPRequestHandler): + def do_GET(self): + self.do_all() + + def do_POST(self): + self.do_all() + + def do_all(self): + pass + +class SlowResponseHandler(BaseHTTPRequestHandler): + def do_GET(self): + self.do_all() + + def do_POST(self): + self.do_all() + + def do_all(self): + time.sleep(30) + self.send_response(200) + self.send_header('Content-type','text/plain') + self.end_headers() + self.wfile.write(self.path) + +class ErrorResponseHandler(BaseHTTPRequestHandler): + def do_GET(self): + self.do_all() + + def do_POST(self): + self.do_all() + + def do_all(self): + global errorcode + self.send_response(errorcode) + self.send_header('Content-type','text/plain') + self.end_headers() + self.wfile.write(self.path) + +class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): + pass + +handlers = {'normal':NormalHandler, 'noresponse':NoResponseHandler, 'slow':SlowResponseHandler, 'error':ErrorResponseHandler} + +parser = optparse.OptionParser(usage = '%prog --ip=IP --port=PORT --handler=HANDLER --error=ERROR') +parser.add_option('-i', '--ip', dest='ip', + help='listen on ip', metavar='IP') +parser.add_option('-p', '--port', dest='port', type=int, default=9000, + help='listen on port PORT', metavar='PORT') +parser.add_option('-l', '--handler', dest='handler', + help='how to respond', metavar='HANDLER') +parser.add_option('-e', '--error', dest='error', type=int, + help='what error to respond with', metavar='ERROR') + +opts, args = parser.parse_args() + +ip = opts.ip +port = opts.port +handler = opts.handler +if opts.error: + errorcode = opts.error + +if not os.path.exists(tmp_dir): + os.mkdir(tmp_dir) + + +server = ThreadingHTTPServer((ip, port), handlers[handler]) +server.serve_forever() diff --git a/src/thirdparty/dirtysdk/sample/tester2/scripts/xexconfig.xml b/src/thirdparty/dirtysdk/sample/tester2/scripts/xexconfig.xml new file mode 100644 index 00000000..bd21979a --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/scripts/xexconfig.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/comm/testercomm.c b/src/thirdparty/dirtysdk/sample/tester2/source/comm/testercomm.c new file mode 100644 index 00000000..5ac2fb5d --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/comm/testercomm.c @@ -0,0 +1,329 @@ +/*H********************************************************************************/ +/*! + \File testercomm.c + + \Description + This module provides a communication layer between the host and the client. + Typical operations are SendLine() and GetLine(), which send and receive + lines of text, commands, debug output, etc. Each platform will implement + its own way of communicating through files, debugger API calls, etc. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/23/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include "DirtySDK/dirtysock.h" +#include "libsample/zmem.h" +#include "libsample/zlib.h" +#include "libsample/zlist.h" +#include "testerregistry.h" +#include "testercomm.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterCommCreate + + \Description + Create a tester host client communication module. + + \Input None + + \Output TesterCommT * - pointer to allocated module + + \Version 03/23/2005 (jfrank) +*/ +/********************************************************************************F*/ +TesterCommT *TesterCommCreate(void) +{ + TesterCommT *pState; + + // create the module + pState = (TesterCommT *)ZMemAlloc(sizeof(TesterCommT)); + ds_memclr((void *)pState, sizeof(TesterCommT)); + pState->pInterface = (TesterCommInterfaceT *)ZMemAlloc(sizeof(TesterCommInterfaceT)); + ds_memclr((void *)pState->pInterface, sizeof(TesterCommInterfaceT)); + + // create the lists + pState->pInputData = ZListCreate(TESTERCOMM_NUMCOMMANDS_MAX, sizeof(TesterCommDataT)); + pState->pOutputData = ZListCreate(TESTERCOMM_NUMCOMMANDS_MAX, sizeof(TesterCommDataT)); + + // add this module to the registry + TesterRegistrySetPointer("COMM", pState); + + // return the pointer + return(pState); +} + + +/*F********************************************************************************/ +/*! + \Function TesterCommMessage + + \Description + Send a message to the other side + + \Input *pState - module state + \Input iMsgType - type of message to send (TESTER_MSGTYPE_XXX) + \Input *pMsgText - text of the message to send + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/28/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterCommMessage(TesterCommT *pState, int32_t iMsgType, const char *pMsgText) +{ + // check for error conditions + if ((pState == NULL) || + (iMsgType <= 0) || + (iMsgType >= TESTER_MSGTYPE_MAX) || + (pMsgText == NULL)) + { + return(-1); + } + + ds_strnzcpy(pState->Message.strBuffer, pMsgText, sizeof(pState->Message.strBuffer)); + pState->Message.iType = iMsgType; + return(ZListPushBack(pState->pOutputData, &pState->Message)); +} + + +/*F********************************************************************************/ +/*! + \Function TesterCommRegister + + \Description + Register a callback for a particular type of message + + \Input *pState - module state + \Input iMsgType - type of message register for (TESTER_MSGTYPE_XXX) + \Input *pCallback - callback to call when the message comes in + \Input *pParam - user pointer to pass to the callback + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterCommRegister(TesterCommT *pState, int32_t iMsgType, TesterCommMsgCbT *pCallback, void *pParam) +{ + // check error conditions + if ((pState == NULL) || + (iMsgType <= 0) || + (iMsgType >= TESTER_MSGTYPE_MAX)) + { + return(-1); + } + + // now register the callback + pState->MessageMap[iMsgType] = pCallback; + pState->pMessageMapUserData[iMsgType] = pParam; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function TesterCommStatus + + \Description + Get module status. + + \Input *pState - module state + \Input iSelect - status selector + \Input iValue - selector specific + + \Output int32_t - selector specific + + \Version 10/31/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t TesterCommStatus(TesterCommT *pState, int32_t iSelect, int32_t iValue) +{ + if (iSelect == 'inpt') + { + return(pState->bGotInput); + } + // unhandled + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function TesterCommSuspend + + \Description + Suspend the comm module. Service the files but don't + execute any commands until the Wake function is called, + + \Input *pState - pointer to host client comm module + + \Output None + + \Version 04/07/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterCommSuspend(TesterCommT *pState) +{ + if(pState) + pState->uSuspended = 1; +} + + +/*F********************************************************************************/ +/*! + \Function TesterCommWake + + \Description + Wake the comm module to begin processing commands again. + + \Input *pState - pointer to host client comm module + + \Output None + + \Version 04/07/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterCommWake(TesterCommT *pState) +{ + if(pState) + pState->uSuspended = 0; +} + + +/*F********************************************************************************/ +/*! + \Function TesterCommDestroy + + \Description + Destroy a tester host client communication module. + + \Input *pState - pointer to host client comm module + + \Output None + + \Version 03/23/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterCommDestroy(TesterCommT *pState) +{ + if (pState) + { + // disconnect first, just in case + TesterCommDisconnect(pState); + ZListDestroy(pState->pInputData); + ZListDestroy(pState->pOutputData); + + // delete this item from the registry + TesterRegistrySetPointer("COMM", NULL); + + // now dump the memory + ZMemFree(pState->pInterface->pData); + ZMemFree(pState->pInterface); + ZMemFree(pState); + } +} + + +/*F********************************************************************************/ +/*! + \Function TesterCommConnect + + \Description + Connect the host client communication module. + + \Input *pState - pointer to host client comm module + \Input *pParams - startup parameters + \Input bIsHost - TRUE=host, FALSE=client + + \Output int32_t - 0 for success, error code otherwise + + \Version 05/02/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterCommConnect(TesterCommT *pState, const char *pParams, uint32_t bIsHost) +{ + // check for errors + if (pState == NULL) + return(-1); + if (pState->pInterface == NULL) + return(-1); + if (pState->pInterface->CommConnectFunc == NULL) + return(-1); + + return(pState->pInterface->CommConnectFunc(pState, pParams, bIsHost)); +} + +/*F********************************************************************************/ +/*! + \Function TesterCommUpdate + + \Description + Give the host/client interface module some processor time. Call this + once in a while to pump the input and output pipes. + + \Input *pState - module state + + \Output int32_t - 0 for success, error code otherwise + + \Version 05/02/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterCommUpdate(TesterCommT *pState) +{ + // check for errors + if (pState == NULL) + return(-1); + if (pState->pInterface == NULL) + return(-1); + if (pState->pInterface->CommUpdateFunc == NULL) + return(-1); + + return(pState->pInterface->CommUpdateFunc(pState)); +} + + +/*F********************************************************************************/ +/*! + \Function TesterCommDisconnect + + \Description + Disconnect the host client communication module. + + \Input *pState - pointer to host client comm module + + \Output int32_t - 0=success, error code otherwise + + \Version 05/02/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterCommDisconnect(TesterCommT *pState) +{ + // check for errors + if (pState == NULL) + return(-1); + if (pState->pInterface == NULL) + return(-1); + if (pState->pInterface->CommDisconnectFunc == NULL) + return(-1); + + return(pState->pInterface->CommDisconnectFunc(pState)); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/comm/testercomm_file.c b/src/thirdparty/dirtysdk/sample/tester2/source/comm/testercomm_file.c new file mode 100644 index 00000000..cb3dcb14 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/comm/testercomm_file.c @@ -0,0 +1,543 @@ +/*H********************************************************************************/ +/*! + \File testercomm_file.c + + \Description + This module provides a communication layer between the host and the client. + Typical operations are SendLine() and GetLine(), which send and receive + lines of text, commands, debug output, etc. Each platform will implement + its own way of communicating through files, debugger API calls, etc. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/23/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/util/base64.h" +#include "DirtySDK/util/jsonformat.h" +#include "DirtySDK/util/jsonparse.h" +#include "libsample/zmem.h" +#include "libsample/zlib.h" +#include "libsample/zlist.h" +#include "libsample/zfile.h" +#include "testerprofile.h" +#include "testerregistry.h" +#include "testercomm.h" + +/*** Defines **********************************************************************/ + +#define TESTERCOMM_FILEPATHSIZE_DEFAULT (256) //!< path/file storage +#define TESTERCOMM_PATH_DEFAULT (TESTERPROFILE_CONTROLDIR_VALUEDEFAULT) //!< default path for control files + +#define TESTERCOMM_OUTPUTTIMEOUT_DEFAULT (60*1000) //!< timeout (ms) for a output message + +#define TESTERCOMM_FILEBUFFERSIZE (4096) //!< size of temp file buffer to use + +#define TESTERCOMM_FILEPROFILING (0) //! turn this on to see times for file writing + +/*** Type Definitions *************************************************************/ + +typedef struct TesterCommFileT TesterCommFileT; +struct TesterCommFileT +{ + // file specific stuff + char strControlDir[TESTERPROFILE_CONTROLDIR_SIZEDEFAULT]; //!< location of control directory + char strTempPathFile[TESTERCOMM_FILEPATHSIZE_DEFAULT]; //!< temporary output filename + char strInputPathFile [TESTERCOMM_FILEPATHSIZE_DEFAULT]; //!< input file filename + char strOutputPathFile[TESTERCOMM_FILEPATHSIZE_DEFAULT]; //!< output file filename +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _TesterCommCheckInput + + \Description + Check for data coming from the other side (host or client) and pull + any data into our internal buffer, if possible. + + \Input *pState - pointer to host client comm module + + \Output int32_t - 0 = no data, >0 = data, error code otherwise + + \Version 03/24/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommCheckInput(TesterCommT *pState) +{ + TesterCommFileT *pInterfaceData; + char *pInputData; + const char *pInputLoop; + int32_t iInputDataSize; + ZFileT iInputFile; + uint16_t aJson[16*1024]; + + if ((pState == NULL) || (pState->pInterface->pData == NULL)) + { + return(-1); + } + pInterfaceData = pState->pInterface->pData; + + // check to make sure our filename is sane + if (strlen(pInterfaceData->strInputPathFile) == 0) + { + return(-1); + } + + // open the input file first + iInputFile = ZFileOpen(pInterfaceData->strInputPathFile, ZFILE_OPENFLAG_RDONLY | ZFILE_OPENFLAG_APPEND); + + if (iInputFile == ZFILE_INVALID) + { + // could not open it - nothing there - not necessarily an error + return(-1); + } + + // get the file size + iInputDataSize = (int32_t)ZFileSize(iInputFile); + + // close the file + ZFileClose(iInputFile); + + // check for empty file + if (iInputDataSize == 0) + { + // delete the file + ZFileDelete(pInterfaceData->strInputPathFile); + // and quit + return(0); + } + + // load the contents of the file into the buffer + pInputData = ZFileLoad(pInterfaceData->strInputPathFile, &(iInputDataSize), 0); + JsonParse(aJson, sizeof(aJson)/sizeof(*aJson), pInputData, iInputDataSize); + + // and delete the file + ZFileDelete(pInterfaceData->strInputPathFile); + + if (pInputData == NULL) + { + // no data to process + return(0); + } + + // remember that we got input + pState->bGotInput = TRUE; + + // push each line onto the list + iInputDataSize = 0; + while ((pInputLoop = JsonFind2(aJson, NULL, "msg[", iInputDataSize)) != NULL) + { + char strBuffer[sizeof(pState->LineData.strBuffer)]; + int32_t iBufLen; + + ds_memclr(&pState->LineData, sizeof(pState->LineData)); + pState->LineData.iType = (int32_t)JsonGetInteger(JsonFind2(aJson, pInputLoop, ".TYPE", iInputDataSize), 0); + iBufLen = JsonGetString(JsonFind2(aJson, pInputLoop, ".TEXT", iInputDataSize), strBuffer, sizeof(strBuffer), ""); + Base64Decode3(strBuffer, iBufLen, pState->LineData.strBuffer, sizeof(pState->LineData.strBuffer)); + // add to the back of the list + // remember to add one for the terminator + ZListPushBack(pState->pInputData, &pState->LineData); + + iInputDataSize += 1; + } + + // kill the file memory we used to store the temp input + ZMemFree(pInputData); + + // done - return how much we got from the file + return(iInputDataSize); +} + + +/*F********************************************************************************/ +/*! + \Function _TesterCommCheckOutput + + \Description + Check and send data from the output buffer, if possible. + + \Input *pState - pointer to host client comm module + + \Output int32_t - 0=success, error code otherwise + + \Version 03/24/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommCheckOutput(TesterCommT *pState) +{ + TesterCommFileT *pInterfaceData; + ZFileStatT FileStat; + ZFileT iTempFile; + int32_t iResult; + int32_t iLineLength=0; +#if TESTERCOMM_FILEPROFILING + int32_t iTime1, iTime2; +#endif + + // file writing stuff + const char *pFileBuf; + + // check for error conditions + if ((pState == NULL) || (pState->pInterface->pData == NULL)) + { + return(-1); + } + pInterfaceData = pState->pInterface->pData; + + // see if there's any data to send + if (ZListPeekFront(pState->pOutputData) == NULL) + { + // reset the last send time - we're not timing out because there's no data + pState->uLastSendTime = ZTick(); + return(0); + } + + // see if we've timed out + if (ZTick() - pState->uLastSendTime > TESTERCOMM_OUTPUTTIMEOUT_DEFAULT) + { + // timeout occurred - dump all pending messages + // and pop a warning + ZListClear(pState->pOutputData); + ZPrintf("testercomm: TIMEOUT in sending data. List cleared. Comms Lost?\n"); + } + + // we've got data to send in the output list + // see if we can send it in the file + iResult = ZFileStat(pInterfaceData->strOutputPathFile, &FileStat); + + // only do something if the file doesn't exist + // so quit if the NOSUCHFILE isn't set + if (iResult != ZFILE_ERROR_NOSUCHFILE) + return(0); + + // open the file + iTempFile = ZFileOpen(pInterfaceData->strTempPathFile, ZFILE_OPENFLAG_WRONLY | ZFILE_OPENFLAG_CREATE); + if (iTempFile == ZFILE_INVALID) + { + // could not open it - error condition + return(-1); + } + +#if TESTERCOMM_FILEPROFILING + printf("--**-- [%12d] Starting get from buffer to file \n", ZTick()); +#endif + JsonInit(pState->strCommand, sizeof(pState->strCommand), 0); + JsonArrayStart(pState->strCommand, "msg"); + while(ZListPeekFront(pState->pOutputData)) + { + char strBuffer[sizeof(pState->LineData.strBuffer)]; + + // snag the data into the local buffer + ZListPopFront(pState->pOutputData, &pState->LineData); + + // create the stuff to write + JsonObjectStart(pState->strCommand, NULL); + JsonAddInt(pState->strCommand, "TYPE", pState->LineData.iType); + Base64Encode2(pState->LineData.strBuffer, (int32_t)strlen(pState->LineData.strBuffer), strBuffer, sizeof(strBuffer)); + if ((iResult = JsonAddStr(pState->strCommand, "TEXT", strBuffer)) == JSON_ERR_FULL) + { + // Sometimes, the text we get in will be too big. Make sure this is an easy error to diagnose. + printf("Text too large to send in TESTERCOMM_COMMANDSIZE_DEFAULT. Discarding.\n"); + } + JsonObjectEnd(pState->strCommand); + } + JsonArrayEnd(pState->strCommand); + pFileBuf = JsonFinish(pState->strCommand); + iLineLength = (int32_t)strlen(pFileBuf); + + // write the last little chunk + if (iLineLength > 0) + { +#if TESTERCOMM_FILEPROFILING + iTime1 = ZTick(); +#endif + ZFileWrite(iTempFile, (void *)pFileBuf, iLineLength); +#if TESTERCOMM_FILEPROFILING + iTime2 = ZTick(); + printf("------ [%12d] Writing [%d] bytes to file took [%d] Ticks.\n", + ZTick(), iWrittenSize, iTime2 - iTime1); +#endif + } + +#if TESTERCOMM_FILEPROFILING + printf("--**-- [%12d] Done pushing from buffer to file \n", ZTick()); +#endif + + // close the temp file + ZFileClose(iTempFile); + iTempFile = 0; +#if TESTERCOMM_FILEPROFILING + printf("--**-- [%12d] File closed. \n", ZTick()); +#endif + + // move the temp file into the real file + iResult = ZFileRename(pInterfaceData->strTempPathFile, pInterfaceData->strOutputPathFile); + if (iResult != 0) + { + NetPrintf(("testercomm: error [%d=0x%X] renaming [%s] to [%s]\n", + iResult, iResult, pInterfaceData->strTempPathFile, pInterfaceData->strOutputPathFile)); + } +#if TESTERCOMM_FILEPROFILING + printf("--**-- [%12d] File renamed. \n", ZTick()); +#endif + + // mark the last send time + pState->uLastSendTime = ZTick(); + + return(0); +} + + +/*F********************************************************************************/ +/*! + \Function _TesterCommConnect + + \Description + Connect the host client communication module. + + \Input *pState - pointer to host client comm module + \Input *pParams - startup parameters + \Input bIsHost - TRUE=host, FALSE=client + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/23/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommConnect(TesterCommT *pState, const char *pParams, uint32_t bIsHost) +{ + TesterCommFileT *pInterfaceData; + uint32_t uControlDirSize; + uint32_t uTempPathFileSize; + uint32_t uInputPathFileSize; + uint32_t uOutputPathFileSize; + char strInputFile[256]; + char strOutputFile[256]; + char strControlDir[256]; + uint16_t aJson[512]; + + // check for error conditions + if ((pState == NULL) || (pParams == NULL) || (pState->pInterface->pData == NULL)) + { + ZPrintf("testercomm: connect got invalid NULL pointer - pState [0x%X] pParams [0x%X]\n", pState, pParams); + return(-1); + } + pInterfaceData = pState->pInterface->pData; + + // get the necessary startup parameters + JsonParse(aJson, sizeof(aJson)/sizeof(*aJson), pParams, -1); + ds_memclr(strInputFile, sizeof(strInputFile)); + ds_memclr(strOutputFile, sizeof(strOutputFile)); + ds_memclr(strControlDir, sizeof(strControlDir)); + JsonGetString(JsonFind(aJson, "INPUTFILE"), strInputFile, sizeof(strInputFile), ""); + JsonGetString(JsonFind(aJson, "OUTPUTFILE"), strOutputFile, sizeof(strOutputFile), ""); + JsonGetString(JsonFind(aJson, "CONTROLDIR"), strControlDir, sizeof(strControlDir), ""); + + // check for more error conditions + if ((strlen(strInputFile) == 0) || (strlen(strOutputFile) == 0)) + { + ZPrintf("testercomm: connect got invalid startup paths - INPUTFILE [%s] OUTPUTFILE [%s] CONTROLDIR [%s}\n", + strInputFile, strOutputFile, strControlDir); + return(-1); + } + + // wipe out the current stored strings + ds_memclr((void *)pInterfaceData->strControlDir, sizeof(pInterfaceData->strControlDir)); + ds_memclr((void *)pInterfaceData->strTempPathFile, sizeof(pInterfaceData->strTempPathFile)); + ds_memclr((void *)pInterfaceData->strInputPathFile, sizeof(pInterfaceData->strInputPathFile)); + ds_memclr((void *)pInterfaceData->strOutputPathFile, sizeof(pInterfaceData->strOutputPathFile)); + + // get the available string size + uControlDirSize = sizeof(pInterfaceData->strControlDir)-1; + uTempPathFileSize = sizeof(pInterfaceData->strTempPathFile)-1; + uInputPathFileSize = sizeof(pInterfaceData->strInputPathFile)-1; + uOutputPathFileSize = sizeof(pInterfaceData->strOutputPathFile)-1; + + // if we got nothing in for the control directory, use the default + if (strlen(strControlDir) == 0) + { + strncat(pInterfaceData->strControlDir, TESTERCOMM_PATH_DEFAULT, uControlDirSize); + strncat(pInterfaceData->strTempPathFile, TESTERCOMM_PATH_DEFAULT, uTempPathFileSize); + strncat(pInterfaceData->strInputPathFile, TESTERCOMM_PATH_DEFAULT, uInputPathFileSize); + strncat(pInterfaceData->strOutputPathFile, TESTERCOMM_PATH_DEFAULT, uOutputPathFileSize); + } + else + { + strncat(pInterfaceData->strControlDir, strControlDir, uControlDirSize); + strncat(pInterfaceData->strTempPathFile, strControlDir, uTempPathFileSize); + strncat(pInterfaceData->strInputPathFile, strControlDir, uInputPathFileSize); + strncat(pInterfaceData->strOutputPathFile, strControlDir, uOutputPathFileSize); + } + + // subtract off the amount of size we've used + uTempPathFileSize -= (uint32_t)strlen(pInterfaceData->strTempPathFile); + uInputPathFileSize -= (uint32_t)strlen(pInterfaceData->strInputPathFile); + uOutputPathFileSize -= (uint32_t)strlen(pInterfaceData->strOutputPathFile); + + // attach the paths + if (strlen(strControlDir) > 0) + { + strncat(pInterfaceData->strTempPathFile, "/", uTempPathFileSize--); + strncat(pInterfaceData->strInputPathFile, "/", uInputPathFileSize--); + strncat(pInterfaceData->strOutputPathFile, "/", uOutputPathFileSize--); + } + + // attach an extra char for the tempfile. + strncat(pInterfaceData->strTempPathFile, "_", uTempPathFileSize--); + + // attach the filename + strncat(pInterfaceData->strTempPathFile, strOutputFile, uTempPathFileSize); + strncat(pInterfaceData->strInputPathFile, strInputFile, uInputPathFileSize); + strncat(pInterfaceData->strOutputPathFile, strOutputFile, uOutputPathFileSize); + + // some debugging + ZPrintf("testercomm: connect ControlDir [%s]\n", pInterfaceData->strControlDir); + ZPrintf("testercomm: connect TempPathFile [%s]\n", pInterfaceData->strTempPathFile); + ZPrintf("testercomm: connect InputPathFile [%s]\n", pInterfaceData->strInputPathFile); + ZPrintf("testercomm: connect OutputPathFile [%s]\n", pInterfaceData->strOutputPathFile); + + // wipe out any existing output files at startup (remove any previous junk) + ZFileDelete(pInterfaceData->strTempPathFile); + ZFileDelete(pInterfaceData->strOutputPathFile); + + // dump anything from an incoming connection + ZFileDelete(pInterfaceData->strInputPathFile); + + // set the tick time so we don't automatically get a timeout + pState->uLastSendTime = ZTick(); + + // done for now + return(0); +} + + +/*F********************************************************************************/ +/*! + \Function _TesterCommUpdate + + \Description + Give the host/client interface module some processor time. Call this + once in a while to pump the input and output pipes. + + \Input *pState - module state + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/28/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommUpdate(TesterCommT *pState) +{ + int32_t iResult; + + if (pState == NULL) + return(-1); + + // quit if we are suspended (don't do any more commands) + if (pState->uSuspended) + return(0); + + // check for outgoing and incoming data + _TesterCommCheckOutput(pState); + _TesterCommCheckInput(pState); + + // now call the callbacks for incoming messages + iResult = ZListPopFront(pState->pInputData, &pState->LineData); + while(iResult > 0) + { + // try to access the message map + if (pState->MessageMap[pState->LineData.iType] != NULL) + { + // protect against recursion by suspending commands until this one completes + TesterCommSuspend(pState); + (pState->MessageMap[pState->LineData.iType])(pState, pState->LineData.strBuffer, pState->pMessageMapUserData[pState->LineData.iType]); + TesterCommWake(pState); + } + + // try to get the next chunk + iResult = ZListPopFront(pState->pInputData, &pState->LineData); + } + + // done + return(0); +} + + +/*F********************************************************************************/ +/*! + \Function _TesterCommDisconnect + + \Description + Disconnect the host client communication module. + + \Input *pState - pointer to host client comm module + + \Output int32_t - 0=success, error code otherwise + + \Version 03/23/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommDisconnect(TesterCommT *pState) +{ + TesterCommFileT *pData; + + // check for error conditions + if ((pState == NULL) || (pState->pInterface->pData == NULL)) + { + return(-1); + } + pData = pState->pInterface->pData; + + // wipe out the current strings + ds_memclr((void *)pData->strControlDir, sizeof(pData->strControlDir)); + ds_memclr((void *)pData->strTempPathFile, sizeof(pData->strTempPathFile)); + ds_memclr((void *)pData->strInputPathFile, sizeof(pData->strInputPathFile)); + ds_memclr((void *)pData->strOutputPathFile, sizeof(pData->strOutputPathFile)); + + // else return no error + return(0); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterCommAttachFile + + \Description + Attach file module function pointers to a tester comm module. + + \Input *pState - pointer to host client comm module + + \Output None + + \Version 05/02/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterCommAttachFile(TesterCommT *pState) +{ + if(pState == NULL) + return; + + ZPrintf("testercomm: attaching FILE interface methods\n"); + + pState->pInterface->CommConnectFunc = &_TesterCommConnect; + pState->pInterface->CommUpdateFunc = &_TesterCommUpdate; + pState->pInterface->CommDisconnectFunc = &_TesterCommDisconnect; + pState->pInterface->pData = (TesterCommFileT *)ZMemAlloc(sizeof(TesterCommFileT)); + ds_memclr(pState->pInterface->pData, sizeof(TesterCommFileT)); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/comm/testercomm_socket.c b/src/thirdparty/dirtysdk/sample/tester2/source/comm/testercomm_socket.c new file mode 100644 index 00000000..9e5ee050 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/comm/testercomm_socket.c @@ -0,0 +1,606 @@ +/*H********************************************************************************/ +/*! + \File testercomm_socket.c + + \Description + This module provides a communication layer between the host and the client + using socket I/O. Typical operations are SendLine() and GetLine(), which + send and receive lines of text, commands, debug output, etc. Each platform + may implement its own way of communicating - through files, debugger API + calls, etc. + + \Copyright + Copyright 2011 Electronic Arts Inc. + + \Version 10/17/2011 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/netconn.h" // for NetConnSleep() +#include "DirtySDK/util/base64.h" +#include "DirtySDK/util/jsonformat.h" +#include "DirtySDK/util/jsonparse.h" +#include "libsample/zmem.h" +#include "libsample/zlib.h" +#include "libsample/zlist.h" +#include "libsample/zfile.h" + +#include "testerprofile.h" +#include "testerregistry.h" +#include "testercomm.h" + +/*** Defines **********************************************************************/ + +#define T2COMM_PORT (3232) //!< communications port +#define TESTERCOMM_OUTPUTTIMEOUT_DEFAULT (60*1000) //!< timeout (ms) for a output message + +/*** Type Definitions *************************************************************/ + +//! module state +typedef struct TesterCommSocketT +{ + SocketT *pSocket; //!< comm socket + SocketT *pListen; //!< listen socket used by host + int32_t iPort; //!< port to bind to + + char strHostName[TESTERPROFILE_HOSTNAME_SIZEDEFAULT]; //!< host address to connect to + enum + { + ST_INIT, + ST_CONN, + ST_LIST, + ST_OPEN, + ST_FAIL + }eState; //!< module state + uint8_t bIsHost; //!< TRUE if host else FALSE + char strInputData[16*1024]; //!< input buffer +} TesterCommSocketT; + +/*** Private Function Prototypes **************************************************/ + +static int32_t _TesterCommDisconnect(TesterCommT *pState); +static int32_t _TesterCommCheckInput(TesterCommT *pState); + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _TesterCommConnect2 + + \Description + Connect the host client communication module. + + \Input *pSocketState - module state + + \Output + int32_t - 0=success, else failure + + \Version 10/17/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommConnect2(TesterCommSocketT *pSocketState) +{ + struct sockaddr SockAddr; + uint32_t uHostAddr; + int32_t iResult; + + // set up sockaddr + SockaddrInit(&SockAddr, AF_INET); + SockaddrInSetPort(&SockAddr, pSocketState->iPort); + + // if we have somewhere to connect to, do it + if (pSocketState->strHostName[0] != '\0') + { + // create the socket + if ((pSocketState->pSocket = SocketOpen(AF_INET, SOCK_STREAM, 0)) == NULL) + { + ZPrintf("testercomm_socket: cannot create socket\n"); + pSocketState->eState = ST_FAIL; + return(-1); + } + + // check for invalid addr + if ((uHostAddr = SocketInTextGetAddr(pSocketState->strHostName)) == 0) + { + ZPrintf("testercomm_socket: connect got invalid host address %s\n", pSocketState->strHostName); + pSocketState->eState = ST_FAIL; + return(-2); + } + + // initiate connection + SockaddrInSetAddr(&SockAddr, uHostAddr); + if ((iResult = SocketConnect(pSocketState->pSocket, &SockAddr, sizeof(SockAddr))) != SOCKERR_NONE) + { + ZPrintf("testercomm_socket: error %d initiating connection to %s\n", iResult, pSocketState->strHostName); + SocketClose(pSocketState->pSocket); + pSocketState->pSocket = NULL; + pSocketState->eState = ST_FAIL; + return(-3); + } + pSocketState->eState = ST_CONN; + ZPrintf("testercomm_socket: connect hostname=%a\n", uHostAddr); + } + else + { + struct sockaddr BindAddr; + + /* only create the listen socket the first time, if the peer disconnects we will + just go back into the listen state to accept the new connection */ + if (pSocketState->pListen == NULL) + { + // create the socket + if ((pSocketState->pListen = SocketOpen(AF_INET, SOCK_STREAM, 0)) == NULL) + { + ZPrintf("testercomm_socket: cannot create socket\n"); + pSocketState->eState = ST_FAIL; + return(-4); + } + + // set SO_LINGER to zero on listen socket so it doesn't linger and cause errors in testing + SocketControl(pSocketState->pListen, 'soli', 0, NULL, NULL); + // set to reuse addr incase it is still bound from previous session + SocketControl(pSocketState->pListen, 'radr', 1, NULL, NULL); + + // bind the socket + if ((iResult = SocketBind(pSocketState->pListen, &SockAddr, sizeof(SockAddr))) != SOCKERR_NONE) + { + ZPrintf("testercomm_socket: error %d binding to port %d\n", iResult, pSocketState->iPort); + SocketClose(pSocketState->pListen); + pSocketState->pListen = NULL; + pSocketState->eState = ST_FAIL; + ZPrintf("testercomm_socket: next bind will use a random port\n"); + pSocketState->iPort = 0; + return(-5); + } + // listen on the socket + if ((iResult = SocketListen(pSocketState->pListen, 2)) != SOCKERR_NONE) + { + ZPrintf("testercomm_socket: error %d listening on socket\n", iResult); + SocketClose(pSocketState->pListen); + pSocketState->pListen = NULL; + pSocketState->eState = ST_FAIL; + return(-6); + } + } + + pSocketState->eState = ST_LIST; + SocketInfo(pSocketState->pListen, 'bind', 0, &BindAddr, sizeof(BindAddr)); + ZPrintf("testercomm_socket: waiting for connection on port %d\n", SockaddrInGetPort(&BindAddr)); + } + + // done for now + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _TesterCommDisconnect2 + + \Description + Disconnect the host client communication module. + + \Input *pSocketState - pointer to host client comm module + + \Output + int32_t - 0=success, error code otherwise + + \Version 10/17/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommDisconnect2(TesterCommSocketT *pSocketState) +{ + ZPrintf("testercomm_socket: disconnect\n"); + // dispose of socket(s) + if (pSocketState->pSocket != NULL) + { + SocketClose(pSocketState->pSocket); + pSocketState->pSocket = NULL; + } + + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _TesterCommCheckConnect + + \Description + Check to see if a connection has been established + + \Input *pState - module state + + \Output + int32_t - 1=open, 0=connecting, negative=error + + \Version 10/17/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommCheckConnect(TesterCommT *pState) +{ + TesterCommSocketT *pSocketState = pState->pInterface->pData; + + // if already open nothing to do + if (pSocketState->eState == ST_OPEN) + { + return(1); + } + + if (pSocketState->eState == ST_LIST) + { + struct sockaddr RequestAddr; + int32_t iAddrLen = sizeof(RequestAddr); + + // accept incoming connection + SockaddrInit(&RequestAddr, AF_INET); + if ((pSocketState->pSocket = SocketAccept(pSocketState->pListen, &RequestAddr, &iAddrLen)) != NULL) + { + ZPrintf("testercomm_socket: accepted incoming connection request from %A\n", &RequestAddr); + pSocketState->eState = ST_OPEN; + } + } + else if (pSocketState->eState == ST_CONN) + { + int32_t iResult; + + // waiting for connection to be complete + if ((iResult = SocketInfo(pSocketState->pSocket, 'stat', 0, NULL, 0)) > 0) + { + ZPrintf("testercomm_socket: connection complete\n"); + pSocketState->eState = ST_OPEN; + } + else if (iResult < 0) + { + pSocketState->eState = ST_FAIL; + } + } + else if (pSocketState->eState == ST_FAIL) + { + _TesterCommDisconnect2(pSocketState); + + // dump all the messages + ZListClear(pState->pOutputData); + + // set that we haven't received any input + pState->bGotInput = FALSE; + + _TesterCommConnect2(pSocketState); + } + + if (pSocketState->eState == ST_OPEN) + { + // set the tick time so we don't automatically get a timeout + pState->uLastSendTime = ZTick(); + return(1); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _TesterCommCheckInput + + \Description + Check for data coming from the other side (host or client) and pull + any data into our internal buffer, if possible. + + \Input *pState - pointer to host client comm module + + \Output + int32_t - 0 = no data, >0 = data, error code otherwise + + \Version 10/17/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommCheckInput(TesterCommT *pState) +{ + TesterCommSocketT *pSocketState = pState->pInterface->pData; + int32_t iBytesRecv = 0, iLinesRecv, iResult; + const char *pInputLoop; + uint16_t *pJson; + + // don't do anything if we have already closed the socket + if (pSocketState->eState != ST_OPEN) + { + return(-1); + } + + // try to get as much data as possible + while ((iResult = SocketRecv(pSocketState->pSocket, pSocketState->strInputData+iBytesRecv, sizeof(pSocketState->strInputData)-iBytesRecv, 0)) != 0) + { + if (iResult > 0) + { + iBytesRecv += iResult; + pState->bGotInput = TRUE; + } + else if (iResult < 0) + { + // an error occurred + ZPrintf("testercomm_socket: error %d receiving data\n", iResult); + pSocketState->eState = ST_FAIL; + return(-1); + } + } + if (iBytesRecv == 0) + { + return(0); + } + + pJson = JsonParse2(pSocketState->strInputData, -1, 0, 0, NULL); + + // push each line onto the list + iLinesRecv = 0; + while ((pInputLoop = JsonFind2(pJson, NULL, "msg[", iLinesRecv)) != NULL) + { + char strBuffer[sizeof(pState->LineData.strBuffer)]; + int32_t iBufLen; + + ds_memclr(&pState->LineData, sizeof(pState->LineData)); + pState->LineData.iType = (int32_t)JsonGetInteger(JsonFind2(pJson, pInputLoop, ".TYPE", iLinesRecv), 0); + iBufLen = JsonGetString(JsonFind2(pJson, pInputLoop, ".TEXT", iLinesRecv), strBuffer, sizeof(strBuffer), ""); + Base64Decode3(strBuffer, iBufLen, pState->LineData.strBuffer, sizeof(pState->LineData.strBuffer)); + + // add to the back of the list; remember to add one for the terminator + ZListPushBack(pState->pInputData, &pState->LineData); + + iLinesRecv += 1; + } + + ZMemFree(pJson); + + // done - return how much we got from the file + return(iLinesRecv); +} + +/*F********************************************************************************/ +/*! + \Function _TesterCommCheckOutput + + \Description + Check and send data from the output buffer, if possible. + + \Input *pState - pointer to host client comm module + + \Output + int32_t - 0=success, error code otherwise + + \Version 10/17/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommCheckOutput(TesterCommT *pState) +{ + TesterCommSocketT *pSocketState = pState->pInterface->pData; + int32_t iLineLength, iBytesSent, iResult, iTickDiff; + char *pWrite, *pBuffer; + + // see if there's any data to send + if (ZListPeekFront(pState->pOutputData) == NULL) + { + // reset the last send time - we're not timing out because there's no data + pState->uLastSendTime = ZTick(); + return(0); + } + + // see if we've timed out + if ((iTickDiff = NetTickDiff(ZTick(), pState->uLastSendTime)) > TESTERCOMM_OUTPUTTIMEOUT_DEFAULT) + { + // timeout occurred - dump all pending messages and pop a warning + ZListClear(pState->pOutputData); + + ZPrintf("testercomm_socket: TIMEOUT in sending data (%dms). List cleared. Comms Lost?\n", iTickDiff); + pSocketState->eState = ST_FAIL; + } + + // push output queue stuff by sending commands + JsonInit(pState->strCommand, sizeof(pState->strCommand), 0); + JsonArrayStart(pState->strCommand, "msg"); + + while (ZListPeekFront(pState->pOutputData) != NULL) + { + char strBuffer[sizeof(pState->LineData.strBuffer)]; + + // snag the data into the local buffer + ZListPopFront(pState->pOutputData, &pState->LineData); + + // create the stuff to write + JsonObjectStart(pState->strCommand, NULL); + JsonAddInt(pState->strCommand, "TYPE", pState->LineData.iType); + Base64Encode2(pState->LineData.strBuffer, (int32_t)strlen(pState->LineData.strBuffer), strBuffer, sizeof(strBuffer)); + if ((iResult = JsonAddStr(pState->strCommand, "TEXT", strBuffer)) == JSON_ERR_FULL) + { + // Sometimes, the text we get in will be too big. Make sure this is an easy error to diagnose. + ZPrintf("testercomm_socket: text too large to send in TESTERCOMM_COMMANDSIZE_DEFAULT. Discarding.\n"); + } + JsonObjectEnd(pState->strCommand); + } + JsonArrayEnd(pState->strCommand); + pBuffer = JsonFinish(pState->strCommand); + iLineLength = (int32_t)strlen(pBuffer); + + // send it + for (pWrite = pBuffer, iBytesSent = 0; iLineLength != 0; ) + { + if ((iResult = SocketSend(pSocketState->pSocket, pWrite, iLineLength, 0)) < 0) + { + ZPrintf("testercomm_socket: error %d sending command to remote target\n", iResult); + pSocketState->eState = ST_FAIL; + return(-1); + } + else + { + iLineLength -= iResult; + pWrite += iResult; + iBytesSent += iResult; + } + + if (iLineLength != 0) + { + NetConnSleep(5); + } + } + + // mark the last send time + pState->uLastSendTime = ZTick(); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _TesterCommConnect + + \Description + Connect the host client communication module. + + \Input *pState - pointer to host client comm module + \Input *pParams - startup parameters + \Input bIsHost - TRUE=host, FALSE=client + + \Output + int32_t - 0 for success, error code otherwise + + \Version 10/17/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommConnect(TesterCommT *pState, const char *pParams, uint32_t bIsHost) +{ + TesterCommSocketT *pSocketState = pState->pInterface->pData; + char *pUserPort; + uint16_t aJson[512]; + + // remember host status + pSocketState->bIsHost = (uint8_t)bIsHost; + pSocketState->iPort = T2COMM_PORT; + + // get startup parameters + JsonParse(aJson, sizeof(aJson)/sizeof(*aJson), pParams, -1); + JsonGetString(JsonFind(aJson, "HOSTNAME"), pSocketState->strHostName, sizeof(pSocketState->strHostName), ""); + + // check for a user specified port + if ((pUserPort = strchr(pSocketState->strHostName, ':')) != NULL ) + { + pSocketState->iPort = atoi(pUserPort+1); + } + + // do the connect + return(_TesterCommConnect2(pSocketState)); +} + +/*F********************************************************************************/ +/*! + \Function _TesterCommUpdate + + \Description + Give the host/client interface module some processor time. Call this + once in a while to pump the input and output pipes. + + \Input *pState - module state + + \Output + int32_t - 0 for success, error code otherwise + + \Version 10/17/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommUpdate(TesterCommT *pState) +{ + int32_t iResult; + + // quit if we are suspended (don't do any more commands) + if (pState->uSuspended) + { + return(0); + } + + // check for accepted connection + if (_TesterCommCheckConnect(pState) <= 0) + { + return(0); + } + + // check for outgoing and incoming data + _TesterCommCheckInput(pState); + _TesterCommCheckOutput(pState); + + // now call the callbacks for incoming messages + while ((iResult = ZListPopFront(pState->pInputData, &pState->LineData)) > 0) + { + // try to access the message map + if ((pState->LineData.iType >= 0) && (pState->MessageMap[pState->LineData.iType] != NULL)) + { + // protect against recursion by suspending commands until this one completes + TesterCommSuspend(pState); + (pState->MessageMap[pState->LineData.iType])(pState, pState->LineData.strBuffer, pState->pMessageMapUserData[pState->LineData.iType]); + TesterCommWake(pState); + } + } + + // done + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _TesterCommDisconnect + + \Description + Disconnect the host client communication module. + + \Input *pState - pointer to host client comm module + + \Output + int32_t - 0=success, error code otherwise + + \Version 10/17/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _TesterCommDisconnect(TesterCommT *pState) +{ + TesterCommSocketT *pSocketState = (TesterCommSocketT *)pState->pInterface->pData; + + _TesterCommDisconnect2(pSocketState); + if (pSocketState->pListen != NULL) + { + SocketClose(pSocketState->pListen); + pSocketState->pListen = NULL; + } + + return(0); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterCommAttachSocket + + \Description + Attach file module function pointers to a tester comm module. + + \Input *pState - pointer to host client comm module + + \Version 10/17/2011 (jbrookes) +*/ +/********************************************************************************F*/ +void TesterCommAttachSocket(TesterCommT *pState) +{ + ZPrintf("testercomm_socket: attaching socket interface methods\n"); + pState->pInterface->CommConnectFunc = &_TesterCommConnect; + pState->pInterface->CommUpdateFunc = &_TesterCommUpdate; + pState->pInterface->CommDisconnectFunc = &_TesterCommDisconnect; + pState->pInterface->pData = (TesterCommSocketT *)ZMemAlloc(sizeof(TesterCommSocketT)); + ds_memclr(pState->pInterface->pData, sizeof(TesterCommSocketT)); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/advert.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/advert.c new file mode 100644 index 00000000..fec72dd6 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/advert.c @@ -0,0 +1,156 @@ +/*H********************************************************************************/ +/*! + \File advert.c + + \Description + Test the ProtoAdvt module. + + \Copyright + Copyright (c) Electronic Arts 2003-2005. ALL RIGHTS RESERVED. + + \Version 03/28/2003 (gschaefer) First Version +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/proto/protoadvt.h" + +#include "libsample/zlib.h" + +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +#define WATCH_BUFFER 4096 + +/*** Type Definitions *************************************************************/ + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Private variables + +static ProtoAdvtRef *_Advert_pAdvert = NULL; + +// Public variables + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdAdvert + + \Description + Handle advertisement watcher + + \Input *argz - + \Input argc - + \Input **argv - + + \Output int32_t - + + \Version 1.0 02/17/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t CmdAdvertWatch(ZContext *argz, int32_t argc, char **argv) +{ + char *pBuffer = (char *)argz; + char strBuffer[WATCH_BUFFER]; + + // get list of advertisements + ProtoAdvtQuery(_Advert_pAdvert, "TESTER", "", strBuffer, sizeof(strBuffer), 0); + if (strcmp(pBuffer, strBuffer) != 0) + { + ZPrintf("Advertising list:\n"); + ZPrintf("%s", strBuffer); + ZPrintf("...end of list...\n"); + strcpy(pBuffer, strBuffer); + } + + // keep running + return(ZCallback(&CmdAdvertWatch, 500)); +} + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdAdvert + + \Description + Exercise the advertising module. + + \Input *argz - + \Input argc - + \Input **argv - + + \Output int32_t - + + \Version 1.0 02/17/2003 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdAdvert(ZContext *argz, int32_t argc, char **argv) +{ + int32_t iCount; + char strBuffer[4096]; + + // handle help + if (argc == 0) + { + ZPrintf("%s list|add|del\n", argv[0]); + return(0); + } + + // startup advertising if needed + if (_Advert_pAdvert == NULL) + { + _Advert_pAdvert = ProtoAdvtConstruct(16); + } + + // handle listing advertisements + if ((argc == 2) && (strcmp(argv[1], "list") == 0)) + { + iCount = ProtoAdvtQuery(_Advert_pAdvert, "TESTER", "", strBuffer, sizeof(strBuffer), 0); + if (iCount > 0) + { + ZPrintf("%s", strBuffer); + } + } + + // constant watch + if ((argc == 2) && (strcmp(argv[1], "watch") == 0)) + { + char *pBuffer = (char *)ZContextCreate(WATCH_BUFFER); + pBuffer[0] = 0; + return(ZCallback(&CmdAdvertWatch, 100)); + } + + // add an advertisement + if ((argc > 2) && (strcmp(argv[1], "add") == 0)) + { + ProtoAdvtAnnounce(_Advert_pAdvert, "TESTER", argv[2], "", "TCP:~1:1024\tUDP:~1:1024", 0); + } + + // delete an advertisement + if ((argc > 2) && (strcmp(argv[1], "del") == 0)) + { + ProtoAdvtCancel(_Advert_pAdvert, "TESTER", argv[2]); + } + + return(0); +} + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/base64.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/base64.c new file mode 100644 index 00000000..6e351a43 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/base64.c @@ -0,0 +1,106 @@ +/*H********************************************************************************/ +/*! + \File base64.c + + \Description + Test the Base64 encoder and decoder + + \Copyright + Copyright (c) 2018 Electronic Arts Inc. + + \Version 12/16/2018 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/util/base64.h" +#include "testermodules.h" + +#include "libsample/zlib.h" +#include "libsample/zfile.h" +#include "libsample/zmem.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +// Variables + +/*** Private Functions ************************************************************/ + + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function CmdBase64 + + \Description + Test the Json formatter and parser + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output standard return value + + \Version 12/16/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdBase64(ZContext *argz, int32_t argc, char *argv[]) +{ + // check for a file argument... if we have one, load and parse it + if ((argc == 4) && !ds_stricmp(argv[1], "decode")) + { + char *pFileData, *pFileDecoded; + ZFileT iOutFileId; + int32_t iFileSize; + + if ((pFileData = ZFileLoad(argv[2], &iFileSize, FALSE)) != NULL) + { + int32_t iDecodedSize = Base64DecodedSize(iFileSize); + if ((pFileDecoded = ZMemAlloc(iDecodedSize)) != NULL) + { + if (Base64Decode3(pFileData, iFileSize, pFileDecoded, iDecodedSize) > 0) + { + if ((iOutFileId = ZFileOpen(argv[3], ZFILE_OPENFLAG_WRONLY|ZFILE_OPENFLAG_BINARY|ZFILE_OPENFLAG_CREATE)) != ZFILE_INVALID) + { + ZFileWrite(iOutFileId, pFileDecoded, iDecodedSize); + ZFileClose(iOutFileId); + ZPrintf("%s: decoded %d bytes\n", argv[0], iDecodedSize); + } + else + { + ZPrintf("%s: could not open file '%s' for writing", argv[0], argv[3]); + } + } + else + { + ZPrintf("%s: could not decode file '%s'\n", argv[0], argv[2]); + } + ZMemFree(pFileDecoded); + } + else + { + ZPrintf("%s: could not allocate %d bytes for base64 decode\n", argv[0], iDecodedSize); + } + ZMemFree(pFileData); + } + else + { + ZPrintf("%s: could not open file '%s' for reading\n", argv[0], argv[2]); + } + } + + return(0); +} + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/crypt.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/crypt.c new file mode 100644 index 00000000..d0658aa6 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/crypt.c @@ -0,0 +1,2179 @@ +/*H********************************************************************************/ +/*! + \File crypt.c + + \Description + Test the crypto modules. + + \Copyright + Copyright (c) 2013 Electronic Arts Inc. + + \Version 01/11/2013 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/crypt/cryptaes.h" +#include "DirtySDK/crypt/cryptarc4.h" +#include "DirtySDK/crypt/cryptchacha.h" +#include "DirtySDK/crypt/cryptcurve.h" +#include "DirtySDK/crypt/cryptgcm.h" +#include "DirtySDK/crypt/cryptmont.h" +#include "DirtySDK/crypt/cryptnist.h" +#include "DirtySDK/crypt/cryptrand.h" +#include "DirtySDK/crypt/cryptrsa.h" +#include "DirtySDK/crypt/cryptsha2.h" +#include "DirtySDK/crypt/crypthash.h" +#include "DirtySDK/crypt/crypthmac.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/util/base64.h" +#include "DirtySDK/util/murmurhash3.h" + +#include "testermodules.h" + +#include "libsample/zlib.h" +#include "libsample/zmem.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! type to encapsulate gcm test data +typedef struct CryptTestGcmT +{ + const uint8_t aKey[32]; + int32_t iKeySize; + const uint8_t aInitVec[12]; + const uint8_t aInput[256]; + int32_t iInputSize; + const uint8_t aData[256]; + int32_t iDataSize; + const uint8_t aOutput[256]; + int32_t iOutputSize; + const uint8_t aTagRslt[16]; +} CryptTestGcmT; + +//! type to encapsulate sha2 test data +typedef struct CryptTestSha2 +{ + const char *pString; + const uint8_t strHashRslt[4][CRYPTHASH_MAXDIGEST]; +} CryptTestSha2; + +//! type to encapsulate hmac test params +typedef struct CryptTestHmacParam +{ + uint8_t uKeyLen; + uint8_t uInpLen; + uint8_t uOutLen; + const uint8_t strRslt[CRYPTHASH_MAXDIGEST]; +} CryptTestHmacParam; + +//! type to encapsulate hmac test data +typedef struct CryptTestHmac +{ + const char *pKey; + const char *pString; + const CryptTestHmacParam Param[CRYPTHASH_NUMHASHES]; +} CryptTestHmac; + +typedef struct CryptTestDsaCaseT +{ + const uint8_t aMessage[16]; + CryptHashTypeE eHashType; + const char strCaseName[16]; + const uint8_t aSignature[97]; +} CryptTestDsaCaseT; + +//! type to encapsulate dsa test data +typedef struct CryptTestDsaT +{ + CryptCurveE eCurveType; + int32_t iCurveSize; + const char strTestName[16]; + const uint8_t aPrivateKey[48]; + const uint8_t aPublicKey[97]; + const CryptTestDsaCaseT aCases[10]; +} CryptTestDsaT; + +/*** Variables ********************************************************************/ + +//! gcm test data from the FIPS standard +static const CryptTestGcmT _Crypt_GcmTest[] = +{ + // Test cases 0-3 use 128 bit keys + + // Test Case 0: validate tag result with zero key and no data + { + { 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }, // key + 16, // key size + { 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }, // iv + { 0 }, 0, // input, input size + { 0 }, 0, // data, data size + { 0 }, 0, // output, output size + { 0x58,0xe2,0xfc,0xce, 0xfa,0x7e,0x30,0x61, 0x36,0x7f,0x1d,0x57, 0xa4,0xe7,0x45,0x5a } // tag result + }, + // Test Case 1: validate encryption and tag result with zero key and zero data + { + { 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }, // key + 16, // key size + { 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }, // iv + { 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }, // input + 16, // input size + { 0 }, 0, // data, data size + { 0x03,0x88,0xda,0xce, 0x60,0xb6,0xa3,0x92, 0xf3,0x28,0xc2,0xb9, 0x71,0xb2,0xfe,0x78 }, // output + 16, // output size + { 0xab,0x6e,0x47,0xd4, 0x2c,0xec,0x13,0xbd, 0xf5,0x3a,0x67,0xb2, 0x12,0x57,0xbd,0xdf } // tag result + }, + // Test Case 2: validate encryption and tag result with non-zero key and data + { + { 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08 }, // key + 16, // key size + { 0xca,0xfe,0xba,0xbe, 0xfa,0xce,0xdb,0xad, 0xde,0xca,0xf8,0x88 }, // iv + { + 0xd9,0x31,0x32,0x25, 0xf8,0x84,0x06,0xe5, 0xa5,0x59,0x09,0xc5, 0xaf,0xf5,0x26,0x9a, + 0x86,0xa7,0xa9,0x53, 0x15,0x34,0xf7,0xda, 0x2e,0x4c,0x30,0x3d, 0x8a,0x31,0x8a,0x72, + 0x1c,0x3c,0x0c,0x95, 0x95,0x68,0x09,0x53, 0x2f,0xcf,0x0e,0x24, 0x49,0xa6,0xb5,0x25, + 0xb1,0x6a,0xed,0xf5, 0xaa,0x0d,0xe6,0x57, 0xba,0x63,0x7b,0x39, 0x1a,0xaf,0xd2,0x55 + }, 64, // input, input size + { 0 }, 0, // data, data size + { + 0x42,0x83,0x1e,0xc2, 0x21,0x77,0x74,0x24, 0x4b,0x72,0x21,0xb7, 0x84,0xd0,0xd4,0x9c, + 0xe3,0xaa,0x21,0x2f, 0x2c,0x02,0xa4,0xe0, 0x35,0xc1,0x7e,0x23, 0x29,0xac,0xa1,0x2e, + 0x21,0xd5,0x14,0xb2, 0x54,0x66,0x93,0x1c, 0x7d,0x8f,0x6a,0x5a, 0xac,0x84,0xaa,0x05, + 0x1b,0xa3,0x0b,0x39, 0x6a,0x0a,0xac,0x97, 0x3d,0x58,0xe0,0x91, 0x47,0x3f,0x59,0x85 + }, 64, // output, output size + { 0x4d,0x5c,0x2a,0xf3, 0x27,0xcd,0x64,0xa6, 0x2c,0xf3,0x5a,0xbd, 0x2b,0xa6,0xfa,0xb4 } // tag result + }, + // Test Case 3: validate encryption and tag result with non-zero key, un-aligned data, and additional data + { + { 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08 }, // key + 16, // key size + { 0xca,0xfe,0xba,0xbe, 0xfa,0xce,0xdb,0xad, 0xde,0xca,0xf8,0x88 }, // iv + { + 0xd9,0x31,0x32,0x25, 0xf8,0x84,0x06,0xe5, 0xa5,0x59,0x09,0xc5, 0xaf,0xf5,0x26,0x9a, + 0x86,0xa7,0xa9,0x53, 0x15,0x34,0xf7,0xda, 0x2e,0x4c,0x30,0x3d, 0x8a,0x31,0x8a,0x72, + 0x1c,0x3c,0x0c,0x95, 0x95,0x68,0x09,0x53, 0x2f,0xcf,0x0e,0x24, 0x49,0xa6,0xb5,0x25, + 0xb1,0x6a,0xed,0xf5, 0xaa,0x0d,0xe6,0x57, 0xba,0x63,0x7b,0x39 + }, 60, // input, input size + { + 0xfe,0xed,0xfa,0xce, 0xde,0xad,0xbe,0xef, 0xfe,0xed,0xfa,0xce, 0xde,0xad,0xbe,0xef, + 0xab,0xad,0xda,0xd2 + }, 20, // data, data size + { // output + 0x42,0x83,0x1e,0xc2, 0x21,0x77,0x74,0x24, 0x4b,0x72,0x21,0xb7, 0x84,0xd0,0xd4,0x9c, + 0xe3,0xaa,0x21,0x2f, 0x2c,0x02,0xa4,0xe0, 0x35,0xc1,0x7e,0x23, 0x29,0xac,0xa1,0x2e, + 0x21,0xd5,0x14,0xb2, 0x54,0x66,0x93,0x1c, 0x7d,0x8f,0x6a,0x5a, 0xac,0x84,0xaa,0x05, + 0x1b,0xa3,0x0b,0x39, 0x6a,0x0a,0xac,0x97, 0x3d,0x58,0xe0,0x91 + }, + 60, // output size + { 0x5b,0xc9,0x4f,0xbc, 0x32,0x21,0xa5,0xdb, 0x94,0xfa,0xe9,0x5a, 0xe7,0x12,0x1a,0x47 } // tag result + }, + + // Test cases 4-7 use 256 bit keys + + // Test Case 4: validate tag result with zero key and no data + { + { 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, // key + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }, + 32, // key size + { 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }, // iv + { 0 }, 0, // input, input size + { 0 }, 0, // data, data size + { 0 }, 0, // output, output size + { 0x53,0x0f,0x8a,0xfb, 0xc7,0x45,0x36,0xb9, 0xa9,0x63,0xb4,0xf1, 0xc4,0xcb,0x73,0x8b } // tag result + }, + // Test Case 5: validate encryption and tag result with zero key and zero data + { + { 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, // key + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }, + 32, // key size + { 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }, // iv + { 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }, // input + 16, // input size + { 0 }, 0, // data, data size + { 0xce,0xa7,0x40,0x3d, 0x4d,0x60,0x6b,0x6e, 0x07,0x4e,0xc5,0xd3, 0xba,0xf3,0x9d,0x18 }, // output + 16, // output size + { 0xd0,0xd1,0xc8,0xa7, 0x99,0x99,0x6b,0xf0, 0x26,0x5b,0x98,0xb5, 0xd4,0x8a,0xb9,0x19 } // tag result + }, + // Test Case 6: validate encryption and tag result with non-zero key and data + { + { 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08, // key + 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08 }, + 32, // key size + { 0xca,0xfe,0xba,0xbe, 0xfa,0xce,0xdb,0xad, 0xde,0xca,0xf8,0x88 }, // iv + { + 0xd9,0x31,0x32,0x25, 0xf8,0x84,0x06,0xe5, 0xa5,0x59,0x09,0xc5, 0xaf,0xf5,0x26,0x9a, + 0x86,0xa7,0xa9,0x53, 0x15,0x34,0xf7,0xda, 0x2e,0x4c,0x30,0x3d, 0x8a,0x31,0x8a,0x72, + 0x1c,0x3c,0x0c,0x95, 0x95,0x68,0x09,0x53, 0x2f,0xcf,0x0e,0x24, 0x49,0xa6,0xb5,0x25, + 0xb1,0x6a,0xed,0xf5, 0xaa,0x0d,0xe6,0x57, 0xba,0x63,0x7b,0x39, 0x1a,0xaf,0xd2,0x55 + }, 64, // input, input size + { 0 }, 0, // data, data size + { + 0x52,0x2d,0xc1,0xf0, 0x99,0x56,0x7d,0x07, 0xf4,0x7f,0x37,0xa3, 0x2a,0x84,0x42,0x7d, + 0x64,0x3a,0x8c,0xdc, 0xbf,0xe5,0xc0,0xc9, 0x75,0x98,0xa2,0xbd, 0x25,0x55,0xd1,0xaa, + 0x8c,0xb0,0x8e,0x48, 0x59,0x0d,0xbb,0x3d, 0xa7,0xb0,0x8b,0x10, 0x56,0x82,0x88,0x38, + 0xc5,0xf6,0x1e,0x63, 0x93,0xba,0x7a,0x0a, 0xbc,0xc9,0xf6,0x62, 0x89,0x80,0x15,0xad, + }, 64, // output, output size + { 0xb0,0x94,0xda,0xc5, 0xd9,0x34,0x71,0xbd, 0xec,0x1a,0x50,0x22, 0x70,0xe3,0xcc,0x6c } // tag result + }, + // Test Case 3: validate encryption and tag result with non-zero key, un-aligned data, and additional data + { + { 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08, // key + 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08 }, + 32, // key size + { 0xca,0xfe,0xba,0xbe, 0xfa,0xce,0xdb,0xad, 0xde,0xca,0xf8,0x88 }, // iv + { + 0xd9,0x31,0x32,0x25, 0xf8,0x84,0x06,0xe5, 0xa5,0x59,0x09,0xc5, 0xaf,0xf5,0x26,0x9a, + 0x86,0xa7,0xa9,0x53, 0x15,0x34,0xf7,0xda, 0x2e,0x4c,0x30,0x3d, 0x8a,0x31,0x8a,0x72, + 0x1c,0x3c,0x0c,0x95, 0x95,0x68,0x09,0x53, 0x2f,0xcf,0x0e,0x24, 0x49,0xa6,0xb5,0x25, + 0xb1,0x6a,0xed,0xf5, 0xaa,0x0d,0xe6,0x57, 0xba,0x63,0x7b,0x39 + }, 60, // input, input size + { + 0xfe,0xed,0xfa,0xce, 0xde,0xad,0xbe,0xef, 0xfe,0xed,0xfa,0xce, 0xde,0xad,0xbe,0xef, + 0xab,0xad,0xda,0xd2 + }, 20, // data, data size + { // output + 0x52,0x2d,0xc1,0xf0, 0x99,0x56,0x7d,0x07, 0xf4,0x7f,0x37,0xa3, 0x2a,0x84,0x42,0x7d, + 0x64,0x3a,0x8c,0xdc, 0xbf,0xe5,0xc0,0xc9, 0x75,0x98,0xa2,0xbd, 0x25,0x55,0xd1,0xaa, + 0x8c,0xb0,0x8e,0x48, 0x59,0x0d,0xbb,0x3d, 0xa7,0xb0,0x8b,0x10, 0x56,0x82,0x88,0x38, + 0xc5,0xf6,0x1e,0x63, 0x93,0xba,0x7a,0x0a, 0xbc,0xc9,0xf6,0x62, + }, + 60, // output size + { 0x76,0xfc,0x6e,0xce, 0x0f,0x4e,0x17,0x68, 0xcd,0xdf,0x88,0x53, 0xbb,0x2d,0x55,0x1b } // tag result + }, +}; + +//! sha2 test data +static const CryptTestSha2 _Crypt_Sha2Test[] = +{ + { + "", + { + { // sha224 + 0xd1,0x4a,0x02,0x8c, 0x2a,0x3a,0x2b,0xc9, 0x47,0x61,0x02,0xbb, 0x28,0x82,0x34,0xc4, + 0x15,0xa2,0xb0,0x1f, 0x82,0x8e,0xa6,0x2a, 0xc5,0xb3,0xe4,0x2f + }, + { // sha256 + 0xe3,0xb0,0xc4,0x42, 0x98,0xfc,0x1c,0x14, 0x9a,0xfb,0xf4,0xc8, 0x99,0x6f,0xb9,0x24, + 0x27,0xae,0x41,0xe4, 0x64,0x9b,0x93,0x4c, 0xa4,0x95,0x99,0x1b, 0x78,0x52,0xb8,0x55 + }, + { // sha384 + 0x38,0xb0,0x60,0xa7, 0x51,0xac,0x96,0x38, 0x4c,0xd9,0x32,0x7e, 0xb1,0xb1,0xe3,0x6a, + 0x21,0xfd,0xb7,0x11, 0x14,0xbe,0x07,0x43, 0x4c,0x0c,0xc7,0xbf, 0x63,0xf6,0xe1,0xda, + 0x27,0x4e,0xde,0xbf, 0xe7,0x6f,0x65,0xfb, 0xd5,0x1a,0xd2,0xf1, 0x48,0x98,0xb9,0x5b + }, + { // sha512 + 0xcf,0x83,0xe1,0x35, 0x7e,0xef,0xb8,0xbd, 0xf1,0x54,0x28,0x50, 0xd6,0x6d,0x80,0x07, + 0xd6,0x20,0xe4,0x05, 0x0b,0x57,0x15,0xdc, 0x83,0xf4,0xa9,0x21, 0xd3,0x6c,0xe9,0xce, + 0x47,0xd0,0xd1,0x3c, 0x5d,0x85,0xf2,0xb0, 0xff,0x83,0x18,0xd2, 0x87,0x7e,0xec,0x2f, + 0x63,0xb9,0x31,0xbd, 0x47,0x41,0x7a,0x81, 0xa5,0x38,0x32,0x7a, 0xf9,0x27,0xda,0x3e + } + } + }, + { + "The quick brown fox jumps over the lazy dog", + { + { // sha224 + 0x73,0x0e,0x10,0x9b, 0xd7,0xa8,0xa3,0x2b, 0x1c,0xb9,0xd9,0xa0, 0x9a,0xa2,0x32,0x5d, + 0x24,0x30,0x58,0x7d, 0xdb,0xc0,0xc3,0x8b, 0xad,0x91,0x15,0x25 + }, + { // sha256 + 0xd7,0xa8,0xfb,0xb3, 0x07,0xd7,0x80,0x94, 0x69,0xca,0x9a,0xbc, 0xb0,0x08,0x2e,0x4f, + 0x8d,0x56,0x51,0xe4, 0x6d,0x3c,0xdb,0x76, 0x2d,0x02,0xd0,0xbf, 0x37,0xc9,0xe5,0x92 + }, + { // sha384 + 0xca,0x73,0x7f,0x10, 0x14,0xa4,0x8f,0x4c, 0x0b,0x6d,0xd4,0x3c, 0xb1,0x77,0xb0,0xaf, + 0xd9,0xe5,0x16,0x93, 0x67,0x54,0x4c,0x49, 0x40,0x11,0xe3,0x31, 0x7d,0xbf,0x9a,0x50, + 0x9c,0xb1,0xe5,0xdc, 0x1e,0x85,0xa9,0x41, 0xbb,0xee,0x3d,0x7f, 0x2a,0xfb,0xc9,0xb1 + }, + { // sha512 + 0x07,0xe5,0x47,0xd9, 0x58,0x6f,0x6a,0x73, 0xf7,0x3f,0xba,0xc0, 0x43,0x5e,0xd7,0x69, + 0x51,0x21,0x8f,0xb7, 0xd0,0xc8,0xd7,0x88, 0xa3,0x09,0xd7,0x85, 0x43,0x6b,0xbb,0x64, + 0x2e,0x93,0xa2,0x52, 0xa9,0x54,0xf2,0x39, 0x12,0x54,0x7d,0x1e, 0x8a,0x3b,0x5e,0xd6, + 0xe1,0xbf,0xd7,0x09, 0x78,0x21,0x23,0x3f, 0xa0,0x53,0x8f,0x3d, 0xb8,0x54,0xfe,0xe6 + } + } + }, + { + "The quick brown fox jumps over the lazy dog.", + { + { // sha224 + 0x61,0x9c,0xba,0x8e, 0x8e,0x05,0x82,0x6e, 0x9b,0x8c,0x51,0x9c, 0x0a,0x5c,0x68,0xf4, + 0xfb,0x65,0x3e,0x8a, 0x3d,0x8a,0xa0,0x4b, 0xb2,0xc8,0xcd,0x4c + }, + { // sha256 + 0xef,0x53,0x7f,0x25, 0xc8,0x95,0xbf,0xa7, 0x82,0x52,0x65,0x29, 0xa9,0xb6,0x3d,0x97, + 0xaa,0x63,0x15,0x64, 0xd5,0xd7,0x89,0xc2, 0xb7,0x65,0x44,0x8c, 0x86,0x35,0xfb,0x6c + }, + { // sha384 + 0xed,0x89,0x24,0x81, 0xd8,0x27,0x2c,0xa6, 0xdf,0x37,0x0b,0xf7, 0x06,0xe4,0xd7,0xbc, + 0x1b,0x57,0x39,0xfa, 0x21,0x77,0xaa,0xe6, 0xc5,0x0e,0x94,0x66, 0x78,0x71,0x8f,0xc6, + 0x7a,0x7a,0xf2,0x81, 0x9a,0x02,0x1c,0x2f, 0xc3,0x4e,0x91,0xbd, 0xb6,0x34,0x09,0xd7 + }, + { // sha512 + 0x91,0xea,0x12,0x45, 0xf2,0x0d,0x46,0xae, 0x9a,0x03,0x7a,0x98, 0x9f,0x54,0xf1,0xf7, + 0x90,0xf0,0xa4,0x76, 0x07,0xee,0xb8,0xa1, 0x4d,0x12,0x89,0x0c, 0xea,0x77,0xa1,0xbb, + 0xc6,0xc7,0xed,0x9c, 0xf2,0x05,0xe6,0x7b, 0x7f,0x2b,0x8f,0xd4, 0xc7,0xdf,0xd3,0xa7, + 0xa8,0x61,0x7e,0x45, 0xf3,0xc4,0x63,0xd4, 0x81,0xc7,0xe5,0x86, 0xc3,0x9a,0xc1,0xed + } + } + }, + // test to validate sha384/sha512 128-bit length pad is correct + { + "The quick brown fox jumps over the lazy dog...\r\n" + "The quick brown fox jumps over the lazy dog...\r\n" + "The quick brown fox j\r\n", + { + { // sha224 + 0x6b,0x7c,0x3d,0xfb, 0xe4,0x3d,0x5c,0x0a, 0x9d,0xe5,0xa0,0x9e, 0x2c,0x90,0xc1,0x62, + 0xa5,0x1c,0xb3,0x32, 0x99,0x9e,0x88,0xce, 0xdb,0x5f,0xbc,0x57 + }, + { // sha256 + 0x0d,0x4c,0xaf,0x0a, 0x31,0x1c,0xb6,0x26, 0xdf,0x91,0x02,0xc2, 0x01,0xc1,0x8a,0x12, + 0x8a,0x0b,0x4b,0x4d, 0x1d,0xfa,0xa3,0xa9, 0xd9,0x43,0xc6,0x47, 0xd3,0x92,0xb6,0x54 + }, + { // sha384 + 0xed,0xae,0xd3,0xa8, 0xc6,0x3b,0x44,0x63, 0xb9,0xd6,0x34,0x8b, 0x86,0xda,0x9f,0xaf, + 0x69,0xe9,0x91,0x48, 0xe6,0xef,0xfc,0x71, 0x8a,0xf9,0x64,0x1e, 0x46,0x69,0x05,0x97, + 0x32,0xb8,0x0f,0xc8, 0x0b,0x84,0x39,0x7f, 0x58,0x3e,0xc3,0x50, 0x2b,0xa4,0x91,0xd8 + }, + { // sha512 + 0xb6,0x39,0x3a,0x1a, 0x27,0x4d,0x62,0xe3, 0x6c,0x9b,0xbf,0x83, 0xc4,0x39,0x68,0xcd, + 0x7b,0xaa,0x36,0x04, 0xb7,0x05,0xea,0x28, 0xd8,0x43,0x23,0x20, 0xe1,0xf5,0x29,0x6b, + 0xc1,0x93,0x6c,0xcb, 0x08,0x4e,0xad,0xfe, 0xd8,0xc9,0x38,0xd2, 0x91,0x9c,0x47,0xbd, + 0x04,0xdc,0xa1,0x3c, 0x66,0x7b,0xaf,0x47, 0xbd,0xc7,0xa4,0xfd, 0xa8,0xa2,0xc0,0xde + } + } + }, + { + "0123456701234567012345670123456701234567012345670123456701234567" + "0123456701234567012345670123456701234567012345670123456701234567" + "0123456701234567012345670123456701234567012345670123456701234567" + "0123456701234567012345670123456701234567012345670123456701234567" + "0123456701234567012345670123456701234567012345670123456701234567" + "0123456701234567012345670123456701234567012345670123456701234567" + "0123456701234567012345670123456701234567012345670123456701234567" + "0123456701234567012345670123456701234567012345670123456701234567" + "0123456701234567012345670123456701234567012345670123456701234567" + "0123456701234567012345670123456701234567012345670123456701234567", + { + { // sha224 + 0x56,0x7F,0x69,0xF1, 0x68,0xCD,0x78,0x44, 0xE6,0x52,0x59,0xCE, 0x65,0x8F,0xE7,0xAA, + 0xDF,0xA2,0x52,0x16, 0xE6,0x8E,0xCA,0x0E, 0xB7,0xAB,0x82,0x62 + }, + { // sha256 + 0x59,0x48,0x47,0x32, 0x84,0x51,0xBD,0xFA, 0x85,0x05,0x62,0x25, 0x46,0x2C,0xC1,0xD8, + 0x67,0xD8,0x77,0xFB, 0x38,0x8D,0xF0,0xCE, 0x35,0xF2,0x5A,0xB5, 0x56,0x2B,0xFB,0xB5 + }, + { // sha384 + 0x2F,0xC6,0x4A,0x4F, 0x50,0x0D,0xDB,0x68, 0x28,0xF6,0xA3,0x43, 0x0B,0x8D,0xD7,0x2A, + 0x36,0x8E,0xB7,0xF3, 0xA8,0x32,0x2A,0x70, 0xBC,0x84,0x27,0x5B, 0x9C,0x0B,0x3A,0xB0, + 0x0D,0x27,0xA5,0xCC, 0x3C,0x2D,0x22,0x4A, 0xA6,0xB6,0x1A,0x0D, 0x79,0xFB,0x45,0x96 + }, + { // sha512 + 0x89,0xD0,0x5B,0xA6, 0x32,0xC6,0x99,0xC3, 0x12,0x31,0xDE,0xD4, 0xFF,0xC1,0x27,0xD5, + 0xA8,0x94,0xDA,0xD4, 0x12,0xC0,0xE0,0x24, 0xDB,0x87,0x2D,0x1A, 0xBD,0x2B,0xA8,0x14, + 0x1A,0x0F,0x85,0x07, 0x2A,0x9B,0xE1,0xE2, 0xAA,0x04,0xCF,0x33, 0xC7,0x65,0xCB,0x51, + 0x08,0x13,0xA3,0x9C, 0xD5,0xA8,0x4C,0x4A, 0xCA,0xA6,0x4D,0x3F, 0x3F,0xB7,0xBA,0xE9 + } + } + } +}; + +//! HMAC test data from https://tools.ietf.org/html/rfc2202 (md5, sha1) and https://tools.ietf.org/html/rfc4231#section-4.2 (sha2), respectively +static const CryptTestHmac _Crypt_HmacTest[] = +{ + // test case 1 + { + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", + "Hi There", + { + { // murmurhash3 + 16, 8, 16, { 0x0C,0x55,0x91,0xD1, 0x01,0x73,0x5B,0x63, 0x43,0x0F,0x65,0x26, 0x9E,0x80,0x7A,0x63 } + }, + { // md5 + 16, 8, 16, { 0x92,0x94,0x72,0x7a, 0x36,0x38,0xbb,0x1c, 0x13,0xf4,0x8e,0xf8, 0x15,0x8b,0xfc,0x9d } + }, + { // sha1 + 20, 8, 20, { 0xB6,0x17,0x31,0x86, 0x55,0x05,0x72,0x64, 0xE2,0x8B,0xC0,0xB6, 0xFB,0x37,0x8C,0x8E, + 0xF1,0x46,0xBE,0x00 } + }, + { // sha224 + 20, 8, 28, { 0x89,0x6F,0xB1,0x12, 0x8A,0xBB,0xDF,0x19, 0x68,0x32,0x10,0x7C, 0xD4,0x9D,0xF3,0x3F, + 0x47,0xB4,0xB1,0x16, 0x99,0x12,0xBA,0x4F, 0x53,0x68,0x4B,0x22 } + }, + { // sha256 + 20, 8, 32, { 0xB0,0x34,0x4C,0x61, 0xD8,0xDB,0x38,0x53, 0x5C,0xA8,0xAF,0xCE, 0xAF,0x0B,0xF1,0x2B, + 0x88,0x1D,0xC2,0x00, 0xC9,0x83,0x3D,0xA7, 0x26,0xE9,0x37,0x6C, 0x2E,0x32,0xCF,0xF7 } + }, + { // sha384 + 20, 8, 48, { 0xAF,0xD0,0x39,0x44, 0xD8,0x48,0x95,0x62, 0x6B,0x08,0x25,0xF4, 0xAB,0x46,0x90,0x7F, + 0x15,0xF9,0xDA,0xDB, 0xE4,0x10,0x1E,0xC6, 0x82,0xAA,0x03,0x4C, 0x7C,0xEB,0xC5,0x9C, + 0xFA,0xEA,0x9E,0xA9, 0x07,0x6E,0xDE,0x7F, 0x4A,0xF1,0x52,0xE8, 0xB2,0xFA,0x9C,0xB6 } + }, + { // sha512 + 20, 8, 64, { 0x87,0xAA,0x7C,0xDE, 0xA5,0xEF,0x61,0x9D, 0x4F,0xF0,0xB4,0x24, 0x1A,0x1D,0x6C,0xB0, + 0x23,0x79,0xF4,0xE2, 0xCE,0x4E,0xC2,0x78, 0x7A,0xD0,0xB3,0x05, 0x45,0xE1,0x7C,0xDE, + 0xDA,0xA8,0x33,0xB7, 0xD6,0xB8,0xA7,0x02, 0x03,0x8B,0x27,0x4E, 0xAE,0xA3,0xF4,0xE4, + 0xBE,0x9D,0x91,0x4E, 0xEB,0x61,0xF1,0x70, 0x2E,0x69,0x6C,0x20, 0x3A,0x12,0x68,0x54 } + }, + }, + }, + // test case 2 + { + "Jefe", + "what do ya want for nothing?", + { + { // murmurhash3 + 4, 28, 16, { 0xD9,0x8B,0x6A,0x6D, 0x61,0x65,0xCB,0xEF, 0x42,0xA4,0x66,0x71, 0x5B,0xD1,0xFE,0xEF } + }, + { // md5 + 4, 28, 16, { 0x75,0x0C,0x78,0x3E, 0x6A,0xB0,0xB5,0x03, 0xEA,0xA8,0x6E,0x31, 0x0A,0x5D,0xB7,0x38 } + }, + { // sha1 + 4, 28, 20, { 0xEF,0xFC,0xDF,0x6A, 0xE5,0xEB,0x2F,0xA2, 0xD2,0x74,0x16,0xD5, 0xF1,0x84,0xDF,0x9C, + 0x25,0x9A,0x7C,0x79 } + }, + { // sha224 + 4, 28, 28, { 0xA3,0x0E,0x01,0x09, 0x8B,0xC6,0xDB,0xBF, 0x45,0x69,0x0F,0x3A, 0x7E,0x9E,0x6D,0x0F, + 0x8B,0xBE,0xA2,0xA3, 0x9E,0x61,0x48,0x00, 0x8F,0xD0,0x5E,0x44 } + }, + { // sha256 + 4, 28, 32, { 0x5B,0xDC,0xC1,0x46, 0xBF,0x60,0x75,0x4E, 0x6A,0x04,0x24,0x26, 0x08,0x95,0x75,0xC7, + 0x5A,0x00,0x3F,0x08, 0x9D,0x27,0x39,0x83, 0x9D,0xEC,0x58,0xB9, 0x64,0xEC,0x38,0x43 } + }, + { // sha384 + 4, 28, 48, { 0xAF,0x45,0xD2,0xE3, 0x76,0x48,0x40,0x31, 0x61,0x7F,0x78,0xD2, 0xB5,0x8A,0x6B,0x1B, + 0x9C,0x7E,0xF4,0x64, 0xF5,0xA0,0x1B,0x47, 0xE4,0x2E,0xC3,0x73, 0x63,0x22,0x44,0x5E, + 0x8E,0x22,0x40,0xCA, 0x5E,0x69,0xE2,0xC7, 0x8B,0x32,0x39,0xEC, 0xFA,0xB2,0x16,0x49 } + }, + { // sha512 + 4, 28, 64, { 0x16,0x4B,0x7A,0x7B, 0xFC,0xF8,0x19,0xE2, 0xE3,0x95,0xFB,0xE7, 0x3B,0x56,0xE0,0xA3, + 0x87,0xBD,0x64,0x22, 0x2E,0x83,0x1F,0xD6, 0x10,0x27,0x0C,0xD7, 0xEA,0x25,0x05,0x54, + 0x97,0x58,0xBF,0x75, 0xC0,0x5A,0x99,0x4A, 0x6D,0x03,0x4F,0x65, 0xF8,0xF0,0xE6,0xFD, + 0xCA,0xEA,0xB1,0xA3, 0x4D,0x4A,0x6B,0x4B, 0x63,0x6E,0x07,0x0A, 0x38,0xBC,0xE7,0x37 } + } + } + }, + // test case 3 + { + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa", + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd", + { + { 0, 0, 0, { 0 } }, // murmurhash3 + { // md5 + 16, 50, 16, { 0x56,0xbe,0x34,0x52, 0x1d,0x14,0x4c,0x88, 0xdb,0xb8,0xc7,0x33, 0xf0,0xe8,0xb3,0xf6 } + }, + { // sha1 + 20, 50, 20, { 0x12,0x5d,0x73,0x42, 0xb9,0xac,0x11,0xcd, 0x91,0xa3,0x9a,0xf4, 0x8a,0xa1,0x7b,0x4f, + 0x63,0xf1,0x75,0xd3 } + }, + { // sha224 + 20, 50, 20, { 0x7f,0xb3,0xcb,0x35, 0x88,0xc6,0xc1,0xf6, 0xff,0xa9,0x69,0x4d, 0x7d,0x6a,0xd2,0x64, + 0x93,0x65,0xb0,0xc1, 0xf6,0x5d,0x69,0xd1, 0xec,0x83,0x33,0xea } + }, + { // sha256 + 20, 50, 20, { 0x77,0x3e,0xa9,0x1e, 0x36,0x80,0x0e,0x46, 0x85,0x4d,0xb8,0xeb, 0xd0,0x91,0x81,0xa7, + 0x29,0x59,0x09,0x8b, 0x3e,0xf8,0xc1,0x22, 0xd9,0x63,0x55,0x14, 0xce,0xd5,0x65,0xfe } + }, + { // sha256 + 20, 50, 20, { 0x88,0x06,0x26,0x08, 0xd3,0xe6,0xad,0x8a, 0x0a,0xa2,0xac,0xe0, 0x14,0xc8,0xa8,0x6f, + 0x0a,0xa6,0x35,0xd9, 0x47,0xac,0x9f,0xeb, 0xe8,0x3e,0xf4,0xe5, 0x59,0x66,0x14,0x4b, + 0x2a,0x5a,0xb3,0x9d, 0xc1,0x38,0x14,0xb9, 0x4e,0x3a,0xb6,0xe1, 0x01,0xa3,0x4f,0x27 } + }, + { // sha512 + 20, 50, 20, { 0xfa,0x73,0xb0,0x08, 0x9d,0x56,0xa2,0x84, 0xef,0xb0,0xf0,0x75, 0x6c,0x89,0x0b,0xe9, + 0xb1,0xb5,0xdb,0xdd, 0x8e,0xe8,0x1a,0x36, 0x55,0xf8,0x3e,0x33, 0xb2,0x27,0x9d,0x39, + 0xbf,0x3e,0x84,0x82, 0x79,0xa7,0x22,0xc8, 0x06,0xb4,0x85,0xa4, 0x7e,0x67,0xc8,0x07, + 0xb9,0x46,0xa3,0x37, 0xbe,0xe8,0x94,0x26, 0x74,0x27,0x88,0x59, 0xe1,0x32,0x92,0xfb } + } + } + }, + // test case 4 + { + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" + "\x11\x12\x13\x14\x15\x16\x17\x18\x19", + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd", + { + { 0, 0, 0, { 0 } }, // murmurhash3 + { // md5 + 25, 50, 16, { 0x69,0x7e,0xaf,0x0a, 0xca,0x3a,0x3a,0xea, 0x3a,0x75,0x16,0x47, 0x46,0xff,0xaa,0x79 } + }, + { // sha1 + 25, 50, 20, { 0x4c,0x90,0x07,0xf4, 0x02,0x62,0x50,0xc6, 0xbc,0x84,0x14,0xf9, 0xbf,0x50,0xc8,0x6c, + 0x2d,0x72,0x35,0xda } + }, + { // sha224 + 25, 50, 28, { 0x6c,0x11,0x50,0x68, 0x74,0x01,0x3c,0xac, 0x6a,0x2a,0xbc,0x1b, 0xb3,0x82,0x62,0x7c, + 0xec,0x6a,0x90,0xd8, 0x6e,0xfc,0x01,0x2d, 0xe7,0xaf,0xec,0x5a } + }, + { // sha256 + 25, 50, 32, { 0x82,0x55,0x8a,0x38, 0x9a,0x44,0x3c,0x0e, 0xa4,0xcc,0x81,0x98, 0x99,0xf2,0x08,0x3a, + 0x85,0xf0,0xfa,0xa3, 0xe5,0x78,0xf8,0x07, 0x7a,0x2e,0x3f,0xf4, 0x67,0x29,0x66,0x5b } + }, + { // sha384 + 25, 50, 48, { 0x3e,0x8a,0x69,0xb7, 0x78,0x3c,0x25,0x85, 0x19,0x33,0xab,0x62, 0x90,0xaf,0x6c,0xa7, + 0x7a,0x99,0x81,0x48, 0x08,0x50,0x00,0x9c, 0xc5,0x57,0x7c,0x6e, 0x1f,0x57,0x3b,0x4e, + 0x68,0x01,0xdd,0x23, 0xc4,0xa7,0xd6,0x79, 0xcc,0xf8,0xa3,0x86, 0xc6,0x74,0xcf,0xfb } + }, + { // sha512 + 25, 50, 64, { 0xb0,0xba,0x46,0x56, 0x37,0x45,0x8c,0x69, 0x90,0xe5,0xa8,0xc5, 0xf6,0x1d,0x4a,0xf7, + 0xe5,0x76,0xd9,0x7f, 0xf9,0x4b,0x87,0x2d, 0xe7,0x6f,0x80,0x50, 0x36,0x1e,0xe3,0xdb, + 0xa9,0x1c,0xa5,0xc1, 0x1a,0xa2,0x5e,0xb4, 0xd6,0x79,0x27,0x5c, 0xc5,0x78,0x80,0x63, + 0xa5,0xf1,0x97,0x41, 0x12,0x0c,0x4f,0x2d, 0xe2,0xad,0xeb,0xeb, 0x10,0xa2,0x98,0xdd } + } + } + }, + // test case 5 + { + "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c", + "Test With Truncation", + { + { 0, 0, 0, { 0 } }, // murmurhash3 + { // md5 + 16, 20, 12, { 0x56,0x46,0x1e,0xf2, 0x34,0x2e,0xdc,0x00, 0xf9,0xba,0xb9,0x95, 0x69,0x0e,0xfd,0x4c } + }, + { // sha1 + 20, 20, 16, { 0x4c,0x1a,0x03,0x42, 0x4b,0x55,0xe0,0x7f, 0xe7,0xf2,0x7b,0xe1, 0xd5,0x8b,0xb9,0x32 } + }, + { // sha224 + 20, 20, 16, { 0x0e,0x2a,0xea,0x68, 0xa9,0x0c,0x8d,0x37, 0xc9,0x88,0xbc,0xdb, 0x9f,0xca,0x6f,0xa8 } + }, + { // sha256 + 20, 20, 16, { 0xa3,0xb6,0x16,0x74, 0x73,0x10,0x0e,0xe0, 0x6e,0x0c,0x79,0x6c, 0x29,0x55,0x55,0x2b } + }, + { // sha384 + 20, 20, 16, { 0x3a,0xbf,0x34,0xc3, 0x50,0x3b,0x2a,0x23, 0xa4,0x6e,0xfc,0x61, 0x9b,0xae,0xf8,0x97 } + }, + { // sha512 + 20, 20, 16, { 0x41,0x5f,0xad,0x62, 0x71,0x58,0x0a,0x53, 0x1d,0x41,0x79,0xbc, 0x89,0x1d,0x87,0xa6 } + } + } + }, + // test case 6 + { + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa", + "Test Using Larger Than Block-Size Key - Hash Key First", + { + { 0, 0, 0, { 0 } }, // murmurhash3 + { // md5 + 80, 54, 16, { 0x6b,0x1a,0xb7,0xfe, 0x4b,0xd7,0xbf,0x8f, 0x0b,0x62,0xe6,0xce, 0x61,0xb9,0xd0,0xcd } + }, + { // sha1 + 80, 54, 20, { 0xaa,0x4a,0xe5,0xe1, 0x52,0x72,0xd0,0x0e, 0x95,0x70,0x56,0x37, 0xce,0x8a,0x3b,0x55, + 0xed,0x40,0x21,0x12 } + }, + { // sha224 + 131, 54, 28, { 0x95,0xe9,0xa0,0xdb, 0x96,0x20,0x95,0xad, 0xae,0xbe,0x9b,0x2d, 0x6f,0x0d,0xbc,0xe2, + 0xd4,0x99,0xf1,0x12, 0xf2,0xd2,0xb7,0x27, 0x3f,0xa6,0x87,0x0e } + }, + { // sha256 + 131, 54, 32, { 0x60,0xe4,0x31,0x59, 0x1e,0xe0,0xb6,0x7f, 0x0d,0x8a,0x26,0xaa, 0xcb,0xf5,0xb7,0x7f, + 0x8e,0x0b,0xc6,0x21, 0x37,0x28,0xc5,0x14, 0x05,0x46,0x04,0x0f, 0x0e,0xe3,0x7f,0x54 } + }, + { // sha384 + 131, 54, 48, { 0x4e,0xce,0x08,0x44, 0x85,0x81,0x3e,0x90, 0x88,0xd2,0xc6,0x3a, 0x04,0x1b,0xc5,0xb4, + 0x4f,0x9e,0xf1,0x01, 0x2a,0x2b,0x58,0x8f, 0x3c,0xd1,0x1f,0x05, 0x03,0x3a,0xc4,0xc6, + 0x0c,0x2e,0xf6,0xab, 0x40,0x30,0xfe,0x82, 0x96,0x24,0x8d,0xf1, 0x63,0xf4,0x49,0x52 } + }, + { // sha512 + 131, 54, 64, { 0x80,0xb2,0x42,0x63, 0xc7,0xc1,0xa3,0xeb, 0xb7,0x14,0x93,0xc1, 0xdd,0x7b,0xe8,0xb4, + 0x9b,0x46,0xd1,0xf4, 0x1b,0x4a,0xee,0xc1, 0x12,0x1b,0x01,0x37, 0x83,0xf8,0xf3,0x52, + 0x6b,0x56,0xd0,0x37, 0xe0,0x5f,0x25,0x98, 0xbd,0x0f,0xd2,0x21, 0x5d,0x6a,0x1e,0x52, + 0x95,0xe6,0x4f,0x73, 0xf6,0x3f,0x0a,0xec, 0x8b,0x91,0x5a,0x98, 0x5d,0x78,0x65,0x98 } + } + }, + }, + // test case 7 for md5/sha1 + { + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", + "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", + { + { 0, 0, 0, { 0 } }, // murmurhash3 + { // md5 + 80, 73, 16, { 0x6f,0x63,0x0f,0xad, 0x67,0xcd,0xa0,0xee, 0x1f,0xb1,0xf5,0x62, 0xdb,0x3a,0xa5,0x3e } + }, + { // sha1 + 80, 73, 20, { 0xe8,0xe9,0x9d,0x0f, 0x45,0x23,0x7d,0x78, 0x6d,0x6b,0xba,0xa7, 0x96,0x5c,0x78,0x08, + 0xbb,0xff,0x1a,0x91 } + }, + { 0, 0, 0, { 0 } }, // sha224 + { 0, 0, 0, { 0 } }, // sha256 + { 0, 0, 0, { 0 } }, // sha384 + { 0, 0, 0, { 0 } }, // sha512 + } + }, + // test case 7 for sha2 + { + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa", + "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", + { + { 0, 0, 0, { 0 } }, // murmurhash3 + { 0, 0, 0, { 0 } }, // md5 + { 0, 0, 0, { 0 } }, // sha1 + { // sha224 + 131, 152, 28, { 0x3a,0x85,0x41,0x66, 0xac,0x5d,0x9f,0x02, 0x3f,0x54,0xd5,0x17, 0xd0,0xb3,0x9d,0xbd, + 0x94,0x67,0x70,0xdb, 0x9c,0x2b,0x95,0xc9, 0xf6,0xf5,0x65,0xd1 } + }, + { // sha256 + 131, 152, 28, { 0x9b,0x09,0xff,0xa7, 0x1b,0x94,0x2f,0xcb, 0x27,0x63,0x5f,0xbc, 0xd5,0xb0,0xe9,0x44, + 0xbf,0xdc,0x63,0x64, 0x4f,0x07,0x13,0x93, 0x8a,0x7f,0x51,0x53, 0x5c,0x3a,0x35,0xe2 } + }, + { // sha384 + 131, 152, 28, { 0x66,0x17,0x17,0x8e, 0x94,0x1f,0x02,0x0d, 0x35,0x1e,0x2f,0x25, 0x4e,0x8f,0xd3,0x2c, + 0x60,0x24,0x20,0xfe, 0xb0,0xb8,0xfb,0x9a, 0xdc,0xce,0xbb,0x82, 0x46,0x1e,0x99,0xc5, + 0xa6,0x78,0xcc,0x31, 0xe7,0x99,0x17,0x6d, 0x38,0x60,0xe6,0x11, 0x0c,0x46,0x52,0x3e } + }, + { // sha512 + 131, 152, 28, { 0xe3,0x7b,0x6a,0x77, 0x5d,0xc8,0x7d,0xba, 0xa4,0xdf,0xa9,0xf9, 0x6e,0x5e,0x3f,0xfd, + 0xde,0xbd,0x71,0xf8, 0x86,0x72,0x89,0x86, 0x5d,0xf5,0xa3,0x2d, 0x20,0xcd,0xc9,0x44, + 0xb6,0x02,0x2c,0xac, 0x3c,0x49,0x82,0xb1, 0x0d,0x5e,0xeb,0x55, 0xc3,0xe4,0xde,0x15, + 0x13,0x46,0x76,0xfb, 0x6d,0xe0,0x44,0x60, 0x65,0xc9,0x74,0x40, 0xfa,0x8c,0x6a,0x58 } + } + } + } +}; + +//! test vectors from https://tools.ietf.org/html/rfc6979#appendix-A.2 +static const CryptTestDsaT _Crypt_DsaTests[] = +{ + { + CRYPTCURVE_SECP256R1, 32, "secp256r1", + { 0xC9, 0xAF, 0xA9, 0xD8, 0x45, 0xBA, 0x75, 0x16, 0x6B, 0x5C, 0x21, 0x57, 0x67, 0xB1, 0xD6, 0x93, + 0x4E, 0x50, 0xC3, 0xDB, 0x36, 0xE8, 0x9B, 0x12, 0x7B, 0x8A, 0x62, 0x2B, 0x12, 0x0F, 0x67, 0x21 }, + { 0x04, 0x60, 0xFE, 0xD4, 0xBA, 0x25, 0x5A, 0x9D, 0x31, 0xC9, 0x61, 0xEB, 0x74, 0xC6, 0x35, 0x6D, 0x68, + 0xC0, 0x49, 0xB8, 0x92, 0x3B, 0x61, 0xFA, 0x6C, 0xE6, 0x69, 0x62, 0x2E, 0x60, 0xF2, 0x9F, 0xB6, + 0x79, 0x03, 0xFE, 0x10, 0x08, 0xB8, 0xBC, 0x99, 0xA4, 0x1A, 0xE9, 0xE9, 0x56, 0x28, 0xBC, 0x64, + 0xF2, 0xF1, 0xB2, 0x0C, 0x2D, 0x7E, 0x9F, 0x51, 0x77, 0xA3, 0xC2, 0x94, 0xD4, 0x46, 0x22, 0x99 }, + { + { "sample", CRYPTHASH_SHA1, "sha1", + { 0x61, 0x34, 0x0C, 0x88, 0xC3, 0xAA, 0xEB, 0xEB, 0x4F, 0x6D, 0x66, 0x7F, 0x67, 0x2C, 0xA9, 0x75, + 0x9A, 0x6C, 0xCA, 0xA9, 0xFA, 0x88, 0x11, 0x31, 0x30, 0x39, 0xEE, 0x4A, 0x35, 0x47, 0x1D, 0x32, + 0x6D, 0x7F, 0x14, 0x7D, 0xAC, 0x08, 0x94, 0x41, 0xBB, 0x2E, 0x2F, 0xE8, 0xF7, 0xA3, 0xFA, 0x26, + 0x4B, 0x9C, 0x47, 0x50, 0x98, 0xFD, 0xCF, 0x6E, 0x00, 0xD7, 0xC9, 0x96, 0xE1, 0xB8, 0xB7, 0xEB } + }, + { "sample", CRYPTHASH_SHA224, "sha224", + { 0x53, 0xB2, 0xFF, 0xF5, 0xD1, 0x75, 0x2B, 0x2C, 0x68, 0x9D, 0xF2, 0x57, 0xC0, 0x4C, 0x40, 0xA5, + 0x87, 0xFA, 0xBA, 0xBB, 0x3F, 0x6F, 0xC2, 0x70, 0x2F, 0x13, 0x43, 0xAF, 0x7C, 0xA9, 0xAA, 0x3F, + 0xB9, 0xAF, 0xB6, 0x4F, 0xDC, 0x03, 0xDC, 0x1A, 0x13, 0x1C, 0x7D, 0x23, 0x86, 0xD1, 0x1E, 0x34, + 0x9F, 0x07, 0x0A, 0xA4, 0x32, 0xA4, 0xAC, 0xC9, 0x18, 0xBE, 0xA9, 0x88, 0xBF, 0x75, 0xC7, 0x4C } + }, + { "sample", CRYPTHASH_SHA256, "sha256", + { 0xEF, 0xD4, 0x8B, 0x2A, 0xAC, 0xB6, 0xA8, 0xFD, 0x11, 0x40, 0xDD, 0x9C, 0xD4, 0x5E, 0x81, 0xD6, + 0x9D, 0x2C, 0x87, 0x7B, 0x56, 0xAA, 0xF9, 0x91, 0xC3, 0x4D, 0x0E, 0xA8, 0x4E, 0xAF, 0x37, 0x16, + 0xF7, 0xCB, 0x1C, 0x94, 0x2D, 0x65, 0x7C, 0x41, 0xD4, 0x36, 0xC7, 0xA1, 0xB6, 0xE2, 0x9F, 0x65, + 0xF3, 0xE9, 0x00, 0xDB, 0xB9, 0xAF, 0xF4, 0x06, 0x4D, 0xC4, 0xAB, 0x2F, 0x84, 0x3A, 0xCD, 0xA8 } + }, + { "sample", CRYPTHASH_SHA384, "sha384", + { 0x0E, 0xAF, 0xEA, 0x03, 0x9B, 0x20, 0xE9, 0xB4, 0x23, 0x09, 0xFB, 0x1D, 0x89, 0xE2, 0x13, 0x05, + 0x7C, 0xBF, 0x97, 0x3D, 0xC0, 0xCF, 0xC8, 0xF1, 0x29, 0xED, 0xDD, 0xC8, 0x00, 0xEF, 0x77, 0x19, + 0x48, 0x61, 0xF0, 0x49, 0x1E, 0x69, 0x98, 0xB9, 0x45, 0x51, 0x93, 0xE3, 0x4E, 0x7B, 0x0D, 0x28, + 0x4D, 0xDD, 0x71, 0x49, 0xA7, 0x4B, 0x95, 0xB9, 0x26, 0x1F, 0x13, 0xAB, 0xDE, 0x94, 0x09, 0x54 } + }, + { "sample", CRYPTHASH_SHA512, "sha512", + { 0x84, 0x96, 0xA6, 0x0B, 0x5E, 0x9B, 0x47, 0xC8, 0x25, 0x48, 0x88, 0x27, 0xE0, 0x49, 0x5B, 0x0E, + 0x3F, 0xA1, 0x09, 0xEC, 0x45, 0x68, 0xFD, 0x3F, 0x8D, 0x10, 0x97, 0x67, 0x8E, 0xB9, 0x7F, 0x00, + 0x23, 0x62, 0xAB, 0x1A, 0xDB, 0xE2, 0xB8, 0xAD, 0xF9, 0xCB, 0x9E, 0xDA, 0xB7, 0x40, 0xEA, 0x60, + 0x49, 0xC0, 0x28, 0x11, 0x4F, 0x24, 0x60, 0xF9, 0x65, 0x54, 0xF6, 0x1F, 0xAE, 0x33, 0x02, 0xFE } + }, + { "test", CRYPTHASH_SHA1, "sha1", + { 0x0C, 0xBC, 0xC8, 0x6F, 0xD6, 0xAB, 0xD1, 0xD9, 0x9E, 0x70, 0x3E, 0x1E, 0xC5, 0x00, 0x69, 0xEE, + 0x5C, 0x0B, 0x4B, 0xA4, 0xB9, 0xAC, 0x60, 0xE4, 0x09, 0xE8, 0xEC, 0x59, 0x10, 0xD8, 0x1A, 0x89, + 0x01, 0xB9, 0xD7, 0xB7, 0x3D, 0xFA, 0xA6, 0x0D, 0x56, 0x51, 0xEC, 0x45, 0x91, 0xA0, 0x13, 0x6F, + 0x87, 0x65, 0x3E, 0x0F, 0xD7, 0x80, 0xC3, 0xB1, 0xBC, 0x87, 0x2F, 0xFD, 0xEA, 0xE4, 0x79, 0xB1 } + }, + { "test", CRYPTHASH_SHA224, "sha224", + { 0xC3, 0x7E, 0xDB, 0x6F, 0x0A, 0xE7, 0x9D, 0x47, 0xC3, 0xC2, 0x7E, 0x96, 0x2F, 0xA2, 0x69, 0xBB, + 0x4F, 0x44, 0x17, 0x70, 0x35, 0x7E, 0x11, 0x4E, 0xE5, 0x11, 0xF6, 0x62, 0xEC, 0x34, 0xA6, 0x92, + 0xC8, 0x20, 0x05, 0x3A, 0x05, 0x79, 0x1E, 0x52, 0x1F, 0xCA, 0xAD, 0x60, 0x42, 0xD4, 0x0A, 0xEA, + 0x1D, 0x6B, 0x1A, 0x54, 0x01, 0x38, 0x55, 0x8F, 0x47, 0xD0, 0x71, 0x98, 0x00, 0xE1, 0x8F, 0x2D } + }, + { "test", CRYPTHASH_SHA256, "sha256", + { 0xF1, 0xAB, 0xB0, 0x23, 0x51, 0x83, 0x51, 0xCD, 0x71, 0xD8, 0x81, 0x56, 0x7B, 0x1E, 0xA6, 0x63, + 0xED, 0x3E, 0xFC, 0xF6, 0xC5, 0x13, 0x2B, 0x35, 0x4F, 0x28, 0xD3, 0xB0, 0xB7, 0xD3, 0x83, 0x67, + 0x01, 0x9F, 0x41, 0x13, 0x74, 0x2A, 0x2B, 0x14, 0xBD, 0x25, 0x92, 0x6B, 0x49, 0xC6, 0x49, 0x15, + 0x5F, 0x26, 0x7E, 0x60, 0xD3, 0x81, 0x4B, 0x4C, 0x0C, 0xC8, 0x42, 0x50, 0xE4, 0x6F, 0x00, 0x83 } + }, + { "test", CRYPTHASH_SHA384, "sha384", + { 0x83, 0x91, 0x0E, 0x8B, 0x48, 0xBB, 0x0C, 0x74, 0x24, 0x4E, 0xBD, 0xF7, 0xF0, 0x7A, 0x1C, 0x54, + 0x13, 0xD6, 0x14, 0x72, 0xBD, 0x94, 0x1E, 0xF3, 0x92, 0x0E, 0x62, 0x3F, 0xBC, 0xCE, 0xBE, 0xB6, + 0x8D, 0xDB, 0xEC, 0x54, 0xCF, 0x8C, 0xD5, 0x87, 0x48, 0x83, 0x84, 0x1D, 0x71, 0x21, 0x42, 0xA5, + 0x6A, 0x8D, 0x0F, 0x21, 0x8F, 0x50, 0x03, 0xCB, 0x02, 0x96, 0xB6, 0xB5, 0x09, 0x61, 0x9F, 0x2C } + }, + { "test", CRYPTHASH_SHA512, "sha512", + { 0x46, 0x1D, 0x93, 0xF3, 0x1B, 0x65, 0x40, 0x89, 0x47, 0x88, 0xFD, 0x20, 0x6C, 0x07, 0xCF, 0xA0, + 0xCC, 0x35, 0xF4, 0x6F, 0xA3, 0xC9, 0x18, 0x16, 0xFF, 0xF1, 0x04, 0x0A, 0xD1, 0x58, 0x1A, 0x04, + 0x39, 0xAF, 0x9F, 0x15, 0xDE, 0x0D, 0xB8, 0xD9, 0x7E, 0x72, 0x71, 0x9C, 0x74, 0x82, 0x0D, 0x30, + 0x4C, 0xE5, 0x22, 0x6E, 0x32, 0xDE, 0xDA, 0xE6, 0x75, 0x19, 0xE8, 0x40, 0xD1, 0x19, 0x4E, 0x55 } + }, + } + }, + { + CRYPTCURVE_SECP384R1, 48, "secp384r1", + { 0x6B, 0x9D, 0x3D, 0xAD, 0x2E, 0x1B, 0x8C, 0x1C, 0x05, 0xB1, 0x98, 0x75, 0xB6, 0x65, 0x9F, 0x4D, + 0xE2, 0x3C, 0x3B, 0x66, 0x7B, 0xF2, 0x97, 0xBA, 0x9A, 0xA4, 0x77, 0x40, 0x78, 0x71, 0x37, 0xD8, + 0x96, 0xD5, 0x72, 0x4E, 0x4C, 0x70, 0xA8, 0x25, 0xF8, 0x72, 0xC9, 0xEA, 0x60, 0xD2, 0xED, 0xF5 }, + { 0x04, 0xEC, 0x3A, 0x4E, 0x41, 0x5B, 0x4E, 0x19, 0xA4, 0x56, 0x86, 0x18, 0x02, 0x9F, 0x42, 0x7F, 0xA5, + 0xDA, 0x9A, 0x8B, 0xC4, 0xAE, 0x92, 0xE0, 0x2E, 0x06, 0xAA, 0xE5, 0x28, 0x6B, 0x30, 0x0C, 0x64, + 0xDE, 0xF8, 0xF0, 0xEA, 0x90, 0x55, 0x86, 0x60, 0x64, 0xA2, 0x54, 0x51, 0x54, 0x80, 0xBC, 0x13, + 0x80, 0x15, 0xD9, 0xB7, 0x2D, 0x7D, 0x57, 0x24, 0x4E, 0xA8, 0xEF, 0x9A, 0xC0, 0xC6, 0x21, 0x89, + 0x67, 0x08, 0xA5, 0x93, 0x67, 0xF9, 0xDF, 0xB9, 0xF5, 0x4C, 0xA8, 0x4B, 0x3F, 0x1C, 0x9D, 0xB1, + 0x28, 0x8B, 0x23, 0x1C, 0x3A, 0xE0, 0xD4, 0xFE, 0x73, 0x44, 0xFD, 0x25, 0x33, 0x26, 0x47, 0x20 }, + { + { "sample", CRYPTHASH_SHA1, "sha1", + { 0xEC, 0x74, 0x8D, 0x83, 0x92, 0x43, 0xD6, 0xFB, 0xEF, 0x4F, 0xC5, 0xC4, 0x85, 0x9A, 0x7D, 0xFF, + 0xD7, 0xF3, 0xAB, 0xDD, 0xF7, 0x20, 0x14, 0x54, 0x0C, 0x16, 0xD7, 0x33, 0x09, 0x83, 0x4F, 0xA3, + 0x7B, 0x9B, 0xA0, 0x02, 0x89, 0x9F, 0x6F, 0xDA, 0x3A, 0x4A, 0x93, 0x86, 0x79, 0x0D, 0x4E, 0xB2, + 0xA3, 0xBC, 0xFA, 0x94, 0x7B, 0xEE, 0xF4, 0x73, 0x2B, 0xF2, 0x47, 0xAC, 0x17, 0xF7, 0x16, 0x76, + 0xCB, 0x31, 0xA8, 0x47, 0xB9, 0xFF, 0x0C, 0xBC, 0x9C, 0x9E, 0xD4, 0xC1, 0xA5, 0xB3, 0xFA, 0xCF, + 0x26, 0xF4, 0x9C, 0xA0, 0x31, 0xD4, 0x85, 0x75, 0x70, 0xCC, 0xB5, 0xCA, 0x44, 0x24, 0xA4, 0x43 } + }, + { "sample", CRYPTHASH_SHA224, "sha224", + { 0x42, 0x35, 0x6E, 0x76, 0xB5, 0x5A, 0x6D, 0x9B, 0x46, 0x31, 0xC8, 0x65, 0x44, 0x5D, 0xBE, 0x54, + 0xE0, 0x56, 0xD3, 0xB3, 0x43, 0x17, 0x66, 0xD0, 0x50, 0x92, 0x44, 0x79, 0x3C, 0x3F, 0x93, 0x66, + 0x45, 0x0F, 0x76, 0xEE, 0x3D, 0xE4, 0x3F, 0x5A, 0x12, 0x53, 0x33, 0xA6, 0xBE, 0x06, 0x01, 0x22, + 0x9D, 0xA0, 0xC8, 0x17, 0x87, 0x06, 0x40, 0x21, 0xE7, 0x8D, 0xF6, 0x58, 0xF2, 0xFB, 0xB0, 0xB0, + 0x42, 0xBF, 0x30, 0x46, 0x65, 0xDB, 0x72, 0x1F, 0x07, 0x7A, 0x42, 0x98, 0xB0, 0x95, 0xE4, 0x83, + 0x4C, 0x08, 0x2C, 0x03, 0xD8, 0x30, 0x28, 0xEF, 0xBF, 0x93, 0xA3, 0xC2, 0x39, 0x40, 0xCA, 0x8D } + }, + { "sample", CRYPTHASH_SHA256, "sha256", + { 0x21, 0xB1, 0x3D, 0x1E, 0x01, 0x3C, 0x7F, 0xA1, 0x39, 0x2D, 0x03, 0xC5, 0xF9, 0x9A, 0xF8, 0xB3, + 0x0C, 0x57, 0x0C, 0x6F, 0x98, 0xD4, 0xEA, 0x8E, 0x35, 0x4B, 0x63, 0xA2, 0x1D, 0x3D, 0xAA, 0x33, + 0xBD, 0xE1, 0xE8, 0x88, 0xE6, 0x33, 0x55, 0xD9, 0x2F, 0xA2, 0xB3, 0xC3, 0x6D, 0x8F, 0xB2, 0xCD, + 0xF3, 0xAA, 0x44, 0x3F, 0xB1, 0x07, 0x74, 0x5B, 0xF4, 0xBD, 0x77, 0xCB, 0x38, 0x91, 0x67, 0x46, + 0x32, 0x06, 0x8A, 0x10, 0xCA, 0x67, 0xE3, 0xD4, 0x5D, 0xB2, 0x26, 0x6F, 0xA7, 0xD1, 0xFE, 0xEB, + 0xEF, 0xDC, 0x63, 0xEC, 0xCD, 0x1A, 0xC4, 0x2E, 0xC0, 0xCB, 0x86, 0x68, 0xA4, 0xFA, 0x0A, 0xB0 } + }, + { "sample", CRYPTHASH_SHA384, "sha384", + { 0x94, 0xED, 0xBB, 0x92, 0xA5, 0xEC, 0xB8, 0xAA, 0xD4, 0x73, 0x6E, 0x56, 0xC6, 0x91, 0x91, 0x6B, + 0x3F, 0x88, 0x14, 0x06, 0x66, 0xCE, 0x9F, 0xA7, 0x3D, 0x64, 0xC4, 0xEA, 0x95, 0xAD, 0x13, 0x3C, + 0x81, 0xA6, 0x48, 0x15, 0x2E, 0x44, 0xAC, 0xF9, 0x6E, 0x36, 0xDD, 0x1E, 0x80, 0xFA, 0xBE, 0x46, + 0x99, 0xEF, 0x4A, 0xEB, 0x15, 0xF1, 0x78, 0xCE, 0xA1, 0xFE, 0x40, 0xDB, 0x26, 0x03, 0x13, 0x8F, + 0x13, 0x0E, 0x74, 0x0A, 0x19, 0x62, 0x45, 0x26, 0x20, 0x3B, 0x63, 0x51, 0xD0, 0xA3, 0xA9, 0x4F, + 0xA3, 0x29, 0xC1, 0x45, 0x78, 0x6E, 0x67, 0x9E, 0x7B, 0x82, 0xC7, 0x1A, 0x38, 0x62, 0x8A, 0xC8 } + }, + { "sample", CRYPTHASH_SHA512, "sha512", + { 0xED, 0x09, 0x59, 0xD5, 0x88, 0x0A, 0xB2, 0xD8, 0x69, 0xAE, 0x7F, 0x6C, 0x29, 0x15, 0xC6, 0xD6, + 0x0F, 0x96, 0x50, 0x7F, 0x9C, 0xB3, 0xE0, 0x47, 0xC0, 0x04, 0x68, 0x61, 0xDA, 0x4A, 0x79, 0x9C, + 0xFE, 0x30, 0xF3, 0x5C, 0xC9, 0x00, 0x05, 0x6D, 0x7C, 0x99, 0xCD, 0x78, 0x82, 0x43, 0x37, 0x09, + 0x51, 0x2C, 0x8C, 0xCE, 0xEE, 0x38, 0x90, 0xA8, 0x40, 0x58, 0xCE, 0x1E, 0x22, 0xDB, 0xC2, 0x19, + 0x8F, 0x42, 0x32, 0x3C, 0xE8, 0xAC, 0xA9, 0x13, 0x53, 0x29, 0xF0, 0x3C, 0x06, 0x8E, 0x51, 0x12, + 0xDC, 0x7C, 0xC3, 0xEF, 0x34, 0x46, 0xDE, 0xFC, 0xEB, 0x01, 0xA4, 0x5C, 0x26, 0x67, 0xFD, 0xD5 } + }, + { "test", CRYPTHASH_SHA1, "sha1", + { 0x4B, 0xC3, 0x5D, 0x3A, 0x50, 0xEF, 0x4E, 0x30, 0x57, 0x6F, 0x58, 0xCD, 0x96, 0xCE, 0x6B, 0xF6, + 0x38, 0x02, 0x5E, 0xE6, 0x24, 0x00, 0x4A, 0x1F, 0x77, 0x89, 0xA8, 0xB8, 0xE4, 0x3D, 0x06, 0x78, + 0xAC, 0xD9, 0xD2, 0x98, 0x76, 0xDA, 0xF4, 0x66, 0x38, 0x64, 0x5F, 0x7F, 0x40, 0x4B, 0x11, 0xC7, + 0xD5, 0xA6, 0x32, 0x6C, 0x49, 0x4E, 0xD3, 0xFF, 0x61, 0x47, 0x03, 0x87, 0x89, 0x61, 0xC0, 0xFD, + 0xE7, 0xB2, 0xC2, 0x78, 0xF9, 0xA6, 0x5F, 0xD8, 0xC4, 0xB7, 0x18, 0x62, 0x01, 0xA2, 0x99, 0x16, + 0x95, 0xBA, 0x1C, 0x84, 0x54, 0x13, 0x27, 0xE9, 0x66, 0xFA, 0x7B, 0x50, 0xF7, 0x38, 0x22, 0x82 } + }, + { "test", CRYPTHASH_SHA224, "sha224", + { 0xE8, 0xC9, 0xD0, 0xB6, 0xEA, 0x72, 0xA0, 0xE7, 0x83, 0x7F, 0xEA, 0x1D, 0x14, 0xA1, 0xA9, 0x55, + 0x7F, 0x29, 0xFA, 0xA4, 0x5D, 0x3E, 0x7E, 0xE8, 0x88, 0xFC, 0x5B, 0xF9, 0x54, 0xB5, 0xE6, 0x24, + 0x64, 0xA9, 0xA8, 0x17, 0xC4, 0x7F, 0xF7, 0x8B, 0x8C, 0x11, 0x06, 0x6B, 0x24, 0x08, 0x0E, 0x72, + 0x07, 0x04, 0x1D, 0x4A, 0x7A, 0x03, 0x79, 0xAC, 0x72, 0x32, 0xFF, 0x72, 0xE6, 0xF7, 0x7B, 0x6D, + 0xDB, 0x8F, 0x09, 0xB1, 0x6C, 0xCE, 0x0E, 0xC3, 0x28, 0x6B, 0x2B, 0xD4, 0x3F, 0xA8, 0xC6, 0x14, + 0x1C, 0x53, 0xEA, 0x5A, 0xBE, 0xF0, 0xD8, 0x23, 0x10, 0x77, 0xA0, 0x45, 0x40, 0xA9, 0x6B, 0x66 } + }, + { "test", CRYPTHASH_SHA256, "sha256", + { 0x6D, 0x6D, 0xEF, 0xAC, 0x9A, 0xB6, 0x4D, 0xAB, 0xAF, 0xE3, 0x6C, 0x6B, 0xF5, 0x10, 0x35, 0x2A, + 0x4C, 0xC2, 0x70, 0x01, 0x26, 0x36, 0x38, 0xE5, 0xB1, 0x6D, 0x9B, 0xB5, 0x1D, 0x45, 0x15, 0x59, + 0xF9, 0x18, 0xEE, 0xDA, 0xF2, 0x29, 0x3B, 0xE5, 0xB4, 0x75, 0xCC, 0x8F, 0x01, 0x88, 0x63, 0x6B, + 0x2D, 0x46, 0xF3, 0xBE, 0xCB, 0xCC, 0x52, 0x3D, 0x5F, 0x1A, 0x12, 0x56, 0xBF, 0x0C, 0x9B, 0x02, + 0x4D, 0x87, 0x9B, 0xA9, 0xE8, 0x38, 0x14, 0x4C, 0x8B, 0xA6, 0xBA, 0xEB, 0x4B, 0x53, 0xB4, 0x7D, + 0x51, 0xAB, 0x37, 0x3F, 0x98, 0x45, 0xC0, 0x51, 0x4E, 0xEF, 0xB1, 0x40, 0x24, 0x78, 0x72, 0x65 } + }, + { "test", CRYPTHASH_SHA384, "sha384", + { 0x82, 0x03, 0xB6, 0x3D, 0x3C, 0x85, 0x3E, 0x8D, 0x77, 0x22, 0x7F, 0xB3, 0x77, 0xBC, 0xF7, 0xB7, + 0xB7, 0x72, 0xE9, 0x78, 0x92, 0xA8, 0x0F, 0x36, 0xAB, 0x77, 0x5D, 0x50, 0x9D, 0x7A, 0x5F, 0xEB, + 0x05, 0x42, 0xA7, 0xF0, 0x81, 0x29, 0x98, 0xDA, 0x8F, 0x1D, 0xD3, 0xCA, 0x3C, 0xF0, 0x23, 0xDB, + 0xDD, 0xD0, 0x76, 0x04, 0x48, 0xD4, 0x2D, 0x8A, 0x43, 0xAF, 0x45, 0xAF, 0x83, 0x6F, 0xCE, 0x4D, + 0xE8, 0xBE, 0x06, 0xB4, 0x85, 0xE9, 0xB6, 0x1B, 0x82, 0x7C, 0x2F, 0x13, 0x17, 0x39, 0x23, 0xE0, + 0x6A, 0x73, 0x9F, 0x04, 0x06, 0x49, 0xA6, 0x67, 0xBF, 0x3B, 0x82, 0x82, 0x46, 0xBA, 0xA5, 0xA5 } + }, + { "test", CRYPTHASH_SHA512, "sha512", + { 0xA0, 0xD5, 0xD0, 0x90, 0xC9, 0x98, 0x0F, 0xAF, 0x3C, 0x2C, 0xE5, 0x7B, 0x7A, 0xE9, 0x51, 0xD3, + 0x19, 0x77, 0xDD, 0x11, 0xC7, 0x75, 0xD3, 0x14, 0xAF, 0x55, 0xF7, 0x6C, 0x67, 0x64, 0x47, 0xD0, + 0x6F, 0xB6, 0x49, 0x5C, 0xD2, 0x1B, 0x4B, 0x6E, 0x34, 0x0F, 0xC2, 0x36, 0x58, 0x4F, 0xB2, 0x77, + 0x97, 0x69, 0x84, 0xE5, 0x9B, 0x4C, 0x77, 0xB0, 0xE8, 0xE4, 0x46, 0x0D, 0xCA, 0x3D, 0x9F, 0x20, + 0xE0, 0x7B, 0x9B, 0xB1, 0xF6, 0x3B, 0xEE, 0xFA, 0xF5, 0x76, 0xF6, 0xB2, 0xE8, 0xB2, 0x24, 0x63, + 0x4A, 0x20, 0x92, 0xCD, 0x37, 0x92, 0xE0, 0x15, 0x9A, 0xD9, 0xCE, 0xE3, 0x76, 0x59, 0xC7, 0x36 } + } + } + } +}; + +// buffer for large random number used in timing tests +static uint8_t _Crypt_aLargeRandom[2*1024*1024]; +const int32_t _Crypt_iTimingIter = 16; + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestArc4 + + \Description + Test the CryptArc4 module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else number of failed tests + + \Version 01/11/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestArc4(ZContext *argz, int32_t argc, char *argv[]) +{ + static const char *_strEncrypt[] = + { + "Test first string encryption.", + "Another string to test encrypt.", + "Strings are fun to encrypt!", + "This string is exactly 127 characters long, sans null. It is meant to test the behavior of Encrypt/Decrypt with a full buffer.", + "This string is more than 127 characters long. It will safely truncate the result string, which will result in the test failing.", + }; + char strEncryptBuf[128], strDecryptBuf[128]; + uint8_t aEncryptKey[32]; + int32_t iFailed, iString; + + ZPrintf("%s: testing CryptArc4\n", argv[0]); + + // generate a random encryption key + CryptRandGet(aEncryptKey, sizeof(aEncryptKey)); + + // test string encryption/decryption + for (iString = 0, iFailed = 0; iString < (signed)(sizeof(_strEncrypt)/sizeof(_strEncrypt[0])); iString += 1) + { + CryptArc4StringEncrypt(strEncryptBuf, sizeof(strEncryptBuf), _strEncrypt[iString], aEncryptKey, sizeof(aEncryptKey), 1); + CryptArc4StringDecrypt(strDecryptBuf, sizeof(strDecryptBuf), strEncryptBuf, aEncryptKey, sizeof(aEncryptKey), 1); + ZPrintf("%s: '%s'->'%s'\n", argv[0], _strEncrypt[iString], strEncryptBuf); + if (strcmp(strDecryptBuf, _strEncrypt[iString])) + { + ZPrintf("%s: encrypt/decrypt failed; '%s' != '%s'\n", argv[0], _strEncrypt[iString], strDecryptBuf); + iFailed += 1; + } + } + + ZPrintf("%s: ---------------------\n", argv[0]); + + // one test intentionally fails, so subtract that out + iFailed -= 1; + return(iFailed); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestGcm + + \Description + Test the CryptGcm module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else number of failed tests + + \Version 07/08/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestGcm(ZContext *argz, int32_t argc, char *argv[]) +{ + uint8_t aBuffer[1024], aTagRslt[16]; + int32_t iFail, iFailed, iTest; + const CryptTestGcmT *pTest; + CryptGcmT Gcm; + + ZPrintf("%s: testing CryptGcm\n", argv[0]); + + for (iTest = 0, iFailed = 0; iTest < (signed)(sizeof(_Crypt_GcmTest)/sizeof(_Crypt_GcmTest[0])); iTest += 1) + { + pTest = &_Crypt_GcmTest[iTest]; + iFail = 0; + + // do encryption + CryptGcmInit(&Gcm, pTest->aKey, pTest->iKeySize); + ds_memcpy(aBuffer, pTest->aInput, pTest->iInputSize); + CryptGcmEncrypt(&Gcm, aBuffer, pTest->iInputSize, pTest->aInitVec, sizeof(pTest->aInitVec), pTest->aData, pTest->iDataSize, aTagRslt, sizeof(aTagRslt)); + + if (memcmp(pTest->aTagRslt, aTagRslt, sizeof(pTest->aTagRslt))) + { + ZPrintf("%s: gcm encrypt test %d failed with invalid tag result\n", argv[0], iTest); + NetPrintMem(pTest->aTagRslt, sizeof(pTest->aTagRslt), "expected"); + NetPrintMem(aTagRslt, sizeof(pTest->aTagRslt), "actual"); + iFail |= 1; + } + if ((pTest->iInputSize != 0) && memcmp(pTest->aOutput, aBuffer, pTest->iOutputSize)) + { + ZPrintf("%s: gcm encrypt test %d failed with invalid output result\n", argv[0], iTest); + NetPrintMem(pTest->aOutput, pTest->iOutputSize, "expected"); + NetPrintMem(aBuffer, pTest->iOutputSize, "actual"); + iFail |= 1; + } + + // decrypt + CryptGcmInit(&Gcm, pTest->aKey, pTest->iKeySize); + CryptGcmDecrypt(&Gcm, aBuffer, pTest->iInputSize, pTest->aInitVec, sizeof(pTest->aInitVec), pTest->aData, pTest->iDataSize, aTagRslt, sizeof(aTagRslt)); + + if (memcmp(pTest->aTagRslt, aTagRslt, sizeof(pTest->aTagRslt))) + { + ZPrintf("%s: gcm decrypt test %d failed with invalid tag result\n", argv[0], iTest); + NetPrintMem(pTest->aTagRslt, sizeof(pTest->aTagRslt), "expected"); + NetPrintMem(aTagRslt, sizeof(pTest->aTagRslt), "actual"); + iFail |= 1; + } + if ((pTest->iInputSize != 0) && memcmp(pTest->aInput, aBuffer, pTest->iInputSize)) + { + ZPrintf("%s: gcm decrypt test %d failed with invalid output result\n", argv[0], iTest); + NetPrintMem(pTest->aInput, pTest->iInputSize, "expected"); + NetPrintMem(aBuffer, pTest->iOutputSize, "actual"); + iFail |= 1; + } + + ZPrintf("%s: test #%d %s\n", argv[0], iTest, iFail ? "failed" : "passed"); + + // add to failed count + iFailed += iFail; + } + + ZPrintf("%s: ---------------------\n", argv[0]); + return(iFailed); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestChaCha + + \Description + Test the CryptChaCha module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else number of failed tests + + \Version 02/14/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestChaCha(ZContext *argz, int32_t argc, char *argv[]) +{ + CryptChaChaT ChaChaState; + int32_t iResult; + + // test vectors from https://tools.ietf.org/html/rfc7539#section-2.8.2 + static const uint8_t _aKey[] = + { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f + }; + static const uint8_t _aNonce[] = + { + 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 + }; + static const uint8_t _aAddData[] = + { + 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 + }; + uint8_t aTagBuf[16]; + static uint8_t aData[] = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; + + ZPrintf("%s: testing CryptChaCha\n", argv[0]); + + // encrypt the data + CryptChaChaInit(&ChaChaState, _aKey, sizeof(_aKey)); + CryptChaChaEncrypt(&ChaChaState, aData, (int32_t)sizeof(aData)-1, _aNonce, (int32_t)sizeof(_aNonce), _aAddData, (int32_t)sizeof(_aAddData), aTagBuf, (int32_t)sizeof(aTagBuf)); + + // decrypt and authenticate the data + CryptChaChaInit(&ChaChaState, _aKey, sizeof(_aKey)); + iResult = CryptChaChaDecrypt(&ChaChaState, aData, (int32_t)sizeof(aData)-1, _aNonce, (int32_t)sizeof(_aNonce), _aAddData, (int32_t)sizeof(_aAddData), aTagBuf, (int32_t)sizeof(aTagBuf)); + + ZPrintf("%s: ---------------------\n", argv[0]); + + return(iResult != -1 ? 0 : 1); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestMurmurHash3 + + \Description + Test MurmurHash3 + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else number of failed tests + + \Version 11/04/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestMurmurHash3(ZContext *argz, int32_t argc, char *argv[]) +{ + char strHashBuf0[16], strHashBuf1[16]; + int32_t iFailed = 0, iCount, iString, iDataSize, iNumIter; + uint32_t uStartTick; + const uint8_t aKeyBuf[16] = // init vector from MurmurHash3Init, little-endian 64bit words + { + 0x89,0xab,0xcd,0xef,0x01,0x23,0x45,0x67, + 0x76,0x54,0x32,0x10,0xfe,0xdc,0xba,0x98, + }; + MurmurHash3T MurmurCtx; + + ZPrintf("%s: testing MurmurHash3 with a %d byte random buffer", argv[0], sizeof(_Crypt_aLargeRandom)); + + // test versions for compatibility; first the crypt-style Init/Update/Final + MurmurHash3Init(&MurmurCtx); + MurmurHash3Update(&MurmurCtx, _Crypt_aLargeRandom, sizeof(_Crypt_aLargeRandom)); + MurmurHash3Final(&MurmurCtx, strHashBuf0, sizeof(strHashBuf0)); + // now the all-in-one-go version + MurmurHash3(strHashBuf1, sizeof(strHashBuf1), _Crypt_aLargeRandom, sizeof(_Crypt_aLargeRandom), aKeyBuf, sizeof(aKeyBuf)); + // test result + if (memcmp(strHashBuf0, strHashBuf1, sizeof(strHashBuf0))) + { + ZPrintf("; hash failed!\n"); + NetPrintMem(strHashBuf0, sizeof(strHashBuf0), "hash0 result"); + NetPrintMem(strHashBuf1, sizeof(strHashBuf1), "hash1 result"); + iFailed += 1; + } + else + { + ZPrintf("; success\n"); + } + + ZPrintf("%s: testing MurmurHash3 with a %d byte random buffer and random sizes", argv[0], sizeof(_Crypt_aLargeRandom)); + + // now test crypt version with multiple updates that are not 16-byte aligned + MurmurHash3Init(&MurmurCtx); + for (iCount = 0, iNumIter = 0; iCount < (int32_t)sizeof(_Crypt_aLargeRandom); iCount += iDataSize, iNumIter += 1) + { + CryptRandGet((uint8_t *)&iDataSize, sizeof(iDataSize)); + iDataSize &= (sizeof(_Crypt_aLargeRandom)-1)/16; + if (iCount+iDataSize > (int32_t)sizeof(_Crypt_aLargeRandom)) + { + iDataSize = sizeof(_Crypt_aLargeRandom)-iCount; + } + MurmurHash3Update(&MurmurCtx, _Crypt_aLargeRandom+iCount, iDataSize); + } + MurmurHash3Final(&MurmurCtx, strHashBuf0, sizeof(strHashBuf0)); + // compare to previous result + if (memcmp(strHashBuf0, strHashBuf1, sizeof(strHashBuf0))) + { + ZPrintf("; hash failed!\n"); + NetPrintMem(strHashBuf0, sizeof(strHashBuf0), "hash0 result"); + NetPrintMem(strHashBuf1, sizeof(strHashBuf1), "hash1 result"); + iFailed += 1; + } + else + { + ZPrintf("; success (%d iterations)\n", iNumIter); + } + + // test all strings + for (iString = 0; iString < (signed)(sizeof(_Crypt_Sha2Test)/sizeof(_Crypt_Sha2Test[0])); iString += 1) + { + ZPrintf("%s: hashing \"%s\"", argv[0], _Crypt_Sha2Test[iString].pString); + + // test versions for compatibility; first the crypt-style Init/Update/Final + MurmurHash3Init(&MurmurCtx); + MurmurHash3Update(&MurmurCtx, (uint8_t *)_Crypt_Sha2Test[iString].pString, (uint32_t)strlen(_Crypt_Sha2Test[iString].pString)); + MurmurHash3Final(&MurmurCtx, strHashBuf0, sizeof(strHashBuf0)); + // now the all-in-one-go version + MurmurHash3(strHashBuf1, sizeof(strHashBuf1), (uint8_t *)_Crypt_Sha2Test[iString].pString, (uint32_t)strlen(_Crypt_Sha2Test[iString].pString), aKeyBuf, sizeof(aKeyBuf)); + + if (memcmp(strHashBuf0, strHashBuf1, sizeof(strHashBuf0))) + { + ZPrintf("; hash failed!\n"); + NetPrintMem(strHashBuf0, sizeof(strHashBuf0), "hash0 result"); + NetPrintMem(strHashBuf1, sizeof(strHashBuf1), "hash1 result"); + iFailed += 1; + } + else + { + ZPrintf("; success\n"); + } + } + + // timing tests + + // time atomic version + ZPrintf("%s: hashing %d bytes of random data %d times for timing test (atomic version); ", argv[0], sizeof(_Crypt_aLargeRandom), _Crypt_iTimingIter); + for (iCount = 0, uStartTick = NetTick(); iCount < _Crypt_iTimingIter; iCount += 1) + { + MurmurHash3(strHashBuf0, sizeof(strHashBuf0), _Crypt_aLargeRandom, sizeof(_Crypt_aLargeRandom)-16, aKeyBuf, sizeof(aKeyBuf)); + } + ZPrintf("%dms\n", NetTickDiff(NetTick(), uStartTick)); + + // time crypt-style version + ZPrintf("%s: hashing %d bytes of random data %d times for timing test (crypt version); ", argv[0], sizeof(_Crypt_aLargeRandom), _Crypt_iTimingIter); + for (iCount = 0, uStartTick = NetTick(); iCount < _Crypt_iTimingIter; iCount += 1) + { + MurmurHash3Init(&MurmurCtx); + MurmurHash3Update(&MurmurCtx, _Crypt_aLargeRandom, sizeof(_Crypt_aLargeRandom)); + MurmurHash3Final(&MurmurCtx, strHashBuf0, sizeof(strHashBuf0)); + } + ZPrintf("%dms\n", NetTickDiff(NetTick(), uStartTick)); + + ZPrintf("%s: ---------------------\n", argv[0]); + return(iFailed); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestMD5 + + \Description + Test the CryptMD5 module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else number of failed tests + + \Version 11/04/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestMD5(ZContext *argz, int32_t argc, char *argv[]) +{ + char strHashBuf[MD5_BINARY_OUT]; + int32_t iFailed = 0, iCount; + uint32_t uStartTick; + CryptMD5T MD5; + + ZPrintf("%s: testing CryptMD5\n", argv[0]); + + // do a timing test + + // test all modes + ZPrintf("%s: hashing %d bytes of random data %d times for timing test; ", argv[0], sizeof(_Crypt_aLargeRandom), _Crypt_iTimingIter); + uStartTick = NetTick(); + + for (iCount = 0; iCount < _Crypt_iTimingIter; iCount += 1) + { + CryptMD5Init(&MD5); + CryptMD5Update(&MD5, _Crypt_aLargeRandom, sizeof(_Crypt_aLargeRandom)); + CryptMD5Final(&MD5, (uint8_t *)strHashBuf, sizeof(strHashBuf)); + } + + ZPrintf("%dms\n", NetTickDiff(NetTick(), uStartTick)); + + ZPrintf("%s: ---------------------\n", argv[0]); + return(iFailed); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestSha1 + + \Description + Test the CryptSha1 module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else number of failed tests + + \Version 11/04/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestSha1(ZContext *argz, int32_t argc, char *argv[]) +{ + char strHashBuf[20]; + int32_t iFailed = 0, iCount; + uint32_t uStartTick; + CryptSha1T Sha1; + + ZPrintf("%s: testing CryptSha1\n", argv[0]); + + // do a timing test + + // test all modes + ZPrintf("%s: hashing %d bytes of random data %d times for timing test; ", argv[0], sizeof(_Crypt_aLargeRandom), _Crypt_iTimingIter); + uStartTick = NetTick(); + + for (iCount = 0; iCount < _Crypt_iTimingIter; iCount += 1) + { + CryptSha1Init(&Sha1); + CryptSha1Update(&Sha1, _Crypt_aLargeRandom, sizeof(_Crypt_aLargeRandom)); + CryptSha1Final(&Sha1, (uint8_t *)strHashBuf, sizeof(strHashBuf)); + } + + ZPrintf("%dms\n", NetTickDiff(NetTick(), uStartTick)); + + ZPrintf("%s: ---------------------\n", argv[0]); + return(iFailed); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestSha2 + + \Description + Test the CryptSha2 module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else number of failed tests + + \Version 11/04/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestSha2(ZContext *argz, int32_t argc, char *argv[]) +{ + char strHashBuf[128]; + static const uint8_t _HashSizes[] = { CRYPTSHA224_HASHSIZE, CRYPTSHA256_HASHSIZE, CRYPTSHA384_HASHSIZE, CRYPTSHA512_HASHSIZE }; + int32_t iFailed, iString, iMode, iCount; + uint32_t uStartTick; + CryptSha2T Sha2; + + ZPrintf("%s: testing CryptSha2\n", argv[0]); + + // test all strings + for (iFailed = 0, iString = 0; iString < (signed)(sizeof(_Crypt_Sha2Test)/sizeof(_Crypt_Sha2Test[0])); iString += 1) + { + // test all modes + for (iMode = 0; iMode < 4; iMode += 1) + { + ZPrintf("%s: hashing \"%s\" (mode %d)", argv[0], _Crypt_Sha2Test[iString].pString, iMode); + + CryptSha2Init(&Sha2, _HashSizes[iMode]); + CryptSha2Update(&Sha2, (uint8_t *)_Crypt_Sha2Test[iString].pString, (uint32_t)strlen(_Crypt_Sha2Test[iString].pString)); + CryptSha2Final(&Sha2, (uint8_t *)strHashBuf, _HashSizes[iMode]); + + if (memcmp(strHashBuf, _Crypt_Sha2Test[iString].strHashRslt[iMode], (uint32_t)_HashSizes[iMode])) + { + ZPrintf("; hash failed!\n"); + NetPrintMem(strHashBuf, (uint32_t)_HashSizes[iMode], "hash result"); + NetPrintMem(_Crypt_Sha2Test[iString].strHashRslt[iMode], (uint32_t)_HashSizes[iMode], "expected result"); + iFailed += 1; + } + else + { + ZPrintf("; success\n"); + } + } + } + + // do a timing test + + // test all modes + for (iMode = 0; iMode < 4; iMode += 1) + { + ZPrintf("%s: hashing %d bytes of random data %d times for timing test (mode %d); ", argv[0], sizeof(_Crypt_aLargeRandom), _Crypt_iTimingIter, iMode); + uStartTick = NetTick(); + + for (iCount = 0; iCount < _Crypt_iTimingIter; iCount += 1) + { + CryptSha2Init(&Sha2, _HashSizes[iMode]); + CryptSha2Update(&Sha2, _Crypt_aLargeRandom, sizeof(_Crypt_aLargeRandom)); + CryptSha2Final(&Sha2, (uint8_t *)strHashBuf, _HashSizes[iMode]); + } + + ZPrintf("%dms\n", NetTickDiff(NetTick(), uStartTick)); + } + + ZPrintf("%s: ---------------------\n", argv[0]); + return(iFailed); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestHmac + + \Description + Test the CryptHmac module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else number of failed tests + + \Version 11/05/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestHmac(ZContext *argz, int32_t argc, char *argv[]) +{ + uint8_t strHmacBuf[128]; + const uint8_t strZeroBuf[64] = { 0 }; + int32_t iFailed, iTest, iMode, iCount; + uint32_t uStartTick; + int32_t iHashSize; + const CryptTestHmac *pTest; + + ZPrintf("%s: testing CryptHmac\n", argv[0]); + + // test all strings + for (iFailed = 0, iTest = 0; iTest < (signed)(sizeof(_Crypt_HmacTest)/sizeof(_Crypt_HmacTest[0])); iTest += 1) + { + ZPrintf("%s: test %d\n", argv[0], iTest+1); + + for (iMode = CRYPTHASH_MURMUR3; iMode <= CRYPTHASH_SHA512; iMode += 1) + { + iHashSize = CryptHashGetSize((CryptHashTypeE)iMode); + pTest = &_Crypt_HmacTest[iTest]; + + // skip tests with blank results + if (!memcmp(pTest->Param[iMode-1].strRslt, strZeroBuf, iHashSize)) + { + continue; + } + + // run the test + ZPrintf("%s: hashing \"%s\" (mode %d)", argv[0], pTest->pString, iMode); + CryptHmacCalc(strHmacBuf, (int32_t)sizeof(strHmacBuf), (uint8_t *)pTest->pString, pTest->Param[iMode-1].uInpLen, (uint8_t *)pTest->pKey, pTest->Param[iMode-1].uKeyLen, iMode); + + // check test result + if (memcmp(strHmacBuf, pTest->Param[iMode-1].strRslt, pTest->Param[iMode-1].uOutLen)) + { + ZPrintf("; hmac failed!\n"); + NetPrintMem(strHmacBuf, pTest->Param[iMode-1].uOutLen, "hmac result"); + NetPrintMem(pTest->Param[iMode-1].strRslt, pTest->Param[iMode-1].uOutLen, "expected result"); + iFailed += 1; + } + else + { + ZPrintf("; success\n"); + } + } + } + + // do a timing test + + // test all modes + for (iMode = CRYPTHASH_MURMUR3, iTest = 0; iMode <= CRYPTHASH_SHA512; iMode += 1) + { + ZPrintf("%s: hashing %d bytes of random data %d times for timing test (mode %d); ", argv[0], sizeof(_Crypt_aLargeRandom), _Crypt_iTimingIter, iMode); + uStartTick = NetTick(); + + for (iCount = 0; iCount < _Crypt_iTimingIter; iCount += 1) + { + CryptHmacCalc(strHmacBuf, (int32_t)sizeof(strHmacBuf), _Crypt_aLargeRandom, sizeof(_Crypt_aLargeRandom), (uint8_t *)_Crypt_HmacTest[iTest].pKey, (int32_t)strlen(_Crypt_HmacTest[iTest].pKey), (CryptHashTypeE)iMode); + } + + ZPrintf("%dms\n", NetTickDiff(NetTick(), uStartTick)); + } + + ZPrintf("%s: ---------------------\n", argv[0]); + return(iFailed); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestRSA + + \Description + Test the CryptRSA module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else number of failed tests + + \Version 11/22/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestRSA(ZContext *argz, int32_t argc, char *argv[]) +{ + static const uint8_t _aModulus[] = + { + 0xe2, 0x02, 0x41, 0x01, 0xa1, 0x02, 0xb8, 0xe2, 0x3e, 0x2a, 0x0a, 0x6d, 0x8b, 0xf9, + 0xf4, 0x73, 0xd3, 0xdb, 0x15, 0xac, 0x0a, 0xf6, 0xf2, 0x4c, 0x6d, 0x46, 0xf7, 0xd4, 0x1b, + 0xb9, 0xce, 0x62, 0xbc, 0x58, 0x20, 0x57, 0x4c, 0xf9, 0xa6, 0x6b, 0xf4, 0xe1, 0x29, 0x93, + 0x15, 0x0e, 0xce, 0x35, 0x7e, 0x02, 0x9f, 0x18, 0xf4, 0xa2, 0x4f, 0x49, 0x69, 0x4a, 0xd0, + 0x98, 0xdd, 0x28, 0x6e, 0x5f, 0xf5, 0x3e, 0xe2, 0xa3, 0x56, 0xe3, 0x8c, 0xcd, 0xf9, 0x07, + 0x8d, 0x44, 0x42, 0x69, 0xf7, 0x41, 0x44, 0x54, 0x13, 0xa7, 0x35, 0x2e, 0x87, 0xc3, 0x0e, + 0x10, 0x00, 0xa7, 0x14, 0xc1, 0x3b, 0x36, 0xfb, 0x37, 0xee, 0x25, 0xc2, 0xa5, 0x04, 0xcb, + 0x7e, 0x26, 0x27, 0xe8, 0x61, 0xb9, 0x33, 0x96, 0x49, 0x5b, 0x1e, 0xa2, 0x09, 0xff, 0xf8, + 0xe2, 0x13, 0x7c, 0x99, 0x34, 0x25, 0xdc, 0xdf, 0xdf, 0xc8, 0x3a, 0x8d, 0x56, 0xbb, 0xc4, + 0x18, 0xe3, 0x8a, 0x7a, 0x1b, 0x38, 0x80, 0x7e, 0x9c, 0x9d, 0x43, 0x04, 0x11, 0x47, 0x81, + 0xf0, 0x32, 0x29, 0xe6, 0x9d, 0x72, 0xc7, 0x8d, 0xff, 0x0e, 0x03, 0x6b, 0xb6, 0xa3, 0xb0, + 0x6d, 0xb6, 0x5e, 0xc7, 0x78, 0x14, 0x08, 0xea, 0xdd, 0xcb, 0x15, 0xb6, 0x1c, 0xdd, 0xe7, + 0x5a, 0x25, 0x49, 0xe7, 0xbd, 0xe7, 0x1b, 0xcc, 0x81, 0xf2, 0x32, 0x93, 0xc8, 0x27, 0xc9, + 0x5d, 0xd8, 0x89, 0x11, 0xc4, 0xf3, 0x6b, 0x37, 0x9b, 0x6d, 0x36, 0x0a, 0x8d, 0xef, 0xca, + 0x79, 0xfa, 0x6e, 0x06, 0xcf, 0x96, 0x12, 0xe3, 0x94, 0xd7, 0x69, 0x34, 0x9d, 0xf5, 0xf6, + 0xe0, 0x54, 0x77, 0xba, 0xc9, 0xe6, 0x74, 0x2a, 0xbe, 0x20, 0xc3, 0xe2, 0x05, 0xa9, 0x61, + 0x11, 0xb8, 0x16, 0x35, 0xb7, 0xad, 0xda, 0xe9, 0xed, 0x60, 0xb4, 0x13, 0xc9, 0xeb, 0x45, + 0xe3, 0xf7 + }; + static const uint8_t _aPublicExponent[] = { 0x01, 0x00, 0x01 }; + static const uint8_t _aPrivateExponent[] = + { + 0x7d, 0x66, 0x78, 0xc1, 0x39, 0xa0, 0x34, 0x0b, 0x3c, 0x28, 0xc1, 0x6e, 0x74, 0xeb, 0x34, + 0x6a, 0x8c, 0x13, 0x14, 0x5c, 0x48, 0x1d, 0x2a, 0xe5, 0xa3, 0x00, 0x6c, 0x83, 0xe0, 0xfa, + 0x60, 0x7c, 0x42, 0x3a, 0xb7, 0x7f, 0x18, 0xf3, 0xb0, 0x16, 0x58, 0x62, 0x12, 0x5a, 0x4c, + 0xa5, 0xd1, 0x5e, 0xb6, 0xd3, 0x27, 0x89, 0x86, 0x3b, 0x04, 0xb9, 0x1b, 0xd5, 0xea, 0x15, + 0xd7, 0x28, 0x16, 0xcd, 0xe1, 0x5a, 0x8a, 0x0f, 0xcd, 0x27, 0x26, 0xba, 0x26, 0x41, 0xbd, + 0x6d, 0x31, 0x58, 0x70, 0x5b, 0x63, 0x59, 0x2f, 0x2a, 0x68, 0x84, 0xaf, 0xc9, 0x57, 0x65, + 0x23, 0xa7, 0x91, 0x09, 0x82, 0x1c, 0x88, 0x99, 0x48, 0xe6, 0xe4, 0xb0, 0x01, 0x10, 0x13, + 0xa7, 0x82, 0x1f, 0x1b, 0x11, 0xd2, 0x80, 0xc1, 0xa4, 0xf0, 0x43, 0x42, 0x3f, 0x27, 0xcd, + 0xf0, 0xb8, 0x02, 0x9d, 0x7f, 0xa0, 0xa5, 0x0a, 0x0f, 0xb0, 0x68, 0xdc, 0x23, 0x46, 0x77, + 0x6b, 0x87, 0xed, 0x29, 0x5f, 0x08, 0xfc, 0xd7, 0x58, 0x0e, 0x7c, 0x85, 0x24, 0x72, 0x2b, + 0xf1, 0x1d, 0x0a, 0x86, 0xe6, 0xe6, 0x05, 0x6f, 0xef, 0x3f, 0x74, 0x6a, 0xf3, 0x53, 0x57, + 0xf1, 0x80, 0x3e, 0x0d, 0x26, 0x5f, 0xce, 0xfd, 0x20, 0x28, 0x8c, 0x61, 0xfe, 0x18, 0x23, + 0xf2, 0x1f, 0x08, 0x20, 0xd6, 0x96, 0x95, 0x32, 0x6c, 0x61, 0xd2, 0xdb, 0xf8, 0x37, 0x37, + 0x02, 0xf7, 0x54, 0x77, 0xe3, 0x8b, 0x2e, 0x59, 0xd8, 0x9c, 0x61, 0x45, 0x54, 0x95, 0x27, + 0x29, 0x16, 0x19, 0xa1, 0xae, 0x76, 0xf4, 0xbf, 0x07, 0x39, 0xc4, 0x5f, 0xac, 0xb8, 0x94, + 0x38, 0x1d, 0xfa, 0x40, 0x0e, 0x4f, 0xb8, 0xa7, 0xa1, 0xb6, 0xe5, 0xe1, 0x91, 0x86, 0x1d, + 0x7f, 0xc2, 0x3e, 0x0d, 0x55, 0x0d, 0xbc, 0x28, 0x40, 0x3d, 0xce, 0xb5, 0xc9, 0xee, 0x17, + 0xc9 + }; + static const uint8_t _aPrime1[] = + { + 0xf3, 0xd4, 0x21, 0xef, 0xb5, 0xab, 0x27, 0x40, 0xb2, 0xb2, 0x55, 0x6c, 0x9b, 0x9f, + 0x68, 0xe0, 0x41, 0x8f, 0x34, 0xa2, 0x2b, 0x53, 0x2d, 0xd4, 0x04, 0x35, 0xd4, 0x37, 0x13, + 0x88, 0x89, 0xef, 0xdd, 0x6f, 0x00, 0x49, 0xbc, 0x8a, 0x32, 0x0a, 0x86, 0x87, 0x79, 0x07, + 0x6c, 0x66, 0xfd, 0x72, 0x7b, 0x84, 0xf5, 0xdd, 0x27, 0x33, 0x87, 0xdb, 0x01, 0x89, 0xd3, + 0x4a, 0x72, 0x48, 0xba, 0xf2, 0x1a, 0x25, 0x8e, 0x6c, 0x46, 0xe4, 0x04, 0x72, 0x5c, 0x69, + 0xa5, 0x0f, 0xc9, 0x99, 0xed, 0x54, 0x64, 0xa5, 0x5f, 0xbe, 0xf4, 0x5f, 0xea, 0xc9, 0x4c, + 0x20, 0xc9, 0xda, 0xdd, 0xc2, 0x33, 0x95, 0xce, 0x3c, 0x09, 0xf9, 0x9b, 0xad, 0xca, 0x00, + 0x43, 0xeb, 0xe4, 0x19, 0xa9, 0x26, 0xb5, 0x4b, 0xa8, 0xa7, 0x35, 0xd2, 0xc1, 0x9d, 0x56, + 0x3c, 0x83, 0x32, 0x94, 0xa8, 0xee, 0xee, 0x87, 0x53 + }; + static const uint8_t _aPrime2[] = + { + 0xed, 0x4a, 0x67, 0x2c, 0xac, 0xa0, 0x96, 0x8d, 0x0e, 0xed, 0x85, 0x0a, 0x3e, 0xf1, + 0xaf, 0xe7, 0xda, 0x63, 0x04, 0x8e, 0x7a, 0x12, 0x5c, 0x15, 0x6d, 0x74, 0x63, 0x1e, 0x3c, + 0x65, 0xc5, 0x0a, 0x37, 0x44, 0xa2, 0x4b, 0x34, 0x9e, 0xaa, 0xbe, 0xce, 0x71, 0xab, 0x49, + 0x3a, 0xc0, 0x66, 0x6f, 0x13, 0xfe, 0x13, 0x54, 0x70, 0x88, 0x24, 0x25, 0x0e, 0x06, 0xde, + 0x85, 0x46, 0x5d, 0xa2, 0x5c, 0x72, 0xde, 0x57, 0x19, 0x4f, 0x33, 0x2b, 0xdb, 0xdf, 0xfb, + 0x3e, 0x86, 0xc6, 0x93, 0x52, 0xb9, 0x21, 0xc1, 0x19, 0xf5, 0xde, 0x96, 0x55, 0x30, 0x76, + 0xc4, 0x22, 0x72, 0x0c, 0x40, 0x4a, 0xa5, 0x61, 0xa4, 0xd7, 0xb4, 0xb4, 0x89, 0x30, 0xf3, + 0x06, 0xe1, 0x32, 0xd4, 0xaf, 0x72, 0x80, 0x86, 0xaa, 0x34, 0x9a, 0x64, 0x89, 0xd3, 0x80, + 0x7c, 0x4b, 0x39, 0xcb, 0x14, 0x9e, 0x66, 0x10, 0x4d + }; + static const uint8_t _aExponent1[] = + { + 0xa7, 0xdd, 0x30, 0xab, 0xf6, 0x37, 0x69, 0xe3, 0xb9, 0xe2, 0xda, 0xba, 0xd5, 0xfd, + 0x0e, 0x57, 0xed, 0xea, 0xa8, 0x82, 0xc9, 0x2f, 0x0f, 0xca, 0xfa, 0x47, 0x10, 0xde, 0x06, + 0x1d, 0xa7, 0x51, 0x32, 0xf2, 0x9b, 0x91, 0x28, 0x33, 0x40, 0x36, 0x4c, 0xdd, 0xf1, 0xad, + 0xf1, 0xac, 0x89, 0xea, 0x8a, 0x2d, 0x44, 0x93, 0x47, 0xcc, 0xcb, 0x48, 0x34, 0xab, 0xed, + 0x82, 0x40, 0x61, 0xe0, 0x0a, 0x93, 0x83, 0xad, 0xa4, 0xcf, 0xbd, 0x65, 0x6e, 0x52, 0x3f, + 0x0d, 0x3b, 0x6c, 0x41, 0x03, 0xca, 0x69, 0x2c, 0x0d, 0x59, 0xca, 0xa6, 0x4a, 0x5e, 0xe1, + 0x81, 0x65, 0x56, 0xbf, 0xfb, 0x56, 0x46, 0x59, 0x60, 0xae, 0x41, 0x61, 0x33, 0x69, 0x71, + 0x7b, 0x51, 0x68, 0x8d, 0x5e, 0x0d, 0xdf, 0x1c, 0xc2, 0x74, 0xb3, 0xb2, 0x70, 0x47, 0x60, + 0xba, 0x72, 0x5c, 0x9d, 0x4a, 0x1c, 0x8c, 0xad, 0x2f + }; + static const uint8_t _aExponent2[] = + { + 0x84, 0x99, 0x05, 0x1a, 0x93, 0xc4, 0x91, 0x1c, 0x75, 0xf1, 0x08, 0x5c, 0xf7, 0x5b, + 0x7b, 0x1e, 0xa6, 0x8c, 0x9a, 0x69, 0x3b, 0x91, 0xb2, 0xdf, 0x4e, 0x70, 0xb1, 0x4a, 0x9e, + 0x19, 0x88, 0x87, 0xf2, 0xe6, 0x69, 0x82, 0x78, 0xff, 0x09, 0x0e, 0xe2, 0xb1, 0xe6, 0x33, + 0x5f, 0x9f, 0x50, 0x1e, 0x56, 0x1f, 0xae, 0x91, 0x8a, 0xe8, 0xa8, 0xba, 0x04, 0x22, 0x96, + 0x8a, 0x07, 0x0e, 0x1f, 0xc2, 0x65, 0x76, 0x15, 0x59, 0xd1, 0x46, 0x19, 0x06, 0x1f, 0x1d, + 0x78, 0x8d, 0x3b, 0xbd, 0xeb, 0x86, 0x04, 0x74, 0xb1, 0x9b, 0x11, 0x2d, 0x14, 0xa1, 0xa6, + 0x5c, 0x67, 0x9b, 0x2f, 0x79, 0x65, 0xbd, 0x10, 0xd9, 0x5a, 0xa8, 0x62, 0x12, 0x1f, 0xc6, + 0x4e, 0x5b, 0xdd, 0x59, 0xb8, 0x48, 0xd5, 0xc5, 0x6a, 0xab, 0x46, 0x73, 0x54, 0x09, 0x5a, + 0x4d, 0x1a, 0x84, 0x4b, 0x15, 0x54, 0x86, 0x58, 0x29 + }; + static const uint8_t _aCoefficient[] = + { + 0x90, 0xba, 0x74, 0xe6, 0x17, 0xc2, 0xf9, 0x0f, 0xad, 0x57, 0x9d, 0x86, 0x78, 0x8d, + 0xa5, 0x6f, 0x86, 0xd1, 0x9a, 0xfe, 0xb6, 0x95, 0x47, 0x14, 0x0f, 0x92, 0xc9, 0xb6, 0x25, + 0x43, 0x63, 0x70, 0xdc, 0xbf, 0x83, 0x8b, 0x27, 0x7a, 0x2d, 0x3c, 0xe7, 0x50, 0xfc, 0x29, + 0x4b, 0xda, 0xbd, 0xa2, 0x38, 0x4c, 0x37, 0xfc, 0xf5, 0x9b, 0x3e, 0xbb, 0x28, 0x0f, 0xda, + 0x52, 0x06, 0x69, 0xd6, 0xf7, 0x3f, 0x2b, 0xd0, 0xae, 0xc0, 0x85, 0x85, 0xb7, 0xd9, 0xbc, + 0x3b, 0x20, 0x2d, 0xe1, 0x10, 0xc9, 0x84, 0x76, 0xbe, 0xa2, 0x6c, 0xaf, 0x36, 0x84, 0x13, + 0xfa, 0x4c, 0x80, 0x6d, 0x09, 0x89, 0xa6, 0x6d, 0xa4, 0x6b, 0x96, 0x58, 0x5f, 0x05, 0x37, + 0x70, 0xee, 0x78, 0x08, 0xc1, 0xa3, 0x26, 0xef, 0xda, 0xa4, 0x71, 0x11, 0x09, 0xcb, 0xa9, + 0x49, 0x05, 0x55, 0x2d, 0x10, 0x98, 0xca, 0xa9, 0x42 + }; + static const CryptBinaryObjT _Prime1 = { (uint8_t *)_aPrime1, sizeof(_aPrime1) }; + static const CryptBinaryObjT _Prime2 = { (uint8_t *)_aPrime2, sizeof(_aPrime2) }; + static const CryptBinaryObjT _Exponent1 = { (uint8_t *)_aExponent1, sizeof(_aExponent1) }; + static const CryptBinaryObjT _Exponent2 = { (uint8_t *)_aExponent2, sizeof(_aExponent2) }; + static const CryptBinaryObjT _Coefficient = { (uint8_t *)_aCoefficient, sizeof(_aCoefficient) }; + static const uint8_t _strRsaEncryptMessage[] = "test rsa"; + + CryptRSAT RSA, RSA2, RSA3; + CryptSha2T Sha2; + ProtoSSLPkcs1T Pkcs1; + uint8_t aHashObj[CRYPTSHA256_HASHSIZE], aSigObj[sizeof(_aModulus)]; + + // encrypt + if (CryptRSAInit(&RSA, _aModulus, sizeof(_aModulus), _aPublicExponent, sizeof(_aPublicExponent)) != 0) + { + return(1); + } + CryptRSAInitMaster(&RSA, _strRsaEncryptMessage, sizeof(_strRsaEncryptMessage)); + CryptRSAEncrypt(&RSA, 0); + ZPrintf("crypt: rsa encrypt took %ums\n", RSA.uCryptMsecs); + + // decrypt using private exponent + if (CryptRSAInit(&RSA2, _aModulus, sizeof(_aModulus), _aPrivateExponent, sizeof(_aPrivateExponent)) != 0) + { + return(1); + } + CryptRSAInitSignature(&RSA2, RSA.EncryptBlock, sizeof(_aModulus)); + CryptRSAEncrypt(&RSA2, 0); + ZPrintf("crypt: rsa decrypt took %ums\n", RSA2.uCryptMsecs); + + // verify result + if (memcmp(_strRsaEncryptMessage, RSA2.EncryptBlock+sizeof(_aModulus)-sizeof(_strRsaEncryptMessage), sizeof(_strRsaEncryptMessage)) != 0) + { + return(1); + } + + // decrypt using crt + if (CryptRSAInit2(&RSA3, sizeof(_aModulus), &_Prime1, &_Prime2, &_Exponent1, &_Exponent2, &_Coefficient) != 0) + { + return(1); + } + CryptRSAInitSignature(&RSA3, RSA.EncryptBlock, sizeof(_aModulus)); + CryptRSAEncrypt(&RSA3, 0); + ZPrintf("crypt: rsa decrypt using crt took %ums\n", RSA3.uCryptMsecs); + + // verify result + if (memcmp(_strRsaEncryptMessage, RSA3.EncryptBlock+sizeof(_aModulus)-sizeof(_strRsaEncryptMessage), sizeof(_strRsaEncryptMessage)) != 0) + { + return(1); + } + + // hash the message + CryptSha2Init(&Sha2, sizeof(aHashObj)); + CryptSha2Update(&Sha2, _strRsaEncryptMessage, sizeof(_strRsaEncryptMessage)); + CryptSha2Final(&Sha2, aHashObj, sizeof(aHashObj)); + + // generate the signature + if (ProtoSSLPkcs1GenerateInit(&Pkcs1, aHashObj, sizeof(aHashObj), CRYPTHASH_SHA256, sizeof(_aModulus), &_Prime1, &_Prime2, &_Exponent1, &_Exponent2, &_Coefficient) != 0) + { + return(1); + } + ProtoSSLPkcs1GenerateUpdate(&Pkcs1, 0, aSigObj, sizeof(aSigObj)); + // verify the signature + if (ProtoSSLPkcs1Verify(aSigObj, sizeof(aSigObj), aHashObj, sizeof(aHashObj), CRYPTHASH_SHA256, _aModulus, sizeof(_aModulus), _aPublicExponent, sizeof(_aPublicExponent)) != 0) + { + return(1); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestNist + + \Description + Test the CryptNist module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else test failed + + \Version 11/29/2018 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestNist(ZContext *argz, int32_t argc, char *argv[]) +{ + uint8_t bResult = TRUE; + CryptNistDhT Alice, Bob; + CryptEccPointT AlicePublic, BobPublic, AliceShared, BobShared; + + // initialize the secp256r1 states + CryptNistInitDh(&Alice, CRYPTCURVE_SECP256R1); + CryptNistInitDh(&Bob, CRYPTCURVE_SECP256R1); + + // generate public key + while (CryptNistPublic(&Alice, &AlicePublic, NULL) > 0) + ; + ZPrintf("crypt: alice public secp256r1 time=%u\n", Alice.Ecc.uCryptUSecs); + + while (CryptNistPublic(&Bob, &BobPublic, NULL) > 0) + ; + ZPrintf("crypt: bob public secp256r1 time=%u\n", Bob.Ecc.uCryptUSecs); + + // generate shared secrets + while (CryptNistSecret(&Alice, &BobPublic, &AliceShared, NULL) > 0) + ; + ZPrintf("crypt: alice shared secp256r1 time=%u\n", Alice.Ecc.uCryptUSecs); + + while (CryptNistSecret(&Bob, &AlicePublic, &BobShared, NULL) > 0) + ; + ZPrintf("crypt: bob shared secp256r1 time=%u\n", Bob.Ecc.uCryptUSecs); + + // validate secp256r1 results + bResult &= memcmp(AliceShared.X.aData, BobShared.X.aData, sizeof(AliceShared.X.aData)) == 0; + bResult &= memcmp(AliceShared.Y.aData, BobShared.Y.aData, sizeof(AliceShared.Y.aData)) == 0; + + // initialize the secp384r1 states + CryptNistInitDh(&Alice, CRYPTCURVE_SECP384R1); + CryptNistInitDh(&Bob, CRYPTCURVE_SECP384R1); + + // generate public key + while (CryptNistPublic(&Alice, &AlicePublic, NULL) > 0) + ; + ZPrintf("crypt: alice public secp384r1 time=%u\n", Alice.Ecc.uCryptUSecs); + + while (CryptNistPublic(&Bob, &BobPublic, NULL) > 0) + ; + ZPrintf("crypt: bob public secp384r1 time=%u\n", Bob.Ecc.uCryptUSecs); + + // generate shared secrets + while (CryptNistSecret(&Alice, &BobPublic, &AliceShared, NULL) > 0) + ; + ZPrintf("crypt: alice shared secp384r1 time=%u\n", Alice.Ecc.uCryptUSecs); + + while (CryptNistSecret(&Bob, &AlicePublic, &BobShared, NULL) > 0) + ; + ZPrintf("crypt: bob shared secp384r1 time=%u\n", Bob.Ecc.uCryptUSecs); + + // validate secp384r1 results + bResult &= memcmp(AliceShared.X.aData, BobShared.X.aData, sizeof(AliceShared.X.aData)) == 0; + bResult &= memcmp(AliceShared.Y.aData, BobShared.Y.aData, sizeof(AliceShared.Y.aData)) == 0; + + return(bResult ? 0 : 1); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestDsa + + \Description + Test the ECDSA signing / verify operations + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else test failed + + \Version 01/17/2019 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestDsa(ZContext *argz, int32_t argc, char *argv[]) +{ + int32_t iCurve, iFailed = 0; + + for (iCurve = 0; iCurve < (signed)(sizeof(_Crypt_DsaTests)/sizeof(_Crypt_DsaTests[0])); iCurve += 1) + { + const CryptTestDsaT *pTestData = &_Crypt_DsaTests[iCurve]; + int32_t iTest; + CryptEccPointT PublicKey; + CryptBnT PrivateKey; + + CryptNistPointInitFrom(&PublicKey, pTestData->aPublicKey, (pTestData->iCurveSize*2)+1); + CryptBnInitFrom(&PrivateKey, -1, pTestData->aPrivateKey, pTestData->iCurveSize); + + for (iTest = 0; iTest < (signed)(sizeof(pTestData->aCases)/sizeof(pTestData->aCases[0])); iTest += 1) + { + const CryptTestDsaCaseT *pCase = &pTestData->aCases[iTest]; + int32_t iHashSize = CryptHashGetSize(pCase->eHashType), iSignResult, iVerifyResult; + + CryptNistDsaT Sign, Verify; + CryptEccPointT Signature; + const CryptHashT *pHash; + uint8_t aHash[CRYPTHASH_MAXDIGEST], aSignature[97]; + + // initialize dsa state + CryptNistInitDsa(&Sign, pTestData->eCurveType); + CryptNistInitDsa(&Verify, pTestData->eCurveType); + + // hash the message + if ((pHash = CryptHashGet(pCase->eHashType)) != NULL) + { + uint8_t aHashState[CRYPTHASH_MAXSTATE]; + pHash->Init(aHashState, iHashSize); + pHash->Update(aHashState, pCase->aMessage, (signed)strlen((const char *)pCase->aMessage)); + pHash->Final(aHashState, aHash, iHashSize); + } + + // generate and check signature for expected result + while (CryptNistSign(&Sign, &PrivateKey, aHash, iHashSize, &Signature, NULL) > 0) + ; + CryptBnFinal(&Signature.X, aSignature, Sign.Ecc.iSize); + CryptBnFinal(&Signature.Y, aSignature+Sign.Ecc.iSize, Sign.Ecc.iSize); + iSignResult = memcmp(aSignature, pCase->aSignature, Sign.Ecc.iSize*2); + ZPrintf("crypt: sign curve=%d, hash=%d, message=%s, time=%u\n", + pTestData->eCurveType, pCase->eHashType, pCase->aMessage, Sign.Ecc.uCryptUSecs); + + // verify signature + while ((iVerifyResult = CryptNistVerify(&Verify, &PublicKey, aHash, iHashSize, &Signature, NULL)) > 0) + ; + ZPrintf("crypt: verify curve=%d, hash=%d, message=%s, time=%u\n", + pTestData->eCurveType, pCase->eHashType, pCase->aMessage, Verify.Ecc.uCryptUSecs); + + if ((iSignResult != 0) || (iVerifyResult != 0)) + { + iFailed += 1; + } + } + } + + return(iFailed); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestMont + + \Description + Test the CryptMont module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else test failed + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestMont(ZContext *argz, int32_t argc, char *argv[]) +{ + #if DIRTYCODE_DEBUG + const uint8_t aAlicePrivate25519[] = { + 0x77, 0x07, 0x6d, 0x0a, 0x73, 0x18, 0xa5, 0x7d, 0x3c, 0x16, 0xc1, 0x72, 0x51, 0xb2, 0x66, 0x45, + 0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a, 0xb1, 0x77, 0xfb, 0xa5, 0x1d, 0xb9, 0x2c, 0x2a + }; + const uint8_t aBobPrivate25519[] = { + 0x5d, 0xab, 0x08, 0x7e, 0x62, 0x4a, 0x8a, 0x4b, 0x79, 0xe1, 0x7f, 0x8b, 0x83, 0x80, 0x0e, 0xe6, + 0x6f, 0x3b, 0xb1, 0x29, 0x26, 0x18, 0xb6, 0xfd, 0x1c, 0x2f, 0x8b, 0x27, 0xff, 0x88, 0xe0, 0xeb + }; + const uint8_t aAlicePublic25519[] = { + 0x85, 0x20, 0xf0, 0x09, 0x89, 0x30, 0xa7, 0x54, 0x74, 0x8b, 0x7d, 0xdc, 0xb4, 0x3e, 0xf7, 0x5a, + 0x0d, 0xbf, 0x3a, 0x0d, 0x26, 0x38, 0x1a, 0xf4, 0xeb, 0xa4, 0xa9, 0x8e, 0xaa, 0x9b, 0x4e, 0x6a + }; + const uint8_t aBobPublic25519[] = { + 0xde, 0x9e, 0xdb, 0x7d, 0x7b, 0x7d, 0xc1, 0xb4, 0xd3, 0x5b, 0x61, 0xc2, 0xec, 0xe4, 0x35, 0x37, + 0x3f, 0x83, 0x43, 0xc8, 0x5b, 0x78, 0x67, 0x4d, 0xad, 0xfc, 0x7e, 0x14, 0x6f, 0x88, 0x2b, 0x4f + }; + const uint8_t aSharedSecrete25519[] = { + 0x4a, 0x5d, 0x9d, 0x5b, 0xa4, 0xce, 0x2d, 0xe1, 0x72, 0x8e, 0x3b, 0xf4, 0x80, 0x35, 0x0f, 0x25, + 0xe0, 0x7e, 0x21, 0xc9, 0x47, 0xd1, 0x9e, 0x33, 0x76, 0xf0, 0x9b, 0x3c, 0x1e, 0x16, 0x17, 0x42 + }; + const uint8_t aAlicePrivate448[] = { + 0x9a, 0x8f, 0x49, 0x25, 0xd1, 0x51, 0x9f, 0x57, 0x75, 0xcf, 0x46, 0xb0, 0x4b, 0x58, 0x00, 0xd4, + 0xee, 0x9e, 0xe8, 0xba, 0xe8, 0xbc, 0x55, 0x65, 0xd4, 0x98, 0xc2, 0x8d, 0xd9, 0xc9, 0xba, 0xf5, + 0x74, 0xa9, 0x41, 0x97, 0x44, 0x89, 0x73, 0x91, 0x00, 0x63, 0x82, 0xa6, 0xf1, 0x27, 0xab, 0x1d, + 0x9a, 0xc2, 0xd8, 0xc0, 0xa5, 0x98, 0x72, 0x6b + }; + const uint8_t aBobPrivate448[] = { + 0x1c, 0x30, 0x6a, 0x7a, 0xc2, 0xa0, 0xe2, 0xe0, 0x99, 0x0b, 0x29, 0x44, 0x70, 0xcb, 0xa3, 0x39, + 0xe6, 0x45, 0x37, 0x72, 0xb0, 0x75, 0x81, 0x1d, 0x8f, 0xad, 0x0d, 0x1d, 0x69, 0x27, 0xc1, 0x20, + 0xbb, 0x5e, 0xe8, 0x97, 0x2b, 0x0d, 0x3e, 0x21, 0x37, 0x4c, 0x9c, 0x92, 0x1b, 0x09, 0xd1, 0xb0, + 0x36, 0x6f, 0x10, 0xb6, 0x51, 0x73, 0x99, 0x2d + }; + const uint8_t aAlicePublic448[] = { + 0x9b, 0x08, 0xf7, 0xcc, 0x31, 0xb7, 0xe3, 0xe6, 0x7d, 0x22, 0xd5, 0xae, 0xa1, 0x21, 0x07, 0x4a, + 0x27, 0x3b, 0xd2, 0xb8, 0x3d, 0xe0, 0x9c, 0x63, 0xfa, 0xa7, 0x3d, 0x2c, 0x22, 0xc5, 0xd9, 0xbb, + 0xc8, 0x36, 0x64, 0x72, 0x41, 0xd9, 0x53, 0xd4, 0x0c, 0x5b, 0x12, 0xda, 0x88, 0x12, 0x0d, 0x53, + 0x17, 0x7f, 0x80, 0xe5, 0x32, 0xc4, 0x1f, 0xa0 + }; + const uint8_t aBobPublic448[] = { + 0x3e, 0xb7, 0xa8, 0x29, 0xb0, 0xcd, 0x20, 0xf5, 0xbc, 0xfc, 0x0b, 0x59, 0x9b, 0x6f, 0xec, 0xcf, + 0x6d, 0xa4, 0x62, 0x71, 0x07, 0xbd, 0xb0, 0xd4, 0xf3, 0x45, 0xb4, 0x30, 0x27, 0xd8, 0xb9, 0x72, + 0xfc, 0x3e, 0x34, 0xfb, 0x42, 0x32, 0xa1, 0x3c, 0xa7, 0x06, 0xdc, 0xb5, 0x7a, 0xec, 0x3d, 0xae, + 0x07, 0xbd, 0xc1, 0xc6, 0x7b, 0xf3, 0x36, 0x09 + }; + const uint8_t aSharedSecret448[] = { + 0x07, 0xff, 0xf4, 0x18, 0x1a, 0xc6, 0xcc, 0x95, 0xec, 0x1c, 0x16, 0xa9, 0x4a, 0x0f, 0x74, 0xd1, + 0x2d, 0xa2, 0x32, 0xce, 0x40, 0xa7, 0x75, 0x52, 0x28, 0x1d, 0x28, 0x2b, 0xb6, 0x0c, 0x0b, 0x56, + 0xfd, 0x24, 0x64, 0xc3, 0x35, 0x54, 0x39, 0x36, 0x52, 0x1c, 0x24, 0x40, 0x30, 0x85, 0xd5, 0x9a, + 0x44, 0x9a, 0x50, 0x37, 0x51, 0x4a, 0x87, 0x9d + }; + #endif + + CryptMontT Alice, Bob; + CryptEccPointT AlicePublic, BobPublic, AliceShared, BobShared; + uint8_t bResult = TRUE; + + // initialize the context (x25519) + CryptMontInit(&Alice, CRYPTCURVE_X25519); + CryptMontInit(&Bob, CRYPTCURVE_X25519); + + #if DIRTYCODE_DEBUG + ZPrintf("crypt: validating x25519 using test vectors\n"); + CryptMontSetPrivateKey(&Alice, aAlicePrivate25519); + CryptMontSetPrivateKey(&Bob, aBobPrivate25519); + #endif + + // generate alice public + while (CryptMontPublic(&Alice, &AlicePublic, NULL) > 0) + ; + #if DIRTYCODE_DEBUG + bResult &= memcmp(aAlicePublic25519, AlicePublic.X.aData, 32) == 0; + #endif + ZPrintf("crypt: alice public x25519 time=%u result=%s\n", Alice.uCryptUsecs, bResult ? "SUCCESS" : "FAILURE"); + + // generate bob public + while (CryptMontPublic(&Bob, &BobPublic, NULL) > 0) + ; + #if DIRTYCODE_DEBUG + bResult &= memcmp(aBobPublic25519, BobPublic.X.aData, 32) == 0; + #endif + ZPrintf("crypt: bob public x25519 time=%u reuslt=%s\n", Bob.uCryptUsecs, bResult ? "SUCCESS" : "FAILURE"); + + // generate alice shared + while (CryptMontSecret(&Alice, &BobPublic, &AliceShared, NULL) > 0) + ; + + // generate bob shared + while (CryptMontSecret(&Bob, &AlicePublic, &BobShared, NULL) > 0) + ; + #if DIRTYCODE_DEBUG + bResult &= memcmp(aSharedSecrete25519, AliceShared.X.aData, 32) == 0; + bResult &= memcmp(aSharedSecrete25519, BobShared.X.aData, 32) == 0; + #else + bResult &= memcmp(AliceShared.X.aData, BobShared.X.aData, sizeof(AliceShared.X.aData)) == 0; + #endif + + ZPrintf("crypt: alice shared x25519 time=%u result=%s\n", Alice.uCryptUsecs, bResult ? "SUCCESS" : "FAILURE"); + ZPrintf("crypt: bob shared x25519 time=%u result=%s\n", Bob.uCryptUsecs, bResult ? "SUCCESS" : "FAILURE"); + + // initialize the context (x448) + CryptMontInit(&Alice, CRYPTCURVE_X448); + CryptMontInit(&Bob, CRYPTCURVE_X448); + + #if DIRTYCODE_DEBUG + ZPrintf("crypt: validating x448 using test vectors\n"); + CryptMontSetPrivateKey(&Alice, aAlicePrivate448); + CryptMontSetPrivateKey(&Bob, aBobPrivate448); + #endif + + // generate alice public + while (CryptMontPublic(&Alice, &AlicePublic, NULL) > 0) + ; + #if DIRTYCODE_DEBUG + bResult &= memcmp(aAlicePublic448, AlicePublic.X.aData, 56) == 0; + #endif + ZPrintf("crypt: alice public x448 time=%u result=%s\n", Alice.uCryptUsecs, bResult ? "SUCCESS" : "FAILURE"); + + // generate bob public + while (CryptMontPublic(&Bob, &BobPublic, NULL) > 0) + ; + #if DIRTYCODE_DEBUG + bResult &= memcmp(aBobPublic448, BobPublic.X.aData, 56) == 0; + #endif + ZPrintf("crypt: bob public x448 time=%u result=%s\n", Bob.uCryptUsecs, bResult ? "SUCCESS" : "FAILURE"); + + // generate alice shared + while (CryptMontSecret(&Alice, &BobPublic, &AliceShared, NULL) > 0) + ; + // generate bob shared + while (CryptMontSecret(&Bob, &AlicePublic, &BobShared, NULL) > 0) + ; + #if DIRTYCODE_DEBUG + bResult &= memcmp(aSharedSecret448, AliceShared.X.aData, 56) == 0; + bResult &= memcmp(aSharedSecret448, BobShared.X.aData, 56) == 0; + #else + bResult &= memcmp(AliceShared.X.aData, BobShared.X.aData, sizeof(AliceShared.X.aData)) == 0; + #endif + + ZPrintf("crypt: alice shared x448 time=%u result=%s\n", Alice.uCryptUsecs, bResult ? "SUCCESS" : "FAILURE"); + ZPrintf("crypt: bob shared x448 time=%u result=%s\n", Bob.uCryptUsecs, bResult ? "SUCCESS" : "FAILURE"); + + return(bResult ? 0 : 1); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestRand + + \Description + Test the CryptRand module + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else test failed + + \Version 01/24/2019 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestRand(ZContext *argz, int32_t argc, char *argv[]) +{ + static uint8_t aTest[2097152]; + int32_t iSize = 16, iIter = 1; + + for (iIter = 1; iSize <= (signed)sizeof(aTest); iIter += 1, iSize <<= 1) + { + uint64_t uTick = NetTickUsec(); + CryptRandGet(aTest, iSize); + ZPrintf("crypt: rand %d (size=%d) took %u\n", + iIter, iSize, NetTickDiff(NetTickUsec(), uTick)); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptAesDecryptPrivateKey + + \Description + Decrypt a base64 encoded cipher text + + \Input iKeyLen - size of the key (16, 32) + \Input *pPass - password uses to encrypt + \Input *pIv - generated IV + \Input *pEncryptedPem - cipher text we are decrypting (base64 encoded) + \Input *pDecryptedPem - plain text we are validating against (base64 encoded) + + \Output + int32_t - zero=success, else test failed + + \Version 03/29/2019 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptAesDecryptPrivateKey(int32_t iKeyLen, const char *pPass, const uint8_t *pIv, const char *pEncryptedPem, const char *pDecryptedPem) +{ + CryptAesT Aes; + CryptMD5T MD5; + uint8_t aKey[32]; + int32_t iKeySize = 0, iPadSize, iPad, iPadStart, iBytesWritten; + char aPrivateKeyDER[2048], aPrivateKeyPEM[2048]; + + // validate key length is valid + if ((iKeyLen != 16) && (iKeyLen != 32)) + { + return(1); + } + + /* The algorithm hashes the password to generate the key of the appropriate size. This is based on the algorithm used in OpenSSL which is not standard. + https://www.openssl.org/docs/man1.0.2/man3/EVP_BytesToKey.html + OpenSSL calls their function with MD5 and count as 1 which is exactly how our function behaves. */ + for (iBytesWritten = 0; iBytesWritten < iKeyLen; iBytesWritten += MD5_BINARY_OUT) + { + CryptMD5Init(&MD5); + + // if we have written bytes add in the previous output + if (iBytesWritten > 0) + { + CryptMD5Update(&MD5, aKey, MD5_BINARY_OUT); + } + CryptMD5Update(&MD5, pPass, (signed)strlen(pPass)); + CryptMD5Update(&MD5, pIv, 8); // the algorithm uses 8 bytes of the IV as salt + CryptMD5Final(&MD5, aKey+iBytesWritten, iKeyLen-iBytesWritten); + } + + // decrypt the private key + CryptAesInit(&Aes, aKey, iKeyLen, CRYPTAES_KEYTYPE_DECRYPT, pIv); + iKeySize = Base64Decode2((signed)strlen(pEncryptedPem), pEncryptedPem, aPrivateKeyDER); + CryptAesDecrypt(&Aes, (uint8_t *)aPrivateKeyDER, iKeySize); + + // verify the padding, remove padding if successful + iPadSize = aPrivateKeyDER[iKeySize-1]; + for (iPad = 0, iPadStart = iKeySize-iPadSize; iPad < iPadSize; iPad += 1) + { + if (aPrivateKeyDER[iPadStart+iPad] == iPadSize) + { + continue; + } + ZPrintf("crypt: aes padding is invalid"); + return(1); + } + iKeySize -= iPadSize; + + // base64 encode to validate + Base64Encode2(aPrivateKeyDER, iKeySize, aPrivateKeyPEM, sizeof(aPrivateKeyPEM)); + return(memcmp(aPrivateKeyPEM, pDecryptedPem, strlen(pDecryptedPem))); +} + +/*F********************************************************************************/ +/*! + \Function _CmdCryptTestAes + + \Description + Test the CryptAes module by decrypting private keys generated by OpenSSL + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - zero=success, else test failed + + \Version 03/29/2019 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CmdCryptTestAes(ZContext *argz, int32_t argc, char *argv[]) +{ + // DEK-Info: AES-128-CBC,40EF2D13FDB65054775F4FD1AF4E3D24 + static const uint8_t _strIv128[] = { 0x40, 0xEF, 0x2D, 0x13, 0xFD, 0xB6, 0x50, 0x54, 0x77, 0x5F, 0x4F, 0xD1, 0xAF, 0x4E, 0x3D, 0x24 }; + static const char _strEncrypted128[] = "8xdgF3zSj27fwsQc0QiNrWNY8dN5fqw/olTn9URQSt2szU4P/uDm12/lVJeXj/errhq9tSZmJEdFTPYPKfrBRWSkJdzRy3LFxCR/RhgNI96UOdxqhhTrAAU7F/Zy5rp7tCG/KhiB5E2IAaxrsF4Ver+XfI77UV+6iTIqgEPJqNyLt8WFtm22qXgkWg412+IxEcNk4yuohyDyNR2XYry9KXjZlc3WjFBYPy++//hOR4NEBqVB4bxHukaS8b10CxXjkFwKI+t4CTjE0pJ6o3kt2lLHzCxj9z9PhJZOX3o2ail7aiGSNeA1dAgx3TPP8iVn7K6yFWrZBa9ysTjWxHpJyGLdE+wPgwQcCOwxRT/HZqoi4doa1g5d7C+g9lycVlBqy9G5xUdTA/ANHOXhFuisjaeFAIifX1veITdq4cTEjwzj6vGPqG8TVxoEZjnkS7tr2g01HdWsvMDTGIU5ZGMVkZESbQr1NfIh1+MgFfCFB22LH0zajgmutU8N5V/FOvvP3vWAW/PAtYoubyethUYtJPEAFnTtBTvhyT3m94njr1BdJdsurCymXUkk2zYj2uPEMpRatNV6YgiODP3yWzGjl3NRDvhbVcjgNfpEozfLh8qOta/AO30azD4r48MD/JiodzA905KdcCfHQGOC8mJ+g2UK8BIv2+OGIPFoy1nUFFHl/ZPAfkm8XlxPN+avfR92bSf/ZOLhte/yliGTRlFVPn7dTzNpbsxGGQ1vK+HL7otUgQSAknIrN74G5zlnKcdz4VUG03Lj2gG53en+ijmSTqVUM+J7j65C2ssnBaeRu24azpc0ON+8bieS4Ii0PvEwk+E+4krHoBH7xRm4dSDl/gkFwPT2DbTF7SaztNCtR/HlpLxCBMBJvD9dHu4tKCa+eTyQbbnJ2IWPy4x7JN3B2EGneJhApNJKRENBpDhABpv+wt6g07bQPwlCKysn1YKKjHaIV9uAjzdRNTJufzL+phmwZ6Au7DD6wRyur5VbcSfjcCW8K1PdnxdUBiiQ07/OfIwht5GL+rPz4M8eu0Wd97lI42Alk3ui5gu00BJ+DoSkkyXx8WTiOp1U7emgTURlWjLdf8sqH9AoZQFI9J8kCeP9jHzHOB5d77PGbt7LxTvxAZO0l0SRTrj6PYj+nmMyyBv8nw4+U1Ww0WsShYnkJV4BO1cT6x2/nkZlASoG/iFtlisJQOq7Maz+R9HZIvCHeliqdvnf/fjyicDbVAmOc7BJvCINcmICmTqvwqrN1r5fUKAr9jwJORbaLA0i6reZ4xHVySfK9bdkgeg5Nxm8X/eUt57s6FGJnJKpCYxN8m6mzsuY+1uhIbSZTrrchoPFWk5MrBku1XfvKRDxFaQMpW+MxyMRqvoGJvtx00iK1EC1GYtZtqUx5HZ1eTyCvX5CoY+z7Kc3TBivbOs1YxQCo4ID3YPV7uErW7+OFGshOuamZeyzTHTC0FlxF+DWGE35URjt7NuggEFFSS33my7j6GB2eC6VnIKGfzobIpN9DbheJgQegilV21LsZJkatfkrHQ0XPyAYd7GbzG9bjg2/Q3mNMqQ5rNJGrsH0G2+ARJBHDOpMXx3bgRKitgVRwxau"; + // DEK-Info: AES-256-CBC,A530A7D78DA83CB609551E79A920E01D + static const uint8_t _strIv256[] = { 0xA5, 0x30, 0xA7, 0xD7, 0x8D, 0xA8, 0x3C, 0xB6, 0x09, 0x55, 0x1E, 0x79, 0xA9, 0x20, 0xE0, 0x1D }; + static const char _strEncrypted256[] = "QKzr7/7Ig3G4h+Gw6opJRRykoMnP7im3kzVV3kVLPj8ZEOa59CjfzERo2rC2Qyh1klkNHogky1rYKF8IHPXdYquVgCqlN2mspr0dnDO44HsIAuVhtM2UsFH0V80ePbl9W8PaMjR+YYqXanLkVsln+xS87ODUptcbLJnSwF0+nWwyR1b71tuMjV28L4i8sVwaGQMJbWQTmyQjV+dNgLO9+JCw7Nrz+rypnPQ4OWfE5b5hfqc9LKxDJsV5IuJ76+7O4DyURyP4+3kjTUbTz/FERao6/CSSlaQShXNsDs8oV1nz5QfkgZdMFshNr994BudgAwb1Xhnfp6A5+4ZhxpSB/Z/e0uItaGJzcmBcLQZv/FXuOM337egUqWo67nTFEHtUnSlsLFZXx4+lpOMN/y0fFJXJOTly3eaOnyn83CsExZansLQeNXwapi4s4L+KZGnEgRp8ouAwkRs7YFa5B+qd4bxTJe7hNabqzMH4RcOFbSW2esyuWu8tcN831nRHI+i76KaGjz2zerWmVjbAOAKJDynRGnZ0wGtWix4mOiciDhSGe95OTP/ayFbIpJAgGdyp3/AA/JSPk7NqBYEqkyELxzRvsUYJdx86tCTALK7z5Cz7CKVjoaksRLrGurphQbPBMY+SRSlI7zEIg3DmtGJh6+pbJ0osV8bK2ffprfm75pod2EDYpFNCLxGWwkQyBQ0oKHs2vIl84Yv087xaE7bcqkIAcr5rilFDUfQhIL8Rdq4A1C0LBNWOkdlCoY0c2+RRU84F6XFm4FfptJkdAvEflMYH8/wfmrxNGj/NzgFsBKSJQvun9C+VMobRgxd0bxZoRH3Gnzk074D1c4kieWRQTfD2zGBmZB/e6SS2TMrRBCsIjmqXC0SyaxW+49aEa4PuDME+udNobAjVKxPh3Mmaxd94D0MhL2TRA1ATDyKJ/vdH5dvem9gcveNk5ZdQRswiJsmiND1RM7Gibbx9vM9wUU1RxI1AuVnM/DqxJ9dfktDb1qWcNKaBf/PrFjRrrK4gsmGJdcuCn935o6H+1/84YwZBnTkmLpjITxLIP5ee6rmZPqLKlD+1c0RDhM/CEDUqAymjw7YPC3RAgcXqK3h3AXj8T5e/na6izS79SuJoQc/zH/9cFRIC0PDTGVefuja+QagIA6zCSU4dnLKB/5JeykaFDnA6lPbZWqWkrjXCfbqXfXNGZg+8EyP6+EnJt33alcouFjnW4N4ZdzbjV08p6g8+MR72lJqMtVjdenl3ZTp8+0NbVU1qNTzPEOazsg2txKICTIAhrJ7hzk1peHrhTg9DBkgngSedFsuOZxCI3PtVEWPJbGH289vjqy9GBGhHkHXW748CnpxGKgHtG/uhJAA6oS7sjVUhkwaw4uOfREmPczokJeolf2QZIPHTkMWaN0SFmSGa6i1bMeFXSGxVLKDhWS0xZFWBwBr1EjgPA13hgXpxZPJ0xIoXTPzXRhpU/k4ALAhw13wYGIARdLJQWV8ttniSV/4NENo0wNX34iWUKaNY4xe6YGP8G4MSKxqj4t6gIgKMbss9EPUR13S6GjphukLJF6bp/w2MeBU9XW24aGHf9LLNk27ORSmyahGe"; + // decrypted pem + static const char _strDecrypted[] = "MIIEpAIBAAKCAQEAqegt555/R+0iotSoCDVF2Hnk8Z++Q05rY9mDep2On6Z7IEALqt1YTm46JOlyBQ3DocwUTO6evfxipEe1drJ5j0nxcHAuBQFv7QqCIWFQCNBrmAgAjtMXWVBWRVPx4Bxz6J57JFQWnsBVN3o6qXEmsvu1AQ5BLSnoJkgfVNUooAw4fumrcxrJniL9HWefch+5Twl5QBAq4pcMImy2j5CoGAinf3CMeewfj0gx71zSQrAh79stTvNXhKcll7+RXzQzSJ8x4T2SCnRqZoAkCm57dHHYPlUqMVpo5rp4+cvM8ThnGix3Bm3vHmo3ECTXmxWZnqVnyrW+xLrhwAng23Km5wIDAQABAoIBABoeqmfwEssg5nE31fklD26/FnYaw6ofNR0thCvmatWaR1Vm4yRKUNgF1AR2quOiARAvinNlP5wfX563s2ri9xMTH/3UEQ8N8IH1Wpn9Fsu0FsZRfJx4UR+W3RxdUlLMrPj23CFyl+Oh17fmn3wK/BMJ5QKbiWODDMBN2Bs3hb5MwoZPrNu2bDgUIGHEKKNO8JTeaQaL06NsiYyZ06cONTxVRSzbA6/ItcyaNURn3s8LWwq/Nw9CRE/sH8DngTD3nPcTCl9IW5Ly1Khktsps8FlIsPIcoGXxaYepi+EEEhn72AnC37tc5Hhy0raeR/V70SyQzBnJ2CpDj+WTeye3sWkCgYEA0ljKLDYNvSrTezSPPbbCk9+lvZoDIgtJBn7FERCH6wjAPi91+DsTk8UMtSUoOoRuA3CA8kG+t0N78+kpQv8GOtRynJdLB3XZ3bIao98Uy6GwihuKhlIw8aaammtKv4M/a+c9IAB6S92A32HnN8p3MdkKjH+Oe53U/dPnN4aVDj0CgYEAzsh79SdenVkAxwAhx4KcI5XIj2/1PyJ2HmjzFKPbHwZTbRhg0Bb+aqxtSxLL84YzbidG36ROoXgrR03/1D75c3GBintMpZU6iZLYuk105/JwjFfbeT7iVTMwbCyt2KceqbIPfuj4Fq4QHPk/3v0dyqFYrGx3biRWrb75Jja73/MCgYEAjJBfEipRBg+fYV7fNftyjcTPiiNoYpCuDRQb9upYGb0wUp3+tJIt4/qzIj4hYvFqXwwIb1t16hvDslwmganQbPHZYUFNF0AvBhJl8Qo7aFS1l6UN7fLw2Btghz6Zpd88O4w8ca1ADICKBTz0eXjoLDyA6yC2g28WjUTU52sdInUCgYBcx21+wKxeExobL774Qm0GNmVnnkc+jZrL6DDw2NgvImp6L474ruh2OmlLXuKtoFAhI3RUUeeJ4V4hvyiDNcI8/veth8cLIFrEcWPWq3xBufCvt5fc3c81hSM71gwmgk1qvF7hhWwS2QSXy2nSBmXAjgY4Tu6DN8DAckhd0f8X6wKBgQC2rHX1Z81G4d/ztPRx2FvhOqLMZSeMSzxZMxEWJH52MszQUlhHC7q32MiMkRmeqk9rwUQV2vCNljBBwdJuIV0OeSjwVfKznEOCL6vwyOEnVqxGo3IJhVPFFxxtEjhIOKznIDPzqdEfPqmzKn1AqRfXyizk9iucm4WbO7mvY59m8Q=="; + + int32_t iResult = 0; + // test aes128 + iResult |= _CmdCryptAesDecryptPrivateKey(16, "device.key", _strIv128, _strEncrypted128, _strDecrypted); + // test aes256 + iResult |= _CmdCryptAesDecryptPrivateKey(32, "device.key", _strIv256, _strEncrypted256, _strDecrypted); + return(iResult); +} + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdCrypt + + \Description + Test the crypto modules + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output standard return value + + \Version 01/11/2013 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdCrypt(ZContext *argz, int32_t argc, char *argv[]) +{ + // populate random buffer + CryptRandGet(_Crypt_aLargeRandom, sizeof(_Crypt_aLargeRandom)); + + if (argc < 2) + { + int32_t iFailed = 0; + iFailed += _CmdCryptTestArc4(argz, argc, argv); + iFailed += _CmdCryptTestChaCha(argz, argc, argv); + iFailed += _CmdCryptTestGcm(argz, argc, argv); + iFailed += _CmdCryptTestHmac(argz, argc, argv); + iFailed += _CmdCryptTestMurmurHash3(argz, argc, argv); + iFailed += _CmdCryptTestMD5(argz, argc, argv); + iFailed += _CmdCryptTestSha1(argz, argc, argv); + iFailed += _CmdCryptTestSha2(argz, argc, argv); + iFailed += _CmdCryptTestRSA(argz, argc, argv); + iFailed += _CmdCryptTestMont(argz, argc, argv); + iFailed += _CmdCryptTestNist(argz, argc, argv); + iFailed += _CmdCryptTestDsa(argz, argc, argv); + iFailed += _CmdCryptTestRand(argz, argc, argv); + iFailed += _CmdCryptTestAes(argz, argc, argv); + + ZPrintf("%s: %d tests failed\n", argv[0], iFailed); + } + else if (strcmp(argv[1], "rc4") == 0) + { + ZPrintf("%s: rc4 test %s\n", argv[0], _CmdCryptTestArc4(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "chacha20") == 0) + { + ZPrintf("%s: chacha20 test %s\n", argv[0], _CmdCryptTestChaCha(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "gcm") == 0) + { + ZPrintf("%s: gcm test %s\n", argv[0], _CmdCryptTestGcm(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "hmac") == 0) + { + ZPrintf("%s: hmac test %s\n", argv[0], _CmdCryptTestHmac(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "murmur3") == 0) + { + ZPrintf("%s: murmur3 test %s\n", argv[0], _CmdCryptTestMurmurHash3(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "md5") == 0) + { + ZPrintf("%s: md5 test %s\n", argv[0], _CmdCryptTestMD5(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "sha1") == 0) + { + ZPrintf("%s: sha1 test %s\n", argv[0], _CmdCryptTestSha1(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "sha2") == 0) + { + ZPrintf("%s: sha2 test %s\n", argv[0], _CmdCryptTestSha2(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "rsa") == 0) + { + ZPrintf("%s: rsa test %s\n", argv[0], _CmdCryptTestRSA(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "mont") == 0) + { + ZPrintf("%s: mont test %s\n", argv[0], _CmdCryptTestMont(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "nist") == 0) + { + ZPrintf("%s: nist test %s\n", argv[0], _CmdCryptTestNist(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "dsa") == 0) + { + ZPrintf("%s: dsa test %s\n", argv[0], _CmdCryptTestDsa(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "rand") == 0) + { + ZPrintf("%s: rand test %s\n", argv[0], _CmdCryptTestRand(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else if (strcmp(argv[1], "aes") == 0) + { + ZPrintf("%s: aes test %s\n", argv[0], _CmdCryptTestAes(argz, argc, argv) == 0 ? "succeeded" : "failed"); + } + else + { + ZPrintf("usage: %s [rc4|chacha20|gcm|hmac|murmur3|md5|sha1|sha2|rsa|mont|nist]\n", argv[0]); + ZPrintf("omitting command executes all tests in sequence\n"); + } + + return(0); +} + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/demangler.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/demangler.c new file mode 100644 index 00000000..edcd74e4 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/demangler.c @@ -0,0 +1,371 @@ +/*H********************************************************************************/ +/*! + \File demangler.c + + \Description + A simple tester of the EA.com demangler service, using ProtoMangle. + + \Copyright + Copyright (c) 2003-2005 Electronic Arts Inc. + + \Version 04/03/2003 (jbrookes) First Version +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/proto/protomangle.h" +#include "DirtySDK/game/netgameutil.h" +#include "DirtySDK/game/netgamelink.h" +#include "DirtySDK/game/netgamepkt.h" +#include "DirtySDK/comm/commudp.h" + +#include "libsample/zlib.h" + +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +#define DEMANGLER_CONN_TIMEOUT (15 * 1000) +#define DEMANGLER_MNGL_TIMEOUT (15 * 1000) +#define DEMANGLER_SHAREDSOCKETTEST (FALSE) // note: this test is a hack and is not valid on PS2 + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct DemanglerRefT +{ + ProtoMangleRefT *pMangler; + int32_t bRequestIssued; + int32_t iGamePort; + int32_t iPeerAddr; + int32_t iPeerPort; + int32_t iConnType; + int32_t iMnglStart; + int32_t iConnStart; + char strSessID[32]; + + NetGameUtilRefT *pUtilRef; + NetGameLinkRefT *pLinkRef; + + int32_t callcnt; + + #if DEMANGLER_SHAREDSOCKETTEST + SocketT *pSocket; + #endif + + // connection state + enum + { + IDLE, INIT, CONNINIT, LISTEN, FAIL, CONN, SHUTDOWN + } state; +} DemanglerRefT; + +/*** Function Prototypes ***************************************************************/ + + +/*** Variables *************************************************************************/ + + +// Private variables + +static DemanglerRefT *_pDemanglerRef = NULL; + +// Public variables + + +/*** Private Functions *****************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _CmdDemanglerCB + + \Description + Update demangling process. + + \Input *argz - + \Input argc - + \Input **argv - + + \Output int32_t - + + \Version 04/03/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdDemanglerCB(ZContext *argz, int32_t argc, char **argv) +{ + DemanglerRefT *pRef = (DemanglerRefT *)argz; + int32_t iResult; + + // check for kill + if (argc == 0) + { + if (pRef->pMangler != NULL) + { + ProtoMangleDestroy(pRef->pMangler); + } + if (pRef->pLinkRef != NULL) + { + NetGameLinkDestroy(pRef->pLinkRef); + } + if (pRef->pUtilRef != NULL) + { + NetGameUtilDestroy(pRef->pUtilRef); + } + return(0); + } + + // update demangler client + if (pRef->pMangler != NULL) + { + ProtoMangleUpdate(pRef->pMangler); + } + + if (pRef->state == INIT) + { + if (pRef->bRequestIssued == FALSE) + { + #if DEMANGLER_SHAREDSOCKETTEST + // create socket and bind it to game port + if (pRef->pSocket == NULL) + { + struct sockaddr bindaddr; + + pRef->pSocket = SocketOpen(AF_INET, SOCK_DGRAM, 0); + SockaddrInit(&bindaddr, AF_INET); + SockaddrInSetPort(&bindaddr, pRef->iGamePort); + SocketBind(pRef->pSocket, &bindaddr, sizeof(bindaddr)); + } + ProtoMangleConnectSocket(pRef->pMangler, (uint32_t)pRef->pSocket, pRef->strSessID); + #else + ProtoMangleConnect(pRef->pMangler, pRef->iGamePort, pRef->strSessID); + #endif + pRef->bRequestIssued = TRUE; + pRef->iMnglStart = NetTick(); + ZPrintf("demangler: connect issued at %d\n", NetTick()); + } + + if ((iResult = ProtoMangleComplete(pRef->pMangler, &pRef->iPeerAddr, &pRef->iPeerPort)) > 0) + { + ZPrintf("Demangler successful:\n"); + ZPrintf(" iPeerAddr: %a\n", pRef->iPeerAddr); + ZPrintf(" iPeerPort: %d\n", pRef->iPeerPort); + + pRef->state = CONNINIT; + } + else if (iResult < 0) + { + if (ProtoMangleStatus(pRef->pMangler, 'time', NULL, 0) == TRUE) + { + ZPrintf("demangler: timeout at %d\n", NetTick()); + } + else + { + ZPrintf("demangler: ProtoMangleComplete()=%d\n", iResult); + } + pRef->state = IDLE; + } + } + + if (pRef->state == CONNINIT) + { + char strAddr[48]; + + // create utilref + pRef->pUtilRef = NetGameUtilCreate(); + ds_snzprintf(strAddr, sizeof(strAddr), "%a:%d:%d", pRef->iPeerAddr, pRef->iGamePort, pRef->iPeerPort); + NetGameUtilConnect(pRef->pUtilRef, pRef->iConnType, strAddr, (CommAllConstructT *)CommUDPConstruct); + + pRef->iConnStart = NetTick(); + pRef->state = LISTEN; + } + + if (pRef->state == LISTEN) + { + void *pCommRef; + + // check for connect + pCommRef = NetGameUtilComplete(pRef->pUtilRef); + if (pCommRef != NULL) + { + // got a connect -- startup link + ZPrintf("%s: comm established\n", argv[0]); + pRef->pLinkRef = NetGameLinkCreate(pCommRef, FALSE, 8192); + ZPrintf("%s: link running\n", argv[0]); + pRef->state = CONN; + + // report success to protomangle server + ProtoMangleReport(pRef->pMangler, PROTOMANGLE_STATUS_CONNECTED, -1); + } + + // check for timeout + if ((NetTick() - pRef->iConnStart) > DEMANGLER_CONN_TIMEOUT) + { + // report failure to protomangle server + ProtoMangleReport(pRef->pMangler, PROTOMANGLE_STATUS_FAILED, -1); + pRef->state = FAIL; + } + } + + if (pRef->state == CONN) + { + NetGameLinkStatT Stats; + + // check connection status + NetGameLinkStatus(pRef->pLinkRef, 'stat', 0, &Stats, sizeof(NetGameLinkStatT)); + if (Stats.isopen) + { + NetGamePacketT TestPacket; + + // send an unreliable packet + TestPacket.head.kind = GAME_PACKET_USER_UNRELIABLE; + TestPacket.head.len = NETGAME_DATAPKT_DEFSIZE; + sprintf((char *)TestPacket.body.data, "demangler test packet %d", pRef->callcnt); + if (NetGameLinkSend(pRef->pLinkRef,&TestPacket,1) > 0) + { + pRef->callcnt++; + } + + // check for receive + while (NetGameLinkRecv(pRef->pLinkRef,&TestPacket,1,FALSE) > 0) + { + // sanity checks that the data got here OK + if (TestPacket.head.len != NETGAME_DATAPKT_DEFSIZE) + { + ZPrintf("packet size invalid\n"); + } + if ((TestPacket.head.kind != GAME_PACKET_USER_UNRELIABLE) && (TestPacket.head.kind != GAME_PACKET_USER)) + { + ZPrintf("packet type invalid\n"); + } + + // output data + ZPrintf("%s\n",(char *)TestPacket.body.data); + } + } + } + + if ((pRef->state == CONN) || (pRef->state == FAIL)) + { + // update ProtoMangle to send response to server + if (pRef->pMangler != NULL) + { + if (ProtoMangleComplete(pRef->pMangler, NULL, NULL) != 0) + { + ZPrintf("posted result to server\n"); + } + } + } + + return(ZCallback(&_CmdDemanglerCB, 100)); +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdDemangler + A simple tester of the EA.com demangler service, using ProtoMangle. + + \Description + + \Input *argz - + \Input argc - + \Input **argv - + + \Output + int32_t - + + \Version 04/03/2003 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdDemangler(ZContext *argz, int32_t argc, char **argv) +{ + const char *pServer, *pSessId; + uint8_t bReuseRef = TRUE; + DemanglerRefT *pRef; + + // usage + if ((argc < 3) || (argc > 4) || (strcmp(argv[1], "conn") && strcmp(argv[1], "list") && strcmp(argv[1], "disc") && strcmp(argv[1], "dest"))) + { + ZPrintf(" connect to a peer using demangler server\n"); + ZPrintf(" usage: %s [conn|list|disc|dest] [sessID]\n", argv[0]); + return(0); + } + + // destroy? + if (!strcmp(argv[1], "dest")) + { + if (_pDemanglerRef != NULL) + { + _CmdDemanglerCB((ZContext *)_pDemanglerRef, 0, NULL); + _pDemanglerRef = NULL; + } + else + { + ZPrintf(" %s: no module to destroy\n", argv[0]); + } + return(0); + } + + // disconnect? + if (!strcmp(argv[1], "disc")) + { + if (_pDemanglerRef != NULL) + { + if (_pDemanglerRef->pLinkRef != NULL) + { + NetGameLinkDestroy(_pDemanglerRef->pLinkRef); + } + if (_pDemanglerRef->pUtilRef != NULL) + { + NetGameUtilDestroy(_pDemanglerRef->pUtilRef); + } + } + return(0); + } + + // resolve server and session identifer + if (argc == 4) + { + pServer = argv[2]; + pSessId = argv[3]; + } + else + { + pServer = PROTOMANGLE_SERVER; + pSessId = argv[2]; + } + + // allocate context? + if ((pRef = _pDemanglerRef) == NULL) + { + pRef = (DemanglerRefT *) ZContextCreate(sizeof(*pRef)); + ds_memclr(pRef, sizeof(*pRef)); + + pRef->pMangler = ProtoMangleCreate(pServer, PROTOMANGLE_PORT, "mangletest-pc-2004", ""); + ProtoMangleControl(pRef->pMangler, 'time', DEMANGLER_MNGL_TIMEOUT, 0, NULL); + + _pDemanglerRef = pRef; + bReuseRef = FALSE; + } + + strcpy(pRef->strSessID, pSessId); + pRef->iConnType = (!strcmp(argv[1],"conn")) ? NETGAME_CONN_CONNECT : NETGAME_CONN_LISTEN; + pRef->state = INIT; + pRef->iGamePort = 10000; + pRef->bRequestIssued = FALSE; + + return(bReuseRef ? 0 : ZCallback(&_CmdDemanglerCB, 100)); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/gamelink.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/gamelink.c new file mode 100644 index 00000000..9ee93de1 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/gamelink.c @@ -0,0 +1,626 @@ +/*H********************************************************************************/ +/*! + \File gamelink.c + + \Description + Test NetGameLink + + \Copyright + Copyright (c) Electronic Arts 2018. + + \Version 02/02/2018 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#ifdef _WIN32 + #pragma warning(push,0) + #include + #pragma warning(pop) +#endif + +#include +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/game/netgameutil.h" +#include "DirtySDK/game/netgamelink.h" +#include "DirtySDK/comm/commudp.h" + +#if defined(DIRTYCODE_XBOXONE) +#include "DirtySDK/DirtySock/dirtyaddr.h" +#endif + +#include "libsample/zlib.h" +#include "libsample/zfile.h" +#include "libsample/zmem.h" +#include "testersubcmd.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +//! gamelink states +enum +{ + GAMELINK_STATUS_DISC, + GAMELINK_STATUS_CONN, + GAMELINK_STATUS_OPEN, + GAMELINK_STATUS_ACTV +}; + +//! default gamelink port +#define GAMELINK_PORT (3658) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct GameLinkAppT +{ + NetGameUtilRefT *pGameUtil; + NetGameLinkRefT *pGameLink; + uint32_t uLoclAddr; + uint8_t bZCallback; + uint8_t uLinkStatus; + uint8_t uPackSendVal; + uint8_t _pad[2]; +} GameLinkAppT; + +/*** Function Prototypes **********************************************************/ + +static void _GameLinkCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _GameLinkDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _GameLinkConnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _GameLinkControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _GameLinkSend(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); + +/*** Variables ********************************************************************/ + +static T2SubCmdT _GameLink_Commands[] = +{ + { "create", _GameLinkCreate }, + { "destroy", _GameLinkDestroy }, + { "connect", _GameLinkConnect }, + { "ctrl", _GameLinkControl }, + { "send", _GameLinkSend }, + { "", NULL }, +}; + +static GameLinkAppT _GameLink_App = { 0 }; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _GetIntArg + + \Description + Get fourcc/integer from command-line argument + + \Input *pArg - pointer to argument + + \Version 10/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _GetIntArg(const char *pArg) +{ + int32_t iValue; + + // check for possible fourcc value + if ((strlen(pArg) == 4) && (isalpha(pArg[0]) || isalpha(pArg[1]) || isalpha(pArg[2]) || isalpha(pArg[3]))) + { + iValue = pArg[0] << 24; + iValue |= pArg[1] << 16; + iValue |= pArg[2] << 8; + iValue |= pArg[3]; + } + else + { + iValue = (signed)strtol(pArg, NULL, 10); + } + return(iValue); +} + +/*F********************************************************************************/ +/*! + \Function _GetConnectParms + + \Description + Create connection string for gamelink connection + + \Input *pConnName - [out] connection string + \Input iNameSize - size of connname buffer + \Input uLoclAddr - local address + \Input uLoclPort - local port + \Input uConnAddr - peer address + \Input uConnPort - peer port + + \Output + int32_t - connection flags + + \Version 02/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _GetConnectParms(char *pConnName, int32_t iNameSize, uint32_t uLoclAddr, uint16_t uLoclPort, uint32_t uConnAddr, uint16_t uConnPort) +{ + char strLoclAddr[32], strConnAddr[32]; + int32_t iConnFlags = NETGAME_CONN_CONNECT; + uint32_t uAddrTemp; + uint8_t bHosting; + + ds_snzprintf(strLoclAddr, sizeof(strLoclAddr), "%a:%d", uLoclAddr, uLoclPort); + ds_snzprintf(strConnAddr, sizeof(strConnAddr), "%a:%d", uConnAddr, uConnPort); + + bHosting = (strcmp(strLoclAddr, strConnAddr) < 0) ? TRUE : FALSE; + + /* set up parms based on whether we are "hosting" or not. the connection name is the + unique address of the "host" concatenated with the unique address of the "client" */ + if (bHosting == TRUE) + { + // swap names + uAddrTemp = uConnAddr; + uConnAddr = uLoclAddr; + uLoclAddr = uAddrTemp; + // set conn flags to listen + iConnFlags = NETGAME_CONN_LISTEN; + } + + // format connection name and return connection flags + ds_snzprintf(pConnName, iNameSize, "%x-%x", uLoclAddr, uConnAddr); + return(iConnFlags); +} + +/*F********************************************************************************/ +/*! + \Function _GameUtilConnect + + \Description + Connect to peer + + \Input *pApp - module state + \Input uLoclAddr - local address + \Input uLoclPort - local port + \Input uConnAddr - peer address + \Input uConnPort - peer port + + \Output + int32_t - result of NetGameUtilConnect, or -1 on failure + + \Version 02/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _GameUtilConnect(GameLinkAppT *pApp, uint32_t uLoclAddr, uint16_t uLoclPort, uint32_t uConnAddr, uint16_t uConnPort) +{ + char strConn[128], strConnName[64]; + int32_t iConnFlags; + + if ((pApp->pGameUtil == NULL) && ((pApp->pGameUtil = NetGameUtilCreate()) == NULL)) + { + ZPrintf("gamelink: could not create util ref\n"); + return(-1); + } + + // set game link options +#if 0 + _SetGamelinkOpt(pClient->pGameUtilRef, 'minp', pConnApi->iGameMinp); + _SetGamelinkOpt(pClient->pGameUtilRef, 'mout', pConnApi->iGameMout); + _SetGamelinkOpt(pClient->pGameUtilRef, 'mwid', pConnApi->iGameMwid); + if (pConnApi->iGameUnackLimit != 0) + { + _SetGamelinkOpt(pClient->pGameUtilRef, 'ulmt', pConnApi->iGameUnackLimit); + } + + // set our client id (used by gameserver to uniquely identify us) + NetGameUtilControl(pClient->pGameUtilRef, 'clid', pClient->ClientInfo.uLocalClientId); + NetGameUtilControl(pClient->pGameUtilRef, 'rcid', pClient->ClientInfo.uRemoteClientId); +#endif + + // determine connection parameters + iConnFlags = _GetConnectParms(strConnName, sizeof(strConnName), uLoclAddr, uLoclPort, uConnAddr, uConnPort); + + // format connect string + ds_snzprintf(strConn, sizeof(strConn), "%a:%d:%d#%s", uConnAddr, uLoclPort, uConnPort, strConnName); + + // start the connection attempt + return(NetGameUtilConnect(pApp->pGameUtil, iConnFlags, strConn, (CommAllConstructT *)CommUDPConstruct)); +} + +/*F********************************************************************************/ +/*! + \Function _GameLinkDestroyApp + + \Description + Destroy app, clearing state + + \Input *pApp - app state + + \Version 02/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _GameLinkDestroyApp(GameLinkAppT *pApp) +{ + ds_memclr(pApp, sizeof(*pApp)); +} + +/* + + GameLink Commands + +*/ + +/*F*************************************************************************************/ +/*! + \Function _GameLinkCreate + + \Description + GameLink subcommand - create gamelink module + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE if usage request + + \Version 02/02/2018 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _GameLinkCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + GameLinkAppT *pApp = &_GameLink_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s create\n", argv[0]); + return; + } + + pApp->uLoclAddr = SocketInfo(NULL, 'addr', 0, NULL, 0); +} + +/*F*************************************************************************************/ +/*! + \Function _GameLinkDestroy + + \Description + GameLink subcommand - destroy gamelink module + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE if usage request + + \Version 02/02/2018 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _GameLinkDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + GameLinkAppT *pApp = &_GameLink_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + return; + } + + _GameLinkDestroyApp(pApp); +} + +/*F*************************************************************************************/ +/*! + \Function _GameLinkConnect + + \Description + GameLink subcommand - connect to peer + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE if usage request + + \Version 02/02/2018 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _GameLinkConnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + GameLinkAppT *pApp = &_GameLink_App; + uint32_t uConnAddr, uConnPort, uLoclPort, uRemoteClientId; + + if ((bHelp == TRUE) || (argc > 4)) + { + ZPrintf(" usage: %s connect [rclientid]\n", argv[0]); + return; + } + + // get remote address, port, and local port (if specified) in addr:port:port2 format + SockaddrInParse2(&uConnAddr, (int32_t *)&uConnPort, (int32_t *)&uLoclPort, argv[2]); + if (uConnPort == 0) + { + uConnPort = GAMELINK_PORT; + } + if (uLoclPort == 0) + { + uLoclPort = GAMELINK_PORT; + } + + // get remote client id, or use remote address if unspecified + uRemoteClientId = (argc == 4) ? (uint32_t)strtol(argv[3], NULL, 16) : uConnAddr; + + // start the connect + if (_GameUtilConnect(pApp, pApp->uLoclAddr, uLoclPort, uConnAddr, uConnPort) < 0) + { + ZPrintf("%s: error trying to connect\n"); + return; + } + // log connection attempt, uptdate status + ZPrintf("%s: connecting to %a:%u:%u (clientId=0x%08x)\n", argv[0], uConnAddr, uConnPort, uLoclPort, uRemoteClientId); + pApp->uLinkStatus = GAMELINK_STATUS_CONN; +} + +/*F*************************************************************************************/ +/*! + \Function _GameLinkControl + + \Description + GameLink control subcommand - set control options + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE if usage request + + \Version 02/02/2018 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _GameLinkControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + GameLinkAppT *pApp = &_GameLink_App; + int32_t iCmd, iValue = 0; + + if ((argc < 3) && (argc > 4)) + { + ZPrintf("%s: invalid ctrl command\n", argv[0]); + bHelp = TRUE; + } + if (bHelp == TRUE) + { + ZPrintf(" usage: %s ctrl \n", argv[0]); + return; + } + + // get control command + iCmd = argv[2][0] << 24; + iCmd |= argv[2][1] << 16; + iCmd |= argv[2][2] << 8; + iCmd |= argv[2][3]; + + // get control argument, if specified + if (argc > 3) + { + iValue = _GetIntArg(argv[3]); + } + + // pass it down + NetGameLinkControl(pApp->pGameLink, iCmd, iValue, NULL); +} + +/*F*************************************************************************************/ +/*! + \Function _GameLinkSend + + \Description + GameLink subcommand - send data + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE if usage request + + \Version 02/02/2018 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _GameLinkSend(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + GameLinkAppT *pApp = &_GameLink_App; + NetGameMaxPacketT Packet; + int32_t iSize, iResult; + + if (argc != 3) + { + ZPrintf("%s: invalid send command\n", argv[0]); + bHelp = TRUE; + } + if (bHelp == TRUE) + { + ZPrintf(" usage: %s send \n", argv[0]); + return; + } + + // get size + if (((iSize = _GetIntArg(argv[2])) < 0) || (iSize > NETGAME_DATAPKT_MAXSIZE)) + { + ZPrintf("%s: size %d invalid, setting size to %d\n", NETGAME_DATAPKT_MAXSIZE); + iSize = NETGAME_DATAPKT_MAXSIZE; + } + + // format packet head + ds_memclr(&Packet.head, sizeof(Packet.head)); + Packet.head.kind = GAME_PACKET_USER; + Packet.head.len = iSize; + + // format packet data + ds_memset(&Packet.body.data, pApp->uPackSendVal++, iSize); + + // send the packet + iResult = NetGameLinkSend(pApp->pGameLink, (NetGamePacketT *)&Packet, 1); + ZPrintf("%s: sent %d bytes\n", argv[0], iResult); +} + +/*F********************************************************************************/ +/*! + \Function _CmdGameLinkCb + + \Description + Update gamelink command + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - standard return value + + \Version 02/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdGameLinkCb(ZContext *argz, int32_t argc, char *argv[]) +{ + GameLinkAppT *pApp = &_GameLink_App; + + // check for kill + if (argc == 0) + { + _GameLinkDestroyApp(pApp); + ZPrintf("%s: killed\n", argv[0]); + return(0); + } + + // update in connecting state + if (pApp->uLinkStatus == GAMELINK_STATUS_CONN) + { + CommRef *pCommRef; + + // check for established connection + if ((pCommRef = NetGameUtilComplete(pApp->pGameUtil)) != NULL) + { + if ((pApp->pGameLink = NetGameLinkCreate(pCommRef, FALSE, 8192)) != NULL) + { + ZPrintf("%s: game connection established\n", argv[0]); + // indicate we've connected + pApp->uLinkStatus = GAMELINK_STATUS_OPEN; + } + else + { + ZPrintf("%s: game connection failed\n", argv[0]); + pApp->uLinkStatus = GAMELINK_STATUS_DISC; + } + } + } + + // update in open state + if (pApp->uLinkStatus == GAMELINK_STATUS_OPEN) + { + NetGameLinkStatT Stat; + + // give time to NetGameLink to run any connection-related processes + NetGameLinkUpdate(pApp->pGameLink); + + // get link stats + NetGameLinkStatus(pApp->pGameLink, 'stat', 0, &Stat, sizeof(NetGameLinkStatT)); + + // see if we're open + if (Stat.isopen == TRUE) + { + // mark as active + ZPrintf("%s: game connection is active\n", argv[0]); + pApp->uLinkStatus = GAMELINK_STATUS_ACTV; + } + } + + // update in active state + if (pApp->uLinkStatus == GAMELINK_STATUS_ACTV) + { + NetGameMaxPacketT Packet; + NetGameLinkStatT Stat; + + // get link stats + NetGameLinkStatus(pApp->pGameLink, 'stat', 0, &Stat, sizeof(NetGameLinkStatT)); + + // make sure connection is still open + if (Stat.isopen == FALSE) + { + ZPrintf("%s: game connection closed\n", argv[0]); + pApp->uLinkStatus = GAMELINK_STATUS_DISC; + } + // see if we've timed out + else if (NetTickDiff(Stat.tick, Stat.rcvdlast) > 20000) + { + ZPrintf("%s: game connection timed out\n", argv[0]); + pApp->uLinkStatus = GAMELINK_STATUS_DISC; + } + + // try and receive something; if we get something echo it to output + if (NetGameLinkRecv(pApp->pGameLink, (NetGamePacketT *)&Packet, sizeof(Packet.body.data), FALSE) > 0) + { + // cap max size to echo + #if DIRTYCODE_DEBUG + int32_t iSize = DS_MIN(Packet.head.len, 64); + NetPrintMem(Packet.body.data, iSize, "packet data"); + #endif + } + } + + // keep running + return(ZCallback(&_CmdGameLinkCb, 16)); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdGameLink + + \Description + Initiate GameLink connection. + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - standard return value + + \Version 02/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdGameLink(ZContext *argz, int32_t argc, char *argv[]) +{ + GameLinkAppT *pApp = &_GameLink_App; + T2SubCmdT *pCmd; + uint8_t bHelp; + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_GameLink_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" test the gamelink module\n"); + T2SubCmdUsage(argv[0], _GameLink_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmd->pFunc != _GameLinkCreate) && (pApp->pGameLink == NULL)) + { + char *pCreate = "create"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _GameLinkCreate(pApp, 1, &pCreate, bHelp); + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + // one-time install of periodic callback + if (pApp->bZCallback == FALSE) + { + pApp->bZCallback = TRUE; + return(ZCallback(_CmdGameLinkCb, 16)); + } + else + { + return(0); + } +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/help.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/help.c new file mode 100644 index 00000000..dd7efdf2 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/help.c @@ -0,0 +1,84 @@ +/*H********************************************************************************/ +/*! + \File help.c + + \Description + Handles help for tester2. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/11/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" + +#include "libsample/zlib.h" + +#include "testerregistry.h" +#include "testermodules.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdHelp + + \Description + Do some registry operations + + \Input *argz - environment + \Input argc - number of args + \Input **argv - argument list + + \Output int32_t - standard return code + + \Version 04/11/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t CmdHelp(ZContext *argz, int32_t argc, char **argv) +{ + TesterModulesT *pModules; + + // get the modules pointer from the registry, if available + pModules = (TesterModulesT *)TesterRegistryGetPointer("MODULES"); + + // as part of the help function, the help command is called to get help on help. + // stop the recusion by not calling the TesterModulesHelp function for help(NULL) + if (pModules == NULL) + { + // no modules created + ZPrintf("HELP: No module help available\n"); + } + else if (argc < 1) + { + // get help on help + ZPrintf(" get help on modules\n"); + ZPrintf(" usage: %s \n", argv[0]); + } + else if (argc == 2) + { + // get help on a specific command + TesterModulesHelp(pModules, argv[1]); + } + else + { + // get help on all commands by default + TesterModulesHelp(pModules, NULL); + } + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/history.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/history.c new file mode 100644 index 00000000..463fcd22 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/history.c @@ -0,0 +1,131 @@ +/*H********************************************************************************/ +/*! + \File history.c + + \Description + Handles the history command for Tester2 + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/11/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" + +#include "libsample/zmem.h" +#include "libsample/zlib.h" + +#include "testercomm.h" +#include "testerregistry.h" +#include "testermodules.h" +#include "testerhistory.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdHistory + + \Description + Handle the history command + + \Input *argz - environment + \Input argc - number of args + \Input **argv - argument list + + \Output int32_t - standard return code + + \Version 04/11/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t CmdHistory(ZContext *argz, int32_t argc, char **argv) +{ + int32_t iNum, iHeadCount, iTailCount; + TesterModulesT *pModules; + TesterHistoryT *pHistory; + TesterCommT *pComm; + const char *pText; + + // get the history module (if we can) + if ((pHistory = (TesterHistoryT *)TesterRegistryGetPointer("HISTORY")) == NULL) + { + ZPrintf("history: could not get information about history - no history module accessible.\n"); + return(-1); + } + + // history help? + if (argc < 1) + { + ZPrintf(" see previously executed command lines\n"); + ZPrintf(" usage: %s ", argv[0]); + return(0); + } + + // otherwise, get some history info + TesterHistoryHeadTailCount(pHistory, &iHeadCount, &iTailCount); + + // see if we just want to dump the history + if (argc == 1) + { + for (iNum = iHeadCount; iNum <= iTailCount; iNum++) + { + if ((pText = TesterHistoryGet(pHistory, iNum, NULL, 0)) != NULL) + { + ZPrintf("history: - [%5d] {%s}\n",iNum, pText); + } + } + } + + // see if we want to execute a command + if (argc == 2) + { + iNum = atoi(argv[1]); + pText = TesterHistoryGet(pHistory, iNum, NULL, 0); + // don't recursively execute the history command itself + if ((pText != NULL) && (iNum < iTailCount)) + { + // execute it locally + if ((pModules = TesterRegistryGetPointer("MODULES")) == NULL) + { + ZPrintf("history: could not dispatch historical command locally {%s}\n", pText); + } + else + { + TesterModulesDispatch(pModules, pText); + } + + // send it to the other side for execution + if ((pComm = (TesterCommT *)TesterRegistryGetPointer("COMM")) == NULL) + { + ZPrintf("history: could not dispatch historical command remotely {%s}\n", pText); + } + else + { + TesterCommMessage(pComm, TESTER_MSGTYPE_COMMAND, pText); + } + } + else + { + ZPrintf("history: - [%5d] error getting historical command number [%s].\n", iNum, argv[1]); + } + } + + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/hpack.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/hpack.c new file mode 100644 index 00000000..af40695f --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/hpack.c @@ -0,0 +1,429 @@ +/*H********************************************************************************/ +/*! + \File hpack.c + + \Description + Test the HPACK encoder and decoder. + + \Copyright + Copyright (c) 2016 Electronic Arts Inc. + + \Version 10/07/2016 (eesponda) +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/util/hpack.h" + +#include "testermodules.h" + +/*** Private functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _CmdHpackDecodeRequest + + \Description + Test cases from the RFC Appendix C.3 + Link: https://tools.ietf.org/html/rfc7541#appendix-C.3 + + \Version 10/07/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHpackDecodeRequest(void) +{ + HpackRefT *pRef; + char strHeader[1024*1]; + uint8_t aFirstRequest[] = { 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, + 0x6f, 0x6d }; + uint8_t aSecondRequest[] = { 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, + 0x63, 0x61, 0x63, 0x68, 0x65 }; + uint8_t aThirdRequest[] = { 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, + 0x75, 0x65 }; + + if ((pRef = HpackCreate(4096, TRUE)) == NULL) + { + return; + } + + HpackDecode(pRef, aFirstRequest, sizeof(aFirstRequest), strHeader, sizeof(strHeader)); + ZPrintf("first request\n%s\n", strHeader); + HpackDecode(pRef, aSecondRequest, sizeof(aSecondRequest), strHeader, sizeof(strHeader)); + ZPrintf("second request\n%s\n", strHeader); + HpackDecode(pRef, aThirdRequest, sizeof(aThirdRequest), strHeader, sizeof(strHeader)); + ZPrintf("third request\n%s\n", strHeader); + + HpackDestroy(pRef); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHpackDecodeResponse + + \Description + Test cases from the RFC Appendix C.5 + Link: https://tools.ietf.org/html/rfc7541#appendix-C.5 + + \Version 10/07/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHpackDecodeResponse(void) +{ + HpackRefT *pRef; + char strHeader[1024*1]; + uint8_t aFirstResponse[] = { 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d, + 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, + 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20, 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, + 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d }; + uint8_t aSecondResponse[] = { 0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf }; + uint8_t aThirdResponse[] = { 0x88, 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, + 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 0x47, 0x4d, + 0x54, 0xc0, 0x5a, 0x04, 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, + 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 0x45, 0x4f, 0x50, 0x49, + 0x55, 0x41, 0x58, 0x51, 0x57, 0x45, 0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, + 0x67, 0x65, 0x3d, 0x33, 0x36, 0x30, 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x3d, 0x31 }; + + if ((pRef = HpackCreate(256, TRUE)) == NULL) + { + return; + } + + HpackDecode(pRef, aFirstResponse, sizeof(aFirstResponse), strHeader, sizeof(strHeader)); + ZPrintf("first response\n%s\n", strHeader); + HpackDecode(pRef, aSecondResponse, sizeof(aSecondResponse), strHeader, sizeof(strHeader)); + ZPrintf("second response\n%s\n", strHeader); + HpackDecode(pRef, aThirdResponse, sizeof(aThirdResponse), strHeader, sizeof(strHeader)); + ZPrintf("third response\n%s\n", strHeader); + + HpackDestroy(pRef); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHpackDecodeRequestHuffman + + \Description + Test cases from the RFC Appendix C.4 + Link: https://tools.ietf.org/html/rfc7541#appendix-C.4 + + \Version 10/18/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHpackDecodeRequestHuffman(void) +{ + HpackRefT *pRef; + char strHeader[1024*1]; + uint8_t aFirstRequest[] = { 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff }; + uint8_t aSecondRequest[] = { 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf }; + uint8_t aThirdRequest[] = { 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f, 0x89, 0x25, + 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf }; + + if ((pRef = HpackCreate(4096, TRUE)) == NULL) + { + return; + } + + HpackDecode(pRef, aFirstRequest, sizeof(aFirstRequest), strHeader, sizeof(strHeader)); + ZPrintf("first request (huffman)\n%s\n", strHeader); + HpackDecode(pRef, aSecondRequest, sizeof(aSecondRequest), strHeader, sizeof(strHeader)); + ZPrintf("second request (huffman)\n%s\n", strHeader); + HpackDecode(pRef, aThirdRequest, sizeof(aThirdRequest), strHeader, sizeof(strHeader)); + ZPrintf("third request (huffman)\n%s\n", strHeader); + + HpackDestroy(pRef); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHpackDecodeResponseHuffman + + \Description + Test cases from the RFC Appendix C.6 + Link: https://tools.ietf.org/html/rfc7541#appendix-C.6 + + \Version 10/18/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHpackDecodeResponseHuffman(void) +{ + HpackRefT *pRef; + char strHeader[1024*1]; + uint8_t aFirstResponse[] = { 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, 0x96, 0xd0, 0x7a, 0xbe, + 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, + 0x2d, 0x1b, 0xff, 0x6e, 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, + 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3 }; + uint8_t aSecondResponse[] = { 0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf }; + uint8_t aThirdResponse[] = { 0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, + 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 0x1b, 0xff, 0xc0, 0x5a, 0x83, 0x9b, 0xd9, 0xab, + 0x77, 0xad, 0x94, 0xe7, 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, + 0x39, 0x60, 0xd5, 0xaf, 0x27, 0x08, 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, 0x0f, 0xb5, 0x29, 0x1f, + 0x95, 0x87, 0x31, 0x60, 0x65, 0xc0, 0x03, 0xed, 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07 }; + + if ((pRef = HpackCreate(256, TRUE)) == NULL) + { + return; + } + + HpackDecode(pRef, aFirstResponse, sizeof(aFirstResponse), strHeader, sizeof(strHeader)); + ZPrintf("first response (huffman)\n%s\n", strHeader); + HpackDecode(pRef, aSecondResponse, sizeof(aSecondResponse), strHeader, sizeof(strHeader)); + ZPrintf("second response (huffman)\n%s\n", strHeader); + HpackDecode(pRef, aThirdResponse, sizeof(aThirdResponse), strHeader, sizeof(strHeader)); + ZPrintf("third response (huffman)\n%s\n", strHeader); + + HpackDestroy(pRef); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHpackEncodeRequest + + \Description + Test cases from the RFC Appendix C.3 (encoding) + Link: https://tools.ietf.org/html/rfc7541#appendix-C.3 + + \Version 10/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHpackEncodeRequest(void) +{ + HpackRefT *pRef; + uint8_t aOutput[1024] = { 0 }; + char strFirstRequest[] = ":method: GET\r\n:scheme: http\r\n:path: /\r\n:authority: www.example.com\r\n"; + uint8_t aFirstRequest[] = { 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x2e, 0x63, 0x6f, 0x6d }; + + char strSecondRequest[] = ":method: GET\r\n:scheme: http\r\n:path: /\r\n:authority: www.example.com\r\ncache-control: no-cache\r\n"; + uint8_t aSecondRequest[] = { 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65 }; + + char strThirdRequest[] = ":method: GET\r\n:scheme: https\r\n:path: /index.html\r\n:authority: www.example.com\r\ncustom-key: custom-value\r\n"; + uint8_t aThirdRequest[] = { 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, + 0x75, 0x65 }; + + if ((pRef = HpackCreate(4096, FALSE)) == NULL) + { + return; + } + + HpackEncode(pRef, strFirstRequest, aOutput, sizeof(aOutput), FALSE); + if (memcmp(aOutput, aFirstRequest, sizeof(aFirstRequest)) == 0) + { + ZPrintf("successfully encoded first request\n"); + } + HpackEncode(pRef, strSecondRequest, aOutput, sizeof(aOutput), FALSE); + if (memcmp(aOutput, aSecondRequest, sizeof(aSecondRequest)) == 0) + { + ZPrintf("successfully encoded second request\n"); + } + HpackEncode(pRef, strThirdRequest, aOutput, sizeof(aOutput), FALSE); + if (memcmp(aOutput, aThirdRequest, sizeof(aThirdRequest)) == 0) + { + ZPrintf("successfully encoded third request\n"); + } + + HpackDestroy(pRef); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHpackEncodeResponse + + \Description + Test cases from the RFC Appendix C.5 (encode) + Link: https://tools.ietf.org/html/rfc7541#appendix-C.5 + + \Version 10/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHpackEncodeResponse(void) +{ + HpackRefT *pRef; + uint8_t aOutput[1024] = { 0 }; + + char strFirstResponse[] = ":status: 302\r\ncache-control: private\r\ndate: Mon, 21 Oct 2013 20:13:21 GMT\r\nlocation: https://www.example.com\r\n"; + uint8_t aFirstResponse[] = { 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d, + 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, + 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20, 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, + 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d }; + + char strSecondResponse[] = ":status: 307\r\ncache-control: private\r\ndate: Mon, 21 Oct 2013 20:13:21 GMT\r\nlocation: https://www.example.com\r\n"; + uint8_t aSecondResponse[] = { 0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf }; + + char strThirdResponse[] = ":status: 200\r\ncache-control: private\r\ndate: Mon, 21 Oct 2013 20:13:22 GMT\r\nlocation: https://www.example.com\r\ncontent-encoding: gzip\r\nset-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\r\n"; + uint8_t aThirdResponse[] = { 0x88, 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, + 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 0x47, 0x4d, + 0x54, 0xc0, 0x5a, 0x04, 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, + 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 0x45, 0x4f, 0x50, 0x49, + 0x55, 0x41, 0x58, 0x51, 0x57, 0x45, 0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, + 0x67, 0x65, 0x3d, 0x33, 0x36, 0x30, 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x3d, 0x31 }; + + if ((pRef = HpackCreate(4096, FALSE)) == NULL) + { + return; + } + + HpackEncode(pRef, strFirstResponse, aOutput, sizeof(aOutput), FALSE); + if (memcmp(aOutput, aFirstResponse, sizeof(aFirstResponse)) == 0) + { + ZPrintf("successfully encoded first response\n"); + } + HpackEncode(pRef, strSecondResponse, aOutput, sizeof(aOutput), FALSE); + if (memcmp(aOutput, aSecondResponse, sizeof(aSecondResponse)) == 0) + { + ZPrintf("successfully encoded second response\n"); + } + HpackEncode(pRef, strThirdResponse, aOutput, sizeof(aOutput), FALSE); + if (memcmp(aOutput, aThirdResponse, sizeof(aThirdResponse)) == 0) + { + ZPrintf("successfully encoded third response\n"); + } + + HpackDestroy(pRef); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHpackEncodeRequestHuffman + + \Description + Test cases from the RFC Appendix C.4 (encode) + Link: https://tools.ietf.org/html/rfc7541#appendix-C.4 + + \Version 10/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHpackEncodeRequestHuffman(void) +{ + HpackRefT *pRef; + uint8_t aOutput[1024] = { 0 }; + char strFirstRequest[] = ":method: GET\r\n:scheme: http\r\n:path: /\r\n:authority: www.example.com\r\n"; + char strSecondRequest[] = ":method: GET\r\n:scheme: http\r\n:path: /\r\n:authority: www.example.com\r\ncache-control: no-cache\r\n"; + char strThirdRequest[] = ":method: GET\r\n:scheme: https\r\n:path: /index.html\r\n:authority: www.example.com\r\ncustom-key: custom-value\r\n"; + uint8_t aFirstRequest[] = { 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff }; + uint8_t aSecondRequest[] = { 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf }; + uint8_t aThirdRequest[] = { 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f, 0x89, 0x25, + 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf }; + + if ((pRef = HpackCreate(4096, FALSE)) == NULL) + { + return; + } + + HpackEncode(pRef, strFirstRequest, aOutput, sizeof(aOutput), TRUE); + if (memcmp(aOutput, aFirstRequest, sizeof(aFirstRequest)) == 0) + { + ZPrintf("successfully encoded first request (huffman)\n"); + } + HpackEncode(pRef, strSecondRequest, aOutput, sizeof(aOutput), TRUE); + if (memcmp(aOutput, aSecondRequest, sizeof(aSecondRequest)) == 0) + { + ZPrintf("successfully encoded second request (huffman)\n"); + } + HpackEncode(pRef, strThirdRequest, aOutput, sizeof(aOutput), TRUE); + if (memcmp(aOutput, aThirdRequest, sizeof(aThirdRequest)) == 0) + { + ZPrintf("successfully encoded third request (huffman)\n"); + } + + HpackDestroy(pRef); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHpackEncodeResponseHuffman + + \Description + Test cases from the RFC Appendix C.6 (encode) + Link: https://tools.ietf.org/html/rfc7541#appendix-C.6 + + \Version 10/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHpackEncodeResponseHuffman(void) +{ + HpackRefT *pRef; + uint8_t aOutput[1024] = { 0 }; + + char strFirstResponse[] = ":status: 302\r\ncache-control: private\r\ndate: Mon, 21 Oct 2013 20:13:21 GMT\r\nlocation: https://www.example.com\r\n"; + char strSecondResponse[] = ":status: 307\r\ncache-control: private\r\ndate: Mon, 21 Oct 2013 20:13:21 GMT\r\nlocation: https://www.example.com\r\n"; + char strThirdResponse[] = ":status: 200\r\ncache-control: private\r\ndate: Mon, 21 Oct 2013 20:13:22 GMT\r\nlocation: https://www.example.com\r\ncontent-encoding: gzip\r\nset-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\r\n"; + uint8_t aFirstResponse[] = { 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, 0x96, 0xd0, 0x7a, 0xbe, + 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, + 0x2d, 0x1b, 0xff, 0x6e, 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, + 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3 }; + uint8_t aSecondResponse[] = { 0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf }; + uint8_t aThirdResponse[] = { 0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, + 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 0x1b, 0xff, 0xc0, 0x5a, 0x83, 0x9b, 0xd9, 0xab, + 0x77, 0xad, 0x94, 0xe7, 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, + 0x39, 0x60, 0xd5, 0xaf, 0x27, 0x08, 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, 0x0f, 0xb5, 0x29, 0x1f, + 0x95, 0x87, 0x31, 0x60, 0x65, 0xc0, 0x03, 0xed, 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07 }; + + if ((pRef = HpackCreate(4096, FALSE)) == NULL) + { + return; + } + + HpackEncode(pRef, strFirstResponse, aOutput, sizeof(aOutput), TRUE); + if (memcmp(aOutput, aFirstResponse, sizeof(aFirstResponse)) == 0) + { + ZPrintf("successfully encoded first response (huffman)\n"); + } + + HpackEncode(pRef, strSecondResponse, aOutput, sizeof(aOutput), TRUE); + if (memcmp(aOutput, aSecondResponse, sizeof(aSecondResponse)) == 0) + { + ZPrintf("successfully encoded second response (huffman)\n"); + } + HpackEncode(pRef, strThirdResponse, aOutput, sizeof(aOutput), TRUE); + if (memcmp(aOutput, aThirdResponse, sizeof(aThirdResponse)) == 0) + { + ZPrintf("successfully encoded third response (huffman)\n"); + } + + HpackDestroy(pRef); +} + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdHpack + + \Description + Test the HPACK encoder and decoder. + + \Input *pArgz - environment + \Input iArgc - standard number of arguments + \Input *pArgv[] - standard arg list + + \Output standard return value + + \Version 10/07/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CmdHpack(ZContext *pArgz, int32_t iArgc, char *pArgcv[]) +{ + _CmdHpackDecodeRequest(); + _CmdHpackDecodeResponse(); + _CmdHpackDecodeRequestHuffman(); + _CmdHpackDecodeResponseHuffman(); + _CmdHpackEncodeRequest(); + _CmdHpackEncodeResponse(); + _CmdHpackEncodeRequestHuffman(); + _CmdHpackEncodeResponseHuffman(); + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/http.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/http.c new file mode 100644 index 00000000..d434ae55 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/http.c @@ -0,0 +1,1310 @@ +/*H********************************************************************************/ +/*! + \File http.c + + \Description + Implements basic http get and post client. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 10/28/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/proto/protohttp.h" +#include "DirtySDK/proto/protossl.h" + +#include "libsample/zlib.h" +#include "libsample/zmem.h" +#include "libsample/zfile.h" + +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +#define HTTP_BUFSIZE (4096) +#define HTTP_RATE (1) + +#define HTTP_XTRAHDR0 "" +#define HTTP_XTRAHDR1 "X-Agent: DirtySock HTTP Tester\r\n" // test "normal" replacement (replaces Accept: header) +#define HTTP_XTRAHDR2 "User-Agent: DirtySock HTTP Tester\r\n" // test "extended" replacement (replaces User-Agent: and Accept: headers) + +//$$ tmp -- special test header used for hard-coded multipart/form-data testing -- this should be removed at some point when we have real multipart/form-data support +#define HTTP_XTRAHDR3 "Content-Type: multipart/form-data; boundary=TeStInG\r\n" \ + "User-Agent: DirtySock HTTP Tester\r\n" \ + "Accept: */*\r\n" + +#define HTTP_APNDHDR HTTP_XTRAHDR0 + +/*** Function Prototypes **********************************************************/ + +static int32_t _CmdHttpIdleCB(ZContext *argz, int32_t argc, char *argv[]); + +/*** Type Definitions *************************************************************/ + +typedef struct HttpRefT // module state storage +{ + ProtoHttpRefT *http; + int64_t show; + int64_t count; + int32_t sttime; + int64_t iDataSize; + int64_t iSentSize; + int32_t iSendBufData; + int32_t iSendBufSent; + ZFileT iInpFile; + ZFileT iOutFile; + int32_t iOutSize; + char *pOutData; + char *pClientCert; + int32_t iCertSize; + char *pClientKey; + int32_t iKeySize; + int32_t iDebugLevel; + uint8_t bStreaming; + uint8_t bUseWriteCb; + uint8_t bRecvAll; + char strModuleName[32]; + char strHost[128]; + char strCookie[2048]; + char strApndHdr[2048]; + char strFileBuffer[64*1024]; // must be at a minimum 4k or protohttp buffer size, whichever is larger + + // module state + enum + { + IDLE, DNLOAD, UPLOAD + } state; +} HttpRefT; + +/*** Variables ********************************************************************/ + +static HttpRefT _Http_Ref; +static uint8_t _Http_bInitialized = FALSE; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _GetIntArg + + \Description + Get fourcc/integer from command-line argument + + \Input *pArg - pointer to argument + + \Version 10/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _GetIntArg(const char *pArg) +{ + int32_t iValue; + + // check for possible fourcc value + if ((strlen(pArg) == 4) && (isalpha(pArg[0]) || isalpha(pArg[1]) || isalpha(pArg[2]) || isalpha(pArg[3]))) + { + iValue = pArg[0] << 24; + iValue |= pArg[1] << 16; + iValue |= pArg[2] << 8; + iValue |= pArg[3]; + } + else + { + iValue = (signed)strtol(pArg, NULL, 10); + } + return(iValue); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttpUsage + + \Description + Display usage information. + + \Input argc - argument count + \Input *argv[] - argument list + + \Version 02/18/2008 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CmdHttpUsage(int argc, char *argv[]) +{ + if (argc == 2) + { + ZPrintf(" execute http get or put operations\n"); + ZPrintf(" usage: %s [cclr|cert|cer2|cver|create|ctrl|debug|destroy|get|head|put|post|puts|delete|options|parse|stat|urlparse]", argv[0]); + } + else if (argc == 3) + { + if (!strcmp(argv[2], "cclr") || !strcmp(argv[2], "cert") || !strcmp(argv[2], "cer2") || !strcmp(argv[2], "cver")) + { + ZPrintf(" usage: %s cert|cer2 - load certificate file\n", argv[0]); + ZPrintf(" %s cclr - clear dynamic CA certs\n"); + ZPrintf(" %s cver - verify dynamic CA certs\n"); + } + else if (!strcmp(argv[2], "create")) + { + ZPrintf(" usage: %s create \n", argv[0]); + } + else if (!strcmp(argv[2], "destroy")) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + } + else if (!strcmp(argv[2], "get") || !strcmp(argv[2], "head") || !strcmp(argv[2], "options") || !strcmp(argv[2], "delete")) + { + ZPrintf(" usage: %s %s [url] \n", argv[0], argv[2]); + } + else if (!strcmp(argv[2], "put") || !strcmp(argv[2], "puts") || !strcmp(argv[2], "post")) + { + ZPrintf(" usage: %s %s [url] [infile] \n", argv[0], argv[2]); + } + else if (!strcmp(argv[2], "debug")) + { + ZPrintf(" usage: %s debug [debuglevel]\n", argv[0]); + } + else if (!strcmp(argv[2], "parse")) + { + ZPrintf(" usage: %s parse [url]\n", argv[0]); + } + } +} + +static int32_t _HttpUrlParseTest(const char *pModuleName, const char *pUrl) +{ + char strKind[16], strHost[256]; + int32_t iPort, iSecure; + uint8_t bPortSpecified; + const char *pUri; + + ZPrintf("%s: parsing url %s\n", pModuleName, pUrl); + pUri = ProtoHttpUrlParse2(pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure, &bPortSpecified); + + ZPrintf("%s: uri=%s\n", pModuleName, pUri); + ZPrintf("%s: kind=%s\n", pModuleName, strKind); + ZPrintf("%s: host=%s\n", pModuleName, strHost); + ZPrintf("%s: iPort=%d\n", pModuleName, iPort); + ZPrintf("%s: iSecure=%d\n", pModuleName, iSecure); + ZPrintf("%s: bPortSpecified=%s\n", pModuleName, bPortSpecified ? "TRUE" : "FALSE"); + + return(0); +} + +static int32_t _HttpUrlEncodeTest(const char *pModuleName, char **pArgs, int32_t iNumArgs) +{ + char strUrl[128]; + int32_t iArg, iOff; + + ds_snzprintf(strUrl, sizeof(strUrl), "https://urltest.ea.com/"); + for (iArg = 0, iOff = (int32_t)strlen(strUrl); iArg < iNumArgs; iArg += 2) + { + iOff += ProtoHttpUrlEncodeStrParm(strUrl+iOff, sizeof(strUrl)-iOff, pArgs[iArg], pArgs[iArg+1]); + } + ProtoHttpUrlEncodeIntParm(strUrl, sizeof(strUrl), "&test=", 2918123); + ZPrintf("%s: url=%s\n", pModuleName, strUrl); + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpLoadCertificate + + \Description + Display usage information. + + \Input *pFilename - certificate filename + \Input *pCertSize - [out] storage for cert size + + \Output + char * - pointer to certificate, or NULL on error + + \Version 10/20/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_HttpLoadCertificate(const char *pFilename, int32_t *pCertSize) +{ + char *pCertBuf; + int32_t iFileSize; + + // load certificate file + if ((pCertBuf = (char *)ZFileLoad(pFilename, &iFileSize, ZFILE_OPENFLAG_RDONLY)) == NULL) + { + return(NULL); + } + + // calculate length + *pCertSize = iFileSize; + // return to caller + return(pCertBuf); +} + +/*F********************************************************************************/ +/*! + \Function _HttpReset + + \Description + Reset transaction state + + \Input *pRef - module state + + \Version 03/21/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpReset(HttpRefT *pRef) +{ + // clear any previous stats + pRef->count = 0; + pRef->show = 0; + pRef->iDataSize = 0; + pRef->iSentSize = 0; + pRef->iSendBufSent = 0; + + // clear previous options + pRef->bStreaming = FALSE; + pRef->bUseWriteCb = FALSE; + pRef->bRecvAll = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function _HttpCustomHeaderCallback + + \Description + ProtoHttp custom header callback. + + \Input *pProtoHttp - protohttp module state + \Input *pHeader - header to be sent + \Input uHeaderSize - header size + \Input *pUserData - user ref (HttpRefT) + + \Output + int32_t - zero + + \Notes + The header returned should be terminated by a *single* CRLF; ProtoHttp will + append the final CRLF to complete the header. The callback may return the + size of the header, or zero, in which case ProtoHttp will calculate the + headersize using strlen(). + + \Version 02/24/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpCustomHeaderCallback(ProtoHttpRefT *pProtoHttp, char *pHeader, uint32_t uHeaderSize, const char *pData, int64_t iDataLen, void *pUserRef) +{ + int32_t iOffset; + + NetPrintf(("http: custom header callback, size=%d, data=%d, pData=%p\n", uHeaderSize, iDataLen, pData)); + + // find end of header + iOffset = (int32_t)strlen(pHeader); + + // append a header + iOffset += ds_snzprintf(pHeader+iOffset, uHeaderSize-iOffset, "X-CustomHeaderCallback: Testing custom header callback\r\n"); + + // we can either return the header length or zero; in the latter case protohttp will re-calculate for us + return(iOffset); +} + +/*F********************************************************************************/ +/*! + \Function _HttpRecvHeaderCallback + + \Description + ProtoHttp recv header callback. + + \Input *pProtoHttp - protohttp module state + \Input *pHeader - received header + \Input uHeaderSize - header size + \Input *pUserData - user ref (HttpRefT) + + \Output + int32_t - zero + + \Version 02/24/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpRecvHeaderCallback(ProtoHttpRefT *pProtoHttp, const char *pHeader, uint32_t uHeaderSize, void *pUserRef) +{ + HttpRefT *pHttp = (HttpRefT *)pUserRef; + char strBuffer[2048], strName[128]; + const char *pHdrTmp; + int32_t iLocnSize; + + // check for location header + if ((iLocnSize = ProtoHttpGetHeaderValue(pProtoHttp, pHeader, "location", NULL, 0, NULL)) > 0) + { + ZPrintf("http: location header size=%d\n", iLocnSize); + if (ProtoHttpGetHeaderValue(pProtoHttp, pHeader, "location", strBuffer, sizeof(strBuffer), NULL) == 0) + { + ZPrintf("http: location url='%s'\n", strBuffer); + } + else + { + ZPrintf("http: error querying location url\n"); + } + } + + // test ProtoHttpGetNextHeader() + for (pHdrTmp = pHeader; ProtoHttpGetNextHeader(pProtoHttp, pHdrTmp, strName, sizeof(strName), strBuffer, sizeof(strBuffer), &pHdrTmp) == 0; ) + { + #if 0 + ZPrintf("http: ===%s: %s\n", strName, strBuffer); + #endif + } + + // parse any set-cookie requests + for (pHdrTmp = pHeader; ProtoHttpGetHeaderValue(pProtoHttp, pHdrTmp, "set-cookie", strBuffer, sizeof(strBuffer), &pHdrTmp) == 0; ) + { + // print the cookie + ZPrintf("http: parsed cookie '%s'\n", strBuffer); + + // add field seperator + if (pHttp->strCookie[0] != '\0') + { + ds_strnzcat(pHttp->strCookie, ", ", sizeof(pHttp->strCookie)); + } + // append to cookie list + ds_strnzcat(pHttp->strCookie, strBuffer, sizeof(pHttp->strCookie)); + } + + // if this is a redirection update append header with any new cookies + if ((PROTOHTTP_GetResponseClass(ProtoHttpStatus(pProtoHttp, 'code', NULL, 0)) == PROTOHTTP_RESPONSE_REDIRECTION) && (pHttp->strCookie[0] != '\0')) + { + // format append header + ds_snzprintf(strBuffer, sizeof(strBuffer), "Cookie: %s\r\n%s", pHttp->strCookie, HTTP_APNDHDR); + ds_strnzcat(strBuffer, pHttp->strApndHdr, sizeof(strBuffer)); + ProtoHttpControl(pProtoHttp, 'apnd', 0, 0, strBuffer); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpReallocBuff + + \Description + Realloc buffer used for RecvAll operation + + \Input *pRef - pointer to http ref + + \Version 07/29/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpReallocBuff(HttpRefT *pRef) +{ + char *pNewData; + int32_t iNewSize; + + // calc new buffer size + if ((iNewSize = pRef->iOutSize) == 0) + { + // try getting body size + if ((iNewSize = ProtoHttpStatus(pRef->http, 'body', NULL, 0)) > 0) + { + // bump up buffer size for recvall null terminator + //$$ TODO V9 -- why 2 required, not 1?? + iNewSize += 2; + } + else + { + // assign a fixed size, since we didn't get a body size + iNewSize = 4096; + } + } + else + { + iNewSize *= 2; + } + + // allocate new buffer + if ((pNewData = ZMemAlloc(iNewSize)) == NULL) + { + return; + } + // if realloc, copy old data and free old pointer + if (pRef->pOutData != NULL) + { + ds_memcpy(pNewData, pRef->pOutData, pRef->iOutSize); + ZMemFree(pRef->pOutData); + } + // save new pointer + pRef->pOutData = pNewData; + pRef->iOutSize = iNewSize; +} + +/*F********************************************************************************/ +/*! + \Function _HttpDownloadProcessData + + \Description + Process data received in a download operation + + \Input *pRef - module state + \Input iLen - data length or PROTOHTTP_RECV* + + \Version 05/03/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpDownloadProcessData(HttpRefT *pRef, int32_t iLen) +{ + int32_t iResult; + + // see if we should show progress + if ((pRef->count/1024) != (pRef->show/1024)) + { + pRef->show = pRef->count; + ZPrintf("%s: downloaded %qd bytes\n", pRef->strModuleName, pRef->count); + } + + // see if we are done + if ((iLen < 0) && (iLen != PROTOHTTP_RECVWAIT)) + { + // completed successfully? + if ((iLen == PROTOHTTP_RECVDONE) || (iLen == PROTOHTTP_RECVHEAD)) + { + int32_t iDlTime = NetTickDiff(NetTick(), pRef->sttime); + int64_t iBodySize; + char strRespHdr[1024]; + + ZPrintf("%s: download complete (%qd bytes)\n", pRef->strModuleName, pRef->count); + ZPrintf("%s: download time: %qd bytes in %.2f seconds (%.3f k/sec)\n", pRef->strModuleName, pRef->count, + (float)iDlTime/1000.0f, ((float)pRef->count * 1000.0f) / ((float)iDlTime * 1024.0f)); + + // make sure we got it all + if ((iBodySize = ProtoHttpStatus(pRef->http, 'body', NULL, 0)) != pRef->count) + { + ZPrintf("%s: WARNING -- mismatch between expected size (%d) and actual size (%d)\n", pRef->strModuleName, iBodySize, pRef->count); + } + + // display response header + if (ProtoHttpStatus(pRef->http, 'htxt', strRespHdr, sizeof(strRespHdr)) >= 0) + { + ZPrintf("%s response header:\n%s\n", pRef->strModuleName, strRespHdr); + } + + // display a couple of parsed fields + if (ProtoHttpStatus(pRef->http, 'head', NULL, 0) > 0) + { + time_t tLastMod = ProtoHttpStatus(pRef->http, 'date', NULL, 0); + const char *pTime; + + if (tLastMod != 0) + { + if ((pTime = ctime(&tLastMod)) != NULL) + { + ZPrintf("%s: Last-Modified: %s", pRef->strModuleName, pTime); + } + } + // get content-length; by passing in a 64bit int we support 64bit transfer sizes + ProtoHttpStatus(pRef->http, 'body', &iBodySize, sizeof(iBodySize)); + ZPrintf("%s: Content-Length: %qd\n", pRef->strModuleName, iBodySize); + } + } + else // failure + { + ProtoSSLAlertDescT AlertDesc; + int32_t iSockErr = ProtoHttpStatus(pRef->http, 'serr', NULL, 0); + int32_t iSslFail = ProtoHttpStatus(pRef->http, 'essl', NULL, 0); + int32_t iAlert = ProtoHttpStatus(pRef->http, 'alrt', &AlertDesc, sizeof(AlertDesc)); + ZPrintf("%s: download failed (err=%d, sockerr=%d sslerr=%d)\n", pRef->strModuleName, iLen, iSockErr, iSslFail); + if ((iSslFail == PROTOSSL_ERROR_CERT_INVALID) || (iSslFail == PROTOSSL_ERROR_CERT_HOST) || + (iSslFail == PROTOSSL_ERROR_CERT_NOTRUST) || (iSslFail == PROTOSSL_ERROR_CERT_REQUEST)) + { + ProtoSSLCertInfoT CertInfo; + if (ProtoHttpStatus(pRef->http, 'cert', &CertInfo, sizeof(CertInfo)) == 0) + { + ZPrintf("%s: cert failure (%d): (C=%s, ST=%s, L=%s, O=%s, OU=%s, CN=%s)\n", pRef->strModuleName, iSslFail, + CertInfo.Ident.strCountry, CertInfo.Ident.strState, CertInfo.Ident.strCity, + CertInfo.Ident.strOrg, CertInfo.Ident.strUnit, CertInfo.Ident.strCommon); + } + else + { + ZPrintf("%s: could not get cert info\n", pRef->strModuleName); + } + } + if (iAlert > 0) + { + ZPrintf("%s: %s ssl alert %s (%d)\n", pRef->strModuleName, (iAlert == 1) ? "recv" : "sent", AlertDesc.pAlertDesc, AlertDesc.iAlertType); + } + } + + ZPrintf("%s: hResult=0x%08x\n", pRef->strModuleName, ProtoHttpStatus(pRef->http, 'hres', NULL, 0)); + + // if file exists, close it + if (pRef->iOutFile != ZFILE_INVALID) + { + if ((iResult = ZFileClose(pRef->iOutFile)) != 0) + { + ZPrintf("%s: error %d closing output file\n", pRef->strModuleName, iResult); + } + pRef->iOutFile = ZFILE_INVALID; + } + + // if output buffer exists, free it + if (pRef->pOutData != NULL) + { + pRef->pOutData = NULL; + } + pRef->iOutSize = 0; + + // set to idle state + pRef->state = IDLE; + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpWriteCb + + \Description + Implementation of ProtoHttp write callback + + \Input *pState - http module state + \Input *pWriteInfo - callback info + \Input *pData - transaction data pointer + \Input iDataSize - size of data + \Input *pUserData - user callback data + + \Output + int32_t - zero + + \Version 05/03/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpWriteCb(ProtoHttpRefT *pState, const ProtoHttpWriteCbInfoT *pWriteInfo, const char *pData, int32_t iDataSize, void *pUserData) +{ + HttpRefT *pRef = (HttpRefT *)pUserData; + //NetPrintf(("http: writecb (%d,%d) %d bytes\n", pWriteInfo->eRequestType, pWriteInfo->eRequestResponse, iDataSize)); + + // if we got data, update count and write to output file if available + if (iDataSize > 0) + { + pRef->count += iDataSize; + if (pRef->iOutFile != ZFILE_INVALID) + { + ZFileWrite(pRef->iOutFile, (void *)pData, iDataSize); + } + } + + // process + _HttpDownloadProcessData(pRef, iDataSize); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpRecvData + + \Description + Receive data from ProtoHttp using either ProtoHttpRecv() or + ProtoHttpRecvAll(). + + \Input *pRef - module state + \Input *pState - transaction state + + \Version 07/02/2013 (jbrookes) brought over from httpmgr +*/ +/********************************************************************************F*/ +static int32_t _HttpRecvData(HttpRefT *pRef) +{ + char strBuf[16*1024]; + int32_t iLen; + + // check for data + if (!pRef->bRecvAll) + { + while ((iLen = ProtoHttpRecv(pRef->http, strBuf, 1, sizeof(strBuf))) > 0) + { + pRef->count += iLen; + if (pRef->iOutFile != ZFILE_INVALID) + { + ZFileWrite(pRef->iOutFile, strBuf, iLen); + } + } + } + else + { + // receive all the data + if ((iLen = ProtoHttpRecvAll(pRef->http, pRef->pOutData, pRef->iOutSize)) > 0) + { + pRef->count = iLen; + if (pRef->iOutFile != ZFILE_INVALID) + { + ZFileWrite(pRef->iOutFile, pRef->pOutData, iLen); + } + iLen = PROTOHTTP_RECVDONE; + } + else if (iLen == PROTOHTTP_RECVBUFF) + { + // grow the buffer + _HttpReallocBuff(pRef); + // swallow error code + iLen = 0; + } + } + return(iLen); +} + +/*F********************************************************************************/ +/*! + \Function _HttpDownloadProcess + + \Description + Process a download transaction, using polling method + + \Input *pRef - module state + + \Version 10/28/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpDownloadProcess(HttpRefT *pRef) +{ + int32_t iLen; + + // if we're not doing the write callback thing, poll for data + for (iLen = 1; (iLen != PROTOHTTP_RECVWAIT) && (iLen != 0) && (pRef->state != IDLE); ) + { + // receive data + iLen = _HttpRecvData(pRef); + // process data + _HttpDownloadProcessData(pRef, iLen); + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpUploadProcess + + \Description + Process an upload transaction. + + \Input *pRef - module state + + \Version 10/28/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpUploadProcess(HttpRefT *pRef) +{ + int32_t iResult; + + // if no input file, nothing to send + if (pRef->iInpFile == ZFILE_INVALID) + { + return; + } + + // send all the data + while ((pRef->state == UPLOAD) && (pRef->iSentSize < pRef->iDataSize)) + { + // do we need more data? + if (pRef->iSendBufSent == pRef->iSendBufData) + { + if ((pRef->iSendBufData = ZFileRead(pRef->iInpFile, pRef->strFileBuffer, sizeof(pRef->strFileBuffer))) > 0) + { + ZPrintf("%s: read %d bytes from file\n", pRef->strModuleName, pRef->iSendBufData); + pRef->iSendBufSent = 0; + } + else + { + ZPrintf("%s: error %d reading from file\n", pRef->strModuleName, pRef->iSendBufData); + pRef->state = IDLE; + } + } + + // do we have buffered data to send? + if (pRef->iSendBufSent < pRef->iSendBufData) + { + iResult = ProtoHttpSend(pRef->http, pRef->strFileBuffer + pRef->iSendBufSent, pRef->iSendBufData - pRef->iSendBufSent); + if (iResult > 0) + { + pRef->iSentSize += iResult; + pRef->iSendBufSent += iResult; + ZPrintf("%s: sent %d bytes (%qd total)\n", pRef->strModuleName, iResult, pRef->iSentSize); + } + else if (iResult < 0) + { + ZPrintf("%s: ProtoHttpSend() failed; error %d\n", pRef->strModuleName, iResult); + pRef->state = IDLE; + } + else + { + break; + } + } + } + // check for upload completion + if (pRef->iSentSize == pRef->iDataSize) + { + int32_t ultime = NetTick() - pRef->sttime; + + // if streaming upload, signal we are done + if (pRef->bStreaming == TRUE) + { + ProtoHttpSend(pRef->http, NULL, PROTOHTTP_STREAM_END); + pRef->bStreaming = FALSE; + } + + // done uploading + ZPrintf("%s: upload complete (%qd bytes)\n", pRef->strModuleName, pRef->iSentSize); + ZPrintf("%s: upload time: %d bytes in %.2f seconds (%.3f k/sec)\n", pRef->strModuleName, pRef->iSentSize, + (float)ultime/1000.0f, ((float)pRef->iSentSize * 1000.0f) / ((float)ultime * 1024.0f)); + + // close the file + ZFileClose(pRef->iInpFile); + pRef->iInpFile = ZFILE_INVALID; + + // transition to download state to receive server response + pRef->state = DNLOAD; + } +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttpIdleCB + + \Description + Callback to process while idle + + \Input *argz - + \Input argc - + \Input *argv[] - + + \Output int32_t - + + \Version 09/26/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdHttpIdleCB(ZContext *argz, int32_t argc, char *argv[]) +{ + HttpRefT *pRef = &_Http_Ref; + + // shut down? + if ((argc == 0) || (pRef->http == NULL)) + { + if (pRef->http != NULL) + { + ProtoHttpDestroy(pRef->http); + pRef->http = NULL; + } + return(0); + } + + // give ref processing time + ProtoHttpUpdate(pRef->http); + + // download processing (if not using write callback) + if ((pRef->state == DNLOAD) && (!pRef->bUseWriteCb)) + { + _HttpDownloadProcess(pRef); + } + // upload processing + if (pRef->state == UPLOAD) + { + _HttpUploadProcess(pRef); + } + + // keep on idling + return(ZCallback(&_CmdHttpIdleCB, HTTP_RATE)); +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdHttp + + \Description + Initiate an HTTP transaction. + + \Input *argz - + \Input argc - + \Input *argv[] - + + \Output int32_t - + + \Version 10/28/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdHttp(ZContext *argz, int32_t argc, char *argv[]) +{ + const char *pFileName = NULL, *pUrl; + HttpRefT *pRef = &_Http_Ref; + int32_t iResult = 0, iBufSize = HTTP_BUFSIZE; + char strBuffer[4096]; + int32_t iStartArg = 2; // first arg past get/put/delete/whatever + + if (argc < 2) + { + return(0); + } + + // check for help + if ((argc >= 2) && !strcmp(argv[1], "help")) + { + _CmdHttpUsage(argc, argv); + return(iResult); + } + + // check for 'parse' command + if ((argc == 3) && !strcmp(argv[1], "parse")) + { + char strKind[8], strHost[128]; + const char *pUri; + int32_t iPort, iSecure; + ds_memclr(strKind, sizeof(strKind)); + ds_memclr(strHost, sizeof(strHost)); + pUri = ProtoHttpUrlParse(argv[2], strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure); + ZPrintf("parsed url: kind=%s, host=%s, port=%d, secure=%d, uri=%s\n", strKind, strHost, iPort, iSecure, pUri); + return(0); + } + + // if not initialized yet, do so now + if (_Http_bInitialized == FALSE) + { + ds_memclr(pRef, sizeof(*pRef)); + ds_strnzcpy(pRef->strModuleName, argv[0], sizeof(pRef->strModuleName)); + pRef->iInpFile = ZFILE_INVALID; + pRef->iOutFile = ZFILE_INVALID; + _Http_bInitialized = TRUE; + } + + // check for explicit destroy + if ((argc == 2) && !ds_stricmp(argv[1], "destroy")) + { + if (pRef->http != NULL) + { + ProtoHttpDestroy(pRef->http); + pRef->http = NULL; + } + if (pRef->pClientCert != NULL) + { + ZMemFree(pRef->pClientCert); + } + if (pRef->pClientKey != NULL) + { + ZMemFree(pRef->pClientKey); + } + _Http_bInitialized = FALSE; + return(iResult); + } + + // check for create request + if ((argc == 3) && !strcmp(argv[1], "create")) + { + iBufSize = (int32_t)strtol(argv[2], NULL, 10); + } + + // check for request to set a CA certificate + if ((argc == 3) && ((!strcmp(argv[1], "cert")) || (!strcmp(argv[1], "cer2")))) + { + const uint8_t *pFileData; + int32_t iFileSize; + + // try and open file + if ((pFileData = (const uint8_t *)ZFileLoad(argv[2], &iFileSize, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) != NULL) + { + if (!strcmp(argv[1], "cert")) + { + iResult = ProtoHttpSetCACert(pFileData, iFileSize); + } + else + { + iResult = ProtoHttpSetCACert2(pFileData, iFileSize); + } + ZMemFree((void *)pFileData); + } + else + { + ZPrintf("%s: unable to load certificate file '%s'\n", argv[0], argv[2]); + } + return((iResult >= 0) ? 0 : -1); + } + else if (!strcmp(argv[1], "cclr")) + { + ZPrintf("%s: clearing dynamic CA certs\n", argv[0]); + ProtoHttpClrCACerts(); + return(0); + } + else if (!strcmp(argv[1], "cver")) + { + int32_t iInvalid; + ZPrintf("%s: verifying dynamic CA certs\n", argv[0]); + if ((iInvalid = ProtoHttpValidateAllCA()) > 0) + { + ZPrintf("%s: could not verify %d CA certs\n", argv[0], iInvalid); + iResult = -1; + } + return(iResult); + } + else if ((argc == 3) && !strcmp(argv[1], "scrt")) + { + if ((pRef->pClientCert = (char *)_HttpLoadCertificate(argv[2], &pRef->iCertSize)) == NULL) + { + ZPrintf("%s: could not load client certificate '%s'\n", argv[0], argv[2]); + iResult = -1; + } + return(iResult); + } + else if ((argc == 3) && !strcmp(argv[1], "skey")) + { + if ((pRef->pClientKey = (char *)_HttpLoadCertificate(argv[2], &pRef->iKeySize)) == NULL) + { + ZPrintf("%s: could not load client private key '%s'\n", argv[0], argv[2]); + iResult = -1; + } + return(iResult); + } + + // test url parsing + if ((argc == 3) && !strcmp(argv[1], "urlparse")) + { + return(_HttpUrlParseTest(argv[0], argv[2])); + } + + // test url encoding + if ((argc >= 3) && !strcmp(argv[1], "urlencode")) + { + return(_HttpUrlEncodeTest(argv[0], argv+2, argc-2)); + } + + // create protohttp module if necessary + if (pRef->http == NULL) + { + // disable the secure flags to help with our testing setup + #if DIRTYCODE_GDK + ProtoHttpControl(pRef->http, 'secu', 0, 0, NULL); + #endif + + ZPrintf("%s: creating module with a %dkbyte buffer\n", argv[0], iBufSize); + pRef->http = ProtoHttpCreate(iBufSize); + if (pRef->http != NULL) + { + ProtoHttpCallback(pRef->http, _HttpCustomHeaderCallback, _HttpRecvHeaderCallback, pRef); + pRef->iDebugLevel = 1; + } + } + + // check for create request -- if so, we're done + if ((argc <= 3) && !strcmp(argv[1], "create")) + { + return(iResult); + } + else if ((argc > 2) && (argc < 7) && !strcmp(argv[1], "ctrl")) + { + int32_t iCmd, iValue = 0, iValue2 = 0; + const char *pValue = NULL; + + iCmd = argv[2][0] << 24; + iCmd |= argv[2][1] << 16; + iCmd |= argv[2][2] << 8; + iCmd |= argv[2][3]; + + if (argc > 3) + { + iValue = _GetIntArg(argv[3]); + } + + if (argc > 4) + { + iValue2 = _GetIntArg(argv[4]); + } + + if (argc > 5) + { + pValue = argv[5]; + } + + // snoop 'spam' + if (iCmd == 'spam') + { + pRef->iDebugLevel = iValue; + } + + return(ProtoHttpControl(pRef->http, iCmd, iValue, iValue2, (void *)pValue)); + } + else if ((argc == 3) && !strcmp(argv[1], "stat")) + { + int32_t iCmd; + + iCmd = argv[2][0] << 24; + iCmd |= argv[2][1] << 16; + iCmd |= argv[2][2] << 8; + iCmd |= argv[2][3]; + + iResult = ProtoHttpStatus(pRef->http, iCmd, strBuffer, sizeof(strBuffer)); + ZPrintf("http: ProtoHttpStatus('%s') returned %d\n", argv[2], iResult); + if (strBuffer[0] != '\0') + { + ZPrintf("%s\n", strBuffer); + } + return(0); + } + // check for setting of base url + else if ((argc == 3) && !strcmp(argv[1], "base")) + { + ProtoHttpSetBaseUrl(pRef->http, argv[2]); + return(iResult); + } + // check for debug setting + else if (!strcmp(argv[1], "debug") && (argc == 3)) + { + int32_t iDebugLevel = (int32_t)strtol(argv[2], NULL, 10); + pRef->iDebugLevel = iDebugLevel; + return(iResult); + } + else if (!ds_stricmp(argv[1], "abrt")) + { + ProtoHttpAbort(pRef->http); + pRef->state = IDLE; + return(iResult); + } + + // check for valid get/put request + else if ((!ds_stricmp(argv[1], "get") || !ds_stricmp(argv[1], "head") || !ds_stricmp(argv[1], "put") || !ds_stricmp(argv[1], "post") || + !ds_stricmp(argv[1], "puts") || !ds_stricmp(argv[1], "delete") || !ds_stricmp(argv[1], "options")) && + ((argc > 2) || (argc < 6))) + { + int32_t iArg; + + // reset to clear any previous stats + _HttpReset(pRef); + + // init start timer + pRef->sttime = NetTick(); + + // set up append header + ds_strnzcpy(pRef->strApndHdr, HTTP_APNDHDR, sizeof(pRef->strApndHdr)); + + // check for args + for (iArg = 2; (iArg < argc) && (argv[iArg][0] == '-'); iArg += 1) + { + if (!ds_strnicmp(argv[iArg], "-header=", 8)) + { + ds_strnzcat(pRef->strApndHdr, argv[iArg]+8, sizeof(pRef->strApndHdr)); + ds_strnzcat(pRef->strApndHdr, "\r\n", sizeof(pRef->strApndHdr)); + } + if (!ds_strnicmp(argv[iArg], "-writecb", 8)) + { + pRef->bUseWriteCb = TRUE; + } + if (!ds_strnicmp(argv[iArg], "-recvall", 8)) + { + pRef->bRecvAll = TRUE; + } + // skip any option arguments to find url and (optionally) filename + iStartArg += 1; + } + + // set client cert if specified + if (pRef->pClientCert != NULL) + { + ProtoHttpControl(pRef->http, 'scrt', pRef->iCertSize, 0, pRef->pClientCert); + } + // set client key if specified + if (pRef->pClientKey != NULL) + { + ProtoHttpControl(pRef->http, 'skey', pRef->iKeySize, 0, pRef->pClientKey); + } + + // fall-through to code below + } + else + { + ZPrintf(" unrecognized or badly formatted command '%s'\n", argv[1]); + _CmdHttpUsage(argc, argv); + return(iResult); + } + + // locate url and filename + pUrl = argv[iStartArg]; + if (argc > iStartArg) + { + pFileName = argv[iStartArg+1]; + } + + // do we have a url? + if (pUrl != NULL) + { + const char* pTemp = pUrl; //< Used for parsing the query param + char strKind[5], strHost[128], strName[32], strValue[32]; + int32_t iPort, iSecure; + + // get url info + ProtoHttpUrlParse(pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure); + + // if the host has changed, reset cookie buffer + if (ds_stricmp(pRef->strHost, strHost) && (pRef->strCookie[0] != '\0')) + { + ZPrintf("http: host change to %s; resetting cookies from %s\n", strHost, pRef->strHost); + pRef->strCookie[0] = '\0'; + } + // save host + ds_strnzcpy(pRef->strHost, strHost, sizeof(strHost)); + + while (ProtoHttpGetNextParam(pRef->http, pTemp, strName, sizeof(strName), strValue, sizeof(strValue), &pTemp) == 0) + { + ProtoHttpUrlDecodeStrParm(strValue, strValue, sizeof(strValue)); + + #if 0 + ZPrintf("http query param: key=%s, value=%s\n", strName, strValue); + #endif + } + } + else + { + return(0); + } + + // set append header + strBuffer[0] = '\0'; + if (pRef->strCookie[0] != '\0') + { + ds_snzprintf(strBuffer, sizeof(strBuffer), "Cookie: %s\r\n", pRef->strCookie); + } + ds_strnzcat(strBuffer, pRef->strApndHdr, sizeof(strBuffer)); + ProtoHttpControl(pRef->http, 'apnd', 0, 0, strBuffer); + // set debug level + ProtoHttpControl(pRef->http, 'spam', pRef->iDebugLevel, 0, NULL); + + // if we're uploading, open required input file + if (!ds_stricmp(argv[1], "put") || !ds_stricmp(argv[1], "post") || !ds_stricmp(argv[1], "puts")) + { + ZPrintf("%s: uploading %s to %s\n", argv[0], pFileName, pUrl); + + // assume failure + iResult = -1; + + // try and open file + if ((pRef->iInpFile = ZFileOpen(pFileName, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) != ZFILE_INVALID) + { + // get the file size + if ((pRef->iDataSize = ZFileSize(pRef->iInpFile)) > 0) + { + // load data from file + if ((pRef->iSendBufData = ZFileRead(pRef->iInpFile, pRef->strFileBuffer, sizeof(pRef->strFileBuffer))) > 0) + { + if (ds_stricmp(argv[1], "puts")) + { + // initiate put/post transaction + ZPrintf("%s: uploading %d bytes\n", argv[0], pRef->iDataSize); + if ((pRef->iSendBufSent = ProtoHttpPost(pRef->http, pUrl, pRef->strFileBuffer, pRef->iDataSize, !ds_stricmp(argv[1], "put") ? PROTOHTTP_PUT : PROTOHTTP_POST)) < 0) + { + ZPrintf("%s: error %d initiating send\n", argv[0], pRef->iSendBufSent); + iResult = -1; + } + else if (pRef->iSendBufSent >= 0) + { + ZPrintf("%s: sent %d bytes\n", argv[0], pRef->iSendBufSent); + pRef->iSentSize = pRef->iSendBufSent; + } + } + else + { + // initiate streaming put + if ((iResult = ProtoHttpPost(pRef->http, pUrl, NULL, PROTOHTTP_STREAM_BEGIN, PROTOHTTP_POST)) >= 0) + { + pRef->bStreaming = TRUE; + } + } + + // wait for reply + pRef->state = UPLOAD; + + // locate output file + pFileName = (argc > (iStartArg+2)) ? argv[iStartArg+2] : NULL; + } + else + { + ZPrintf("%s: error %d reading data from file\n", argv[0], pRef->iSendBufData, pFileName); + } + } + else + { + ZPrintf("%s: error %d getting size of file %s\n", argv[0], pRef->iDataSize, pFileName); + } + } + else + { + ZPrintf("%s: unable to load file '%s'\n", argv[0], pFileName); + } + } + + // open output file? + if (pFileName != NULL) + { + ZPrintf("%s: saving %s data to %s\n", argv[0], pUrl, pFileName); + if ((pRef->iOutFile = ZFileOpen(pFileName, ZFILE_OPENFLAG_WRONLY|ZFILE_OPENFLAG_BINARY|ZFILE_OPENFLAG_CREATE)) == ZFILE_INVALID) + { + ZPrintf("%s: error opening file '%s' for writing\n", argv[0], pFileName); + } + } + + // issue request + if (!ds_stricmp(argv[1], "get") || !ds_stricmp(argv[1], "head") || !ds_stricmp(argv[1], "delete") || !ds_stricmp(argv[1], "options")) + { + ProtoHttpRequestTypeE eRequestType; + + ZPrintf("%s: downloading %s\n", argv[0], pUrl); + + // map to protohttp request type + if (!ds_stricmp(argv[1], "head")) + { + eRequestType = PROTOHTTP_REQUESTTYPE_HEAD; + } + else if (!ds_stricmp(argv[1], "get")) + { + eRequestType = PROTOHTTP_REQUESTTYPE_GET; + } + else if (!ds_stricmp(argv[1], "delete")) + { + eRequestType = PROTOHTTP_REQUESTTYPE_DELETE; + } + else if (!ds_stricmp(argv[1], "options")) + { + eRequestType = PROTOHTTP_REQUESTTYPE_OPTIONS; + } + else + { + ZPrintf("%s: unrecognized request %s\n", argv[0], argv[1]); + return(-1); + } + + if (pRef->bUseWriteCb) + { + iResult = ProtoHttpRequestCb(pRef->http, pUrl, NULL, 0, eRequestType, _HttpWriteCb, pRef); + } + else + { + iResult = ProtoHttpRequest(pRef->http, pUrl, NULL, 0, eRequestType); + } + + if (iResult == 0) + { + pRef->state = DNLOAD; + } + } + + // set up recurring callback to process transaction + if (pRef->state != IDLE) + { + iResult = ZCallback(_CmdHttpIdleCB, HTTP_RATE); + } + + return(iResult); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/http2.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/http2.c new file mode 100644 index 00000000..e69db7ec --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/http2.c @@ -0,0 +1,989 @@ +/*H********************************************************************************/ +/*! + \File http2.c + + \Description + Test the ProtoHttp2 client + + \Copyright + Copyright (c) 2016 Electronic Arts Inc. + + \Version 12/01/2016 (eesponda) +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protohttp2.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/util/protobufcommon.h" +#include "DirtySDK/util/protobufwrite.h" +#include "DirtySDK/util/protobufread.h" +#include "libsample/zmem.h" + +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +//! default address for the grpc server we are testing +#define DEFAULT_GRPC_SERVER ("http://10.14.141.208:50051") + +/*** Type Definitions *************************************************************/ + +//! wrapper for the data payloads used for our tests +typedef struct FixedBufferT +{ + int32_t iSize; + uint8_t aData[256]; +} FixedBufferT; + +//! generator function for data +typedef FixedBufferT (GenerateFn)(void); + +//! handle response function +typedef const uint8_t *(ResponseFn)(const uint8_t *pBuf, int32_t iBufLen); + +//! location used for grpc tests +typedef struct LocationT +{ + uint64_t iLatitude; + uint64_t iLongitude; +} LocationT; + +typedef struct Http2RefT +{ + ProtoHttp2RefT *pHttp; //!< http2 module ref + int32_t iStreamId; //!< main stream identifier + + char strGrpcHost[32]; //!< the default host to run grpc tests + + uint8_t *pBuf; //!< buffer for reading data + int32_t iBufSize; //!< size of the buffer + + FixedBufferT Buffer; //!< buffer for sending data + int32_t iDataSent; //!< how much data we have sent + + GenerateFn *pGen; //!< streaming generator fn + ResponseFn *pResp; //!< response handling fn + int32_t iIndex; //!< index of the data for client/bi-directional sends + int32_t iMaxIndex; //!< max index of data + + uint32_t uTimer; //!< timer to gauge operations + int32_t iCount; //!< number of bytes downloaded + int32_t iShow; //!< current bytes show in logging +} Http2RefT; + +/*** Variables ********************************************************************/ + +// instance for used in testing +static Http2RefT _Http2 = { NULL, 0, DEFAULT_GRPC_SERVER, NULL, 0, { 0, { 0 } }, 0, NULL, NULL, 0, 0, 0, 0, 0 }; + +//! list of locations use for RecordRoute RPC +static const LocationT _aFeatureLocations[] = +{ + { 407838351, (uint64_t)-746143763 }, + { 408122808, (uint64_t)-743999179 }, + { 413628156, (uint64_t)-749015468 }, + { 419999544, (uint64_t)-740371136 }, + { 414008389, (uint64_t)-743951297 }, + { 419611318, (uint64_t)-746524769 }, + { 406109563, (uint64_t)-742186778 }, + { 416802456, (uint64_t)-742370183 }, + { 412950425, (uint64_t)-741077389 }, + { 412144655, (uint64_t)-743949739 }, + { 415736605, (uint64_t)-742847522 }, + { 413843930, (uint64_t)-740501726 }, + { 410873075, (uint64_t)-744459023 }, + { 412346009, (uint64_t)-744026814 }, + { 402948455, (uint64_t)-747903913 }, + { 406337092, (uint64_t)-740122226 }, + { 406421967, (uint64_t)-747727624 }, + { 416318082, (uint64_t)-749677716 }, + { 415301720, (uint64_t)-748416257 }, + { 402647019, (uint64_t)-747071791 }, + { 412567807, (uint64_t)-741058078 }, + { 416855156, (uint64_t)-744420597 }, + { 404663628, (uint64_t)-744820157 }, + { 407113723, (uint64_t)-749746483 }, + { 402133926, (uint64_t)-743613249 }, + { 400273442, (uint64_t)-741220915 }, + { 411236786, (uint64_t)-744070769 }, + { 411633782, (uint64_t)-746784970 }, + { 415830701, (uint64_t)-742952812 }, + { 413447164, (uint64_t)-748712898 }, + { 405047245, (uint64_t)-749800722 }, + { 418858923, (uint64_t)-746156790 }, + { 417951888, (uint64_t)-748484944 }, + { 407033786, (uint64_t)-743977337 }, + { 417548014, (uint64_t)-740075041 }, + { 410395868, (uint64_t)-744972325 }, + { 404615353, (uint64_t)-745129803 }, + { 406589790, (uint64_t)-743560121 }, + { 414653148, (uint64_t)-740477477 }, + { 405957808, (uint64_t)-743255336 }, + { 411733589, (uint64_t)-741648093 }, + { 412676291, (uint64_t)-742606606 }, + { 409224445, (uint64_t)-748286738 }, + { 406523420, (uint64_t)-742135517 }, + { 401827388, (uint64_t)-740294537 }, + { 410564152, (uint64_t)-743685054 }, + { 408472324, (uint64_t)-740726046 }, + { 412452168, (uint64_t)-740214052 }, + { 409146138, (uint64_t)-746188906 }, + { 404701380, (uint64_t)-744781745 }, + { 409642566, (uint64_t)-746017679 }, + { 408031728, (uint64_t)-748645385 }, + { 413700272, (uint64_t)-742135189 }, + { 404310607, (uint64_t)-740282632 }, + { 409319800, (uint64_t)-746201391 }, + { 406685311, (uint64_t)-742108603 }, + { 419018117, (uint64_t)-749142781 }, + { 412856162, (uint64_t)-745148837 }, + { 416560744, (uint64_t)-746721964 }, + { 405314270, (uint64_t)-749836354 }, + { 414219548, (uint64_t)-743327440 }, + { 415534177, (uint64_t)-742900616 }, + { 406898530, (uint64_t)-749127080 }, + { 407586880, (uint64_t)-741670168 }, + { 400106455, (uint64_t)-742870190 }, + { 400066188, (uint64_t)-746793294 }, + { 418803880, (uint64_t)-744102673 }, + { 414204288, (uint64_t)-747895140 }, + { 414777405, (uint64_t)-740615601 }, + { 415464475, (uint64_t)-747175374 }, + { 404062378, (uint64_t)-746376177 }, + { 405688272, (uint64_t)-749285130 }, + { 400342070, (uint64_t)-748788996 }, + { 401809022, (uint64_t)-744157964 }, + { 404226644, (uint64_t)-740517141 }, + { 410322033, (uint64_t)-747871659 }, + { 407100674, (uint64_t)-747742727 }, + { 418811433, (uint64_t)-741718005 }, + { 415034302, (uint64_t)-743850945 }, + { 411349992, (uint64_t)-743694161 }, + { 404839914, (uint64_t)-744759616 }, + { 414638017, (uint64_t)-745957854 }, + { 412127800, (uint64_t)-740173578 }, + { 401263460, (uint64_t)-747964303 }, + { 412843391, (uint64_t)-749086026 }, + { 418512773, (uint64_t)-743067823 }, + { 404318328, (uint64_t)-740835638 }, + { 419020746, (uint64_t)-741172328 }, + { 404080723, (uint64_t)-746119569 }, + { 401012643, (uint64_t)-744035134 }, + { 404306372, (uint64_t)-741079661 }, + { 403966326, (uint64_t)-748519297 }, + { 405002031, (uint64_t)-748407866 }, + { 409532885, (uint64_t)-742200683 }, + { 416851321, (uint64_t)-742674555 }, + { 406411633, (uint64_t)-741722051 }, + { 413069058, (uint64_t)-744597778 }, + { 418465462, (uint64_t)-746859398 }, + { 411733222, (uint64_t)-744228360 }, + { 410248224, (uint64_t)-747127767 } +}; + +/*** Private functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2WriteGrpcHeader + + \Description + Encodes the required size of the message into the buffer + + \Input *pEncoder - the message encoder + \Input *pPayload - the payload the message was encoded to + + \Version 07/05/2017 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHttp2WriteGrpcHeader(ProtobufWriteRefT *pEncoder, FixedBufferT *pPayload) +{ + pPayload->iSize = ProtobufWriteDestroy(pEncoder); + // add extra for compression byte + pPayload->iSize += 1; +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2GetFeature + + \Description + Encodes the payload for the GetFeature RPC + + \Output + FixedBufferT - the buffer we have encoded + + \Version 07/05/2017 (eesponda) +*/ +/********************************************************************************F*/ +static FixedBufferT _CmdHttp2GetFeature(void) +{ + FixedBufferT Payload; + ProtobufWriteRefT *pEncoder; + + /* + rpc GetFeature(Point) returns (Feature) {} + + message Point { + int32 latitude = 1; + int32 longitude = 2; + } + */ + + ds_memclr(&Payload, sizeof(Payload)); + pEncoder = ProtobufWriteCreate(Payload.aData+1, sizeof(Payload.aData)-1, TRUE); + + ProtobufWriteVarint(pEncoder, 409146138, 1); + ProtobufWriteVarint(pEncoder, (uint64_t)-746188906, 2); + + // write the header + _CmdHttp2WriteGrpcHeader(pEncoder, &Payload); + + return(Payload); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2GetFeatureResponse + + \Description + Handles the response for the GetFeature RPC + + \Input *pBuffer - payload + \Input iBufLen - size of payload + + \Output + const uint8_t * - new buffer location past the response + + \Version 07/05/2017 (eesponda) +*/ +/********************************************************************************F*/ +static const uint8_t *_CmdHttp2GetFeatureResponse(const uint8_t *pBuffer, int32_t iBufLen) +{ + int32_t iMsgSize; + ProtobufReadT Reader, Msg; + struct { + char strName[256]; + LocationT Point; + } Response; + + ds_memset(&Response, -1, sizeof(Response)); + + /* + rpc GetFeature(Point) returns (Feature) {} + + message Point { + int32 latitude = 1; + int32 longitude = 2; + } + + message Feature { + string name = 1; + Point location = 2; + } + */ + + // get message size (skipping compression) + pBuffer = ProtobufCommonReadSize(pBuffer+1, iBufLen-1, &iMsgSize); + ProtobufReadInit(&Reader, pBuffer, iMsgSize); + + ProtobufReadString(&Reader, ProtobufReadFind(&Reader, 1), Response.strName, sizeof(Response.strName)); + if (ProtobufReadMessage(&Reader, ProtobufReadFind(&Reader, 2), &Msg) != NULL) + { + Response.Point.iLatitude = ProtobufReadVarint(&Msg, ProtobufReadFind(&Msg, 1)); + Response.Point.iLongitude = ProtobufReadVarint(&Msg, ProtobufReadFind(&Msg, 2)); + } + + ZPrintf("http2: name (%s), location (%d/%d)\n", Response.strName, Response.Point.iLatitude, Response.Point.iLongitude); + return(pBuffer+iMsgSize); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2ListFeatures + + \Description + Encodes the payload for the ListFeatures RPC + + \Output + FixedBufferT - the buffer we have encoded + + \Version 07/05/2017 (eesponda) +*/ +/********************************************************************************F*/ +static FixedBufferT _CmdHttp2ListFeatures(void) +{ + /* + rpc ListFeatures(Rectangle) returns (stream Feature) {} + + message Rectangle { + Point lo = 1; + Point hi = 2; + } + */ + + FixedBufferT Payload; + ProtobufWriteRefT *pEncoder; + + ds_memclr(&Payload, sizeof(Payload)); + pEncoder = ProtobufWriteCreate(Payload.aData+1, sizeof(Payload.aData)-1, TRUE); + + ProtobufWriteMessageBegin(pEncoder, 1); + ProtobufWriteVarint(pEncoder, 400000000, 1); + ProtobufWriteVarint(pEncoder, (uint64_t)-750000000, 2); + ProtobufWriteMessageEnd(pEncoder); + + ProtobufWriteMessageBegin(pEncoder, 2); + ProtobufWriteVarint(pEncoder, 420000000, 1); + ProtobufWriteVarint(pEncoder, (uint64_t)-730000000, 2); + ProtobufWriteMessageEnd(pEncoder); + + // write the header + _CmdHttp2WriteGrpcHeader(pEncoder, &Payload); + + return(Payload); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2ListFeaturesResponse + + \Description + Handles the response for the ListFeatures RPC + + \Input *pBuffer - payload + \Input iBufLen - size of payload + + \Output + const uint8_t * - new buffer location past the response + + \Version 07/05/2017 (eesponda) +*/ +/********************************************************************************F*/ +static const uint8_t *_CmdHttp2ListFeaturesResponse(const uint8_t *pBuffer, int32_t iBufLen) +{ + int32_t iMsgSize; + ProtobufReadT Reader, Msg; + + struct { + char strName[256]; + LocationT Point; + } Response; + + /* + rpc ListFeatures(Rectangle) returns (stream Feature) {} + + message Point { + int32 latitude = 1; + int32 longitude = 2; + } + + message Feature { + string name = 1; + Point location = 2; + } + */ + + // get message size (skipping compression) + pBuffer = ProtobufCommonReadSize(pBuffer+1, iBufLen-1, &iMsgSize); + ProtobufReadInit(&Reader, pBuffer, iMsgSize); + + ProtobufReadString(&Reader, ProtobufReadFind(&Reader, 1), Response.strName, sizeof(Response.strName)); + if (ProtobufReadMessage(&Reader, ProtobufReadFind(&Reader, 2), &Msg) != NULL) + { + Response.Point.iLatitude = ProtobufReadVarint(&Msg, ProtobufReadFind(&Msg, 1)); + Response.Point.iLongitude = ProtobufReadVarint(&Msg, ProtobufReadFind(&Msg, 2)); + } + + ZPrintf("http2: name (%s), location (%d/%d)\n", Response.strName, Response.Point.iLatitude, Response.Point.iLongitude); + return(pBuffer+iMsgSize); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2RecordRoute + + \Description + Encodes the payload for the RecordRoute RPC + + \Output + FixedBufferT - the buffer we have encoded + + \Version 07/05/2017 (eesponda) +*/ +/********************************************************************************F*/ +static FixedBufferT _CmdHttp2RecordRoute(void) +{ + /* + rpc RecordRoute(stream Point) returns (RouteSummary) {} + + message Point { + int32 latitude = 1; + int32 longitude = 2; + } + */ + + FixedBufferT Payload; + ProtobufWriteRefT *pEncoder; + const int32_t iIndex = rand() % (sizeof(_aFeatureLocations)/sizeof(_aFeatureLocations[0])); + + ds_memclr(&Payload, sizeof(Payload)); + pEncoder = ProtobufWriteCreate(Payload.aData+1, sizeof(Payload.aData)-1, TRUE); + ProtobufWriteVarint(pEncoder, _aFeatureLocations[iIndex].iLatitude, 1); + ProtobufWriteVarint(pEncoder, _aFeatureLocations[iIndex].iLongitude, 2); + + // write the header + _CmdHttp2WriteGrpcHeader(pEncoder, &Payload); + + return(Payload); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2RecordRouteResponse + + \Description + Handles the response for the RecordRoute RPC + + \Input *pBuffer - payload + \Input iBufLen - size of payload + + \Output + const uint8_t * - new buffer location past the response + + \Version 07/05/2017 (eesponda) +*/ +/********************************************************************************F*/ +static const uint8_t *_CmdHttp2RecordRouteResponse(const uint8_t *pBuffer, int32_t iBufLen) +{ + int32_t iMsgSize; + ProtobufReadT Reader; + + struct { + int32_t iPointCount; + int32_t iFeatureCount; + int32_t iDistance; + int32_t iElapsedTime; + } Response; + + /* + rpc RecordRoute(stream Point) returns (RouteSummary) {} + + message RouteSummary { + int32 point_count = 1; + int32 feature_count = 2; + int32 distance = 3; + int32 elapsed_time = 4; + } + */ + + // get message size (skipping compression) + pBuffer = ProtobufCommonReadSize(pBuffer+1, iBufLen-1, &iMsgSize); + ProtobufReadInit(&Reader, pBuffer, iMsgSize); + + Response.iPointCount = (int32_t)ProtobufReadVarint(&Reader, ProtobufReadFind(&Reader, 1)); + Response.iFeatureCount = (int32_t)ProtobufReadVarint(&Reader, ProtobufReadFind(&Reader, 2)); + Response.iDistance = (int32_t)ProtobufReadVarint(&Reader, ProtobufReadFind(&Reader, 3)); + Response.iElapsedTime = (int32_t)ProtobufReadVarint(&Reader, ProtobufReadFind(&Reader, 4)); + + ZPrintf("http2: pointcount %d, featurecount %d, distance %d, elapsedtime %d\n", Response.iPointCount, Response.iFeatureCount, Response.iDistance, Response.iElapsedTime); + return(pBuffer+iMsgSize); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2RouteChat + + \Description + Encodes the payload for the RouteChat RPC + + \Output + FixedBufferT - the buffer we have encoded + + \Version 07/05/2017 (eesponda) +*/ +/********************************************************************************F*/ +static FixedBufferT _CmdHttp2RouteChat(void) +{ + /* + rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} + + message RouteNote { + Point location = 1; + string message = 2; + } + */ + + FixedBufferT Payload = { 0, { 0 } }; + int32_t iIndex; + const char *aMessages[] = { "First message", "Second message", "Third message", "Fourth message" }; + LocationT aLocations[] = { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 0, 0 } }; + + for (iIndex = 0; iIndex < 4; iIndex += 1) + { + FixedBufferT Message = { 0, { 0 } }; + ProtobufWriteRefT *pEncoder; + + pEncoder = ProtobufWriteCreate(Message.aData+1, sizeof(Message.aData)-1, TRUE); + ProtobufWriteMessageBegin(pEncoder, 1); + ProtobufWriteVarint(pEncoder, aLocations[iIndex].iLatitude, 1); + ProtobufWriteVarint(pEncoder, aLocations[iIndex].iLongitude, 2); + ProtobufWriteMessageEnd(pEncoder); + ProtobufWriteString(pEncoder, aMessages[iIndex], (signed)strlen(aMessages[iIndex]), 2); + + _CmdHttp2WriteGrpcHeader(pEncoder, &Message); + + ds_memcpy(Payload.aData+Payload.iSize, Message.aData, Message.iSize); + Payload.iSize += Message.iSize; + } + + return(Payload); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2RouteChatResponse + + \Description + Handles the response for the RouteChat RPC + + \Input *pBuffer - payload + \Input iBufLen - size of payload + + \Output + const uint8_t * - new buffer location past the response + + \Version 07/05/2017 (eesponda) +*/ +/********************************************************************************F*/ +static const uint8_t *_CmdHttp2RouteChatResponse(const uint8_t *pBuffer, int32_t iBufLen) +{ + int32_t iMsgSize; + ProtobufReadT Reader, Msg; + + struct { + LocationT Point; + char strMessage[256]; + } Response; + + /* + rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} + + message RouteNote { + Point location = 1; + string message = 2; + } + */ + + // get message size (skipping compression) + pBuffer = ProtobufCommonReadSize(pBuffer+1, iBufLen-1, &iMsgSize); + ProtobufReadInit(&Reader, pBuffer, iMsgSize); + + if (ProtobufReadMessage(&Reader, ProtobufReadFind(&Reader, 1), &Msg) != NULL) + { + Response.Point.iLatitude = ProtobufReadVarint(&Msg, ProtobufReadFind(&Msg, 1)); + Response.Point.iLongitude = ProtobufReadVarint(&Msg, ProtobufReadFind(&Msg, 2)); + } + ProtobufReadString(&Reader, ProtobufReadFind(&Reader, 2), Response.strMessage, sizeof(Response.strMessage)); + + ZPrintf("http2: location (%d/%d), message (%s)\n", Response.Point.iLatitude, Response.Point.iLongitude, Response.strMessage); + return(pBuffer+iMsgSize); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2CleaupSend + + Cleans up the module ref for a request send + + \Input *pHttp2 - module ref for this test to cleanup + + \Version 12/01/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHttp2CleaupSend(Http2RefT *pHttp2) +{ + ds_memclr(&pHttp2->Buffer, sizeof(pHttp2->Buffer)); + pHttp2->iDataSent = 0; + pHttp2->pGen = NULL; + pHttp2->iMaxIndex = 0; + pHttp2->iIndex = 0; +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2IdleCb + + \Description + Update for the http2 testing module + + \Input *pArgz - environment + \Input iArgc - standard number of arguments + \Input *pArgv[] - standard arg list + + \Version 12/01/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CmdHttp2IdleCb(ZContext *pArgz, int32_t iArgc, char *pArgv[]) +{ + int32_t iResult; + Http2RefT *pHttp2 = &_Http2; + + // clean up the ref if needed + if (iArgc == 0) + { + if (pHttp2->pHttp != NULL) + { + ProtoHttp2Destroy(pHttp2->pHttp); + ProtoSSLClrCACerts(); + pHttp2->pHttp = NULL; + } + return(0); + } + + // try to send data + if (pHttp2->Buffer.iSize > 0) + { + if (pHttp2->iDataSent < pHttp2->Buffer.iSize) + { + int32_t iDataSent; + if ((iDataSent = ProtoHttp2Send(pHttp2->pHttp, pHttp2->iStreamId, pHttp2->Buffer.aData+pHttp2->iDataSent, pHttp2->Buffer.iSize-pHttp2->iDataSent)) < 0) + { + return(1); + } + pHttp2->iDataSent += iDataSent; + } + else if (pHttp2->iIndex < pHttp2->iMaxIndex) + { + pHttp2->Buffer = pHttp2->pGen(); + pHttp2->iDataSent = 0; + pHttp2->iIndex += 1; + } + else + { + _CmdHttp2CleaupSend(pHttp2); + ProtoHttp2Send(pHttp2->pHttp, pHttp2->iStreamId, NULL, PROTOHTTP2_STREAM_END); + } + } + + // try to read data + if (pHttp2->iStreamId != 0) + { + ProtoHttpRequestTypeE eRequestType = ProtoHttp2Status(pHttp2->pHttp, pHttp2->iStreamId, 'rtyp', NULL, 0); + + /* for straight gets we want to use this to measure how fast our downloads are + other types can be handled as before */ + if (eRequestType == PROTOHTTP_REQUESTTYPE_GET) + { + uint8_t strBuf[16*1024]; + while ((iResult = ProtoHttp2Recv(pHttp2->pHttp, pHttp2->iStreamId, strBuf, 1, sizeof(strBuf))) > 0) + { + ProtoHttp2Update(pHttp2->pHttp); + pHttp2->iCount += iResult; + } + + if (pHttp2->iCount != pHttp2->iShow) + { + ZPrintf("http2: downloaded %d bytes\n", pHttp2->iCount); + pHttp2->iShow = pHttp2->iCount; + } + if (iResult == PROTOHTTP2_RECVDONE || iResult == PROTOHTTP2_RECVHEAD) + { + int32_t iTickDiff = NetTickDiff(NetTick(), pHttp2->uTimer); + + iResult = ProtoHttp2Status(pHttp2->pHttp, pHttp2->iStreamId, 'head', NULL, 0); + ZPrintf("http2: 'head' %d\n", iResult); + + iResult = ProtoHttp2Status(pHttp2->pHttp, pHttp2->iStreamId, 'body', NULL, 0); + ZPrintf("http2: 'body' %d in %.2f seconds (%.3f k/sec)\n", iResult, (float)iTickDiff/1000.0f, + ((float)iResult * 1000.0f) / ((float)iTickDiff * 1024.0f)); + + iResult = ProtoHttp2Status(pHttp2->pHttp, pHttp2->iStreamId, 'done', NULL, 0); + ZPrintf("http2: 'done' %s\n", iResult ? "TRUE" : "FALSE"); + + ProtoHttp2StreamFree(pHttp2->pHttp, pHttp2->iStreamId); + pHttp2->iStreamId = 0; + pHttp2->iCount = pHttp2->iShow = 0; + } + } + else + { + uint8_t bDone; + iResult = ProtoHttp2RecvAll(pHttp2->pHttp, pHttp2->iStreamId, pHttp2->pBuf, pHttp2->iBufSize); + bDone = iResult != PROTOHTTP2_RECVWAIT && iResult != PROTOHTTP2_RECVBUFF; + + // handle finished + if (iResult > 0) + { + int32_t iBody; + int32_t iTickDiff = NetTickDiff(NetTick(), pHttp2->uTimer); + + iResult = ProtoHttp2Status(pHttp2->pHttp, pHttp2->iStreamId, 'head', NULL, 0); + ZPrintf("http2: 'head' %d\n", iResult); + + iBody = ProtoHttp2Status(pHttp2->pHttp, pHttp2->iStreamId, 'body', NULL, 0); + ZPrintf("http2: 'body' %d in %.2f seconds (%.3f k/sec)\n", iBody, (float)iTickDiff/1000.0f, + ((float)iBody * 1000.0f) / ((float)iTickDiff * 1024.0f)); + + iResult = ProtoHttp2Status(pHttp2->pHttp, pHttp2->iStreamId, 'done', NULL, 0); + ZPrintf("http2: 'done' %s\n", iResult ? "TRUE" : "FALSE"); + + iResult = ProtoHttp2Status(pHttp2->pHttp, pHttp2->iStreamId, 'hres', NULL, 0); + ZPrintf("http2: 'hres' 0x%08x\n", iResult); + + // we have received the full response, now we can handle it + if (pHttp2->pResp != NULL) + { + const uint8_t *pCur = pHttp2->pBuf; + const uint8_t *pEnd = pHttp2->pBuf+iBody; + while (pCur < pEnd) + { + pCur = pHttp2->pResp(pCur, (int32_t)(pEnd-pCur)); + } + } + } + // increase size if needed + else if (iResult == PROTOHTTP2_RECVBUFF) + { + if (pHttp2->iBufSize == 0) + { + pHttp2->pBuf = ZMemAlloc(4096); + pHttp2->iBufSize = 4096; + } + else + { + uint8_t *pNewBuf = ZMemAlloc(pHttp2->iBufSize * 2); + ds_memcpy(pNewBuf, pHttp2->pBuf, pHttp2->iBufSize); + + ZMemFree(pHttp2->pBuf); + pHttp2->pBuf = pNewBuf; + pHttp2->iBufSize *= 2; + } + + ZPrintf("http2: allocated larger buffer: new size %d\n", pHttp2->iBufSize); + } + else if ((iResult == PROTOHTTP2_RECVFAIL) || (iResult == PROTOHTTP2_TIMEOUT)) + { + iResult = ProtoHttp2Status(pHttp2->pHttp, pHttp2->iStreamId, 'hres', NULL, 0); + ZPrintf("http2: receive failed (0x%08x)\n", iResult); + } + + // cleanup now that we are done + if (bDone == TRUE) + { + ProtoHttp2StreamFree(pHttp2->pHttp, pHttp2->iStreamId); + pHttp2->iStreamId = 0; + + ZMemFree(pHttp2->pBuf); + pHttp2->pBuf = NULL; + pHttp2->iBufSize = 0; + pHttp2->pResp = NULL; + } + } + } + + // update the ref + ProtoHttp2Update(pHttp2->pHttp); + + return(ZCallback(_CmdHttp2IdleCb, 1)); // slow enough to allow us to test abort +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2WriteCb + + \Description + Callback when we receive data (if registered) + + \Input *pState - module state + \Input *pCbInfo - information about the request + \Input *pData - data we are receiving + \Input iDataSize - size of the data + \Input *pUserData - user specific data + + \Output + int32_t - result of the operation + + \Version 12/01/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CmdHttp2WriteCb(ProtoHttp2RefT *pState, const ProtoHttp2WriteCbInfoT *pCbInfo, const uint8_t *pData, int32_t iDataSize, void *pUserData) +{ + Http2RefT *pHttp2 = (Http2RefT *)pUserData; + ZPrintf("received %d\n", iDataSize); + + // if the request is complete then cleanup the stream + if ((iDataSize == PROTOHTTP2_RECVDONE) || (iDataSize == PROTOHTTP2_RECVFAIL) || (iDataSize == PROTOHTTP2_TIMEOUT)) + { + ProtoHttp2StreamFree(pState, pCbInfo->iStreamId); + pHttp2->iStreamId = 0; + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttp2CreateGrpcRequest + + \Description + Wrapper to create grpc request + + \Input *pHttp2 - module state + \Input *pUri - the path of the request + \Input *pPayloadFn - pointer to function to generate payload data + \Input iNumPayload - number of entries in array + \Input *pResponseFn - pointer to function to handle response data + + \Version 12/01/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _CmdHttp2CreateGrpcRequest(Http2RefT *pHttp2, const char *pUri, GenerateFn *pPayloadFn, int32_t iNumPayloads, ResponseFn *pResponseFn) +{ + char strUrl[128]; + + // setup url + ds_snzprintf(strUrl, sizeof(strUrl), "%s/%s", pHttp2->strGrpcHost, pUri); + + // update headers, we set null first to clear the previous headers + ProtoHttp2Control(pHttp2->pHttp, 0, 'apnd', 0, 0, NULL); + ProtoHttp2Control(pHttp2->pHttp, 0, 'apnd', 0, 0, (void *)"te: trailers\r\ncontent-type: application/grpc\r\n"); + + pHttp2->uTimer = NetTick(); + + // create the request + if (ProtoHttp2Request(pHttp2->pHttp, strUrl, NULL, PROTOHTTP2_STREAM_BEGIN, PROTOHTTP_REQUESTTYPE_POST, &pHttp2->iStreamId) < 0) + { + ZPrintf("http2: failed to create request %s\n", pUri); + return; + } + // set the array and num indexies + pHttp2->pGen = pPayloadFn; + pHttp2->pResp = pResponseFn; + pHttp2->iMaxIndex = iNumPayloads; + // set index to 1 as we always read first entry + pHttp2->iIndex = 1; + // read the first entry + pHttp2->Buffer = pPayloadFn(); +} + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdHttp2 + + \Description + Entrypoint for the http2 testing module + + \Input *pArgz - environment + \Input iArgc - standard number of arguments + \Input *pArgv[] - standard arg list + + \Output + int32_t - standard return value + + \Version 12/01/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CmdHttp2(ZContext *pArgz, int32_t iArgc, char *pArgv[]) +{ + Http2RefT *pHttp2 = &_Http2; + + // allocate the module if needed + if (pHttp2->pHttp == NULL) + { + if ((pHttp2->pHttp = ProtoHttp2Create(0)) == NULL) + { + ZPrintf("http2: could not allocate module state\n"); + return(1); + } + } + + // make sure there are enough parameters + if (iArgc < 2) + { + return(0); + } + + // close the connection + if (ds_stricmp(pArgv[1], "close") == 0) + { + ProtoHttp2Close(pHttp2->pHttp); + } + // update the logging + else if (ds_stricmp(pArgv[1], "spam") == 0) + { + ProtoHttp2Control(pHttp2->pHttp, PROTOHTTP2_INVALID_STREAMID, 'spam', atoi(pArgv[2]), 0, NULL); + } + else if (ds_stricmp(pArgv[1], "time") == 0) + { + ProtoHttp2Control(pHttp2->pHttp, PROTOHTTP2_INVALID_STREAMID, 'time', atoi(pArgv[2]), 0, NULL); + } + else if (ds_stricmp(pArgv[1], "grpc") == 0) + { + ds_strnzcpy(pHttp2->strGrpcHost, pArgv[2], sizeof(pHttp2->strGrpcHost)); + } + + if (pHttp2->iStreamId == 0) + { + // handle the normal get/head/options + if ((ds_stricmp(pArgv[1], "get") == 0) || (ds_stricmp(pArgv[1], "head") == 0) || (ds_stricmp(pArgv[1], "options") == 0)) + { + // a bit nasty but gets the job done + ProtoHttpRequestTypeE eRequestType = *pArgv[1] == 'g' ? PROTOHTTP_REQUESTTYPE_GET : *pArgv[1] == 'h' ? PROTOHTTP_REQUESTTYPE_HEAD : PROTOHTTP_REQUESTTYPE_OPTIONS; + + pHttp2->uTimer = NetTick(); + + if ((iArgc == 4) && (ds_stricmp(pArgv[3], "-cb") == 0)) + { + ProtoHttp2RequestCb(pHttp2->pHttp, pArgv[2], NULL, 0, eRequestType, &pHttp2->iStreamId, _CmdHttp2WriteCb, &_Http2); + } + else + { + ProtoHttp2Request(pHttp2->pHttp, pArgv[2], NULL, 0, eRequestType, &pHttp2->iStreamId); + } + } + else if (ds_stricmp(pArgv[1], "test1") == 0) + { + _CmdHttp2CreateGrpcRequest(pHttp2, "routeguide.RouteGuide/GetFeature", &_CmdHttp2GetFeature, 0, &_CmdHttp2GetFeatureResponse); + } + else if (ds_stricmp(pArgv[1], "test2") == 0) + { + _CmdHttp2CreateGrpcRequest(pHttp2, "routeguide.RouteGuide/ListFeatures", &_CmdHttp2ListFeatures, 0, &_CmdHttp2ListFeaturesResponse); + } + else if (ds_stricmp(pArgv[1], "test3") == 0) + { + _CmdHttp2CreateGrpcRequest(pHttp2, "routeguide.RouteGuide/RecordRoute", &_CmdHttp2RecordRoute, 10, _CmdHttp2RecordRouteResponse); + } + else if (ds_stricmp(pArgv[1], "test4") == 0) + { + _CmdHttp2CreateGrpcRequest(pHttp2, "routeguide.RouteGuide/RouteChat", &_CmdHttp2RouteChat, 5, _CmdHttp2RouteChatResponse); + } + } + else + { + if (ds_stricmp(pArgv[1], "abort") == 0) + { + // abort the request + ProtoHttp2Abort(pHttp2->pHttp, pHttp2->iStreamId); + + // cleanup send data + _CmdHttp2CleaupSend(pHttp2); + + // cleanup stream + ProtoHttp2StreamFree(pHttp2->pHttp, pHttp2->iStreamId); + pHttp2->iStreamId = 0; + } + } + + return(ZCallback(_CmdHttp2IdleCb, 1)); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/httpmgr.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/httpmgr.c new file mode 100644 index 00000000..514b75e2 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/httpmgr.c @@ -0,0 +1,1514 @@ +/*H********************************************************************************/ +/*! + \File httpmgr.c + + \Description + Implements basic http get and post client. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 10/28/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/proto/protohttpmanager.h" +#include "DirtySDK/proto/protossl.h" + +#include "libsample/zlib.h" +#include "libsample/zmem.h" +#include "libsample/zfile.h" + +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +#define HTTP_BUFSIZE (100) +#define HTTP_RATE (1) +#define HTTP_MAXCMDS (64) // max number of in-flight commands + +#define HTTP_XTRAHDR0 "" +#define HTTP_XTRAHDR1 "X-Agent: DirtySock HTTP Tester\r\n" // test "normal" replacement (replaces Accept: header) +#define HTTP_XTRAHDR2 "User-Agent: DirtySock HTTP Tester\r\n" // test "extended" replacement (replaces User-Agent: and Accept: headers) + +//$$ tmp -- special test header used for hard-coded multipart/form-data testing -- this should be removed at some point when we have real multipart/form-data support +#define HTTP_XTRAHDR3 "Content-Type: multipart/form-data; boundary=TeStInG\r\n" \ + "User-Agent: DirtySock HTTP Tester\r\n" \ + "Accept: */*\r\n" + +#define HTTP_APNDHDR HTTP_XTRAHDR0 + +/*** Function Prototypes **********************************************************/ + +static int32_t _CmdHttpMgrIdleCB(ZContext *argz, int32_t argc, char *argv[]); + +/*** Type Definitions *************************************************************/ + +typedef struct HttpStateT // individual request states +{ + HttpManagerRefT *pHttpManager; + enum + { + IDLE, DNLOAD, UPLOAD, MGET + } state; + int32_t iHandle; + char strCookie[1024]; + int64_t iDataSize; + int64_t iSentSize; + int32_t iSendBufData; + int32_t iSendBufSent; + ZFileT iInpFile; + ZFileT iOutFile; + int32_t iOutSize; + char *pOutData; + int64_t show; + int64_t count; + int32_t sttime; + uint8_t bStreaming; + char strFileBuffer[16*1024]; +}HttpStateT; + +typedef struct HttpMgrRefT // module state storage +{ + HttpManagerRefT *pHttpManager; + int32_t iDebugLevel; + const char *pMgetBuffer; + const char *pMgetOffset; + uint32_t uMgetStart; + uint32_t uMgetTransactions; + + uint8_t bMgetShowUrlsOnly; + uint8_t bRecvAll; // use recvall instead of recv + uint8_t bUseWriteCb; + uint8_t _pad; + + char strModuleName[32]; + char strMgetFilename[256]; + char strApndHdr[2048]; + + // module state + HttpStateT States[HTTP_MAXCMDS]; + +} HttpMgrRefT; + +/*** Variables ********************************************************************/ + +static HttpMgrRefT _HttpMgr_Ref; +static uint8_t _HttpMgr_bInitialized = FALSE; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _HttpCheckHndlAndHref + + \Description + Validate 'hndl' and 'href' status selectors + + \Input *pState - transaction state + \Input *pProtoHttp - protohttpref to get handle for + \Input *pLogStr - string to identify caller + + \Version 10/03/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpCheckHndlAndHref(HttpStateT *pState, ProtoHttpRefT *pProtoHttp, const char *pLogStr) +{ + HttpMgrRefT *pRef = &_HttpMgr_Ref; + ProtoHttpRefT *pProtoHttpCheck; + int32_t iHandle, iResult; + + // get handle from href + if ((iHandle = HttpManagerStatus(pState->pHttpManager, -1, 'hndl', &pProtoHttp, sizeof(pProtoHttp))) < 0) + { + ZPrintf("httpmgr: %s request could not get handle for href %p\n", pLogStr, pProtoHttp); + return; + } + // get href from handle + if ((iResult = HttpManagerStatus(pState->pHttpManager, iHandle, 'href', &pProtoHttpCheck, sizeof(pProtoHttpCheck))) < 0) + { + ZPrintf("httpmgr: %s request could not get href for handle %d\n", pLogStr, iHandle); + return; + } + // make sure we got the right href + if (pProtoHttp != pProtoHttpCheck) + { + ZPrintf("httpmgr: %s request got wrong href %p for handle %d (expected %p)\n", pLogStr, pProtoHttpCheck, iHandle, pProtoHttp); + return; + } + // log success + if (pRef->iDebugLevel > 1) + { + ZPrintf("httpmgr: processing %s request for handle %d (ref %p)\n", pLogStr, iHandle, pProtoHttp); + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpCustomHeaderCallback + + \Description + ProtoHttp send header callback. + + \Input *pProtoHttp - protohttp module state + \Input *pHeader - received header + \Input uHeaderSize - header size + \Input *pUserData - user ref (HttpMgrRefT) + + \Output + int32_t - zero + + \Notes + The header returned should be terminated by a *single* CRLF; ProtoHttp will + append the final CRLF to complete the header. The callback may return the + size of the header, or zero, in which case ProtoHttp will calculate the + headersize using strlen(). + + \Version 02/24/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpCustomHeaderCallback(ProtoHttpRefT *pProtoHttp, char *pHeader, uint32_t uHeaderSize, const char *pData, int64_t iDataLen, void *pUserRef) +{ + HttpStateT *pState = (HttpStateT *)pUserRef; + uint32_t uBufSize; + char *pAppend; + + // check 'hndl' and 'href' status selectors + _HttpCheckHndlAndHref(pState, pProtoHttp, "custom header"); + + // find append point and calc free header space + pAppend = pHeader + strlen(pHeader); + uBufSize = uHeaderSize - (uint32_t)(pAppend - pHeader); + + // append our header info +#if !DIRTYCODE_XBOXONE + if (pState->strCookie[0] != '\0') + { + /* $$todo -- cookies aren't really saved across multiple transactions, so that has + to be solved before this will work */ + ds_snzprintf(pAppend, uBufSize, "Cookie: %s\r\n%s", pState->strCookie, HTTP_APNDHDR); + } + else +#endif + { + ds_strnzcpy(pAppend, "X-Append: Custom append test\r\n", uBufSize); + } + + // recalc header size + uHeaderSize = (uint32_t)strlen(pHeader); + + return(uHeaderSize); +} + +/*F********************************************************************************/ +/*! + \Function _HttpRecvHeaderCallback + + \Description + ProtoHttp recv header callback. + + \Input *pProtoHttp - protohttp module state + \Input *pHeader - received header + \Input uHeaderSize - header size + \Input *pUserData - user ref (HttpMgrRefT) + + \Output + None. + + \Version 02/24/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpRecvHeaderCallback(ProtoHttpRefT *pProtoHttp, const char *pHeader, uint32_t uHeaderSize, void *pUserRef) +{ + HttpMgrRefT *pRef = &_HttpMgr_Ref; + HttpStateT *pState = (HttpStateT *)pUserRef; + char strBuffer[1024], strName[128]; + const char *pHdrTmp; + int32_t iLocnSize; + + // check 'hndl' and 'href' status selectors + _HttpCheckHndlAndHref(pState, pProtoHttp, "receive header"); + + // check for location header + if ((iLocnSize = ProtoHttpGetHeaderValue(pProtoHttp, pHeader, "location", NULL, 0, NULL)) > 0) + { + if (pRef->iDebugLevel > 1) + { + ZPrintf("httpmgr: location header size=%d\n", iLocnSize); + } + if (ProtoHttpGetHeaderValue(pProtoHttp, pHeader, "location", strBuffer, sizeof(strBuffer), NULL) == 0) + { + if (pRef->iDebugLevel > 1) + { + ZPrintf("httpmgr: location url='%s'\n", strBuffer); + } + } + else + { + ZPrintf("httpmgr: error querying location url\n"); + } + } + + // test ProtoHttpGetNextHeader() + for (pHdrTmp = pHeader; ProtoHttpGetNextHeader(pProtoHttp, pHdrTmp, strName, sizeof(strName), strBuffer, sizeof(strBuffer), &pHdrTmp) == 0; ) + { + #if 0 + ZPrintf("httpmgr: ===%s: %s\n", strName, strBuffer); + #endif + } + + // parse any set-cookie requests + for (pHdrTmp = pHeader; ProtoHttpGetHeaderValue(pProtoHttp, pHdrTmp, "set-cookie", strBuffer, sizeof(strBuffer), &pHdrTmp) == 0; ) + { + // print the cookie + if (pRef->iDebugLevel > 1) + { + ZPrintf("httpmgr: parsed cookie '%s'\n", strBuffer); + } + + // add field seperator + if (pState->strCookie[0] != '\0') + { + ds_strnzcat(pState->strCookie, ", ", sizeof(pState->strCookie)); + } + // append to cookie list + ds_strnzcat(pState->strCookie, strBuffer, sizeof(pState->strCookie)); + } + + // if we have any cookies, set them here for inclusion in next request + if (pState->strCookie[0] != '\0') + { + // format append header + ds_snzprintf(strBuffer, sizeof(strBuffer), "Cookie: %s\r\n%s", pState->strCookie, HTTP_APNDHDR); + ProtoHttpControl(pProtoHttp, 'apnd', 0, 0, strBuffer); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpMgrAllocState + + \Description + Allocate an HttpStateT ref for tracking a transaction. + + \Input *pRef - pointer to httpmgr ref + + \Output + HttpStateT * - allocated state, or NULL on failure + + \Version 02/15/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static HttpStateT *_HttpMgrAllocState(HttpMgrRefT *pRef) +{ + HttpStateT *pState; + int32_t iState; + + for (iState = 0; iState < HTTP_MAXCMDS; iState += 1) + { + pState = &pRef->States[iState]; + if (pState->iHandle == 0) + { + // clear any previous stats + pState->count = 0; + pState->show = 0; + pState->iDataSize = 0; + pState->iSentSize = 0; + pState->bStreaming = FALSE; + pState->iHandle = HttpManagerAlloc(pRef->pHttpManager); + pState->pHttpManager = pRef->pHttpManager; + + // set callback user data pointer + HttpManagerControl(pRef->pHttpManager, pState->iHandle, 'cbup', 0, 0, pState); + + // init start timer + pState->sttime = NetTick(); + + // return initialized state to caller + return(pState); + } + } + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function _HttpMgrFreeState + + \Description + Free an allocated HttpStateT ref. + + \Input *pRef - pointer to httpmgr ref + \Input *pState - pointer to state to free + + + \Version 02/18/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpMgrFreeState(HttpMgrRefT *pRef, HttpStateT *pState) +{ + // free handle + HttpManagerFree(pRef->pHttpManager, pState->iHandle); + // reset state memory + ds_memclr(pState, sizeof(*pState)); +} + +/*F********************************************************************************/ +/*! + \Function _HttpMgrReallocBuff + + \Description + Free an allocated HttpStateT ref. + + \Input *pRef - pointer to httpmgr ref + \Input *pState - pointer to transaction state + + \Version 07/29/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpMgrReallocBuff(HttpMgrRefT *pRef, HttpStateT *pState) +{ + char *pNewData; + int32_t iNewSize; + + // calc new buffer size + if ((iNewSize = pState->iOutSize) == 0) + { + // try getting body size + if ((iNewSize = HttpManagerStatus(pRef->pHttpManager, pState->iHandle, 'body', NULL, 0)) > 0) + { + // bump up buffer size for recvall null terminator + //$$ TODO V9 -- why 2 required, not 1?? + iNewSize += 2; + } + else + { + // assign a fixed size, since we didn't get a body size + iNewSize = 4096; + } + } + else + { + iNewSize *= 2; + } + + // allocate new buffer + if ((pNewData = ZMemAlloc(iNewSize)) == NULL) + { + return; + } + // if realloc, copy old data and free old pointer + if (pState->pOutData != NULL) + { + ds_memcpy(pNewData, pState->pOutData, pState->iOutSize); + ZMemFree(pState->pOutData); + } + // save new pointer + pState->pOutData = pNewData; + pState->iOutSize = iNewSize; +} + +/*F********************************************************************************/ +/*! + \Function _HttpMgrCheckComplete + + \Description + See if the HTTP transaction is complete. + + \Input *pRef - pointer to http ref + \Input *pCmdName - module name + + \Output + int32_t - completion status from ProtoHttpStatus() 'done' selector + + \Version 10/28/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpMgrCheckComplete(HttpManagerRefT *pHttpManager, HttpStateT *pState, const char *pCmdName) +{ + ProtoHttpResponseE eResponse; + int32_t iResult; + + // wait for ref to be assigned before checking ref status + if ((iResult = HttpManagerStatus(pHttpManager, pState->iHandle, 'href', NULL, 0)) < 0) + { + ZPrintf("httpmgr: waiting for httpref to be assigned\n"); + return(0); + } + // wait for header response + if ((iResult = HttpManagerStatus(pHttpManager, pState->iHandle, 'head', NULL, 0)) == -2) + { + ZPrintf("httpmgr: waiting for head response (result=%d)\n", iResult); + return(0); + } + // check for completion + if ((iResult = HttpManagerStatus(pHttpManager, pState->iHandle, 'done', NULL, 0)) == 0) + { + ZPrintf("httpmgr: waiting for completion (result=%d)\n", iResult); + return(0); + } + + // get completion result + eResponse = (ProtoHttpResponseE)HttpManagerStatus(pHttpManager, pState->iHandle, 'code', NULL, 0); + switch (PROTOHTTP_GetResponseClass(eResponse)) + { + case PROTOHTTP_RESPONSE_SUCCESSFUL: + ZPrintf("%s: success (%d)\n", pCmdName, eResponse); + break; + + case PROTOHTTP_RESPONSE_CLIENTERROR: + ZPrintf("%s: client error %d\n", pCmdName, eResponse); + break; + + case PROTOHTTP_RESPONSE_SERVERERROR: + ZPrintf("%s: server error %d\n", pCmdName, eResponse); + break; + + default: + ZPrintf("%s: unexpected result code %d\n", pCmdName, eResponse); + break; + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _HttpMgrMget + + \Description + Process an mget request + + \Input *pRef - module state + + \Version 02/16/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpMgrMget(HttpMgrRefT *pRef) +{ + const char *pHref = NULL, *pHref2, *pEndQuote; + char strUrl[1024], strFile[1024]; + char *strArgs[4] = { "httpmgr", "get", "", "" }; + + if (pRef->pMgetOffset != NULL) + { + // look for hrefs + for (pHref = strstr(pRef->pMgetOffset, "href=\""); pHref != NULL; pHref = strstr(pHref2, "href=\"")) + { + // skip href text + pHref2 = pHref + 6; + + // find trailing quote + if ((pEndQuote = strchr(pHref2, '"')) == NULL) + { + // skip it + continue; + } + + // copy the url + ds_strsubzcpy(strUrl, sizeof(strUrl), pHref2, (int32_t)(pEndQuote-pHref2)); + + // make sure it's a full URL + if (strncmp(strUrl, "http://", 7) && strncmp(strUrl, "https://", 8)) + { + // skip it + continue; + } + + // make a filename for the file, skipping http ref + ds_snzprintf(strFile, sizeof(strFile), "%s-data\\%s", pRef->strMgetFilename, strUrl+7); + + // issue an http command + strArgs[2] = strUrl; + strArgs[3] = "";//strFile; + + if (!pRef->bMgetShowUrlsOnly) + { + if (CmdHttpMgr(NULL, 3, strArgs) != 0) + { + // safe current url for next time around + pRef->pMgetOffset = pHref2; + break; + } + else + { + pRef->uMgetTransactions += 1; + } + } + else + { + ZPrintf("%s %s %s %s\n", strArgs[0], strArgs[1], strArgs[2], strArgs[3]); + } + } + } + + // update HttpManager ($$note -- should we need to have this here?) + HttpManagerUpdate(pRef->pHttpManager); + + // have we parsed the whole buffer? + if (pHref == NULL) + { + // mark that we have completed parsing the buffer + if (pRef->pMgetOffset != NULL) + { + pRef->pMgetOffset = NULL; + } + + // are all of our transactions complete? + if (HttpManagerStatus(pRef->pHttpManager, -1, 'busy', NULL, 0) == 0) + { + HttpManagerStatT MgetStats; + + // report time taken for mget request + ZPrintf("httpmgr: mget completed in %dms (%d transactions)\n", NetTickDiff(NetTick(), pRef->uMgetStart), pRef->uMgetTransactions); + + // get and display stats + if (HttpManagerStatus(pRef->pHttpManager, -1, 'stat', &MgetStats, sizeof(MgetStats)) == 0) + { + // display stats + ZPrintf("httpmgr: mget transactions: %d\n", MgetStats.uNumTransactions); + if (MgetStats.uNumTransactions > 0) + { + ZPrintf("httpmgr: keepalive transactions: %d\n", MgetStats.uNumKeepAliveTransactions); + ZPrintf("httpmgr: pipelined transactions: %d\n", MgetStats.uNumPipelinedTransactions); + ZPrintf("httpmgr: max active transactions: %d\n", MgetStats.uMaxActiveTransactions); + ZPrintf("httpmgr: max queued transactions: %d\n", MgetStats.uMaxQueuedTransactions); + ZPrintf("httpmgr: sum queue wait time: %dms\n", MgetStats.uSumQueueWaitLatency); + ZPrintf("httpmgr: avg queue wait time: %dms\n", MgetStats.uSumQueueWaitLatency/MgetStats.uNumTransactions); + ZPrintf("httpmgr: max queue wait time: %dms\n", MgetStats.uMaxQueueWaitLatency); + ZPrintf("httpmgr: sum queue free time: %dms\n", MgetStats.uSumQueueFreeLatency); + ZPrintf("httpmgr: avg queue free time: %dms\n", MgetStats.uSumQueueFreeLatency/MgetStats.uNumTransactions); + ZPrintf("httpmgr: max queue free time: %dms\n", MgetStats.uMaxQueueFreeLatency); + ZPrintf("httpmgr: total bytes transferred: %d\n", MgetStats.uTransactionBytes); + ZPrintf("httpmgr: total transaction time: %d\n", MgetStats.uTransactionTime); + ZPrintf("httpmgr: avg bytes per second %.2f\n", (float)MgetStats.uTransactionBytes*1000.0f/(float)MgetStats.uTransactionTime); + ZPrintf("httpmgr: avg transaction size %.2f\n", (float)MgetStats.uTransactionBytes/(float)MgetStats.uNumTransactions); + } + } + else + { + ZPrintf("%s: could not get stats\n", pRef->strModuleName); + } + + // reset stats + ZPrintf("httpmgr: resetting stats\n"); + HttpManagerControl(pRef->pHttpManager, -1, 'stcl', 0, 0, NULL); + + // dispose of mget buffer + ZMemFree((void *)pRef->pMgetBuffer); + pRef->pMgetBuffer = NULL; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpMgrDownloadProcessData + + \Description + Process data for a download transaction. + + \Input *pRef - httpmanager state + \Input *pState - transaction state + \Input iLen - recv response + + \Version 07/02/2012 (jbrookes) split from _HttpMgrDownloadProcess() +*/ +/********************************************************************************F*/ +static void _HttpMgrDownloadProcessData(HttpMgrRefT *pRef, HttpStateT *pState, int32_t iLen) +{ + char strBuf[1024]; + + // see if we should show progress + if ((pState->count/1024) != (pState->show/1024)) + { + pState->show = pState->count; + if (pRef->iDebugLevel > 1) + { + ZPrintf("%s: downloaded %qd bytes\n", pRef->strModuleName, pState->count); + } + } + + // see if we are done + if ((iLen < 0) && (iLen != PROTOHTTP_RECVWAIT)) + { + // get the url we issued + HttpManagerStatus(pRef->pHttpManager, pState->iHandle, 'urls', strBuf, sizeof(strBuf)); + + // completed successfully? + if ((iLen == PROTOHTTP_RECVDONE) || (iLen == PROTOHTTP_RECVHEAD)) + { + if (pRef->iDebugLevel > 1) + { + int32_t iDlTime = NetTickDiff(NetTick(), pState->sttime); + int32_t iHdrCode = HttpManagerStatus(pRef->pHttpManager, pState->iHandle, 'code', NULL, 0); + + ZPrintf("%s: %s download done (%d): %qd bytes in %.2f seconds (%.3f k/sec)\n", pRef->strModuleName, strBuf, iHdrCode, pState->count, + (float)iDlTime/1000.0f, ((float)pState->count * 1000.0f) / ((float)iDlTime * 1024.0f)); + } + + // display some header info (suppress if it's an mget, unless our debuglevel is high) + if ((pRef->pMgetBuffer == NULL) || (pRef->iDebugLevel > 1)) + { + if (HttpManagerStatus(pRef->pHttpManager, pState->iHandle, 'htxt', strBuf, sizeof(strBuf)) >= 0) + { + ZPrintf("%s response header:\n%s\n", pRef->strModuleName, strBuf); + } + + // display a couple of parsed fields + if (HttpManagerStatus(pRef->pHttpManager, pState->iHandle, 'head', NULL, 0) > 0) + { + time_t tLastMod = HttpManagerStatus(pRef->pHttpManager, pState->iHandle, 'date', NULL, 0); + const char *pTime; + int64_t iBodySize; + + if (tLastMod != 0) + { + if ((pTime = ctime(&tLastMod)) != NULL) + { + ZPrintf("%s: Last-Modified: %s", pRef->strModuleName, pTime); + } + } + HttpManagerStatus(pRef->pHttpManager, pState->iHandle, 'body', &iBodySize, sizeof(iBodySize)); + ZPrintf("%s: Content-Length: %qd\n", pRef->strModuleName, iBodySize); + } + } + } + else + { + int32_t iSslFail = HttpManagerStatus(pRef->pHttpManager, pState->iHandle, 'essl', NULL, 0); + ZPrintf("%s: download failed (err=%d, sslerr=%d)\n", pRef->strModuleName, iLen, iSslFail); + if ((iSslFail == PROTOSSL_ERROR_CERT_INVALID) || (iSslFail == PROTOSSL_ERROR_CERT_HOST) || (iSslFail == PROTOSSL_ERROR_CERT_NOTRUST)) + { + ProtoSSLCertInfoT CertInfo; + if (HttpManagerStatus(pRef->pHttpManager, pState->iHandle, 'cert', &CertInfo, sizeof(CertInfo)) == 0) + { + ZPrintf("%s: cert failure (%d): (C=%s, ST=%s, L=%s, O=%s, OU=%s, CN=%s)\n", pRef->strModuleName, iSslFail, + CertInfo.Ident.strCountry, CertInfo.Ident.strState, CertInfo.Ident.strCity, + CertInfo.Ident.strOrg, CertInfo.Ident.strUnit, CertInfo.Ident.strCommon); + } + else + { + ZPrintf("%s: could not get cert info\n", pRef->strModuleName); + } + } + } + + // if file exists, close it + if (pState->iOutFile > 0) + { + ZFileClose(pState->iOutFile); + } + pState->iOutFile = 0; + + // if output buffer exists, free it + if (pState->pOutData != NULL) + { + pState->pOutData = NULL; + } + pState->iOutSize = 0; + + // free state tracking + _HttpMgrFreeState(pRef, pState); + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpMgrRecvData + + \Description + Receive data from HttpManager using eithe HttpManagerRecv() or + HttpManagerRecvAll(). + + \Input *pRef - httpmanager state + \Input *pState - transaction state + + \Version 07/02/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpMgrRecvData(HttpMgrRefT *pRef, HttpStateT *pState) +{ + char strBuf[16*1024]; + int32_t iLen; + + // check for data + if (!pRef->bRecvAll) + { + while ((iLen = HttpManagerRecv(pRef->pHttpManager, pState->iHandle, strBuf, 1, sizeof(strBuf))) > 0) + { + pState->count += iLen; + if (pState->iOutFile != 0) + { + ZFileWrite(pState->iOutFile, strBuf, iLen); + } + } + } + else + { + // receive all the data + if ((iLen = HttpManagerRecvAll(pRef->pHttpManager, pState->iHandle, pState->pOutData, pState->iOutSize)) > 0) + { + pState->count = iLen; + if (pState->iOutFile != 0) + { + ZFileWrite(pState->iOutFile, pState->pOutData, iLen); + } + iLen = PROTOHTTP_RECVDONE; + } + else if (iLen == PROTOHTTP_RECVBUFF) + { + // grow the buffer + _HttpMgrReallocBuff(pRef, pState); + // swallow error code + iLen = 0; + } + } + return(iLen); +} + +/*F********************************************************************************/ +/*! + \Function _HttpMgrDownloadProcess + + \Description + Process a download transaction. + + \Input *pRef - httpmanager state + \Input *pState - transaction state + + \Version 10/28/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpMgrDownloadProcess(HttpMgrRefT *pRef, HttpStateT *pState) +{ + int32_t iLen; + + // if we're not doing the write callback thing, poll for data + for (iLen = 1; (iLen != PROTOHTTP_RECVWAIT) && (iLen != 0) && (pState->state != IDLE); ) + { + // receive data + iLen = _HttpMgrRecvData(pRef, pState); + // process data + _HttpMgrDownloadProcessData(pRef, pState, iLen); + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpMgrWriteCb + + \Description + Implementation of ProtoHttp write callback + + \Input *pState - http module state + \Input *pWriteInfo - callback info + \Input *pData - transaction data pointer + \Input iDataSize - size of data + \Input *pUserData - user callback data + + \Output + int32_t - zero + + \Version 05/03/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpMgrWriteCb(ProtoHttpRefT *pProtoHttp, const ProtoHttpWriteCbInfoT *pWriteInfo, const char *pData, int32_t iDataSize, void *pUserData) +{ + HttpStateT *pState = (HttpStateT *)pUserData; +#if 0 + static const char *_strRequestNames[] = + { + "PROTOHTTP_REQUESTTYPE_HEAD", "PROTOHTTP_REQUESTTYPE_GET", "PROTOHTTP_REQUESTTYPE_POST", + "PROTOHTTP_REQUESTTYPE_PUT", "PROTOHTTP_REQUESTTYPE_DELETE", "PROTOHTTP_REQUESTTYPE_OPTIONS" + }; + ZPrintf("httpmgr: writecb (%s,%d) recv=%d\n", _strRequestNames[pWriteInfo->eRequestType], pWriteInfo->eRequestResponse, iDataSize); +#endif + + // detect minbuff error + if (iDataSize == PROTOHTTP_RECVBUFF) + { + // grow the buffer and return + _HttpMgrReallocBuff(&_HttpMgr_Ref, pState); + return(0); + } + + // update count and write to output file if available + pState->count += iDataSize; + if (pState->iOutFile != ZFILE_INVALID) + { + ZFileWrite(pState->iOutFile, (void *)pData, iDataSize); + } + + // update/completion procesing + _HttpMgrDownloadProcessData(&_HttpMgr_Ref, pState, iDataSize); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpMgrUploadProcess + + \Description + Process an upload transaction. + + \Input *pRef - module state + + \Output + None. + + \Version 10/28/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpMgrUploadProcess(HttpMgrRefT *pRef, HttpStateT *pState) +{ + char strResponse[1024]; + int32_t iCode, iResult; + + // read data? + if (pState->iInpFile != ZFILE_INVALID) + { + while (pState->iSentSize < pState->iDataSize) + { + // do we need more data? + if (pState->iSendBufSent == pState->iSendBufData) + { + if ((pState->iSendBufData = ZFileRead(pState->iInpFile, pState->strFileBuffer, sizeof(pState->strFileBuffer))) > 0) + { + ZPrintf("%s: read %d bytes from file\n", pRef->strModuleName, pState->iSendBufData); + pState->iSendBufSent = 0; + } + else + { + ZPrintf("%s: error %d reading from file\n", pRef->strModuleName, pState->iSendBufData); + pState->state = IDLE; + } + } + + // do we have buffered data to send? + if (pState->iSendBufSent < pState->iSendBufData) + { + iResult = HttpManagerSend(pRef->pHttpManager, pState->iHandle, pState->strFileBuffer + pState->iSendBufSent, pState->iSendBufData - pState->iSendBufSent); + if (iResult > 0) + { + pState->iSentSize += iResult; + pState->iSendBufSent += iResult; + ZPrintf("%s: sent %d bytes\n", pRef->strModuleName, iResult); + } + else if (iResult < 0) + { + ZPrintf("%s: HttpManagerSend() failed; error %d\n", pRef->strModuleName, iResult); + pState->state = IDLE; + } + else + { + break; + } + } + } + // check for upload completion + if (pState->iSentSize == pState->iDataSize) + { + // if streaming upload, signal we are done + if (pState->bStreaming == TRUE) + { + HttpManagerSend(pRef->pHttpManager, pState->iHandle, NULL, PROTOHTTP_STREAM_END); + pState->bStreaming = FALSE; + } + + // done uploading + ZPrintf("%s: uploaded %qd bytes\n", pRef->strModuleName, pState->iSentSize); + + // close the file + ZFileClose(pState->iInpFile); + pState->iInpFile = ZFILE_INVALID; + } + } + + // give it time + HttpManagerUpdate(pRef->pHttpManager); + + // see if we've received an HTTP 1xx (INFORMATIONAL) header + iCode = HttpManagerStatus(pRef->pHttpManager, pState->iHandle, 'info', strResponse, sizeof(strResponse)); + if (PROTOHTTP_GetResponseClass(iCode) == PROTOHTTP_RESPONSE_INFORMATIONAL) + { + // got response header, so print it + NetPrintf(("httpmgr: received %d response header\n----------------------------------\n%s----------------------------------\n", iCode, strResponse)); + } + + // done? + if ((iResult = _HttpMgrCheckComplete(pRef->pHttpManager, pState, pRef->strModuleName)) != 0) + { + if (iResult > 0) + { + int32_t ultime = NetTickDiff(NetTick(), pState->sttime); + ZPrintf("%s: upload complete (%qd bytes)\n", pRef->strModuleName, pState->iSentSize); + ZPrintf("upload time: %qd bytes in %.2f seconds (%.3f k/sec)\n", pState->iSentSize, (float)ultime/1000.0f, + ((float)pState->iSentSize * 1000.0f) / ((float)ultime * 1024.0f)); + + ds_memclr(strResponse, sizeof(strResponse)); + iResult = HttpManagerRecv(pRef->pHttpManager, pState->iHandle, strResponse, 1, sizeof(strResponse)); + if (iResult > 0) + { + ZPrintf("http result:\n%s", strResponse); + } + } + + _HttpMgrFreeState(pRef, pState); + } +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttpMgrIdleCB + + \Description + Callback to process while idle + + \Input *argz - + \Input argc - + \Input *argv[] - + + \Output int32_t - + + \Version 09/26/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdHttpMgrIdleCB(ZContext *argz, int32_t argc, char *argv[]) +{ + HttpMgrRefT *pRef = &_HttpMgr_Ref; + int32_t iState; + + // shut down? + if ((argc == 0) || (pRef->pHttpManager == NULL)) + { + if (pRef->pHttpManager != NULL) + { + HttpManagerDestroy(pRef->pHttpManager); + pRef->pHttpManager = NULL; + } + return(0); + } + + // update httpmanager + if (pRef->pHttpManager != NULL) + { + HttpManagerUpdate(pRef->pHttpManager); + } + + // look for active transactions to process + for (iState = 0; iState < HTTP_MAXCMDS; iState += 1) + { + // process a download (if not using write callback) + if ((pRef->States[iState].state == DNLOAD) && (!pRef->bUseWriteCb)) + { + _HttpMgrDownloadProcess(pRef, &pRef->States[iState]); + } + + // process an upload + if (pRef->States[iState].state == UPLOAD) + { + _HttpMgrUploadProcess(pRef, &pRef->States[iState]); + } + } + + // process mget request + if (pRef->pMgetBuffer != NULL) + { + _HttpMgrMget(pRef); + } + + // keep on idling + return(ZCallback(&_CmdHttpMgrIdleCB, HTTP_RATE)); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttpMgrUsage + + \Description + Display usage information. + + \Input argc - argument count + \Input *argv[] - argument list + + \Output + None. + + \Version 02/18/2008 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CmdHttpMgrUsage(int argc, char *argv[]) +{ + if (argc == 2) + { + ZPrintf(" execute http get or put operations\n"); + ZPrintf(" usage: %s [cclr|cert|cer2|cver|create|ctrl|destroy|free|get|mget|put|puts|parse|stat]", argv[0]); + } + else if (argc == 3) + { + if (!strcmp(argv[2], "cclr") || !strcmp(argv[2], "cert") || !strcmp(argv[2], "cer2") || !strcmp(argv[2], "cver")) + { + ZPrintf(" usage: %s cert|cer2 - load certificate file\n", argv[0]); + ZPrintf(" %s cclr - clear dynamic certificates\n"); + ZPrintf(" %s cver - verify dynamic CA certs\n"); + } + else if (!strcmp(argv[2], "create")) + { + ZPrintf(" usage: %s create \n", argv[0]); + } + else if (!strcmp(argv[2], "destroy")) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + } + else if (!strcmp(argv[2], "free")) + { + ZPrintf(" usage: %s get \n", argv[0]); + } + else if (!strcmp(argv[2], "get")) + { + ZPrintf(" usage: %s get [url] \n", argv[0]); + } + else if (!strcmp(argv[2], "mget")) + { + ZPrintf(" usage: %s mget \n", argv[0]); + } + else if (!strcmp(argv[2], "put")) + { + ZPrintf(" usage: %s put [url] [file]\n", argv[0]); + } + else if (!strcmp(argv[2], "puts")) + { + ZPrintf(" usage: %s puts [url] [file]\n", argv[0]); + } + else if (!strcmp(argv[2], "parse")) + { + ZPrintf(" usage: %s parse [url]\n", argv[0]); + } + } +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdHttpMgr + + \Description + Initiate an HTTP transaction. + + \Input *argz - + \Input argc - + \Input *argv[] - + + \Output int32_t - + + \Version 10/28/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdHttpMgr(ZContext *argz, int32_t argc, char *argv[]) +{ + int32_t iResult = 0, iBufSize = HTTP_BUFSIZE, iNumRefs = 4; + const char *pFileName = NULL, *pUrl; + HttpMgrRefT *pRef = &_HttpMgr_Ref; + HttpStateT *pState = NULL; + const char *pFileData; + int32_t iArg, iStartArg = 2; // first arg past get/put/delete/whatever + int32_t iFileSize; + + if (argc < 2) + { + return(0); + } + + // check for help + if ((argc >= 2) && !strcmp(argv[1], "help")) + { + _CmdHttpMgrUsage(argc, argv); + return(iResult); + } + + // check for 'parse' command + if ((argc == 3) && !strcmp(argv[1], "parse")) + { + char strKind[5], strHost[128]; + const char *pUri; + int32_t iPort, iSecure; + ds_memclr(strKind, sizeof(strKind)); + ds_memclr(strHost, sizeof(strHost)); + pUri = ProtoHttpUrlParse(argv[2], strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure); + ZPrintf("parsed url: kind=%s, host=%s, port=%d, secure=%d, uri=%s\n", strKind, strHost, iPort, iSecure, pUri); + return(0); + } + + // if not initialized yet, do so now + if (_HttpMgr_bInitialized == FALSE) + { + ds_memclr(pRef, sizeof(*pRef)); + _HttpMgr_bInitialized = TRUE; + } + + // check for explicit destroy + if ((argc == 2) && !ds_stricmp(argv[1], "destroy")) + { + if (pRef->pHttpManager != NULL) + { + HttpManagerDestroy(pRef->pHttpManager); + pRef->pHttpManager = NULL; + } + return(iResult); + } + + // check for request to set a certificate + if ((argc == 3) && ((!strcmp(argv[1], "cert")) || (!strcmp(argv[1], "cer2")))) + { + const uint8_t *pCertData; + int32_t iCertSize; + + // try and open file + if ((pCertData = (const uint8_t *)ZFileLoad(argv[2], &iCertSize, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) != NULL) + { + if (!strcmp(argv[1], "cert")) + { + iResult = ProtoHttpSetCACert(pCertData, iCertSize); + } + else + { + iResult = ProtoHttpSetCACert2(pCertData, iCertSize); + } + ZMemFree((void *)pCertData); + } + else + { + ZPrintf("%s: unable to load certificate file '%s'\n", argv[0], argv[2]); + } + return((iResult > 0) ? 0 : -1); + } + else if (!strcmp(argv[1], "cclr")) + { + ZPrintf("%s: clearing dynamic certs\n", argv[0]); + ProtoHttpClrCACerts(); + return(0); + } + else if (!strcmp(argv[1], "cver")) + { + int32_t iInvalid; + ZPrintf("%s: verifying dynamic CA certs\n", argv[0]); + if ((iInvalid = ProtoHttpValidateAllCA()) > 0) + { + ZPrintf("%s: could not verify %d CA certs\n", iInvalid); + iResult = -1; + } + return(iResult); + } + + // check for create request + if ((argc >= 2) && !strcmp(argv[1], "create")) + { + if (argc >= 3) + { + iBufSize = (int32_t)strtol(argv[2], NULL, 10); + } + if (argc >= 4) + { + iNumRefs = (int32_t)strtol(argv[3], NULL, 10); + } + } + + // create httpmanager module if necessary + if (pRef->pHttpManager == NULL) + { + ZPrintf("%s: creating module with a %d refs and %dkbyte buffer\n", argv[0], iNumRefs, iBufSize); + ds_memclr(pRef, sizeof(*pRef)); + ds_strnzcpy(pRef->strModuleName, argv[0], sizeof(pRef->strModuleName)); + pRef->pHttpManager = HttpManagerCreate(iBufSize, iNumRefs); + if (pRef->pHttpManager != NULL) + { + HttpManagerCallback(pRef->pHttpManager, _HttpCustomHeaderCallback, _HttpRecvHeaderCallback); + pRef->iDebugLevel = 1; + HttpManagerControl(pRef->pHttpManager, -1, 'spam', pRef->iDebugLevel, 0, NULL); + } + } + + // check for create request -- if so, we're done + if ((argc >= 2) && !strcmp(argv[1], "create")) + { + return(iResult); + } + else if ((argc > 2) && (argc < 6) && !strcmp(argv[1], "ctrl")) + { + int32_t iCmd, iValue = 0, iValue2 = 0; + + iCmd = argv[2][0] << 24; + iCmd |= argv[2][1] << 16; + iCmd |= argv[2][2] << 8; + iCmd |= argv[2][3]; + + if (argc > 3) + { + iValue = (int32_t)strtol(argv[3], NULL, 10); + } + + if (argc > 4) + { + iValue2 = (int32_t)strtol(argv[4], NULL, 10); + } + + // snoop 'spam' + if (iCmd == 'spam') + { + pRef->iDebugLevel = iValue; + } + + return(HttpManagerControl(pRef->pHttpManager, /*iHandle*/ -1, iCmd, iValue, iValue2, NULL)); + } + else if ((argc == 3) && !strcmp(argv[1], "stat")) + { + int32_t iCmd; + char strBufferTemp[1024] = ""; + + iCmd = argv[2][0] << 24; + iCmd |= argv[2][1] << 16; + iCmd |= argv[2][2] << 8; + iCmd |= argv[2][3]; + + iResult = HttpManagerStatus(pRef->pHttpManager, /*iHandle*/ -1, iCmd, strBufferTemp, sizeof(strBufferTemp)); + ZPrintf("%s: ProtoHttpStatus('%s') returned %d\n", argv[0], argv[2], iResult); + if (strBufferTemp[0] != '\0') + { + ZPrintf("%s\n", strBufferTemp); + } + return(0); + } + // check for setting of base url + else if ((argc == 3) && !strcmp(argv[1], "base")) + { + HttpManagerSetBaseUrl(pRef->pHttpManager, /*iHandle*/ -1, argv[2]); + return(iResult); + } + // check for valid get/put request + else if ((!ds_stricmp(argv[1], "get") || !ds_stricmp(argv[1], "head") || !ds_stricmp(argv[1], "put") || !ds_stricmp(argv[1], "post") || + !ds_stricmp(argv[1], "puts") || !ds_stricmp(argv[1], "delete") || !ds_stricmp(argv[1], "options")) && + ((argc > 2) || (argc < 5))) + { + // allocate a new state record + pState = _HttpMgrAllocState(pRef); + if (pState == NULL) + { + // if we could not allocate state, return error so upper layer can deal with it + return(-1); + } + + // fall-through to code below + } + else if (!ds_stricmp(argv[1], "mget") || !ds_stricmp(argv[1], "free")) + { + // do nothing, fall through + } + else + { + ZPrintf(" unrecognized or badly formatted command '%s'\n", argv[1]); + _CmdHttpMgrUsage(argc, argv); + return(-1); + } + + // clear previous options + pRef->bUseWriteCb = FALSE; + pRef->bRecvAll = FALSE; + + // set up append header + ds_strnzcpy(pRef->strApndHdr, HTTP_APNDHDR, sizeof(pRef->strApndHdr)); + + // check for args + for (iArg = 2; (iArg < argc) && (argv[iArg][0] == '-'); iArg += 1) + { + if (!ds_strnicmp(argv[iArg], "-header=", 8)) + { + ds_strnzcat(pRef->strApndHdr, argv[iArg]+8, sizeof(pRef->strApndHdr)); + ds_strnzcat(pRef->strApndHdr, "\r\n", sizeof(pRef->strApndHdr)); + } + if (!ds_strnicmp(argv[iArg], "-writecb", 8)) + { + pRef->bUseWriteCb = TRUE; + } + if (!ds_strnicmp(argv[iArg], "-recvall", 8)) + { + pRef->bRecvAll = TRUE; + } + // skip any option arguments to find url and (optionally) filename + iStartArg += 1; + } + + // locate url and filename + pUrl = argv[iStartArg]; + if (argc > (iStartArg+1)) + { + pFileName = argv[iStartArg+1]; + } + + if (!pUrl) + { + ZPrintf("%s: no url specified\n", argv[0]); + return(-1); + } + + // set append header + if ((pState != NULL) || (!ds_stricmp(argv[1], "mget"))) + { + char strBuffer[1024] = "\0"; + int32_t iHandle = (pState != NULL) ? pState->iHandle : -1; + //if (pRef->strCookie[0] != '\0') + //{ + // ds_snzprintf(strBuffer, sizeof(strBuffer), "Cookie: %s\r\n", pRef->strCookie); + //} + ds_strnzcat(strBuffer, pRef->strApndHdr, sizeof(strBuffer)); + HttpManagerControl(pRef->pHttpManager, iHandle, 'apnd', 0, 0, strBuffer); + } + + // see if we're uploading or downloading + if (!ds_stricmp(argv[1], "put") || !ds_stricmp(argv[1], "post") || !ds_stricmp(argv[1], "puts")) + { + ZPrintf("%s: uploading %s to %s\n", argv[0], pFileName, pUrl); + + // assume failure + iResult = -1; + + // try and open file + if ((pState->iInpFile = ZFileOpen(pFileName, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) != ZFILE_INVALID) + { + // get the file size + if ((pState->iDataSize = ZFileSize(pState->iInpFile)) > 0) + { + // load data from file + if ((pState->iSendBufData = ZFileRead(pState->iInpFile, pState->strFileBuffer, sizeof(pState->strFileBuffer))) > 0) + { + if (ds_stricmp(argv[1], "puts")) + { + // initiate put/post transaction + ZPrintf("%s: uploading %qd bytes\n", argv[0], pState->iDataSize); + if ((pState->iSendBufSent = HttpManagerPost(pRef->pHttpManager, pState->iHandle, pUrl, pState->strFileBuffer, pState->iDataSize, !ds_stricmp(argv[1], "put") ? PROTOHTTP_PUT : PROTOHTTP_POST)) < 0) + { + ZPrintf("%s: error %d initiating send\n", pState->iSendBufSent); + iResult = -1; + } + else if (pState->iSendBufSent > 0) + { + ZPrintf("%s: sent %d bytes\n", argv[0], pState->iSendBufSent); + pState->iSentSize = pState->iSendBufSent; + } + } + else + { + // initiate streaming put + if ((iResult = HttpManagerPost(pRef->pHttpManager, pState->iHandle, pUrl, NULL, PROTOHTTP_STREAM_BEGIN, PROTOHTTP_PUT)) == 0) + { + pState->bStreaming = TRUE; + } + } + + // wait for reply + pState->state = UPLOAD; + iResult = 0; + } + else + { + ZPrintf("%s: error %d reading data from file\n", argv[0], pState->iSendBufData, pFileName); + } + } + else + { + ZPrintf("%s: error %d getting size of file %s\n", argv[0], pState->iDataSize, pFileName); + } + } + else + { + ZPrintf("%s: unable to load file '%s'\n", argv[0], pFileName); + } + } + else if (!ds_stricmp(argv[1], "head") || !ds_stricmp(argv[1], "get")) + { + ProtoHttpRequestTypeE eRequestType = !ds_stricmp(argv[1], "head") ? PROTOHTTP_REQUESTTYPE_HEAD : PROTOHTTP_REQUESTTYPE_GET; + + if (pFileName != NULL) + { + if (pRef->iDebugLevel > 1) + { + pState->iOutFile = ZFileOpen(pFileName, ZFILE_OPENFLAG_WRONLY|ZFILE_OPENFLAG_BINARY|ZFILE_OPENFLAG_CREATE); + } + } + else if (pRef->iDebugLevel > 1) + { + ZPrintf("%s: downloading %s\n", argv[0], pUrl); + } + + // initiate transaction + if (pRef->bUseWriteCb) + { + iResult = HttpManagerRequestCb(pRef->pHttpManager, pState->iHandle, pUrl, NULL, 0, eRequestType, _HttpMgrWriteCb, pState); + } + else + { + iResult = HttpManagerRequest(pRef->pHttpManager, pState->iHandle, pUrl, NULL, 0, eRequestType); + } + if (iResult == 0) + { + pState->state = DNLOAD; + } + } + else if (!ds_stricmp(argv[1], "mget") && (pRef->pMgetBuffer == NULL)) + { + if ((pFileData = ZFileLoad(argv[iStartArg], &iFileSize, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) != NULL) + { + // set up for mget process + pRef->pMgetBuffer = pRef->pMgetOffset = pFileData; + pRef->uMgetStart = NetTick(); + ds_strnzcpy(pRef->strMgetFilename, argv[iStartArg], sizeof(pRef->strMgetFilename)); + } + } + else if (!ds_stricmp(argv[1], "delete")) + { + if ((iResult = HttpManagerRequest(pRef->pHttpManager, pState->iHandle, pUrl, NULL, 0, PROTOHTTP_REQUESTTYPE_DELETE)) == 0) + { + pState->state = DNLOAD; + } + } + else if (!ds_stricmp(argv[1], "options")) + { + if ((iResult = HttpManagerRequest(pRef->pHttpManager, pState->iHandle, pUrl, NULL, 0, PROTOHTTP_REQUESTTYPE_DELETE)) == 0) + { + pState->state = DNLOAD; + } + } + else if (!ds_stricmp(argv[1], "free")) + { + int32_t iHandle = atoi(argv[2]); + ZPrintf("HttpManagerFree %d\n", iHandle); + HttpManagerFree(pRef->pHttpManager, iHandle); + } + else + { + ZPrintf("%s: unrecognized request %s\n", argv[0], argv[1]); + iResult = -1; + } + + // set up recurring callback to process transaction, if any + if (((pState != NULL) && (pState->state != IDLE) && (pRef->pMgetBuffer == NULL)) || ((pState == NULL) && (pRef->pMgetBuffer != NULL))) + { + iResult = ZCallback(_CmdHttpMgrIdleCB, HTTP_RATE); + } + return(iResult); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/httpserv.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/httpserv.c new file mode 100644 index 00000000..3ef23eeb --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/httpserv.c @@ -0,0 +1,904 @@ +/*H********************************************************************************/ +/*! + \File httpserv.c + + \Description + Implements basic http server using ProtoHttpServ + + \Copyright + Copyright (c) 2012 Electronic Arts Inc. + + \Version 09/11/2012 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include // gettimeofday +#endif + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtyvers.h" +#include "DirtySDK/dirtysock/netconn.h" // NetConnSleep +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/proto/protohttpserv.h" + +#include "libsample/zlib.h" +#include "libsample/zmem.h" +#include "libsample/zfile.h" + +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +#define HTTPSERV_RATE (1) +#define HTTPSERV_LISTENPORT (9000) + +/*** Function Prototypes **********************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct HttpServT +{ + ProtoHttpServRefT *pHttpServ; + + char *pServerCert; + int32_t iServerCertLen; + char *pServerKey; + int32_t iServerKeyLen; + int32_t iChunkLen; + + char strServerName[128]; + char strFileDir[512]; +} HttpServT; + +/*** Variables ********************************************************************/ + +static HttpServT _HttpServ_Ref; +static uint8_t _HttpServ_bInitialized = FALSE; + +//! map of filename extensions to content-types +static const char *_ProtoHttpServ_strContentTypes[][2] = +{ + { ".htm", "text/html" }, + { ".html", "text/html" }, + { ".css", "text/css" }, + { ".xml", "text/xml" }, + { ".jpg", "image/jpeg" }, + { ".gif", "image/gif" }, + { ".png", "image/png" }, + { ".mp3", "audio/mpeg" } +}; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _GetIntArg + + \Description + Get fourcc/integer from command-line argument + + \Input *pArg - pointer to argument + + \Version 10/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _GetIntArg(const char *pArg) +{ + int32_t iValue; + + // check for possible fourcc value + if ((strlen(pArg) == 4) && (isalpha(pArg[0]) || isalpha(pArg[1]) || isalpha(pArg[2]) || isalpha(pArg[3]))) + { + iValue = pArg[0] << 24; + iValue |= pArg[1] << 16; + iValue |= pArg[2] << 8; + iValue |= pArg[3]; + } + else + { + iValue = (signed)strtol(pArg, NULL, 10); + } + return(iValue); +} + +/*F********************************************************************************/ +/*! + \Function _HttpServGetCurrentTime + + \Description + Gets and parses the current time into components + + \Input *uYear - pointer to uint32_t var to store 'year' in + \Input *uMonth - pointer to uint8_t var to store 'month' in + \Input *uDay - pointer to uint8_t var to store 'day' in + \Input *uHour - pointer to uint8_t var to store 'hour' in + \Input *uMin - pointer to uint8_t var to store 'min' in + \Input *uSec - pointer to uint8_t var to store 'sec' in + \Input *uMillis - pointer to uint32_t var to store 'milliseconds' in + + \Version 10/30/2013 (jbrookes) Borrowed from eafn logger +*/ +/********************************************************************************F*/ +static void _HttpServGetCurrentTime(uint32_t *uYear, uint8_t *uMonth, uint8_t *uDay, uint8_t *uHour, uint8_t *uMin, uint8_t *uSec, uint32_t *uMillis) +{ +#if DIRTYCODE_PC + SYSTEMTIME SystemTime; + GetLocalTime(&SystemTime); + *uYear = SystemTime.wYear; + *uMonth = SystemTime.wMonth; + *uDay = SystemTime.wDay; + *uHour = SystemTime.wHour; + *uMin = SystemTime.wMinute; + *uSec = SystemTime.wSecond; + *uMillis = SystemTime.wMilliseconds; +#else // all non-pc +#if DIRTYCODE_LINUX + struct timeval tv; + struct tm *pTime; + gettimeofday(&tv, NULL); + pTime = gmtime((time_t *)&tv.tv_sec); + *uMillis = tv.tv_usec / 1000; +#else + //$$TODO: plat-time doesn't have anything to get millis + struct tm TmTime, *pTime; + pTime = ds_secstotime(&TmTime, ds_timeinsecs()); + *uMillis = 0; +#endif + *uYear = 1900 + pTime->tm_year; + *uMonth = pTime->tm_mon + 1; + *uDay = pTime->tm_mday; + *uHour = pTime->tm_hour; + *uMin = pTime->tm_min; + *uSec = pTime->tm_sec; +#endif // !DIRTYCODE_PC +} + +/*F********************************************************************************/ +/*! + \Function _HttpServGetContentType + + \Description + Get content-type based on target url + + \Input *strUrl - full url including file + + \Output + const char * - content type + + \Version 07/09/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_HttpServGetContentType(const char *strUrl) +{ + const char *pContentType = _ProtoHttpServ_strContentTypes[0][1]; + int32_t iType; + + for (iType = 0; iType < (signed)(sizeof(_ProtoHttpServ_strContentTypes)/sizeof(_ProtoHttpServ_strContentTypes[0])); iType += 1) + { + if (ds_stristr(strUrl, _ProtoHttpServ_strContentTypes[iType][0])) + { + pContentType = _ProtoHttpServ_strContentTypes[iType][1]; + break; + } + } + + return(pContentType); +} + +/*F********************************************************************************/ +/*! + \Function _HttpServLoadCertificate + + \Description + Load pem certificate file and trim begin/end text. + + \Input *pFilename - name of certificate file to open + \Input *pCertSize - [out] storage for size of certificate + + \Output + const char * - certificate data + + \Version 10/11/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_HttpServLoadCertificate(const char *pFilename, int32_t *pCertSize) +{ + char *pCertBuf; + int32_t iFileSize; + + // load certificate file + if ((pCertBuf = (char *)ZFileLoad(pFilename, &iFileSize, ZFILE_OPENFLAG_RDONLY)) == NULL) + { + return(NULL); + } + + // set size and return buffer to caller + *pCertSize = iFileSize; + return(pCertBuf); +} + +/*F********************************************************************************/ +/*! + \Function _HttpServProcessGet + + \Description + Process GET/HEAD request + + \Input *pServerState - module state + \Input *pRequest - request information + \Input *pResponse - [out] response information + + \Output + int32_t - response code + + \Version 12/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpServProcessGet(HttpServT *pServerState, const ProtoHttpServRequestT *pRequest, ProtoHttpServResponseT *pResponse) +{ + const char* pHdr = pRequest->strHeader; + char strFilePath[4096], strName[128], strValue[4*1024]; + ZFileStatT FileStat; + int32_t iFileLen, iResult; + ZFileT iFileId; + uint32_t uModifiedSince = 0, uUnmodifiedSince = 0; + struct tm TmTime; + char strTime[64]; + + // if empty url, substitute default + if (*(pRequest->strUrl+1) == '\0') + { + ds_snzprintf(strFilePath, sizeof(strFilePath), "%s/index.html", pServerState->strFileDir, pRequest->strUrl + 1); + } + else + { + ds_snzprintf(strFilePath, sizeof(strFilePath), "%s/%s", pServerState->strFileDir, pRequest->strUrl + 1); + } + + // stat the file + if ((iResult = ZFileStat(strFilePath, &FileStat)) != ZFILE_ERROR_NONE) + { + // no file + ZPrintf("httpserv: could not stat file '%s'\n", strFilePath); + pResponse->eResponseCode = PROTOHTTP_RESPONSE_NOTFOUND; + return(-1); + } + + // see if url refers to a file + if ((iFileId = ZFileOpen(strFilePath, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) == ZFILE_INVALID) + { + ZPrintf("httpserv: could not open file '%s' for reading\n", strFilePath); + pResponse->eResponseCode = PROTOHTTP_RESPONSE_INTERNALSERVERERROR; + return(-2); + } + // get the file size + if ((iFileLen = (int32_t)ZFileSize(iFileId)) < 0) + { + ZPrintf("httpserv: error %d getting file size\n", iFileLen); + pResponse->eResponseCode = PROTOHTTP_RESPONSE_INTERNALSERVERERROR; + return(-3); + } + + // parse the header for request specific data + while (ProtoHttpGetNextHeader(NULL, pHdr, strName, sizeof(strName), strValue, sizeof(strValue), &pHdr) == 0) + { + if (ds_stricmp(strName, "if-modified-since") == 0) + { + uModifiedSince = (uint32_t)ds_strtotime(strValue); + } + else if (ds_stricmp(strName, "if-unmodified-since") == 0) + { + uUnmodifiedSince = (uint32_t)ds_strtotime(strValue); + } + } + + if (uModifiedSince != 0 && ((int32_t)(FileStat.uTimeModify-uModifiedSince) <= 0)) + { + pResponse->eResponseCode = PROTOHTTP_RESPONSE_NOTMODIFIED; + ZPrintf("httpserv: file not modified (%d-%d=%d)\n", uModifiedSince, FileStat.uTimeModify, + (int32_t)(FileStat.uTimeModify-uModifiedSince)); + return(0); + } + if (uUnmodifiedSince != 0 && ((int32_t)(FileStat.uTimeModify-uUnmodifiedSince) > 0)) + { + ZPrintf("httpserv: file modified since (%d-%d=%d)\n", uUnmodifiedSince, FileStat.uTimeModify, + (int32_t)(FileStat.uTimeModify-uUnmodifiedSince)); + pResponse->eResponseCode = PROTOHTTP_RESPONSE_PRECONFAILED; + return(-4); + } + + // set last modified time + ds_secstotime(&TmTime, FileStat.uTimeModify); + pResponse->iHeaderLen += ds_snzprintf(pResponse->strHeader, sizeof(pResponse->strHeader)-pResponse->iHeaderLen, "Last-Modified: %s\r\n", + ds_timetostr(&TmTime, TIMETOSTRING_CONVERSION_RFC_0822, FALSE, strTime, sizeof(strTime))); + + // set content-type + ds_strnzcpy(pResponse->strContentType, _HttpServGetContentType(pRequest->strUrl), sizeof(pResponse->strContentType)); + + // set request info + pResponse->iContentLength = iFileLen; + pResponse->iChunkLength = pServerState->iChunkLen; + pResponse->pData = (void *)iFileId; + pResponse->eResponseCode = PROTOHTTP_RESPONSE_OK; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpServProcessPut + + \Description + Process PUT/POST request + + \Input *pServerState - module state + \Input *pRequest - request information + \Input *pResponse - [out] response information + + \Output + int32_t - response code + + \Version 12/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpServProcessPut(HttpServT *pServerState, const ProtoHttpServRequestT *pRequest, ProtoHttpServResponseT *pResponse) +{ + ZFileT iFileId = (ZFileT)(uintptr_t)pRequest->pData; + if (iFileId == 0 || iFileId == ZFILE_INVALID) + { + pResponse->eResponseCode = PROTOHTTP_RESPONSE_INTERNALSERVERERROR; + return(-1); + } + + // we've processed the request + pResponse->pData = (void *)(uintptr_t)iFileId; + pResponse->eResponseCode = PROTOHTTP_RESPONSE_CREATED; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpServRequestCb + + \Description + ProtoHttpServ request callback handler + + \Input *pRequest - request information + \Input *pResponse - [out] response information + \Input *pUserData - callback user data + + \Output + int32_t - response code + + \Version 12/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpServRequestCb(ProtoHttpServRequestT *pRequest, ProtoHttpServResponseT *pResponse, void *pUserData) +{ + HttpServT *pServerState = (HttpServT *)pUserData; + int32_t iResult = -1; + + // init default response values + pResponse->eResponseCode = PROTOHTTP_RESPONSE_NOTIMPLEMENTED; + + // handle the request + if ((pRequest->eRequestType == PROTOHTTP_REQUESTTYPE_GET) || (pRequest->eRequestType == PROTOHTTP_REQUESTTYPE_HEAD)) + { + iResult = _HttpServProcessGet(pServerState, pRequest, pResponse); + } + if ((pRequest->eRequestType == PROTOHTTP_REQUESTTYPE_PUT) || (pRequest->eRequestType == PROTOHTTP_REQUESTTYPE_PATCH) || (pRequest->eRequestType == PROTOHTTP_REQUESTTYPE_POST)) + { + iResult = _HttpServProcessPut(pServerState, pRequest, pResponse); + } + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _HttpServReceiveCb + + \Description + ProtoHttpServ inbound data callback handler + + \Input *pServerState- module state + \Input *pBuffer - data to write + \Input iBufSize - size of the data + \Input *pUserData - user data + + \Output + int32_t - negative=failure, else bytes written + + \Version 12/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpServReceiveCb(ProtoHttpServRequestT *pRequest, const char *pBuffer, int32_t iBufSize, void *pUserData) +{ + ZFileT iFileId = (ZFileT)(uintptr_t)pRequest->pData; + int32_t iResult = 0; + + // if the file failed to load previously or + // not the correct request type + // then early out + if (iFileId == ZFILE_INVALID) + { + return(-1); + } + if (pRequest->eRequestType != PROTOHTTP_REQUESTTYPE_POST && pRequest->eRequestType != PROTOHTTP_REQUESTTYPE_PUT && pRequest->eRequestType != PROTOHTTP_REQUESTTYPE_PATCH) + { + return(iBufSize); + } + + // check for upload completion + if (pBuffer == NULL) + { + ZFileClose(iFileId); + } + else if ((iResult = ZFileWrite(iFileId, (void *)pBuffer, iBufSize)) < 0) + { + NetPrintf(("httpserv: error %d writing to file\n", iResult)); + } + // return result + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _HttpServHeaderCb + + \Description + ProtoHttpServ inbound data callback handler + + \Input *pRequest - [in/out] data used to process the request + \Input *pResponse - [out] data used to send the response + \Input *pUserData - user data + + \Output + int32_t - negative=failure, zero=success +*/ +/********************************************************************************F*/ +static int32_t _HttpServHeaderCb(ProtoHttpServRequestT *pRequest, ProtoHttpServResponseT *pResponse, void *pUserData) +{ + HttpServT *pServerState = (HttpServT *)pUserData; + char strFilePath[1024]; + ZFileT iFileId = ZFILE_INVALID; + + if (pRequest->eRequestType == PROTOHTTP_REQUESTTYPE_POST || + pRequest->eRequestType == PROTOHTTP_REQUESTTYPE_PUT || + pRequest->eRequestType == PROTOHTTP_REQUESTTYPE_PATCH) + { + // create filepath + ds_snzprintf(strFilePath, sizeof(strFilePath), "%s\\%s", pServerState->strFileDir, pRequest->strUrl + 1); + + // try to open the file + if ((iFileId = ZFileOpen(strFilePath, ZFILE_OPENFLAG_WRONLY | ZFILE_OPENFLAG_BINARY)) == ZFILE_INVALID) + { + ZPrintf("httpserv: could not open file '%s' for writing\n", strFilePath); + pResponse->eResponseCode = PROTOHTTP_RESPONSE_INTERNALSERVERERROR; + return(-1); + } + } + + // set the file id for use during receiving/sending + pRequest->pData = (void *)(uintptr_t)iFileId; + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpServSendCb + + \Description + ProtoHttpServ outbound callback handler + + \Input *pServerState - module state + \Input *pBuffer - data to write to + \Input iBufSize - size of the data + \Input *pUserData - user data + + \Output + int32_t - positive=success, zero=in progress, negative=failure + + \Version 12/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpServSendCb(ProtoHttpServResponseT *pResponse, char *pBuffer, int32_t iBufSize, void *pUserData) +{ + //HttpServT *pServerState = (HttpServT *)pUserData; + ZFileT iFileId = (ZFileT)(uintptr_t)pResponse->pData; + int32_t iResult = 0; + + if (iFileId == ZFILE_INVALID) + { + return(0); + } + + // check for download completion + if (pBuffer == NULL) + { + ZFileClose(iFileId); + } + else if ((iResult = ZFileRead(iFileId, pBuffer, iBufSize)) < 0) + { + ZPrintf("httpserv: error %d reading from file\n", iResult); + } + // return result + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _HttpServLogCb + + \Description + ProtoHttpServ logging function + + \Input *pText - text to print + \Input *pUserData - user data (module state) + + \Output + int32_t - zero + + \Version 12/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpServLogCb(const char *pText, void *pUserData) +{ + uint32_t uYear, uMillis; + uint8_t uMonth, uDay, uHour, uMin, uSec; + + // format prefix to output buffer + _HttpServGetCurrentTime(&uYear, &uMonth, &uDay, &uHour, &uMin, &uSec, &uMillis); + + ZPrintf("%02u/%02u/%02u %02u:%02u:%02u.%03u %s", uYear, uMonth, uDay, uHour, uMin, uSec, uMillis, pText); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttpServIdleCB + + \Description + Callback to process while idle + + \Input *argz - + \Input argc - + \Input *argv[] - + + \Output int32_t - + + \Version 09/26/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdHttpServIdleCB(ZContext *argz, int32_t argc, char *argv[]) +{ + HttpServT *pRef = &_HttpServ_Ref; + + // shut down? + if (argc == 0) + { + if (pRef->pHttpServ != NULL) + { + ProtoHttpServDestroy(pRef->pHttpServ); + pRef->pHttpServ = NULL; + } + return(0); + } + // httpserv destroyed? + if (pRef->pHttpServ == NULL) + { + return(0); + } + + // update protohttpserv module + ProtoHttpServUpdate(pRef->pHttpServ); + + // keep on idling + return(ZCallback(&_CmdHttpServIdleCB, HTTPSERV_RATE)); +} + +/*F********************************************************************************/ +/*! + \Function _CmdHttpServUsage + + \Description + Display usage information. + + \Input argc - argument count + \Input *argv[] - argument list + + \Version 09/13/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CmdHttpServUsage(int argc, char *argv[]) +{ + if (argc <= 2) + { + ZPrintf("httpserv: basic http server\n"); + ZPrintf("httpserv: usage: %s [alpn|cert|chunked|ciph|filedir|listen|setcert|setkey|stop|vers|vmin]", argv[0]); + } + else if (argc == 3) + { + if (!strcmp(argv[2], "alpn")) + { + ZPrintf("httpserv: usage: %s alpn [alpnstr]\n", argv[0]); + } + if (!strcmp(argv[2], "ccrt")) + { + ZPrintf("httpserv: usage: %s ccrt [level]\n", argv[0]); + } + else if (!strcmp(argv[2], "cert")) + { + ZPrintf("httpserv: usage: %s cert [cacertfile]\n", argv[0]); + } + else if (!strcmp(argv[2], "chunked")) + { + ZPrintf("httpserv: usage: %s chunked [chunklen]\n", argv[0]); + } + else if (!strcmp(argv[2], "ciph")) + { + ZPrintf("httpserv: usage: %s ciph [ciphers]\n", argv[0]); + } + else if (!strcmp(argv[2], "filedir")) + { + ZPrintf("httpserv: usage: %s filedir \n", argv[0]); + } + else if (!strcmp(argv[2], "listen")) + { + ZPrintf("httpserv: usage: %s listen [listenport] \n", argv[0]); + } + else if (!strcmp(argv[2], "setcert")) + { + ZPrintf("httpserv: usage: %s setcert [certfile]\n", argv[0]); + } + else if (!strcmp(argv[2], "setkey")) + { + ZPrintf("httpserv: usage: %s setkey [certkey]\n", argv[0]); + } + else if (!strcmp(argv[2], "stop")) + { + ZPrintf("httpserv: usage: %s stop\n", argv[0]); + } + else if (!strcmp(argv[2], "vers")) + { + ZPrintf("httpserv: usage: %s vers [version]\n", argv[0]); + } + else if (!strcmp(argv[2], "vmin")) + { + ZPrintf("httpserv: usage: %s vmin [version]\n", argv[0]); + } + } +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdHttpServ + + \Description + Simple HTTP server + + \Input *argz - context + \Input argc - command count + \Input *argv[] - command arguments + + \Output int32_t - + + \Version 09/11/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdHttpServ(ZContext *argz, int32_t argc, char *argv[]) +{ + HttpServT *pServerState = &_HttpServ_Ref; + int32_t iResult = 0; + + if ((argc < 2) || !ds_stricmp(argv[1], "help")) + { + _CmdHttpServUsage(argc, argv); + return(iResult); + } + + // if not initialized yet, do so now + if (_HttpServ_bInitialized == FALSE) + { + ds_memclr(pServerState, sizeof(*pServerState)); + ds_strnzcpy(pServerState->strServerName, "HttpServ", sizeof(pServerState->strServerName)); + ds_strnzcpy(pServerState->strFileDir, "c:\\temp\\httpserv", sizeof(pServerState->strFileDir)); + _HttpServ_bInitialized = TRUE; + } + + // check for filedir set + if ((argc == 3) && !ds_stricmp(argv[1], "filedir")) + { + ds_strnzcpy(pServerState->strFileDir, argv[2], sizeof(pServerState->strFileDir)); + return(iResult); + } + + // check for listen + if ((argc >= 2) && (argc < 5) && !ds_stricmp(argv[1], "listen")) + { + int32_t iPort = HTTPSERV_LISTENPORT; + int32_t iSecure = 0; // insecure by default + + if ((argc > 3) && !ds_stricmp(argv[3], "secure")) + { + iSecure = 1; + } + if (argc > 2) + { + iPort = (int32_t)strtol(argv[2], NULL, 10); + if ((iPort < 1) || (iPort > 65535)) + { + ZPrintf("httpserv: invalid port %d specified in listen request\n", iPort); + return(-1); + } + } + + // destroy previous httpserv ref, if any + if (pServerState->pHttpServ != NULL) + { + ProtoHttpServDestroy(pServerState->pHttpServ); + } + + // create new httpserv ref + if ((pServerState->pHttpServ = ProtoHttpServCreate(iPort, iSecure, pServerState->strServerName)) == NULL) + { + ZPrintf("httpserv: could not create httpserv state on port %d\n", iPort); + return(-2); + } + + // set up httpserv callbacks + ProtoHttpServCallback(pServerState->pHttpServ, _HttpServRequestCb, _HttpServReceiveCb, _HttpServSendCb, _HttpServHeaderCb, _HttpServLogCb, pServerState); + + // install recurring update + iResult = ZCallback(_CmdHttpServIdleCB, HTTPSERV_RATE); + } + + // the following functions require http serv state + if (pServerState->pHttpServ == NULL) + { + ZPrintf("%s: '%s' requires httpserv creation (use 'listen' command)\n", argv[0], argv[1]); + return(iResult); + } + + // check for server stop + if ((argc == 2) && !ds_stricmp(argv[1], "stop")) + { + ProtoHttpServDestroy(pServerState->pHttpServ); + pServerState->pHttpServ = NULL; + + ZPrintf("httpserv: protohttpserv stopped listening\n"); + } + + // check for setting alpn + if ((argc == 3) && !ds_stricmp(argv[1], "alpn")) + { + return(ProtoHttpServControl(pServerState->pHttpServ, 'alpn', 0, 0, argv[2])); + } + + // check for client cert level specification + if ((argc == 3) && !ds_stricmp(argv[1], "ccrt")) + { + return(ProtoHttpServControl(pServerState->pHttpServ, 'ccrt', (int32_t)strtol(argv[2], NULL, 10), 0, NULL)); + } + + // check for cacert load + if ((argc == 3) && !ds_stricmp(argv[1], "cert")) + { + const uint8_t *pFileData; + int32_t iFileSize; + + // try and open file + if ((pFileData = (const uint8_t *)ZFileLoad(argv[2], &iFileSize, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) != NULL) + { + iResult = ProtoSSLSetCACert(pFileData, iFileSize); + ZMemFree((void *)pFileData); + } + else + { + ZPrintf("%s: unable to load certificate file '%s'\n", argv[0], argv[2]); + } + return((iResult > 0) ? 0 : -1); + } + + // check for chunked transfer (download) enable + if ((argc >= 2) && !ds_stricmp(argv[1], "chunked")) + { + int32_t iChunkLen = 4096; + if (argc > 2) + { + iChunkLen = (int32_t)strtol(argv[2], NULL, 10); + } + ZPrintf("httpserv: enabling chunked transfers and setting chunk size to %d\n", iChunkLen); + pServerState->iChunkLen = iChunkLen; + return(iResult); + } + + // check for cipher set + if ((argc == 3) && !ds_stricmp(argv[1], "ciph")) + { + return(ProtoHttpServControl(pServerState->pHttpServ, 'ciph', (int32_t)strtol(argv[2], NULL, 16), 0, NULL)); + } + + // check for server certificate + if ((argc == 3) && !ds_stricmp(argv[1], "setcert")) + { + if ((pServerState->pServerCert = _HttpServLoadCertificate(argv[2], &pServerState->iServerCertLen)) != NULL) + { + iResult = ProtoHttpServControl(pServerState->pHttpServ, 'scrt', pServerState->iServerCertLen, 0, pServerState->pServerCert); + } + else + { + ZPrintf("%s: could not load certificate '%s'\n", argv[0], argv[2]); + iResult = -1; + } + return(iResult); + } + + // check for server private key + if ((argc == 3) && !ds_stricmp(argv[1], "setkey")) + { + if ((pServerState->pServerKey = _HttpServLoadCertificate(argv[2], &pServerState->iServerKeyLen)) != NULL) + { + iResult = ProtoHttpServControl(pServerState->pHttpServ, 'skey', pServerState->iServerKeyLen, 0, pServerState->pServerKey); + } + else + { + ZPrintf("httpserv: could not load private key '%s'\n", argv[2]); + iResult = -1; + } + return(iResult); + } + + // check for version set + if ((argc == 3) && !ds_stricmp(argv[1], "vers")) + { + return(ProtoHttpServControl(pServerState->pHttpServ, 'vers', (int32_t)strtol(argv[2], NULL, 16), 0, NULL)); + } + + // check for min version set + if ((argc == 3) && !ds_stricmp(argv[1], "vmin")) + { + return(ProtoHttpServControl(pServerState->pHttpServ, 'vmin', (int32_t)strtol(argv[2], NULL, 16), 0, NULL)); + } + + // check for control + if ((argc > 3) && (!ds_stricmp(argv[1], "ctrl"))) + { + int32_t iCmd, iThread, iValue = 0, iValue2 = 0; + const char *pValue = NULL; + + iCmd = _GetIntArg(argv[2]); + iThread = _GetIntArg(argv[3]); + + if (argc > 4) + { + iValue = _GetIntArg(argv[3]); + } + if (argc > 5) + { + iValue2 = _GetIntArg(argv[4]); + } + if (argc > 6) + { + pValue = argv[5]; + } + + return(ProtoHttpServControl2(pServerState->pHttpServ, iThread, iCmd, iValue, iValue2, (void *)pValue)); + } + + return(iResult); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/imageconv.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/imageconv.c new file mode 100644 index 00000000..b6ab1b42 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/imageconv.c @@ -0,0 +1,731 @@ +/*H********************************************************************************/ +/*! + \File imageconv.c + + \Description + Test DirtyGraph image conversion routines. + + \Copyright + Copyright (c) 2006 Electronic Arts Inc. + + \Version 02/23/2006 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/graph/dirtygraph.h" +#include "DirtySDK/graph/dirtygif.h" +#include "DirtySDK/graph/dirtyjpg.h" +#include "DirtySDK/graph/dirtypng.h" + +#include "libsample/zfile.h" +#include "libsample/zlib.h" +#include "libsample/zmem.h" + +#include "testermodules.h" + +#define RUNLIBJPEG (FALSE) + +#if RUNLIBJPEG +extern BITMAPINFO *jpeg_read_dibitmap(char *fname, char *errbuf, long errlen, long *cmpsize); +#endif + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +static DirtyGraphRefT *pDirtyGraph; + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _CmdImgConvParseHeader + + \Description + Read input image and parse it + + \Input *pInputFile - input file data + \Input uInputSize - size of input file data + \Input *pImageInfo - [out] storage for image info + + \Output + DirtyGraphInfoT * - pointer to parsed image info, or NULL + + \Version 01/17/2020 (jbrookes) Split from _CmdImgConvDecodeImage +*/ +/********************************************************************************F*/ +static DirtyGraphInfoT *_CmdImgConvParseHeader(const uint8_t *pInputFile, uint32_t uInputSize, DirtyGraphInfoT *pImageInfo) +{ + int32_t iError; + const char* _strImageTypes[] = { "unknown", "gif", "jpg", "png" }; + + // create module state + if (pDirtyGraph == NULL) + { + pDirtyGraph = DirtyGraphCreate(); + } + + // parse the image header + if ((iError = DirtyGraphDecodeHeader(pDirtyGraph, pImageInfo, pInputFile, uInputSize)) < 0) + { + ZPrintf("imgconv: error %d trying to parse image\n", iError); + DirtyGraphDestroy(pDirtyGraph); + pDirtyGraph = NULL; + return(NULL); + } + + // identify image type + ZPrintf("imgconv: parsed %s image\n", _strImageTypes[pImageInfo->uType]); + return(pImageInfo); +} + +/*F********************************************************************************/ +/*! + \Function _CmdImgConvDecodeImage + + \Description + Read input image, and decode it to a 32bit ARGB file using DirtyGraph + + \Input *pInputFile - input file data + \Input uInputSize - size of input file data + \Input pImageInfo - image info + \Input bMultiImage - multi-image decoding enabled + + \Output + uint8_t * - pointer to output 32bit ARGB image, or NULL + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t *_CmdImgConvDecodeImage(const uint8_t *pInputFile, uint32_t uInputSize, DirtyGraphInfoT *pImageInfo, uint8_t bMultiImage) +{ + uint8_t *p32Bit; + int32_t iError, iNumFrames = bMultiImage ? pImageInfo->uNumFrames : 1; + + // allocate space for 32bit raw image + if ((p32Bit = ZMemAlloc(pImageInfo->uNumFrames*pImageInfo->iWidth*pImageInfo->iHeight*4)) == NULL) + { + ZPrintf("imgconv: could not allocate memory for decoded image\n"); + DirtyGraphDestroy(pDirtyGraph); + pDirtyGraph = NULL; + return(NULL); + } + + // decode the image + if (iNumFrames == 1) + { + if ((iError = DirtyGraphDecodeImage(pDirtyGraph, pImageInfo, p32Bit, pImageInfo->iWidth, pImageInfo->iHeight)) < 0) + { + ZPrintf("imgconv: error %d trying to decode image\n", iError); + DirtyGraphDestroy(pDirtyGraph); + pDirtyGraph = NULL; + ZMemFree(p32Bit); + return(NULL); + } + } + else + { + // get animation info + int32_t *pAnimInfo = ZMemAlloc(pImageInfo->uNumFrames*sizeof(int32_t)); + int32_t iFrame, iFrames = DirtyGraphGetImageInfo(pDirtyGraph, pImageInfo, 'anim', pAnimInfo, pImageInfo->uNumFrames*sizeof(int32_t)); + ZPrintf("imgconv: %d frames with animation delays of "); + for (iFrame = 0; iFrame < iFrames; iFrame += 1) + { + ZPrintf("%d,", pAnimInfo[iFrame]); + } + ZPrintf("\n"); + ZMemFree(pAnimInfo); + + // decode the multiframe inmage + if ((iError = DirtyGraphDecodeImageMulti(pDirtyGraph, pImageInfo, p32Bit, pImageInfo->iWidth, pImageInfo->iHeight)) < 0) + { + ZPrintf("imgconv: error %d trying to decode image\n", iError); + DirtyGraphDestroy(pDirtyGraph); + pDirtyGraph = NULL; + ZMemFree(p32Bit); + return(NULL); + } + + } + + // return 32bit image buffer + return(p32Bit); +} + +/*F********************************************************************************/ +/*! + \Function _CmdImgConvSwizzleLineBMP + + \Description + Swizzle scanline in place. + + \Input *pScan - scanline to swizzled + \Input iWidth - width of scanline + + \Output + None + + \Version 03/07/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CmdImgConvSwizzleLineBMP(uint8_t *pScan, int32_t iWidth) +{ + int32_t iCurW; + uint8_t uTmp; + + // do the swap and swizzle in one pass + for (iCurW = 0; iCurW < iWidth; iCurW += 1, pScan += 4) + { + // swap a and b + uTmp = pScan[0]; // save a + pScan[0] = pScan[3]; // b->a + pScan[3] = uTmp; // a->b + + // swap r and g + uTmp = pScan[1]; // save r + pScan[1] = pScan[2]; // g->r + pScan[2] = uTmp; // r->g + } +} + +/*F********************************************************************************/ +/*! + \Function _CmdImgConvSwapAndSwizzleLineBMP + + \Description + Swap scanlines pointed to by pScanLo and pScanHi and swizzle in-place. + + \Input *pScanLo - lo scanline to swap&swizzle + \Input *pScanHi - hi scanline to swap&swizzle + \Input iWidth - width of scanline + + \Output + None + + \Version 03/07/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CmdImgConvSwapAndSwizzleLineBMP(uint8_t *pScanLo, uint8_t *pScanHi, int32_t iWidth) +{ + int32_t iCurW; + uint8_t uTmp; + + // do the swap and swizzle in one pass + for (iCurW = 0; iCurW < iWidth; iCurW += 1, pScanLo += 4, pScanHi += 4) + { + // swap a(hi) and b(lo) + uTmp = pScanHi[0]; // save a(hi) + pScanHi[0] = pScanLo[3]; // b(lo)->a(hi) + pScanLo[3] = uTmp; // a(hi)->b(lo) + + // swap b(hi) and a(lo) + uTmp = pScanHi[3]; // save b(hi) + pScanHi[3] = pScanLo[0]; // a(lo)->b(hi) + pScanLo[0] = uTmp; // b(hi)->a(lo) + + // swap r(hi) and g(lo) + uTmp = pScanHi[1]; // save r(hi) + pScanHi[1] = pScanLo[2]; // g(lo)->r(hi) + pScanLo[2] = uTmp; // r(hi)->g(lo) + + // swap g(hi) and r(lo) + uTmp = pScanHi[2]; // save g(hi) + pScanHi[2] = pScanLo[1]; // r(lo)->g(hi) + pScanLo[1] = uTmp; // g(hi)->r(lo) + } +} + +/*F********************************************************************************/ +/*! + \Function _CmdImgConvSaveBMP + + \Description + Save input 32bit ARGB image as a 32bit BMP file + + \Input *pFilename - filename of file to save + \Input *pImageData - input image data + \Input iWidth - width of input image + \Input iHeight - height of input image + + \Output + int32_t - ZFileClose() result + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdImgConvSaveBMP(const char *pFileName, const uint8_t *pImageData, int32_t iWidth, int32_t iHeight) +{ + BITMAPFILEHEADER BitmapFileHeader; + BITMAPINFOHEADER BitmapInfoHeader; + uint8_t *pScanHi, *pScanLo, *pBMPData; + ZFileT iFileId; + int32_t iCurH; + + // open the file for writing + if ((iFileId = ZFileOpen(pFileName, ZFILE_OPENFLAG_WRONLY|ZFILE_OPENFLAG_CREATE|ZFILE_OPENFLAG_BINARY)) < 0) + { + return(-1); + } + + // make a temp copy for conversion to bmp format + if ((pBMPData = ZMemAlloc(iWidth*iHeight*4)) == NULL) + { + return(-2); + } + memcpy(pBMPData, pImageData, iWidth*iHeight*4); + + // vflip image and convert from ARGB to BGRA in one pass + for (iCurH = 0; iCurH < (iHeight/2); iCurH++) + { + // ref scanlines to swap and swizzle + pScanLo = pBMPData + (iCurH*iWidth*4); + pScanHi = pBMPData + ((iHeight-iCurH-1)*iWidth*4); + + // do the swap and swizzle + _CmdImgConvSwapAndSwizzleLineBMP(pScanLo, pScanHi, iWidth); + } + // if height is odd, swizzle the center scanline + if (iHeight & 1) + { + // ref scanlines to swap and swizzle + pScanLo = pBMPData + (iCurH*iWidth*4); + + // do the swap and swizzle + _CmdImgConvSwizzleLineBMP(pScanLo, iWidth); + } + + // format bitmap header + ds_memclr(&BitmapFileHeader, sizeof(BitmapFileHeader)); + BitmapFileHeader.bfType = 'MB'; + BitmapFileHeader.bfSize = sizeof(BitmapFileHeader)+sizeof(BitmapInfoHeader)+(iWidth*iHeight*4); + BitmapFileHeader.bfOffBits = sizeof(BitmapFileHeader)+sizeof(BitmapInfoHeader); + + // write fileheader to output file + if (ZFileWrite(iFileId, &BitmapFileHeader, sizeof(BitmapFileHeader)) < 0) + { + ZFileClose(iFileId); + return(-1); + } + + // format bitmapinfo header + ds_memclr(&BitmapInfoHeader, sizeof(BitmapInfoHeader)); + BitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER); + BitmapInfoHeader.biWidth = iWidth; + BitmapInfoHeader.biHeight = iHeight; + BitmapInfoHeader.biPlanes = 1; + BitmapInfoHeader.biBitCount = 32; + BitmapInfoHeader.biCompression = BI_RGB; + BitmapInfoHeader.biSizeImage = iWidth*iHeight*4; + BitmapInfoHeader.biXPelsPerMeter = 0; + BitmapInfoHeader.biYPelsPerMeter = 0; + BitmapInfoHeader.biClrUsed = 0; + BitmapInfoHeader.biClrImportant = 0; + + // write infoheader to output file + if (ZFileWrite(iFileId, &BitmapInfoHeader, sizeof(BitmapInfoHeader)) < 0) + { + ZFileClose(iFileId); + return(-1); + } + + // write pixel data to output file + if (ZFileWrite(iFileId, pBMPData, iWidth*iHeight*4) < 0) + { + ZFileClose(iFileId); + return(-1); + } + + // free pixel data buffer + ZMemFree(pBMPData); + + // close the file + return(ZFileClose(iFileId)); +} + +/*F********************************************************************************/ +/*! + \Function _CmdImgConvSaveRAW + + \Description + Save input 32bit ARGB image as a 24bit RAW image. + + \Input *pFilename - filename of file to save + \Input *pImageData - input image data + \Input iWidth - width of input image + \Input iHeight - height of input image + + \Output + int32_t - ZFileSave() result + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdImgConvSaveRAW(const char *pFileName, uint8_t *pImageData, int32_t iWidth, int32_t iHeight) +{ + uint8_t *pSrc, *pDst; + int32_t iW, iH; + + // convert 32bit ARGB image to 24bit RGB image (inline) + for (pSrc=pImageData, pDst=pImageData, iH=0; iH < iHeight; iH++) + { + for (iW = 0; iW < iWidth; iW++) + { + pDst[0] = pSrc[1]; + pDst[1] = pSrc[2]; + pDst[2] = pSrc[3]; + pSrc += 4; + pDst += 3; + } + } + + // write the file + return(ZFileSave(pFileName, (const char *)pImageData, iWidth*iHeight*3, ZFILE_OPENFLAG_WRONLY|ZFILE_OPENFLAG_CREATE|ZFILE_OPENFLAG_BINARY)); +} + +/*F********************************************************************************/ +/*! + \Function _CmdImgConvDecodeAndSave + + \Description + Decode input to 32bit ARGB and save as raw or bmp. + + \Input *pFilename - filename of file to save + \Input *pInputFile - input file + \Input iInputSize - input file size + \Input *pImageInfo - image info + + \Output + int32_t - ZFileSave() result + + \Version 01/17/2020 (jbrookes) Split from CmdImgConv() +*/ +/********************************************************************************F*/ +static int32_t _CmdImgConvDecodeAndSave(const char *pFileName, uint8_t *pInputFile, int32_t iInputSize, DirtyGraphInfoT *pImageInfo) +{ + uint8_t *p32BitImage; + int32_t iResult; + + // convert to 32bit raw image + if ((p32BitImage = _CmdImgConvDecodeImage(pInputFile, iInputSize, pImageInfo, FALSE)) == NULL) + { + return(0); + } + + // save output image based on type + if (ds_stristr(pFileName, ".raw")) + { + iResult = _CmdImgConvSaveRAW(pFileName, p32BitImage, pImageInfo->iWidth, pImageInfo->iHeight); + } + else if (ds_stristr(pFileName, ".bmp")) + { + iResult = _CmdImgConvSaveBMP(pFileName, p32BitImage, pImageInfo->iWidth, pImageInfo->iHeight); + } + else + { + ZPrintf("imgconv: output filetype unrecognized\n"); + iResult = -1; + } + + // success? + if (iResult >= 0) + { + ZPrintf("imgconv: saved output image %s\n", pFileName); + } + else + { + ZPrintf("imgconv: error writing output file '%s'\n", pFileName); + } + + // dispose of buffer and return result to caller + ZMemFree(p32BitImage); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _CmdImgConvDecodeAndSaveMulti + + \Description + Decode input multi-frame file to 32bit ARGB and save as raw or bmp files. + A numerical extension is appended to differentiate the frames. Uses + DirtyGraphDecodeImage32Multi() + + \Input *pFilename - filename of file to save + \Input *pInputFile - input file + \Input iInputSize - input file size + \Input *pImageInfo - image info + + \Output + int32_t - ZFileSave() result + + \Version 01/17/2020 (jbrookes) Split from CmdImgConv() +*/ +/********************************************************************************F*/ +static int32_t _CmdImgConvDecodeAndSaveMulti(const char *pFileName, uint8_t *pInputFile, int32_t iInputSize, DirtyGraphInfoT *pImageInfo) +{ + char strOutputFileName[1024], *pExt; + uint8_t *p32BitImage; + int32_t iResult, iFrame, iFrameSize; + uint8_t bBmp; + + // convert to 32bit raw image + if ((p32BitImage = _CmdImgConvDecodeImage(pInputFile, iInputSize, pImageInfo, TRUE)) == NULL) + { + return(0); + } + + // copy filename + ds_strnzcpy(strOutputFileName, pFileName, sizeof(strOutputFileName)); + + // save output image based on type + if ((pExt = ds_stristr(strOutputFileName, ".raw")) != NULL) + { + bBmp = FALSE; + } + else if ((pExt = ds_stristr(strOutputFileName, ".bmp")) != NULL) + { + bBmp = TRUE; + } + else + { + ZPrintf("imgconv: output filetype unrecognized\n"); + return(0); + } + + // set up filename for writing multiple frames; first truncate the extension + *pExt = '\0'; + + // write out frame data as successive images + for (iFrame = 0, iFrameSize = pImageInfo->iWidth*pImageInfo->iHeight*4, iResult = 0; (iFrame < pImageInfo->uNumFrames) && (iResult == 0); iFrame += 1) + { + ds_snzprintf(pExt, (signed)sizeof(strOutputFileName)-(pExt-strOutputFileName), "-%02d%s", iFrame, bBmp ? ".bmp" : ".raw"); + iResult = bBmp ? _CmdImgConvSaveBMP(strOutputFileName, p32BitImage+(iFrame*iFrameSize), pImageInfo->iWidth, pImageInfo->iHeight) : _CmdImgConvSaveRAW(strOutputFileName, p32BitImage+(iFrame * iFrameSize), pImageInfo->iWidth, pImageInfo->iHeight); + } + + // success? + if (iResult >= 0) + { + ds_snzprintf(pExt, (signed)sizeof(strOutputFileName)-(pExt-strOutputFileName), "-XX%s", bBmp ? ".bmp" : ".raw"); + ZPrintf("imgconv: saved %d output images %s\n", iFrame, strOutputFileName); + } + else + { + ZPrintf("imgconv: error writing output file '%s'\n", strOutputFileName); + } + + // dispose of buffer and return result to caller + ZMemFree(p32BitImage); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _CmdImgConvDecodeAndSaveMulti2 + + \Description + Decode input multi-frame file to 32bit ARGB and save as raw or bmp files. + A numerical extension is appended to differentiate the frames. Uses + DirtyGraphDecodeImageFrame(). + + \Input *pFilename - filename of file to save + \Input *pInputFile - input file + \Input iInputSize - input file size + \Input *pImageInfo - image info + + \Output + int32_t - ZFileSave() result + + \Version 02/06/2020 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdImgConvDecodeAndSaveMulti2(const char *pFileName, uint8_t *pInputFile, int32_t iInputSize, DirtyGraphInfoT *pImageInfo) +{ + char strOutputFileName[1024], *pExt; + uint8_t *p32BitImage; + int32_t iResult, iFrame, iError; + uint8_t bBmp; + + // allocate space for 32bit raw image + if ((p32BitImage = ZMemAlloc(pImageInfo->iWidth*pImageInfo->iHeight*4)) == NULL) + { + ZPrintf("imgconv: could not allocate memory for decoded image\n"); + DirtyGraphDestroy(pDirtyGraph); + pDirtyGraph = NULL; + return(0); + } + + // copy filename + ds_strnzcpy(strOutputFileName, pFileName, sizeof(strOutputFileName)); + + // save output image based on type + if ((pExt = ds_stristr(strOutputFileName, ".raw")) != NULL) + { + bBmp = FALSE; + } + else if ((pExt = ds_stristr(strOutputFileName, ".bmp")) != NULL) + { + bBmp = TRUE; + } + else + { + ZPrintf("imgconv: output filetype unrecognized\n"); + return(0); + } + + // set up filename for writing multiple frames; first truncate the extension + *pExt = '\0'; + + // write out frame data as successive images + for (iFrame = 0, iResult = 0; (iFrame < pImageInfo->uNumFrames) && (iResult == 0); iFrame += 1) + { + // decode the multiframe inmage + uint64_t uTick = NetTickUsec(); + if ((iError = DirtyGraphDecodeImageFrame(pDirtyGraph, pImageInfo, p32BitImage, pImageInfo->iWidth, pImageInfo->iHeight, iFrame)) < 0) + { + ZPrintf("imgconv: error %d trying to decode image\n", iError); + DirtyGraphDestroy(pDirtyGraph); + pDirtyGraph = NULL; + ZMemFree(p32BitImage); + return(0); + } + ZPrintf("imgconv: %dus for decode\n", NetTickDiff(NetTickUsec(), uTick)); + + // create filename + ds_snzprintf(pExt, (signed)sizeof(strOutputFileName)-(pExt-strOutputFileName), "-%02d%s", iFrame, bBmp ? ".bmp" : ".raw"); + + // save the image + iResult = bBmp ? _CmdImgConvSaveBMP(strOutputFileName, p32BitImage, pImageInfo->iWidth, pImageInfo->iHeight) : _CmdImgConvSaveRAW(strOutputFileName, p32BitImage, pImageInfo->iWidth, pImageInfo->iHeight); + } + + // success? + if (iResult >= 0) + { + ds_snzprintf(pExt, (signed)sizeof(strOutputFileName)-(pExt-strOutputFileName), "-XX%s", bBmp ? ".bmp" : ".raw"); + ZPrintf("imgconv: saved %d output images %s\n", iFrame, strOutputFileName); + } + else + { + ZPrintf("imgconv: error writing output file '%s'\n", strOutputFileName); + } + + // dispose of buffer and return result to caller + ZMemFree(p32BitImage); + return(iResult); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdStream + + \Description + Create the Module module. + + \Input *argz - environment + \Input argc - number of args + \Input *argv[] - argument list + + \Output int32_t - standard return code + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdImgConv(ZContext *argz, int32_t argc, char *argv[]) +{ + int32_t iInputSize, iResult, iArg = 1; + uint8_t *pInputFile; + const char *pInputName; + DirtyGraphInfoT ImageInfo; + uint8_t bMultiFrame = FALSE, bMultiFrame2 = FALSE; + + // check for module state destroy (no arguments) + if ((argc == 1) && (pDirtyGraph != NULL)) + { + // destroy state + DirtyGraphDestroy(pDirtyGraph); + pDirtyGraph = NULL; + ZPrintf("%s: graph instance destroyed\n", argv[0]); + return(0); + } + + // check for multiframe argument + if ((argc > 1) && !strcmp(argv[iArg], "-m")) + { + iArg += 1; + bMultiFrame = TRUE; + } + // check for multiframe flavor #2 + if ((argc > 1) && !strcmp(argv[iArg], "-m2")) + { + iArg += 1; + bMultiFrame = TRUE; + bMultiFrame2 = TRUE; + } + + // usage + if ((argc == iArg) || (argc > iArg+2)) + { + ZPrintf("usage: %s [-m] \n", argv[0]); + return(0); + } + pInputName = argv[iArg]; + + // open input file for reading + if ((pInputFile = (uint8_t *)ZFileLoad(pInputName, &iInputSize, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) == NULL) + { + ZPrintf("%s: unable to open input file '%s'\n", argv[0], pInputName); + return(0); + } + + #if RUNLIBJPEG + // first read it with libjpeg for comparison + if (ds_stristr(pInputName, ".jpg")) + { + char strError[256]; + jpeg_read_dibitmap((char *)pInputName, strError, sizeof(strError), NULL); + } + #endif + + // parse image info + if (_CmdImgConvParseHeader(pInputFile, iInputSize, &ImageInfo) == NULL) + { + return(0); + } + + // output? + if (argc == iArg+2) + { + if ((ImageInfo.uNumFrames == 1) || !bMultiFrame) + { + iResult = _CmdImgConvDecodeAndSave(argv[iArg+1], pInputFile, iInputSize, &ImageInfo); + } + else if (!bMultiFrame2) + { + iResult = _CmdImgConvDecodeAndSaveMulti(argv[iArg+1], pInputFile, iInputSize, &ImageInfo); + } + else + { + iResult = _CmdImgConvDecodeAndSaveMulti2(argv[iArg+1], pInputFile, iInputSize, &ImageInfo); + } + } + + // dispose of source image + ZMemFree(pInputFile); + + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/json.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/json.c new file mode 100644 index 00000000..5203e6aa --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/json.c @@ -0,0 +1,235 @@ +/*H********************************************************************************/ +/*! + \File json.c + + \Description + Test the JSON formatter and parser. + + \Copyright + Copyright (c) 2012 Electronic Arts Inc. + + \Version 12/11/2012 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/util/jsonformat.h" +#include "DirtySDK/util/jsonparse.h" +#include "testermodules.h" + +#include "libsample/zlib.h" +#include "libsample/zfile.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +// Variables + +static const char *_pSimpleArrayStringTest = \ +"{" \ + "[" \ + "\"abcd\"," \ + "\"efgh\"," \ + "\"ijkl\"," \ + "\"mnop\"" \ + "]" \ +"};"; + +static const char *_pSimpleArrayNumberTest = \ +"{" \ + "[" \ + "101," \ + "5005," \ + "7," \ + "32768" \ + "]" \ +"};"; + + +/*** Private Functions ************************************************************/ + +static void _SimpleDecode(void) +{ + char strBuffer[1024], *pBuffer; + uint16_t buff[4096]; + const char *pData = "{\"isAllowed\":false,\"reasons\":[{\"reason\":\"CheckFailed\"}]}"; + const char *pResult; + uint8_t bAllowed; + + JsonParse(buff, sizeof(buff) / sizeof(buff[0]), pData, (int32_t)strlen(pData)); + pResult = JsonFind(buff, "isAllowed"); + bAllowed = (JsonGetBoolean(pResult, 0) == 0) ? FALSE : TRUE; + + // encode some Json + // "id": 2533274790412952, + // "name": "TestPlayer0", + // "seatIndex": -1 + JsonInit(strBuffer, sizeof(strBuffer), JSON_FL_WHITESPACE); + JsonObjectStart(strBuffer, NULL); + JsonAddInt(strBuffer, "id", 2533274790412952LL); + JsonAddStr(strBuffer, "name", "TestPlayer0"); + JsonAddInt(strBuffer, "seatIndex", -1); + JsonObjectEnd(strBuffer); + pBuffer = JsonFinish(strBuffer); + ZPrintf("encoded Json:\n------------\n%s\n-----------\n", pBuffer); +} + +static void _SimpleEncode(void) +{ + char strBuffer[1024], *pBuffer; + + /* now something a little more challenging: + + { + "body": + { + "attachments":null, + "partnerData": + { + "MultiplayerMessageType":"YourTurn", + "SessionId": "C8921C4E-4FC2-439E-9368-5D26889BD1BB" + } + }, + "header": + { + "attributes":null, + "expiration":"2011-10-11T23:59:59.9999999", + "id":null, + "messageType":"Multiplayer", + "recipients":[{"userId":"GoTeamEmily","userType":"Gamertag"},{"userId":"Longstreet360","userType":"Gamertag"}]], + "sender":"Striker", + "senderPlatform":null, + "sent":"2011-10-13T16:40:58.1890842-07:00", + "targetPlatforms":["MP3"], + "title":1297287259 + } + } + + */ + JsonInit(strBuffer, sizeof(strBuffer), JSON_FL_WHITESPACE); + JsonObjectStart(strBuffer, "body"); + JsonAddStr(strBuffer, "attachments", NULL); + JsonObjectStart(strBuffer, "partnerData"); + JsonAddStr(strBuffer, "MultiplayerMessageType", "YourTurn"); + JsonAddStr(strBuffer, "SessionId", "C8921C4E-4FC2-439E-9368-5D26889BD1BB"); // AddGuid()? + JsonObjectEnd(strBuffer); + JsonObjectEnd(strBuffer); + JsonObjectStart(strBuffer, "header"); + JsonAddStr(strBuffer, "attributes", NULL); + JsonAddStr(strBuffer, "expiration", "2011-10-11T23:59:59.9999999"); // AddDate()? + JsonAddStr(strBuffer, "id", NULL); + JsonAddStr(strBuffer, "messageType", "Multiplayer"); + JsonArrayStart(strBuffer, "recipients"); + JsonObjectStart(strBuffer, NULL); + JsonAddStr(strBuffer, "userId", "GoTeamEmily"); + JsonAddStr(strBuffer, "userType", "Gamertag"); + JsonObjectEnd(strBuffer); + JsonObjectStart(strBuffer, NULL); + JsonAddStr(strBuffer, "userId", "Longstreet360"); + JsonAddStr(strBuffer, "userType", "Gamertag"); + JsonObjectEnd(strBuffer); + JsonArrayEnd(strBuffer); + JsonAddStr(strBuffer, "sender", "Striker"); + JsonAddStr(strBuffer, "senderPlatform", "null"); + JsonAddStr(strBuffer, "sent", "2011-10-13T16:40:58.1890842-07:00"); // AddDate()? + JsonArrayStart(strBuffer, "targetPlatforms"); + JsonAddStr(strBuffer, NULL, "MP3"); + JsonArrayEnd(strBuffer); + JsonAddInt(strBuffer, "title", 1297287259); + JsonObjectEnd(strBuffer); + pBuffer = JsonFinish(strBuffer); + ZPrintf("encoded Json:\n------------\n%s\n-----------\n", pBuffer); +} + +static void _ArrayTests(void) +{ + int32_t iElement, iValue; + uint16_t aJsonParseBuf[512]; + char strTemp[16]; + + JsonParse(aJsonParseBuf, sizeof(aJsonParseBuf)/sizeof(aJsonParseBuf[0]), _pSimpleArrayStringTest, (int32_t)strlen(_pSimpleArrayStringTest)); + for (iElement = 0; iElement < 4; iElement += 1) + { + JsonGetString(JsonFind2(aJsonParseBuf, NULL, "[", iElement), strTemp, sizeof(strTemp), ""); + ZPrintf("str[%d]=%s\n", iElement, strTemp); + } + + JsonParse(aJsonParseBuf, sizeof(aJsonParseBuf)/sizeof(aJsonParseBuf[0]), _pSimpleArrayNumberTest, (int32_t)strlen(_pSimpleArrayNumberTest)); + for (iElement = 0; iElement < 5; iElement += 1) + { + iValue = (int32_t)JsonGetInteger(JsonFind2(aJsonParseBuf, NULL, "[", iElement), -1); + ZPrintf("str[%d]=%d\n", iElement, iValue); + } +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdJson + + \Description + Test the Json formatter and parser + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output standard return value + + \Version 12/11/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdJson(ZContext *argz, int32_t argc, char *argv[]) +{ + // check for a file argument... if we have one, load and parse it + if (argc == 2) + { + uint16_t *pJsonParseBuf; + int32_t iFileSize; + char *pFileData; + + if ((pFileData = ZFileLoad(argv[1], &iFileSize, FALSE)) != NULL) + { + if ((pJsonParseBuf = JsonParse2(pFileData, -1, 0, 0, NULL)) != NULL) + { + // + // insert custom parsing code here + // + } + else + { + ZPrintf("%s: could not parse buffer\n", argv[0]); + } + + free(pFileData); + } + else + { + ZPrintf("%s: could not open file '%s'\n", argv[0], argv[1]); + } + } + else + { + _SimpleDecode(); + _SimpleEncode(); + // test parsing some simple arrays + _ArrayTests(); + } + + //$$todo - more tests + return(0); +} + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/lang.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/lang.c new file mode 100644 index 00000000..97207261 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/lang.c @@ -0,0 +1,138 @@ +/*H********************************************************************************/ +/*! + \File lang.c + + \Description + Test LobbyLang macros + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/03/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtylang.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "libsample/zlib.h" + +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function CmdLangTest + + \Description + Test LobbyLang macros + + \Input ZContext *argz - environment + \Input int32_t argc - standard number of arguments + \Input char **argv - standard arg list + + \Output standard return value +*/ +/********************************************************************************F*/ +int32_t CmdLang(ZContext *argz, int32_t argc, char **argv) +{ + // check usage + if (argc < 1) + { + ZPrintf(" test all lobbylang macros and display output\n"); + ZPrintf(" usage: %s \n", argv[0]); + return(0); + } + else + { + uint32_t uToken; + uint32_t uCurrency; + uint16_t uLanguage; + uint16_t uCountry; + uint16_t uSymbols; + uint16_t uTemp; + char strCurrency[4]; + char strLang[3]; + char strCountry[3]; + char strToken[5]; + + uLanguage = LOBBYAPI_LANGUAGE_GERMAN; + uCountry = LOBBYAPI_COUNTRY_GERMANY; + uToken = LOBBYAPI_LocalizerTokenCreate(uLanguage, uCountry); + ZPrintf("token for germany and german: %c%c%c%c\n", LOBBYAPI_LocalizerTokenPrintCharArray(uToken)); + uCountry = LOBBYAPI_LocalizerTokenGetCountry(uToken); + uLanguage = LOBBYAPI_LocalizerTokenGetLanguage(uToken); + uSymbols = LOBBYAPI_LocalizerTokenGetShortFromString("*^"); + uTemp = LOBBYAPI_LocalizerTokenShortToUpper(uLanguage); + ZPrintf("language [%c%c=0x%X] toupper [%c%c=0x%X]\n", + (uLanguage>>8)&0xFF, uLanguage&0xFF, uLanguage, + (uTemp>>8)&0xFF, uTemp&0xFF, uTemp); + uTemp = LOBBYAPI_LocalizerTokenShortToLower(uCountry); + ZPrintf("country [%c%c=0x%X] tolower [%c%c=0x%X]\n", + (uCountry>>8)&0xFF, uCountry&0xFF, uCountry, + (uTemp>>8)&0xFF, uTemp&0xFF, uTemp); + ZPrintf("symbols (should not change) 0x%X toupper 0x%X\n",uSymbols, LOBBYAPI_LocalizerTokenShortToUpper(uSymbols)); + LOBBYAPI_LocalizerTokenSetCountry(uToken, LOBBYAPI_COUNTRY_UNITED_STATES); + ZPrintf("token change to USA: %c%c%c%c\n", LOBBYAPI_LocalizerTokenPrintCharArray(uToken)); + LOBBYAPI_LocalizerTokenSetLanguage(uToken, LOBBYAPI_LANGUAGE_ENGLISH); + ZPrintf("token change to USA and ENGLISH: %c%c%c%c\n", LOBBYAPI_LocalizerTokenPrintCharArray(uToken)); + LOBBYAPI_LocalizerTokenCreateCountryString(strCountry, uToken); + ZPrintf("Country string [%s]\n", strCountry); + LOBBYAPI_LocalizerTokenCreateLanguageString(strLang, uToken); + ZPrintf("Language string [%s]\n", strLang); + LOBBYAPI_LocalizerTokenCreateLocalityString(strToken, uToken); + ZPrintf("Locality string [%s]\n", strToken); + ZPrintf("int16_t from string 'Aa': 0x%X\n", LOBBYAPI_LocalizerTokenGetShortFromString("Aa")); + uToken = LOBBYAPI_LocalizerTokenCreateFromStrings("FR", "FR"); + ZPrintf("create from strings: %c%c%c%c\n", LOBBYAPI_LocalizerTokenPrintCharArray(uToken)); + sprintf(strToken, "frCA"); + uToken = LOBBYAPI_LocalizerTokenCreateFromString(strToken); + ZPrintf("create from string: %s --> %c%c%c%c\n", strToken, LOBBYAPI_LocalizerTokenPrintCharArray(uToken)); + + // currency + uCurrency = LOBBYAPI_CURRENCY_EURO; + LOBBYAPI_CreateCurrencyString(strCurrency, uCurrency); + ZPrintf("Currency string [%s]\n", strCurrency); + + uCurrency = LOBBYAPI_CURRENCY_UNITED_STATES_DOLLAR; + LOBBYAPI_CreateCurrencyString(strCurrency, uCurrency); + ZPrintf("Currency string [%s]\n", strCurrency); + + uCurrency = LOBBYAPI_CURRENCY_CANADIAN_DOLLAR; + LOBBYAPI_CreateCurrencyString(strCurrency, uCurrency); + ZPrintf("Currency string [%s]\n", strCurrency); + +// > langtest +// token for germany and german: deDE +// language [de=0x6465] toupper [DE=0x4445] +// country [DE=0x4445] tolower [de=0x6465] +// symbols (should not change) 0x2A5E toupper 0x2A5E +// token change to USA: deUS +// token change to USA and ENGLISH: enUS +// Country string [US] +// Language string [en] +// Locality string [enUS] +// int16_t from string 'Aa': 0x4161 +// create from strings: frFR +// create from string: frCA --> frCA + + uToken = NetConnStatus('locl', 0, NULL, 0); + ZPrintf("Current locl: %c%c%c%c\n", LOBBYAPI_LocalizerTokenPrintCharArray(uToken)); + + } + return(0); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/memdebug.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/memdebug.c new file mode 100644 index 00000000..5e4e8d23 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/memdebug.c @@ -0,0 +1,73 @@ +/*H********************************************************************************/ +/*! + \File memdebug.c + + \Description + Enable/Disable DirtySock memory debugging. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 11/01/2005 (jbookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/netconn.h" + +#include "libsample/zlib.h" + +#include "testermemory.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdMemDebug + + \Description + Enable/Disable DirtySock memory debugging. + + \Input *argz - environment + \Input argc - number of args + \Input **argv - argument list + + \Output int32_t - standard return code + + \Version 11/01/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t CmdMemDebug(ZContext *argz, int32_t argc, char **argv) +{ + uint32_t bEnable; + + // check usage + if (argc != 2) + { + ZPrintf(" control memory auditing features\n"); + ZPrintf(" usage: %s [0|1] - disable/enable memory debugging\n", argv[0]); + return(0); + } + + // parse the arg + bEnable = (int32_t)strtol(argv[1], NULL, 10); + TesterMemorySetDebug(bEnable); + ZPrintf(" %s: memory debugging %s\n", argv[0], bEnable ? "enabled" : "disabled"); + + // done + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/net.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/net.c new file mode 100644 index 00000000..be0996b2 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/net.c @@ -0,0 +1,592 @@ +/*H*************************************************************************************/ +/*! + \File lobby.c + + \Description + Reference application for the NetApi module. + + \Notes + Base code from locker test function by jbrookes. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 04/12/2005 (jfrank) First Version +*/ +/*************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include +#include +#include + +#if defined (__CELLOS_LV2__) +#include +#include +#include +#endif + +#ifdef _XBOX +#include +#endif + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/dirtysock/dirtyerr.h" + +#include "libsample/zlib.h" +#include "libsample/zmem.h" + +#include "testerregistry.h" +#include "testerhostcore.h" +#include "testersubcmd.h" +#include "testermodules.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct NetAppT +{ + int32_t iNetUp; + int8_t bExternalCleanupComplete; +} NetAppT; + +/*** Function Prototypes ***************************************************************/ + +static void _NetStartup(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _NetQuery(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +#if defined(DIRTYCODE_PS4) +static void _NetTicket(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +#endif +static void _NetConnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _NetId(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _NetDisconnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _NetShutdown(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _NetControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _NetStatus(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _NetPorts(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); + +/*** Variables *************************************************************************/ + +static T2SubCmdT _Net_Commands[] = +{ + { "startup", _NetStartup }, + { "start", _NetStartup }, + { "query", _NetQuery }, + { "ctrl", _NetControl }, + { "id", _NetId }, + { "status", _NetStatus }, + { "ports", _NetPorts }, +#if defined(DIRTYCODE_PS4) + { "ticket", _NetTicket }, +#endif + { "connect", _NetConnect }, + { "disconnect", _NetDisconnect }, + { "shutdown", _NetShutdown }, + { "stop", _NetShutdown }, + { "", NULL } +}; + +static NetAppT _Net_App = +{ 0, + 0 +}; + +/*** Private Functions *****************************************************************/ + +/*F*************************************************************************************************/ +/*! + \Function _NetExtCleanupCallback + + \Description + To test external cleanup mechanism + + \Input *pCallbackData - pointer to platform-specific dataspace + + \Output + int32_t - zero=success; -1=try again; other negative=error + + \Version 10/01/2011 (mclouatre) +*/ +/*************************************************************************************************F*/ +static int32_t _NetExtCleanupCallback(void *pCallbackData) +{ + NetAppT *pApp = (NetAppT *)pCallbackData; + + if (pApp->bExternalCleanupComplete) + { + return(0); // complete + } + else + { + return(-1); // try again + } +} + +/*F*************************************************************************************/ +/*! + \Function _NetStartup + + \Description + Net subcommand - start dirtysock + + \Input *pApp - pointer to lobby app + \Input argc - argument count + \Input *argv[] - argument list + + \Version 04/12/2005 (jfrank) +*/ +/**************************************************************************************F*/ +static void _NetStartup(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + NetAppT *pApp = (NetAppT *)pCmdRef; + char strBuf[256] = "-servicename=tester2"; + int32_t iLoop; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s [start|startup] \"\"\n", argv[0], argv[1]); + return; + } + + for (iLoop = 2; iLoop < argc; iLoop++) + { + ds_strnzcat(strBuf, " ", (int32_t)(sizeof(strBuf) - strlen(strBuf) - 1)); + ds_strnzcat(strBuf, argv[iLoop], (int32_t)(sizeof(strBuf) - strlen(strBuf) - 1)); + } + + ZPrintf("NET: Starting dirtysock with params {%s}.\n", strBuf); + NetConnStartup(strBuf); + + pApp->iNetUp = 1; +} + +/*F*************************************************************************************/ +/*! + \Function _NetQuery + + \Description + Net subcommand - issue a NetConnQuery call + + \Input *pApp - pointer to lobby app + \Input argc - argument count + \Input *argv[] - argument list + + \Version 05/12/2005 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _NetQuery(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + if ((bHelp == TRUE) || (argc > 3)) + { + ZPrintf(" usage: %s [query] \"\"\n", argv[0], argv[1]); + return; + } + + if (argc == 2) + { + NetConnQuery(NULL, NULL, 0); + } + else if (argc == 3) + { + NetConnQuery(argv[2], NULL, 0); + } +} + +#if defined(DIRTYCODE_PS4) +/*F*************************************************************************************/ +/*! + \Function _NetTicket + + \Description + Net subcommand - test ps3 ticketing system + + \Input *pApp - pointer to lobby app + \Input argc - argument count + \Input *argv[] - argument list + + \Version 10/09/2009 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _NetTicket(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + uint8_t aTicketBuf[1024]; + int32_t iResult; + + // acquire ticket + if ((iResult = NetConnStatus('tick', 0, aTicketBuf, sizeof(aTicketBuf))) < 1) + { + NetPrintf(("net: NetConnStatus('tick') returned %d\n", iResult)); + return; + } + // display environment + if ((iResult = NetConnStatus('envi', 0, NULL, 0)) < 1) + { + NetPrintf(("net: NetConnStatus('envi') returned %d\n", iResult)); + return; + } + NetPrintf(("net: platform environment is %d\n")); +} +#endif //defined(DIRTYCODE_PS4) + +/*F*************************************************************************************/ +/*! + \Function _NetConnect + + \Description + Net subcommand - connect the networking + + \Input *pApp - pointer to lobby app + \Input argc - argument count + \Input *argv[] - argument list + + \Version 04/12/2005 (jfrank) First Version +*/ +/**************************************************************************************F*/ +static void _NetConnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + if ((bHelp == TRUE) || (argc < 2) || (argc > 4)) + { + ZPrintf(" usage: %s connect \"\"\n", argv[0]); + return; + } + + if (argc == 2) + { + NetConnConnect(NULL, NULL, 0); + } + else if (argc == 3) + { + NetConnConnect((const NetConfigRecT *)argv[2], NULL, 0); + } + else if (argv[2][0] == '-') + { + NetConnConnect(NULL, argv[3], 0); + } + else + { + NetConnConnect((const NetConfigRecT *)argv[2], argv[3], 0); + } +} + +/*F*************************************************************************************/ +/*! + \Function _NetId + + \Description + Net subcommand - set up accountId and personaId + + \Input *pApp - pointer to lobby app + \Input argc - argument count + \Input *argv[] - argument list + + \Version 07/31/2019 (mgallant) +*/ +/**************************************************************************************F*/ +static void _NetId(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s id [accountId] [personaId]\n", argv[0]); + return; + } + + int64_t iAccountId = (int64_t) strtol(argv[2], NULL, 10); + int64_t iPersonaId = (int64_t) strtol(argv[3], NULL, 10); + + ZPrintf("net: executing NetConnControl('%s', %d, %d, %s, %s)\n", "acid", 0, sizeof(iAccountId), "", NULL); + NetConnControl('acid', 0, sizeof(iAccountId), &iAccountId, NULL); + + ZPrintf("net: executing NetConnControl('%s', %d, %d, %s, %s)\n", "peid", 0, sizeof(iPersonaId), "", NULL); + NetConnControl('peid', 0, sizeof(iPersonaId), &iPersonaId, NULL); + + return; + +} + +/*F*************************************************************************************/ +/*! + \Function _NetDisconnect + + \Description + Net subcommand - connect the networking + + \Input *pApp - pointer to lobby app + \Input argc - argument count + \Input *argv[] - argument list + + \Version 04/12/2005 (jfrank) First Version +*/ +/**************************************************************************************F*/ +static void _NetDisconnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + if (bHelp == TRUE) + { + ZPrintf(" usage: %s disconnect\n", argv[0]); + return; + } + + NetConnDisconnect(); +} + +/*F*************************************************************************************/ +/*! + \Function _NetShutdown + + \Description + Net subcommand - shut down dirtysock + + \Input *pApp - pointer to lobby app + \Input argc - argument count + \Input *argv[] - argument list + + \Version 04/12/2005 (jfrank) First Version +*/ +/**************************************************************************************F*/ +static void _NetShutdown(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + NetAppT *pApp = (NetAppT *)pCmdRef; + TesterHostCoreT *pCore; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s [stop|shutdown]\n", argv[0]); + return; + } + + pApp->iNetUp = 0; + + // signal to the core that we want to shut everything down + if ((pCore = (TesterHostCoreT *)TesterRegistryGetPointer("CORE")) != NULL) + { + TesterHostCoreShutdown(pCore); + } +} + +/*F********************************************************************************/ +/*! + \Function _NetControl + + \Description + Execute NetConnControl() + + \Input *pApp - pointer to upnp module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Version 09/27/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _NetControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + NetAppT *pApp = (NetAppT *)pCmdRef; + int32_t iCmd, iValue=0, iValue2=0; + void *pValue = NULL; + void *pValue2 = NULL; + + if ((bHelp == TRUE) || (argc < 3)) + { + ZPrintf(" usage: %s ctrl [val1] [val2] [strVal] [strVal]\n", argv[0]); + return; + } + + iCmd = argv[2][0] << 24; + iCmd |= argv[2][1] << 16; + iCmd |= argv[2][2] << 8; + iCmd |= argv[2][3]; + + if (argc > 3) + { + iValue = (int32_t)strtol(argv[3], NULL, 10); + } + if (argc > 4) + { + iValue2 = (int32_t)strtol(argv[4], NULL, 10); + } + if (argc > 5) + { + pValue = argv[5]; + } + if (argc > 6) + { + pValue2 = argv[6]; + } + + if (strcmp("recu", argv[2]) == 0) + { + pApp->bExternalCleanupComplete = FALSE; + pValue = (void *)_NetExtCleanupCallback; + pValue2 = (void *)pApp; + } + + ZPrintf("net: executing NetConnControl('%s', %d, %d, %s, %s)\n", argv[2], iValue, iValue2, pValue ? "" : "(null)", pValue2 ? "" : "(null)"); + NetConnControl(iCmd, iValue, iValue2, pValue, pValue2); +} + +/*F*************************************************************************************/ +/*! + \Function _NetStatus + + \Description + Net subcommand - Execute NetConnStatus() + + \Input *pApp - pointer to lobby app + \Input argc - argument count + \Input *argv[] - argument list + + \Version 04/12/2005 (jfrank) First Version +*/ +/**************************************************************************************F*/ +static void _NetStatus(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + unsigned char *pToken; + uint32_t uResult, uToken, iData; + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s status <4-char status token to get> \n", argv[0]); + return; + } + + pToken = (unsigned char *)argv[2]; + uToken = (pToken[0] << 24) | (pToken[1] << 16) | (pToken[2] << 8) | pToken[3]; + iData = (int32_t)strtol(argv[3], NULL, 10); + +#if defined(DIRTYCODE_XBOXONE) + if (strcmp("tick", argv[2]) == 0) + { + char strToken[16 * 1024]; + uResult = NetConnStatus(uToken, iData, (void *)strToken, sizeof(strToken)); + } + else if (strcmp("xadd", argv[2]) == 0) + { + uint8_t aSecureDeviceAddressBlob[256]; + + uResult = NetConnStatus(uToken, iData, (void *)aSecureDeviceAddressBlob, sizeof(aSecureDeviceAddressBlob)); + } + else +#endif + { + uResult = NetConnStatus(uToken, iData, NULL, 0); + } + + // if printable, display result as text + if (isprint((uResult >> 24) & 0xff) && isprint((uResult >> 16) & 0xff) && + isprint((uResult >> 8) & 0xff) && isprint((uResult >> 0) & 0xff)) + { + ZPrintf("%s: status of ('%C', %d) is {'%C')\n", argv[0], uToken, iData, uResult); + } + else // display result as decimal and hex + { + ZPrintf("%s: status of ('%C', %d) is {%d/0x%08X)\n", argv[0], uToken, iData, uResult, uResult); + } +} + +/*F********************************************************************************/ +/*! + \Function _NetPorts + + \Description + Like "netstat" on unix + + \Input *pApp - pointer to upnp module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Version 09/25/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _NetPorts(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SocketT *pSocket; + int32_t iResult; + uint32_t uPort; + struct sockaddr SockName; + + SockaddrInit(&SockName, AF_INET); + + // find ports that are bound + for (uPort = 1024; uPort < 65536; uPort++) + { + // create a UDP socket + if ((pSocket = SocketOpen(AF_INET, SOCK_DGRAM, IPPROTO_IP)) == NULL) + { + NetPrintf(("net: error -- could not allocate socket resource\n")); + return; + } + + // bind the socket + SockaddrInSetPort(&SockName, uPort); + iResult = SocketBind(pSocket, &SockName, sizeof(SockName)); + if (iResult == SOCKERR_ADDRINUSE) + { + ZPrintf("net: %5s %5d\n", "UDP", uPort); + } + + // close the socket + if (SocketClose(pSocket) != 0) + { + NetPrintf(("net: error -- could not free socket resource\n")); + return; + } + + // give some time to network stack + NetConnSleep(1); + NetConnIdle(); + } +} + + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function CmdNet + + \Description + Net command. + + \Input *argz - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Output + int32_t - zero + + \Version 11/24/04 (jbrookes) +*/ +/**************************************************************************************F*/ +int32_t CmdNet(ZContext *argz, int32_t argc, char *argv[]) +{ + void *pCmdRef = &_Net_App; + unsigned char bHelp; + T2SubCmdT *pCmd; + + // handle shutdown + if(argc == 0) + { + // nothing to do + return(0); + } + // handle basic help + else if ((argc < 2) || (((pCmd = T2SubCmdParse(_Net_Commands, argc, argv, &bHelp)) == NULL))) + { + T2SubCmdUsage(argv[0], _Net_Commands); + return(0); + } + + // hand off to command + pCmd->pFunc(pCmdRef, argc, argv, bHelp); + return(0); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/netprint.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/netprint.c new file mode 100644 index 00000000..6a2cc3b6 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/netprint.c @@ -0,0 +1,927 @@ +/*H********************************************************************************/ +/*! + \File netprint.c + + \Description + Test ds_vsnprintf() for compliance with standard routines. + + \Copyright + Copyright (c) 2009-2010 Electronic Arts Inc. + + \Version 10/28/2009 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "libsample/zlib.h" + +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +// Variables + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _NetFormatAddr6 + + \Description + Initialize a struct sockaddr_in6 given address and port + + \Input *pAddr6 - address to fill in + \Input *pWords - 128 bit address + \Input *uPort - port + + \Version 09/01/2017 (jbrookes) +*/ +/********************************************************************************F*/ +#if !defined(DIRTYCODE_NX) +static void _NetFormatAddr6(struct sockaddr_in6 *pAddr6, const uint16_t *pWords, uint16_t uPort) +{ + int32_t iWord; + ds_memclr(pAddr6, sizeof(*pAddr6)); + pAddr6->sin6_family = AF_INET6; + pAddr6->sin6_port = SocketNtohs(uPort); + pAddr6->sin6_flowinfo = 0; + for (iWord = 0; iWord < 8; iWord += 1) + { + pAddr6->sin6_addr.s6_addr[(iWord*2)+0] = (uint8_t)(pWords[iWord]>>8); + pAddr6->sin6_addr.s6_addr[(iWord*2)+1] = (uint8_t)(pWords[iWord]&0xff); + } +} +#endif + +/*F********************************************************************************/ +/*! + \Function _NetPrintStrCmp + + \Description + Compare two strings; if they are the same, print one out, otherwise + print both out. + + \Input *pStrCtrl - pointer to "control" string (what we are expecting) + \Input *pStrTest - pointer to "test" string (what we actually produced) + \Input *pStrType - pointer to type field, to use in displaying the result + + \Output + int32_t - 0=same, 1=different + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintStrCmp(const char *pStrCtrl, const char *pStrTest, const char *pStrType) +{ + int32_t iResult; + if (strcmp(pStrCtrl, pStrTest) != 0) + { + ZPrintf("netprint: [%s] ctrl(%s) != test(%s)\n", pStrType, pStrCtrl, pStrTest); + iResult = 1; + } + else + { + ZPrintf("netprint: [%s] \"%s\"\n", pStrType, pStrTest); + iResult = 0; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintMemCmp + + \Description + Compare two buffers; if they are the same, print one out, otherwise + print both out. + + \Input *pCtrl - pointer to "control" buffer (what we are expecting) + \Input *pTest - pointer to "test" buffer (what we actually produced) + \Input iLen - buffer length + \Input *pType - pointer to type field, to use in displaying the result + + \Output + int32_t - 0=same, 1=different + + \Version 09/01/2017 (jbrookes) +*/ +/********************************************************************************F*/ +#ifndef DIRTYCODE_NX +static int32_t _NetPrintMemCmp(const uint8_t *pCtrl, int32_t iCtrlLen, const uint8_t *pTest, int32_t iTestLen, const char *pStrType) +{ + int32_t iResult = 1; + if (iCtrlLen != iTestLen) + { + ZPrintf("netprint: [%s] ctrllen(%d) != testlen(%d)\n", pStrType, iCtrlLen, iTestLen); + } + else if (memcmp(pCtrl, pTest, iCtrlLen) != 0) + { + ZPrintf("netprint: [%s] ctrl != test\n", pStrType); + NetPrintMem(pCtrl, iCtrlLen, "ctrl"); + NetPrintMem(pTest, iCtrlLen, "test"); + } + else + { + ZPrintf("netprint: [%s] ctrl=test\n", pStrType); + iResult = 0; + } + return(iResult); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _NetPrintStrIntCmp + + \Description + Compare two strings AND two results; if they are the same, print one out, + otherwise print both out. + + \Input *pStrCtrl - pointer to "control" string (what we are expecting) + \Input *_pStrTest - pointer to "test" string (what we actually produced) + \Input iCtrlRslt - expected result + \Input iTestRslt - actual result + \Input iBufferLimit - size of buffer we were writing into + \Input *pStrType - pointer to type field, to use in displaying the result + + \Output + int32_t - 0=same, 1=different + + \Version 02/15/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintStrIntCmp(const char *pStrCtrl, const char *_pStrTest, int32_t iCtrlRslt, int32_t iTestRslt, int32_t iBufferLimit, const char *pStrType) +{ + int32_t iResult, iStrCmp; + const char *pStrTest; + + // if we have a zero-sized buffer, point to an empty string so we can compare/print it safely + pStrTest = (iBufferLimit > 0) ? _pStrTest : ""; + + // compare the strings + iStrCmp = strcmp(pStrCtrl, pStrTest); + + if ((iStrCmp != 0) && (iCtrlRslt != iTestRslt)) + { + ZPrintf("netprint: [%s] ctrl(%s) != test(%s) and ctrl(%d) != test(%d)\n", pStrType, pStrCtrl, pStrTest, iCtrlRslt, iTestRslt); + iResult = 1; + } + else if (iStrCmp != 0) + { + ZPrintf("netprint: [%s] ctrl(%s) != test(%s)\n", pStrType, pStrCtrl, pStrTest); + iResult = 1; + } + else if (iCtrlRslt != iTestRslt) + { + ZPrintf("netprint: [%s] ctrl(%d) != test(%d)\n", pStrType, iCtrlRslt, iTestRslt); + iResult = 1; + } + else + { + if (iTestRslt > 0) + { + ZPrintf("netprint: [%s] \"%s\" (%d)\n", pStrType, pStrTest, iTestRslt); + } + else + { + ZPrintf("netprint: [%s] "" (%d)\n", pStrType, iTestRslt); + } + iResult = 0; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintFloat + + \Description + Print a floating-point value with both ds_snzprintf and platform sprintf(), + and compare the results. Flag a warning if they are different. + + \Input *pFmt - format string + \Input fValue - float to print + + \Output + int32_t - 0=same, 1=different + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintFloat(const char *pFmt, double fValue) +{ + char strCtrl[256], strTest[256]; + + sprintf(strCtrl, pFmt, fValue); + ds_snzprintf(strTest, sizeof(strTest), pFmt, fValue); + + return(_NetPrintStrCmp(strCtrl, strTest, "flt")); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintInt + + \Description + Print an integer value with both ds_snzprintf and platform sprintf(), + and compare the results. Flag a warning if they are different. + + \Input *pFmt - format string + \Input iValue - integer to print + + \Output + int32_t - 0=same, 1=different + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintInt(const char *pFmt, int32_t iValue) +{ + char strCtrl[256], strTest[256]; + + sprintf(strCtrl, pFmt, iValue); + ds_snzprintf(strTest, sizeof(strTest), pFmt, iValue); + + return(_NetPrintStrCmp(strCtrl, strTest, "int")); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintLongInt + + \Description + Print a 64-bit integer value with both ds_snzprintf and platform sprintf(), + and compare the results. Flag a warning if they are different. + + \Input *pFmt - format string + \Input iValue - 64-bit integer to print + + \Output + int32_t - 0=same, 1=different + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintLongInt(const char *pFmt, int64_t iValue) +{ + char strCtrl[256], strTest[256]; + + sprintf(strCtrl, pFmt, iValue); + ds_snzprintf(strTest, sizeof(strTest), pFmt, iValue); + + return(_NetPrintStrCmp(strCtrl, strTest, "lng")); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintStr + + \Description + Print a string with both ds_snzprintf() and platform sprintf(), + and compare the results. Flag a warning if they are different. + + \Input *pFmt - format string + \Input *pStr - string to print + + \Output + int32_t - 0=same, 1=different + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintStr(const char *pFmt, const char *pStr) +{ + char strCtrl[256], strTest[256]; + + sprintf(strCtrl, pFmt, pStr); + ds_snzprintf(strTest, sizeof(strTest), pFmt, pStr); + + return(_NetPrintStrCmp(strCtrl, strTest, "str")); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintPregen + + \Description + Print formatted output with ds_vsnprintf() and compare to a pre-generated + (static) string. Flag a warning if they are different. + + \Input *pPreGen - pointer to pre-generated string (what we expect) + \Input *pFmt - format specifier + \Input ... - variable argument list + + \Output + int32_t - 0=same, 1=different + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintPregen(const char *pPreGen, const char *pFmt, ...) +{ + char strTest[1024]; + va_list Args; + + // format the output + va_start(Args, pFmt); + ds_vsnprintf(strTest, sizeof(strTest), pFmt, Args); + va_end(Args); + + return(_NetPrintStrCmp(pPreGen, strTest, "pre")); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintOverflow + + \Description + Print formatted output with ds_vsnprintf() and compare to a pre-generated + (static) string. Flag a warning if they are different. + + \Input iBufferLimit - size we want to limit buffer to + \Input *pPreGen - pre-generated string to compare formatted result to + \Input iExpectedResult - expected result code from ds_vsnzprintf() + \Input *pFmt - format specifier + \Input ... - variable argument list + + \Output + int32_t - 0=same, 1=different + + \Version 02/15/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintOverflow(int32_t iBufferLimit, const char *pPreGen, int32_t iExpectedResult, const char *pFmt, ...) +{ + char strTest[128]; + int32_t iResult, iStrCmp, iCheck, iMemStomp; + char cMemChar = 0xcc; + va_list Args; + + // pre-initialize array + ds_memset(strTest, cMemChar, sizeof(strTest)); + + // format the output + va_start(Args, pFmt); + iResult = ds_vsnzprintf(strTest, iBufferLimit, pFmt, Args); + va_end(Args); + + // make sure we didn't write outside our bounds + for (iCheck = iBufferLimit, iMemStomp = 0; iCheck < (signed)sizeof(strTest); iCheck += 1) + { + if (strTest[iCheck] != cMemChar) + { + iMemStomp += 1; + } + } + + // did the test succeed or fail? + iStrCmp = _NetPrintStrIntCmp(pPreGen, strTest, iExpectedResult, iResult, iBufferLimit, "ovr"); + if ((iStrCmp != 0) || (iMemStomp != 0)) + { + return(1); + } + else + { + return(0); + } +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintTestFlts + + \Description + Execute a series of floating-point printing tests. + + \Output + int32_t - number of test failures + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintTestFlts(void) +{ + int32_t iResult = 0; + ZPrintf("netprint: Floating-point comparative tests\n"); + iResult += _NetPrintFloat("%2.0f", 10.0f); + iResult += _NetPrintFloat("%f", pow(2,52)); + iResult += _NetPrintFloat("%f", pow(2,53)); + iResult += _NetPrintFloat("%.15f", 0.00000000000000000000099f); // we don't test .16 here; ms printf doesn't round at 16+ digits + iResult += _NetPrintFloat("%.15f", 0.0099f); + iResult += _NetPrintFloat("%.15f", 0.0000000099f); + iResult += _NetPrintFloat("%f", 1.99f); + iResult += _NetPrintFloat("%f", -1.99); + iResult += _NetPrintFloat("%f", 1.0f); + iResult += _NetPrintFloat("%f", 0.75f); + iResult += _NetPrintFloat("%2.2f", 1.0); + iResult += _NetPrintFloat("%+2.2f", 1.0); + iResult += _NetPrintFloat("%f", -1.99); + iResult += _NetPrintFloat("%.2f", 9.99); + iResult += _NetPrintFloat("%.2f", 9.999); + iResult += _NetPrintFloat("%.2f", -1.999); + iResult += _NetPrintFloat("%.2f", 0.1); + iResult += _NetPrintFloat("%.15f", 3.1415926535897932384626433832795); + + /* this section is for stuff that is not compatible with sprintf() or + is not compatible with the single-param _NetPrintFloat(). For these + tests, we compare against a pre-generated string to make sure our + output is consistently what we expect across all platforms. */ + + // make sure all fp selectors result in %f behavior + iResult += _NetPrintPregen("%e 1.000000 %E 1.000000", "%%e %e %%E %E", 1.0f, 1.0f); + iResult += _NetPrintPregen("%g 1.000000 %G 1.000000", "%%g %g %%G %G", 1.0f, 1.0f); + iResult += _NetPrintPregen("%F 1.000000", "%%F %F", 1.0f); + // test variable width with fp + iResult += _NetPrintPregen(" 1", "%2.*f", 2, 1.0f); + // test a really large number, but less than our maximum + iResult += _NetPrintPregen("9223372036854775808.000000", "%f", pow(2,63)); + // test a really large number, greater than our max + iResult += _NetPrintPregen("0.(BIG)", "%f", pow(2,64)); + + // floating-point test summary + ZPrintf("netprint: %d floating-point test discrepencies\n", iResult); + ZPrintf("netprint: ------------------------------------\n"); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintTestInts + + \Description + Execute a series of integer printing tests. + + \Output + int32_t - number of test failures + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintTestInts(void) +{ + int32_t iResult = 0; + const int32_t iBits = (signed)sizeof(int32_t)*8; + const int32_t iMaxInt = (1 << (iBits - 1)) + 1; + int32_t iInt0 = 8; + + ZPrintf("netprint: Integer tests\n"); + + iResult += _NetPrintInt("%d = 8", iInt0); + iResult += _NetPrintInt("%+d = +8", iInt0); + iResult += _NetPrintInt("%d = -maxint", iMaxInt); + iResult += _NetPrintInt("char %c = 'a'", 'a'); + iResult += _NetPrintInt("hex %x = ff", 0xff); + iResult += _NetPrintInt("hex %02x = 00", 0); + iResult += _NetPrintInt("oct %o = 10", 010); + iResult += _NetPrintInt("oct %03o = 010", 8); + iResult += _NetPrintLongInt("%llu", 0x900f123412341234ull); + iResult += _NetPrintLongInt("0x%llx", 0x900f123412341234ull); +#if defined(DIRTYCODE_PC) + iResult += _NetPrintLongInt("%I64d", 0x900f123412341234ull); +#endif +#if defined(DIRTYCODE_LINUX) || defined(DIRTYCODE_APPLEIOS) + iResult += _NetPrintLongInt("%qd", 0x900f123412341234ull); +#endif + iResult += _NetPrintPregen("signed -5 = unsigned 4294967291 = hex fffffffb", "signed %d = unsigned %u = hex %x", -5, -5, -5); + iResult += _NetPrintPregen("4294967286,-10", "%u,%d", -10, -10); + iResult += _NetPrintPregen("0,10,20,30,100,200,1000", "%d,%d,%d,%d,%d,%d,%d", 0, 10, 20, 30, 100, 200, 1000); + iResult += _NetPrintPregen("0,-10,-20,-30,-100,-200,-1000", "%d,%d,%d,%d,%d,%d,%d", 0, -10, -20, -30, -100, -200, -1000); + + ZPrintf("netprint: %d integer test discrepencies\n", iResult); + ZPrintf("netprint: ------------------------------------\n"); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintTestPtrs + + \Description + Execute a series of pointer printing tests. + + \Output + int32_t - number of test failures + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintTestPtrs(void) +{ + int32_t iResult = 0; + char *pStr = "string test", *pNul = NULL; + char strTemp[128]; + + ZPrintf("netprint: Pointer tests\n"); +#if DIRTYCODE_64BITPTR + _NetPrintPregen("p=$123456789abcdef0", "p=%p", (void *)0x123456789abcdef0); + +#if defined(DIRTYCODE_LINUX) || defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_APPLEOSX) || defined(DIRTYCODE_PS4) + sprintf(strTemp, "p=$%016lx, null p=(null)", (uintptr_t)pStr); +#else + sprintf(strTemp, "p=$%016llx, null p=(null)", (uintptr_t)pStr); +#endif + +#else + _NetPrintPregen("p=$12345678", "p=%p", (void *)0x12345678); + sprintf(strTemp, "p=$%08x, null p=(null)", (uint32_t)pStr); +#endif + iResult += _NetPrintPregen(strTemp, "p=%p, null p=%p", pStr, pNul); + + ZPrintf("netprint: %d pointer test discrepencies\n", iResult); + ZPrintf("netprint: ------------------------------------\n"); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintTestStrs + + \Description + Execute a series of string printing tests. + + \Output + int32_t - number of test failures + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintTestStrs(void) +{ + int32_t iResult = 0; + char *pStr = "string test"; + wchar_t *pWideStr = L"wide string test"; + + ZPrintf("netprint: String tests\n"); + iResult += _NetPrintStr("string test=%s", pStr); +#if defined(DIRTYCODE_PC) + iResult += _NetPrintStr("wide string test=%S", (const char *)pWideStr); +#else + iResult += _NetPrintStr("wide string test=%ls", (const char *)pWideStr); +#endif + iResult += _NetPrintStr("string test=%s", NULL); + + ZPrintf("netprint: %d string test discrepencies\n", iResult); + ZPrintf("netprint: ------------------------------------\n"); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintTestAlgn + + \Description + Execute a series of string alignment formatting tests. + + \Output + int32_t - number of test failures + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintTestAlgn(void) +{ + int32_t iResult = 0; + + ZPrintf("netprint: Alignment tests\n"); + + iResult += _NetPrintStr(" left just: \"%-10s\"", "left"); + iResult += _NetPrintStr("right just: \"%10s\"", "right"); + iResult += _NetPrintInt(" 6: %04d zero padded", 6); + iResult += _NetPrintInt(" 6: %-4d left just", 6); + iResult += _NetPrintInt(" 6: %4d right just", 6); + iResult += _NetPrintInt("-6: %04d zero padded", -6); + iResult += _NetPrintInt("-6: %-4d left just", -6); + iResult += _NetPrintInt("-6: %4d right just", -6); + iResult += _NetPrintPregen(" 6: 0006 zero padded, variable-length", " 6: %0*d zero padded, variable-length", 4, 6); + iResult += _NetPrintPregen(" 6: 6 left just, variable-length", " 6: %-*d left just, variable-length", 4, 6); + + iResult += _NetPrintPregen(" a: a left just", " a: %-8c left just", 'a'); + iResult += _NetPrintPregen(" b: b right just", " b: %8c right just", 'b'); + iResult += _NetPrintPregen(" c: c left just, variable-length", " c: %-*c left just, variable-length", 8, 'c'); + iResult += _NetPrintPregen(" d: d right just, variable-length", " d: %*c right just, variable-length", 8, 'd'); + + ZPrintf("netprint: %d alignment test discrepencies\n", iResult); + ZPrintf("netprint: ------------------------------------\n"); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintTestMisc + + \Description + Execute a series of miscelleneous printing tests. + + \Output + int32_t - number of test failures + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintTestMisc(void) +{ + int32_t iResult = 0; + int32_t iInt1 = 0; + char strTest[256], strTest2[128]; + + ZPrintf("netprint: Misc tests\n"); + + // test %n selector + ds_snzprintf(strTest, sizeof(strTest), "%%n test %n", &iInt1); + ds_snzprintf(strTest2, sizeof(strTest2), "(%d chars written)", iInt1); + ds_strnzcat(strTest, strTest2, sizeof(strTest)); + iResult += _NetPrintStrCmp("%n test (8 chars written)", strTest, "msc"); + + iResult += _NetPrintPregen("# test: 10", "# test: %#d", 10); + iResult += _NetPrintPregen("1 1 1 1 1.000000 1 1 1", "%hd %hhd %ld %lld %f %zd %jd %td", 1, 1, 1l, 1ll, 1.0, 1, 1, 1); + iResult += _NetPrintPregen("testing invalid trailing pct: ", "testing invalid trailing pct: %"); + iResult += _NetPrintPregen("testing valid trailing pct: %", "testing valid trailing pct: %%"); + + ZPrintf("netprint: %d misc test discrepencies\n", iResult); + ZPrintf("netprint: ------------------------------------\n"); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintTestAddr + + \Description + Execute a series of address printing tests. + + \Output + int32_t - number of test failures + + \Version 08/28/2017 (jbrookes) +*/ +/********************************************************************************F*/ +#ifndef DIRTYCODE_NX +static int32_t _NetPrintTestAddr(void) +{ + uint32_t uAddr, uNumAddrs, uWord; + struct sockaddr_in6 SockAddr6; + char strAddr6[48]; + int32_t iResult = 0; + uint16_t aWords[8]; + + struct _NetAddr6 + { + const uint16_t aWords[8]; + const char *pAddrText; + }; + static const struct _NetAddr6 _aAddr6ToString[] = + { + { { 0x2001, 0x0db8, 0x0000, 0x0000, 0x0008, 0x0800, 0x200c, 0x417A }, "2001:db8::8:800:200c:417a" }, // a unicast address + { { 0xff01, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0101 }, "ff01::101" }, // a multicast address + { { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001 }, "::1" }, // the loopback address + { { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, "::" }, // the unspecified address + { { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xc0a8, 0x0001 }, "::ffff:192.168.0.1" }, // an ipv4-mapped ipv6 address + { { 0x0064, 0xff9b, 0x0000, 0x0000, 0x0000, 0x0000, 0xc0a8, 0x0001 }, "64:ff9b::192.168.0.1" }, // a nat64 address + { { 0x0102, 0x0000, 0x0506, 0x0000, 0x0000, 0x0708, 0x090a, 0x0000 }, "102:0:506::708:90a:0" }, // synthetic test + { { 0x1234, 0x5678, 0x9abc, 0xdef0, 0x0000, 0x1234, 0x5678, 0x9abc }, "1234:5678:9abc:def0:0:1234:5678:9abc" } // synthetic test + }; + static const struct _NetAddr6 _aStringToAddr6[] = + { + { { 0x2001, 0x0db8, 0x0000, 0x0000, 0x0008, 0x0800, 0x200c, 0x417A }, "2001:DB8::8:800:200C:417A" }, // a unicast address + { { 0xff01, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0101 }, "FF01::101" }, // a multicast address + { { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001 }, "::1" }, // the loopback address + { { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, "::" }, // the unspecified address + { { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xc0a8, 0x0001 }, "::ffff:c0a8:1" }, // an ipv4-mapped ipv6 address + { { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xc0a8, 0x0001 }, "::ffff:192.168.0.1" }, // a mixed-notation ipv4-mapped ipv6 address + { { 0x0064, 0xff9b, 0x0000, 0x0000, 0x0000, 0x0000, 0xc0a8, 0x0001 }, "64:ff9b::c0a8:1" }, // a nat64 address + { { 0x0064, 0xff9b, 0x0000, 0x0000, 0x0000, 0x0000, 0xc0a8, 0x9b18 }, "64:ff9b::192.168.155.24" }, // a mixed-notation nat64 address + { { 0x0102, 0x0000, 0x0506, 0x0000, 0x0000, 0x0708, 0x090a, 0x0000 }, "102:0:506::708:90a:0" }, // synthetic test + { { 0x1234, 0x5678, 0x9abc, 0xdef0, 0x0000, 0x1234, 0x5678, 0x9abc }, "1234:5678:9abc:def0:0:1234:5678:9abc" } // synthetic test + }; + + // test addr6 to string conversion + uNumAddrs = sizeof(_aAddr6ToString) / sizeof(_aAddr6ToString[0]); + for (uAddr = 0; uAddr < uNumAddrs; uAddr += 1) + { + _NetFormatAddr6(&SockAddr6, _aAddr6ToString[uAddr].aWords, 0); + SockaddrInGetAddrText((struct sockaddr *)&SockAddr6, strAddr6, sizeof(strAddr6)); + iResult += _NetPrintStrCmp(_aAddr6ToString[uAddr].pAddrText, strAddr6, "addr"); + } + + // test string to addr6 conversion + uNumAddrs = sizeof(_aStringToAddr6)/sizeof(_aStringToAddr6[0]); + for (uAddr = 0; uAddr < uNumAddrs; uAddr += 1) + { + SockaddrInSetAddrText((struct sockaddr *)&SockAddr6, _aStringToAddr6[uAddr].pAddrText); + for (uWord = 0; uWord < 8; uWord += 1) + { + aWords[uWord] = SocketNtohs(*(uint16_t *)(SockAddr6.sin6_addr.s6_addr+(uWord*2))); + } + iResult += _NetPrintMemCmp((const uint8_t *)_aStringToAddr6[uAddr].aWords, sizeof(_aStringToAddr6[uAddr].aWords), (const uint8_t *)aWords, sizeof(aWords), "addr"); + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintTestCust + + \Description + Execute a series of custom printing tests. These are for selectors that + are specific to the DirtySock platform string formatting functions. + + \Output + int32_t - number of test failures + + \Version 05/05/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintTestCust(void) +{ + int32_t iResult = 0; + struct sockaddr SockAddr4; + struct sockaddr_in6 SockAddr6, SockAddr6_2, SockAddr6_3; + uint8_t aSin6Addr[16] = { 0x5a, 0x23, 0x01, 0x32, 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0xde, 0x00, 0x02 }; + uint32_t uAddr; + + SockaddrInit(&SockAddr4, AF_INET); + SockaddrInSetAddr(&SockAddr4, 0xC0A80001); + SockaddrInSetPort(&SockAddr4, 3658); + + // create an IPv4-mapped IPv6 address + ds_memclr(&SockAddr6, sizeof(SockAddr6)); + uAddr = SockaddrInGetAddr(&SockAddr4); + SockAddr6.sin6_family = AF_INET6; + SockAddr6.sin6_port = SocketNtohs(SockaddrInGetPort(&SockAddr4)); + SockAddr6.sin6_flowinfo = 0; + SockAddr6.sin6_addr.s6_addr[10] = 0xff; + SockAddr6.sin6_addr.s6_addr[11] = 0xff; + SockAddr6.sin6_addr.s6_addr[12] = (uint8_t)(uAddr >> 24); + SockAddr6.sin6_addr.s6_addr[13] = (uint8_t)(uAddr >> 16); + SockAddr6.sin6_addr.s6_addr[14] = (uint8_t)(uAddr >> 8); + SockAddr6.sin6_addr.s6_addr[15] = (uint8_t)(uAddr >> 0); + + // create a simulated full IPv6 address + SockaddrInit6(&SockAddr6_2, AF_INET6); + SockAddr6_2.sin6_port = SocketNtohs(3658); + ds_memcpy(&SockAddr6_2.sin6_addr, aSin6Addr, sizeof(SockAddr6_2.sin6_addr)); + + // create a NAT64 address + ds_memcpy(&SockAddr6_3, &SockAddr6, sizeof(SockAddr6_3)); + SockAddr6_3.sin6_addr.s6_addr[1] = 0x64; + SockAddr6_3.sin6_addr.s6_addr[2] = 0xff; + SockAddr6_3.sin6_addr.s6_addr[3] = 0x9b; + SockAddr6_3.sin6_addr.s6_addr[10] = 0x00; + SockAddr6_3.sin6_addr.s6_addr[11] = 0x00; + + ZPrintf("netprint: Custom tests\n"); + + iResult += _NetPrintPregen("addr=192.168.0.1", "addr=%a", SockaddrInGetAddr(&SockAddr4)); + iResult += _NetPrintPregen("addr=255.255.255.255", "addr=%a", 0xffffffff); + iResult += _NetPrintPregen("addr=[192.168.0.1]:3658", "addr=%A", &SockAddr4); + iResult += _NetPrintPregen("addr=[::ffff:192.168.0.1]:3658", "addr=%A", &SockAddr6); + iResult += _NetPrintPregen("addr=[64:ff9b::192.168.0.1]:3658", "addr=%A", &SockAddr6_3); + iResult += _NetPrintPregen("addr=[5a23:132:ff12::12de:2]:3658", "addr=%A", &SockAddr6_2); + iResult += _NetPrintPregen("'dflt' = 'dflt'", "'dflt' = '%C'", 'dflt'); + iResult += _NetPrintPregen("'d*l*' = 'd*l*'", "'d*l*' = '%C'", ('d' << 24) | ('l' << 8)); + + ZPrintf("netprint: %d custom test discrepencies\n", iResult); + ZPrintf("netprint: ------------------------------------\n"); + + return(iResult); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _NetPrintTestOver + + \Description + Execute a series of printing tests exercising the overflow functionality. + + \Output + int32_t - number of test failures + + \Version 02/15/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintTestOver(void) +{ + const char strOver[] = "<<>>"; + int32_t iResult0 = 0, iResult1 = 0; + + // test some overflow scenarios with ds_snzprintf2 + ZPrintf("netprint: Overflow tests\n"); + iResult1 += _NetPrintOverflow( 0, "", 14, "%s", strOver); + iResult1 += _NetPrintOverflow( 1, "", 14, "%s", strOver); + iResult1 += _NetPrintOverflow(12, "<<>", 14, "%s", strOver); + iResult1 += _NetPrintOverflow(15, "<<>>", 14, "%s", strOver); + iResult1 += _NetPrintOverflow(20, " <<>", 20, "%20s", strOver); + iResult1 += _NetPrintOverflow(20, "<<>> ", 20, "%-20s", strOver); + iResult1 += _NetPrintOverflow(21, " <<>>", 20, "%20s", strOver); + iResult1 += _NetPrintOverflow(16, "-<<>>", 16, "-%s-", strOver); + iResult1 += _NetPrintOverflow(17, "-<<>>-", 16, "-%s-", strOver); + + ZPrintf("netprint: %d overflow test discrepencies\n", iResult1); + ZPrintf("netprint: ------------------------------------\n"); + return(iResult0+iResult1); +} + +/*F********************************************************************************/ +/*! + \Function _NetPrintTestSpam + + \Description + Execute a series of printing tests exercising the spam suppression + + \Output + int32_t - number of test failures + + \Version 04/05/2016 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetPrintTestSpam(void) +{ + int32_t iTest; + + // test suppression of multiple identical lines, with output forced by a non-identical line + NetPrintf(("netprint: rate limit test #1 start\n")); + for (iTest = 0; iTest < 32; iTest += 1) + { + NetPrintf(("netprint: testing rate limiter\n")); + } + NetPrintf(("netprint: rate limit test #1 finish\n")); + + // test suppression of multiple identical lines, with output forced by timeout + NetPrintf(("netprint: rate limit test #2 start\n")); + for (iTest = 0; iTest < 32; iTest += 1) + { + NetPrintf(("netprint: testing rate limiter\n")); + NetConnSleep(30); + } + NetPrintf(("netprint: rate limit test #2 finish\n")); + + return(0); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdNetPrint + + \Description + Test the ds_vsnprintf function + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - standard return value + + \Version 10/28/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdNetPrint(ZContext *argz, int32_t argc, char *argv[]) +{ + int32_t iResult = 0; + + ZPrintf("netprint: ------------------------------------\n"); + ZPrintf("netprint: Testing ds_snzprintf() vs sprintf()\n"); + ZPrintf("netprint: ------------------------------------\n"); + + iResult += _NetPrintTestStrs(); + iResult += _NetPrintTestFlts(); + iResult += _NetPrintTestInts(); + iResult += _NetPrintTestPtrs(); + + #ifndef DIRTYCODE_NX + iResult += _NetPrintTestAddr(); + iResult += _NetPrintTestCust(); + #endif + + iResult += _NetPrintTestAlgn(); + iResult += _NetPrintTestMisc(); + iResult += _NetPrintTestOver(); + iResult += _NetPrintTestSpam(); + + ZPrintf("netprint: Test results: %d total discrepencies\n", iResult); + ZPrintf("netprint: ------------------------------------\n"); + + return(0); +} + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/privilege.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/privilege.c new file mode 100644 index 00000000..22d40b1b --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/privilege.c @@ -0,0 +1,385 @@ +/*H*************************************************************************************/ +/*! + \File privilege.c + + \Description + Reference application for the privilege api. + + \Copyright + Copyright (c) Electronic Arts 2014. ALL RIGHTS RESERVED. + + \Version 18/02/2014 (amakoukji) +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" + +#if defined(DIRTYCODE_PS4) +#include +#include +#endif + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/misc/privilegeapi.h" + +#include "libsample/zlib.h" +#include "libsample/zfile.h" +#include "libsample/zmem.h" +#include "testersubcmd.h" +#include "testermodules.h" + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct PrivaAppT +{ + PrivilegeApiRefT *pPrivApi; + int32_t iCurrentQueryId; + + uint8_t bStarted; + + unsigned char bZCallback; +} PrivaAppT; + +/*** Function Prototypes ***************************************************************/ + +static void _PrivApiCreate(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _PrivApiDestroy(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _PrivApiCheckPriv(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _PrivApiAbort(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static int32_t _CmdPrivTickCb(ZContext *argz, int32_t argc, char *argv[]); + +/*** Variables *************************************************************************/ + +// Private variables +static T2SubCmdT _Ula_Commands[] = +{ + { "create", _PrivApiCreate }, + { "destroy", _PrivApiDestroy }, + { "check", _PrivApiCheckPriv }, + { "abort", _PrivApiAbort }, + { "", NULL } +}; + +static PrivaAppT _Priv_App; + +// Public variables + +/*** Private Functions *****************************************************************/ +/*F********************************************************************************/ +/*! + \Function _CmdPrivTickCb + + \Description + Priv callback, called after command has been issued. + + \Input *argz - pointer to context + \Input argc - number of command-line arguments + \Input *argv[] - command-line argument list + + \Output + int32_t - result of zcallback, or zero to terminate + + \Version 18/02/2014 (amakoukji) +*/ +/********************************************************************************F*/ +static int32_t _CmdPrivTickCb(ZContext *argz, int32_t argc, char *argv[]) +{ + PrivaAppT *pApp = (PrivaAppT*)&_Priv_App; + int32_t iResult = 0; + + // check for kill + if (argc == 0) + { + ZPrintf("%s: killed\n", argv[0]); + PrivilegeApiDestroy(pApp->pPrivApi); + return(0); + } + + // update module + if (pApp->pPrivApi != NULL && pApp->iCurrentQueryId != -1) + { + iResult = PrivilegeApiCheckResult(pApp->pPrivApi, pApp->iCurrentQueryId); + + if (iResult < 0) + { + ZPrintf("%s: error in privcheck for %d with code %d. Possibly Aborted? \n", argv[0], pApp->iCurrentQueryId, iResult); + pApp->iCurrentQueryId = -1; + } + else if (iResult & PRIVILEGEAPI_STATUS_IN_PROGRESS) + { + + } + else + { + ZPrintf("%s: Result of priv check %d = %d \n", argv[0], pApp->iCurrentQueryId, iResult); + PrivilegeApiReleaseRequest(pApp->pPrivApi, pApp->iCurrentQueryId); + pApp->iCurrentQueryId = -1; + } + } + + // keep recurring + return(ZCallback(&_CmdPrivTickCb, 17)); +} + +/*F*************************************************************************************/ +/*! + \Function _PrivApiCreate + + \Description + UserList create + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _PrivApiCreate(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + PrivaAppT *pApp = (PrivaAppT*)&_Priv_App; +#if defined(DIRTYCODE_PS4) + int32_t iResult = 0; +#endif + + if ((bHelp == TRUE) || (argc != 1 && argc != 2)) + { + ZPrintf(" usage: %s create \n", argv[0]); + return; + } + + if (pApp->bStarted) + { + ZPrintf("%s: already created\n", argv[0]); + return; + } + + pApp->iCurrentQueryId = -1; +#if defined(DIRTYCODE_PS4) + if ((iResult = sceSysmoduleLoadModule(SCE_SYSMODULE_NP_COMMERCE)) != SCE_OK) + { + ZPrintf("%s: error loading commerce module with code %d\n", argv[0], iResult); + return; + } + if ((iResult = sceSysmoduleLoadModule(SCE_SYSMODULE_MESSAGE_DIALOG)) != SCE_OK) + { + ZPrintf("%s: error loading message module with code %d\n", argv[0], iResult); + return; + } +#endif + + // allocate ProtoUdp module + if ((pApp->pPrivApi = PrivilegeApiCreate()) == NULL) + { + ZPrintf("%s: unable to create userlist module\n", argv[0]); + return; + } + + pApp->bStarted = TRUE; + + // one-time install of periodic callback + if (pApp->bZCallback == FALSE) + { + pApp->bZCallback = TRUE; + ZCallback(_CmdPrivTickCb, 17); + } +} + +/*F*************************************************************************************/ +/*! + \Function _PrivApiDestroy + + \Description + UserList destroy + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _PrivApiDestroy(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + PrivaAppT *pApp = (PrivaAppT*)&_Priv_App; + + if ((bHelp == TRUE) || (argc != 2)) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: not yet created\n", argv[0]); + return; + } + + PrivilegeApiDestroy(pApp->pPrivApi); + pApp->bStarted = FALSE; +} + +/*F*************************************************************************************/ +/*! + \Function _PrivApiCheckPriv + + \Description + Fetch friends list + + \Input *_pApp - pointer to app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _PrivApiCheckPriv(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + PrivaAppT *pApp = (PrivaAppT*)&_Priv_App; + int32_t iResult; + int32_t iUserIndex = 0; + PrivilegeApiPrivE ePrivId = PRIVILEGEAPI_PRIV_INVALID; + int32_t iHint = 0; + int32_t bUseUI = FALSE; + + if ((bHelp == TRUE) || ((argc != 4) && (argc != 5))) + { + ZPrintf(" usage: %s checkpriv [user index] [privilege id] [use UI?]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: not yet created\n", argv[0]); + return; + } + + if (pApp->iCurrentQueryId > -1) + { + ZPrintf("%s: check busy! Try again later\n", argv[0]); + return; + } + + // get arguments + iUserIndex = (int32_t)strtol(argv[2], NULL, 10); + ePrivId = (PrivilegeApiPrivE)strtol(argv[3], NULL, 10); + if (argc == 5) + { + int iUserIn = (int32_t)strtol(argv[4], NULL, 10); + bUseUI = iUserIn > 0 ? TRUE : FALSE; + } + + iResult = PrivilegeApiCheckPrivilegesAsyncWithUi(pApp->pPrivApi, iUserIndex, &ePrivId, 1, &iHint, bUseUI ? "Test" : NULL); + + if (iResult > 0) + { + pApp->iCurrentQueryId = iResult; + ZPrintf("%s check query %d started\n", argv[0], iResult); + } + else if (iResult == 0) + { + ZPrintf("%s: Privilege Check result %d\n", argv[0], iHint); + } + else + { + ZPrintf("%s: PrivilegeApiCheckPrivilegesAsyncWithUi() failed with code %d\n", argv[0], iResult); + } +} + +/*F*************************************************************************************/ +/*! + \Function _PrivApiAbort + + \Description + Fetch blocked list + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _PrivApiAbort(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + PrivaAppT *pApp = (PrivaAppT*)&_Priv_App; + int32_t iResult; + int32_t iId = 0; + + if ((bHelp == TRUE) || (argc != 3)) + { + ZPrintf(" usage: %s abort [query id]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: priv not yet created\n", argv[0]); + return; + } + // get the receive buffer size (use 0=ANY if not specified) + iId = (int32_t)strtol(argv[2], NULL, 10); + iResult = PrivilegeApiAbort(pApp->pPrivApi, iId); + if (iResult < 0) + { + ZPrintf("%s failed with error code\n", argv[0], iResult); + } + else + { + ZPrintf("%s: query %d aborted\n", argv[0], iId); + } +} + +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function CmdPriv + + \Description + Priv command. + + \Input *argz - unused + \Input argc - argument count + \Input **argv - argument list + + \Output + int32_t - zero + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +int32_t CmdPriv(ZContext *argz, int32_t argc, char *argv[]) +{ + T2SubCmdT *pCmd; + PrivaAppT *pApp = &_Priv_App; + unsigned char bHelp; + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_Ula_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" test the privilege api module\n"); + T2SubCmdUsage(argv[0], _Ula_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmd->pFunc != _PrivApiCreate) && (pApp->pPrivApi == NULL)) + { + char *pCreate = "create"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _PrivApiCreate(pApp, 1, &pCreate, bHelp); + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/qos.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/qos.c new file mode 100644 index 00000000..39c8efdd --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/qos.c @@ -0,0 +1,408 @@ +/*H*************************************************************************************/ +/*! + \File qos.c + + \Description + Reference application for QosClient. + + \Copyright + Copyright (c) Electronic Arts 2008. ALL RIGHTS RESERVED. + + \Version 1.0 05/25/2008 (cadam) First Version +*/ +/*************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/proto/protoname.h" +#include "DirtySDK/misc/qosclient.h" + +#include "libsample/zlib.h" + +#include "testerregistry.h" +#include "testersubcmd.h" +#include "testermodules.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct QosAppT +{ + QosClientRefT *pQosClient; + + unsigned char bZCallback; +} QosAppT; + +/*** Function Prototypes ***************************************************************/ + +static void _QosCreate(void *pCmdRef, int32_t argc, char **argv, unsigned char bHelp); +static void _QosDestroy(void *pCmdRef, int32_t argc, char **argv, unsigned char bHelp); +static void _QosStart(void *pCmdRef, int32_t argc, char **argv, unsigned char bHelp); +static void _QosControl(void *pCmdRef, int32_t argc, char **argv, unsigned char bHelp); +static void _QosStatus(void *pCmdRef, int32_t argc, char **argv, unsigned char bHelp); + +/*** Variables *************************************************************************/ + +static QosAppT _Qos_App = { NULL, FALSE }; + +static T2SubCmdT _Qos_Commands[] = +{ + { "create", _QosCreate }, + { "destroy", _QosDestroy }, + { "start", _QosStart }, + { "control", _QosControl }, + { "status", _QosStatus }, + { "", NULL } +}; + +/*** Private Functions *****************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _CmdQosUsage + + \Description + Display usage information. + + \Input argc - argument count + \Input *argv[] - argument list + + \Output + None. + + \Version 04/25/2008 (cadam) +*/ +/********************************************************************************F*/ +/*static void _CmdQosUsage(int argc, char *argv[]) +{ + if (argc == 2) + { + ZPrintf(" listen for, request, and/or control and get the status of current requests\n"); + ZPrintf(" usage: %s [create|destroy|listen|request|service|cancel|control|status|nattype]", argv[0]); + } + else if (argc == 3) + { + if (!strcmp(argv[2], "create")) + { + ZPrintf(" usage: %s create [serviceport]\n", argv[0]); + } + else if (!strcmp(argv[2], "destroy")) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + } + else if (!strcmp(argv[2], "listen")) + { + ZPrintf(" usage: %s listen [response]\n", argv[0]); + } + else if (!strcmp(argv[2], "request")) + { + ZPrintf(" usage: %s request [address] [probes]\n", argv[0]); + } + else if (!strcmp(argv[2], "service")) + { + ZPrintf(" usage: %s service [address] [serviceid] [probes]\n", argv[0]); + } + else if (!strcmp(argv[2], "cancel")) + { + ZPrintf(" usage: %s cancel [requestid]\n", argv[0]); + } + else if (!strcmp(argv[2], "control")) + { + ZPrintf(" usage: %s control [control] [value] [pvalue]\n", argv[0]); + } + else if (!strcmp(argv[2], "status")) + { + ZPrintf(" usage: %s status [select] [data]\n", argv[0]); + } + else if (!strcmp(argv[2], "nattype")) + { + ZPrintf(" usage: %s nattype\n", argv[0]); + } + } +} +*/ + +/*F*************************************************************************************/ +/*! + \Function _QosCreate + + \Description + Qos subcommand - create qosclient reference + + \Input *pCmdRef + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 04/25/2008 (cadam) First Version +*/ +/**************************************************************************************F*/ +static void _QosCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + QosAppT *pApp = &_Qos_App; + + if ((bHelp == TRUE) || (argc < 3)) + { + ZPrintf(" usage: %s create [serviceport]\n", argv[0]); + return; + } + + if (pApp->pQosClient != NULL) + { + ZPrintf(" %s: ref has already been created\n", argv[0]); + return; + } + + pApp->pQosClient = QosClientCreate(NULL, "", (int32_t)strtol(argv[2], NULL, 10)); +} + + +/*F*************************************************************************************/ +/*! + \Function _QosDestroy + + \Description + Qos subcommand - destroy qosclient reference + + \Input *pCmdRef + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 04/25/2008 (cadam) First Version +*/ +/**************************************************************************************F*/ +static void _QosDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + QosAppT *pApp = &_Qos_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + return; + } + + QosClientDestroy(pApp->pQosClient); + pApp->pQosClient = NULL; +} + +/*F*************************************************************************************/ +/*! + \Function _QosStart + + \Description + Qos subcommand - issue a QoS service request + + \Input *pCmdRef + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 04/25/2008 (cadam) First Version +*/ +/**************************************************************************************F*/ +static void _QosStart(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + QosAppT *pApp = &_Qos_App; + + if ((bHelp == TRUE) || (argc < 5)) + { + ZPrintf(" usage: %s service [address] [probeport] [profile name]\n", argv[0]); + return; + } + + QosClientStart(pApp->pQosClient, argv[2], (int32_t)strtol(argv[3], NULL, 10), argv[4]); +} + +/*F*************************************************************************************/ +/*! + \Function _QosControl + + \Description + Qos subcommand - QoS control function + + \Input *pCmdRef + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 04/25/2008 (cadam) First Version +*/ +/**************************************************************************************F*/ +static void _QosControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + int32_t iCtrl; + QosAppT *pApp = &_Qos_App; + + if ((bHelp == TRUE) || (argc < 5)) + { + ZPrintf(" usage: %s control [control] [value] [pvalue]\n", argv[0]); + return; + } + + iCtrl = argv[2][0] << 24; + iCtrl |= argv[2][1] << 16; + iCtrl |= argv[2][2] << 8; + iCtrl |= argv[2][3]; + + QosClientControl(pApp->pQosClient, iCtrl, (int32_t)strtol(argv[3], NULL, 10), argv[4]); +} + + +/*F*************************************************************************************/ +/*! + \Function _QosStatus + + \Description + Qos subcommand - QoS status function + + \Input *pCmdRef + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 04/25/2008 (cadam) First Version +*/ +/**************************************************************************************F*/ +static void _QosStatus(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + int32_t iSelect; + QosAppT *pApp = &_Qos_App; + char strBuffer[256]; + + if ((bHelp == TRUE) || (argc < 4)) + { + ZPrintf(" usage: %s status [select] [data]\n", argv[0]); + return; + } + + iSelect = argv[2][0] << 24; + iSelect |= argv[2][1] << 16; + iSelect |= argv[2][2] << 8; + iSelect |= argv[2][3]; + + QosClientStatus(pApp->pQosClient, iSelect, (int32_t)strtol(argv[3], NULL, 10), &strBuffer, sizeof(strBuffer)); +} + +/*F*************************************************************************************/ +/*! + \Function _CmdQosCb + + \Description + Qos idle callback. + + \Input *argz - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Output + int32_t - zero + + \Version 1.0 04/25/2008 (cadam) First Version +*/ +/**************************************************************************************F*/ +static int32_t _CmdQosCb(ZContext *argz, int32_t argc, char *argv[]) +{ + QosAppT *pApp = &_Qos_App; + + // check for kill + if (argc == 0) + { + ZPrintf("%s: killed\n", argv[0]); + QosClientDestroy(pApp->pQosClient); + return(0); + } + + // update at fastest rate + return(ZCallback(_CmdQosCb, 1000)); +} + + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function CmdQos + + \Description + QoS command. + + \Input *argz - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Output + int32_t - zero + + \Version 1.0 04/25/2008 (cadam) First Version +*/ +/**************************************************************************************F*/ +int32_t CmdQos(ZContext *argz, int32_t argc, char *argv[]) +{ + T2SubCmdT *pCmd; + QosAppT *pApp = &_Qos_App; + unsigned char bHelp; + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_Qos_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" %s: issue a qos request, get the nat information, or get the status of or control a request\n", argv[0]); + T2SubCmdUsage(argv[0], _Qos_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmd->pFunc != _QosCreate) && (pApp->pQosClient == NULL)) + { + char *pCreate = "create 11204"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _QosCreate(pApp, 1, &pCreate, bHelp); + if (pApp->pQosClient == NULL) + { + ZPrintf(" %s: error creating module\n", argv[0]); + return(0); + } + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + // one-time install of periodic callback + if (pApp->bZCallback == FALSE) + { + pApp->bZCallback = TRUE; + return(ZCallback(_CmdQosCb, 1000)); + } + else + { + return(0); + } +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/registry.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/registry.c new file mode 100644 index 00000000..5081c7e5 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/registry.c @@ -0,0 +1,66 @@ +/*H********************************************************************************/ +/*! + \File registry.c + + \Description + Handles registry for tester2. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/11/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/platform.h" + +#include "libsample/zlib.h" + +#include "testerregistry.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdRegistry + + \Description + Do some registry operations + + \Input *argz - environment + \Input argc - number of args + \Input *argv[] - argument list + + \Output int32_t - standard return code + + \Version 04/11/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t CmdRegistry(ZContext *argz, int32_t argc, char *argv[]) +{ + if(argc < 1) + { + ZPrintf(" print registry\n"); + ZPrintf(" usage: %s\n", argv[0]); + } + else + { + ZPrintf("registry: printing registry:\n"); + TesterRegistryPrint(); + ZPrintf("registry: done\n"); + } + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/runscript.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/runscript.c new file mode 100644 index 00000000..7ba243d8 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/runscript.c @@ -0,0 +1,189 @@ +/*H********************************************************************************/ +/*! + \File runscript.c + + \Description + Run a script in Tester2. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/11/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" + +#include "libsample/zmem.h" +#include "libsample/zfile.h" + +#include "testerregistry.h" +#include "testermodules.h" +#include "testercomm.h" +#include "testerclientcore.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _dispatchCommand + + \Description + Dispatch a single command line, locally and remotely. + + \Input *pCmd - pointer to this command eg. run. + \Input *bClientOnly - if true, only run on client side. + \Input *pLine - the command line to be run. + + \Output int32_t - return code; 0 if command ran, -1 if empty or some error + + \Version 30/11/2005 (TE) +*/ +/********************************************************************************F*/ +int32_t _dispatchCommand(char* pCmd, int bClientOnly, char * pLine) +{ + TesterModulesT *pModules; + TesterCommT *pComm = NULL; + int32_t iResult = -1; + + if ((strlen(pLine) == 0) || (pLine[0] == '#')) + { + return(iResult); + } + // execute it locally + if ((pModules = (TesterModulesT *)TesterRegistryGetPointer("MODULES")) == NULL) + { + ZPrintf("%s: could not dispatch script command locally {%s}\n", pCmd, pLine); + } + else + { + ZPrintf("%s: {%s}\n", pCmd, pLine); + // check to see if its a local command + TesterModulesDispatch(pModules, pLine); + iResult = 0; + } + + // try host too... + if (!bClientOnly) + { + // send it to the other side for execution + if ((pComm = TesterRegistryGetPointer("COMM")) == NULL) + { + ZPrintf("%s: could not dispatch command remotely {%s}\n", pCmd, pLine); + } + else + { + TesterCommMessage(pComm, TESTER_MSGTYPE_COMMAND, pLine); + iResult = 0; + } + } + return(iResult); +} + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdRunScript + + \Description + Run a script + + \Input *argz - environment + \Input argc - number of args + \Input *argv[] - argument list + + \Output int32_t - standard return code + + \Version 04/11/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t CmdRunScript(ZContext *argz, int32_t argc, char *argv[]) +{ + int32_t iFileSize; + char *pFile = NULL, *pLine = NULL, *pEnd = NULL; + TesterClientCoreT *pState = NULL; + char strSleep[100]; //to format a sleep command + static int iSleepTimeStatic =2; //default sleep is 2 secs between commands + + // check usage + if (argc < 2) + { + ZPrintf( "usage: %s - run a script\n", argv[0]); + ZPrintf("usage: %s -s -set sleep secs between script commands\n", argv[0]); + return(-1); + } + if (strcmp (argv[1],"-s") == 0) + { //set the sleep time.. + if (argc < 3) + { + ZPrintf( "usage: %s -s \n", argv[0]); + return(-1); + } + iSleepTimeStatic = atoi(argv[2]); + ZPrintf("%s: sleep between commands set to: %d secs \n", argv[0], iSleepTimeStatic); + return(0); + } + + // otherwise run the script + ZPrintf("%s: running script {%s}\n", argv[0], argv[1]); + + // load the data in + pFile = ZFileLoad(argv[1], &iFileSize, 0); + if(pFile == NULL) + { + ZPrintf("%s: failed to load file {%s}\n", argv[0], argv[1]); + return(0); + } + pState = (TesterClientCoreT *)TesterRegistryGetPointer("CORE"); + + // fire off each line + pLine = pFile; + //NO strtok() --it is static and i/o uses it too!! + if ( (pEnd = strstr(pFile, "\n")) != NULL) + { + *pEnd++ = '\0'; + } + + while(pLine) + { + if ((_dispatchCommand(argv[0], 0, pLine) == 0) && (iSleepTimeStatic > 0)) + { //did something.. so sleep + //format sleep command and sleep to catch up + sprintf (strSleep, "sleep %d", iSleepTimeStatic); + _dispatchCommand( argv[0], 1, strSleep); //clientOnly=1 + } + if (pState) + { // pump i/o between commands.. + TesterClientCoreIdle(pState); + } + + // try to process the next line + pLine = pEnd; + if ( (pLine) &&((pEnd = strstr(pLine, "\n")) != NULL)) + { + *pEnd++ = '\0'; + } + } + + ZPrintf("%s: ran scripts in {%s}\n", argv[0], argv[1]); + + // kill the memory allocated by loadfile + ZMemFree(pFile); + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/secure.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/secure.c new file mode 100644 index 00000000..01c14135 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/secure.c @@ -0,0 +1,152 @@ +/*H********************************************************** + * secure.c + * + * Description: + * + * Test SSL code. + * + * Copyright (c) Tiburon Entertainment, Inc. 2002. + * All rights reserved. + * + * Ver. Description + * 1.0 03/08/2002 (GWS) [Greg Schaefer] Cleanup and revision + * + *H*/ + +// Includes + +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/proto/protossl.h" + +#include "libsample/zlib.h" + +#include "testermodules.h" + + +// Constants + +typedef struct SecureRef // module state storage +{ + uint32_t timeout; // operation timeout + + ProtoSSLRefT *ssl; + char url[256]; + +} SecureRef; + + +/*F****************************************************** + * CmdSecureCB + * + * Description: + * Callback to wait for authent completion. + * + * Inputs: + * Context, arg count, arg strings (zlib standard) + * + * Outputs: + * Exit code + * + * Ver. Description + * 1.0 10/4/99 (GWS) Initial version + * + *F*/ +static int32_t _CmdSecureCB(ZContext *argz, int32_t argc, char *argv[]) +{ + SecureRef *ref = (SecureRef *)argz; + + // check for kill + if (argc == 0) + { + ProtoSSLDestroy(ref->ssl); + return(0); + } + + // check for timeout + if (ZTick() > ref->timeout) + { + ZPrintf("%s: timeout\n", argv[0]); + ProtoSSLDestroy(ref->ssl); + return(-1); + } + + // give transaction time + ProtoSSLUpdate(ref->ssl); + + // see if connection complete + if (ProtoSSLStat(ref->ssl, 'stat', NULL, 0) > 0) + { + int32_t len; + char buf[1024]; + + if (ref->url[0] != 0) + { + ProtoSSLSend(ref->ssl, ref->url, -1); + ref->url[0] = 0; + } + + len = ProtoSSLRecv(ref->ssl, buf, sizeof(buf)); + if (len > 0) + { + ZPrintf("[%s]\n", buf); + } + } + + // keep running + return(ZCallback(&_CmdSecureCB, 100)); +} + +/*F****************************************************** + * CmdSecure + * + * Description: + * Test the SSL handler + * + * Inputs: + * Context, arg count, arg strings (zlib standard) + * + * Outputs: + * Exit code + * + * Ver. Description + * 1.0 10/4/99 (GWS) Initial version + * + *F*/ +int32_t CmdSecure(ZContext *argz, int32_t argc, char *argv[]) +{ + SecureRef *ref; + + // handle help + if (argc < 3) + { + ZPrintf(" test protossl module\n"); + ZPrintf(" usage: %s [address] [port] - setup SSL connection\n", argv[0]); + return(0); + } + + // setup connection + ZPrintf("%s: ssl connect\n", argv[0]); + + ref = (SecureRef *) ZContextCreate(sizeof(*ref)); + ref->timeout = ZTick()+120*1000; + + // setup ssl state + ref->ssl = ProtoSSLCreate(); + + // attempt to connect + ProtoSSLConnect(ref->ssl, TRUE, argv[1], 0, strtol(argv[2], NULL, 10)); + + // setup the url + strcpy(ref->url, "GET / HTTP/1.0\r\n" + "Accept: */*\r\n" + "Content-Length: 0\r\n" + "User-Agent: Custom/1.0\r\n" + "\r\n"); + + // wait for reply + return(ZCallback(&_CmdSecureCB, 100)); +} \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/session.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/session.c new file mode 100644 index 00000000..0d3c2479 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/session.c @@ -0,0 +1,732 @@ +/*H********************************************************************************/ +/*! + \File session.c + + \Description + Test sessions + + \Notes + Test framework largely borrowed from ws.c by James Brookes. + + \Copyright + Copyright (c) Electronic Arts 2013. + + \Version 03/26/2013 (cvienneau) First Version +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtysessionmanager.h" +#include "DirtySDK/dirtysock/dirtyuser.h" +#include "DirtySDK/dirtysock/netconn.h" + +#include "libsample/zlib.h" +#include "libsample/zfile.h" +#include "libsample/zmem.h" +#include "testersubcmd.h" +#include "testermodules.h" + +#if defined(DIRTYCODE_PS4) && !defined(DIRTYCODE_PS5) +#include +#include +#endif + +/*** Defines **********************************************************************/ +#if defined(DIRTYCODE_PS4) && !defined(DIRTYCODE_PS5) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct SessionAppT +{ + DirtySessionManagerRefT *pDirtySessionManager; +} SessionAppT; + +/*** Function Prototypes **********************************************************/ +static void _SessionCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionStatus(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionMaxUsers(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionImage(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionSetup(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionInvite(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionInviteNoDialog(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionAccept(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionAcceptDialog(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SessionAbortRequest(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); + +/*** Variables ********************************************************************/ + +static T2SubCmdT _Session_Commands[] = +{ + { "create", _SessionCreate }, + { "destroy", _SessionDestroy }, + { "ctrl", _SessionControl }, + { "stat", _SessionStatus }, + { "maxusers", _SessionMaxUsers }, + { "image", _SessionImage }, + { "setup", _SessionSetup }, + { "invite", _SessionInvite }, + { "inviteNoDialog", _SessionInviteNoDialog}, + { "accept", _SessionAccept }, + { "acceptdialog",_SessionAcceptDialog}, + { "abort", _SessionAbortRequest }, + { "", NULL }, +}; + +static SessionAppT _Session_App = { NULL }; + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _SessionDestroyApp + + \Description + Destroy app, freeing modules. + + \Input *pApp - app state + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SessionDestroyApp(SessionAppT *pApp) +{ + if (pApp->pDirtySessionManager != NULL) + { + DirtySessionManagerDestroy(pApp->pDirtySessionManager); + } + ds_memclr(pApp, sizeof(*pApp)); +} + +/* + + Session Commands + +*/ + +/*F*************************************************************************************/ +/*! + \Function _SessionCreate + + \Description + Session subcommand - create websocket module + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/27/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _SessionCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s create\n", argv[0]); + return; + } + + // create a websocket module if it isn't already started + if ((pApp->pDirtySessionManager = DirtySessionManagerCreate()) == NULL) + { + ZPrintf("%s: error creating DirtySessionManager ref.\n", argv[0]); + return; + } +} + +/*F*************************************************************************************/ +/*! + \Function _SessionDestroy + + \Description + Session subcommand - destroy websocket module + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/27/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _SessionDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + return; + } + + _SessionDestroyApp(pApp); +} + +/*F*************************************************************************************/ +/*! + \Function _SessionControl + + \Description + Session control subcommand - set control options + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/27/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _SessionControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + int32_t iCmd, iValue = 0, iValue2 = 0; + void *pValue = NULL; + + if ((bHelp == TRUE) || (argc < 3) || (argc > 6)) + { + ZPrintf(" usage: %s ctrl [cmd] \n", argv[0]); + return; + } + + // get the command + iCmd = ZGetIntArg(argv[2]); + + // get optional arguments + if (argc > 3) + { + iValue = ZGetIntArg(argv[3]); + } + if (argc > 4) + { + iValue2 = ZGetIntArg(argv[4]); + } + if (argc > 5) + { + pValue = argv[5]; + } + + // issue the control call + DirtySessionManagerControl(pApp->pDirtySessionManager, iCmd, iValue, iValue2, pValue); +} + +/*F*************************************************************************************/ +/*! + \Function _SessionStatus + + \Description + Session status subcommand - query module status + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/27/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _SessionStatus(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + int32_t iCmd, iResult, iValue = 0, iValue2 = 0, iValue3 = 0; + char strBuffer[512] = ""; + + if ((bHelp == TRUE) || (argc < 3) || (argc > 7)) + { + ZPrintf(" usage: %s stat \n", argv[0]); + return; + } + + // get the command + iCmd = ZGetIntArg(argv[2]); + + // get optional arguments + if (argc > 3) + { + iValue = ZGetIntArg(argv[3]); + } + if (argc > 4) + { + iValue2 = ZGetIntArg(argv[4]); + } + if (argc > 5) + { + iValue3 = ZGetIntArg(argv[5]); + } + + // issue the status call + iResult = DirtySessionManagerStatus2(pApp->pDirtySessionManager, iCmd, iValue, iValue2, iValue3, strBuffer, sizeof(strBuffer)); + + // report result + ZPrintf("Session: DirtySessionManagerStatus('%C') returned %d (\"%s\")\n", iCmd, iResult, strBuffer); +} + +/*F*************************************************************************************/ +/*! + \Function _SessionMaxUsers + + \Description + Wrapper for 'smau' + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 12/09/2013 (mclouatre) +*/ +/**************************************************************************************F*/ +static void _SessionMaxUsers(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + int32_t iResult; + int32_t iMaxUsers; + + if ((bHelp == TRUE) || (argc != 3)) + { + ZPrintf(" usage: %s \n", argv[0]); + return; + } + + sscanf(argv[2], "%d", &iMaxUsers); + + if ((iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'smau', 0, iMaxUsers, NULL)) < 0) + { + ZPrintf("Session: max users update ('smau') failed with err %d\n", iResult); + return; + } + if ((iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'umau', 0, 0, NULL)) < 0) + { + ZPrintf("Session: max users update ('umau') failed with err %d\n", iResult); + return; + } + + // report result + ZPrintf("Session: successfully initiated session max users update to %d\n", iMaxUsers); +} + +/*F*************************************************************************************/ +/*! + \Function _SessionImage + + \Description + Wrapper for 'simg' + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 6/6/2013 (cvienneau) +*/ +/**************************************************************************************F*/ +static void _SessionImage(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + int32_t iResult; + + // T2 image Blue + uint8_t img_buf[] = {255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 1, 1, 0, 96, 0, 96, 0, 0, 255, 225, 0, 104, 69, 120, 105, 102, 0, 0, 77, 77, 0, 42, 0, 0, 0, 8, 0, 4, 1, 26, 0, 5, 0, 0, 0, 1, 0, 0, 0, 62, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0, 70, 1, 40, 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 1, 49, 0, 2, 0, 0, 0, 18, 0, 0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 1, 0, 0, 0, 96, 0, 0, 0, 1, 80, 97, 105, 110, 116, 46, 78, 69, 84, 32, 118, 51, 46, 53, 46, 49, 48, 0, 255, 219, 0, 67, 0, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 5, 3, 3, 3, 3, 3, 6, 4, 4, 3, 5, 7, 6, 7, 7, 7, 6, 7, 7, 8, 9, 11, 9, 8, 8, 10, 8, 7, 7, 10, 13, 10, 10, 11, 12, 12, 12, 12, 7, 9, 14, 15, 13, 12, 14, 11, 12, 12, 12, 255, 219, 0, 67, 1, 2, 2, 2, 3, 3, 3, 6, 3, 3, 6, 12, 8, 7, 8, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 255, 192, 0, 17, 8, 0, 36, 0, 64, 3, 1, 34, 0, 2, 17, 1, 3, 17, 1, 255, 196, 0, 31, 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 255, 196, 0, 181, 16, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125, 1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19, 81, 97, 7, 34, 113, 20, 50, 129, 145, 161, 8, 35, 66, 177, 193, 21, 82, 209, 240, 36, 51, 98, 114, 130, 9, 10, 22, 23, 24, 25, 26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 255, 196, 0, 31, 1, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 255, 196, 0, 181, 17, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119, 0, 1, 2, 3, 17, 4, 5, 33, 49, 6, 18, 65, 81, 7, 97, 113, 19, 34, 50, 129, 8, 20, 66, 145, 161, 177, 193, 9, 35, 51, 82, 240, 21, 98, 114, 209, 10, 22, 36, 52, 225, 37, 241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 130, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242, 243, 244, 245, 246, 247, 248, 249, 250, 255, 218, 0, 12, 3, 1, 0, 2, 17, 3, 17, 0, 63, 0, 252, 195, 162, 138, 43, 253, 208, 62, 76, 40, 175, 217, 223, 216, 183, 224, 15, 252, 19, 115, 246, 162, 189, 240, 55, 129, 116, 189, 39, 251, 115, 226, 118, 185, 165, 199, 246, 139, 31, 181, 120, 162, 215, 207, 187, 138, 204, 207, 117, 251, 198, 116, 129, 112, 34, 153, 184, 96, 167, 110, 23, 57, 0, 247, 191, 181, 71, 236, 101, 255, 0, 4, 221, 253, 137, 252, 97, 167, 104, 31, 19, 188, 55, 255, 0, 8, 206, 173, 171, 89, 255, 0, 104, 90, 193, 253, 161, 226, 139, 223, 54, 13, 237, 30, 253, 214, 242, 200, 163, 230, 70, 24, 36, 30, 58, 98, 191, 2, 196, 253, 32, 178, 250, 25, 143, 246, 68, 242, 156, 127, 214, 26, 114, 80, 250, 188, 121, 165, 20, 218, 230, 140, 125, 167, 51, 142, 143, 222, 74, 218, 62, 199, 74, 195, 54, 185, 185, 149, 189, 79, 194, 90, 43, 245, 43, 254, 9, 173, 255, 0, 4, 184, 248, 1, 251, 68, 233, 127, 22, 62, 48, 248, 227, 88, 150, 231, 225, 135, 134, 117, 205, 84, 232, 26, 13, 165, 244, 246, 237, 253, 145, 109, 230, 74, 46, 174, 57, 251, 89, 79, 36, 97, 23, 42, 231, 201, 114, 196, 158, 43, 211, 190, 16, 255, 0, 193, 51, 63, 100, 95, 248, 42, 199, 236, 217, 226, 173, 119, 224, 23, 135, 124, 103, 240, 203, 196, 62, 30, 185, 147, 79, 130, 77, 78, 246, 226, 85, 107, 161, 16, 146, 47, 58, 57, 110, 46, 81, 160, 125, 195, 38, 55, 87, 24, 57, 3, 128, 222, 174, 107, 227, 166, 67, 151, 226, 170, 208, 196, 80, 175, 236, 232, 202, 17, 171, 85, 83, 94, 206, 148, 167, 180, 102, 220, 148, 174, 182, 124, 177, 149, 158, 154, 147, 28, 60, 154, 186, 177, 248, 207, 69, 62, 226, 6, 182, 157, 227, 112, 3, 198, 197, 88, 2, 14, 8, 224, 242, 41, 149, 251, 66, 102, 1, 69, 20, 80, 7, 216, 191, 240, 64, 111, 249, 75, 103, 194, 111, 251, 140, 127, 233, 154, 250, 189, 243, 254, 14, 162, 255, 0, 147, 192, 248, 119, 255, 0, 98, 112, 255, 0, 210, 219, 154, 248, 35, 246, 66, 253, 169, 124, 65, 251, 22, 126, 209, 62, 30, 248, 153, 225, 107, 61, 26, 255, 0, 94, 240, 215, 218, 126, 203, 6, 171, 20, 146, 218, 73, 231, 219, 75, 108, 251, 214, 57, 35, 115, 132, 153, 136, 195, 143, 152, 12, 228, 100, 30, 187, 246, 253, 255, 0, 130, 134, 120, 211, 254, 10, 53, 241, 35, 70, 241, 71, 141, 244, 191, 11, 233, 90, 134, 135, 166, 255, 0, 101, 65, 30, 135, 109, 60, 16, 188, 94, 107, 203, 185, 132, 211, 74, 75, 110, 144, 242, 8, 24, 3, 142, 245, 248, 222, 103, 193, 57, 157, 127, 18, 240, 156, 81, 77, 71, 234, 212, 240, 206, 148, 157, 253, 238, 119, 42, 175, 225, 237, 239, 173, 77, 213, 68, 169, 56, 117, 185, 232, 223, 240, 73, 191, 248, 36, 223, 136, 63, 224, 164, 223, 16, 110, 174, 174, 174, 238, 60, 57, 240, 223, 195, 147, 34, 107, 90, 194, 32, 105, 167, 144, 128, 194, 210, 212, 48, 218, 102, 43, 130, 88, 229, 98, 86, 12, 193, 137, 68, 127, 209, 31, 219, 119, 226, 255, 0, 140, 63, 98, 255, 0, 128, 55, 223, 179, 223, 236, 157, 251, 62, 252, 86, 91, 84, 142, 91, 61, 75, 197, 150, 222, 15, 212, 102, 182, 83, 34, 237, 154, 75, 105, 188, 162, 215, 87, 47, 208, 220, 147, 177, 66, 175, 151, 184, 5, 41, 240, 7, 236, 89, 255, 0, 5, 203, 248, 167, 251, 8, 124, 5, 177, 248, 121, 224, 191, 9, 124, 49, 185, 210, 108, 174, 103, 188, 123, 189, 79, 78, 190, 150, 242, 242, 105, 164, 44, 207, 43, 71, 119, 26, 18, 6, 212, 24, 65, 242, 198, 160, 228, 130, 79, 171, 255, 0, 196, 82, 159, 180, 15, 253, 9, 255, 0, 7, 63, 240, 85, 169, 127, 242, 125, 124, 7, 28, 112, 167, 30, 231, 92, 73, 245, 186, 216, 74, 88, 140, 13, 9, 94, 141, 25, 86, 229, 131, 107, 106, 149, 34, 149, 231, 39, 171, 81, 147, 180, 83, 181, 183, 190, 180, 231, 78, 48, 178, 118, 103, 230, 221, 205, 180, 150, 119, 18, 67, 52, 111, 20, 177, 49, 71, 71, 82, 172, 140, 14, 8, 32, 244, 32, 211, 42, 206, 181, 170, 201, 174, 235, 55, 119, 211, 42, 44, 183, 147, 60, 238, 16, 16, 161, 153, 139, 16, 51, 158, 50, 106, 181, 127, 80, 198, 252, 171, 155, 115, 140, 40, 162, 138, 160, 10, 40, 162, 128, 10, 40, 162, 128, 10, 40, 162, 128, 63, 255, 217}; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s image\n", argv[0]); + return; + } + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'simg', 0, sizeof(img_buf), img_buf); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'uimg', 0, 0, NULL); + + // report result + ZPrintf("Session: new image attached\n"); +} + + +/*F*************************************************************************************/ +/*! + \Function _SessionSetup + + \Description + Wrapper for creating a session with common defaults + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 6/6/2013 (cvienneau) +*/ +/**************************************************************************************F*/ +static void _SessionSetup(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + int32_t iResult; + + // T2 image Green + uint8_t img_buf[] = {255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 1, 1, 0, 96, 0, 96, 0, 0, 255, 225, 0, 104, 69, 120, 105, 102, 0, 0, 77, 77, 0, 42, 0, 0, 0, 8, 0, 4, 1, 26, 0, 5, 0, 0, 0, 1, 0, 0, 0, 62, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0, 70, 1, 40, 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 1, 49, 0, 2, 0, 0, 0, 18, 0, 0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 1, 0, 0, 0, 96, 0, 0, 0, 1, 80, 97, 105, 110, 116, 46, 78, 69, 84, 32, 118, 51, 46, 53, 46, 49, 48, 0, 255, 219, 0, 67, 0, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 5, 3, 3, 3, 3, 3, 6, 4, 4, 3, 5, 7, 6, 7, 7, 7, 6, 7, 7, 8, 9, 11, 9, 8, 8, 10, 8, 7, 7, 10, 13, 10, 10, 11, 12, 12, 12, 12, 7, 9, 14, 15, 13, 12, 14, 11, 12, 12, 12, 255, 219, 0, 67, 1, 2, 2, 2, 3, 3, 3, 6, 3, 3, 6, 12, 8, 7, 8, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 255, 192, 0, 17, 8, 0, 36, 0, 64, 3, 1, 34, 0, 2, 17, 1, 3, 17, 1, 255, 196, 0, 31, 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 255, 196, 0, 181, 16, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125, 1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19, 81, 97, 7, 34, 113, 20, 50, 129, 145, 161, 8, 35, 66, 177, 193, 21, 82, 209, 240, 36, 51, 98, 114, 130, 9, 10, 22, 23, 24, 25, 26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 255, 196, 0, 31, 1, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 255, 196, 0, 181, 17, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119, 0, 1, 2, 3, 17, 4, 5, 33, 49, 6, 18, 65, 81, 7, 97, 113, 19, 34, 50, 129, 8, 20, 66, 145, 161, 177, 193, 9, 35, 51, 82, 240, 21, 98, 114, 209, 10, 22, 36, 52, 225, 37, 241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 130, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242, 243, 244, 245, 246, 247, 248, 249, 250, 255, 218, 0, 12, 3, 1, 0, 2, 17, 3, 17, 0, 63, 0, 240, 122, 40, 171, 254, 21, 176, 181, 213, 124, 81, 166, 218, 223, 77, 246, 107, 43, 155, 168, 162, 184, 155, 120, 79, 42, 54, 112, 25, 183, 30, 6, 1, 39, 39, 129, 138, 252, 145, 43, 187, 31, 198, 145, 87, 105, 34, 133, 21, 250, 175, 240, 247, 254, 8, 243, 251, 44, 124, 91, 214, 165, 211, 124, 41, 241, 131, 93, 241, 62, 163, 4, 6, 230, 75, 93, 39, 197, 122, 69, 236, 209, 196, 25, 84, 200, 82, 59, 102, 96, 161, 157, 70, 226, 49, 150, 3, 184, 172, 255, 0, 138, 31, 240, 73, 127, 217, 87, 225, 61, 214, 161, 167, 107, 127, 25, 117, 109, 19, 94, 178, 183, 50, 255, 0, 103, 106, 94, 45, 209, 237, 174, 1, 41, 185, 55, 68, 246, 234, 248, 97, 130, 56, 228, 30, 43, 215, 121, 30, 37, 71, 154, 241, 183, 170, 62, 213, 240, 6, 102, 169, 251, 87, 42, 124, 189, 249, 213, 190, 243, 242, 226, 138, 251, 147, 246, 53, 255, 0, 130, 83, 248, 91, 226, 31, 236, 201, 47, 198, 47, 139, 222, 51, 212, 60, 37, 224, 214, 134, 91, 152, 98, 211, 213, 22, 117, 183, 141, 204, 126, 115, 200, 233, 39, 222, 117, 33, 99, 88, 217, 155, 43, 131, 150, 11, 90, 223, 28, 191, 224, 147, 159, 15, 188, 71, 251, 42, 106, 31, 22, 254, 5, 120, 247, 88, 241, 86, 137, 164, 90, 207, 125, 53, 182, 171, 18, 151, 185, 134, 2, 124, 253, 172, 34, 133, 163, 116, 85, 118, 216, 241, 229, 128, 224, 140, 140, 227, 28, 171, 16, 233, 251, 68, 150, 215, 181, 213, 237, 222, 199, 4, 56, 67, 50, 158, 29, 98, 20, 86, 177, 231, 81, 230, 92, 238, 63, 205, 203, 123, 216, 248, 22, 138, 40, 175, 52, 249, 128, 162, 138, 40, 3, 238, 207, 248, 55, 199, 254, 79, 55, 196, 223, 246, 37, 221, 127, 233, 117, 133, 112, 159, 240, 91, 31, 249, 72, 111, 139, 63, 235, 203, 78, 255, 0, 210, 56, 171, 132, 255, 0, 130, 124, 126, 218, 191, 240, 194, 63, 25, 245, 63, 23, 127, 194, 53, 255, 0, 9, 87, 246, 142, 139, 46, 143, 246, 79, 237, 31, 176, 249, 123, 231, 130, 111, 51, 127, 149, 38, 113, 228, 99, 110, 209, 247, 179, 158, 48, 112, 127, 109, 159, 218, 131, 254, 27, 19, 246, 135, 213, 188, 123, 253, 135, 255, 0, 8, 239, 246, 164, 22, 240, 253, 135, 237, 191, 108, 242, 188, 168, 86, 60, 249, 158, 92, 121, 206, 220, 253, 209, 140, 227, 158, 181, 235, 75, 21, 73, 229, 202, 130, 126, 247, 53, 237, 229, 175, 200, 251, 42, 185, 174, 22, 92, 51, 12, 189, 79, 247, 170, 167, 53, 172, 246, 179, 214, 246, 183, 94, 247, 61, 131, 254, 9, 231, 251, 30, 252, 85, 253, 185, 52, 219, 189, 21, 124, 99, 226, 111, 15, 124, 35, 178, 145, 96, 213, 157, 175, 230, 107, 75, 150, 82, 142, 45, 161, 183, 221, 229, 201, 32, 219, 27, 18, 70, 216, 240, 172, 114, 118, 43, 123, 95, 237, 193, 251, 122, 252, 56, 253, 155, 191, 102, 235, 159, 217, 243, 224, 88, 75, 235, 115, 107, 46, 151, 171, 107, 17, 200, 100, 134, 20, 114, 69, 194, 172, 191, 242, 222, 121, 114, 193, 221, 127, 118, 161, 200, 92, 240, 19, 152, 253, 151, 255, 0, 224, 183, 154, 127, 236, 191, 240, 19, 195, 62, 5, 211, 126, 16, 139, 184, 124, 63, 104, 33, 150, 235, 254, 18, 113, 9, 188, 157, 137, 121, 102, 41, 246, 70, 219, 190, 70, 102, 219, 185, 176, 8, 25, 56, 205, 116, 63, 20, 191, 224, 224, 79, 248, 89, 95, 12, 188, 71, 225, 207, 248, 84, 159, 98, 254, 223, 210, 238, 116, 223, 180, 127, 194, 81, 230, 121, 30, 116, 77, 30, 253, 191, 100, 27, 177, 187, 56, 200, 206, 58, 138, 239, 163, 91, 9, 79, 13, 203, 10, 214, 155, 86, 111, 150, 77, 250, 46, 200, 250, 28, 22, 51, 38, 195, 101, 158, 203, 15, 140, 229, 175, 56, 218, 114, 116, 231, 39, 103, 246, 34, 244, 81, 87, 210, 250, 223, 126, 214, 252, 226, 162, 138, 43, 230, 143, 203, 2, 138, 40, 160, 2, 138, 40, 160, 2, 138, 40, 160, 2, 138, 40, 160, 15, 255, 217}; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s setup\n", argv[0]); + return; + } + + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'simg', 0, sizeof(img_buf), img_buf); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'smau', 0, 22, NULL); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'slid', 0, 8, "12345678"); + + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'snam', 0, 0, "Default Name"); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'snam', 0, 'enUS', "enUS Name"); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'snam', 0, 'frFR', "frFR Name"); + + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'ssta', 0, 0, "Default Status"); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'ssta', 0, 'enUS', "enUS Status"); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'ssta', 0, 'frFR', "frFR Status"); + + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'scre', 0, 0, NULL); + + // report result + ZPrintf("Session: Setup Complete\n"); +} + +/*F*************************************************************************************/ +/*! + \Function _SessionInvite + + \Description + Wrapper for opening invite dialog + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 6/6/2013 (cvienneau) +*/ +/**************************************************************************************F*/ +static void _SessionInvite(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + int32_t iResult; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s invite\n", argv[0]); + return; + } + + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'imus', 16, 0, NULL); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'imsg', 0, 0, "Play T2 with me"); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'iued', 1, 0, NULL); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'osid', 0, 0, NULL); + + // report result + ZPrintf("Session: Invite Complete\n"); +} + + + +/*F*************************************************************************************/ +/*! + \Function _SessionInviteNoDialog + + \Description + Wrapper for _SessionInviteNoDialog + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/4/2013 (tcho) +*/ +/**************************************************************************************F*/ +static void _SessionInviteNoDialog(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + #if defined(DIRTYCODE_PS4) + + SessionAppT *pApp = &_Session_App; + int32_t iResult; + + if (bHelp == TRUE) + { + ZPrintf("usage: %s inviteNoDialog \n", argv[0]); + return; + } + + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'imus', 16, 0, NULL); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'imsg', 0, 0, "Play T2 with me"); + + for(int32_t index = 2; index < argc; ++index) + { + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'ianp', 0, 0, argv[index]); + } + + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'sind', 1, 0, NULL); + iResult = DirtySessionManagerControl(pApp->pDirtySessionManager, 'icnp', 0, 0, NULL); + + // report result + ZPrintf("Session: Invite Complete\n"); + + #else + + ZPrintf("Session: Invite without dialog not support\n"); + + #endif +} + +/*F*************************************************************************************/ +/*! + \Function _SessionAccept + + \Description + Examin current invites and join one of them + Must be done after DirtySessionManagerControl('ginv') has completed + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 6/6/2013 (cvienneau) +*/ +/**************************************************************************************F*/ +static void _SessionAccept(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + int32_t i, iInviteCount, iUserIndex = 0; + DirtyUserT user; + char strUserName[32]; + char strMessage[512]; + char strSessionId[64]; + strUserName[0] = 0; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s accept \n", argv[0]); + return; + } + + // get optional arguments + if (argc > 3) + { + iUserIndex = ZGetIntArg(argv[3]); + } + + iInviteCount = DirtySessionManagerStatus2(pApp->pDirtySessionManager, 'ginv', iUserIndex, 0, 0, NULL, 0); + + ZPrintf(" Current Invitations: %d\n", iInviteCount); + + //print out info from all the invites + for (i = 0; i < iInviteCount; i++) + { + DirtySessionManagerStatus2(pApp->pDirtySessionManager, 'gims', iUserIndex, i, 0, strMessage, sizeof(strMessage)); + DirtySessionManagerStatus2(pApp->pDirtySessionManager, 'gisi', iUserIndex, i, 0, strSessionId, sizeof(strSessionId)); + DirtySessionManagerStatus2(pApp->pDirtySessionManager, 'giun', iUserIndex, i, 0, &user, sizeof(user)); + #if defined(DIRTYCODE_PS4) + SceNpAccountId accountId; + DirtyUserToNativeUser(&accountId, sizeof(accountId), &user); + ds_snzprintf(strUserName, sizeof(strUserName), "%llu", accountId); + #endif + ZPrintf(" * %d %s %llu %s\n", iInviteCount, strSessionId, strUserName, strMessage); + + } + + if (iInviteCount > 0) + { + ZPrintf(" Joining: %s\n", strSessionId); + //join what ever last invite was processes + DirtySessionManagerControl(pApp->pDirtySessionManager, 'sjoi', 0, 0, strSessionId); + DirtySessionManagerControl(pApp->pDirtySessionManager, 'uinv', 0, 0, NULL); + } + + // report result + ZPrintf("Session: Accept Complete\n"); +} + +/*F*************************************************************************************/ +/*! + \Function _SessionAcceptDialog + + \Description + Join the session the user selected from the invitation dialog 'osrd' + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 6/6/2013 (cvienneau) +*/ +/**************************************************************************************F*/ +static void _SessionAcceptDialog(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + char strSessionId[64]; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s acceptdialog\n", argv[0]); + return; + } + + //check to see that we do have a session id + DirtySessionManagerStatus(pApp->pDirtySessionManager, 'gssi', strSessionId, sizeof(strSessionId)); + + if (strSessionId[0] == '\0') + { + //it doen't look the there is a selected session + ZPrintf("Session: no session slected to join\n"); + } + else + { + ZPrintf("Session: joining selected session\n"); + DirtySessionManagerControl(pApp->pDirtySessionManager, 'sjoi', 0, 0, strSessionId); + DirtySessionManagerControl(pApp->pDirtySessionManager, 'cssi', 0, 0, NULL); + } + + // report result + ZPrintf("Session: AcceptDialog Complete\n"); +} + +/*F*************************************************************************************/ +/*! + \Function _SessionAbortRequest + + \Description + Create several requests then abort them. + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 6/6/2013 (cvienneau) +*/ +/**************************************************************************************F*/ +static void _SessionAbortRequest(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + SessionAppT *pApp = &_Session_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s abort\n", argv[0]); + return; + } + + //que getting invites for every index + DirtySessionManagerControl(pApp->pDirtySessionManager, 'ginv', 0, 0, NULL); + DirtySessionManagerControl(pApp->pDirtySessionManager, 'ginv', 0, 0, NULL); + DirtySessionManagerControl(pApp->pDirtySessionManager, 'ginv', 0, 0, NULL); + DirtySessionManagerControl(pApp->pDirtySessionManager, 'ginv', 0, 0, NULL); + + //give the items a chance to process just a bit + //NetConnSleep(500); + + //abort all outsanding requests + DirtySessionManagerControl(pApp->pDirtySessionManager, 'abrt', 0, 0, NULL); + + // report result + ZPrintf("Session: Abort Test Complete\n"); +} + +/*F********************************************************************************/ +/*! + \Function _CmdSessionCb + + \Description + Update Session command + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_T -standard return value + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdSessionCb(ZContext *argz, int32_t argc, char *argv[]) +{ + SessionAppT *pApp = &_Session_App; + + // check for kill + if (argc == 0) + { + _SessionDestroyApp(pApp); + ZPrintf("%s: killed\n", argv[0]); + return(0); + } + + // give life to the module + if (pApp->pDirtySessionManager != NULL) + { + // update the module + DirtySessionManagerUpdate(pApp->pDirtySessionManager); + } + + // keep running + return(ZCallback(&_CmdSessionCb, 16)); +} + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdSession + + \Description + Session (WebSocket) command + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - standard return value + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdSession(ZContext *argz, int32_t argc, char *argv[]) +{ + T2SubCmdT *pCmd; + SessionAppT *pApp = &_Session_App; + int32_t iResult = 0; + uint8_t bHelp; + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_Session_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" test the DirtySessionManager module\n"); + T2SubCmdUsage(argv[0], _Session_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmd->pFunc != _SessionCreate) && (pApp->pDirtySessionManager == NULL)) + { + char *pCreate = "create"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _SessionCreate(pApp, 1, &pCreate, bHelp); + iResult = ZCallback(_CmdSessionCb, 16); + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + // one-time install of periodic callback + if (pCmd->pFunc == _SessionCreate) + { + iResult = ZCallback(_CmdSessionCb, 16); + } + return(iResult); +} + +#endif //defined(DIRTYCODE_PS4) + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/sleep.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/sleep.c new file mode 100644 index 00000000..57639349 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/sleep.c @@ -0,0 +1,92 @@ +/*H********************************************************************************/ +/*! + \File net.c + + \Description + Handles SLEEP for tester2 + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/11/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" + +#include "libsample/zlib.h" + +#include "testerregistry.h" +#include "testercomm.h" +#include "testerclientcore.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdSleep + + \Description + Sleep for a while + + \Input *argz - environment + \Input argc - number of args + \Input *argv[] - argument list + + \Output int32_t - standard return code + + \Version 04/11/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t CmdSleep(ZContext *argz, int32_t argc, char *argv[]) +{ + int32_t iTime, iTick; + TesterClientCoreT *pCore; + TesterCommT *pComm; + + //milliseconds to sleep before pumping I/O.. + #define TICK_STEP 100 + + pCore = (TesterClientCoreT *)TesterRegistryGetPointer("CORE"); + + // don't recurse + if (argc < 2) + { + ZPrintf(" sleep for the given amount of time\n"); + ZPrintf(" usage: %s \n", argv[0]); + } + else + { + iTime = atoi(argv[1]) *1000; + ZPrintf("%s: sleep for [%d] seconds.\n", argv[0], iTime/1000); + for (iTick = 0; iTick < iTime; iTick += TICK_STEP) + { + // pump output when sleeping; This makes "sleep" client only. + TesterClientCoreIdle(pCore); + ZSleep(TICK_STEP); //NOTE: ZSleep calls NetConnIdle.. + } + } + + // now, reset comm timer since we were sleeping.. + if ((pComm = (TesterCommT *)TesterRegistryGetPointer("COMM")) != NULL) + { + // slept - so bump timeout.. + pComm->uLastSendTime = ZTick(); + } + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/socket.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/socket.c new file mode 100644 index 00000000..ece667db --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/socket.c @@ -0,0 +1,565 @@ +/*H*************************************************************************************/ +/*! + \File socket.c + + \Description + Reference application for the socket module. + + \Copyright + Copyright (c) Electronic Arts 2011. ALL RIGHTS RESERVED. + + \Version 01/20/2011 (mclouatre) +*/ +/*************************************************************************************H*/ + + +/*** Include files *********************************************************************/ +#include +#include // for sscanf() + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" + +#include "libsample/zlib.h" + +#include "DirtySDK/dirtysock/dirtynet.h" +#include "testersubcmd.h" +#include "testermodules.h" + + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ +typedef struct SocketAppT +{ + SocketT *pSocket; + int32_t iSocketType; + int32_t bSocketBound; + int32_t iVerbosityLevel; + int32_t bAverageCalculationCycleStarted; + uint32_t uAverageCalculationCycleStartTick; + + // reception data + uint8_t bReceiving; + uint8_t recvBuffer[64]; + int32_t iUdpDatagramsReceived; + int32_t iReadIntervalInMs; + uint32_t uLastRecvTick; + struct sockaddr source; + + // transmission data + uint8_t bTransmitting; + uint8_t xmitBuffer[64]; + int32_t iUdpDatagramsSent; + int32_t iWriteIntervalInMs; + uint32_t uLastSentTick; + struct sockaddr destination; + +} SocketAppT; + +/*** Function Prototypes ***************************************************************/ + +static void _SocketOpen(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _SocketBind(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _SocketClose(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _SocketRecvFrom(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _SocketSendTo(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _SetDebugVerbosity(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); + + + +/*** Variables *************************************************************************/ + +// Private variables +static T2SubCmdT _Socket_Commands[] = +{ + { "open", _SocketOpen }, + { "bind", _SocketBind }, + { "close", _SocketClose }, + { "sendto", _SocketSendTo }, + { "recvfrom", _SocketRecvFrom }, + { "verbose", _SetDebugVerbosity }, + { "", NULL } +}; + +static SocketAppT _Socket_App; +uint8_t _SocketApp_bInitialized; + +/*** Private Functions *****************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function _CmdSocketCb + + \Description + Socket idle callback. + + \Input *argz - unused + \Input argc - argument count + \Input **argv - argument list + + \Output + int32_t - zero + + \Version 01/20/2011 (mclouatre) +*/ +/**************************************************************************************F*/ +static int32_t _CmdSocketCb(ZContext *argz, int32_t argc, char **argv) +{ + SocketAppT *pApp = &_Socket_App; + int32_t iInterval; + + // if socket no more exists, stop invoking this callback + if (!pApp->pSocket) + { + return(0); + } + + // display send/recv average every 5 sec + if (pApp->bReceiving || pApp->bTransmitting) + { + if (pApp->bAverageCalculationCycleStarted) + { + // check if cycle completed + if (NetTickDiff(NetTick(), pApp->uAverageCalculationCycleStartTick) > 10000) + { + uint32_t uSendingAverage; + uint32_t uReceivingAverage; + + pApp->bAverageCalculationCycleStarted = FALSE; + + uSendingAverage = pApp->iUdpDatagramsSent / 10; + + uReceivingAverage = pApp->iUdpDatagramsReceived / 10; + + ZPrintf(" LAST 10 SECONDS -> dgrams sent=%d | dgrams rcved=%d | avg dgrams sent=%d/sec | avg dgrams rcved=%d/sec\n", + pApp->iUdpDatagramsSent, pApp->iUdpDatagramsReceived, uSendingAverage, uReceivingAverage); + } + } + + if (!pApp->bAverageCalculationCycleStarted) + { + // mark cycle as started + pApp->bAverageCalculationCycleStarted = TRUE; + + // reset cycle start tick + pApp->uAverageCalculationCycleStartTick = NetTick(); + + // reset counters + pApp->iUdpDatagramsSent = 0; + pApp->iUdpDatagramsReceived = 0; + } + } + + if (pApp->iSocketType == SOCK_DGRAM) + { + // is receiving enabled? + if (pApp->bReceiving) + { + uint32_t uCurrentTick = NetTick(); + int32_t iRecvLen; + int32_t iSourceLen; + int32_t iSourcePort; + char strSourceAddrText[16]; + iRecvLen = SocketRecvfrom(pApp->pSocket, (char *)&pApp->recvBuffer, sizeof(pApp->recvBuffer), 0, &pApp->source, &iSourceLen); + if (iRecvLen > 0) + { + pApp->iUdpDatagramsReceived++; + SockaddrInGetAddrText(&pApp->source, strSourceAddrText, sizeof(strSourceAddrText)); + iSourcePort = SockaddrInGetPort(&pApp->source); + if (pApp->iVerbosityLevel > 3) + { + ZPrintf(" received 1 datagram (size %d) from %s:%d at time %d (delta = %d ms)\n", + iRecvLen, strSourceAddrText, iSourcePort, uCurrentTick, NetTickDiff(uCurrentTick, pApp->uLastRecvTick)); + } + pApp->uLastRecvTick = uCurrentTick; + } + else + { + if (pApp->iVerbosityLevel > 3) + { + ZPrintf(" SocketRecvfrom() returned %d at time %d\n", iRecvLen, uCurrentTick); + } + } + } + + // is transmitting enabled? + if (pApp->bTransmitting) + { + uint32_t uCurrentTick = NetTick(); + int32_t iSentLen; + int32_t iDestinationPort; + char strDestAddrText[16]; + iSentLen = SocketSendto(pApp->pSocket, (char *)&pApp->xmitBuffer, sizeof(pApp->recvBuffer), 0, &pApp->destination, sizeof(pApp->destination)); + if (iSentLen > 0) + { + pApp->iUdpDatagramsSent++; + SockaddrInGetAddrText(&pApp->destination, strDestAddrText, sizeof(strDestAddrText)); + iDestinationPort = SockaddrInGetPort(&pApp->destination); + if (pApp->iVerbosityLevel > 3) + { + ZPrintf(" sent 1 datagram (size %d) to %s:%d at time %d (delta =%d ms)\n", + iSentLen, strDestAddrText, iDestinationPort, uCurrentTick, NetTickDiff(uCurrentTick, pApp->uLastSentTick)); + } + pApp->uLastSentTick = uCurrentTick; + } + else + { + if (pApp->iVerbosityLevel > 3) + { + ZPrintf(" SocketSendto() returned %d at time %d\n", iSentLen, uCurrentTick); + } + } + } + } + else + { + ZPrintf(" send/recv processing not implemented for raw or tcp sockets (current socket type = %d)\n", pApp->iSocketType); + return(0); + } + + if (pApp->iReadIntervalInMs < pApp->iWriteIntervalInMs) + { + iInterval = pApp->iReadIntervalInMs; + } + else + { + iInterval = pApp->iWriteIntervalInMs; + } + return(ZCallback(_CmdSocketCb, iInterval)); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketOpen + + \Description + Socket subcommand - open a socket + + \Input *pApp - pointer to Socket app + \Input argc - argument count + \Input **argv - argument list + + \Version 01/20/2011 (mclouatre) +*/ +/**************************************************************************************F*/ +static void _SocketOpen(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + SocketAppT *pApp = (SocketAppT *)_pApp; + + if ((bHelp == TRUE) || (argc != 3)) + { + ZPrintf(" usage: %s %s \n", argv[0], argv[1]); + return; + } + + if (strcmp(argv[2], "raw") == 0) + { + pApp->iSocketType = SOCK_RAW; + } + else if (strcmp(argv[2], "udp") == 0) + { + pApp->iSocketType = SOCK_DGRAM; + } + else if (strcmp(argv[2], "tcp") == 0) + { + pApp->iSocketType = SOCK_STREAM; + } + else + { + ZPrintf(" invalid socket type\n", argv[2]); + ZPrintf(" usage: %s %s \n", argv[0], argv[1]); + return; + } + + // make sure we don't already have a socket + if (pApp->pSocket != NULL) + { + ZPrintf(" %s: there is already a socket opened\n", argv[0]); + return; + } + + pApp->pSocket = SocketOpen(AF_INET, pApp->iSocketType, 0); + if (pApp->pSocket) + { + ZPrintf(" %s: opened %s socket (ref=%p)\n", argv[0], argv[2], pApp->pSocket); + } + else + { + ZPrintf(" %s: failed to open socket\n", argv[0]); + return; + } + pApp->bSocketBound = FALSE; + + // read and write interval defaul to 1 sec until recv or send is initiated + pApp->iReadIntervalInMs = 1000; + pApp->iWriteIntervalInMs = 1000; + + // register periodic callback + ZCallback(_CmdSocketCb, pApp->iReadIntervalInMs); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketBind + + \Description + Socket subcommand - bind a socket to a local IP address and port + + \Input *pApp - pointer to Socket app + \Input argc - argument count + \Input **argv - argument list + + \Version 01/20/2011 (mclouatre) +*/ +/**************************************************************************************F*/ +static void _SocketBind(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + SocketAppT *pApp = (SocketAppT *)_pApp; + int32_t iResult; + struct sockaddr bindAddr; + + if ((bHelp == TRUE) || (argc != 3)) + { + ZPrintf(" usage: %s %s \n", argv[0], argv[1]); + return; + } + + if (!pApp->pSocket) + { + ZPrintf(" %s: socket is not yet opened\n", argv[0]); + return; + } + + if (pApp->bSocketBound) + { + ZPrintf(" %s: socket (ref=%p) is already bound\n", argv[0], pApp->pSocket); + return; + } + + // initialize bindAddr from input string + if ((SockaddrInParse(&bindAddr, argv[2]) & 0x3) != 0x3) + { + ZPrintf(" %s: badly formatted parameter\n", argv[0]); + return; + } + + iResult = SocketBind(pApp->pSocket, &bindAddr, sizeof(bindAddr)); + if (iResult < 0) + { + ZPrintf(" %s: socket (ref=%p) failed to bind to %s - err = %d\n", argv[0], pApp->pSocket, argv[2], iResult); + } + else + { + SocketInfo(pApp->pSocket, 'bind', 0, &bindAddr, sizeof(bindAddr)); + ZPrintf(" %s: socket (ref=%p) bound to %A\n", argv[0], pApp->pSocket, &bindAddr); + pApp->bSocketBound = TRUE; + } +} + +/*F*************************************************************************************/ +/*! + \Function _SocketClose + + \Description + Socket subcommand - close a socket + + \Input *pApp - pointer to Socket app + \Input argc - argument count + \Input **argv - argument list + + \Version 01/20/2011 (mclouatre) +*/ +/**************************************************************************************F*/ +static void _SocketClose(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + SocketAppT *pApp = (SocketAppT *)_pApp; + + if ((bHelp == TRUE) || (argc != 2)) + { + ZPrintf(" usage: %s %s\n", argv[0], argv[1]); + return; + } + + if (!pApp->pSocket) + { + ZPrintf(" %s: socket is not yet opened\n", argv[0]); + return; + } + + SocketClose(pApp->pSocket); + pApp->bSocketBound = FALSE; + ZPrintf(" %s: closed socket (ref=%p)\n", argv[0], pApp->pSocket); + pApp->pSocket = NULL; +} + +/*F*************************************************************************************/ +/*! + \Function _SocketRecvFrom + + \Description + Socket subcommand - recvfrom operation on previously created socket + + \Input *pApp - pointer to Socket app + \Input argc - argument count + \Input **argv - argument list + + \Version 01/20/2011 (mclouatre) +*/ +/**************************************************************************************F*/ +static void _SocketRecvFrom(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + SocketAppT *pApp = (SocketAppT *)_pApp; + + if ((bHelp == TRUE) || (argc != 3)) + { + ZPrintf(" usage: %s %s \n", argv[0], argv[1]); + return; + } + + if (!pApp->pSocket) + { + ZPrintf(" %s: socket is not yet opened\n", argv[0]); + return; + } + + if (!pApp->bSocketBound) + { + ZPrintf(" %s: socket (ref=%p) is not yet bound\n", argv[0], pApp->pSocket); + return; + } + + // get read rate + sscanf(argv[2], "%d", &pApp->iReadIntervalInMs); + + ZPrintf(" %s: data reception enabled on socket (ref=%p) - read rate is: 1 read operation every %d ms\n", argv[0], pApp->pSocket, pApp->iReadIntervalInMs); + pApp->bReceiving = TRUE; +} + +/*F*************************************************************************************/ +/*! + \Function _SocketSendTo + + \Description + Socket subcommand - sendto operation on previously created socket + + \Input *pApp - pointer to Socket app + \Input argc - argument count + \Input **argv - argument list + + \Version 01/20/2011 (mclouatre) +*/ +/**************************************************************************************F*/ +static void _SocketSendTo(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + SocketAppT *pApp = (SocketAppT *)_pApp; + + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s %s \n", argv[0], argv[1]); + return; + } + + if (!pApp->pSocket) + { + ZPrintf(" %s: socket is not yet opened\n", argv[0]); + return; + } + + // initialize destination from input string + if ((SockaddrInParse(&pApp->destination, argv[2]) & 0x3) != 0x3) + { + ZPrintf(" %s: badly formatted parameter\n", argv[0]); + return; + } + + if (!pApp->bSocketBound) + { + ZPrintf(" %s: socket (ref=%p) is not yet bound\n", argv[0], pApp->pSocket); + return; + } + + // get write rate + sscanf(argv[3], "%d", &pApp->iWriteIntervalInMs); + + ZPrintf(" %s: data transmission enabled on socket (ref=%p) - write rate is: 1 write operation every %d ms\n", argv[0], pApp->pSocket, pApp->iWriteIntervalInMs); + + pApp->bTransmitting = TRUE; +} + +/*F*************************************************************************************/ +/*! + \Function _SetDebugVerbosity + + \Description + Socket subcommand - sendto operation on previously created socket + + \Input *pApp - pointer to Socket app + \Input argc - argument count + \Input **argv - argument list + + \Version 01/20/2011 (mclouatre) +*/ +/**************************************************************************************F*/ +static void _SetDebugVerbosity(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + SocketAppT *pApp = (SocketAppT *)_pApp; + + if ((bHelp == TRUE) || (argc != 3)) + { + ZPrintf(" usage: %s %s \n", argv[0], argv[1]); + return; + } + + // get write rate + sscanf(argv[2], "%d", &pApp->iVerbosityLevel); +} + +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function CmdSocket + + \Description + Socket command. + + \Input *argz - unused + \Input argc - argument count + \Input **argv - argument list + + \Output + int32_t - zero + + \Version 01/20/2011 (mclouatre) First Version +*/ +/**************************************************************************************F*/ +int32_t CmdSocket(ZContext *argz, int32_t argc, char **argv) +{ + T2SubCmdT *pCmd; + SocketAppT *pApp = &_Socket_App; + unsigned char bHelp; + + // initialize state + if (!_SocketApp_bInitialized) + { + ds_memclr(pApp, sizeof(*pApp)); + _SocketApp_bInitialized = TRUE; + } + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_Socket_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" exercises DirtySock socket API\n"); + T2SubCmdUsage(argv[0], _Socket_Commands); + return(0); + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/source.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/source.c new file mode 100644 index 00000000..4094f807 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/source.c @@ -0,0 +1,234 @@ +/*H********************************************************************************/ +/*! + \File source.c + + \Description + A tester command to implement 'source' (like unix) command + + \Copyright + Copyright (c) 2012 Electronic Arts Inc. + + \Version 10/10/2012 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" + +#include "libsample/zlib.h" +#include "libsample/zmem.h" +#include "libsample/zfile.h" + +#include "testerregistry.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct SourceRefT +{ + TesterModulesT *pModulesState; + char *pScriptData; + char *pScriptLine; + int32_t iFileSize; + int32_t iCurrProc; + int32_t iCurrRslt; + int32_t iCurrStat; + int32_t iNumCmds; + int32_t iNumCmdsFailed; +} SourceRefT; + +/*** Function Prototypes ***************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _SourceExecuteCmd + + \Description + 'source' callback, called after command has been issued. + + \Input *pRef - command module state + \Input *pScriptLine - current line of source script + + \Output + char * - pointer to next line of script, or NULL + + \Version 10/10/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_SourceExecuteCmd(SourceRefT *pRef, char *pScriptLine) +{ + char *pScriptEnd; + + // find end of command + for (pScriptEnd = pScriptLine; (*pScriptEnd != '\r') && (*pScriptEnd != '\n') && (*pScriptEnd != '\0'); pScriptEnd += 1) + ; + + // terminate and skip to next command start + if (*pScriptEnd != '\0') + { + for (*pScriptEnd++ = '\0'; ((*pScriptEnd == '\r') || (*pScriptEnd == '\n')) && (*pScriptEnd != '\0'); pScriptEnd += 1) + ; + } + + // execute the line + ZPrintf("source: executing '%s'\n", pScriptLine); + pRef->iCurrStat = TesterModulesDispatch(pRef->pModulesState, pScriptLine); + pRef->iNumCmds += 1; + // get process id of process we just executed + pRef->iCurrProc = ZGetPid(); + + /* restore current environment... required because TexterModulesDispatch()/ZInvoke() leaves the dispatched + command as the current environment, which messes up our subsequent call to ZCallback() */ + ZSet((ZContext *)pRef); + + // return pointer to next command, or NULL if no more commands + return((*pScriptEnd != '\0') ? pScriptEnd : NULL); +} + +/*F********************************************************************************/ +/*! + \Function _CmdSourceCb + + \Description + 'source' callback, called after command has been issued. + + \Input *argz - pointer to context + \Input argc - number of command-line arguments + \Input *argv[] - command-line argument list + + \Output + int32_t - result of zcallback, or zero to terminate + + \Version 10/10/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdSourceCb(ZContext *argz, int32_t argc, char *argv[]) +{ + SourceRefT *pRef = (SourceRefT *)argz;\ + int32_t iStatus; + + // check for kill + if (argc == 0) + { + ZPrintf("%s: killed\n", argv[0]); + return(0); + } + + // if we have a current process running, check it + if (pRef->iCurrProc != -1) + { + /* $$ TODO - this works to determine the command is no longer running, but getting the final status/return code + doesn't work; it is not available since the command is no more */ + if ((iStatus = ZGetStatusPid(pRef->iCurrProc)) != ZLIB_STATUS_RUNNING) + { + // determine process result -- first check for immediate-exit result + if (pRef->iCurrStat != ZLIB_STATUS_RUNNING) + { + iStatus = pRef->iCurrStat; + } + else if (iStatus == ZLIB_STATUS_UNKNOWN) + { + iStatus = 0; // any 'async' command will do this until we can implement a way to get the exit code + } + ZPrintf("%s: process %d complete (result=%d)\n", argv[0], pRef->iCurrProc, iStatus); + // track completion status + pRef->iNumCmdsFailed += iStatus != 0; + // clear current command + pRef->iCurrProc = -1; + } + } + + // issue a new command? + if (pRef->iCurrProc == -1) + { + // if we have more commands, start a new one + if (pRef->pScriptLine != NULL) + { + pRef->pScriptLine = _SourceExecuteCmd(pRef, pRef->pScriptLine); + } + else + { + // we're done, time to quit + ZMemFree(pRef->pScriptData); + ZPrintf("%s: done (%d commands, %d failed)\n", argv[0], pRef->iNumCmds, pRef->iNumCmdsFailed); + return(0); + } + } + + // keep recurring + return(ZCallback(&_CmdSourceCb, 16)); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdSource + + \Description + Upnp command. This command starts the ProtoUpnp module. + + \Input *argz - pointer to context + \Input argc - number of command-line arguments + \Input *argv[] - command-line argument list + + \Output + int32_t - result of zcallback + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdSource(ZContext *argz, int32_t argc, char *argv[]) +{ + SourceRefT *pRef; + + // handle basic help + if (argc != 2) + { + ZPrintf("usage: %s \n", argv[0]); + return(0); + } + + // otherwise run the script + ZPrintf("%s: running script '%s'\n", argv[0], argv[1]); + + // allocate context + if ((pRef = (SourceRefT *)ZContextCreate(sizeof(*pRef))) == NULL) + { + ZPrintf("%s: could not allocate state\n", argv[0]); + return(-1); + } + ds_memclr(pRef, sizeof(*pRef)); + + // get modules state + if ((pRef->pModulesState = (TesterModulesT *)TesterRegistryGetPointer("MODULES")) == NULL) + { + ZPrintf("%s: could not get module state for dispatch\n", argv[0]); + return(-2); + } + + // load the script + if ((pRef->pScriptData = ZFileLoad(argv[1], &pRef->iFileSize, 0)) == NULL) + { + ZPrintf("%s: failed to load file {%s}\n", argv[0], argv[1]); + return(-3); + } + pRef->pScriptLine = pRef->pScriptData; + pRef->iCurrProc = -1; + + // set up recurring callback + return(ZCallback(_CmdSourceCb, 16)); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/stream.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/stream.c new file mode 100644 index 00000000..aded589b --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/stream.c @@ -0,0 +1,1285 @@ +/*H********************************************************************************/ +/*! + \File stream.c + + \Description + Test the ProtoStream module. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 11/16/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/proto/protostream.h" +#include "DirtySDK/xml/xmlparse.h" + +#include "libsample/zlib.h" +#include "libsample/zmem.h" +#include "libsample/zfile.h" + +#include "testersubcmd.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +#define STREAMER_USECALLBACK (TRUE) + +#define STREAMER_DEBUG (FALSE) + +#define STREAM_DEFAULTSKIP (15) // 15 seconds + +#define STREAM_MAXURLS (4) + +/*** Type Definitions *************************************************************/ + +typedef struct StreamStationT +{ + char strName[32]; + char strInfo[64]; + char strType[32]; + char strUrl[STREAM_MAXURLS][256]; +} StreamStationT; + +typedef struct StreamPlaylistT +{ + int32_t iNumStations; + StreamStationT Stations[1]; //!< variable-length array of stations +} StreamPlaylistT; + +typedef struct StreamCmdRefT +{ + ProtoStreamRefT *pProtoStream; + StreamPlaylistT *pPlaylist; + int32_t *pRandom; + int32_t iStartTick; + int32_t iSkipTick; + int32_t iSkipTime; + int32_t iCurStation; + int32_t iCurUrl; + int32_t iMetaInterval; + int32_t iMetaOffset; + int32_t iMetaSize; + uint8_t bRandomPlay; + char strMetaBuffer[(255*16)+1]; + char strLastMetaBuffer[(255*16)+1]; +} StreamCmdRefT; + +/*** Function Prototypes **********************************************************/ + +static void _SubcmdStreamCreate(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SubcmdStreamDestroy(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SubcmdStreamOpen(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SubcmdStreamClose(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SubcmdStreamSkip(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _SubcmdStreamControl(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); + +/*** Variables ********************************************************************/ + +//! subcommand table +static T2SubCmdT _Stream_Commands[] = +{ + { "create", _SubcmdStreamCreate }, + { "destroy", _SubcmdStreamDestroy }, + { "open", _SubcmdStreamOpen }, + { "close", _SubcmdStreamClose }, + { "skip", _SubcmdStreamSkip }, + { "ctrl", _SubcmdStreamControl }, + { "", NULL } +}; + +//! single instance of the stream module +static StreamCmdRefT *_Stream_pCmdRef = NULL; + +//! basic playlist with sportscenter ref +static StreamPlaylistT _Playlist = +{ + 1, + { + { + "ESPN Sportscenter", + "sportscenter 32k/sec", + "Sports", + { + "http://stestbesl01.beta.ea.com/espnradio/sportscenter/sportscenter.mp3", + } + } + }, +}; + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _StreamOpen + + \Description + Open the stream specified by the given url + + \Input *pCmdRef - module state + \Input *pStation - station reference + \Input iRestartFreq - restart frequency + + \Output + None. + + \Version 11/21/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _StreamOpen(StreamCmdRefT *pCmdRef, StreamStationT *pStation, int32_t iRestartFreq) +{ + char strTempUrl[128], *pUrl; + + // ref url + pUrl = pStation->strUrl[pCmdRef->iCurUrl]; + + // validate url + if (strncmp(pUrl, "http://", 7)) + { + ZPrintf("stream: invalid URL '%s'\n", pUrl); + return; + } + + // see if URL needs a trailing slash + if (!strchr(pUrl+7, '/')) + { + ds_strnzcpy(strTempUrl, pUrl, sizeof(strTempUrl)); + ds_strnzcat(strTempUrl, "/", sizeof(strTempUrl)); + pUrl = strTempUrl; + } + + // open the stream + if (pStation->strName[0] != '\0') + { + ZPrintf("stream: opening [%d] %s (url=%s)\n", (int32_t)(pStation - pCmdRef->pPlaylist->Stations), pStation->strName, pUrl); + } + else + { + ZPrintf("stream: opening url %s\n", pUrl); + } + ProtoStreamOpen(pCmdRef->pProtoStream, pUrl, iRestartFreq); + pCmdRef->iMetaInterval = 0; + pCmdRef->iMetaSize = 0; + pCmdRef->iMetaOffset = 0; +} + +/*F********************************************************************************/ +/*! + \Function _StreamXmlGetString + + \Description + Get an xml entity's string contents + + \Input *pXml - source xml to get data from + \Input *pName - name of entity + \Input *pBuffer - [out] storage for entity contents + \Input iBufSize - size of buffer pointed to by pBuffer + + \Output + None. + + \Version 11/21/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _StreamXmlGetString(const char *pXml, const char *pName, char *pBuffer, int32_t iBufSize) +{ + ds_memclr(pBuffer, iBufSize); + if ((pXml = XmlFind(pXml, pName)) != NULL) + { + return(XmlContentGetString(pXml, pBuffer, iBufSize, "")); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _StreamDisplayPlaylist + + \Description + Display the given playlist + + \Input *pCmdRef - module state + \Input *pPlaylist - playlist to display + + \Output + None. + + \Version 08/18/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _StreamDisplayPlaylist(StreamCmdRefT *pCmdRef, StreamPlaylistT *pPlaylist) +{ + int32_t iStation; + + for (iStation = 0; iStation < pPlaylist->iNumStations; iStation++) + { + ZPrintf("stream: [%2d] %s\n", iStation, pPlaylist->Stations[iStation].strName); + } +} + +/*F********************************************************************************/ +/*! + \Function _StreamOpenPlaylist + + \Description + Open the playlist specified by the given filename + + \Input *pCmdRef - module state + \Input *pName - name of playlist to open + + \Output + None. + + \Version 08/16/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _StreamOpenPlaylist(StreamCmdRefT *pCmdRef, const char *pName) +{ + const char *pPlayFile, *pXml, *pXml2; + int32_t iFileSize, iNumStations; + + // open the playlist + ZPrintf("stream: opening playlist '%s'\n", pName); + if ((pPlayFile = ZFileLoad(pName, &iFileSize, FALSE)) == NULL) + { + ZPrintf("stream: error opening playlist\n"); + return; + } + + // see if it is a playlist + if ((pXml = XmlFind(pPlayFile, "playlist.station")) == NULL) + { + ZPrintf("stream: file is not a playlist\n", pName); + return; + } + + // see how many stations are defined + for (pXml2 = pXml, iNumStations = 1; ; ) + { + if ((pXml2 = XmlSkip(pXml2)) == NULL) + { + break; + } + if (!strncmp(pXml2+1, "station", 7)) + { + iNumStations += 1; + } + } + + ZPrintf("stream: parsed %d stations\n", iNumStations); + if (iNumStations != 0) + { + int32_t iPlaylistSize, iRandom, iRandIdx; + StreamStationT *pStation; + int32_t *pTmpRandom; + + // allocate a new playlist structure + if ((pCmdRef->pPlaylist != NULL) && (pCmdRef->pPlaylist != &_Playlist)) + { + ZMemFree(pCmdRef->pPlaylist); + } + if (pCmdRef->pRandom != NULL) + { + ZMemFree(pCmdRef->pPlaylist); + } + iPlaylistSize = sizeof(*pCmdRef->pPlaylist) + (sizeof(pCmdRef->pPlaylist->Stations[0]) * (iNumStations - 1)); + pCmdRef->pPlaylist = ZMemAlloc(iPlaylistSize); + ds_memclr(pCmdRef->pPlaylist, iPlaylistSize); + pCmdRef->pPlaylist->iNumStations = iNumStations; + + // parse xml into new playlist + for (pStation = pCmdRef->pPlaylist->Stations; ; ) + { + if (!strncmp((char *)pXml+1, "station", 7)) + { + _StreamXmlGetString(pXml, "station.name", pStation->strName, sizeof(pStation->strName)); + _StreamXmlGetString(pXml, "station.info", pStation->strInfo, sizeof(pStation->strInfo)); + _StreamXmlGetString(pXml, "station.type", pStation->strType, sizeof(pStation->strType)); + _StreamXmlGetString(pXml, "station.url0", pStation->strUrl[0], sizeof(pStation->strUrl[0])); + _StreamXmlGetString(pXml, "station.url1", pStation->strUrl[1], sizeof(pStation->strUrl[1])); + _StreamXmlGetString(pXml, "station.url2", pStation->strUrl[2], sizeof(pStation->strUrl[2])); + _StreamXmlGetString(pXml, "station.url3", pStation->strUrl[3], sizeof(pStation->strUrl[3])); + pStation += 1; + } + if ((pXml = XmlSkip(pXml)) == NULL) + { + break; + } + } + + // create new buffer for randomizing playlist + pCmdRef->pRandom = ZMemAlloc(iNumStations*sizeof(*pCmdRef->pRandom)); + + // create playlist indices + pTmpRandom = ZMemAlloc((iNumStations+1)*sizeof(*pCmdRef->pRandom)); + for (iRandom = 0; iRandom < iNumStations; iRandom++) + { + pTmpRandom[iRandom] = iRandom; + } + + // generate random walk through playlist + for (iRandom = 0; iRandom < (iNumStations-1); iRandom++) + { + iRandIdx = rand() % (iNumStations-iRandom); + pCmdRef->pRandom[iRandom] = pTmpRandom[iRandIdx]; + memmove(&pTmpRandom[iRandIdx], &pTmpRandom[iRandIdx+1], (iNumStations-iRandIdx-1)*sizeof(*pTmpRandom)); + } + pCmdRef->pRandom[iRandom] = pTmpRandom[0]; + + // free temp buffer + ZMemFree(pTmpRandom); + } + + // done with the file + ZMemFree((void *)pPlayFile); +} + +/*F********************************************************************************/ +/*! + \Function _StreamGetRandIndex + + \Description + Get index of given station in random playlist. + + \Input *pCmdRef - module state + \Input iIndex - station index in playlist + + \Output + int32_t - index of station in random playlist + + \Version 11/14/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _StreamGetRandIndex(StreamCmdRefT *pCmdRef, int32_t iIndex) +{ + int32_t iStation; + for (iStation = 0; iStation < pCmdRef->pPlaylist->iNumStations; iStation++) + { + if (pCmdRef->pRandom[iStation] == iIndex) + { + return(iStation); + } + } + NetPrintf(("stream: unable to find station %d in random playlist\n", iIndex)); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _StreamClose + + \Description + Close the current stream, if any + + \Input *pCmdRef - module state + + \Output + None. + + \Version 11/21/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _StreamClose(StreamCmdRefT *pCmdRef) +{ + // close the stream + ZPrintf("stream: closing active stream\n"); + ProtoStreamClose(pCmdRef->pProtoStream); +} + +/*F********************************************************************************/ +/*! + \Function _StreamGetCurStation + + \Description + Get index of current station (possibly applying random index) + + \Input *pCmdRef - module state + + \Output + int32_t - index of current station + + \Version 11/24/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _StreamGetCurStation(StreamCmdRefT *pCmdRef) +{ + int32_t iStation; + iStation = (pCmdRef->bRandomPlay) ? pCmdRef->pRandom[pCmdRef->iCurStation] : pCmdRef->iCurStation; + return(iStation); +} + +/*F********************************************************************************/ +/*! + \Function _StreamNextUrl + + \Description + Skip to next url for current station + + \Input *pCmdRef - module state + + \Output + None. + + \Version 11/22/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _StreamNext(StreamCmdRefT *pCmdRef) +{ + pCmdRef->iCurUrl = pCmdRef->iCurUrl + 1; + _StreamOpen(pCmdRef, &pCmdRef->pPlaylist->Stations[_StreamGetCurStation(pCmdRef)], PROTOSTREAM_FREQ_IMMED); +} + +/*F********************************************************************************/ +/*! + \Function _StreamSkip + + \Description + Skip to next stream. + + \Input *pCmdRef - module state + \Input iCurTick - current tick + + \Output + None. + + \Version 11/22/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _StreamSkip(StreamCmdRefT *pCmdRef, int32_t iCurTick) +{ + // switch to next station + pCmdRef->iCurStation = (pCmdRef->iCurStation + 1) % pCmdRef->pPlaylist->iNumStations; + + // switch to next stream + pCmdRef->iCurUrl = 0; + + // open the stream + _StreamOpen(pCmdRef, &pCmdRef->pPlaylist->Stations[_StreamGetCurStation(pCmdRef)], PROTOSTREAM_FREQ_IMMED); + + // set next update + pCmdRef->iSkipTick = iCurTick; +} + +/*F********************************************************************************/ +/*! + \Function _StreamParseHeader + + \Description + Parse data from from icy header response + + \Input *pCmdRef - module state + \Input *pHeader - http response header + + \Output + None. + + \Version 03/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _StreamParseHeader(StreamCmdRefT *pCmdRef, const char *pHeader) +{ + char strData[256], *pData, *pHeaderInfo; + + // parse meta-interval data, if present + if ((pHeaderInfo = ds_stristr(pHeader, "icy-metaint")) != NULL) + { + // parse meta interval + while (!isdigit(*pHeaderInfo)) + { + pHeaderInfo += 1; + } + pCmdRef->iMetaInterval = strtol(pHeaderInfo, NULL, 10); + NetPrintf(("stream: metadata interval is %d\n", pCmdRef->iMetaInterval)); + + pCmdRef->iMetaOffset = pCmdRef->iMetaInterval; + } + + // parse icy-name, if present + if ((pHeaderInfo = ds_stristr(pHeader, "icy-name:")) != NULL) + { + pHeaderInfo += sizeof("icy-name:")-1; + for (pData = strData; *pHeaderInfo != '\r'; pHeaderInfo += 1, pData += 1) + { + *pData = *pHeaderInfo; + } + *pData = '\0'; + ZPrintf("=====================================================================================================\n"); + ZPrintf("stream: %s\n", strData); + } + + // parse icy-genre, if present + if ((pHeaderInfo = ds_stristr(pHeader, "icy-genre:")) != NULL) + { + pHeaderInfo += sizeof("icy-genre:")-1; + for (pData = strData; *pHeaderInfo != '\r'; pHeaderInfo += 1, pData += 1) + { + *pData = *pHeaderInfo; + } + *pData = '\0'; + ZPrintf("stream: %s\n", strData); + } + + // parse icy-br, if present + if ((pHeaderInfo = ds_stristr(pHeader, "icy-br:")) != NULL) + { + pHeaderInfo += sizeof("icy-br:")-1; + for (pData = strData; *pHeaderInfo != '\r'; pHeaderInfo += 1, pData += 1) + { + *pData = *pHeaderInfo; + } + *pData = '\0'; + ZPrintf("stream: %skbps\n", strData); + ZPrintf("=====================================================================================================\n"); + } +} + +/*F********************************************************************************/ +/*! + \Function _StreamProcessMetaData + + \Description + Process metadata embedded in mp3 stream. + + \Input *pCmdRef - module state + \Input *pData - stream data + \Input iSize - amount of data available for processing + + \Output + int32_t - number of bytes consumed + + \Version 03/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _StreamProcessMetaData(StreamCmdRefT *pCmdRef, const uint8_t *pData, int32_t iSize) +{ + int32_t iConsume, iConsumed; + + // process all data + for ( iConsumed = 0; iSize > 0; ) + { + // if we're processing mp3 data, bail + if (pCmdRef->iMetaOffset > 0) + { + break; + } + + // are we processing a metadata header? + if (pCmdRef->iMetaSize > 0) + { + if (iSize >= pCmdRef->iMetaSize) + { + iConsume = pCmdRef->iMetaSize; + pCmdRef->iMetaOffset = pCmdRef->iMetaInterval; + } + else + { + iConsume = iSize; + } + + // copy metadata into buffer + ds_strsubzcat(pCmdRef->strMetaBuffer, sizeof(pCmdRef->strMetaBuffer), (char *)pData, iConsume); + + // skip metadata + pData += iConsume; + iSize -= iConsume; + + // mark metasize as consumed + pCmdRef->iMetaSize -= iConsume; + iConsumed += iConsume; + } + // do we have a metadata header? + else if (pCmdRef->iMetaOffset == 0) + { + pCmdRef->iMetaSize = *pData * 16; + pCmdRef->strMetaBuffer[0] = '\0'; + pData += 1; + iSize -= 1; + iConsumed += 1; + } + + // if we're done with the header, reset the offset + if (pCmdRef->iMetaSize == 0) + { + // did we get any metadata? + if (pCmdRef->strMetaBuffer[0] != '\0') + { + // did the metadata change since the last update? + if (strcmp(pCmdRef->strLastMetaBuffer, pCmdRef->strMetaBuffer)) + { + char *pTitle, *pEndTitle; + + // first, save the update + strcpy(pCmdRef->strLastMetaBuffer, pCmdRef->strMetaBuffer); + + // try and find title for display + if ((pTitle = strstr(pCmdRef->strMetaBuffer, "StreamTitle")) != NULL) + { + if ((pTitle = strchr(pTitle, '\'')) != NULL) + { + time_t uTime; + + pTitle += 1; + if ((pEndTitle = strchr(pTitle, ';')) != NULL) + { + *(pEndTitle-1) = '\0'; + } + else if ((pEndTitle = strchr(pTitle, '\'')) != NULL) + { + *pEndTitle = '\0'; + } + + if ((uTime = ds_timeinsecs()) != 0) + { + struct tm CurTime; + ds_secstotime(&CurTime, uTime); + ZPrintf("stream: %02d:%02d now playing - %s\n", CurTime.tm_hour, CurTime.tm_min, pTitle); + } + else + { + ZPrintf("stream: now playing - %s\n", pTitle); + } + } + } + } + } + + // reset offset + pCmdRef->iMetaOffset = pCmdRef->iMetaInterval; + } + } + + return(iConsumed); +} + +/*F********************************************************************************/ +/*! + \Function _StreamDone + + \Description + Process stream completion + + \Input *pCmdRef - module state + \Input iCurTick - current tick + + \Output + None. + + \Version 03/31/2008 (jbrookes) +*/ +/********************************************************************************F*/ +static void _StreamDone(StreamCmdRefT *pCmdRef, int32_t iCurTick) +{ + if (pCmdRef->pPlaylist->iNumStations > 1) + { + int32_t iNextUrl = pCmdRef->iCurUrl + 1; + if ((iNextUrl < STREAM_MAXURLS) && (pCmdRef->pPlaylist->Stations[_StreamGetCurStation(pCmdRef)].strUrl[iNextUrl][0] != '\0')) + { + _StreamNext(pCmdRef); + } + else + { + _StreamSkip(pCmdRef, iCurTick); + } + } + else + { + // if it's a one-shot, just stop playback + ProtoStreamClose(pCmdRef->pProtoStream); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoStreamCallback + + \Description + ProtoStream callback handler + + \Input *pProtoStream - protostream module state + \Input eStatus - callback status + \Input *pBuffer - pointer to buffered data, or NULL if no data + \Input iBufSize - amount of data available in buffer + \Input *pUserData - user data pointer + + \Output + int32_t - number of bytes consumed + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +#if STREAMER_USECALLBACK +static int32_t _ProtoStreamCallback(ProtoStreamRefT *pProtoStream, ProtoStreamStatusE eStatus, const uint8_t *pBuffer, int32_t iBufSize, void *pUserData) +{ + StreamCmdRefT *pCmdRef = (StreamCmdRefT *)pUserData; + const uint8_t *pBufStart = pBuffer; + int32_t iResult; + #if STREAMER_DEBUG + int32_t iTick; + #endif + + // validate cmdref + if ((pCmdRef == NULL) || (pCmdRef->pProtoStream != pProtoStream)) + { + NetPrintf(("stream: error -- invalid callback ref\n")); + return(0); + } + + // handle stream start state + if (eStatus == PROTOSTREAM_STATUS_BEGIN) + { + char strHeader[512]; + + // get header + ProtoStreamStatus(pProtoStream, 'htxt', strHeader, sizeof(strHeader)); + + // print header + NetPrintf(("stream: stream start\n")); + + // parse icy header + _StreamParseHeader(pCmdRef, strHeader); + + // save start time + pCmdRef->iStartTick = ZTick(); + } + + // calc current tick + #if STREAMER_DEBUG + iTick = ZTick() - pCmdRef->iStartTick; + #endif + + // handle stream data ready state + if ((eStatus == PROTOSTREAM_STATUS_BEGIN) || (eStatus == PROTOSTREAM_STATUS_DATA)) + { + // decode processing + for ( iResult = 1; iResult > 0; ) + { + // process metadata, if any + if (pCmdRef->iMetaInterval != 0) + { + // clamp maximum amount of data + iResult = _StreamProcessMetaData(pCmdRef, pBuffer, iBufSize); + if (iResult > 0) + { + pBuffer += iResult; + iBufSize -= iResult; + } + // make sure we don't consume data into the next metaheader + if (iBufSize > pCmdRef->iMetaOffset) + { + iBufSize = pCmdRef->iMetaOffset; + } + } + + // process mp3 data + if (iBufSize > 0) + { + // $$TODO$$ mimic mp3 decoding: for now just consume the entire buffer + pBuffer += iBufSize; + + // update meta offset + if (pCmdRef->iMetaInterval != 0) + { + pCmdRef->iMetaOffset -= iBufSize; + } + } + else + { + break; + } + } + } + + // handle stream done state + if (eStatus == PROTOSTREAM_STATUS_DONE) + { + // kill the player + NetPrintf(("stream: stream done; resetting player\n")); + _StreamDone(pCmdRef, ZTick()); + } + + #if STREAMER_DEBUG + if ((pBuffer - pBufStart) > 0) + { + // display status info + ZPrintf("stream: consumed %d bytes at %d:%02d:%03d\n", pBuffer - pBufStart, + iTick / (1000*60), // minutes + (iTick / 1000) % 60, // seconds + iTick % 1000); // thousandths of a second + } + #endif + + return(pBuffer - pBufStart); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _StreamUpdate + + \Description + Read data from ProtoStream (non-callback interface) + + \Input *pProtoStream - protostream module state + \Input eStatus - callback status + \Input *pBuffer - pointer to buffered data, or NULL if no data + \Input iBufSize - amount of data available in buffer + \Input *pUserData - user data pointer + + \Output + int32_t - number of bytes consumed + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +#if !STREAMER_USECALLBACK +static int32_t _StreamUpdate(StreamCmdRefT *pCmdRef) +{ + static char aBuffer[64 * 1024]; + int32_t iResult; + + if ((iResult = ProtoStreamRead(pCmdRef->pProtoStream, aBuffer, sizeof(aBuffer), 64)) > 0) + { + + } + + return(iResult); +} +#endif + + +/* + Stream Commands +*/ + +/*F*************************************************************************************/ +/*! + \Function _SubcmdStreamCreate + + \Description + Stream subcommand - create the module + + \Input *_pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE means display command help + + \Output None. + + \Version 1.0 11/21/2005 (jbrookes) First Version +*/ +/**************************************************************************************F*/ +static void _SubcmdStreamCreate(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + StreamCmdRefT *pCmdRef; + int32_t iBufSize; + + // usage + if ((argc > 3) || (bHelp == TRUE)) + { + ZPrintf(" usage: %s create [bufsize]\n", argv[0]); + return; + } + + // get buffer size + iBufSize = (argc == 3) ? strtol(argv[2], NULL, 10) * 1024 : 32 * 1024; + + // allocate context + pCmdRef = _Stream_pCmdRef = ZMemAlloc(sizeof(*pCmdRef)); + ds_memclr(pCmdRef, sizeof(*pCmdRef)); + + // create the streaming module + pCmdRef->pProtoStream = ProtoStreamCreate(iBufSize); + +#if STREAMER_USECALLBACK + // set up callback info + ProtoStreamSetCallback(pCmdRef->pProtoStream, 100, _ProtoStreamCallback, pCmdRef); +#endif + + // allow shoutcast server compatibility (shoutcast returns "ICY" instead of "HTTP" response) + ProtoStreamControl(pCmdRef->pProtoStream, 'hver', FALSE, 0, NULL); + + // pretend to be WinAmp and enable metadata, as some servers require these + ProtoStreamControl(pCmdRef->pProtoStream, 'apnd', 0, 0, + "User-Agent: WinampMPEG/5.11\r\n" + "Icy-MetaData:1\r\n" + "Accept: */*\r\n"); + + // set default playlist + pCmdRef->pPlaylist = &_Playlist; + + // seed random number generator for random playlist traversal + srand(NetTick()); + // enable random play + pCmdRef->bRandomPlay = TRUE; +} + +/*F*************************************************************************************/ +/*! + \Function _SubcmdStreamDestroy + + \Description + Stream subcommand - destroy module + + \Input *_pCmdRef - module state + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE means display command help + + \Output None. + + \Version 1.0 11/21/2005 (jbrookes) First Version +*/ +/**************************************************************************************F*/ +static void _SubcmdStreamDestroy(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + StreamCmdRefT *pCmdRef = (StreamCmdRefT *)_pCmdRef; + + // validate arguments + if ((argc != 2) || bHelp) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + return; + } + + // close current strean, if any + _StreamClose(pCmdRef); + + // free playlist, if any + if (pCmdRef->pPlaylist != &_Playlist) + { + ZMemFree(pCmdRef->pPlaylist); + } + if (pCmdRef->pRandom != NULL) + { + ZMemFree(pCmdRef->pRandom); + } + + // destroy streamer + ProtoStreamDestroy(pCmdRef->pProtoStream); + + // destroy module state + ZMemFree(pCmdRef); + _Stream_pCmdRef = NULL; +} + +/*F*************************************************************************************/ +/*! + \Function _SubcmdStreamOpen + + \Description + Stream subcommand - open a stream + + \Input *_pCmdRef - module state + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE means display command help + + \Output None. + + \Version 1.0 11/21/2005 (jbrookes) First Version +*/ +/**************************************************************************************F*/ +static void _SubcmdStreamOpen(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + StreamCmdRefT *pCmdRef = (StreamCmdRefT *)_pCmdRef; + StreamStationT Station, *pStation; + int32_t iFreq, iStation = -1; + + // validate arguments + if (((argc != 3) && (argc != 4)) || bHelp) + { + ZPrintf(" usage: %s open [url|playlist|index] [freq]\n", argv[0]); + return; + } + + // playlist? + if (strstr(argv[2], ".xml")) + { + _StreamOpenPlaylist(pCmdRef, argv[2]); + _StreamDisplayPlaylist(pCmdRef, pCmdRef->pPlaylist); + return; + } + else if (!ds_strnicmp(argv[2], "http", 4)) // built-in url? + { + ds_memclr(&Station, sizeof(Station)); + ds_strnzcpy(Station.strUrl[0], argv[2], sizeof(Station.strUrl[0])); + pCmdRef->iCurUrl = 0; + pStation = &Station; + } + else // assume it is a playlist index + { + if ((iStation = strtol(argv[2], NULL, 10)) >= pCmdRef->pPlaylist->iNumStations) + { + iStation = 0; + } + pStation = &pCmdRef->pPlaylist->Stations[iStation]; + pCmdRef->iCurStation = pCmdRef->bRandomPlay ? _StreamGetRandIndex(pCmdRef, iStation) : iStation; + } + + // get restart frequency + iFreq = (argc == 4) ? strtol(argv[3], NULL, 10) : PROTOSTREAM_FREQ_IMMED; + + // reset url index + pCmdRef->iCurUrl = 0; + + // open the stream + _StreamOpen(pCmdRef, pStation, iFreq); +} + +/*F*************************************************************************************/ +/*! + \Function _SubcmdStreamClose + + \Description + Stream subcommand - close a stream + + \Input *_pCmdRef - module state + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE means display command help + + \Output None. + + \Version 1.0 11/21/2005 (jbrookes) First Version +*/ +/**************************************************************************************F*/ +static void _SubcmdStreamClose(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + StreamCmdRefT *pCmdRef = (StreamCmdRefT *)_pCmdRef; + + // validate arguments + if ((argc != 2) || bHelp) + { + ZPrintf(" usage: %s close\n", argv[0]); + return; + } + + // reset skip + pCmdRef->iSkipTime = 0; + + // destroy stream + _StreamClose(pCmdRef); +} + +/*F*************************************************************************************/ +/*! + \Function _SubcmdStreamSkip + + \Description + Stream subcommand - enable skip mode + + \Input *_pCmdRef - module state + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE means display command help + + \Output None. + + \Version 1.0 11/21/2005 (jbrookes) First Version +*/ +/**************************************************************************************F*/ +static void _SubcmdStreamSkip(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + StreamCmdRefT *pCmdRef = (StreamCmdRefT *)_pCmdRef; + + // validate arguments + if (((argc != 2) && (argc != 3)) || bHelp) + { + ZPrintf(" usage: %s skip [random]|[time]\n", argv[0]); + return; + } + + // activate? + if ((pCmdRef->iSkipTime == 0) || (argc == 3)) + { + // get skip time in seconds + pCmdRef->iSkipTime = (argc == 3) ? strtol(argv[2], NULL, 10) : STREAM_DEFAULTSKIP; + + // convert to milliseconds + pCmdRef->iSkipTime *= 1000; + } + else + { + // stop skipping + pCmdRef->iSkipTime = 0; + } +} + +/*F*************************************************************************************/ +/*! + \Function _SubcmdStreamControl + + \Description + Stream subcommand - call control function + + \Input *_pCmdRef - module state + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - TRUE means display command help + + \Output None. + + \Version 1.0 11/21/2005 (jbrookes) First Version +*/ +/**************************************************************************************F*/ +static void _SubcmdStreamControl(void *_pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + StreamCmdRefT *pCmdRef = (StreamCmdRefT *)_pCmdRef; + int32_t iCmd, iValue=0, iValue2=0; + void *pValue=NULL; + + if ((bHelp == TRUE) || (argc < 3)) + { + ZPrintf(" usage: %s ctrl \n", argv[0]); + return; + } + + iCmd = argv[2][0] << 24; + iCmd |= argv[2][1] << 16; + iCmd |= argv[2][2] << 8; + iCmd |= argv[2][3]; + + if (argc > 3) + { + iValue = strtol(argv[3], NULL, 10); + } + + if (argc > 4) + { + iValue2 = strtol(argv[4], NULL, 10); + } + + // handle stream-specific commands + if (iCmd == 'rand') + { + pCmdRef->bRandomPlay = !pCmdRef->bRandomPlay; + ZPrintf("stream: random play %s\n", pCmdRef->bRandomPlay ? "enabled" : "disabled"); + return; + } + // pass unhandled selectors to ProtoStreamControl(); + ZPrintf("stream: executing ProtoStreamControl(pProtoUpnp, '%s', %d, %d, %s)\n", argv[2], iValue, iValue2, pValue ? pValue : "(null)"); + ProtoStreamControl(pCmdRef->pProtoStream, iCmd, iValue, iValue2, pValue); +} + +/*F********************************************************************************/ +/*! + \Function _CmdStreamCb + + \Description + Recurring stream callback. + + \Input *argz - environment + \Input argc - number of args + \Input *argv[] - argument list + + \Output int32_t - standard return code + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdStreamCb(ZContext *argz, int32_t argc, char *argv[]) +{ + StreamCmdRefT *pCmdRef = _Stream_pCmdRef; + int32_t iCurTick = ZTick(); + + // if no ref, we're done + if (pCmdRef == NULL) + { + return(0); + } + + // check for kill + if (argc == 0) + { + char *strArgs[2] = { "stream", "destroy" }; + ZPrintf("%s: killed\n", argv[0]); + _SubcmdStreamDestroy(pCmdRef, 2, strArgs, 0); + return(0); + } + + // update skip processing + if (pCmdRef->iSkipTime != 0) + { + if ((iCurTick - pCmdRef->iSkipTick) > pCmdRef->iSkipTime) + { + _StreamSkip(pCmdRef, iCurTick); + } + } + + // update protostream module + ProtoStreamUpdate(pCmdRef->pProtoStream); + + #if !STREAMER_USECALLBACK + _StreamUpdate(pCmdRef); + #endif + + // keep running + return(ZCallback(&_CmdStreamCb, 16)); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdStream + + \Description + Create the Module module. + + \Input *argz - environment + \Input argc - number of args + \Input *argv[] - argument list + + \Output int32_t - standard return code + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdStream(ZContext *argz, int32_t argc, char *argv[]) +{ + unsigned char bHelp, bCreate = FALSE; + StreamCmdRefT *pCmdRef = _Stream_pCmdRef; + T2SubCmdT *pCmd; + + // handle basic help + if ((argc < 2) || (((pCmd = T2SubCmdParse(_Stream_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" test the protostream module\n"); + T2SubCmdUsage(argv[0], _Stream_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmdRef == NULL) && strcmp(pCmd->strName, "create")) + { + char *pCreate = "create"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _SubcmdStreamCreate(pCmdRef, 1, &pCreate, bHelp); + pCmdRef = _Stream_pCmdRef; + bCreate = TRUE; + } + + // hand off to command + pCmd->pFunc(pCmdRef, argc, argv, bHelp); + + // if we executed create, remember + if (pCmd->pFunc == _SubcmdStreamCreate) + { + bCreate = TRUE; + } + + // if we executed create, install periodic callback + return((bCreate == TRUE) ? ZCallback(_CmdStreamCb, 100) : 0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/string.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/string.c new file mode 100644 index 00000000..e0861482 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/string.c @@ -0,0 +1,138 @@ +/*H********************************************************************************/ +/*! + \File string.c + + \Description + Test the plat-str functionality. + + \Copyright + Copyright (c) 2012 Electronic Arts + + \Version 10/01/2012 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "libsample/zlib.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +// Variables + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _StringCompareWild + + \Description + Compare two strings with wildcard matching and print the result + + \Input bExpected - expected result of comparison (true or false) + \Input *pStrTest - pointer to string to match against + \Input *pStrWild - pointer to wildcard string to match with + \Input bNoCase - TRUE for case insensitive, else FALSE + + \Output + int32_t - 0=expected result, 1=different result + + \Version 10/01/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _StringCompareWild(uint32_t bExpected, const char *pStrTest, const char *pStrWild, uint8_t bNoCase) +{ + uint32_t bMatch; + int32_t iResult; + + if (bNoCase) + { + bMatch = ds_stricmpwc(pStrTest, pStrWild) == 0 ? TRUE : FALSE; + } + else + { + bMatch = ds_strcmpwc(pStrTest, pStrWild) == 0 ? TRUE : FALSE; + } + + iResult = (bMatch != bExpected) ? 1 : 0; + ZPrintf("string: %s; strtest(%s) %s strwild(%s)\n", iResult ? "failure" : "success", pStrTest, bMatch ? "matched" : "did not match", pStrWild); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _StringTestCompareWild + + \Description + Perform wild-card string comparison tests + + \Output + int32_t - 0=passed, else failed + + \Version 10/01/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _StringTestCompareWild(void) +{ + int32_t iResult = 0; + + ZPrintf("string: String wild-card comparison tests\n"); + + iResult += _StringCompareWild(TRUE, "gosca.ea.com", "*.ea.com", FALSE); + iResult += _StringCompareWild(FALSE, "gosca.ea.com", "*.online.ea.com", FALSE); + + ZPrintf("string: String wild-card case-insensitive comparison tests\n"); + + iResult += _StringCompareWild(TRUE, "GOSCA.EA.COM", "*.ea.com", TRUE); + iResult += _StringCompareWild(FALSE, "GOSCA.EA.COM", "*.online.ea.com", TRUE); + + ZPrintf("string: %d string test discrepencies\n", iResult); + ZPrintf("string: ------------------------------------\n"); + return(iResult); +} + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function CmdString + + \Description + Test platform string functions + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output standard return value + + \Version 10/01/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdString(ZContext *argz, int32_t argc, char *argv[]) +{ + int32_t iResult = 0; + + ZPrintf("string: ------------------------------------------------\n"); + ZPrintf("string: Testing platform string (non-printing) functions\n"); + ZPrintf("string: ------------------------------------------------\n"); + + iResult += _StringTestCompareWild(); + + ZPrintf("string: Test results: %d total discrepencies\n", iResult); + ZPrintf("string: ----------------------------------------------\n"); + + return(0); +} + + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/time.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/time.c new file mode 100644 index 00000000..af5dab4a --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/time.c @@ -0,0 +1,172 @@ +/*H********************************************************************************/ +/*! + \File time.c + + \Description + Test the plat-time functionality. + + \Copyright + Copyright (c) 2012 Electronic Arts + + \Version 07/12/2012 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "libsample/zlib.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct TimeStrToTimeT +{ + char strTime[32]; + uint64_t uTime; + TimeToStringConversionTypeE eConvType; +} TimeStrToTimeT; + +/*** Variables ********************************************************************/ + +// Variables + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _TimeStrToTime + + \Description + Execute ds_strtotime/ds_strtotime2 and compare to expected result + + \Output + int32_t - 0=success, 1=failed + + \Version 02/27/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _TimeStrToTime(const TimeStrToTimeT *pTime, int32_t iTest) +{ + uint64_t uEpoch = ds_strtotime2(pTime->strTime, pTime->eConvType); + if (uEpoch != pTime->uTime) + { + ZPrintf("time: ds_strtotime2() test %d failed; got=%qd, expected=%qd\n", uEpoch, pTime->uTime); + } + return(uEpoch != pTime->uTime); +} + +/*F********************************************************************************/ +/*! + \Function _TimeTestStrToTime + + \Description + Test ds_strtotime/ds_strtotime2 + + \Output + int32_t - number of test result failures + + \Version 02/27/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _TimeTestStrToTime(void) +{ + static const TimeStrToTimeT _Times[] = + { + { "Sun, 06 Nov 1994 08:49:37 GMT", 784111777, TIMETOSTRING_CONVERSION_UNKNOWN }, // RFC 822/1123 + { "Sun Nov 6 08:49:37 1994", 784111777, TIMETOSTRING_CONVERSION_UNKNOWN }, // asctime + { "1994-11-06T08:49:37Z", 784111777, TIMETOSTRING_CONVERSION_ISO_8601 }, + { "941106084937", 784111777, TIMETOSTRING_CONVERSION_ASN1_UTCTIME }, + { "641106084937", 2993186977, TIMETOSTRING_CONVERSION_ASN1_UTCTIME }, + { "701106084937", 26729377, TIMETOSTRING_CONVERSION_ASN1_UTCTIME }, + { "9411060849", 784111740, TIMETOSTRING_CONVERSION_ASN1_UTCTIME }, + { "19941106084937", 784111777, TIMETOSTRING_CONVERSION_ASN1_GENTIME }, + { "19941106084937.123", 784111777, TIMETOSTRING_CONVERSION_ASN1_GENTIME }, + { "20390101000000", 2177452800, TIMETOSTRING_CONVERSION_ASN1_GENTIME }, + { "20501106084937", 2551337377, TIMETOSTRING_CONVERSION_ASN1_GENTIME }, + { "21500621143040", 5695108240, TIMETOSTRING_CONVERSION_ASN1_GENTIME } + }; + int32_t iTime, iResult = 0; + for (iResult = 0, iTime = 0; iTime < (int32_t)(sizeof(_Times)/sizeof(_Times[0])); iTime += 1) + { + iResult += _TimeStrToTime(&_Times[iTime], iTime); + } + return(iResult); +} + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function CmdTime + + \Description + Test the time functions + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output standard return value + + \Version 07/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdTime(ZContext *argz, int32_t argc, char *argv[]) +{ + char strBuf[20]; + struct tm curtime; + uint64_t uTime; + int32_t iZone; + void *pPlatTime = NULL; + int32_t iResult = 0; + + // get current time... this is equivalent to time(0) + uTime = ds_timeinsecs(); + ZPrintf("time: ds_timeinsecs()=%qd\n", uTime); + + // convert to tm time + ds_secstotime(&curtime, uTime); + ZPrintf("time: ds_secstotime()=%d/%d/%d %02d:%02d:%02d\n", + curtime.tm_mon+1, curtime.tm_mday, curtime.tm_year+1900, + curtime.tm_hour, curtime.tm_min, curtime.tm_sec); + + // test ISO_8601 conversion + ZPrintf("time: ds_timetostr(ISO_8601)=%s\n", ds_timetostr(&curtime, TIMETOSTRING_CONVERSION_ISO_8601, 1, strBuf, sizeof(strBuf))); + + // test RFC_0822 conversion + ZPrintf("time: ds_timetostr(RFC_0822)=%s\n", ds_timetostr(&curtime, TIMETOSTRING_CONVERSION_RFC_0822, 1, strBuf, sizeof(strBuf))); + + // get localtime + ds_memclr(&curtime, sizeof(curtime)); + ds_localtime(&curtime, uTime); + ZPrintf("time: ds_localtime()=%d/%d/%d %d:%d:%d\n", + curtime.tm_mon+1, curtime.tm_mday, curtime.tm_year+1900, + curtime.tm_hour, curtime.tm_min, curtime.tm_sec); + + // timezone (delta between local and GMT time in seconds) + iZone = ds_timezone(); + ZPrintf("time: ds_timezone=%ds, %dh\n", iZone, iZone/(60*60)); + + // test ds_strtotime() + iResult += _TimeTestStrToTime(); + ZPrintf("time: %d failed strtotime tests\n", iResult); + + if (pPlatTime != NULL) + { + ds_memclr(&curtime, sizeof(curtime)); + ds_plattimetotime(&curtime, pPlatTime); + ZPrintf("time: ds_plattimetotime()=%d/%d/%d %d:%d:%d\n", + curtime.tm_mon+1, curtime.tm_mday, curtime.tm_year+1900, + curtime.tm_hour, curtime.tm_min, curtime.tm_sec); + } + + return(0); +} + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/tunnel.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/tunnel.c new file mode 100644 index 00000000..887537f3 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/tunnel.c @@ -0,0 +1,367 @@ +/*H********************************************************************************/ +/*! + \File tunnel.c + + \Description + A tester command to test ProtoTunnel + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 12/02/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/voip/voip.h" +#include "DirtySDK/proto/prototunnel.h" + +#include "libsample/zlib.h" +#include "libsample/zmem.h" +#include "testerregistry.h" +#include "testersubcmd.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct TunnelAppT +{ + ProtoTunnelRefT *pProtoTunnel; + unsigned char bZCallback; +} TunnelAppT; + +/*** Function Prototypes ***************************************************************/ + +static void _TunnelCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _TunnelDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _TunnelAlloc(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _TunnelFree(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _TunnelControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); + +/*** Variables ********************************************************************/ + +static T2SubCmdT _Tunnel_Commands[] = +{ + { "create", _TunnelCreate }, + { "destroy", _TunnelDestroy }, + { "alloc", _TunnelAlloc }, + { "free", _TunnelFree }, + { "ctrl", _TunnelControl }, + { "", NULL } +}; + +static TunnelAppT _Tunnel_App = { NULL, FALSE }; + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _TunnelCreate + + \Description + Tunnel subcommand - create tunnel module + + \Input *pApp - pointer to tunnel module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _TunnelCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + TunnelAppT *pApp = &_Tunnel_App; + int32_t iTunnelPort; + uint32_t uClientId; + + if ((bHelp == TRUE) || (argc < 3)) + { + ZPrintf(" usage: %s create [port]\n", argv[0]); + return; + } + + // get the port + iTunnelPort = (argc == 4) ? (int32_t)strtol(argv[3], NULL, 10) : 9600; + + // create the tunnel + pApp->pProtoTunnel = ProtoTunnelCreate(4, iTunnelPort); + + // set client id + uClientId = (uint32_t)strtol(argv[2], NULL, 16); + ProtoTunnelControl(pApp->pProtoTunnel, 'clid', uClientId, 0, NULL); +} + +/*F********************************************************************************/ +/*! + \Function _TunnelDestroy + + \Description + Tunnel subcommand - destroy tunnel module + + \Input *pApp - pointer to tunnel module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _TunnelDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + TunnelAppT *pApp = &_Tunnel_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s create\n", argv[0]); + return; + } + + if (pApp->pProtoTunnel != NULL) + { + ProtoTunnelDestroy(pApp->pProtoTunnel); + ds_memclr(&pApp, sizeof(pApp)); + } +} + +/*F********************************************************************************/ +/*! + \Function _TunnelAlloc + + \Description + Tunnel subcommand - allocate a tunnel + + \Input *pApp - pointer to tunnel module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 12/07/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _TunnelAlloc(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + TunnelAppT *pApp = &_Tunnel_App; + ProtoTunnelInfoT Info; + char *pKey, strKey[] = "24906usdfnasdf1&"; + + if ((bHelp == TRUE) || (argc < 5)) + { + ZPrintf(" usage: %s alloc [clientId] [addr] [port] \n", argv[0]); + return; + } + + // ref key + pKey = (argc == 6) ? argv[5] : strKey; + + ds_memclr(&Info, sizeof(Info)); + Info.uRemoteClientId = (uint32_t)strtol(argv[2], 0, 16); + Info.uRemoteAddr = SocketInTextGetAddr(argv[3]); + Info.uRemotePort = strtol(argv[4], 0, 10); + //$$ hardcode for now + Info.aRemotePortList[0] = 3658; + Info.aPortFlags[0] = PROTOTUNNEL_PORTFLAG_ENCRYPTED; + Info.aRemotePortList[1] = VOIP_PORT; + Info.aPortFlags[1] = PROTOTUNNEL_PORTFLAG_ENCRYPTED; + + // allocate a mapping + ZPrintf("%s: tunnel alloc clientId=0x%08x addr=%a port=%d key=%s\n", argv[0], Info.uRemoteClientId, Info.uRemoteAddr, Info.uRemotePort, pKey); + ProtoTunnelAlloc(pApp->pProtoTunnel, &Info, pKey); +} + +/*F********************************************************************************/ +/*! + \Function _TunnelFree + + \Description + Tunnel subcommand - free a tunnel + + \Input *pApp - pointer to tunnel module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 12/07/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _TunnelFree(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + TunnelAppT *pApp = &_Tunnel_App; + uint32_t uTunnelId; + const char *pKey; + + if ((bHelp == TRUE) || (argc < 3)) + { + ZPrintf(" usage: %s free [tunnelId] \n", argv[0]); + return; + } + + // ref key + pKey = (argc == 4) ? argv[3] : NULL; + + // free a mapping + uTunnelId = (uint32_t)strtol(argv[2], NULL, 16); + ProtoTunnelFree(pApp->pProtoTunnel, uTunnelId, pKey); +} + +/*F********************************************************************************/ +/*! + \Function _TunnelControl + + \Description + Tunnel subcommand - execute a UPnP command + + \Input *pApp - pointer to tunnel module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _TunnelControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + TunnelAppT *pApp = &_Tunnel_App; + int32_t iCmd, iValue, iValue2; + const char *pValue=NULL; + + if ((bHelp == TRUE) || (argc < 3)) + { + ZPrintf(" usage: %s ctrl [parm]...\n", argv[0]); + return; + } + + iCmd = argv[2][0] << 24; + iCmd |= argv[2][1] << 16; + iCmd |= argv[2][2] << 8; + iCmd |= argv[2][3]; + + iValue = (argc > 3) ? (int32_t)strtol(argv[3], NULL, 10) : 0; + iValue2 = (argc > 4) ? (int32_t)strtol(argv[4], NULL, 10) : 0; + + ZPrintf("tunnel: executing ProtoTunnelControl(pProtoTunnel, '%s', %d, %d, %s)\n", argv[2], iValue, iValue2, pValue ? pValue : "(null)"); + ProtoTunnelControl(pApp->pProtoTunnel, iCmd, iValue, iValue2, pValue); +} + +/*F********************************************************************************/ +/*! + \Function _CmdTunnelCb + + \Description + Tunnel callback, called after command has been issued. + + \Input *argz - pointer to context + \Input argc - number of command-line arguments + \Input *argv[] - command-line argument list + + \Output + int32_t - result of zcallback, or zero to terminate + + \Version 1.0 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdTunnelCb(ZContext *argz, int32_t argc, char *argv[]) +{ + TunnelAppT *pApp = &_Tunnel_App; + + // check for kill + if (argc == 0) + { + ZPrintf("%s: killed\n", argv[0]); + ProtoTunnelDestroy(pApp->pProtoTunnel); + return(0); + } + + // update module + if (pApp->pProtoTunnel != NULL) + { + ProtoTunnelUpdate(pApp->pProtoTunnel); + } + + // keep recurring + return(ZCallback(&_CmdTunnelCb, 17)); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdTunnel + + \Description + Tunnel command. This command starts the ProtoTunnel module. + + \Input *argz - pointer to context + \Input argc - number of command-line arguments + \Input *argv[] - command-line argument list + + \Output + int32_t - result of zcallback + + \Version 1.0 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdTunnel(ZContext *argz, int32_t argc, char *argv[]) +{ + T2SubCmdT *pCmd; + TunnelAppT *pApp = &_Tunnel_App; + unsigned char bHelp; + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_Tunnel_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" test the prototunnel module\n"); + T2SubCmdUsage(argv[0], _Tunnel_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmd->pFunc != _TunnelCreate) && (pApp->pProtoTunnel == NULL)) + { + char *pCreate = "create"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _TunnelCreate(pApp, 1, &pCreate, bHelp); + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + // one-time install of periodic callback + if (pApp->bZCallback == FALSE) + { + pApp->bZCallback = TRUE; + return(ZCallback(_CmdTunnelCb, 17)); + } + else + { + return(0); + } +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/upnp.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/upnp.c new file mode 100644 index 00000000..62837b62 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/upnp.c @@ -0,0 +1,327 @@ +/*H********************************************************************************/ +/*! + \File upnp.c + + \Description + A tester command to test ProtoUpnp + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/23/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/proto/protoupnp.h" + +#include "libsample/zlib.h" +#include "libsample/zmem.h" +#include "libsample/zfile.h" + +#include "testerregistry.h" +#include "testersubcmd.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct UpnpAppT +{ + ProtoUpnpRefT *pProtoUpnp; + + unsigned char bZCallback; +} UpnpAppT; + +/*** Function Prototypes ***************************************************************/ + +static void _UpnpCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _UpnpDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _UpnpControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _UpnpFakeResponse(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); + +/*** Variables ********************************************************************/ + +static T2SubCmdT _Upnp_Commands[] = +{ + { "create", _UpnpCreate }, + { "destroy", _UpnpDestroy }, + { "ctrl", _UpnpControl }, + { "fake", _UpnpFakeResponse }, + { "", NULL } +}; + +static UpnpAppT _Upnp_App = { NULL, FALSE }; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _UpnpCreate + + \Description + Upnp subcommand - create upnp module + + \Input *pApp - pointer to upnp module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 09/27/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _UpnpCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + UpnpAppT *pApp = &_Upnp_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s create\n", argv[0]); + return; + } + + pApp->pProtoUpnp = ProtoUpnpCreate(); +} + +/*F********************************************************************************/ +/*! + \Function _UpnpDestroy + + \Description + Upnp subcommand - destroy upnp module + + \Input *pApp - pointer to upnp module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 09/27/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _UpnpDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + UpnpAppT *pApp = &_Upnp_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + return; + } + + ProtoUpnpDestroy(pApp->pProtoUpnp); +} + +/*F********************************************************************************/ +/*! + \Function _UpnpControl + + \Description + Upnp subcommand - execute a UPnP command + + \Input *pApp - pointer to upnp module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 09/27/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _UpnpControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + UpnpAppT *pApp = &_Upnp_App; + int32_t iCmd, iValue=0, iValue2=0; + const char *pValue=NULL; + + if ((bHelp == TRUE) || (argc < 3)) + { + ZPrintf(" usage: %s command \n", argv[0]); + return; + } + + iCmd = argv[2][0] << 24; + iCmd |= argv[2][1] << 16; + iCmd |= argv[2][2] << 8; + iCmd |= argv[2][3]; + + if (argc == 4) + { + if (iCmd == 'macr') + { + iValue = argv[3][0] << 24; + iValue |= argv[3][1] << 16; + iValue |= argv[3][2] << 8; + iValue |= argv[3][3]; + } + else if (iCmd == 'gvar') + { + pValue = argv[3]; + } + else + { + iValue = (int32_t)strtol(argv[3], NULL, 10); + } + } + + ZPrintf("upnp: executing ProtoUpnpControl(pProtoUpnp, '%s', %d, %d, %s)\n", argv[2], iValue, iValue2, pValue ? pValue : "(null)"); + ProtoUpnpControl(pApp->pProtoUpnp, iCmd, iValue, iValue2, pValue); +} + +/*F********************************************************************************/ +/*! + \Function _UpnpFakeResponse + + \Description + Upnp subcommand - fake a upnp response + + \Input *pApp - pointer to upnp module + \Input argc - argument count + \Input *argv[] - argument list + \Input bHelp - true if help request, else false + + \Output + None. + + \Version 1.0 09/27/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _UpnpFakeResponse(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + UpnpAppT *pApp = &_Upnp_App; + char *pResponseFile; + int32_t iCmd, iFileSize; + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s fake \n", argv[0]); + return; + } + + // assemble command + iCmd = argv[2][0] << 24; + iCmd |= argv[2][1] << 16; + iCmd |= argv[2][2] << 8; + iCmd |= argv[2][3]; + + // load response file + if ((pResponseFile = ZFileLoad(argv[3], &iFileSize, ZFILE_OPENFLAG_RDONLY)) == NULL) + { + ZPrintf("%s: unable to open response file '%s'\n", argv[3]); + return; + } + + // fake the command + ProtoUpnpControl(pApp->pProtoUpnp, 'fake', iCmd, 0, pResponseFile); + + // unload the file + ZMemFree(pResponseFile); +} + +/*F********************************************************************************/ +/*! + \Function _CmdUpnpCb + + \Description + Upnp callback, called after command has been issued. + + \Input *argz - pointer to context + \Input argc - number of command-line arguments + \Input *argv[] - command-line argument list + + \Output + int32_t - result of zcallback, or zero to terminate + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdUpnpCb(ZContext *argz, int32_t argc, char *argv[]) +{ + UpnpAppT *pApp = &_Upnp_App; + + // check for kill + if (argc == 0) + { + ZPrintf("%s: killed\n", argv[0]); + ProtoUpnpDestroy(pApp->pProtoUpnp); + return(0); + } + + // update module + ProtoUpnpUpdate(pApp->pProtoUpnp); + + // keep recurring + return(ZCallback(&_CmdUpnpCb, 17)); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdUpnp + + \Description + Upnp command. This command starts the ProtoUpnp module. + + \Input *argz - pointer to context + \Input argc - number of command-line arguments + \Input *argv[] - command-line argument list + + \Output + int32_t - result of zcallback + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdUpnp(ZContext *argz, int32_t argc, char *argv[]) +{ + T2SubCmdT *pCmd; + UpnpAppT *pApp = &_Upnp_App; + unsigned char bHelp; + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_Upnp_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" test the protoupnp module\n"); + T2SubCmdUsage(argv[0], _Upnp_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmd->pFunc != _UpnpCreate) && (pApp->pProtoUpnp == NULL)) + { + char *pCreate = "create"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _UpnpCreate(pApp, 1, &pCreate, bHelp); + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + // one-time install of periodic callback + if (pApp->bZCallback == FALSE) + { + pApp->bZCallback = TRUE; + return(ZCallback(_CmdUpnpCb, 17)); + } + else + { + return(0); + } +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/user.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/user.c new file mode 100644 index 00000000..f9c82d82 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/user.c @@ -0,0 +1,725 @@ +/*H*************************************************************************************/ +/*! + \File user.c + + \Description + Reference application for the userapi. + + \Copyright + Copyright (c) Electronic Arts 2014. ALL RIGHTS RESERVED. + + \Version 18/02/2014 (amakoukji) +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/misc/userapi.h" + +#include "libsample/zlib.h" +#include "libsample/zfile.h" +#include "libsample/zmem.h" +#include "testersubcmd.h" +#include "testermodules.h" + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct UaAppT +{ + UserApiRefT *pUa; + + uint8_t bStarted; + + unsigned char bZCallback; +} UaAppT; + +/*** Function Prototypes ***************************************************************/ + +static void _UaCreate(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UaControl(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UaDestroy(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UaGetProfiles(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UaGetProfile(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UaRegister(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UaRecentlyMet(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UaSetRichPresence(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static int32_t _CmdUaTickCb(ZContext *argz, int32_t argc, char *argv[]); +static void _CmdUaCb(UserApiRefT *pRef, UserApiEventDataT *pUserApiEventData, void *pUserData); +static void _CmdUaUpdateCb(UserApiRefT *pRef, UserApiNotifyTypeE eNotifyType, UserApiNotifyDataT *pData, void *pUserData); +static void _CmdUaPostCb(UserApiRefT *pRef, UserApiPostResponseT *pResponse, void *pUserData); + +/*** Variables *************************************************************************/ + +// Private variables +static T2SubCmdT _Ua_Commands[] = +{ + { "create", _UaCreate }, + { "control", _UaControl }, + { "destroy", _UaDestroy }, + { "getprofiles", _UaGetProfiles }, + { "getprofile", _UaGetProfile }, + { "register", _UaRegister }, + { "recentlymet", _UaRecentlyMet }, + { "setrichpresence", _UaSetRichPresence }, + { "", NULL } +}; + +static UaAppT _Ua_App; + +// Public variables + +/*** Private Functions *****************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _CmdUaTickCb + + \Description + UserList callback, called after command has been issued. + + \Input *argz - pointer to context + \Input argc - number of command-line arguments + \Input *argv[] - command-line argument list + + \Output + int32_t - result of zcallback, or zero to terminate + + \Version 18/02/2014 (amakoukji) +*/ +/********************************************************************************F*/ +static int32_t _CmdUaTickCb(ZContext *argz, int32_t argc, char *argv[]) +{ + UaAppT *pApp = &_Ua_App; + + if (pApp->bStarted != FALSE) + { + // check for kill + if (argc == 0) + { + ZPrintf("%s: killed\n", argv[0]); + UserApiDestroy(pApp->pUa); + return(0); + } + + // update module + if (pApp->pUa != NULL) + { + UserApiUpdate(pApp->pUa); + } + + // keep recurring + return(ZCallback(&_CmdUaTickCb, 17)); + } + + return(ZLIB_STATUS_UNKNOWN); +} + +/*F********************************************************************************/ +/*! + \Function _CmdUaCb + + \Description + user callback + + \Input *pRef - UserListApiRefT reference + \Input *pUserApiEventData - result data struct + \Input pUserData - NULL + + + \Version 18/02/2014 (amakoukji) +*/ +/********************************************************************************F*/ +static void _CmdUaCb(UserApiRefT *pRef, UserApiEventDataT *pUserApiEventData, void *pUserData) +{ + uint64_t uUserId; + + if (pUserApiEventData->eEventType == USERAPI_EVENT_DATA) + { + DirtyUserToNativeUser(&uUserId, sizeof(uUserId), &(pUserApiEventData->EventDetails.UserData.DirtyUser)); + ZPrintf("user: profile data \n\t id %llu ", uUserId); + + if (pUserApiEventData->EventDetails.UserData.uUserDataMask & USERAPI_MASK_PROFILE) + { + ZPrintf("\n\twith gamertag %s and avatar at %s ", pUserApiEventData->EventDetails.UserData.Profile.strGamertag, pUserApiEventData->EventDetails.UserData.Profile.strAvatarUrl); + } + + if (pUserApiEventData->EventDetails.UserData.uUserDataMask & USERAPI_MASK_PRESENCE) + { + ZPrintf("\n\t%s ", (pUserApiEventData->EventDetails.UserData.Presence.ePresenceStatus == USERAPI_PRESENCE_ONLINE) ? "is online" : + (pUserApiEventData->EventDetails.UserData.Presence.ePresenceStatus == USERAPI_PRESENCE_AWAY) ? "is away" : "is offline"); + if (pUserApiEventData->EventDetails.UserData.Presence.ePresenceStatus == USERAPI_PRESENCE_ONLINE) + { + ZPrintf("\n\tin title %s on plaform %s", pUserApiEventData->EventDetails.UserData.Presence.strTitleName, pUserApiEventData->EventDetails.UserData.Presence.strPlatform); + } + } + + if (pUserApiEventData->EventDetails.UserData.uUserDataMask & USERAPI_MASK_RICH_PRESENCE) + { + ZPrintf("\n\twith rich presence \"%s\"", pUserApiEventData->EventDetails.UserData.RichPresence.strData); + } + ZPrintf("\n"); + } + else if (pUserApiEventData->eEventType == USERAPI_EVENT_END_OF_LIST) + { + ZPrintf("user: End of profiles report\n \tTotal Requested : %d\n\tTotal Received : %d\n\tTotalErrors : %d\n", pUserApiEventData->EventDetails.EndOfList.iTotalRequested, + pUserApiEventData->EventDetails.EndOfList.iTotalReceived, + pUserApiEventData->EventDetails.EndOfList.iTotalErrors); + } +} + +/*F********************************************************************************/ +/*! + \Function _CmdUaUpdateCb + + \Description + user callback for 1st party notifications + + \Input *pRef - UserListApiRefT reference + \Input eNotifyType - type of callback + \Input *pUserApiEventData - result data struct + \Input pUserData - NULL + + + \Version 18/02/2014 (amakoukji) +*/ +/********************************************************************************F*/ +static void _CmdUaUpdateCb(UserApiRefT *pRef, UserApiNotifyTypeE eNotifyType, UserApiNotifyDataT *pData, void *pUserData) +{ + uint64_t uUserId; + + if (eNotifyType == USERAPI_NOTIFY_PRESENCE_UPDATE) + { + DirtyUserToNativeUser(&uUserId, sizeof(uUserId), &(pData->PresenceData.DirtyUser)); + ZPrintf("user: presence update for id %llu\n", uUserId); + } + else if (eNotifyType == USERAPI_NOTIFY_TITLE_UPDATE) + { + DirtyUserToNativeUser(&uUserId, sizeof(uUserId), &(pData->TitleData.DirtyUser)); + ZPrintf("user: title update for id %llu\n", uUserId); + } + else if (eNotifyType == USERAPI_NOTIFY_RICH_PRESENCE_UPDATE) + { + DirtyUserToNativeUser(&uUserId, sizeof(uUserId), &(pData->RichPresenceData.DirtyUser)); + ZPrintf("user: rich presence update for id %llu\n", uUserId); + } + + +} + +/*F********************************************************************************/ +/*! + \Function _CmdUaPostCb + + \Description + user callback for posted data + + \Input *pRef - UserListApiRefT reference + \Input *pResponse - result data struct + \Input pUserData - NULL + + + \Version 18/02/2014 (amakoukji) +*/ +/********************************************************************************F*/ +static void _CmdUaPostCb(UserApiRefT *pRef, UserApiPostResponseT *pResponse, void *pUserData) +{ + ZPrintf("user: response from data push, error code=%d, message=\"%s\"\n", pResponse->eError, pResponse->pMessage); +} + +/*F*************************************************************************************/ +/*! + \Function _UaCreate + + \Description + Udp subcommand - create udp socket + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UaCreate(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UaAppT *pApp = (UaAppT *)_pApp; + + if ((bHelp == TRUE) || (argc != 2 )) + { + ZPrintf(" usage: %s create\n", argv[0]); + return; + } + + if (pApp->bStarted) + { + ZPrintf("%s: user already created\n", argv[0]); + return; + } + + // allocate UserApi module + if ((pApp->pUa = UserApiCreate()) == NULL) + { + ZPrintf("%s: unable to create protoudp module\n", argv[0]); + return; + } + + pApp->bStarted = TRUE; + + // one-time install of periodic callback + if (pApp->bZCallback == FALSE) + { + pApp->bZCallback = TRUE; + ZCallback(_CmdUaTickCb, 17); + } +} + +/*F*************************************************************************************/ +/*! + \Function _UaControl + + \Description + Call UserApiControl + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UaControl(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UaAppT *pApp = (UaAppT *)_pApp; + int32_t iResult; + int32_t iControl = 0; + int32_t iValue = 0; + int32_t iValue2 = 0; + + if ((bHelp == TRUE) || (argc != 6) || strlen(argv[2]) != 4) + { + ZPrintf(" usage: %s control [4 character selector] [iValue] [iValue2] [pValue]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: user not yet created\n", argv[0]); + return; + } + + iControl += argv[2][0] << 24; + iControl += argv[2][1] << 16; + iControl += argv[2][2] << 8; + iControl += argv[2][3]; + iValue = strtol(argv[3], NULL, 10); + iValue2 = strtol(argv[4], NULL, 10); + iResult = UserApiControl(pApp->pUa, iControl, iValue, iValue2, argv[5]); + ZPrintf("control result for %s\n", argv[2], iResult); +} + +/*F*************************************************************************************/ +/*! + \Function _UaDestroy + + \Description + Destroy UserApi + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UaDestroy(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UaAppT *pApp = (UaAppT *)_pApp; + + if ((bHelp == TRUE) || (argc != 2)) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: user not yet created\n", argv[0]); + return; + } + + UserApiDestroy(pApp->pUa); + pApp->bZCallback = FALSE; + pApp->bStarted = FALSE; +} + +/*F*************************************************************************************/ +/*! + \Function _UaGetProfiles + + \Description + Batch profile request. Cannot get presence concurrently. Max 100 + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UaGetProfiles(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UaAppT *pApp = (UaAppT *)_pApp; + int32_t iResult; + uint32_t uUserIndex = 0; + char *pch; + uint32_t uNumUsers = 0; + DirtyUserT aDirtyUsers[100]; + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s getprofiles [user index] [comma seperated user user ids]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: user not yet created\n", argv[0]); + return; + } + + uUserIndex = (uint32_t)strtol(argv[2], NULL, 10); + ds_memclr(aDirtyUsers, sizeof(aDirtyUsers)); + + // parse list + pch = strtok (argv[3],","); + while (pch != NULL) + { + uint64_t uUserId = ds_strtoull(pch, NULL, 10); + DirtyUserFromNativeUser(&aDirtyUsers[uNumUsers++], &uUserId); + pch = strtok (NULL, ","); + } + + iResult = UserApiRequestProfilesAsync(pApp->pUa, uUserIndex, aDirtyUsers, uNumUsers, &_CmdUaCb, NULL); + if (iResult == USERAPI_ERROR_UNSUPPORTED) + { + ZPrintf("getprofiles is not supported on this platform\n", iResult); + } + else + { + ZPrintf("getprofiles: UserApiRequestProfilesAsync() returned %d\n", iResult); + } +} + +/*F*************************************************************************************/ +/*! + \Function _UaGetProfile + + \Description + Profile request. Can get presence concurrently + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UaGetProfile(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UaAppT *pApp = (UaAppT *)_pApp; + int32_t iResult; + uint32_t uUserIndex = 0; + char *pCh; + uint32_t uUserDataMask = 0; + DirtyUserT DirtyUser; + uint64_t uUserId; + int32_t iBase; + + if ((bHelp == TRUE) || (argc != 5)) + { + ZPrintf(" usage: %s getprofile [user index] [user ids] [comma seperated flags in profile,presence,richpresence]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: user not yet created\n", argv[0]); + return; + } + + uUserIndex = (uint32_t)strtol(argv[2], NULL, 10); + + pCh = argv[3]; + iBase = 10; + if ((pCh[0] == '0') && (pCh[1] == 'x')) + { + pCh += 2; + iBase = 16; + } + uUserId = ds_strtoull(pCh, NULL, iBase); + DirtyUserFromNativeUser(&DirtyUser, &uUserId); + + // parse list + pCh = strtok(argv[4], ","); + while (pCh != NULL) + { + if (ds_stricmp(pCh, "profile") == 0) + { + uUserDataMask |= USERAPI_MASK_PROFILE; + } + else if (ds_stricmp(pCh, "presence") == 0) + { + uUserDataMask |= USERAPI_MASK_PRESENCE; + } + else if (ds_stricmp(pCh, "richpresence") == 0) + { + uUserDataMask |= USERAPI_MASK_RICH_PRESENCE; + } + + pCh = strtok (NULL, ","); + } + + iResult = UserApiRequestProfileAsync(pApp->pUa, uUserIndex, &DirtyUser, &_CmdUaCb, uUserDataMask, NULL); + if (iResult == USERAPI_ERROR_UNSUPPORTED) + { + ZPrintf("getprofiles is not supported on this platform\n", iResult); + } + else + { + ZPrintf("getprofiles: UserListApiGetListAsync() returned %d\n", iResult); + } +} + +/*F*************************************************************************************/ +/*! + \Function _UaRegister + + \Description + Register for 1st party notifications + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UaRegister(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UaAppT *pApp = (UaAppT *)_pApp; + int32_t iResult; + uint32_t uUserIndex = 0; + UserApiNotifyTypeE eType = USERAPI_NOTIFY_PRESENCE_UPDATE; + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s register [user index] [presence|title|richpresence]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: userlist not yet created\n", argv[0]); + return; + } + // get the receive buffer size (use 0=ANY if not specified) + uUserIndex = (uint32_t)strtol(argv[2], NULL, 10); + + if (ds_stricmp(argv[3], "presence") == 0) + { + eType = USERAPI_NOTIFY_PRESENCE_UPDATE; + } + else if (ds_stricmp(argv[3], "title") == 0) + { + eType = USERAPI_NOTIFY_TITLE_UPDATE; + } + else if (ds_stricmp(argv[3], "richpresence") == 0) + { + eType = USERAPI_NOTIFY_RICH_PRESENCE_UPDATE; + } + else + { + ZPrintf(" usage: %s register [user index] [presence|title|richpresence]\n", argv[0]); + return; + } + + iResult = UserApiRegisterUpdateEvent(pApp->pUa, uUserIndex, eType, &_CmdUaUpdateCb, NULL); + if (iResult == USERAPI_ERROR_UNSUPPORTED) + { + ZPrintf("%s is not supported on this platform\n", argv[0], iResult); + } + else + { + ZPrintf("%s: UserApiRegisterUpdateEvent() returned %d\n", argv[0], iResult); + } +} + +/*F*************************************************************************************/ +/*! + \Function _UaRecentlyMet + + \Description + Submit a recently met player report + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UaRecentlyMet(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ +#if !defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) + UaAppT *pApp = (UaAppT *)_pApp; + int32_t iResult = 0; + uint32_t uUserIndex = 0; + DirtyUserT DirtyUser; + uint64_t AcountId; + int32_t iBase = 0; + char *pCh; + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s recentlymet [user index] [user online ids]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: userlist not yet created\n", argv[0]); + return; + } + + // get the receive buffer size (use 0=ANY if not specified) + uUserIndex = (uint32_t)strtol(argv[2], NULL, 10); + + pCh = argv[3]; + iBase = 10; + if ((pCh[0] == '0') && (pCh[1] == 'x')) + { + pCh += 2; + iBase = 16; + } + AcountId = ds_strtoll(pCh, NULL, iBase); + DirtyUserFromNativeUser(&DirtyUser, &AcountId); + + iResult = UserApiPostRecentlyMetAsync(pApp->pUa, uUserIndex, &DirtyUser, NULL, &_CmdUaPostCb, NULL); + if (iResult == USERAPI_ERROR_UNSUPPORTED) + { + ZPrintf("recentlymet is not supported on this platform\n", iResult); + } + else + { + ZPrintf("recentlymet: UserApiPostRecentlyMetAsync() returned %d\n", iResult); + } +#else + ZPrintf(" %s recentlymet is not supported on XBox\n", argv[0]); +#endif +} + +/*F*************************************************************************************/ +/*! + \Function _UaSetRichPresence + + \Description + Set Rich presence + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UaSetRichPresence(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UaAppT *pApp = (UaAppT *)_pApp; + int32_t iResult = 0; + uint32_t uUserIndex = 0; + UserApiRichPresenceT Data; + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s setrichpresence [user index] [rich presence string]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: userlist not yet created\n", argv[0]); + return; + } + + // get the receive buffer size (use 0=ANY if not specified) + uUserIndex = (uint32_t)strtol(argv[2], NULL, 10); + + ds_memclr(&Data, sizeof(UserApiRichPresenceT)); + ds_strnzcpy(Data.strData, argv[3], (int32_t)sizeof(Data.strData)); + + iResult = UserApiPostRichPresenceAsync(pApp->pUa, uUserIndex, &Data, &_CmdUaPostCb, NULL); + if (iResult == USERAPI_ERROR_UNSUPPORTED) + { + ZPrintf("setrichpresence is not supported on this platform\n", iResult); + } + else + { + ZPrintf("recentlymet: UserApiPostRichPresenceAsync() returned %d\n", iResult); + } +} + + +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function CmdUdp + + \Description + Udp command. + + \Input *argz - unused + \Input argc - argument count + \Input **argv - argument list + + \Output + int32_t - zero + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +int32_t CmdUser(ZContext *argz, int32_t argc, char *argv[]) +{ + T2SubCmdT *pCmd; + UaAppT *pApp = &_Ua_App; + unsigned char bHelp; + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_Ua_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" test the user module\n"); + T2SubCmdUsage(argv[0], _Ua_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmd->pFunc != _UaCreate) && (pApp->pUa == NULL)) + { + char *pCreate = "create"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _UaCreate(pApp, 1, &pCreate, bHelp); + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/userlist.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/userlist.c new file mode 100644 index 00000000..1e1c2d3b --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/userlist.c @@ -0,0 +1,604 @@ +/*H*************************************************************************************/ +/*! + \File userlist.c + + \Description + Reference application for the userlistapi. + + \Copyright + Copyright (c) Electronic Arts 2014. ALL RIGHTS RESERVED. + + \Version 18/02/2014 (amakoukji) +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/misc/userlistapi.h" + +#include "libsample/zlib.h" +#include "libsample/zfile.h" +#include "libsample/zmem.h" +#include "testersubcmd.h" +#include "testermodules.h" + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef struct UlaAppT +{ + UserListApiRefT *pUla; + char aRecvBuf[2048]; + char aSendBuf[2048]; + + uint8_t bStarted; + + unsigned char bZCallback; +} UlaAppT; + +/*** Function Prototypes ***************************************************************/ + +static void _UlaCreate(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UlaDestroy(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UlaFriends(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UlaBlocked(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UlaRegister(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UlaIsFriend(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _UlaIsBlocked(void *_pApp, int32_t argc, char **argv, unsigned char bHelp); +static void _CmdUlaCb(UserListApiRefT *pRef, UserListApiReturnTypeE eResponseType, UserListApiEventDataT *pUserApiEventData, void *pUserData); +static void _CmdUlaIsACb(UserListApiRefT *pRef, UserListApiIfATypeE eResponseType, UserListApiIsADataT *pUserApiEventData, void *pUserData); +static void _CmdUlaUpdateCb(UserListApiRefT *pRef, UserListApiNotifyTypeE eType, UserListApiNotifyDataT *pData, void *pUserData); +static int32_t _CmdUlaTickCb(ZContext *argz, int32_t argc, char *argv[]); + +/*** Variables *************************************************************************/ + +// Private variables +static T2SubCmdT _Ula_Commands[] = +{ + { "create", _UlaCreate }, + { "destroy", _UlaDestroy }, + { "getfriends", _UlaFriends }, + { "getblocked", _UlaBlocked }, + { "register", _UlaRegister }, + { "isfriend", _UlaIsFriend }, + { "isblocked", _UlaIsBlocked }, + { "", NULL } +}; + +static UlaAppT _Ula_App; + +// Public variables + +/*** Private Functions *****************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _CmdUlaTickCb + + \Description + UserList callback, called after command has been issued. + + \Input *argz - pointer to context + \Input argc - number of command-line arguments + \Input *argv[] - command-line argument list + + \Output + int32_t - result of zcallback, or zero to terminate + + \Version 18/02/2014 (amakoukji) +*/ +/********************************************************************************F*/ +static int32_t _CmdUlaTickCb(ZContext *argz, int32_t argc, char *argv[]) +{ + UlaAppT *pApp = &_Ula_App; + + if (pApp->bStarted != FALSE) + { + // check for kill + if (argc == 0) + { + ZPrintf("%s: killed\n", argv[0]); + UserListApiDestroy(pApp->pUla); + return(0); + } + + // update module + if (pApp->pUla != NULL) + { + UserListApiUpdate(pApp->pUla); + } + + // keep recurring + return(ZCallback(&_CmdUlaTickCb, 17)); + } + + return(ZLIB_STATUS_UNKNOWN); +} + +/*F********************************************************************************/ +/*! + \Function _CmdUlaCb + + \Description + userlist callback + + \Input *pRef - UserListApiRefT reference + \Input eResponseType - type of query + \Input *pUserApiEventData - result data struct + \Input pUserData - NULL + + + \Version 18/02/2014 (amakoukji) +*/ +/********************************************************************************F*/ +static void _CmdUlaCb(UserListApiRefT *pRef, UserListApiReturnTypeE eResponseType, UserListApiEventDataT *pUserApiEventData, void *pUserData) +{ + uint64_t uUserId; + + if (eResponseType == TYPE_USER_DATA) + { + DirtyUserToNativeUser(&uUserId, sizeof(uUserId), &pUserApiEventData->UserData.DirtyUser); + + if (pUserApiEventData->UserData.ExtendedUserData.uUserDataMask != 0) + { + ZPrintf("Friend: userid: %llu, gamertag %s\n", uUserId, pUserApiEventData->UserData.ExtendedUserData.Profile.strGamertag); + } + else + { + ZPrintf("Friend: userid: %llu\n", uUserId); + } + } + else if (eResponseType == TYPE_LIST_END) + { + ZPrintf("Friend: END OF LIST\n"); + } + +} + +/*F********************************************************************************/ +/*! + \Function _CmdUlaIsACb + + \Description + userlist callback + + \Input *pRef - UserListApiRefT reference + \Input eResponseType - type of query + \Input *pUserApiEventData - result data struct + \Input pUserData - NULL + + + \Version 18/02/2014 (amakoukji) +*/ +/********************************************************************************F*/ +static void _CmdUlaIsACb(UserListApiRefT *pRef, UserListApiIfATypeE eResponseType, UserListApiIsADataT *pUserApiEventData, void *pUserData) +{ + ZPrintf("%s: %s %s\n", eResponseType == USERLISTAPI_IS_FRIENDS ? "IsFriend" : "IsBlocked", + pUserApiEventData->eIsaType == USERLISTAPI_IS_OF_TYPE ? "is a" : "is NOT a", + eResponseType == USERLISTAPI_IS_FRIENDS ? "friend" : "blocked user"); +} + +/*F********************************************************************************/ +/*! + \Function _CmdUlaUpdateCb + + \Description + userlist callback + + \Input *pRef - UserListApiRefT reference + \Input eType - type of query + \Input *pData - result data struct + \Input pUserData - NULL + + + \Version 18/02/2014 (amakoukji) +*/ +/********************************************************************************F*/ +static void _CmdUlaUpdateCb(UserListApiRefT *pRef, UserListApiNotifyTypeE eType, UserListApiNotifyDataT *pData, void *pUserData) +{ + if (eType == USERLISTAPI_NOTIFY_FRIENDLIST_UPDATE) + { + ZPrintf("userlist: Friend list update notification received!\n"); + } + else if (eType == USERLISTAPI_NOTIFY_BLOCKEDLIST_UPDATE) + { + ZPrintf("userlist: Blocked list update notification received!\n"); + } + else + { + ZPrintf("userlist: Unknown first party notification received!\n"); + } +} + +/*F*************************************************************************************/ +/*! + \Function _UlaCreate + + \Description + UserList create + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UlaCreate(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UlaAppT *pApp = (UlaAppT *)_pApp; + int32_t iReceiveBufferSize; + + if ((bHelp == TRUE) || (argc != 2 && argc != 3)) + { + ZPrintf(" usage: %s create [receive buffer size]\n", argv[0]); + return; + } + + if (pApp->bStarted) + { + ZPrintf("%s: userlist already created, re-creating\n", argv[0]); + UserListApiDestroy(pApp->pUla); + } + + // get the receive buffer size (use 0=ANY if not specified) + iReceiveBufferSize = (argc == 3) ? strtol(argv[2], NULL, 10) : 0; + + // allocate ProtoUdp module + if ((pApp->pUla = UserListApiCreate(iReceiveBufferSize)) == NULL) + { + ZPrintf("%s: unable to create userlist module\n", argv[0]); + return; + } + + pApp->bStarted = TRUE; + + // one-time install of periodic callback + if (pApp->bZCallback == FALSE) + { + pApp->bZCallback = TRUE; + ZCallback(_CmdUlaTickCb, 17); + } +} + +/*F*************************************************************************************/ +/*! + \Function _UlaDestroy + + \Description + UserList destroy + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UlaDestroy(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UlaAppT *pApp = (UlaAppT *)_pApp; + + if ((bHelp == TRUE) || (argc != 2)) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: userlist not yet created\n", argv[0]); + return; + } + + UserListApiDestroy(pApp->pUla); + pApp->bZCallback = FALSE; + pApp->bStarted = FALSE; +} + +/*F*************************************************************************************/ +/*! + \Function _UlaFriends + + \Description + Fetch friends list + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UlaFriends(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UlaAppT *pApp = (UlaAppT *)_pApp; + int32_t iResult; + uint32_t uUserIndex = 0; + + if ((bHelp == TRUE) || (argc != 3)) + { + ZPrintf(" usage: %s getfriends [user index]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: userlist not yet created\n", argv[0]); + return; + } + // get the receive buffer size (use 0=ANY if not specified) + uUserIndex = (uint32_t)strtol(argv[2], NULL, 10); + iResult = UserListApiGetListAsync(pApp->pUla, uUserIndex, USERLISTAPI_TYPE_FRIENDS, 1000, 0, NULL, &_CmdUlaCb, USERLISTAPI_MASK_PROFILE | USERLISTAPI_MASK_PRESENCE, NULL); + if (iResult == USERLISTAPI_ERROR_UNSUPPORTED) + { + ZPrintf("%s is not supported on this platform\n", argv[0], iResult); + } + else + { + ZPrintf("%s: UserListApiGetListAsync() returned %d\n", argv[0], iResult); + } +} + +/*F*************************************************************************************/ +/*! + \Function _UlaBlocked + + \Description + Fetch blocked list + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UlaBlocked(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UlaAppT *pApp = (UlaAppT *)_pApp; + int32_t iResult; + uint32_t uUserIndex = 0; + + if ((bHelp == TRUE) || (argc != 3)) + { + ZPrintf(" usage: %s getblocked [user index]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: userlist not yet created\n", argv[0]); + return; + } + // get the receive buffer size (use 0=ANY if not specified) + uUserIndex = (uint32_t)strtol(argv[2], NULL, 10); + iResult = UserListApiGetListAsync(pApp->pUla, uUserIndex, USERLISTAPI_TYPE_BLOCKED, 50, 0, NULL, &_CmdUlaCb, USERLISTAPI_MASK_PROFILE, NULL); + if (iResult == USERLISTAPI_ERROR_UNSUPPORTED) + { + ZPrintf("%s is not supported on this platform\n", argv[0], iResult); + } + else + { + ZPrintf("%s: UserListApiGetListAsync() returned %d\n", argv[0], iResult); + } +} + +/*F*************************************************************************************/ +/*! + \Function _UlaIsFriend + + \Description + Check if a user is a friend + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UlaIsFriend(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UlaAppT *pApp = (UlaAppT *)_pApp; + int32_t iResult; + uint32_t uUserIndex = 0; + DirtyUserT User; + uint64_t uUserId; + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s isfriend [user index] [userid]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: userlist not yet created\n", argv[0]); + return; + } + // get the receive buffer size (use 0=ANY if not specified) + uUserIndex = (uint32_t)strtol(argv[2], NULL, 10); + + uUserId = ds_strtoull(argv[3], NULL, 10); + DirtyUserFromNativeUser(&User, &uUserId); + + iResult = UserListApiIsFriendAsync(pApp->pUla, uUserIndex, &User, &_CmdUlaIsACb, NULL); + if (iResult == USERLISTAPI_ERROR_UNSUPPORTED) + { + ZPrintf("%s is not supported on this platform\n", argv[0], iResult); + } + else + { + ZPrintf("%s: UserListApiIsFriendAsync() returned %d\n", argv[0], iResult); + } +} + +/*F*************************************************************************************/ +/*! + \Function _UlaIsBlocked + + \Description + Check if a user is blocked + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UlaIsBlocked(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UlaAppT *pApp = (UlaAppT *)_pApp; + int32_t iResult; + uint32_t uUserIndex = 0; + DirtyUserT User; + uint64_t uUserId; + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s isblocked [user index] [userid]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: userlist not yet created\n", argv[0]); + return; + } + // get the receive buffer size (use 0=ANY if not specified) + uUserIndex = (uint32_t)strtol(argv[2], NULL, 10); + + uUserId = ds_strtoull(argv[3], NULL, 10); + DirtyUserFromNativeUser(&User, &uUserId); + + iResult = UserListApiIsBlockedAsync(pApp->pUla, uUserIndex, &User, &_CmdUlaIsACb, NULL); + if (iResult == USERLISTAPI_ERROR_UNSUPPORTED) + { + ZPrintf("%s is not supported on this platform\n", argv[0], iResult); + } + else + { + ZPrintf("%s: UserListApiIsBlockedAsync() returned %d\n", argv[0], iResult); + } +} + +/*F*************************************************************************************/ +/*! + \Function _UlaRegister + + \Description + Check if a user is blocked + + \Input *_pApp - pointer to FriendApi app + \Input argc - argument count + \Input **argv - argument list + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +static void _UlaRegister(void *_pApp, int32_t argc, char **argv, unsigned char bHelp) +{ + UlaAppT *pApp = (UlaAppT *)_pApp; + int32_t iResult; + uint32_t uUserIndex = 0; + DirtyUserT User; + uint8_t bTypeFriends = TRUE; + uint64_t uUserId; + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s register [user index] [friend|blocked]\n", argv[0]); + return; + } + + if (!pApp->bStarted) + { + ZPrintf("%s: userlist not yet created\n", argv[0]); + return; + } + // get the receive buffer size (use 0=ANY if not specified) + uUserIndex = (uint32_t)strtol(argv[2], NULL, 10); + + uUserId = ds_strtoull(argv[3], NULL, 10); + DirtyUserFromNativeUser(&User, &uUserId); + + if (ds_stricmp(argv[3], "friend") == 0) + { + bTypeFriends = TRUE; + } + else if (ds_stricmp(argv[3], "blocked") == 0) + { + bTypeFriends = FALSE; + } + else + { + ZPrintf(" usage: %s register [user index] [friend|blocked]\n", argv[0]); + return; + } + + iResult = UserListApiRegisterUpdateEvent(pApp->pUla, uUserIndex, bTypeFriends == TRUE ? USERLISTAPI_NOTIFY_FRIENDLIST_UPDATE : USERLISTAPI_NOTIFY_BLOCKEDLIST_UPDATE, &_CmdUlaUpdateCb, NULL); + if (iResult == USERLISTAPI_ERROR_UNSUPPORTED) + { + ZPrintf("%s is not supported on this platform\n", argv[0], iResult); + } + else + { + ZPrintf("%s: UserListApiRegisterUpdateEvent() returned %d\n", argv[0], iResult); + } +} + +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function CmdUserList + + \Description + Udp command. + + \Input *argz - unused + \Input argc - argument count + \Input **argv - argument list + + \Output + int32_t - zero + + \Version 18/02/2014 (amakoukji) +*/ +/**************************************************************************************F*/ +int32_t CmdUserList(ZContext *argz, int32_t argc, char *argv[]) +{ + T2SubCmdT *pCmd; + UlaAppT *pApp = &_Ula_App; + unsigned char bHelp; + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_Ula_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" test the userlist module\n"); + T2SubCmdUsage(argv[0], _Ula_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmd->pFunc != _UlaCreate) && (pApp->pUla == NULL)) + { + char *pCreate = "create"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _UlaCreate(pApp, 1, &pCreate, bHelp); + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/utf8.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/utf8.c new file mode 100644 index 00000000..67e6bde8 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/utf8.c @@ -0,0 +1,260 @@ +/*H********************************************************************************/ +/*! + \File utf8.c + + \Description + Tester routine for UTF-8 encoding/decoding. + + \Copyright + Copyright (c) 2003-2005 Electronic Arts Inc. + + \Version 03/25/2003 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/util/utf8.h" + +#include "libsample/zfile.h" +#include "libsample/zlib.h" +#include "libsample/zmem.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +#define NUM_SUBTABLES (3) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + + +// Private variables + + +/* + UTF-8 to 7bit ASCII translation tables +*/ + +// Unicode: C0 Controls and Basic Latin +static unsigned char _TransBasicLatinTo7Bit[128] = +{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0xA, 255, 255, 0xD, 255, 255, // 00-0F + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 10-2F + ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', // 20-2F + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', // 30-3F + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', // 40-4F + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', // 50-5F + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', // 60-6F + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 255 // 70-7F +}; + +// Unicode: C1 Controls and Latin-1 Supplement +static unsigned char _TransLatin1To7Bit[] = +{ + // NOTE: 0x80-0x9F skipped + 255, '!', 'c', 'L', 'o', 'Y', '|', 255, 255, 255, 255, 255, 255, 255, 255, '-', // A0-AF + 255, 255, 255, 255, '`', 'u', 'P', '.', ',', 255, 255, 255, 255, 255, 255, '?', // B0-BF + 'A', 'A', 'A', 'A', 'A', 'A', 255, 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', // C0-CF + 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'x', 255, 'U', 'U', 'U', 'U', 'Y', 255, 'B', // D0-DF + 'a', 'a', 'a', 'a', 'a', 'a', 255, 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', // E0-EF + 255, 'n', 'o', 'o', 'o', 'o', 'o', '/', 255, 'u', 'u', 'u', 'u', 'y', 255, 'y' // F0-FF +}; + +// Unicode: Latin Extended-A +static unsigned char _TransLatinExtATo7Bit[128] = +{ + 'A', 'a', 'A', 'a', 'A', 'a', 'C', 'c', 'C', 'c', 'C', 'c', 'C', 'c', 'D', 'd', // 100-10F + 'D', 'd', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'G', 'g', 'G', 'g', // 110-11F + 'G', 'g', 'G', 'g', 'H', 'h', 'H', 'h', 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', // 120-12F + 'I', 'i', 255, 255, 'J', 'j', 'K', 'k', 'k', 'L', 'l', 'L', 'l', 'L', 'l', 'L', // 130-13F + 'l', 'L', 'l', 'N', 'n', 'N', 'n', 'N', 'n', 'n', 'N', 'n', 'O', 'o', 'O', 'o', // 140-14F + 'O', 'o', 255, 255, 'R', 'r', 'R', 'r', 'R', 'r', 'S', 's', 'S', 's', 'S', 's', // 150-15F + 'S', 's', 'T', 't', 'T', 't', 'T', 't', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', // 160-16F + 'U', 'u', 'U', 'u', 'W', 'w', 'Y', 'y', 'Y', 'Z', 'z', 'Z', 'z', 'Z', 'z', 'f', // 170-17F +}; + +// master table +static Utf8TransTblT _TransTo7BitASCII[NUM_SUBTABLES+1] = +{ + { 0x0000, 0x007F, _TransBasicLatinTo7Bit}, // handle Basic Latin set + { 0x00A0, 0x00FF, _TransLatin1To7Bit }, // handle Latin1 + { 0x0100, 0x017F, _TransLatinExtATo7Bit }, // handle LatinExtA + + { 0x0000, 0x0000, NULL, }, // NULL terminator +}; + +/* + UTF-8 to 8bit ASCII translation tables +*/ + +static unsigned char _TransTo8BitASCIIa[256]; + +// master table +static Utf8TransTblT _TransTo8BitASCII[NUM_SUBTABLES+1] = +{ + { 0x0000, 0x00FF, _TransTo8BitASCIIa }, // handle Basic Latin set + { 0x0000, 0x0000, NULL, }, // NULL terminator +}; + + +// Public variables + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _CmdUtf8Init8BitTransTbl + + \Description + + \Input None. + + \Output + None. + + \Version 03/25/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CmdUtf8Init8BitTransTbl(void) +{ + int32_t iEntry; + + for (iEntry = 0; iEntry < 256; iEntry++) + { + _TransTo8BitASCIIa[iEntry] = (unsigned char)iEntry; + } +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdUtf8 + + \Description + + \Input *argz - + \Input argc - + \Input **argv - + + \Output + int32_t - + + \Version 1.0 03/25/03 (JLB) First Version +*/ +/********************************************************************************F*/ +int32_t CmdUtf8(ZContext *argz, int32_t argc, char **argv) +{ + int32_t iFileSize; + char *pCharData; + + if (argc != 3) + { + ZPrintf(" test the utf8 module\n"); + ZPrintf(" usage: %s [l|r|s|7|8|c|u] [filename]\n", argv[0]); + ZPrintf(" r = replace\n"); + ZPrintf(" s = strip\n"); + ZPrintf(" 7 = translate UTF-8 to 7bit ASCII\n"); + ZPrintf(" 8 = translate UTF-8 to 8bit ASCII\n"); + ZPrintf(" c = translate UTF-8 to UCS-2\n"); + ZPrintf(" u = translate UCS-2 to UTF-8\n"); + return(0); + } + + _CmdUtf8Init8BitTransTbl(); + + if ((pCharData = ZFileLoad(argv[2], &iFileSize, TRUE)) != NULL) + { + unsigned char *pUTF8Data = NULL; + uint16_t *pUCS2Data = NULL; + int32_t iUCS2Size = 0; + int32_t iUTF8Size = 0; + char cOption; + + cOption = argv[1][0]; + if (cOption != 'u') + { + ZPrintf("\n\n--Input--\n\n%s", pCharData); + } + + if (cOption == 'l') + { + iUTF8Size = Utf8StrLen(pCharData); + ZPrintf(" strlen('%s')=%d\n", pCharData, iUTF8Size); + } + else if (cOption == 'r') + { + Utf8Replace(pCharData, iFileSize, pCharData, '*'); + ZPrintf("\n\n--Output--\n\n%s", pCharData); + } + else if (cOption == 's') + { + Utf8Strip(pCharData, iFileSize, pCharData); + ZPrintf("\n\n--Output--\n\n%s", pCharData); + } + else if (cOption == '7') + { + Utf8TranslateTo8Bit(pCharData, iFileSize, pCharData, '*', _TransTo7BitASCII); + ZPrintf("\n\n--7 Bit ASCII--\n\n%s", pCharData); + } + else if (cOption == '8') + { + Utf8TranslateTo8Bit(pCharData, iFileSize, pCharData, '*', _TransTo8BitASCII); + ZPrintf("\n\n--8 Bit ASCII--\n\n%s", pCharData); + } + else if (cOption == 'c') + { + pUCS2Data = (uint16_t *)ZMemAlloc(iFileSize*sizeof(int16_t)); + iUCS2Size = Utf8DecodeToUCS2(pUCS2Data, iFileSize, pCharData) * sizeof(int16_t); + } + else if (cOption == 'u') + { + pUTF8Data = (unsigned char *)ZMemAlloc(iFileSize); + iUTF8Size = Utf8EncodeFromUCS2((char *)pUTF8Data, iFileSize, (const uint16_t *)pCharData); + } + else + { + ZPrintf("Unsupported option '%c'\n",cOption); + } + + // free file buffer + ZMemFree(pCharData); + + // write out UCS-2 data, if any + if ((pUCS2Data != NULL) && (iUCS2Size > 0)) + { + ZFileSave("out_ucs2.txt", (const char *)pUCS2Data, iUCS2Size, ZFILE_OPENFLAG_WRONLY|ZFILE_OPENFLAG_BINARY); + ZMemFree(pUCS2Data); + } + + // write out UTF-8 data, if any + if ((pUTF8Data != NULL) && (iUTF8Size > 0)) + { + ZFileSave("out_utf8.txt", (char *)pUTF8Data, iUTF8Size, ZFILE_OPENFLAG_WRONLY|ZFILE_OPENFLAG_BINARY); + ZMemFree(pUTF8Data); + } + } + else + { + ZPrintf("Unable to open input file.\n"); + } + + return(0); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/voice.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/voice.c new file mode 100644 index 00000000..3106bc82 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/voice.c @@ -0,0 +1,1583 @@ +/*H********************************************************************************/ +/*! + \File voice.c + + \Description + Test Voice over IP + + \Copyright + Copyright (c) Electronic Arts 2004-2005. ALL RIGHTS RESERVED. + + \Version 07/22/2004 (jbrookes) First Version + \Version 07/09/2012 (akirchner) Added functionality to play pre-recorded file +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#ifdef _WIN32 + #pragma warning(push,0) + #include + #pragma warning(pop) +#endif + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/voip/voip.h" +#include "DirtySDK/voip/voipdef.h" +#include "DirtySDK/voip/voipgroup.h" +#include "DirtySDK/voip/voipnarrate.h" +#include "DirtySDK/voip/voiptranscribe.h" + +#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) +#include "DirtySDK/DirtySock/dirtyaddr.h" +#endif + +#include "libsample/zlib.h" +#include "libsample/zfile.h" +#include "libsample/zmem.h" +#include "testersubcmd.h" +#include "testermodules.h" + +#if !defined(DIRTYCODE_PS4) && (!defined(DIRTYCODE_XBOXONE) || !defined(DIRTYCODE_GDK)) + #include "DirtySDK/voip/voipcodec.h" +#endif + +#if defined(DIRTYCODE_PC) + #include "t2hostresource.h" + #include "voipaux/voipspeex.h" +#endif +#if defined(DIRTYCODE_PS4) || defined(DIRTYCODE_PC) || defined(DIRTYCODE_STADIA) + #include "voipaux/voipopus.h" +#endif + +/*** Defines **********************************************************************/ + +// defined in math.h but not part of the standard so just define it here +#define M_PI 3.14159265358979323846 + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct VoiceAppT // gamer daemon state +{ + VoipRefT *pVoip; + VoipGroupRefT *pVoipGroup; + int16_t *pBuffer; + ZFileT iFile; + int32_t iAddress; + int32_t iConnID; + int32_t iSamples; + int32_t iModulation; + uint8_t bRecording; + uint8_t bPlaying; + uint8_t bZCallback; + uint8_t _pad; + uint32_t uClientId; +} VoiceAppT; + +/*** Function Prototypes **********************************************************/ + +static void _VoiceCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceConnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +#if !defined(DIRTYCODE_PS4) && !defined(DIRTYCODE_STADIA) +static void _VoiceCodecControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +#endif +static void _VoiceSTT(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceTTS(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceRecord(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoicePlay(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceModulate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceResetChannels(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceSelectChannel(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceShowChannels(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceVolume(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); + +static void _VoiceConn(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceUserLocal(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceUserRemote(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceSetLocalUser(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _VoiceActivateLocalUser(void *CmdRef, int32_t argc, char *argv[], unsigned char bHelp); + +/*** Variables ********************************************************************/ + +static T2SubCmdT _Voice_Commands[] = +{ + { "create", _VoiceCreate }, + { "destroy", _VoiceDestroy }, + { "connect", _VoiceConnect }, +#if !defined(DIRTYCODE_PS4) && (!defined(DIRTYCODE_XBOXONE) || !defined(DIRTYCODE_GDK)) && !defined(DIRTYCODE_STADIA) + { "cdec", _VoiceCodecControl }, +#endif + { "ctrl", _VoiceControl }, + { "record", _VoiceRecord }, + { "play", _VoicePlay }, + { "modulate", _VoiceModulate }, + { "resetchans", _VoiceResetChannels }, + { "selectchan", _VoiceSelectChannel }, + { "showchans", _VoiceShowChannels }, + { "volume", _VoiceVolume }, + { "conn", _VoiceConn }, + { "userlocal", _VoiceUserLocal }, + { "userremote", _VoiceUserRemote }, + { "set", _VoiceSetLocalUser }, + { "activate", _VoiceActivateLocalUser }, + { "stt", _VoiceSTT }, + { "tts", _VoiceTTS }, + { "", NULL }, +}; + +static VoiceAppT _Voice_App = { NULL, NULL, NULL, (ZFileT)NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _VoiceSpkrModulate + + \Description + Voice modulation using a simple ring modulation filter. Used to test + modification of voice data using the speaker callback. + + \Input *pApp - module state + \Input *pFrameData - pointer to output data + \Input iNumSamples - number of samples in output data + + \Version 11/27/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoiceSpkrModulate(VoiceAppT *pApp, int16_t *pFrameData, int32_t iNumSamples) +{ + static int _iCounter = 0; + int32_t iSample; + double dSin; + + if (pApp->iModulation == 0) + { + return; + } + + for (iSample = 0; iSample < iNumSamples; iSample += 1) + { + dSin = sin(2 * M_PI * pApp->iModulation * _iCounter/15999); + pFrameData[iSample] = (int16_t)(((int32_t)pFrameData[iSample] * (int32_t)(dSin*32768.0))/32768); + if (++_iCounter >= 16000) + { + _iCounter = 0; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoiceEvntCallback + + \Description + Event callback - optional callback to receive voice events + + \Input *pVoip - voip module state + \Input eCbType - callback type (VOIP_CBTYPE_*) + \Input iValue - callback value + \Input *pUserData - user data pointer + + \Version 10/31/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoiceEvntCallback(VoipRefT *pVoip, VoipCbTypeE eCbType, int32_t iValue, void *pUserData) +{ + static const char *_strEventName[] = { "VOIP_CBTYPE_AMBREVENT", "VOIP_CBTYPE_HSETEVENT", "VOIP_CBTYPE_FROMEVENT", "VOIP_CBTYPE_SENDEVENT", "VOIP_CBTYPE_TTOSEVENT" }; + ZPrintf("voice: %s event callback (iValue=%d)\n", _strEventName[eCbType], iValue); +} + +/*F********************************************************************************/ +/*! + \Function _VoiceSpkrCallback + + \Description + Speaker callback - optional callback to receive voice data ready for + output; this module uses this callback to capture voice data for writing + to a file. + + \Input *pFrameData - pointer to output data + \Input iNumSamples - number of samples in output data + \Input *pUserData - app ref + + \Version 10/31/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoiceSpkrCallback(int16_t *pFrameData, int32_t iNumSamples, void *pUserData) +{ + VoiceAppT *pApp = (VoiceAppT *)pUserData; + int32_t iDataSize, iResult; + + // apply modulation if enabled + _VoiceSpkrModulate(pApp, pFrameData, iNumSamples); + + // no file to write to? + if (pApp->iFile <= 0) + { + return; + } + + // write audio to output file + iDataSize = iNumSamples * sizeof(*pFrameData); + if ((iResult = ZFileWrite(pApp->iFile, pFrameData, iDataSize)) != iDataSize) + { + ZPrintf("voice: error %d writing %d samples to file\n", iResult, iNumSamples); + } + else + { + pApp->iSamples += iNumSamples; + ZPrintf("voice: wrote %d samples to output file (%d total)\n", iNumSamples, pApp->iSamples); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoiceFirstyParytIdCallback + + \Description + Retrieves the id of the local user for testing loopback + + \Input uPersonaId - unused + \Input *pUserData - unused + + \Output + uint64_t - local player id or zero (error/unhandled) +*/ +/********************************************************************************F*/ +static uint64_t _VoiceFirstyParytIdCallback(uint64_t uPersonaId, void *pUserData) +{ + uint64_t uPlayerId = 0; + #if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + NetConnStatus('xuid', 0, &uPlayerId, sizeof(uPlayerId)); + #elif defined(DIRTYCODE_STADIA) + NetConnStatus('gpid', 0, &uPlayerId, sizeof(uPlayerId)); + #endif + return(uPlayerId); +} + +/*F********************************************************************************/ +/*! + \Function _VoiceDisplayTranscribedTextCallback + + \Description + Callback handling notification about transcribed text being ready for local display. + + \Input iConnId - connection identifier + \Input iRemoteUserIndex - remote user index + \Input *pText - transcribed text + \Input *pUserData - callback user data + + \Output + int32_t - TRUE + + \Version 10/31/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoiceDisplayTranscribedTextCallback(int32_t iConnId, int32_t iUserIndex, const char *pText, void *pUserData) +{ + ZPrintf("voice: %s user[%d] \"%s\"\n", ((iConnId != -1) ? "remote" : "local"), iUserIndex, pText); + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function _CmdVoiceDevSelectProc + + \Description + Voice device select for PC, + + \Input win - window handle + \Input msg - window message + \Input wparm - message parameter + \Input lparm - message parameter + + \Output + LRESULT - FALSE + + \Version 07/22/2004 (jbrookes) +*/ +/********************************************************************************F*/ +#if defined(DIRTYCODE_PC) +static LRESULT CALLBACK _CmdVoiceDevSelectProc(HWND win, UINT msg, WPARAM wparm, LPARAM lparm) +{ + // handle init special (create the class) + if (msg == WM_INITDIALOG) + { + char pDeviceName[64]; + int32_t iDevice, iNumDevices; + VoipRefT *pVoip; + + pVoip = VoipGetRef(); + + // add input devices to combo box and select the first item + iNumDevices = VoipStatus(pVoip, 'idev', -1, NULL, 0); + for (iDevice = 0; iDevice < iNumDevices; iDevice++) + { + VoipStatus(pVoip, 'idev', iDevice, pDeviceName, 64); + SendDlgItemMessage(win, IDC_VOICEINP, CB_ADDSTRING, 0, (LPARAM)pDeviceName); + } + // select default input device, if available; otherwise just pick the first + if ((iDevice = VoipStatus(pVoip, 'idft', 0, NULL, 0)) == -1) + { + iDevice = 0; + } + SendDlgItemMessage(win, IDC_VOICEINP, CB_SETCURSEL, iDevice, 0); + + // add output devices to combo box and select the first item + iNumDevices = VoipStatus(pVoip, 'odev', -1, NULL, 0); + for (iDevice = 0; iDevice < iNumDevices; iDevice++) + { + VoipStatus(pVoip, 'odev', iDevice, pDeviceName, 64); + SendDlgItemMessage(win, IDC_VOICEOUT, CB_ADDSTRING, 0, (LPARAM)pDeviceName); + } + // select default output device, if available; otherwise just pick the first + if ((iDevice = VoipStatus(pVoip, 'odft', VOIP_DEFAULTDEVICE_VOICECOM, NULL, 0)) == -1) + { + iDevice = 0; + } + SendDlgItemMessage(win, IDC_VOICEOUT, CB_SETCURSEL, iDevice, 0); + } + + // handle ok + if ((msg == WM_COMMAND) && (LOWORD(wparm) == IDOK)) + { + VoipRefT *pVoip; + int32_t iInpDev, iOutDev; + + pVoip = VoipGetRef(); + + iInpDev = SendDlgItemMessage(win, IDC_VOICEINP, CB_GETCURSEL, 0, 0); + iOutDev = SendDlgItemMessage(win, IDC_VOICEOUT, CB_GETCURSEL, 0, 0); + + VoipControl(pVoip, 'idev', iInpDev, NULL); + VoipControl(pVoip, 'odev', iOutDev, NULL); + + DestroyWindow(win); + } + + // let windows handle + return(FALSE); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _VoiceDestroyApp + + \Description + Destroy app, freeing modules. + + \Input *pApp - app state + + \Output + None. + + \Version 12/12/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoiceDestroyApp(VoiceAppT *pApp) +{ + if (pApp->pVoipGroup != NULL) + { + VoipGroupDestroy(pApp->pVoipGroup); + } + if (pApp->pVoip) + { + VoipShutdown(pApp->pVoip, 0); + } + ds_memclr(pApp, sizeof(*pApp)); +} + +/*F********************************************************************************/ +/*! + \Function _VoiceConnSharingCb + + \Description + Connection Sharing Callback fired by the VoipGroup + + \Input *pVoipGroup - voip group ref + \Input eCbType - event identifier + \Input iConnId - connection ID + \Input *pUserData - user callback data + \Input bSending - client sending flag + \Input bReceiving - client receiving flag + + \Version 02/13/2019 (eesponda) +*/ +/********************************************************************************F*/ +static void _VoiceConnSharingCb(VoipGroupRefT *pVoipGroup, ConnSharingCbTypeE eCbType, int32_t iConnId, void *pUserData, uint8_t bSending, uint8_t bReceiving) +{ + // no behavior is implemented, the connections should be tracked if we want to support multiple concurrent groups + ZPrintf("voice: conn sharing callback hit with %s for id %d\n", + eCbType == VOIPGROUP_CBTYPE_CONNSUSPEND ? "VOIPGROUP_CBTYPE_CONNSUSPEND" : "VOIPGROUP_CBTYPE_CONNRESUME", iConnId); +} + +/* + + Voice Commands + +*/ + +/*F*************************************************************************************/ +/*! + \Function _VoiceCreate + + \Description + Voice subcommand - create voice module + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Output None. + + \Version 12/12/2005 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _VoiceCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + int32_t iArg=2, iMaxPeers, iQuality=-1; + #if defined(DIRTYCODE_PC) + uint8_t bDevSelect = TRUE; + #elif defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + const char *pOpt; + #endif + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s create -nodevselect -qual \n", argv[0]); + return; + } + + // check for device select disable + #if defined(DIRTYCODE_PC) + if ((argc > 2) && !strcmp(argv[iArg], "-nodevselect")) + { + bDevSelect = FALSE; + iArg += 1; + argc -= 1; + } + #endif + + // set quality? + #if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + if ((argc > 2) && ((pOpt = strstr(argv[iArg], "-qual")) != NULL)) + { + pOpt += strlen("-qual") + 1; + iQuality = strtol(pOpt, NULL, 10); + iArg += 1; + argc -= 1; + } + #endif + + // allow setting of max number of remote clients, defaulting to sixteen + iMaxPeers = (argc == 3) ? strtol(argv[iArg], NULL, 10) : 16; + + // start up voice module if it isn't already started + if ((pApp->pVoip = VoipGetRef()) == NULL) + { + if ((pApp->pVoip = VoipStartup(iMaxPeers, 0)) == NULL) + { + ZPrintf("voice: failed to create voip module\n"); + return; + } + } + + // register and select speex (for PC) and set the default volume to 90 + #if defined(DIRTYCODE_PC) + VoipControl(pApp->pVoip, 'creg', 'spex', (void *)&VoipSpeex_CodecDef); + VoipControl(pApp->pVoip, 'svol', 90, NULL); + #endif + #if defined(DIRTYCODE_PS4) || defined(DIRTYCODE_PC) || defined(DIRTYCODE_STADIA) + VoipControl(pApp->pVoip, 'creg', 'opus', (void *)&VoipOpus_CodecDef); + VoipControl(pApp->pVoip, 'cdec', 'opus', NULL); + #endif + + // set speaker callback + VoipSpkrCallback(pApp->pVoip, _VoiceSpkrCallback, pApp); + + // set first party id callback + VoipRegisterFirstPartyIdCallback(pApp->pVoip, _VoiceFirstyParytIdCallback, pApp); + + if (iQuality != -1) + { + VoipControl(pApp->pVoip, 'qual', iQuality, NULL); + } + + // create the voipgroup, with max 8 groups + pApp->pVoipGroup = VoipGroupCreate(8); + // set event callback + VoipGroupSetEventCallback(pApp->pVoipGroup, _VoiceEvntCallback, pApp); + // set the connection sharing callback + VoipGroupSetConnSharingEventCallback(pApp->pVoipGroup, _VoiceConnSharingCb, pApp); + + // set our clientId using local address + pApp->uClientId = SocketInfo(NULL, 'addr', 0, NULL, 0); + VoipGroupControl(pApp->pVoipGroup, 'clid', pApp->uClientId, 0, NULL); + + // select output device (PC only) + #if defined(DIRTYCODE_PC) + if (bDevSelect) + { + ShowWindow(CreateDialogParam(GetModuleHandle(NULL), "VOICEDEVSELECT", HWND_DESKTOP, (DLGPROC)_CmdVoiceDevSelectProc,0), TRUE); + } + #endif +} + +/*F*************************************************************************************/ +/*! + \Function _VoiceDestroy + + \Description + Voice subcommand - destroy voice module + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Output None. + + \Version 12/12/2005 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _VoiceDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + return; + } + + if (pApp->pBuffer) + { + ZMemFree((void *) pApp->pBuffer); + pApp->pBuffer = NULL; + } + + _VoiceDestroyApp(pApp); +} + +/*F*************************************************************************************/ +/*! + \Function _VoiceConnect + + \Description + Voice subcommand - connect to peer + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Output None. + + \Version 12/12/2005 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _VoiceConnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + + if ((bHelp == TRUE) || (argc > 5)) + { + ZPrintf(" usage: %s connect [rclientid] [connindex]\n", argv[0]); + return; + } + + if (argc >= 3) + { + uint32_t uRemoteAddr, uRemotePort, uManglePort, uRemoteClientId; + int32_t iClientIndex; + + // get remote address, port, and local port (if specified) in addr:port:port2 format + SockaddrInParse2(&uRemoteAddr, (int32_t *)&uRemotePort, (int32_t *)&uManglePort, argv[2]); + if (uRemotePort == 0) + { + uRemotePort = VOIP_PORT; + } + if (uManglePort == 0) + { + uManglePort = VOIP_PORT; + } + + // get remote client id, or use remote address if unspecified + uRemoteClientId = (argc >= 4) ? strtoul(argv[3], NULL, 16) : uRemoteAddr; + iClientIndex = (argc >= 5) ? strtol(argv[4], NULL, 10) : VOIP_CONNID_NONE; + + // disable loop-back mode, in case it was previously set + VoipGroupControl(pApp->pVoipGroup, 'loop', FALSE, 0, NULL); + + // log connection attempt + ZPrintf("%s: connecting to %a:%u:%u (clientId=0x%08x)\n", argv[0], uRemoteAddr, uManglePort, uRemotePort, uRemoteClientId); + + // start connect to remote peer + VoipGroupControl(pApp->pVoipGroup, 'vcid', iClientIndex, 0, &pApp->uClientId); + pApp->iConnID = VoipGroupConnect(pApp->pVoipGroup, iClientIndex, uRemoteAddr, uManglePort, uRemotePort, pApp->uClientId, 0, FALSE, uRemoteClientId); + } + else + { + #if !defined(DIRTYCODE_XBOXONE) || !defined(DIRTYCODE_GDK) + // set loopback + ZPrintf("%s: enabling loopback\n", argv[0]); + VoipGroupControl(pApp->pVoipGroup, 'loop', TRUE, 0, NULL); + #else + ZPrintf("%s: loopback not supported on xboxone\n", argv[0]); + #endif + } +} + +#if !defined(DIRTYCODE_PS4) && (!defined(DIRTYCODE_XBOXONE) || !defined(DIRTYCODE_GDK)) && !defined(DIRTYCODE_STADIA) +/*F*************************************************************************************/ +/*! + \Function _VoiceCodecControl + + \Description + Voice control subcommand - set control options + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Output None. + + \Version 05/06/2011 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _VoiceCodecControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + if (bHelp == TRUE) + { + ZPrintf(" usage: %s cdec [arg2]\n", argv[0]); + return; + } + + if ((argc > 4) && (argc < 7)) + { + int32_t iIdent, iCmd, iValue = 0, iValue2 = 0; + + iIdent = ZGetIntArg(argv[2]); + iCmd = ZGetIntArg(argv[3]); + + if (argc > 4) + { + iValue = ZGetIntArg(argv[4]); + } + if (argc > 5) + { + iValue2 = ZGetIntArg(argv[5]); + } + + VoipCodecControl(iIdent, iCmd, iValue, iValue2, NULL); + } +} +#endif + +/*F*************************************************************************************/ +/*! + \Function _VoiceControl + + \Description + Voice control subcommand - set control options + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Output None. + + \Version 05/05/2011 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _VoiceControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + void *pValue = NULL; + int32_t iValue2; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s ctrl \n", argv[0]); + return; + } + + if (argc > 2) + { + int32_t iCmd, iValue = 0; + + // get control selector + iCmd = ZGetIntArg(argv[2]); + + // get control value if specified + if (argc > 3) + { + iValue = ZGetIntArg(argv[3]); + } + // if control is clid save the value + if (iCmd == 'clid') + { + pApp->uClientId = (unsigned)iValue; + } + + // if enabling speech-to-text, set callback + if (iCmd == 'stot') + { + iValue2 = (argc > 4) ? ZGetIntArg(argv[4]) : 0; + pValue = &iValue2; + VoipGroupSetDisplayTranscribedTextCallback(pApp->pVoipGroup, _VoiceDisplayTranscribedTextCallback, pApp); + } + else if (argc > 4) + { + pValue = argv[4]; + } + + VoipControl(pApp->pVoip, iCmd, iValue, pValue); + } + else + { + ZPrintf("%s: invalid ctrl command\n", argv[0]); + } +} + +/*F*************************************************************************************/ +/*! + \Function _VoiceRecord + + \Description + Voice subcommand - start/stop recording + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 10/28/2011 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _VoiceRecord(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + uint8_t bStart; + + if ((bHelp == TRUE) || (argc < 3) || (argc > 4)) + { + ZPrintf(" usage: %s record [start|stop] \n", argv[0]); + return; + } + + // see if we are stopping or starting + if (!strcmp(argv[2], "start")) + { + bStart = TRUE; + } + else if (!strcmp(argv[2], "stop")) + { + bStart = FALSE; + } + else + { + ZPrintf("%s: invalid start/stop argument %s\n", argv[0], argv[2]); + return; + } + + if (bStart) + { + if (pApp->bPlaying) + { + ZPrintf("%s: cannot start, currently playing\n", argv[0]); + return; + } + + if (pApp->bRecording) + { + ZPrintf("%s: cannot start, already recording\n", argv[0]); + return; + } + if (argc != 4) + { + ZPrintf("%s: cannot record without a filename to write to\n", argv[0]); + return; + } + if ((pApp->iFile = ZFileOpen(argv[3], ZFILE_OPENFLAG_CREATE|ZFILE_OPENFLAG_WRONLY|ZFILE_OPENFLAG_BINARY)) < 0) + { + ZPrintf("%s: error %d opening file '%s' for recording\n", pApp->iFile, argv[0], argv[3]); + } + ZPrintf("%s: opened file '%s' for recording\n", argv[0], argv[3]); + pApp->bRecording = TRUE; + pApp->iSamples = 0; + } + else + { + int32_t iResult; + ZFileT iFileId; + + if (!pApp->bRecording) + { + ZPrintf("%s: cannot stop, not recording\n", argv[0]); + return; + } + + // save fileid, clear it from ref (stop the callback from writing to it) + iFileId = pApp->iFile; + pApp->iFile = 0; + pApp->bRecording = FALSE; + ZPrintf("%s: recording stopped, %d samples (%d bytes) written\n", argv[0], pApp->iSamples, pApp->iSamples * sizeof(int16_t)); + + // close the file + if ((iResult = ZFileClose(iFileId)) < 0) + { + ZPrintf("%s: error %d closing recording file\n", argv[0], iResult); + } + } +} + +/*F*************************************************************************************/ +/*! + \Function _VoicePlay + + \Description + Voice subcommand - start/stop playing + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 7/9/2012 (akirchner) +*/ +/**************************************************************************************F*/ +static void _VoicePlay(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT * pApp = & _Voice_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s play [start|stop] \n", argv[0]); + return; + } + + if ((argc >= 3) && (!strcmp(argv[2], "start"))) + { + int32_t iResult; + + if (pApp->bRecording) + { + ZPrintf("%s: cannot start, currently recording\n", argv[0]); + return; + } + + if (pApp->bPlaying) + { + ZPrintf("%s: cannot start, already playing\n", argv[0]); + return; + } + + if (argc != 4) + { + ZPrintf(" usage: %s play start \n", argv[0]); + return; + } + + // open file + if ((pApp->iFile = ZFileOpen(argv[3], ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) < 0) + { + ZPrintf("%s: error %d opening file '%s' for playing\n", argv[0], pApp->iFile, argv[3]); + return; + } + + // get size + if ((pApp->iSamples = ZFileSize(pApp->iFile)) < 0) + { + ZPrintf("%s: error %d invalid size of file '%s'\n", argv[0], pApp->iFile, argv[3]); + return; + } + + // read file + if ((pApp->pBuffer = (int16_t *) ZMemAlloc(pApp->iSamples)) == NULL) + { + ZPrintf("%s: error allocating %d bytes of memory\n", argv[0], pApp->iSamples); + return; + } + + if ((iResult = ZFileSeek(pApp->iFile, 0, ZFILE_SEEKFLAG_SET)) < 0) + { + ZMemFree((void *) pApp->pBuffer); + pApp->pBuffer = NULL; + + ZPrintf("%s: error %d seeking begging of file '%s'\n", argv[0], iResult, argv[3]); + return; + } + + if (ZFileRead(pApp->iFile, (void *) pApp->pBuffer, pApp->iSamples) < 0) + { + ZMemFree((void *) pApp->pBuffer); + pApp->pBuffer = NULL; + + ZPrintf("%s: error %d reading file '%s'\n", argv[0], pApp->iFile, argv[3]); + return; + } + + // close file + if ((iResult = ZFileClose(pApp->iFile)) < 0) + { + ZMemFree((void *) pApp->pBuffer); + pApp->pBuffer = NULL; + + ZPrintf("%s: error %d closing file '%s' for playing\n", argv[0], iResult, argv[3]); + return; + } + + if (VoipControl(pApp->pVoip, 'play', pApp->iSamples, (void *) pApp->pBuffer) < 0) + { + ZMemFree((void *) pApp->pBuffer); + pApp->pBuffer = NULL; + + ZPrintf("%s: failed to activate playing mode\n", argv[0]); + return; + } + + ZPrintf("%s: opened file '%s' of %d bytes for playing\n", argv[0], argv[3], pApp->iSamples); + + pApp->iFile = 0; + pApp->bPlaying = TRUE; + } + else if ((argc >= 3) && (!strcmp(argv[2], "stop"))) + { + if (argc != 3) + { + ZPrintf(" usage: %s play stop\n", argv[0]); + return; + } + + if (!pApp->bPlaying) + { + ZPrintf("%s: cannot stop, not playing\n", argv[0]); + return; + } + + if (VoipControl(pApp->pVoip, 'play', 0, NULL) < 0) + { + ZMemFree((void *) pApp->pBuffer); + + ZPrintf("%s: failed to deactivate playing mode\n", argv[0]); + return; + } + + pApp->bPlaying = FALSE; + + if (pApp->pBuffer) + { + ZMemFree((void *) pApp->pBuffer); + pApp->pBuffer = NULL; + } + + ZPrintf("%s: playing stopped\n", argv[0]); + } + else + { + ZPrintf("%s: invalid argument. Neither start nor stop\n", argv[0]); + return; + } +} + +/*F*************************************************************************************/ +/*! + \Function _VoiceModulate + + \Description + Voice subcommand - set voice modulation (zero=disabled) + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/27/2018 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _VoiceModulate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT * pApp = & _Voice_App; + + if ((bHelp == TRUE) || (argc > 3)) + { + ZPrintf(" usage: %s modulate \n", argv[0]); + return; + } + + // get modulation rate + pApp->iModulation = (argc == 3) ? strtol(argv[2], NULL, 10) : 1000; +} + +/*F*************************************************************************************/ +/*! + \Function _VoiceResetChannels + + \Description + Voice subcommand - reset channel configuration + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 02/16/2012 (mclouatre) +*/ +/**************************************************************************************F*/ +static void _VoiceResetChannels(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + if ((bHelp == TRUE) || (argc != 2)) + { + ZPrintf(" usage: %s resetchans\n", argv[0]); + return; + } + + ZPrintf("%s: resetting channel config\n", argv[0]); + VoipResetChannels(VoipGetRef(), 0); +} + +/*F*************************************************************************************/ +/*! + \Function _VoiceSelectChannel + + \Description + Voice subcommand - subscribe/unsubcribes to/from a voip channel + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 02/16/2012 (mclouatre) +*/ +/**************************************************************************************F*/ +static void _VoiceSelectChannel(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + int32_t iChannel; + VoipChanModeE eMode; + + if ((bHelp == TRUE) || (argc != 4)) + { + ZPrintf(" usage: %s selectchan \n", argv[0]); + return; + } + + sscanf(argv[2], "%d", &iChannel); + if ((iChannel < 0) && (iChannel > 63)) + { + ZPrintf("%s: invalid channel id argument: %s\n", argv[0], argv[2]); + return; + } + + if (!strcmp(argv[3], "talk")) + { + eMode = VOIP_CHANSEND; + } + else if (!strcmp(argv[3], "listen")) + { + eMode = VOIP_CHANRECV; + } + else if (!strcmp(argv[3], "both")) + { + eMode = VOIP_CHANSENDRECV; + } + else if (!strcmp(argv[3], "none")) + { + eMode = VOIP_CHANNONE; + } + else + { + ZPrintf("%s: invalid channel mode argument: %s\n", argv[0], argv[3]); + return; + } + + ZPrintf("%s: setting channel %d:%s\n", argv[0], iChannel, argv[3]); + VoipSelectChannel(VoipGetRef(), 0, iChannel, eMode); +} + +/*F*************************************************************************************/ +/*! + \Function _VoiceTTS + + \Description + Voice subcommand - Send Text as Voice + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 05/14/2018 (tcho) +*/ +/**************************************************************************************F*/ +static void _VoiceTTS(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + char strText[1024]; + + if (bHelp) + { + ZPrintf(" usage: %s tts config [params] or gender [male|female] or \"text you wish to say\" [user index]\n", argv[0]); + return; + } + + if (!ds_stricmp(argv[2], "config")) + { + if (argc == 6) + { + VoipNarrateProviderE eProvider = VOIPNARRATE_PROVIDER_NONE; + if (!ds_stricmp(argv[3], "watson")) eProvider = VOIPNARRATE_PROVIDER_IBMWATSON; + if (!ds_stricmp(argv[3], "microsoft")) eProvider = VOIPNARRATE_PROVIDER_MICROSOFT; + if (!ds_stricmp(argv[3], "google")) eProvider = VOIPNARRATE_PROVIDER_GOOGLE; + if (!ds_stricmp(argv[3], "amazon")) eProvider = VOIPNARRATE_PROVIDER_AMAZON; + VoipConfigNarration(VoipGetRef(), eProvider, argv[4], argv[5]); + } + else + { + ZPrintf(" usage: %s tts config [none|watson|microsoft|google] \n", argv[0]); + } + } + else if (!ds_stricmp(argv[2], "gender")) + { + VoipSynthesizedSpeechCfgT VoipSpeechConfig; + ds_memclr(&VoipSpeechConfig, sizeof(VoipSpeechConfig)); + if (!ds_stricmp(argv[3], "female")) + { + VoipSpeechConfig.iPersonaGender = 1; + } + VoipControl(VoipGetRef(), 'voic', 0, &VoipSpeechConfig); + } + else + { + sscanf(argv[2], "%256[^\n]", strText); + VoipControl(VoipGetRef(), 'ttos', (argc > 3) ? ds_strtoll(argv[3], NULL, 10) : 0, strText); + } +} + +/*F*************************************************************************************/ +/*! + \Function _VoiceSTT + + \Description + Voice subcommand - Speech to Text functionality + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 12/13/2018 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _VoiceSTT(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoipTranscribeProviderE eProvider = VOIPTRANSCRIBE_PROVIDER_NONE; + VoipTranscribeTransportE eTransport = VOIPTRANSCRIBE_TRANSPORT_HTTP; + VoipTranscribeFormatE eFormat = VOIPTRANSCRIBE_FORMAT_LI16; + if (bHelp || ds_stricmp(argv[2], "config") || ((ds_stricmp(argv[3], "none") && (argc != 8)))) + { + ZPrintf(" usage: %s stt config [none|watson|microsoft|google|amazon] [li16|wav|opus] [http|h2|ws] \n", argv[0]); + return; + } + if (!ds_stricmp(argv[3], "watson")) eProvider = VOIPTRANSCRIBE_PROVIDER_IBMWATSON; + if (!ds_stricmp(argv[3], "microsoft")) eProvider = VOIPTRANSCRIBE_PROVIDER_MICROSOFT; + if (!ds_stricmp(argv[3], "google")) eProvider = VOIPTRANSCRIBE_PROVIDER_GOOGLE; + if (!ds_stricmp(argv[3], "amazon")) eProvider = VOIPTRANSCRIBE_PROVIDER_AMAZON; + if (!ds_stricmp(argv[4], "wav")) eFormat = VOIPTRANSCRIBE_FORMAT_WAV16; + if (!ds_stricmp(argv[4], "opus")) eFormat = VOIPTRANSCRIBE_FORMAT_OPUS; + if (!ds_stricmp(argv[5], "h2")) eTransport = VOIPTRANSCRIBE_TRANSPORT_HTTP2; + if (!ds_stricmp(argv[5], "ws")) eTransport = VOIPTRANSCRIBE_TRANSPORT_WEBSOCKETS; + VoipConfigTranscription(VoipGetRef(), VOIPTRANSCRIBE_PROFILE_CONSTRUCT(eProvider, eFormat, eTransport), argv[6], argv[7]); +} + +/*F*************************************************************************************/ +/*! + \Function _VoiceShowChannels + + \Description + Voice subcommand - show local voip channel config + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 02/16/2012 (mclouatre) +*/ +/**************************************************************************************F*/ +static void _VoiceShowChannels(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + int32_t iChannelSlot, iChannelId; + char *pChannelModeStr; + VoipChanModeE eChannelMode; + char strChannelId[8]; + char strChannelSlot[32]; + char strChannelCfg[32]; + + if ((bHelp == TRUE) || (argc != 2)) + { + ZPrintf(" usage: %s showchans\n", argv[0]); + return; + } + + for (iChannelSlot = 0; iChannelSlot < VoipGroupStatus(NULL, 'chnc', 0, NULL, 0); iChannelSlot++) + { + // get channel id and channel mode + iChannelId = VoipGroupStatus(NULL, 'chnl', iChannelSlot, &eChannelMode, sizeof(eChannelMode)); + // init to default + pChannelModeStr = "UNKNOWN"; + + // initialize channel mode string and + switch (eChannelMode) + { + case VOIP_CHANNONE: + pChannelModeStr = "UNSUBSCRIBED"; + ds_snzprintf(strChannelId, sizeof(strChannelId), "n/a"); + break; + case VOIP_CHANSEND: + pChannelModeStr = "TALK-ONLY"; + ds_snzprintf(strChannelId, sizeof(strChannelId), "%03d", iChannelId); + break; + case VOIP_CHANRECV: + pChannelModeStr = "LISTEN-ONLY"; + ds_snzprintf(strChannelId, sizeof(strChannelId), "%03d", iChannelId); + break; + case VOIP_CHANSENDRECV: + pChannelModeStr = "TALK+LISTEN"; + ds_snzprintf(strChannelId, sizeof(strChannelId), "%03d", iChannelId); + break; + } + + // build channel slot string + ds_snzprintf(strChannelSlot, sizeof(strChannelSlot), "VOIP channel slot %d", iChannelSlot); + + // build channel config string + ds_snzprintf(strChannelCfg, sizeof(strChannelCfg), "id=%s mode=%s", strChannelId, pChannelModeStr); + + ZPrintf(" %s ----> %s\n", strChannelSlot, strChannelCfg); + } +} + +/*F*************************************************************************************/ +/*! + \Function _VoiceVolume + + \Description + Voice subcommand - set the output volume level + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Output None. + + \Version 04/22/2009 (cadam) +*/ +/**************************************************************************************F*/ +static void _VoiceVolume(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + float fOutputLevel; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s volume \n", argv[0]); + return; + } + + if (argc == 3) + { + fOutputLevel = strtod(argv[2], NULL); + ZPrintf("%s: setting volume to %f\n", argv[0], fOutputLevel); + VoipControl(pApp->pVoip, 'plvl', 0, &fOutputLevel); + } +} + +static void _VoiceSetLocalUser(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + int32_t iLocalUserIndex; + uint32_t bRegister; + int32_t iOnOffIndex; + +#if defined(DIRTYCODE_PS4) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + if (bHelp == TRUE || argc != 4) + { + ZPrintf(" usage: %s set \n", argv[0]); + return; + } + + iLocalUserIndex = strtol(argv[2], NULL, 10); + iOnOffIndex = 3; +#else + if (bHelp == TRUE || argc != 3) + { + ZPrintf(" usage: %s set \n", argv[0]); + return; + } + + iLocalUserIndex = 0; + iOnOffIndex = 2; +#endif + + if (strcmp(argv[iOnOffIndex], "on") == 0) + { + bRegister = TRUE; + ZPrintf("%s: registering local user at index %d with voip sub-system\n", argv[0], iLocalUserIndex); + } + else + { + bRegister = FALSE; + ZPrintf("%s: unregistering local user at index %d from voip sub-system\n", argv[0], iLocalUserIndex); + } + + VoipSetLocalUser(pApp->pVoip, iLocalUserIndex, bRegister); +} + +static void _VoiceActivateLocalUser(void *CmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + uint32_t iLocalUserIndex; + uint32_t bActivate; + int32_t iOnOffIndex; + +#if defined(DIRTYCODE_PS4) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + if (bHelp == TRUE || argc != 4) + { + ZPrintf(" usage: %s participate \n", argv[0]); + return; + } + + iLocalUserIndex = strtol(argv[2], NULL, 10); + iOnOffIndex = 3; +#else + if (bHelp == TRUE || argc != 3) + { + ZPrintf(" usage: %s participate \n", argv[0]); + return; + } + + iLocalUserIndex = 0; + iOnOffIndex = 2; +#endif + + if (strcmp(argv[iOnOffIndex], "on") == 0) + { + bActivate = TRUE; + ZPrintf("%s: promoting local user %d to participating state\n", argv[0], iLocalUserIndex); + } + else + { + bActivate = FALSE; + ZPrintf("%s: forcing local user %d out of participating state\n", argv[0], iLocalUserIndex); + } + + VoipGroupActivateLocalUser(pApp->pVoipGroup, iLocalUserIndex, bActivate); +} + +static void _VoiceConn(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + uint32_t uOutput; + int32_t iConnId = 0; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s connremote \n", argv[0]); + return; + } + + if (argc == 3) + { + iConnId = strtod(argv[2], NULL); + uOutput = VoipGroupConnStatus(pApp->pVoipGroup, iConnId); + ZPrintf("Status = %08x\n", uOutput); + if (uOutput == 0) + { + ZPrintf("No VoipConnRemote flags set\n"); + } + else + { + if (uOutput & VOIP_CONN_CONNECTED) + ZPrintf("VOIP_CONN_CONNECTED\n"); + if (uOutput & VOIP_CONN_BROADCONN) + ZPrintf("VOIP_CONN_BROADCONN\n"); + if (uOutput & VOIP_CONN_ACTIVE) + ZPrintf("VOIP_CONN_ACTIVE\n"); + if (uOutput & VOIP_CONN_STOPPED) + ZPrintf("VOIP_CONN_STOPPED\n"); + } + } +} + +static void _VoiceUserLocal(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + uint32_t uOutput; + int32_t iUserIndex = 0; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s userlocal \n", argv[0]); + return; + } + + if (argc == 3) + { + iUserIndex = strtod(argv[2], NULL); + uOutput = VoipLocalUserStatus(pApp->pVoip, iUserIndex); + ZPrintf("Status = %08x\n", uOutput); + if (uOutput == 0) + { + ZPrintf("No VoipUserLocal flags set\n"); + } + else + { + if (uOutput & VOIP_LOCAL_USER_HEADSETOK) + ZPrintf("VOIP_LOCAL_USER_HEADSETOK\n"); + if (uOutput & VOIP_LOCAL_USER_TALKING) + ZPrintf("VOIP_LOCAL_USER_TALKING\n"); + if (uOutput & VOIP_LOCAL_USER_SENDVOICE) + ZPrintf("VOIP_LOCAL_USER_SENDVOICE\n"); + if (uOutput & VOIP_LOCAL_USER_INPUTDEVICEOK) + ZPrintf("VOIP_LOCAL_USER_INPUTDEVICEOK\n"); + if (uOutput & VOIP_LOCAL_USER_OUTPUTDEVICEOK) + ZPrintf("VOIP_LOCAL_USER_OUTPUTDEVICEOK\n"); + } + } +} + +static void _VoiceUserRemote(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + VoiceAppT *pApp = &_Voice_App; + uint32_t uOutput; + int32_t iUserIndex = 0; + int32_t iConnId = 0; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s userremote \n", argv[0]); + return; + } + + if (argc == 4) + { + iConnId = strtod(argv[2], NULL); + iUserIndex = strtod(argv[3], NULL); + uOutput = VoipGroupRemoteUserStatus(pApp->pVoipGroup, iConnId, iUserIndex); + ZPrintf("Status = %08x\n", uOutput); + if (uOutput == 0) + { + ZPrintf("No VoipUserRemote flags set\n"); + } + else + { + if (uOutput & VOIP_REMOTE_USER_HEADSETOK) + ZPrintf("VOIP_REMOTE_USER_HEADSETOK\n"); + if (uOutput & VOIP_REMOTE_USER_RECVVOICE) + ZPrintf("VOIP_REMOTE_CONN_RECVVOICE\n"); + } + } +} + + +/*F********************************************************************************/ +/*! + \Function _CmdVoiceCb + + \Description + Update voice command + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output standard return value + + \Version 07/22/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdVoiceCb(ZContext *argz, int32_t argc, char *argv[]) +{ + VoiceAppT *pApp = &_Voice_App; + + // check for kill + if (argc == 0) + { + _VoiceDestroyApp(pApp); + ZPrintf("%s: killed\n", argv[0]); + return(0); + } + + // keep running + return(ZCallback(&_CmdVoiceCb, 16)); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdVoice + + \Description + Initiate Voice connection. + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output standard return value + + \Version 07/22/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdVoice(ZContext *argz, int32_t argc, char *argv[]) +{ + T2SubCmdT *pCmd; + VoiceAppT *pApp = &_Voice_App; + unsigned char bHelp; + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_Voice_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" test the voip module\n"); + T2SubCmdUsage(argv[0], _Voice_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmd->pFunc != _VoiceCreate) && (pApp->pVoip == NULL)) + { + // only create the new ref if we are not exercising VoiceControl('dcde')\VoiceControl('drat') + // to modify the default sample rate and codec BEFORE the module creation + if ( !((strcmp(argv[1], "ctrl") == 0) && (strcmp(argv[2], "dcde") == 0)) && + !((strcmp(argv[1], "ctrl") == 0) && (strcmp(argv[2], "drat") == 0)) ) + { + char *pCreate = "create"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _VoiceCreate(pApp, 1, &pCreate, bHelp); + } + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + // one-time install of periodic callback + if (pApp->bZCallback == FALSE) + { + pApp->bZCallback = TRUE; + return(ZCallback(_CmdVoiceCb, 16)); + } + else + { + return(0); + } +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/ws.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/ws.c new file mode 100644 index 00000000..a4ce1fa6 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/ws.c @@ -0,0 +1,671 @@ +/*H********************************************************************************/ +/*! + \File ws.c + + \Description + Test WebSockets + + \Notes + Websockets test URL: ws://echo.websocket.org/ (see + http://www.websocket.org/echo.html). + + \Copyright + Copyright (c) Electronic Arts 2012. + + \Version 11/27/2012 (jbrookes) First Version +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/proto/protowebsocket.h" + +#include "libsample/zlib.h" +#include "libsample/zfile.h" +#include "libsample/zmem.h" +#include "testersubcmd.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct WSAppT +{ + ProtoWebSocketRefT *pWebSocket; + char *pSendBuf; + int32_t iSendLen; + int32_t iSendOff; + char *pRecvBuf; + int32_t iRecvBufLen; + uint8_t bUseMessageApis; // if TRUE, use message semantics for send/recv; specified as send argument +} WSAppT; + +/*** Function Prototypes **********************************************************/ + +static void _WSCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _WSDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _WSConnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _WSDisconnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _WSControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _WSStatus(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); +static void _WSSend(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); + +/*** Variables ********************************************************************/ + +static T2SubCmdT _WS_Commands[] = +{ + { "create", _WSCreate }, + { "destroy", _WSDestroy }, + { "connect", _WSConnect }, + { "disconnect", _WSDisconnect }, + { "ctrl", _WSControl }, + { "stat", _WSStatus }, + { "send", _WSSend }, + { "", NULL }, +}; + +static WSAppT _WS_App = { NULL, NULL, 0, 0, "", 0, 0 }; + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _WSResetApp + + \Description + Reset app, freeing buffers / resetting state + + \Input *pApp - app state + + \Version 03/31/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _WSResetApp(WSAppT *pApp) +{ + if (pApp->pSendBuf != NULL) + { + ZMemFree(pApp->pSendBuf); + pApp->pSendBuf = NULL; + } + pApp->iSendLen = 0; + pApp->iSendOff = 0; +} + +/*F********************************************************************************/ +/*! + \Function _WSDestroyApp + + \Description + Destroy app, freeing modules. + + \Input *pApp - app state + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _WSDestroyApp(WSAppT *pApp) +{ + // reset state + _WSResetApp(pApp); + + // free recv buffer + if (pApp->pRecvBuf != NULL) + { + ZMemFree(pApp->pRecvBuf); + pApp->pRecvBuf = NULL; + } + pApp->iRecvBufLen = 0; + + // destroy module + if (pApp->pWebSocket != NULL) + { + ProtoWebSocketDestroy(pApp->pWebSocket); + } + ds_memclr(pApp, sizeof(*pApp)); +} + +/* + + WS Commands + +*/ + +/*F*************************************************************************************/ +/*! + \Function _WSCreate + + \Description + WS subcommand - create websocket module + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/27/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _WSCreate(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + WSAppT *pApp = &_WS_App; + int32_t iBufSize = 4*1024; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s create \n", argv[0]); + return; + } + + // if we already have one, skip + if (pApp->pWebSocket != NULL) + { + ZPrintf("%s: websocket module already created\n", argv[0]); + return; + } + + // bufsize arg? + if (argc > 2) + { + iBufSize = (int32_t)strtol(argv[2], NULL, 10); + } + + // create app receive buffer (+1 to hold terminator character) + if ((pApp->pRecvBuf = ZMemAlloc(iBufSize+1)) == NULL) + { + ZPrintf("%s: could not allocate memory for application receive buffer\n", argv[0]); + _WSDestroyApp(pApp); + return; + } + pApp->iRecvBufLen = iBufSize+1; + + // create a websocket module if it isn't already started + if ((pApp->pWebSocket = ProtoWebSocketCreate(iBufSize)) == NULL) + { + ZPrintf("%s: error creating websocket module\n", argv[0]); + _WSDestroyApp(pApp); + } +} + +/*F*************************************************************************************/ +/*! + \Function _WSDestroy + + \Description + WS subcommand - destroy websocket module + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/27/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _WSDestroy(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + WSAppT *pApp = &_WS_App; + + if (bHelp == TRUE) + { + ZPrintf(" usage: %s destroy\n", argv[0]); + return; + } + + _WSDestroyApp(pApp); +} + +/*F*************************************************************************************/ +/*! + \Function _WSConnect + + \Description + WS subcommand - connect to server + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/27/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _WSConnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + WSAppT *pApp = &_WS_App; + int32_t iArg, iStartArg = 2; + char strApndHdr[1024] = ""; + + if ((bHelp == TRUE) || (argc < 3)) + { + ZPrintf(" usage: %s connect \n", argv[0]); + return; + } + + // reset app state + _WSResetApp(pApp); + + // check for connect args + for (iArg = iStartArg; (iArg < argc) && (argv[iArg][0] == '-'); iArg += 1) + { + if (!ds_strnicmp(argv[iArg], "-header=", 8)) + { + ds_strnzcat(strApndHdr, argv[iArg]+8, sizeof(strApndHdr)); + ds_strnzcat(strApndHdr, "\r\n", sizeof(strApndHdr)); + } + // skip any option arguments to find url and (optionally) filename + iStartArg += 1; + } + + // log connection attempt + ZPrintf("%s: connecting to '%s'\n", argv[0], argv[iStartArg]); + + // set append header? + if (strApndHdr[0] != '\0') + { + ProtoWebSocketControl(pApp->pWebSocket, 'apnd', 0, 0, strApndHdr); + } + + // start connect to remote user + ProtoWebSocketConnect(pApp->pWebSocket, argv[iStartArg]); +} + +/*F*************************************************************************************/ +/*! + \Function _WSDisconnect + + \Description + WS subcommand - disconnect from server + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/30/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _WSDisconnect(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + WSAppT *pApp = &_WS_App; + + if ((bHelp == TRUE) || (argc != 2)) + { + ZPrintf(" usage: %s disconnect\n", argv[0]); + return; + } + + // log connection attempt + ZPrintf("%s: disconnecting from server\n", argv[0], argv[2]); + + // start connect to remote user + ProtoWebSocketDisconnect(pApp->pWebSocket); +} + +/*F*************************************************************************************/ +/*! + \Function _WSControl + + \Description + WS control subcommand - set control options + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/27/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _WSControl(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + WSAppT *pApp = &_WS_App; + int32_t iCmd, iValue = 0, iValue2 = 0; + void *pValue = NULL; + + if ((bHelp == TRUE) || (argc < 3) || (argc > 6)) + { + ZPrintf(" usage: %s ctrl [cmd] \n", argv[0]); + return; + } + + // get the command + iCmd = ZGetIntArg(argv[2]); + + // get optional arguments + if (argc > 3) + { + iValue = ZGetIntArg(argv[3]); + } + if (argc > 4) + { + iValue2 = ZGetIntArg(argv[4]); + } + if (argc > 5) + { + pValue = argv[5]; + } + + // issue the control call + ProtoWebSocketControl(pApp->pWebSocket, iCmd, iValue, iValue2, pValue); +} + +/*F*************************************************************************************/ +/*! + \Function _WSStatus + + \Description + WS status subcommand - query module status + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/27/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _WSStatus(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + WSAppT *pApp = &_WS_App; + int32_t iCmd, iResult; + char strBuffer[256] = ""; + + if ((bHelp == TRUE) || (argc < 3) || (argc > 5)) + { + ZPrintf(" usage: %s stat \n", argv[0]); + return; + } + + // get the command + iCmd = ZGetIntArg(argv[2]); + + // issue the status call + iResult = ProtoWebSocketStatus(pApp->pWebSocket, iCmd, strBuffer, sizeof(strBuffer)); + + // report result + ZPrintf("ws: ProtoWebSocketStatus('%C') returned %d (\"%s\")\n", iCmd, iResult, strBuffer); +} + +/*F*************************************************************************************/ +/*! + \Function _WSSend + + \Description + WS status subcommand - send data + + \Input *pCmdRef - unused + \Input argc - argument count + \Input *argv[] - argument list + + \Version 11/29/2012 (jbrookes) +*/ +/**************************************************************************************F*/ +static void _WSSend(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp) +{ + WSAppT *pApp = &_WS_App; + int32_t iMsgLen, iStartArg=2, iMinArgs=4; + char *pMessage; + + if ((bHelp == TRUE) || (argc < iMinArgs)) + { + ZPrintf(" usage: %s send [-msg] -f -m \"message\"\n", argv[0]); + return; + } + + if (pApp->pSendBuf != NULL) + { + ZPrintf("%s: already sending data, please try later\n", argv[0]); + return; + } + + if (!strcmp(argv[iStartArg], "-msg")) + { + pApp->bUseMessageApis = TRUE; + iStartArg += 1; + iMinArgs += 1; + } + + if (!strcmp(argv[iStartArg], "-f") && (argc == iMinArgs)) + { + char *pFileData; + int32_t iFileSize; + + // try to open file + if ((pFileData = ZFileLoad(argv[iStartArg+1], &iFileSize, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) != NULL) + { + pMessage = pFileData; + iMsgLen = iFileSize; + } + else + { + ZPrintf("%s: could not open file '%s' for sending\n", argv[0], argv[iStartArg+1]); + return; + } + } + else if (!strcmp(argv[iStartArg], "-m")) + { + if ((iMsgLen = (int32_t)strlen(argv[iStartArg+1])) > 0) + { + pMessage = ZMemAlloc(iMsgLen+1); + ds_strnzcpy(pMessage, argv[iStartArg+1], iMsgLen+1); + } + else + { + ZPrintf("%s: will not send empty message\n", argv[0]); + return; + } + } + else + { + ZPrintf("%s: unknown send option '%s'\n", argv[0], argv[iStartArg]); + return; + } + + // set up send params + pApp->pSendBuf = pMessage; + pApp->iSendLen = iMsgLen; + pApp->iSendOff = 0; +} + +/*F*************************************************************************************/ +/*! + \Function _CmdWSSend + + \Description + Wraps ProtoWebSocketSend/Message + + \Input *pApp - WS app ref + \Input *pBuffer - send data + \Input iLength - length of send data + + \Version 04/11/2017 (jbrookes) +*/ +/**************************************************************************************F*/ +static int32_t _CmdWSSend(WSAppT *pApp, const char *pBuffer, int32_t iLength) +{ + return ((pApp->bUseMessageApis) ? ProtoWebSocketSendMessage(pApp->pWebSocket, pBuffer, iLength) : ProtoWebSocketSend(pApp->pWebSocket, pBuffer, iLength)); +} + +/*F*************************************************************************************/ +/*! + \Function _CmdWSRecv + + \Description + Wraps ProtoWebSocketRecv/Message + + \Input *pApp - WS app ref + \Input *pBuffer - [out] recv buffer + \Input iLength - length of recv buffer + + \Version 04/11/2017 (jbrookes) +*/ +/**************************************************************************************F*/ +static int32_t _CmdWSRecv(WSAppT *pApp, char *pBuffer, int32_t iLength) +{ + return ((pApp->bUseMessageApis) ? ProtoWebSocketRecvMessage(pApp->pWebSocket, pBuffer, iLength) : ProtoWebSocketRecv(pApp->pWebSocket, pBuffer, iLength)); +} + +/*F********************************************************************************/ +/*! + \Function _CmdWSCb + + \Description + Update WS command + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_T -standard return value + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CmdWSCb(ZContext *argz, int32_t argc, char *argv[]) +{ + WSAppT *pApp = &_WS_App; + int32_t iResult; + + // check for kill + if (argc == 0) + { + _WSDestroyApp(pApp); + ZPrintf("%s: killed\n", argv[0]); + return(0); + } + + // give life to the module + if (pApp->pWebSocket != NULL) + { + // update the module + ProtoWebSocketUpdate(pApp->pWebSocket); + + // processing, if we're connected + if (ProtoWebSocketStatus(pApp->pWebSocket, 'stat', NULL, 0) == 1) + { + // try and receive some data + if ((iResult = _CmdWSRecv(pApp, pApp->pRecvBuf, pApp->iRecvBufLen - 1)) > 0) + { + NetPrintf(("%s: received %d byte server frame\n", argv[0], iResult)); + // null terminate it so we can print it + pApp->pRecvBuf[iResult] = '\0'; + // print it + NetPrintWrap(pApp->pRecvBuf, 80); + // clear message flag + pApp->bUseMessageApis = FALSE; + } + else if (iResult == SOCKERR_NOMEM) + { + // our buffer is too small, so double it + int32_t iRecvBufLen = pApp->iRecvBufLen * 2; + char *pRecvBuf = ZMemAlloc(iRecvBufLen); + if (pRecvBuf != NULL) + { + ZPrintf("%s: increasing receive buffer to %d bytes\n", argv[0], iRecvBufLen); + ds_memcpy_s(pRecvBuf, iRecvBufLen, pApp->pRecvBuf, pApp->iRecvBufLen); + ZMemFree(pApp->pRecvBuf); + pApp->pRecvBuf = pRecvBuf; + pApp->iRecvBufLen = iRecvBufLen; + } + else + { + ZPrintf("%s: error allocating %d bytes of memory for receive buffer\n", iRecvBufLen); + } + } + else if (iResult < 0) + { + ZPrintf("%s: error %d receiving\n", argv[0], iResult); + // clear message flag + pApp->bUseMessageApis = FALSE; + // keep running + return(ZCallback(&_CmdWSCb, 16)); + } + + // try and send some data + if (pApp->pSendBuf != NULL) + { + if ((iResult = _CmdWSSend(pApp, pApp->pSendBuf + pApp->iSendOff, pApp->iSendLen - pApp->iSendOff)) > 0) + { + pApp->iSendOff += iResult; + if (pApp->iSendOff == pApp->iSendLen) + { + ZPrintf("%s: sent %d byte message\n", argv[0], pApp->iSendLen); + ZMemFree(pApp->pSendBuf); + pApp->pSendBuf = NULL; + } + } + else if (iResult < 0) + { + ZPrintf("%s: error %d sending message\n", argv[0], iResult); + } + } + } + } + + // keep running + return(ZCallback(&_CmdWSCb, 16)); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CmdWS + + \Description + WS (WebSocket) command + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output + int32_t - standard return value + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CmdWS(ZContext *argz, int32_t argc, char *argv[]) +{ + T2SubCmdT *pCmd; + WSAppT *pApp = &_WS_App; + int32_t iResult = 0; + uint8_t bHelp; + + // handle basic help + if ((argc <= 1) || (((pCmd = T2SubCmdParse(_WS_Commands, argc, argv, &bHelp)) == NULL))) + { + ZPrintf(" test the websocket module\n"); + T2SubCmdUsage(argv[0], _WS_Commands); + return(0); + } + + // if no ref yet, make one + if ((pCmd->pFunc != _WSCreate) && (pApp->pWebSocket == NULL)) + { + char *pCreate = "create"; + ZPrintf(" %s: ref has not been created - creating\n", argv[0]); + _WSCreate(pApp, 1, &pCreate, bHelp); + iResult = ZCallback(_CmdWSCb, 16); + } + + // hand off to command + pCmd->pFunc(pApp, argc, argv, bHelp); + + // one-time install of periodic callback + if (pCmd->pFunc == _WSCreate) + { + iResult = ZCallback(_CmdWSCb, 16); + } + return(iResult); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/modules/xml.c b/src/thirdparty/dirtysdk/sample/tester2/source/modules/xml.c new file mode 100644 index 00000000..9136573b --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/modules/xml.c @@ -0,0 +1,320 @@ +/*H********************************************************************************/ +/*! + \File xml.c + + \Description + Test the XML parser. + + \Copyright + Copyright (c) 1999-2005 Electronic Arts Inc. + + \Version 10/04/1999 (gschaefer) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/xml/xmlparse.h" +#include "DirtySDK/xml/xmlformat.h" +#include "testermodules.h" + +#include "libsample/zlib.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +// Variables + +static const char _CmdXml_strData1[] = +"" +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +"]> " +"" +" 123456789012345678" +" 0" +" Successfully Retrieved Data" +" Dudeman" +" Dudeman" +" 0" +" 1" +" dudeman@ea.com" +" 25" +" 12" +" 1970" +" USA" +" 1" +" 0" +""; + +static const char _CmdXml_strData2[] = +" " +" " +" " +" " +" " +" " +" " +" " +""; + +static const char _CmdXml_strData3[] = +#if 0 +"" +"1" +"" +"" +"" +"xml to entity failed, xml is invalid.unexpected element (uri:\"\", local:\"html\"). Expected elements are <{}error>" +//"xml to entity failed, xml is invalid.unexpected element (uri:\"\", local:\"html\"). Expected elements are {}error" +"" +"" +"" +#elif 0 +"" +"" +"" +"" +"" +"" +"My Default Persona" +"" +"" +"" +"" +"" +"" +"Feature not supported yet." +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"Feature not supported yet." +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"Requested list not found." +"" +"" +"" +"" +"" +"" +"" +"My Default Persona" +"" +"" +"" +"" +"" +"" +"Feature not supported yet." +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"Feature not supported yet." +"" +"" +"" +"" +"" +"" +"" +"" +"" +"Requested list not found." +"" +"" +"<" +"list name='block" +"ed_users'/>" +"" +" " +#endif +; + + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function CmdXml + + \Description + Test the Xml parser + + \Input *argz - environment + \Input argc - standard number of arguments + \Input *argv[] - standard arg list + + \Output standard return value + + \Version 10/04/1999 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t CmdXml(ZContext *argz, int32_t argc, char *argv[]) +{ + int32_t iIndex; + char strData[1024]; + const char *pXml; + + for (pXml = _CmdXml_strData3; pXml != NULL; pXml = XmlSkip(pXml)) + { + ZPrintf("%s\n", XmlComplete(pXml) ? "valid" : "invalid"); + } + + XmlPrintFmt((_CmdXml_strData3, "")); + + // check usage + if (argc < 2) + { + ZPrintf(" test xmlparse and xmlformat modules\n"); + ZPrintf(" usage: %s testnum [1..3]\n", argv[0]); + return(0); + } + + // get the index + iIndex = atoi(argv[1]); + + // do the tests + if (iIndex == 1) + { + XmlContentGetString(XmlFind(_CmdXml_strData1, "get-account-info-reply.email"), strData, sizeof(strData), ""); + ZPrintf("email=%s\n", strData); + + ZPrintf("id=%lld\n", XmlContentGetInteger64(XmlFind(_CmdXml_strData1, "get-account-info-reply.id"), -1)); + ZPrintf("bdateyear=%d\n", XmlContentGetInteger(XmlFind(_CmdXml_strData1, "get-account-info-reply.bdateyear"), -1)); + } + if (iIndex == 2) + { + pXml = XmlFind(_CmdXml_strData2, "TOURNAMENT.MATCHES"); + ZPrintf("hit=%s\n", pXml); + + ZPrintf("beginDate=%lld\n", XmlAttribGetInteger64(XmlFind(_CmdXml_strData2, "TOURNAMENT"), "beginDate", -1)); + ZPrintf("id=%d\n", XmlAttribGetInteger(XmlFind(_CmdXml_strData2, "TOURNAMENT"), "id", -1)); + } + if (iIndex == 3) + { + char strBuffer[1024]; + + // encode some XML + XmlInit(strBuffer, sizeof(strBuffer), XML_FL_WHITESPACE); + + XmlTagStart(strBuffer, "TestFormat1"); + XmlElemAddString(strBuffer, "strText", "Some chars that should be encoded: =, <, >, &, \", \x7f"); + XmlTagEnd(strBuffer); + + XmlTagStart(strBuffer, "TestFormat2"); + XmlElemAddString(strBuffer, "strText", "Some chars that should not be encoded: \t, \r, \n"); + XmlTagEnd(strBuffer); + + XmlFinish(strBuffer); + + ZPrintf("encoded xml:\n------------\n%s\n-----------\n", strBuffer); + + // now parse the encoded xml + XmlContentGetString(XmlFind(strBuffer, "TestFormat1.strText"), strData, sizeof(strData), ""); + ZPrintf("test1=%s\n", strData); + + XmlContentGetString(XmlFind(strBuffer, "TestFormat2.strText"), strData, sizeof(strData), ""); + ZPrintf("test2=%s\n", strData); + } + + return(0); +} + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/pc/T2Client.cpp b/src/thirdparty/dirtysdk/sample/tester2/source/pc/T2Client.cpp new file mode 100644 index 00000000..8f520e76 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/pc/T2Client.cpp @@ -0,0 +1,795 @@ +/*H********************************************************************************/ +/*! + \File T2Client.cpp + + \Description + Defines the entry point for the application. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/21/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#endif + +#pragma warning(push,0) +#include +#pragma warning(pop) + +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/util/jsonformat.h" +#include "DirtySDK/util/jsonparse.h" +#include "libsample/zmem.h" +#include "libsample/zmemtrack.h" +#include "testermodules.h" +#include "testerprofile.h" +#include "testerregistry.h" +#include "testerclientcore.h" +#include "testercomm.h" +#include "T2ClientResource.h" + +/*** Defines **********************************************************************/ + +#define MAX_LOADSTRING (100) + +// custom events +#define WM_CLEARSCREEN (WM_APP+1000) + +/*** Type Definitions *************************************************************/ + +typedef struct T2ClientGeometryT +{ + uint32_t uTopBorder; + uint32_t uConsoleBottomToCommandTop; + uint32_t uCommandHeight; + uint32_t uBottomBorder; + uint32_t uLeftBorder; + uint32_t uRightBorder; +} T2ClientGeometryT; + +typedef struct T2ClientT +{ + HINSTANCE hInst; //!< current instance + TCHAR strTitle[MAX_LOADSTRING]; //!< The title bar text + TCHAR strWindowClass[MAX_LOADSTRING]; //!< the main window class name + char strCurrentProfile[TESTERPROFILE_PROFILEFILENAME_SIZEDEFAULT]; // active profile + TesterClientCoreT *pClientCore; //!< pointer to client core structure + int32_t iTimerID; //!< timer used to call NetIdle(); + WNDPROC CommandEditProc; //!< edit box handler + WNDPROC ConsoleEditProc; //!< console box handler + int32_t iCommandHistoryOffset; //!< command history offset + T2ClientGeometryT Geometry; //!< window geometry + char strConnectParams[512]; //!< connect parameters + char strScript[256]; //!< script to execute + int32_t iScrollbackSize; +} T2ClientT; + +/*** forward Declaration ********************************************************************/ +VOID CALLBACK _T2ClientTimerCallback(HWND hDlg, UINT uMsg, UINT_PTR pIDEvent, DWORD dTime); + + +/*** Variables ********************************************************************/ + +static T2ClientT T2Client = {NULL, "", "", "", NULL, 0, NULL, NULL, 1, {0,0,0,0,0,0}}; + +/*** External Functions ***********************************************************/ + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _T2ClientDisplayOutput + + \Description + Take input from TesterConsole and dump it to the specified edit box. + + \Input *pBuf - string containing the debug output to display + \Input iLen - length of buffer + \Input iRefcon - user-specified parameter (unused) + \Input *pRefptr - user-specified parameter (window pointer) + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _T2ClientDisplayOutput(const char *pBuf, int32_t iLen, int32_t iRefcon, void *pRefptr) +{ + int32_t iAdd; + HWND Ctrl = (HWND)pRefptr; + char strTempText[16*1024], *pTempText, cPrev; + + // replace cr with crlf for proper display + for (pTempText = strTempText, cPrev = '\0'; (*pBuf != '\0') && ((pTempText-strTempText) < (sizeof(strTempText)-1)); pBuf++, pTempText++) + { + if ((*pBuf == '\n') && (cPrev != '\r')) + { + *pTempText++ = '\r'; + } + *pTempText = cPrev = *pBuf; + } + *pTempText = '\0'; + + // see if we need to delete old data + if (SendMessage(Ctrl, WM_GETTEXTLENGTH, 0, 0) > T2Client.iScrollbackSize) { + SendMessage(Ctrl, EM_SETSEL, 0, 8192); + SendMessage(Ctrl, EM_REPLACESEL, FALSE, (LPARAM)""); + iAdd = SendMessage(Ctrl, WM_GETTEXTLENGTH, 0, 0); + SendMessage(Ctrl, EM_SETSEL, iAdd-1, iAdd); + SendMessage(Ctrl, EM_REPLACESEL, FALSE, (LPARAM)""); + } + + iAdd = SendMessage(Ctrl, WM_GETTEXTLENGTH, 0, 0); + SendMessage(Ctrl, EM_SETSEL, iAdd, iAdd); + SendMessage(Ctrl, EM_REPLACESEL, FALSE, (LPARAM)strTempText); + SendMessage(Ctrl, EM_SCROLLCARET, 0, 0); +} + + +/*F********************************************************************************/ +/*! + \Function _T2ClientCommandEditProc + + \Description + Message handler for the command line edit box. Intercepts uparrow, etc. + + \Input hDlg - dialog handle + \Input uMessage - message identifier + \Input wParam - message specifics (pointer to uint32_t) + \Input lParam - message specifics (pointer) + + \Output LRESULT - message handling status return code + + \Version 04/06/2005 (jfrank) +*/ +/********************************************************************************F*/ +static LRESULT CALLBACK _T2ClientCommandEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + const char *pCommandLine = NULL; + + if (msg == WM_KEYDOWN) + { + // adjust if necessary + if (wParam == VK_UP) + { + T2Client.iCommandHistoryOffset--; + pCommandLine = TesterClientCoreGetHistoricalCommand(T2Client.pClientCore, &(T2Client.iCommandHistoryOffset), NULL, 0); + } + else if (wParam == VK_DOWN) + { + T2Client.iCommandHistoryOffset++; + pCommandLine = TesterClientCoreGetHistoricalCommand(T2Client.pClientCore, &(T2Client.iCommandHistoryOffset), NULL, 0); + } + + // if we got a key, redraw and quit + if (pCommandLine != NULL) + { + SetWindowText(hWnd, pCommandLine); + SendMessage(hWnd, EM_SETSEL, 0, -1); + SendMessage(hWnd, EM_SETSEL, (WPARAM)-1, 0); + return(0); + } + } + // not handled by this handler - call the original one + return(CallWindowProc( static_cast(T2Client.CommandEditProc), hWnd, msg, wParam, lParam )); +} + + +/*F********************************************************************************/ +/*! + \Function _T2ClientConsoleEditProc + + \Description + Message handler for the console box. Intercepts messages. + + \Input hDlg - dialog handle + \Input uMessage - message identifier + \Input wParam - message specifics (pointer to uint32_t) + \Input lParam - message specifics (pointer) + + \Output LRESULT - message handling status return code + + \Version 04/06/2005 (jfrank) +*/ +/********************************************************************************F*/ +static LRESULT CALLBACK _T2ClientConsoleEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + int32_t wmId, wmEvent; + + // find out the control ID which requires attention + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + + if (msg == WM_CONTEXTMENU) + { + // Create the popup menu + HMENU hPopup; + POINT p; + + GetCursorPos(&p); + hPopup = LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE( IDR_CONSOLECONTEXTMENU )); + hPopup = GetSubMenu(hPopup, 0); + TrackPopupMenu(hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON, p.x, p.y, 0, hWnd, NULL); + return(0); + } + else if (msg == WM_COMMAND) + { + if (wmId == ID_CONSOLEOPTIONS_CLEAR) + { + TesterConsoleT *pConsole; + + if ((pConsole = (TesterConsoleT *)TesterRegistryGetPointer("CONSOLE")) != NULL) + { + TesterConsoleClear(pConsole); + SetWindowText(hWnd, ""); + } + return(0); + } + else if (wmId == ID_CONSOLEOPTIONS_SELECTALL) + { + SendMessage(hWnd, EM_SETSEL, 0, -1); + } + else if (wmId == ID_CONSOLEOPTIONS_RECONNECT) + { + ZPrintf("T2Client ---> Reconnecting the client and host.\n"); + TesterClientCoreDisconnect(T2Client.pClientCore); + TesterClientCoreConnect(T2Client.pClientCore, T2Client.strConnectParams); + } + else if (wmId == ID_CONSOLEOPTIONS_DISCONNECT) + { + ZPrintf("T2Client ---> Disconnecting the client from the host.\n"); + TesterClientCoreDisconnect(T2Client.pClientCore); + } + } + // not handled by this handler - call the original one + return(CallWindowProc( static_cast(T2Client.ConsoleEditProc), hWnd, msg, wParam, lParam)); +} + + +/*F********************************************************************************/ +/*! + \Function _T2ClientCommandConsole + + \Description + Message handler for command console box. + + \Input hDlg - dialog handle + \Input uMessage - message identifier + \Input wParam - message specifics (pointer to uint32_t) + \Input lParam - message specifics (pointer) + + \Output LRESULT - message handling status return code + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static LRESULT CALLBACK _T2ClientCommandConsole(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam) +{ + const char strTitlePrefix[] = "TesterClient:"; + char strCommand[TESTERCOMM_COMMANDSIZE_MAX]; + char strTitle[TESTERPROFILE_PROFILEFILENAME_SIZEDEFAULT + sizeof(strTitlePrefix) + 1]; + char *pCommand; + int32_t wmId, wmEvent; + HFONT font; + RECT CommandConsoleRect, ConsoleRect, CommandRect; + uint32_t uX, uY, uWidth, uHeight; + + // find out the control ID which requires attention + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + + switch (uMessage) + { + case WM_INITDIALOG: + { + char strHostname[128]; + uint16_t aJson[512]; + + // get the window geometry + GetWindowRect(hDlg, &CommandConsoleRect); + GetWindowRect(GetDlgItem(hDlg, IDC_EDIT_CONSOLE), &ConsoleRect); + GetWindowRect(GetDlgItem(hDlg, IDC_EDIT_COMMAND), &CommandRect); + T2Client.Geometry.uLeftBorder = ConsoleRect.left - CommandConsoleRect.left - 4; + T2Client.Geometry.uRightBorder = CommandConsoleRect.right - ConsoleRect.right - 4; + T2Client.Geometry.uBottomBorder = CommandConsoleRect.bottom - CommandRect.bottom; + T2Client.Geometry.uConsoleBottomToCommandTop = CommandRect.top - ConsoleRect.bottom; + T2Client.Geometry.uCommandHeight = CommandRect.bottom - CommandRect.top; + T2Client.Geometry.uTopBorder = ConsoleRect.top - 23; + + // set up custom handler for up arrow + #if defined(_WIN64) + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + T2Client.CommandEditProc = reinterpret_cast(SetWindowLongPtr( GetDlgItem(hDlg, IDC_EDIT_COMMAND), GWLP_WNDPROC,(LPARAM)_T2ClientCommandEditProc )); + T2Client.ConsoleEditProc = reinterpret_cast(SetWindowLongPtr( GetDlgItem(hDlg, IDC_EDIT_CONSOLE), GWLP_WNDPROC,(LPARAM)_T2ClientConsoleEditProc )); + #else + SetWindowLong(hDlg, GWL_USERDATA, lParam); + T2Client.CommandEditProc = reinterpret_cast(SetWindowLong( GetDlgItem(hDlg, IDC_EDIT_COMMAND), GWL_WNDPROC,(LPARAM)_T2ClientCommandEditProc )); + T2Client.ConsoleEditProc = reinterpret_cast(SetWindowLong( GetDlgItem(hDlg, IDC_EDIT_CONSOLE), GWL_WNDPROC,(LPARAM)_T2ClientConsoleEditProc )); + #endif + + // set the font + font = CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FIXED_PITCH | FF_DONTCARE, ""); + SendDlgItemMessage(hDlg, IDC_EDIT_CONSOLE, WM_SETFONT, (LPARAM)font, 0); + + // set the display function + TesterClientCoreDisplayFunc(T2Client.pClientCore, _T2ClientDisplayOutput, 0, GetDlgItem(hDlg, IDC_EDIT_CONSOLE)); + + SendMessage( GetDlgItem(hDlg, IDC_EDIT_CONSOLE), (UINT) EM_LIMITTEXT, T2Client.iScrollbackSize+8192, 0 ); + + // set the title bar + JsonParse(aJson, sizeof(aJson)/sizeof(*aJson), T2Client.strConnectParams, -1); + JsonGetString(JsonFind(aJson, "HOSTNAME"), strHostname, sizeof(strHostname), ""); + if (strHostname[0] == '\0') + { + ds_strnzcpy(strHostname, "listening", sizeof(strHostname)); + } + ds_snzprintf(strTitle, sizeof(strTitle), "%s %s (%s)", strTitlePrefix, T2Client.strCurrentProfile, strHostname); + SetWindowText(hDlg, strTitle); + + // start the timer for IDLE callback handling + T2Client.iTimerID = SetTimer( GetParent(hDlg), 1, (uint32_t)(1000/60), _T2ClientTimerCallback); + } + + break; + case WM_SIZE: + + // get the new main window size + GetClientRect(hDlg, &CommandConsoleRect); + + // draw the console + uX = CommandConsoleRect.left + T2Client.Geometry.uLeftBorder; + uY = CommandConsoleRect.top + T2Client.Geometry.uTopBorder; + uWidth = (CommandConsoleRect.right - CommandConsoleRect.left) + - T2Client.Geometry.uLeftBorder - T2Client.Geometry.uRightBorder; + uHeight = (CommandConsoleRect.bottom - CommandConsoleRect.top) + - T2Client.Geometry.uBottomBorder - T2Client.Geometry.uTopBorder + - T2Client.Geometry.uCommandHeight - T2Client.Geometry.uConsoleBottomToCommandTop; + MoveWindow(GetDlgItem(hDlg, IDC_EDIT_CONSOLE), uX, uY, uWidth, uHeight, TRUE); + + // draw the command window - shares some stuff with the console + uY = CommandConsoleRect.bottom - T2Client.Geometry.uBottomBorder + - T2Client.Geometry.uCommandHeight; + uHeight = T2Client.Geometry.uCommandHeight; + MoveWindow(GetDlgItem(hDlg, IDC_EDIT_COMMAND), uX, uY, uWidth, uHeight, TRUE); + + break; + case WM_COMMAND: + // check for EXIT button or exit icon (upper right X) + if ((wmId == ID_BUTTON_EXIT) || (wmId == IDCANCEL)) + { + EndDialog(hDlg, wmId); + PostQuitMessage(0); + return(TRUE); + } + // check to see if we hit enter + else if (wmId == IDOK) + { + GetWindowText( GetDlgItem(hDlg, IDC_EDIT_COMMAND), &(strCommand[1]), sizeof(strCommand)-2); + + // specially process the ! command + if ((strCommand[1] == '!') || (strCommand[1] == '?')) + { + strCommand[0] = strCommand[1]; + strCommand[1] = ' '; + pCommand = &(strCommand[0]); + } + else + { + pCommand = &(strCommand[1]); + } + TesterClientCoreSendCommand(T2Client.pClientCore, pCommand); + SetWindowText( GetDlgItem(hDlg, IDC_EDIT_COMMAND), ""); + // reset the offset + T2Client.iCommandHistoryOffset = 1; + } + // end + break; + } + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function _T2ClientTimerCallback + + \Description + Timer callback - used to activate the TesterCoreIdle function. + + \Input Not used. + + \Output None + + \Version 03/22/2005 (jfrank) +*/ +/********************************************************************************F*/ +VOID CALLBACK _T2ClientTimerCallback(HWND hDlg, UINT uMsg, UINT_PTR pIDEvent, DWORD dTime) +{ + if (T2Client.pClientCore) + { + if(strlen(T2Client.strScript) > 0) + { + char strCommand[256]; + sprintf(strCommand, "runscript %s", T2Client.strScript); + TesterClientCoreSendCommand(T2Client.pClientCore, strCommand); + T2Client.strScript[0] = '\0'; + } + + // pump the networking layer + TesterClientCoreIdle(T2Client.pClientCore); + } + return; +} + +/*F********************************************************************************/ +/*! + \Function _T2ClientProfilesLoad + + \Description + Load all the profile information into the profile dialog combo boxes. + + \Input hDlg - dialog handle + + \Output None + + \Version 03/22/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _T2ClientProfilesLoad(HWND hDlg) +{ + TesterProfileEntryT Entry; + int32_t iEntryNum = 0; + + // get all the entries + while(TesterClientCoreProfileGet(T2Client.pClientCore, iEntryNum++, &Entry) >= 0) + { + // set the dialog combo boxes + SendMessage( GetDlgItem(hDlg, IDC_COMBO_PROFILENAME), + (UINT)CB_INSERTSTRING, (WPARAM)-1, (LPARAM)(LPCTSTR)(Entry.strProfileName)); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_SHARINGLOCATION), + (UINT)CB_INSERTSTRING, (WPARAM)-1, (LPARAM)(LPCTSTR)(Entry.strControlDirectory)); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_STARTUPPARAMS), + (UINT)CB_INSERTSTRING, (WPARAM)-1, (LPARAM)(LPCTSTR)(Entry.strCommandLine)); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_FILELOGDIRECTORY), + (UINT)CB_INSERTSTRING, (WPARAM)-1, (LPARAM)(LPCTSTR)(Entry.strLogDirectory)); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_HOSTNAME), + (UINT)CB_INSERTSTRING, (WPARAM)-1, (LPARAM)(LPCTSTR)(Entry.strHostname)); + } +} + +/*F********************************************************************************/ +/*! + \Function _T2ClientProfileDialogReset + + \Description + Reset the contents and selections in the profile dialog box + and reload all the profiles. + + \Input hDlg - dialog handle + + \Output None + + \Version 03/22/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _T2ClientProfileDialogReset(HWND hDlg) +{ + // clear all the lists + SendMessage( GetDlgItem(hDlg, IDC_COMBO_PROFILENAME), (UINT)CB_RESETCONTENT, 0, 0); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_SHARINGLOCATION), (UINT)CB_RESETCONTENT, 0, 0); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_STARTUPPARAMS), (UINT)CB_RESETCONTENT, 0, 0); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_FILELOGDIRECTORY), (UINT)CB_RESETCONTENT, 0, 0); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_HOSTNAME), (UINT)CB_RESETCONTENT, 0, 0); + // clear the check boxes + SendMessage( GetDlgItem(hDlg, IDC_CHECKBOX_LOGGING), (UINT)BM_SETCHECK, BST_UNCHECKED, 0); + SendMessage( GetDlgItem(hDlg, IDC_CHECKBOX_STARTNETWORKING),(UINT)BM_SETCHECK, BST_UNCHECKED, 0); + // load all the profiles + _T2ClientProfilesLoad(hDlg); +} + +/*F********************************************************************************/ +/*! + \Function _T2ClientProfileSave + + \Description + Save the currently selected profile using the profile manager + + \Input hDlg - dialog handle + \Input *pEntry - [out,optional] storage for entry + + \Version 03/22/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _T2ClientProfileSave(HWND hDlg, TesterProfileEntryT *pEntry) +{ + TesterProfileEntryT Entry; + ds_memclr(&Entry, sizeof(Entry)); + + // create the profile entry parts + GetWindowText(GetDlgItem(hDlg, IDC_COMBO_PROFILENAME), Entry.strProfileName, sizeof(Entry.strProfileName)-1); + GetWindowText(GetDlgItem(hDlg, IDC_COMBO_SHARINGLOCATION), Entry.strControlDirectory, sizeof(Entry.strControlDirectory)-1); + GetWindowText(GetDlgItem(hDlg, IDC_COMBO_STARTUPPARAMS), Entry.strCommandLine, sizeof(Entry.strCommandLine)-1); + GetWindowText(GetDlgItem(hDlg, IDC_COMBO_FILELOGDIRECTORY), Entry.strLogDirectory, sizeof(Entry.strLogDirectory)-1); + GetWindowText(GetDlgItem(hDlg, IDC_COMBO_HOSTNAME), Entry.strHostname, sizeof(Entry.strHostname)-1); + Entry.uLogEnable = (unsigned char)SendMessage( GetDlgItem(hDlg, IDC_CHECKBOX_LOGGING), (UINT)BM_GETCHECK, 0, 0); + Entry.uNetworkStartup = (unsigned char)SendMessage( GetDlgItem(hDlg, IDC_CHECKBOX_STARTNETWORKING), (UINT)BM_GETCHECK, 0, 0); + + // make sure we have a profile name + if (Entry.strProfileName[0] == '\0') + { + ds_snzprintf(Entry.strProfileName, sizeof(Entry.strProfileName), "profile-%d\n", NetRand(32*1024)); + } + + // add the profile + TesterClientCoreProfileAdd(T2Client.pClientCore, &Entry); + + // copy back to user + if (pEntry != NULL) + { + ds_memcpy(pEntry, &Entry, sizeof(*pEntry)); + } +} + + +/*F********************************************************************************/ +/*! + \Function _T2ClientSetActiveProfile + + \Description + Set the active profile in the profile list + + \Input hDlg - dialog handle + \Input iItemNum - item number in the list + + \Output None + + \Version 03/28/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _T2ClientSetActiveProfile(HWND hDlg, int32_t iItemNum) +{ + TesterProfileEntryT Entry; + //char strProfile[TESTERPROFILE_PROFILEFILENAME_SIZEDEFAULT]; + //char strPlatform[TESTERPROFILE_PLATFORM_SIZEDEFAULT]; + //uint32_t uLogEnable, uNetworkStartup; + + // determine the numeric settings for the profile + iItemNum = TesterClientCoreProfileGet(T2Client.pClientCore, iItemNum, &Entry); + + // set the defaults + SendMessage( GetDlgItem(hDlg, IDC_COMBO_PROFILENAME), (UINT)CB_SETCURSEL, iItemNum, 0); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_SHARINGLOCATION), (UINT)CB_SETCURSEL, iItemNum, 0); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_STARTUPPARAMS), (UINT)CB_SETCURSEL, iItemNum, 0); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_FILELOGDIRECTORY), (UINT)CB_SETCURSEL, iItemNum, 0); + SendMessage( GetDlgItem(hDlg, IDC_COMBO_HOSTNAME), (UINT)CB_SETCURSEL, iItemNum, 0); + SendMessage( GetDlgItem(hDlg, IDC_CHECKBOX_LOGGING), (UINT)BM_SETCHECK, (Entry.uLogEnable ? BST_CHECKED : BST_UNCHECKED), 0); + SendMessage( GetDlgItem(hDlg, IDC_CHECKBOX_STARTNETWORKING),(UINT)BM_SETCHECK, (Entry.uNetworkStartup ? BST_CHECKED : BST_UNCHECKED), 0); +} + + + +/*F********************************************************************************/ +/*! + \Function _T2ClientProfileSelect + + \Description + Message handler for profile select dialog. + + \Input hDlg - dialog handle + \Input uMessage - message identifier + \Input wParam - message specifics (pointer to uint32_t) + \Input lParam - message specifics (pointer) + + \Output LRESULT - message handling status return code + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +static LRESULT CALLBACK _T2ClientProfileSelect(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam) +{ + TesterProfileEntryT Entry; + int32_t wmId, wmEvent; + int32_t iItemNum; + + // find out the control ID which requires attention + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + + switch (uMessage) + { + case WM_INITDIALOG: + // wipe out all the lists and start over + _T2ClientProfileDialogReset(hDlg); + // activate the default profile + _T2ClientSetActiveProfile(hDlg, -1); + break; + case WM_COMMAND: + // check for EXIT button or exit icon (upper right X) + if ((wmId == IDCANCEL) || (wmId == ID_BUTTON_EXIT)) + { + EndDialog(hDlg, wmId); + PostQuitMessage(0); + return(TRUE); + } + // check for action on the profile name + else if (wmId == IDC_COMBO_PROFILENAME) + { + if (wmEvent == CBN_SELENDOK) + { + // we've selected something in the profile list + iItemNum = SendMessage( GetDlgItem(hDlg, IDC_COMBO_PROFILENAME), (UINT)CB_GETCURSEL, 0, 0); + _T2ClientSetActiveProfile(hDlg, iItemNum); + } + } + // check for action on the sharing location + else if (wmId == IDC_COMBO_SHARINGLOCATION) + { + // nothing to do + } + // check for action on the sharing location + else if (wmId == IDC_COMBO_HOSTNAME) + { + // nothing to do + } + // check for saving the profile + else if (wmId == IDC_BUTTON_SAVEPROFILE) + { + GetWindowText( GetDlgItem(hDlg, IDC_COMBO_PROFILENAME), Entry.strProfileName, sizeof(Entry.strProfileName)-1); + if (strlen(Entry.strProfileName) > 0) + { + // save the current profile manually + _T2ClientProfileSave(hDlg, NULL); + // reset the window state + _T2ClientProfileDialogReset(hDlg); + // now set the current one as default and display it + TesterClientCoreProfileDefault(T2Client.pClientCore, Entry.strProfileName); + _T2ClientSetActiveProfile(hDlg, -1); + } + } + // check for deleting the profile + else if (wmId == IDC_BUTTON_DELETEPROFILE) + { + ds_memclr((void *)Entry.strProfileName, sizeof(Entry.strProfileName)); + GetWindowText( GetDlgItem(hDlg, IDC_COMBO_PROFILENAME), Entry.strProfileName, sizeof(Entry.strProfileName)-1); + // delete the current profile + TesterClientCoreProfileDelete(T2Client.pClientCore, Entry.strProfileName); + // reset the window state + _T2ClientProfileDialogReset(hDlg); + } + // connect? + else if (wmId == ID_BUTTON_CONNECT) + { + char buffer[64]; + + // save the current profile manually + _T2ClientProfileSave(hDlg, &Entry); + // set it as the default + TesterClientCoreProfileDefault(T2Client.pClientCore, Entry.strProfileName); + ds_strnzcpy(T2Client.strCurrentProfile, Entry.strProfileName, sizeof(T2Client.strCurrentProfile)); + // get profile index + iItemNum = SendMessage(GetDlgItem(hDlg, IDC_COMBO_PROFILENAME), (UINT)CB_GETCURSEL, 0, 0); + + // create the specified entry parameters + JsonInit(T2Client.strConnectParams, sizeof(T2Client.strConnectParams), 0); + JsonAddInt(T2Client.strConnectParams, "PROFILENUM", iItemNum); + JsonAddStr(T2Client.strConnectParams, "PROFILENAME", Entry.strProfileName); + + JsonAddStr(T2Client.strConnectParams, "INPUTFILE", TESTERCOMM_CLIENTINPUTFILE); + JsonAddStr(T2Client.strConnectParams, "OUTPUTFILE", TESTERCOMM_CLIENTOUTPUTFILE); + JsonAddStr(T2Client.strConnectParams, "CONTROLDIR", Entry.strControlDirectory); + JsonAddStr(T2Client.strConnectParams, "HOSTNAME", Entry.strHostname); + + // connect the client core + TesterClientCoreConnect(T2Client.pClientCore, JsonFinish(T2Client.strConnectParams)); + + // start the timer for IDLE callback handling + T2Client.iTimerID = SetTimer( GetParent(hDlg), 1, (uint32_t)(1000/60), _T2ClientTimerCallback); + + GetWindowText(GetDlgItem(hDlg, IDC_SCROLLBACKSIZE), buffer, sizeof(buffer)); + + T2Client.iScrollbackSize = atol(buffer)*1024; + if (T2Client.iScrollbackSize<512000) + { + T2Client.iScrollbackSize = 512000; + } + + // and kill this dialog + EndDialog(hDlg, wmId); + return(TRUE); + } + + // end + break; + } + return(FALSE); +} + + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function WinMain + + \Description + Main() routine. Starts GUI. + + \Input hInstance - window instance + \Input hPrevInstance - previous (calling) window instance + \Input lpCmdLine - command line parameters + \Input iCmdShow - show/hide the window + + \Output 0 for success + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPTSTR lpCmdLine, int32_t iCmdShow) +{ + ZPrintf(("\nStarting T2Client.\n\n")); + + ZMemtrackStartup(); + + // start up networking + NetConnStartup(""); + + // create and connect the core module + T2Client.pClientCore = TesterClientCoreCreate(lpCmdLine); + + if(lpCmdLine[0] == 0) + { + DialogBox(T2Client.hInst, (LPCTSTR)IDD_PROFILESELECT, NULL, (DLGPROC)_T2ClientProfileSelect); + } + else + { + char strPlatform[256]; + char strHost[256]; + char strLogFile[256]; + char strControlDir[256]; + + ds_strnzcpy(strPlatform, strtok(lpCmdLine, " "), sizeof(strPlatform)); + ds_strnzcpy(strHost, strtok(NULL, " "), sizeof(strHost)); + ds_strnzcpy(T2Client.strScript, strtok(NULL, " "), sizeof(T2Client.strScript)); + ds_strnzcpy(strControlDir, strtok(NULL, " "), sizeof(strControlDir)); + ds_strnzcpy(strLogFile, strtok(NULL, " "), sizeof(strLogFile)); + + ds_strnzcpy(T2Client.strCurrentProfile, "auto", sizeof(T2Client.strCurrentProfile)); + + JsonInit(T2Client.strConnectParams, sizeof(T2Client.strConnectParams), 0); + JsonAddStr(T2Client.strConnectParams, "INPUTFILE", TESTERCOMM_CLIENTINPUTFILE); + JsonAddStr(T2Client.strConnectParams, "OUTPUTFILE", TESTERCOMM_CLIENTOUTPUTFILE); + JsonAddStr(T2Client.strConnectParams, "LOGFILE", strLogFile); + JsonAddStr(T2Client.strConnectParams, "CONTROLDIR", strControlDir); + JsonAddStr(T2Client.strConnectParams, "HOSTNAME", strHost); + // connect the client core + TesterClientCoreConnect(T2Client.pClientCore, JsonFinish(T2Client.strConnectParams)); + } + DialogBox(T2Client.hInst, (LPCTSTR)IDD_COMMANDCONSOLE, NULL, (DLGPROC)_T2ClientCommandConsole); + + // disconnect and destroy the client core module + TesterClientCoreDestroy(T2Client.pClientCore); + + // shut down networking + NetConnShutdown(0); + + ZMemtrackShutdown(); + + ZPrintf(("Quitting T2Client.\n")); + + // return success + return(0); +} + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/pc/T2Host.cpp b/src/thirdparty/dirtysdk/sample/tester2/source/pc/T2Host.cpp new file mode 100644 index 00000000..bc8aabad --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/pc/T2Host.cpp @@ -0,0 +1,452 @@ +/*H********************************************************************************/ +/*! + \File T2Host.cpp + + \Description + Main file for Tester2 Host Application. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/22/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#pragma warning(push,0) +#include +#pragma warning(pop) + +#include +#include +#include +#include + +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/util/jsonformat.h" + +#include "libsample/zmemtrack.h" +#include "libsample/zlib.h" + +#include "testerhostcore.h" +#include "testercomm.h" +#include "testerregistry.h" + +#include "t2hostresource.h" + +/*** Defines **********************************************************************/ + +#ifndef GWL_WNDPROC +#define GWL_WNDPROC GWLP_WNDPROC // pc64 +#endif + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +TesterHostCoreT *g_pHostCore; //!< global host core pointer +HWND g_pMainWin = NULL; //!< pointer to the main window +WNDPROC g_hEditProc; //!< edit box handler +int32_t g_iTimerID; //!< handle to global timer to activate callbacks + +/*** Private Functions ************************************************************/ + +#if defined(DIRTYCODE_DLL) +// pull in the dependencies to call DirtyMemFuncSet +extern "C" void* DirtyMemAlloc2(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void* pMemGroupUserData); +extern "C" void DirtyMemFree2(void* pMem, int32_t iMemModule, int32_t iMemGroup, void* pMemGroupUserData); +#endif + +/*F********************************************************************************/ +/*! + \Function _T2HostDisplayOutput + + \Description + Take input from TesterConsole and dump it to the specified edit box. + + \Input *pBuf - string containing the debug output to display + \Input iLen - length of buffer + \Input iRefcon - user-specified parameter (unused) + \Input *pRefptr - user-specified parameter (window pointer) + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _T2HostDisplayOutput(const char *pBuf, int32_t iLen, int32_t iRefcon, void *pRefptr) +{ + int32_t iAdd; + HWND Ctrl = (HWND)pRefptr; + char strTempText[16*1025], *pTempText, cPrev; + + // replace cr with crlf for proper display + for (pTempText = strTempText, cPrev = '\0'; (*pBuf != '\0') && ((pTempText-strTempText) < (sizeof(strTempText)-1)); pBuf++, pTempText++) + { + if ((*pBuf == '\n') && (cPrev != '\r')) + { + *pTempText++ = '\r'; + } + *pTempText = cPrev = *pBuf; + } + *pTempText = '\0'; + + // see if we need to delete old data + if (SendMessage(Ctrl, WM_GETTEXTLENGTH, 0, 0) > 24000) + { + SendMessage(Ctrl, EM_SETSEL, 0, 8192); + SendMessage(Ctrl, EM_REPLACESEL, FALSE, (LPARAM)""); + iAdd = SendMessage(Ctrl, WM_GETTEXTLENGTH, 0, 0); + SendMessage(Ctrl, EM_SETSEL, (WPARAM)(iAdd-1), iAdd); + SendMessage(Ctrl, EM_REPLACESEL, FALSE, (LPARAM)""); + } + + iAdd = SendMessage(Ctrl, WM_GETTEXTLENGTH, 0, 0); + SendMessage(Ctrl, EM_SETSEL, (WPARAM)iAdd, iAdd); + SendMessage(Ctrl, EM_REPLACESEL, FALSE, (LPARAM)strTempText); + SendMessage(Ctrl, EM_SCROLLCARET, 0, 0); +} + + +/*F********************************************************************************/ +/*! + \Function _T2HostTimerCallback + + \Description + Timer callback - used to activate the TesterCoreIdle function. + + \Input Not used. + + \Output None + + \Version 03/22/2005 (jfrank) +*/ +/********************************************************************************F*/ +VOID CALLBACK _T2HostTimerCallback(HWND hDlg, UINT uMsg, UINT_PTR pIDEvent, DWORD dTime) +{ + // pump the networking layer + TesterHostCoreIdle(g_pHostCore); +} + +/*F********************************************************************************/ +/*! + \Function _T2HostCommandEditProc + + \Description + Message handler for the command line edit box. Intercepts uparrow, etc. + + \Input hDlg - dialog handle + \Input uMessage - message identifier + \Input wParam - message specifics (pointer to uint32_t) + \Input lParam - message specifics (pointer) + + \Output LRESULT - message handling status return code + + \Version 04/06/2005 (jfrank) +*/ +/********************************************************************************F*/ +static LRESULT CALLBACK _T2HostCommandEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + const char *pCommandLine = NULL; + + if (msg == WM_KEYDOWN) + { + if (wParam == VK_UP) + { + pCommandLine = TesterHostCoreGetHistory(g_pHostCore, -1, NULL, 0); + } + else if (wParam == VK_DOWN) + { + pCommandLine = TesterHostCoreGetHistory(g_pHostCore, 1, NULL, 0); + } + + // if we got a key, redraw and quit + if (pCommandLine != NULL) + { + SetWindowText(hWnd, pCommandLine); + SendMessage(hWnd, EM_SETSEL, 0, -1); + SendMessage(hWnd, EM_SETSEL, (WPARAM)-1, 0); + return(0); + } + } + + // not handled by this handler - call the original one + return(CallWindowProc(static_cast(g_hEditProc), hWnd, msg, wParam, lParam)); +} + + +/*F********************************************************************************/ +/*! + \Function _T2HostDialogProc + + \Description + Main window dialog handler + + \Input Standard windows WNDPROC + + \Output Message special. + + \Version 03/22/2005 (jfrank) +*/ +/********************************************************************************F*/ +static LRESULT CALLBACK _T2HostDialogProc(HWND win, UINT msg, WPARAM wparm, LPARAM lparm) +{ + // handle close special (delete the class) + if (msg == WM_DESTROY) + { + return(FALSE); + } + + // handle init special (create the class) + if (msg == WM_INITDIALOG) + { + HFONT font = CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FIXED_PITCH | FF_DONTCARE, ""); + SendDlgItemMessage(win, IDC_OUTPUT, WM_SETFONT, (WPARAM)font, 0); + + // set up command window handler + g_hEditProc = reinterpret_cast(SetWindowLongPtr(GetDlgItem(win, IDC_INPUT), GWL_WNDPROC, (LONG_PTR)_T2HostCommandEditProc)); + + // start the timer for IDLE callback handling + g_iTimerID = (int32_t)SetTimer( win, 1, (uint32_t)(1000/60), _T2HostTimerCallback); + } + + // close dialog + if (msg == WM_CLOSE) + { + TesterHostCoreDisconnect(g_pHostCore); + KillTimer(win, (UINT_PTR)g_iTimerID); + PostQuitMessage(0); + return(DefWindowProc(win, msg, wparm, lparm)); + } + + // look for return key + if ((msg == WM_COMMAND) && (LOWORD(wparm) == IDOK)) + { + if (g_pHostCore != NULL) + { + char strInput[16*1024]; + + // get the command line + GetWindowText(GetDlgItem(win, IDC_INPUT), strInput, sizeof(strInput)); + SetWindowText(GetDlgItem(win, IDC_INPUT), ""); + + // local echo + ZPrintf("\n> %s\n", strInput); + + // and execute the command + TesterHostCoreDispatch(g_pHostCore, strInput); + } + } + + // let windows handle + return(FALSE); +} + + +/*F********************************************************************************/ +/*! + \Function _T2HostCmdClear + + \Description + Clear the console + + \Input *argz - environment + \Input argc - num args + \Input **argv - arg list + + \Output int32_t - standard return code + + \Version 04/07/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _T2HostCmdClear(ZContext *argz, int32_t argc, char **argv) +{ + TesterConsoleT *pConsole; + + if (argc < 1) + { + ZPrintf(" clear the display.\n"); + ZPrintf(" usage: %s\n", argv[0]); + } + else + { + // clear the display + SetWindowText(GetDlgItem(g_pMainWin, IDC_OUTPUT), ""); + + // clear the console + if ((pConsole = (TesterConsoleT *)TesterRegistryGetPointer("CONSOLE")) != NULL) + { + TesterConsoleClear(pConsole); + } + } + return(0); +} + + +/*F********************************************************************************/ +/*! + \Function _T2HostCmdExit + + \Description + Quit + + \Input *argz - environment + \Input argc - num args + \Input **argv - arg list + + \Output int32_t - standard return code + + \Version 04/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _T2HostCmdExit(ZContext *argz, int32_t argc, char **argv) +{ + if (argc >= 1) + { + PostQuitMessage(0); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _T2HostRegisterModules + + \Description + Register client commands (local commands, like exit, history, etc.) + + \Input None + + \Output None + + \Version 04/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _T2HostRegisterModules(void) +{ + TesterHostCoreRegister(g_pHostCore, "exit", &_T2HostCmdExit); + TesterHostCoreRegister(g_pHostCore, "clear", &_T2HostCmdClear); +} + + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function WinMain + + \Description + Main windows entry point. + + \Input Standard windows startup params + + \Output Process exit code + + \Version 03/22/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t APIENTRY WinMain(HINSTANCE inst, HINSTANCE prev, char *cmdline, int32_t show) +{ + char strBase[128] = "", strHostName[128] = "", *pBase, strParams[512]; + MSG msg; + + ZPrintf("\nStarting T2Host.\n\n"); + + // check for path argument (indicates file passing) + if ((pBase = strstr(cmdline, "-path=")) != NULL) + { + pBase += strlen("-path="); + ds_strnzcpy(strBase, pBase, sizeof(strBase)); + ZPrintf("t2host: base path=%s\n", strBase); + } + + // check for connect argument (indicates host connect instead of accept) + if ((pBase = strstr(cmdline, "-connect=")) != NULL) + { + pBase += strlen("-connect="); + ds_strnzcpy(strHostName, pBase, sizeof(strHostName)); + ZPrintf("t2host: connect=%s\n", strHostName); + } + +#if defined(DIRTYCODE_DLL) + + DirtyMemFuncSet(&DirtyMemAlloc2, &DirtyMemFree2); + +#endif + + ZMemtrackStartup(); + + // start the network + NetConnStartup("-servicename=tester2"); + + // create the module + JsonInit(strParams, sizeof(strParams), 0); + JsonAddStr(strParams, "INPUTFILE", TESTERCOMM_HOSTINPUTFILE); + JsonAddStr(strParams, "OUTPUTFILE", TESTERCOMM_HOSTOUTPUTFILE); + JsonAddStr(strParams, "CONTROLDIR", strBase); + JsonAddStr(strParams, "HOSTNAME", strHostName); + g_pHostCore = TesterHostCoreCreate(JsonFinish(strParams)); + + // create the tester dialog + g_pMainWin = CreateDialogParam(GetModuleHandle(NULL), "MAIN", HWND_DESKTOP, (DLGPROC)_T2HostDialogProc, 0); + + TesterHostCoreDisplayFunc(g_pHostCore, _T2HostDisplayOutput, 0, GetDlgItem(g_pMainWin, IDC_OUTPUT)); + + _T2HostRegisterModules(); + + // command-line command? + if (cmdline[0] != '\0') + { + // set the command + SetWindowText(GetDlgItem(g_pMainWin, IDC_INPUT), cmdline); + // fake a carriage return + _T2HostDialogProc(g_pMainWin, WM_COMMAND, IDOK, 0); + } + + // main message loop + while (GetMessage(&msg, NULL, 0, 0)) + { + // pump the host core module + TesterHostCoreUpdate(g_pHostCore, 1); + + // let dialog manager run + if (!IsDialogMessage(g_pMainWin, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // give time to zlib + ZTask(); + ZCleanup(); + + // give time to network + NetConnIdle(); + } + + // kill all active processes + ZShutdown(); + + // kill the host core module + TesterHostCoreDestroy(g_pHostCore); + + // done with dialog + DestroyWindow(g_pMainWin); + + // shut down the network + NetConnShutdown(0); + + ZMemtrackShutdown(); + + _CrtDumpMemoryLeaks(); + + ZPrintf("\nQuitting T2Host.\n\n"); + + return(0); +} + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2Client.rc b/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2Client.rc new file mode 100644 index 00000000..5bab1793 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2Client.rc @@ -0,0 +1,173 @@ +// Microsoft Visual C++ generated resource script. +// +#include "T2ClientResource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PROFILESELECT DIALOGEX 0, 0, 282, 222 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | + WS_SYSMENU +CAPTION "Tester2 Profile Selection" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Connect",ID_BUTTON_CONNECT,54,201,50,14 + PUSHBUTTON "Save Profile",IDC_BUTTON_SAVEPROFILE,220,20,55,20, + BS_MULTILINE + PUSHBUTTON "Delete Profile",IDC_BUTTON_DELETEPROFILE,220,45,55,20, + BS_MULTILINE + PUSHBUTTON "Exit",ID_BUTTON_EXIT,169,201,50,14 + COMBOBOX IDC_COMBO_PROFILENAME,85,20,125,130,CBS_DROPDOWN | + CBS_SORT | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_HOSTNAME,85,40,125,130,CBS_DROPDOWN | CBS_SORT | + WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_SHARINGLOCATION,85,60,125,130,CBS_DROPDOWN | + CBS_SORT | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_STARTUPPARAMS,85,80,125,130,CBS_DROPDOWN | + CBS_SORT | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_FILELOGDIRECTORY,85,120,125,130,CBS_DROPDOWN | + CBS_SORT | WS_DISABLED | WS_VSCROLL | WS_TABSTOP + LTEXT "Profile Name",IDC_LABEL_PROFILENAME,5,20,75,13, + SS_CENTERIMAGE,WS_EX_RIGHT + LTEXT "Hostname",IDC_HOSTNAME,5,40,75,13,SS_CENTERIMAGE, + WS_EX_RIGHT + LTEXT "Sharing Location",IDC_LABEL_SHARINGLOCATION,5,60,75,13, + SS_CENTERIMAGE,WS_EX_RIGHT + LTEXT "Extra Startup Params",IDC_LABEL_STARTUPPARAMS,5,80,75, + 13,SS_CENTERIMAGE,WS_EX_RIGHT + LTEXT "File Log Directory",IDC_LABEL_FILELOGDIRECTORY,17,120, + 63,13,SS_CENTERIMAGE | WS_DISABLED,WS_EX_RIGHT + CONTROL "Log to File",IDC_CHECKBOX_LOGGING,"Button", + BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,85,161,123,10 + CONTROL "Start Networking on Connect", + IDC_CHECKBOX_STARTNETWORKING,"Button",BS_AUTOCHECKBOX | + WS_DISABLED | WS_TABSTOP,85,180,123,10 + LTEXT "Scrollback size (KB)",IDC_LABEL_SCROLLBACKSIZE,7,100,73, + 13,SS_CENTERIMAGE,WS_EX_RIGHT + EDITTEXT IDC_SCROLLBACKSIZE,85,100,125,14,ES_AUTOHSCROLL +END + +IDD_COMMANDCONSOLE DIALOGEX 0, 0, 500, 217 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | + WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_WINDOWEDGE +CAPTION "Tester2 Client Command Console" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_EDIT_CONSOLE,1,7,497,191,ES_MULTILINE | + ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL | + NOT WS_TABSTOP + EDITTEXT IDC_EDIT_COMMAND,1,202,497,12,ES_AUTOHSCROLL +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "T2ClientResource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""windows.h""\r\n" + "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PROFILESELECT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 275 + TOPMARGIN, 7 + BOTTOMMARGIN, 215 + END + + IDD_COMMANDCONSOLE, DIALOG + BEGIN + LEFTMARGIN, 1 + RIGHTMARGIN, 498 + TOPMARGIN, 7 + BOTTOMMARGIN, 214 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_CONSOLECONTEXTMENU MENU +BEGIN + POPUP "Console Options" + BEGIN + MENUITEM "Clear", ID_CONSOLEOPTIONS_CLEAR + MENUITEM "Select All", ID_CONSOLEOPTIONS_SELECTALL + MENUITEM SEPARATOR + MENUITEM "Reconnect", ID_CONSOLEOPTIONS_RECONNECT + MENUITEM "Disconnect", ID_CONSOLEOPTIONS_DISCONNECT + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2ClientResource.h b/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2ClientResource.h new file mode 100644 index 00000000..3ace7df3 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2ClientResource.h @@ -0,0 +1,60 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by T2Client.rc +// +#define IDC_MYICON 2 +#define IDC_HOSTNAME 2 +#define IDD_T2CLIENT_DIALOG 102 +#define IDS_APP_TITLE 103 +#define IDM_ABOUT 104 +#define IDM_EXIT 105 +#define IDC_T2CLIENT 109 +#define IDR_MAINFRAME 128 +#define IDD_PROFILESELECT 129 +#define IDD_COMMANDCONSOLE 130 +#define IDR_MENU1 131 +#define IDR_CONSOLECONTEXTMENU 131 +#define IDC_LABEL_PROFILENAME 1000 + +#define IDC_LABEL_STARTUPPARAMS 1002 +#define IDC_LABEL_SHARINGLOCATION 1003 +#define IDC_COMBO_PROFILENAME 1004 +#define IDC_BUTTON_SAVEPROFILE 1005 +#define IDC_BUTTON_DELETEPROFILE 1006 + +#define IDC_COMBO_STARTUPPARAMS 1008 +#define IDC_COMBO_SHARINGLOCATION 1009 +#define ID_BUTTON_CONNECT 1010 +#define ID_BUTTON_EXIT 1011 +#define IDC_CHECKBOX_STARTNETWORKING 1012 +#define IDC_COMBO_FILELOGDIRECTORY 1013 +#define IDC_COMBO_STARTUPPARAMS2 1014 +#define IDC_COMBO_HOSTNAME 1014 +#define IDC_CHECKBOX_LOGGING 1015 +#define IDC_LABEL_FILELOGDIRECTORY 1016 +#define IDC_BUTTON1 1017 +#define IDC_UPDATECLIENTCORE 1017 +#define IDC_EDIT_CONSOLE 1018 +#define IDC_LABEL_STARTUPPARAMS2 1018 +#define IDC_LABEL_SCROLLBACKSIZE 1018 +#define IDC_EDIT2 1019 +#define IDC_EDIT_COMMAND 1019 +#define IDC_SCROLLBACKSIZE 1019 +#define IDM_PROFILE_SELECT 32773 +#define ID_CONSOLEOPTIONS_CLEAR 32775 +#define ID_CONSOLEOPTIONS_SELECTALL 32776 +#define ID_CONSOLEOPTIONS_RECONNECT 32777 +#define ID_CONSOLEOPTIONS_DISCONNECT 32778 +#define IDC_STATIC -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 132 +#define _APS_NEXT_COMMAND_VALUE 32780 +#define _APS_NEXT_CONTROL_VALUE 1021 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2Host.rc b/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2Host.rc new file mode 100644 index 00000000..94e374e3 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2Host.rc @@ -0,0 +1,184 @@ +// Microsoft Visual C++ generated resource script. +// +#include "T2HostResource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +MAIN DIALOGEX 0, 0, 500, 226 +STYLE DS_SETFONT | DS_MODALFRAME | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | + WS_CAPTION | WS_SYSMENU +CAPTION "Tester2 PC Host" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_INPUT,7,205,486,14,ES_AUTOHSCROLL + EDITTEXT IDC_OUTPUT,7,7,486,195,ES_MULTILINE | ES_AUTOVSCROLL | + ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL +END + +MODEMSELECT DIALOGEX 0, 0, 194, 85 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | + WS_SYSMENU +CAPTION "Select Modem" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + DEFPUSHBUTTON "OK",IDOK,7,60,50,14 + PUSHBUTTON "Cancel",IDCANCEL,134,60,50,14 + COMBOBOX IDC_MODEMS,43,15,131,83,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + LTEXT "Modem:",IDC_STATIC,16,18,26,8 + EDITTEXT IDC_NUMBER,43,35,130,14,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "Number:",IDC_STATIC,15,38,28,8 +END + +VOICEDEVSELECT DIALOGEX 0, 0, 194, 85 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | + WS_SYSMENU +CAPTION "Select Voice Device" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + DEFPUSHBUTTON "OK",IDOK,71,60,50,14 + COMBOBOX IDC_VOICEINP,43,15,131,83,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + LTEXT "Input:",IDC_STATIC,18,18,19,8 + COMBOBOX IDC_VOICEOUT,43,35,131,83,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + LTEXT "Output:",IDC_STATIC,17,38,24,8 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + "MAIN", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 493 + TOPMARGIN, 7 + BOTTOMMARGIN, 219 + END + + "MODEMSELECT", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 184 + TOPMARGIN, 7 + BOTTOMMARGIN, 74 + END + + "VOICEDEVSELECT", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 184 + TOPMARGIN, 7 + BOTTOMMARGIN, 74 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "T2HostResource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Electronic Arts" + VALUE "FileDescription", "Tester2 Host" + VALUE "FileVersion", "1, 0, 0, 1" + VALUE "InternalName", "T2Host" + VALUE "LegalCopyright", "Copyright 2005" + VALUE "OriginalFilename", "T2Host.exe" + VALUE "ProductName", " T2Host" + VALUE "ProductVersion", "1, 0, 0, 1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2HostResource.h b/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2HostResource.h new file mode 100644 index 00000000..18deceec --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/pc/resource/T2HostResource.h @@ -0,0 +1,22 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by T2Host.rc +// +#define IDD_DIALOG1 101 +#define IDC_INPUT 1005 +#define IDC_OUTPUT 1006 +#define IDC_NUMBER 1007 +#define IDC_VOICEINP 1008 +#define IDC_VOICEOUT 1010 +#define IDC_MODEMS 1141 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 107 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1009 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/pc/testermoduleshost.c b/src/thirdparty/dirtysdk/sample/tester2/source/pc/testermoduleshost.c new file mode 100644 index 00000000..f66f961f --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/pc/testermoduleshost.c @@ -0,0 +1,51 @@ +/*H********************************************************************************/ +/*! + \File testermodulespchost.c + + \Description + PC specific module startup. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/11/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function TesterModulesRegisterPlatformCommands + + \Description + Register all PC-specific modules + + \Input *pState - module state + + \Output 0=success, error code otherwise + + \Version 04/11/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterModulesRegisterPlatformCommands(TesterModulesT *pState) +{ + // tester2 pc-specific modules + TesterModulesRegister(pState, "secure",&CmdSecure); + TesterModulesRegister(pState, "voice", &CmdVoice); + return(TESTERMODULES_ERROR_NONE); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/render.h b/src/thirdparty/dirtysdk/sample/tester2/source/render.h new file mode 100644 index 00000000..0e072e02 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/render.h @@ -0,0 +1,53 @@ +/*H********************************************************************************/ +/*! + \File render.h + + \Description + Sample app rendering + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 06/04/2013 (cvienneau) First Version +*/ +/********************************************************************************H*/ +#ifndef _T2Render_h +#define _T2Render_h + +/*** Include files ****************************************************************/ +#include "DirtySDK/dirtysock.h" +#include + +/*** Defines **********************************************************************/ +#if defined(DIRTYCODE_PS4) + + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ +typedef struct T2Render +{ + int32_t iVideoOut; + int32_t ibufferIndex; + SceKernelEqueue eqFlip; + int64_t flipArg; + int32_t loop; +} T2Render; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ +#ifdef __cplusplus +extern "C" { +#endif + +int32_t T2RenderInit(T2Render *pApp); +int32_t T2RenderUpdate(T2Render *pApp); +int32_t T2RenderTerm(T2Render *pApp); + +#ifdef __cplusplus +}; +#endif + +#endif // #if defined(DIRTYCODE_PS4) +#endif //_T2Render_h \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerclientcore.c b/src/thirdparty/dirtysdk/sample/tester2/source/testerclientcore.c new file mode 100644 index 00000000..40e54f52 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerclientcore.c @@ -0,0 +1,663 @@ +/*H********************************************************************************/ +/*! + \File testerclientcore.c + + \Description + Main control module for the Tester2 Client application. + + \Notes + This is the main host module for the tester2 client application. + It contains mostly global-variable type objects and operations, similar + to LobbyAPI. TesterClientCore is responsible for starting up all the + necessary child modules like TesterConsole, TesterComm, etc. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/17/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/util/jsonparse.h" + +#include "libsample/zmem.h" +#include "libsample/zfile.h" + +#include "testerprofile.h" +#include "testercomm.h" +#include "testerconsole.h" +#include "testerhistory.h" +#include "testermodules.h" +#include "testerregistry.h" +#include "testerclientcore.h" + +/*** Defines **********************************************************************/ + +#define TESTERCLIENTCORE_ROOTPATH_DEFAULT ".\\T2Client" +#define TESTERCLIENTCORE_PROFILEFILE_DEFAULT "profiles.txt" +#define TESTERCLIENTCORE_CONSOLESIZE_DEFAULT 16384 + +/*** Type Definitions *************************************************************/ + +struct TesterClientCoreT +{ + TesterProfileT *pProfile; //!< profile manager module + TesterCommT *pComm; //!< host/client communication module + TesterConsoleT *pConsole; //!< console for managing output + TesterHistoryT *pHistory; //!< command history module + TesterModulesT *pModules; //!< client side command modules + TesterProfileEntryT CmdLineProfile; //!< the command line as passed + int iLogFile; //!< Log File identifier + TesterConsoleDisplayCbT *pDisplayProc; //!< procedure for displaying output +}; + +/*** Variables ********************************************************************/ + +/*** External Functions ***********************************************************/ + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _TesterClientCoreMsgControl + + \Description + Handle incoming messages from TesterComm + + \Input *pState - Module state + \Input *pMsg - Message text + \Input *pParam - user supplied data + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterClientCoreMsgControl(TesterCommT *pState, const char *pMsg, void *pParam) +{ + TesterClientCoreT *pCore = (TesterClientCoreT *)pParam; + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_CONTROL, pMsg); +} + +/*F********************************************************************************/ +/*! + \Function _TesterClientCoreMsgCommand + + \Description + Handle incoming messages from TesterComm + + \Input *pState - Module state + \Input *pMsg - Message text + \Input *pParam - user supplied data + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterClientCoreMsgCommand(TesterCommT *pState, const char *pMsg, void *pParam) +{ + TesterClientCoreT *pCore = (TesterClientCoreT *)pParam; + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_COMMAND, "> "); + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_COMMAND, pMsg); + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_COMMAND, "\n"); +} + +/*F********************************************************************************/ +/*! + \Function _TesterClientCoreMsgStatus + + \Description + Handle incoming messages from TesterComm + + \Input *pState - Module state + \Input *pMsg - Message text + \Input *pParam - user supplied data + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterClientCoreMsgStatus(TesterCommT *pState, const char *pMsg, void *pParam) +{ + //TesterClientCoreT *pCore = (TesterClientCoreT *)pParam; + //TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_STATUS, pMsg); +} + +/*F********************************************************************************/ +/*! + \Function _TesterClientCoreMsgConsole + + \Description + Handle incoming messages from TesterComm + + \Input *pState - Module state + \Input *pMsg - Message text + \Input *pParam - user supplied data + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterClientCoreMsgConsole(TesterCommT *pState, const char *pMsg, void *pParam) +{ + TesterClientCoreT *pCore = (TesterClientCoreT *)pParam; + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_CONSOLE, pMsg); +} + + +/*F********************************************************************************/ +/*! + \Function _TesterClientCorePrintf + + \Description + Tester Printf function. + + \Input *pParm - Module state + \Input *pText - Message text + + \Output 0=success + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +#if DIRTYCODE_LOGGING +static int32_t _TesterClientCorePrintf(void *pParm, const char *pText) +{ + TesterClientCoreT *pCore = pParm; + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_CONSOLE, pText); + return(0); +} +#endif + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreCreate + + \Description + Create a TesterClientCore module and return the pointer to it + + \Input None + + \Output TesterClientCoreT * - newly allocated and created TesterClientCoreT + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +TesterClientCoreT *TesterClientCoreCreate(char *strCmdLine) +{ + TesterClientCoreT *pState; + + // create and wipe clean the module + pState = (TesterClientCoreT *)ZMemAlloc(sizeof(TesterClientCoreT)); + ds_memclr((void *)pState, sizeof(TesterClientCoreT)); + + // start up the registry + TesterRegistryCreate(-1); + TesterRegistrySetPointer("CORE", pState); + TesterRegistrySetString("ROOT", TESTERCLIENTCORE_ROOTPATH_DEFAULT); + + // now create the children modules + pState->pProfile = TesterProfileCreate(); + pState->pComm = TesterCommCreate(); + pState->pConsole = TesterConsoleCreate(TESTERCLIENTCORE_CONSOLESIZE_DEFAULT, TRUE); + pState->pModules = TesterModulesCreate(); + pState->pHistory = TesterHistoryCreate(-1); + +#if DIRTYCODE_LOGGING + // hook in the netprintf to the console + NetPrintfHook(_TesterClientCorePrintf, pState); +#endif + + // start up profile earlier so we can get data + TesterProfileConnect(pState->pProfile, TESTERCLIENTCORE_ROOTPATH_DEFAULT "\\" TESTERCLIENTCORE_PROFILEFILE_DEFAULT); + + // register client commands + TesterModulesRegisterClientCommands(pState->pModules); + + // done + return(pState); +} + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreConnect + + \Description + Connect the core module and all its children + + \Input *pState - TesterClientCoreT module to connect + \Input *pParams - startup parameters + + \Output int32_t - 0=success, error code otherwise + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterClientCoreConnect(TesterClientCoreT *pState, const char *pParams) +{ + TesterProfileEntryT CurProfile; + char strHostname[64], strProfilename[64], strMessage[128], strControlDir[2]; + uint16_t aJson[512]; + + ds_memclr(&CurProfile, sizeof(TesterProfileEntryT)); + + // check the state + if (pState == NULL) + { + return(TESTERCLIENTCORE_ERROR_NULLPOINTER); + } + // check the children + if ((pState->pComm == NULL) || (pState->pProfile == NULL)) + { + return(TESTERCLIENTCORE_ERROR_NULLPOINTER); + } + + JsonParse(aJson, sizeof(aJson)/sizeof(*aJson), pParams, -1); + + // attach an interface method + JsonGetString(JsonFind(aJson, "CONTROLDIR"), strControlDir, sizeof(strControlDir), ""); + if (strControlDir[0] != '\0') + { + TesterCommAttachFile(pState->pComm); + } + else + { + TesterCommAttachSocket(pState->pComm); + } + + // connect the children modules + TesterCommConnect(pState->pComm, pParams, FALSE); + + // register callbacks for all the types of messages we'll see + TesterCommRegister(pState->pComm, TESTER_MSGTYPE_CONTROL, _TesterClientCoreMsgControl, (void *)pState); + TesterCommRegister(pState->pComm, TESTER_MSGTYPE_COMMAND, _TesterClientCoreMsgCommand, (void *)pState); + TesterCommRegister(pState->pComm, TESTER_MSGTYPE_STATUS, _TesterClientCoreMsgStatus , (void *)pState); + TesterCommRegister(pState->pComm, TESTER_MSGTYPE_CONSOLE, _TesterClientCoreMsgConsole, (void *)pState); + + // send connected message to the host + JsonGetString(JsonFind(aJson, "HOSTNAME"), strHostname, sizeof(strHostname), ""); + JsonGetString(JsonFind(aJson, "PROFILENAME"), strProfilename, sizeof(strProfilename), ""); + ds_snzprintf(strMessage, sizeof(strMessage), "t2client: client '%s' connected to host %s\n", strProfilename, strHostname); + TesterCommMessage(pState->pComm, TESTER_MSGTYPE_CONSOLE, strMessage); + + // load historical commands + TesterProfileGet(pState->pProfile, JsonGetInteger(JsonFind(aJson, "PROFILENUM"), -2), &CurProfile); + TesterHistoryLoad(pState->pHistory, CurProfile.strHistoryFile); + + // now just wait for something + return(TESTERCLIENTCORE_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreIdle + + \Description + Idle function - pump this to make networking, etc. happy. + + \Input *pState - TesterClientCoreT module to service + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterClientCoreIdle(TesterClientCoreT *pState) +{ + if(pState == NULL) + return; + + if(pState->pComm) + { + // pump the comm update function + TesterCommUpdate(pState->pComm); + } + + // now flush any debug output + if((pState->pDisplayProc) && (pState->pConsole)) + { + TesterConsoleFlush(pState->pConsole, pState->pDisplayProc); + } +} + + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreSendCommand + + \Description + Send a command to the host - should comes from the client GUI command line. + + \Input *pState - TesterClientCoreT module + \Input *pData - command to send + + \Output 0=success, error code otherwise + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterClientCoreSendCommand(TesterClientCoreT *pState, const char *pData) +{ + int32_t iResult; + + // echo it locally + _TesterClientCoreMsgCommand(pState->pComm, pData, pState); + + // add it to the history + TesterHistoryAdd(pState->pHistory, pData); + + // check to see if its a local command + iResult = TesterModulesDispatch(pState->pModules, pData); + + // did it work locally? + if(iResult == 0) + { + return(TESTERCLIENTCORE_ERROR_NONE); + } + // not a local command - send it to the host to deal with + else + { + return(TesterCommMessage(pState->pComm, TESTER_MSGTYPE_COMMAND, pData)); + } +} + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreProfileGet + + \Description + Return a tagfield with a specified profile's data + + \Input *pState - TesterClientCoreT module state + \Input iIndex - 0-based index of the profile to get, -1 for default entry + \Input *pDest - destination entry for the profile + + \Output int32_t - 0=success, error code otherwise + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterClientCoreProfileGet(TesterClientCoreT *pState, int32_t iIndex, TesterProfileEntryT *pDest) +{ + // check for error conditions + if ((pState == NULL) || (pDest == NULL)) + return(TESTERCLIENTCORE_ERROR_NULLPOINTER); + + // get the specified profile + return(TesterProfileGet(pState->pProfile, iIndex, pDest)); +} + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreRegister + + \Description + Register a command + + \Input *pState - TesterClientCoreT module state + \Input *pCommand - command name to register with + \Input *pFunctionPtr - the ZCommand module to call + + \Output int32_t - 0=success, error code otherwise + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterClientCoreRegister(TesterClientCoreT *pState, const char *pCommand, ZCommand *pFunctionPtr) +{ + if(pState == NULL) + return(TESTERCLIENTCORE_ERROR_NULLPOINTER); + + return(TesterModulesRegister(pState->pModules, pCommand, pFunctionPtr)); +} + + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreProfileAdd + + \Description + Add a profile to the list + + \Input *pState - module state + \Input *pEntry - entry to add + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterClientCoreProfileAdd(TesterClientCoreT *pState, TesterProfileEntryT *pEntry) +{ + int32_t iResult; + + // add the profile + iResult = TesterProfileAdd(pState->pProfile, pEntry); + if(iResult != 0) + return(iResult); + + // trigger a save automatically + iResult = TesterProfileSave(pState->pProfile); + return(iResult); +} + + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreProfileDelete + + \Description + Remove a profile from the list + + \Input *pState - module state + \Input *pName - profile to nuke + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterClientCoreProfileDelete(TesterClientCoreT *pState, const char *pName) +{ + return(TesterProfileDelete(pState->pProfile, pName)); +} + + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreProfileDefault + + \Description + Remove a profile from the list + + \Input *pState - module state + \Input *pName - profile to set as default + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterClientCoreProfileDefault(TesterClientCoreT *pState, const char *pName) +{ + return(TesterProfileSetDefaultName(pState->pProfile, pName)); +} + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreDisplayFunc + + \Description + Register a display function to show stuff on the screen. + + \Input *pState - TesterClientCoreT module state + \Input *pProc - function pointer to display procedure + \Input iRefcon - int32_t value to pass to the display function when called + \Input *pDisplayRef - ref value to pass to the display function when called (hDlg) + + \Output None + + \Version 03/28/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterClientCoreDisplayFunc(TesterClientCoreT *pState, TesterConsoleDisplayCbT *pProc, int32_t iRefcon, void *pRefptr) +{ + pState->pDisplayProc = pProc; + TesterConsoleConnect(pState->pConsole, iRefcon, pRefptr); +} + + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreGetHistoricalCommand + + \Description + Get a historical command from the history module. + + \Input *pState - TesterClientCoreT module state + \Input *pOffsetFromCurrent - pointer to the offset, may be clamped if necessary + \Input *pBuf - destination buffer (may be NULL) + \Input iSize - size of destination buffer + + \Output + const char * - history text, or null + + \Version 04/06/2005 (jfrank) +*/ +/********************************************************************************F*/ +const char *TesterClientCoreGetHistoricalCommand(TesterClientCoreT *pState, int32_t *pOffsetFromCurrent, char *pBuf, int32_t iSize) +{ + int32_t iHeadCount, iTailCount; + int32_t iOffsetFromCurrent; + const char *pText = NULL; + + // check for errors + if (pState == NULL) + { + return(NULL); + } + + // get the current head and tail + TesterHistoryHeadTailCount(pState->pHistory, &iHeadCount, &iTailCount); + + // clamp the incoming value + iOffsetFromCurrent = *pOffsetFromCurrent; + if (iOffsetFromCurrent > 1) + { + iOffsetFromCurrent = 1; + } + if ((iHeadCount - iTailCount) > iOffsetFromCurrent) + { + iOffsetFromCurrent = (iHeadCount - iTailCount); + } + *pOffsetFromCurrent = iOffsetFromCurrent; + + // get the command + if ((iOffsetFromCurrent == 1) && (pBuf != NULL)) + { + // set a blank entry + ds_memclr(pBuf, iSize); + } + else + { + pText = TesterHistoryGet(pState->pHistory, (iTailCount + iOffsetFromCurrent), pBuf, iSize); + } + return(pText); +} + + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreDisconnect + + \Description + Disconnect the core module and all its children + + \Input *pState - TesterClientCoreT module to disconnect + + \Output int32_t - 0=success, error code otherwise + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterClientCoreDisconnect(TesterClientCoreT *pState) +{ + // check the state + if (pState == NULL) + return(TESTERCLIENTCORE_ERROR_NULLPOINTER); + // check the children + if ((pState->pComm == NULL) || (pState->pProfile == NULL)) + return(TESTERCLIENTCORE_ERROR_NULLPOINTER); + + // disconnect the children modules + TesterCommDisconnect(pState->pComm); + + return(TESTERCLIENTCORE_ERROR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function TesterClientCoreDestroy + + \Description + Destroy a TesterClientCoreT module and all its children + + \Input *pState - TesterClientCoreT module to destroy + + \Output None + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterClientCoreDestroy(TesterClientCoreT *pState) +{ + TesterProfileEntryT CurProfile; + + // check the state + if (pState == NULL) + return; + + TesterProfileGet(pState->pProfile, -1, &CurProfile); + TesterHistorySave(pState->pHistory, CurProfile.strHistoryFile); + + // shut down profile late so we can save settings if need be + TesterProfileDisconnect(pState->pProfile); + + // destoy all the children modules + TesterProfileDestroy(pState->pProfile); + TesterCommDestroy(pState->pComm); + TesterConsoleDestroy(pState->pConsole); + TesterModulesDestroy(pState->pModules); + TesterHistoryDestroy(pState->pHistory); + TesterRegistryDestroy(); + +#if DIRTYCODE_LOGGING + // unhook debug output + NetPrintfHook(NULL, NULL); +#endif + + // wipe the pointers clean + pState->pComm = NULL; + pState->pProfile = NULL; + pState->pConsole = NULL; + pState->pHistory = NULL; + + // now destroy this module + ZMemFree((void *)pState); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerclientcore.h b/src/thirdparty/dirtysdk/sample/tester2/source/testerclientcore.h new file mode 100644 index 00000000..00376c2d --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerclientcore.h @@ -0,0 +1,100 @@ +/*H********************************************************************************/ +/*! + \File testerclientcore.h + + \Description + Main control module for the Tester2 Client application. + + \Notes + This is the main host module for the tester2 client application. + It contains mostly global-variable type objects and operations, similar + to LobbyAPI. TesterClientCore is responsible for starting up all the + necessary child modules like TesterConsole, TesterHostClientComm, etc. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/17/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ +#ifndef _testerclientcore_h +#define _testerclientcore_h + +/*** Include files ****************************************************************/ + +#include "testerconsole.h" +#include "testerprofile.h" + +/*** Defines **********************************************************************/ + +#define TESTERCLIENTCORE_ERROR_NONE (0) //!< no error (success) +#define TESTERCLIENTCORE_ERROR_NULLPOINTER (-1) //!< a null pointer ref was used + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// module state object +typedef struct TesterClientCoreT TesterClientCoreT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// -------------------- FRAMEWORK -------------------------------- + +// create a testerclientcore module +TesterClientCoreT *TesterClientCoreCreate(char* strCmdLine); + +// connect the core module +int32_t TesterClientCoreConnect(TesterClientCoreT *pState, const char *pParams); + +// register function to flush output to the display +void TesterClientCoreDisplayFunc(TesterClientCoreT *pState, + TesterConsoleDisplayCbT *pProc, + int32_t iRefcon, void *pRefptr); + +// idle function - pump this to make networking, etc. happy +void TesterClientCoreIdle(TesterClientCoreT *pState); + +// register a module +int32_t TesterClientCoreRegister(TesterClientCoreT *pState, const char *pCommand, ZCommand *pFunctionPtr); + +// disconnect the core module +int32_t TesterClientCoreDisconnect(TesterClientCoreT *pState); + +// destroy a testerclientcore module +void TesterClientCoreDestroy(TesterClientCoreT *pState); + +// get a historical command +const char *TesterClientCoreGetHistoricalCommand(TesterClientCoreT *pState, int32_t *pOffsetFromCurrent, char *pBuf, int32_t iSize); + +// -------------------- COMMUNICATION FUNCTIONS ------------------ + +// send a command (from GUI command line) +int32_t TesterClientCoreSendCommand(TesterClientCoreT *pState, const char *pData); + +// -------------------- PROFILE FUNCTIONS ------------------------ + +// return a tagfield with a specified profile's data +int32_t TesterClientCoreProfileGet(TesterClientCoreT *pState, int32_t iIndex, TesterProfileEntryT *pDest); + +// add a profile +int32_t TesterClientCoreProfileAdd(TesterClientCoreT *pState, TesterProfileEntryT *pEntry); + +// delete a profile +int32_t TesterClientCoreProfileDelete(TesterClientCoreT *pState, const char *pName); + +// set a profile as the default +int32_t TesterClientCoreProfileDefault(TesterClientCoreT *pState, const char *pName); + +#ifdef __cplusplus +}; +#endif + +#endif // _testerclientcore_h + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testercomm.h b/src/thirdparty/dirtysdk/sample/tester2/source/testercomm.h new file mode 100644 index 00000000..bf8b578b --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testercomm.h @@ -0,0 +1,152 @@ +/*H********************************************************************************/ +/*! + \File testercomm.h + + \Description + This module provides a communication layer between the host and the client. + Typical operations are SendLine() and GetLine(), which send and receive + lines of text, commands, debug output, etc. Each platform will implement + its own way of communicating through files, debugger API calls, etc. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/23/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +#ifndef _testercomm_h +#define _testercomm_h + +/*** Include files ****************************************************************/ + +#include "DirtySDK/dirtysock.h" +#include "libsample/zlist.h" + +/*** Defines **********************************************************************/ + +#define TESTER_MSGTYPE_NONE (0) //!< no message type +#define TESTER_MSGTYPE_CONTROL (1) //!< control message - for communication between host/client +#define TESTER_MSGTYPE_COMMAND (2) //!< command message - execute host command +#define TESTER_MSGTYPE_STATUS (3) //!< status message - for status updates between host/client +#define TESTER_MSGTYPE_CONSOLE (4) //!< console output - for display on-screen +#define TESTER_MSGTYPE_MAX (8) //!< max number of message types + +#define TESTERCOMM_NUMCOMMANDS_MAX (512) //!< number of possible input/output commands outstanding +#define TESTERCOMM_COMMANDSIZE_MAX (256*1024) //!< max size of each command line +#define TESTERCOMM_ARGNUM_MAX (16) //!< max number of arguments for each command line + +//! FILE-SPECIFIC defines +#define TESTERCOMM_CLIENTINPUTFILE ("clientinput.txt") +#define TESTERCOMM_CLIENTOUTPUTFILE ("clientoutput.txt") +#define TESTERCOMM_HOSTINPUTFILE (TESTERCOMM_CLIENTOUTPUTFILE) +#define TESTERCOMM_HOSTOUTPUTFILE (TESTERCOMM_CLIENTINPUTFILE) + +//! MSDM-SPECIFIC defines + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// the comm module itself +typedef struct TesterCommT TesterCommT; + +// message type callback +// Callback prototype used for status updates, if enabled +typedef void (TesterCommMsgCbT)(TesterCommT *pState, const char *pMsg, void *pParam); + +// data container for messages to send between host and client +typedef struct TesterCommDataT +{ + int32_t iType; //!< message type + char strBuffer[TESTERCOMM_COMMANDSIZE_MAX]; //!< command data pointer +} TesterCommDataT; + +// interface pointers - filled in by a particular interface adapter function +typedef struct TesterCommInterfaceT +{ + //! connection function + int32_t (*CommConnectFunc) (TesterCommT *pState, const char *pParams, uint32_t bIsHost); + //! update function + int32_t (*CommUpdateFunc) (TesterCommT *pState); + //! disconnection function + int32_t (*CommDisconnectFunc)(TesterCommT *pState); + void *pData; //!< interface-specific data +} TesterCommInterfaceT; + +// module state +struct TesterCommT +{ + // function calls and interface specific stuff + TesterCommInterfaceT *pInterface; //!< interface-specific functions + + // communication stuff + uint32_t uLastConnectTime; //!< the last time a connect was attempted + uint32_t uLastSendTime; //!< last time an output message was sent + TesterCommMsgCbT *MessageMap[TESTER_MSGTYPE_MAX]; //!< message map array + void *pMessageMapUserData[TESTER_MSGTYPE_MAX]; //!< user-supplied data to call back with + uint8_t uSuspended; //!< 1=suspended, 0=awake + uint8_t bGotInput; //!< TRUE=got input from the other side, else FALSE + uint8_t uPad[2]; //!< pad data + + char strCommand[TESTERCOMM_COMMANDSIZE_MAX]; //!< temporary command processing buffer + char strResponse[TESTERCOMM_COMMANDSIZE_MAX]; //!< temporary response processing buffer (XENON only) + TesterCommDataT LineData; //!< temporary line data buffer + TesterCommDataT Message; //!< temporary message buffer + + // data lists + ZListT *pInputData; //!< input commands + ZListT *pOutputData; //!< output commands +}; + + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create a tester host client communication module +TesterCommT *TesterCommCreate(void); + +// connect the host client communication module +int32_t TesterCommConnect(TesterCommT *pState, const char *pParams, uint32_t bIsHost); + +// give the host/client interface some processing time (call this once in a while) +int32_t TesterCommUpdate(TesterCommT *pState); + +// send a message to the other side +int32_t TesterCommMessage(TesterCommT *pState, int32_t iMsgType, const char *pMsgText); + +// register a callback with a particular message type +int32_t TesterCommRegister(TesterCommT *pState, int32_t iMsgType, TesterCommMsgCbT *pCallback, void *pParam); + +// get module status +int32_t TesterCommStatus(TesterCommT *pState, int32_t iSelect, int32_t iValue); + +// suspend the comm module, active until a wake call is issued +void TesterCommSuspend(TesterCommT *pState); + +// wake the comm module from a suspend +void TesterCommWake(TesterCommT *pState); + +// disconnect the host client communication module +int32_t TesterCommDisconnect(TesterCommT *pState); + +// destroy a tester host client communication module +void TesterCommDestroy(TesterCommT *pState); + +// testercomm_file method to attach to host +void TesterCommAttachFile(TesterCommT *pState); + +// testercomm_socket method to attach to host +void TesterCommAttachSocket(TesterCommT *pState); + +#ifdef __cplusplus +}; +#endif + +#endif // _testercomm_h + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerconsole.c b/src/thirdparty/dirtysdk/sample/tester2/source/testerconsole.c new file mode 100644 index 00000000..001b0fa8 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerconsole.c @@ -0,0 +1,258 @@ +/*H********************************************************************************/ +/*! + \File testerconsole.c + + \Description + This module buffers console output for the tester application. + In essence, it is just a large FIFO which knows how to handle + newline characters as expected. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 09/15/1999 (gschaefer) First Version + \Version 11/08/1999 (gschaefer) Cleanup and revision + \Version 03/29/2005 (jfrank) Update for Tester2 +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include "DirtySDK/dirtysock.h" +#include "libsample/zmem.h" +#include "testercomm.h" +#include "testerregistry.h" +#include "testerconsole.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +struct TesterConsoleT //!< console state structure +{ + char *pBuf; //!< pointer to console buffer + int32_t iLen; //!< length of console buffer + int32_t iInp; //!< fifo input offset + int32_t iOut; //!< fifo output offset + int32_t iWrap; //!< flag to indicate overflow handling + int32_t iRefcon; //!< reference constant for callback + void *pRefptr; //!< reference pointer for callback +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterConsoleOutput + + \Description + Add text to console buffer. + + \Input *pRef - console reference + \Input *pText - text to add to buffer (\n newlines are fine) + + \Output None + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +static void _TesterConsoleAddText(TesterConsoleT *pRef, const char *pText) +{ + char ch; + + // insert complete string + for ( ; (ch = *pText++) != 0; ) + { + // save the data and advance index + pRef->pBuf[pRef->iInp] = ch; + pRef->iInp = (pRef->iInp+1) % pRef->iLen; + + // check for overflow + if (pRef->iInp == pRef->iOut) + { + if (!pRef->iWrap) + { + // restore input index to old value + pRef->iInp = (pRef->iInp+pRef->iLen-1) % pRef->iLen; + break; + } + // delete oldest character + pRef->iOut = (pRef->iOut+1) % pRef->iLen; + } + } +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterConsoleCreate + + \Description + Create instance of a console buffer. + + \Input iSize - buffer size (recommended 4K min) + \Input iWrap - flag to indicate overflow action (true=wrap over oldest) + \Input iRefcon - constant passed back during flush callback + \Input pRefptr - pointer passed back during flush callback + + \Output TesterConsoleT * - new console pointer + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +TesterConsoleT *TesterConsoleCreate(int32_t iSize, int32_t iWrap) +{ + TesterConsoleT *pRef; + + pRef = ZMemAlloc(sizeof(*pRef)); + if (pRef == NULL) + return(NULL); + + pRef->iLen = iSize; + + pRef->iWrap = iWrap; + pRef->iInp = pRef->iOut = 0; + pRef->pBuf = ZMemAlloc(pRef->iLen+1); + + TesterRegistrySetPointer("CONSOLE", pRef); + + return(pRef); +} + + +/*F********************************************************************************/ +/*! + \Function TesterConsoleCreate + + \Description + Connect a console to a particular ref set. + + \Input *pRef - console reference + \Input iRefcon - constant passed back during flush callback + \Input pRefptr - pointer passed back during flush callback + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterConsoleConnect(TesterConsoleT *pRef, int32_t iRefcon, void *pRefptr) +{ + pRef->iRefcon = iRefcon; + pRef->pRefptr = pRefptr; +} + + +/*F********************************************************************************/ +/*! + \Function TesterConsoleClear + + \Description + Clear the console + + \Input *pRef - console reference + + \Output None + + \Version 04/07/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterConsoleClear(TesterConsoleT *pRef) +{ + // check for errors + if(pRef == NULL) + return; + + // wipe both the data and the head/tail pointers + ds_memclr(pRef->pBuf, (pRef->iLen)+1); + pRef->iInp = pRef->iOut = 0; +} + + +/*F********************************************************************************/ +/*! + \Function TesterConsoleOutput + + \Description + Add text to console buffer. + + \Input *pRef - console reference + \Input iMsgType - message type to prepend to the console output, NONE for none + \Input *pText - text to add to buffer (\n newlines are fine) + + \Output None + + \Version 04/04/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterConsoleOutput(TesterConsoleT *pRef, int32_t iMsgType, const char *pText) +{ + // add the content of the text + _TesterConsoleAddText(pRef, pText); +} + + +/*F********************************************************************************/ +/*! + \Function TesterConsoleFlush + + \Description + Flush buffer data to output handler (calls output handler). + + \Input *pRef - console reference + \Input *pProc - Address of output handler + + \Output None + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +void TesterConsoleFlush(TesterConsoleT *pRef, TesterConsoleDisplayCbT *pProc) +{ + if (pRef->iOut < pRef->iInp) + { + // grab data in one chunk + pRef->pBuf[pRef->iInp] = 0; + (*pProc)(pRef->pBuf + pRef->iOut, pRef->iInp - pRef->iOut, pRef->iRefcon, pRef->pRefptr); + pRef->iOut = pRef->iInp; + } + else if (pRef->iOut > pRef->iInp) + { + // grab data in two chunks + pRef->pBuf[pRef->iLen] = 0; + (*pProc)(pRef->pBuf + pRef->iOut, pRef->iLen - pRef->iOut, pRef->iRefcon, pRef->pRefptr); + pRef->pBuf[pRef->iInp] = 0; + (*pProc)(pRef->pBuf, pRef->iInp, pRef->iRefcon, pRef->pRefptr); + pRef->iOut = pRef->iInp; + } +} + + +/*F********************************************************************************/ +/*! + \Function TesterConsoleDestroy + + \Description + Release resources and destroy console module. + + \Input *pRef - console reference + + \Output None + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +void TesterConsoleDestroy(TesterConsoleT *pRef) +{ + ZMemFree(pRef->pBuf); + ZMemFree(pRef); + TesterRegistrySetPointer("CONSOLE", NULL); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerconsole.h b/src/thirdparty/dirtysdk/sample/tester2/source/testerconsole.h new file mode 100644 index 00000000..09635dc5 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerconsole.h @@ -0,0 +1,69 @@ +/*H********************************************************************************/ +/*! + \File testerconsole.h + + \Description + This module buffers console output for the tester application. + In essense, it is just a large FIFO which knows how to handle + newline characters as expected. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 09/15/1999 (gschaefer) First Version + \Version 11/08/1999 (gschaefer) Cleanup and revision + \Version 03/29/2005 (jfrank) Update for Tester2 +*/ +/********************************************************************************H*/ + +#ifndef _testerconsole_h +#define _testerconsole_h + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// module state +typedef struct TesterConsoleT TesterConsoleT; + +// display callback +typedef void (TesterConsoleDisplayCbT)(const char *pBuf, int32_t iLen, int32_t iRefcon, void *pRefptr); + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// Create instance of a console buffer. +TesterConsoleT *TesterConsoleCreate(int32_t iSize, int32_t iWrap); + +// Connect a console to a particular ref set. +void TesterConsoleConnect(TesterConsoleT *pRef, int32_t iRefcon, void *pRefptr); + +// Clear the console +void TesterConsoleClear(TesterConsoleT *pRef); + +// Add text to console buffer. +void TesterConsoleOutput(TesterConsoleT *pRef, int32_t iMsgType, const char *pText); + +// Flush buffer data to output handler (calls output handler) +void TesterConsoleFlush(TesterConsoleT *pRef, TesterConsoleDisplayCbT *pProc); + +// Release resources and destroy console module. +void TesterConsoleDestroy(TesterConsoleT *pRef); + +#ifdef __cplusplus +}; +#endif + +#endif // _testerconsole_h + + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerhistory.c b/src/thirdparty/dirtysdk/sample/tester2/source/testerhistory.c new file mode 100644 index 00000000..ef8e4522 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerhistory.c @@ -0,0 +1,408 @@ +/*H********************************************************************************/ +/*! + \File testerhistory.c + + \Description + Maintains command history for a particular user. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/05/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/dirtysock.h" +#include "libsample/zlib.h" +#include "libsample/zfile.h" +#include "libsample/zmem.h" +#include "testercomm.h" +#include "testerregistry.h" +#include "testerhistory.h" + +/*** Defines **********************************************************************/ + +#define TESTERHISTORY_SIZE_DEFAULT (100) //!< number of entries to save + +/*** Type Definitions *************************************************************/ + +//! Each history entry has a structure +typedef struct TesterHistoryEntryT +{ + int32_t iCount; //!< history entry count + char strText[TESTERCOMM_COMMANDSIZE_MAX]; //!< command text +} TesterHistoryEntryT; + +struct TesterHistoryT +{ + int32_t iTotalEntries; //!< total entries + int32_t iHeadIndex; //!< head index - first valid entry + int32_t iTailIndex; //!< tail index - last valid entry + int32_t iHeadCount; //!< command count of the head entry + int32_t iTailCount; //!< command count of the tail entry + int32_t iCount; //!< history count object - keep track of how many entries have gone past + TesterHistoryEntryT *pEntries; //!< tester history entries +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _TesterHistoryGetFullPath + + \Description + Get full path to history file. + + \Input *pFilename - name of history file + + \Output + const char * - pointer to full name + + \Version 05/11/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_TesterHistoryGetFullPath(const char *pFilename) +{ + static char strHistoryName[128]; + + // create full path + TesterRegistryGetString("ROOT", strHistoryName, sizeof(strHistoryName)); + if (strHistoryName[0] != '\0') + { + ds_strnzcat(strHistoryName, "\\", sizeof(strHistoryName)); + ds_strnzcat(strHistoryName, pFilename, sizeof(strHistoryName)); + } + else + { + ds_strnzcpy(strHistoryName, ".\\", sizeof(strHistoryName)); + } + return(strHistoryName); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterHistoryCreate + + \Description + Create a tester history module + + \Input iSize - size of history to create, 0 for default + + \Output TesterHistoryT * - allocated tester history module + + \Version 04/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +TesterHistoryT *TesterHistoryCreate(int32_t iSize) +{ + TesterHistoryT *pState; + uint32_t uBufferSize; + + // allocate module state + pState = ZMemAlloc(sizeof(TesterHistoryT)); + ds_memclr(pState, sizeof(TesterHistoryT)); + + // allocate history entries + pState->iTotalEntries = (iSize <= 0) ? TESTERHISTORY_SIZE_DEFAULT : iSize; + uBufferSize = pState->iTotalEntries * sizeof(TesterHistoryEntryT); + pState->pEntries = ZMemAlloc(uBufferSize); + ds_memclr(pState->pEntries, uBufferSize); + + TesterRegistrySetPointer("HISTORY", pState); + + return(pState); +} + +/*F********************************************************************************/ +/*! + \Function TesterHistoryGet + + \Description + Return the text for a particular tester command + + \Input *pState - module state + \Input iNum - entry number requested + \Input *pBuf - destination buffer (may be NULL) + \Input iSize - size of destination buffer + + \Output + const char * - pointer to history entry, or NULL + + \Version 04/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +const char *TesterHistoryGet(TesterHistoryT *pState, int32_t iNum, char *pBuf, int32_t iSize) +{ + TesterHistoryEntryT *pEntry = NULL; + int32_t iIndex; + + // see if we have the entry + for (iIndex = 0; (iIndex < pState->iTotalEntries) && (pEntry == NULL); iIndex++) + { + if (pState->pEntries[iIndex].iCount == iNum) + { + pEntry = &(pState->pEntries[iIndex]); + break; + } + } + + // did we find it? + if (pEntry == NULL) + { + return(NULL); + } + + // copy in the data + if (pBuf != NULL) + { + ds_strnzcpy(pBuf, pEntry->strText, iSize); + } + return(pEntry->strText); +} + +/*F********************************************************************************/ +/*! + \Function TesterHistorySave + + \Description + Save historical commands to a file. + + \Input *pState - module state + \Input *pFilename - filename to save the history in + + \Output int32_t - number of entries saved, <0 if error + + \Version 05/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterHistorySave(TesterHistoryT *pState, const char *pFilename) +{ + const char *pCommand; + int32_t iHead, iTail, iLoop, iTotalSize; + ZFileT iFileId; + + if ((pState == NULL) || (pFilename == NULL)) + { + return(-1); + } + + // convert to full path + pFilename = _TesterHistoryGetFullPath(pFilename); + + // get the count + if (TesterHistoryHeadTailCount(pState, &iHead, &iTail)) + { + // probably an empty list - don't save + return(0); + } + + // try and open the file + if ((iFileId = ZFileOpen(pFilename, ZFILE_OPENFLAG_WRONLY | ZFILE_OPENFLAG_CREATE)) < 0) + { + ZPrintf("testerhistory: error %d trying to save history to %s\n", iFileId, pFilename); + return(0); + } + + // save the history (overwrite anything already there) + for (iLoop = iHead, iTotalSize = 0; iLoop <= iTail; iLoop += 1) + { + if ((pCommand = TesterHistoryGet(pState, iLoop, NULL, 0)) != NULL) + { + ZFileWrite(iFileId, (void *)pCommand, (int32_t)strlen(pCommand)); + ZFileWrite(iFileId, (void *)"\n", 1); + iTotalSize += 1; + } + } + ZFileClose(iFileId); + + // return the number of entries saved + return(iTotalSize); +} + +/*F********************************************************************************/ +/*! + \Function TesterHistoryLoad + + \Description + Load historical commands into the command history from a file. + + \Notes + Loads all commands in the file into memory, but based on the size + of the history created, only the last N number of commands + (where N is the number of commands requested at TesterHistoryCreate) + will be saved in memory. + + \Input *pState - module state + \Input *pFilename - filename with the history, one command per line + + \Output int32_t - number of entries in the file, <0 if error + + \Version 05/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterHistoryLoad(TesterHistoryT *pState, const char *pFilename) +{ + char *pHistory, *pCmd; + const char strSep[] = "\r\n"; + int32_t iFileSize; + int32_t iNumCommands = 0; + + if ((pState == NULL) || (pFilename == NULL)) + { + return(-1); + } + + // convert to full path + pFilename = _TesterHistoryGetFullPath(pFilename); + + // load history file + if ((pHistory = ZFileLoad(pFilename, &iFileSize, 0)) == NULL) + { + return(0); + } + + // parse history file + for (pCmd = strtok(pHistory, strSep); pCmd != NULL; pCmd = strtok(NULL, strSep)) + { + TesterHistoryAdd(pState, pCmd); + iNumCommands++; + } + + ZMemFree(pHistory); + return(iNumCommands); +} + +/*F********************************************************************************/ +/*! + \Function TesterHistoryAdd + + \Description + Add an entry to the tester history + + \Input *pState - module state + \Input *pBuf - text to add + + \Output int32_t - index value ( >= 0 ) or error code ( < 0 ) if error occurs + + \Version 04/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterHistoryAdd(TesterHistoryT *pState, const char *pBuf) +{ + TesterHistoryEntryT *pEntry; + + // check for errors + if ((pState == NULL) || (pBuf == NULL)) + { + return(TESTERHISTORY_ERROR_NULLPOINTER); + } + + // clear the new entry + pEntry = &(pState->pEntries[pState->iTailIndex]); + ds_memclr(pEntry, sizeof(TesterHistoryEntryT)); + + // and copy the new entry in + ds_strnzcpy(pEntry->strText, pBuf, sizeof(pEntry->strText)); + pState->iTailCount = pState->iCount; + pEntry->iCount = pState->iCount; + pState->iCount++; + + // adjust the tail pointer + pState->iTailIndex++; + if (pState->iTailIndex >= pState->iTotalEntries) + { + pState->iTailIndex = 0; + } + + // see if we need to adjust the head pointer + if (pState->iHeadIndex == pState->iTailIndex) + { + // we're looping around - adjust the pointer + pState->iHeadIndex++; + // adjust if we're looping around + if(pState->iHeadIndex >= pState->iTotalEntries) + pState->iHeadIndex = 0; + // get the head count + pState->iHeadCount = pState->pEntries[pState->iHeadIndex].iCount; + } + + // return the index number + return(pState->iCount); +} + +/*F********************************************************************************/ +/*! + \Function TesterHistoryHeadTailCount + + \Description + Return the head and tail history numbers + + \Input *pState - module state + \Input *pHeadNum - destination history entry number of the first entry in the buffer + \Input *pTailNum - destination history entry number of the last entry in the buffer + + \Output int32_t - 0=success, error code otherwise + + \Version 04/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterHistoryHeadTailCount(TesterHistoryT *pState, int32_t *pHeadNum, int32_t *pTailNum) +{ + // set the incoming values to some default values, just in case we run into an error + *pHeadNum = 0; + *pTailNum = 0; + + // check for errors + if ((pState == NULL) || (pHeadNum == NULL) || (pTailNum == NULL)) + { + return(TESTERHISTORY_ERROR_NULLPOINTER); + } + + // see if the buffer is empty + if (pState->iHeadIndex == pState->iTailIndex) + { + return(TESTERHISTORY_ERROR_NOSUCHENTRY); + } + + // otherwise give out some information + *pHeadNum = pState->iHeadCount; + *pTailNum = pState->iTailCount; + return(TESTERHISTORY_ERROR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function TesterHistoryDestroy + + \Description + Destroy a tester history object + + \Input *pState - module state + + \Output None + + \Version 04/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterHistoryDestroy(TesterHistoryT *pState) +{ + // kill the object if non-null + if (pState) + { + ZMemFree(pState->pEntries); + ZMemFree(pState); + } + + TesterRegistrySetPointer("HISTORY", NULL); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerhistory.h b/src/thirdparty/dirtysdk/sample/tester2/source/testerhistory.h new file mode 100644 index 00000000..a4faf90f --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerhistory.h @@ -0,0 +1,66 @@ +/*H********************************************************************************/ +/*! + \File testerhistory.h + + \Description + Maintains command history for a particular user. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/05/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +#ifndef _testerhistory_h +#define _testerhistory_h + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +#define TESTERHISTORY_ERROR_NONE (0) //!< no error +#define TESTERHISTORY_ERROR_NULLPOINTER (-1) //!< null pointer sent (invalid) +#define TESTERHISTORY_ERROR_NOSUCHENTRY (-2) //!< entry requested doesn't exist + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct TesterHistoryT TesterHistoryT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create a tester history module +TesterHistoryT *TesterHistoryCreate(int32_t iSize); + +// get an entry in the history +const char *TesterHistoryGet(TesterHistoryT *pState, int32_t iNum, char *pBuf, int32_t iSize); + +// add an entry to the history +int32_t TesterHistoryAdd(TesterHistoryT *pState, const char *pBuf); + +// return the head and tail history numbers +int32_t TesterHistoryHeadTailCount(TesterHistoryT *pState, int32_t *pHeadNum, int32_t *pTailNum); + +// save historical commands to a file +int32_t TesterHistorySave(TesterHistoryT *pState, const char *pFilename); + +// load historical commands from a file +int32_t TesterHistoryLoad(TesterHistoryT *pState, const char *pFilename); + +// destroy a tester history object +void TesterHistoryDestroy(TesterHistoryT *pState); + +#ifdef __cplusplus +}; +#endif + +#endif // _testerhistory_h + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerhostcore.c b/src/thirdparty/dirtysdk/sample/tester2/source/testerhostcore.c new file mode 100644 index 00000000..fbf4f950 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerhostcore.c @@ -0,0 +1,868 @@ +/*H********************************************************************************/ +/*! + \File testerhostcore.c + + \Description + Main control module for the Tester2 host application. + + \Notes + This is the main host module for the tester2 host application. + It contains mostly global-variable type objects and operations, similar + to LobbyAPI. TesterHostCore is responsible for starting up all the + necessary child modules like TesterConsole, TesterComm, etc. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/17/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtydefs.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/dirtyvers.h" +#include "DirtySDK/util/jsonformat.h" +#include "DirtySDK/util/jsonparse.h" + +#include "libsample/zfile.h" +#include "libsample/zmem.h" +#include "testercomm.h" +#include "testerconsole.h" +#include "testermodules.h" +#include "testerregistry.h" +#include "testerhistory.h" +#include "testerhostcore.h" + +/*** Defines **********************************************************************/ + +#define TESTERHOSTCORE_CONSOLESIZE_DEFAULT (16384) //!< default size of display console + +/*** Type Definitions *************************************************************/ + +struct TesterHostCoreT +{ + TesterCommT *pComm; //!< host/client communication module + TesterConsoleT *pConsole; //!< console for managing output + TesterConsoleDisplayCbT *pDisplayProc; //!< procedure for displaying output + TesterModulesT *pModules; //!< tester modules/dispatcher + TesterHistoryT *pHistory; //!< host history (only supported on some platforms) + int32_t iCurHistory; //!< current history index + int16_t iShutdown; //!< internal - set to 1 to shut stuff down + int16_t iLocalEcho; //!< internal - set to 1 to locally echo output + ZFileT zFile; //!< logfile + char strCommand[TESTERCOMM_COMMANDSIZE_MAX]; //!< temporary command processing buffer +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Private Variables ************************************************************/ + +//! used to format strings for sending +static char _TesterHostCore_strTempText[4096] = ""; + +/*** External Functions ***********************************************************/ + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreUpdate + + \Description + Update function to pump the host/client buffers + and do other idle processing tasks. + + \Input *pData - user specified data (modules state TesterHostCoreT *) + \Input uTick - NetIdle ticks + + \Output None + + \Version 03/28/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterHostCoreUpdate(void *pData, uint32_t uTick) +{ + TesterHostCoreT *pState = (TesterHostCoreT *)pData; + + // check for errors + if (pState == NULL) + return; + + // update the host client comm pipe + if (pState->pComm) + { + TesterCommUpdate(pState->pComm); + } + + // service the output + if ((pState->pConsole) && (pState->pDisplayProc)) + TesterConsoleFlush(pState->pConsole, pState->pDisplayProc); + + // see if we want to shut all running commands down + if(pState->iShutdown) + { + // shut down all running commands + pState->iShutdown = 0; + ZShutdown(); + // now disconnect dirtysock + NetConnDisconnect(); + } +} + + +/*F********************************************************************************/ +/*! + \Function _TesterHostCoreMsgControl + + \Description + Handle incoming messages from TesterComm + + \Input *pState - Module state + \Input *pMsg - Message text + \Input *pParam - User-supplied data + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterHostCoreMsgControl(TesterCommT *pState, const char *pMsg, void *pParam) +{ + const char strNetStart[] = "NETSTART"; + const char strNetStop[] = "NETSTOP"; + TesterHostCoreT *pCore = pParam; + char *pNetParams; + + // print locally + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_CONTROL, pMsg); + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_CONTROL, "\n"); + + // ---- CONNECT ---- : + if (strcmp(pMsg, "CONNECT") == 0) + { + // print locally + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_CONTROL, "CONNECTED\n"); + // send it along + TesterCommMessage(pState, TESTER_MSGTYPE_CONTROL, "CONNECTED\n"); + } + // ---- NETSTART ---- : bring up the network + else if (strncmp(pMsg, strNetStart, strlen(strNetStart)) == 0) + { + // extract the net params + pNetParams = (char *)(&pMsg[strlen(strNetStart)+1]); + + // connect the module + TesterHostCoreConnect(pCore, pNetParams); + + // resolve local address + ZPrintf("Local address: %a\n", NetConnStatus('addr', 0, NULL, 0)); + + // get MAC address + ZPrintf("Local MAC address: %s\n", NetConnMAC()); + + } + // ---- NETSTOP ---- : shut down the network + else if (strncmp(pMsg, strNetStop, strlen(strNetStop)) == 0) + { + // disconnect stuff + TesterHostCoreDisconnect(pCore); + } + else + { + ZPrintf("Unknown CONTROL call: {%s}\n", pMsg); + } +} + + +/*F********************************************************************************/ +/*! + \Function _TesterHostCoreMsgStatus + + \Description + Handle incoming messages from TesterComm + + \Input *pState - Module state + \Input *pMsg - Message text + \Input *pParam - User-supplied data + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterHostCoreMsgStatus(TesterCommT *pState, const char *pMsg, void *pParam) +{ + TesterHostCoreT *pCore = pParam; + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_STATUS, pMsg); +} + + +/*F********************************************************************************/ +/*! + \Function _TesterHostCoreMsgCommand + + \Description + Handle incoming messages from TesterComm + + \Input *pState - Module state + \Input *pMsg - Message text + \Input *pParam - User-supplied data + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterHostCoreMsgCommand(TesterCommT *pState, const char *pMsg, void *pParam) +{ + TesterHostCoreT *pCore = (TesterHostCoreT *)pParam; + int32_t iResult; + + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_COMMAND, "\n> "); + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_COMMAND, pMsg); + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_COMMAND, "\n"); + + // signal that we've received the message + JsonInit(pState->strCommand, sizeof(pState->strCommand), 0); + JsonAddStr(pState->strCommand, "COMMAND", pMsg); + JsonAddInt(pState->strCommand, "STATUS", 'rcvd'); + TesterCommMessage(pState, TESTER_MSGTYPE_STATUS, JsonFinish(pState->strCommand)); + + // signal that we're executing the message + JsonInit(pState->strCommand, sizeof(pState->strCommand), 0); + JsonAddStr(pState->strCommand, "COMMAND", pMsg); + JsonAddInt(pState->strCommand, "STATUS", 'exec'); + TesterCommMessage(pState, TESTER_MSGTYPE_STATUS, JsonFinish(pState->strCommand)); + + // dispatch the command + iResult = TesterHostCoreDispatch(pCore, pMsg); + + // display the results locally + ds_memclr(pState->strCommand, sizeof(pState->strCommand)); + if (iResult == 0) + { + ds_snzprintf(pState->strCommand, sizeof(pState->strCommand), "done {%s} error {none}\n", pMsg); + } + else + { + ds_snzprintf(pState->strCommand, sizeof(pState->strCommand), " {%s} error {%d}\n", pMsg, iResult); + } + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_CONSOLE, pState->strCommand); + ZPrintf("%s", pState->strCommand); //send it back too? + + // send results back to the client + JsonInit(pState->strCommand, sizeof(pState->strCommand), 0); + JsonAddStr(pState->strCommand, "COMMAND", pMsg); + JsonAddInt(pState->strCommand, "STATUS", 'done'); + // include an error if present + if (iResult) + { + JsonAddInt(pState->strCommand, "ERROR", iResult); + } + TesterCommMessage(pState, TESTER_MSGTYPE_STATUS, JsonFinish(pState->strCommand)); +} + + +/*F********************************************************************************/ +/*! + \Function _TesterHostCoreMsgConsole + + \Description + Handle incoming messages from TesterComm + + \Input *pState - Module state + \Input *pMsg - Message text + \Input *pParam - User-supplied data + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterHostCoreMsgConsole(TesterCommT *pState, const char *pMsg, void *pParam) +{ + TesterHostCoreT *pCore = pParam; + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_CONSOLE, pMsg); +} + + +/*F********************************************************************************/ +/*! + \Function _TesterHostCorePrintf + + \Description + Tester Printf function. + + \Input *pParm - Module state + \Input *pText - Message text + + \Output 0=success + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _TesterHostCorePrintf(void *pParm, const char *pText) +{ + TesterHostCoreT *pCore = pParm; + + // this is where all NetPrintf calls wind up + // and remember that ZPrintf calls NetPrintf + // so, on the host, we have to do three things: + // 1. print locally + // 2. send back to the client + // 3. write to logfile + + // add locally + TesterConsoleOutput(pCore->pConsole, TESTER_MSGTYPE_CONSOLE, pText); + + // send to the client, but only if we've gotten input from the client + if (TesterCommStatus(pCore->pComm, 'inpt', 0)) + { + // append to buffer + ds_strnzcat(_TesterHostCore_strTempText, pText, sizeof(_TesterHostCore_strTempText)); + + // do we have a linefeed? + if (strrchr(_TesterHostCore_strTempText, '\n') != NULL) + { + TesterCommMessage(pCore->pComm, TESTER_MSGTYPE_CONSOLE, _TesterHostCore_strTempText); + _TesterHostCore_strTempText[0] = '\0'; + } + } + + // write to logfile + if (pCore->zFile != ZFILE_INVALID) + { + ZFileWrite(pCore->zFile, (char *)pText, (int32_t)(sizeof(char) * strlen(pText))); + } + + // code to write directly to output; useful when running from command-line without a debugger + #if defined(DIRTYCODE_XBOXONE) && 0 + { + DWORD dwBytesWritten; + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), + pText, + strlen(pText), + &dwBytesWritten, + NULL); + } + #endif + + // returning a 1 here echoes locally + return(pCore->iLocalEcho); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreCreate + + \Description + Create a TesterHostCoreT module and return the pointer to it + + \Input *pParams - startup parameters + + \Output TesterHostCoreT * - newly allocated and created TesterHostCoreT + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +TesterHostCoreT *TesterHostCoreCreate(const char *pParams) +{ + TesterHostCoreT *pState; + uint16_t aJson[512]; + char strControlDir[2]; // check to see if a control dir was specified + + // create and wipe clean the module + pState = (TesterHostCoreT *)ZMemAlloc(sizeof(TesterHostCoreT)); + ds_memclr((void *)pState, sizeof(TesterHostCoreT)); + pState->zFile = (ZFileT)ZFILE_INVALID; + + JsonParse(aJson, sizeof(aJson)/sizeof(*aJson), pParams, -1); + + // create a registry + TesterRegistryCreate(-1); + TesterRegistrySetPointer("CORE", pState); + + // now create the children modules + pState->pComm = TesterCommCreate(); + pState->pConsole = TesterConsoleCreate(TESTERHOSTCORE_CONSOLESIZE_DEFAULT, TRUE); + pState->pModules = TesterModulesCreate(); + + // determine if we should locally echo stuff + pState->iLocalEcho = (int16_t)JsonGetInteger(JsonFind(aJson, "LOCALECHO"), 1); + + // register host commands + TesterModulesRegisterHostCommands(pState->pModules); + + // register platform-specific commands + TesterModulesRegisterPlatformCommands(pState->pModules); + + // attach an interface method + JsonGetString(JsonFind(aJson, "CONTROLDIR"), strControlDir, sizeof(strControlDir), ""); + if (strControlDir[0] != '\0') + { + TesterCommAttachFile(pState->pComm); + } + else + { + TesterCommAttachSocket(pState->pComm); + } + + // pass the incoming startup parameters through to the comm modules + TesterCommConnect(pState->pComm, pParams, TRUE); + + // register callbacks for all the types of messages we'll see + TesterCommRegister(pState->pComm, TESTER_MSGTYPE_CONTROL, _TesterHostCoreMsgControl, (void *)pState); + TesterCommRegister(pState->pComm, TESTER_MSGTYPE_COMMAND, _TesterHostCoreMsgCommand, (void *)pState); + TesterCommRegister(pState->pComm, TESTER_MSGTYPE_STATUS, _TesterHostCoreMsgStatus, (void *)pState); + TesterCommRegister(pState->pComm, TESTER_MSGTYPE_CONSOLE, _TesterHostCoreMsgConsole, (void *)pState); + + // create history + pState->pHistory = TesterHistoryCreate(-1); + + #if DIRTYCODE_LOGGING + // in debug mode, hook into debug output + NetPrintfHook(_TesterHostCorePrintf, pState); + #endif + + // hook into tester output + ZPrintfHook(_TesterHostCorePrintf, pState); + + // done + return(pState); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreConnect + + \Description + Connect the core module and all its children + + \Input *pState - TesterHostCoreT module to connect + \Input *pNetParams - Parameters to pass to NetConnStartup() + + \Output int32_t - 0=success, error code otherwise + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterHostCoreConnect(TesterHostCoreT *pState, const char *pNetParams) +{ + // check the state + if (pState == NULL) + return(TESTERHOSTCORE_ERROR_NULLPOINTER); + + // start dirtysock + ZPrintf("HOSTCORE: Starting Dirtysock with NetParams {%s}\n", + (pNetParams==NULL) ? "" : pNetParams ); + NetConnStartup(pNetParams); + + // done + return(TESTERHOSTCORE_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreIdle + + \Description + Idle function - pump this to make networking and other modules happy. + + \Input *pState - TesterHostCoreT module state + + \Output None + + \Version 03/28/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterHostCoreIdle(TesterHostCoreT *pState) +{ + // give time to the network + NetConnIdle(); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreDisplayFunc + + \Description + Register a display function to show stuff on the screen. + + \Input *pState - TesterHostCoreT module state + \Input *pProc - function pointer to display procedure + \Input iRefcon - int32_t value to pass to the display function when called + \Input *pDisplayRef - ref value to pass to the display function when called (hDlg) + + \Output None + + \Version 03/28/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterHostCoreDisplayFunc(TesterHostCoreT *pState, TesterConsoleDisplayCbT *pProc, int32_t iRefcon, void *pRefptr) +{ + pState->pDisplayProc = pProc; + TesterConsoleConnect(pState->pConsole, iRefcon, pRefptr); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreDispatch + + \Description + Dispatch a function (if possible) based on the incoming command line. + + \Input *pState - pointer to host core module + \Input *pCommandLine - standard command line (command arg1 arg2 arg3 ...) + + \Output 0=success, error code otherwise + + \Version 10/31/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t TesterHostCoreDispatch(TesterHostCoreT *pState, const char *pCommandLine) +{ + const char *pSemi; + uint8_t bQuoted; + + // check for multiple commands separated by a semi-colon + for (bQuoted = *pCommandLine == '"', pSemi = pCommandLine+1; *pSemi != '\0'; pSemi += 1) + { + // update quoted status + if ((*pSemi == '"') && (*(pSemi-1) != '\\')) + { + bQuoted = !bQuoted; + } + + // process semicolon, if not quoted + if ((*pSemi == ';') && (*(pSemi-1) != '\\') && !bQuoted) + { + ds_strsubzcpy(pState->strCommand, sizeof(pState->strCommand), pCommandLine, (int32_t)(pSemi - pCommandLine)); + + // dispatch the command + TesterHistoryAdd(pState->pHistory, pState->strCommand); + pState->iCurHistory = -1; + TesterModulesDispatch(pState->pModules, pState->strCommand); + + // skip past command + pCommandLine = pSemi + 1; + } + } + + // dispatch the command + TesterHistoryAdd(pState->pHistory, pCommandLine); + pState->iCurHistory = -1; + return(TesterModulesDispatch(pState->pModules, pCommandLine)); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreGetHistory + + \Description + Get prev/next history entry. + + \Input *pState - pointer to host core module + \Input iPrevNext - amount to index prev (negative) or next (positive) in buffer + \Input *pBuf - [out] storage for returned history entry (may be null) + \Input iSize - size of output buffer + + \Output + const char * - pointer to history text, or NULL + + \Version 10/31/2005 (jbrookes) +*/ +/********************************************************************************F*/ +const char *TesterHostCoreGetHistory(TesterHostCoreT *pState, int32_t iPrevNext, char *pBuf, int32_t iSize) +{ + int32_t iHeadCount, iTailCount; + const char *pText = NULL; + + // get current min/max + TesterHistoryHeadTailCount(pState->pHistory, &iHeadCount, &iTailCount); + + // if we have a current history index, modify from there + if (pState->iCurHistory != -1) + { + // update with clamping + pState->iCurHistory += iPrevNext; + if (pState->iCurHistory > iTailCount) + { + // iTailCount+1 to return an empty entry at the tail (current commandline) + pState->iCurHistory = iTailCount+1; + } + if (pState->iCurHistory < iHeadCount) + { + pState->iCurHistory = iHeadCount; + } + } + else + { + pState->iCurHistory = (iPrevNext < 0) ? iTailCount : iHeadCount; + } + + // get history entry + if (pState->iCurHistory <= iTailCount) + { + pText = TesterHistoryGet(pState->pHistory, pState->iCurHistory, pBuf, iSize); + } + else if (pBuf != NULL) + { + *pBuf = '\0'; + } + + // return history text + return(pText); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreSendStatus + + \Description + Send a command to the host - should comes from the client GUI command line. + + \Input *pState - TesterHostCoreT module + \Input *pData - command to send + + \Output 0=success, error code otherwise + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterHostCoreSendStatus(TesterHostCoreT *pState, const char *pData) +{ + // echo it locally + _TesterHostCoreMsgStatus(pState->pComm, pData, (void *)pState); + + // send it along + return(TesterCommMessage(pState->pComm, TESTER_MSGTYPE_STATUS, pData)); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreRegister + + \Description + Register a command + + \Input *pState - TesterClientCoreT module state + \Input *pCommand - command name to register with + \Input *pFunctionPtr - the ZCommand module to call + + \Output int32_t - 0=success, error code otherwise + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterHostCoreRegister(TesterHostCoreT *pState, const char *pCommand, ZCommand *pFunctionPtr) +{ + if (pState == NULL) + return(TESTERHOSTCORE_ERROR_NULLPOINTER); + + return(TesterModulesRegister(pState->pModules, pCommand, pFunctionPtr)); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreDisconnect + + \Description + Disconnect the core module and all its children + + \Input *pState - TesterHostCoreT module to disconnect + + \Output int32_t - 0=success, error code otherwise + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterHostCoreDisconnect(TesterHostCoreT *pState) +{ + // check the state + if (pState == NULL) + return(TESTERHOSTCORE_ERROR_NULLPOINTER); + // check the children + if (pState->pComm == NULL) + return(TESTERHOSTCORE_ERROR_NULLPOINTER); + + // kill running commands first + ZShutdown(); + + return(TESTERHOSTCORE_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreStartSavingLog + + \Description + Start writting log message in strFileName file + + \Input *pState - TesterHostCoreT module to disconnect + + \Output int32_t - 0=success, error code otherwise + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterHostCoreStartSavingLog(TesterHostCoreT *pState, const char * strLogfileName) +{ + int32_t retVal = -1; + + if (pState) + { + if (pState->zFile == ZFILE_INVALID) + { + int8_t strCompleteLogfileName[128]; + + ds_memclr(strCompleteLogfileName, sizeof(strCompleteLogfileName)); + + ds_strnzcpy((char *)strCompleteLogfileName, "GAME:\\", sizeof(strCompleteLogfileName)); + ds_strnzcat((char *)strCompleteLogfileName, (char *)strLogfileName, sizeof(strCompleteLogfileName)); + + pState->zFile = ZFileOpen((char *)strCompleteLogfileName, ZFILE_OPENFLAG_WRONLY | ZFILE_OPENFLAG_CREATE); + + if (pState->zFile != ZFILE_INVALID) + { + retVal = 0; + } + } + } + + return(retVal); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreStopSavingLog + + \Description + Stop writting log message in strFileName file + + \Input *pState - TesterHostCoreT module reference + + \Output int32_t - 0=success, error code otherwise + + \Version 09/24/2012 (akirchner) +*/ +int32_t TesterHostCoreStopSavingLog(TesterHostCoreT *pState) +{ + int32_t retVal = -1; + + if (pState->zFile != ZFILE_INVALID) + { + if(ZFileClose(pState->zFile) == 0) + { + pState->zFile = (ZFileT)ZFILE_INVALID; + retVal = 0; + } + } + + return(retVal); +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreShutdown + + \Description + Signal for a shutdown of all running tester modules + + \Input *pState - TesterHostCoreT module to shutdown + + \Output None + + \Version 04/13/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterHostCoreShutdown(TesterHostCoreT *pState) +{ + if(pState) + { + pState->iShutdown = 1; + } +} + + +/*F********************************************************************************/ +/*! + \Function TesterHostCoreDestroy + + \Description + Destroy a TesterHostCoreT module and all its children + + \Input *pState - TesterHostCoreT module to destroy + + \Output None + + \Version 03/21/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterHostCoreDestroy(TesterHostCoreT *pState) +{ + // check the state + if (pState == NULL) + { + return; + } + + // close log file + if (pState->zFile != ZFILE_INVALID) + { + ZFileClose(pState->zFile); + + if(ZFileClose(pState->zFile) == 0) + { + pState->zFile = (ZFileT)ZFILE_INVALID; + } + } + + // destroy history + TesterHistoryDestroy(pState->pHistory); + + // disconnect the children modules + TesterCommDisconnect(pState->pComm); + + // destoy all the children modules + TesterCommDestroy(pState->pComm); + TesterConsoleDestroy(pState->pConsole); + TesterModulesDestroy(pState->pModules); + + // unhook debug output + #if DIRTYCODE_LOGGING + NetPrintfHook(NULL, NULL); + #endif + + // unhook tester output + ZPrintfHook(NULL, NULL); + + // dump the registry + TesterRegistryDestroy(); + + // now destroy this module + ZMemFree((void *)pState); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerhostcore.h b/src/thirdparty/dirtysdk/sample/tester2/source/testerhostcore.h new file mode 100644 index 00000000..53ef15d0 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerhostcore.h @@ -0,0 +1,101 @@ +/*H********************************************************************************/ +/*! + \File testerhostcore.h + + \Description + Main control module for the Tester2 host application. + + \Notes + This is the main host module for the tester2 host application. + It contains mostly global-variable type objects and operations, similar + to LobbyAPI. TesterhostCore is responsible for starting up all the + necessary child modules like TesterConsole, TesterHostClientComm, etc. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/17/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ +#ifndef _testerhostcore_h +#define _testerhostcore_h + +/*** Include files ****************************************************************/ + +#include "libsample/zlib.h" +#include "testerprofile.h" +#include "testerconsole.h" + +/*** Defines **********************************************************************/ + +#define TESTERHOSTCORE_ERROR_NONE (0) //!< no error (success) +#define TESTERHOSTCORE_ERROR_NULLPOINTER (-1) //!< a null pointer ref was used + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// module state object +typedef struct TesterHostCoreT TesterHostCoreT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create a testerhostcore module +TesterHostCoreT *TesterHostCoreCreate(const char *pParams); + +// connect the core module +int32_t TesterHostCoreConnect(TesterHostCoreT *pState, const char *pNetParams); + +// idle function - pump this to make networking, etc. happy +void TesterHostCoreIdle(TesterHostCoreT *pState); + +// register function to flush output to the display +void TesterHostCoreDisplayFunc(TesterHostCoreT *pState, TesterConsoleDisplayCbT *pProc, int32_t iRefcon, void *pRefptr); + +// dispatch a command from an incoming command line - local version +int32_t TesterHostCoreDispatch(TesterHostCoreT *pCore, const char *pCommandLine); + +// get local history info +const char *TesterHostCoreGetHistory(TesterHostCoreT *pState, int32_t iPrevNext, char *pBuf, int32_t iSize); + +// update function - pump this to get data flowing, sending a pointer to the core module as data +void TesterHostCoreUpdate(void *pData, uint32_t uTick); + +// register a module +int32_t TesterHostCoreRegister(TesterHostCoreT *pState, const char *pCommand, ZCommand *pFunctionPtr); + +// start writting log messages in logfile +int32_t TesterHostCoreStartSavingLog(TesterHostCoreT *pState, const char * strLogfileName); + +// stop writting log messages in logfile +int32_t TesterHostCoreStopSavingLog(TesterHostCoreT *pState); + +// signal for a shutdown of all running tester modules +void TesterHostCoreShutdown(TesterHostCoreT *pState); + +// disconnect the core module +int32_t TesterHostCoreDisconnect(TesterHostCoreT *pState); + +// destroy a testerhostcore module +void TesterHostCoreDestroy(TesterHostCoreT *pState); + +// -------------------- COMMUNICATION FUNCTIONS ------------------ + +// send status back to the client +int32_t TesterHostCoreSendStatus(TesterHostCoreT *pState, const char *pData); + +// send console output back to the client +int32_t TesterHostCoreSendConsole(TesterHostCoreT *pState, const char *pData); + +#ifdef __cplusplus +}; +#endif + +#endif // _testerhostcore_h + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testermemory.c b/src/thirdparty/dirtysdk/sample/tester2/source/testermemory.c new file mode 100644 index 00000000..d70f9df2 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testermemory.c @@ -0,0 +1,125 @@ +/*H********************************************************************************/ +/*! + \File testermemory.c + + \Description + Implement dirtysock memory functions like DirtyMemAlloc and DirtyMemFree. + + \Version 04/01/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ +#include "DirtySDK/platform.h" +#include "testermemory.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "libsample/zmem.h" + + + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +static uint32_t _TesterMemory_bDebug = FALSE; + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterMemorySetDebug + + \Description + Enable/disable verbose memory debugging. + + \Input bDebug - TRUE=enable, FALSE=disable + + \Output + None. + + \Version 11/01/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void TesterMemorySetDebug(uint32_t bDebug) +{ + _TesterMemory_bDebug = bDebug; +} + + +/*F********************************************************************************/ +/*! + \Function DirtyMemAlloc + + \Description + Implementation of the required DirtySock memory allocation routine. + + \Input iSize - size of memory to allocate + \Input iMemModule - memory module id + \Input iMemGroup - memory group id + \Input *pMemGroupUserData - user data associated with mem group + + \Output + void * - pointer to newly allocated memory, or NULL + + \Version 11/01/2005 (jbrookes) + \Version 11/19/2008 (mclouatre) adding suppor for mem group user data +*/ +/********************************************************************************F*/ +#if !defined(DIRTYCODE_DLL) +void *DirtyMemAlloc(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void * pMemGroupUserData) +#else +void *DirtyMemAlloc2(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void * pMemGroupUserData) +#endif +{ + void *pMem = ZMemAlloc(iSize); + if (_TesterMemory_bDebug) + { + DirtyMemDebugAlloc(pMem, iSize, iMemModule, iMemGroup, pMemGroupUserData); + } + return(pMem); +} + +/*F********************************************************************************/ +/*! + \Function DirtyMemFree + + \Description + Implementation of the required DirtySock memory free routine. + + \Input *pMem - pointer to memory block to free + \Input iMemModule - memory module id + \Input iMemGroup - memory group id + \Input *pMemGroupUserData - user data associated with mem group + + \Output + None. + + \Version 11/01/2005 (jbrookes) + \Version 11/19/2008 (mclouatre) adding suppor for mem group user data +*/ +/********************************************************************************F*/ +#if !defined(DIRTYCODE_DLL) +void DirtyMemFree(void *pMem, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#else +void DirtyMemFree2(void *pMem, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#endif +{ + #if DIRTYCODE_DEBUG + uint32_t uSize = ZMemFree(pMem); + #else + ZMemFree(pMem); + #endif + + if (_TesterMemory_bDebug) + { + DirtyMemDebugFree(pMem, uSize, iMemModule, iMemGroup, pMemGroupUserData); + } +} + + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testermemory.h b/src/thirdparty/dirtysdk/sample/tester2/source/testermemory.h new file mode 100644 index 00000000..995190bc --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testermemory.h @@ -0,0 +1,42 @@ +/*H********************************************************************************/ +/*! + \File testermemory.h + + \Description + Tester memory management. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 11/01/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _testermemory_h +#define _testermemory_h + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// enable/disable verbose dirtysock memory debugging + void TesterMemorySetDebug(uint32_t bDebug); + +#ifdef __cplusplus +}; +#endif + +#endif // _testermemory_h + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testermodules.c b/src/thirdparty/dirtysdk/sample/tester2/source/testermodules.c new file mode 100644 index 00000000..2f0be6b2 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testermodules.c @@ -0,0 +1,370 @@ +/*H********************************************************************************/ +/*! + \File testermodules.c + + \Description + Common, platform independent tester modules. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/22/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "libsample/zmem.h" +#include "testercomm.h" +#include "testerregistry.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct TesterModulesCommandT +{ + char strName[TESTERMODULES_COMMANDNAMESIZE_DEFAULT]; //!< name of the command + ZCommand *pFunc; //!< function to call when dispatched +} TesterModulesCommandT; + +struct TesterModulesT +{ + TesterModulesCommandT Commands[TESTERMODULES_NUMCOMMANDS_DEFAULT]; //!< command list + int32_t iNumCommands; +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterModulesCreate + + \Description + Create a tester host client communication module. + + \Input None + + \Output TesterModulesT * - pointer to allocated module + + \Version 04/01/2005 (jfrank) +*/ +/********************************************************************************F*/ +TesterModulesT *TesterModulesCreate(void) +{ + TesterModulesT *pState; + + pState = ZMemAlloc(sizeof(TesterModulesT)); + ds_memclr(pState, sizeof(TesterModulesT)); + TesterRegistrySetPointer("MODULES", pState); + + return(pState); +} + +/*F********************************************************************************/ +/*! + \Function TesterModulesRegister + + \Description + Register a particular command with a function call pointer. + + \Input *pState - pointer to host client comm module + \Input *pCommand - null-terminated string to register the function call with + \Input *pFunction - function call to associate with the string + + \Output int32_t - 0=success, error code otherwise + + \Version 04/01/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterModulesRegister(TesterModulesT *pState, const char *pCommand, ZCommand *pFunctionPtr) +{ + TesterModulesCommandT *pCmd = NULL; + uint32_t uCommand; + int32_t iStrcmp; + + // check for error conditions + if ((pState == NULL) || (pCommand == NULL) || (pFunctionPtr == NULL)) + { + return(TESTERMODULES_ERROR_NULLPOINTER); + } + + // make sure there's room + if (pState->iNumCommands == TESTERMODULES_NUMCOMMANDS_DEFAULT) + { + return(TESTERMODULES_ERROR_COMMANDLISTFULL); + } + + // find where to insert the command + for (uCommand = 0; uCommand < TESTERMODULES_NUMCOMMANDS_DEFAULT; uCommand++) + { + // ref the command + pCmd = &pState->Commands[uCommand]; + + // empty slot? + if (pCmd->pFunc == NULL) + { + break; + } + + // compare the commands + iStrcmp = strcmp(pCommand, pCmd->strName); + + // see if we've already registered this command + if (iStrcmp == 0) + { + if (pCmd->pFunc == pFunctionPtr) + { + NetPrintf(("testermodules: warning -- benign redefinition of command %s\n", pCommand)); + return(TESTERMODULES_ERROR_NONE); + } + else + { + NetPrintf(("testermodules: error -- redefinition of command %s\n", pCommand)); + return(TESTERMODULES_ERROR_REDEFINITON); + } + } + // check to see if we should insert to preserve alphabetical order + else if (iStrcmp < 0) + { + // create a space for the command + memmove(pCmd+1, pCmd, (pState->iNumCommands-uCommand) * sizeof(*pCmd)); + break; + } + } + + // register the command + ds_strnzcpy(pCmd->strName, pCommand, sizeof(pCmd->strName)); + pCmd->pFunc = pFunctionPtr; + pState->iNumCommands += 1; + + return(TESTERMODULES_ERROR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function TesterModulesGetCommandName + + \Description + Get a command name in the list at a particular index. + + \Input *pState - pointer to host client comm module + \Input iIndex - index of command name to retrieve + \Input *pBuf - destination string for the command + \Input iBufSize - size of the destination string + + \Output int32_t - 0=success, error code otherwise + + \Version 04/01/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterModulesGetCommandName(TesterModulesT *pState, int32_t iCommandNum, char *pBuf, int32_t iBufSize) +{ + TesterModulesCommandT *pCmd; + + // check for error conditions + if ((pState == NULL) || (pBuf == NULL) || (iBufSize <= 0)) + { + return(TESTERMODULES_ERROR_NULLPOINTER); + } + + // check to see if we're even in range + if ((iCommandNum < 0) || (iCommandNum >= TESTERMODULES_NUMCOMMANDS_DEFAULT)) + { + return(TESTERMODULES_ERROR_NOSUCHCOMMAND); + } + + // now get the entry we want, if it's non-null + pCmd = &(pState->Commands[iCommandNum]); + if (pCmd->strName[0] != 0) + { + ds_strnzcpy(pBuf, pCmd->strName, iBufSize); + } + + return(TESTERMODULES_ERROR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function TesterModulesDispatch + + \Description + Dispatch a function (if possible) based on the incoming command line. + + \Input *pState - pointer to host client comm module + \Input *pCommandLine - standard command line (command arg1 arg2 arg3 ...) + + \Output 0=success, error code otherwise + + \Version 04/01/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterModulesDispatch(TesterModulesT *pState, const char *pCommandLine) +{ + char strCommand[256]; + TesterModulesCommandT *pCmd = NULL; + uint32_t uLoop; + char *pCommand; + ZEnviron *pEnv = NULL; + + // helper variables + char *pListName; + uint32_t uListNameSize, uCommandNameSize; + + // check for error conditions + if ((pState == NULL) || (pCommandLine == NULL)) + { + return(TESTERMODULES_ERROR_NULLPOINTER); + } + + // find start of strCommand name + for (pCommand = (char *)pCommandLine; (*pCommand != 0) && (*pCommand <= ' '); pCommand += 1) + ; + + // copy over strCommand and null-terminate + ds_strnzcpy(strCommand, pCommand, sizeof(strCommand)); + + // terminate command name + for (pCommand = strCommand; (*pCommand != 0) && (*pCommand > ' '); pCommand += 1) + ; + *pCommand = 0; + uCommandNameSize = (uint32_t)strlen(strCommand); + + // locate corresponding strCommand + for (uLoop = 0; uLoop < TESTERMODULES_NUMCOMMANDS_DEFAULT; uLoop++) + { + // check to see if we match and we want to re-register a function + // or if we're at the end of the list + pListName = pState->Commands[uLoop].strName; + uListNameSize = (uint32_t)strlen(pListName); + if ((uListNameSize > 0) && (uListNameSize == uCommandNameSize) && (strncmp(strCommand, pListName, uCommandNameSize) == 0)) + { + pCmd = &(pState->Commands[uLoop]); + pEnv = ZCreate(NULL, pCommandLine); + ZInvoke(pEnv, pCmd->pFunc); + break; + } + } + + // show error if no command. or problem with env. + if (((pCmd == NULL) && (strCommand[0] != 0)) || (pEnv == NULL)) + { + return(TESTERMODULES_ERROR_NOSUCHCOMMAND); + } + else //give the status of the actual command.. + { + return(ZGetStatus(pEnv)); + } +} + +/*F********************************************************************************/ +/*! + \Function TesterModulesHelp + + \Description + Display help for a command. Pass NULL to get help for all commands. + + \Input *pState - pointer to host client comm module + \Input *pCommandLine - standard command line (command arg1 arg2 arg3 ...) + + \Output None + + \Version 04/01/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterModulesHelp(TesterModulesT *pState, const char *pCommand) +{ + int32_t iLoop, iFoundHelp=0; + + // check error conditions + if(pState == NULL) + { + return; + } + + // if NULL, list all commands (do not show help for them!) + if(pCommand == NULL) + { + char strLine[128] = ""; + + ZPrintf("tester2 modules\n"); + + // create list of modules + for(iLoop = 0; iLoop < TESTERMODULES_NUMCOMMANDS_DEFAULT; iLoop++) + { + if(pState->Commands[iLoop].strName[0] != '\0') + { + ds_strnzcat(strLine, pState->Commands[iLoop].strName, sizeof(strLine)); + if (strlen(strLine) > 79) + { + ZPrintf("%s\n", strLine); + strLine[0] = '\0'; + } + else + { + ds_strnzcat(strLine, " ", sizeof(strLine)); + } + } + } + + // print last entry? + if (strLine[0] != '\0') + { + ZPrintf("%s\n", strLine); + } + } + // otherwise get help on a specific command + else + { + for(iLoop = 0; iLoop < TESTERMODULES_NUMCOMMANDS_DEFAULT; iLoop++) + { + if(strncmp(pCommand, pState->Commands[iLoop].strName, strlen(pCommand)) == 0) + { + char *pBuf[1]; + pBuf[0] = (pState->Commands[iLoop]).strName; + pState->Commands[iLoop].pFunc(NULL, 0, pBuf); + iFoundHelp = 1; + break; + } + } + + if(iFoundHelp == 0) + { + ZPrintf("ERROR: Could not find help for {%s}\n",pCommand); + } + } +} + +/*F********************************************************************************/ +/*! + \Function TesterModulesDestroy + + \Description + Destroy a tester host client communication module. + + \Input *pState - pointer to host client comm module + + \Output None + + \Version 04/01/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterModulesDestroy(TesterModulesT *pState) +{ + if(pState) + { + ZMemFree(pState); + } + TesterRegistrySetPointer("MODULES", NULL); +} + + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testermodules.h b/src/thirdparty/dirtysdk/sample/tester2/source/testermodules.h new file mode 100644 index 00000000..8bc6ef3a --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testermodules.h @@ -0,0 +1,143 @@ +/*H********************************************************************************/ +/*! + \File testermodules.h + + \Description + Prototypes for tester modules, to be included in platform-specific + implementations of the tester2 host application. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/22/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +#ifndef _testermodules_h +#define _testermodules_h + +/*** Include files ****************************************************************/ + +#include "libsample/zlib.h" + +/*** Defines **********************************************************************/ + +#define TESTERMODULES_COMMANDNAMESIZE_DEFAULT (16) //!< max command name length +#define TESTERMODULES_NUMCOMMANDS_DEFAULT (64) //!< max number of commands + +#define TESTERMODULES_ERROR_NONE (0) //!< no error +#define TESTERMODULES_ERROR_NULLPOINTER (-1) //!< invalid pointer used +#define TESTERMODULES_ERROR_COMMANDLISTFULL (-2) //!< command list full - cannot register another command +#define TESTERMODULES_ERROR_NOSUCHCOMMAND (-3) //!< no such command in the list +#define TESTERMODULES_ERROR_NODIRTYSOCK (-4) //!< dirtysock isn't started +#define TESTERMODULES_ERROR_REDEFINITON (-5) //!< attempt to redefine a command + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// module state +typedef struct TesterModulesT TesterModulesT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create a testermodules module +TesterModulesT *TesterModulesCreate(void); + +// register a module +int32_t TesterModulesRegister(TesterModulesT *pState, const char *pCommand, ZCommand *pFunctionPtr); + +// dispatch a command from an incoming command line +int32_t TesterModulesDispatch(TesterModulesT *pState, const char *pCommandLine); + +// get help on a command, or NULL for help on all commands +void TesterModulesHelp(TesterModulesT *pState, const char *pCommand); + +// return a specific registered module's command line name +int32_t TesterModulesGetCommandName(TesterModulesT *pState, int32_t iCommandNum, char *pBuf, int32_t iBufSize); + +// destroy +void TesterModulesDestroy(TesterModulesT *pState); + +// ---------------------------- CLIENT SPECIFIC ------------------------------ + +// modules registered in this function are available to clients on all platforms +int32_t TesterModulesRegisterClientCommands(TesterModulesT *pState); + +// ---------------------------- HOST SPECIFIC -------------------------------- + +// modules registered in this function are available to hosts on all platforms +int32_t TesterModulesRegisterHostCommands(TesterModulesT *pState); + +// ---------------------------- PLATFORM SPECIFIC ---------------------------- + +/* each platform will need to implement a version of this function to + handle registering any platform-specific functions it will want */ +int32_t TesterModulesRegisterPlatformCommands(TesterModulesT *pState); + +// ---------------------------- TESTER MODULES ------------------------------- + +// tester2 built-ins +int32_t CmdExit(ZContext *argz, int32_t argc, char **argv); +int32_t CmdHelp(ZContext *argz, int32_t argc, char **argv); +int32_t CmdHistory(ZContext *argz, int32_t argc, char **argv); +int32_t CmdMemDebug(ZContext *argz, int32_t argc, char **argv); +int32_t CmdRegistry(ZContext *argz, int32_t argc, char **argv); +int32_t CmdRunScript(ZContext *argz, int32_t argc, char **argv); +int32_t CmdSleep(ZContext *argz, int32_t argc, char **argv); +int32_t CmdSource(ZContext *argz, int32_t argc, char **argv); + +// tester2 modules +int32_t CmdAdvert(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdBase64(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdCrypt(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdDemangler(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdDriver(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdGameLink(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdHpack(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdHttp(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdHttp2(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdHttpMgr(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdHttpServ(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdImgConv(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdJson(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdLang(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdMbTest(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdNet(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdNetPrint(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdNetPrint(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdQos(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdSecure(ZContext *argz, int32_t argc, char *argv[]); +#if defined(DIRTYCODE_PS4) +int32_t CmdSession(ZContext *argz, int32_t argc, char *argv[]); +#endif +int32_t CmdSocket(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdStream(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdString(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdTime(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdTunnel(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdUpnp(ZContext *argz, int32_t argc, char *argv[]); +#if defined(DIRTYCODE_PS4) || defined (DIRTYCODE_XBOXONE) || defined(DIRTYCODE_STADIA) +int32_t CmdUser(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdUserList(ZContext *argz, int32_t argc, char *argv[]); +#endif +#if defined(DIRTYCODE_PS4) || defined (DIRTYCODE_XBOXONE) || defined(DIRTYCODE_STADIA) +int32_t CmdPriv(ZContext *argz, int32_t argc, char *argv[]); +#endif +int32_t CmdUtf8(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdVoice(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdWS(ZContext *argz, int32_t argc, char *argv[]); +int32_t CmdXml(ZContext *argz, int32_t argc, char *argv[]); + +#ifdef __cplusplus +}; +#endif + +#endif // _testermodules_h + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testermodulesclient.c b/src/thirdparty/dirtysdk/sample/tester2/source/testermodulesclient.c new file mode 100644 index 00000000..fa6c169e --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testermodulesclient.c @@ -0,0 +1,55 @@ +/*H********************************************************************************/ +/*! + \File testermodulesclient.c + + \Description + PC specific module startup. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/11/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function TesterModulesRegisterHostCommands + + \Description + Register all PC-specific modules + + \Input *pState - module state + + \Output 0=success, error code otherwise + + \Version 04/11/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterModulesRegisterClientCommands(TesterModulesT *pState) +{ + TesterModulesRegister(pState, "run", &CmdRunScript); + TesterModulesRegister(pState, "runscript", &CmdRunScript); + TesterModulesRegister(pState, "history", &CmdHistory); + TesterModulesRegister(pState, "!", &CmdHistory); + TesterModulesRegister(pState, "reg", &CmdRegistry); + TesterModulesRegister(pState, "registry", &CmdRegistry); + TesterModulesRegister(pState, "sleep", &CmdSleep); + return(TESTERMODULES_ERROR_NONE); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testermoduleshostcommon.c b/src/thirdparty/dirtysdk/sample/tester2/source/testermoduleshostcommon.c new file mode 100644 index 00000000..69b6718c --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testermoduleshostcommon.c @@ -0,0 +1,99 @@ +/*H********************************************************************************/ +/*! + \File testermoduleshost.c + + \Description + PC specific module startup. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/11/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function TesterModulesRegisterHostCommands + + \Description + Register all PC-specific modules + + \Input *pState - module state + + \Output 0=success, error code otherwise + + \Version 04/11/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterModulesRegisterHostCommands(TesterModulesT *pState) +{ + // tester2 built-ins + TesterModulesRegister(pState, "help", &CmdHelp); + TesterModulesRegister(pState, "history", &CmdHistory); + TesterModulesRegister(pState, "!", &CmdHistory); + TesterModulesRegister(pState, "memdebug", &CmdMemDebug); + TesterModulesRegister(pState, "registry", &CmdRegistry); + TesterModulesRegister(pState, "source", &CmdSource); + + // common tester2 modules + TesterModulesRegister(pState, "base64", &CmdBase64); + TesterModulesRegister(pState, "crypt", &CmdCrypt); + TesterModulesRegister(pState, "gamelink", &CmdGameLink); + TesterModulesRegister(pState, "hpack", &CmdHpack); + TesterModulesRegister(pState, "http", &CmdHttp); + TesterModulesRegister(pState, "http2", &CmdHttp2); + TesterModulesRegister(pState, "httpmgr", &CmdHttpMgr); + TesterModulesRegister(pState, "httpserv", &CmdHttpServ); +#if (defined(DIRTYCODE_PC)) + TesterModulesRegister(pState, "ic", &CmdImgConv); +#endif + TesterModulesRegister(pState, "json", &CmdJson); + TesterModulesRegister(pState, "lang", &CmdLang); + TesterModulesRegister(pState, "net", &CmdNet); + TesterModulesRegister(pState, "netprint", &CmdNetPrint); +#if defined(DIRTYCODE_PS4) && !defined(DIRTYCODE_PS5) + TesterModulesRegister(pState, "session", &CmdSession); +#endif + TesterModulesRegister(pState, "socket", &CmdSocket); +#if defined(DIRTYCODE_PC) + TesterModulesRegister(pState, "stream", &CmdStream); +#endif + TesterModulesRegister(pState, "string", &CmdString); + TesterModulesRegister(pState, "time", &CmdTime); + TesterModulesRegister(pState, "tunnel", &CmdTunnel); +#if (defined(DIRTYCODE_PS4) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_STADIA)) && !defined(DIRTYCODE_PS5) + TesterModulesRegister(pState, "user", &CmdUser); + TesterModulesRegister(pState, "userlist", &CmdUserList); +#endif +#if (defined(DIRTYCODE_PS4) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_STADIA)) && !defined(DIRTYCODE_PS5) + TesterModulesRegister(pState, "priv", &CmdPriv); +#endif + TesterModulesRegister(pState, "utf8", &CmdUtf8); + TesterModulesRegister(pState, "ws", &CmdWS); + TesterModulesRegister(pState, "xml", &CmdXml); + TesterModulesRegister(pState, "qos", &CmdQos); + + // tester2 modules for non-Xbox platforms +#if !defined(DIRTYCODE_XBOXONE) + TesterModulesRegister(pState, "demangler", &CmdDemangler); + TesterModulesRegister(pState, "upnp", &CmdUpnp); +#endif + + return(TESTERMODULES_ERROR_NONE); +} diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerprofile.c b/src/thirdparty/dirtysdk/sample/tester2/source/testerprofile.c new file mode 100644 index 00000000..e4e18d21 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerprofile.c @@ -0,0 +1,706 @@ +/*H********************************************************************************/ +/*! + \File testerprofile.c + + \Description + Maintain user profiles for the tester application. + + \Notes + A profile is selected, created, or deleted after client GUI start + and before anything else happens. Currently, the following items + will be stored in a tester2 profile: command history, network startup, + and lobby parameters. Profiles will be stored in a single file on the + clients disk, the elements of the profile encoded into tagfields. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/18/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/util/jsonformat.h" +#include "DirtySDK/util/jsonparse.h" +#include "libsample/zmem.h" +#include "libsample/zlib.h" +#include "libsample/zfile.h" +#include "testerprofile.h" +#include "testerregistry.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// module state +struct TesterProfileT +{ + TesterProfileEntryT Profiles[TESTERPROFILE_NUMPROFILES_DEFAULT]; //!< profile entry list + uint32_t uProfileTail; //!< tail index for the profile list + char strFilename[TESTERPROFILE_PROFILEFILENAME_SIZEDEFAULT]; //!< filename for the profile file +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _TesterProfileGenerateHistoryFilename + + \Description + Use an entry's name to generate a history file name + + \Input *pState - module state + + \Output int32_t - error codes, 0 if success + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterProfileGenerateHistoryFilename(TesterProfileEntryT *pEntry) +{ + int32_t iNumChars, iChecksum; + const char strFileSuffix[] = ".history.txt"; + char strChecksum[16]; + char cReplace; + + + // check for errors + if(pEntry == NULL) + return; + + // create the history filename + iChecksum=0; + ds_memclr(strChecksum, sizeof(strChecksum)); + ds_memclr(pEntry->strHistoryFile, sizeof(pEntry->strHistoryFile)); + for(iNumChars = 0; + (iNumChars < (signed)sizeof(pEntry->strProfileName)) && (pEntry->strProfileName[iNumChars] != 0); + iNumChars++) + { + cReplace = pEntry->strProfileName[iNumChars]; + iChecksum += (int32_t)cReplace; + if( ((cReplace >= '0') && (cReplace <= '9')) || + ((cReplace >= 'a') && (cReplace <= 'z')) || + ((cReplace >= 'A') && (cReplace <= 'Z')) || + ((cReplace >= '0') && (cReplace <= '9')) ) + { + // character OK + } + else + { + cReplace = '_'; + } + pEntry->strHistoryFile[iNumChars] = cReplace; + } + sprintf(strChecksum, "%d", iChecksum); + strncat(pEntry->strHistoryFile, strChecksum, sizeof(pEntry->strHistoryFile) - strlen(pEntry->strHistoryFile) - 1); + strncat(pEntry->strHistoryFile, strFileSuffix, sizeof(pEntry->strHistoryFile) - strlen(pEntry->strHistoryFile) - 1); +} + + +/*F********************************************************************************/ +/*! + \Function _TesterProfileKillAllEntries + + \Description + Delete all the profile entries in the profile list + + \Input *pState - module state to delete all the entries from + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterProfileKillAllEntries(TesterProfileT *pState) +{ + pState->uProfileTail = 0; + ds_memclr(pState->Profiles, sizeof(pState->Profiles)); +} + +/*F********************************************************************************/ +/*! + \Function _TesterProfileParseLine + + \Description + Take an incoming tagfield and create a profile structure from it + + \Input *pData - incoming data buffer + \Input *pEntry - profile entry structure to put the data into + + \Output None + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterProfileParseLine(char *pData, TesterProfileEntryT *pEntry) +{ + uint16_t aJson[512]; + + // check error conditions + if((pData == NULL) || (pEntry == NULL)) + return; + + // wipe the supplied structure + ds_memclr(pEntry, sizeof(*pEntry)); + + JsonParse(aJson, sizeof(aJson)/sizeof(*aJson), pData, -1); + + // populate with data + JsonGetString(JsonFind(aJson, TESTERPROFILE_PROFILENAME_TAGVALUE), pEntry->strProfileName, sizeof(pEntry->strProfileName), TESTERPROFILE_PROFILENAME_VALUEDEFAULT); + JsonGetString(JsonFind(aJson, TESTERPROFILE_CONTROLDIR_TAGVALUE), pEntry->strControlDirectory, sizeof(pEntry->strControlDirectory), TESTERPROFILE_CONTROLDIR_VALUEDEFAULT); + JsonGetString(JsonFind(aJson, TESTERPROFILE_COMMANDLINE_TAGVALUE), pEntry->strCommandLine, sizeof(pEntry->strCommandLine), TESTERPROFILE_COMMANDLINE_VALUEDEFAULT); + JsonGetString(JsonFind(aJson, TESTERPROFILE_LOGDIRECTORY_TAGVALUE), pEntry->strLogDirectory, sizeof(pEntry->strLogDirectory), TESTERPROFILE_LOGDIRECTORY_VALUEDEFAULT); + JsonGetString(JsonFind(aJson, TESTERPROFILE_HOSTNAME_TAGVALUE), pEntry->strHostname, sizeof(pEntry->strHostname), TESTERPROFILE_HOSTNAME_VALUEDEFAULT); + JsonGetString(JsonFind(aJson, TESTERPROFILE_HISTORYFILE_TAGVALUE), pEntry->strHistoryFile, sizeof(pEntry->strHistoryFile), TESTERPROFILE_HISTORYFILE_VALUEDEFAULT); + pEntry->uLogEnable = (uint8_t)JsonGetInteger(JsonFind(aJson, TESTERPROFILE_LOGENABLE_TAGVALUE), TESTERPROFILE_LOGENABLE_VALUEDEFAULT); + pEntry->uNetworkStartup = (uint8_t)JsonGetInteger(JsonFind(aJson, TESTERPROFILE_NETWORKSTART_TAGVALUE), TESTERPROFILE_NETWORKSTART_VALUEDEFAULT); + pEntry->uDefault = (uint8_t)JsonGetInteger(JsonFind(aJson, TESTERPROFILE_DEFAULTPROFILE_TAGVALUE), 0); + + // create a history file name + _TesterProfileGenerateHistoryFilename(pEntry); +} + +/*F********************************************************************************/ +/*! + \Function _TesterProfileParseFile + + \Description + Take incoming data (from a file) and parse into profile structures + + \Notes + This function is destructive and will modify the contents at pData. + + \Input *pState - module state + \Input *pData - incoming data buffer + \Input iSize - amount of data in the buffer + + \Output None + + \Version 03/18/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _TesterProfileParseFile(TesterProfileT *pState, char *pData) +{ + const char strSep[] = {"\r\n"}; + char *pDataPtr; + + // wipe out the previous list in case it exists + _TesterProfileKillAllEntries(pState); + + // walk the pData buffer and parse each incoming line + pDataPtr = (char *)strtok(pData, strSep); + while(pDataPtr) + { + // get an entry based on the line + _TesterProfileParseLine(pDataPtr, &pState->Profiles[pState->uProfileTail]); + pDataPtr = (char *)strtok(NULL, strSep); + pState->uProfileTail++; + } +} + + +/*F********************************************************************************/ +/*! + \Function _TesterProfileReadFile + + \Description + Read a file containing profiles and store it in the profile list. + + \Input *pState - module state + + \Output int32_t - error codes, 0 if success + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _TesterProfileReadFile(TesterProfileT *pState) +{ + char *pProfileString; + int32_t iFileSize; + + // load the file in + pProfileString = ZFileLoad(pState->strFilename, &iFileSize, 0); + if(pProfileString == NULL) + { + ZPrintf("testerprofile: TesterProfileConnect error opening file [%s]\n", pState->strFilename); + return(TESTERPROFILE_ERROR_FILEOPEN); + } + + // parse the file out + _TesterProfileParseFile(pState, pProfileString); + + // don't forget to dump the memory when done + ZMemFree(pProfileString); + + // quit + return(TESTERPROFILE_ERROR_NONE); +} + + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function TesterProfileCreate + + \Description + Create a tester profile manager. + + \Input None + + \Output TesterProfileT * - allocated module state pointer + + \Version 03/18/2005 (jfrank) +*/ +/********************************************************************************F*/ +TesterProfileT *TesterProfileCreate(void) +{ + TesterProfileT *pState = (TesterProfileT *)ZMemAlloc(sizeof(TesterProfileT)); + ds_memclr(pState, sizeof(TesterProfileT)); + TesterRegistrySetPointer("PROFILE", pState); + return(pState); +} + + +/*F********************************************************************************/ +/*! + \Function TesterProfileConnect + + \Description + Connect the profile manager to a set of profiles in a file. + + \Input *pState - module state + \Input *pFilename - filename to try to read + + \Output int32_t - error codes, 0 if success + + \Version 03/18/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterProfileConnect(TesterProfileT *pState, const char *pFilename) +{ + + // check for error conditions + if (pState == NULL) + return(TESTERPROFILE_ERROR_NULLPOINTER); + if (pFilename == NULL) + return(TESTERPROFILE_ERROR_INVALIDFILENAME); + if (pState->strFilename[0] != 0) + return(TESTERPROFILE_ERROR_FILEALREADYOPEN); + + // copy the filename in + ds_strnzcpy(pState->strFilename, pFilename, sizeof(pState->strFilename)); + + // read the contents of the requested file + _TesterProfileReadFile(pState); + + // done + return(TESTERPROFILE_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterProfileAdd + + \Description + Add a profile to the list + + \Input *pState - module state + \Input *pEntry - profile entry to add + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/18/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterProfileAdd(TesterProfileT *pState, TesterProfileEntryT *pEntry) +{ + TesterProfileEntryT *pTarget = NULL; + uint32_t uLoop, uNameLen, uStoredNameLen; + char *pStoredProfileName; + + // check for errors + if((pState == NULL) || (pEntry == NULL)) + { + return(TESTERPROFILE_ERROR_NULLPOINTER); + } + + // make sure we have a valid history filename + _TesterProfileGenerateHistoryFilename(pEntry); + + // walk the list and look for a place to put it + // watch for a matching profile name as well + uNameLen = (uint32_t)strlen(pEntry->strProfileName); + for(uLoop = 0; (uLoop < pState->uProfileTail) && (pTarget == NULL); uLoop++) + { + pStoredProfileName = pState->Profiles[uLoop].strProfileName; + uStoredNameLen = (uint32_t)strlen(pStoredProfileName); + // if it matches, we have a target + if((uStoredNameLen == uNameLen) && + (strcmp(pStoredProfileName, pEntry->strProfileName) == 0)) + { + pTarget = &pState->Profiles[uLoop]; + } + } + + // if we don't have a match, see if we can make some room at the end + if(pTarget == NULL) + { + // no match - see if we have room + if(pState->uProfileTail < TESTERPROFILE_NUMPROFILES_DEFAULT) + { + pTarget = &pState->Profiles[pState->uProfileTail]; + pState->uProfileTail++; + } + } + + // copy all the parameters in if we found a spot + if(pTarget != NULL) + { + ds_memcpy(pTarget, pEntry, sizeof(TesterProfileEntryT)); + } + + return(TESTERPROFILE_ERROR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function TesterProfileDelete + + \Description + Remove a profile from the list + + \Input *pState - module state + \Input *pName - profile to nuke + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/18/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterProfileDelete(TesterProfileT *pState, const char *pName) +{ + uint32_t uLoop, uNameLen, uStoredNameLen; + char *pStoredProfileName; + + // check for errors + if((pState == NULL) || (pName == NULL)) + return(TESTERPROFILE_ERROR_NULLPOINTER); + + // nuke the entry in question + uNameLen = (uint32_t)strlen(pName); + for(uLoop = 0; uLoop < pState->uProfileTail; uLoop++) + { + // if it matches, we have a target + pStoredProfileName = (pState->Profiles[uLoop]).strProfileName; + uStoredNameLen = (uint32_t)strlen(pStoredProfileName); + if((uStoredNameLen == uNameLen) && + (strcmp(pStoredProfileName, pName) == 0)) + { + // nuke the entry so we won't save it + ds_memclr(&pState->Profiles[uLoop],sizeof(TesterProfileEntryT)); + } + } + + // now force a save (save won't dump a NULL entry) + TesterProfileSave(pState); + // and re-read the file + _TesterProfileReadFile(pState); + + return(TESTERPROFILE_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterProfileSetDefaultName + + \Description + Set a particular profile as the default profile + + \Input *pState - module state + \Input *pProfileName - profile to set as default + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/28/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterProfileSetDefaultName(TesterProfileT *pState, const char *pProfileName) +{ + uint32_t uStoredNameLen, uNameLen; + char *pStoredProfileName; + uint32_t uLoop; + + // check for errors first + if((pState == NULL) || (pProfileName == NULL)) + return(TESTERPROFILE_ERROR_NULLPOINTER); + + uNameLen = (uint32_t)strlen(pProfileName); + for(uLoop = 0; uLoop < TESTERPROFILE_NUMPROFILES_DEFAULT; uLoop++) + { + pState->Profiles[uLoop].uDefault = 0; + pStoredProfileName = pState->Profiles[uLoop].strProfileName; + uStoredNameLen = (uint32_t)strlen(pStoredProfileName); + if((uNameLen == uStoredNameLen) && + (strcmp(pProfileName, pStoredProfileName) == 0)) + { + pState->Profiles[uLoop].uDefault = 1; + } + } + + // no error + return(TESTERPROFILE_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterProfileGetDefaultIndex + + \Description + Return the index of the default profile + + \Input *pState - module state + + \Output int32_t - index of default profile, 0 if no default + + \Version 03/28/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterProfileGetDefaultIndex(TesterProfileT *pState) +{ + uint32_t uLoop; + int32_t iIndex = TESTERPROFILE_ERROR_NOSUCHENTRY; + + if(pState == NULL) + return(TESTERPROFILE_ERROR_NULLPOINTER); + + // loop while less than total number and not a default entry + for(uLoop = 0; uLoop < TESTERPROFILE_NUMPROFILES_DEFAULT; uLoop++) + { + if(pState->Profiles[uLoop].uDefault != 0) + iIndex = uLoop; + } + return(iIndex); +} + + +/*F********************************************************************************/ +/*! + \Function TesterProfileSave + + \Description + Save all profiles to disk. + + \Notes + Called automatically on disconnect - no need to call this function + unless special functionality is desired. Will not save NULL entries. + + \Input *pState - module state + + \Output int32_t - 0 for success, error code otherwise + + \Version 03/18/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterProfileSave(TesterProfileT *pState) +{ + char strProfile[1024]; + ZFileT iFilePointer; + int32_t iEntryNum; + + // open the file for create (no append) + iFilePointer = ZFileOpen(pState->strFilename, ZFILE_OPENFLAG_CREATE | ZFILE_OPENFLAG_WRONLY); + if (iFilePointer < 0) + { + return(TESTERPROFILE_ERROR_FILEOPEN); + } + + // loop through all entries and write the valid ones out + for (iEntryNum = 0; iEntryNum < TESTERPROFILE_NUMPROFILES_DEFAULT; iEntryNum++) + { + // will return an error if the entry is NULL or nuked + if (TesterProfileGetJson(pState, iEntryNum, strProfile, sizeof(strProfile)) >= 0) + { + // write it to the file + ZFileWrite(iFilePointer, (void *)strProfile, (int32_t)strlen(strProfile)); + } + } + + // close the file + ZFileClose(iFilePointer); + + // done + return(TESTERPROFILE_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterProfileGet + + \Description + Return a specific profile index into a tagfield string buffer. + + \Input *pState - module state + \Input iIndex - 0-based index of the profile to get + \Input *pDest - destination buffer + + \Output int32_t - index number of retreived entry (>=0) , error code otherwise + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterProfileGet(TesterProfileT *pState, int32_t iIndex, TesterProfileEntryT *pDest) +{ + TesterProfileEntryT *pEntry; + + // see if we want the default entry + if (iIndex == -1) + iIndex = TesterProfileGetDefaultIndex(pState); + + // check for error conditions + if ((pState == NULL) || (pDest == NULL)) + return(TESTERPROFILE_ERROR_NULLPOINTER); + else if (iIndex < -1) + return(TESTERPROFILE_ERROR_NOSUCHENTRY); + + // set the entry pointer + pEntry = &pState->Profiles[iIndex]; + + // if the entry has been nuked, don't do anything + if(pEntry->strProfileName[0] == 0) + return(TESTERPROFILE_ERROR_NOSUCHENTRY); + + // copy the data in + ds_memcpy(pDest, pEntry, sizeof(TesterProfileEntryT)); + + // done + return(iIndex); +} + + +/*F********************************************************************************/ +/*! + \Function TesterProfileGetJson + + \Description + Return a specific profile index into a json string buffer. + + \Input *pState - module state + \Input iIndex - 0-based index of the profile to get + \Input *pDest - destination buffer + \Input iSize - size of the destination buffer + + \Output int32_t - index number of retreived entry (>=0) , error code otherwise + + \Version 03/29/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterProfileGetJson(TesterProfileT *pState, int32_t iIndex, char *pDest, int32_t iSize) +{ + TesterProfileEntryT Entry; + int32_t iResult; + + if (pState == NULL) + return(TESTERPROFILE_ERROR_NULLPOINTER); + + iResult = TesterProfileGet(pState, iIndex, &Entry); + if (iResult < 0) + return(iResult); + + // otherwise dump the json into the buffer + JsonInit(pDest, iSize, 0); + JsonAddStr(pDest, TESTERPROFILE_PROFILENAME_TAGVALUE, Entry.strProfileName); + JsonAddStr(pDest, TESTERPROFILE_HOSTNAME_TAGVALUE, Entry.strHostname); + JsonAddStr(pDest, TESTERPROFILE_CONTROLDIR_TAGVALUE, Entry.strControlDirectory); + JsonAddStr(pDest, TESTERPROFILE_COMMANDLINE_TAGVALUE, Entry.strCommandLine); + JsonAddStr(pDest, TESTERPROFILE_LOGDIRECTORY_TAGVALUE, Entry.strLogDirectory); + JsonAddStr(pDest, TESTERPROFILE_HISTORYFILE_TAGVALUE, Entry.strHistoryFile); + JsonAddInt(pDest, TESTERPROFILE_LOGENABLE_TAGVALUE, Entry.uLogEnable); + JsonAddInt(pDest, TESTERPROFILE_NETWORKSTART_TAGVALUE, Entry.uNetworkStartup); + JsonAddInt(pDest, TESTERPROFILE_DEFAULTPROFILE_TAGVALUE, Entry.uDefault); + pDest = JsonFinish(pDest); + + // done + return(iIndex); +} + + +/*F********************************************************************************/ +/*! + \Function TesterProfileDisconnect + + \Description + Disconnect from a set of profiles and save the current profiles. + + \Input *pState - module state + + \Output int32_t - result of trying to save the profiles (TesterProfileSave()) + + \Version 03/18/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterProfileDisconnect(TesterProfileT *pState) +{ + int32_t iResult; + + // check for valid state - not really an error + if (pState == NULL) + return(TESTERPROFILE_ERROR_NONE); + // check to see if we're already disconnected + if (pState->strFilename[0] == 0) + return(TESTERPROFILE_ERROR_NONE); + + // make a best effort at saving the file + iResult = TesterProfileSave(pState); + if (iResult) + { + ZPrintf("testerprofile: TesterProfileDisconnect: error saving file [%s]\n", + pState->strFilename); + } + + // kill all the entries in the profile list + _TesterProfileKillAllEntries(pState); + + // wipe out the filename to signify we've disconnected + ds_memclr(pState->strFilename, sizeof(pState->strFilename)); + + // return the fclose error code + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function TesterProfileDestroy + + \Description + Destroy and allocated tester profile object. + + \Input *pState - module state + + \Output None + + \Version 03/18/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterProfileDestroy(TesterProfileT *pState) +{ + if(pState) + { + TesterProfileDisconnect(pState); + ZMemFree(pState); + } + TesterRegistrySetPointer("PROFILE", NULL); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerprofile.h b/src/thirdparty/dirtysdk/sample/tester2/source/testerprofile.h new file mode 100644 index 00000000..c9a3a588 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerprofile.h @@ -0,0 +1,142 @@ +/*H********************************************************************************/ +/*! + \File testerprofile.h + + \Description + Maintain user profiles for the tester application. + + \Notes + A profile is selected, created, or deleted after client GUI start + and before anything else happens. Currently, the following items + will be stored in a tester2 profile: command history, network startup, + and lobby parameters. Profiles will be stored in a single file on the + clients disk, the elements of the profile encoded into tagfields. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/18/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +#ifndef _testerprofile_h +#define _testerprofile_h + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +#define TESTERPROFILE_ERROR_NONE (0) //!< no error +#define TESTERPROFILE_ERROR_NULLPOINTER (-1) //!< invalid pointer used +#define TESTERPROFILE_ERROR_INVALIDFILENAME (-2) //!< bad filename +#define TESTERPROFILE_ERROR_FILEALREADYOPEN (-3) //!< file already opened +#define TESTERPROFILE_ERROR_FILEOPEN (-4) //!< could not open requested file +#define TESTERPROFILE_ERROR_NOSUCHENTRY (-5) //!< entry not found + +#define TESTERPROFILE_NUMPROFILES_DEFAULT (16) //!< max number of profiles to keep around + +#define TESTERPROFILE_PROFILEFILENAME_SIZEDEFAULT (256) //!< size of the profile filename string +#define TESTERPROFILE_PROFILEFILENAME_VALUEDEFAULT (".\\profile.txt") //!< profile filename + +#define TESTERPROFILE_PROFILENAME_SIZEDEFAULT (32) //!< size of each profile name +#define TESTERPROFILE_PROFILENAME_VALUEDEFAULT ("") //!< default name for the profile if none specified +#define TESTERPROFILE_PROFILENAME_TAGVALUE ("NAME") //!< the tag the data will be stored under + +#define TESTERPROFILE_CONTROLDIR_SIZEDEFAULT (256) //!< size of the directory entry +#define TESTERPROFILE_CONTROLDIR_VALUEDEFAULT (".\\") //!< default file sharing directory +#define TESTERPROFILE_CONTROLDIR_TAGVALUE ("CONTROLDIR") //!< the tag the data will be stored under + +#define TESTERPROFILE_COMMANDLINE_SIZEDEFAULT (256) //!< size of the optional command line params +#define TESTERPROFILE_COMMANDLINE_VALUEDEFAULT ("") //!< default command line params +#define TESTERPROFILE_COMMANDLINE_TAGVALUE ("COMMANDLINE") //!< the tag the data will be stored under + +#define TESTERPROFILE_LOGDIRECTORY_SIZEDEFAULT (256) //!< size of the file log location +#define TESTERPROFILE_LOGDIRECTORY_VALUEDEFAULT (".\\") //!< location to create a logfile at +#define TESTERPROFILE_LOGDIRECTORY_TAGVALUE ("LOGDIR") //!< the tag the data will be stored under + +#define TESTERPROFILE_HOSTNAME_SIZEDEFAULT (32) //!< size of the hostname +#define TESTERPROFILE_HOSTNAME_VALUEDEFAULT ("") //!< default hostname +#define TESTERPROFILE_HOSTNAME_TAGVALUE ("HOSTNAME") //!< the tag the data will be stored under + +#define TESTERPROFILE_LOGENABLE_VALUEDEFAULT (1) //!< log to a file by default +#define TESTERPROFILE_LOGENABLE_TAGVALUE ("LOGENABLE") //!< the tag the data will be stored under + +#define TESTERPROFILE_NETWORKSTART_VALUEDEFAULT (1) //!< bring up the network by default +#define TESTERPROFILE_NETWORKSTART_TAGVALUE ("NETSTART") //!< the tag the data will be stored under + +#define TESTERPROFILE_DEFAULTPROFILE_TAGVALUE ("DEFAULT") //!< the tag the data will be stored under + +#define TESTERPROFILE_HISTORYFILE_SIZEDEFAULT (288) //!< size of the history filename +#define TESTERPROFILE_HISTORYFILE_VALUEDEFAULT ("") //!< default value +#define TESTERPROFILE_HISTORYFILE_TAGVALUE ("HISTORYFILE") //!< tag the data will be stored under + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// module state container +typedef struct TesterProfileT TesterProfileT; + +// here's the actual profile data structure +typedef struct TesterProfileEntryT +{ + char strProfileName[TESTERPROFILE_PROFILENAME_SIZEDEFAULT]; //!< profile name + char strControlDirectory[TESTERPROFILE_CONTROLDIR_SIZEDEFAULT]; //!< control directory name + char strCommandLine[TESTERPROFILE_COMMANDLINE_SIZEDEFAULT]; //!< command line options + char strLogDirectory[TESTERPROFILE_LOGDIRECTORY_SIZEDEFAULT]; //!< logfile directory + char strHostname[TESTERPROFILE_HOSTNAME_SIZEDEFAULT]; //!< hostname + char strHistoryFile[TESTERPROFILE_HISTORYFILE_SIZEDEFAULT]; //!< history filename + char strLogFileName[TESTERPROFILE_LOGDIRECTORY_SIZEDEFAULT]; //!< Log file name + unsigned char uLogEnable; //!< 1 to enable file logging + unsigned char uNetworkStartup; //!< 1 for network startup on connect + unsigned char uDefault; //!< 1 for the default profile, 0 otherwise + unsigned char uPad1; //!< pad variable +} TesterProfileEntryT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create a tester profile manager +TesterProfileT *TesterProfileCreate(void); + +// connect the profile manager to a set of profiles in the given profile file +int32_t TesterProfileConnect(TesterProfileT *pState, const char *pFilename); + +// return a specifically indexed profile into a json buffer +int32_t TesterProfileGet(TesterProfileT *pState, int32_t iIndex, TesterProfileEntryT *pDest); + +// return a specifically indexed profile into a json buffer +int32_t TesterProfileGetJson(TesterProfileT *pState, int32_t iIndex, char *pDest, int32_t iSize); + +// add a profile to the list of profiles +int32_t TesterProfileAdd(TesterProfileT *pState, TesterProfileEntryT *pEntry); + +// delete a profile from the list of profiles +int32_t TesterProfileDelete(TesterProfileT *pState, const char *pName); + +// set a particular profile as a default profile (loads first next time) +int32_t TesterProfileSetDefaultName(TesterProfileT *pState, const char *pProfileName); + +// get the index of the default profile +int32_t TesterProfileGetDefaultIndex(TesterProfileT *pState); + +// save the profile file (manual save - will happen automatically on disconnect) +int32_t TesterProfileSave(TesterProfileT *pState); + +// save the current profiles and disconnect the profile manager from the profile set +int32_t TesterProfileDisconnect(TesterProfileT *pState); + +// destroy a tester profile object +void TesterProfileDestroy(TesterProfileT *pState); + +#ifdef __cplusplus +}; +#endif + +#endif // _testerprofile_h + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerregistry.c b/src/thirdparty/dirtysdk/sample/tester2/source/testerregistry.c new file mode 100644 index 00000000..0878e098 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerregistry.c @@ -0,0 +1,459 @@ +/*H********************************************************************************/ +/*! + \File testerregistry.h + + \Description + Maintains a global registry for the tester2 application. + + \Notes + This module works a little differently than other DirtySock modules. + Because of the nature of a registry (a global collector of shared + information) there is no pointer to pass around - Create simply + allocates memory of a given size and Destroy frees this memory. + Registry entries are stored in tagfield format. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/08/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/dirtysock.h" +#include "libsample/zmem.h" +#include "libsample/zlib.h" +#include "testerregistry.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct TesterRegistryEntryT +{ + uintptr_t uPtr; + int32_t iNum; + + char strName[256]; + char strBuffer[1024]; +} TesterRegistryEntryT; + +// registry module structure - private and not intended for external use +typedef struct TesterRegistryT +{ + int32_t iMaxEntries; //!< maximum number of entries supported + int32_t iNumEntries; //!< current number of entries + TesterRegistryEntryT pReg[1]; //!< the actual registry (variable size array) +} TesterRegistryT; + +/*** Variables ********************************************************************/ + +static TesterRegistryT *_TesterRegistry = NULL; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _TesterRegistryFindEntry + + \Description + Looks up a registry entry by name + + \Input *pEntryName - name of entry we are looking for + + \Output + TesterRegistryEntryT * - NULL or entry if found + + \Version 01/11/2017 (eesponda) +*/ +/********************************************************************************F*/ +static TesterRegistryEntryT *_TesterRegistryFindEntry(const char *pEntryName) +{ + TesterRegistryEntryT *pEntry = NULL; + int32_t iIndex; + + for (iIndex = 0; iIndex < _TesterRegistry->iNumEntries; iIndex += 1) + { + if (strcmp(pEntryName, _TesterRegistry->pReg[iIndex].strName) == 0) + { + pEntry = &_TesterRegistry->pReg[iIndex]; + break; + } + } + + return(pEntry); +} + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function TesterRegistryCreate + + \Description + Create a new registry of a given size. + + \Input iSize - Size of registry, in bytes, to create. (<0) for default size + + \Output None + + \Notes + Calling this function creates a global (static, private) registry which + any module can access. If TesterRegistryCreate is called multiple times, + the old registry is blown away (if it existed) the memory is freed, and + a new registry is created. No memory will be leaked if TesterRegistryCreate + is called multiple times. + + \Version 04/08/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterRegistryCreate(int32_t iSize) +{ + int32_t iMemSize; + + // dump the old registry if it exists + if(_TesterRegistry != NULL) + TesterRegistryDestroy(); + + if(iSize < 0) + { + // use default size + iSize = TESTERREGISTRY_SIZE_DEFAULT; + } + iMemSize = sizeof(*_TesterRegistry) - sizeof(_TesterRegistry->pReg) + (sizeof(_TesterRegistry->pReg) * iSize); + + // now create a new registry + _TesterRegistry = ZMemAlloc(iMemSize); + ds_memclr(_TesterRegistry, iMemSize); + _TesterRegistry->iMaxEntries = iSize; +} + +/*F********************************************************************************/ +/*! + \Function TesterRegistrySetPointer + + \Description + Set a registry entry for a pointer + + \Input *pEntryName - the registry entry name + \Input *pPtr - the pointer to save + + \Output int32_t - 0=success, error code otherwise + + \Notes + Reference count is automatically set to 1. To increment reference count + use TesterRegistryGetPointer. To decrement reference count use + TesterRegistryDecrementRefCount. + + \Version 04/08/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterRegistrySetPointer(const char *pEntryName, const void *pPtr) +{ + TesterRegistryEntryT *pEntry; + + // check for errors + if(_TesterRegistry == NULL) + { + ZPrintf("WARNING: TesterRegistry used before Create.\n"); + return(TESTERREGISTRY_ERROR_NOTINITIALIZED); + } + if(pEntryName == NULL) + { + ZPrintf("WARNING: TesterRegistrySet got NULL pointer for entry name\n"); + return(TESTERREGISTRY_ERROR_BADDATA); + } + + /* find the entry if it exists in the list + otherwise add a new entry to the list + note: we don't remove entries just to simplify */ + + if ((pEntry = _TesterRegistryFindEntry(pEntryName)) == NULL) + { + pEntry = &_TesterRegistry->pReg[_TesterRegistry->iNumEntries]; + ds_strnzcpy(pEntry->strName, pEntryName, sizeof(pEntry->strName)); + _TesterRegistry->iNumEntries += 1; + } + + pEntry->uPtr = (uintptr_t)pPtr; + return(TESTERREGISTRY_ERROR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function TesterRegistrySetNumber + + \Description + Set a registry entry for a number + + \Input *pEntryName - the registry entry name + \Input iNum - the number to save + + \Output int32_t - 0=success, error code otherwise + + \Version 04/08/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterRegistrySetNumber(const char *pEntryName, const int32_t iNum) +{ + TesterRegistryEntryT *pEntry; + + // check for errors + if(_TesterRegistry == NULL) + { + ZPrintf("WARNING: TesterRegistry used before Create.\n"); + return(TESTERREGISTRY_ERROR_NOTINITIALIZED); + } + if(pEntryName == NULL) + { + ZPrintf("WARNING: TesterRegistrySet got NULL pointer for entry name\n"); + return(TESTERREGISTRY_ERROR_BADDATA); + } + + /* find the entry if it exists in the list + otherwise add a new entry to the list + note: we don't remove entries just to simplify */ + + if ((pEntry = _TesterRegistryFindEntry(pEntryName)) == NULL) + { + pEntry = &_TesterRegistry->pReg[_TesterRegistry->iNumEntries]; + ds_strnzcpy(pEntry->strName, pEntryName, sizeof(pEntry->strName)); + _TesterRegistry->iNumEntries += 1; + } + + pEntry->iNum = iNum; + return(TESTERREGISTRY_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterRegistrySetString + + \Description + Set a registry entry for a pointer + + \Input *pEntryName - the registry entry name + \Input *pStr - the string to save + + \Output int32_t - 0=success, error code otherwise + + \Version 04/08/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterRegistrySetString(const char *pEntryName, const char *pStr) +{ + TesterRegistryEntryT *pEntry; + + // check for errors + if(_TesterRegistry == NULL) + { + ZPrintf("WARNING: TesterRegistry used before Create.\n"); + return(TESTERREGISTRY_ERROR_NOTINITIALIZED); + } + if((pEntryName == NULL) || (pStr == NULL)) + { + ZPrintf("WARNING: TesterRegistrySet got NULL pointer for entry name or entry value\n"); + return(TESTERREGISTRY_ERROR_BADDATA); + } + + /* find the entry if it exists in the list + otherwise add a new entry to the list + note: we don't remove entries just to simplify */ + + if ((pEntry = _TesterRegistryFindEntry(pEntryName)) == NULL) + { + pEntry = &_TesterRegistry->pReg[_TesterRegistry->iNumEntries]; + ds_strnzcpy(pEntry->strName, pEntryName, sizeof(pEntry->strName)); + _TesterRegistry->iNumEntries += 1; + } + + ds_strnzcpy(pEntry->strBuffer, pStr, sizeof(pEntry->strBuffer)); + return(TESTERREGISTRY_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterRegistryGetPointer + + \Description + Get a pointer entry from the registry + + \Input *pEntryName - the registry entry name + + \Output + void * - requested pointer, or NULL if no pointer is set + + \Notes + Reference count is incremented when Get is called. To decrement + reference count use TesterRegsitryDecrementRefCount. + + \Version 04/08/2005 (jfrank) +*/ +/********************************************************************************F*/ +void *TesterRegistryGetPointer(const char *pEntryName) +{ + TesterRegistryEntryT *pEntry; + + // check for errors + if (_TesterRegistry == NULL) + { + ZPrintf("WARNING: TesterRegistry used before Create.\n"); + return(NULL); + } + + if ((pEntry = _TesterRegistryFindEntry(pEntryName)) == NULL) + { + // no entry found + return(NULL); + } + + return((void *)pEntry->uPtr); +} + +/*F********************************************************************************/ +/*! + \Function TesterRegistryGetNumber + + \Description + Get a numeric entry from the registry + + \Input *pEntryName - the registry entry name + \Input *pNum - destination integer + + \Output int32_t - 0=success, error code otherwise + + \Version 04/08/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterRegistryGetNumber(const char *pEntryName, int32_t *pNum) +{ + TesterRegistryEntryT *pEntry; + + // check for errors + if(_TesterRegistry == NULL) + { + ZPrintf("WARNING: TesterRegistry used before Create.\n"); + return(TESTERREGISTRY_ERROR_NOTINITIALIZED); + } + if((pEntryName == NULL) || (pNum == NULL)) + { + ZPrintf("WARNING: TesterRegistryGet got NULL pointer for entry name\n"); + return(TESTERREGISTRY_ERROR_BADDATA); + } + + if ((pEntry = _TesterRegistryFindEntry(pEntryName)) == NULL) + { + // no entry found + *pNum = 0; + return(TESTERREGISTRY_ERROR_NOSUCHENTRY); + } + + *pNum = pEntry->iNum; + return(TESTERREGISTRY_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterRegistryGetString + + \Description + Get a string entry from the registry + + \Input *pEntryName - the registry entry name + \Input *pBuf - destination string + \Input iBufSize - size of the destination string + + \Output int32_t - 0=success, error code otherwise + + \Version 04/08/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterRegistryGetString(const char *pEntryName, char *pBuf, int32_t iBufSize) +{ + TesterRegistryEntryT *pEntry; + + // check for errors + if(_TesterRegistry == NULL) + { + ZPrintf("WARNING: TesterRegistry used before Create.\n"); + return(TESTERREGISTRY_ERROR_NOTINITIALIZED); + } + if((pEntryName == NULL) || (pBuf == NULL) || (iBufSize == 0)) + { + ZPrintf("WARNING: TesterRegistryGet got NULL pointer for entry name\n"); + return(TESTERREGISTRY_ERROR_BADDATA); + } + + ds_memclr(pBuf, iBufSize); + if ((pEntry = _TesterRegistryFindEntry(pEntryName)) == NULL) + { + // no entry found + return(TESTERREGISTRY_ERROR_NOSUCHENTRY); + } + + ds_strnzcpy(pBuf, pEntry->strBuffer, iBufSize-1); + return(TESTERREGISTRY_ERROR_NONE); +} + + +/*F********************************************************************************/ +/*! + \Function TesterRegistryPrint + + \Description + Print all the members of the registry to the console + + \Input None + + \Output None + + \Version 04/11/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterRegistryPrint(void) +{ + if(_TesterRegistry->iNumEntries == 0) + { + ZPrintf("REGISTRY: SIZE=%d {}\n",_TesterRegistry->iMaxEntries); + } + else + { + int32_t iEntryIndex; + + ZPrintf("REGISTRY: SIZE=%d :\n",_TesterRegistry->iMaxEntries); + for (iEntryIndex = 0; iEntryIndex < _TesterRegistry->iNumEntries; iEntryIndex += 1) + { + const TesterRegistryEntryT *pEntry = &_TesterRegistry->pReg[iEntryIndex]; + ZPrintf("REGISTRY: {name: %s, ptr: 0x%08x, num: %d, str: %s}\n", + pEntry->strName, pEntry->uPtr, pEntry->iNum, pEntry->strBuffer); + } + } +} + +/*F********************************************************************************/ +/*! + \Function TesterRegistryDestroy + + \Description + Destroy the registry and free all associated memory. + + \Input None + + \Output None + + \Version 04/08/2005 (jfrank) +*/ +/********************************************************************************F*/ +void TesterRegistryDestroy(void) +{ + ZMemFree(_TesterRegistry); + _TesterRegistry = NULL; +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testerregistry.h b/src/thirdparty/dirtysdk/sample/tester2/source/testerregistry.h new file mode 100644 index 00000000..87f29176 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testerregistry.h @@ -0,0 +1,78 @@ +/*H********************************************************************************/ +/*! + \File testerregistry.h + + \Description + Maintains a global registry for the tester2 application. + + \Notes + This module works a little differently than other DirtySock modules. + Because of the nature of a registry (a global collector of shared + information) there is no pointer to pass around - Create simply + allocates memory of a given size and Destroy frees this memory. + Registry entries are stored in tagfield format. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/08/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +#ifndef _testerregistry_h +#define _testerregistry_h + +/*** Include files ****************************************************************/ + +#define TESTERREGISTRY_ERROR_NONE (0) //!< no error +#define TESTERREGISTRY_ERROR_NOTINITIALIZED (-1) //!< create not called +#define TESTERREGISTRY_ERROR_NOSUCHENTRY (-2) //!< entry requested does not exist +#define TESTERREGISTRY_ERROR_OUTOFSPACE (-3) //!< no more room left in the registry +#define TESTERREGISTRY_ERROR_BADDATA (-4) //!< bad data passed to a function + +/*** Defines **********************************************************************/ + +#define TESTERREGISTRY_SIZE_DEFAULT (4096) //!< default registry size + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// pass in a registry size, or (-1) for default size, allocates that much memory. +// calling create multiple times frees the previous registry (no leak) +void TesterRegistryCreate(int32_t iSize); + +// set a registry entry - pointer +int32_t TesterRegistrySetPointer(const char *pEntryName, const void *pPtr); +// set a registry entry - number +int32_t TesterRegistrySetNumber(const char *pEntryName, const int32_t iNum); +// set a registry entry - string +int32_t TesterRegistrySetString(const char *pEntryName, const char *pStr); + +// get a registry entry - pointer - return error code (0 for none) +void *TesterRegistryGetPointer(const char *pEntryName); +// get a registry entry - pointer - return error code (0 for none) +int32_t TesterRegistryGetNumber(const char *pEntryName, int32_t *pNum); +// get a registry entry - pointer - return error code (0 for none) +int32_t TesterRegistryGetString(const char *pEntryName, char *pBuf, int32_t iBufSize); + +// print the registry +void TesterRegistryPrint(void); + +// destroy a registry +void TesterRegistryDestroy(void); + +#ifdef __cplusplus +}; +#endif + +#endif // _testerregistry_h + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testersubcmd.c b/src/thirdparty/dirtysdk/sample/tester2/source/testersubcmd.c new file mode 100644 index 00000000..315f403c --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testersubcmd.c @@ -0,0 +1,116 @@ +/*H********************************************************************************/ +/*! + \File .c + + \Description + Helper functions for modules to implement sub-commands. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 05/10/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "libsample/zlib.h" +#include "testersubcmd.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function T2SubCmdUsage + + \Description + Display basic usage for the given subcommand. + + \Input *pCmdName - name of command + \Input *pCmdList - list of subcommands + + \Output + None. + + \Version 05/10/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void T2SubCmdUsage(const char *pCmdName, T2SubCmdT *pCmdList) +{ + char strUsage[1024] = "help|"; + int32_t iCmd; + + for (iCmd = 0; pCmdList[iCmd].pFunc != NULL; iCmd++) + { + strcat(strUsage, pCmdList[iCmd].strName); + if (pCmdList[iCmd+1].pFunc != NULL) + { + strcat(strUsage, "|"); + } + } + ZPrintf(" usage: %s [%s]\n", pCmdName, strUsage); +} + +/*F********************************************************************************/ +/*! + \Function T2SubCmdParse + + \Description + Parse commandline for subcommand, and return matching command or NULL + if not found. + + \Input *pCmdList - pointer to command list to match against + \Input argc - argument count + \Input *argv[] - argument list + \Input *pHelp - [out] storage for whether help is requested or not + + \Output + T2SubCmdT * - pointer to parsed subcommand, or NULL if no match + + \Version 05/10/2005 (jbrookes) +*/ +/********************************************************************************F*/ +T2SubCmdT *T2SubCmdParse(T2SubCmdT *pCmdList, int32_t argc, char *argv[], unsigned char *pHelp) +{ + T2SubCmdT *pCmd = NULL; + int32_t iArgIdx; + + if (argc > 1) + { + iArgIdx = (!strcmp(argv[1], "help")) ? 2 : 1; + + if (iArgIdx < argc) + { + *pHelp = (iArgIdx == 2) ? 1 : 0; + + for (pCmd = pCmdList; pCmd->pFunc != NULL; pCmd++) + { + if (!strcmp(argv[iArgIdx], pCmd->strName)) + { + break; + } + } + + if (pCmd->pFunc == NULL) + { + pCmd = NULL; + } + } + } + + return(pCmd); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/testersubcmd.h b/src/thirdparty/dirtysdk/sample/tester2/source/testersubcmd.h new file mode 100644 index 00000000..94d5290a --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/testersubcmd.h @@ -0,0 +1,55 @@ +/*H********************************************************************************/ +/*! + \File testersubcmd.h + + \Description + Helper functions for modules to implement sub-commands. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 05/10/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _testersubcmd_h +#define _testersubcmd_h + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! subfunction command function prototype +typedef void (T2SubFuncT)(void *pCmdRef, int32_t argc, char *argv[], unsigned char bHelp); + +//! subfunction command description +typedef struct T2SubCmdT +{ + char strName[16]; + T2SubFuncT *pFunc; +} T2SubCmdT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +//! display list of subcommands +void T2SubCmdUsage(const char *pCmdName, T2SubCmdT *pCmdList); + +//! parse commandline to resolve subcommand +T2SubCmdT *T2SubCmdParse(T2SubCmdT *pCmdList, int32_t argc, char *argv[], unsigned char *pHelp); + +#ifdef __cplusplus +}; +#endif + +#endif // _testersubcmd_h + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/unix/T2Host.c b/src/thirdparty/dirtysdk/sample/tester2/source/unix/T2Host.c new file mode 100644 index 00000000..516ce2e0 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/unix/T2Host.c @@ -0,0 +1,326 @@ +/*H*************************************************************************************/ +/*! + + \File T2Host.c + + \Description + Tester2 Host Application Framework + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 03/22/2005 (jfrank) First Version +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/util/jsonformat.h" + +#include "libsample/zlib.h" +#include "libsample/zmemtrack.h" + +#include "testerhostcore.h" +#include "testercomm.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + + +/*** Type Definitions ******************************************************************/ + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// tester host module +TesterHostCoreT *g_pHostCore; + +static volatile uint8_t _bContinue = TRUE; + +/*** Private Functions *****************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _T2HostDisplayOutput + + \Description + Take input from TesterConsole and display it somewhere + + \Input *pBuf - string containing the debug output to display + \Input iLen - length of buffer + \Input iRefcon - user-specified parameter (unused) + \Input *pRefptr - user-specified parameter (window pointer) + + \Output None + + \Version 04/13/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _T2HostDisplayOutput(const char *pBuf, int32_t iLen, int32_t iRefcon, void *pRefptr) +{ + //printf("%s",pBuf); +} + + +/*F********************************************************************************/ +/*! + \Function _T2HostCommandlineProcess + + \Description + Clear the console + + \Input *argz - environment + \Input argc - num args + \Input **argv - arg list + + \Output int32_t - standard return code + + \Version 04/07/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _TestHostProcessInput(char *pCommandLine, int32_t iCommandLen, int32_t *pCommandOff) +{ + struct pollfd PollFd; + int32_t iResult; + char cInput = 0; + + // poll for input, blocking up to 20ms + PollFd.fd = 0; + PollFd.events = POLLIN; + if ((iResult = poll(&PollFd, 1, 20)) < 0) + { + NetPrintf(("t2host: poll() error %d trying to get input from stdin\n", errno)); + } + + // did we get any input? + if (PollFd.revents & POLLIN) + { + if ((iResult = (int32_t)read(0, &cInput, sizeof(cInput))) > 0) + { + pCommandLine[*pCommandOff] = cInput; + *pCommandOff += 1; + } + else if (iResult < 0) + { + NetPrintf(("t2host: read() error %d trying to read input from stdin\n", errno)); + } + } + + // no update + return(cInput == '\n'); +} + +/*F********************************************************************************/ +/*! + \Function _T2HostCmdClear + + \Description + Clear the console + + \Input *argz - environment + \Input argc - num args + \Input **argv - arg list + + \Output int32_t - standard return code + + \Version 04/07/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _T2HostCmdClear(ZContext *argz, int32_t argc, char **argv) +{ + if (argc < 1) + { + ZPrintf(" clear the display.\n"); + ZPrintf(" usage: %s\n", argv[0]); + } + else + { +#if 0 + // clear the console + //TesterConsoleT *pConsole; + if ((pConsole = (TesterConsoleT *)TesterRegistryGetPointer("CONSOLE")) != NULL) + { + TesterConsoleClear(pConsole); + } +#endif + } + return(0); +} + + +/*F********************************************************************************/ +/*! + \Function _T2HostCmdExit + + \Description + Quit + + \Input *argz - environment + \Input argc - num args + \Input **argv - arg list + + \Output int32_t - standard return code + + \Version 04/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +static int32_t _T2HostCmdExit(ZContext *argz, int32_t argc, char **argv) +{ + _bContinue = FALSE; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _T2HostRegisterModules + + \Description + Register client commands (local commands, like exit, history, etc.) + + \Input None + + \Output None + + \Version 04/05/2005 (jfrank) +*/ +/********************************************************************************F*/ +static void _T2HostRegisterModules(void) +{ + TesterHostCoreRegister(g_pHostCore, "exit", &_T2HostCmdExit); + TesterHostCoreRegister(g_pHostCore, "clear", &_T2HostCmdClear); +} + + +/*** Public Functions ******************************************************************/ + +int main(int32_t argc, char *argv[]) +{ + char strParams[512], strCommandLine[256] = "", strBase[256] = "", strHostName[128] = ""; + int32_t iArg, iCommandOff = 0; + uint8_t bInteractive = TRUE; + char strStartupParams[256] = "-servicename=tester2"; + + // start the memtracker before we do anything else + ZMemtrackStartup(); + + ZPrintf(("\nStarting T2Host.\n\n")); + + // get arguments + for (iArg = 0; iArg < argc; iArg++) + { + if (!strncmp(argv[iArg], "-path=", 6)) + { + ds_strnzcpy(strBase, &argv[iArg][6], sizeof(strBase)); + ZPrintf("t2host: base path=%s\n", strBase); + } + if (!strncmp(argv[iArg], "-connect=", 9)) + { + ds_strnzcpy(strHostName, &argv[iArg][9], sizeof(strHostName)); + ZPrintf("t2host: connect=%s\n", strHostName); + } + if (!strncmp(argv[iArg], "-notty", 6)) + { + bInteractive = FALSE; + ZPrintf("t2host: notty mode enabled\n"); + } + if (!strncmp(argv[iArg], "-singlethread", 13)) + { + ds_strnzcat(strStartupParams, " -singlethreaded", sizeof(strStartupParams)); + ZPrintf("t2host: single-threaded mode enabled\n"); + } + } + + // create the module + JsonInit(strParams, sizeof(strParams), 0); + JsonAddStr(strParams, "INPUTFILE", TESTERCOMM_HOSTINPUTFILE); + JsonAddStr(strParams, "OUTPUTFILE", TESTERCOMM_HOSTOUTPUTFILE); + JsonAddStr(strParams, "CONTROLDIR", strBase); + JsonAddStr(strParams, "HOSTNAME", strHostName); + + // startup dirtysdk (must come before TesterHostCoreCreate() if we are using socket comm) + NetConnStartup(strStartupParams); + + NetPrintf(("t2host: ipaddr=%a\n", NetConnStatus('addr', 0, NULL, 0))); + + NetPrintf(("t2host: macaddr=%s\n", NetConnMAC())); + + g_pHostCore = TesterHostCoreCreate(JsonFinish(strParams)); + + // define the function which will show stuff on the screen + TesterHostCoreDisplayFunc(g_pHostCore, _T2HostDisplayOutput, 0, NULL); + + // register basic functions + _T2HostRegisterModules(); + + ZPrintf("T2Host Unix Application Successfully started.\n"); + + // check for command-line command + for (iArg = 1; iArg < argc; iArg += 1) + { + // don't include -base or -connect arg, if present + if (!strncmp(argv[iArg], "-base=", 6) || !strncmp(argv[iArg], "-connect=", 9) || !strncmp(argv[iArg], "-notty", 6) || !strncmp(argv[iArg], "-singlethread", 13)) + { + continue; + } + // add to command-line + ds_strnzcat(strCommandLine, argv[iArg], sizeof(strCommandLine)); + ds_strnzcat(strCommandLine, " ", sizeof(strCommandLine)); + } + ZPrintf("> %s\n", strCommandLine); + TesterHostCoreDispatch(g_pHostCore, strCommandLine); + strCommandLine[0] = '\0'; + + while(_bContinue) + { + // check for input + if (bInteractive && _TestHostProcessInput(strCommandLine, sizeof(strCommandLine), &iCommandOff)) + { + strCommandLine[iCommandOff-1] = '\0'; // remove lf + TesterHostCoreDispatch(g_pHostCore, strCommandLine); + strCommandLine[0] = '\0'; + iCommandOff = 0; + } + + // pump the host core module + TesterHostCoreUpdate(g_pHostCore, 1); + + // give time to zlib + ZTask(); + ZCleanup(); + + // give time to network + NetConnIdle(); + + // sleep for a short while + fflush(stdout); + ZSleep(20); + } + + // code is unreachable for now + TesterHostCoreDisconnect(g_pHostCore); + TesterHostCoreDestroy(g_pHostCore); + + // shut down the network + NetConnShutdown(0); + + ZMemtrackShutdown(); + + ZPrintf("\nQuitting T2Host.\n\n"); + + return(0); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/unix/t2new.cpp b/src/thirdparty/dirtysdk/sample/tester2/source/unix/t2new.cpp new file mode 100644 index 00000000..375ec656 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/unix/t2new.cpp @@ -0,0 +1,28 @@ +/*H*************************************************************************************/ +/*! + + \File t2new.cpp + + \Description + This implements code in support of memory allocation in C++ code + + \Copyright + Copyright (c) Electronic Arts 2017. ALL RIGHTS RESERVED. + + \Version 12/01/2017 (eesponda) +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include + +void* operator new[](size_t size, unsigned long, unsigned long, char const*, int, unsigned int, char const*, int) +{ + return operator new(size); +} +void* operator new[](size_t size, char const*, int, unsigned int, char const*, int) +{ + return operator new(size); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/source/unix/testermoduleshost.c b/src/thirdparty/dirtysdk/sample/tester2/source/unix/testermoduleshost.c new file mode 100644 index 00000000..cf751ff1 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/source/unix/testermoduleshost.c @@ -0,0 +1,54 @@ +/*H********************************************************************************/ +/*! + \File testermoduleshost.c + + \Description + Unix-specific module startup. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 04/13/2005 (jfrank) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +#include "testermodules.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function TesterModulesRegisterAllCommands + + \Description + Register all modules for the Unix platform + + \Input *pState - module state + + \Output 0=success, error code otherwise + + \Version 04/13/2005 (jfrank) +*/ +/********************************************************************************F*/ +int32_t TesterModulesRegisterPlatformCommands(TesterModulesT *pState) +{ + #if defined(DIRTYCODE_STADIA) + TesterModulesRegister(pState, "voice", &CmdVoice); + #endif + + // tester2 linux-specific modules + return(TESTERMODULES_ERROR_NONE); +} + diff --git a/src/thirdparty/dirtysdk/sample/tester2/tester2_todo.html b/src/thirdparty/dirtysdk/sample/tester2/tester2_todo.html new file mode 100644 index 00000000..e5db9e32 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/tester2/tester2_todo.html @@ -0,0 +1,200 @@ + + +
+
+

Tester2: To-Do/Feature Task List

+ (in order of attention)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SummaryDescriptionStatusTime Estimate
(days)
Actual Time Spent
(days)
Subcommands for NETMake the "net" command use the subcommand architectureactive20
PS2 LoginImplement network config chooser for the "net login" command in T2Host.active80
Xbox LoginMake the Xbox "net login" command do all the xbox live login stuff.active80
PSP DSDemo FrameworkCreate basic PSP DSDemo framework. Do not fill in completely - just use as needed.idle20
PSP dynamic module loadingBuild a module loader for PSP - like the PS2 IRX loader - to dynamically load PRXesidle80
Delete Stale Commands on StartupOn startup, both the host and client should delete any data already present in the buffer (file)idle.250
'-' command prefixUsing the '-' prefix to a command ("-help") clears the debug output and then executes the command.idle.250
Script on StartupAdd the ability to run a script on startupidle.250
Differentiate host and client console outputColor-code or otherwise mark differently data which is coming from the host and data which is coming from the client.idle.50
Client CleanupAdd "net stop" printf and make sure all other states properly printf as well.idle???0
Client CleanupDebug the clear command for both the host and client.idle???0
Separate out Debug Output(from James)
+ The debug output can be overwhelming and it might be nice to have it separated + out (optionally?). Alternative ideas might be the ability to hide/show debug + output (as a toggle) or have a different coloration for it.
idle10
File LoggingAdd the ability to log debug output to a file.idle.50
PING Command FixDirtysock is leaking memory from the allocation at line 1618 in SocketLookup, + where the HostentT structure is allocated. This is being called by ProtoNameAsync + inside the ping command. The ping module probably isn't disposing of the + host structure in all casesidle.10
Help CommandThe help command by itself should only list all available commands. + Additional help can then be accessed by typing "help ". + The current help model comes from the original tester application and + should be updated to reflect this design.idle.250
Concurrent Multi-user profilesFix the profile module to handle the case where multiple Tester2 clients can be open at once and write to the profile file safely.idle40
+


+
+

Tester2: Done/Suspended Tasks

+

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SummaryDescriptionStatusTime Estimate
(days)
Actual Time Spent
(days)
Window ResizingCurrently, both windows are fixed in size. This matches the way the original + Tester application worked, but it would be nice to be able to resize the + window. Additionally add a horizontal scroll bar.DONE.25.5
Client CleanupFind out why the client is starting dirtysock and kill it if possible.DONE.05.05
Xenon Investigation and DesignInvestigate Xenon debugger interface. Examine refactoring/extracting common + functionality from the hostclientcomm module and adapting it to the debug + interface.DONE.5.5
Save HistorySave historical commands and load them back in at startupDONE.250
Xenon DevelopmentImplement hostclientcomm module for Xenon.DONE  
Title BarMake the title bar text on the client show the profile name.DONE.1.1
+ + diff --git a/src/thirdparty/dirtysdk/sample/ticker/source/pc/ticker.c b/src/thirdparty/dirtysdk/sample/ticker/source/pc/ticker.c new file mode 100644 index 00000000..9200a358 --- /dev/null +++ b/src/thirdparty/dirtysdk/sample/ticker/source/pc/ticker.c @@ -0,0 +1,228 @@ +/*H*************************************************************************************/ +/*! + \File ticker.c + + \Description + This application demonstrates simple use of DirtySock including starting up the + network stack and using ProtoHttp to hit the Yahoo stock ticker on a recurring + basis. + + \Copyright + Copyright (c) Electronic Arts 2016. + + \Version 1.0 12/10/2000 (gschaefer) Initial version + \Version 2.0 11/27/2002 (gschaefer) Revived from the dead + \Version 2.1 02/23/2003 (jbrookes) Rewrote to use demo lib + \Version 2.2 03/06/2003 (jbrookes) Updated to use netconn + \Version 3.0 10/05/2005 (jbrookes) PS3 version + \Version 3.1 11/19/2008 (mclouatre) Adding user data concept to mem group support + \Version 4.0 04/01/2016 (jbrookes) Ported from PS4 version +*/ +/*************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include +#include + +// dirtysock includes +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/misc/weblog.h" +#include "DirtySDK/proto/protohttp.h" + +/*** Defines ***************************************************************************/ + +#define TICKER_WEBLOG_ENABLED (DIRTYCODE_DEBUG && FALSE) + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + + +// Public variables + + +/*** Public Functions ******************************************************************/ + +// dll-friendly DirtyMemAlloc +#if !defined(DIRTYCODE_DLL) +void *DirtyMemAlloc(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#else +void *DirtyMemAlloc2(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#endif +{ + return(malloc(iSize)); +} + +// dll-friendly DirtyMemFree +#if !defined(DIRTYCODE_DLL) +void DirtyMemFree(void *pMem, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#else +void DirtyMemFree2(void *pMem, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +#endif +{ + free(pMem); +} + +int main(int32_t argc, char *argv[]) +{ + int32_t iAddr, iLen, iStatus, iTimeout, iCount; + ProtoHttpRefT *pHttp; + char strBuffer[16*1024]; + #if TICKER_WEBLOG_ENABLED + WebLogRefT *pWebLog; + int32_t iVerbose = 1; + #endif + + #if defined(DIRTYCODE_DLL) + DirtyMemFuncSet(&DirtyMemAlloc2, &DirtyMemFree2); + #endif + + #if TICKER_WEBLOG_ENABLED + // create weblog module + pWebLog = WebLogCreate(8192); + #if 0 + // set to post to dev server + WebLogConfigure(pWebLog, "eggplant.online.ea.com:8001", NULL); + #endif + // set weblog protohttp debugging to a high level + WebLogControl(pWebLog, 'spam', iVerbose, 0, NULL); + // hook in to netprintf debug output + #if DIRTYCODE_LOGGING + NetPrintfHook(WebLogDebugHook, pWebLog); + #endif + // start logging + WebLogStart(pWebLog); + #endif + + // start network + NetConnStartup("-servicename=ticker"); + + // bring up the interface + NetConnConnect(NULL, NULL, 0); + + // wait for network interface activation + for (iTimeout = NetTick()+15*1000 ; ; ) + { + // update network + NetConnIdle(); + + // get current status + iStatus = NetConnStatus('conn', 0, NULL, 0); + if ((iStatus == '+onl') || ((iStatus >> 24) == '-')) + { + break; + } + + // check for timeout + if (iTimeout < (signed)NetTick()) + { + NetPrintf(("ticker: timeout waiting for interface activation\n")); + return(-1); + } + + // give time to other threads + NetConnSleep(500); + } + + // check result code + if ((iStatus = NetConnStatus('conn', 0, NULL, 0)) == '+onl') + { + NetPrintf(("ticker: interface active\n")); + } + else if ((iStatus >> 14) == '-') + { + NetPrintf(("ticker: error bringing up interface\n")); + return(-11); + } + + // broadband check (should return TRUE if broadband, else FALSE) + NetPrintf(("iftype=%d\n", NetConnStatus('type', 0, NULL, 0))); + NetPrintf(("broadband=%s\n", NetConnStatus('bbnd', 0, NULL, 0) ? "TRUE" : "FALSE")); + + // acquire and display address + iAddr = NetConnStatus('addr', 0, NULL, 0); + NetPrintf(("addr=%a\n", iAddr)); + + // display mac address + NetPrintf(("mac=%s\n", NetConnMAC())); + + // setup http module + pHttp = ProtoHttpCreate(4096); + + // just keep working + for ( iTimeout = NetTick()-1, iLen=-1, iCount = 0; iCount < 8; ) + { + // see if its time to query + if ((iTimeout != 0) && (NetTick() > (unsigned)iTimeout)) + { + ProtoHttpGet(pHttp, "http://quote.yahoo.com/d/quotes.csv?s=^DJI,^SPC,^IXIC,EA,SNE,YHOO,^AORD,^N225,^FTSE&f=sl1c1&e=.csv", FALSE); + iTimeout = NetTick()+30*1000; + iLen = 0; + iCount += 1; // count the attempt + } + + // update protohttp + ProtoHttpUpdate(pHttp); + + // read incoming data into buffer + if ((iLen == 0) || (iLen == PROTOHTTP_RECVWAIT)) + { + if ((iLen = ProtoHttpRecvAll(pHttp, strBuffer, sizeof(strBuffer)-1)) > 0) + { + // print response + NetPrintf(("ticker: received response\n")); + NetPrintf(("%s", strBuffer)); + // print to console + printf("%s", strBuffer); + } + } + + #if TICKER_WEBLOG_ENABLED + WebLogUpdate(pWebLog); + #endif + + NetConnIdle(); + } + + NetPrintf(("ticker: done\n")); + + // shut down HTTP + ProtoHttpDestroy(pHttp); + + #if TICKER_WEBLOG_ENABLED + // stop the logging + WebLogStop(pWebLog); + // give it five seconds to flush the remaining data and end the transaction gracefully + for (iCount = 0; iCount < (5*1000); iCount += 100) + { + NetConnIdle(); + WebLogUpdate(pWebLog); + NetConnSleep(100); + } + // unhook netprintf debug output *before* we destroy WebLog + #if DIRTYCODE_LOGGING + NetPrintfHook(NULL, NULL); + #endif + // destroy module + WebLogDestroy(pWebLog); + #endif + + // disconnect from the network + NetConnDisconnect(); + + // shutdown the network connections && destroy the dirtysock code + NetConnShutdown(FALSE); + return(0); +} \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/source/comm/commsrp.c b/src/thirdparty/dirtysdk/source/comm/commsrp.c new file mode 100644 index 00000000..6aa4cbd7 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/comm/commsrp.c @@ -0,0 +1,1656 @@ +/*H*************************************************************************************************/ +/*! + + \File commsrp.c + + \Description + This is CommSRP (Selectively Reliable Protocol), a datagram packet-based + transport class. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 1999-2003. ALL RIGHTS RESERVED. + + \Version 0.5 01/03/03 (JLB) Initial Version, based on CommTCP + \Version 0.7 01/07/03 (JLB) Working unreliable transport, based on CommUDP + \Version 0.8 01/08/03 (JLB) Working reliable transport. + \Version 0.9 02/09/03 (JLB) Added support for sending zero-byte packets, and + fixed PS2 alignment issue. +*/ +/*************************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#define ESC_CAUSES_LOSS (0) + +#if ESC_CAUSES_LOSS +#include +#endif + +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/comm/commall.h" +#include "DirtySDK/comm/commsrp.h" + +/*** Defines ***************************************************************************/ + +//! enable debug spam +#define COMMSRP_VERBOSE (COMM_PRINT && FALSE) + +//! control packet types +enum +{ + CTRL_PACKET_FIRST = 16, //!< 16: beginning of packet code range + + CTRL_PACKET_INIT = CTRL_PACKET_FIRST, //!< 16: init packet + CTRL_PACKET_POKE, //!< 17: firewall poke packet + CTRL_PACKET_CONN, //!< 18: connection confirmation packet + CTRL_PACKET_KEEP, //!< 19: keep-alive packet + CTRL_PACKET_DISC, //!< 20: connection terminated packet + + CTRL_PACKET_LAST = 63 //!< 63: end of packet code range +}; + +//! size of sequence set +#define SEQN_SIZE (64) +//! sequence set mask +#define SEQN_MASK (SEQN_SIZE-1) + +//! base of unreliable sequence set +#define UNRELSEQN_BASE (64) +//! base of reliable sequence set +#define RELSEQN_BASE (128) +//! base of reliable packet reception acknowledgement sequence set +#define RELSEQNACK_BASE (192) + +//! rate at which to resend unacknoweldged packets +#define RELIABLE_RESEND_RATE (250) + +//! percentage of packets in receive buffer reserved for reliable packets (divisor) +#define RELIABLE_PCT_RESERVED (8) + + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! raw protocol packet format +/*! Notes: + The RawSRPPacket code field looks like this: + + value(s) meaning + ~~~~~~~~~~~~~~~~~~~~~~ + 0 reserved + 1-15 bundled packet count + 16 ctrl packet: init connection (CTRL_PACKET_INIT) + 17 ctrl packet: poke firewall (CTRL_PACKET_POKE) + 18 ctrl packet: connection established (CTRL_PACKET_CONN) + 19 ctrl packet: keep-alive (CTRL_PACKET_KEEP) + 20 ctrl packet: disconnect (CTRL_PACKET_DISC) + 21-63 reserved + 64-127 unreliable packet: sequence number is code-64 + 128-191 reliable packet: sequence number is code-128 + 192-255 reliable packet acknowledgement: sequence number is code-192 + ~~~~~~~~~~~~~~~~~~~~~~ + + If the code field is 1-15, then the value represents the number of packets + bundled together into the same UDP frame minus one (a value of one is + unsupported, as it would simply make the single packet bulkier). The + bundle format looks as follows: + + offset value meaning + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 code bundle count-1 (1-15) + + 1 len0-1 length of first bundled packet + 2 code0 code of first packet + 3 data0 data for first packet + + 4+len0 len1-1 length of second bundled packet + 5+len0 code1 code of second packet + 6+len0 data1 data for second packet + + [...] + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +*/ + +typedef struct +{ + //! packet header which is not sent/received + //! (this data is used internally) + struct + { + uint32_t when; //!< tick when a packet was received + uint32_t len; //!< size of packet + } head; + //! packet body which is sent/received + struct + { + unsigned char code; //!< packet code (see packet codes above) + unsigned char data[1]; //!< packet user data (variable length) + } body; +} RawSRPPacketT; + + +//! private module storage +struct CommSRPRef +{ + //! common header + CommRef common; + //! linked list of all instances + CommSRPRef *link; + //! comm socket + SocketT *socket; + //! peer address + struct sockaddr peeraddr; + + //! port state + enum { + ST_IDLE, //!< no connection + ST_CONN, //!< connecting + ST_LIST, //!< listening + ST_OPEN, //!< connection established + ST_CLOSE //!< connection closed + } state; + + //! identifier to keep from getting spoofed + uint32_t connident; + + //! width of receive records (same as width of send) + int32_t rcvwid; + //! number of packets reserved for reliable transport + int32_t rcvrelresv; + //! length of receive buffer (multiple of rcvwid) + int32_t rcvlen; + //! fifo input offset + int32_t rcvinp; + //! fifo output offset + int32_t rcvout; + //! pointer to buffer storage + char *rcvbuf; + + //! width of send record (same as width of receive) + int32_t sndwid; + //! length of send buffer (multiple of sndwid) + int32_t sndlen; + //! fifo input offset + int32_t sndinp; + //! fifo output offset + int32_t sndout; + //! pointer to buffer storage + char *sndbuf; + + //! tick at which last packet was sent + uint32_t sendtick; + //! tick at which last packet was received + uint32_t recvtick; + + //! unreliable sequence number + uint32_t unrelseqn; + //! unreliable sequence number + uint32_t unrelrecvseqn; + //! reliable sequence number + uint32_t relseqn; + //! reliable received sequence number + uint32_t relrecvseqn; + + //! allow for dns lookup + uint32_t dnsid; + char dnsquery[256]; + char *dnsbuf; + int32_t dnslen; + char dnsdiv; + + //! semaphore to synchronize thread access + NetCritT crit; + //! callback synchronization + volatile int32_t callback; + //! indcate pending event + int32_t gotevent; + //! callback routine pointer + void (*callproc)(CommRef *ref, int32_t event); +}; + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +// Public variables + + +/*** Private Functions *****************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPSetAddrInfo + + \Description + Sets peer/host addr/port info in common ref. + + \Input *ref - reference pointer + \Input sin - address pointer + + \Version 04/16/04 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPSetAddrInfo(CommSRPRef *ref, struct sockaddr *sin) +{ + struct sockaddr SockAddr; + + // save peer addr/port info in common ref + ref->common.peerip = SockaddrInGetAddr(sin); + ref->common.peerport = SockaddrInGetPort(sin); + + // save host addr/port info in common ref + SocketInfo(ref->socket, 'bind', 0, &SockAddr, sizeof(SockAddr)); + ref->common.hostip = SocketGetLocalAddr(); + ref->common.hostport = SockaddrInGetPort(&SockAddr); + + // debug output + NetPrintf(("commsrp: peer=0x%08x:%d, host=0x%08x:%d\n", + ref->common.peerip, + ref->common.peerport, + ref->common.hostip, + ref->common.hostport)); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPSetSocket + + \Description + Sets socket in ref and socketref in common portion of ref. + + \Input *pRef - reference pointer + \Input *pSocket - socket to set + + \Version 08/24/04 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPSetSocket(CommSRPRef *pRef, SocketT *pSocket) +{ + pRef->socket = pSocket; + pRef->common.sockptr = pSocket; +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPResetTransfer + + \Description + Reset the transfer state + + \Input *ref - reference pointer + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPResetTransfer(CommSRPRef *ref) +{ + // reset the send queue + ref->sndinp = 0; + ref->sndout = 0; + + // reset the receive queue + ref->rcvinp = 0; + ref->rcvout = 0; + + // make sendtick really old (in protocol terms) + ref->sendtick = NetTick()-5000; + + // start reliable received sequence at an invalid sequence number + ref->relrecvseqn = (uint32_t)-1; +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPSendImmediate + + \Description + Send an packet to the peer. + + \Input *ref - reference pointer + \Input *packet - packet pointer + + \Output + int32_t - negative=error, else amount of data sent + + \Version 01/07/03 (JLB) +*/ +/*************************************************************************************************F*/ +static int32_t _CommSRPSendImmediate(CommSRPRef *ref, RawSRPPacketT *packet) +{ + int32_t len, err; + + #if ESC_CAUSES_LOSS + // lose packets when escape is pressed + if (GetAsyncKeyState(VK_ESCAPE) < 0) + { + ref->sendtick = NetTick(); + return(0); + } + #endif + + // figure out amount to send + len = sizeof(packet->body)-sizeof(packet->body.data); // packet framing + len += packet->head.len; // variable data + + #if COMMSRP_VERBOSE + { + char addrbuf[32]; + + SockaddrInGetAddrText(&ref->peeraddr,addrbuf,sizeof(addrbuf)), + NetPrintf(("_CommSRPSendImmediate: Sending %d bytes to %s:%d\n",len, addrbuf, + SockaddrInGetPort(&ref->peeraddr))); + } + #endif + + // send some data + err = SocketSendto(ref->socket,(char *)&packet->body,len,0, + &ref->peeraddr,sizeof(ref->peeraddr)); + + // check for send failure + if (err != len) + { + NetPrintf(("_CommSRPSendImmediate: SocketSendto returned %d\n", err)); + return(-1); + } + + // update last send time + ref->sendtick = NetTick(); + ref->common.datasent += len; + ref->common.packsent += 1; + + return(len); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPSendCtrl + + \Description + Send a control packet. + + \Input *ref - reference pointer + \Input code - control code to send + + \Notes + Control packets are sent unreliably. + + \Version 01/08/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPSendCtrl(CommSRPRef *ref, unsigned char code) +{ + RawSRPPacketT packet; + + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPSendCtrl: Sending control packet id %d\n", code)); + #endif + + packet.head.len = 0; + packet.body.code = code; + + _CommSRPSendImmediate(ref, &packet); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPProcessInitRequest + + \Description + Send an INIT control packet if we know our peer. + + \Input *ref - reference pointer + + \Version 01/08/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPProcessInitRequest(CommSRPRef *ref) +{ + // see if we know our peer + if ((SockaddrInGetAddr(&ref->peeraddr) == 0) || (SockaddrInGetPort(&ref->peeraddr) == 0)) + { + return; + } + + // send connect message once a second + if ((NetTick() - ref->sendtick) >= 1000) + { + if (ref->state == ST_CONN) + { + // send connection initiation packet + _CommSRPSendCtrl(ref, CTRL_PACKET_INIT); + } + else if (ref->state == ST_LIST) + { + // send poke packet + _CommSRPSendCtrl(ref, CTRL_PACKET_POKE); + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPProcessACK + + \Description + Process a reliable packet acknowledgement + + \Input *ref - reference pointer + \Input *packet - ack packet + + \Version 01/08/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPProcessACK(CommSRPRef *ref, RawSRPPacketT *packet) +{ + uint32_t ackseqnid, refseqnid; + RawSRPPacketT *refpkt; + + // get a pointer to packet this should be an ack for + refpkt = (RawSRPPacketT *)&ref->sndbuf[ref->sndout]; + + // decode sequence ids + ackseqnid = packet->body.code - RELSEQNACK_BASE; + refseqnid = refpkt->body.code - RELSEQN_BASE; + + // compare sequence ids + if (ackseqnid == refseqnid) + { + // packet was successfully transmitted, so dequeue it + ref->sndout = (ref->sndout+ref->sndwid)%ref->sndlen; + + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessACK: Success: confirmed delivery of packet %d\n", + refseqnid)); + #endif + } + else + { + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessACK: Got old ack %d, wanted ack %d\n", + ackseqnid,refseqnid)); + #endif + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPProcessCtrl + + \Description + Process an incoming control message. + + \Input *ref - reference pointer + \Input *packet - control packet to process + \Input *pFrom - sender's socket address + + \Version 01/07/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPProcessCtrl(CommSRPRef *ref, RawSRPPacketT *packet, struct sockaddr *pFrom) +{ + // update valid receive time + // must put into past to avoid race condition + ref->recvtick = NetTick()-1000; + + switch(packet->body.code) + { + // response to connection request packet + case CTRL_PACKET_INIT: + { + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessSetup: Received INIT\n")); + #endif + + if (ref->state == ST_LIST) + { + // set the peer + ds_memcpy_s(&ref->peeraddr, sizeof(ref->peeraddr), pFrom, sizeof(*pFrom)); + + // set peer/host addr/port info + _CommSRPSetAddrInfo(ref, pFrom); + + // update state + ref->state = ST_OPEN; + } + + // always send a response + _CommSRPSendCtrl(ref, CTRL_PACKET_CONN); + } + break; + + // response to poke packet + case CTRL_PACKET_POKE: + { + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessSetup: Received POKE\n")); + #endif + + if (ref->state == ST_CONN) + { + // set the peer + ds_memcpy_s(&ref->peeraddr, sizeof(ref->peeraddr), pFrom, sizeof(*pFrom)); + } + } + break; + + // response to a connect confirmation + case CTRL_PACKET_CONN: + { + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessSetup: Received CONN\n")); + #endif + + // change to open if not already there + if (ref->state == ST_CONN) + { + // set peer/host addr/port info + _CommSRPSetAddrInfo(ref, pFrom); + + ref->state = ST_OPEN; + } + } + break; + + // response to disconnect message + case CTRL_PACKET_DISC: + { + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessSetup: Received DISC\n")); + #endif + + // close the connection + if (ref->state == ST_OPEN) + { + ref->state = ST_CLOSE; + } + } + break; + + // response to keepalive message + case CTRL_PACKET_KEEP: + { + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessSetup: Received KEEP\n")); + #endif + } + break; + + // this case should not happen + default: + { + NetPrintf(("_CommSRPProcessSetup: Unrecognized control packet type %d\n",packet->body.code)); + } + break; + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPProcessData + + \Description + Process incoming data packet. + + \Input *ref - reference pointer + \Input *packet - incoming packet + + \Output + int32_t - <0 = error, 0 = packet discarded (duplicate resend), 1 = packet added to buffer + + \Version 01/07/03 (JLB) +*/ +/*************************************************************************************************F*/ +static int32_t _CommSRPProcessData(CommSRPRef *ref, RawSRPPacketT *packet) +{ + RawSRPPacketT *buffer; + + if ((packet->body.code >= UNRELSEQN_BASE) && (packet->body.code < RELSEQN_BASE)) + { + int32_t queuepos, pktsfree; + + // calculate the number of free packets in rcvbuf + queuepos = ((ref->rcvinp+ref->rcvlen)-ref->rcvout)%ref->rcvlen; + pktsfree = (ref->rcvlen - queuepos)/ref->rcvwid; + + // see if there is room in buffer for packet (leave extra space for reliable packets) + if (pktsfree <= ref->rcvrelresv) + { + // no room in buffer -- just drop packet + #if COMMSRP_VERBOSE // (we expect lots of these, so don't spam) + NetPrintf(("_CommSRPProcessData: Unreliable packet %d discarded due to input buffer overrun.\n",ref->relrecvseqn)); + #endif + return(-1); + } + + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessData: Received packet %d\n",packet->body.code - UNRELSEQN_BASE)); + #endif + + // update unreliably received sequence number + ref->unrelrecvseqn = packet->body.code - UNRELSEQN_BASE; + } + else + { + uint32_t uPacketId; + + // see if room in buffer for packet + if ((ref->rcvinp+ref->rcvwid)%ref->rcvlen == ref->rcvout) + { + // no room in buffer -- just drop packet + NetPrintf(("_CommSRPProcessData: Reliable packet discarded due to input buffer overrun\n")); + return(-1); + } + + // get packet ID + uPacketId = (uint32_t)(packet->body.code - RELSEQN_BASE); + + // is this the packet we're expecting? + if (uPacketId == ((ref->relrecvseqn+1) % SEQN_SIZE)) + { + // update reliably received sequence number + ref->relrecvseqn = uPacketId; + + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessData: Received packet %d\n", ref->relrecvseqn)); + #endif + } + else if (uPacketId == ref->relrecvseqn) + { + // this is the previously received packet - return TRUE to ack it in case our last ack got lost + return(1); + } + else + { + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessData: Discarding duplicate packet %d\n",ref->relrecvseqn)); + #endif + return(0); + } + } + + // add the packet to the buffer + buffer = (RawSRPPacketT *) &ref->rcvbuf[ref->rcvinp]; + ds_memcpy(buffer, packet, ref->rcvwid); + + // limit receive access for callbacks + ref->callback += 1; + // add item to receive buffer + ref->rcvinp = (ref->rcvinp+ref->rcvwid) % ref->rcvlen; + // indicate we got an event + ref->gotevent |= 1; + // let the callback process it + if (ref->common.RecvCallback != NULL) + { + ref->common.RecvCallback((CommRef *)ref, buffer->body.data, buffer->head.len, buffer->head.when); + } + // release access to receive + ref->callback -= 1; + return(1); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPProcessAlive + + \Description + Send out a keep-alive packet (CTRL_PACKET_KEEP) periodically + + \Input *ref - reference pointer + + \Version 01/07/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPProcessAlive(CommSRPRef *ref) +{ + if ((ref->state == ST_OPEN) && (ref->sndinp == ref->sndout)) + { + // see if time has elapsed + if (NetTick() > ref->sendtick+5000) + { + // queue up a keepalive packet + _CommSRPSendCtrl(ref, CTRL_PACKET_KEEP); + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPProcessRecvQueue + + \Description + Attempt to read data from peer and add it to the receive queue. + + \Input *ref - reference pointer + + \Version 01/08/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPProcessRecvQueue(CommSRPRef *ref) +{ + int32_t len, sinlen, iResult = 0; + char *buffer; + struct sockaddr sin; + RawSRPPacketT *packet; + + while((ref->state >= ST_CONN) && (ref->state <= ST_OPEN) && (iResult >= 0)) + { + // get pointer to recv queue + packet = (RawSRPPacketT *) &ref->rcvbuf[ref->rcvinp]; + buffer = (char *) &packet->body; + + // attempt to get a packet + sinlen = sizeof(sin); + len = SocketRecvfrom(ref->socket, buffer, ref->rcvwid, 0, &sin, &sinlen); + + // if we got data add it to recv queue + if (len > 0) + { + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessRecvQueue: Received %d bytes of data\n",len)); + #endif + + packet->head.len = len; + packet->head.when = SockaddrInGetMisc(&sin); + + // process packet + if ((packet->body.code >= CTRL_PACKET_FIRST) && (packet->body.code <= CTRL_PACKET_LAST)) + { + // handle control packets + _CommSRPProcessCtrl(ref, packet, &sin); + } + else if (packet->body.code >= RELSEQNACK_BASE) + { + // handle ack of reliable packet + _CommSRPProcessACK(ref, packet); + } + else + { + // handle data packets + iResult = _CommSRPProcessData(ref, packet); + if (iResult == 1) + { + // check to see if this is a reliable packet and requires an acknowledgement + if ((packet->body.code >= RELSEQN_BASE) && (packet->body.code < RELSEQNACK_BASE)) + { + // send an ack (seqid +64) + #if COMMSRP_VERBOSE + NetPrintf(("_CommSRPProcessRecvQueue: Sending ack for packet %d\n",packet->body.code)); + #endif + + _CommSRPSendCtrl(ref,packet->body.code + SEQN_SIZE); + } + } + } + } + else if (len < 0) + { + // error in recv - close connection + NetPrintf(("_CommSRPProcessRecvQueue: Error %d - closing connection\n",len)); + ref->state = ST_CLOSE; + break; + } + else + { + // no data to receive + break; + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPProcessSendQueue + + \Description + Send data to peer from send queue. + + \Input *ref - reference pointer + + \Version 01/08/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPProcessSendQueue(CommSRPRef *ref) +{ + RawSRPPacketT *packet; + + if ((ref->state == ST_OPEN) && (ref->sndinp != ref->sndout)) + { + // ref next packet to send + packet = (RawSRPPacketT *) &ref->sndbuf[ref->sndout]; + + // send packet + if ((packet->head.when == 0) || ((NetTick() - packet->head.when) >= RELIABLE_RESEND_RATE)) + { + #if COMMSRP_VERBOSE + if (packet->head.when != 0) + { + NetPrintf(("_CommSRPProcessSendQueue: Resending sequence id %d\n", + packet->body.code - RELSEQN_BASE)); + } + #endif + + #if COMMSRP_VERBOSE + if (packet->head.when == 0) + { + NetPrintf(("_CommSRPProcessSendQueue: Sending sequence id %d\n", + packet->body.code - RELSEQN_BASE)); + } + #endif + + // send the packet + if (_CommSRPSendImmediate(ref, packet) < 0) + { + return; + } + + // update sent tick + packet->head.when = NetTick(); + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPClose + + \Description + Close the connection + + \Input *ref - reference pointer + + \Output + int32_t - zero + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +static int32_t _CommSRPClose(CommSRPRef *ref) +{ + // see if we are even connected + if (ref->state != ST_OPEN) + { + return(0); + } + + // send a disconnect message + _CommSRPSendCtrl(ref, CTRL_PACKET_DISC); + + // set to disconnect state + ref->connident = 0; + ref->state = ST_CLOSE; + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPEvent0 + + \Description + Private socket callback function for CommSRP event processing. + + \Input *ref - reference pointer + + \Notes + This function should only be called by _CommSRPEvent() + + \Version 01/08/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommSRPEvent0(CommSRPRef *ref) +{ + // send init request if we're connecting + if ((ref->state == ST_CONN) || (ref->state == ST_LIST)) + { + _CommSRPProcessInitRequest(ref); + } + + // receive data into recv queue + _CommSRPProcessRecvQueue(ref); + + // periodically send a keep-alive + _CommSRPProcessAlive(ref); + + // send all queued data + _CommSRPProcessSendQueue(ref); + + // do callback if needed + if ((ref->callback == 0) && (ref->gotevent != 0)) + { + // limit callback access + ref->callback += 1; + // notify user + if (ref->callproc != NULL) + { + ref->callproc((CommRef *)ref, ref->gotevent); + } + // limit callback access + ref->callback -= 1; + // done with event + ref->gotevent = 0; + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPEvent + + \Description + Socket callback function for CommSRP event processing, protected + with critical section. + + \Input *sock - unused + \Input flags - unused + \Input *_ref - reference pointer + + \Output + int32_t - zero + + \Version 01/08/03 (JLB) +*/ +/*************************************************************************************************F*/ +static int32_t _CommSRPEvent(SocketT *sock, int32_t flags, void *_ref) +{ + CommSRPRef *ref = _ref; + + // see if we have exclusive access + if (NetCritTry(&ref->crit)) + { + // update + _CommSRPEvent0(ref); + + // free access + NetCritLeave(&ref->crit); + } + + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommSRPSendQueued + + \Description + Add packet to send queue. + + \Input *ref - reference pointer + \Input *packet - packet to add to send queue + + \Output + int32_t - buffer depth + + \Notes + Only reliable packets should be added to the send queue; use _CommSRPSendImmediate() for + unreliable packet data. + + \Version 01/08/03 (JLB) +*/ +/*************************************************************************************************F*/ +static int32_t _CommSRPSendQueued(CommSRPRef *ref, RawSRPPacketT *packet) +{ + int32_t pos; + + // mark timestamp=0 for immediate send + packet->head.when = 0; + + // add the packet to the queue + ref->sndinp = (ref->sndinp+ref->sndwid) % ref->sndlen; + + // try and send immediately + _CommSRPEvent(ref->socket, 0, ref); + + // return buffer depth + pos = (((ref->sndinp+ref->sndlen)-ref->sndout)%ref->sndlen)/ref->sndwid; + return(pos); +} + + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function CommSRPConstruct + + \Description + Construct the class + + \Input maxwid - max record width + \Input maxinp - input packet buffer size + \Input maxout - output packet buffer size + + \Output + CommSRPRef - reference pointer + + \Notes + Initialized winsock for first class. also creates linked + list of all current instances of the class and worker thread + to do most udp stuff. + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +CommSRPRef *CommSRPConstruct(int32_t maxwid, int32_t maxinp, int32_t maxout) +{ + CommSRPRef *ref; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate class storage + ref = DirtyMemAlloc(sizeof(*ref), COMMSRP_MEMID, iMemGroup, pMemGroupUserData); + if (ref == NULL) + return(NULL); + ds_memclr(ref, sizeof(*ref)); + ref->common.memgroup = iMemGroup; + ref->common.memgrpusrdata = pMemGroupUserData; + + // initialize the callback routines + ref->common.Construct = (CommAllConstructT *)CommSRPConstruct; + ref->common.Destroy = (CommAllDestroyT *)CommSRPDestroy; + ref->common.Resolve = (CommAllResolveT *)CommSRPResolve; + ref->common.Unresolve = (CommAllUnresolveT *)CommSRPUnresolve; + ref->common.Listen = (CommAllListenT *)CommSRPListen; + ref->common.Unlisten = (CommAllUnlistenT *)CommSRPUnlisten; + ref->common.Connect = (CommAllConnectT *)CommSRPConnect; + ref->common.Unconnect = (CommAllUnconnectT *)CommSRPUnconnect; + ref->common.Callback = (CommAllCallbackT *)CommSRPCallback; + ref->common.Status = (CommAllStatusT *)CommSRPStatus; + ref->common.Tick = (CommAllTickT *)CommSRPTick; + ref->common.Send = (CommAllSendT *)CommSRPSend; + ref->common.Peek = (CommAllPeekT *)CommSRPPeek; + ref->common.Recv = (CommAllRecvT *)CommSRPRecv; + + // remember max packet width + ref->common.maxwid = maxwid; + ref->common.maxinp = maxinp; + ref->common.maxout = maxout; + + // reset access to shared resources + NetCritInit(&ref->crit, "commsrp"); + + // allocate the buffers + ref->rcvwid = sizeof(RawSRPPacketT)-sizeof(((RawSRPPacketT *)0)->body.data)+maxwid; + ref->rcvwid = (ref->rcvwid+3) & 0x7ffc; + ref->rcvlen = ref->rcvwid * maxinp; + ref->rcvbuf = DirtyMemAlloc(ref->rcvlen, COMMSRP_MEMID, ref->common.memgroup, ref->common.memgrpusrdata); + ref->sndwid = sizeof(RawSRPPacketT)-sizeof(((RawSRPPacketT *)0)->body.data)+maxwid; + ref->sndwid = (ref->sndwid+3) & 0x7ffc; + ref->sndlen = ref->sndwid * maxout; + ref->sndbuf = DirtyMemAlloc(ref->sndlen, COMMSRP_MEMID, ref->common.memgroup, ref->common.memgrpusrdata); + + // calculate number of packets reserved for reliable transport + ref->rcvrelresv = maxinp/RELIABLE_PCT_RESERVED; + + // reset the socket + _CommSRPSetSocket(ref, NULL); + + // reset the state + ref->state = ST_IDLE; + ref->connident = 0; + + // return the reference + return(ref); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPDestroy + + \Description + Destruct the class + + \Input *ref - reference pointer + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +void CommSRPDestroy(CommSRPRef *ref) +{ + // if port is open, close it + if (ref->state == ST_OPEN) + { + _CommSRPClose(ref); + } + + // kill the socket + if (ref->socket != NULL) + { + SocketClose(ref->socket); + _CommSRPSetSocket(ref, NULL); + } + + // get rid of sockets critical section + NetCritKill(&ref->crit); + + // release resources + DirtyMemFree(ref->rcvbuf, COMMSRP_MEMID, ref->common.memgroup, ref->common.memgrpusrdata); + DirtyMemFree(ref->sndbuf, COMMSRP_MEMID, ref->common.memgroup, ref->common.memgrpusrdata); + DirtyMemFree(ref, COMMSRP_MEMID, ref->common.memgroup, ref->common.memgrpusrdata); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPCallback + + \Description + Set upper layer callback + + \Input *ref - reference pointer + \Input *callback - socket generating callback + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +void CommSRPCallback(CommSRPRef *ref, void (*callback)(CommRef *ref, int32_t event)) +{ + ref->callproc = callback; + ref->gotevent |= 2; +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPResolve + + \Description + Resolve an address (unimplemented) + + \Input *ref - endpoint + \Input *addr - resolve address + \Input *buf - target buffer + \Input len - target length (min 64 bytes) + \Input div - divider char + + \Output + int32_t - <0=error, 0=complete (COMM_NOERROR), >0=in progress (COMM_PENDING) + + \Notes + Target list is always double null terminated allowing null + to be used as the divider character if desired. when COMM_PENDING + is returned, target buffer is set to "~" until completion + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t CommSRPResolve(CommSRPRef *ref, const char *addr, char *buf, int32_t len, char div) +{ + NetPrintf(("commsrp: resolve functionality not supported\n")); + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPUnresolve + + \Description + Stop the resolver + + \Input *ref - endpoint ref + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +void CommSRPUnresolve(CommSRPRef *ref) +{ + return; +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPUnlisten + + \Description + Stop listening. + + \Input *ref - reference pointer + + \Output + int32_t - negative=error, zero=ok + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t CommSRPUnlisten(CommSRPRef *ref) +{ + // get rid of socket if presernt + if (ref->socket != NULL) + { + SocketClose(ref->socket); + _CommSRPSetSocket(ref, NULL); + } + + // return to idle mode + ref->state = ST_IDLE; + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPUnconnect + + \Description + Terminate a connection + + \Input *ref - reference pointer + + \Output + int32_t - negative=error, zero=ok + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t CommSRPUnconnect(CommSRPRef *ref) +{ + // get rid of socket if presernt + if (ref->socket != NULL) + { + SocketClose(ref->socket); + _CommSRPSetSocket(ref, NULL); + } + + // return to idle mode + ref->state = ST_IDLE; + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPStatus + + \Description + Return current stream status + + \Input *ref - reference pointer + + \Output + int32_t - COMM_CONNECTING, COMM_OFFLINE, COMM_ONLINE or COMM_FAILURE + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t CommSRPStatus(CommSRPRef *ref) +{ + // return state + if ((ref->state == ST_CONN) || (ref->state == ST_LIST)) + return(COMM_CONNECTING); + if ((ref->state == ST_IDLE) || (ref->state == ST_CLOSE)) + return(COMM_OFFLINE); + if (ref->state == ST_OPEN) + return(COMM_ONLINE); + return(COMM_FAILURE); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPTick + + \Description + Return current tick + + \Input *ref - reference pointer + + \Output + uint32_t - elapsed milliseconds + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +uint32_t CommSRPTick(CommSRPRef *ref) +{ + return(NetTick()); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPSend + + \Description + Send a packet + + \Input *ref - reference pointer + \Input *buffer - pointer to data + \Input length - length of data + \Input flags - COMM_FLAGS_* + + \Output + int32_t - negative=error, zero=buffer full (temp fail), positive=queue position (ok) + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t CommSRPSend(CommSRPRef *ref, const void *buffer, int32_t length, uint32_t flags) +{ + RawSRPPacketT *packet; + int32_t pos; + + // make sure port is open + if (ref->state != ST_OPEN) + { + return(COMM_BADSTATE); + } + + // see if input queue full + if ((ref->sndinp+ref->sndwid)%ref->sndlen == ref->sndout) + { + NetPrintf(("commsrp: input queue full\n")); + return(0); + } + + // return error for oversized packets + if (length > (signed)(ref->sndwid-(sizeof(RawSRPPacketT)-sizeof((RawSRPPacketT *)0)->body.data))) + { + NetPrintf(("commsrp: oversized packet send (%d bytes)\n", length)); + return(COMM_MINBUFFER); + } + + // zero sized packet cannot be sent (they are used for acks) + // instead, treat them as successful which means the queue + // position is returned + if (length == 0) + { + pos = (((ref->sndinp+ref->sndlen)-ref->sndout)%ref->sndlen)/ref->sndwid; + return(pos+1); + } + + // add packet to send queue + packet = (RawSRPPacketT *) &(ref->sndbuf[ref->sndinp]); + + // set packet length + packet->head.len = length; + + // copy user data into queue + ds_memcpy(packet->body.data, buffer, length); + + // send reliable or unreliable? + if (flags & COMM_FLAGS_UNRELIABLE) + { + // unreliable sequence tracking + packet->body.code = (unsigned char)ref->unrelseqn++ + UNRELSEQN_BASE; + ref->unrelseqn &= SEQN_MASK; + + #if COMMSRP_VERBOSE + NetPrintf(("commsrp: sending unreliable sequence number %d (len=%d)\n",packet->body.code-UNRELSEQN_BASE,length)); + #endif + + // unreliable packets are sent immediately + if ((pos = _CommSRPSendImmediate(ref,packet)) > 0) + { + pos = 1; + } + } + else + { + // reliable sequence tracking + packet->body.code = (unsigned char)ref->relseqn++ + RELSEQN_BASE; + ref->relseqn &= SEQN_MASK; + + #if COMMSRP_VERBOSE + NetPrintf(("CommSRPSend: Sending reliable sequence number %d (len=%d)\n",packet->body.code-RELSEQN_BASE,length)); + #endif + + // reliable packets are added to the send queue + pos = _CommSRPSendQueued(ref,packet); + } + + // return buffer depth + return((pos > 0) ? pos : 1); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPPeek + + \Description + Peek at waiting packet, and copy to target buffer if present. + + \Input *ref - reference pointer + \Input *target - target buffer + \Input length - buffer length + \Input *when - tick received at + + \Output + int32_t - negative=nothing pending, else packet length + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t CommSRPPeek(CommSRPRef *ref, void *target, int32_t length, uint32_t *when) +{ + RawSRPPacketT *packet; + int32_t packetlen; + + // see if a packet is available + if (ref->rcvout == ref->rcvinp) + { + return(COMM_NODATA); + } + + // point to the packet + packet = (RawSRPPacketT *) &(ref->rcvbuf[ref->rcvout]); + + #if COMMSRP_VERBOSE + NetPrintf(("commsrp: received packet, sequence number %d (len=%d)\n",packet->body.code,packet->head.len)); + #endif + + // packet length minus code byte + packetlen = packet->head.len - (sizeof(packet->body)-sizeof(packet->body.data)); + + // copy over the data portion + ds_memcpy(target, packet->body.data, (packetlen < length ? packetlen : length)); + + // get the timestamp + if (when != NULL) + { + *when = packet->head.when; + } + + // return packet data length + return(packetlen); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPRecv + + \Description + Receive a packet from the buffer + + \Input *ref - reference pointer + \Input *target - target buffer + \Input length - buffer length + \Input *when - tick received at + + \Output + int32_t - negative=error, else packet length + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t CommSRPRecv(CommSRPRef *ref, void *target, int32_t length, uint32_t *when) +{ + // use peek to remove the data + int32_t len = CommSRPPeek(ref, target, length, when); + + if (len >= 0) + { + ref->rcvout = (ref->rcvout+ref->rcvwid)%ref->rcvlen; + } + + // all done + return(len); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPListen + + \Description + Listen for a connection + + \Input *ref - reference pointer + \Input *addr - port to listen on (only :port portion used) + + \Output + int32_t - negative=error, zero=ok + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t CommSRPListen(CommSRPRef *ref, const char *addr) +{ + int32_t err, iListenPort, iConnPort; + uint32_t uPokeAddr; + struct sockaddr bindaddr; + SocketT *sock; + + // make sure in valid state + if ((ref->state != ST_IDLE) || (ref->socket != NULL)) + { + return(COMM_BADSTATE); + } + + // setup bind points + SockaddrInit(&bindaddr, AF_INET); + + // parse at least port + if ((SockaddrInParse2(&uPokeAddr, &iListenPort, &iConnPort, addr) & 0x2) != 0x2) + { + return(COMM_BADADDRESS); + } + SockaddrInSetPort(&bindaddr, iListenPort); + + // reset to unresolved + _CommSRPResetTransfer(ref); + + // create socket in case its needed + sock = SocketOpen(AF_INET, SOCK_DGRAM, 0); + _CommSRPSetSocket(ref, sock); + if (ref->socket == NULL) + { + return(COMM_NORESOURCE); + } + + // bind locally + #if COMMSRP_VERBOSE + NetPrintf(("commsrp: binding to port %d\n", SockaddrInGetPort(&bindaddr))); + #endif + if ((err = SocketBind(ref->socket, &bindaddr, sizeof(bindaddr))) < 0) + { + NetPrintf(("commsrp: error %d binding socket\n", err)); + SocketClose(ref->socket); + _CommSRPSetSocket(ref, NULL); + return(COMM_UNEXPECTED); + } + + // setup for callbacks + SocketCallback(ref->socket, CALLB_RECV, 100, ref, &_CommSRPEvent); + + // see if we should setup peer address + if (uPokeAddr != 0) + { + if (iConnPort == 0) + { + iConnPort = iListenPort+1; + } + + SockaddrInit(&ref->peeraddr, AF_INET); + SockaddrInSetAddr(&ref->peeraddr, uPokeAddr); + SockaddrInSetPort(&ref->peeraddr, iConnPort); + } + + // put into init mode + ref->state = ST_LIST; + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function CommSRPConnect + + \Description + Initiate a connection to a peer + + \Input *ref - reference pointer + \Input *pAddr - address in ip-address:port form + + \Output + int32_t - negative=error, zero=ok + + \Notes + Does not perform dns translation + + \Version 01/03/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t CommSRPConnect(CommSRPRef *ref, const char *pAddr) +{ + struct sockaddr bindaddr; + int32_t iErr, iConnPort, iListenPort; + uint32_t uAddr; + SocketT *sock; + + // make sure in valid state + if ((ref->state != ST_IDLE) || (ref->socket != NULL)) + { + return(COMM_BADSTATE); + } + + // parse address and port from addr string + if ((SockaddrInParse2(&uAddr, &iListenPort, &iConnPort, pAddr) & 0x3) != 0x3) + { + return(COMM_BADADDRESS); + } + + // if we don't have an alternate connect port: connect=listen, listen=listen+1 + if (iConnPort == 0) + { + iConnPort = iListenPort++; + } + + // reset to unresolved + _CommSRPResetTransfer(ref); + + // create the actual socket + sock = SocketOpen(AF_INET, SOCK_DGRAM, 0); + _CommSRPSetSocket(ref, sock); + if (ref->socket == NULL) + { + return(COMM_NORESOURCE); + } + + // set listen port + SockaddrInit(&bindaddr, AF_INET); + SockaddrInSetPort(&bindaddr, iListenPort); + + // bind to port + #if COMMSRP_VERBOSE + NetPrintf(("commsrp: binding to port %d\n", iListenPort)); + #endif + if ((iErr = SocketBind(ref->socket, &bindaddr, sizeof(bindaddr))) < 0) + { + NetPrintf(("commsrp: error %d binding socket\n", iErr)); + return(COMM_UNEXPECTED); + } + + // setup target info + SockaddrInit(&ref->peeraddr, AF_INET); + SockaddrInSetAddr(&ref->peeraddr, uAddr); + SockaddrInSetPort(&ref->peeraddr, iConnPort); + + // setup for callbacks + SocketCallback(ref->socket, CALLB_RECV, 100, ref, &_CommSRPEvent); + + // change the state + ref->state = ST_CONN; + return(0); +} diff --git a/src/thirdparty/dirtysdk/source/comm/commudp.c b/src/thirdparty/dirtysdk/source/comm/commudp.c new file mode 100644 index 00000000..5315f3e0 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/comm/commudp.c @@ -0,0 +1,2665 @@ +/*H*************************************************************************************************/ +/*! + + \File commudp.c + + \Description + This is a reliable UDP transport class optimized for use in a + controller passing game applications. When the actual data + bandwidth is low (as is typical with controller passing), it + sends redundant data in order to quickly recover from any lost + packets. Overhead is low adding only 8 bytes per packet in + addition to UDP/IP overhead. This module support mutual + connects in order to allow connections through firewalls. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 1999-2003. ALL RIGHTS RESERVED. + + \Version 0.1 02/09/99 (GWS) First Version + \Version 0.2 02/14/99 (GWS) Revised and enhanced + \Version 0.5 02/14/99 (GWS) Alpha release + \Version 1.0 07/30/99 (GWS) Final release + \Version 2.0 10/27/99 (GWS) Revised to use winsock 1.1/2.0 + \Version 2.1 12/04/99 (GWS) Removed winsock 1.1 support + \Version 2.2 01/12/00 (GWS) Fixed receive tick bug + \Version 2.3 06/12/00 (GWS) Added fastack for low-latency nets + \Version 2.4 12/04/00 (GWS) Added firewall penetrator + \Version 3.0 12/04/00 (GWS) Reported to dirtysock + \Version 3.1 11/20/02 (JLB) Added Send() flags parameter + \Version 3.2 02/18/03 (JLB) Fixes for multiple connection support + \Version 3.3 05/06/03 (GWS) Allowed poke to come from any IP + \Version 3.4 09/02/03 (JLB) Added unreliable packet type + \Version 4.0 09/12/03 (JLB) Per-send optional unreliable transport + \Version 5.0 07/07/09 (jrainy) Putting meta-data bits over the high bits of the sequence number +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/comm/commall.h" +#include "DirtySDK/comm/commudp.h" +#include "DirtySDK/comm/commudputil.h" + +/*** Defines ***************************************************************************/ + +#undef COMM_PRINT +#define COMM_PRINT (0) + +#if defined(DIRTYCODE_PC) + #define ESC_CAUSES_LOSS (DIRTYCODE_DEBUG && 0) +#else + #define ESC_CAUSES_LOSS (0) +#endif + +#if ESC_CAUSES_LOSS +#include +#endif + +#define BUSY_KEEPALIVE (100) +#define IDLE_KEEPALIVE (2500) +#define PENETRATE_RATE (1000) +#define UNACK_LIMIT (2048) + +//! max additional space needed by a commudp meta type +#define COMMUDP_MAX_METALEN (8) + +#define REDUNDANT_LIMIT_DEFAULT (64) + +#define COMMUDP_VERSION_1_0 (0x0100) +#define COMMUDP_VERSION_1_1 (0x0101) +#define COMMUDP_VERSION (COMMUDP_VERSION_1_1) + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! raw protocol packet format +typedef struct +{ + //! packet header which is not sent/received (this data is used internally) + struct { + int32_t iLen; //!< variable data len (-1=none) (used int32_t for alignment) + uint32_t uWhen; //!< tick when a packet was received + uint32_t uMeta; //!< four-bit metadata field extracted from seq field + } head; + //! packet body which is sent/received + struct { + uint32_t uSeq; //!< packet type or sequence number + uint32_t uAck; //!< acknowledgement of last packet + uint8_t aData[SOCKET_MAXUDPRECV-8];//!< user data + } body; +} RawUDPPacketT; + +//! raw protocol packet header -- used for stack local formatting of handshake packets, and packets with no data +typedef struct +{ + //! packet header which is not sent/received (this data is used internally) + struct { + int32_t iLen; //!< variable data len (-1=none) (used int32_t for alignment) + uint32_t uWhen; //!< tick when a packet was received + uint32_t uMeta; //!< four-bit metadata field extracted from seq field + } head; + //! packet body which is sent/received + struct { + uint32_t uSeq; //!< packet type or seqeunce number + uint32_t uAck; //!< acknowledgement of last packet + uint32_t uCid; //!< client id (v1.0: in INIT/POKE packets; v1.1+: in INIT/POKE/CONN packets) + uint8_t aVers[2]; //!< protocol version (v1.1+ only) + uint8_t aData[62]; //!< space for possible metadata included in control packets + } body; +} RawUDPPacketHeadT; + +//! private module storage +struct CommUDPRef +{ + //! common header is first + CommRef Common; + + //! max amount of unacknowledged data that can be sent in one go (default 2k) + uint32_t uUnackLimit; + + //! max amount of data that can be sent redundantly (default = REDUNDANT_LIMIT_DEFAULT) + uint32_t uRedundantLimit; + + //! linked list of all instances + CommUDPRef *pLink; + //! comm socket + SocketT *pSocket; + //! peer address + struct sockaddr PeerAddr; + + //! port state + enum { + DEAD, //!< dead + IDLE, //!< idle + CONN, //!< conn + LIST, //!< list + OPEN, //!< open + CLOSE //!< close + } eState; + + //! identifier to keep from getting spoofed + uint32_t uConnIdent; + + //! type of metachunk to include in stream (zero=none) + uint32_t uMetaType; + + //! protocol version + uint16_t uVers; + uint16_t _pad; + + //! unique client identifier (used for game server identification) + uint32_t uClientIdent; + //! remote client identifier + uint32_t uRemClientIdent; + + //! width of receive records (same as width of send) + int32_t iRcvWid; + //! length of receive buffer (multiple of rcvwid) + int32_t iRcvLen; + //! fifo input offset + int32_t iRcvInp; + //! fifo output offset + int32_t iRcvOut; + //! pointer to buffer storage + char *pRcvBuf; + //! next packet expected (sequence number) + uint32_t uRcvSeq; + //! next unreliable packet expected + uint32_t uUnreliableRcvSeq; + //! last packet we acknowledged + uint32_t uRcvAck; + //! number of unacknowledged received bytes + int32_t iRcvUnack; + + //! width of send record (same as width of receive) + int32_t iSndWid; + //! length of send buffer (multiple of sndwid) + int32_t iSndLen; + //! fifo input offset + int32_t iSndInp; + //! fifo output offset + int32_t iSndOut; + //! current output point within fifo + int32_t iSndNxt; + //! pointer to buffer storage + char *pSndBuf; + //! next packet to send (sequence number) + uint32_t uSndSeq; + //! unreliable packet sequence number + uint32_t uUnreliableSndSeq; + //! last send result + uint32_t uSndErr; + + //! tick at which last packet was sent + uint32_t uSendTick; + //! tick at which last reliable packet was sent (used for resend tracking) + uint32_t uSendReliableTick; + //! tick at which last packet was received + uint32_t uRecvTick; + //! tick at which last idle callback made + uint32_t uIdleTick; + + //! control access during callbacks + volatile int32_t iCallback; + //! indicate there is an event pending + uint32_t uGotEvent; + //! callback routine pointer + void (*pCallProc)(void *pRef, int32_t iEvent); +}; + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +//! linked list of port objects +static CommUDPRef *g_link = NULL; + +//! semaphore to synchronize thread access +static NetCritT g_crit; + +//! missed event marker +static int32_t g_missed; + +//! variable indicates call to _CommUDPEvent() in progress +static int32_t g_inevent; + +#if DIRTYCODE_LOGGING +static const char *g_strConnNames[] = { "COMMUDP_RAW_PACKET_INVALID", "COMMUDP_RAW_PACKET_INIT", "COMMUDP_RAW_PACKET_CONN", "COMMUDP_RAW_PACKET_DISC", "COMMUDP_RAW_PACKET_NAK", "COMMUDP_RAW_PACKET_POKE" }; +#endif + +// Public variables + + +/*** Private Functions *****************************************************************/ + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPSeqnDelta + + \Description + Compute the sequence number off of uPos by iDelta places + Can NOT be used for unreliable sequence offsetting + + \Input uPos - starting position + \Input iDelta - offset + + \Output + uint32_t - resulting position + + \Version 07/07/09 (jrainy) +*/ +/*************************************************************************************************F*/ +static uint32_t _CommUDPSeqnDelta(uint32_t uPos, int32_t iDelta) +{ + return(((uPos + iDelta + COMMUDP_RAW_PACKET_DATA_WINDOW - COMMUDP_RAW_PACKET_DATA) % COMMUDP_RAW_PACKET_DATA_WINDOW) + COMMUDP_RAW_PACKET_DATA); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPSeqnDiff + + \Description + Compute the difference in position between two sequence numbers + Can NOT be used to compute unreliable sequence differences + + \Input uPos1 - source position + \Input uPos2 - target position + + \Output + int32_t - signed difference in position + + \Version 07/07/09 (jrainy) +*/ +/*************************************************************************************************F*/ +static int32_t _CommUDPSeqnDiff(uint32_t uPos1, uint32_t uPos2) +{ + return((((uPos1 - uPos2) + (3 * COMMUDP_RAW_PACKET_DATA_WINDOW / 2)) % COMMUDP_RAW_PACKET_DATA_WINDOW) - (COMMUDP_RAW_PACKET_DATA_WINDOW / 2)); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPSetAddrInfo + + \Description + Sets peer/host addr/port info in common ref. + + \Input *pRef - reference pointer + \Input *pSin - address pointer + + \Version 04/16/04 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommUDPSetAddrInfo(CommUDPRef *pRef, struct sockaddr *pSin) +{ + // save peer addr/port info in common ref + pRef->Common.peerip = SockaddrInGetAddr(pSin); + pRef->Common.peerport = SockaddrInGetPort(pSin); + NetPrintf(("commudp: [%p] peer=%a:%d\n", pRef, pRef->Common.peerip, pRef->Common.peerport)); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPSetSocket + + \Description + Sets socket in ref and socketref in common portion of ref. + + \Input *pRef - reference pointer + \Input *pSocket - socket to set + + \Version 08/24/04 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommUDPSetSocket(CommUDPRef *pRef, SocketT *pSocket) +{ + pRef->pSocket = pSocket; + pRef->Common.sockptr = pSocket; + if (pSocket != NULL) + { + struct sockaddr SockAddr; + // save host addr/port info in common ref + SocketInfo(pRef->pSocket, 'bind', 0, &SockAddr, sizeof(SockAddr)); + pRef->Common.hostip = SocketGetLocalAddr(); + pRef->Common.hostport = SockaddrInGetPort(&SockAddr); + NetPrintf(("commudp: [%p] host=%a:%d\n", pRef, pRef->Common.hostip, pRef->Common.hostport)); + } + else + { + pRef->Common.hostip = 0; + pRef->Common.hostport = 0; + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPSetConnID + + \Description + Sets connident to the 32bit hash of the specified connection identifier string, if any. + + \Input *pRef - reference pointer + \Input *pStrConn - pointer to user-specified connection string + + \Version 06/16/04 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommUDPSetConnID(CommUDPRef *pRef, const char *pStrConn) +{ + const char *pConnID = strchr(pStrConn, '#'); + if (pConnID != NULL) + { + pRef->uConnIdent = NetHash(pConnID+1); + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPResetTransfer + + \Description + Reset the transfer state + + \Input *pRef - reference pointer + + \Version 12/04/00 (GWS) +*/ +/*************************************************************************************************F*/ +static void _CommUDPResetTransfer(CommUDPRef *pRef) +{ + // reset packet received bool + pRef->Common.bpackrcvd = FALSE; + // reset the send queue + pRef->iSndInp = 0; + pRef->iSndOut = 0; + pRef->iSndNxt = 0; + // reset the sequence number + pRef->uSndSeq = COMMUDP_RAW_PACKET_DATA; + pRef->uUnreliableSndSeq = COMMUDP_RAW_PACKET_UNREL; + // reset the receive queue + pRef->iRcvInp = 0; + pRef->iRcvOut = 0; + // reset the packet sequence number + pRef->uRcvSeq = COMMUDP_RAW_PACKET_DATA; + pRef->uUnreliableRcvSeq = COMMUDP_RAW_PACKET_UNREL; + + // no unack data + pRef->iRcvUnack = 0; + + // make sendtick really old (in protocol terms) + pRef->uSendTick = pRef->uSendReliableTick = NetTick()-5000; + // recvtick must be older than tick because the first + // packet that arrives may come in moments before this + // initialization takes place and without this adjustment + // code can compute an elapsed receive time of 0xffffffff] + pRef->uRecvTick = NetTick()-5000; +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPOverhead + + \Description + Computes the bandwidth overhead associated with a packet of length packetLength + + \Input *pRef - module reference + \Input iPktLen - length of the packet we are about to send + + \Output + int32_t - the associated overhead. 28 on most platforms, but higher on xbox360. + + \Version 01/08/07 (JRainy) + +*/ +/*************************************************************************************************F*/ +static int32_t _CommUDPOverhead(CommUDPRef *pRef, int32_t iPktLen) +{ + // start with basic IP+UDP header size + int32_t iOverhead = 28; + return(iOverhead); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPWrite + + \Description + Send a packet to the peer + + \Input *pRef - reference pointer + \Input *pPacket - packet pointer + \Input *pPeerAddr - address of peer to send to + \Input uCurrTick - current tick + + \Output + int32_t - negative=error, zero=ok + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +static int32_t _CommUDPWrite(CommUDPRef *pRef, RawUDPPacketT *pPacket, struct sockaddr *pPeerAddr, uint32_t uCurrTick) +{ + int32_t iErr; + int32_t iLen; + + // figure full packet length (nak/ack fields + variable data) + iLen = sizeof(pPacket->body)-sizeof(pPacket->body.aData)+pPacket->head.iLen; + + // fill in metatype info + if (pRef->uMetaType == 1) + { + int32_t iMetaOffset = ((pPacket->body.uSeq == COMMUDP_RAW_PACKET_INIT) || (pPacket->body.uSeq == COMMUDP_RAW_PACKET_POKE)) ? 4 : 0; + // make room for metadata + memmove(&pPacket->body.aData[COMMUDP_RAW_METATYPE1_SIZE+iMetaOffset], &pPacket->body.aData[0+iMetaOffset], iLen-iMetaOffset); + iLen += COMMUDP_RAW_METATYPE1_SIZE; + // metatype1 src clientident + pPacket->body.aData[0+iMetaOffset] = (uint8_t)(pRef->uClientIdent >> 24); + pPacket->body.aData[1+iMetaOffset] = (uint8_t)(pRef->uClientIdent >> 16); + pPacket->body.aData[2+iMetaOffset] = (uint8_t)(pRef->uClientIdent >> 8); + pPacket->body.aData[3+iMetaOffset] = (uint8_t)(pRef->uClientIdent); + // metatype1 dst clientident + pPacket->body.aData[4+iMetaOffset] = (uint8_t)(pRef->uRemClientIdent >> 24); + pPacket->body.aData[5+iMetaOffset] = (uint8_t)(pRef->uRemClientIdent >> 16); + pPacket->body.aData[6+iMetaOffset] = (uint8_t)(pRef->uRemClientIdent >> 8); + pPacket->body.aData[7+iMetaOffset] = (uint8_t)(pRef->uRemClientIdent); + // set metatype header + pPacket->body.uSeq |= (pRef->uMetaType & 0xf) << COMMUDP_SEQ_META_SHIFT; + } + + #if COMM_PRINT > 1 + NetPrintf(("commudp: [%p] seq:0x%08x ack:0x%08x send %d bytes to %a:%d\n", pRef, pPacket->body.uSeq, pPacket->body.uAck, iLen, SockaddrInGetAddr(pPeerAddr), SockaddrInGetPort(pPeerAddr))); + #endif + #if COMM_PRINT > 2 + NetPrintMem(&pPacket->body, iLen, "cudp-send"); + #endif + + // translate seq and ack to network order for send + pPacket->body.uSeq = SocketHtonl(pPacket->body.uSeq); + pPacket->body.uAck = SocketHtonl(pPacket->body.uAck); + + // store send time in misc field of sockaddr + SockaddrInSetMisc(pPeerAddr, uCurrTick); + + #if ESC_CAUSES_LOSS + // lose packets when escape is pressed + if (GetAsyncKeyState(VK_ESCAPE) < 0) + { + NetPrintf(("commudp: [%p] dropping packet to simulate packet loss (seq=0x%08x)\n", pRef, SocketNtohl(pPacket->body.uSeq))); + iErr = iLen; + } + else + { + // send the packet + iErr = SocketSendto(pRef->pSocket, (char *)&pPacket->body, iLen, 0, pPeerAddr, sizeof(*pPeerAddr)); + } + #else + // send the packet + iErr = SocketSendto(pRef->pSocket, (char *)&pPacket->body, iLen, 0, pPeerAddr, sizeof(*pPeerAddr)); + #endif + + + // translate seq and ack back to host order + pPacket->body.uSeq = SocketNtohl(pPacket->body.uSeq); + pPacket->body.uAck = SocketNtohl(pPacket->body.uAck); + + // check for success + if (iErr == iLen) + { + // update last send time + pRef->uSendTick = uCurrTick; + + // do stats + if (pRef->eState == OPEN) + { + pRef->Common.datasent += iLen; + pRef->Common.packsent += 1; + } + + pRef->Common.overhead += _CommUDPOverhead(pRef, iLen); + + // is the packet reliable? + if ((pPacket->body.uSeq & COMMUDP_SEQ_MASK) >= COMMUDP_RAW_PACKET_DATA) + { + // we assume any reliable send includes an up to date ack value + // which means that we can reset the unacked data count to zero + pRef->iRcvUnack = 0; + // update reliable send timer + pRef->uSendReliableTick = uCurrTick; + } + } + else + { + NetPrintf(("commudp: [%p] SocketSendto() returned %d\n", pRef, iErr)); + pRef->uSndErr = iErr; + iErr = -1; + } + + return(iErr); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPClose + + \Description + Close the connection + + \Input *pRef - reference pointer + \Input uCurrTick- current tick + + \Output + int32_t - negative=error, zero=ok + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +static int32_t _CommUDPClose(CommUDPRef *pRef, uint32_t uCurrTick) +{ + RawUDPPacketHeadT Packet; + + // if we're open, shut down connection + if (pRef->eState == OPEN) + { + // see if any output data pending + if (pRef->iSndNxt != pRef->iSndInp) + { + NetPrintf(("commudp: [%p] unsent data pending\n", pRef)); + } + else if (pRef->iSndOut != pRef->iSndInp) + { + NetPrintf(("commudp: [%p] unacked data pending\n", pRef)); + } + + // send a disconnect message + Packet.head.iLen = 0; + Packet.body.uSeq = COMMUDP_RAW_PACKET_DISC; + Packet.body.uAck = pRef->uConnIdent; + _CommUDPWrite(pRef, (RawUDPPacketT *)&Packet, &pRef->PeerAddr, uCurrTick); + } + + // set to disconnect state + NetPrintf(("commudp: [%p] closed connection\n", pRef)); + pRef->uConnIdent = 0; + pRef->eState = CLOSE; + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPFormatHandshake + + \Description + Format handshake packet + + \Input *pRef - reference pointer + \Input *pPacket - [out] buffer to format handshake packet into + \Input uKind - packet kind (COMMUDP_RAW_PACKET_INIT, COMMUDP_RAW_PACKET_POKE, COMMUDP_RAW_PACKET_CONN) + + \Version 10/29/2015 (jbrookes) +*/ +/*************************************************************************************************F*/ +static void _CommUDPFormatHandshake(CommUDPRef *pRef, RawUDPPacketHeadT *pPacket, uint32_t uKind) +{ + pPacket->body.uSeq = uKind; + pPacket->body.uAck = pRef->uConnIdent; + #if COMMUDP_VERSION > COMMUDP_VERSION_1_0 + pPacket->body.uCid = SocketHtonl(pRef->uClientIdent); + pPacket->body.aVers[0] = COMMUDP_VERSION>>8; + pPacket->body.aVers[1] = COMMUDP_VERSION&0x0f; + pPacket->head.iLen = 6; + #else + if ((kind == COMMUDP_RAW_PACKET_INIT) || (kind == COMMUDP_RAW_PACKET_POKE)) + { + pPacket->body.cid = SocketHtonl(pRef->clientident); + pPacket->head.iLen = 4; + } + else + { + pPacket->head.iLen = 0; + } + #endif +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPProcessSetup + + \Description + Process a setup/teardown request + + \Input *pRef - reference pointer + \Input *pPacket - requesting packet + \Input *pSin - address + \Input uCurrTick- current tick + + \Version 12/04/00 (GWS) +*/ +/*************************************************************************************************F*/ +static void _CommUDPProcessSetup(CommUDPRef *pRef, const RawUDPPacketT *pPacket, struct sockaddr *pSin, uint32_t uCurrTick) +{ + // make sure the session identifier matches + if (pPacket->body.uAck != pRef->uConnIdent) + { + NetPrintf(("commudp: [%p] warning - connident mismatch (expected=0x%08x got=0x%08x)\n", pRef, pRef->uConnIdent, pPacket->body.uAck)); + // an init packet with a different session identifier + // indicates that the old session has closed + if (pPacket->body.uSeq == COMMUDP_RAW_PACKET_INIT) + { + pRef->eState = CLOSE; + } + return; + } + + // update valid receive time -- must put into past to avoid race condition + pRef->uRecvTick = uCurrTick-1000; + + // version identification + #if COMMUDP_VERSION > COMMUDP_VERSION_1_0 + if ((pRef->uVers == 0) && ((pPacket->body.uSeq == COMMUDP_RAW_PACKET_INIT) || (pPacket->body.uSeq == COMMUDP_RAW_PACKET_CONN) || (pPacket->body.uSeq == COMMUDP_RAW_PACKET_POKE))) + { + RawUDPPacketHeadT *hshk = (RawUDPPacketHeadT *)pPacket; + pRef->uVers = (pPacket->head.iLen > 4) ? (hshk->body.aVers[0] << 8) | hshk->body.aVers[1] : COMMUDP_VERSION_1_0; + NetPrintf(("commudp: [%p] vers=%d.%d\n", pRef, pRef->uVers >> 8, pRef->uVers & 0xff)); + } + #else + pRef->uVers = COMMUDP_VERSION_1_0; + #endif + + // response to connection/poke query + if ((pPacket->body.uSeq == COMMUDP_RAW_PACKET_INIT) || (pPacket->body.uSeq == COMMUDP_RAW_PACKET_POKE)) + { + RawUDPPacketHeadT connpacket; + + // set host/peer addr/port info + _CommUDPSetAddrInfo(pRef, pSin); + + // send CONN in response to INIT/POKE + NetPrintf(("commudp: [%p] sending CONN packet to %a:%d connident=0x%08x\n", pRef, SockaddrInGetAddr(&pRef->PeerAddr), + SockaddrInGetPort(&pRef->PeerAddr), pRef->uConnIdent)); + _CommUDPFormatHandshake(pRef, &connpacket, COMMUDP_RAW_PACKET_CONN); + _CommUDPWrite(pRef, (RawUDPPacketT *)&connpacket, &pRef->PeerAddr, uCurrTick); + return; + } + + // response to a connect confirmation + if (pPacket->body.uSeq == COMMUDP_RAW_PACKET_CONN) + { + // change to open if not already there + if (pRef->eState == CONN) + { + // set host/peer addr/port info + _CommUDPSetAddrInfo(pRef, pSin); + NetPrintf(("commudp: [%p] transitioning to OPEN state due to received COMMUDP_RAW_PACKET_CONN\n", pRef)); + pRef->eState = OPEN; + } + return; + } + + // response to disconnect message + if (pPacket->body.uSeq == COMMUDP_RAW_PACKET_DISC) + { + // close the connection + if (pRef->eState == OPEN) + { + pRef->eState = CLOSE; + } + NetPrintf(("commudp: [%p] received DISC packet\n", pRef)); + } + + // should not get here +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPProcessInit + + \Description + Initiate a connection + + \Input *pRef - reference pointer + \Input uCurrTick- current tick + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +static void _CommUDPProcessInit(CommUDPRef *pRef, uint32_t uCurrTick) +{ + RawUDPPacketHeadT Packet; + + // send INIT to peer + NetPrintf(("commudp: [%p] sending INIT packet to %a:%d connident=0x%08x clientident=0x%08x\n", pRef, SockaddrInGetAddr(&pRef->PeerAddr), + SockaddrInGetPort(&pRef->PeerAddr), pRef->uConnIdent, pRef->uClientIdent)); + _CommUDPFormatHandshake(pRef, &Packet, COMMUDP_RAW_PACKET_INIT); + _CommUDPWrite(pRef, (RawUDPPacketT *)&Packet, &pRef->PeerAddr, uCurrTick); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPProcessOutput + + \Description + Send data packet(s) + + \Input *pRef - reference pointer + \Input uCurrTick- current tick + + \Version 12/04/00 (GWS) +*/ +/*************************************************************************************************F*/ +static void _CommUDPProcessOutput(CommUDPRef *pRef, uint32_t uCurrTick) +{ + int32_t iIndex, iCount; + int32_t iTLimit = pRef->uUnackLimit, iPLimit; + static RawUDPPacketT Multi; + RawUDPPacketT *pBuffer; + static uint32_t uMLimit = 0x20000000; + int32_t iMetaLen = CommUDPUtilGetMetaSize(pRef->uMetaType); + int32_t iSubpacketLimit = (pRef->uVers > COMMUDP_VERSION_1_0) ? 1530 : 250; + + // figure unacked data length + for (iIndex = pRef->iSndOut; iIndex != pRef->iSndNxt; iIndex = (iIndex+pRef->iSndWid)%pRef->iSndLen) { + pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[iIndex]; + // count down the limit + iTLimit -= pBuffer->head.iLen; + } + + // allow a minimum send rate (256 bytes per 250 ms) + if ((iTLimit < 256) && (uCurrTick-pRef->uSendTick > 250)) + iTLimit = 256; + + // send as much data as limit allows + while (iTLimit > 0) { + + // limit size of forward packet consolidation + iPLimit = SocketInfo(NULL, 'maxp', 0, NULL, 0) - iMetaLen - sizeof(Multi.body) + sizeof(Multi.body.aData); + + if (iPLimit > iTLimit) + iPLimit = iTLimit; + + // attempt forward consolidation of packets + for (iCount = 0; (iCount < 8) && (iPLimit > 0) && (pRef->iSndNxt != pRef->iSndInp); ++iCount) { + pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[pRef->iSndNxt]; + iPLimit -= pBuffer->head.iLen; + iPLimit -= CommUDPUtilEncodeSubpacketSize(NULL, pBuffer->head.iLen); + // if not the first packet, then we must be careful about size + if ((iCount > 0) && (iPLimit <= 0)) + break; + pRef->iSndNxt = (pRef->iSndNxt+pRef->iSndWid) % pRef->iSndLen; + // if packet is too large for subpacket encoding, it must be final packet (first in multisend) + if (pBuffer->head.iLen > iSubpacketLimit) { + ++iCount; + break; + } + } + if (iCount == 0) + return; + + // setup main packet + iIndex = (pRef->iSndNxt+pRef->iSndLen-pRef->iSndWid)%pRef->iSndLen; + pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[iIndex]; + // if they want a callback, do it now + if (pRef->Common.SendCallback != NULL) + pRef->Common.SendCallback((CommRef *)pRef, pBuffer->body.aData, pBuffer->head.iLen, uCurrTick); + ds_memcpy(&Multi, pBuffer, sizeof(pBuffer->head)+8+pBuffer->head.iLen); + iCount -= 1; + + // add in required preceding packets + for (; iCount > 0; --iCount) { + // move to preceding packet + iIndex = (iIndex+pRef->iSndLen-pRef->iSndWid)%pRef->iSndLen; + // point to potential piggyback packet + pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[iIndex]; + // if they want a callback, do it now + if (pRef->Common.SendCallback != NULL) + pRef->Common.SendCallback((CommRef *)pRef, pBuffer->body.aData, pBuffer->head.iLen, uCurrTick); + // combine the packets + Multi.body.uSeq += COMMUDP_SEQ_MULTI_INC; + ds_memcpy(Multi.body.aData+Multi.head.iLen, pBuffer->body.aData, pBuffer->head.iLen); + Multi.head.iLen += pBuffer->head.iLen; + Multi.head.iLen += CommUDPUtilEncodeSubpacketSize(Multi.body.aData+Multi.head.iLen, pBuffer->head.iLen); + } + + // add in optional redundant packets + while ((iIndex != pRef->iSndOut) && (Multi.body.uSeq <= uMLimit)) { + // move to preceding packet + iIndex = (iIndex+pRef->iSndLen-pRef->iSndWid)%pRef->iSndLen; + // point to potential piggyback packet + pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[iIndex]; + // if packet is too large for subpacket encoding, we cannot multi-send it + if (pBuffer->head.iLen > iSubpacketLimit) + break; + // see if combined length would be a problem + if ((Multi.head.iLen + pBuffer->head.iLen + 1) > (signed)pRef->uRedundantLimit) + break; + // if they want a callback, do it now + if (pRef->Common.SendCallback != NULL) + pRef->Common.SendCallback((CommRef *)pRef, pBuffer->body.aData, pBuffer->head.iLen, uCurrTick); + // combine the packets + Multi.body.uSeq += COMMUDP_SEQ_MULTI_INC; + ds_memcpy(Multi.body.aData+Multi.head.iLen, pBuffer->body.aData, pBuffer->head.iLen); + Multi.head.iLen += pBuffer->head.iLen; + Multi.head.iLen += CommUDPUtilEncodeSubpacketSize(Multi.body.aData+Multi.head.iLen, pBuffer->head.iLen); + } + + // adjust the max redundancy + if (iIndex == pRef->iSndOut) { + uMLimit = 0x20000000; + } else { + uMLimit = (uMLimit < 0x80000000 ? uMLimit*2 : 0xf0000000); + } + + // update the ack value (one less than one we are waiting for) + pRef->uRcvAck = pRef->uRcvSeq; + Multi.body.uAck = _CommUDPSeqnDelta(pRef->uRcvSeq, -1); + // go ahead and send the packet + if (_CommUDPWrite(pRef, &Multi, &pRef->PeerAddr, uCurrTick) < 0) + { + return; + } + // count the bandwidth for this packet + iTLimit -= Multi.head.iLen; + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPProcessFlow + + \Description + Perform flow control based on ack/nak packets + + \Input *pRef - reference pointer + \Input *pPacket - incoming packet header + \Input uCurrTick- current tick + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +static void _CommUDPProcessFlow(CommUDPRef *pRef, RawUDPPacketHeadT *pPacket, uint32_t uCurrTick) +{ + int32_t iNak; + uint32_t uAck; + + // grab the ack point and nak flag + iNak = (pPacket->body.uSeq == COMMUDP_RAW_PACKET_NAK); + + if (pPacket->body.uAck < COMMUDP_RAW_PACKET_DATA) + { + uAck = (iNak ? pPacket->body.uAck-1 : pPacket->body.uAck); + } + else + { + uAck = (iNak ? _CommUDPSeqnDelta(pPacket->body.uAck, -1) : pPacket->body.uAck); + } + + // advance ack point + while (pRef->iSndOut != pRef->iSndInp) + { + RawUDPPacketT *pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[pRef->iSndOut]; + // see if this packet has been acked + if (((uAck & COMMUDP_SEQ_MASK) < COMMUDP_RAW_PACKET_DATA) || + (_CommUDPSeqnDiff(uAck, pBuffer->body.uSeq) < 0)) + { + break; + } + + // if about to send this packet, skip to next + if (pRef->iSndNxt == pRef->iSndOut) + pRef->iSndNxt = (pRef->iSndNxt+pRef->iSndWid) % pRef->iSndLen; + // remove the packet from the queue + pRef->iSndOut = (pRef->iSndOut+pRef->iSndWid) % pRef->iSndLen; + } + + // reset send point for nak + if (iNak) + { + #if COMM_PRINT + NetPrintf(("commudp: [%p] got NAK for packet %d\n", pRef, uAck+1)); + #endif + // reset send point + pRef->iSndNxt = pRef->iSndOut; + // immediate restart + _CommUDPProcessOutput(pRef, uCurrTick); + } +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPProcessInput + + \Description + Process incoming data packet + + \Input *pRef - module state + \Input *pPacket - incoming packet header + \Input *pData - pointer to packet data + \Input uCurrTick- current network tick + + \Output + int32_t - -1=nak, 0=old, 1=new, 2=buffer full + + \Version 12/04/2000 (gschaefer) +*/ +/*************************************************************************************************F*/ +static int32_t _CommUDPProcessInput(CommUDPRef *pRef, RawUDPPacketHeadT *pPacket, uint8_t *pData, uint32_t uCurrTick) +{ + RawUDPPacketT *pBuffer; + + // see if room in buffer for packet + if ((pRef->iRcvInp+pRef->iRcvWid)%pRef->iRcvLen == pRef->iRcvOut) + { + // no room in buffer -- just drop packet + // could nak, but would generate lots of + // network activity with no result + NetPrintf(("commudp: [%p] input buffer overflow\n", pRef)); + return(2); + } + + // handle unreliable receive + if (((pPacket->body.uSeq & COMMUDP_SEQ_MASK) >= COMMUDP_RAW_PACKET_UNREL) && ((pPacket->body.uSeq & COMMUDP_SEQ_MASK) < COMMUDP_RAW_PACKET_DATA)) + { + // calculate the number of free packets in pRcvBuf + int32_t queuepos = ((pRef->iRcvInp+pRef->iRcvLen)-pRef->iRcvOut)%pRef->iRcvLen; + int32_t pktsfree = (pRef->iRcvLen - queuepos)/pRef->iRcvWid; + + // calculate delta between received sequence and expected sequence, accounting for wrapping + int32_t delta = (pPacket->body.uSeq - pRef->uUnreliableRcvSeq) & (COMMUDP_RAW_PACKET_UNREL - 1); + + // update lost packet count + if ((pPacket->body.uSeq > pRef->uUnreliableRcvSeq) || (delta < COMMUDP_RAW_PACKET_UNREL/4)) + { + pRef->Common.packlost += delta; + } + + // calculate new sequence number + if ((pRef->uUnreliableRcvSeq = (pPacket->body.uSeq + 1)) >= COMMUDP_RAW_PACKET_DATA) + { + pRef->uUnreliableRcvSeq = COMMUDP_RAW_PACKET_UNREL + (pRef->uUnreliableRcvSeq - COMMUDP_RAW_PACKET_DATA); + } + + // see if there is room to buffer (leave room for reliable packets) + if (pktsfree <= 4) + { + return(2); + } + } + else + { + // ignore old packets + if (_CommUDPSeqnDiff(pPacket->body.uSeq, pRef->uRcvSeq) < 0) + { + return(0); + } + + // immediate nak for missing packets + if (_CommUDPSeqnDiff(pPacket->body.uSeq, pRef->uRcvSeq) > 0) + { + // update lost packet count + pRef->Common.packlost += _CommUDPSeqnDiff(pPacket->body.uSeq, pRef->uRcvSeq); + + // send a nak packet + #if COMM_PRINT + NetPrintf(("commudp: [%p] sending a NAK of packet %d (tick=%u)\n", pRef, pRef->uRcvSeq, NetTick())); + #endif + pPacket->body.uSeq = COMMUDP_RAW_PACKET_NAK; + pPacket->body.uAck = pRef->uRcvSeq; + pPacket->head.iLen = 0; + _CommUDPWrite(pRef, (RawUDPPacketT *)pPacket, &pRef->PeerAddr, uCurrTick); + + // update number of NAKs sent + pRef->Common.naksent += 1; + + return(-1); + } + + // no further processing for empty (ack) packets + if (pPacket->head.iLen == 0) + { + return(0); + } + } + + // add the packet to the buffer + pBuffer = (RawUDPPacketT *) &pRef->pRcvBuf[pRef->iRcvInp]; + // copy the packet + ds_memcpy_s(pBuffer, sizeof(*pBuffer), pPacket, sizeof(*pPacket)); + ds_memcpy(pBuffer->body.aData, pData, pPacket->head.iLen); + + // limit receive access for callbacks + pRef->iCallback += 1; + // add item to receive buffer + pRef->iRcvInp = (pRef->iRcvInp+pRef->iRcvWid) % pRef->iRcvLen; + + // reliable specific processing + if ((pPacket->body.uSeq & COMMUDP_SEQ_MASK) >= COMMUDP_RAW_PACKET_DATA) + { + pRef->uRcvSeq = _CommUDPSeqnDelta(pRef->uRcvSeq, 1); + // add to unacknowledged byte count + pRef->iRcvUnack += pBuffer->head.iLen; + } + + // indicate we got an event + pRef->uGotEvent |= 1; + // let the callback process it + if (pRef->Common.RecvCallback != NULL) + pRef->Common.RecvCallback((CommRef *)pRef, pBuffer->body.aData, pBuffer->head.iLen, pBuffer->head.uWhen); + // release access to receive + pRef->iCallback -= 1; + return(1); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPProcessPoke + + \Description + Penetrate firewall with poke packet + + \Input *pRef - reference pointer + \Input uCurrTick- current tick + + \Version 07/07/00 (GWS) + +*/ +/*************************************************************************************************F*/ +static void _CommUDPProcessPoke(CommUDPRef *pRef, uint32_t uCurrTick) +{ + RawUDPPacketHeadT Packet; + + // send POKE to peer + NetPrintf(("commudp: [%p] sending POKE packet to %a:%d connident=0x%08x clientident=0x%08x\n", pRef, SockaddrInGetAddr(&pRef->PeerAddr), + SockaddrInGetPort(&pRef->PeerAddr), pRef->uConnIdent, pRef->uClientIdent)); + _CommUDPFormatHandshake(pRef, &Packet, COMMUDP_RAW_PACKET_POKE); + _CommUDPWrite(pRef, (RawUDPPacketT *)&Packet, &pRef->PeerAddr, uCurrTick); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPProcessAlive + + \Description + Send a keepalive packet + + \Input *pRef - reference pointer + \Input uCurrTick- current tick + + \Version 12/04/00 (GWS) +*/ +/*************************************************************************************************F*/ +static void _CommUDPProcessAlive(CommUDPRef *pRef, uint32_t uCurrTick) +{ + RawUDPPacketHeadT Packet; + + // see if we should resend most recent packet + if ((pRef->iSndOut != pRef->iSndInp) && (pRef->iSndNxt == pRef->iSndInp)) { + // this shound result in a multi-send + pRef->iSndNxt = (pRef->iSndNxt+pRef->iSndLen-pRef->iSndWid)%pRef->iSndLen; + _CommUDPProcessOutput(pRef, uCurrTick); + return; + } + + // set our packet number + Packet.body.uSeq = pRef->uSndSeq; + // acknowledge packet prior to one we are waiting for + pRef->uRcvAck = pRef->uRcvSeq; + Packet.body.uAck = _CommUDPSeqnDelta(pRef->uRcvSeq, -1); + + // no data means keepalive + Packet.head.iLen = 0; + // send it + #if COMM_PRINT + NetPrintf(("commudp: [%p] sending keep-alive\n", pRef)); + #endif + _CommUDPWrite(pRef, (RawUDPPacketT *)&Packet, &pRef->PeerAddr, uCurrTick); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPFlush + + \Description + Flush output buffer + + \Input *pRef - reference pointer + \Input uLimit - timeout in milliseconds + \Input uCurrTick- current tick + + \Output + uint32_t - number of packets in buffer + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +static uint32_t _CommUDPFlush(CommUDPRef *pRef, uint32_t uLimit, uint32_t uCurrTick) +{ + int32_t iNumPackets; + int32_t iIndex; + RawUDPPacketT *pBuffer; + + iNumPackets = (((pRef->iSndInp+pRef->iSndLen)-pRef->iSndOut)%pRef->iSndLen)/pRef->iSndWid; + NetPrintf(("commudp: [%p] flushing %d packets in send queue\n", pRef, iNumPackets)); + + for (iIndex = pRef->iSndOut; iIndex != pRef->iSndNxt; iIndex = (iIndex+pRef->iSndWid)%pRef->iSndLen) + { + pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[iIndex]; + _CommUDPWrite(pRef, pBuffer, &pRef->PeerAddr, uCurrTick); + } + + return(iNumPackets); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPThreadData + + \Description + Take care of data processing + + \Input uCurrTick- current tick + + \Output + int32_t - number of packets received + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +static int32_t _CommUDPThreadData(uint32_t uCurrTick) +{ + int32_t iLen; + int32_t iCount = 0; + CommUDPRef *pRef; + CommUDPRef *pListen = NULL; + struct sockaddr Sin; + static RawUDPPacketT Packet; + SocketT *pSocket; + uint32_t bConnIdentMatch=0; + uint32_t uLClientIdent=0, uRClientIdent=0; + + // init sockaddr (else unused bytes cause mismatch during ident) + SockaddrInit(&Sin, AF_INET); + + // scan sockets for incoming packet + Packet.head.iLen = -1; + pSocket = NULL; + for (pRef = g_link; pRef != NULL; pRef = pRef->pLink) + { + // make sure we need to scan this one + if ((pRef->pSocket != NULL) && (pRef->pSocket != pSocket)) + { + // see if data is pending + pSocket = pRef->pSocket; + iLen = sizeof(Sin); + iLen = SocketRecvfrom(pSocket, (char *)&Packet.body, sizeof(Packet.body), 0, &Sin, &iLen); + if (iLen > 0) + { + // track recieve overhead + pRef->Common.rcvoverhead += sizeof(Packet.body)-sizeof(Packet.body.aData) + _CommUDPOverhead(pRef, iLen); + pRef->Common.rcvoverhead += CommUDPUtilGetMetaSize(pRef->uMetaType); + // get length and timestamp the packet + Packet.head.iLen = iLen-(sizeof(Packet.body)-sizeof(Packet.body.aData)); + Packet.head.uWhen = SockaddrInGetMisc(&Sin); + // translate seq and ack to host order + Packet.body.uSeq = SocketNtohl(Packet.body.uSeq); + Packet.body.uAck = SocketNtohl(Packet.body.uAck); + // extract and clear meta bits + Packet.head.uMeta = CommUDPUtilGetMetaTypeFromSeq(Packet.body.uSeq); + Packet.body.uSeq &= ~(0x0f << COMMUDP_SEQ_META_SHIFT); + + #if COMM_PRINT > 1 + NetPrintf(("commudp: [%p] seq:0x%08x ack:0x%08x recv %d bytes from %a:%d (head.iLen=%d, meta=%d) at tick=%d\n", + pRef, Packet.body.uSeq, Packet.body.uAck, iLen, SockaddrInGetAddr(&Sin), SockaddrInGetPort(&Sin), Packet.head.iLen, Packet.head.uMeta, Packet.head.uWhen)); + #endif + #if COMM_PRINT > 2 + NetPrintMem(&Packet.body, iLen, "cudp-recv"); + #endif + + if (Packet.body.uSeq == COMMUDP_RAW_PACKET_CONN || (Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_POKE)) + { + NetPrintf(("commudp: [%p] got %s connident=0x%08x len=%d\n", pRef, g_strConnNames[Packet.body.uSeq], Packet.body.uAck, Packet.head.iLen)); + } + // count the packet + ++iCount; + + break; + } + } + } + + // if we have meta chunk use that for matching with our connection ref + if ((Packet.head.iLen >= COMMUDP_RAW_METATYPE1_SIZE) && (Packet.head.uMeta == 1)) + { + int32_t iMetaOffset = ((Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_POKE)) ? 4 : 0; + + // read meta type one chunk data + uRClientIdent = ((uint32_t)Packet.body.aData[0+iMetaOffset])<<24 | ((uint32_t)Packet.body.aData[1+iMetaOffset])<<16 | ((uint32_t)Packet.body.aData[2+iMetaOffset])<<8 | (uint32_t)Packet.body.aData[3+iMetaOffset]; + uLClientIdent = ((uint32_t)Packet.body.aData[4+iMetaOffset])<<24 | ((uint32_t)Packet.body.aData[5+iMetaOffset])<<16 | ((uint32_t)Packet.body.aData[6+iMetaOffset])<<8 | (uint32_t)Packet.body.aData[7+iMetaOffset]; + + // remove metadata from packet + Packet.head.iLen -= COMMUDP_RAW_METATYPE1_SIZE; + if ((Packet.head.iLen-iMetaOffset) > 0) + { + memmove(&Packet.body.aData[0+iMetaOffset], &Packet.body.aData[COMMUDP_RAW_METATYPE1_SIZE+iMetaOffset], Packet.head.iLen-iMetaOffset); + } + #if COMM_PRINT > 2 + NetPrintf(("commudp: [%p] processed metadata chunk\n", pRef)); + #endif + } + + // walk port list and handle processing + for (pRef = g_link; pRef != NULL; pRef = pRef->pLink) + { + // get latest tick + uCurrTick = NetTick(); + + // identify port to handle connection request + if ((pListen == NULL) && (pSocket == pRef->pSocket) && (pRef->eState == LIST) && (Packet.head.iLen >= 0) && + ((Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_CONN)) && (pRef->uConnIdent == Packet.body.uAck)) + { + pListen = pRef; + } + + if (Packet.head.iLen >= 0) + { + if (Packet.head.uMeta == 1) + { + bConnIdentMatch = (uRClientIdent == pRef->uRemClientIdent) && (uLClientIdent == pRef->uClientIdent); + #if COMM_PRINT > 2 + NetPrintf(("commudp: [%p] matching with metadata: src=0x%08x(0x%08x) dst=0x%08x(0x%08x) match=%d\n", pRef, + uRClientIdent, pRef->uRemClientIdent, uLClientIdent, pRef->uClientIdent, bConnIdentMatch)); + #endif + } + else + { + // if this is an INIT or POKE, make sure the connident matches + bConnIdentMatch = ((Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_POKE)) ? (Packet.body.uAck == pRef->uConnIdent) : 1; + } + } + + // see if packet belongs to someone + if ((Packet.head.iLen >= 0) && (pRef->eState != LIST) && (pRef->eState != CLOSE) && (pSocket == pRef->pSocket) && + (SockaddrCompare(&pRef->PeerAddr, &Sin) == 0) && (bConnIdentMatch)) + { + // we got a packet. + pRef->Common.bpackrcvd = TRUE; + + // transition to open state if we're getting data from peer + if ((pRef->eState == CONN) && ((Packet.body.uSeq == COMMUDP_RAW_PACKET_UNREL) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_DATA))) + { + NetPrintf(("commudp: [%p] transitioning to OPEN state due to received data from peer\n", pRef)); + pRef->eState = OPEN; + } + + // do stats + if (pRef->eState == OPEN) + { + pRef->Common.datarcvd += Packet.head.iLen; + pRef->Common.packrcvd += 1; + } + + // process an incoming packet + if ((Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || + (Packet.body.uSeq == COMMUDP_RAW_PACKET_POKE) || + (Packet.body.uSeq == COMMUDP_RAW_PACKET_CONN) || + (Packet.body.uSeq == COMMUDP_RAW_PACKET_DISC)) + { + // handle connection setup/teardown + _CommUDPProcessSetup(pRef, &Packet, &Sin, uCurrTick); + } + else if (pRef->eState != OPEN) + { + // ignore the packet + continue; + } + else if (Packet.body.uSeq == COMMUDP_RAW_PACKET_NAK) + { + // remember we got something + pRef->uRecvTick = Packet.head.uWhen; + // resend the missing data + _CommUDPProcessFlow(pRef, (RawUDPPacketHeadT *)&Packet, uCurrTick); + } + else if (Packet.body.uSeq > COMMUDP_SEQ_MULTI_INC) + { + RawUDPPacketHeadT Multi; + uint32_t uOldSeq = pRef->uRcvSeq; + int32_t iExtraSubPktCount = CommUDPUtilGetExtraSubPktCountFromSeq(Packet.body.uSeq); + Multi.head.uWhen = Packet.head.uWhen; + Multi.body.uSeq = _CommUDPSeqnDelta((Packet.body.uSeq & COMMUDP_SEQ_MASK), -iExtraSubPktCount); + Multi.body.uAck = Packet.body.uAck; + // remember we got something + pRef->uRecvTick = Packet.head.uWhen; + // process all the packets + for (; iExtraSubPktCount >= 0; --iExtraSubPktCount) + { + if (iExtraSubPktCount > 0) + { + Packet.head.iLen -= CommUDPUtilDecodeSubpacketSize(Packet.body.aData+Packet.head.iLen-1, &Multi.head.iLen); + } + else + { + Multi.head.iLen = Packet.head.iLen; + } + Packet.head.iLen -= Multi.head.iLen; + + if (Packet.head.iLen < 0) + { + // we just received corrupt data, bail out + break; + } + + // process the ack info + _CommUDPProcessFlow(pRef, &Multi, uCurrTick); + // process the input data and check for missing data (nak) + // (if nak, stop processing so we only send one nak packet) + if (_CommUDPProcessInput(pRef, &Multi, Packet.body.aData+ Packet.head.iLen, uCurrTick) < 0) + { + iExtraSubPktCount = 0; + } + // see if packets were saved + if ((iExtraSubPktCount > 0) && (uOldSeq != pRef->uRcvSeq)) + { + pRef->Common.packsaved += 1; + #if COMM_PRINT + NetPrintf(("commudp: [%p] redundant packet %d prevented loss of packet %d\n", pRef, Packet.body.uSeq & COMMUDP_SEQ_MASK, uOldSeq)); + #endif + uOldSeq = pRef->uRcvSeq; + } + // advance the packet sequence number + + // if we sent a nak, multi.body.seq got wiped and count2 set to 0, so don't increment it. + if (Multi.body.uSeq >= COMMUDP_RAW_PACKET_UNREL) + { + Multi.body.uSeq = _CommUDPSeqnDelta(Multi.body.uSeq, 1); + } + } + } + else + { + // remember we got something + pRef->uRecvTick = Packet.head.uWhen; + // process the ack info + _CommUDPProcessFlow(pRef, (RawUDPPacketHeadT *)&Packet, uCurrTick); + // save the data + _CommUDPProcessInput(pRef, (RawUDPPacketHeadT *)&Packet, Packet.body.aData, uCurrTick); + } + // mark packet as processed + Packet.head.iLen = -1; + } + + // see if we are trying to connect + if ((pRef->eState == CONN) && (uCurrTick-pRef->uSendTick > 1000)) + { + _CommUDPProcessInit(pRef, uCurrTick); + } + + // see if any output for this port + if ((pRef->eState == OPEN) && (pRef->iSndNxt != pRef->iSndInp)) + { + _CommUDPProcessOutput(pRef, uCurrTick); + } + + // check for connection timeout + if ((pRef->eState == OPEN) && (NetTickDiff(uCurrTick, pRef->uRecvTick) > 120*1000) && (NetTickDiff(uCurrTick, pRef->uSendTick) < 2000)) + { + NetPrintf(("commudp: [%p] closing connection due to timeout\n", pRef)); + NetPrintf(("commudp: [%p] tick=%d, rtick=%d, stick=%d\n", pRef, uCurrTick, pRef->uRecvTick, pRef->uSendTick)); + _CommUDPClose(pRef, uCurrTick); + } + + // see if we should run the penetrator + if ((pRef->eState == LIST) && (pRef->PeerAddr.sa_family == AF_INET) && + (uCurrTick > pRef->uSendTick+PENETRATE_RATE)) + { + // penetrate the firewall + _CommUDPProcessPoke(pRef, uCurrTick); + } + + // see if callback needs an idle tick + if ((pRef->uGotEvent == 0) && (uCurrTick > pRef->uIdleTick+250)) + { + pRef->uIdleTick = uCurrTick; + pRef->uGotEvent |= 4; + } + + // do callback if needed + if ((pRef->iCallback == 0) && (pRef->uGotEvent != 0)) + { + // limit callback access + pRef->iCallback += 1; + // callback the handler + if (pRef->pCallProc != NULL) + { + pRef->pCallProc((CommRef *)pRef, pRef->uGotEvent); + } + else if (pRef->eState == OPEN) + { + #if COMM_PRINT + NetPrintf(("commudp: [%p] no upper layer callback\n", pRef)); + #endif + } + // limit callback access + pRef->iCallback -= 1; + // reset event count + pRef->uGotEvent = 0; + } + + // see if we need a keepalive + // do this after callback since callback often generates a + // packet send that eliminates the need for the idle packet + if ((pRef->eState == OPEN) && (pRef->iSndNxt == pRef->iSndInp)) + { + int32_t timeout = NetTickDiff(uCurrTick, pRef->uSendTick); + int32_t reliabletimeout = NetTickDiff(uCurrTick, pRef->uSendReliableTick); + if (((reliabletimeout > BUSY_KEEPALIVE) && (pRef->uRcvAck != pRef->uRcvSeq)) || + ((reliabletimeout > BUSY_KEEPALIVE) && (pRef->iSndInp != pRef->iSndOut)) || + (timeout > IDLE_KEEPALIVE) || + (pRef->iRcvUnack >= UNACK_LIMIT)) + { + // force reset of unack count just in case + pRef->iRcvUnack = 0; + // send a keepalive + _CommUDPProcessAlive(pRef, uCurrTick); + } + } + } + + // check for unclaimed connection packet (port mangling) + if ((Packet.head.iLen >= 0) && (Sin.sa_family == AF_INET) && + ((Packet.body.uSeq == COMMUDP_RAW_PACKET_POKE) || + (Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || + (Packet.body.uSeq == COMMUDP_RAW_PACKET_CONN))) + { + uint32_t bIgnored = FALSE; + NetPrintf(("commudp: [%p] received %s packet from %a:%d\n", pRef, g_strConnNames[Packet.body.uSeq], SockaddrInGetAddr(&Sin), SockaddrInGetPort(&Sin))); + + // look for matching reference + for (pRef = g_link; pRef != NULL; pRef = pRef->pLink) + { + if (((pRef->eState == CONN) || (pRef->eState == LIST)) && (pRef->PeerAddr.sa_family == AF_INET)) + { + if (pRef->uConnIdent == Packet.body.uAck) + { + // see if they are trying to connect to poke source but port is mangled + if ((pSocket == pRef->pSocket) && (SockaddrInGetAddr(&pRef->PeerAddr) == SockaddrInGetAddr(&Sin)) && (SockaddrInGetPort(&pRef->PeerAddr) != SockaddrInGetPort(&Sin))) + { + // assume poke source is masq, change port number to correspond + NetPrintf(("commudp: [%p] changing peer to %a:%d (was expecting %a:%d)\n", pRef, SockaddrInGetAddr(&Sin), SockaddrInGetPort(&Sin), + SockaddrInGetAddr(&pRef->PeerAddr), SockaddrInGetPort(&pRef->PeerAddr))); + ds_memcpy_s(&pRef->PeerAddr, sizeof(pRef->PeerAddr), &Sin, sizeof(Sin)); + } + break; + } + else // remember we ignored a packet with non-matching connident + { + bIgnored = TRUE; + } + } + } + if ((pRef == NULL) && (bIgnored == TRUE)) + { + NetPrintf(("commudp: [%p] ignoring %s packet with connident 0x%08x\n", pRef, g_strConnNames[Packet.body.uSeq], Packet.body.uAck)); + } + } + + // see if this was an initial connection request + if ((pListen != NULL) && (Packet.head.iLen >= 0) && ((Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_CONN))) + { + int32_t iPeerAddr = SockaddrInGetAddr(&pListen->PeerAddr); + pRef = pListen; + + if ((pRef->uConnIdent == Packet.body.uAck) && ((iPeerAddr == 0) || (iPeerAddr == SockaddrInGetAddr(&Sin)))) + { + NetPrintf(("commudp: [%p] transitioning to CONN state after receiving %s packet\n", pRef, g_strConnNames[Packet.body.uSeq])); + // change to connecting state + pRef->eState = CONN; + // if we were listening without a peer address, set it now + if (iPeerAddr == 0) + { + ds_memcpy_s(&pRef->PeerAddr, sizeof(pRef->PeerAddr), &Sin, sizeof(Sin)); + } + // process init packet + _CommUDPProcessSetup(pRef, &Packet, &Sin, uCurrTick); + } + } + + return(iCount); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPEvent + + \Description + Main event function + + \Input *pSock - socket + \Input iFlags - flags + \Input *_ref - to be completed + + \Output + int32_t - 0 + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +static int32_t _CommUDPEvent(SocketT *pSock, int32_t iFlags, void *_ref) +{ + // remember we're in an event call + g_inevent = 1; + + // see if we have exclusive access + if (NetCritTry(&g_crit)) + { + uint32_t uCurrTick = NetTick(); + // clear event counter before calling _CommUDPProcessThreadData to prevent possible unwanted recursion + g_missed = 0; + // process data as long as we get something + while (_CommUDPThreadData(uCurrTick) > 0) + ; + // free access + NetCritLeave(&g_crit); + } + else + { + g_missed += 1; + #if COMM_PRINT + NetPrintf(("commudp: missed %d events\n", g_missed)); + #endif + } + + // leaving event call + g_inevent = 0; + + // done for now + return(0); +} + + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPEnlistRef + + \Description + Add the given ref to the global linked list of references. + + \Input *pRef - ref to add + + \Notes + This also handles initialization of the global critical section global + missed event counter. + + \Version 02/18/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommUDPEnlistRef(CommUDPRef *pRef) +{ + // if first ref, init global critical section + if (g_link == NULL) + { + NetCritInit(&g_crit, "commudp-global"); + g_missed = 0; + g_inevent = 0; + } + + // add to linked list of ports + NetCritEnter(&g_crit); + pRef->pLink = g_link; + g_link = pRef; + NetCritLeave(&g_crit); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPDelistRef + + \Description + Remove the given ref from the global linked list of references. + + \Input *pRef - ref to remove + + \Notes + This also handles destruction of the global critical section. + + \Version 02/18/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _CommUDPDelistRef(CommUDPRef *pRef) +{ + CommUDPRef **ppLink; + + // remove from linked list of ports + NetCritEnter(&g_crit); + for (ppLink = &(g_link); *ppLink != pRef; ppLink = &((*ppLink)->pLink)) + ; + *ppLink = pRef->pLink; + NetCritLeave(&g_crit); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPListen0 + + \Description + Listen for a connection (private version) + + \Input *pRef - reference pointer + \Input *pSock - fallback socket + \Input *pBindAddr - local port + + \Output + int32_t - negative=error, zero=ok + + \Version 12/04/00 (GWS) +*/ +/*************************************************************************************************F*/ +static int32_t _CommUDPListen0(CommUDPRef *pRef, SocketT *pSock, struct sockaddr *pBindAddr) +{ + int32_t iErr; + CommUDPRef *pFind; + struct sockaddr GlueAddr; + + // make sure in valid state + if (pRef->eState != IDLE) + { + SocketClose(pSock); + return(COMM_BADSTATE); + } + + // reset to unresolved + _CommUDPSetSocket(pRef, NULL); + _CommUDPResetTransfer(pRef); + + // setup bind points + ds_memclr(&GlueAddr, sizeof(GlueAddr)); + ds_memclr(&pRef->PeerAddr, sizeof(pRef->PeerAddr)); + +#if defined(DIRTYCODE_PC) + /* + the following line will fill in bindaddr with the IP addr previously set with + SocketControl(... 'ladr' ...). If that selector was never used, then the IP + address field of bindaddr will just be filled with 0. + note: only required for multi-homed system (PC) + */ + SocketInfo(NULL, 'ladr', 0, pBindAddr, sizeof(*pBindAddr)); +#endif + + // see if there is an existing socket bound to this port + for (pFind = g_link; pFind != NULL; pFind = pFind->pLink) + { + // dont check ourselves or unbound sockets + if ((pFind == pRef) || (pFind->pSocket == NULL)) + continue; + + // see where this endpoint is bound + if (SocketInfo(pFind->pSocket, 'bind', 0, &GlueAddr, sizeof(GlueAddr)) < 0) + continue; + + /* + see if the socket can be reused + if the endpoint is virtual: we only compare the ports + if the endpoint is not virtual: + if bindaddr (specifying what we want to bind to) has ipaddr=0, we only compare the ports + otherwise we compare the full sockaddr structs (i.e. family, port, addr) + */ + if (SockaddrInGetPort(pBindAddr) == SockaddrInGetPort(&GlueAddr)) + { + if ((SocketInfo(pFind->pSocket, 'virt', 0, NULL, 0) == TRUE) || + (SockaddrInGetAddr(pBindAddr) == 0) || + (SockaddrCompare(pBindAddr, &GlueAddr) == 0)) + { + // share the socket + _CommUDPSetSocket(pRef, pFind->pSocket); + + // dont need supplied socket + SocketClose(pSock); + break; + } + } + } + + // create socket if no existing one + if (pFind == NULL) + { + // bind the address to the socket + iErr = SocketBind(pSock, pBindAddr, sizeof(*pBindAddr)); + if (iErr < 0) + { + NetPrintf(("commudp: [%p] bind to %d failed with %d\n", pRef, SockaddrInGetPort(pBindAddr), iErr)); + SockaddrInSetPort(pBindAddr, 0); + if ((iErr = SocketBind(pSock, pBindAddr, sizeof(*pBindAddr))) < 0) + { + NetPrintf(("commudp: [%p] bind to 0 failed with result %d\n", pRef, iErr)); + pRef->eState = DEAD; + SocketClose(pSock); + return((iErr == SOCKERR_INVALID) ? COMM_PORTBOUND : COMM_UNEXPECTED); + } + } + // use the supplied socket + _CommUDPSetSocket(pRef, pSock); + + // setup for socket events + SocketCallback(pSock, CALLB_RECV, 100, NULL, &_CommUDPEvent); + } + + // put into listen mode + pRef->eState = LIST; + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function _CommUDPConnect0 + + \Description + Initiate a connection to a peer (private version) + + \Input *pRef - reference pointer + \Input *pSock - socket + \Input *pPeerAddr - peer address + + \Output + int32_t - negative=error, zero=ok + + \Notes + Does not currently perform dns translation + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +static int32_t _CommUDPConnect0(CommUDPRef *pRef, SocketT *pSock, struct sockaddr *pPeerAddr) +{ + // make sure in valid state + if (pRef->eState != IDLE) { + SocketClose(pSock); + return(COMM_BADSTATE); + } + + // reset to unresolved + _CommUDPSetSocket(pRef, NULL); + _CommUDPResetTransfer(pRef); + + // setup target info + ds_memcpy_s(&pRef->PeerAddr, sizeof(pRef->PeerAddr), pPeerAddr, sizeof(*pPeerAddr)); + + // save the socket + _CommUDPSetSocket(pRef, pSock); + + // setup for callbacks + SocketCallback(pSock, CALLB_RECV, 100, NULL, &_CommUDPEvent); + + // change the state + pRef->eState = CONN; + return(0); +} + +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************************/ +/*! + \Function CommUDPConstruct + + \Description + Construct the class + + \Input iMaxWid - max record width + \Input iMaxInp - input packet buffer size + \Input iMaxOut - output packet buffer size + + \Output + CommUDPRef - construct pointer + + \Notes + Initialized winsock for first class. also creates linked + list of all current instances of the class and worker thread + to do most udp stuff. + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +CommUDPRef *CommUDPConstruct(int32_t iMaxWid, int32_t iMaxInp, int32_t iMaxOut) +{ + CommUDPRef *pRef; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate class storage + pRef = DirtyMemAlloc(sizeof(*pRef), COMMUDP_MEMID, iMemGroup, pMemGroupUserData); + if (pRef == NULL) + return(NULL); + ds_memclr(pRef, sizeof(*pRef)); + pRef->Common.memgroup = iMemGroup; + pRef->Common.memgrpusrdata = pMemGroupUserData; + + // initialize the callback routines + pRef->Common.Construct = (CommAllConstructT *)CommUDPConstruct; + pRef->Common.Destroy = (CommAllDestroyT *)CommUDPDestroy; + pRef->Common.Resolve = (CommAllResolveT *)CommUDPResolve; + pRef->Common.Unresolve = (CommAllUnresolveT *)CommUDPUnresolve; + pRef->Common.Listen = (CommAllListenT *)CommUDPListen; + pRef->Common.Unlisten = (CommAllUnlistenT *)CommUDPUnlisten; + pRef->Common.Connect = (CommAllConnectT *)CommUDPConnect; + pRef->Common.Unconnect = (CommAllUnconnectT *)CommUDPUnconnect; + pRef->Common.Callback = (CommAllCallbackT *)CommUDPCallback; + pRef->Common.Control = (CommAllControlT *)CommUDPControl; + pRef->Common.Status = (CommAllStatusT *)CommUDPStatus; + pRef->Common.Tick = (CommAllTickT *)CommUDPTick; + pRef->Common.Send = (CommAllSendT *)CommUDPSend; + pRef->Common.Peek = (CommAllPeekT *)CommUDPPeek; + pRef->Common.Recv = (CommAllRecvT *)CommUDPRecv; + + // remember max packet width + pRef->Common.maxwid = iMaxWid; + pRef->Common.maxinp = iMaxInp; + pRef->Common.maxout = iMaxOut; + + // allocate the buffers + pRef->iRcvWid = sizeof(RawUDPPacketT)-sizeof(((RawUDPPacketT *)0)->body.aData)+iMaxWid+COMMUDP_MAX_METALEN; + pRef->iRcvWid = (pRef->iRcvWid +3) & 0x7ffc; + pRef->iRcvLen = pRef->iRcvWid * iMaxInp; + pRef->pRcvBuf = (char *)DirtyMemAlloc(pRef->iRcvLen, COMMUDP_MEMID, pRef->Common.memgroup, pRef->Common.memgrpusrdata); + pRef->iSndWid = sizeof(RawUDPPacketT)-sizeof(((RawUDPPacketT *)0)->body.aData)+iMaxWid+COMMUDP_MAX_METALEN; + pRef->iSndWid = (pRef->iSndWid+3) & 0x7ffc; + pRef->iSndLen = pRef->iSndWid * iMaxOut; + pRef->pSndBuf = (char *)DirtyMemAlloc(pRef->iSndLen, COMMUDP_MEMID, pRef->Common.memgroup, pRef->Common.memgrpusrdata); + + // reset the socket + _CommUDPSetSocket(pRef, NULL); + + // reset peer address + ds_memclr(&pRef->PeerAddr, sizeof(pRef->PeerAddr)); + + // reset the state + pRef->eState = IDLE; + pRef->uConnIdent = 0; + pRef->uUnackLimit = UNACK_LIMIT; + pRef->uRedundantLimit = REDUNDANT_LIMIT_DEFAULT; + + // add to port list + _CommUDPEnlistRef(pRef); + + return(pRef); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPDestroy + + \Description + Destruct the class + + \Input *pRef - reference pointer + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +void CommUDPDestroy(CommUDPRef *pRef) +{ + CommUDPRef *pFind; + uint32_t uCurrTick = NetTick(); + + // flush final data + _CommUDPFlush(pRef, 200, uCurrTick); + + // remove from port list + _CommUDPDelistRef(pRef); + + // if port is open, close it + if (pRef->eState == OPEN) + _CommUDPClose(pRef, uCurrTick); + + // kill the socket + if (pRef->pSocket != NULL) { + // see if socket shared + for (pFind = g_link; pFind != NULL; pFind = pFind->pLink) { + if (pFind->pSocket == pRef->pSocket) + break; + } + // if we are only user of this socket + if (pFind == NULL) { + SocketClose(pRef->pSocket); + _CommUDPSetSocket(pRef, NULL); + } + } + + // if last ref, destroy global critical section + if (g_link == NULL) + { + NetCritKill(&g_crit); + } + + // release resources + DirtyMemFree(pRef->pRcvBuf, COMMUDP_MEMID, pRef->Common.memgroup, pRef->Common.memgrpusrdata); + DirtyMemFree(pRef->pSndBuf, COMMUDP_MEMID, pRef->Common.memgroup, pRef->Common.memgrpusrdata); + DirtyMemFree(pRef, COMMUDP_MEMID, pRef->Common.memgroup, pRef->Common.memgrpusrdata); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPCallback + + \Description + Set upper layer callback + + \Input *pRef - reference pointer + \Input *pCallback - socket generating callback + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +void CommUDPCallback(CommUDPRef *pRef, void (*pCallback)(void *pRef, int32_t iEvent)) +{ + NetCritEnter(&g_crit); + pRef->pCallProc = pCallback; + pRef->uGotEvent |= 2; + NetCritLeave(&g_crit); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPResolve + + \Description + Resolve an address + + \Input *pRef - endpoint + \Input *pAddr - resolve address + \Input *pBuf - target buffer + \Input iLen - target length (min 64 bytes) + \Input cDiv - divider char + + \Output + int32_t - <0=error, 0=complete (COMM_NOERROR), >0=in progress (COMM_PENDING) + + \Notes + Target list is always double null terminated allowing null + to be used as the divider character if desired. when COMM_PENDING + is returned, target buffer is set to "~" until completion. + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +int32_t CommUDPResolve(CommUDPRef *pRef, const char *pAddr, char *pBuf, int32_t iLen, char cDiv) +{ + NetPrintf(("commudp: [%p] resolve functionality not supported\n", pRef)); + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPUnresolve + + \Description + Stop the resolver + + \Input *pRef - reference pointer + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +void CommUDPUnresolve(CommUDPRef *pRef) +{ +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPUnlisten + + \Description + Stop listening + + \Input *pRef - reference pointer + + \Output + int32_t - negative=error, zero=ok + + \Version 12/04/00 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t CommUDPUnlisten(CommUDPRef *pRef) +{ + uint32_t uCurrTick = NetTick(); + + // flush final data + _CommUDPFlush(pRef, 200, uCurrTick); + + // never close listening socket (it is shared) + if (pRef->eState == LIST) + { + _CommUDPSetSocket(pRef, NULL); + } + + // get rid of socket if presernt + if (pRef->pSocket != NULL) + { + // attempt to close socket + _CommUDPClose(pRef, uCurrTick); + // done with socket + SocketClose(pRef->pSocket); + _CommUDPSetSocket(pRef, NULL); + } + + // return to idle mode + pRef->eState = IDLE; + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPUnconnect + + \Description + Terminate a connection + + \Input *pRef - reference pointer + + \Output + int32_t - negative=error, zero=ok + + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +int32_t CommUDPUnconnect(CommUDPRef *pRef) +{ + uint32_t uCurrTick = NetTick(); + + // flush final data + _CommUDPFlush(pRef, 200, uCurrTick); + + // never close listening socket (it is shared) + if (pRef->eState == LIST) + { + _CommUDPSetSocket(pRef, NULL); + } + + // get rid of socket if presernt + if (pRef->pSocket != NULL) + { + // attempt to close socket + _CommUDPClose(pRef, uCurrTick); + // done with socket + SocketClose(pRef->pSocket); + _CommUDPSetSocket(pRef, NULL); + } + + // return to idle mode + pRef->eState = IDLE; + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPStatus + + \Description + Return current stream status + + \Input *pRef - reference pointer + + \Output + int32_t - CONNECTING, OFFLINE, ONLINE or FAILURE + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +int32_t CommUDPStatus(CommUDPRef *pRef) +{ + // return state + if ((pRef->eState == CONN) || (pRef->eState == LIST)) + return(COMM_CONNECTING); + if ((pRef->eState == IDLE) || (pRef->eState == CLOSE)) + return(COMM_OFFLINE); + if (pRef->eState == OPEN) + return(COMM_ONLINE); + return(COMM_FAILURE); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPControl + + \Description + Set connection behavior. + + \Input *pRef - reference pointer + \Input iControl - control selector + \Input iValue - selector specfic + \Input *pValue - selector specific + + \Output + int32_t - negative=error, else selector result + + \Notes + iControl can be one of the following: + + \verbatim + 'clid' - client identifier + 'meta' - set metatype (default=0) + 'rcid' - remote client identifier + 'rlmt' - redundant packet size limit (default = REDUNDANT_LIMIT_DEFAULT) + 'ulmt' - unacknowledged packet limit (2k default) + \endverbatim + + \Version 02/20/2007 (jbrookes) +*/ +/*************************************************************************************************F*/ +int32_t CommUDPControl(CommUDPRef *pRef, int32_t iControl, int32_t iValue, void *pValue) +{ + if (iControl == 'clid') + { + pRef->uClientIdent = iValue; + return(0); + } + if (iControl == 'meta') + { + NetPrintf(("commudp: [%p] setting metatype=%d\n", pRef, iValue)); + pRef->uMetaType = iValue; + return(0); + } + if (iControl == 'rcid') + { + pRef->uRemClientIdent = iValue; + return(0); + } + if (iControl == 'rlmt') + { + const int32_t iMaxLimit = sizeof(((RawUDPPacketT *)0)->body.aData); + if (iValue == 0) + { + iValue = REDUNDANT_LIMIT_DEFAULT; + } + if (iValue >= iMaxLimit) + { + iValue = iMaxLimit; + } + NetPrintf(("commudp: [%p] redundant limit changed from %d bytes to %d bytes\n", pRef, pRef->uRedundantLimit, iValue)); + pRef->uRedundantLimit = iValue; + return(0); + } + if (iControl == 'ulmt') + { + pRef->uUnackLimit = iValue; + NetPrintf(("commudp: [%p] setting ulimit to %d bytes\n", pRef, pRef->uUnackLimit)); + return(0); + } + // unhandled; pass through to socket module if we have a socket + if (pRef->pSocket != NULL) + { + return(SocketControl(pRef->pSocket, iControl, iValue, pValue, NULL)); + } + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPTick + + \Description + Return current tick + + \Input *pRef - reference pointer + + \Output + uint32_t - elaped milliseconds + + \Version 12/04/00 (GWS) +*/ +/*************************************************************************************************F*/ +uint32_t CommUDPTick(CommUDPRef *pRef) +{ + return(NetTick()); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPSend + + \Description + Send a packet + + \Input *pRef - reference pointer + \Input *pBuffer - pointer to data + \Input iLength - length of data + \Input uFlags - COMM_FLAGS_* + + \Output + int32_t - negative=error, zero=buffer full (temp fail), positive=queue position (ok) + + \Notes + Zero length packets may not be sent (they are used for buffer query) + + \Version 12/04/00 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t CommUDPSend(CommUDPRef *pRef, const void *pBuffer, int32_t iLength, uint32_t uFlags) +{ + RawUDPPacketT PacketBuffer, *pPacket; + uint32_t uCurrTick = NetTick(); + int32_t iPos, iMetaLen, iResult=1; // result=1 is default (min queue) result + + // make sure port is open + if (pRef->eState != OPEN) + { + return(COMM_BADSTATE); + } + + // if metachunk enabled, adjust size + iMetaLen = CommUDPUtilGetMetaSize(pRef->uMetaType); + + // return error for oversized packets + if ((iLength+iMetaLen) > (signed)(pRef->iSndWid-(sizeof(RawUDPPacketT)-sizeof(((RawUDPPacketT *)0)->body.aData)))) + { + NetPrintf(("commudp: [%p] oversized packet send (%d bytes)\n", pRef, iLength)); + return(COMM_MINBUFFER); + } + + /* check for zero-length packets, which cannot be sent (they are used for acks); instead, treat them as successful, + which means the queue position is returned */ + if (iLength == 0) + { + iPos = (((pRef->iSndInp+pRef->iSndLen)-pRef->iSndOut)%pRef->iSndLen)/pRef->iSndWid; + return(iPos+1); + } + + // get packet buffer + if (uFlags & COMM_FLAGS_UNRELIABLE) + { + // unreliable packets are staged locally and flushed immediately + pPacket = &PacketBuffer; + } + else + { + // make sure output buffer isn't full + if ((pRef->iSndInp + pRef->iSndWid) % pRef->iSndLen == pRef->iSndOut) + { + NetPrintfVerbose((COMM_PRINT, 0, "commudp: [%p] send overflow (connident=0x%08x)\n", pRef, pRef->uConnIdent)); + return(0); + } + // reference packet buffer in output queue + pPacket = (RawUDPPacketT *) &(pRef->pSndBuf[pRef->iSndInp]); + } + + // copy the packet to the buffer + ds_memcpy(pPacket->body.aData, pBuffer, iLength); + pPacket->head.iLen = iLength; + // set the send time + pPacket->head.uWhen = uCurrTick; + + // handle unreliable send + if (uFlags & COMM_FLAGS_UNRELIABLE) + { + int32_t iErr = -1; + NetCritEnter(&g_crit); + + // set up and send an unreliable packet + pPacket->body.uSeq = pRef->uUnreliableSndSeq; + pPacket->body.uAck = _CommUDPSeqnDelta(pRef->uRcvSeq, -1); + if (uFlags & COMM_FLAGS_BROADCAST) + { + struct sockaddr PeerAddr; + ds_memcpy_s(&PeerAddr, sizeof(PeerAddr), &pRef->PeerAddr, sizeof(pRef->PeerAddr)); + SockaddrInSetAddr(&PeerAddr, 0xffffffff); + iErr = _CommUDPWrite(pRef, pPacket, &PeerAddr, uCurrTick); + } + else + { + iErr = _CommUDPWrite(pRef, pPacket, &pRef->PeerAddr, uCurrTick); + } + + // calculate new sequence number + if (++pRef->uUnreliableSndSeq >= COMMUDP_RAW_PACKET_DATA) + { + pRef->uUnreliableSndSeq = COMMUDP_RAW_PACKET_UNREL; + } + + NetCritLeave(&g_crit); + // if send failed, return buffer-full(0) or error + if (iErr < 0) + { + iResult = (pRef->uSndErr == SOCKERR_NONE) ? 0 : iErr; + } + } + else + { + // set the data fields + pPacket->body.uSeq = pRef->uSndSeq; + pRef->uSndSeq = _CommUDPSeqnDelta(pRef->uSndSeq, 1); + + pPacket->body.uAck = _CommUDPSeqnDelta(pRef->uRcvSeq, -1); + + // add the packet to the queue + pRef->iSndInp = (pRef->iSndInp+pRef->iSndWid) % pRef->iSndLen; + iPos = (((pRef->iSndInp+pRef->iSndLen)-pRef->iSndOut)%pRef->iSndLen)/pRef->iSndWid; + + // try to send packet immediately if buffer is at least half empty + if (iPos < (pRef->Common.maxout/2)) + { + NetCritEnter(&g_crit); + _CommUDPProcessOutput(pRef, uCurrTick); + + // process incoming if we missed event + if (g_missed != 0) + { + // clear event counter before calling _CommUDPProcessThreadData to prevent possible unwanted recursion + g_missed = 0; + // process data as long as we get something + while (_CommUDPThreadData(uCurrTick) > 0) + ; + NetPrintfVerbose((COMM_PRINT, 0, "commudp: [%p] processing %d after send\n", pRef, g_missed)); + } + NetCritLeave(&g_crit); + } + // return buffer depth + if (iPos > 0) + { + iResult = iPos; + } + } + + return(iResult); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPPeek + + \Description + Peek at waiting packet + + \Input *pRef - reference pointer + \Input *pTarget - target buffer + \Input iLength - buffer length + \Input *pWhen - tick received at + + \Output + int32_t - negative=nothing pending, else packet length + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +int32_t CommUDPPeek(CommUDPRef *pRef, void *pTarget, int32_t iLength, uint32_t *pWhen) +{ + RawUDPPacketT *pPacket; + + // see if a packet is available + if (pRef->iRcvOut == pRef->iRcvInp) + return(COMM_NODATA); + + // point to the packet + pPacket = (RawUDPPacketT *) &(pRef->pRcvBuf[pRef->iRcvOut]); + + // copy data? + if (iLength > 0) + { + // make sure enough space is available + if (iLength < pPacket->head.iLen) + return(COMM_MINBUFFER); + + // copy over the data portion + ds_memcpy(pTarget, pPacket->body.aData, pPacket->head.iLen); + } + + // get the timestamp + if (pWhen != NULL) + *pWhen = pPacket->head.uWhen; + + // return packet data length + return(pPacket->head.iLen); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPRecv + + \Description + Receive a packet from the buffer + + \Input *pRef - reference pointer + \Input *pTarget - target buffer + \Input iLength - buffer length + \Input *pWhen - tick received at + + \Output + int32_t - negative=error, else packet length + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +int32_t CommUDPRecv(CommUDPRef *pRef, void *pTarget, int32_t iLength, uint32_t *pWhen) +{ + // use peek to remove the data + int32_t iLen = CommUDPPeek(pRef, pTarget, iLength, pWhen); + if (iLen >= 0) + pRef->iRcvOut = (pRef->iRcvOut+pRef->iRcvWid)%pRef->iRcvLen; + // all done + return(iLen); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPListen + + \Description + Listen for a connection + + \Input *pRef - reference pointer + \Input *pAddr - port to listen on (only :port portion used) + + \Output + int32_t - negative=error, zero=ok + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +int32_t CommUDPListen(CommUDPRef *pRef, const char *pAddr) +{ + int32_t iErr, iListenPort, iConnPort; + uint32_t uPoke; + SocketT *pSock; + struct sockaddr BindAddr; + + // setup bind points + SockaddrInit(&BindAddr, AF_INET); + + // parse at least port + if ((SockaddrInParse2(&uPoke, &iListenPort, &iConnPort, pAddr) & 0x2) != 0x2) + { + return(COMM_BADADDRESS); + } + SockaddrInSetPort(&BindAddr, iListenPort); + + // create socket in case its needed + pSock = SocketOpen(AF_INET, SOCK_DGRAM, 0); + if (pSock == NULL) + { + return(COMM_NORESOURCE); + } + + // let common code finish up + iErr = _CommUDPListen0(pRef, pSock, &BindAddr); + + // set connection identifier + _CommUDPSetConnID(pRef, pAddr); + + NetPrintf(("commudp: [%p] listen err=%d, bind=%d, connident=0x%08x\n", pRef, iErr, iListenPort, pRef->uConnIdent)); + + // see if we should setup peer address + if ((iErr == 0) && (uPoke != 0)) + { + if (iConnPort == 0) + { + iConnPort = iListenPort+1; + } + + NetPrintf(("commudp: [%p] poke address=%08x:%d\n", pRef, uPoke, iConnPort)); + SockaddrInit(&pRef->PeerAddr, AF_INET); + SockaddrInSetAddr(&pRef->PeerAddr, uPoke); + SockaddrInSetPort(&pRef->PeerAddr, iConnPort); + } + + // clear any previous receive errors + pRef->uSndErr = 0; + return(iErr); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPConnect + + \Description + Initiate a connection to a peer + + \Input *pRef - reference pointer + \Input *pAddr - address in ip-address:port form + + \Output + int32_t - negative=error, zero=ok + + \Notes + Does not currently perform dns translation + + \Version 12/04/00 (GWS) + +*/ +/*************************************************************************************************F*/ +int32_t CommUDPConnect(CommUDPRef *pRef, const char *pAddr) +{ + int32_t iErr, iConnPort, iListenPort; + uint32_t uAddr; + CommUDPRef *pFind; + SocketT *sock; + struct sockaddr BindAddr, GlueAddr, PeerAddr; + + // setup target info + SockaddrInit(&PeerAddr, AF_INET); + SockaddrInit(&BindAddr, AF_INET); + + // parse addr - make sure we have at least an address and port + iErr = SockaddrInParse2(&uAddr, &iListenPort, &iConnPort, pAddr); + if ((iErr & 3) != 3) + { + return(COMM_BADADDRESS); + } + + // if we don't have an alternate connect port, connect=listen + if (iConnPort == 0) + { + iConnPort = iListenPort++; + } + + // set listen port + SockaddrInSetPort(&BindAddr, iListenPort); + + // set connection identifier + _CommUDPSetConnID(pRef, pAddr); + NetPrintf(("commudp: [%p] connect addr=%08x, bind=%d, peer=%d connident=0x%08x\n", + pRef, uAddr, iListenPort, iConnPort, pRef->uConnIdent)); + +#if defined(DIRTYCODE_PC) + /* + the following line will fill in bindaddr with the IP addr previously set with + SocketControl(... 'ladr' ...). If that selector was never used, then the IP + address field of bindaddr will just be filled with 0. + note: only required for multi-homed system (PC) + */ + SocketInfo(NULL, 'ladr', 0, &BindAddr, sizeof(BindAddr)); +#endif + + // see if there is an existing socket bound to this port + for (pFind = g_link, sock = NULL; pFind != NULL; pFind = pFind->pLink) + { + // dont check ourselves or unbound sockets + if ((pFind == pRef) || (pFind->pSocket == NULL)) + continue; + + // see where this endpoint is bound + if (SocketInfo(pFind->pSocket, 'bind', 0, &GlueAddr, sizeof(GlueAddr)) < 0) + continue; + + /* + see if the socket can be reused + if the endpoint is virtual: we only compare the ports + if the endpoint is not virtual: + if bindaddr (specifying what we want to bind to) has ipaddr=0, we only compare the ports + otherwise we compare the full sockaddr structs (i.e. family, port, addr) + */ + if (SockaddrInGetPort(&BindAddr) == SockaddrInGetPort(&GlueAddr)) + { + if ((SocketInfo(pFind->pSocket, 'virt', 0, NULL, 0) == TRUE) || + (SockaddrInGetAddr(&BindAddr) == 0) || + (SockaddrCompare(&BindAddr, &GlueAddr) == 0)) + { + // share the socket + sock = pFind->pSocket; + break; + } + } + } + + // create the actual socket + if (sock == NULL) + { + sock = SocketOpen(AF_INET, SOCK_DGRAM, 0); + if (sock == NULL) + { + return(COMM_NORESOURCE); + } + + // bind socket + if ((iErr = SocketBind(sock, &BindAddr, sizeof(BindAddr))) < 0) + { + NetPrintf(("commudp: [%p] bind to %d failed with %d\n", pRef, iListenPort, iErr)); + SockaddrInSetPort(&BindAddr, 0); + if ((iErr = SocketBind(sock, &BindAddr, sizeof(BindAddr))) < 0) + { + NetPrintf(("commudp: [%p] bind to 0 failed with result %d\n", pRef, iErr)); + SocketClose(sock); + return(COMM_UNEXPECTED); + } + else + { + SocketInfo(sock, 'bind', 0, &BindAddr, sizeof(BindAddr)); + NetPrintf(("commudp: [%p] bound socket to port %d\n", pRef, SockaddrInGetPort(&BindAddr))); + } + } + } + + // set connect sockaddr + SockaddrInSetAddr(&PeerAddr, uAddr); + SockaddrInSetPort(&PeerAddr, iConnPort); + + // clear any previous receive errors + pRef->uSndErr = 0; + + // pass to common handler + return(_CommUDPConnect0(pRef, sock, &PeerAddr)); +} + diff --git a/src/thirdparty/dirtysdk/source/comm/commudputil.c b/src/thirdparty/dirtysdk/source/comm/commudputil.c new file mode 100644 index 00000000..58b308a3 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/comm/commudputil.c @@ -0,0 +1,194 @@ +/*H*************************************************************************************************/ +/*! + + \File commudputil.c + + \Description + CommUdp knowledge to be shared with gameserver implementation. + + \Copyright + Copyright (c) 2006-2017 Electronic Arts Inc. + + \Version 1.0 09/01/2017 (mclouatre) First Version +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include "DirtySDK/comm/commudputil.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +/*** Private Functions *****************************************************************/ + +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************************/ +/*! + \Function CommUDPUtilEncodeSubpacketSize + + \Description + Encodes a subpacket size field. A size of 0-250 is encoded in a single byte, while a size + of 251-1530 requires two. 1530 bytes is the maximum allowed size of a subpacket. + + \Input *pBuf - buffer holding encoded subpacket size (NULL to just calculate encoded size) + \Input uVal - subpacket size + + \Output + uint32_t - number of bytes that encoded size consumed (1 or 2) + + \Notes + CommUDP uses a special encoding scheme to encode subpacket sizes in one or two bytes. The + original version of the protocol stored subpacket sizes up to 250 (the maximum previously + allowed) in a single byte at the end of the subpacket data, and the newer scheme was + designed to retain compatibility with that method. Subpacket sizes up to 250 bytes are + therefore still encoded in a single byte that immediately follows the subpacket data. A + value of 251 through 255 is used to represent larger subpacket sizes, which are encoded + with an additional byte, stored immediately previous to the final byte. + + The following table shows how the subpacket size is encoded, with the first column + containing the value of the final byte B0, and B1 representing the previous byte, if + present. + + \verbatim + base10 equation to equivalent expression in format: resulting + val of get sub-pkt size base10 + base2_prefix [base16 range] sub-pkt size + ----------- ------------------ ------------------------------------ ------------ + 0x00-0xFA -> = [0x00-0xFA] = 0 - 250 + 0xFB -> 251 + 0 + = 251 + 00 [0x00-0xFF] = 251 - 506 + 0xFC -> 251 + 256 + = 251 + 01 [0x00-0xFF] = 507 - 762 + 0xFD -> 251 + 512 + = 251 + 10 [0x00-0xFF] = 763 - 1018 + 0xFE -> 251 + 768 + = 251 + 11 [0x00-0xFF] = 1019 - 1274 + 0xFF -> 251 + 1024 + = 251 + 100 [0x00-0xFF] = 1275 - 1530 + \endverbatim + + \Version 10/30/2015 (jbrookes) +*/ +/*************************************************************************************************F*/ +uint32_t CommUDPUtilEncodeSubpacketSize(uint8_t *pBuf, uint32_t uVal) +{ + uint32_t uOffset, uTemp; + if (uVal > 250) + { + if (pBuf != NULL) + { + uTemp = uVal - 251; + pBuf[1] = (uTemp >> 8) + 251; + pBuf[0] = (uint8_t)(uTemp & 0xff); + } + uOffset = 2; + } + else + { + if (pBuf != NULL) + { + pBuf[0] = (uint8_t)uVal; + } + uOffset = 1; + } + return(uOffset); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPUtilDecodeSubpacketSize + + \Description + Decodes a subpacket size field (see _CommUDPEncodeSubpacketSize for notes) + + \Input *pBuf - buffer holding encoded subpacket size + \Input *pVal - [out] storage for decoded subpacket size + + \Output + uint32_t - number of bytes encoded size consumed (1 or 2) + + \Version 10/30/2015 (jbrookes) +*/ +/*************************************************************************************************F*/ +uint32_t CommUDPUtilDecodeSubpacketSize(const uint8_t *pBuf, int32_t *pVal) +{ + uint32_t uOffset, uTemp; + if (pBuf[0] > 250) + { + pBuf -= 1; + uTemp = pBuf[1] - 251; + *pVal = 251 + (pBuf[0] | (uTemp << 8)); + uOffset = 2; + } + else + { + *pVal = pBuf[0]; + uOffset = 1; + } + return(uOffset); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPUtilGetMetaTypeFromSeq + + \Description + Extracts metatype from host-ordered commupd header seq field (RawUDPPacketT.body.uSeq) + + \Input uSeq - host-ordered commupd header seq field + + \Output + uint32_t - extracted meta type value + + \Version 09/01/2017 (mclouatre) +*/ +/*************************************************************************************************F*/ +uint32_t CommUDPUtilGetMetaTypeFromSeq(uint32_t uSeq) +{ + return((uSeq >> COMMUDP_SEQ_META_SHIFT) & 0xf); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPUtilGetMetaSize + + \Description + Returns the metadata size for a given metadata type. + + \Input uMetaType - metadata type + + \Output + uint32_t - metadata size + + \Version 09/01/2017 (mclouatre) +*/ +/*************************************************************************************************F*/ +uint32_t CommUDPUtilGetMetaSize(uint32_t uMetaType) +{ + return(uMetaType ? COMMUDP_RAW_METATYPE1_SIZE : 0); +} + +/*F*************************************************************************************************/ +/*! + \Function CommUDPUtilGetExtraSubPktCountFromSeq + + \Description + Extracts the number of extra subpackets (excluding the main subpacket) from host-ordered + commupd header seq field (RawUDPPacketT.body.uSeq) + + \Input uSeq - host-ordered commupd header seq field + + \Output + uint32_t - extracted extra subpackets count (does not include the main subpacket) + + \Version 09/01/2017 (mclouatre) +*/ +/*************************************************************************************************F*/ +uint32_t CommUDPUtilGetExtraSubPktCountFromSeq(uint32_t uSeq) +{ + return(uSeq >> COMMUDP_SEQ_MULTI_SHIFT); +} diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptaes.c b/src/thirdparty/dirtysdk/source/crypt/cryptaes.c new file mode 100644 index 00000000..6a2bd2df --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptaes.c @@ -0,0 +1,722 @@ +/*H********************************************************************************/ +/*! + \File cryptaes.c + + \Description + An implementation of the AES-128 and AES-256 cipher, based on the FIPS + standard, intended for use with the TLS AES cipher suites. + + This implementation deliberately uses the naming conventions from the + standard where possible in order to aid comprehension. + + \Notes + References: + FIPS197 standard: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + + \Copyright + Copyright (c) 2011 Electronic Arts Inc. + + \Version 01/20/2011 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/crypt/cryptaes.h" + +/*** Defines **********************************************************************/ + +/* + 32-bit word rotation +*/ +#define CRYPTAES_ROT(_x, _a) (((_x) << (_a)) | ((_x) >> (32-(_a)))) + +#define CRYPTAES_ROT1(_x) CRYPTAES_ROT((_x), 24) +#define CRYPTAES_ROT2(_x) CRYPTAES_ROT((_x), 16) +#define CRYPTAES_ROT3(_x) CRYPTAES_ROT((_x), 8) + +/* + Read/write big-endian words +*/ +#define CRYPTAES_RDWORD(_ptr) ((((uint32_t)(_ptr)[0]) << 24) | ((uint32_t)((_ptr)[1]) << 16) | (((uint32_t)(_ptr)[2]) << 8) | ((uint32_t)(_ptr)[3])) +#define CRYPTAES_WRWORD(_x, _ptr) ((_ptr)[3] = (uint8_t)(_x),\ + (_ptr)[2] = (uint8_t)((_x)>>8),\ + (_ptr)[1] = (uint8_t)((_x)>>16),\ + (_ptr)[0] = (uint8_t)((_x)>>24)) + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +// AES s-box table +static const uint8_t _CryptAes_Sbox[256] = +{ + 0x63,0x7C,0x77,0x7B, 0xF2,0x6B,0x6F,0xC5, 0x30,0x01,0x67,0x2B, 0xFE,0xD7,0xAB,0x76, + 0xCA,0x82,0xC9,0x7D, 0xFA,0x59,0x47,0xF0, 0xAD,0xD4,0xA2,0xAF, 0x9C,0xA4,0x72,0xC0, + 0xB7,0xFD,0x93,0x26, 0x36,0x3F,0xF7,0xCC, 0x34,0xA5,0xE5,0xF1, 0x71,0xD8,0x31,0x15, + 0x04,0xC7,0x23,0xC3, 0x18,0x96,0x05,0x9A, 0x07,0x12,0x80,0xE2, 0xEB,0x27,0xB2,0x75, + 0x09,0x83,0x2C,0x1A, 0x1B,0x6E,0x5A,0xA0, 0x52,0x3B,0xD6,0xB3, 0x29,0xE3,0x2F,0x84, + 0x53,0xD1,0x00,0xED, 0x20,0xFC,0xB1,0x5B, 0x6A,0xCB,0xBE,0x39, 0x4A,0x4C,0x58,0xCF, + 0xD0,0xEF,0xAA,0xFB, 0x43,0x4D,0x33,0x85, 0x45,0xF9,0x02,0x7F, 0x50,0x3C,0x9F,0xA8, + 0x51,0xA3,0x40,0x8F, 0x92,0x9D,0x38,0xF5, 0xBC,0xB6,0xDA,0x21, 0x10,0xFF,0xF3,0xD2, + 0xCD,0x0C,0x13,0xEC, 0x5F,0x97,0x44,0x17, 0xC4,0xA7,0x7E,0x3D, 0x64,0x5D,0x19,0x73, + 0x60,0x81,0x4F,0xDC, 0x22,0x2A,0x90,0x88, 0x46,0xEE,0xB8,0x14, 0xDE,0x5E,0x0B,0xDB, + 0xE0,0x32,0x3A,0x0A, 0x49,0x06,0x24,0x5C, 0xC2,0xD3,0xAC,0x62, 0x91,0x95,0xE4,0x79, + 0xE7,0xC8,0x37,0x6D, 0x8D,0xD5,0x4E,0xA9, 0x6C,0x56,0xF4,0xEA, 0x65,0x7A,0xAE,0x08, + 0xBA,0x78,0x25,0x2E, 0x1C,0xA6,0xB4,0xC6, 0xE8,0xDD,0x74,0x1F, 0x4B,0xBD,0x8B,0x8A, + 0x70,0x3E,0xB5,0x66, 0x48,0x03,0xF6,0x0E, 0x61,0x35,0x57,0xB9, 0x86,0xC1,0x1D,0x9E, + 0xE1,0xF8,0x98,0x11, 0x69,0xD9,0x8E,0x94, 0x9B,0x1E,0x87,0xE9, 0xCE,0x55,0x28,0xDF, + 0x8C,0xA1,0x89,0x0D, 0xBF,0xE6,0x42,0x68, 0x41,0x99,0x2D,0x0F, 0xB0,0x54,0xBB,0x16 +}; + +// AES is-box table +static const uint8_t _CryptAes_Isbox[256] = +{ + 0x52,0x09,0x6a,0xd5, 0x30,0x36,0xa5,0x38, 0xbf,0x40,0xa3,0x9e, 0x81,0xf3,0xd7,0xfb, + 0x7c,0xe3,0x39,0x82, 0x9b,0x2f,0xff,0x87, 0x34,0x8e,0x43,0x44, 0xc4,0xde,0xe9,0xcb, + 0x54,0x7b,0x94,0x32, 0xa6,0xc2,0x23,0x3d, 0xee,0x4c,0x95,0x0b, 0x42,0xfa,0xc3,0x4e, + 0x08,0x2e,0xa1,0x66, 0x28,0xd9,0x24,0xb2, 0x76,0x5b,0xa2,0x49, 0x6d,0x8b,0xd1,0x25, + 0x72,0xf8,0xf6,0x64, 0x86,0x68,0x98,0x16, 0xd4,0xa4,0x5c,0xcc, 0x5d,0x65,0xb6,0x92, + 0x6c,0x70,0x48,0x50, 0xfd,0xed,0xb9,0xda, 0x5e,0x15,0x46,0x57, 0xa7,0x8d,0x9d,0x84, + 0x90,0xd8,0xab,0x00, 0x8c,0xbc,0xd3,0x0a, 0xf7,0xe4,0x58,0x05, 0xb8,0xb3,0x45,0x06, + 0xd0,0x2c,0x1e,0x8f, 0xca,0x3f,0x0f,0x02, 0xc1,0xaf,0xbd,0x03, 0x01,0x13,0x8a,0x6b, + 0x3a,0x91,0x11,0x41, 0x4f,0x67,0xdc,0xea, 0x97,0xf2,0xcf,0xce, 0xf0,0xb4,0xe6,0x73, + 0x96,0xac,0x74,0x22, 0xe7,0xad,0x35,0x85, 0xe2,0xf9,0x37,0xe8, 0x1c,0x75,0xdf,0x6e, + 0x47,0xf1,0x1a,0x71, 0x1d,0x29,0xc5,0x89, 0x6f,0xb7,0x62,0x0e, 0xaa,0x18,0xbe,0x1b, + 0xfc,0x56,0x3e,0x4b, 0xc6,0xd2,0x79,0x20, 0x9a,0xdb,0xc0,0xfe, 0x78,0xcd,0x5a,0xf4, + 0x1f,0xdd,0xa8,0x33, 0x88,0x07,0xc7,0x31, 0xb1,0x12,0x10,0x59, 0x27,0x80,0xec,0x5f, + 0x60,0x51,0x7f,0xa9, 0x19,0xb5,0x4a,0x0d, 0x2d,0xe5,0x7a,0x9f, 0x93,0xc9,0x9c,0xef, + 0xa0,0xe0,0x3b,0x4d, 0xae,0x2a,0xf5,0xb0, 0xc8,0xeb,0xbb,0x3c, 0x83,0x53,0x99,0x61, + 0x17,0x2b,0x04,0x7e, 0xba,0x77,0xd6,0x26, 0xe1,0x69,0x14,0x63, 0x55,0x21,0x0c,0x7d +}; + +// AES Rcon (round constant word) table +static const uint8_t _CryptAes_Rcon[30] = +{ + 0x01,0x02,0x04,0x08, 0x10,0x20,0x40,0x80, 0x1b,0x36,0x6c,0xd8, 0xab,0x4d,0x9a,0x2f, + 0x5e,0xbc,0x63,0xc6, 0x97,0x35,0x6a,0xd4, 0xb3,0x7d,0xfa,0xef, 0xc5,0x91 +}; + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _CryptAesMul2 + + \Description + Optimized implementation of AES mul-by-two operation x4 using shifts and adds. + + \Input uValue - four-byte composite value to mul-by-two + + \Output + uint32_t - mul-by-two result + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _CryptAesMul2(uint32_t uValue) +{ + static const uint32_t _mt = 0x80808080; + static const uint32_t _mh = 0xfefefefe; + static const uint32_t _mm = 0x1b1b1b1b; + + uint32_t uTemp = uValue & _mt; + uTemp = ((uValue + uValue) & _mh) ^ ((uTemp - (uTemp >> 7)) & _mm); + return(uTemp); +} + +/*F********************************************************************************/ +/*! + \Function _CryptAesXtime + + \Description + AES xtime operation + + \Input uValue - input state value + + \Output + uint8_t - xtime result + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t _CryptAesXtime(uint32_t uValue) +{ + uValue = (uValue & 0x80) ? (uValue << 1) ^ 0x1b : uValue << 1; + return((uint8_t)uValue); +} + +/*F********************************************************************************/ +/*! + \Function _CryptAesInit + + \Description + Initialize crypt state based in input key and initialization vector + + \Input *pAes - cipher state + \Input *pKeyBuf - input key + \Input iKeyLen - length of key in bytes (may be 16 for 128 bit AES, or 32 for 256 bit AES) + \Input *pInitVec - initialization vector for CBC mode + \Input uKeyType - key type (CRYPTAES_KEYTYPE_*) + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptAesInit(CryptAesT *pAes, const uint8_t *pKeyBuf, int32_t iKeyLen, const uint8_t *pInitVec, uint32_t uKeyType) +{ + // init AES key schedule + CryptAesInitKeySchedule(&pAes->KeySchedule, pKeyBuf, iKeyLen, uKeyType); + + // copy the initialization vector + ds_memcpy(pAes->aInitVec, pInitVec, sizeof(pAes->aInitVec)); +} + +/*F********************************************************************************/ +/*! + \Function _CryptAesInvMixCol + + \Description + Perform inverse mix column operation on a key schedule word + + \Input uValue - key schedule word to perform operation on + + \Output + uint32_t - operation result + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _CryptAesInvMixCol(uint32_t uValue) +{ + uint32_t a0, a1, a2, a3; + + a0 = _CryptAesMul2(uValue); + a1 = _CryptAesMul2(a0); + a2 = _CryptAesMul2(a1); + a3 = uValue ^ a2; + a2 = a0 ^ a1 ^ a2; + a0 ^= a3; + a1 ^= a3; + a2 ^= CRYPTAES_ROT3(a0); + a2 ^= CRYPTAES_ROT2(a1); + a2 ^= CRYPTAES_ROT1(a3); + return(a2); +} + +/*F********************************************************************************/ +/*! + \Function _CryptAesInvertKey + + \Description + Invert key schedule, used for decryption + + \Input *pKeySchedule - key schedule + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptAesInvertKey(CryptAesKeyScheduleT *pKeySchedule) +{ + uint32_t uCount, *pState; + for (uCount = 4, pState = pKeySchedule->aKeySchedule; uCount < (unsigned)pKeySchedule->uNumRounds*4; uCount += 1) + { + pState[uCount] = _CryptAesInvMixCol(pState[uCount]); + } +} + +/*F********************************************************************************/ +/*! + \Function _CryptAesEncrypt + + \Description + Encrypt a single block + + \Input *pKeySchedule - cipher key schedule + \Input *pData - [inp/out] data to encrypt, storage for encrypted data + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptAesEncrypt(const CryptAesKeyScheduleT *pKeySchedule, uint32_t *pData) +{ + const uint32_t *pState = pKeySchedule->aKeySchedule; + const uint8_t *pSbox = _CryptAes_Sbox; + uint32_t uRound, uNumRounds; + uint32_t uRow, aRowData[4]; + uint32_t a0, a1, a2, a3, t0, t1; + + // handle pre-round key addition + for (uRow = 0; uRow < 4; uRow += 1, pState += 1) + { + pData[uRow] ^= *pState; + } + + // encrypt a single block of data + for (uRound = 0, uNumRounds = pKeySchedule->uNumRounds; uRound < uNumRounds; uRound += 1) + { + // ByteSub+ShiftRow + for (uRow = 0; uRow < 4; uRow += 1) + { + a0 = (uint32_t)pSbox[(pData[uRow%4] >> 24) & 0xff]; + a1 = (uint32_t)pSbox[(pData[(uRow+1)%4] >> 16) & 0xff]; + a2 = (uint32_t)pSbox[(pData[(uRow+2)%4] >> 8) & 0xff]; + a3 = (uint32_t)pSbox[(pData[(uRow+3)%4]) & 0xff]; + + // MixColumn (unless last round) + if (uRound != (uNumRounds - 1)) + { + t0 = a0 ^ a1 ^ a2 ^ a3; + t1 = a0; + + a0 ^= t0 ^ _CryptAesXtime(a0 ^ a1); + a1 ^= t0 ^ _CryptAesXtime(a1 ^ a2); + a2 ^= t0 ^ _CryptAesXtime(a2 ^ a3); + a3 ^= t0 ^ _CryptAesXtime(a3 ^ t1); + } + + aRowData[uRow] = ((a0 << 24) | (a1 << 16) | (a2 << 8) | a3); + } + + // KeyAddition + for (uRow = 0; uRow < 4; uRow += 1, pState += 1) + { + pData[uRow] = aRowData[uRow] ^ *pState; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _CryptAesEncryptCBC + + \Description + CBC encrypt of encrypted data blocks (must be block-sized). + + \Input *pAes - cipher state + \Input *pBuffer - [inp/out] data to encrypt, storage for encrypted data + \Input iLength - data length in bytes + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptAesEncryptCBC(CryptAesT *pAes, uint8_t *pBuffer, int32_t iLength) +{ + uint32_t s0, s1, s2, s3; + uint32_t a0, a1, a2, a3; + uint32_t aBlock[4]; + + // read the input vector from bytes into words + s0 = CRYPTAES_RDWORD(pAes->aInitVec+0); + s1 = CRYPTAES_RDWORD(pAes->aInitVec+4); + s2 = CRYPTAES_RDWORD(pAes->aInitVec+8); + s3 = CRYPTAES_RDWORD(pAes->aInitVec+12); + + // encrypt the data + for (iLength -= 16; iLength >= 0; iLength -= 16, pBuffer += 16) + { + // read a block of data from bytes into words + a0 = CRYPTAES_RDWORD(pBuffer+0); + a1 = CRYPTAES_RDWORD(pBuffer+4); + a2 = CRYPTAES_RDWORD(pBuffer+8); + a3 = CRYPTAES_RDWORD(pBuffer+12); + + // xor CBC state against data prior to encryption + aBlock[0] = a0 ^ s0; + aBlock[1] = a1 ^ s1; + aBlock[2] = a2 ^ s2; + aBlock[3] = a3 ^ s3; + + // encrypt the block + _CryptAesEncrypt(&pAes->KeySchedule, aBlock); + + // update state + s0 = aBlock[0]; + s1 = aBlock[1]; + s2 = aBlock[2]; + s3 = aBlock[3]; + + // write encrypted data to buffer + CRYPTAES_WRWORD(s0, pBuffer+0); + CRYPTAES_WRWORD(s1, pBuffer+4); + CRYPTAES_WRWORD(s2, pBuffer+8); + CRYPTAES_WRWORD(s3, pBuffer+12); + } + + // write back updated state + CRYPTAES_WRWORD(s0, pAes->aInitVec+0); + CRYPTAES_WRWORD(s1, pAes->aInitVec+4); + CRYPTAES_WRWORD(s2, pAes->aInitVec+8); + CRYPTAES_WRWORD(s3, pAes->aInitVec+12); +} + +/*F********************************************************************************/ +/*! + \Function _CryptAesDecrypt + + \Description + Decrypt a single block + + \Input *pKeySchedule - cipher key schedule + \Input *pData - [inp/out] data to decrypt, storage for decrypted data + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptAesDecrypt(const CryptAesKeyScheduleT *pKeySchedule, uint32_t *pData) +{ + uint32_t uRound, uNumRounds = pKeySchedule->uNumRounds; + const uint32_t *pState = pKeySchedule->aKeySchedule + ((uNumRounds + 1) * 4); + const uint8_t *pIsbox = _CryptAes_Isbox; + uint32_t uRow, aRowData[4]; + uint32_t a0, a1, a2, a3; + uint32_t xt0, xt1, xt2, xt3, xt4, xt5, xt6; + + // handle pre-round key addition + for (uRow = 4; uRow > 0; uRow -= 1) + { + pData[uRow-1] ^= *(--pState); + } + + // decrypt a single block of data + for (uRound = 0; uRound < uNumRounds; uRound += 1) + { + // ByteSub+ShiftRow + for (uRow = 4; uRow > 0; uRow -= 1) + { + a0 = pIsbox[(pData[(uRow+3)%4] >> 24) & 0xff]; + a1 = pIsbox[(pData[(uRow+2)%4] >> 16) & 0xff]; + a2 = pIsbox[(pData[(uRow+1)%4] >> 8) & 0xff]; + a3 = pIsbox[(pData[uRow%4]) & 0xff]; + + // MixColumn (unless last row) + if (uRound != (uNumRounds - 1)) + { + xt0 = _CryptAesXtime(a0 ^ a1); + xt1 = _CryptAesXtime(a1 ^ a2); + xt2 = _CryptAesXtime(a2 ^ a3); + xt3 = _CryptAesXtime(a3 ^ a0); + xt4 = _CryptAesXtime(xt0 ^ xt1); + xt5 = _CryptAesXtime(xt1 ^ xt2); + xt6 = _CryptAesXtime(xt4 ^ xt5); + + xt0 ^= a1 ^ a2 ^ a3 ^ xt4 ^ xt6; + xt1 ^= a0 ^ a2 ^ a3 ^ xt5 ^ xt6; + xt2 ^= a0 ^ a1 ^ a3 ^ xt4 ^ xt6; + xt3 ^= a0 ^ a1 ^ a2 ^ xt5 ^ xt6; + + aRowData[uRow-1] = (xt0 << 24) | (xt1 << 16) | (xt2 << 8) | xt3; + } + else + { + aRowData[uRow-1] = (a0 << 24) | (a1 << 16) | (a2 << 8) | a3; + } + } + + // KeyAddition + for (uRow = 4; uRow > 0; uRow -= 1) + { + pData[uRow-1] = aRowData[uRow-1] ^ *(--pState); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _CryptAesDecryptCBC + + \Description + CBC decrypt of encrypted data blocks (must be block-sized). + + \Input *pAes - cipher state + \Input *pBuffer - [inp/out] data to decrypt, storage for decrypted data + \Input iLength - data length in bytes + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptAesDecryptCBC(CryptAesT *pAes, uint8_t *pBuffer, int32_t iLength) +{ + uint32_t s0, s1, s2, s3; + uint32_t a0, a1, a2, a3; + uint32_t t0, t1, t2, t3; + uint32_t aBlock[4]; + + // read the input vector from bytes into words + s0 = CRYPTAES_RDWORD(pAes->aInitVec+0); + s1 = CRYPTAES_RDWORD(pAes->aInitVec+4); + s2 = CRYPTAES_RDWORD(pAes->aInitVec+8); + s3 = CRYPTAES_RDWORD(pAes->aInitVec+12); + + // encrypt the data + for (iLength -= 16; iLength >= 0; iLength -= 16, pBuffer += 16) + { + // read a block of data from bytes into words + a0 = CRYPTAES_RDWORD(pBuffer+0); + a1 = CRYPTAES_RDWORD(pBuffer+4); + a2 = CRYPTAES_RDWORD(pBuffer+8); + a3 = CRYPTAES_RDWORD(pBuffer+12); + + // set up for decrypt + aBlock[0] = a0; + aBlock[1] = a1; + aBlock[2] = a2; + aBlock[3] = a3; + + // encrypt the block + _CryptAesDecrypt(&pAes->KeySchedule, aBlock); + + // undo xor against state + t0 = aBlock[0] ^ s0; + t1 = aBlock[1] ^ s1; + t2 = aBlock[2] ^ s2; + t3 = aBlock[3] ^ s3; + + // update state + s0 = a0; + s1 = a1; + s2 = a2; + s3 = a3; + + // write encrypted data to buffer + CRYPTAES_WRWORD(t0, pBuffer+0); + CRYPTAES_WRWORD(t1, pBuffer+4); + CRYPTAES_WRWORD(t2, pBuffer+8); + CRYPTAES_WRWORD(t3, pBuffer+12); + } + + // write back updated state + CRYPTAES_WRWORD(s0, pAes->aInitVec+0); + CRYPTAES_WRWORD(s1, pAes->aInitVec+4); + CRYPTAES_WRWORD(s2, pAes->aInitVec+8); + CRYPTAES_WRWORD(s3, pAes->aInitVec+12); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CryptAesInit + + \Description + Init the AES cipher with the specified key + + \Input *pAes - cipher state to initialize + \Input *pKeyBuf - pointer to key + \Input iKeyLen - key length + \Input uKeyType - type of crypt operation (CRYPTAES_KEYTYPE_*) + \Input *pInitVec - initialization vector + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptAesInit(CryptAesT *pAes, const uint8_t *pKeyBuf, int32_t iKeyLen, uint32_t uKeyType, const uint8_t *pInitVec) +{ + // clear state + ds_memclr(pAes, sizeof(*pAes)); + // init state + _CryptAesInit(pAes, pKeyBuf, iKeyLen, pInitVec, uKeyType); +} + +/*F********************************************************************************/ +/*! + \Function CryptAesEncrypt + + \Description + Encrypt data with the AES cipher in CBC mode + + \Input *pAes - cipher state + \Input *pBuffer - [inp/out] data to encrypt + \Input iLength - length of data to encrypt (must be a multiple of 16) + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptAesEncrypt(CryptAesT *pAes, uint8_t *pBuffer, int32_t iLength) +{ + _CryptAesEncryptCBC(pAes, pBuffer, iLength); +} + +/*F********************************************************************************/ +/*! + \Function CryptAesEncryptBlock + + \Description + Encrypt 16 byte block with the AES cipher + + \Input *pKeySchedule - cipher key schedule + \Input *pInput - [inp] data to encrypt + \Input *pOutput - [out] encrypted data + + \Notes + pInput and pOutput may overlap + + \Version 07/15/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptAesEncryptBlock(CryptAesKeyScheduleT *pKeySchedule, const uint8_t *pInput, uint8_t *pOutput) +{ + uint32_t aBlock[4]; + + // read a block of data from bytes into words + aBlock[0] = CRYPTAES_RDWORD(pInput+0); + aBlock[1] = CRYPTAES_RDWORD(pInput+4); + aBlock[2] = CRYPTAES_RDWORD(pInput+8); + aBlock[3] = CRYPTAES_RDWORD(pInput+12); + + _CryptAesEncrypt(pKeySchedule, aBlock); + + // write encrypted data to buffer + CRYPTAES_WRWORD(aBlock[0], pOutput+0); + CRYPTAES_WRWORD(aBlock[1], pOutput+4); + CRYPTAES_WRWORD(aBlock[2], pOutput+8); + CRYPTAES_WRWORD(aBlock[3], pOutput+12); +} + +/*F********************************************************************************/ +/*! + \Function CryptAesDecrypt + + \Description + Decrypt data with the AES cipher in CBC mode + + \Input *pAes - cipher state + \Input *pBuffer - [inp/out] data to decrypt + \Input iLength - length of data to decrypt (must be a multiple of 16) + + \Version 01/20/2011 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptAesDecrypt(CryptAesT *pAes, uint8_t *pBuffer, int32_t iLength) +{ + _CryptAesDecryptCBC(pAes, pBuffer, iLength); +} + +/*F********************************************************************************/ +/*! + \Function CryptAesDecryptBlock + + \Description + Decrypt 16 byte block with the AES cipher + + \Input *pKeySchedule - cipher key schedule + \Input *pInput - [inp] data to encrypt + \Input *pOutput - [out] encrypted data + + \Notes + pInput and pOutput may overlap + + \Version 07/15/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptAesDecryptBlock(CryptAesKeyScheduleT *pKeySchedule, const uint8_t *pInput, uint8_t *pOutput) +{ + uint32_t aBlock[4]; + + // read a block of data from bytes into words + aBlock[0] = CRYPTAES_RDWORD(pInput+0); + aBlock[1] = CRYPTAES_RDWORD(pInput+4); + aBlock[2] = CRYPTAES_RDWORD(pInput+8); + aBlock[3] = CRYPTAES_RDWORD(pInput+12); + + _CryptAesDecrypt(pKeySchedule, aBlock); + + // write decrypted data to buffer + CRYPTAES_WRWORD(aBlock[0], pOutput+0); + CRYPTAES_WRWORD(aBlock[1], pOutput+4); + CRYPTAES_WRWORD(aBlock[2], pOutput+8); + CRYPTAES_WRWORD(aBlock[3], pOutput+12); +} + +/*F********************************************************************************/ +/*! + \Function CryptAesInitKeySchedule + + \Description + Init AES key schedule + + \Input *pKeySchedule - cipher key schedule + \Input *pKeyBuf - cipher key + \Input iKeyLen - cipher key length + \Input uKeyType - cipher key type (CRYPTAES_KEYTYPE_*) + + \Version 07/15/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptAesInitKeySchedule(CryptAesKeyScheduleT *pKeySchedule, const uint8_t *pKeyBuf, int32_t iKeyLen, uint32_t uKeyType) +{ + uint32_t uNumRounds, uKeyWords, uWord; + uint32_t uCount, uTemp, uTemp2; + const uint8_t *pRcon = _CryptAes_Rcon; + const uint8_t *pSbox = _CryptAes_Sbox; + uint32_t *pState; + + if (iKeyLen == 16) + { + uNumRounds = 10; + uKeyWords = 4; + } + else if (iKeyLen == 32) + { + uNumRounds = 14; + uKeyWords = 8; + } + else + { + NetPrintf(("cryptaes: key length of %d is not supported\n", iKeyLen)); + return; + } + + // save in state + pKeySchedule->uNumRounds = uNumRounds; + pKeySchedule->uKeyWords = uKeyWords; + + // copy key to key state + for (uWord = 0, pState = pKeySchedule->aKeySchedule; uWord < uKeyWords; uWord += 1, pKeyBuf += 4) + { + pState[uWord] = ((uint32_t)pKeyBuf[0]<<24) | ((uint32_t)pKeyBuf[1]<<16) | ((uint32_t)pKeyBuf[2]<<8) | ((uint32_t)pKeyBuf[3]); + } + + // key expansion + for (uWord = uKeyWords, uCount = (pKeySchedule->uNumRounds+1) * 4; uWord < uCount; uWord += 1) + { + // read key state value + uTemp = pState[uWord-1]; + + // key expansion + if ((uWord % uKeyWords) == 0) + { + uTemp2 = (uint32_t)pSbox[uTemp&0xff]<<8; + uTemp2 |= (uint32_t)pSbox[(uTemp>>8)&0xff]<<16; + uTemp2 |= (uint32_t)pSbox[(uTemp>>16)&0xff]<<24; + uTemp2 |= (uint32_t)pSbox[uTemp>>24]; + uTemp = uTemp2 ^ (((uint32_t)*pRcon)<<24); + pRcon += 1; + } + if ((uKeyWords == 8) && ((uWord % uKeyWords) == 4)) + { + uTemp2 = (uint32_t)pSbox[uTemp&0xff]; + uTemp2 |= (uint32_t)pSbox[(uTemp>>8)&0xff]<<8; + uTemp2 |= (uint32_t)pSbox[(uTemp>>16)&0xff]<<16; + uTemp2 |= (uint32_t)pSbox[uTemp>>24]<<24; + uTemp = uTemp2; + } + + // write back to key schedule + pState[uWord] = pState[uWord-uKeyWords] ^ uTemp; + } + + // if decrypt, invert key schedule + if (uKeyType == CRYPTAES_KEYTYPE_DECRYPT) + { + _CryptAesInvertKey(pKeySchedule); + } +} + diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptarc4.c b/src/thirdparty/dirtysdk/source/crypt/cryptarc4.c new file mode 100644 index 00000000..93ce1932 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptarc4.c @@ -0,0 +1,409 @@ +/*H********************************************************************************/ +/*! + \File cryptarc4.c + + \Description + This module is a from-scratch ARC4 implementation designed to avoid + any intellectual property complications. The ARC4 stream cipher is + known to produce output that is compatible with the RC4 stream cipher. + + \Notes + This algorithm from this cypher was taken from the web site: + ciphersaber.gurus.com. It is based on the RC4 compatible algorithm that + was published in the 2nd ed of the book Applied Cryptography by Bruce + Schneier. This is a private-key stream cipher which means that some other + secure way of exchanging cipher keys prior to algorithm use is required. + Its strength is directly related to the key exchange algorithm strength. + In operation, each individual stream message must use a unique key. This + is handled by appending on 10 byte random value onto the private key. This + 10-byte data can be sent by public means to the receptor (or included at the + start of a file or whatever). When the private key is combined with this + public key, it essentially puts the cypher into a random starting state (it + is like using a message digest routine to generate a random hash for + password comparison). The code below was written from scratch using only + a textual algorithm description. + + \Copyright + Copyright (c) 2000-2005 Electronic Arts Inc. + + \Version 1.0 02/25/2000 (gschaefer) First Version + \Version 1.1 11/06/2002 (jbrookes) Removed Create()/Destroy() to eliminate mem + alloc dependencies. +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/crypt/cryptarc4.h" +#include "DirtySDK/crypt/cryptrand.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Private variables + +// Public variables + + +/*** Private Functions ************************************************************/ + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CryptArc4Init + + \Description + Initialize state for ARC4 encryption/decryption module. + + \Input *pState - module state + \Input *pKeyBuf - pointer to crypt key + \Input iKeyLen - length of crypt key (sixteen for RC4-128) + \Input iIter - internal iterator (1=RC4 compat, larger for slightly improved security + + \Version 02/26/2000 (gschaefer) +*/ +/********************************************************************************F*/ +void CryptArc4Init(CryptArc4T *pState, const uint8_t *pKeyBuf, int32_t iKeyLen, int32_t iIter) +{ + uint32_t uWalk; + uint8_t uSwap; + uint8_t uTemp0; + uint8_t uTemp1; + + // clamp iteration count + if (iIter < 1) + { + iIter = 1; + } + + // reset the permutators + pState->walk = 0; + pState->swap = 0; + + // init the state array + for (uWalk = 0; uWalk < 256; ++uWalk) + { + pState->state[uWalk] = (uint8_t)uWalk; + } + + // only do setup if key is valid + if ((iKeyLen > 0) && (iIter > 0)) + { + // mixup the state table + for (uWalk = 0, uSwap = 0; uWalk < 256; ++uWalk) + { + // determine the swap point + uSwap += pState->state[uWalk] + pKeyBuf[uWalk % iKeyLen]; + // swap the entries + uTemp0 = pState->state[uWalk]; + uTemp1 = pState->state[uSwap]; + pState->state[uWalk] = uTemp1; + pState->state[uSwap] = uTemp0; + } + + // advance state for added security (not RC4 compatible) + if (iIter > 1) + { + CryptArc4Advance(pState, iIter*256); + } + } +} + +/*F********************************************************************************/ +/*! + \Function CryptArc4Apply + + \Description + Apply the cipher to the data. Uses reversible XOR so calling twice undoes + the uncryption (assuming the key state has been reset). + + \Input *pState - module state + \Input *pBuffer - buffer to encrypt/decrypt + \Input iLength - length of buffer + + \Version 02/26/2000 (gschaefer) +*/ +/********************************************************************************F*/ +void CryptArc4Apply(CryptArc4T *pState, uint8_t *pBuffer, int32_t iLength) +{ + uint32_t uTemp0; + uint32_t uTemp1; + uint32_t uWalk = pState->walk; + uint32_t uSwap = pState->swap; + + // do each data byte + for (; iLength > 0; --iLength) + { + // determine the swap points + uWalk = (uWalk+1)&255; + uSwap = (uSwap+pState->state[uWalk])&255; + + // swap the state entries + uTemp0 = pState->state[uWalk]; + uTemp1 = pState->state[uSwap]; + pState->state[uWalk] = uTemp1; + pState->state[uSwap] = uTemp0; + + // apply the cypher + *pBuffer++ ^= pState->state[(uTemp0+uTemp1)&255]; + } + + // update the state + pState->walk = uWalk; + pState->swap = uSwap; +} + +/*F********************************************************************************/ +/*! + \Function CryptArc4Advance + + \Description + Advance the cipher state with by iLength bytes. + + \Input *pState - module state + \Input iLength - length to advance + + \Version 12/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptArc4Advance(CryptArc4T *pState, int32_t iLength) +{ + uint32_t uTemp0; + uint32_t uTemp1; + uint32_t uWalk = pState->walk; + uint32_t uSwap = pState->swap; + + // do each data byte + for (; iLength > 0; --iLength) + { + // determine the swap points + uWalk = (uWalk+1)&255; + uSwap = (uSwap+pState->state[uWalk])&255; + + // swap the state entries + uTemp0 = pState->state[uWalk]; + uTemp1 = pState->state[uSwap]; + pState->state[uWalk] = uTemp1; + pState->state[uSwap] = uTemp0; + } + + // update the state + pState->walk = uWalk; + pState->swap = uSwap; +} + +/*F********************************************************************************/ +/*! + \Function CryptArc4StringEncrypt + + \Description + Easy way to encode an asciiz string. The resulting string is iLen chars + of visible ascii characters. String characters must fall in the range of + 32 to 127 to be considered valid. + + \Input *pDst - pointer to output string + \Input iLen - length of output string + \Input *pSrc - pointer to source string (asciiz) + \Input *pKey - key to encrypt with + \Input iKey - length of key (-1=key is asciiz) + \Input iIter - number of initialization iterations + + \Version 01/10/2013 (jbrookes) Based on CryptSSC2 by GWS +*/ +/********************************************************************************F*/ +void CryptArc4StringEncrypt(char *pDst, int32_t iLen, const char *pSrc, const uint8_t *pKey, int32_t iKey, int32_t iIter) +{ + static uint8_t _CryptArc4_aRandom[32], _CryptArc4_bRandStateInit = FALSE; + static CryptArc4T _CryptArc4_Rand; + uint8_t uDat, uEnc; + int32_t iSum; + CryptArc4T Arc4Data; + + // get initial random data + if (_CryptArc4_bRandStateInit == FALSE) + { + CryptRandGet(_CryptArc4_aRandom, sizeof(_CryptArc4_aRandom)); + CryptArc4Init(&_CryptArc4_Rand, pKey, iKey, iIter); + _CryptArc4_bRandStateInit = TRUE; + } + + // init state for string encryption + CryptArc4Init(&Arc4Data, pKey, iKey, iIter); + + // update the random cipher + CryptArc4Apply(&_CryptArc4_Rand, _CryptArc4_aRandom, sizeof(_CryptArc4_aRandom)); + CryptArc4Init(&_CryptArc4_Rand, _CryptArc4_aRandom, sizeof(_CryptArc4_aRandom), iIter); + + // encrypt the string + for (uDat = 0, uEnc = 0; iLen > 1; pDst += 1, iLen -= 1) + { + // get source data + if (pSrc == NULL) + { + // append random fill data + CryptArc4Apply(&_CryptArc4_Rand, &uDat, 1); + // fix the range + uDat = 32+(uDat & 63); + } + else if ((uDat = *pSrc++) == '\0') + { + // append random fill data + pSrc = NULL; + } + + // deal with out of range data + if ((uDat < 32) || (uDat >= 127)) + { + uDat = 127; + } + + // get cipher character + CryptArc4Apply(&Arc4Data, &uEnc, 1); + + // encode the data + iSum = (96 - 32) + uDat + (uEnc % 96); + *pDst = (char)(32 + (iSum % 96)); + } + + // terminate buffer + if (iLen > 0) + { + *pDst = 0; + } +} + +/*F*************************************************************************************************/ +/*! + \Function CryptArc4StringDecrypt + + \Description + Decode an asciiz string encoded with CryptArc4StringEncrypt. + + \Input *pDst - pointer to output string + \Input iLen - length of output string + \Input *pSrc - pointer to source string (asciiz) + \Input *pKey - key to decrypt with + \Input iKey - length of key (-1=key is asciiz) + \Input iIter - number of initialization iterations + + \Version 01/10/2013 (jbrookes) Based on CryptSSC2 by GWS +*/ +/*************************************************************************************************F*/ +void CryptArc4StringDecrypt(char *pDst, int32_t iLen, const char *pSrc, const uint8_t *pKey, int32_t iKey, int32_t iIter) +{ + int32_t iIdx, iSum; + uint8_t uDat, uDec; + CryptArc4T Arc4; + + // setup initial state + CryptArc4Init(&Arc4, pKey, iKey, iIter); + + // decrypt the string + for (iIdx = 0, uDat = 0, uDec = 0; iIdx < iLen-1; pDst += 1) + { + // get encoded source + if ((uDat = pSrc[iIdx++]) == '\0') + { + break; + } + + // get cipher character + CryptArc4Apply(&Arc4, &uDec, 1); + + // decode the data + iSum = (96 - 32) + uDat - (uDec % 96); + *pDst = (char)(32 + (iSum % 96)); + if (*pDst == 127) + { + break; + } + } + + // terminate buffer + if (iIdx < iLen) + { + *pDst = '\0'; + } +} + +/*F*************************************************************************************************/ +/*! + \Function CryptArc4StringEncryptStaticCode + + \Description + This function decrypts an encrypted key/encoded string pair into the source plaintext string. + It also accepts the plaintext string as an argument to verify that the source plaintext string + matches the resulting decrypted string. If there is a mismatch updated key and encoded string + data is printed in c-style array form and an error is returned. The source plaintext string + parameter is only referenced in debug builds to prevent the plaintext string from leaking into + the release binary. The CryptArc4StringEncryptStatic() macro wrapper should always be used to + call this function, and the plaintext string itself should be defined via the preprocessor, + to prevent the plaintext string from leaking into the release binary. + + \Input *pStr - [in/out] pointer to encrypted string, and output buffer for decrypted string + \Input iStrSize - length of string buffer + \Input *pKey - key to decrypt with + \Input iKeySize - length of key + \Input *pStrSrc - decrypted key (debug only) + + \Output + int32_t - zero=success, negative means the decrypted string does not match the plaintext string (debug only) + + \Version 06/05/2014 (jbrookes) +*/ +/*************************************************************************************************F*/ +int32_t CryptArc4StringEncryptStaticCode(char *pStr, int32_t iStrSize, const uint8_t *pKey, int32_t iKeySize, const char *pStrSrc) +{ + CryptArc4T Arc4; + int32_t iResult = 0; + + // decrypt the url using RC4-dropN + CryptArc4Init(&Arc4, pKey, iKeySize, 3*1024); + CryptArc4Apply(&Arc4, (uint8_t *)pStr, iStrSize); + + /* validate the decrypted string matches what we expect; if not it means someone forgot + to update the encrypted data. we only compile this in a DEBUG build to prevent the + plaintext string from leaking into the executable */ + #if DIRTYCODE_DEBUG + if ((pStrSrc != NULL) && (strcmp(pStr, pStrSrc))) + { + uint8_t aKey[32], aEnc[1024]; + NetPrintf(("cryptarc4: mismatch between decrypted string %s and source string %s; encrypted string needs to be updated\n", pStr, pStrSrc)); + // set up url buffer to encrypt... we encrypt the whole buffer so first we clear it + ds_memclr(aEnc, sizeof(aEnc)); + ds_strnzcpy((char *)aEnc, pStrSrc, iStrSize); + // encrypt the url buffer using RC4-dropN + CryptRandGet(aKey, sizeof(aKey)); + CryptArc4Init(&Arc4, aKey, sizeof(aKey), 3*1024); + CryptArc4Apply(&Arc4, aEnc, iStrSize); + // format the encryption key and encrypted url buffer to debug output + NetPrintArray(aKey, sizeof(aKey), "strKey"); + NetPrintArray(aEnc, iStrSize, "strEnc"); + // return failure to caller + iResult = -1; + } + else + { + NetPrintf(("cryptarc4: decrypted string %s\n", pStr)); + } + #endif + + return(iResult); +} + diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptbn.c b/src/thirdparty/dirtysdk/source/crypt/cryptbn.c new file mode 100644 index 00000000..343692a1 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptbn.c @@ -0,0 +1,1375 @@ +/*H*************************************************************************************/ +/*! + \File cryptbn.c + + \Description + This module is implements big integer math needed for our cryptography + + \Copyright + Copyright (c) Electronic Arts 2017. ALL RIGHTS RESERVED. + + \Version 01/18/2017 (eesponda) First version split from CryptRSA +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include + +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/crypt/cryptbn.h" + +/*** Private Functions *****************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function _CryptBnLzCnt + + \Description + Implements the function to find the MSB + + \Input uValue - the input we are finding the msb for + + \Output + int32_t - the index of the MSB + + \Notes + Uses BitScanReverse on PC as some processors that completely support + the lzcnt instruction. + + Durango supports the lzcnt instruction so it can be used. + + The remaining platforms use the builtin as it has been supported by gcc + for some time. + + \Version 03/06/2017 (eesponda) +*/ +/*************************************************************************************H*/ +static int32_t _CryptBnLzCnt(ucrypt_t uValue) +{ +#if defined(DIRTYCODE_PC) + unsigned long uResult; + + #if UCRYPT_SIZE == 4 + _BitScanReverse(&uResult, uValue); + #else + _BitScanReverse64(&uResult, uValue); + #endif + return(uResult + 1); +#elif defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + #if UCRYPT_SIZE == 4 + return(UCRYPT_BITSIZE - __lzcnt(uValue)); + #else + return(UCRYPT_BITSIZE - __lzcnt64(uValue)); + #endif +#else + #if UCRYPT_SIZE == 4 + return(UCRYPT_BITSIZE - __builtin_clz(uValue)); + #elif DIRTYCODE_64BITPTR == 1 + return(UCRYPT_BITSIZE - __builtin_clzl(uValue)); + #else + return(UCRYPT_BITSIZE - __builtin_clzll(uValue)); + #endif +#endif +} + +/*F*************************************************************************************/ +/*! + \Function _CryptBnExpand + + \Description + Expands the width of the big number by one prepending an entry + + \Input *pState - the big number + \Input uNewEntry - the entry we are adding + + \Version 02/27/2017 (eesponda) +*/ +/*************************************************************************************H*/ +static void _CryptBnExpand(CryptBnT *pState, ucrypt_t uNewEntry) +{ + if (pState->iWidth < CRYPTBN_MAX_WIDTH) + { + pState->aData[pState->iWidth++] = uNewEntry; + } + else + { + NetPrintf(("cryptbn: tried to increase the size of number's width past the max\n")); + } +} + +/*F*************************************************************************************/ +/*! + \Function _CryptBnShrink + + \Description + Shrink until you find a non-zero entry or the width is one + + \Input *pState - the big number + + \Version 02/27/2017 (eesponda) +*/ +/*************************************************************************************H*/ +static void _CryptBnShrink(CryptBnT *pState) +{ + int32_t iWidth = pState->iWidth; + + while ((iWidth > 1) && (pState->aData[iWidth-1] == 0)) + { + iWidth -= 1; + } + pState->iWidth = iWidth; +} + +/*F*************************************************************************************/ +/*! + \Function _CryptBnSet + + \Description + Set an entry from the big number + + \Input *pState - the big number + \Input iIndex - the index of the entry + \Input uValue - the entry value + + \Notes + This helps us safely retrieve entries when the index is outside our bounds + + \Version 02/27/2017 (eesponda) +*/ +/*************************************************************************************H*/ +static void _CryptBnSet(CryptBnT *pState, int32_t iIndex, ucrypt_t uValue) +{ + if (iIndex < pState->iWidth) + { + pState->aData[iIndex] = uValue; + } + else if (iIndex < CRYPTBN_MAX_WIDTH) + { + pState->aData[iIndex] = uValue; + pState->iWidth = iIndex + 1; + } +} + +/*F*************************************************************************************/ +/*! + \Function _CryptBnIsZero + + \Description + Checks if a big number is zero + + \Input *pState - the big number + + \Output + uint8_t - TRUE if zero, FALSE otherwise + + \Version 02/17/2017 (eesponda) +*/ +/*************************************************************************************H*/ +static uint8_t _CryptBnIsZero(const CryptBnT *pState) +{ + int32_t iWidth; + + for (iWidth = pState->iWidth; iWidth > 0; iWidth -= 1) + { + if (pState->aData[iWidth-1] != 0) + { + return(FALSE); + } + } + return(TRUE); +} + +/*F*************************************************************************************/ +/*! + \Function _CryptBnIsEven + + \Description + Checks if a big number is even + + \Input *pState - the big number + + \Output + uint8_t - TRUE if even, FALSE if odd + + \Version 02/07/2018 (eesponda) +*/ +/*************************************************************************************H*/ +static uint8_t _CryptBnIsEven(const CryptBnT *pState) +{ + // we only check the bottom limb + return((pState->aData[0] & 1) == 0); +} + +/*F*************************************************************************************/ +/*! + \Function _CryptBnAdd + + \Description + Adds two big numbers together + + \Input *pState - the result of the operation + \Input *pAdd1 - the left side + \Input *pAdd2 - the right side + + \Version 02/17/2017 (eesponda) +*/ +/*************************************************************************************H*/ +static void _CryptBnAdd(CryptBnT *pState, const CryptBnT *pAdd1, const CryptBnT *pAdd2) +{ + ucrypt_t uAccum; + int32_t iWidth = DS_MAX(pAdd1->iWidth, pAdd2->iWidth), iCount; + + // do the add + for (uAccum = 0, iCount = 0; iCount < iWidth; iCount += 1) + { + ucrypt_t uResult = uAccum, uLhs = pAdd1->aData[iCount], uRhs = pAdd2->aData[iCount]; + // do the math, setting carry on overflow + uAccum = ((uResult += uLhs) < uLhs); + uAccum |= ((uResult += uRhs) < uRhs); + // set the value + _CryptBnSet(pState, iCount, uResult); + } + + if (uAccum != 0) + { + _CryptBnExpand(pState, uAccum); + } +} + +/*F*************************************************************************************/ +/*! + \Function _CryptBnSubtract + + \Description + Subtracts two big numbers + + \Input *pState - result of subtraction + \Input *pSub1 - the left hand side of the equation + \Input *pSub2 - the right hand side of the equation + + \Version 02/17/2017 (eesponda) +*/ +/*************************************************************************************H*/ +static void _CryptBnSubtract(CryptBnT *pState, const CryptBnT *pSub1, const CryptBnT *pSub2) +{ + ucrypt_t uAccum; + int32_t iWidth = DS_MAX(pSub1->iWidth, pSub2->iWidth), iCount; + + for (uAccum = 0, iCount = 0; iCount < iWidth; iCount += 1) + { + ucrypt_t uResult, uFinalResult; + ucrypt_t uLhs = pSub1->aData[iCount], uRhs = pSub2->aData[iCount]; + + // calculate the result without borrow + uResult = uLhs - uRhs; + // calculate the result after borrow + uFinalResult = uResult - uAccum; + + // calculate the new borrow if first or second calculation resulted in overflow + uAccum = (uResult > uLhs) | (uFinalResult > uResult); + + // save result + _CryptBnSet(pState, iCount, uFinalResult); + } +} + +/*** Public functions ******************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function CryptBnInit + + \Description + Initializes the large number with zero at a given width + + \Input *pState - pointer to output large number array of ucrypt_t units + \Input iWidth - width of output + + \Version 01/18/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnInit(CryptBnT *pState, int32_t iWidth) +{ + ds_memclr(pState, sizeof(*pState)); + pState->iWidth = iWidth; +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnInitSet + + \Description + Initializes the large number with a number + + \Input *pState - pointer to output large number array of ucrypt_t units + \Input uValue - value we are initializing the big number with + + \Version 01/18/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnInitSet(CryptBnT *pState, uint32_t uValue) +{ + ds_memclr(pState, sizeof(*pState)); + pState->iWidth = 1; + pState->aData[0] = uValue; +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnInitFrom + + \Description + Convert from msb bytes to int32_t number format + + \Input *pState - pointer to output large number array of ucrypt_t units + \Input iWidth - width of output - if negative, auto-calculate width + \Input *pSource - pointer to input large number array of bytes + \Input iLength - length of input in bytes + + \Output + int32_t - width in ucrypt_t units + + \Version 03/08/2002 (gschaefer) + \Version 03/03/2004 (sbevan) Handle odd iLength +*/ +/*************************************************************************************H*/ +int32_t CryptBnInitFrom(CryptBnT *pState, int32_t iWidth, const uint8_t *pSource, int32_t iLength) +{ + ucrypt_t *pResult = pState->aData; + int32_t iFullWordCount = iLength/sizeof(*pResult); + int32_t iWordCount = (iLength+sizeof(*pResult)-1)/sizeof(*pResult); + + // clear the contents + ds_memclr(pState, sizeof(*pState)); + + if (iWidth < 0) + { + iWidth = iWordCount; + } + pState->iWidth = iWidth; + + if (iFullWordCount != iWordCount) + { + /* calculate how many bytes we need to write and + generate a left shift amount based on that */ + int32_t iDiff = iLength - (iFullWordCount * UCRYPT_SIZE); + int32_t iLeftShiftAmount = (iDiff - 1) * 8; + + // encode the bytes into the buffer until there is nothing left + while (iLeftShiftAmount >= 0) + { + pResult[iWidth - 1] |= ((ucrypt_t)*pSource++ << iLeftShiftAmount); + iLeftShiftAmount -= 8; + } + iWidth -= 1; + } + for (; iWidth > 0; iWidth -= 1) + { + #if (UCRYPT_SIZE == 4) + pResult[iWidth-1] = (pSource[0] << 24) | (pSource[1] << 16) | (pSource[2] << 8) | (pSource[3] << 0); + #else + pResult[iWidth-1] = ((ucrypt_t)pSource[0] << 56) | ((ucrypt_t)pSource[1] << 48) | ((ucrypt_t)pSource[2] << 40) | ((ucrypt_t)pSource[3] << 32) | + ((ucrypt_t)pSource[4] << 24) | ((ucrypt_t)pSource[5] << 16) | ((ucrypt_t)pSource[6] << 8) | ((ucrypt_t)pSource[7] << 0); + #endif + pSource += UCRYPT_SIZE; + } + return(iWordCount); +} + + +/*F*************************************************************************************/ +/*! + \Function CryptBnInitLeFrom + + \Description + Convert the bytes into a big number in little-endian form + + \Input *pState - pointer to output large number array of ucrypt_t units + \Input *pSource - pointer to input large number array of bytes + \Input iLength - length of input in bytes + + \Output + int32_t - width in ucrypt_t units + + \Version 04/11/2018 (eesponda) +*/ +/*************************************************************************************H*/ +int32_t CryptBnInitLeFrom(CryptBnT *pState, const uint8_t *pSource, int32_t iLength) +{ + //int32_t iWidth; + ucrypt_t *pResult = pState->aData; + int32_t iWordCount = (iLength+sizeof(*pResult)-1)/sizeof(*pResult); + + // clear the contents and init + ds_memclr(pState, sizeof(*pState)); + pState->iWidth = iWordCount; + ds_memcpy_s(pState->aData, pState->iWidth*sizeof(*pState->aData), pSource, iLength); + + return(iWordCount); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnLeftShift + + \Description + Left shifts a large number by a bit + + \Input *pState - pointer to input and output large number array + + \Notes + pState:iWidth <-- pState:iWidth << 1 + + \Version 10/11/2011 (mdonais) +*/ +/*************************************************************************************H*/ +void CryptBnLeftShift(CryptBnT *pState) +{ + ucrypt_t uAccum; + int32_t iWidth, iCount; + + // do the shift + for (uAccum = 0, iCount = 0, iWidth = pState->iWidth; iCount < iWidth; iCount += 1) + { + ucrypt_t uResult, uFinalResult, uVal; + + uVal = pState->aData[iCount]; + uResult = uVal << 1; + uFinalResult = uResult + uAccum; + uAccum = (uResult < uVal); + pState->aData[iCount] = uFinalResult; + } + + // expand if necessary + if (uAccum != 0) + { + _CryptBnExpand(pState, uAccum); + } +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnLeftShift2 + + \Description + Left shifts a large number by a number of bits + + \Input *pState - pointer to input and output large number array + \Input iBits - the number of bits to shift + + \Notes + pState:iWidth <-- pState:iWidth << iBits + + \Version 4/14/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnLeftShift2(CryptBnT *pState, int32_t iBits) +{ + int32_t iOffset, iIndex; + const int32_t iBitLen = CryptBnBitLen(pState) + iBits; + + /* make sure this shift would not exceed the width of the number + normally I would just set to zero but I need to indicate to the caller + that the output might not be what they expected */ + if (iBitLen > CRYPTBN_MAX_BITS) + { + NetPrintf(("cryptbn: tried to left shift past the max width which will truncate all the data\n")); + return; + } + + /* if we are trying to shift up one ucrypt_t unit or more + then calculate the number of ucrypt_t units and the number + of leftover bits we need to calculate */ + if (iBits >= UCRYPT_BITSIZE) + { + iOffset = iBits / UCRYPT_BITSIZE; + iBits %= UCRYPT_BITSIZE; + + /* shift the array to the right (left shift) the number of full ucrypt_t units (UCRYPT_BITSIZE) + clear the lower ucrypt_t units + update the width */ + memmove(pState->aData+iOffset, pState->aData, pState->iWidth * sizeof(*pState->aData)); + ds_memclr(pState->aData, iOffset * sizeof(*pState->aData)); + pState->iWidth += iOffset; + + // if there are no left over bits then we have nothing left to do + if (iBits == 0) + { + return; + } + } + + // check if we past a ucrypt_t boundary and need to expand + if (iBitLen > pState->iWidth*UCRYPT_BITSIZE) + { + pState->iWidth += 1; + } + + // shift the items in the array over manually + for (iIndex = pState->iWidth-1; iIndex > 0; iIndex -= 1) + { + pState->aData[iIndex] = (pState->aData[iIndex] << iBits) | (pState->aData[iIndex - 1] >> (UCRYPT_BITSIZE - iBits)); + } + pState->aData[0] <<= iBits; +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnRightShift2 + + \Description + Right shifts a large number by a number of bits + + \Input *pState - pointer to input and output large number array + \Input iBits - the number of bits to shift + + \Notes + pState:iWidth <-- pState:iWidth >> iBits + + \Version 4/17/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnRightShift2(CryptBnT *pState, int32_t iBits) +{ + int32_t iIndex; + + /* if shifting right more than the bit length is the same + as setting to zero */ + if (CryptBnBitLen(pState) <= iBits) + { + CryptBnInitSet(pState, 0); + return; + } + + /* if we are trying to shift up one ucrypt_t unit or more + then calculate the number of ucrypt_t units and the number + of leftover bits we need to calculate */ + if (iBits >= UCRYPT_BITSIZE) + { + int32_t iOffset = iBits / UCRYPT_BITSIZE; + iBits %= UCRYPT_BITSIZE; + + /* shift the array to the left (right shift) the number of full ucrypt_t units (UCRYPT_BITSIZE) + update the width + clear the upper ucrypt_t units */ + memmove(pState->aData, pState->aData+iOffset, (pState->iWidth-iOffset)*sizeof(*pState->aData)); + pState->iWidth -= iOffset; + ds_memclr(pState->aData+pState->iWidth, iOffset*sizeof(*pState->aData)); + + // if there are no left over bits then we have nothing left to do + if (iBits == 0) + { + return; + } + } + + // shift the items in the array over manually + for (iIndex = 0; iIndex < pState->iWidth-1; iIndex += 1) + { + pState->aData[iIndex] = (pState->aData[iIndex] >> iBits) | (pState->aData[iIndex + 1] << (UCRYPT_BITSIZE - iBits)); + } + pState->aData[iIndex] >>= iBits; + + /* check if we past a ucrypt_t boundary and need to shrink + if the number of bits is at or under the next boundary then shrink to there + we do not shrink under 1, as that is handled at the top of the function */ + if ((pState->iWidth > 1) && (CryptBnBitLen(pState) <= ((pState->iWidth-1)*UCRYPT_BITSIZE))) + { + pState->iWidth -= 1; + } +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnBitTest + + \Description + Check to see if a particular bit is set within a large bit vector + + \Input *pState - pointer to input bit vector + \Input iBit - bit number to test (zero-relative) + + \Output + uint8_t - TRUE if set, else FALSE + + \Notes + uResult <-- pValue:iWidth & (1 << iBit) + + \Version 03/08/2002 (gschaefer) +*/ +/*************************************************************************************H*/ +uint8_t CryptBnBitTest(const CryptBnT *pState, int32_t iBit) +{ + int32_t iBitOff = iBit & (UCRYPT_BITSIZE-1); + int32_t iOffset = iBit / UCRYPT_BITSIZE; + return((pState->aData[iOffset] & ((ucrypt_t)1 << iBitOff)) != 0); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnBitSet + + \Description + Set a particular bit in the big number + + \Input *pState - pointer to input bit vector + \Input iBit - bit number to set (zero-relative) + + \Version 02/17/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnBitSet(CryptBnT *pState, int32_t iBit) +{ + int32_t iBitOff, iOffset; + + while (pState->iWidth*UCRYPT_BITSIZE <= iBit) + { + _CryptBnExpand(pState, 0); + } + iBitOff = iBit & (UCRYPT_BITSIZE - 1); + iOffset = iBit / UCRYPT_BITSIZE; + pState->aData[iOffset] |= ((ucrypt_t)1 << iBitOff); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnBitAnd + + \Description + Performs bitwise and on two big numbers + + \Input *pState - [out] output of the result of the operation + \Input *pLhs - big number on left hand side of operation + \Input *pRhs - big number on right hand side of operation + + \Version 04/06/2018 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnBitAnd(CryptBnT *pState, const CryptBnT *pLhs, const CryptBnT *pRhs) +{ + int32_t iWidth = DS_MAX(pLhs->iWidth, pRhs->iWidth), iCount; + for (iCount = 0; iCount < iWidth; iCount += 1) + { + _CryptBnSet(pState, iCount, pLhs->aData[iCount] & pRhs->aData[iCount]); + } + + // compact the big number in the case the operation zeroed out the upper limps + _CryptBnShrink(pState); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnBitXor + + \Description + Performs bitwise xor on two big numbers + + \Input *pState - [out] output of the result of the operation + \Input *pLhs - big number on left hand side of operation + \Input *pRhs - big number on right hand side of operation + + \Version 04/06/2018 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnBitXor(CryptBnT *pState, const CryptBnT *pLhs, const CryptBnT *pRhs) +{ + int32_t iWidth = DS_MAX(pLhs->iWidth, pRhs->iWidth), iCount; + for (iCount = 0; iCount < iWidth; iCount += 1) + { + _CryptBnSet(pState, iCount, pLhs->aData[iCount] ^ pRhs->aData[iCount]); + } + + // compact the big number in the case the operation zeroed out the upper limps + _CryptBnShrink(pState); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnBitLen + + \Description + Figures out the bit length of big number + + \Input *pState - big number state + + \Output + int32_t - the number of bits in the big number + + \Version 03/02/2017 (eesponda) +*/ +/*************************************************************************************H*/ +int32_t CryptBnBitLen(const CryptBnT *pState) +{ + int32_t iResult, iIndex; + for (iResult = 0, iIndex = pState->iWidth - 1; iIndex >= 0; iIndex -= 1) + { + if (pState->aData[iIndex] != 0) + { + iResult = _CryptBnLzCnt(pState->aData[iIndex]); + break; + } + } + return(iResult > 0 ? iResult + (UCRYPT_BITSIZE * iIndex) : 0); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnByteLen + + \Description + Figures out the byte length of big number + + \Input *pState - big number state + + \Output + int32_t - the number of bytes in the big number + + \Version 03/13/2018 (eesponda) +*/ +/*************************************************************************************H*/ +int32_t CryptBnByteLen(const CryptBnT *pState) +{ + return(pState->iWidth * (signed)sizeof(*pState->aData)); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnModAdd + + \Description + Adds to big numbers over a modulus + + \Input *pState - big number state + \Input *pAdd1 - first big number to add + \Input *pAdd2 - second big number to add + \Input *pMod - modulus we are adding over + + \Notes + This function does the redunction in a very simple way and isn't meant for + numbers very large over the modulus. We needed to make sure that if we use + the output for modulus multiply it would work under those constraints. + + \Version 04/11/2018 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnModAdd(CryptBnT *pState, const CryptBnT *pAdd1, const CryptBnT *pAdd2, const CryptBnT *pMod) +{ + // do the addition + _CryptBnAdd(pState, pAdd1, pAdd2); + + // reduce? + if (CryptBnCompare(pState, pMod) > 0) + { + CryptBnSubtract(pState, pState, pMod); + } +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnAccumulate + + \Description + Accumulate one large number into another + + \Input *pState - pointer to large number accumulation buffer + \Input *pAdd - pointer to large number to accumulate to accumulation buffer + + \Notes + pState:iWidth <-- pState:iWidth + pAdd:iWidth + + \Version 02/17/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnAccumulate(CryptBnT *pState, const CryptBnT *pAdd) +{ + if (pState->uSign == 0) + { + _CryptBnAdd(pState, pState, pAdd); + } + else + { + pState->uSign = 0; + CryptBnSubtract(pState, pAdd, pState); + } +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnIncrement + + \Description + Increment a large number by 1 + + \Input *pState - pointer to large number accumulation buffer + + \Version 02/21/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnIncrement(CryptBnT *pState) +{ + ucrypt_t uAccum; + int32_t iCount, iWidth; + + // do the add + for (uAccum = 1, iCount = 0, iWidth = pState->iWidth; iCount < iWidth; iCount += 1) + { + ucrypt_t uResult = uAccum, uVal = pState->aData[iCount]; + + uAccum = ((uResult += uVal) < uVal); + pState->aData[iCount] = uResult; + + // if we have nothing to carry over we are done + if (uAccum == 0) + { + break; + } + } + + if (uAccum != 0) + { + _CryptBnExpand(pState, uAccum); + } +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnSubtract + + \Description + Subtract two large numbers + + \Input *pState - pointer to output large number array + \Input *pSub1 - pointer to large number to subtract pSub2 from. + \Input *pSub2 - pointer to second large number + + \Notes + pState:iWidth <-- pSub1:iWidth - pSub2:iWidth + + \Version 02/17/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnSubtract(CryptBnT *pState, const CryptBnT *pSub1, const CryptBnT *pSub2) +{ + // if sub1 - (-sub2) or -sub1 - (sub2) then sub1 + sub2, sign depends on which side is negative + if (pSub1->uSign ^ pSub2->uSign) + { + // add + _CryptBnAdd(pState, pSub1, pSub2); + // set sign + pState->uSign = pSub1->uSign; + } + // if -sub1 - (-sub2) or sub1 - sub2 + else + { + const int32_t iCompare = CryptBnCompare(pSub1, pSub2); + + // if sub1 > sub2 + if (iCompare > 0) + { + // sub1 - sub2 + _CryptBnSubtract(pState, pSub1, pSub2); + // set sign + pState->uSign = pSub1->uSign; + } + // else if sub1 < sub2 + else if (iCompare < 0) + { + // sub2 - sub1 + _CryptBnSubtract(pState, pSub2, pSub1); + // set sign + pState->uSign = ~pSub1->uSign; + } + // else they are the same so it is zero + else + { + CryptBnInitSet(pState, 0); + } + } + + _CryptBnShrink(pState); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnDecrement + + \Description + Decrement a large number by 1 + + \Input *pState - pointer to output large number array + + \Version 02/21/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnDecrement(CryptBnT *pState) +{ + ucrypt_t uAccum; + int32_t iWidth, iCount; + + for (uAccum = 1, iCount = 0, iWidth = pState->iWidth; iCount < iWidth; iCount += 1) + { + ucrypt_t uResult, uVal = pState->aData[iCount]; + + // calculate the result after borrow + uResult = uVal - uAccum; + // calculate the new borrow if first or second calculation resulted in overflow + uAccum = (uResult > uVal); + // save result + _CryptBnSet(pState, iCount, uResult); + + // if nothing left to borrow we are done + if (uAccum == 0) + { + break; + } + } +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnModMultiply + + \Description + Modular multiply using the classical algorithm + + \Input *pState - pointer to output large number array + \Input *pMul1 - pointer to large number to multiply with pMul2 + \Input *pMul2 - pointer to second large number + \Input *pMod - pointer to modulous large number + + \Notes + pState:iWidth <-- pMul1:iWidth * pMul2:iWidth % pMod:iWidth + + \Version 02/27/2002 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnModMultiply(CryptBnT *pState, const CryptBnT *pMul1, const CryptBnT *pMul2, const CryptBnT *pMod) +{ + int32_t iCount; + CryptBnT Temp1; + CryptBnT *pCur = &Temp1; + uint32_t uSign = pMul2->uSign ^ pMul1->uSign; + + CryptBnInit(pCur, pMul1->iWidth); + + // do all the bits + for (iCount = CryptBnBitLen(pMul1) - 1; iCount >= 0; iCount -= 1) + { + // left shift the result + CryptBnLeftShift(pCur); + + // do modulus reduction? + if (CryptBnCompare(pCur, pMod) > 0) + { + CryptBnSubtract(pCur, pCur, pMod); + } + + // see if we need to add in multiplicand + if (CryptBnBitTest(pMul1, iCount)) + { + // add it in + CryptBnAccumulate(pCur, pMul2); + + // do modulus reduction? + if (CryptBnCompare(pCur, pMod) > 0) + { + CryptBnSubtract(pCur, pCur, pMod); + } + } + } + + // deal with going negative + if (uSign != 0) + { + CryptBnSubtract(pCur, pMod, pCur); + } + + // copy over the result + CryptBnClone(pState, pCur); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnMultiply + + \Description + Performs multiplication of two big numbers using the classical method + + \Input *pState - pointer to output large number array + \Input *pMul1 - pointer to large number to multiply with pMul2 + \Input *pMul2 - pointer to second large number + + \Notes + pState:iWidth <-- pMul1:iWidth * pMul2:iWidth + + \Version 02/28/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnMultiply(CryptBnT *pState, const CryptBnT *pMul1, const CryptBnT *pMul2) +{ + int32_t iCount; + CryptBnT Temp1; + CryptBnT *pCur = &Temp1; + + CryptBnInit(pCur, pMul1->iWidth); + + // do all the bits + for (iCount = CryptBnBitLen(pMul1) - 1; iCount >= 0; iCount -= 1) + { + // left shift the result + CryptBnLeftShift(pCur); + + // see if we need to add in multiplicand + if (CryptBnBitTest(pMul1, iCount)) + { + // add it in + CryptBnAccumulate(pCur, pMul2); + } + } + + pCur->uSign = pMul1->uSign ^ pMul2->uSign; + + // copy over the result + CryptBnClone(pState, pCur); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnMod + + \Description + Performs modulo / divison of dividend by divisor + + \Input *pDividend - the dividend in the equation + \Input *pDivisor - the divisor in the equation + \Input *pRemainder - [out] the result of the modulo operation can be NULL + \Input *pQuotient - [out] the result of the division operation can be NULL + + \Version 02/17/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnMod(const CryptBnT *pDividend, const CryptBnT *pDivisor, CryptBnT *pRemainder, CryptBnT *pQuotient) +{ + int32_t iCount; + CryptBnT Result, Quotient; + + /* if the dividend is less than the divisor means the result of + the modulus is the dividend and the quotient is zero */ + const int32_t iCompare = CryptBnCompare(pDividend, pDivisor); + if (iCompare < 0) + { + CryptBnClone(&Result, pDividend); + CryptBnInitSet(&Quotient, 0); + } + else if (iCompare > 0) + { + CryptBnInitSet(&Result, 0); + CryptBnInitSet(&Quotient, 0); + + /* since we don't need to perform the other operations until we increment + past zero, lets do this in a loop before we move onto the normal flow */ + for (iCount = CryptBnBitLen(pDividend) - 1; iCount >= 0; iCount -= 1) + { + if (CryptBnBitTest(pDividend, iCount)) + { + CryptBnIncrement(&Result); + CryptBnLeftShift(&Result); + break; + } + } + + for (iCount = iCount - 1; iCount >= 0; iCount -= 1) + { + if (CryptBnBitTest(pDividend, iCount)) + { + CryptBnIncrement(&Result); + } + + // check if we need to reduce, we can bypass when the limbs don't match + if ((Result.iWidth >= pDivisor->iWidth) && (CryptBnCompare(&Result, pDivisor) >= 0)) + { + CryptBnSubtract(&Result, &Result, pDivisor); + CryptBnBitSet(&Quotient, iCount); + } + + if (iCount > 0) + { + CryptBnLeftShift(&Result); + } + } + } + else + { + CryptBnInitSet(&Result, 0); + CryptBnInitSet(&Quotient, 1); + } + + if ((pDividend->uSign != 0) && (CryptBnBitLen(&Result) != 0)) + { + Result.uSign = 0; + CryptBnSubtract(&Result, pDivisor, &Result); + } + if (pRemainder != NULL) + { + CryptBnClone(pRemainder, &Result); + } + if (pQuotient != NULL) + { + CryptBnClone(pQuotient, &Quotient); + } +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnInverseMod + + \Description + Calculates the modular multiplicative inverse using binary extended gcd + + \Input *pState - the big number state + \Input *pMod - the modulus + + \Notes + See Handbook of Applied Cryptography Chapter 14.4.3 (14.61) + http://cacr.uwaterloo.ca/hac/about/chap14.pdf + + \Version 02/17/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnInverseMod(CryptBnT *pState, const CryptBnT *pMod) +{ + int32_t iShift; + CryptBnT U, V, A, B, C, D; + + // if the number is negative then switch the sign and subtract the result from mod + if (pState->uSign != 0) + { + pState->uSign = 0; + CryptBnInverseMod(pState, pMod); + CryptBnSubtract(pState, pMod, pState); + return; + } + + CryptBnClone(&U, pState); + CryptBnClone(&V, pMod); + CryptBnInitSet(&A, 1); + CryptBnInitSet(&B, 0); + CryptBnInitSet(&C, 0); + CryptBnInitSet(&D, 1); + + // while u and v are even divide by zero, accounting for the number of shifts + for (iShift = 0; (_CryptBnIsEven(&U) && _CryptBnIsEven(&V)); iShift += 1) + { + CryptBnRightShift(&U); + CryptBnRightShift(&V); + } + + // quit out when u is zero + while (!_CryptBnIsZero(&U)) + { + // while u is even + while (_CryptBnIsEven(&U)) + { + // u <- u/2 + CryptBnRightShift(&U); + + /* if a congruent to b congruent to 0 mod 2 (they are both even) + then a <- a/2 and b <- b/2 */ + if (_CryptBnIsEven(&A) && _CryptBnIsEven(&B)) + { + CryptBnRightShift(&A); + CryptBnRightShift(&B); + } + // else a <- (a+y)/2 and b <- (b-x)/2 + else + { + CryptBnAccumulate(&A, pMod); + CryptBnRightShift(&A); + CryptBnSubtract(&B, &B, pState); + CryptBnRightShift(&B); + } + } + + + // while v is even + while (_CryptBnIsEven(&V)) + { + // v <- v/2 + CryptBnRightShift(&V); + + /* if c congruent to d congruent to 0 mod 2 (they are both even) + then c <- c/2 and d <- d/2 */ + if (_CryptBnIsEven(&C) && _CryptBnIsEven(&D)) + { + CryptBnRightShift(&C); + CryptBnRightShift(&D); + } + // else c <- (c+y)/2 and d <- (d-x)/2 + else + { + CryptBnAccumulate(&C, pMod); + CryptBnRightShift(&C); + CryptBnSubtract(&D, &D, pState); + CryptBnRightShift(&D); + } + } + + // if u > v then u <- u-v, a <- a-c and b <- b-d + if (CryptBnCompare(&U, &V) >= 0) + { + CryptBnSubtract(&U, &U, &V); + CryptBnSubtract(&A, &A, &C); + CryptBnSubtract(&B, &B, &D); + } + // else v <- v-u, c <- c-a and d <- d-b + else + { + CryptBnSubtract(&V, &V, &U); + CryptBnSubtract(&C, &C, &A); + CryptBnSubtract(&D, &D, &B); + } + } + + // a <- C, b <- D but we only need a for the inverse so return that + CryptBnClone(pState, &C); + + /* note: if you needed to return v, you would need to left shift by iShift + but since we don't we don't do anything else */ +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnFinal + + \Description + Convert from ucrypt_t units back to big-endian bytes + + \Input *pState - the ucrypt_t units to convert + \Input *pResult - where to write the 8-bit result. + \Input iLength - size of the result array in bytes. + + \Version 03/08/2002 (gschaefer) +*/ +/*************************************************************************************H*/ +void CryptBnFinal(const CryptBnT *pState, uint8_t *pResult, int32_t iLength) +{ + const ucrypt_t *pSource = pState->aData; + + // make the length in terms of words + iLength /= sizeof(*pSource); + + // word to byte conversion + for (; iLength > 0; iLength -= 1) + { + #if UCRYPT_SIZE > 4 + *pResult++ = (uint8_t)(pSource[iLength-1] >> 56); + *pResult++ = (uint8_t)(pSource[iLength-1] >> 48); + *pResult++ = (uint8_t)(pSource[iLength-1] >> 40); + *pResult++ = (uint8_t)(pSource[iLength-1] >> 32); + #endif + *pResult++ = (uint8_t)(pSource[iLength-1] >> 24); + *pResult++ = (uint8_t)(pSource[iLength-1] >> 16); + *pResult++ = (uint8_t)(pSource[iLength-1] >> 8); + *pResult++ = (uint8_t)(pSource[iLength-1]); + } +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnFinalLe + + \Description + Convert from ucrypt_t units back to little-endian bytes + + \Input *pState - the ucrypt_t units to convert + \Input *pResult - where to write the 8-bit result. + \Input iLength - size of the result array in bytes. + + \Version 04/11/2018 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnFinalLe(const CryptBnT *pState, uint8_t *pResult, int32_t iLength) +{ + ds_memcpy_s(pResult, iLength, pState->aData, CryptBnByteLen(pState)); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnClone + + \Description + Copies a big number's contents + + \Input *pDst - where we are copying to + \Input *pSrc - where we are copying from + + \Version 02/27/2017 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnClone(CryptBnT *pDst, const CryptBnT *pSrc) +{ + ds_memcpy(pDst, pSrc, sizeof(*pDst)); +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnPrint + + \Description + Prints the contents of a big number + + \Input *pState - the big number we are printing + \Input *pTitle - title of the big number + + \Version 02/06/2018 (eesponda) +*/ +/*************************************************************************************H*/ +void CryptBnPrint(const CryptBnT *pState, const char *pTitle) +{ + #if DIRTYCODE_LOGGING + int32_t iIndex, iOffset; + char strNumber[16*CRYPTBN_MAX_WIDTH]; + const char *pPrefix = (pState->uSign == 0) ? "0x" : "-0x"; + + for (iIndex = pState->iWidth, iOffset = 0; iIndex > 0; iIndex -= 1) + { + iOffset += ds_snzprintf(strNumber+iOffset, sizeof(strNumber)-iOffset, "%016llx", pState->aData[iIndex - 1]); + } + NetPrintf(("cryptbn: %s %s%s (%d)\n", pTitle, pPrefix, strNumber, CryptBnBitLen(pState))); + #endif +} + +/*F*************************************************************************************/ +/*! + \Function CryptBnCompare + + \Description + Compare two big numbers together + + \Input *pLhs - the left side + \Input *pRhs - the right side + + \Output + int32_t - 0=equal, >0=left side larger, <0=right side larger + + \Version 02/17/2017 (eesponda) +*/ +/*************************************************************************************H*/ +int32_t CryptBnCompare(const CryptBnT *pLhs, const CryptBnT *pRhs) +{ + int32_t iWidth; + + for (iWidth = DS_MAX(pLhs->iWidth, pRhs->iWidth); iWidth > 0; iWidth -= 1) + { + ucrypt_t uLhs = pLhs->aData[iWidth - 1], uRhs = pRhs->aData[iWidth - 1]; + if (uLhs == uRhs) + { + continue; + } + + return((uLhs > uRhs) ? 1 : -1); + } + return(0); +} diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptchacha.c b/src/thirdparty/dirtysdk/source/crypt/cryptchacha.c new file mode 100644 index 00000000..a9289f3e --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptchacha.c @@ -0,0 +1,621 @@ +/*H********************************************************************************/ +/*! + \File cryptchacha.c + + \Description + Implements the ChaCha20-Poly1305 AEAD cipher + + \Notes + This implementation is based on the IETF Protocol and implements the + ChaCha20-Poly1305 AEAD cipher as used in TLS et all. + + References: + - ChaCha20 and Poly1305 for IETF Protocols: https://tools.ietf.org/html/rfc8439 + - ChaCha20-Poly1305 Cipher Suites for TLS: https://tools.ietf.org/html/rfc7905 + - Original implementation of ChaCha: https://cr.yp.to/chacha.html + - Original implementation of Poly1305: https://cr.yp.to/mac.html + + As per https://tools.ietf.org/html/rfc7539#section-2.7, Poly1305 requires + a one-time key and is "...biased, unlike HMAC". As such it is not suitable + for general use, and is therefore included dicrectly in the ChaCha20 + AEAD cipher implementation. + + \Copyright + Copyright (c) 2018 Electronic Arts + + \Version 02/12/2018 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" + +#include "DirtySDK/crypt/cryptbn.h" +#include "DirtySDK/crypt/cryptchacha.h" + +/*** Defines **********************************************************************/ + +#define CRYPTCHACHA_VERBOSE (DIRTYCODE_DEBUG && FALSE) + +/*** Macros ***********************************************************************/ + +#define CHACHA_RDWORD(_ptr) (((uint32_t)(_ptr)[0]) | ((uint32_t)((_ptr)[1])<<8) | (((uint32_t)(_ptr)[2])<<16) | (((uint32_t)(_ptr)[3])<<24)) +#define CHACHA_WRWORD(_ptr, _x) ((_ptr)[0] = (uint8_t)(_x),\ + (_ptr)[1] = (uint8_t)((_x)>>8),\ + (_ptr)[2] = (uint8_t)((_x)>>16),\ + (_ptr)[3] = (uint8_t)((_x)>>24)) +#define CHACHA_ROTATE(_x, _n) (((_x)<<(_n))|((_x)>>(32-(_n)))) +#define CHACHA_XOR(_v, _w) ((_v) ^ (_w)) +#define CHACHA_PLUS(_v, _w) ((_v) + (_w)) +#define CHACHA_PLUSONE(_v) (CHACHA_PLUS((_v), 1)) + +#define CHACHA_QUARTERROUND(_x, _a, _b, _c, _d) \ + (_x)[_a] = CHACHA_PLUS((_x)[_a], (_x)[_b]); (_x)[_d] = CHACHA_ROTATE(CHACHA_XOR((_x)[_d], (_x)[_a]), 16); \ + (_x)[_c] = CHACHA_PLUS((_x)[_c], (_x)[_d]); (_x)[_b] = CHACHA_ROTATE(CHACHA_XOR((_x)[_b], (_x)[_c]), 12); \ + (_x)[_a] = CHACHA_PLUS((_x)[_a], (_x)[_b]); (_x)[_d] = CHACHA_ROTATE(CHACHA_XOR((_x)[_d], (_x)[_a]), 8); \ + (_x)[_c] = CHACHA_PLUS((_x)[_c], (_x)[_d]); (_x)[_b] = CHACHA_ROTATE(CHACHA_XOR((_x)[_b], (_x)[_c]), 7); + +/*** Type Definitions *************************************************************/ + +//! Poly1305 state +typedef struct CryptPolyT +{ + CryptBnT r, s, a, p; +} CryptPolyT; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + + +/* + Poly1305 Routines +*/ + +/*F********************************************************************************/ +/*! + \Function _CryptPolyLE16 + + \Description + Covert input to little endian and pad to 16 bytes + + \Input *pOutput - [out] output for padded and flipped data + \Input *pInput - input to pad/flip + \Input iLength - length of input data + + \Output + int32_t - length (16) + + \Version 02/14/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CryptPolyLE16(uint8_t *pOutput, const uint8_t *pInput, int32_t iLength) +{ + int32_t iData; + if (iLength > 16) + { + iLength = 16; + } + for (iData = 0; iData < 16-iLength; iData += 1) + { + pOutput[iData] = 0; + } + for ( ; iData < 16; iData += 1) + { + pOutput[iData] = pInput[16-iData-1]; + } + return(16); +} + +/*F********************************************************************************/ +/*! + \Function _CryptPolyBnInit + + \Description + Init a BigNumber from input data, optionally extending with 0x01 + + \Input *pState - BigNumber to init + \Input iWidth - width to pass through to CryptBnInit + \Input *pBuffer - input data + \Input iLength - length of input data + \Input iExtra - one to extend with 0x01, else zero + + \Version 02/14/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptPolyBnInit(CryptBnT *pState, int32_t iWidth, const uint8_t *pBuffer, int32_t iLength, int32_t iExtra) +{ + uint8_t aLEData[17]; + if (iExtra) + { + aLEData[0] = 0x01; + } + iLength = _CryptPolyLE16(aLEData+iExtra, pBuffer, iLength); + CryptBnInitFrom(pState, iWidth, aLEData, iLength+iExtra); +} + +/*F********************************************************************************/ +/*! + \Function _CryptPolyClamp + + \Description + Clamp vector as per https://tools.ietf.org/html/rfc7539#section-2.5 + + \Input *pInput - input vector to clamp + + \Version 02/14/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptPolyClamp(uint8_t *pInput) +{ + pInput[3] &= 15; + pInput[7] &= 15; + pInput[11] &= 15; + pInput[15] &= 15; + pInput[4] &= 252; + pInput[8] &= 252; + pInput[12] &= 252; +} + +/*F********************************************************************************/ +/*! + \Function _CryptPolyInit + + \Description + Init Poly state with specified key + + \Input *pState - module state + \Input *pKey - input encryption key + \Input iKeyLen - length of key + + \Version 02/14/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptPolyInit(CryptPolyT *pState, uint8_t *pKey, int32_t iKeyLen) +{ + static const uint8_t p_src[] = { 0x3,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfb }; + // clamp(r): r &= 0x0ffffffc0ffffffc0ffffffc0fffffff + _CryptPolyClamp(pKey); + // r = (le_bytes_to_num(key[0..15]) + _CryptPolyBnInit(&pState->r, -1, pKey, 16, 0); + // s = le_num(key[16..31]) + _CryptPolyBnInit(&pState->s, -1, pKey+16, 16, 0); + // accumulator = 0 + CryptBnInitSet(&pState->a, 0); + // p = (1<<130)-5 + CryptBnInitFrom(&pState->p, -1, p_src, sizeof(p_src)); +} + +/*F********************************************************************************/ +/*! + \Function _CryptPolyUpdate + + \Description + Update Poly state with specified input data. Data is padded to 16 byte + width and extended with 0x1 as per https://tools.ietf.org/html/rfc7539#section-2.5.1 + + \Input *pState - module state + \Input *pData - input data to process + \Input iLength - length of data + + \Notes + BnMultiply and BnMod steps are utilized instead of BnModMultiply because + the product of a and r can exceed the size of the modulus p; this is not + supported by CryptBn. + + \Version 02/14/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptPolyUpdate(CryptPolyT *pState, const uint8_t *pData, int32_t iLength) +{ + CryptBnT n; + + // for i=1 upto ceil(msg length in bytes / 16) + for ( ; iLength > 0; pData += 16, iLength -= 16) + { + // n = le_bytes_to_num(msg[((i-1)*16)..(i*16)] | [0x01]) + _CryptPolyBnInit(&n, -1, pData, iLength, 1); + // a += n + CryptBnAccumulate(&pState->a, &n); + // a *= r + CryptBnMultiply(&pState->a, &pState->r, &pState->a); + // a %= p + CryptBnMod(&pState->a, &pState->p, &pState->a, NULL); + } +} + +/*F********************************************************************************/ +/*! + \Function _CryptPolyFinal + + \Description + Generate final tag + + \Input *pState - module state + \Input *pData - [out] output buffer to write tag to + \Input iLength - length of buffer + + \Version 02/14/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptPolyFinal(CryptPolyT *pState, uint8_t *pData, int32_t iLength) +{ + uint8_t aTag[16]; + // a += s + CryptBnAccumulate(&pState->a, &pState->s); + // num_to_16_le_bytes(a) + CryptBnFinal(&pState->a, aTag, sizeof(aTag)); + // write out the tag + _CryptPolyLE16(pData, aTag, iLength); +} + +/*F********************************************************************************/ +/*! + \Function _CryptPolyGenerateTag + + \Description + Generate authentication tag based on input data, key, and additional + authentication data. + + \Input *pBuffer - input data to generate tag for + \Input iLength - length of input data + \Input *pKey - encryption key used for poly operation + \Input iKeyLen - length of key + \Input *pAddData - additional unencrypted data + \Input iAddLen - length of additional data + \Input *pTag - [out] buffer to write tag to + \Input iTagLen - length of tag buffer + + \Version 02/14/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptPolyGenerateTag(const uint8_t *pBuffer, int32_t iLength, uint8_t *pKey, int32_t iKeyLen, const uint8_t *pAddData, int32_t iAddLen, uint8_t *pTag, int32_t iTagLen) +{ + CryptPolyT Poly; + uint8_t aLengths[16]; + + // generate add and txt lengths + aLengths[0] = (uint8_t)(iAddLen>>0); + aLengths[1] = (uint8_t)(iAddLen>>8); + aLengths[2] = (uint8_t)(iAddLen>>16); + aLengths[3] = (uint8_t)(iAddLen>>24); + aLengths[4] = aLengths[5] = aLengths[6] = aLengths[7] = 0; + aLengths[8] = (uint8_t)(iLength>>0); + aLengths[9] = (uint8_t)(iLength>>8); + aLengths[10] = (uint8_t)(iLength>>16); + aLengths[11] = (uint8_t)(iLength>>24); + aLengths[12] = aLengths[13] = aLengths[14] = aLengths[15] = 0; + + // init poly state + _CryptPolyInit(&Poly, pKey, iKeyLen); + + // update with add data (padded) | text (padded) | lengths + _CryptPolyUpdate(&Poly, pAddData, iAddLen); + _CryptPolyUpdate(&Poly, pBuffer, iLength); + _CryptPolyUpdate(&Poly, aLengths, sizeof(aLengths)); + + // generate tag + _CryptPolyFinal(&Poly, pTag, iTagLen); +} + +/* + ChaCha20 Routines +*/ + +/*F********************************************************************************/ +/*! + \Function _ChaCha20Block + + \Description + ChaCha20 block round as per https://tools.ietf.org/html/rfc7539#section-2.3.1 + + \Input *pOutput - [out] buffer to write output + \Input *pInput - input for block round + + \Version 02/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ChaCha20Block(uint8_t *pOutput, const uint32_t *pInput) +{ + uint32_t aBlock[16]; + int32_t iBlock; + + // read iBlockInput + ds_memcpy(aBlock, pInput, sizeof(aBlock)); + + // quarter rounds; note that the original reference had eight rounds, while the IETF version has ten + for (iBlock = 0; iBlock < 10; iBlock += 1) + { + CHACHA_QUARTERROUND(aBlock, 0, 4, 8, 12); + CHACHA_QUARTERROUND(aBlock, 1, 5, 9, 13); + CHACHA_QUARTERROUND(aBlock, 2, 6, 10, 14); + CHACHA_QUARTERROUND(aBlock, 3, 7, 11, 15); + CHACHA_QUARTERROUND(aBlock, 0, 5, 10, 15); + CHACHA_QUARTERROUND(aBlock, 1, 6, 11, 12); + CHACHA_QUARTERROUND(aBlock, 2, 7, 8, 13); + CHACHA_QUARTERROUND(aBlock, 3, 4, 9, 14); + } + + // accumulate input and write output + for (iBlock = 0; iBlock < 16; iBlock += 1) + { + CHACHA_WRWORD(pOutput + (4*iBlock), CHACHA_PLUS(aBlock[iBlock], pInput[iBlock])); + } +} + +/*F********************************************************************************/ +/*! + \Function _CryptChaChaInitKey + + \Description + Initialize crypt state based on input key + + \Input *pChaCha - cipher state + \Input *pKeyBuf - input key + \Input iKeyLen - length of key in bytes + + \Version 02/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptChaChaInitKey(CryptChaChaT *pChaCha, const uint8_t *pKeyBuf, int32_t iKeyLen) +{ + static const char _strSigma[16] = "expand 32-byte k"; + static const char _strTau[16] = "expand 16-byte k"; + const char *pConstant; + + // read first half of key + pChaCha->aInput[4] = CHACHA_RDWORD(pKeyBuf+0); + pChaCha->aInput[5] = CHACHA_RDWORD(pKeyBuf+4); + pChaCha->aInput[6] = CHACHA_RDWORD(pKeyBuf+8); + pChaCha->aInput[7] = CHACHA_RDWORD(pKeyBuf+12); + + // determine 2nd half based on key length + if (iKeyLen == 32) + { + pKeyBuf += 16; + pConstant = _strSigma; + } + else + { + pConstant = _strTau; + } + + // read second half of key + pChaCha->aInput[8] = CHACHA_RDWORD(pKeyBuf+0); + pChaCha->aInput[9] = CHACHA_RDWORD(pKeyBuf+4); + pChaCha->aInput[10] = CHACHA_RDWORD(pKeyBuf+8); + pChaCha->aInput[11] = CHACHA_RDWORD(pKeyBuf+12); + + // read in constant + pChaCha->aInput[0] = CHACHA_RDWORD(pConstant+0); + pChaCha->aInput[1] = CHACHA_RDWORD(pConstant+4); + pChaCha->aInput[2] = CHACHA_RDWORD(pConstant+8); + pChaCha->aInput[3] = CHACHA_RDWORD(pConstant+12); +} + +/*F********************************************************************************/ +/*! + \Function _CryptChaChaGeneratePolyKey + + \Description + Generate the Poly key as per https://tools.ietf.org/html/rfc7539#section-2.6 + + \Input *pChaCha - cipher state + \Input *pKey - [out] buffer for generated key + \Input iKeyLen - length of buffer + + \Version 02/14/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptChaChaGeneratePolyKey(CryptChaChaT *pChaCha, uint8_t *pKey, int32_t iKeyLen) +{ + uint8_t aPoly[64]; + uint32_t uCounter; + // save current counter, set it to zero + uCounter = pChaCha->aInput[12]; + pChaCha->aInput[12] = 0; + // use chacha20 to generate key state + _ChaCha20Block(aPoly, pChaCha->aInput); + // clamp r + _CryptPolyClamp(aPoly); + // copy out key + ds_memcpy_s(pKey, iKeyLen, aPoly, 32); + // restore counter + pChaCha->aInput[12] = uCounter; +} + +/*F********************************************************************************/ +/*! + \Function _CryptChaChaInitIv + + \Description + Initialize IV/Nonce portion of ChaCha state as per + https://tools.ietf.org/html/rfc7539#section-2.3 + + \Input *pChaCha - cipher state + \Input *pInitVec - input iv + \Input iIvLen - iv length + + \Version 02/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptChaChaInitIv(CryptChaChaT *pChaCha, const uint8_t *pInitVec, int32_t iIvLen) +{ + pChaCha->aInput[12] = 1; // as per https://tools.ietf.org/html/rfc7539#section-2.8 initial counter value is one + pChaCha->aInput[13] = CHACHA_RDWORD(pInitVec+0); + pChaCha->aInput[14] = CHACHA_RDWORD(pInitVec+4); + pChaCha->aInput[15] = CHACHA_RDWORD(pInitVec+8); +} + +/*F********************************************************************************/ +/*! + \Function _CryptChaChaEncrypt + + \Description + Encrypted plaintext as per https://tools.ietf.org/html/rfc7539#section-2.4 + + \Input *pChaCha - cipher state + \Input *pInput - input to encrypt + \Input *pOutput - [out] buffer to write decrypted data to (may overlap) + \Input iLength - data length + + \Version 02/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptChaChaEncrypt(CryptChaChaT *pChaCha, const uint8_t *pInput, uint8_t *pOutput, int32_t iLength) +{ + int32_t iCount, iCopyLen; + uint8_t aOutput[64]; + + while (iLength > 0) + { + _ChaCha20Block(aOutput, pChaCha->aInput); + pChaCha->aInput[12] = CHACHA_PLUSONE(pChaCha->aInput[12]); + if (!pChaCha->aInput[12]) + { + pChaCha->aInput[13] = CHACHA_PLUSONE(pChaCha->aInput[13]); + } + for (iCount = 0, iCopyLen = (iLength >= 64) ? 64 : iLength; iCount < iCopyLen; iCount += 1) + { + pOutput[iCount] = pInput[iCount] ^ aOutput[iCount]; + } + iLength -= iCopyLen; + pInput += iCopyLen; + pOutput += iCopyLen; + } +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CryptChaChaInit + + \Description + Init the ChaCha cipher with the specified key + + \Input *pChaCha - cipher state to initialize + \Input *pKeyBuf - pointer to key + \Input iKeyLen - key length + + \Version 02/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptChaChaInit(CryptChaChaT *pChaCha, const uint8_t *pKeyBuf, int32_t iKeyLen) +{ + // clear state + ds_memclr(pChaCha, sizeof(*pChaCha)); + // init state with key + _CryptChaChaInitKey(pChaCha, pKeyBuf, iKeyLen); +} + +/*F********************************************************************************/ +/*! + \Function CryptChaChaEncrypt + + \Description + Encrypt input data in place and optionally generate authentication tag + + \Input *pChaCha - cipher state + \Input *pBuffer - [inp/out] data to encrypt + \Input iLength - length of data to encrypt + \Input *pInitVec - initialization vector (IV) + \Input iIvLen - IV len + \Input *pAddData - additional authenticated data + \Input iAddLen - length of additional data + \Input *pTag - [out] tag buffer + \Input iTagLen - tag length + + \Notes + Pass NULL to pTag if you don't want authentication tag to be generated. + + \Output + int32_t - encrypted data size + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CryptChaChaEncrypt(CryptChaChaT *pChaCha, uint8_t *pBuffer, int32_t iLength, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen, uint8_t *pTag, int32_t iTagLen) +{ + uint8_t aPolyKey[32]; + + // set IV in state + _CryptChaChaInitIv(pChaCha, pInitVec, iIvLen); + + if (pTag != NULL) + { + // generate the Poly1305 key + _CryptChaChaGeneratePolyKey(pChaCha, aPolyKey, sizeof(aPolyKey)); + } + + // encrypt the input + _CryptChaChaEncrypt(pChaCha, pBuffer, pBuffer, iLength); + + if (pTag != NULL) + { + // generate the authentication tag + _CryptPolyGenerateTag(pBuffer, iLength, aPolyKey, sizeof(aPolyKey), pAddData, iAddLen, pTag, iTagLen); + } + + // return success + return(iLength); +} + +/*F********************************************************************************/ +/*! + \Function CryptChaChaDecrypt + + \Description + Decrypt input data in place and optionally generate authentication tag; + verify it matches the specified tag. + + \Input *pChaCha - cipher state + \Input *pBuffer - [inp/out] data to decrypt + \Input iLength - length of data to decrypt + \Input *pInitVec - initialization vector (IV) + \Input iIvLen - IV len + \Input *pAddData - additional authenticated data + \Input iAddLen - length of additional data + \Input *pTag - tag buffer + \Input iTagLen - tag length + + \Notes + Pass NULL to pTag if you don't want to generate and verify the authentication + tag. + + \Output + int32_t - decrypted data size + + \Version 02/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CryptChaChaDecrypt(CryptChaChaT *pChaCha, uint8_t *pBuffer, int32_t iLength, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen, const uint8_t *pTag, int32_t iTagLen) +{ + uint8_t aPolyKey[32], aTag[16]; + + // set IV in state + _CryptChaChaInitIv(pChaCha, pInitVec, iIvLen); + + if (pTag != NULL) + { + // generate the Poly1305 key + _CryptChaChaGeneratePolyKey(pChaCha, aPolyKey, sizeof(aPolyKey)); + + // generate the authentication tag on the encrypted data + _CryptPolyGenerateTag(pBuffer, iLength, aPolyKey, sizeof(aPolyKey), pAddData, iAddLen, aTag, sizeof(aTag)); + } + + // do the decrypt + _CryptChaChaEncrypt(pChaCha, pBuffer, pBuffer, iLength); + + // return length of decrypted data, or -1 if tag mismatch + return((pTag == NULL) || !memcmp(pTag, aTag, iTagLen) ? iLength : -1); +} + + diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptcurve.c b/src/thirdparty/dirtysdk/source/crypt/cryptcurve.c new file mode 100644 index 00000000..340efa8b --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptcurve.c @@ -0,0 +1,98 @@ +/*H********************************************************************************/ +/*! + \File cryptcurve.c + + \Description + This module implements an interface over our different curve crypto APIs + + \Copyright + Copyright (c) Electronic Arts 2018. ALL RIGHTS RESERVED. +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/crypt/cryptmont.h" +#include "DirtySDK/crypt/cryptcurve.h" + +/*** Variables ********************************************************************/ + +//! mappings for the dh functions +static const CryptCurveDhT _CryptCurve_Dh[] = +{ + { (CryptCurveInitT *)CryptNistInitDh, (CryptCurvePublicT *)CryptNistPublic, (CryptCurveSecretT *)CryptNistSecret, (CryptCurvePointInitT *)CryptNistPointInitFrom, (CryptCurvePointFinalT *)CryptNistPointFinal, CRYPTCURVE_SECP256R1 }, + { (CryptCurveInitT *)CryptNistInitDh, (CryptCurvePublicT *)CryptNistPublic, (CryptCurveSecretT *)CryptNistSecret, (CryptCurvePointInitT *)CryptNistPointInitFrom, (CryptCurvePointFinalT *)CryptNistPointFinal, CRYPTCURVE_SECP384R1 }, + { (CryptCurveInitT *)CryptMontInit, (CryptCurvePublicT *)CryptMontPublic, (CryptCurveSecretT *)CryptMontSecret, (CryptCurvePointInitT *)CryptMontPointInitFrom, (CryptCurvePointFinalT *)CryptMontPointFinal, CRYPTCURVE_X25519 }, + { (CryptCurveInitT *)CryptMontInit, (CryptCurvePublicT *)CryptMontPublic, (CryptCurveSecretT *)CryptMontSecret, (CryptCurvePointInitT *)CryptMontPointInitFrom, (CryptCurvePointFinalT *)CryptMontPointFinal, CRYPTCURVE_X448 } +}; + +//! mappings for the dsa functions +static const CryptCurveDsaT _CryptCurve_Dsa[] = +{ + { (CryptCurveInitT *)CryptNistInitDsa, (CryptCurveSignT *)CryptNistSign, (CryptCurveVerifyT *)CryptNistVerify, (CryptCurvePointInitT *)CryptNistPointInitFrom, CRYPTCURVE_SECP256R1 }, + { (CryptCurveInitT *)CryptNistInitDsa, (CryptCurveSignT *)CryptNistSign, (CryptCurveVerifyT *)CryptNistVerify, (CryptCurvePointInitT *)CryptNistPointInitFrom, CRYPTCURVE_SECP384R1 } +}; + +/*** Public Functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function CryptCurveGetDh + + \Description + Gets the diffie hellmen functions based on the curve type + + \Input iCurveType - type that identifies the curve + + \Output + const CryptCurveDhT * - pointer containing functions or NULL (not found) + + \Version 05/04/2018 (eesponda) +*/ +/********************************************************************************F*/ +const CryptCurveDhT *CryptCurveGetDh(int32_t iCurveType) +{ + const CryptCurveDhT *pCurve = NULL; + int32_t iCurveIdx; + + for (iCurveIdx = 0; iCurveIdx < (signed)(sizeof(_CryptCurve_Dh)/sizeof(_CryptCurve_Dh[0])); iCurveIdx += 1) + { + if (_CryptCurve_Dh[iCurveIdx].iCurveType == iCurveType) + { + pCurve = &_CryptCurve_Dh[iCurveIdx]; + break; + } + } + return(pCurve); +} + +/*F********************************************************************************/ +/*! + \Function CryptCurveGetDsa + + \Description + Gets the digital signature algorithm functions based on the curve type + + \Input iCurveType - type that identifies the curve + + \Output + const CryptCurveDsaT * - pointer containing functions or NULL (not found) + + \Version 05/04/2018 (eesponda) +*/ +/********************************************************************************F*/ +const CryptCurveDsaT *CryptCurveGetDsa(int32_t iCurveType) +{ + const CryptCurveDsaT *pCurve = NULL; + int32_t iCurveIdx; + + for (iCurveIdx = 0; iCurveIdx < (signed)(sizeof(_CryptCurve_Dsa)/sizeof(_CryptCurve_Dsa[0])); iCurveIdx += 1) + { + if (_CryptCurve_Dsa[iCurveIdx].iCurveType == iCurveType) + { + pCurve = &_CryptCurve_Dsa[iCurveIdx]; + break; + } + } + return(pCurve); +} diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptgcm.c b/src/thirdparty/dirtysdk/source/crypt/cryptgcm.c new file mode 100644 index 00000000..cb83b677 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptgcm.c @@ -0,0 +1,630 @@ +/*H********************************************************************************/ +/*! + \File cryptgcm.c + + \Description + An implementation of the GCM-128 and GCM-256 cipher, based on the NIST + standard, intended for use in implementation of TLS1.1 GCM cipher suites. + + This implementation uses Shoup's method utilizing 4-bit tables as described + in the GCM specifications. While not particularly fast, table generation + is quick and memory usage required small. This implementation is restricted + to a feature set suitable for implementation of TLS1.1 GCM cipher suites. + + \Notes + References: + - GCM specifications: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf + - NIST recommendation: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf + - TLS1.1 RFC AEAD ciphers: http://tools.ietf.org/html/rfc5246#section-6.2.3.3 + - TLS1.1 AES-GCM cipher specifications: http://tools.ietf.org/html/rfc5288 + - AEAD AES-GCM definitions: http://tools.ietf.org/html/rfc5116 + + \Copyright + Copyright (c) 2014 Electronic Arts + + \Version 07/01/2014 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" + +#include "DirtySDK/crypt/cryptgcm.h" + +/*** Defines **********************************************************************/ + +#define CRYPTGCM_VERBOSE (DIRTYCODE_DEBUG && FALSE) + +/*** Macros ***********************************************************************/ + +/* + Read/write big-endian words +*/ +#define CRYPTGCM_RDWORD(_ptr) ((((uint32_t)(_ptr)[0]) << 24) | ((uint32_t)((_ptr)[1]) << 16) | (((uint32_t)(_ptr)[2]) << 8) | ((uint32_t)(_ptr)[3])) +#define CRYPTGCM_WRWORD(_x, _ptr) ((_ptr)[3] = (uint8_t)(_x),\ + (_ptr)[2] = (uint8_t)((_x)>>8),\ + (_ptr)[1] = (uint8_t)((_x)>>16),\ + (_ptr)[0] = (uint8_t)((_x)>>24)) + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +// shoup's 4bit multiplication table +static const uint64_t _CryptGCM_aLast4[16] = +{ + 0x0000, 0x1c20, 0x3840, 0x2460, + 0x7080, 0x6ca0, 0x48c0, 0x54e0, + 0xe100, 0xfd20, 0xd940, 0xc560, + 0x9180, 0x8da0, 0xa9c0, 0xb5e0 +}; + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _CryptGcmGenTable + + \Description + Precompute GCM multiplication table + + \Input *pGcm - cipher state + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptGcmGenTable(CryptGcmT *pGcm) +{ + uint64_t uHi, uLo; + uint64_t uVh, uVl; + uint8_t hInit[16]; + int32_t i, j; + + // generate table source + ds_memclr(hInit, sizeof(hInit)); + CryptAesEncryptBlock(&pGcm->KeySchedule, hInit, hInit); + + // convert table source to 64bit ints + uHi = CRYPTGCM_RDWORD(hInit+0); + uLo = CRYPTGCM_RDWORD(hInit+4); + uVh = (uint64_t) uHi << 32 | uLo; + + uHi = CRYPTGCM_RDWORD(hInit+8); + uLo = CRYPTGCM_RDWORD(hInit+12); + uVl = (uint64_t) uHi << 32 | uLo; + + // init table + pGcm->HL[8] = uVl; + pGcm->HH[8] = uVh; + pGcm->HH[0] = 0; + pGcm->HL[0] = 0; + + for (i = 4; i > 0; i >>= 1) + { + uint32_t uT= (uVl & 1) * 0xe1000000U; + uVl = (uVh << 63) | (uVl >> 1); + uVh = (uVh >> 1) ^ ((uint64_t)uT << 32); + + pGcm->HL[i] = uVl; + pGcm->HH[i] = uVh; + } + + for (i = 2; i < 16; i <<= 1) + { + uint64_t *pHiL = pGcm->HL + i; + uint64_t *pHiH = pGcm->HH + i; + uVh = *pHiH; + uVl = *pHiL; + for (j = 1; j < i; j += 1) + { + pHiH[j] = uVh ^ pGcm->HH[j]; + pHiL[j] = uVl ^ pGcm->HL[j]; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _CryptGcmMultiply + + \Description + GCM multiply of input block by precomputed table + + \Input *pGcm - cipher state + \Input *pOut - [out] storage for result + \Input *pInp - input block to multiply + + \Notes + pOut may overlap pInp + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptGcmMultiply(CryptGcmT *pGcm, uint8_t *pOut, const uint8_t *pInp) +{ + uint8_t uLo, uHi; // lookups into HL/HH + uint8_t uRem; // lookup into static table + uint64_t uZh, uZl; + int32_t iData; + + uLo = pInp[15] & 0xf; + uZh = pGcm->HH[uLo]; + uZl = pGcm->HL[uLo]; + + for (iData = 15; iData >= 0; iData -= 1) + { + uLo = pInp[iData] & 0xf; + uHi = pInp[iData] >> 4; + + if (iData != 15) + { + uRem = (uint8_t) uZl & 0xf; + uZl = (uZh << 60) | (uZl >> 4); + uZh = (uZh >> 4); + uZh ^= (uint64_t) _CryptGCM_aLast4[uRem] << 48; + uZh ^= pGcm->HH[uLo]; + uZl ^= pGcm->HL[uLo]; + + } + + uRem = (uint8_t) uZl & 0xf; + uZl = (uZh << 60) | (uZl >> 4); + uZh = (uZh >> 4); + uZh ^= (uint64_t) _CryptGCM_aLast4[uRem] << 48; + uZh ^= pGcm->HH[uHi]; + uZl ^= pGcm->HL[uHi]; + } + + CRYPTGCM_WRWORD(uZh >> 32, pOut+0); + CRYPTGCM_WRWORD(uZh >> 0, pOut+4); + CRYPTGCM_WRWORD(uZl >> 32, pOut+8); + CRYPTGCM_WRWORD(uZl >> 0, pOut+12); +} + +/*F********************************************************************************/ +/*! + \Function _CryptGcmStart + + \Description + Start of encryption/decryption processing + + \Input *pGcm - cipher state + \Input iMode - CRYPTAES_KEYTYPE_ENCRYPT or CRYPTAES_KEYTYPE_DECRYPT + \Input *pInitVec - initialization vector (IV) + \Input iIvLen - length of IV + \Input *pAddData - additional authenticated data (AAD) + \Input iAddLen - length of AAD + + \Output + int32_t - zero=success, negative=failure + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CryptGcmStart(CryptGcmT *pGcm, int32_t iMode, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen) +{ + int32_t iBlockLen, iData; + const uint8_t *pAad; + + ds_memclr(pGcm->aY, sizeof(pGcm->aY)); + ds_memclr(pGcm->aBuf, sizeof(pGcm->aBuf)); + + pGcm->uMode = iMode; + pGcm->uLen = 0; + pGcm->uAddLen = 0; + + // validate IV length (we only support 12-byte IVs) + if (iIvLen == 12) + { + // generate y0 + ds_memcpy(pGcm->aY, pInitVec, iIvLen); + pGcm->aY[15] = 1; + } + else + { + NetPrintf(("cryptgcm: iv length of %d not supported\n", iIvLen)); + return(-1); + } + + // encrypt the block (GCM always uses forward encryption) and save output for later tag generation + CryptAesEncryptBlock(&pGcm->KeySchedule, pGcm->aY, pGcm->aBaseEctr); + + // GCM process AAD + for (pAad = pAddData, pGcm->uAddLen = iAddLen; iAddLen > 0; ) + { + iBlockLen = (iAddLen < 16) ? iAddLen : 16; + + for (iData = 0; iData < iBlockLen; iData += 1) + { + pGcm->aBuf[iData] ^= pAad[iData]; + } + + _CryptGcmMultiply(pGcm, pGcm->aBuf, pGcm->aBuf); + + iAddLen -= iBlockLen; + pAad += iBlockLen; + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _CryptGcmUpdate + + \Description + Encrypt/decrypt the data + + \Input *pGcm - cipher state + \Input *pOutput - [out] output buffer for decrypted data (may overlap with input) + \Input *pInput - encrypted data to decrypt + \Input iLength - length of input/output + + \Output + int32_t - zero=success, negative=failure + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CryptGcmUpdate(CryptGcmT *pGcm, uint8_t *pOutput, const uint8_t *pInput, int32_t iLength) +{ + uint8_t *pOut = pOutput, aInput[16], aEctr[16]; + int32_t iData, iBlockLen; + const uint8_t *pInp; + + // add in length + pGcm->uLen += iLength; + + // handle input in blocks of 16 + for (pInp = pInput; iLength > 0; ) + { + // get block size + iBlockLen = (iLength < 16) ? iLength : 16; + // copy block to local buffer so we can handle overlapping I/O buffers + ds_memcpy(aInput, pInp, iBlockLen); + + for (iData = 16; iData > 12; iData -= 1) + { + if (++pGcm->aY[iData-1] != 0) + { + break; + } + } + + // encrypt the block (GCM always uses forward encryption) + CryptAesEncryptBlock(&pGcm->KeySchedule, pGcm->aY, aEctr); + + for (iData = 0; iData < iBlockLen; iData += 1) + { + if (pGcm->uMode == CRYPTAES_KEYTYPE_DECRYPT) + { + pGcm->aBuf[iData] ^= aInput[iData]; + } + pOut[iData] = aEctr[iData] ^ aInput[iData]; + if (pGcm->uMode == CRYPTAES_KEYTYPE_ENCRYPT) + { + pGcm->aBuf[iData] ^= pOut[iData]; + } + } + + _CryptGcmMultiply(pGcm, pGcm->aBuf, pGcm->aBuf); + + // move to next block + iLength -= iBlockLen; + pInp += iBlockLen; + pOut += iBlockLen; + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _CryptGcmFinish + + \Description + Generate authentication tag + + \Input *pGcm - cipher state + \Input *pTag - [out] buffer to hold generated tag + \Input iTagLen - size of tag buffer + + \Output + int32_t - zero=success, negative=failure + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CryptGcmFinish(CryptGcmT *pGcm, uint8_t *pTag, int32_t iTagLen) +{ + uint8_t aTagBuf[16]; + uint64_t uLen = pGcm->uLen * 8; + uint64_t uAddLen = pGcm->uAddLen * 8; + int32_t iTag; + + if (iTagLen > (signed)sizeof(aTagBuf)) + { + return(-1); + } + if (iTagLen > 0) + { + ds_memcpy(pTag, pGcm->aBaseEctr, iTagLen); + } + + if (uLen || uAddLen) + { + ds_memclr(aTagBuf, sizeof(aTagBuf)); + + CRYPTGCM_WRWORD(uAddLen >> 32, aTagBuf+0); + CRYPTGCM_WRWORD(uAddLen >> 0, aTagBuf+4); + CRYPTGCM_WRWORD(uLen >> 32, aTagBuf+8); + CRYPTGCM_WRWORD(uLen >> 0, aTagBuf+12); + + for (iTag = 0; iTag < (signed)sizeof(aTagBuf); iTag += 1) + { + pGcm->aBuf[iTag] ^= aTagBuf[iTag]; + } + + _CryptGcmMultiply(pGcm, pGcm->aBuf, pGcm->aBuf); + + for (iTag = 0; iTag < iTagLen; iTag += 1) + { + pTag[iTag] ^= pGcm->aBuf[iTag]; + } + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _CryptGcmEncrypt + + \Description + GCM encrypt/decrypt data + + \Input *pGcm - cipher state + \Input iMode - CRYPTAES_KEYTYPE_ENCRYPT or CRYPTAES_KEYTYPE_DECRYPT + \Input *pOutput - [out] output buffer for decrypted data (may overlap with input) + \Input *pInput - encrypted data to decrypt + \Input iLength - length of input/output + \Input *pInitVec - initialization vector (IV) + \Input iIvLen - length of IV + \Input *pAddData - additional authenticated data (AAD) + \Input iAddLen - length of AAD + \Input *pTag - tag to compare against for authentication + \Input iTagLen - length of tag + + \Output + int32_t - zero=success, negative=failure + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CryptGcmEncrypt(CryptGcmT *pGcm, int32_t iMode, uint8_t *pOutput, const uint8_t *pInput, int32_t iLength, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen, uint8_t *pTag, int32_t iTagLen) +{ + int32_t iResult; + + // set up encryption + if ((iResult = _CryptGcmStart(pGcm, iMode, pInitVec, iIvLen, pAddData, iAddLen)) != 0) + { + return(iResult); + } + // do the encryption + if ((iResult = _CryptGcmUpdate(pGcm, pOutput, pInput, iLength)) != 0) + { + return(iResult); + } + // generate the authentication tag + if ((iResult = _CryptGcmFinish(pGcm, pTag, iTagLen)) != 0) + { + return(iResult); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _CryptGcmDecrypt + + \Description + GCM decrypt data, validate against authentication tag + + \Input *pGcm - cipher state + \Input *pOutput - [out] output buffer for decrypted data (may overlap with input) + \Input *pInput - encrypted data to decrypt + \Input iLength - length of input/output + \Input *pInitVec - initialization vector (IV) + \Input iIvLen - length of IV + \Input *pAddData - additional authenticated data (AAD) + \Input iAddLen - length of AAD + \Input *pTag - tag to compare against for authentication + \Input iTagLen - length of tag + + \Output + int32_t - zero=success, negative=failure + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CryptGcmDecrypt(CryptGcmT *pGcm, uint8_t *pOutput, const uint8_t *pInput, int32_t iLength, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen, const uint8_t *pTag, int32_t iTagLen) +{ + int32_t iByte, iDiff, iResult; + uint8_t aDecryptTag[16]; + + // do the decryption and generate authentication tag + if ((iResult = _CryptGcmEncrypt(pGcm, CRYPTAES_KEYTYPE_DECRYPT, pOutput, pInput, iLength, pInitVec, iIvLen, pAddData, iAddLen, aDecryptTag, iTagLen)) != 0) + { + return(iResult); + } + + // constant-time comparison of authentication tag + for (iByte = 0, iDiff = 0; iByte < iTagLen; iByte += 1) + { + iDiff |= pTag[iByte] ^ aDecryptTag[iByte]; + } + + #if CRYPTGCM_VERBOSE + NetPrintMem(aDecryptTag, iTagLen, "CT"); + NetPrintMem(pTag, iTagLen, "T"); + #endif + + // if generated authentication tag does not match input tag, clear output and fail + if (iDiff != 0) + { + ds_memclr(pOutput, iLength); + NetPrintf(("cryptgcm: tag mismatch, decrypt failed\n")); + return(-1); + } + + // success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _CryptGcmInit + + \Description + Initialize crypt state based on input key + + \Input *pGcm - cipher state + \Input *pKeyBuf - input key + \Input iKeyLen - length of key in bytes + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CryptGcmInit(CryptGcmT *pGcm, const uint8_t *pKeyBuf, int32_t iKeyLen) +{ + // init key schedule + CryptAesInitKeySchedule(&pGcm->KeySchedule, pKeyBuf, iKeyLen, CRYPTAES_KEYTYPE_ENCRYPT); + + // generate Shoop(?) table + _CryptGcmGenTable(pGcm); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CryptGcmInit + + \Description + Init the GCM cipher with the specified key + + \Input *pGcm - cipher state to initialize + \Input *pKeyBuf - pointer to key + \Input iKeyLen - key length + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptGcmInit(CryptGcmT *pGcm, const uint8_t *pKeyBuf, int32_t iKeyLen) +{ + // clear state + ds_memclr(pGcm, sizeof(*pGcm)); + // init state + _CryptGcmInit(pGcm, pKeyBuf, iKeyLen); +} + +/*F********************************************************************************/ +/*! + \Function CryptGcmEncrypt + + \Description + Encrypt data with AES-GCM + + \Input *pGcm - cipher state + \Input *pBuffer - [inp/out] data to encrypt + \Input iLength - length of data to encrypt + \Input *pInitVec - initialization vector (IV) + \Input iIvLen - IV len + \Input *pAddData - additional authenticated data (AAD) + \Input iAddLen - length of AAD + \Input *pTag - [out] tag buffer + \Input iTagLen - tag length + + \Output + int32_t - encrypted data size + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CryptGcmEncrypt(CryptGcmT *pGcm, uint8_t *pBuffer, int32_t iLength, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen, uint8_t *pTag, int32_t iTagLen) +{ + int32_t iResult; + + #if CRYPTGCM_VERBOSE + NetPrintf(("cryptgcm: encrypting size=%d ivlen=%d datalen=%d\n", iLength, iIvLen, iAddLen)); + NetPrintMem(pBuffer, iLength, "P"); + NetPrintMem(pInitVec, iIvLen, "V"); + NetPrintMem(pAddData, iAddLen, "A"); + #endif + + iResult = _CryptGcmEncrypt(pGcm, CRYPTAES_KEYTYPE_ENCRYPT, pBuffer, pBuffer, iLength, pInitVec, iIvLen, pAddData, iAddLen, pTag, iTagLen); + + #if CRYPTGCM_VERBOSE + NetPrintMem(pBuffer, iLength, "C"); + #endif + + return(iResult == 0 ? iLength : 0); +} + +/*F********************************************************************************/ +/*! + \Function CryptGcmDecrypt + + \Description + Decrypt data with AES-GCM + + \Input *pGcm - cipher state + \Input *pBuffer - [inp/out] data to decrypt + \Input iLength - length of data to decrypt + \Input *pInitVec - initialization vector (IV) + \Input iIvLen - IV len + \Input *pAddData - additional data (ADD) + \Input iAddLen - length of AAD + \Input *pTag - tag buffer + \Input iTagLen - tag length + + \Output + int32_t - decrypted data size + + \Version 07/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CryptGcmDecrypt(CryptGcmT *pGcm, uint8_t *pBuffer, int32_t iLength, const uint8_t *pInitVec, int32_t iIvLen, const uint8_t *pAddData, int32_t iAddLen, uint8_t *pTag, int32_t iTagLen) +{ + int32_t iResult; + + if (iLength < 0) + { + NetPrintf(("cryptgcm: skipping decrypt of input with size=%d\n", iLength)); + return(-1); + } + + #if CRYPTGCM_VERBOSE + NetPrintf(("cryptgcm: decrypting size=%d ivlen=%d datalen=%d\n", iLength, iIvLen, iAddLen)); + NetPrintMem(pBuffer, iLength, "C"); + NetPrintMem(pInitVec, iIvLen, "V"); + NetPrintMem(pAddData, iAddLen, "A"); + #endif + + iResult = _CryptGcmDecrypt(pGcm, pBuffer, pBuffer, iLength, pInitVec, iIvLen, pAddData, iAddLen, pTag, iTagLen); + + #if CRYPTGCM_VERBOSE + NetPrintMem(pBuffer, iLength, "P"); + #endif + + return(iResult == 0 ? iLength : -1); +} + + diff --git a/src/thirdparty/dirtysdk/source/crypt/crypthash.c b/src/thirdparty/dirtysdk/source/crypt/crypthash.c new file mode 100644 index 00000000..98d19a3b --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/crypthash.c @@ -0,0 +1,132 @@ +/*H*******************************************************************/ +/*! + \File crypthash.c + + \Description + This module implements a generic wrapper for all cryptograph + hash modules. + + \Copyright + Copyright (c) Electronic Arts 2013 + + \Version 1.0 11/05/2013 (jbrookes) First Version +*/ +/*******************************************************************H*/ + +/*** Include files ***************************************************/ + +#include + +#include "DirtySDK/platform.h" + +#include "DirtySDK/crypt/cryptmd5.h" +#include "DirtySDK/crypt/cryptsha1.h" +#include "DirtySDK/crypt/cryptsha2.h" +#include "DirtySDK/util/murmurhash3.h" + +#include "DirtySDK/crypt/crypthash.h" + +/*** Defines *********************************************************/ + +/*** Type Definitions ************************************************/ + +/*** Variables *******************************************************/ + +static const CryptHashT _CryptHash_Hashes[] = +{ + { (CryptHashInitT *)MurmurHash3Init2, (CryptHashUpdateT *)MurmurHash3Update, (CryptHashFinalT *)MurmurHash3Final, CRYPTHASH_MURMUR3, MURMURHASH_HASHSIZE }, + { (CryptHashInitT *)CryptMD5Init2, (CryptHashUpdateT *)CryptMD5Update, (CryptHashFinalT *)CryptMD5Final, CRYPTHASH_MD5, MD5_BINARY_OUT }, + { (CryptHashInitT *)CryptSha1Init2, (CryptHashUpdateT *)CryptSha1Update, (CryptHashFinalT *)CryptSha1Final, CRYPTHASH_SHA1, CRYPTSHA1_HASHSIZE }, + { (CryptHashInitT *)CryptSha2Init, (CryptHashUpdateT *)CryptSha2Update, (CryptHashFinalT *)CryptSha2Final, CRYPTHASH_SHA224, CRYPTSHA224_HASHSIZE }, + { (CryptHashInitT *)CryptSha2Init, (CryptHashUpdateT *)CryptSha2Update, (CryptHashFinalT *)CryptSha2Final, CRYPTHASH_SHA256, CRYPTSHA256_HASHSIZE }, + { (CryptHashInitT *)CryptSha2Init, (CryptHashUpdateT *)CryptSha2Update, (CryptHashFinalT *)CryptSha2Final, CRYPTHASH_SHA384, CRYPTSHA384_HASHSIZE }, + { (CryptHashInitT *)CryptSha2Init, (CryptHashUpdateT *)CryptSha2Update, (CryptHashFinalT *)CryptSha2Final, CRYPTHASH_SHA512, CRYPTSHA512_HASHSIZE } +}; + +/*** Private functions ***********************************************/ + +/*** Public functions ************************************************/ + + +/*F*******************************************************************/ +/*! + \Function CryptHashGet + + \Description + Get CryptHash function block based on input hash size + + \Input eHashType - hash type to get size of + + \Output + CryptHashT * - hash block pointer, or NULL if not found + + \Version 11/05/2013 (jbrookes) +*/ +/*******************************************************************F*/ +const CryptHashT *CryptHashGet(CryptHashTypeE eHashType) +{ + const CryptHashT *pHash; + pHash = ((eHashType > CRYPTHASH_NULL) && (eHashType < CRYPTHASH_NUMHASHES)) ? &_CryptHash_Hashes[((int32_t)eHashType)-1] : (const CryptHashT *)NULL; + return(pHash); +} +/*F*******************************************************************/ +/*! + \Function CryptHashGetSize + + \Description + Get hash size for specified hash type + + \Input eHashType - hash type to get size of + + \Output + int32_t - size of specified hash + + \Version 03/07/2014 (jbrookes) +*/ +/*******************************************************************F*/ +int32_t CryptHashGetSize(CryptHashTypeE eHashType) +{ + const CryptHashT *pHash = CryptHashGet(eHashType); + int32_t iHashSize = (pHash != NULL) ? pHash->iHashSize : -1; + return(iHashSize); +} + +/*F*******************************************************************/ +/*! + \Function CryptHashGetBySize + + \Description + Get the hash based on its size + + \Input iHashSize - size of hash we are trying to lookup + + \Output + CryptHashTypeE - hash type, CRYPTHASH_NULL if not found + + \Notes + Due to murmurhash3 using the same size as MD5, we choose to + not include it. + + \Version 01/09/2018 (jbrookes) +*/ +/*******************************************************************F*/ +CryptHashTypeE CryptHashGetBySize(int32_t iHashSize) +{ + switch (iHashSize) + { + case MD5_BINARY_OUT: + return(CRYPTHASH_MD5); + case CRYPTSHA1_HASHSIZE: + return(CRYPTHASH_SHA1); + case CRYPTSHA224_HASHSIZE: + return(CRYPTHASH_SHA224); + case CRYPTSHA256_HASHSIZE: + return(CRYPTHASH_SHA256); + case CRYPTSHA384_HASHSIZE: + return(CRYPTHASH_SHA384); + case CRYPTSHA512_HASHSIZE: + return(CRYPTHASH_SHA512); + default: + return(CRYPTHASH_NULL); + } +} diff --git a/src/thirdparty/dirtysdk/source/crypt/crypthmac.c b/src/thirdparty/dirtysdk/source/crypt/crypthmac.c new file mode 100644 index 00000000..ed3b376b --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/crypthmac.c @@ -0,0 +1,150 @@ +/*H********************************************************************************/ +/*! + \File crypthmac.c + + \Description + Implements HMAC as defined in https://tools.ietf.org/html/rfc2104 + + \Copyright + Copyright (c) 2013 Electronic Arts Inc. + + \Version 01/14/2013 (jbrookes) First Version; refactored from ProtoSSL + \Version 07/26/2018 (jbrookes) Fixed to properly handle keys larger than block size +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/crypt/crypthash.h" + +#include "DirtySDK/crypt/crypthmac.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CryptHmacCalc + + \Description + Implements HMAC as defined in https://tools.ietf.org/html/rfc2104 + + \Input *pBuffer - [out] storage for generated digest + \Input iBufLen - size of output buffer + \Input *pMessage - input data to hash + \Input iMessageLen - size of input data + \Input *pKey - seed + \Input iKeyLen - seed length + \Input eHashType - hash type + + \Output + int32_t - negative=error, else success + + \Version 01/14/2013 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CryptHmacCalc(uint8_t *pBuffer, int32_t iBufLen, const uint8_t *pMessage, int32_t iMessageLen, const uint8_t *pKey, int32_t iKeyLen, CryptHashTypeE eHashType) +{ + CryptHmacMsgT Message; + Message.pMessage = pMessage; + Message.iMessageLen = iMessageLen; + return(CryptHmacCalcMulti(pBuffer, iBufLen, &Message, 1, pKey, iKeyLen, eHashType)); +} + +/*F********************************************************************************/ +/*! + \Function CryptHmacCalcMulti + + \Description + Implements HMAC as defined in https://tools.ietf.org/html/rfc2104. + This version allows multiple buffers to be specified in one call, which is + useful when the input to be hashed is not in a single contiguous buffer. + + \Input *pBuffer - [out] storage for generated MAC + \Input iBufLen - size of output buffer (may be smaller than hash size for truncated HMAC) + \Input *pMessageList- list of messages to hash + \Input iNumMessages - number of messages to hash + \Input *pKey - key + \Input iKeyLen - key length + \Input eHashType - hash type + + \Output + int32_t - negative=error, else success + + \Version 01/14/2013 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CryptHmacCalcMulti(uint8_t *pBuffer, int32_t iBufLen, const CryptHmacMsgT *pMessageList, int32_t iNumMessages, const uint8_t *pKey, int32_t iKeyLen, CryptHashTypeE eHashType) +{ + uint8_t aKiPad[128], aKoPad[128], aKeyHash[128]; // max message block size of SHA-384/512 + int32_t iBufCnt, iMessage, iBlockSize; + const CryptHashT *pHash; + uint8_t aWorkBuf[CRYPTHASH_MAXDIGEST]; + uint8_t aContext[CRYPTHASH_MAXSTATE]; + + // get hash function block for this hash type + if ((pHash = CryptHashGet(eHashType)) == NULL) + { + NetPrintf(("crypthmac: invalid hash type %d\n", eHashType)); + return(-1); + } + // get block size + iBlockSize = (pHash->iHashSize < CRYPTSHA384_HASHSIZE) ? 64 : 128; + // as per https://tools.ietf.org/html/rfc2104#section-3, keys longer than the block size are hashed to the digest size + if (iKeyLen > iBlockSize) + { + pHash->Init(aContext, pHash->iHashSize); + pHash->Update(aContext, pKey, iKeyLen); + pHash->Final(aContext, aKeyHash, pHash->iHashSize); + pKey = aKeyHash; + iKeyLen = pHash->iHashSize; + } + // limit output to hash size + if (iBufLen > pHash->iHashSize) + { + iBufLen = pHash->iHashSize; + } + + // copy key to ipad and opad, pad with zero + ds_memcpy(aKiPad, pKey, iKeyLen); + ds_memclr(aKiPad+iKeyLen, iBlockSize-iKeyLen); + ds_memcpy(aKoPad, pKey, iKeyLen); + ds_memclr(aKoPad+iKeyLen, iBlockSize-iKeyLen); + + // calculate xor of ipad and opad with 0x36/0x5c respectively + for (iBufCnt = 0; iBufCnt < iBlockSize; iBufCnt += 1) + { + aKiPad[iBufCnt] ^= 0x36; + aKoPad[iBufCnt] ^= 0x5c; + } + + // execute hash of ipad + pHash->Init(aContext, pHash->iHashSize); + pHash->Update(aContext, aKiPad, iBlockSize); + for (iMessage = 0; iMessage < iNumMessages; iMessage += 1) + { + pHash->Update(aContext, pMessageList[iMessage].pMessage, pMessageList[iMessage].iMessageLen); + } + pHash->Final(aContext, aWorkBuf, pHash->iHashSize); + + // execute hash of ipad+opad + pHash->Init(aContext, pHash->iHashSize); + pHash->Update(aContext, aKoPad, iBlockSize); + pHash->Update(aContext, aWorkBuf, pHash->iHashSize); + pHash->Final(aContext, pBuffer, iBufLen); + + // success + return(0); +} diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptmd5.c b/src/thirdparty/dirtysdk/source/crypt/cryptmd5.c new file mode 100644 index 00000000..467b124b --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptmd5.c @@ -0,0 +1,513 @@ +/*H*************************************************************************************************/ +/*! + + \File cryptmd5.c + + \Description + The MD5 message digest algorithm developed by Ron Rivest and documented + in RFC1321. This implementation is based on the RFC but does not use the + sample code.. It should be free from intellectual property concerns and + a reference is included below which further clarifies this point. + + Note that this implementation is limited to hashing no more than 2^32 + bytes after which its results would be impatible with a fully compliant + implementation. + + \Notes + http://www.ietf.org/ietf/IPR/RSA-MD-all + + The following was recevied Fenbruary 23,2000 + From: "Linn, John" + + February 19, 2000 + + The purpose of this memo is to clarify the status of intellectual + property rights asserted by RSA Security Inc. ("RSA") in the MD2, MD4 and + MD5 message-digest algorithms, which are documented in RFC-1319, RFC-1320, + and RFC-1321 respectively. + + Implementations of these message-digest algorithms, including + implementations derived from the reference C code in RFC-1319, RFC-1320, and + RFC-1321, may be made, used, and sold without license from RSA for any + purpose. + + No rights other than the ones explicitly set forth above are + granted. Further, although RSA grants rights to implement certain + algorithms as defined by identified RFCs, including implementations derived + from the reference C code in those RFCs, no right to use, copy, sell, or + distribute any other implementations of the MD2, MD4, or MD5 message-digest + algorithms created, implemented, or distributed by RSA is hereby granted by + implication, estoppel, or otherwise. Parties interested in licensing + security components and toolkits written by RSA should contact the company + to discuss receiving a license. All other questions should be directed to + Margaret K. Seif, General Counsel, RSA Security Inc., 36 Crosby Drive, + Bedford, Massachusetts 01730. + + Implementations of the MD2, MD4, or MD5 algorithms may be subject to + United States laws and regulations controlling the export of technical data, + computer software, laboratory prototypes and other commodities (including + the Arms Export Control Act, as amended, and the Export Administration Act + of 1970). The transfer of certain technical data and commodities may + require a license from the cognizant agency of the United States Government. + RSA neither represents that a license shall not be required for a particular + implementation nor that, if required, one shall be issued. + + + DISCLAIMER: RSA MAKES NO REPRESENTATIONS AND EXTENDS NO WARRANTIES + OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, VALIDITY OF + INTELLECTUAL PROPERTY RIGHTS, ISSUED OR PENDING, OR THE ABSENCE OF LATENT OR + OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE, IN CONNECTION WITH THE MD2, MD4, + OR MD5 ALGORITHMS. NOTHING IN THIS GRANT OF RIGHTS SHALL BE CONSTRUED AS A + REPRESENTATION OR WARRANTY GIVEN BY RSA THAT THE IMPLEMENTATION OF THE + ALGORITHM WILL NOT INFRINGE THE INTELLECTUAL PROPERTY RIGHTS OF ANY THIRD + PARTY. IN NO EVENT SHALL RSA, ITS TRUSTEES, DIRECTORS, OFFICERS, EMPLOYEES, + PARENTS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF + ANY KIND RESULTING FROM IMPLEMENTATION OF THIS ALGORITHM, INCLUDING ECONOMIC + DAMAGE OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER RSA + SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 1.0 03/16/2001 (GWS) First Version + +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptmd5.h" + +/*** Defines ***************************************************************************/ + +// the core transformations (ff/gg slightly optimized) +#define FF(x, y, z) (z ^ ((y ^ z) & x)) +#define GG(x, y, z) (y ^ ((x ^ y) & z)) +#define HH(x, y, z) (x ^ y ^ z) +#define II(x, y, z) (y ^ (x | ~z)) + +// accumulate the result of the transformation +#define ACC(r, t1, t2, t3, s, x) \ + r += (t1); r += (t2); r+= (t3); r = (r<>(32-s)); r += x; + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +static const char *_MD5_HexChars = "0123456789abcdef"; + +// Public variables + + +/*** Private Functions *****************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function _CryptMD5Transform + + \Description + Incorpate 512 bit data block into current context + + \Input *pContext - target MD5 context + \Input *pBlock - pointer to data block + + \Version 03/16/02 (GWS) +*/ +/*************************************************************************************************F*/ +static void _CryptMD5Transform(CryptMD5T *pContext, const unsigned char *pBlock) +{ + register uint32_t a; + register uint32_t b; + register uint32_t c; + register uint32_t d; + uint32_t uData[16]; + // not really volatile, but keeps gcc optimizer from + // going crazy and creating really slow code + volatile uint32_t *pVector = (uint32_t *)pBlock; + + // this is not actually required on a little-endian + // machine like intel, but gcc screws up the code so + // badly without it that its faster with this + // unneeded reordering code than without it. + if (1) + { + // convert the incoming data stream to little endian + pBlock += 64; + pVector = uData+16; + while (pVector != uData) + { + // go from end to start since data is in little endian + // (this is the most efficient method to grab the data) + b = *--pBlock; + b = (b << 8) + *--pBlock; + b = (b << 8) + *--pBlock; + b = (b << 8) + *--pBlock; + *--pVector = b; + } + } + + // load the register values + a = pContext->uRegs[0]; + b = pContext->uRegs[1]; + c = pContext->uRegs[2]; + d = pContext->uRegs[3]; + + // this magic comes from the rfc + ACC(a, FF(b, c, d), pVector[ 0],0xd76aa478, 7, b); + ACC(d, FF(a, b, c), pVector[ 1],0xe8c7b756, 12, a); + ACC(c, FF(d, a, b), pVector[ 2],0x242070db, 17, d); + ACC(b, FF(c, d, a), pVector[ 3],0xc1bdceee, 22, c); + ACC(a, FF(b, c, d), pVector[ 4],0xf57c0faf, 7, b); + ACC(d, FF(a, b, c), pVector[ 5],0x4787c62a, 12, a); + ACC(c, FF(d, a, b), pVector[ 6],0xa8304613, 17, d); + ACC(b, FF(c, d, a), pVector[ 7],0xfd469501, 22, c); + ACC(a, FF(b, c, d), pVector[ 8],0x698098d8, 7, b); + ACC(d, FF(a, b, c), pVector[ 9],0x8b44f7af, 12, a); + ACC(c, FF(d, a, b), pVector[10],0xffff5bb1, 17, d); + ACC(b, FF(c, d, a), pVector[11],0x895cd7be, 22, c); + ACC(a, FF(b, c, d), pVector[12],0x6b901122, 7, b); + ACC(d, FF(a, b, c), pVector[13],0xfd987193, 12, a); + ACC(c, FF(d, a, b), pVector[14],0xa679438e, 17, d); + ACC(b, FF(c, d, a), pVector[15],0x49b40821, 22, c); + + ACC(a, GG(b, c, d), pVector[ 1],0xf61e2562, 5, b); + ACC(d, GG(a, b, c), pVector[ 6],0xc040b340, 9, a); + ACC(c, GG(d, a, b), pVector[11],0x265e5a51, 14, d); + ACC(b, GG(c, d, a), pVector[ 0],0xe9b6c7aa, 20, c); + ACC(a, GG(b, c, d), pVector[ 5],0xd62f105d, 5, b); + ACC(d, GG(a, b, c), pVector[10],0x02441453, 9, a); + ACC(c, GG(d, a, b), pVector[15],0xd8a1e681, 14, d); + ACC(b, GG(c, d, a), pVector[ 4],0xe7d3fbc8, 20, c); + ACC(a, GG(b, c, d), pVector[ 9],0x21e1cde6, 5, b); + ACC(d, GG(a, b, c), pVector[14],0xc33707d6, 9, a); + ACC(c, GG(d, a, b), pVector[ 3],0xf4d50d87, 14, d); + ACC(b, GG(c, d, a), pVector[ 8],0x455a14ed, 20, c); + ACC(a, GG(b, c, d), pVector[13],0xa9e3e905, 5, b); + ACC(d, GG(a, b, c), pVector[ 2],0xfcefa3f8, 9, a); + ACC(c, GG(d, a, b), pVector[ 7],0x676f02d9, 14, d); + ACC(b, GG(c, d, a), pVector[12],0x8d2a4c8a, 20, c); + + ACC(a, HH(b, c, d), pVector[ 5],0xfffa3942, 4, b); + ACC(d, HH(a, b, c), pVector[ 8],0x8771f681, 11, a); + ACC(c, HH(d, a, b), pVector[11],0x6d9d6122, 16, d); + ACC(b, HH(c, d, a), pVector[14],0xfde5380c, 23, c); + ACC(a, HH(b, c, d), pVector[ 1],0xa4beea44, 4, b); + ACC(d, HH(a, b, c), pVector[ 4],0x4bdecfa9, 11, a); + ACC(c, HH(d, a, b), pVector[ 7],0xf6bb4b60, 16, d); + ACC(b, HH(c, d, a), pVector[10],0xbebfbc70, 23, c); + ACC(a, HH(b, c, d), pVector[13],0x289b7ec6, 4, b); + ACC(d, HH(a, b, c), pVector[ 0],0xeaa127fa, 11, a); + ACC(c, HH(d, a, b), pVector[ 3],0xd4ef3085, 16, d); + ACC(b, HH(c, d, a), pVector[ 6],0x04881d05, 23, c); + ACC(a, HH(b, c, d), pVector[ 9],0xd9d4d039, 4, b); + ACC(d, HH(a, b, c), pVector[12],0xe6db99e5, 11, a); + ACC(c, HH(d, a, b), pVector[15],0x1fa27cf8, 16, d); + ACC(b, HH(c, d, a), pVector[ 2],0xc4ac5665, 23, c); + + ACC(a, II(b, c, d), pVector[ 0],0xf4292244, 6, b); + ACC(d, II(a, b, c), pVector[ 7],0x432aff97, 10, a); + ACC(c, II(d, a, b), pVector[14],0xab9423a7, 15, d); + ACC(b, II(c, d, a), pVector[ 5],0xfc93a039, 21, c); + ACC(a, II(b, c, d), pVector[12],0x655b59c3, 6, b); + ACC(d, II(a, b, c), pVector[ 3],0x8f0ccc92, 10, a); + ACC(c, II(d, a, b), pVector[10],0xffeff47d, 15, d); + ACC(b, II(c, d, a), pVector[ 1],0x85845dd1, 21, c); + ACC(a, II(b, c, d), pVector[ 8],0x6fa87e4f, 6, b); + ACC(d, II(a, b, c), pVector[15],0xfe2ce6e0, 10, a); + ACC(c, II(d, a, b), pVector[ 6],0xa3014314, 15, d); + ACC(b, II(c, d, a), pVector[13],0x4e0811a1, 21, c); + ACC(a, II(b, c, d), pVector[ 4],0xf7537e82, 6, b); + ACC(d, II(a, b, c), pVector[11],0xbd3af235, 10, a); + ACC(c, II(d, a, b), pVector[ 2],0x2ad7d2bb, 15, d); + ACC(b, II(c, d, a), pVector[ 9],0xeb86d391, 21, c); + + // update the registers + pContext->uRegs[0] += a; + pContext->uRegs[1] += b; + pContext->uRegs[2] += c; + pContext->uRegs[3] += d; +} + + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function CryptMD5Init + + \Description + Init the MD5 context. + + \Input *pContext - target MD5 context + + \Version 03/16/02 (GWS) +*/ +/*************************************************************************************************F*/ +void CryptMD5Init(CryptMD5T *pContext) +{ + // reset the byte count + pContext->uCount = 0; + + // init as per RFC1321 + pContext->uRegs[0] = 0x67452301; // word A + pContext->uRegs[1] = 0xefcdab89; // word B + pContext->uRegs[2] = 0x98badcfe; // word C + pContext->uRegs[3] = 0x10325476; // word D +} + +/*F*************************************************************************************************/ +/*! + \Function CryptMD5Init2 + + \Description + Init the MD5 context (alternate form) + + \Input *pContext - target MD5 context + \Input iHashSize - hash size (unused) + + \Version 11/05/13 (jbrookes) +*/ +/*************************************************************************************************F*/ +void CryptMD5Init2(CryptMD5T *pContext, int32_t iHashSize) +{ + CryptMD5Init(pContext); +} + +/*F*************************************************************************************************/ +/*! + \Function CryptMD5Update + + \Description + Add data to the MD5 context (hash the data). + + \Input *pContext - target MD5 context + \Input *_pBuffer - input data to hash + \Input iLength - length of buffer (-1=treat pBuffer as asciiz) + + \Version 03/16/02 (GWS) +*/ +/*************************************************************************************************F*/ +void CryptMD5Update(CryptMD5T *pContext, const void *_pBuffer, int32_t iLength) +{ + int32_t uAdd; + uint32_t uCount; + const unsigned char *pBuffer = _pBuffer; + + // allow easy string access + if (iLength < 0) + { + for (iLength = 0; pBuffer[iLength] != 0; ++iLength) + ; + } + + // get index into block buffer + uCount = pContext->uCount&63; + pContext->uCount += iLength; + + // see if we need to append to existing data + if (uCount > 0) + { + // figure out number to fill block + uAdd = 64-uCount; + + // if less than a full block + if (iLength < uAdd) + { + ds_memcpy(pContext->strData+uCount, pBuffer, iLength); + return; + } + + // finish off the block and transform + ds_memcpy(pContext->strData+uCount, pBuffer, uAdd); + pBuffer += uAdd; + iLength -= uAdd; + _CryptMD5Transform(pContext, pContext->strData); + } + + // do 64 byte blocks of data + while (iLength >= 64) + { + _CryptMD5Transform(pContext, pBuffer); + pBuffer += 64; + iLength -= 64; + } + + // store leftover data + if (iLength > 0) + { + ds_memcpy(pContext->strData, pBuffer, iLength); + } +} + +/*F*************************************************************************************************/ +/*! + \Function CryptMD5Final + + \Description + Convert MD5 state into final output form + + \Input *pContext - the MD5 state (from create) + \Input *_pBuffer - the digest output + \Input iLength - length of output buffer + + \Version 03/16/02 (GWS) +*/ +/*************************************************************************************************F*/ +void CryptMD5Final(CryptMD5T *pContext, void *_pBuffer, int32_t iLength) +{ + int32_t uIndex; + uint32_t uZero; + uint32_t *pZero; + uint32_t uData = 0; + unsigned char *pBuffer = _pBuffer; + + // add ending marker + uIndex = pContext->uCount & 63; + pContext->strData[uIndex++] = 0x80; + + // transform block if no room for length data + if (uIndex > 56) + { + // zero rest of the buffer + // (we have 8 extra bytes so this can run over) + pContext->strData[uIndex+0] = 0; + pContext->strData[uIndex+1] = 0; + pContext->strData[uIndex+2] = 0; + pContext->strData[uIndex+3] = 0; + pContext->strData[uIndex+4] = 0; + pContext->strData[uIndex+5] = 0; + pContext->strData[uIndex+6] = 0; + pContext->strData[uIndex+7] = 0; + // transform the block + _CryptMD5Transform(pContext, pContext->strData); + uIndex = 0; + } + + // force zero to next int32_t + pContext->strData[uIndex+0] = 0; + pContext->strData[uIndex+1] = 0; + pContext->strData[uIndex+2] = 0; + // zero to end of block + uZero = (uIndex+3)>>2; + pZero = ((uint32_t *)pContext->strData)+uZero; + do + { + *pZero++ = 0; + } + while (++uZero < 64/4); + + // setup length mask + pContext->strData[56] = (unsigned char)(pContext->uCount<<3); + pContext->strData[57] = (unsigned char)(pContext->uCount>>5); + pContext->strData[58] = (unsigned char)(pContext->uCount>>13); + pContext->strData[59] = (unsigned char)(pContext->uCount>>21); + pContext->strData[60] = (unsigned char)(pContext->uCount>>29); + // final transformation + _CryptMD5Transform(pContext, pContext->strData); + +#ifdef __linux__ + // fast output of binary data in linux (more memory) + if (iLength == MD5_BINARY_OUT/2) + { + uData = pContext->uRegs[0]; + pBuffer[0] = uData; + uData >>= 8; + pBuffer[1] = uData; + uData >>= 8; + pBuffer[2] = uData; + uData >>= 8; + pBuffer[3] = uData; + + uData = pContext->uRegs[1]; + pBuffer[4] = uData; + uData >>= 8; + pBuffer[5] = uData; + uData >>= 8; + pBuffer[6] = uData; + uData >>= 8; + pBuffer[7] = uData; + return; + } + + if (iLength == MD5_BINARY_OUT) + { + uData = pContext->uRegs[0]; + pBuffer[0] = uData; + uData >>= 8; + pBuffer[1] = uData; + uData >>= 8; + pBuffer[2] = uData; + uData >>= 8; + pBuffer[3] = uData; + + uData = pContext->uRegs[1]; + pBuffer[4] = uData; + uData >>= 8; + pBuffer[5] = uData; + uData >>= 8; + pBuffer[6] = uData; + uData >>= 8; + pBuffer[7] = uData; + + uData = pContext->uRegs[2]; + pBuffer[8] = uData; + uData >>= 8; + pBuffer[9] = uData; + uData >>= 8; + pBuffer[10] = uData; + uData >>= 8; + pBuffer[11] = uData; + + uData = pContext->uRegs[3]; + pBuffer[12] = uData; + uData >>= 8; + pBuffer[13] = uData; + uData >>= 8; + pBuffer[14] = uData; + uData >>= 8; + pBuffer[15] = uData; + return; + } +#endif + + // extract data from buffer and save + for (uIndex = 0; uIndex < 16; ++uIndex) + { + // see if we need to fetch a byte + if ((uIndex & 3) == 0) + { + uData = pContext->uRegs[uIndex>>2]; + } + // store the byte + if (iLength >= MD5_STRING_OUT) + { + *pBuffer++ = _MD5_HexChars[(uData>>4)&15]; + *pBuffer++ = _MD5_HexChars[(uData>>0)&15]; + } + else if (uIndex < iLength) + { + *pBuffer++ = (unsigned char)uData; + } + // shift down the data + uData >>= 8; + } + + // add final terminator if needed + if (iLength >= MD5_STRING_OUT) + { + *pBuffer = 0; + } +} + diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptmont.c b/src/thirdparty/dirtysdk/source/crypt/cryptmont.c new file mode 100644 index 00000000..939ce246 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptmont.c @@ -0,0 +1,648 @@ +/*H********************************************************************************/ +/*! + \File cryptmont.h + + \Description + This module implements the math for elliptic curve cryptography + using montgomery curves + + \Copyright + Copyright (c) Electronic Arts 2018. ALL RIGHTS RESERVED. +*/ +/********************************************************************************H*/ + +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/crypt/cryptrand.h" + +#include "DirtySDK/crypt/cryptmont.h" + +/*** Defines **********************************************************************/ + +//! size of the window used to determined the table size +#define CRYPTMONT_WINDOW_SIZE (5) + +//! calculation of the table size based on the window +#define CRYPTMONT_TABLE_SIZE (1 << (CRYPTMONT_WINDOW_SIZE - 1)) + +//! memgroup for allocating the table +#define CRYPTMONT_MEMID ('mont') + +//! number of iterations to do per call +#define CRYPTMONT_NUM_ITERATIONS (0x10) + +/*** Variables ********************************************************************/ + +//! prime for x25519 +static const uint8_t _aPrime25519[] = +{ + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed +}; + +//! prime for x25519 +static const uint8_t _aPrime448[] = +{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _CryptMontSwap + + \Description + Constant time conditional swap of two big numbers + + \Input uSwap - determintes if we should swap + \Input *pLhs - [out] first big number in the swap + \Input *pRhs - [out] second big number in the swap + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +static void _CryptMontSwap(uint8_t uSwap, CryptBnT *pLhs, CryptBnT *pRhs) +{ + CryptBnT Temp, Mask; + + // mask = (1 << bits) - swap + CryptBnInitSet(&Mask, 1); + CryptBnInitSet(&Temp, uSwap); + CryptBnLeftShift2(&Mask, DS_MAX(CryptBnBitLen(pLhs), CryptBnBitLen(pRhs))); + CryptBnSubtract(&Mask, &Mask, &Temp); + + // temp = mask & (lhs ^ rhs) + CryptBnBitXor(&Temp, pLhs, pRhs); + CryptBnBitAnd(&Temp, &Temp, &Mask); + + // lhs ^= temp + CryptBnBitXor(pLhs, pLhs, &Temp); + + // rhs ^= temp + CryptBnBitXor(pRhs, pRhs, &Temp); +} + +/*F********************************************************************************/ +/*! + \Function _CryptMontPointCalculate + + \Description + Calculates the point multiplication on the curve based on the private key + before doing the final result calculation + + \Input *pState - curve state + \Input *pU - the point we multiply with the private key + + \Output + uint8_t - TRUE=complete, FALSE=pending + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +static uint8_t _CryptMontPointCalculate(CryptMontT *pState, CryptBnT *pU) +{ + int32_t iIter; + + // initialize the operation + if (pState->iBitIndex < 0) + { + pState->iBitIndex = CryptBnBitLen(&pState->Prime) - 1; + pState->uCryptUsecs = 0; + + CryptBnInitSet(&pState->X_2, 1); + CryptBnInitSet(&pState->Result.X, 0); + CryptBnClone(&pState->X_3, pU); + CryptBnInitSet(&pState->Result.Y, 1); + } + + for (iIter = 0; (pState->iBitIndex >= 0) && (iIter < CRYPTMONT_NUM_ITERATIONS); pState->iBitIndex -= 1, iIter += 1) + { + CryptBnT A, AA, B, BB, C, D, DA, CB, E; + uint8_t bBitSet = CryptBnBitTest(&pState->PrivateKey, pState->iBitIndex); + const uint64_t uTickUsecs = NetTickUsec(); + + CryptBnInitSet(&A, 0); + CryptBnInitSet(&B, 0); + CryptBnInitSet(&C, 0); + CryptBnInitSet(&D, 0); + CryptBnInitSet(&E, 0); + + // swap ^= bitset; + pState->uSwap ^= bBitSet; + + // constant time conditional swap + _CryptMontSwap(pState->uSwap, &pState->X_2, &pState->X_3); + _CryptMontSwap(pState->uSwap, &pState->Result.X, &pState->Result.Y); + + // swap = bitset + pState->uSwap = bBitSet; + + // A = (x_2 + z_2) % p + CryptBnModAdd(&A, &pState->X_2, &pState->Result.X, &pState->Prime); + + // AA = (A * A) % p + CryptBnModMultiply(&AA, &A, &A, &pState->Prime); + + // B = (x_2 - z_2) + CryptBnSubtract(&B, &pState->X_2, &pState->Result.X); + + // BB = (B * B) % p + CryptBnModMultiply(&BB, &B, &B, &pState->Prime); + + // E = (AA - BB) + CryptBnSubtract(&E, &AA, &BB); + + // x_2 = (AA * BB) % p + CryptBnModMultiply(&pState->X_2, &AA, &BB, &pState->Prime); + + // z_2 = (E * (AA + (a24 * E))) % p + CryptBnModMultiply(&pState->Result.X, &pState->A24, &E, &pState->Prime); + CryptBnModAdd(&pState->Result.X, &AA, &pState->Result.X, &pState->Prime); + CryptBnModMultiply(&pState->Result.X, &E, &pState->Result.X, &pState->Prime); + + // C = (x_3 + z_3) % p + CryptBnModAdd(&C, &pState->X_3, &pState->Result.Y, &pState->Prime); + + // D = x_3 - z_3 + CryptBnSubtract(&D, &pState->X_3, &pState->Result.Y); + + // DA = (D * A) % p + CryptBnModMultiply(&DA, &D, &A, &pState->Prime); + + // CB = (C * B) % p + CryptBnModMultiply(&CB, &C, &B, &pState->Prime); + + // x_3 = (DA+CB)^2 % p + CryptBnModAdd(&pState->X_3, &DA, &CB, &pState->Prime); + CryptBnModMultiply(&pState->X_3, &pState->X_3, &pState->X_3, &pState->Prime); + + // z_3 = (u * ((DA-CB)^2 % p)) % p + CryptBnSubtract(&pState->Result.Y, &DA, &CB); + CryptBnModMultiply(&pState->Result.Y, &pState->Result.Y, &pState->Result.Y, &pState->Prime); + CryptBnModMultiply(&pState->Result.Y, pU, &pState->Result.Y, &pState->Prime); + + // update timing + pState->uCryptUsecs += (uint32_t)NetTickDiff(NetTickUsec(), uTickUsecs); + } + + // check for completion of first stage, perform required operations and move to next + if (pState->iBitIndex < 0) + { + // constant time conditional swap + _CryptMontSwap(pState->uSwap, &pState->X_2, &pState->X_3); + _CryptMontSwap(pState->uSwap, &pState->Result.X, &pState->Result.Y); + } + + return((pState->iBitIndex < 0) ? TRUE : FALSE); +} + +/*F********************************************************************************/ +/*! + \Function _CryptMontResultCalculate + + \Description + Determines the final using the final result using the formula: + result = x_2 * (z_2 ^ (p - 2) % p) % p + + \Input *pState - curve state + + \Output + uint8_t - TRUE=complete, FALSE=pending + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +static uint8_t _CryptMontResultCalculate(CryptMontT *pState) +{ + int32_t iIter; + + // initialize the operation + if (pState->iBitIndex < 0) + { + int32_t iTableIndex; + CryptBnT Square; + + // query memgroup + DirtyMemGroupQuery(&pState->iMemGroup, &pState->pMemGroupUserdata); + + if ((pState->pTable = (CryptBnT *)DirtyMemAlloc(sizeof(*pState->pTable) * CRYPTMONT_TABLE_SIZE, CRYPTMONT_MEMID, pState->iMemGroup, pState->pMemGroupUserdata)) == NULL) + { + NetPrintf(("cryptmont: failed to allocate window table\n")); + return(TRUE); + } + ds_memclr(pState->pTable, sizeof(*pState->pTable) * CRYPTMONT_TABLE_SIZE); + + /* put the already reduced input into the first index of the table, this will make it so further + entries in the table will not be too large for our modulus operations */ + CryptBnClone(&pState->pTable[0], &pState->Result.X); + + // calculate (input^2) % mod as the basis for the other calculations + CryptBnModMultiply(&Square, &pState->pTable[0], &pState->pTable[0], &pState->Prime); + + // calculate (input^(2+iTableIndex)) % mod for the rest of the table + for (iTableIndex = 1; iTableIndex < CRYPTMONT_TABLE_SIZE; iTableIndex += 1) + { + CryptBnModMultiply(&pState->pTable[iTableIndex], &pState->pTable[iTableIndex - 1], &Square, &pState->Prime); + } + + pState->bAccumulOne = TRUE; + + // save prime - 2 + CryptBnInitSet(&pState->PrimeMin2, 2); + CryptBnSubtract(&pState->PrimeMin2, &pState->Prime, &pState->PrimeMin2); + + pState->iBitIndex = CryptBnBitLen(&pState->PrimeMin2) - 1; + } + + for (iIter = 0; (pState->iBitIndex >= 0) && (iIter < CRYPTMONT_NUM_ITERATIONS); iIter += 1) + { + const uint64_t uTickUsecs = NetTickUsec(); + + /* scan backwards from the current exponent bit until a set bit is found to denote the start of the window + squaring the results until such a bit is found */ + if (CryptBnBitTest(&pState->PrimeMin2, pState->iBitIndex)) + { + int32_t iWindowBit, iWindowValue, iWindowEnd; + + /* scan backwards from the start of the window until the last set bit in the range of the window size is found which denotes the end bit index of the window + calculate the value of the window that will be used when multiplying against our precomputed table we skip the first bit as we know it is set based on the + current exponent bit check we make right above */ + for (iWindowBit = 1, iWindowValue = 1, iWindowEnd = 0; (iWindowBit < CRYPTMONT_WINDOW_SIZE) && ((pState->iBitIndex - iWindowBit) >= 0); iWindowBit += 1) + { + if (CryptBnBitTest(&pState->PrimeMin2, pState->iBitIndex - iWindowBit)) + { + iWindowValue <<= (iWindowBit - iWindowEnd); + iWindowValue |= 1; /* force odd */ + iWindowEnd = iWindowBit; + } + } + + // square for all the bits in the window and moving the exponent bit index down each time + for (iWindowBit = 0; iWindowBit < iWindowEnd + 1; iWindowBit += 1, pState->iBitIndex -= 1) + { + CryptBnModMultiply(&pState->Result.X, &pState->Result.X, &pState->Result.X, &pState->Prime); + } + + // skip the first multiply + if (!pState->bAccumulOne) + { + CryptBnModMultiply(&pState->Result.X, &pState->Result.X, &pState->pTable[iWindowValue/2], &pState->Prime); + } + else + { + CryptBnClone(&pState->Result.X, &pState->pTable[iWindowValue/2]); + pState->bAccumulOne = FALSE; + } + } + else + { + CryptBnModMultiply(&pState->Result.X, &pState->Result.X, &pState->Result.X, &pState->Prime); + pState->iBitIndex -= 1; + } + + // update timing + pState->uCryptUsecs += (uint32_t)NetTickDiff(NetTickUsec(), uTickUsecs); + } + + // calculate the final result + if (pState->iBitIndex < 0) + { + DirtyMemFree(pState->pTable, CRYPTMONT_MEMID, pState->iMemGroup, pState->pMemGroupUserdata); + pState->pTable = NULL; + + CryptBnModMultiply(&pState->Result.X, &pState->X_2, &pState->Result.X, &pState->Prime); + } + + return((pState->iBitIndex < 0) ? TRUE : FALSE); +} + +/*F********************************************************************************/ +/*! + \Function _CryptMontInit25519PrivateKey + + \Description + Initializes the private key based on the requirements for this curve + (x25519) + + \Input *pPrivateKey - [out] private key state + \Input *pK - private key buffer or NULL if we should generate one + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +static void _CryptMontInit25519PrivateKey(CryptBnT *pPrivateKey, const uint8_t *pK) +{ + uint8_t aSecret[32]; + + /* per: https://tools.ietf.org/html/rfc7748#section-5 + For X25519, in order to decode 32 random bytes as an integer scalar, set the three least significant bits of the first byte and + the most significant bit of the last to zero, set the second most significant bit of the last byte to 1 and, finally, decode as + little-endian. This means that the resulting integer is of the form 2^254 plus eight times a value between 0 and 2^251 - 1 (inclusive) */ + + // retrieve the random bytes if not provided one + if (pK == NULL) + { + CryptRandGet(aSecret, sizeof(aSecret)); + } + else + { + ds_memcpy(aSecret, pK, sizeof(aSecret)); + } + + aSecret[0] &= 248; + aSecret[31] &= 127; + aSecret[31] |= 64; + CryptBnInitLeFrom(pPrivateKey, aSecret, sizeof(aSecret)); +} + +/*F********************************************************************************/ +/*! + \Function _CryptMontInit448PrivateKey + + \Description + Initializes the private key based on the requirements for this curve + (x448) + + \Input *pPrivateKey - [out] private key state + \Input *pK - private key buffer or NULL if we should generate one + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +static void _CryptMontInit448PrivateKey(CryptBnT *pPrivateKey, const uint8_t *pK) +{ + uint8_t aSecret[56]; + + /* per: https://tools.ietf.org/html/rfc7748#section-5 + Likewise, for X448, set the two least significant bits of the first byte to 0, and the most significant bit of the last byte to 1. This + means that the resulting integer is of the form 2^447 plus four times a value between 0 and 2^445 - 1 (inclusive). */ + + if (pK == NULL) + { + CryptRandGet(aSecret, sizeof(aSecret)); + } + else + { + ds_memcpy(aSecret, pK, sizeof(aSecret)); + } + + aSecret[0] &= 252; + aSecret[55] |= 128; + CryptBnInitLeFrom(pPrivateKey, aSecret, sizeof(aSecret)); +} + +/*** Public Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CryptMontInit + + \Description + Initializes the curve given an identifier + + \Input *pState - curve state we are initializing + \Input iCurveType - the curve identifier (CRYPTMONT_CURVE_*) + + \Output + int32_t - 0=success, negative=failure + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptMontInit(CryptMontT *pState, int32_t iCurveType) +{ + // init state + ds_memclr(pState, sizeof(*pState)); + pState->iBitIndex = -1; + pState->iCurveType = iCurveType; + + if (iCurveType == CRYPTCURVE_X25519) + { + // save prime + CryptBnInitFrom(&pState->Prime, -1, _aPrime25519, sizeof(_aPrime25519)); + // save a24 + CryptBnInitSet(&pState->A24, 121665); + // save u + CryptBnInitSet(&pState->BasePoint, 9); + // get k + _CryptMontInit25519PrivateKey(&pState->PrivateKey, NULL); + } + else if (iCurveType == CRYPTCURVE_X448) + { + // save prime + CryptBnInitFrom(&pState->Prime, -1, _aPrime448, sizeof(_aPrime448)); + // save a24 + CryptBnInitSet(&pState->A24, 39081); + // save u + CryptBnInitSet(&pState->BasePoint, 5); + // get k + _CryptMontInit448PrivateKey(&pState->PrivateKey, NULL); + } + else + { + NetPrintf(("cryptmont: unrecognized curve type (%d) passed to init\n", iCurveType)); + return(-1); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function CryptMontSetPrivateKey + + \Description + Sets our internal private key for verifying our test vectors based + on the state's curve type + + \Input *pState - curve state + \Input *pKey - the private key buffer + + \Notes + This is for testing purposes only + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +void CryptMontSetPrivateKey(CryptMontT *pState, const uint8_t *pKey) +{ + #if DIRTYCODE_DEBUG + if (pState->iCurveType == CRYPTCURVE_X25519) + { + _CryptMontInit25519PrivateKey(&pState->PrivateKey, pKey); + } + else if (pState->iCurveType == CRYPTCURVE_X448) + { + _CryptMontInit448PrivateKey(&pState->PrivateKey, pKey); + } + #endif +} + +/*F********************************************************************************/ +/*! + \Function CryptMontPublic + + \Description + Generates our public key by doing our PrivateKey * BasePoint on the curve + + \Input *pState - curve state + \Input *pResult - [out] result output (optional) + \Input *pCryptUsecs - [out] timing information for the operation (optional) + + \Output + int32_t - zero=success, otherwise=pending + + \Notes + If pResult is NULL, the result can be pulled from CryptMontT.Result + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptMontPublic(CryptMontT *pState, CryptEccPointT *pResult, uint32_t *pCryptUsecs) +{ + int32_t iResult = 1; + + if ((pState->eState == CRYPTMONT_COMPUTE_POINT) && (_CryptMontPointCalculate(pState, &pState->BasePoint))) + { + // switch state on completion + pState->eState = CRYPTMONT_COMPUTE_EXP; + } + else if ((pState->eState == CRYPTMONT_COMPUTE_EXP) && (_CryptMontResultCalculate(pState))) + { + // reset back to original state in case they want to perform a different operation + pState->eState = CRYPTMONT_COMPUTE_POINT; + + // copy the timing if passed in + if (pCryptUsecs != NULL) + { + *pCryptUsecs = pState->uCryptUsecs; + } + // signal completion + if (pResult != NULL) + { + CryptBnClone(&pResult->X, &pState->Result.X); + } + iResult = 0; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function CryptMontSecret + + \Description + Generates our shared secret by doing our PrivateKey * PublicKey on the curve + + \Input *pState - curve state + \Input *pPublicKey - the peer's public key + \Input *pResult - [out] result output (optional) + \Input *pCryptUsecs - [out] timing information for the operation (optional) + + \Output + int32_t - zero=success, otherwise=pending + + \Notes + If pResult is NULL, the result can be pulled from CryptMontT.Result + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptMontSecret(CryptMontT *pState, CryptEccPointT *pPublicKey, CryptEccPointT *pResult, uint32_t *pCryptUsecs) +{ + int32_t iResult = 1; + + if ((pState->eState == CRYPTMONT_COMPUTE_POINT) && (_CryptMontPointCalculate(pState, &pPublicKey->X))) + { + // switch state on completion + pState->eState = CRYPTMONT_COMPUTE_EXP; + } + else if ((pState->eState == CRYPTMONT_COMPUTE_EXP) && (_CryptMontResultCalculate(pState))) + { + // reset back to original state in case they want to perform a different operation + pState->eState = CRYPTMONT_COMPUTE_POINT; + + // copy the timing if passed in + if (pCryptUsecs != NULL) + { + *pCryptUsecs = pState->uCryptUsecs; + } + // signal completion + if (pResult != NULL) + { + CryptBnClone(&pResult->X, &pState->Result.X); + } + iResult = 0; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function CryptMontPointInitFrom + + \Description + Initializes our point representation given a buffer + + \Input *pPoint - point state + \Input *pBuffer - the buffer we are copying from + \Input iBufSize - size of the buffer + + \Output + int32_t - zero=success, otherwise=failure + + \Notes + These curves work in little endian so need to copy different functions + from our nist curves. + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptMontPointInitFrom(CryptEccPointT *pPoint, const uint8_t *pBuffer, int32_t iBufSize) +{ + CryptBnInitLeFrom(&pPoint->X, pBuffer, iBufSize); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function CryptMontPointFinal + + \Description + Copies our point data into an output buffer + + \Input *pState - curve state + \Input *pPoint - point state or NULL to use curve result + \Input bSecret - is this the shared secret? + \Input *pBuffer - [out] the buffer we are copying into + \Input iBufSize - size of the buffer + + \Output + int32_t - number of bytes encoded into the buffer + + \Notes + These curves work in little endian so need to copy different functions + from our nist curves. + + The bSecret is unused here but is left for compatibility with the other + curves' API. + + \Version 04/11/2018 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptMontPointFinal(const CryptMontT *pState, const CryptEccPointT *pPoint, uint8_t bSecret, uint8_t *pBuffer, int32_t iBufSize) +{ + // if point not provided assume we are using the result + if ((pState != NULL) && (pPoint == NULL)) + { + pPoint = &pState->Result; + } + + CryptBnFinalLe(&pPoint->X, pBuffer, iBufSize); + return(CryptBnByteLen(&pPoint->X)); +} diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptnist.c b/src/thirdparty/dirtysdk/source/crypt/cryptnist.c new file mode 100644 index 00000000..9576e2c7 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptnist.c @@ -0,0 +1,1081 @@ +/*H********************************************************************************/ +/*! + \File cryptnist.c + + \Description + This module implements the math for elliptic curve cryptography + using curves in short Weierstrass form (NIST curves) + + \Copyright + Copyright (c) Electronic Arts 2017. ALL RIGHTS RESERVED. +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/crypt/crypthash.h" +#include "DirtySDK/crypt/crypthmac.h" +#include "DirtySDK/crypt/cryptrand.h" +#include "DirtySDK/crypt/cryptnist.h" + +/*** Defines **********************************************************************/ + +//! the marker that marks uncompressed point format +#define CRYPTNIST_UNCOMPRESSED_MARKER (0x04) + +//! number of iterations to do per call +#if !defined(CRYPTNIST_NUM_ITERATIONS) + #define CRYPTNIST_NUM_ITERATIONS (0x10) +#endif + +//! memgroup for allocating the table +#define CRYPTNIST_MEMID ('nist') + +/*** Type Definitions *************************************************************/ + +//! defintion of a curve, basis of initialization into big numbers +typedef struct NistCurveT +{ + int32_t iIdent; + int32_t iSize; + uint8_t aPrime[48]; + uint8_t aCoefficientA[48]; + uint8_t aCoefficientB[48]; + uint8_t aBasePoint[97]; + uint8_t aOrder[48]; +} NistCurveT; + +/*** Variables ********************************************************************/ + +//! used to easily checked for default points +static const CryptEccPointT _Zero = { { { 0x0 }, 1, 0 }, { { 0x0 }, 1, 0 } }; + +//! the curves we support +static const NistCurveT _aEccCurves[] = +{ + { + CRYPTCURVE_SECP256R1, 32, + { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC }, + { 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3, 0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27, 0xD2, 0x60, 0x4B }, + { 0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96, + 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51 } + }, + { + CRYPTCURVE_SECP384R1, 48, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC }, + { 0xB3, 0x31, 0x2F, 0xA7, 0xE2, 0x3E, 0xE7, 0xE4, 0x98, 0x8E, 0x05, 0x6B, 0xE3, 0xF8, 0x2D, 0x19, 0x18, 0x1D, 0x9C, 0x6E, 0xFE, 0x81, 0x41, 0x12, 0x03, 0x14, 0x08, 0x8F, 0x50, 0x13, 0x87, 0x5A, + 0xC6, 0x56, 0x39, 0x8D, 0x8A, 0x2E, 0xD1, 0x9D, 0x2A, 0x85, 0xC8, 0xED, 0xD3, 0xEC, 0x2A, 0xEF }, + { 0x04, 0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05, 0x37, 0x8E, 0xB1, 0xC7, 0x1E, 0xF3, 0x20, 0xAD, 0x74, 0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B, 0x98, 0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A, 0x38, + 0x55, 0x02, 0xF2, 0x5D, 0xBF, 0x55, 0x29, 0x6C, 0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A, 0xB7, 0x36, 0x17, 0xDE, 0x4A, 0x96, 0x26, 0x2C, 0x6F, 0x5D, 0x9E, 0x98, 0xBF, 0x92, 0x92, 0xDC, 0x29, + 0xF8, 0xF4, 0x1D, 0xBD, 0x28, 0x9A, 0x14, 0x7C, 0xE9, 0xDA, 0x31, 0x13, 0xB5, 0xF0, 0xB8, 0xC0, 0x0A, 0x60, 0xB1, 0xCE, 0x1D, 0x7E, 0x81, 0x9D, 0x7A, 0x43, 0x1D, 0x7C, 0x90, 0xEA, 0x0E, 0x5F }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF, + 0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A, 0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73 } + } +}; + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _CryptNistPointCalculate + + \Description + Does the math for doubling / adding points given they you have calculated + S over the curve's prime + + \Input *pState - curve state + \Input *pP - point P + \Input *pQ - point Q + \Input *pS - S used in calculation + \Input *pResult - [out] point R that is calculation of P+Q + + \Version 02/17/2017 (eesponda) +*/ +/********************************************************************************F*/ +static void _CryptNistPointCalculate(CryptNistT *pState, CryptEccPointT *pP, CryptEccPointT *pQ, CryptBnT *pS, CryptEccPointT *pResult) +{ + CryptEccPointT Result; + + // X = S² - P.X - Q.X + CryptBnInitSet(&Result.X, 0); + CryptBnModMultiply(&Result.X, pS, pS, &pState->Prime); + CryptBnSubtract(&Result.X, &Result.X, &pP->X); + CryptBnSubtract(&Result.X, &Result.X, &pQ->X); + + // Y = S * (P.X - X) - P.Y + CryptBnInitSet(&Result.Y, 0); + CryptBnSubtract(&Result.Y, &pP->X, &Result.X); + CryptBnModMultiply(&Result.Y, &Result.Y, pS, &pState->Prime); + CryptBnSubtract(&Result.Y, &Result.Y, &pP->Y); + + // X %= Curve.Prime + CryptBnMod(&Result.X, &pState->Prime, &Result.X, NULL); + // Y %= Curve.Prime + CryptBnMod(&Result.Y, &pState->Prime, &Result.Y, NULL); + + // write out the result + ds_memcpy(pResult, &Result, sizeof(*pResult)); +} + +/*F********************************************************************************/ +/*! + \Function _CryptNistPointAddition + + \Description + Adds two points together + + \Input *pState - curve state + \Input *pPoint1 - first point in addition + \Input *pPoint2 - second point in addtion + \Input *pResult - [out] point R that is result of addition + + \Version 02/17/2017 (eesponda) +*/ +/********************************************************************************F*/ +static void _CryptNistPointAddition(CryptNistT *pState, CryptEccPointT *pPoint1, CryptEccPointT *pPoint2, CryptEccPointT *pResult) +{ + if (memcmp(pPoint1, &_Zero, sizeof(*pPoint1)) == 0) + { + ds_memcpy(pResult, pPoint2, sizeof(*pResult)); + } + else if (memcmp(pPoint2, &_Zero, sizeof(*pPoint2)) == 0) + { + ds_memcpy(pResult, pPoint1, sizeof(*pResult)); + } + else + { + CryptBnT A, B; + + CryptBnInitSet(&A, 0); + CryptBnInitSet(&B, 0); + + // A = y1 - y2 + CryptBnSubtract(&A, &pPoint1->Y, &pPoint2->Y); + + // B = (x1 - x2)⁻¹ % Curve.Prime [Inverse Modulus] + CryptBnSubtract(&B, &pPoint1->X, &pPoint2->X); + CryptBnInverseMod(&B, &pState->Prime); + + // A *= B + CryptBnModMultiply(&A, &A, &B, &pState->Prime); + + // calculate the result + _CryptNistPointCalculate(pState, pPoint1, pPoint2, &A, pResult); + } +} + +/*F********************************************************************************/ +/*! + \Function _CryptNistPointDouble + + \Description + Doubles a point + + \Input *pState - curve state + \Input *pPoint - point that we are doubling + \Input *pResult - [out] result of the doubling + + \Version 02/17/2017 (eesponda) +*/ +/********************************************************************************F*/ +static void _CryptNistPointDouble(CryptNistT *pState, CryptEccPointT *pPoint, CryptEccPointT *pResult) +{ + CryptBnT A, B; + + // if attempting to double zero, return + if (memcmp(pPoint, &_Zero, sizeof(*pPoint)) == 0) + { + return; + } + + // A = 3 * x * x + a + CryptBnInitSet(&A, 3); + CryptBnMultiply(&A, &A, &pPoint->X); + CryptBnMultiply(&A, &A, &pPoint->X); + CryptBnAccumulate(&A, &pState->CoefficientA); + + // B = (2 * y)⁻¹ % Curve.Prime [Inverse Modulus] + CryptBnClone(&B, &pPoint->Y); + CryptBnLeftShift(&B); + CryptBnInverseMod(&B, &pState->Prime); + + // A *= B + CryptBnModMultiply(&A, &A, &B, &pState->Prime); + + // calculate the result + _CryptNistPointCalculate(pState, pPoint, pPoint, &A, pResult); +} + +/*F********************************************************************************/ +/*! + \Function _CryptNistDoubleAndAdd + + \Description + Performs the scalar multiplication using double and add + operations using a sliding window for the private key + + \Input *pState - curve state + \Input *pInput - point we are multiplying by the private key + \Input *pPrivateKey - private key was using to multiply + \Input *pResult - [out] result of the operation + + \Output + int32_t - zero=success, otherwise=pending + + \Notes + Adapted from the sliding window algorithm used for exponentiation + in RSA + See Handbook of Applied Cryptography Chapter 14.6.1 (14.85): + http://cacr.uwaterloo.ca/hac/about/chap14.pdf + + \Version 05/08/2017 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CryptNistDoubleAndAdd(CryptNistT *pState, CryptEccPointT *pInput, CryptBnT *pPrivateKey, CryptEccPointT *pResult) +{ + int32_t iIter = CRYPTNIST_NUM_ITERATIONS; + + do + { + const uint64_t uTick = NetTickUsec(); + + if (pState->iKeyIndex < 0) + { + int32_t iTableIndex; + CryptEccPointT Double; + + // init working state + ds_memcpy(pResult, &_Zero, sizeof(*pResult)); + + // query memgroup + DirtyMemGroupQuery(&pState->iMemGroup, &pState->pMemGroupUserdata); + + if ((pState->pTable = (CryptEccPointT *)DirtyMemAlloc(sizeof(*pState->pTable) * CRYPTNIST_TABLE_SIZE, CRYPTNIST_MEMID, pState->iMemGroup, pState->pMemGroupUserdata)) == NULL) + { + NetPrintf(("cryptnist: failed to allocate window table\n")); + return(0); + } + ds_memclr(pState->pTable, sizeof(*pState->pTable) * CRYPTNIST_TABLE_SIZE); + + /* put the input into the first + index of the table, this will make it so further + entries in the table will not be too large + for our modulus operations */ + ds_memcpy(&pState->pTable[0], pInput, sizeof(pState->pTable[0])); + + // calculate doubled input as the basis for the other calculations + _CryptNistPointDouble(pState, pInput, &Double); + + // calculate the further added points for the rest of the table + for (iTableIndex = 1; iTableIndex < CRYPTNIST_TABLE_SIZE; iTableIndex += 1) + { + _CryptNistPointAddition(pState, &pState->pTable[iTableIndex - 1], &Double, &pState->pTable[iTableIndex]); + } + + // set the defaults + pState->iKeyIndex = CryptBnBitLen(pPrivateKey) - 1; + + // clear the profiling information + pState->uCryptUSecs = 0; + } + + /* scan backwards from the current private key bit + until a set bit is found to denote the start of the window + doubling the results until such a bit is found */ + if (CryptBnBitTest(pPrivateKey, pState->iKeyIndex)) + { + int32_t iWindowBit, iWindowValue, iWindowEnd; + + /* scan backwards from the start of the window until the last set bit in the range of the window size is found which denotes the end bit index of the window + calculate the value of the window that will be used when adding against our precomputed table. + we skip the first bit as we know it is set based on the current private key bit check we make right above */ + for (iWindowBit = 1, iWindowValue = 1, iWindowEnd = 0; (iWindowBit < CRYPTNIST_WINDOW_SIZE) && ((pState->iKeyIndex - iWindowBit) >= 0); iWindowBit += 1) + { + if (CryptBnBitTest(pPrivateKey, pState->iKeyIndex - iWindowBit)) + { + iWindowValue <<= (iWindowBit - iWindowEnd); + iWindowValue |= 1; // force odd + iWindowEnd = iWindowBit; + } + } + + // double for each bits in the window and moving the private key bit index down each time + for (iWindowBit = 0; iWindowBit < iWindowEnd + 1; iWindowBit += 1, pState->iKeyIndex -= 1) + { + _CryptNistPointDouble(pState, pResult, pResult); + } + + // use the window value to get which precomputed power we need to multiply + _CryptNistPointAddition(pState, pResult, &pState->pTable[iWindowValue/2], pResult); + } + else + { + _CryptNistPointDouble(pState, pResult, pResult); + pState->iKeyIndex -= 1; + } + + // update profiler information + pState->uCryptUSecs += NetTickDiff(NetTickUsec(), uTick); + } + while ((pState->iKeyIndex >= 0) && (--iIter > 0)); + + // cleanup memory when done + if (pState->iKeyIndex < 0) + { + DirtyMemFree(pState->pTable, CRYPTNIST_MEMID, pState->iMemGroup, pState->pMemGroupUserdata); + pState->pTable = NULL; + } + + return(pState->iKeyIndex >= 0); +} + +/*F********************************************************************************/ +/*! + \Function _CryptNistDoubleMultiply + + \Description + Uses Shamir's Trick to do double point multiplication: + (pInput1 * pPrivateKey1) + (pInput2 * pPrivateKey2) + + \Input *pState - curve state + \Input *pInput1 - point we are multiplying by pMul1 + \Input *pMul1 - big number we are multiplying by pInput1 + \Input *pInput2 - point we are multiplying by pMul2 + \Input *pMul2 - big number we are multiplying by pInput2 + \Input *pResult - [out] result of the operation + + \Output + int32_t - zero=success, otherwise=pending + + \Notes + For more information on the algorithm, see the Double-scalar + multiplication slides: http://www.lirmm.fr/~imbert/talks/laurent_Asilomar_08.pdf + + \Version 05/15/2017 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CryptNistDoubleMultiply(CryptNistT *pState, CryptEccPointT *pInput1, CryptBnT *pMul1, CryptEccPointT *pInput2, CryptBnT *pMul2, CryptEccPointT *pResult) +{ + int32_t iIter = CRYPTNIST_NUM_ITERATIONS; + + do + { + const uint64_t uTick = NetTickUsec(); + uint8_t bLHBitSet, bRHBitSet; + + if (pState->iKeyIndex < 0) + { + DirtyMemGroupQuery(&pState->iMemGroup, &pState->pMemGroupUserdata); + + if ((pState->pTable = (CryptEccPointT *)DirtyMemAlloc(sizeof(*pState->pTable), CRYPTNIST_MEMID, pState->iMemGroup, pState->pMemGroupUserdata)) == NULL) + { + NetPrintf(("cryptnist: failed to allocate window table\n")); + return(0); + } + ds_memclr(pState->pTable, sizeof(*pState->pTable)); + + // precompute pInput1 + pInput2 + _CryptNistPointAddition(pState, pInput1, pInput2, &pState->pTable[0]); + + // init working state + ds_memcpy(pResult, &_Zero, sizeof(*pResult)); + + // set the maximum bit size + pState->iKeyIndex = DS_MAX(CryptBnBitLen(pMul1), CryptBnBitLen(pMul2)) - 1; + + // clear the profiling information + pState->uCryptUSecs = 0; + } + + // double for each column + _CryptNistPointDouble(pState, pResult, pResult); + + // check the bit set in the column + bLHBitSet = CryptBnBitTest(pMul1, pState->iKeyIndex); + bRHBitSet = CryptBnBitTest(pMul2, pState->iKeyIndex); + + // if only left hand, then add by that side + if ((bLHBitSet == TRUE) && (bRHBitSet == FALSE)) + { + _CryptNistPointAddition(pState, pResult, pInput1, pResult); + } + // if only right hand, then add by that side + else if ((bLHBitSet == FALSE) && (bRHBitSet == TRUE)) + { + _CryptNistPointAddition(pState, pResult, pInput2, pResult); + } + // if both sides, then add by the sum of both + else if ((bLHBitSet == TRUE) && (bRHBitSet == TRUE)) + { + _CryptNistPointAddition(pState, pResult, &pState->pTable[0], pResult); + } + + // update profiler information + pState->uCryptUSecs += NetTickDiff(NetTickUsec(), uTick); + } + while ((--pState->iKeyIndex >= 0) && (--iIter > 0)); + + // cleanup table memory + if (pState->iKeyIndex < 0) + { + DirtyMemFree(pState->pTable, CRYPTNIST_MEMID, pState->iMemGroup, pState->pMemGroupUserdata); + pState->pTable = NULL; + } + + return(pState->iKeyIndex >= 0); +} + +/*F********************************************************************************/ +/*! + \Function _CryptNistRand + + \Description + Generates a random number in the range [1, curve.order-1] + + \Input *pState - curve state + \Input *pResult - [out] random number generated + + \Version 05/15/2017 (eesponda) +*/ +/********************************************************************************F*/ +static void _CryptNistRand(const CryptNistT *pState, CryptBnT *pResult) +{ + uint8_t aSecret[48]; + CryptBnT Temp; + + // generate a secret to the specified size + CryptRandGet(aSecret, sizeof(aSecret)); + CryptBnInitFrom(pResult, -1, aSecret, DS_MIN((int32_t)sizeof(aSecret), pState->iSize)); + + // clone order so we don't modify + CryptBnClone(&Temp, &pState->Order); + + // random number = (rand() % order-1 + 1) + CryptBnDecrement(&Temp); + CryptBnMod(pResult, &Temp, pResult, NULL); + CryptBnIncrement(pResult); +} + +/*F********************************************************************************/ +/*! + \Function _CryptNistDsaInitHash + + \Description + Initializes the hash, truncating if necessary + + \Input *pState - curve state + \Input *pHashData - the buffer with the hash digest + \Input iHashSize - size of the buffer + \Input *pHash - [out] big number representation of the hash + + \Notes + Based on the algorithm for ECDSA, the hash needs to be truncated to the + bit length of curve.order which can be done by a right shift. The problem + is that other implementations don't follow this and just truncate whole + bytes first. For this reason that is what we do here. + + \Version 02/13/2018 (eesponda) +*/ +/********************************************************************************F*/ +static void _CryptNistDsaInitHash(const CryptNistT *pState, const uint8_t *pHashData, int32_t iHashSize, CryptBnT *pHash) +{ + int32_t iOrderSize = CryptBnBitLen(&pState->Order); + + if ((iHashSize*8) > iOrderSize) + { + iHashSize = (iOrderSize + 7) / 8; + } + CryptBnInitFrom(pHash, -1, pHashData, iHashSize); +} + +/*F********************************************************************************/ +/*! + \Function _CryptNistInit + + \Description + Initializes the curve state + + \Input *pState - curve state + \Input *pCurve - curve data to initialize the state + + \Output + int32_t - 0=success, negative=failure + + \Version 02/17/2017 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CryptNistInit(CryptNistT *pState, const NistCurveT *pCurve) +{ + // initialize the curve parameters + pState->iSize = pCurve->iSize; + CryptBnInitFrom(&pState->Prime, -1, pCurve->aPrime, pCurve->iSize); + CryptBnInitFrom(&pState->CoefficientA, -1, pCurve->aCoefficientA, pCurve->iSize); + CryptBnInitFrom(&pState->CoefficientB, -1, pCurve->aCoefficientB, pCurve->iSize); + CryptBnInitFrom(&pState->Order, -1, pCurve->aOrder, pCurve->iSize); + if (CryptNistPointInitFrom(&pState->BasePoint, pCurve->aBasePoint, pCurve->iSize * 2 + 1) < 0) + { + return(-1); + } + + // initialize the computation state + pState->iKeyIndex = -1; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _CryptNistDsaInitK + + \Description + Generates a K value for signing operations deterministically based on + the private key, message hash and curve + + \Input *pState - curve state + \Input *pPrivateKey - private key information + \Input *pHash - hash of the message we are signing + \Input iHashSize - size of the hash + \Input *pResult - [out] K value for signing + + \Notes + This work is based on RFC6979: https://tools.ietf.org/html/rfc6979 + It allows for selecting a K value without the use of random data. + Aside from this generating benefit the ability to generate deterministic + signatures are required for other cryptographic operations. + + \Version 01/08/2018 (eesponda) +*/ +/********************************************************************************F*/ +static void _CryptNistDsaInitK(const CryptNistT *pState, const CryptBnT *pPrivateKey, const uint8_t *pHash, int32_t iHashSize, CryptBnT *pResult) +{ + static const uint8_t _aZero[] = { 0x00 }, _aOne[] = { 0x01 }; + + CryptHashTypeE eHashType; + uint8_t aNewHash[CRYPTHASH_MAXDIGEST], aK[CRYPTHASH_MAXDIGEST], aV[CRYPTHASH_MAXDIGEST], aT[CRYPTHASH_MAXDIGEST], aPrivateKey[48]; + CryptHmacMsgT aMessages[4]; + int32_t iHashOffset = iHashSize < pState->iSize ? pState->iSize - iHashSize : 0; + + // figure out the hash based on the type + if ((eHashType = CryptHashGetBySize(iHashSize)) == CRYPTHASH_NULL) + { + NetPrintf(("cryptnist: cannot generate deterministic k, unsupported hash used (size=%d)\n", iHashSize)); + return; + } + + // copy the hash to our buffer, this ensures correct format + ds_memclr(aNewHash, sizeof(aNewHash)); + ds_memcpy_s(aNewHash+iHashOffset, sizeof(aNewHash)-iHashOffset, pHash, iHashSize); + + // init k, v, private key + ds_memclr(aK, sizeof(aK)); + ds_memset(aV, 0x01, sizeof(aV)); + CryptBnFinal(pPrivateKey, aPrivateKey, pState->iSize); + + // K = HMAC(V || 0x00 || PrivateKey || H(m)) + aMessages[0].pMessage = aV; + aMessages[0].iMessageLen = iHashSize; + aMessages[1].pMessage = _aZero; + aMessages[1].iMessageLen = sizeof(_aZero); + aMessages[2].pMessage = aPrivateKey; + aMessages[2].iMessageLen = pState->iSize; + aMessages[3].pMessage = aNewHash; + aMessages[3].iMessageLen = pState->iSize; + CryptHmacCalcMulti(aK, iHashSize, aMessages, 4, aK, iHashSize, eHashType); + + // V = HMAC(V) + CryptHmacCalc(aV, iHashSize, aV, iHashSize, aK, iHashSize, eHashType); + + // K = HMAC(V || 0x01 || PrivateKey || H(m)) + aMessages[0].pMessage = aV; + aMessages[0].iMessageLen = iHashSize; + aMessages[1].pMessage = _aOne; + aMessages[1].iMessageLen = sizeof(_aOne); + aMessages[2].pMessage = aPrivateKey; + aMessages[2].iMessageLen = pState->iSize; + aMessages[3].pMessage = aNewHash; + aMessages[3].iMessageLen = pState->iSize; + CryptHmacCalcMulti(aK, iHashSize, aMessages, 4, aK, iHashSize, eHashType); + + // V = HMAC(V) + CryptHmacCalc(aV, iHashSize, aV, iHashSize, aK, iHashSize, eHashType); + + // generate T for new K candidate + while (1) + { + int32_t iOffset = 0, iBytes; + + while (iOffset < pState->iSize) + { + // V = HMAC(V) + CryptHmacCalc(aV, iHashSize, aV, iHashSize, aK, iHashSize, eHashType); + iBytes = DS_MIN(iHashSize, pState->iSize - iOffset); + ds_memcpy(aT+iOffset, aV, iBytes); + iOffset += iBytes; + } + + // copy result and check if suitable + CryptBnInitFrom(pResult, -1, aT, pState->iSize); + if (CryptBnCompare(pResult, &pState->Order) < 0) + { + break; + } + + // K = HMAC(V || 0x00) + aMessages[0].pMessage = aV; + aMessages[0].iMessageLen = iHashSize; + aMessages[1].pMessage = _aZero; + aMessages[1].iMessageLen = sizeof(_aZero); + CryptHmacCalcMulti(aK, iHashSize, aMessages, 2, aK, iHashSize, eHashType); + + // V = HMAC(V) + CryptHmacCalc(aV, iHashSize, aV, iHashSize, aK, iHashSize, eHashType); + } +} + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CryptNistInitDh + + \Description + Initializes the curve state for performing diffie hellmen key exchange + + \Input *pState - curve state + \Input iCurveType - identifier for the curve we are using + + \Output + int32_t - 0=success, negative=failure + + \Version 02/17/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptNistInitDh(CryptNistDhT *pState, int32_t iCurveType) +{ + int32_t iResult, iCurveIdx; + const NistCurveT *pCurve = NULL; + + ds_memclr(pState, sizeof(*pState)); + + // find the curve + for (iCurveIdx = 0; iCurveIdx < (signed)(sizeof(_aEccCurves)/sizeof(_aEccCurves[0])); iCurveIdx += 1) + { + if (iCurveType == _aEccCurves[iCurveIdx].iIdent) + { + pCurve = &_aEccCurves[iCurveIdx]; + break; + } + } + // check to make sure we found a valid curve + if (pCurve == NULL) + { + NetPrintf(("cryptnist: could not find matching curve information for dh given ident %d\n", iCurveType)); + return(-1); + } + + // initialize the curve and computation data + if ((iResult = _CryptNistInit(&pState->Ecc, pCurve)) == 0) + { + // generate random private key + _CryptNistRand(&pState->Ecc, &pState->PrivateKey); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function CryptNistInitDsa + + \Description + Initializes the curve state for performing dsa sign and verify + + \Input *pState - curve state + \Input iCurveType - identifier for the curve we are using + + \Output + int32_t - 0=success, negative=failure + + \Version 04/12/2018 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptNistInitDsa(CryptNistDsaT *pState, int32_t iCurveType) +{ + int32_t iCurveIdx; + const NistCurveT *pCurve = NULL; + + ds_memclr(pState, sizeof(*pState)); + + // find the curve + for (iCurveIdx = 0; iCurveIdx < (signed)(sizeof(_aEccCurves)/sizeof(_aEccCurves[0])); iCurveIdx += 1) + { + if (iCurveType == _aEccCurves[iCurveIdx].iIdent) + { + pCurve = &_aEccCurves[iCurveIdx]; + break; + } + } + // check to make sure we found a valid curve + if (pCurve == NULL) + { + NetPrintf(("cryptnist: could not find matching curve information for dh given ident %d\n", iCurveType)); + return(-1); + } + + // initialize the curve and computation data + return(_CryptNistInit(&pState->Ecc, pCurve)); +} + +/*F********************************************************************************/ +/*! + \Function CryptNistPublic + + \Description + Generates a public key + + \Input *pState - curve state + \Input *pResult - [out] point on the curve that represents the public key (optional) + \Input *pCryptUsecs - [out] timing information for the operation (optional) + + \Output + int32_t - zero=success, otherwise=pending + + \Notes + If pResult is NULL, the result can be taken from CryptNistT.Result + + \Version 02/17/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptNistPublic(CryptNistDhT *pState, CryptEccPointT *pResult, uint32_t *pCryptUsecs) +{ + int32_t iResult; + + if ((iResult = _CryptNistDoubleAndAdd(&pState->Ecc, &pState->Ecc.BasePoint, &pState->PrivateKey, &pState->Result)) == 0) + { + // copy the timing if passed in + if (pCryptUsecs != NULL) + { + *pCryptUsecs = pState->Ecc.uCryptUSecs; + } + /* copy the final result if a valid destination is provided (the caller can copy the result + later if desired) */ + if (pResult != NULL) + { + ds_memcpy(pResult, &pState->Result, sizeof(*pResult)); + } + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function CryptNistSecret + + \Description + Generates a shared secret + + \Input *pState - curve state + \Input *pPublicKey - point as a basis to compute the shared secret + \Input *pResult - [out] point on the curve that represents the shared secret (optional) + \Input *pCryptUsecs - [out] timing information for the operation (optional) + + \Output + int32_t - zero=success, otherwise=pending + + \Notes + If pResult is NULL, the result can be taken from CryptNistT.Result + + \Version 02/17/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptNistSecret(CryptNistDhT *pState, CryptEccPointT *pPublicKey, CryptEccPointT *pResult, uint32_t *pCryptUsecs) +{ + int32_t iResult; + + if ((iResult = _CryptNistDoubleAndAdd(&pState->Ecc, pPublicKey, &pState->PrivateKey, &pState->Result)) == 0) + { + // copy the timing if passed in + if (pCryptUsecs != NULL) + { + *pCryptUsecs = pState->Ecc.uCryptUSecs; + } + /* copy the final result if a valid destination is provided (the caller can copy the result + later if desired) */ + if (pResult != NULL) + { + ds_memcpy(pResult, &pState->Result, sizeof(*pResult)); + } + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function CryptNistSign + + \Description + Generates an elliptic curve dsa signature + + \Input *pState - curve state + \Input *pPrivateKey - key used to sign + \Input *pHash - hash we are creating a signature for + \Input iHashSize - size of the hash + \Input *pSignature - [out] point on the curve that represents our signature (optional) + \Input *pCryptUsecs - [out] timing information for the operation (optional) + + \Output + int32_t - zero=success, otherwise=pending + + \Notes + If pSignature is NULL, the result can be taken from CryptNistT.Result + + \Version 05/15/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptNistSign(CryptNistDsaT *pState, const CryptBnT *pPrivateKey, const uint8_t *pHash, int32_t iHashSize, CryptEccPointT *pSignature, uint32_t *pCryptUsecs) +{ + int32_t iResult; + CryptNistT *pEcc = &pState->Ecc; + + // generate a secret to the specified size + if (CryptBnBitLen(&pState->K) == 0) + { + _CryptNistDsaInitK(pEcc, pPrivateKey, pHash, iHashSize, &pState->K); + } + + // calculate x,y using k * basepoint + if ((iResult = _CryptNistDoubleAndAdd(pEcc, &pEcc->BasePoint, &pState->K, &pState->Result)) == 0) + { + const uint64_t uTickUsecs = NetTickUsec(); + CryptBnT Hash; + + // r = x % order + CryptBnMod(&pState->Result.X, &pEcc->Order, &pState->Result.X, NULL); + + // s1 = ((hash + r * private) + CryptBnInitFrom(&Hash, -1, pHash, iHashSize); + + // truncate the hash if necessary + _CryptNistDsaInitHash(pEcc, pHash, iHashSize, &Hash); + + CryptBnMultiply(&pState->Result.Y, &pState->Result.X, pPrivateKey); + CryptBnAccumulate(&pState->Result.Y, &Hash); + + // s2 = inverse_mod(k, order) + CryptBnInverseMod(&pState->K, &pEcc->Order); + + // s = s1 * s2 % order + CryptBnModMultiply(&pState->Result.Y, &pState->Result.Y, &pState->K, &pEcc->Order); + + /* copy the final result if a valid destination is provided (the caller can copy the result + later if desired) */ + if (pSignature != NULL) + { + ds_memcpy(pSignature, &pState->Result, sizeof(*pSignature)); + } + + // update profiler information + pEcc->uCryptUSecs += NetTickDiff(NetTickUsec(), uTickUsecs); + + // copy the timing if passed in + if (pCryptUsecs != NULL) + { + *pCryptUsecs = pEcc->uCryptUSecs; + } + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function CryptNistVerify + + \Description + Verify the elliptic curve dsa signature + + \Input *pState - curve state + \Input *pPublicKey - the public used for verifying the signature + \Input *pHash - hash we are verifying a signature for + \Input iHashSize - size of the hash + \Input *pSignature - point on the curve that represents our signature + \Input *pCryptUsecs - [out] timing information for the operation (optional) + + \Output + int32_t - zero=success, >0=pending, <0=failure + + \Version 05/15/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptNistVerify(CryptNistDsaT *pState, CryptEccPointT *pPublicKey, const uint8_t *pHash, int32_t iHashSize, CryptEccPointT *pSignature, uint32_t *pCryptUsecs) +{ + int32_t iResult; + CryptNistT *pEcc = &pState->Ecc; + + // initialize state if necessary + if (pEcc->iKeyIndex < 0) + { + CryptBnT S, Hash; + + // truncate the hash if necessary + _CryptNistDsaInitHash(pEcc, pHash, iHashSize, &Hash); + + // s' = inverse_mod(signature.s, order); + CryptBnClone(&S, &pSignature->Y); + CryptBnInverseMod(&S, &pEcc->Order); + + // u1 = s' * hash % order + CryptBnModMultiply(&pState->U1, &Hash, &S, &pEcc->Order); + // u2 = s' * signature.r % order + CryptBnModMultiply(&pState->U2, &pSignature->X, &S, &pEcc->Order); + } + + // result = (u1 * basepoint) + (u2 * publickey) + if ((iResult = _CryptNistDoubleMultiply(pEcc, &pEcc->BasePoint, &pState->U1, pPublicKey, &pState->U2, &pState->Result)) == 0) + { + // copy the timing if passed in + if (pCryptUsecs != NULL) + { + *pCryptUsecs = pEcc->uCryptUSecs; + } + // signature valid if result.x == signature.x + iResult = (memcmp(&pSignature->X, &pState->Result.X, sizeof(pSignature->X)) == 0) ? 0 : -1; + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function CryptNistPointValidate + + \Description + Validate that the point is on the curve + + \Input *pState - curve state + \Input *pPoint - point we are validating + + \Output + uint8_t - TRUE=on the curve, FALSE=not on the curve + + \Version 02/17/2017 (eesponda) +*/ +/********************************************************************************F*/ +uint8_t CryptNistPointValidate(const CryptNistDhT *pState, const CryptEccPointT *pPoint) +{ + CryptBnT Result, X, Y, A; + const CryptNistT *pEcc = &pState->Ecc; + + // a' = curve.a * x + CryptBnMultiply(&A, &pEcc->CoefficientA, &pPoint->X); + + // y' = y * y + CryptBnMultiply(&Y, &pPoint->Y, &pPoint->Y); + + // x' = x * x * x + CryptBnMultiply(&X, &pPoint->X, &pPoint->X); + CryptBnMultiply(&X, &X, &pPoint->X); + + // result = y' - x' - a' - curve.b + CryptBnSubtract(&Result, &Y, &X); + CryptBnSubtract(&Result, &Result, &A); + CryptBnSubtract(&Result, &Result, &pEcc->CoefficientB); + + CryptBnMod(&Result, &pEcc->Prime, &Result, NULL); + return(CryptBnBitLen(&Result) == 0); +} + +/*F********************************************************************************/ +/*! + \Function CryptNistPointInitFrom + + \Description + Loads a point from a buffer + + \Input *pPoint - [out] the point we are writing the information to + \Input *pBuffer - buffer we are reading the point data from + \Input iBufLen - size of the buffer + + \Output + int32_t - 0=success, negative=failure + + \Notes + We only support reading uncompressed points + + \Version 02/17/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptNistPointInitFrom(CryptEccPointT *pPoint, const uint8_t *pBuffer, int32_t iBufLen) +{ + int32_t iPointLen = (iBufLen-1)/2; + + // we only support uncompressed points, so ensure that this is the case + if (*pBuffer++ != CRYPTNIST_UNCOMPRESSED_MARKER) + { + NetPrintf(("cryptnist: could not initialize curve, base point in wrong format\n")); + return(-1); + } + CryptBnInitFrom(&pPoint->X, -1, pBuffer, iPointLen); + CryptBnInitFrom(&pPoint->Y, -1, pBuffer+iPointLen, iPointLen); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function CryptNistPointFinal + + \Description + Loads a point into a buffer for dh + + \Input *pState - curve state + \Input *pPoint - point state or NULL to use curve result + \Input bSecret - is this the shared secret? (only need x) + \Input *pBuffer - buffer we are writing the point data to + \Input iBufLen - size of the buffer + + \Output + int32_t - 0=success, negative=failure + + \Notes + We only support writing uncompressed points + + \Version 02/17/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptNistPointFinal(const CryptNistDhT *pState, const CryptEccPointT *pPoint, uint8_t bSecret, uint8_t *pBuffer, int32_t iBufLen) +{ + if ((pState != NULL) && (pPoint == NULL)) + { + pPoint = &pState->Result; + } + const int32_t iXSize = CryptBnByteLen(&pPoint->X); + const int32_t iYSize = CryptBnByteLen(&pPoint->Y); + const int32_t iOutputSize = !bSecret ? (iXSize + iYSize + 1) : iXSize; + + if (iBufLen < iOutputSize) + { + NetPrintf(("cryptnist: not enough space in output buffer to encode points\n")); + return(-1); + } + + if (!bSecret) + { + *pBuffer++ = CRYPTNIST_UNCOMPRESSED_MARKER; + CryptBnFinal(&pPoint->X, pBuffer, iXSize); + CryptBnFinal(&pPoint->Y, pBuffer+iXSize, iYSize); + } + else + { + CryptBnFinal(&pPoint->X, pBuffer, iXSize); + } + return(iOutputSize); +} diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptrand.c b/src/thirdparty/dirtysdk/source/crypt/cryptrand.c new file mode 100644 index 00000000..207ae14d --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptrand.c @@ -0,0 +1,217 @@ +/*H********************************************************************************/ +/*! + \File cryptrand.c + + \Description + Cryptographic random number generator using system-defined rng and chacha + cipher. + + \Notes + Native implementations used on: Android, Apple OSX, Apple iOS, Linux, PS4, Windows + See the platform specific cryptrand source files for more details. + + References: http://tools.ietf.org/html/rfc4086 + http://randomnumber.org/links.htm + + \Copyright + Copyright (c) 2012-2020 Electronic Arts Inc. +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "cryptrandpriv.h" + +#if defined(CRYPTRAND_LINUX) +#include +#include +#include +#elif defined(CRYPTRAND_APPLE) +#include +#include +#elif defined(CRYPTRAND_WINDOWS) +#define WIN32_LEAN_AND_MEAN 1 +#include +#include +#elif defined(CRYPTRAND_SONY) +#include +#endif + +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/crypt/cryptrand.h" +#include "cryptrandcommon.h" + +/*** Type Definitions *************************************************************/ + +//! internal rand state +typedef struct CryptRandRefT +{ + CryptRandCommonT Common; //!< common state + int32_t iMemGroup; //!< memgroup id + void *pMemGroupUserData; //!< user data associated with memgroup + + #if defined(CRYPTRAND_LINUX) + int32_t iFd; //!< urandom file descriptor + #elif defined(CRYPTRAND_WINDOWS) + BCRYPT_ALG_HANDLE hAlgProvider; + #endif +} CryptRandRefT; + +/*** Variables ********************************************************************/ + +//! global rand state +static CryptRandRefT *_CryptRand_pState = NULL; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _CryptRandGetEntropy + + \Description + Get random data from the entropy pool + + \Input *pBuffer - [out] random data + \Input iBufSize - size of random data + + \Version 01/24/2019 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _CryptRandGetEntropy(uint8_t *pBuffer, int32_t iBufSize) +{ + #if defined(CRYPTRAND_LINUX) + if (read(_CryptRand_pState->iFd, pBuffer, iBufSize) == iBufSize) + { + return(0); + } + NetPrintf(("cryptrand: read(/dev/urandom) failed (err=%s)\n", DirtyErrGetName(errno))); + #elif defined(CRYPTRAND_APPLE) + int32_t iResult; + if ((iResult = SecRandomCopyBytes(kSecRandomDefault, iBufSize, pBuffer)) >= 0) + { + return(0); + } + NetPrintf(("cryptrand: SecRandomCopyBytes() failed (err=%s)\n", DirtyErrGetName(iResult))); + #elif defined(CRYPTRAND_WINDOWS) + if (BCryptGenRandom(_CryptRand_pState->hAlgProvider, pBuffer, iBufSize, 0) == S_OK) + { + return(0); + } + NetPrintf(("cryptrand: BCryptGetRandom() failed (err=%d)\n", GetLastError())); + #elif defined(CRYPTRAND_SONY) + int32_t iResult; + if ((iResult = sceRandomGetRandomNumber(pBuffer, iBufSize)) == SCE_OK) + { + return(0); + } + NetPrintf(("cryptrand: sceRandomGetRandomNumber() failed (err=%s)\n", DirtyErrGetName(iResult))); + #endif + return(-1); +} + +/*** Public Functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function CryptRandInit + + \Description + Initialize CryptRand module + + \Version 12/05/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t CryptRandInit(void) +{ + CryptRandRefT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + + // query memgroup info + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate state memory + if ((pState = (CryptRandRefT *)DirtyMemAlloc(sizeof(*pState), CRYPTRAND_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("cryptrand: failed to allocate state\n")); + return(-1); + } + ds_memclr(pState, sizeof(*pState)); + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + pState->Common.GetEntropy = &_CryptRandGetEntropy; + + #if defined(CRYPTRAND_LINUX) + // open urandom + if ((pState->iFd = open("/dev/urandom", O_RDONLY)) < 0) + { + DirtyMemFree(pState, CRYPTRAND_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + return(-1); + } + #elif defined(CRYPTRAND_WINDOWS) + // open algorithm provider + if (BCryptOpenAlgorithmProvider(&pState->hAlgProvider, BCRYPT_RNG_ALGORITHM, NULL, 0) != S_OK) + { + DirtyMemFree(pState, CRYPTRAND_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + return(-1); + } + #endif + _CryptRand_pState = pState; + + // init the crypt rand state after the proper setup has been done + if (CryptRandCommonInit(&pState->Common) < 0) + { + CryptRandShutdown(); + return(-1); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function CryptRandShutdown + + \Description + Shut down the CryptRand module + + \Version 12/05/2012 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptRandShutdown(void) +{ + CryptRandRefT *pState = _CryptRand_pState; + + #if defined(CRYPTRAND_LINUX) + // close file descriptor + close(pState->iFd); + pState->iFd = -1; + #elif defined(CRYPTRAND_WINDOWS) + // close algorithm provider + BCryptCloseAlgorithmProvider(pState->hAlgProvider, 0); + pState->hAlgProvider = 0; + #endif + + // free memory and reset state pointer + DirtyMemFree(pState, CRYPTRAND_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + _CryptRand_pState = NULL; +} + +/*F********************************************************************************/ +/*! + \Function CryptRandGet + + \Description + Get random data + + \Input *pBuffer - [out] random data + \Input iBufSize - size of random data + + \Version 12/05/2012 (jbrookes) +*/ +/********************************************************************************F*/ +void CryptRandGet(uint8_t *pBuffer, int32_t iBufSize) +{ + CryptRandCommonGet(&_CryptRand_pState->Common, pBuffer, iBufSize); +} diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptrandcommon.c b/src/thirdparty/dirtysdk/source/crypt/cryptrandcommon.c new file mode 100644 index 00000000..e51a97c0 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptrandcommon.c @@ -0,0 +1,116 @@ +/*H********************************************************************************/ +/*! + \File cryptrandcommon.c + + \Description + Cryptographic random number generator using system-defined rng and chacha + cipher. + + \Notes + Native implementations used on: Android, Apple OSX, Apple iOS, Linux, PS4, Windows + See the platform specific cryptrand source files for more details. + + References: http://tools.ietf.org/html/rfc4086 + http://randomnumber.org/links.htm + + \Copyright + Copyright (c) 2012-2019 Electronic Arts Inc. + + \Version 12/05/2012 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/dirtysock/dirtylib.h" +#include "cryptrandcommon.h" + +/*** Defines **********************************************************************/ + +//! key rotation interval in bytes +#define CRYPTRAND_KEY_INTERVAL (4*1024) + +/*** Public Functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function CryptRandCommonInit + + \Description + Initialize CryptRand module's common state + + \Input *pCommon - common state + + \Output + int32_t - result of initialization (0=success, negative=failure) + + \Version 01/24/2019 (eesponda) +*/ +/********************************************************************************F*/ +int32_t CryptRandCommonInit(CryptRandCommonT *pCommon) +{ + uint8_t aKey[32]; + + // pull key from entropy source + if (pCommon->GetEntropy(aKey, sizeof(aKey)) != 0) + { + NetPrintf(("cryptrand: unable to pull bytes from entropy for cipher key\n")); + return(-1); + } + + // initialize the stream cipher and initialize bytes left + CryptChaChaInit(&pCommon->Cipher, aKey, sizeof(aKey)); + pCommon->iBytesLeft = CRYPTRAND_KEY_INTERVAL; + + // clear key from stack memory + ds_memclr(aKey, sizeof(aKey)); + + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function CryptRandCommonGet + + \Description + Get random data + + \Input *pCommon - common state + \Input *pBuffer - [out] random bytes + \Input iBufSize - size of the random bytes we are requesting + + \Version 01/24/2019 (eesponda) +*/ +/********************************************************************************F*/ +void CryptRandCommonGet(CryptRandCommonT *pCommon, uint8_t *pBuffer, int32_t iBufSize) +{ + uint8_t aNonce[12]; + uint8_t bGetEntropySuccess = TRUE; + + // check if we need to rotate the key (which pulls from entropy) + if (pCommon->iBytesLeft == 0) + { + bGetEntropySuccess &= (CryptRandCommonInit(pCommon) == 0); + } + + // get nonce from entropy source + bGetEntropySuccess &= (pCommon->GetEntropy(aNonce, sizeof(aNonce)) == 0); + + // abort if getting entropy failed + if (bGetEntropySuccess == FALSE) + { + /* in the case that getting bytes from the entropy pool fails, we have no other alternative + other than to abort. there is no secure fallback we could use and most notable software + prng will abort in this case. + in an attempt to prevent this case at all costs we make sure that this operation succeeds + in the initialize, otherwise the initialization fails which prevents the startup of + netconn. it is better that we fail then sacrifice the security guarentees of our software. */ + NetPrintf(("cryptrand: unable to pull bytes from entropy pool for encrypt; aborting\n")); + *(volatile uint8_t*)(0) = 0; + } + + // encrypt bytes to get our pseudo-random data + CryptChaChaEncrypt(&pCommon->Cipher, pBuffer, iBufSize, aNonce, sizeof(aNonce), NULL, 0, NULL, 0); + pCommon->iBytesLeft = (iBufSize >= pCommon->iBytesLeft) ? 0 : pCommon->iBytesLeft - iBufSize; +} diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptrandcommon.h b/src/thirdparty/dirtysdk/source/crypt/cryptrandcommon.h new file mode 100644 index 00000000..e2107702 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptrandcommon.h @@ -0,0 +1,57 @@ +/*H********************************************************************************/ +/*! + \File cryptrandcommon.h + + \Description + Common APIs used for internally in the CryptRand module + + \Copyright + Copyright (c) 2019 Electronic Arts Inc. + + \Version 01/24/2019 (eesponda) +*/ +/********************************************************************************H*/ + +#ifndef _cryptrandcommon_h +#define _cryptrandcommon_h + +/*! +\Moduledef CryptRandCommon CryptRandCommon +\Modulemember Crypt +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptchacha.h" + +/*** Type Definitions *************************************************************/ + +//! common state used for random number generation +typedef struct CryptRandCommonT +{ + CryptChaChaT Cipher; //!< cipher used to generate random numbers + int32_t iBytesLeft; //!< bytes remaining before key rotation + int32_t (*GetEntropy)(uint8_t *pBuffer, int32_t iBufSize); //!< pointer to internal function to get entropy +} CryptRandCommonT; + +/*** Functions ********************************************************************/ + +#if defined(__cplusplus) +extern "C" { +#endif + +// initialize the common state +int32_t CryptRandCommonInit(CryptRandCommonT *pCommon); + +// generate a psuedo-random number +void CryptRandCommonGet(CryptRandCommonT *pCommon, uint8_t *pBuffer, int32_t iBufSize); + +#if defined(__cplusplus) +} +#endif + +//@} + +#endif // _cryptrandcommon_h diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptrandpriv.h b/src/thirdparty/dirtysdk/source/crypt/cryptrandpriv.h new file mode 100644 index 00000000..4d1d6677 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptrandpriv.h @@ -0,0 +1,61 @@ +/*H********************************************************************************/ +/*! + \File cryptrandpriv.h + + \Description + Internal APIs for the CryptRand module + + \Copyright + Copyright (c) 2020 Electronic Arts Inc. + + \Version 02/04/2020 (eesponda) +*/ +/********************************************************************************H*/ + +#ifndef _cryptrandpriv_h +#define _cryptrandpriv_h + +/*! +\Moduledef CryptRandPriv CryptRandPriv +\Modulemember Crypt +*/ +//@{ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" + +/*** Defines **********************************************************************/ + +// define OS flavors of random +#if defined(DIRTYCODE_LINUX) || defined(DIRTYCODE_ANDROID) +#define CRYPTRAND_LINUX +#elif defined(DIRTYCODE_APPLEOSX) || defined(DIRTYCODE_APPLEIOS) +#define CRYPTRAND_APPLE +#elif defined(DIRTYCODE_PC) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) +#define CRYPTRAND_WINDOWS +#elif defined(DIRTYCODE_PS4) || defined(DIRTYCODE_PS5) +#define CRYPTRAND_SONY +#elif defined(DIRTYCODE_NX) +#define CRYPTRAND_NX +#endif + +/*** Functions ********************************************************************/ + +#if defined(__cplusplus) +extern "C" { +#endif + +// initialize the module +int32_t CryptRandInit(void); + +// destroy the module +void CryptRandShutdown(void); + +#if defined(__cplusplus) +} +#endif + +//@} + +#endif // _cryptrandpriv_h diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptrsa.c b/src/thirdparty/dirtysdk/source/crypt/cryptrsa.c new file mode 100644 index 00000000..90494d62 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptrsa.c @@ -0,0 +1,556 @@ +/*H********************************************************************************/ +/*! + \File cryptrsa.c + + \Description + This module is a from-scratch RSA implementation in order to avoid + any intellectual property issues. The 1024 bit RSA public key + encryption algorithm was implemented from a specification provided + by Netscape for SSL implementation (see protossl.h). + + \Copyright + Copyright (c) Electronic Arts 2002-2011. + + \Todo + 64bit word support (UCRYPT_SIZE=8) has been tested to work with the unix64-gcc + (Linux) and ps3-gcc (PS3) compilers; however the current implementation does + not work with certain odd modulus sizes (for example the 1000-bit modulus of + the built-in RSA CA certificate), so it is not currently enabled. + + \Version 1.0 03/08/2002 (gschaefer) First Version (protossl) + \Version 1.1 03/05/2003 (jbrookes) Split RSA encryption from protossl + \Version 1.2 11/16/2011 (jbrookes) Optimizations to improve dbg&opt performance +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include // memset + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/crypt/cryptrand.h" + +#include "DirtySDK/crypt/cryptrsa.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Private variables + + +// Public variables + + +/*** Private functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _CryptRSAExponentiate + + \Description + Do the actual encryption: result = (value ^ exponent) % modulus. + Exponentiation is done using a sliding window, this function needs to + be called iteratively until the function returns zero. + + \Input *pState - crypt state + \Input *pAccumul - [out] where we accumulate our result + \Input *pPowerof - contains our initial value and is used for squaring + \Input *pExponent - the exponent of the equation + \Input *pModulus - the modulus of the equation + + \Output + int32_t - zero=done, else call again + + \Notes + See Handbook of Applied Cryptography Chapter 14.6.1 (14.85): + http://cacr.uwaterloo.ca/hac/about/chap14.pdf + + \Version 05/08/2017 (eesponda) +*/ +/********************************************************************************H*/ +static int32_t _CryptRSAExponentiate(CryptRSAT *pState, CryptBnT *pAccumul, CryptBnT *pPowerof, const CryptBnT *pExponent, const CryptBnT *pModulus) +{ + uint64_t uTickUsecs = NetTickUsec(); + int32_t iResult = 1; + int32_t iMaxExponentBit = CryptBnBitLen(pExponent); + /* use a fixed window size of 6 for larger exponents, + we provide a fallthrough for smaller exponents */ + const int32_t iWindowSize = iMaxExponentBit > 512 ? 6 : 1; + + // if this is the first time, handle our precomputations + if (pState->pTable == NULL) + { + const int32_t iTableSize = 1 << (iWindowSize - 1); + + if (iTableSize == 1) + { + // point to fixed array + pState->pTable = pState->aTable; + } + else + { + DirtyMemGroupQuery(&pState->iMemGroup, &pState->pMemGroupUserData); + + // allocate memory for the table to avoid large data on the stack + if ((pState->pTable = (CryptBnT *)DirtyMemAlloc(sizeof(CryptBnT) * iTableSize, CRYPTRSA_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL) + { + NetPrintf(("cryptrsa: [%p] unable to allocate memory for precomputed table used for sliding window\n", pState)); + return(0); + } + } + ds_memclr(pState->pTable, sizeof(CryptBnT) * iTableSize); + + /* put the already reduced input into the first + index of the table, this will make it so further + entries in the table will not be too large + for our modulus operations */ + CryptBnClone(&pState->pTable[0], pPowerof); + + /* precompute the different powers of input + that we use when we have a window that is + greater than one */ + if (iTableSize > 1) + { + int32_t iTableIndex; + CryptBnT Square; + + // calculate (input^2) % mod as the basis for the other calculations + CryptBnModMultiply(&Square, &pState->pTable[0], &pState->pTable[0], pModulus); + + // calculate (input^(2+iTableIndex)) % mod for the rest of the table + for (iTableIndex = 1; iTableIndex < iTableSize; iTableIndex += 1) + { + CryptBnModMultiply(&pState->pTable[iTableIndex], &pState->pTable[iTableIndex - 1], &Square, pModulus); + } + } + + // set the defaults + pState->iExpBitIndex = iMaxExponentBit - 1; + } + + /* scan backwards from the current exponent bit + until a set bit is found to denote the start of the window + squaring the results until such a bit is found */ + if (CryptBnBitTest(pExponent, pState->iExpBitIndex)) + { + int32_t iWindowBit, iWindowValue, iWindowEnd; + + /* scan backwards from the start of the window until the last set bit in the range of the window size is found which denotes the end bit index of the window + calculate the value of the window that will be used when multiplying against our precomputed table + we skip the first bit as we know it is set based on the current exponent bit check we make right above */ + for (iWindowBit = 1, iWindowValue = 1, iWindowEnd = 0; (iWindowBit < iWindowSize) && ((pState->iExpBitIndex - iWindowBit) >= 0); iWindowBit += 1) + { + if (CryptBnBitTest(pExponent, pState->iExpBitIndex - iWindowBit)) + { + iWindowValue <<= (iWindowBit - iWindowEnd); + iWindowValue |= 1; /* force odd */ + iWindowEnd = iWindowBit; + } + } + + // square for all the bits in the window and moving the exponent bit index down each time + for (iWindowBit = 0; iWindowBit < iWindowEnd + 1; iWindowBit += 1, pState->iExpBitIndex -= 1) + { + CryptBnModMultiply(pAccumul, pAccumul, pAccumul, pModulus); + } + + // use the window value to get which precomputed power we need to multiply skip the first multiply + if (!pState->bAccumulOne) + { + CryptBnModMultiply(pAccumul, pAccumul, &pState->pTable[iWindowValue/2], pModulus); + } + else + { + CryptBnClone(pAccumul, &pState->pTable[iWindowValue/2]); + pState->bAccumulOne = FALSE; + } + } + else + { + CryptBnModMultiply(pAccumul, pAccumul, pAccumul, pModulus); + pState->iExpBitIndex -= 1; + } + + // check if we are done + if (pState->iExpBitIndex < 0) + { + // we are done, update timings and return appropriate result + pState->uCryptMsecs = (pState->uCryptUsecs+500)/1000; + iResult = 0; + + if (pState->pTable != pState->aTable) + { + // clean up the table memory + DirtyMemFree(pState->pTable, CRYPTRSA_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + pState->pTable = NULL; + } + + // update timing + pState->uCryptUsecs += (uint32_t)NetTickDiff(NetTickUsec(), uTickUsecs); + pState->uNumExpCalls += 1; + + return(iResult); +} + + +/*F********************************************************************************/ +/*! + \Function _CryptRSAEncryptPublic + + \Description + Handle the exponentiate calls when using the public key data + + \Input *pState - crypt state + \Input iIter - number of iterations + + \Output + int32_t - zero=done, else call again + + \Version 04/11/2017 (eesponda) +*/ +/********************************************************************************H*/ +static int32_t _CryptRSAEncryptPublic(CryptRSAT *pState, int32_t iIter) +{ + int32_t iCount, iResult; + + for (iCount = 0, iResult = 1; (iCount < iIter) && (iResult > 0); iCount += 1) + { + iResult = _CryptRSAExponentiate(pState, &pState->Working.PublicKey.Accumul, &pState->Working.PublicKey.Powerof, &pState->Working.PublicKey.Exponent, &pState->Working.PublicKey.Modulus); + } + // if we are done, return encrypted data + if (iResult == 0) + { + CryptBnFinal(&pState->Working.PublicKey.Accumul, pState->EncryptBlock, pState->iKeyModSize); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _CryptRSAEncryptPrivate + + \Description + Handle the exponentiate calls when using the private key data which + using chinese remainder theorum + + \Input *pState - crypt state + \Input iIter - number of iterations + + \Output + int32_t - zero=done, else call again + + \Version 04/11/2017 (eesponda) +*/ +/********************************************************************************H*/ +static int32_t _CryptRSAEncryptPrivate(CryptRSAT *pState, int32_t iIter) +{ + int32_t iCount = 0, iResult = 1; + + if (pState->Working.PrivateKey.eState == CRT_COMPUTE_M1) + { + /* compute m1 + m1 = c ^ dP % p */ + for (; (iCount < iIter) && (iResult > 0); iCount += 1) + { + iResult = _CryptRSAExponentiate(pState, &pState->Working.PrivateKey.M1, &pState->Working.PrivateKey.PowerofP, &pState->Working.PrivateKey.ExponentP, &pState->Working.PrivateKey.PrimeP); + } + + if (iResult == 0) + { + // reset the state and move to next part of computation + pState->Working.PrivateKey.eState = CRT_COMPUTE_M2; + pState->bAccumulOne = TRUE; + pState->iExpBitIndex = 0; + iResult = 1; + } + } + if (pState->Working.PrivateKey.eState == CRT_COMPUTE_M2) + { + /* compute m2 + m2 = c ^ dQ % q */ + for (; (iCount < iIter) && (iResult > 0); iCount += 1) + { + iResult = _CryptRSAExponentiate(pState, &pState->Working.PrivateKey.M2, &pState->Working.PrivateKey.PowerofQ, &pState->Working.PrivateKey.ExponentQ, &pState->Working.PrivateKey.PrimeQ); + } + + if (iResult == 0) + { + CryptBnT Result; + + /* compute h + h = qInv * (m1 - m2) % p */ + CryptBnInit(&Result, 1); + CryptBnSubtract(&Result, &pState->Working.PrivateKey.M1, &pState->Working.PrivateKey.M2); + CryptBnModMultiply(&Result, &Result, &pState->Working.PrivateKey.Coeffecient, &pState->Working.PrivateKey.PrimeP); + + /* compute m + m = m2 + h * q */ + CryptBnMultiply(&Result, &Result, &pState->Working.PrivateKey.PrimeQ); + CryptBnAccumulate(&Result, &pState->Working.PrivateKey.M2); + + // return final result + CryptBnFinal(&Result, pState->EncryptBlock, pState->iKeyModSize); + } + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _CryptRSAInitEncrypt + + \Description + Initializes the common encrypt data and data depending on the type + of key + + \Input *pState - crypt state + + \Version 04/11/2017 (eesponda) +*/ +/********************************************************************************H*/ +static void _CryptRSAInitEncrypt(CryptRSAT *pState) +{ + // initialize accumulator to 1 (and remember for first-multiply optimization) + pState->bAccumulOne = TRUE; + + // convert data to encrypt to native word size we will operate in + if (!pState->bPrivate) + { + CryptBnInitFrom(&pState->Working.PublicKey.Powerof, -1, pState->EncryptBlock, pState->iKeyModSize); + } + else + { + /* we need to reduce before every operation to get the cipher + text to less than our modulus in the operations. + our modular multiply in bn doesn't handle the cases where they + are larger */ + CryptBnInitFrom(&pState->Working.PrivateKey.PowerofP, -1, pState->EncryptBlock, pState->iKeyModSize); + CryptBnInitFrom(&pState->Working.PrivateKey.PowerofQ, -1, pState->EncryptBlock, pState->iKeyModSize); + CryptBnMod(&pState->Working.PrivateKey.PowerofP, &pState->Working.PrivateKey.PrimeP, &pState->Working.PrivateKey.PowerofP, NULL); + CryptBnMod(&pState->Working.PrivateKey.PowerofQ, &pState->Working.PrivateKey.PrimeQ, &pState->Working.PrivateKey.PowerofQ, NULL); + } +} + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function CryptRSAInit + + \Description + Init RSA state. + + \Input *pState - module state + \Input *pModulus - crypto modulus + \Input iModSize - size of modulus + \Input *pExponent - crypto exponent + \Input iExpSize - size of exponent + + \Output + int32_t - zero for success, negative for error + + \Notes + This module supports a max modulus size of 4096 (iModSize=512) and requires + a minimum size of 1024 (iModSize=64). The exponent must be between 1 + and 512 bytes in size, inclusive. + + \Version 03/05/03 (jbrookes) Split from protossl +*/ +/********************************************************************************H*/ +int32_t CryptRSAInit(CryptRSAT *pState, const uint8_t *pModulus, int32_t iModSize, const uint8_t *pExponent, int32_t iExpSize) +{ + int32_t iResult = 0; + + // validate modulus size + if ((iModSize < 64) || ((iModSize/UCRYPT_SIZE) > CRYPTBN_MAX_WIDTH)) + { + NetPrintf(("cryptrsa: iModSize of %d is invalid\n", iModSize)); + return(-1); + } + // validate exponent size + if ((iExpSize < 1) || ((iExpSize/UCRYPT_SIZE) > CRYPTBN_MAX_WIDTH)) + { + NetPrintf(("cryptrsa: iExpSize of %d is invalid\n", iExpSize)); + return(-2); + } + + // initialize state + ds_memclr(pState, sizeof(*pState)); + CryptBnInitFrom(&pState->Working.PublicKey.Modulus, -1, pModulus, pState->iKeyModSize = iModSize); + CryptBnInitFrom(&pState->Working.PublicKey.Exponent, -1, pExponent, iExpSize); + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function CryptRSAInit2 + + \Description + Init RSA state for private key operations + + \Input *pState - module state + \Input iModSize - size of the public key modulus + \Input *pPrimeP - factor p of modulus + \Input *pPrimeQ - factor q of modulus + \Input *pExponentP - inverse of exponent mod p-1 + \Input *pExponentQ - inverse of exponent mod q-1 + \Input *pCoeffecient- inverse of q mod p + \Output + int32_t - zero for success, negative for error + + \Version 04/11/2017 (eesponda) +*/ +/********************************************************************************H*/ +int32_t CryptRSAInit2(CryptRSAT *pState, int32_t iModSize, const CryptBinaryObjT *pPrimeP, const CryptBinaryObjT *pPrimeQ, const CryptBinaryObjT *pExponentP, const CryptBinaryObjT *pExponentQ, const CryptBinaryObjT *pCoeffecient) +{ + // initialize state + ds_memclr(pState, sizeof(*pState)); + + // init state + CryptBnInitFrom(&pState->Working.PrivateKey.PrimeP, -1, pPrimeP->pObjData, pPrimeP->iObjSize); + CryptBnInitFrom(&pState->Working.PrivateKey.PrimeQ, -1, pPrimeQ->pObjData, pPrimeQ->iObjSize); + CryptBnInitFrom(&pState->Working.PrivateKey.ExponentP, -1, pExponentP->pObjData, pExponentP->iObjSize); + CryptBnInitFrom(&pState->Working.PrivateKey.ExponentQ, -1, pExponentQ->pObjData, pExponentQ->iObjSize); + CryptBnInitFrom(&pState->Working.PrivateKey.Coeffecient, -1, pCoeffecient->pObjData, pCoeffecient->iObjSize); + pState->iKeyModSize = iModSize; + pState->bPrivate = TRUE; + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function CryptRSAInitMaster + + \Description + Setup the master shared secret for encryption + + \Input *pState - module state + \Input *pMaster - the master shared secret to encrypt + \Input iMasterLen - the length of the master shared secret + + \Version 02/07/2014 (jbrookes) Rewritten to use CryptRand +*/ +/********************************************************************************H*/ +void CryptRSAInitMaster(CryptRSAT *pState, const uint8_t *pMaster, int32_t iMasterLen) +{ + int32_t iIndex; + uint32_t uRandom; + + // fill encrypt block with random data + CryptRandGet(pState->EncryptBlock, pState->iKeyModSize); + /* As per PKCS1 http://www.emc.com/emc-plus/rsa-labs/pkcs/files/h11300-wp-pkcs-1v2-2-rsa-cryptography-standard.pdf section 7.2.1, + the pseudo-random padding octets must be non-zero. Failing to adhere to this restriction (or any other errors in pkcs1 encoding) + will typically result in a bad_record_mac fatal alert from the remote host. The following code gets four random bytes as seed + and uses a simple Knuth RNG to fill in any zero bytes that were originally generated. */ + CryptRandGet((uint8_t *)&uRandom, sizeof(uRandom)); + for (iIndex = 0; iIndex < pState->iKeyModSize; iIndex += 1) + { + while (pState->EncryptBlock[iIndex] == 0) + { + uRandom = (uRandom * 69069) + 69069; + pState->EncryptBlock[iIndex] ^= (uint8_t)uRandom; + } + } + // set PKCS public key signature + pState->EncryptBlock[0] = 0; // zero pad + pState->EncryptBlock[1] = 2; // public key is type 2 + // add data to encrypt block + iIndex = pState->iKeyModSize - iMasterLen; + pState->EncryptBlock[iIndex-1] = 0; // zero byte prior to data + ds_memcpy(pState->EncryptBlock+iIndex, pMaster, iMasterLen); + // handle initializing the state according to the type of key + _CryptRSAInitEncrypt(pState); +} + +/*F********************************************************************************/ +/*! + \Function CryptRSAInitPrivate + + \Description + Setup the master shared secret for encryption using PKCS1.5 + + \Input *pState - module state + \Input *pMaster - the master shared secret to encrypt + \Input iMasterLen - the length of the master shared secret + + \Version 11/03/2013 (jbrookes) +*/ +/********************************************************************************H*/ +void CryptRSAInitPrivate(CryptRSAT *pState, const uint8_t *pMaster, int32_t iMasterLen) +{ + int32_t iIndex; + // put in PKCS1.5 signature + pState->EncryptBlock[0] = 0; + pState->EncryptBlock[1] = 1; + // PKCS1.5 signing pads with 0xff + ds_memset(pState->EncryptBlock+2, 0xff, pState->iKeyModSize-2); + // add data to encrypt block + iIndex = pState->iKeyModSize - iMasterLen; + pState->EncryptBlock[iIndex-1] = 0; // zero byte prior to data + ds_memcpy(pState->EncryptBlock+iIndex, pMaster, iMasterLen); + // handle initializing the state according to the type of key + _CryptRSAInitEncrypt(pState); +} + +/*F********************************************************************************/ +/*! + \Function CryptRSAInitSignature + + \Description + Setup the encrypted signature for decryption. + + \Input *pState - module state + \Input *pSig - the encrypted signature + \Input iSigLen - the length of the encrypted signature. + + \Version 03/03/2004 (sbevan) +*/ +/********************************************************************************H*/ +void CryptRSAInitSignature(CryptRSAT *pState, const uint8_t *pSig, int32_t iSigLen) +{ + ds_memcpy(pState->EncryptBlock, pSig, iSigLen); + // handle initializing the state according to the type of key + _CryptRSAInitEncrypt(pState); +} + +/*F********************************************************************************/ +/*! + \Function CryptRSAEncrypt + + \Description + Encrypt data. + + \Input *pState - module state + \Input iIter - number of iterations to execute (zero=do all) + + \Output + int32_t - zero=operation complete, else call again + + \Version 03/05/2003 (jbrookes) Split from protossl +*/ +/********************************************************************************H*/ +int32_t CryptRSAEncrypt(CryptRSAT *pState, int32_t iIter) +{ + if (iIter == 0) + { + iIter = 0x7fffffff; + } + + return((!pState->bPrivate) ? _CryptRSAEncryptPublic(pState, iIter) : _CryptRSAEncryptPrivate(pState, iIter)); +} + + diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptsha1.c b/src/thirdparty/dirtysdk/source/crypt/cryptsha1.c new file mode 100644 index 00000000..0efd03e7 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptsha1.c @@ -0,0 +1,344 @@ +/*H*******************************************************************/ +/*! + \File cryptsha1.c + + \Description + This module implements SHA1 as defined in RFC 3174. + + \Notes + The implementation is based on the algorithm description in sections + 3 through 6 of RFC 3174 and not on the C code in section 7. + + Currently the code is a straightforward implementation of the + algorithm, no attempt has been made to optimize it in any way. + See the notes for individual functions for descriptions of where + optimizations are possible, but note that what is good for an x86 + with large caches may not be good for a MIPS based PS2 which has + a much smaller instruction cache. + + The code deliberately uses some of the naming conventions from + the RFC to in order to aid comprehension. + + This implementation is limited to hashing no more than 2^32-9 bytes. + It will silently produce the wrong result if an attempt is made to + hash more data. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 06/06/2004 (sbevan) First Version +*/ +/*******************************************************************H*/ + +/*** Include files ***************************************************/ + +#include /* memcpy */ + +#include "DirtySDK/platform.h" +#include "DirtySDK/crypt/cryptsha1.h" + +/*** Defines *********************************************************/ + +// A circular left shift, see RFC 3174 section 3.c +// +// gcc understands the two shift idiom and replaces this +// with single rotate instruction on platforms that have it. +// +#define CRYPTSHA1_rol(n, x) (((x)<<(n))|((x)>>(32-(n)))) + +/*** Type Definitions ************************************************/ + +/*** Variables *******************************************************/ + +/*** Private functions ***********************************************/ + +/*F*******************************************************************/ +/*! + \Function _CryptSha1CopyHash + + \Description + Extract the SHA1 hash and copy it to a byte buffer. + + \Input *pSha1 - SHA1 state + \Input *pBuffer - where to store the hash + \Input uLength - how many bytes of the hash to extract + + \Output None + + \Version 1.0 06/06/2004 (sbevan) First Version +*/ +/*******************************************************************F*/ +static void _CryptSha1CopyHash(CryptSha1T *pSha1, void *pBuffer, uint32_t uLength) +{ + unsigned char *pOutput = pBuffer; + uint32_t i; + + if (uLength > CRYPTSHA1_HASHSIZE) + { + uLength = CRYPTSHA1_HASHSIZE; + } + for (i = 0; i != uLength; i += 1) + { + pOutput[i] = pSha1->H[(i/4)]>>((3-(i%4))*8); + } +} + +/*F*******************************************************************/ +/*! + \Function _CryptSha1ProcessBlock + + \Description + SHA1 a 64-byte block of data. + + \Input *pSha1 - SHA1 state + \Input *M - start of 64-bytes to be processed + + \Output None + + \Notes + This is a literal translation of Method 1 as described in + RFC 3174 section 6.1. The variable names deliberately match those + used in that section in order to aid manual verification of algorithm + correctness. + + There is a lot of scope for performance improvements. For example :- + + a) performing manual loop unrolling and loop fusion. + An the extreme this could result in a single straight line + chunk of code that on a register rich machine this could + result in everything except the input and hash living in + a register. + + b) taking advantage of hardware that can perform big-endian (MIPS) + and/or mis-aligned (x86) loads. + + OpenSSL does a) but it causes problems for some compilers since + they don't do a good job of optimizing functions containing 80+ + local variables. GCC should be able to cope but the x86 and + MIPS assembly would need to be checked to make sure. + + \Version 1.0 06/06/2004 (sbevan) First Version +*/ +/*******************************************************************F*/ +static void _CryptSha1ProcessBlock(CryptSha1T *pSha1, const unsigned char *M) +{ + uint32_t i; + uint32_t t; + unsigned A, B, C, D, E; + uint32_t W[80]; + + // RFC 3174 section 6.1.a, divide input into 16 words (big-endian format) + for (i = 0; i != 16; i += 1) + { + W[i] = (M[i*4]<<24)|(M[i*4+1]<<16)|(M[i*4+2]<<8)|M[i*4+3]; + } + // RFC 3174 section 6.1.b + for (t = 16; t != 80; t += 1) + { + W[t] = CRYPTSHA1_rol(1, W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); + } + // RFC 3174 section 6.1.c + A = pSha1->H[0]; + B = pSha1->H[1]; + C = pSha1->H[2]; + D = pSha1->H[3]; + E = pSha1->H[4]; + + // RFC 3174 section 6.1.d split into 4 groups one for each variation of + // f as defined in RFC 3174 section 5. + for (t = 0; t != 20; t += 1) + { + uint32_t TEMP = CRYPTSHA1_rol(5, A) + ((B&C)|((~B)&D)) + E + W[t] + 0x5A827999; + E = D; D = C; C = CRYPTSHA1_rol(30, B); B = A; A = TEMP; + } + + for (t = 20; t != 40; t += 1) + { + uint32_t TEMP = CRYPTSHA1_rol(5, A) + (B^C^D) + E + W[t] + 0x6ED9EBA1; + E = D; D = C; C = CRYPTSHA1_rol(30, B); B = A; A = TEMP; + } + + for (t = 40; t != 60; t += 1) + { + uint32_t TEMP = CRYPTSHA1_rol(5, A) + ((B&C)|(B&D)|(C&D)) + E + W[t] + 0x8F1BBCDC; + E = D; D = C; C = CRYPTSHA1_rol(30, B); B = A; A = TEMP; + } + + for (t = 60; t != 80; t += 1) + { + uint32_t TEMP = CRYPTSHA1_rol(5, A) + (B^C^D) + E + W[t] + 0xCA62C1D6; + E = D; D = C; C = CRYPTSHA1_rol(30, B); B = A; A = TEMP; + } + + // Section 6.1.e + pSha1->H[0] += A; + pSha1->H[1] += B; + pSha1->H[2] += C; + pSha1->H[3] += D; + pSha1->H[4] += E; +} + + +/*** Public functions ************************************************/ + + +/*F*******************************************************************/ +/*! + \Function CryptSha1Init + + \Description + Hash the input and add it to the state + + \Input *pSha1 - SHA1 state + + \Output None + + \Version 1.0 06/06/2004 (sbevan) First Version +*/ +/*******************************************************************F*/ +void CryptSha1Init(CryptSha1T *pSha1) +{ + pSha1->uCount = 0; + pSha1->uPartialCount = 0; + // All the constants come from RFC 3174 section 6.1 + pSha1->H[0] = 0x67452301; + pSha1->H[1] = 0xEFCDAB89; + pSha1->H[2] = 0x98BADCFE; + pSha1->H[3] = 0x10325476; + pSha1->H[4] = 0xC3D2E1F0; +} + +/*F*******************************************************************/ +/*! + \Function CryptSha1Init2 + + \Description + Hash the input and add it to the state (alternate form) + + \Input *pSha1 - SHA1 state + \Input iHashSize - hash size (unused) + + \Version 11/05/2013 (jbrookes) +*/ +/*******************************************************************F*/ +void CryptSha1Init2(CryptSha1T *pSha1, int32_t iHashSize) +{ + CryptSha1Init(pSha1); +} + +/*F*******************************************************************/ +/*! + \Function CryptSha1Update + + \Description + Hash the input and add it to the state + + \Input *pSha1 - SHA1 state + \Input *pInput - the input + \Input uInputLen - length of input in bytes + + \Output None + + \Version 1.0 06/06/2004 (sbevan) First Version +*/ +/*******************************************************************F*/ +void CryptSha1Update(CryptSha1T *pSha1, const unsigned char *pInput, uint32_t uInputLen) +{ + if (pSha1->uPartialCount != 0) + { + uint32_t uWant = sizeof(pSha1->strData) - pSha1->uPartialCount; + uint32_t uHave = uWant > uInputLen ? uInputLen : uWant; + ds_memcpy(&pSha1->strData[pSha1->uPartialCount], pInput, uHave); + pInput += uHave; + uInputLen -= uHave; + if (uHave == uWant) + { + _CryptSha1ProcessBlock(pSha1, pSha1->strData); + pSha1->uCount += sizeof(pSha1->strData); + pSha1->uPartialCount = 0; + } + else + { + pSha1->uPartialCount += uHave; + } + } + while (uInputLen >= sizeof(pSha1->strData)) + { + _CryptSha1ProcessBlock(pSha1, pInput); + pSha1->uCount += sizeof(pSha1->strData); + uInputLen -= sizeof(pSha1->strData); + pInput += sizeof(pSha1->strData); + } + if (uInputLen != 0) + { + ds_memcpy(&pSha1->strData[pSha1->uPartialCount], pInput, uInputLen); + pSha1->uPartialCount += uInputLen; + } +} + +/*F*******************************************************************/ +/*! + \Function CryptSha1Final + + \Description + Generate the final hash from the SHA1 state + + \Input *pSha1 - the SHA1 state + \Input *pBuffer - where the hash should be written + \Input uLength - the number of bytes to write, [0..CRYPTSHA1_HASHSIZE] + + \Output none + + \Notes + Usually callers want the whole hash and so uOutputLen would be + CRYPTSHA1_HASHSIZE. However, if only a partial hash is needed then + any value up to CRYPTSHA1_HASHSIZE can be used. Any value + greater than CRYPTSHA1_HASHSIZE is silently truncated to + CRYPTSHA1_HASHSIZE. + + CryptSha1Final is not idempotent. It could easily be made so but + callers typically tend not to need it so it is not supported. + + \Version 1.0 06/06/2004 (sbevan) First Version +*/ +/*******************************************************************F*/ +void CryptSha1Final(CryptSha1T *pSha1, void *pBuffer, uint32_t uLength) +{ + uint32_t i; + uint8_t uPad = 0x80; + uint32_t uSpace = sizeof(pSha1->strData) - pSha1->uPartialCount; + + pSha1->uCount += pSha1->uPartialCount; + if (uSpace < 9) + { + pSha1->strData[pSha1->uPartialCount] = uPad; + for (i = pSha1->uPartialCount+1; i < sizeof(pSha1->strData); i += 1) + { + pSha1->strData[i] = 0x0; + } + _CryptSha1ProcessBlock(pSha1, pSha1->strData); + uPad = 0x0; + pSha1->uPartialCount = 0; + } + pSha1->strData[pSha1->uPartialCount] = uPad; + for (i = pSha1->uPartialCount+1; i < sizeof(pSha1->strData)-8; i += 1) + { + pSha1->strData[i] = 0x0; + } + + /* Append length in bits, as per RFC 3174 section 4.c except we only + support a 32-bit byte length / 35-bit bit length. Uses some + bit shifting to avoid having to explicitly calculate + pSha1->uCount*8 which could overflow 32-bits. */ + pSha1->strData[56] = 0; + pSha1->strData[57] = 0; + pSha1->strData[58] = 0; + pSha1->strData[59] = (pSha1->uCount>>(32-3))&0xFF; + pSha1->strData[60] = (pSha1->uCount>>(24-3))&0xFF; + pSha1->strData[61] = (pSha1->uCount>>(16-3))&0xFF; + pSha1->strData[62] = (pSha1->uCount>>(8-3))&0xFF; + pSha1->strData[63] = (pSha1->uCount<<3)&0xFF; + _CryptSha1ProcessBlock(pSha1, pSha1->strData); + + _CryptSha1CopyHash(pSha1, pBuffer, uLength); +} diff --git a/src/thirdparty/dirtysdk/source/crypt/cryptsha2.c b/src/thirdparty/dirtysdk/source/crypt/cryptsha2.c new file mode 100644 index 00000000..3647d67e --- /dev/null +++ b/src/thirdparty/dirtysdk/source/crypt/cryptsha2.c @@ -0,0 +1,513 @@ +/*H*******************************************************************/ +/*! + \File cryptsha2.c + + \Description + This module implements SHA2 as defined in RFC 6234, which is + itself based on FIPS 180-2. This implementation is modeled + after the CryptSha1 implementation by sbevan. + + \Notes + The implementation is based on the algorithm description in sections + 4 through 6 of RFC 6234 and not on the C code in section 8. + + Currently the code is a straightforward implementation of the + algorithm, no attempt has been made to optimize it in any way. + + The code deliberately uses some of the naming conventions from + the RFC to in order to aid comprehension. + + This implementation is limited to hashing no more than 2^32-9 + bytes. It will silently produce the wrong result if an attempt + is made to hash more data. + + \Copyright + Copyright (c) Electronic Arts 2013 + + \Version 1.0 11/05/2013 (jbrookes) First Version +*/ +/*******************************************************************H*/ + +/*** Include files ***************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/crypt/cryptsha2.h" + +/*** Defines *********************************************************/ + +// definitions for functions and constants used: http://tools.ietf.org/html/rfc6234#section-5 +#define SHR(_n,_x) ((_x)>>(_n)) + +#define ROTR(_n,_x,_w) (((_x)>>(_n))|((_x)<<((_w)-(_n)))) +#define ROTL(_n,_x,_w) (((_x)<<(_n))|((_x)>>((_w)-(_n)))) + +#define CH(_x,_y,_z) (((_x)&(_y))^(~(_x)&(_z))) +#define MAJ(_x,_y,_z) (((_x)&(_y))^((_x)&(_z))^((_y)&(_z))) + +#define BSIG0_32(_x) (ROTR(2,_x,32)^ROTR(13,_x,32)^ROTR(22,_x,32)) +#define BSIG1_32(_x) (ROTR(6,_x,32)^ROTR(11,_x,32)^ROTR(25,_x,32)) +#define SSIG0_32(_x) (ROTR(7,_x,32)^ROTR(18,_x,32)^SHR(3,_x)) +#define SSIG1_32(_x) (ROTR(17,_x,32)^ROTR(19,_x,32)^SHR(10,_x)) + +#define BSIG0_64(_x) (ROTR(28,_x,64)^ROTR(34,_x,64)^ROTR(39,_x,64)) +#define BSIG1_64(_x) (ROTR(14,_x,64)^ROTR(18,_x,64)^ROTR(41,_x,64)) +#define SSIG0_64(_x) (ROTR(1,_x,64)^ROTR(8,_x,64)^SHR(7,_x)) +#define SSIG1_64(_x) (ROTR(19,_x,64)^ROTR(61,_x,64)^SHR(6,_x)) + +/*** Type Definitions ************************************************/ + +/*** Variables *******************************************************/ + +// constants defined in FIPS 180-3 section 4.2.2 +static const uint32_t K_32[64] = +{ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +// constants defined in FIPS 180-3 section 4.2.3 +static const uint64_t K_64[80] = +{ + 0x428A2F98D728AE22ull, 0x7137449123EF65CDull, 0xB5C0FBCFEC4D3B2Full, 0xE9B5DBA58189DBBCull, + 0x3956C25BF348B538ull, 0x59F111F1B605D019ull, 0x923F82A4AF194F9Bull, 0xAB1C5ED5DA6D8118ull, + 0xD807AA98A3030242ull, 0x12835B0145706FBEull, 0x243185BE4EE4B28Cull, 0x550C7DC3D5FFB4E2ull, + 0x72BE5D74F27B896Full, 0x80DEB1FE3B1696B1ull, 0x9BDC06A725C71235ull, 0xC19BF174CF692694ull, + 0xE49B69C19EF14AD2ull, 0xEFBE4786384F25E3ull, 0x0FC19DC68B8CD5B5ull, 0x240CA1CC77AC9C65ull, + 0x2DE92C6F592B0275ull, 0x4A7484AA6EA6E483ull, 0x5CB0A9DCBD41FBD4ull, 0x76F988DA831153B5ull, + 0x983E5152EE66DFABull, 0xA831C66D2DB43210ull, 0xB00327C898FB213Full, 0xBF597FC7BEEF0EE4ull, + 0xC6E00BF33DA88FC2ull, 0xD5A79147930AA725ull, 0x06CA6351E003826Full, 0x142929670A0E6E70ull, + 0x27B70A8546D22FFCull, 0x2E1B21385C26C926ull, 0x4D2C6DFC5AC42AEDull, 0x53380D139D95B3DFull, + 0x650A73548BAF63DEull, 0x766A0ABB3C77B2A8ull, 0x81C2C92E47EDAEE6ull, 0x92722C851482353Bull, + 0xA2BFE8A14CF10364ull, 0xA81A664BBC423001ull, 0xC24B8B70D0F89791ull, 0xC76C51A30654BE30ull, + 0xD192E819D6EF5218ull, 0xD69906245565A910ull, 0xF40E35855771202Aull, 0x106AA07032BBD1B8ull, + 0x19A4C116B8D2D0C8ull, 0x1E376C085141AB53ull, 0x2748774CDF8EEB99ull, 0x34B0BCB5E19B48A8ull, + 0x391C0CB3C5C95A63ull, 0x4ED8AA4AE3418ACBull, 0x5B9CCA4F7763E373ull, 0x682E6FF3D6B2B8A3ull, + 0x748F82EE5DEFB2FCull, 0x78A5636F43172F60ull, 0x84C87814A1F0AB72ull, 0x8CC702081A6439ECull, + 0x90BEFFFA23631E28ull, 0xA4506CEBDE82BDE9ull, 0xBEF9A3F7B2C67915ull, 0xC67178F2E372532Bull, + 0xCA273ECEEA26619Cull, 0xD186B8C721C0C207ull, 0xEADA7DD6CDE0EB1Eull, 0xF57D4F7FEE6ED178ull, + 0x06F067AA72176FBAull, 0x0A637DC5A2C898A6ull, 0x113F9804BEF90DAEull, 0x1B710B35131C471Bull, + 0x28DB77F523047D84ull, 0x32CAAB7B40C72493ull, 0x3C9EBE0A15C9BEBCull, 0x431D67C49C100D4Cull, + 0x4CC5D4BECB3E42B6ull, 0x597F299CFC657E2Aull, 0x5FCB6FAB3AD6FAECull, 0x6C44198C4A475817ull +}; + + +/*** Private functions ***********************************************/ + + +/*F*******************************************************************/ +/*! + \Function _CryptSha2CopyHash224_256 + + \Description + Extract the SHA2 hash and copy it to a byte buffer. + + \Input *pSha2 - SHA2 state + \Input *pBuffer - where to store the hash + \Input uLength - how many bytes of the hash to extract + + \Version 11/04/2013 (jbrookes) +*/ +/*******************************************************************F*/ +static void _CryptSha2CopyHash224_256(CryptSha2T *pSha2, uint8_t *pBuffer, uint32_t uLength) +{ + uint32_t uByte; + + if (uLength > pSha2->uHashSize) + { + uLength = pSha2->uHashSize; + } + for (uByte = 0; uByte != uLength; uByte += 1) + { + pBuffer[uByte] = pSha2->TempHash.H_32[(uByte/4)]>>((3-(uByte%4))*8); + } +} + +/*F*******************************************************************/ +/*! + \Function _CryptSha2CopyHash384_512 + + \Description + Extract the SHA2 hash and copy it to a byte buffer. + + \Input *pSha2 - SHA2 state + \Input *pBuffer - where to store the hash + \Input uLength - how many bytes of the hash to extract + + \Version 11/04/2013 (jbrookes) +*/ +/*******************************************************************F*/ +static void _CryptSha2CopyHash384_512(CryptSha2T *pSha2, uint8_t *pBuffer, uint32_t uLength) +{ + uint32_t uByte; + + if (uLength > pSha2->uHashSize) + { + uLength = pSha2->uHashSize; + } + for (uByte = 0; uByte != uLength; uByte += 1) + { + pBuffer[uByte] = pSha2->TempHash.H_64[(uByte/8)]>>((7-(uByte%8))*8); + } +} + +/*F*******************************************************************/ +/*! + \Function _CryptSha2ProcessBlock224_256 + + \Description + SHA2 a 64-byte block of data using 224 or 256 bit version. + + \Input *pSha2 - SHA2 state + \Input *M - start of 64-bytes to be processed + + \Notes + This is a literal translation of the method described in RFC + 6234 section 6.2. The variable names deliberately match those + used in that section in order to aid manual verification of + algorithm correctness. + + \Version 11/04/2013 (jbrookes) +*/ +/*******************************************************************F*/ +static void _CryptSha2ProcessBlock224_256(CryptSha2T *pSha2, const uint8_t *M) +{ + unsigned a, b, c, d, e, f, g, h; + uint32_t W[64]; + uint32_t t; + unsigned T1, T2; + + // RFC 6234 section 6.2: http://tools.ietf.org/html/rfc6234#section-6.2 + for (t = 0; t < 16; t += 1) + { + W[t] = (M[t*4]<<24)|(M[t*4+1]<<16)|(M[t*4+2]<<8)|M[t*4+3]; + } + for (t = 16; t < 64; t += 1) + { + W[t] = SSIG1_32(W[t-2]) + W[t-7] + SSIG0_32(W[t-15]) + W[t-16]; + } + + // section 6.2.2 + a = pSha2->TempHash.H_32[0]; + b = pSha2->TempHash.H_32[1]; + c = pSha2->TempHash.H_32[2]; + d = pSha2->TempHash.H_32[3]; + e = pSha2->TempHash.H_32[4]; + f = pSha2->TempHash.H_32[5]; + g = pSha2->TempHash.H_32[6]; + h = pSha2->TempHash.H_32[7]; + + // section 6.1.3 + for (t = 0; t < 64; t += 1) + { + T1 = h + BSIG1_32(e) + CH(e, f, g) + K_32[t] + W[t]; + T2 = BSIG0_32(a) + MAJ(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + // section 6.1.4 + pSha2->TempHash.H_32[0] += a; + pSha2->TempHash.H_32[1] += b; + pSha2->TempHash.H_32[2] += c; + pSha2->TempHash.H_32[3] += d; + pSha2->TempHash.H_32[4] += e; + pSha2->TempHash.H_32[5] += f; + pSha2->TempHash.H_32[6] += g; + pSha2->TempHash.H_32[7] += h; +} + +/*F*******************************************************************/ +/*! + \Function _CryptSha2ProcessBlock384_512 + + \Description + SHA2 a 64-byte block of data using 384 or 512 bit version + + \Input *pSha2 - SHA2 state + \Input *M - start of 64-bytes to be processed + + \Notes + This is a literal translation of the method described in RFC + 6234 section 6.4. The variable names deliberately match those + used in that section in order to aid manual verification of + algorithm correctness. + + \Version 11/04/2013 (jbrookes) +*/ +/*******************************************************************F*/ +static void _CryptSha2ProcessBlock384_512(CryptSha2T *pSha2, const uint8_t *M) +{ + uint64_t a, b, c, d, e, f, g, h; + uint64_t T1, T2; + uint64_t W[80]; + uint32_t t; + + // RFC 6234 section 6.4: http://tools.ietf.org/html/rfc6234#section-6.4 + for (t = 0; t < 16; t += 1) + { + W[t] = ((uint64_t)M[t*8+0]<<56)|((uint64_t)M[t*8+1]<<48)|((uint64_t)M[t*8+2]<<40)|((uint64_t)M[t*8+3]<<32)| + ((uint64_t)M[t*8+4]<<24)|((uint64_t)M[t*8+5]<<16)|((uint64_t)M[t*8+6]<<8)|(uint64_t)M[t*8+7]; + } + for (t = 16; t < 80; t += 1) + { + W[t] = SSIG1_64(W[t-2]) + W[t-7] + SSIG0_64(W[t-15]) + W[t-16]; + } + + // section 6.4.2 + a = pSha2->TempHash.H_64[0]; + b = pSha2->TempHash.H_64[1]; + c = pSha2->TempHash.H_64[2]; + d = pSha2->TempHash.H_64[3]; + e = pSha2->TempHash.H_64[4]; + f = pSha2->TempHash.H_64[5]; + g = pSha2->TempHash.H_64[6]; + h = pSha2->TempHash.H_64[7]; + + // section 6.4.3 + for (t = 0; t < 80; t += 1) + { + T1 = h + BSIG1_64(e) + CH(e, f, g) + K_64[t] + W[t]; + T2 = BSIG0_64(a) + MAJ(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + // section 6.4.4 + pSha2->TempHash.H_64[0] += a; + pSha2->TempHash.H_64[1] += b; + pSha2->TempHash.H_64[2] += c; + pSha2->TempHash.H_64[3] += d; + pSha2->TempHash.H_64[4] += e; + pSha2->TempHash.H_64[5] += f; + pSha2->TempHash.H_64[6] += g; + pSha2->TempHash.H_64[7] += h; +} + + +/*** Public functions ************************************************/ + + +/*F*******************************************************************/ +/*! + \Function CryptSha2Init + + \Description + Initialize SHA2 state based on mode + + \Input *pSha2 - SHA2 state + \Input iHashSize - hash size (CRYPTSHA*_HASHSIZE) + + \Version 11/04/2013 (jbrookes) +*/ +/*******************************************************************F*/ +void CryptSha2Init(CryptSha2T *pSha2, int32_t iHashSize) +{ + pSha2->uCount = 0; + pSha2->uPartialCount = 0; + pSha2->uHashSize = (uint8_t)iHashSize; + pSha2->uBlockSize = (pSha2->uHashSize < CRYPTSHA384_HASHSIZE) ? 64 : 128; + + // all the constants come from RFC 6234 section 6.1 + if (pSha2->uHashSize == CRYPTSHA224_HASHSIZE) + { + pSha2->TempHash.H_32[0] = 0xc1059ed8; + pSha2->TempHash.H_32[1] = 0x367cd507; + pSha2->TempHash.H_32[2] = 0x3070dd17; + pSha2->TempHash.H_32[3] = 0xf70e5939; + pSha2->TempHash.H_32[4] = 0xffc00b31; + pSha2->TempHash.H_32[5] = 0x68581511; + pSha2->TempHash.H_32[6] = 0x64f98fa7; + pSha2->TempHash.H_32[7] = 0xbefa4fa4; + } + else if (pSha2->uHashSize == CRYPTSHA256_HASHSIZE) + { + pSha2->TempHash.H_32[0] = 0x6a09e667; + pSha2->TempHash.H_32[1] = 0xbb67ae85; + pSha2->TempHash.H_32[2] = 0x3c6ef372; + pSha2->TempHash.H_32[3] = 0xa54ff53a; + pSha2->TempHash.H_32[4] = 0x510e527f; + pSha2->TempHash.H_32[5] = 0x9b05688c; + pSha2->TempHash.H_32[6] = 0x1f83d9ab; + pSha2->TempHash.H_32[7] = 0x5be0cd19; + } + else if (pSha2->uHashSize == CRYPTSHA384_HASHSIZE) + { + pSha2->TempHash.H_64[0] = 0xcbbb9d5dc1059ed8; + pSha2->TempHash.H_64[1] = 0x629a292a367cd507; + pSha2->TempHash.H_64[2] = 0x9159015a3070dd17; + pSha2->TempHash.H_64[3] = 0x152fecd8f70e5939; + pSha2->TempHash.H_64[4] = 0x67332667ffc00b31; + pSha2->TempHash.H_64[5] = 0x8eb44a8768581511; + pSha2->TempHash.H_64[6] = 0xdb0c2e0d64f98fa7; + pSha2->TempHash.H_64[7] = 0x47b5481dbefa4fa4; + } + else if (pSha2->uHashSize == CRYPTSHA512_HASHSIZE) + { + pSha2->TempHash.H_64[0] = 0x6a09e667f3bcc908; + pSha2->TempHash.H_64[1] = 0xbb67ae8584caa73b; + pSha2->TempHash.H_64[2] = 0x3c6ef372fe94f82b; + pSha2->TempHash.H_64[3] = 0xa54ff53a5f1d36f1; + pSha2->TempHash.H_64[4] = 0x510e527fade682d1; + pSha2->TempHash.H_64[5] = 0x9b05688c2b3e6c1f; + pSha2->TempHash.H_64[6] = 0x1f83d9abfb41bd6b; + pSha2->TempHash.H_64[7] = 0x5be0cd19137e2179; + } + else + { + NetPrintf(("cryptsha2: invalid hashsize %d\n", pSha2->uHashSize)); + } +} + +/*F*******************************************************************/ +/*! + \Function CryptSha2Update + + \Description + Hash the input and add it to the state + + \Input *pSha2 - SHA2 state + \Input *pInput - the input + \Input uInputLen - length of input in bytes + + \Version 11/04/2013 (jbrookes) +*/ +/*******************************************************************F*/ +void CryptSha2Update(CryptSha2T *pSha2, const uint8_t *pInput, uint32_t uInputLen) +{ + if (pSha2->uPartialCount != 0) + { + uint32_t uWant = pSha2->uBlockSize - pSha2->uPartialCount; + uint32_t uHave = uWant > uInputLen ? uInputLen : uWant; + ds_memcpy(&pSha2->strData[pSha2->uPartialCount], pInput, uHave); + pInput += uHave; + uInputLen -= uHave; + if (uHave == uWant) + { + pSha2->uHashSize < CRYPTSHA384_HASHSIZE ? _CryptSha2ProcessBlock224_256(pSha2, pSha2->strData) : _CryptSha2ProcessBlock384_512(pSha2, pSha2->strData); + pSha2->uCount += pSha2->uBlockSize; + pSha2->uPartialCount = 0; + } + else + { + pSha2->uPartialCount += uHave; + } + } + while (uInputLen >= pSha2->uBlockSize) + { + pSha2->uHashSize < CRYPTSHA384_HASHSIZE ? _CryptSha2ProcessBlock224_256(pSha2, pInput) : _CryptSha2ProcessBlock384_512(pSha2, pInput); + pSha2->uCount += pSha2->uBlockSize; + uInputLen -= pSha2->uBlockSize; + pInput += pSha2->uBlockSize; + } + if (uInputLen != 0) + { + ds_memcpy(&pSha2->strData[pSha2->uPartialCount], pInput, uInputLen); + pSha2->uPartialCount += uInputLen; + } +} + +/*F*******************************************************************/ +/*! + \Function CryptSha2Final + + \Description + Generate the final hash from the SHA2 state + + \Input *pSha2 - the SHA2 state + \Input *pBuffer - [out] where the hash should be written + \Input uLength - the number of bytes to write (may be less than hash size) + + \Version 11/04/2013 (jbrookes) +*/ +/*******************************************************************F*/ +void CryptSha2Final(CryptSha2T *pSha2, uint8_t *pBuffer, uint32_t uLength) +{ + uint32_t uByte, uSpace = pSha2->uBlockSize - pSha2->uPartialCount; + const uint32_t uLengthSize = (pSha2->uHashSize < CRYPTSHA384_HASHSIZE) ? 8 : 16; + uint8_t uPad = 0x80; + + pSha2->uCount += pSha2->uPartialCount; + if (uSpace < (uLengthSize+1)) + { + pSha2->strData[pSha2->uPartialCount] = uPad; + for (uByte = pSha2->uPartialCount+1; uByte < pSha2->uBlockSize; uByte += 1) + { + pSha2->strData[uByte] = 0x0; + } + pSha2->uHashSize < CRYPTSHA384_HASHSIZE ? _CryptSha2ProcessBlock224_256(pSha2, pSha2->strData) : _CryptSha2ProcessBlock384_512(pSha2, pSha2->strData); + uPad = 0x0; + pSha2->uPartialCount = 0; + } + pSha2->strData[pSha2->uPartialCount] = uPad; + for (uByte = pSha2->uPartialCount+1; uByte < (uint32_t)pSha2->uBlockSize-uLengthSize; uByte += 1) + { + pSha2->strData[uByte] = 0x0; + } + + /* append length in bits, as per RFC 6234 section 4 except we only support a 32-bit byte + length (35-bit bit length). uses some bit shifting to avoid having to explicitly calculate + pSha1->uCount*8 which could overflow 32-bits */ + if (uLengthSize == 16) + { + pSha2->strData[pSha2->uBlockSize-16] = 0; + pSha2->strData[pSha2->uBlockSize-15] = 0; + pSha2->strData[pSha2->uBlockSize-14] = 0; + pSha2->strData[pSha2->uBlockSize-13] = 0; + pSha2->strData[pSha2->uBlockSize-12] = 0; + pSha2->strData[pSha2->uBlockSize-11] = 0; + pSha2->strData[pSha2->uBlockSize-10] = 0; + pSha2->strData[pSha2->uBlockSize- 9] = 0; + } + pSha2->strData[pSha2->uBlockSize-8] = 0; + pSha2->strData[pSha2->uBlockSize-7] = 0; + pSha2->strData[pSha2->uBlockSize-6] = 0; + pSha2->strData[pSha2->uBlockSize-5] = (pSha2->uCount>>(32-3))&0xFF; + pSha2->strData[pSha2->uBlockSize-4] = (pSha2->uCount>>(24-3))&0xFF; + pSha2->strData[pSha2->uBlockSize-3] = (pSha2->uCount>>(16-3))&0xFF; + pSha2->strData[pSha2->uBlockSize-2] = (pSha2->uCount>>(8-3))&0xFF; + pSha2->strData[pSha2->uBlockSize-1] = (pSha2->uCount<<3)&0xFF; + + // process final block, and copy the hash to final buffer + if (pSha2->uHashSize < CRYPTSHA384_HASHSIZE) + { + _CryptSha2ProcessBlock224_256(pSha2, pSha2->strData); + _CryptSha2CopyHash224_256(pSha2, pBuffer, uLength); + } + else + { + _CryptSha2ProcessBlock384_512(pSha2, pSha2->strData); + _CryptSha2CopyHash384_512(pSha2, pBuffer, uLength); + } +} + + + diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtyaddr.c b/src/thirdparty/dirtysdk/source/dirtysock/dirtyaddr.c new file mode 100644 index 00000000..6721c43b --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtyaddr.c @@ -0,0 +1,135 @@ +/*H********************************************************************************/ +/*! + \File dirtyaddr.c + + \Description + Opaque address functions. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 04/09/2004 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtyaddr.h" +#include "DirtySDK/dirtysock/netconn.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function DirtyAddrToHostAddr + + \Description + Convert from DirtyAddrT to native format. + + \Input *pOutput - [out] storage for native format address + \Input iBufLen - length of output buffer + \Input *pAddr - source address to convert + + \Output + uint32_t - number of characters consumed from input, or zero on failure + + \Notes + On common platforms, a DirtyAddrT is a string-encoded network-order address + field. This function converts that input to a binary-encoded host-order + address. + + \Version 04/09/2004 (jbrookes) +*/ +/********************************************************************************F*/ +uint32_t DirtyAddrToHostAddr(void *pOutput, int32_t iBufLen, const DirtyAddrT *pAddr) +{ + uint32_t uAddress; + + // make sure our buffer is big enough + if (iBufLen < (signed)sizeof(uAddress)) + { + NetPrintf(("dirtyaddr: output buffer too small\n")); + return(0); + } + + // convert from string to int32_t + uAddress = (uint32_t)strtoul(&pAddr->strMachineAddr[1], NULL, 16); + + // convert from network order to host order + uAddress = SocketNtohl(uAddress); + + // copy to output + ds_memcpy(pOutput, &uAddress, sizeof(uAddress)); + return(9); +} + +/*F********************************************************************************/ +/*! + \Function DirtyAddrFromHostAddr + + \Description + Convert from native format to DirtyAddrT. + + \Input *pAddr - [out] storage for output DirtyAddrT + \Input *pInput - pointer to native format address + + \Output + uint32_t - TRUE if successful, else FALSE + + \Notes + On common platforms, a DirtyAddrT is a string-encoded network-order address + field. This function converts a binary-encoded host-order address to that + format. + + \Version 04/09/2004 (jbrookes) +*/ +/********************************************************************************F*/ +uint32_t DirtyAddrFromHostAddr(DirtyAddrT *pAddr, const void *pInput) +{ + uint32_t uAddress; + + // make sure output buffer is okay + if ((pInput == NULL) || (((intptr_t)pInput & 0x3) != 0)) + { + return(FALSE); + } + + uAddress = SocketHtonl(*(const uint32_t *)pInput); + ds_snzprintf(pAddr->strMachineAddr, sizeof(pAddr->strMachineAddr), "$%08x", uAddress); + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function DirtyAddrGetLocalAddr + + \Description + Get the local address in DirtyAddr form. + + \Input *pAddr - [out] storage for output DirtyAddrT + + \Output + uint32_t - TRUE if successful, else FALSE + + \Version 09/29/2004 (jbrookes) +*/ +/********************************************************************************F*/ +uint32_t DirtyAddrGetLocalAddr(DirtyAddrT *pAddr) +{ + uint32_t uLocalAddr = NetConnStatus('addr', 0, NULL, 0); + DirtyAddrFromHostAddr(pAddr, &uLocalAddr); + return(TRUE); +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtycert.c b/src/thirdparty/dirtysdk/source/dirtysock/dirtycert.c new file mode 100644 index 00000000..f07c7e3d --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtycert.c @@ -0,0 +1,1239 @@ +/*H**************************************************************************/ +/*! + + \File dirtycert.c + + \Description + This module defines the CA fallback mechanism which is used by ProtoSSL. + + \Notes + This module is designed to be thread-safe except the creating/destroying APIs, + however because some other code (such as CA list) are not thread-safe yet, + so it's not totally thread-safe right now. + If two protossl instances are requesting the same CA cert at the same time, + ref-counting is used and only one CA fetch request will be made to the server. + + \Copyright + Copyright (c) Electronic Arts 2012. + + \Version 01/23/2012 (szhu) +*/ +/***************************************************************************H*/ + +/*** Include files ***********************************************************/ + +#include + +#include "DirtySDK/dirtyvers.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/crypt/cryptarc4.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/proto/protohttp.h" +#include "DirtySDK/xml/xmlparse.h" +#include "DirtySDK/util/base64.h" + +#include "DirtySDK/dirtysock/dirtycert.h" + +/*** Defines ***************************************************************************/ + +#define DIRTYCERT_MAXREQUESTS (16) //!< max concurrent CA requests +#define DIRTYCERT_TIMEOUT (30*1000) //!< default dirtycert timeout +#define DIRTYCERT_MAXURL (2048) //!< max url length +#define DIRTYCERT_MAXCERTIFICATE (8*1024) //!< max certificate size, in bytes +#define DIRTYCERT_MAXRESPONSE ((DIRTYCERT_MAXCERTIFICATE)*3) //! max response size, in bytes +#define DIRTYCERT_MAXCERTDECODED ((Base64DecodedSize(DIRTYCERT_MAXCERTIFICATE)+3)&0x7ffc) //!< max certificate size (decoded), in bytes +#define DIRTYCERT_REQUESTID_NONE (-1) //!< request id none +#define DIRTYCERT_VERSION (0x0101) //!< dirtycert version: update this for major bug fixes or protocol additions/changes + +/* dirtycert production url; NOTE: this url is encrypted as stored in the binary to prevent aa + malicious user from being able to use an easy string search to find and possibly modify it. + if the url is changed, _DirtyCertUrlSetup() will complain and spit out new enc/key parameters + for the updated url, which must be used to replace the current definitions in that function. */ +#define DIRTYCERT_URL "https://gosca18.ea.com:4432%d/redirector" +#define DIRTYCERT_URL_ENCRYPTED (TRUE) //!< TRUE if the dirtycert URL is specified in encrypted form; DO NOT CHECK IN DISABLED + +#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + #define DIRTYCERT_PORT_OFFSET (6) //!< xbox platform offset is +1 from other platforms +#else + #define DIRTYCERT_PORT_OFFSET (5) //!< default platform offset is +5 from the base port number of 44320 +#endif + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! request type +typedef enum RequestTypeE +{ + RT_ONDEMAND = 0, + RT_PREFETCH +} RequestTypeE; + +//! request status +typedef enum RequestStatusE +{ + RS_NONE = 0, + RS_NOT_STARTED, + RS_IN_PROGRESS, + RS_DONE, + RS_FAILED, +} RequestStatusE; + +//! CA fetch request +typedef struct DirtyCertCARequestT +{ + ProtoSSLCertInfoT CertInfo; //!< certificate info (on-demand request only) + char strHost[256]; //!< host we are making an on-demand request for (on-demand request only) + int32_t iPort; //!< port we are making an on-demand request for (on-demand request only) + RequestTypeE eType; //!< request type + RequestStatusE eStatus; //!< request status + int32_t iRefCount; //!< ref count for this request +}DirtyCertCARequestT; + +//! module state +struct DirtyCertRefT +{ + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + NetCritT crit; //!< critical section used to ensure thread safety + ProtoHttpRefT *pHttp; //!< http ref + + char strServiceName[DIRTYCERT_SERVICENAME_SIZE]; //!< service name used to identify requester (required) + char strUrl[DIRTYCERT_MAXURL]; //!< url buffer + + // buffer for response processing + char aResponseBuf[DIRTYCERT_MAXRESPONSE]; //!< buffer for http response + char aCertBuf[DIRTYCERT_MAXCERTIFICATE]; //!< buffer for a single certificate + char aCertDecodedBuf[DIRTYCERT_MAXCERTDECODED]; //!< buffer for a single certificate (decoded) + uint8_t bPreload; //!< TRUE if we are to execute preload, else FALSE + + uint32_t uTimeout; //!< request timeout + int32_t iRequestId; //!< current on-going request (==index in request list) + int32_t iCount; //!< count of valid requests + DirtyCertCARequestT requests[DIRTYCERT_MAXREQUESTS]; //!< request list +}; + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +//! module state ref +static DirtyCertRefT *_DirtyCert_pState = NULL; + +//! dirtycert url +#if DIRTYCERT_URL_ENCRYPTED + static char _DirtyCert_strRedirectorUrl[64]; +#else + #if 1 + static const char *_DirtyCert_strRedirectorUrl = DIRTYCERT_URL; + #else // dev redirector, use only for testing, do NOT check in enabled + #error DO NOT CHECK THIS IN ENABLED!! + static const char *_DirtyCert_strRedirectorUrl = "http://gosredirector.online.ea.com:42125/redirector"; + #endif +#endif + +// Public variables + +/*** Private functions ******************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _DirtyCertUrlSetup + + \Description + Decrypt encrypted DirtyCert URL; also used to generate encrypted URL + + \Output + int32_t - 0=success, negative=failure + + \Version 06/04/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _DirtyCertUrlSetup(void) +{ +#if DIRTYCERT_URL_ENCRYPTED + static const uint8_t strKey[] = + { + 0x67,0x9A,0xE3,0x11, 0x6E,0x4C,0xFD,0xA2, 0x6D,0xC8,0x2D,0xF8, 0x5B,0x22,0xF1,0x40, + 0x40,0xE8,0x92,0x22, 0x8A,0x96,0x75,0x2B, 0x64,0x53,0xC6,0x8D, 0xED,0xA0,0xF9,0x21, + }; + static const uint8_t strEnc[] = + { + 0x25,0xEB,0x51,0x91, 0x6C,0x78,0xCB,0xF2, 0x2F,0xEA,0xE7,0x56, 0x37,0x96,0xE2,0x7C, + 0x49,0x8C,0x19,0x75, 0x59,0xBD,0xD8,0x3D, 0x39,0x81,0x91,0xE5, 0x9B,0x6C,0xE7,0x29, + 0x2B,0x72,0xB7,0x0A, 0x76,0x60,0x49,0x04, 0x90,0x7F,0xDB,0x54, 0xFD,0xC1,0x5E,0x2E, + 0xDB,0x37,0xF8,0x77, 0x21,0x06,0xDD,0x4E, 0xCD,0xCB,0xA3,0xF9, 0x87,0x74,0xDE,0x64, + }; + char strUrlDecrypt[sizeof(_DirtyCert_strRedirectorUrl)]; + ds_memcpy_s(strUrlDecrypt, sizeof(strUrlDecrypt), strEnc, sizeof(strEnc)); + if (CryptArc4StringEncryptStatic(strUrlDecrypt, sizeof(strUrlDecrypt), strKey, sizeof(strKey), DIRTYCERT_URL) < 0) + { + return(-1); + } + // url has the port offset as a format paramter, so we fill it in here + ds_snzprintf(_DirtyCert_strRedirectorUrl, sizeof(_DirtyCert_strRedirectorUrl), strUrlDecrypt, DIRTYCERT_PORT_OFFSET); + return(0); +#else + return(0); +#endif +} + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _DirtyCertCAGetStrIdent + + \Description + Debug-only function to get a string identifier of the request, for debug printing + + \Input *pState - dirtycert state + \Input *pRequest - request to get string identifier for + + \Output + const char * - pointer to string ident + + \Version 04/18/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_DirtyCertCAGetStrIdent(DirtyCertRefT *pState, DirtyCertCARequestT *pRequest) +{ + static char _strIdent[512]; + if (pRequest->eType == RT_ONDEMAND) + { + ds_snzprintf(_strIdent, sizeof(_strIdent), "[%s:%s:%d]", pRequest->CertInfo.Ident.strCommon, pRequest->CertInfo.Ident.strUnit, pRequest->CertInfo.iKeyModSize); + } + else + { + ds_snzprintf(_strIdent, sizeof(_strIdent), "[%s]", pState->strServiceName); + } + return(_strIdent); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _DirtyCertUrlEncodeStrParm + + \Description + Wrapper for ProtoHttpUrlEncodeStrParm() that won't encode a null string. + + \Input *pBuffer - output buffer to encode into + \Input iLength - length of output buffer + \Input *pParm - parameter name (not encoded) + \Input *pData - parameter data + + \Version 04/18/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _DirtyCertUrlEncodeStrParm(char *pBuffer, int32_t iLength, const char *pParm, const char *pData) +{ + if (*pData != '\0') + { + ProtoHttpUrlEncodeStrParm(pBuffer, iLength, pParm, pData); + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyCertCompareCertInfo + + \Description + Compare two cert infos. + + \Input *pCertInfo1 - the cert info to be compared + \Input *pCertInfo2 - the cert info to be compared + + \Output int32_t - zero=match, non-zero=no-match + + \Version 01/23/2012 (szhu) +*/ +/********************************************************************************F*/ +static int32_t _DirtyCertCompareCertInfo(const ProtoSSLCertInfoT *pCertInfo1, const ProtoSSLCertInfoT *pCertInfo2) +{ + if ((pCertInfo1->iKeyModSize != pCertInfo2->iKeyModSize) + || strcmp(pCertInfo1->Ident.strCountry, pCertInfo2->Ident.strCountry) + || strcmp(pCertInfo1->Ident.strState, pCertInfo2->Ident.strState) + || strcmp(pCertInfo1->Ident.strCity, pCertInfo2->Ident.strCity) + || strcmp(pCertInfo1->Ident.strOrg, pCertInfo2->Ident.strOrg) + || strcmp(pCertInfo1->Ident.strCommon, pCertInfo2->Ident.strCommon) + || strcmp(pCertInfo1->Ident.strUnit, pCertInfo2->Ident.strUnit)) + { + return(1); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyCertValidateServiceName + + \Description + Validate that specified servicename is valid. + + \Input *pServiceName - servicename to validate + + \Output + int32_t - negative=invalid, else valid + + \Version 03/15/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _DirtyCertValidateServiceName(const char *pServiceName) +{ + if (*pServiceName == '\0') + { + return(-1); + } + else + { + return(0); + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyCertSetServiceName + + \Description + Format a request into provided buffer. + + \Input *pState - module ref + \Input *pName - service name to set (game-year-platform or game) + + \Version 03/19/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static void _DirtyCertSetServiceName(DirtyCertRefT *pState, const char *pName) +{ + char strServiceName[sizeof(pState->strServiceName)]; + if (!strchr(pName, '-')) + { + ds_snzprintf(strServiceName, sizeof(strServiceName), "%s-%d-%s", pName, 2000+DIRTYSDK_VERSION_YEAR, DIRTYCODE_PLATNAME_SHORT); + } + else + { + ds_strnzcpy(strServiceName, pName, sizeof(strServiceName)); + } + if (strcmp(pState->strServiceName, strServiceName)) + { + ds_strnzcpy(pState->strServiceName, strServiceName, sizeof(pState->strServiceName)); + NetPrintf(("dirtycert: servicename set to '%s'\n", pState->strServiceName)); + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyCertFormatRequestUrl + + \Description + Format a request into provided buffer. + + \Input *pState - module ref + \Input *pRequest - CA request + \Input *pBuf - user-supplied url buffer + \Input iBufLen - size of buffer pointed to by pBuf + + \Version 01/23/2012 (szhu) +*/ +/********************************************************************************F*/ +static void _DirtyCertFormatRequestUrl(DirtyCertRefT *pState, DirtyCertCARequestT *pRequest, char *pBuf, int32_t iBufLen) +{ + // table of "safe" characters + // 0=hex encode, non-zero=direct encode, @-O=valid hex digit (&15 to get value) + static char _DirtyCert_strSafe[256] = + "0000000000000000" "0000000000000000" // 0x00 - 0x1f + "0000000000000110" "@ABCDEFGHI000000" // 0x20 - 0x3f (allow period, dash, and digits) + "0JKLMNO111111111" "1111111111100000" // 0x40 - 0x5f (allow uppercase) + "0JKLMNO111111111" "1111111111100000" // 0x60 - 0x7f (allow lowercase) + "0000000000000000" "0000000000000000" // 0x80 - 0x9f + "0000000000000000" "0000000000000000" // 0xa0 - 0xbf + "0000000000000000" "0000000000000000" // 0xc0 - 0xdf + "0000000000000000" "0000000000000000"; // 0xe0 - 0xff} + char strTemp[32]; + + // create URL and attach DirtySDK version + ds_snzprintf(pBuf, iBufLen, "%s/%s", _DirtyCert_strRedirectorUrl, (pRequest->eType == RT_ONDEMAND) ? "findCACertificates" : "getCACertificates"); + + // append dirtysdk version + ds_snzprintf(strTemp, sizeof(strTemp), "%d.%d.%d.%d.%d", DIRTYSDK_VERSION_YEAR, DIRTYSDK_VERSION_SEASON, DIRTYSDK_VERSION_MAJOR, DIRTYSDK_VERSION_MINOR, DIRTYSDK_VERSION_PATCH); + ProtoHttpUrlEncodeStrParm2(pBuf, iBufLen, "?v=", strTemp, _DirtyCert_strSafe); + + // append dirtycert version + ProtoHttpUrlEncodeIntParm(pBuf, iBufLen, "&vers=", DIRTYCERT_VERSION); + + // append servicename + ProtoHttpUrlEncodeStrParm2(pBuf, iBufLen, "&name=", pState->strServiceName, _DirtyCert_strSafe); + + /* add additional on-demand parameters; for params name and format, refer to: + blazeserver\dev3\component\redirector\gen\redirectortypes.tdf */ + if (pRequest->eType == RT_ONDEMAND) + { + // append required parameters + ProtoHttpUrlEncodeStrParm2(pBuf, iBufLen, "&host=", pRequest->strHost, _DirtyCert_strSafe); + ProtoHttpUrlEncodeIntParm(pBuf, iBufLen, "&port=", pRequest->iPort); + ProtoHttpUrlEncodeIntParm(pBuf, iBufLen, "&bits=", pRequest->CertInfo.iKeyModSize * 8); + // append parameters identifying required CA (empty fields are not included) + _DirtyCertUrlEncodeStrParm(pBuf, iBufLen, "&entr|CN=", pRequest->CertInfo.Ident.strCommon); + _DirtyCertUrlEncodeStrParm(pBuf, iBufLen, "&entr|C=", pRequest->CertInfo.Ident.strCountry); + _DirtyCertUrlEncodeStrParm(pBuf, iBufLen, "&entr|O=", pRequest->CertInfo.Ident.strOrg); + _DirtyCertUrlEncodeStrParm(pBuf, iBufLen, "&entr|OU=", pRequest->CertInfo.Ident.strUnit); + _DirtyCertUrlEncodeStrParm(pBuf, iBufLen, "&entr|L=", pRequest->CertInfo.Ident.strCity); + _DirtyCertUrlEncodeStrParm(pBuf, iBufLen, "&entr|ST=", pRequest->CertInfo.Ident.strState); + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyCertCreateRequest + + \Description + Issue a CA fetch request. + + \Input *pState - module ref + \Input iRequestId - CA request id + \Input *pRequest - CA request + + \Output int32_t - 0 for success + + \Version 01/23/2012 (szhu) +*/ +/********************************************************************************F*/ +static int32_t _DirtyCertCreateRequest(DirtyCertRefT *pState, int32_t iRequestId, DirtyCertCARequestT *pRequest) +{ + // if we're not busy + if (pState->iRequestId == DIRTYCERT_REQUESTID_NONE) + { + ds_memclr(pState->strUrl, sizeof(pState->strUrl)); + // format request url + _DirtyCertFormatRequestUrl(pState, pRequest, pState->strUrl, sizeof(pState->strUrl)); + // set timeout + ProtoHttpControl(pState->pHttp, 'time', pState->uTimeout, 0, NULL); + // if pre-load set keep-alive + if (pRequest->eType == RT_PREFETCH) + { + ProtoHttpControl(pState->pHttp, 'keep', 1, 0, NULL); + } + // http GET + NetPrintf(("dirtycert: making CA request: %s\n", pState->strUrl)); + if (ProtoHttpGet(pState->pHttp, pState->strUrl, 0) >= 0) + { + pState->iRequestId = iRequestId; + pRequest->eStatus = RS_IN_PROGRESS; + } + else + { + // failed? + pRequest->eStatus = RS_FAILED; + NetPrintf(("dirtycert: ProtoHttpGet(%p, %s) failed.\n", pState->pHttp, pState->strUrl)); + } + } + + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function _DirtyCertCARequestFree + + \Description + Release resources used by a CA fetch request. + + \Input *pState - module state + \Input *pRequest - request ptr + \Input iRequestId - request id + + \Output + int32_t - negative=error, else success + + \Version 04/18/2012 (jbrookes) +*/ +/************************************************************************************F*/ +static int32_t _DirtyCertCARequestFree(DirtyCertRefT *pState, DirtyCertCARequestT *pRequest, int32_t iRequestId) +{ + int32_t iResult = 0; + + if (pRequest->iRefCount <= 0) + { + // error request id? + iResult = -3; + } + else if (--pRequest->iRefCount == 0) + { + // if this is an active request, abort it + if ((pState->iRequestId == iRequestId) && (pState->iRequestId != -1)) + { + ProtoHttpAbort(pState->pHttp); + pState->iRequestId = DIRTYCERT_REQUESTID_NONE; + } + NetPrintf(("dirtycert: freeing CA fetch request for %s\n", _DirtyCertCAGetStrIdent(pState, pRequest))); + ds_memclr(pRequest, sizeof(*pRequest)); + pRequest->eStatus = RS_NONE; + // if no queued requests, close connection + if (--pState->iCount == 0) + { + ProtoHttpControl(pState->pHttp, 'disc', 0, 0, NULL); + } + // success + iResult = 1; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyCertProcessResponse + + \Description + Process response. + + \Input *pState - module ref + \Input *pResponse - http response + \Input iLen - length of response + \Input *pFailed - [out] storage for number of CA certificate loads that failed + + \Output int32_t - positive=installed certs, 0 or negative=error + + \Version 01/24/2012 (szhu) +*/ +/********************************************************************************F*/ +static int32_t _DirtyCertProcessResponse(DirtyCertRefT *pState, const char *pResponse, int32_t iLen, int32_t *pFailed) +{ + int32_t iCount = 0, iCertLen, iResult; + const char *pCert; + + *pFailed = 0; + + // for response format, refer to: blazeserver\dev3\component\redirector\gen\redirectortypes.tdf + pCert = XmlFind(pResponse, "cacertificate.certificatelist.certificatelist"); + while (pCert) + { + ds_memclr(pState->aCertBuf, sizeof(pState->aCertBuf)); + if ((iCertLen = XmlContentGetString(pCert, pState->aCertBuf, sizeof(pState->aCertBuf), "")) > 0) + { + // the cert response might be base64 encoded + char sEncoding[32]; + ds_memclr(sEncoding, sizeof(sEncoding)); + XmlAttribGetString(pCert, "enc", sEncoding, sizeof(sEncoding), ""); + // base64 encoded, decode and install the cert + if (ds_stricmp(sEncoding, "base64") == 0) + { + ds_memclr(pState->aCertDecodedBuf, sizeof(pState->aCertDecodedBuf)); + if (Base64Decode(iCertLen, pState->aCertBuf, pState->aCertDecodedBuf)) + { + if ((iResult = ProtoSSLSetCACert((uint8_t *)pState->aCertDecodedBuf, (int32_t)strlen((const char*)pState->aCertDecodedBuf))) >= 0) + { + iCount += iResult; + } + else + { + *pFailed += 1; + } + } + } + else + { + // plain, install the cert + if ((iResult = ProtoSSLSetCACert((uint8_t *)pState->aCertBuf, iCertLen)) > 0) + { + iCount += iResult; + } + else + { + *pFailed += 1; + } + } + } + pCert = XmlNext(pCert); + } + return(iCount); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyCertUpdate + + \Description + Update status of DirtyCert module. + + \Input *pData - pointer to DirtyCert module ref + + \Notes + This is be the only private 'static' function that needs to acquire crit + because it's actually called from NetIdle thread. + + \Version 01/23/2012 (szhu) +*/ +/********************************************************************************F*/ +static void _DirtyCertUpdate(void *pData) +{ + DirtyCertRefT *pState = (DirtyCertRefT*)pData; + DirtyCertCARequestT *pRequest; + + /* attempt to acquire critical section. there is dependency between this crit + and the idle crit, we want to prevent a deadlock in those conditions */ + if (!NetCritTry(&pState->crit)) + { + return; + } + + if (pState->iRequestId != DIRTYCERT_REQUESTID_NONE) + { + pRequest = &pState->requests[pState->iRequestId]; + + if (pRequest->eStatus == RS_IN_PROGRESS) + { + int32_t iHttpStatus; + // update http + ProtoHttpUpdate(pState->pHttp); + // if we've done with current http request + if ((iHttpStatus = ProtoHttpStatus(pState->pHttp, 'done', NULL, 0)) > 0) + { + // check http response code, we only deal with "20X OK" + if ((ProtoHttpStatus(pState->pHttp, 'code', NULL, 0) / 100) == 2) + { + int32_t iRecvLen, iFailed; + // zero buf + ds_memclr(pState->aResponseBuf, sizeof(pState->aResponseBuf)); + if ((iRecvLen = ProtoHttpRecvAll(pState->pHttp, (char*)pState->aResponseBuf, sizeof(pState->aResponseBuf))) > 0) + { + // install received certs + iRecvLen = _DirtyCertProcessResponse(pState, pState->aResponseBuf, iRecvLen, &iFailed); + pRequest->eStatus = RS_DONE; + pState->iRequestId = DIRTYCERT_REQUESTID_NONE; + NetPrintf(("dirtycert: %d cert(s) installed for %s (%d failed)\n", iRecvLen, + _DirtyCertCAGetStrIdent(pState, pRequest), iFailed)); + } + } + + // unknown error? (ex. http 500 error) + if (pRequest->eStatus != RS_DONE) + { + iHttpStatus = -1; + NetPrintf(("dirtycert: CA fetch request failed (httpcode=%d)\n", ProtoHttpStatus(pState->pHttp, 'code', NULL, 0))); + } + } + else if (iHttpStatus == 0) + { + // if we're missing CA cert for our http ref, we fail. + if (ProtoHttpStatus(pState->pHttp, 'cfip', NULL, 0) > 0) + { + // mark request as failed but don't reset pState->iRequestId otherwise a new request may be issued + pRequest->eStatus = RS_FAILED; + NetPrintf(("dirtycert: CA fetch request failed because of no CA available for redirector: %s\n", _DirtyCert_strRedirectorUrl)); + } + } + + // if we failed (or timed-out) + if (iHttpStatus < 0) + { + pRequest->eStatus = RS_FAILED; + pState->iRequestId = DIRTYCERT_REQUESTID_NONE; + NetPrintf(("dirtycert: CA fetch request for %s %s\n", _DirtyCertCAGetStrIdent(pState, pRequest), + ProtoHttpStatus(pState->pHttp, 'time', NULL, 0) ? "timeout" : "failed")); + } + + // prefetch requests are executed only once, and automatically clean up after themselves upon completion + if ((pRequest->eType == RT_PREFETCH) && (iHttpStatus != 0)) + { + pState->bPreload = FALSE; + _DirtyCertCARequestFree(pState, pRequest, pState->iRequestId); + } + } + } + + // if we need to issue a new request? + if ((pState->iRequestId == DIRTYCERT_REQUESTID_NONE) && (pState->iCount > 0)) + { + int32_t iRequestId; + for (iRequestId = 0; iRequestId < DIRTYCERT_MAXREQUESTS; iRequestId++) + { + pRequest = &pState->requests[iRequestId]; + if ((pRequest->iRefCount > 0) && (pRequest->eStatus == RS_NOT_STARTED)) + { + if (_DirtyCertCreateRequest(pState, iRequestId, pRequest) == 0) + { + break; + } + } + } + } + + // release critical section + NetCritLeave(&pState->crit); +} + + +/*** Public functions ********************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function DirtyCertCreate + + \Description + Startup DirtyCert module. + + \Output int32_t - 0 for success, negative for error + + \Version 01/23/2012 (szhu) +*/ +/*************************************************************************************************F*/ +int32_t DirtyCertCreate(void) +{ + DirtyCertRefT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + + if (_DirtyCert_pState != NULL) + { + NetPrintf(("dirtycert: DirtyCertCreate() called while module is already active\n")); + return(-1); + } + + // decrypt url + if (_DirtyCertUrlSetup() < 0) + { + NetPrintf(("dirtycert: error setting up url; dirtycert startup failure\n")); + return(-2); + } + + // query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pState = DirtyMemAlloc(sizeof(*pState), DIRTYCERT_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtycert: could not allocate module state\n")); + return(-3); + } + ds_memclr(pState, sizeof(*pState)); + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + pState->bPreload = TRUE; + + // create http ref + if ((pState->pHttp = ProtoHttpCreate(DIRTYCERT_MAXRESPONSE)) == NULL) + { + DirtyMemFree(pState, DIRTYCERT_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + return(-4); + } + // no request + pState->iRequestId = DIRTYCERT_REQUESTID_NONE; + + // set request timeout + pState->uTimeout = DIRTYCERT_TIMEOUT; + + // init crit + NetCritInit(&pState->crit, "DirtyCert"); + + // add update function to netidle handler + NetIdleAdd(_DirtyCertUpdate, pState); + + // save module ref + _DirtyCert_pState = pState; + + NetPrintf(("dirtycert: created dirtycert 0x%p (pHttp=%p)\n", pState, pState->pHttp)); + // return module state + return(0); +} + +/*F************************************************************************/ +/*! + \Function DirtyCertDestroy + + \Description + Destroy the module and release its state. + + \Output int32_t - 0 for success, negative for error + + \Version 01/23/2012 (szhu) +*/ +/*************************************************************************F*/ +int32_t DirtyCertDestroy(void) +{ + DirtyCertRefT *pState = _DirtyCert_pState; + + // error if not active + if (pState == NULL) + { + NetPrintf(("dirtycert: DirtyCertDestroy() called while module is not active\n")); + return(-1); + } + + // remove idle handler + NetIdleDel(_DirtyCertUpdate, pState); + + // clear global module ref + _DirtyCert_pState = NULL; + + // destroy http ref + ProtoHttpDestroy(pState->pHttp); + + // destroy crit + NetCritKill(&pState->crit); + + // free the memory + DirtyMemFree(pState, DIRTYCERT_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + + NetPrintf(("dirtycert: freed dirtycert %p\n", pState)); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function DirtyCertCARequestCert + + \Description + Initialize a CA fetch request. + + \Input *pCertInfo - CA Cert Info + \Input *pHost - host we are fetching CA for + \Input iPort - port we are fetching CA for + + \Output + int32_t - positive=id of request, negative=error + + \Version 01/23/2012 (szhu) +*/ +/********************************************************************************F*/ +int32_t DirtyCertCARequestCert(const ProtoSSLCertInfoT *pCertInfo, const char *pHost, int32_t iPort) +{ + DirtyCertRefT *pState = _DirtyCert_pState; + int32_t i, iSlot = -1; + + // error if not active + if (pState == NULL) + { + NetPrintf(("dirtycert: DirtyCertCARequestCert() called while module is not active\n")); + return(-1); + } + + // acquire critical section + NetCritEnter(&pState->crit); + + // require a valid service name + if (_DirtyCertValidateServiceName(pState->strServiceName) < 0) + { + NetCritLeave(&pState->crit); + return(-2); + } + + // if we haven't successfully executed pre-load yet, slot in a pre-load request first + if (pState->bPreload) + { + DirtyCertCAPreloadCerts(pState->strServiceName); + } + + // check if it matches any in-progress request + for (i = 0; i < DIRTYCERT_MAXREQUESTS; i++) + { + // if this is an empty slot + if (pState->requests[i].iRefCount <= 0) + { + // mark it + if (iSlot < 0) + { + iSlot = i; + } + continue; + } + + // we found an in-progress request with the same cert info + if (_DirtyCertCompareCertInfo(pCertInfo, &pState->requests[i].CertInfo) == 0) + { + iSlot = i; + break; + } + } + + if (iSlot >= 0) + { + DirtyCertCARequestT *pRequest = &pState->requests[iSlot]; + // a CA request for this cert is already queued + if (pRequest->iRefCount > 0) + { + // inc refcount + pRequest->iRefCount++; + NetPrintf(("dirtycert: CA fetch request %s already queued (status=%d), updated refcount to %d.\n", + _DirtyCertCAGetStrIdent(pState, pRequest), (int32_t)pRequest->eStatus, pRequest->iRefCount)); + } + // no matching request, create a new one + else + { + ds_memclr(pRequest, sizeof(*pRequest)); + // set request type and status + pRequest->eType = RT_ONDEMAND; + pRequest->eStatus = RS_NOT_STARTED; + // save host info + ds_strnzcpy(pRequest->strHost, pHost, sizeof(pRequest->strHost)); + pRequest->iPort = iPort; + // save cert info + ds_memcpy_s(&pRequest->CertInfo, sizeof(pRequest->CertInfo), pCertInfo, sizeof(*pCertInfo)); + // set refcount + pRequest->iRefCount = 1; + pState->iCount++; + NetPrintf(("dirtycert: queued CA fetch request for %s\n", _DirtyCertCAGetStrIdent(pState, pRequest))); + _DirtyCertCreateRequest(pState, iSlot, pRequest); + } + // public slot reference is +1 to reserve zero + iSlot += 1; + } + else + { + NetPrintf(("dirtycert: too many CA fetch requests.\n")); + } + + // release critical section + NetCritLeave(&pState->crit); + + // return slot ref to caller + return(iSlot); +} + +/*F********************************************************************************/ +/*! + \Function DirtyCertCAPreloadCerts + + \Description + Initialize a CA preload request + + \Input *pServiceName - servicename to identify CA preload set ("gamename-gameyear-platform") + + \Notes + Unlike the explicit CA load, a preload request is fire and forget, and + will clean up after itself when complete. + + \Version 04/18/2012 (jbrookes) +*/ +/********************************************************************************F*/ +void DirtyCertCAPreloadCerts(const char *pServiceName) +{ + DirtyCertRefT *pState = _DirtyCert_pState; + int32_t i, iSlot = -1; + + // error if not active + if (pState == NULL) + { + NetPrintf(("dirtycert: DirtyCertCARequestCert() called while module is not active\n")); + return; + } + + // require a valid servicename + if (_DirtyCertValidateServiceName(pServiceName) < 0) + { + return; + } + + // acquire critical section + NetCritEnter(&pState->crit); + + // if there is already a prefetch queued, ignore this request + for (i = 0; i < DIRTYCERT_MAXREQUESTS; i++) + { + if ((pState->requests[i].iRefCount > 0) && (pState->requests[i].eType == RT_PREFETCH)) + { + NetPrintf(("dirtycert: CA prefetch request already queued; request ignored\n")); + NetCritLeave(&pState->crit); + return; + } + } + + // save service name (required for all request types) + _DirtyCertSetServiceName(pState, pServiceName); + + // find an empty slot + for (i = 0; i < DIRTYCERT_MAXREQUESTS; i++) + { + // if this is an empty slot + if (pState->requests[i].iRefCount <= 0) + { + // mark it + if (iSlot < 0) + { + iSlot = i; + break; + } + } + } + + if (iSlot >= 0) + { + DirtyCertCARequestT *pRequest = &pState->requests[iSlot]; + + ds_memclr(pRequest, sizeof(*pRequest)); + // set request type and status + pRequest->eType = RT_PREFETCH; + pRequest->eStatus = RS_NOT_STARTED; + // set refcount + pRequest->iRefCount = 1; + pState->iCount++; + + NetPrintf(("dirtycert: queued CA prefetch request for %s\n", _DirtyCertCAGetStrIdent(pState, pRequest))); + _DirtyCertCreateRequest(pState, iSlot, pRequest); + } + else + { + NetPrintf(("dirtycert: too many CA fetch requests.\n")); + } + + // release critical section + NetCritLeave(&pState->crit); +} + +/*F*************************************************************************************/ +/*! + \Function DirtyCertCARequestDone + + \Description + Determine if a CA fetch request is complete. + + \Input iRequestId - id of CA request + + \Output + int32_t - zero=in progess, neg=done w/error, pos=done w/success + + \Version 01/23/2012 (szhu) +*/ +/************************************************************************************F*/ +int32_t DirtyCertCARequestDone(int32_t iRequestId) +{ + DirtyCertRefT *pState = _DirtyCert_pState; + DirtyCertCARequestT *pRequest; + int32_t iResult = 0; + + // internal request id is public id - 1 + iRequestId -= 1; + + // error if not active + if (pState == NULL) + { + NetPrintf(("dirtycert: DirtyCertCARequestDone() called while module is not active\n")); + return(-1); + } + // if it's a valid request id + if ((iRequestId < 0) || (iRequestId >= DIRTYCERT_MAXREQUESTS)) + { + NetPrintf(("dirtycert: invalid request id (%d)\n", iRequestId)); + return(-2); + } + + // acquire critical section + NetCritEnter(&pState->crit); + + pRequest = &pState->requests[iRequestId]; + if (pRequest->iRefCount <= 0) + { + // error request id? + iResult = -3; + } + else if (pRequest->eStatus == RS_FAILED) + { + iResult = -4; + } + else if (pRequest->eStatus == RS_DONE) + { + iResult = 1; + } + + // release critical section + NetCritLeave(&pState->crit); + + return(iResult); +} + +/*F*************************************************************************************/ +/*! + \Function DirtyCertCARequestFree + + \Description + Release resources used by a CA fetch request. + + \Input iRequestId - id of CA request + + \Output int32_t - negative=error, else success + + \Version 01/23/2012 (szhu) +*/ +/************************************************************************************F*/ +int32_t DirtyCertCARequestFree(int32_t iRequestId) +{ + DirtyCertRefT *pState = _DirtyCert_pState; + int32_t iResult; + + // internal request id is public id - 1 + iRequestId -= 1; + + // error if not active + if (pState == NULL) + { + NetPrintf(("dirtycert: DirtyCertCARequestFree() called while module is not active\n")); + return(-1); + } + // if it's a valid request id + if ((iRequestId < 0) || (iRequestId >= DIRTYCERT_MAXREQUESTS)) + { + NetPrintf(("dirtycert: invalid request id (%d)\n", iRequestId)); + return(-2); + } + + // acquire critical section + NetCritEnter(&pState->crit); + + // free the request + iResult = _DirtyCertCARequestFree(pState, &pState->requests[iRequestId], iRequestId); + + // release critical section + NetCritLeave(&pState->crit); + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function DirtyCertControl + + \Description + Set module behavior based on input selector. + + \Input iControl - input selector + \Input iValue - selector input + \Input iValue2 - selector input + \Input *pValue - selector input + + \Output + int32_t - selector result + + \Notes + iControl can be one of the following: + + \verbatim + SELECTOR DESCRIPTION + 'prld' Enables/disables preload + 'snam' Sets service name to use for on-demand requests + 'time' Sets CA request timeout in milliseconds (default=30s) + \endverbatim + + \Version 01/24/2012 (szhu) +*/ +/********************************************************************************F*/ +int32_t DirtyCertControl(int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + DirtyCertRefT *pState = _DirtyCert_pState; + int32_t iResult = -100; + + // error if not active + if (pState == NULL) + { + NetPrintf(("dirtycert: DirtyCertControl() called while module is not active\n")); + return(-1); + } + + // acquire critical section + NetCritEnter(&pState->crit); + + // set preload enable/disable + if (iControl == 'prld') + { + NetPrintf(("dirtycert: %s CA preload\n", (iValue != 0) ? "enabling" : "disabling")); + pState->bPreload = (iValue != 0) ? TRUE : FALSE; + iResult = 0; + } + // set service name + if (iControl == 'snam') + { + _DirtyCertSetServiceName(pState, (const char *)pValue); + iResult = 0; + } + // set timeout + if (iControl == 'time') + { + NetPrintf(("dirtycert: setting timeout to %d ms\n", iValue)); + pState->uTimeout = (unsigned)iValue; + iResult = 0; + } + + // release critical section + NetCritLeave(&pState->crit); + + // unhandled? + if (iResult == -100) + { + NetPrintf(("dirtycert: unhandled control option '%C'\n", iControl)); + iResult = -1; + } + + // return control result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function DirtyCertStatus + + \Description + Get module status + + \Input iStatus - info selector (see notes) + \Input *pBuffer - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector result + + \Notes + iStatus can be one of the following: + + \verbatim + SELECTOR DESCRIPTION + 'snam' Stores service name in output buffer (if not NULL); returns validity + \endverbatim + + \Version 03/25/2013 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyCertStatus(int32_t iStatus, void *pBuffer, int32_t iBufSize) +{ + DirtyCertRefT *pState = _DirtyCert_pState; + int32_t iResult = -100; + + // error if not active + if (pState == NULL) + { + NetPrintf(("dirtycert: DirtyCertStatus() called while module is not active\n")); + return(-1); + } + + // acquire critical section + NetCritEnter(&pState->crit); + + // get service name, return whether it is valid or not + if (iStatus == 'snam') + { + if (pBuffer != NULL) + { + ds_strnzcpy(pBuffer, pState->strServiceName, iBufSize); + } + iResult = _DirtyCertValidateServiceName(pState->strServiceName); + } + + // release critical section + NetCritLeave(&pState->crit); + + // unhandled? + if (iResult == -100) + { + NetPrintf(("dirtycert: unhandled status option '%C'\n", iStatus)); + iResult = -1; + } + + // return control result to caller + return(iResult); +} + diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtyerr.c b/src/thirdparty/dirtysdk/source/dirtysock/dirtyerr.c new file mode 100644 index 00000000..41dadb9d --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtyerr.c @@ -0,0 +1,105 @@ +/*H********************************************************************************/ +/*! + \File dirtyerr.c + + \Description + Dirtysock platform independent debug error routines. + + \Copyright + Copyright (c) 2014 Electronic Arts Inc. + + \Version 09/16/2014 (cvienneau) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtyerr.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function DirtyErrGetHResult + + \Description + create a unique error code for use across DirtySDK + + \Input uFacility - the module id the hResult is being generated for (only bottom 11 bits) + \Input iCode - the error code + \Input bFailure - true if the code represents a failure + + \Output uint32_t - hResult value + + \Notes + \verbatim + Description of HRESULT: + http://msdn.microsoft.com/en-us/library/cc231198.aspx + Bit [ 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07 06, 05, 04, 03, 02, 01, 00] + Field[ S, R, C, N, X, - - - - - - - - - - - FACILITY - - - - - , - - - - - - - - - - - - - - - - CODE - - - - - - - - - - - - ] + S - Severity - indicates success/fail; 0 - Success, 1 - Failure + R - Reserved portion of the facility code, corresponds to NT's second severity bit; 1 - Severe Failure + C - Customer. This bit specifies if the value is customer-defined or Microsoft-defined; 0 - Microsoft-defined, 1 - Customer-defined + N - Reserved portion of the facility code. Used to indicate a mapped NT status value. + X - Reserved portion of the facility code. Reserved for internal use. Used to indicate HRESULT values that are not status values, but are instead message ids for display strings. + FACILITY - indicates the system service that is responsible for the error. + CODE - is the facility's status code + \endverbatim + \Version 09/16/2014 (cvienneau) +*/ +/********************************************************************************F*/ +uint32_t DirtyErrGetHResult(uint16_t uFacility, int16_t iCode, uint8_t bFailure) +{ + uint32_t hResult = 0; + + if (bFailure) + { + hResult |= 0x80000000; //set the "Severity" bit, usually we use an hResult to describe failures so this is the usual case + } + hResult |= 0x20000000; //set the "Customer" bit, we aren't MS so we'll always set this + uFacility &= ~(0xF800); //the top 5 bits of the facility passed in do not belong to the user, we will clear them out so they don't mess up the upper bits (we could maybe assert this) + hResult |= (uFacility << 16); //set the "FACILITY", the module producing the error + hResult |= (uint16_t)iCode; //set the "CODE", the error id + return(hResult); +} + +/*F********************************************************************************/ +/*! + \Function DirtyErrDecodeHResult + + \Description + break a hresult back into its components + + \Input hResult - the hresult to be decoded + \Input pFacility - return the module id the hResult is being generated for + \Input pCode - return the error code + \Input pCustomer - return true if the customer bit is set (note DS will always set the customer bit in DirtyErrGetHResult). + \Input pFailure - return true if the code represents a failure + + \Version 01/17/2017 (cvienneau) +*/ +/********************************************************************************F*/ +void DirtyErrDecodeHResult(uint32_t hResult, uint16_t* pFacility, int16_t* pCode, uint8_t* pCustomer, uint8_t* pFailure) +{ + if (pFailure != NULL) + *pFailure = (hResult & 0x80000000) >> 31; + + if (pCustomer != NULL) + *pCustomer = (hResult & 0x20000000) >> 29; + + if (pFacility != NULL) + *pFacility = (hResult & 0x7FF0000) >> 16; + + if (pCode != NULL) + *pCode = (hResult & 0x0000FFFF); +} \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtylib.c b/src/thirdparty/dirtysdk/source/dirtysock/dirtylib.c new file mode 100644 index 00000000..0d5ec652 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtylib.c @@ -0,0 +1,899 @@ +/*H********************************************************************************/ +/*! + \File dirtylib.c + + \Description + Platform independent routines for support library for network code. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 09/15/1999 (gschaefer) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/dirtysock.h" + +#if defined(DIRTYCODE_PC) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) +#include +#endif + +/*** Defines **********************************************************************/ + +// max size of line that will be tracked for netprint rate limiting +#define NETPRINT_RATELIMIT_MAXSTRINGLEN (256) + +/*** Type Definitions *************************************************************/ + +// rate limit info +typedef struct NetPrintRateLimitT +{ + NetCritT RateCrit; + uint32_t uRateLimitCounter; + uint32_t uPrintTime; + uint8_t bRateLimitInProgress; + char strRateLimitBuffer[NETPRINT_RATELIMIT_MAXSTRINGLEN+32]; +} NetPrintRateLimitT; + +/*** Variables ********************************************************************/ + +#if DIRTYCODE_LOGGING +// time stamp functionality +static uint8_t _NetLib_bEnableTimeStamp = TRUE; + +// rate limit variables +static NetPrintRateLimitT _NetLib_RateLimit; +static uint8_t _NetLib_bRateLimitInitialized = FALSE; + +// debugging hooks +static void *_NetLib_pDebugParm = NULL; +static int32_t (*_NetLib_pDebugHook)(void *pParm, const char *pText) = NULL; +#endif + +// idle critical section +static NetCritT _NetLib_IdleCrit; + +// idle task list +static struct +{ + void (*pProc)(void *pRef); + void *pRef; +} _NetLib_IdleList[32]; + +// number of installed idle task handlers +static int32_t _NetLib_iIdleSize = 0; + +//! table for calculating classic CRC-32 +static const uint32_t _NetLib_Crc32Table[256] = +{ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + + +/*** Private Functions ************************************************************/ + + +#if DIRTYCODE_LOGGING +/*F*************************************************************************************************/ +/*! + \Function _NetPrintRateLimit + + \Description + Rate limit NetPrintf output; identical lines of fewer than 256 characters that are + line-terminated (end in a \n) will be suppressed, until a new line of text is output or + a 100ms timer elapses. At that point the line will be output, with additional text + indicating the number of lines that would have been output in total. A critical section + guarantees integrity of the buffer and tracking information, but it is only tried to prevent + any possibility of deadlock. In the case of a critical section try failure, rate limit + processing is skipped until the next print statement. + + \Input *pText - output text to (potentially) rate limit + + \Output + int32_t - zero=not rate limited (line should be printed), one=rate limited (do not print) + + \Version 04/05/2016 (jbrookes) +*/ +/*************************************************************************************************F*/ +static int32_t _NetPrintRateLimit(const char *pText) +{ + NetPrintRateLimitT *pRateLimit = &_NetLib_RateLimit; + int32_t iTextLen = (int32_t)strlen(pText); + uint32_t uCurTick = NetTick(), uDiffTick = 0; + int32_t iStrCmp, iResult = 0; + /* rate limit IFF: + - output is relatively small & line-terminated + - we have initialized rate limiting + - we are not printing rate-limited text ourselves + - we can secure access to the critical section (MUST COME LAST!) */ + if ((iTextLen >= NETPRINT_RATELIMIT_MAXSTRINGLEN) || (pText[iTextLen-1] != '\n') || !_NetLib_bRateLimitInitialized || pRateLimit->bRateLimitInProgress || !NetCritTry(&pRateLimit->RateCrit)) + { + return(iResult); + } + // does the string match our current string? + if ((iStrCmp = strcmp(pText, pRateLimit->strRateLimitBuffer)) == 0) + { + // if yes, calculate tick difference between now and when the line was buffered + uDiffTick = NetTickDiff(uCurTick, pRateLimit->uPrintTime); + } + // if we have a line we are rate-limiting, and either the timeout has elapsed or there is a new line + if ((pRateLimit->uRateLimitCounter > 1) && ((uDiffTick > 100) || (iStrCmp != 0))) + { + // print the line, tagging on how many times it was printed + iTextLen = (int32_t)strlen(pRateLimit->strRateLimitBuffer); + pRateLimit->strRateLimitBuffer[iTextLen-1] = '\0'; + pRateLimit->bRateLimitInProgress = TRUE; + NetPrintfCode("%s (%d times)\n", pRateLimit->strRateLimitBuffer, pRateLimit->uRateLimitCounter-1); + pRateLimit->bRateLimitInProgress = FALSE; + // set compare to non-matching to force restart below + iStrCmp = 1; + } + // if this line doesn't match our current buffered line, buffer it + if (iStrCmp != 0) + { + ds_strnzcpy(pRateLimit->strRateLimitBuffer, pText, sizeof(pRateLimit->strRateLimitBuffer)); + pRateLimit->uRateLimitCounter = 1; + pRateLimit->uPrintTime = NetTick(); + } + else + { + // match, so increment rate counter and suppress the line + pRateLimit->uRateLimitCounter += 1; + iResult = 1; + } + NetCritLeave(&pRateLimit->RateCrit); + return(iResult); +} +#endif + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function NetTimeStamp + + \Description + A printf function that returns a date time string for time stamp. + Only if time stamps is enabled + + \Input *pBuffer - pointer to format time stamp string + \Input iLen - length of the supplied buffer + + \Output + int32_t - return the length of the Time Stamp String + + \Version 09/10/2013 (tcho) +*/ +/********************************************************************************F*/ +static int32_t _NetTimeStamp(char *pBuffer, int32_t iLen) +{ + int32_t iTimeStampLen = 0; + + if (_NetLib_bEnableTimeStamp == TRUE) + { + struct tm tm; + int32_t imsec; + + ds_plattimetotimems(&tm, &imsec); + iTimeStampLen = ds_snzprintf(pBuffer, iLen,"%d/%02d/%02d-%02d:%02d:%02d.%03.3d ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, imsec); + } + + return(iTimeStampLen); +} +#endif + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function NetLibCommonInit + + \Description + NetLib platform-common initialization + + \Version 04/05/2016 (jbrookes) +*/ +/********************************************************************************F*/ +void NetLibCommonInit(void) +{ + // reset the idle list + NetIdleReset(); + + // initialize critical sections + NetCritInit(NULL, "lib-global"); + NetCritInit(&_NetLib_IdleCrit, "lib-idle"); + + // set up rate limiting + #if DIRTYCODE_LOGGING + ds_memclr(&_NetLib_RateLimit, sizeof(_NetLib_RateLimit)); + NetCritInit(&_NetLib_RateLimit.RateCrit, "lib-rate"); + _NetLib_bRateLimitInitialized = TRUE; + #endif +} + +/*F********************************************************************************/ +/*! + \Function NetLibCommonShutdown + + \Description + NetLib platform-common shutdown + + \Version 04/05/2016 (jbrookes) +*/ +/********************************************************************************F*/ +void NetLibCommonShutdown(void) +{ + // kill critical sections + #if DIRTYCODE_LOGGING + _NetLib_bRateLimitInitialized = FALSE; + NetCritKill(&_NetLib_RateLimit.RateCrit); + #endif + NetCritKill(&_NetLib_IdleCrit); + NetCritKill(NULL); +} + +/*F********************************************************************************/ +/*! + \Function NetIdleReset + + \Description + Reset idle function count. + + \Version 06/21/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void NetIdleReset(void) +{ + _NetLib_iIdleSize = 0; +} + +/*F********************************************************************************/ +/*! + \Function NetIdleAdd + + \Description + Add a function to the idle callback list. The functions are called whenever + NetIdleCall() is called. + + \Input *pProc - callback function pointer + \Input *pRef - function specific parameter + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +void NetIdleAdd(void (*pProc)(void *pRef), void *pRef) +{ + // make sure proc is valid + if (pProc == NULL) + { + NetPrintf(("dirtylib: attempt to add an invalid idle function\n")); + return; + } + + // add item to list + _NetLib_IdleList[_NetLib_iIdleSize].pProc = pProc; + _NetLib_IdleList[_NetLib_iIdleSize].pRef = pRef; + _NetLib_iIdleSize += 1; +} + +/*F********************************************************************************/ +/*! + \Function NetIdleDel + + \Description + Remove a function from the idle callback list. + + \Input *pProc - callback function pointer + \Input *pRef - function specific parameter + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +void NetIdleDel(void (*pProc)(void *pRef), void *pRef) +{ + int32_t iProc; + + // make sure proc is valid + if (pProc == NULL) + { + NetPrintf(("dirtylib: attempt to delete an invalid idle function\n")); + return; + } + + // mark item as deleted + for (iProc = 0; iProc < _NetLib_iIdleSize; ++iProc) + { + if ((_NetLib_IdleList[iProc].pProc == pProc) && (_NetLib_IdleList[iProc].pRef == pRef)) + { + _NetLib_IdleList[iProc].pProc = NULL; + _NetLib_IdleList[iProc].pRef = NULL; + break; + } + } +} + +/*F********************************************************************************/ +/*! + \Function NetIdleDone + + \Description + Make sure all idle calls have completed + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +void NetIdleDone(void) +{ + NetCritEnter(&_NetLib_IdleCrit); + NetCritLeave(&_NetLib_IdleCrit); +} + +/*F********************************************************************************/ +/*! + \Function NetIdleCall + + \Description + Call all of the functions in the idle list. + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +void NetIdleCall(void) +{ + int32_t iProc; + + // only do idle call if we have control + if (NetCritTry(&_NetLib_IdleCrit)) + { + // walk the table calling routines + for (iProc = 0; iProc < _NetLib_iIdleSize; ++iProc) + { + // get pProc pointer + void (*pProc)(void *pRef) = _NetLib_IdleList[iProc].pProc; + void *pRef = _NetLib_IdleList[iProc].pRef; + + /* if pProc is deleted, handle removal here (this + helps prevent corrupting table in race condition) */ + if (pProc == NULL) + { + // swap with final element + _NetLib_IdleList[iProc].pProc = _NetLib_IdleList[_NetLib_iIdleSize-1].pProc; + _NetLib_IdleList[iProc].pRef = _NetLib_IdleList[_NetLib_iIdleSize-1].pRef; + _NetLib_IdleList[_NetLib_iIdleSize-1].pProc = NULL; + _NetLib_IdleList[_NetLib_iIdleSize-1].pRef = NULL; + + // drop the item count + _NetLib_iIdleSize -= 1; + + // restart the loop at new current element + --iProc; + continue; + } + // perform the idle call + (*pProc)(pRef); + } + // done with critical section + NetCritLeave(&_NetLib_IdleCrit); + } +} + +/*F********************************************************************************/ +/*! + \Function NetHash + + \Description + Calculate a unique 32-bit hash based on the given input string. + + \Input *pString - pointer to string to calc hash of + + \Output + int32_t - resultant 32bit hash + + \Version 2.0 07/26/2011 (jrainy) rewrite to lower collision rate +*/ +/********************************************************************************F*/ +int32_t NetHash(const char *pString) +{ + return(NetHashBin(pString, (uint32_t)strlen(pString))); +} + +/*F********************************************************************************/ +/*! + \Function NetHashBin + + \Description + Calculate a unique 32-bit hash based on the given buffer. + + \Input *pBuffer - pointer to buffer to calc hash of + \Input *uLength - length of buffer to calc hash of + + \Output + int32_t - resultant 32bit hash + + \Version 1.0 02/14/2011 (jrainy) First Version +*/ +/********************************************************************************F*/ +int32_t NetHashBin(const void *pBuffer, uint32_t uLength) +{ + // prime factor to multiply by at each 16-char block boundary + static uint32_t uShift = 436481627; + + // prime factors to multiply individual characters by + static uint32_t uFactors[16] = { + 682050377, 933939593, 169587707, 131017121, + 926940523, 102453581, 543947221, 775968049, + 129461173, 793216343, 870352919, 455044847, + 747808279, 727551509, 431178773, 519827743}; + + // running hash + uint32_t uSum = 0; + uint32_t uChar; + + for (uChar = 0; uChar != uLength; uChar++) + { + // at each 16-byte boundary, multiply the running hash by fixed factor + if ((uChar & 0xf) == 0) + { + uSum *= uShift; + } + // sum up the value of the char at position iChar by prime factor iChar%16 + uSum += ((uint8_t)((char*)pBuffer)[uChar]) * uFactors[uChar & 0xf]; + } + + return((int32_t)uSum); +} + + +/*F********************************************************************************/ +/*! + \Function NetCrc32 + + \Description + Calculate CRC32 of specified data. If no table is specified, the default + table is used. + + \Input *pBuffer - buffer to calculate crc32 + \Input iBufLen - length of buffer + \Input *pCrcTable - crc32 table to use, or NULL for the default + + \Output + int32_t - CRC32 of input data + + \Version 01/17/2019 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetCrc32(const uint8_t *pBuffer, int32_t iBufLen, const uint32_t *pCrcTable) +{ + uint32_t uCrc32; + // use default table if none specified + if (pCrcTable == NULL) + { + pCrcTable = _NetLib_Crc32Table; + } + // calculate crc + for (uCrc32 = 0xffffffff; iBufLen > 0; iBufLen -= 1) + { + uCrc32 = (uCrc32 >> 8) ^ pCrcTable[(uCrc32 ^ *pBuffer++) & 0xff]; + } + // return to caller + return(uCrc32 ^ 0xffffffff); +} + +/*F*************************************************************************************************/ +/*! + \Function NetRand + + \Description + A simple pseudo-random sequence generator. The sequence is implicitly seeded in the first + call with the millisecond tick count at the time of the call + + \Input uLimit - upper bound of pseudo-random number output + + \Output + uint32_t - pseudo-random number from [0...(uLimit - 1)] + + \Version 06/25/2009 (jbrookes) +*/ +/*************************************************************************************************F*/ +uint32_t NetRand(uint32_t uLimit) +{ + static uint32_t _aRand = 0; + if (_aRand == 0) + { + _aRand = NetTick(); + } + if (uLimit == 0) + { + return(0); + } + _aRand = (_aRand * 125) % 2796203; + return(_aRand % uLimit); +} + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function NetTimeStampEnableCode + + \Description + Enables Time Stamp in Logging + + \Input bEnableTimeStamp - TRUE to enable Time Stamp in Logging + + \Version 9/11/2014 (tcho) +*/ +/********************************************************************************F*/ +void NetTimeStampEnableCode(uint8_t bEnableTimeStamp) +{ + _NetLib_bEnableTimeStamp = bEnableTimeStamp; +} +#endif + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function NetPrintfHook + + \Description + Hook into debug output. + + \Input *pPrintfDebugHook - pointer to function to call with debug output + \Input *pParm - user parameter + + \Version 03/29/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void NetPrintfHook(int32_t (*pPrintfDebugHook)(void *pParm, const char *pText), void *pParm) +{ + _NetLib_pDebugHook = pPrintfDebugHook; + _NetLib_pDebugParm = pParm; +} +#endif + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function NetPrintfCode + + \Description + Debug formatted output + + \Input *pFormat - pointer to format string + \Input ... - variable argument list + + \Output + int32_t - zero + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t NetPrintfCode(const char *pFormat, ...) +{ + va_list pFmtArgs; + char strText[4096]; + const char *pText = strText; + int32_t iOutput = 1; + int32_t iTimeStampLen = 0; + + // init the string buffer (not done at instantiation so as to avoid having to clear the entire array, for perf) + strText[0] = '\0'; + + // only returns time stamp if time stamp is enabled + iTimeStampLen = _NetTimeStamp(strText, sizeof(strText)); + + // format the text + va_start(pFmtArgs, pFormat); + if ((pFormat[0] == '%') && (pFormat[1] == 's') && (pFormat[2] == '\0')) + { + ds_strnzcat(strText + iTimeStampLen, va_arg(pFmtArgs, const char *), sizeof(strText) - iTimeStampLen); + } + else + { + ds_vsnprintf(strText + iTimeStampLen, sizeof(strText) - iTimeStampLen, pFormat, pFmtArgs); + } + va_end(pFmtArgs); + + // check for rate limit (omit timestamp from consideration) + if (_NetPrintRateLimit(strText+iTimeStampLen)) + { + return(0); + } + + // forward to debug hook, if defined + if (_NetLib_pDebugHook != NULL) + { + iOutput = _NetLib_pDebugHook(_NetLib_pDebugParm, pText); + } + + // output to debug output, unless suppressed by debug hook + if (iOutput != 0) + { + #if defined(DIRTYCODE_PC) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + OutputDebugStringA(pText); + #else + printf("%s", pText); + #endif + } + + return(0); +} +#endif + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function NetPrintfVerboseCode + + \Description + Display input data if iVerbosityLevel is > iCheckLevel + + \Input iVerbosityLevel - current verbosity level + \Input iCheckLevel - level to check against + \Input *pFormat - format string + + \Version 12/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void NetPrintfVerboseCode(int32_t iVerbosityLevel, int32_t iCheckLevel, const char *pFormat, ...) +{ + va_list Args; + char strText[1024]; + + if (iVerbosityLevel > iCheckLevel) + { + va_start(Args, pFormat); + ds_vsnprintf(strText, sizeof(strText), pFormat, Args); + va_end(Args); + + NetPrintf(("%s", strText)); + } +} +#endif + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function NetPrintWrapCode + + \Description + Display input data with wrapping. + + \Input *pString - pointer to packet data to display + \Input iWrapCol - number of columns to wrap at + + \Version 09/15/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void NetPrintWrapCode(const char *pString, int32_t iWrapCol) +{ + const char *pTemp, *pEnd, *pEqual, *pSpace; + char strTemp[256] = " "; + uint32_t bDone; + int32_t iLen; + + // loop through whole packet + for (bDone = FALSE; bDone == FALSE; ) + { + // scan forward, tracking whitespace, linefeeds, and equal signs + for (pTemp=pString, pEnd=pTemp+iWrapCol, pSpace=NULL, pEqual=NULL; (pTemp < pEnd); pTemp++) + { + // remember most recent whitespace + if ((*pTemp == ' ') || (*pTemp == '\t')) + { + pSpace = pTemp; + } + + // remember most recent equal sign + if (*pTemp == '=') + { + pEqual = pTemp; + } + + // if eol or eos, break here + if ((*pTemp == '\n') || (*pTemp == '\0')) + { + break; + } + } + + // scanned an entire line? + if (pTemp == pEnd) + { + // see if we have whitespace to break on + if (pSpace != NULL) + { + pTemp = pSpace; + } + // see if we have an equals to break on + else if (pEqual != NULL) + { + pTemp = pEqual; + } + } + + // format string for output + iLen = (int32_t)(pTemp - pString + 1); + strncpy(strTemp + 3, pString, iLen); + if (*pTemp == '\0') + { + strTemp[iLen+2] = '\n'; + strTemp[iLen+3] = '\0'; + bDone = TRUE; + } + else if ((*pTemp != '\n') && (*pTemp != '\r')) + { + strTemp[iLen+3] = '\n'; + strTemp[iLen+4] = '\0'; + } + else + { + strTemp[iLen+3] = '\0'; + } + + // print it out + NetPrintf(("%s", strTemp)); + + // increment to next line + pString += iLen; + } +} +#endif // #if DIRTYCODE_LOGGING + +#if DIRTYCODE_LOGGING +/*F*************************************************************************************************/ +/*! + \Function NetPrintMemCode + + \Description + Dump memory to debug output + + \Input *pMem - pointer to memory to dump + \Input iSize - size of memory block to dump + \Input *pTitle - pointer to title of memory block + + \Version 1.0 05/17/05 (jbrookes) First Version +*/ +/*************************************************************************************************F*/ +void NetPrintMemCode(const void *pMem, int32_t iSize, const char *pTitle) +{ + static const char _hex[] = "0123456789ABCDEF"; + char strOutput[128]; + int32_t iBytes, iOutput = 2; + + ds_memset(strOutput, ' ', sizeof(strOutput)-1); + strOutput[sizeof(strOutput)-1] = '\0'; + + NetPrintf(("dirtylib: dumping memory for object %s (%d bytes)\n", pTitle, iSize)); + + for (iBytes = 0; iBytes < iSize; iBytes++, iOutput += 2) + { + unsigned char cByte = ((unsigned char *)pMem)[iBytes]; + strOutput[iOutput] = _hex[cByte>>4]; + strOutput[iOutput+1] = _hex[cByte&0xf]; + strOutput[(iOutput/2)+40] = isprint(cByte) ? cByte : '.'; + if (iBytes > 0) + { + if (((iBytes+1) % 16) == 0) + { + strOutput[(iOutput/2)+40+1] = '\0'; + NetPrintf(("%s\n", strOutput)); + ds_memset(strOutput, ' ', sizeof(strOutput)-1); + strOutput[sizeof(strOutput)-1] = '\0'; + iOutput = 0; + } + else if (((iBytes+1) % 4) == 0) + { + iOutput++; + } + } + } + + if ((iBytes % 16) != 0) + { + strOutput[(iOutput/2)+40+1] = '\0'; + NetPrintf(("%s\n", strOutput)); + } +} +#endif + +#if DIRTYCODE_LOGGING +/*F*************************************************************************************************/ +/*! + \Function NetPrintArrayCode + + \Description + Dump memory to debug output in the form of a c-style array declaration + + \Input *pMem - pointer to memory to dump + \Input iSize - size of memory block to dump + \Input *pTitle - pointer to title of memory block + + \Version 06/05/2014 (jbrookes) +*/ +/*************************************************************************************************F*/ +void NetPrintArrayCode(const void *pMem, int32_t iSize, const char *pTitle) +{ + static const char _hex[] = "0123456789ABCDEF"; + char strOutput[128]; + int32_t iBytes, iOutput = 4; + + ds_memset(strOutput, ' ', sizeof(strOutput)-1); + strOutput[sizeof(strOutput)-1] = '\0'; + + NetPrintf(("dirtylib: dumping declaration for object %s (%d bytes)\n", pTitle, iSize)); + NetPrintf(("static const uint8_t %s[] =\n", pTitle)); + NetPrintf(("{\n")); + + for (iBytes = 0; iBytes < iSize; iBytes++) + { + uint8_t cByte = ((uint8_t *)pMem)[iBytes]; + strOutput[iOutput+0] = '0'; + strOutput[iOutput+1] = 'x'; + strOutput[iOutput+2] = _hex[cByte>>4]; + strOutput[iOutput+3] = _hex[cByte&0xf]; + strOutput[iOutput+4] = ','; + iOutput += 5; + if (iBytes > 0) + { + if (((iBytes+1) % 16) == 0) + { + strOutput[iOutput] = '\0'; + NetPrintf(("%s\n", strOutput)); + ds_memset(strOutput, ' ', sizeof(strOutput)-1); + strOutput[sizeof(strOutput)-1] = '\0'; + iOutput = 4; + } + else if (((iBytes+1) % 4) == 0) + { + iOutput++; + } + } + } + + if ((iBytes % 16) != 0) + { + strOutput[iOutput] = '\0'; + NetPrintf(("%s\n", strOutput)); + } + + NetPrintf(("};\n")); +} +#endif diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtylib.cpp b/src/thirdparty/dirtysdk/source/dirtysock/dirtylib.cpp new file mode 100644 index 00000000..2c2aeb91 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtylib.cpp @@ -0,0 +1,230 @@ +/*H********************************************************************************/ +/*! + + \File dirtylib.cpp + + \Description + Platform specific support library for network code. Suppplies + simple time, memory, and semaphore functions. + + \Copyright + Copyright (c) Electronic Arts 2002-2018. ALL RIGHTS RESERVED. + + \Version 01/02/02 (eesponda) Initial C++ version ported to use EAThread +*/ +/********************************************************************************H*/ + +#include "eathread/eathread_mutex.h" + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/dirtysock/dirtylib.h" + +/*** Type Definitions ************************************************************/ + +// critical section state +struct NetCritPrivT +{ + EA::Thread::Mutex Mutex; //!< mutex state, must come first + + uint8_t bEnabled; //!< controls if this lock is enabled + uint8_t _pad[3]; + + int32_t iMemGroup; //!< memory group identifier + void *pMemGroupUserdata; //!< user data passed along with allocation +}; + +/*** Variables *******************************************************************/ + +// global critical section +static NetCritPrivT _NetLib_GlobalCrit; + +// sets the functions to no-op in this situation +#if defined(DIRTYCODE_LINUX) +extern uint8_t _NetLib_bSingleThreaded; +#else +static uint8_t _NetLib_bSingleThreaded = FALSE; +#endif + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function NetCritInit + + \Description + Initialize a critical section for use. Allocates persistant storage + for the lifetime of the critical section if not using the global crit. + + \Input *pCrit - critical section marker + \Input *pCritName - name of the critical section + + \Output + int32_t - zero=success, negative=failure + + \Version 09/26/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t NetCritInit(NetCritT *pCrit, const char *pCritName) +{ + return(NetCritInit2(pCrit, pCritName, NETCRIT_OPTION_NONE)); +} + +/*F********************************************************************************/ +/*! + \Function NetCritInit2 + + \Description + Initialize a critical section for use. Allocates persistant storage + for the lifetime of the critical section if not using the global crit. + + \Input *pCrit - critical section marker + \Input *pCritName - name of the critical section + \Input uFlags - NETCRIT_OPTIONS_* flag options to set on the crit + + \Output + int32_t - zero=success, negative=failure + + \Version 07/24/2018 (eesponda) +*/ +/********************************************************************************F*/ +int32_t NetCritInit2(NetCritT *pCrit, const char *pCritName, uint32_t uFlags) +{ + NetCritPrivT *pPriv; + EA::Thread::MutexParameters Params; + ds_strnzcpy(Params.mName, pCritName, sizeof(Params.mName)); + Params.mbIntraProcess = true; + + // allocate memory if necessary + if (pCrit != NULL) + { + int32_t iMemGroup; + void *pMemGroupUserdata; + + // query memgroup info + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserdata); + + // allocate the memory needed for mutex state + if ((pCrit->pData = (NetCritPrivT *)DirtyMemAlloc(sizeof(*pCrit->pData), DIRTYTHREAD_MEMID, iMemGroup, pMemGroupUserdata)) == NULL) + { + return(-1); + } + ds_memclr(pCrit->pData, sizeof(*pCrit->pData)); + pCrit->pData->iMemGroup = iMemGroup; + pCrit->pData->pMemGroupUserdata = pMemGroupUserdata; + } + /* we always clear the single-thread enable option on the global crit. it would be counter productive if it was + enabled when we are in single-threaded mode */ + else + { + uFlags &= ~NETCRIT_OPTION_SINGLETHREADENABLE; + } + + pPriv = (pCrit ? pCrit->pData : &_NetLib_GlobalCrit); + pPriv->Mutex.Init(&Params); + pPriv->bEnabled = !_NetLib_bSingleThreaded || ((uFlags & NETCRIT_OPTION_SINGLETHREADENABLE) == NETCRIT_OPTION_SINGLETHREADENABLE); + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetCritKill + + \Description + Release resources and destroy critical section. + + \Input *pCrit - critical section marker + + \Version 09/26/2017 (eesponda) +*/ +/********************************************************************************F*/ +void NetCritKill(NetCritT *pCrit) +{ + EA::Thread::Mutex *pMutex = (pCrit ? &pCrit->pData->Mutex : &_NetLib_GlobalCrit.Mutex); + pMutex->~Mutex(); + + // if we are not using the global crit free the memory + if (pCrit != NULL) + { + NetCritPrivT *pPriv = pCrit->pData; + DirtyMemFree(pPriv, DIRTYTHREAD_MEMID, pPriv->iMemGroup, pPriv->pMemGroupUserdata); + pCrit->pData = NULL; + } +} + +/*F********************************************************************************/ +/*! + \Function NetCritEnter + + \Description + Enter a critical section, blocking if needed. + + \Input *pCrit - critical section marker + + \Version 09/26/2017 (eesponda) +*/ +/********************************************************************************F*/ +void NetCritEnter(NetCritT *pCrit) +{ + NetCritPrivT *pPriv = (pCrit ? pCrit->pData : &_NetLib_GlobalCrit); + if (pPriv->bEnabled) + { + pPriv->Mutex.Lock(); + } +} + +/*F********************************************************************************/ +/*! + \Function NetCritTry + + \Description + Attempt to gain access to critical section. Always returns immediately + regadless of access status. A thread that already has access to a critical + section can always receive repeated access to it. + + \Input *pCrit - critical section marker + + \Output + int32_t - zero=unable to get access, non-zero=access granted + + \Version 09/26/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t NetCritTry(NetCritT *pCrit) +{ + NetCritPrivT *pPriv = (pCrit ? pCrit->pData : &_NetLib_GlobalCrit); + if (pPriv->bEnabled) + { + return(pPriv->Mutex.Lock(EA::Thread::kTimeoutImmediate) > 0); + } + else + { + return(1); + } +} + +/*F********************************************************************************/ +/*! + \Function NetCritLeave + + \Description + Leave a critical section. Must be called once for every NetCritEnter (or + successful NetCritTry). + + \Input *pCrit - critical section marker + + \Version 09/26/2017 (eesponda) +*/ +/********************************************************************************F*/ +void NetCritLeave(NetCritT *pCrit) +{ + NetCritPrivT *pPriv = (pCrit ? pCrit->pData : &_NetLib_GlobalCrit); + if (pPriv->bEnabled) + { + pPriv->Mutex.Unlock(); + } +} + diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtymem.c b/src/thirdparty/dirtysdk/source/dirtysock/dirtymem.c new file mode 100644 index 00000000..30035549 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtymem.c @@ -0,0 +1,278 @@ +/*H********************************************************************************/ +/*! + \File dirtymem.c + + \Description + DirtySock memory allocation routines. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 10/12/2005 (jbrookes) First Version + \Version 11/19/2008 (mclouatre) Adding pMemGroupUserData to mem groups +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +/*** Defines **********************************************************************/ + +#define DIRTYMEM_MAXGROUPS (16) + +/*** Type Definitions *************************************************************/ + +typedef struct DirtyMemInfoT +{ + int32_t iMemGroup; + void *pMemGroupUserData; +} DirtyMemInfoT; + + +/*** Variables ********************************************************************/ + +static int32_t _DirtyMem_iGroup = 0; +static DirtyMemInfoT _DirtyMem_iGroupStack[DIRTYMEM_MAXGROUPS] = { {'dflt', NULL} }; + +//#if defined(DIRTYCODE_DLL) + +static DirtyMemFreeT *_DirtyMem_pMemFreeFunc = NULL; +static DirtyMemAllocT *_DirtyMem_pMemAllocFunc = NULL; + +//#endif + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function DirtyMemGroupEnter + + \Description + Set group that will be used for allocations. + + \Input iMemGroup - group id + \Input *pMemGroupUserData - User-provided data pointer + + \Output + None. + + \Version 10/13/2005 (jbrookes) + \Version 11/19/2008 (mclouatre) Adding pMemGroupUserData to mem groups +*/ +/********************************************************************************F*/ +void DirtyMemGroupEnter(int32_t iMemGroup, void *pMemGroupUserData) +{ + // DIRTYMEM_MAXGROUPS - 1: the first slot is occupied by 'dflt' + if (_DirtyMem_iGroup >= (DIRTYMEM_MAXGROUPS - 1)) + { + NetPrintf(("dirtymem: group stack overflow\n")); + return; + } + _DirtyMem_iGroup += 1; + _DirtyMem_iGroupStack[_DirtyMem_iGroup].iMemGroup = iMemGroup; + _DirtyMem_iGroupStack[_DirtyMem_iGroup].pMemGroupUserData = pMemGroupUserData; +} + +/*F********************************************************************************/ +/*! + \Function DirtyMemGroupLeave + + \Description + Restore previous group that will be used for allocations. + + \Version 10/13/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void DirtyMemGroupLeave(void) +{ + if (_DirtyMem_iGroup <= 0) + { + NetPrintf(("dirtymem: group stack underflow\n")); + return; + } + _DirtyMem_iGroup -= 1; +} + +/*F********************************************************************************/ +/*! + \Function DirtyMemGroupQuery + + \Description + Return current memory group data. + + \Input *pMemGroup - [OUT param] pointer to variable to be filled with mem group id + \Input **ppMemGroupUserData - [OUT param] pointer to variable to be filled with pointer to user data + + \Output + None. + + \Version 10/13/2005 (jbrookes) + \Version 11/18/2008 (mclouatre) returned values now passed in [OUT] parameters +*/ +/********************************************************************************F*/ +void DirtyMemGroupQuery(int32_t *pMemGroup, void **ppMemGroupUserData) +{ + if (pMemGroup != NULL) + { + *pMemGroup = _DirtyMem_iGroupStack[_DirtyMem_iGroup].iMemGroup; + } + if (ppMemGroupUserData != NULL) + { + *ppMemGroupUserData = _DirtyMem_iGroupStack[_DirtyMem_iGroup].pMemGroupUserData; + } +} + +/*F********************************************************************************/ +/*! + \Function DirtyMemDebugAlloc + + \Description + Display memory allocation information to debug output. + + \Input *pMem - address of memory being freed + \Input iSize - size of allocation + \Input iMemModule - memory module + \Input iMemGroup - memory group + \Input *pMemGroupUserData - pointer to user data + + \Output + None. + + \Version 10/13/2005 (jbrookes) + \Version 11/18/2008 (mclouatre) adding pMemGroupUserData parameter +*/ +/********************************************************************************F*/ +#if DIRTYCODE_DEBUG +void DirtyMemDebugAlloc(void *pMem, int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +{ + NetPrintf(("dirtymem: [a] %p mod=%C grp=%C udataptr=%p size=%d\n", pMem, iMemModule, iMemGroup, pMemGroupUserData, iSize)); +} +#endif + +/*F********************************************************************************/ +/*! + \Function DirtyMemDebugFree + + \Description + Display memory free information to debug output. + + \Input *pMem - address of memory being freed + \Input iSize - size of allocation (if available), or zero + \Input iMemModule - memory module + \Input iMemGroup - memory group + \Input *pMemGroupUserData - pointer to user data + + \Output + None. + + \Version 10/13/2005 (jbrookes) + \Version 11/18/2008 (mclouatre) adding pMemGroupUserData parameter +*/ +/********************************************************************************F*/ +#if DIRTYCODE_DEBUG +void DirtyMemDebugFree(void *pMem, int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +{ + NetPrintf(("dirtymem: [f] %p mod=%C grp=%C udataptr=%p size=%d\n", pMem, iMemModule, iMemGroup, pMemGroupUserData, iSize)); +} +#endif + +//#if defined(DIRTYCODE_DLL) + +/*F********************************************************************************/ +/*! + \Function DirtyMemFuncSet + + \Description + This is only avaliable in the DLL mode. + Set Memory Allocate and Free functions that DirtySDK will use. + If any of the parameters are NULL it will use the default implementation + + \Input *pMemAlloc - function pointer to Mem Alloc function + \Input *pMemFree - function pointer to Mem Free function + + \Output + None. + + \Version 3/14/2014 (tcho) +*/ +/********************************************************************************F*/ +void DirtyMemFuncSet(DirtyMemAllocT *pMemAlloc, DirtyMemFreeT *pMemFree) +{ + _DirtyMem_pMemFreeFunc = pMemFree; + _DirtyMem_pMemAllocFunc = pMemAlloc; +} + + +/*F********************************************************************************/ +/*! + \Function DirtyMemAlloc + + \Description + Only Avaliable in DLL mode. + Implementation of the required DirtySock memory allocation routine. + + \Input iSize - size of memory to allocate + \Input iMemModule - memory module id + \Input iMemGroup - memory group id + \Input *pMemGroupUserData - user data associated with mem group + + \Output + void * - pointer to newly allocated memory, or NULL + + \Version 3/14/2014 (tcho) +*/ +/********************************************************************************F*/ +void *DirtyMemAlloc(int32_t iSize, int32_t iMemModule, int32_t iMemGroup, void * pMemGroupUserData) +{ + void *pMem = NULL; + + if (_DirtyMem_pMemAllocFunc != NULL) + { + pMem = _DirtyMem_pMemAllocFunc(iSize, iMemModule, iMemGroup, pMemGroupUserData); + } + else + { + pMem = (void *)malloc(iSize); + } + + return(pMem); +} + +/*F********************************************************************************/ +/*! + \Function DirtyMemFree + + \Description + Only Avaliable in DLL mode. + Implementation of the required DirtySock memory free routine. + + \Input *pMem - pointer to memory block to free + \Input iMemModule - memory module id + \Input iMemGroup - memory group id + \Input *pMemGroupUserData - user data associated with mem group + + \Output + None. + + \Version 3/14/2014 (tcho) +*/ +/********************************************************************************F*/ +void DirtyMemFree(void *pMem, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +{ + if (_DirtyMem_pMemFreeFunc != NULL) + { + _DirtyMem_pMemFreeFunc(pMem, iMemModule, iMemGroup, pMemGroupUserData); + } + else + { + free(pMem); + } +} + +//#endif diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtynames.c b/src/thirdparty/dirtysdk/source/dirtysock/dirtynames.c new file mode 100644 index 00000000..15d242c4 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtynames.c @@ -0,0 +1,246 @@ +/*H*************************************************************************************************/ +/*! + + \File dirtynames.c + + \Description + This module provides helper functions for manipulating persona and master + account name strings. + + \Notes + None. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002-2003. ALL RIGHTS RESERVED. + + \Version 1.0 12/10/02 (DBO) First Version + +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtynames.h" + +/*** Defines ***************************************************************************/ + +#ifndef TRUE +#define TRUE (1) +#define FALSE (0) +#endif + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +static const unsigned char xlat[256] ={ + 0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f, + 0x60,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x7b,0x7c,0x7d,0x7e,0x01, + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01 + }; + +// Public variables + + +/*** Private Functions *****************************************************************/ + +/*F************************************************************************************************/ +/*! + \Function toXlatIndex + + \Description + Converts a char to xlat table index using its ascii char representation. + Characters outside the range [0, 255] are treated as 255. + + \Input str - Input char + + \Output xlat index - int32_t (0 to 255) +*/ +/************************************************************************************************F*/ +int32_t toXlatIndex(const char str) +{ + int32_t xlatIndex = (int32_t)str; + return (xlatIndex >= 0 && xlatIndex < 256) ? xlatIndex : 255; +} + +/*** Public Functions ******************************************************************/ + + +/*F************************************************************************************************/ +/*! + \Function DirtyUsernameCompare + + \Description + Compare two master account names. Ignore case and non-printable ascii characters. + + \Input pName1 - Input account name 1 + \Input pName2 - Input account name 2 + + \Output int32_t - < 0 = pName1 < pName2 + = 0 = pName1 = pName2 + > 0 = pName1 > pName2 + + \Version 1.0 05/01/2003 (DBO) Taken from LobbyHasher (HashStrCmp) +*/ +/************************************************************************************************F*/ +int32_t DirtyUsernameCompare(const char *pName1, const char *pName2) +{ + int32_t iCmp; + unsigned char cCh1, cCh2; + + // compare the strings + do { + // grab the data + while ((cCh1 = xlat[toXlatIndex(*pName1++)]) == 1) + ; + while ((cCh2 = xlat[toXlatIndex(*pName2++)]) == 1) + ; + // see if different + if ((iCmp = cCh1-cCh2) != 0) + return(iCmp); + } while (cCh1 != 0); + + // they are the same + return(0); +} + + +/*F************************************************************************************************/ +/*! + \Function DirtyUsernameSubstr + + \Description + Determine if pMatch is a substring of pSrc. Ignore case and non-printable ascii characters. + + \Input pSrc - Location to store converted name + \Input pMatch - Input account name + + \Output int32_t - TRUE if pMatch is a substring of pSrc; FALSE otherwise + + \Version 1.0 05/02/2003 (DBO) Initial version. +*/ +/************************************************************************************************F*/ +int32_t DirtyUsernameSubstr ( const char *pSrc, const char *pMatch ) +{ + int32_t iSrcIdx; + int32_t iMatchIdx1; + int32_t iMatchIdx2; + char cCh1; + char cCh2; + + if ( (pMatch == NULL) || (pMatch[0] == '\0') ) + return(TRUE); + if ( (pSrc == NULL) || (pSrc[0] == '\0') ) + return(FALSE); + + for(iSrcIdx = 0; pSrc[iSrcIdx] != '\0'; iSrcIdx++) + { + iMatchIdx1 = iSrcIdx; + iMatchIdx2 = 0; + + do + { + // Skip over ignored characters + while ( (cCh1 = xlat[toXlatIndex(pSrc[iMatchIdx1++])]) == 1 ) + ; + while ( (cCh2 = xlat[toXlatIndex(pMatch[iMatchIdx2++])]) == 1 ) + ; + + if ( cCh2 == 0 ) + return(TRUE); + } + while ( (cCh1 != 0) && (cCh1 == cCh2) ); + } + return(FALSE); +} + +/*F************************************************************************************************/ +/*! + \Function DirtyNameCreateCanonical + + \Description + Create the canonical form of the given name. This essentially lowercases the name and + strips non-printable ascii characters as per the xlat table. + + \Input pName - The input name to create the canonical name from. + \Input pCanonical - The buffer to output the canonical name into + \Input uLen - The length of pCanonical + + \Output int32_t - 0 on success; -1 if the output buffer is too small +*/ +/************************************************************************************************F*/ +int32_t DirtyNameCreateCanonical(const char *pName, char * pCanonical, size_t uLen) +{ + unsigned char cCh; + + while (uLen > 0) + { + // Strip the ignored characters + while ((cCh = xlat[toXlatIndex(*pName++)]) == 1) + ; + + if ((cCh >= 'A') && (cCh <= 'Z')) + cCh |= 32; + *pCanonical++ = cCh; + uLen--; + if (cCh == 0) + return 0; + } + return -1; +} + +/*F************************************************************************************************/ +/*! + \Function DirtyUsernameHash + + \Description + Generate the hash code for the given persona name. This hashing function will hash the + canonical form of the name rather than the raw bytes to ensure that equivalent names + always hash to the same value, as per the xlat table. + + \Input pName - The input name to generate a hash code. + + \Output uint32_t - The generated hash code. +*/ +/************************************************************************************************F*/ +uint32_t DirtyUsernameHash(const char *pName) +{ + unsigned char cCh; + uint32_t result = 2166136261U; // FNV1 hash. Perhaps the best string hash. + + do + { + // Strip the ignored characters + while ((cCh = xlat[toXlatIndex(*pName++)]) == 1) + ; + + result = (result * 16777619) ^ cCh; + + } while (cCh != 0); + + return result; +} + diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtynet.c b/src/thirdparty/dirtysdk/source/dirtysock/dirtynet.c new file mode 100644 index 00000000..fcf4a767 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtynet.c @@ -0,0 +1,2777 @@ +/*H*************************************************************************************************/ +/*! + \File dirtynet.c + + \Description + Platform-independent network related routines. + + \Copyright + Copyright (c) Electronic Arts 2002-2018 + + \Version 1.0 01/02/2002 (gschaefer) First Version + \Version 1.1 01/27/2003 (jbrookes) Split from dirtynetwin.c +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtynet.h" + +#include "dirtynetpriv.h" + +/*** Defines ***************************************************************************/ + +//! 30s hostname cache timeout +#define DIRTYNET_HOSTNAMECACHELIFETIME (30*1000) + +//! verbose logging of dirtynet packet queue operations +#define DIRTYNET_PACKETQUEUEDEBUG (DIRTYCODE_LOGGING && FALSE) + +//! verbose logging of dirtynet rate estimation +#define DIRTYNET_RATEDEBUG (DIRTYCODE_LOGGING && FALSE) + +//! maximum allowable packet queue size +#define DIRTYNET_PACKETQUEUEMAX (1024) + +//! minimum throttle rate supported +#define DIRTYNET_MIN_THROTTLE_RATE (1460) + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! socket hostname cache entry +typedef struct SocketHostnameCacheEntryT +{ + char strDnsName[256]; + uint32_t uAddress; + uint32_t uTimer; +} SocketHostnameCacheEntryT; + +//! socket hostname cache +struct SocketHostnameCacheT +{ + int32_t iMaxEntries; + int32_t iMemGroup; + void *pMemGroupUserData; + NetCritT Crit; + SocketHostnameCacheEntryT CacheEntries[1]; //!< variable-length cache entry list +}; + +//! socket packet queue +struct SocketPacketQueueT +{ + int32_t iMemGroup; + void *pMemGroupUserData; + + int16_t iNumPackets; //!< number of packets in the queue + int16_t iMaxPackets; //!< max queue size + int16_t iPacketHead; //!< current packet queue head + int16_t iPacketTail; //!< current packet queue tail + + uint32_t uLatency; //!< simulated packet latency target, in milliseconds + uint32_t uDeviation; //!< simulated packet deviation target, in milliseconds + uint32_t uPacketLoss; //!< packet loss percentage, 16.16 fractional integer + + uint32_t uPacketDrop; //!< number of packets overwritten due to queue overflow + uint32_t uPacketMax; //!< maximum number of packets in the queue (high water mark) + + uint32_t uLatencyTime; //!< current amount of latency in the packet queue + int32_t iDeviationTime; //!< current deviation + + SocketPacketQueueEntryT aPacketQueue[1]; //!< variable-length queue entry list +}; + +#ifndef DIRTYCODE_NX +//! socket address map entry +struct SocketAddrMapEntryT +{ + int32_t iRefCount; + int32_t iVirtualAddress; + struct sockaddr_in6 SockAddr6; +}; +#endif + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + + +// Public variables + + +/*** Private Functions *****************************************************************/ + +#ifndef DIRTYCODE_NX +/*F********************************************************************************/ +/*! + \Function _SockaddrIn6Identify + + \Description + Identify IPv6 sockaddr based on specified matching sequence (must come at + the start of address bytes). + + \Input *pAddr6 - address to identify + \Input *pCheckVal - bytes to check + \Input iValLen - length of byte sequence to check + + \Output + uint8_t - TRUE if the address matches, else FALSE + + \Version 04/12/2016 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t _SockaddrIn6Identify(const struct sockaddr_in6 *pAddr6, const uint8_t *pCheckVal, int32_t iValLen) +{ + uint8_t bResult = !memcmp(pCheckVal, pAddr6->sin6_addr.s6_addr, iValLen) ? TRUE : FALSE; + return(bResult); +} + +/*F********************************************************************************/ +/*! + \Function _SockaddrIn6IsIPv4 + + \Description + Identify if specified sockaddr_in6 is an IPv4-mapped IPv6 address + + \Input *pAddr6 - address to identify + + \Output + uint8_t - TRUE if the address is IPv4-mapped, else FALSE + + \Version 04/12/2016 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t _SockaddrIn6IsIPv4(const struct sockaddr_in6 *pAddr6) +{ + const uint8_t aIpv4Prefix[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; + return(_SockaddrIn6Identify(pAddr6, aIpv4Prefix, sizeof(aIpv4Prefix))); +} + +/*F********************************************************************************/ +/*! + \Function _SockaddrIn6IsNAT64 + + \Description + Identify if specified sockaddr_in6 is a NAT64 IPv6 address + + \Input *pAddr6 - address to identify + + \Output + uint8_t - TRUE if the address is NAT64, else FALSE + + \Version 04/12/2016 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t _SockaddrIn6IsNAT64(const struct sockaddr_in6 *pAddr6) +{ + const uint8_t aNat64Prefix[] = { 0x00, 0x64, 0xff, 0x9b }; + return(_SockaddrIn6Identify(pAddr6, aNat64Prefix, sizeof(aNat64Prefix))); +} + +/*F********************************************************************************/ +/*! + \Function _SockaddrIn6IsZero + + \Description + Identify if specified sockaddr_in6 is zero (unspecified) + + \Input *pAddr6 - address to identify + + \Output + uint8_t - TRUE if the address is zero, else FALSE + + \Version 04/22/2016 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t _SockaddrIn6IsZero(const struct sockaddr_in6 *pAddr6) +{ + const uint8_t aIpv6Zero[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + return(_SockaddrIn6Identify(pAddr6, aIpv6Zero, sizeof(aIpv6Zero))); +} + +/*F*************************************************************************************************/ +/*! +\Function _SockaddrIn6SetAddrText + +\Description +Set Internet address component of sockaddr_in6 struct from textural address. + +\Input *pAddr6 - pointer to ipv6 address +\Input *pStr - pointer to source ipv6 address + +\Output +int32_t - zero=no error, negative=error + +\Notes +See _SockaddrIn6GetAddrText for format considerations and references + +\Version 08/28/2017 (jbrookes) +*/ +/*************************************************************************************************F*/ +static int32_t _SockaddrIn6SetAddrText(struct sockaddr_in6 *pAddr6, const char *pStr) +{ + uint16_t aAddrWords[8], *pWords, uWordVal; + int32_t iWordIdx, iWordCt, iSkipIdx, iWriteIdx; + const char *pEnd, *pDot; + uint32_t uAddr32 = 0; + + // see if we have dot notation (e.g. nat64, ipv4-mapped) + if ((pDot = strchr(pStr, '.')) != NULL) + { + // find start of dot notation number + for (pDot -= 1; isalnum(*pDot) && (pDot > pStr); pDot -= 1) + ; + // get 32 bit address encoded in dot notation + uAddr32 = SocketInTextGetAddr(pDot + 1); + } + + // init word array + ds_memclr(aAddrWords, sizeof(aAddrWords)); + + // convert address words, remember if we had a skip + for (iWordIdx = 0, iSkipIdx = -1; (*pStr != '\0') && (iWordIdx < 8); pStr += 1) + { + uWordVal = SocketHtons(strtol(pStr, (char **)&pEnd, 16)); + pStr = pEnd; + + if ((*pStr == ':') || (*pStr == '\0')) + { + aAddrWords[iWordIdx++] = uWordVal; + } + if ((*pStr == ':') && (pStr[1] == ':')) + { + iSkipIdx = iWordIdx; + pStr += 1; + } + if ((pStr == pDot) && (uAddr32 != 0)) + { + aAddrWords[iWordIdx++] = SocketHtons((uint16_t)(uAddr32 >> 16)); + aAddrWords[iWordIdx++] = SocketHtons((uint16_t)(uAddr32 >> 0)); + break; + } + + if (*pStr == '\0') + { + break; + } + } + + // copy to sockaddr, with skip + for (iWordCt = iWordIdx, iWordIdx = iWriteIdx = 0, pWords = (uint16_t *)pAddr6->sin6_addr.s6_addr; iWriteIdx < 8; ) + { + if (iWriteIdx == iSkipIdx) + { + for (iWordCt = 8 - iWordCt; iWordCt > 0; iWordCt -= 1) + { + pWords[iWriteIdx++] = 0; + } + } + else + { + pWords[iWriteIdx++] = aAddrWords[iWordIdx++]; + } + } + + return(0); +} +/*F*************************************************************************************************/ +/*! + \Function _SockaddrIn6GetAddrText + + \Description + Return Internet address component of sockaddr_in6 struct in textual form (see below). + + \Input *pAddr6 - pointer to ipv6 address + \Input *pStr - pointer to storage for text address + \Input iLen - length of buffer + + \Output + char * - returns pStr on success, NULL on failure + + \Notes + IPv6 textual format guidelines are outlined in https://tools.ietf.org/html/rfc4291#section-2.2 + and later refined in https://tools.ietf.org/html/rfc5952, which narrows the range of + supported options to standardize are more rigid specification. This implementation + follows the more limited set of specifications outlined in rfc5952. The short version is: + + - Preferred form is x:x:x:x:x:x:x:x, where the 'x's are one of four hexadecimal digits of + the eight 16-bit pieces of the address + - Leading zeros within a field MUST be suppressed + - The use of :: indicates one or more groups of 16 bits of zeros; it can appear only once + in an address + - The :: symbole MUST be used to its maximum capability. It MUST NOT be used to shorten + just one 16-bit field + - If there are two or more sets of 16-bit zeros of equal length, the left-most MUST be + shortened + - Alphabetic characters in the address MUST be represented in lowercase + - It is RECOMMENDED to use mixed notation if the address can be distinguished as having + an IPv4 address embedded in the lower 32 bits solely from the address field through + the use of a well-known prefix. + + \Version 08/28/2017 (jbrookes) +*/ +/*************************************************************************************************F*/ +static char *_SockaddrIn6GetAddrText(const struct sockaddr_in6 *pAddr6, char *pStr, int32_t iLen) +{ + uint16_t aAddrWords[8], *pWords; + int32_t iWord, iOffset; + int32_t iZeroStart, iZeroCount; + int32_t iZeroStartTmp, iZeroCountTmp; + char strAddr32[16] = ""; + + // convert to words in host format + for (iWord = 0, pWords = (uint16_t *)pAddr6->sin6_addr.s6_addr; iWord < 8; iWord += 1) + { + aAddrWords[iWord] = SocketNtohs(pWords[iWord]); + } + + // if this is a mixed-notation address convert the final 32bits to a dot-notation address string + if (_SockaddrIn6IsIPv4(pAddr6) || _SockaddrIn6IsNAT64(pAddr6)) + { + uint32_t uAddr32 = (uint32_t)aAddrWords[6] << 16 | (uint32_t)aAddrWords[7]; + SocketInAddrGetText(uAddr32, strAddr32, sizeof(strAddr32)); + } + + // find longest stretch of two or more zeros (if there is one) + for (iWord = iZeroStart = iZeroCount = iZeroCountTmp = iZeroStartTmp = 0; iWord < 8; iWord += 1) + { + if (aAddrWords[iWord] == 0) + { + if (iZeroCountTmp == 0) + { + iZeroStartTmp = iWord; + } + iZeroCountTmp += 1; + } + else + { + iZeroCountTmp = 0; + } + + if (iZeroCountTmp > iZeroCount) + { + iZeroStart = iZeroStartTmp; + iZeroCount = iZeroCountTmp; + } + } + + // format address string + for (iWord = 0, iOffset = 0; iWord < 8; iWord += 1) + { + if ((iWord != iZeroStart) || (iZeroCount < 2)) + { + iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, "%x", aAddrWords[iWord]); + if (iWord < 7) + { + iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, ":"); + } + } + else + { + iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, (iWord == 0) ? "::" : ":"); + iWord += iZeroCount - 1; + } + + // output dot portion of mixed-notation address, if present + if ((strAddr32[0] != '\0') && (iWord == 5)) + { + iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, "%s", strAddr32); + iWord += 2; + } + } + + // return to caller + return(pStr); +} + +#endif +/*F*************************************************************************************************/ +/*! + \Function _SockaddrIn4SetAddrText + + \Description + Set Internet address component of sockaddr struct from textual address (a.b.c.d). + + \Input *pAddr - sockaddr structure + \Input *pStr - textual address + + \Output + int32_t - zero=no error, negative=error + + \Version 10/04/1999 (gschaefer) +*/ +/*************************************************************************************************F*/ +static int32_t _SockaddrIn4SetAddrText(struct sockaddr *pAddr, const char *pStr) +{ + uint8_t *pIpAddr = (uint8_t *)(pAddr->sa_data+2); + int32_t iOctet; + + for (iOctet = 0; iOctet < 4; iOctet += 1, pStr += 1) + { + pIpAddr[iOctet] = '\0'; + while ((*pStr >= '0') && (*pStr <= '9')) + { + pIpAddr[iOctet] = (pIpAddr[iOctet]*10) + (*pStr++ & 15); + } + if ((iOctet < 3) && (*pStr != '.')) + { + pIpAddr[0] = pIpAddr[1] = pIpAddr[2] = pIpAddr[3] = '\0'; + return(-1); + } + } + + return(0); +} + + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function SockaddrCompare + + \Description + Compare two sockaddr structs and see if address is the same. This is different + from simple binary compare because only relevent fields are checked. + + \Input *pAddr1 - address #1 + \Input *pAddr2 - address to compare with address #1 + + \Output + int32_t - zero=no error, negative=error + + \Version 08/30/1999 (gschaefer) +*/ +/*************************************************************************************************F*/ +int32_t SockaddrCompare(const struct sockaddr *pAddr1, const struct sockaddr *pAddr2) +{ + int32_t len = sizeof(*pAddr1)-sizeof(pAddr1->sa_family); + + // make sure address family matches + if (pAddr1->sa_family != pAddr2->sa_family) + { + return(pAddr1->sa_family- pAddr2->sa_family); + } + + // do type specific comparison + if (pAddr1->sa_family == AF_INET) + { + // compare port and address + len = 2 + 4; + } + else if (pAddr1->sa_family == AF_INET6) + { + // compare port, flow info, and address + len = 2 + 4 + 8; + } + + // binary compare of address data + return(memcmp(pAddr1->sa_data, pAddr2->sa_data, len)); +} + +/*F*************************************************************************************************/ +/*! + \Function SockaddrInSetAddrText + + \Description + Set Internet address component of sockaddr struct from textual address. + Important: Only works with AF_INET addresses for nx + + \Input *pAddr - sockaddr structure + \Input *pStr - textual address + + \Output + int32_t - zero=no error, negative=error + + \Version 10/04/1999 (gschaefer) +*/ +/*************************************************************************************************F*/ +int32_t SockaddrInSetAddrText(struct sockaddr *pAddr, const char *pStr) +{ + int32_t iResult = -1; + if (pAddr->sa_family == AF_INET) + { + iResult = _SockaddrIn4SetAddrText(pAddr, pStr); + } + #ifndef DIRTYCODE_NX + else if (pAddr->sa_family == AF_INET6) + { + iResult = _SockaddrIn6SetAddrText((struct sockaddr_in6 *)pAddr, pStr); + } + #endif + return(iResult); +} + +/*F*************************************************************************************************/ +/*! + \Function SockaddrInGetAddrText + + \Description + Convert a sockaddr into textual form based on address family + + \Input *pAddr - sockaddr struct + \Input *pStr - address buffer + \Input iLen - address length + + \Output + char * - returns str on success, NULL on failure + + \Version 10/04/1999 (gschaefer) +*/ +/*************************************************************************************************F*/ +char *SockaddrInGetAddrText(const struct sockaddr *pAddr, char *pStr, int32_t iLen) +{ + char *pResult = NULL; + if (pAddr->sa_family == AF_INET) + { + pResult = SocketInAddrGetText(SockaddrInGetAddr(pAddr), pStr, iLen); + } + #ifndef DIRTYCODE_NX + else if (pAddr->sa_family == AF_INET6) + { + pResult = _SockaddrIn6GetAddrText((const struct sockaddr_in6 *)pAddr, pStr, iLen); + } + #endif + return(pResult); +} + +/*F*************************************************************************************************/ +/*! + \Function SockaddrInParse + + \Description + Convert textual internet address:port into sockaddr structure + + \Input *pAddr - sockaddr to fill in + \Input *pParse - textual address + + \Output + int32_t - flags: + 0=parsed nothing + 1=parsed addr + 2=parsed port + 3=parsed addr+port + + \Version 11/23/2002 (gschaefer) +*/ +/*************************************************************************************************F*/ +int32_t SockaddrInParse(struct sockaddr *pAddr, const char *pParse) +{ + int32_t iReturn = 0, iPort = 0; + uint32_t uAddr = 0; + + // init the address + SockaddrInit(pAddr, AF_INET); + + // parse addr:port + iReturn = SockaddrInParse2(&uAddr, &iPort, NULL, pParse); + + // set addr:port in sockaddr + SockaddrInSetAddr(pAddr, uAddr); + SockaddrInSetPort(pAddr, iPort); + + // return parse info + return(iReturn); +} + +/*F*************************************************************************************************/ +/*! + \Function SockaddrInParse2 + + \Description + Convert textual internet address:port into sockaddr structure + + If the textual internet address:port is followed by a second :port, the second port + is optionally parsed into pPort2, if not NULL. + + \Input *pAddr - address to fill in + \Input *pPort - port to fill in + \Input *pPort2 - second port to fill in + \Input *pParse - textual address + + \Output + int32_t - flags: + 0=parsed nothing + 1=parsed addr + 2=parsed port + 3=parsed addr+port + 4=parsed port2 + + \Version 11/23/02 (GWS) First Version +*/ +/*************************************************************************************************F*/ +int32_t SockaddrInParse2(uint32_t *pAddr, int32_t *pPort, int32_t *pPort2, const char *pParse) +{ + int32_t iReturn = 0; + uint32_t uVal; + + // skip embedded white-space + while ((*pParse > 0) && (*pParse <= ' ')) + { + ++pParse; + } + + // parse the address (no dns for listen) + for (uVal = 0; ((*pParse >= '0') && (*pParse <= '9')) || (*pParse == '.'); ++pParse) + { + // either add or shift + if (*pParse != '.') + { + uVal = (uVal - (uVal & 255)) + ((uVal & 255) * 10) + (*pParse & 15); + } + else + { + uVal <<= 8; + } + } + if ((*pAddr = uVal) != 0) + { + iReturn |= 1; + } + + // skip non-port info + while ((*pParse != ':') && (*pParse != 0)) + { + ++pParse; + } + + // parse the port + uVal = 0; + if (*pParse == ':') + { + for (++pParse; (*pParse >= '0') && (*pParse <= '9'); ++pParse) + { + uVal = (uVal * 10) + (*pParse & 15); + } + iReturn |= 2; + } + *pPort = (int32_t)uVal; + + // parse port2 (optional) + if (pPort2 != NULL) + { + uVal = 0; + if (*pParse == ':') + { + for (++pParse; (*pParse >= '0') && (*pParse <= '9'); ++pParse) + { + uVal = (uVal * 10) + (*pParse & 15); + } + iReturn |= 4; + } + *pPort2 = (int32_t)uVal; + } + + // return the address + return(iReturn); +} + +/*F*************************************************************************************************/ +/*! + \Function SocketInAddrGetText + + \Description + Convert 32-bit internet address into textual form. + + \Input uAddr - address + \Input *pStr - [out] address buffer + \Input iLen - address length + + \Output + char * - returns str on success, NULL on failure + + \Version 06/17/2009 (jbrookes) +*/ +/*************************************************************************************************F*/ +char *SocketInAddrGetText(uint32_t uAddr, char *pStr, int32_t iLen) +{ + uint8_t uAddrByte[4]; + int32_t iIndex; + char *pStrStart = pStr; + + uAddrByte[0] = (uint8_t)(uAddr>>24); + uAddrByte[1] = (uint8_t)(uAddr>>16); + uAddrByte[2] = (uint8_t)(uAddr>>8); + uAddrByte[3] = (uint8_t)(uAddr>>0); + + for (iIndex = 0; iIndex < 4; iIndex += 1) + { + uint32_t uNumber = uAddrByte[iIndex]; + if (uNumber > 99) + { + *pStr++ = (char)('0' + (uNumber / 100)); + uNumber %= 100; + *pStr++ = (char)('0' + (uNumber / 10)); + uNumber %= 10; + } + if (uNumber > 9) + { + *pStr++ = (char)('0' + (uNumber / 10)); + uNumber %= 10; + } + *pStr++ = (char)('0' + uNumber); + if (iIndex < 3) + { + *pStr++ = '.'; + } + } + *pStr = '\0'; + return(pStrStart); +} + +/*F*************************************************************************************************/ +/*! + \Function SocketInTextGetAddr + + \Description + Convert textual internet address into 32-bit integer form + + \Input *pAddrText - textual address + + \Output + int32_t - integer form + + \Version 11/23/02 (JLB) First Version + +*/ +/*************************************************************************************************F*/ +int32_t SocketInTextGetAddr(const char *pAddrText) +{ + struct sockaddr SockAddr; + int32_t iAddr = 0; + + SockaddrInit(&SockAddr, AF_INET); + if (SockaddrInSetAddrText(&SockAddr, pAddrText) == 0) + { + iAddr = SockaddrInGetAddr(&SockAddr); + } + return(iAddr); +} + +/*F*************************************************************************************************/ +/*! + \Function SocketHtons + + \Description + Convert uint16_t from host to network byte order + + \Input uAddr - value to convert + + \Output + uint16_t - converted value + + \Version 10/04/1999 (gschaefer) +*/ +/*************************************************************************************************F*/ +uint16_t SocketHtons(uint16_t uAddr) +{ + uint8_t uNetw[2]; + ds_memcpy_s(uNetw, sizeof(uNetw), &uAddr, sizeof(uAddr)); + return((uNetw[0]<<8)|(uNetw[1]<<0)); +} + +/*F*************************************************************************************************/ +/*! + \Function SocketHtonl + + \Description + Convert uint32_t from host to network byte order. + + \Input uAddr - value to convert + + \Output + uint32_t - converted value + + \Version 10/04/1999 (gschaefer) +*/ +/*************************************************************************************************F*/ +uint32_t SocketHtonl(uint32_t uAddr) +{ + uint8_t uNetw[4]; + ds_memcpy_s(uNetw, sizeof(uNetw), &uAddr, sizeof(uAddr)); + return((((((uNetw[0]<<8)|uNetw[1])<<8)|uNetw[2])<<8)|uNetw[3]); +} + +/*F*************************************************************************************************/ +/*! + \Function SocketNtohs + + \Description + Convert uint16_t from network to host byte order. + + \Input uAddr - value to convert + + \Output + uint16_t - converted value + + \Version 10/0/99 (GWS) First Version + +*/ +/*************************************************************************************************F*/ +uint16_t SocketNtohs(uint16_t uAddr) +{ + uint8_t uNetw[2]; + uNetw[1] = (uint8_t)uAddr; + uAddr >>= 8; + uNetw[0] = (uint8_t)uAddr; + ds_memcpy_s(&uAddr, sizeof(uAddr), uNetw, sizeof(uNetw)); + return(uAddr); +} + +/*F*************************************************************************************************/ +/*! + \Function SocketNtohl + + \Description + Convert uint32_t from network to host byte order. + + \Input uAddr - value to convert + + \Output + uint32_t - converted value + + \Version 10/04/1999 (gschaefer) +*/ +/*************************************************************************************************F*/ +uint32_t SocketNtohl(uint32_t uAddr) +{ + uint8_t uNetw[4]; + uNetw[3] = (uint8_t)uAddr; + uAddr >>= 8; + uNetw[2] = (uint8_t)uAddr; + uAddr >>= 8; + uNetw[1] = (uint8_t)uAddr; + uAddr >>= 8; + uNetw[0] = (uint8_t)uAddr; + ds_memcpy_s(&uAddr, sizeof(uAddr), uNetw, sizeof(uNetw)); + return(uAddr); +} + +/* + HostName Cache functions +*/ + +/*F********************************************************************************/ +/*! + \Function SocketHostnameCacheCreate + + \Description + Create short-term hostname (DNS) cache + + \Input iMemGroup - memgroup to alloc/free with + \Input *pMemGroupUserData - memgroup user data to alloc/free with + + \Output + SocketHostnameCacheT * - hostname cache or NULL on failure + + \Version 10/09/2013 (jbrookes) +*/ +/********************************************************************************F*/ +SocketHostnameCacheT *SocketHostnameCacheCreate(int32_t iMemGroup, void *pMemGroupUserData) +{ + const int32_t iMaxEntries = 16; + int32_t iCacheSize = sizeof(SocketHostnameCacheT) + (iMaxEntries * sizeof(SocketHostnameCacheEntryT)); + SocketHostnameCacheT *pCache; + + // alloc and init cache + if ((pCache = DirtyMemAlloc(iCacheSize, SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynet: could not alloc %d bytes for hostname cache\n", iCacheSize)); + return(NULL); + } + ds_memclr(pCache, iCacheSize); + + // set base info + pCache->iMaxEntries = iMaxEntries; + pCache->iMemGroup = iMemGroup; + pCache->pMemGroupUserData = pMemGroupUserData; + + // initialize crit + NetCritInit2(&pCache->Crit, "HostnameCache", NETCRIT_OPTION_SINGLETHREADENABLE); + + // return to caller + return(pCache); +} + +/*F********************************************************************************/ +/*! + \Function SocketHostnameCacheDestroy + + \Description + Destroy short-term hostname (DNS) cache + + \Input *pCache - hostname cache + + \Version 10/09/2013 (jbrookes) +*/ +/********************************************************************************F*/ +void SocketHostnameCacheDestroy(SocketHostnameCacheT *pCache) +{ + NetCritKill(&pCache->Crit); + DirtyMemFree(pCache, SOCKET_MEMID, pCache->iMemGroup, pCache->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function SocketHostnameCacheAdd + + \Description + Add hostname and address to hostname cache + + \Input *pCache - hostname cache + \Input *pStrHost - hostname to add + \Input uAddress - address of hostname + \Input iVerbose - debug level + + \Version 10/09/2013 (jbrookes) +*/ +/********************************************************************************F*/ +void SocketHostnameCacheAdd(SocketHostnameCacheT *pCache, const char *pStrHost, uint32_t uAddress, int32_t iVerbose) +{ + SocketHostnameCacheEntryT *pCacheEntry; + int32_t iCacheIdx; + + // if we're already in the cache, bail + if (SocketHostnameCacheGet(pCache, pStrHost, 0) != 0) + { + return; + } + + // scan cache for an open entry + NetCritEnter(&pCache->Crit); + for (iCacheIdx = 0; iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1) + { + pCacheEntry = &pCache->CacheEntries[iCacheIdx]; + if (pCacheEntry->uAddress == 0) + { + NetPrintfVerbose((iVerbose, 1, "dirtynet: adding hostname cache entry %s/%a\n", pStrHost, uAddress)); + ds_strnzcpy(pCacheEntry->strDnsName, pStrHost, sizeof(pCacheEntry->strDnsName)); + pCacheEntry->uAddress = uAddress; + pCacheEntry->uTimer = NetTick(); + break; + } + } + NetCritLeave(&pCache->Crit); +} + +/*F********************************************************************************/ +/*! + \Function SocketHostnameCacheGet + + \Description + Get address for hostname from cache, if available. + + \Input *pCache - hostname cache + \Input *pStrHost - hostname to add + \Input iVerbose - debug level + + \Output + uint32_t - address for hostname, or zero if not in cache + + \Version 10/09/2013 (jbrookes) +*/ +/********************************************************************************F*/ +uint32_t SocketHostnameCacheGet(SocketHostnameCacheT *pCache, const char *pStrHost, int32_t iVerbose) +{ + SocketHostnameCacheEntryT *pCacheEntry; + uint32_t uAddress; + int32_t iCacheIdx; + + // scan cache for dns entry + NetCritEnter(&pCache->Crit); + for (iCacheIdx = 0, uAddress = 0; iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1) + { + pCacheEntry = &pCache->CacheEntries[iCacheIdx]; + // skip empty entries + if (pCacheEntry->strDnsName[0] == '\0') + { + continue; + } + // check for entry we want + if (!strcmp(pCacheEntry->strDnsName, pStrHost)) + { + NetPrintfVerbose((iVerbose, 0, "dirtynet: %s=%a [cache]\n", pCacheEntry->strDnsName, pCacheEntry->uAddress)); + uAddress = pCache->CacheEntries[iCacheIdx].uAddress; + break; + } + } + NetCritLeave(&pCache->Crit); + return(uAddress); +} + +/*F********************************************************************************/ +/*! + \Function SocketHostnameCacheDel + + \Description + Remove hostname cache entry from the cache, if it exists. pStrHost is + checked if non-NULL and uAddress is checked if non-zero (note that both + can be checked if desired). + + \Input *pCache - hostname cache + \Input *pStrHost - hostname of cache entry to delete, or NULL + \Input uAddress - address of cache entry to delete, or zero + \Input iVerbose - debug level + + \Version 09/23/2016 (jbrookes) +*/ +/********************************************************************************F*/ +void SocketHostnameCacheDel(SocketHostnameCacheT *pCache, const char *pStrHost, uint32_t uAddress, int32_t iVerbose) +{ + SocketHostnameCacheEntryT *pCacheEntry; + int32_t iCacheIdx; + + // scan cache for dns entry + NetCritEnter(&pCache->Crit); + for (iCacheIdx = 0; iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1) + { + pCacheEntry = &pCache->CacheEntries[iCacheIdx]; + // skip empty entries + if (pCacheEntry->strDnsName[0] == '\0') + { + continue; + } + // check for hostname match + if ((pStrHost != NULL) && strcmp(pCacheEntry->strDnsName, pStrHost)) + { + continue; + } + // check for address match + if ((uAddress != 0) && (pCacheEntry->uAddress != uAddress)) + { + continue; + } + // found a match; delete the entry + NetPrintfVerbose((iVerbose, 1, "dirtynet: deleting hostname cache entry %s/%a\n", pCacheEntry->strDnsName, pCacheEntry->uAddress)); + ds_memclr(pCacheEntry, sizeof(*pCacheEntry)); + break; + } + NetCritLeave(&pCache->Crit); +} + +/*F********************************************************************************/ +/*! + \Function SocketHostnameCacheProcess + + \Description + Process the hostname cache, clear expired cache entries + + \Input *pCache - hostname cache + \Input iVerbose - debug level + + \Version 04/23/2019 (eesponda) +*/ +/********************************************************************************F*/ +void SocketHostnameCacheProcess(SocketHostnameCacheT *pCache, int32_t iVerbose) +{ + SocketHostnameCacheEntryT *pCacheEntry; + uint32_t uCurTick; + int32_t iCacheIdx; + + if (!NetCritTry(&pCache->Crit)) + { + return; + } + + // scan cache for dns entry + for (iCacheIdx = 0, uCurTick = NetTick(); iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1) + { + pCacheEntry = &pCache->CacheEntries[iCacheIdx]; + // skip empty entries + if (pCacheEntry->strDnsName[0] == '\0') + { + continue; + } + // check for expiration + if (NetTickDiff(uCurTick, pCacheEntry->uTimer) > DIRTYNET_HOSTNAMECACHELIFETIME) + { + NetPrintfVerbose((iVerbose, 1, "dirtynet: expiring hostname cache entry %s/%a\n", pCacheEntry->strDnsName, pCacheEntry->uAddress)); + ds_memclr(pCacheEntry, sizeof(*pCacheEntry)); + } + } + NetCritLeave(&pCache->Crit); +} + +/*F********************************************************************************/ +/*! + \Function SocketHostnameAddRef + + \Description + Check for in-progress DNS requests we can piggyback on, instead of issuing + a new request. + + \Input **ppHostList - list of active lookups + \Input *pHost - current lookup + \Input bUseRef - force using a new entry if bUseRef=FALSE (should normally be TRUE) + + \Output + HostentT * - Pre-existing DNS request we have refcounted, or NULL + + \Version 01/16/2014 (jbrookes) +*/ +/********************************************************************************F*/ +HostentT *SocketHostnameAddRef(HostentT **ppHostList, HostentT *pHost, uint8_t bUseRef) +{ + HostentT *pHost2 = NULL; + + // look for an in-progress refcounted lookup + NetCritEnter(NULL); + if (bUseRef == TRUE) + { + for (pHost2 = *ppHostList; pHost2 != NULL; pHost2 = pHost2->pNext) + { + if (!strcmp(pHost2->name, pHost->name) && !pHost2->done) + { + break; + } + } + } + + // new lookup, so add to list + if (pHost2 == NULL) + { + pHost->refcount = 1; + pHost->pNext = *ppHostList; + *ppHostList = pHost; + pHost = NULL; + } + else // found an in-progress lookup, so piggyback on it + { + pHost = pHost2; + pHost->refcount += 1; + NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 0, "dirtynet: %s lookup refcounted (%d refs)\n", pHost->name, pHost->refcount)); + } + + NetCritLeave(NULL); + return(pHost); +} + +/*F********************************************************************************/ +/*! + \Function SocketHostnameListProcess + + \Description + Process list of in-progress DNS requests, disposing of those that are + completed and no longer referenced. + + \Input **ppHostList - list of active lookups + \Input iMemGroup - memgroup hostname lookup records are allocated with + \Input *pMemGroupUserData - memgroup userdata hostname lookup records are allocated with + + \Notes + This function is called from the SocketIdle thread, which is already guarded + by the global critical section. It is therefore assumed that it does not + need to be explicitly guarded here. + + \Version 01/16/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void SocketHostnameListProcess(HostentT **ppHostList, int32_t iMemGroup, void *pMemGroupUserData) +{ + HostentT **ppHost; + for (ppHost = ppHostList; *ppHost != NULL;) + { + if ((*ppHost)->refcount == 0) + { + HostentT *pHost = *ppHost; + *ppHost = (*ppHost)->pNext; + DirtyMemFree(pHost, SOCKET_MEMID, iMemGroup, pMemGroupUserData); + } + else + { + ppHost = &(*ppHost)->pNext; + } + } +} + +/* + Packet Queue functions +*/ + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueCreate + + \Description + Create a packet queue + + \Input iMaxPackets - size of queue, in packets (max 127) + \Input iMemGroup - memgroup to alloc/free with + \Input *pMemGroupUserData - memgroup user data to alloc/free with + + \Output + SocketPacketQueueT * - packet queue, or NULL on failure + + \Version 02/21/2014 (jbrookes) +*/ +/********************************************************************************F*/ +SocketPacketQueueT *SocketPacketQueueCreate(int32_t iMaxPackets, int32_t iMemGroup, void *pMemGroupUserData) +{ + SocketPacketQueueT *pPacketQueue; + int32_t iQueueSize; + + // enforce min/max queue sizes + iMaxPackets = DS_CLAMP(iMaxPackets, 1, DIRTYNET_PACKETQUEUEMAX); + + // calculate memory required for queue + iQueueSize = sizeof(*pPacketQueue) + ((iMaxPackets-1) * sizeof(pPacketQueue->aPacketQueue[0])); + + // alloc and init queue + if ((pPacketQueue = DirtyMemAlloc(iQueueSize, SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynet: could not alloc %d bytes for packet queue\n", iQueueSize)); + return(NULL); + } + ds_memclr(pPacketQueue, iQueueSize); + + // set base info + pPacketQueue->iNumPackets = 0; + pPacketQueue->iMaxPackets = iMaxPackets; + pPacketQueue->iMemGroup = iMemGroup; + pPacketQueue->pMemGroupUserData = pMemGroupUserData; + + // latency/packet loss simulation setup + pPacketQueue->uLatencyTime = NetTick(); + + //$$temp - testing + //pPacketQueue->uLatency = 100; + //pPacketQueue->uDeviation = 5; + //pPacketQueue->uPacketLoss = 5*65536; + + // return queue to caller + return(pPacketQueue); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueDestroy + + \Description + Destroy packet queue + + \Input *pPacketQueue - packet queue to destroy + + \Version 02/21/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void SocketPacketQueueDestroy(SocketPacketQueueT *pPacketQueue) +{ + DirtyMemFree(pPacketQueue, SOCKET_MEMID, pPacketQueue->iMemGroup, pPacketQueue->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueResize + + \Description + Resize a packet queue, if the max packet size is different + + \Input *pPacketQueue - packet queue to resize (may be null) + \Input iMaxPackets - new size of queue, in packets (max 127) + \Input iMemGroup - memgroup to alloc/free with + \Input *pMemGroupUserData - memgroup user data to alloc/free with + + \Output + SocketPacketQueueT * - pointer to resized packet queue + + \Notes + If the new max queue size is less than the number of packets in the current + queue, packets will be overwritten in the usual manner (older discarded in + favor of newer). + + \Version 05/30/2014 (jbrookes) +*/ +/********************************************************************************F*/ +SocketPacketQueueT *SocketPacketQueueResize(SocketPacketQueueT *pPacketQueue, int32_t iMaxPackets, int32_t iMemGroup, void *pMemGroupUserData) +{ + uint8_t aPacketData[SOCKET_MAXUDPRECV]; + SocketPacketQueueT *pNewPacketQueue; + struct sockaddr PacketAddr; + int32_t iPacketSize; + + // enforce min/max queue sizes + iMaxPackets = DS_CLAMP(iMaxPackets, 1, DIRTYNET_PACKETQUEUEMAX); + + // if we have a queue and it's already the right size, return it + if ((pPacketQueue != NULL) && (pPacketQueue->iMaxPackets == iMaxPackets)) + { + return(pPacketQueue); + } + + // create new queue + NetPrintf(("dirtynet: [%p] re-creating socket packet queue with %d max packets\n", pPacketQueue, iMaxPackets)); + if ((pNewPacketQueue = SocketPacketQueueCreate(iMaxPackets, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynet: could not allocate new packet queue\n")); + return(pPacketQueue); + } + + // copy old data (if any) over, and destroy the old packet queue + if (pPacketQueue != NULL) + { + while ((iPacketSize = SocketPacketQueueRem(pPacketQueue, aPacketData, sizeof(aPacketData), &PacketAddr)) > 0) + { + SocketPacketQueueAdd(pNewPacketQueue, aPacketData, iPacketSize, &PacketAddr); + } + SocketPacketQueueDestroy(pPacketQueue); + } + + // return resized queue to caller + return(pNewPacketQueue); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueAdd + + \Description + Add a packet to packet queue + + \Input *pPacketQueue - packet queue to add to + \Input *pPacketData - packet data to add to queue + \Input iPacketSize - size of packet data + \Input *pPacketAddr - remote address associated with packet + + \Output + int32_t - >=0: number of bytes buffered, -1: packet too large + + \Version 07/28/2020 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t SocketPacketQueueAdd(SocketPacketQueueT *pPacketQueue, const uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr) +{ + return(SocketPacketQueueAdd2(pPacketQueue, pPacketData, iPacketSize, pPacketAddr, FALSE)); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueAdd2 + + \Description + Add a packet to packet queue + + \Input *pPacketQueue - packet queue to add to + \Input *pPacketData - packet data to add to queue + \Input iPacketSize - size of packet data + \Input *pPacketAddr - remote address associated with packet + \Input bPartialAllowed - allow consuming only a portion of the submitted data (NX only) + + \Output + int32_t - >=0: number of bytes buffered, -1: packet too large + + \Version 02/21/2014 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketPacketQueueAdd2(SocketPacketQueueT *pPacketQueue, const uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr, uint32_t bPartialAllowed) +{ + SocketPacketQueueEntryT *pQueueEntry; + + if (bPartialAllowed == FALSE) + { + // reject if packet data is too large + if (iPacketSize > SOCKET_MAXUDPRECV) + { + NetPrintf(("dirtynet: [%p] packet too large to add to queue\n", pPacketQueue)); + return(-1); + } + } + + // if queue is full, overwrite oldest member + if (pPacketQueue->iNumPackets == pPacketQueue->iMaxPackets) + { + NetPrintf(("dirtynet: [%p] add to full queue; oldest entry will be overwritten\n", pPacketQueue)); + pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets; + pPacketQueue->uPacketDrop += 1; + } + else + { + pPacketQueue->iNumPackets += 1; + if (pPacketQueue->uPacketMax < (unsigned)pPacketQueue->iNumPackets) + { + pPacketQueue->uPacketMax = (unsigned)pPacketQueue->iNumPackets; + } + } + // set packet entry + NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] packet queue entry added (%d entries)\n", pPacketQueue, pPacketQueue->iPacketTail, pPacketQueue->iNumPackets)); + pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketTail]; + + if (iPacketSize > (signed)sizeof(pQueueEntry->aPacketData)) + { + iPacketSize = sizeof(pQueueEntry->aPacketData); + } + ds_memcpy_s(pQueueEntry->aPacketData, sizeof(pQueueEntry->aPacketData), pPacketData, iPacketSize); + + if (pPacketAddr) + { + ds_memcpy(&pQueueEntry->PacketAddr, pPacketAddr, sizeof(pQueueEntry->PacketAddr)); + } + else + { + ds_memclr(&pQueueEntry->PacketAddr, sizeof(pQueueEntry->PacketAddr)); + pQueueEntry->PacketAddr.sa_family = AF_UNSPEC; + } + + pQueueEntry->iPacketSize = iPacketSize; + pQueueEntry->uPacketTick = NetTick(); + // add to queue + pPacketQueue->iPacketTail = (pPacketQueue->iPacketTail + 1) % pPacketQueue->iMaxPackets; + NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail)); + // return number of bytes buffered + return(pQueueEntry->iPacketSize); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueAlloc + + \Description + Alloc a packet queue entry. This is used when receiving data directly into + the packet queue data buffer. + + \Input *pPacketQueue - packet queue to alloc entry from + + \Output + SocketPacketQueueEntryT * - packet queue entry + + \Version 02/24/2014 (jbrookes) +*/ +/********************************************************************************F*/ +SocketPacketQueueEntryT *SocketPacketQueueAlloc(SocketPacketQueueT *pPacketQueue) +{ + SocketPacketQueueEntryT *pQueueEntry; + // if queue is full, alloc over oldest member + if (pPacketQueue->iNumPackets == pPacketQueue->iMaxPackets) + { + // print a warning if we're altering a slot that is in use + if (pPacketQueue->aPacketQueue[pPacketQueue->iPacketTail].iPacketSize != -1) + { + NetPrintf(("dirtynet: [%p] alloc to full queue; oldest entry will be overwritten\n", pPacketQueue)); + pPacketQueue->uPacketDrop += 1; + } + pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets; + } + else + { + pPacketQueue->iNumPackets += 1; + if (pPacketQueue->uPacketMax < (unsigned)pPacketQueue->iNumPackets) + { + pPacketQueue->uPacketMax = (unsigned)pPacketQueue->iNumPackets; + } + } + // allocate queue entry + pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketTail]; + NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] packet queue entry alloc (%d entries)\n", pPacketQueue, pPacketQueue->iPacketTail, pPacketQueue->iNumPackets)); + // mark packet as allocated + pQueueEntry->iPacketSize = -1; + // add to queue + pPacketQueue->iPacketTail = (pPacketQueue->iPacketTail + 1) % pPacketQueue->iMaxPackets; + NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail)); + return(pQueueEntry); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueAllocUndo + + \Description + Undo a recent call to SocketPacketQueueAlloc(). Cannot be used if packet + queue has been altered in between calling SocketPacketQueueAlloc() and + SocketPacketQueueAllocUndo() + + \Input *pPacketQueue - packet queue to undo alloc entry from + + \Version 09/04/2018 (mclouatre) +*/ +/********************************************************************************F*/ +void SocketPacketQueueAllocUndo(SocketPacketQueueT *pPacketQueue) +{ + pPacketQueue->iNumPackets -= 1; + NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] recent alloc undone from tail (%d entries)\n", pPacketQueue, pPacketQueue->iPacketTail, pPacketQueue->iNumPackets)); + + // modular arithmetic is not used here to avoid complications when the substraction underflows + pPacketQueue->iPacketTail -= 1; + if (pPacketQueue->iPacketTail < 0) + { + pPacketQueue->iPacketTail = pPacketQueue->iMaxPackets - 1; + } + + NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail)); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueRem + + \Description + Remove a packet from packet queue + + \Input *pPacketQueue - packet queue to remove from + \Input *pPacketData - [out] storage for packet data or NULL + \Input iPacketSize - size of packet output data buffer + \Input *pPacketAddr - [out] storage for packet addr or NULL + + \Output + int32_t - positive=size of packet, zero=no packet, negative=failure + + \Notes + The packet data and address outputs are optional if you are just + trying to remove the entry at the head. + + \Version 02/21/2014 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketPacketQueueRem(SocketPacketQueueT *pPacketQueue, uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr) +{ + SocketPacketQueueEntryT *pQueueEntry; + uint32_t uCurTick = NetTick(); + + // nothing to do if queue is empty + if (pPacketQueue->iNumPackets == 0) + { + return(0); + } + // get head packet + pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketHead]; + // nothing to do if packet was allocated and has not been filled + if (pQueueEntry->iPacketSize < 0) + { + return(0); + } + + // apply simulated latency, if enabled + if (pPacketQueue->uLatency != 0) + { + // compare to current latency + if (NetTickDiff(uCurTick, pQueueEntry->uPacketTick) < (signed)pPacketQueue->uLatency + pPacketQueue->iDeviationTime) + { + NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] latency=%d, deviation=%d\n", pPacketQueue, NetTickDiff(uCurTick, pPacketQueue->uLatencyTime), pPacketQueue->iDeviationTime)); + return(0); + } + + // update packet receive timestamp + SockaddrInSetMisc(&pQueueEntry->PacketAddr, uCurTick); + + // recalculate deviation + pPacketQueue->iDeviationTime = (signed)NetRand(pPacketQueue->uDeviation*2) - (signed)pPacketQueue->uDeviation; + } + + // get packet size to copy (will truncate if output buffer is too small) + if (iPacketSize > pQueueEntry->iPacketSize) + { + iPacketSize = pQueueEntry->iPacketSize; + } + // copy out packet data and source + if (pPacketData != NULL) + { + ds_memcpy(pPacketData, pQueueEntry->aPacketData, iPacketSize); + } + if (pPacketAddr != NULL) + { + ds_memcpy(pPacketAddr, &pQueueEntry->PacketAddr, sizeof(pQueueEntry->PacketAddr)); + } + // remove packet from queue + pPacketQueue->iNumPackets -= 1; + NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] packet queue entry removed from head in %dms (%d entries)\n", pPacketQueue, pPacketQueue->iPacketHead, + NetTickDiff(NetTick(), pQueueEntry->uPacketTick), pPacketQueue->iNumPackets)); + pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets; + NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail)); + + // simulate packet loss + if (pPacketQueue->uPacketLoss != 0) + { + uint32_t uRand = NetRand(100*65536); + if (uRand < pPacketQueue->uPacketLoss) + { + NetPrintf(("dirtynet: [%p] lost packet (rand=%d, comp=%d)!\n", pPacketQueue, uRand, pPacketQueue->uPacketLoss)); + return(0); + } + } + + // return success + return(pQueueEntry->iPacketSize); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueRemStream + + \Description + Remove a stream packet from packet queue + + \Input *pPacketQueue - packet queue to add to + \Input *pPacketData - [out] storage for packet data or NULL + \Input iPacketSize - size of packet output data buffer + + \Output + int32_t - positive=amount of data read, zero=no packet, negative=failure + + \Notes + The packet data output is optional if you are just trying to remove the entry + at the head. + + \Version 11/20/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t SocketPacketQueueRemStream(SocketPacketQueueT *pPacketQueue, uint8_t *pPacketData, int32_t iPacketSize) +{ + SocketPacketQueueEntryT *pQueueEntry; + int32_t iPacketRead; + + // nothing to do if queue is empty + if (pPacketQueue->iNumPackets == 0) + { + return(0); + } + // get head packet + pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketHead]; + // nothing to do if packet was allocated and has not been filled + if (pQueueEntry->iPacketSize < 0) + { + return(0); + } + + // get packet size to copy + iPacketRead = DS_MIN(iPacketSize, pQueueEntry->iPacketSize); + + // copy the packet data and offset by amount read + if (pPacketData != NULL) + { + ds_memcpy(pPacketData, pQueueEntry->aPacketData, iPacketRead); + + pPacketData += iPacketRead; + iPacketSize -= iPacketRead; + } + + // if we copied less than the size of the entry adjust the entry as needed, otherwise remove packet from queue + if ((iPacketRead > 0) && (iPacketRead < pQueueEntry->iPacketSize)) + { + memmove(pQueueEntry->aPacketData, pQueueEntry->aPacketData+iPacketRead, pQueueEntry->iPacketSize-iPacketRead); + pQueueEntry->iPacketSize -= iPacketRead; + } + else + { + pPacketQueue->iNumPackets -= 1; + pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets; + } + + // continue to copy if we still have space in the buffer + if (iPacketSize > 0) + { + iPacketRead += SocketPacketQueueRemStream(pPacketQueue, pPacketData, iPacketSize); + } + return(iPacketRead); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueGetHead + + \Description + Get pointers to data associated with queue head. + + \Input *pPacketQueue - packet queue to remove from + \Input **ppPacketData - [out] to be filled with pointer to packet data (can be NULL) + \Input *pPacketSize - [out] to be filled with size of packet data (can be NULL) + \Input **ppPacketAddr - [out] to be filled with pointer to packet addr (can be NULL) + + \Output + int32_t - positive=size of packet, zero=no packet + + \Version 08/07/2020 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t SocketPacketQueueGetHead(SocketPacketQueueT *pPacketQueue, uint8_t **ppPacketData, int32_t *pPacketSize, struct sockaddr **ppPacketAddr) +{ + SocketPacketQueueEntryT *pQueueEntry; + + // nothing to do if queue is empty + if (pPacketQueue->iNumPackets == 0) + { + return(0); + } + + // get head packet + pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketHead]; + // nothing to do if packet was allocated and has not been filled + if (pQueueEntry->iPacketSize < 0) + { + return(0); + } + + // fill output variables + if (ppPacketData != NULL) + { + *ppPacketData = pQueueEntry->aPacketData; + } + if (pPacketSize != NULL) + { + *pPacketSize = pQueueEntry->iPacketSize; + } + if (ppPacketAddr != NULL) + { + *ppPacketAddr = &pQueueEntry->PacketAddr; + } + + // return success + return(pQueueEntry->iPacketSize); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueTouchHead + + \Description + Update queue's head entry such that it no longer includes successfully + consumed portion of data. + + \Input *pPacketQueue - packet queue to remove from + \Input iConsumedSize - amount of data consumed from head entry (in bytes) + + \Output + int32_t - 0 success, negative: error + + \Version 08/07/2020 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t SocketPacketQueueTouchHead(SocketPacketQueueT *pPacketQueue, int32_t iConsumedSize) +{ + SocketPacketQueueEntryT *pQueueEntry; + + // nothing to do if queue is empty + if (pPacketQueue->iNumPackets == 0) + { + return(-1); + } + + // get head packet + pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketHead]; + // nothing to do if packet was allocated and has not been filled + if (pQueueEntry->iPacketSize < 0) + { + return(-2); + } + + // update size of packet queue entry + pQueueEntry->iPacketSize -= iConsumedSize; + + // remove consumed data from data buffer + memmove(pQueueEntry->aPacketData, pQueueEntry->aPacketData + iConsumedSize, pQueueEntry->iPacketSize); + + // return success + return(0); +} + + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueControl + + \Description + Control socket packet queue options + + \Input *pPacketQueue - packet queue control function; different selectors + control different behaviors + \Input iControl - control selector + \Input iValue - selector specific + + \Output + int32_t - selector result + + \Notes + iControl can be one of the following: + + \verbatim + 'pdev' - set simulated packet deviation + 'plat' - set simulated packet latency + 'plos' - set simulated packet loss + \endverbatim + + \Version 10/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketPacketQueueControl(SocketPacketQueueT *pPacketQueue, int32_t iControl, int32_t iValue) +{ + if (iControl == 'pdev') + { + NetPrintf(("dirtynet: setting simulated packet deviation=%dms\n", iValue)); + pPacketQueue->uDeviation = iValue; + return(0); + } + if (iControl == 'plat') + { + NetPrintf(("dirtynet: setting simulated packet latency=%dms\n", iValue)); + pPacketQueue->uLatency = iValue; + return(0); + } + if (iControl == 'plos') + { + NetPrintf(("dirtynet: setting simulated packet loss to %d.%d\n", iValue >> 16, iValue & 0xffff)); + pPacketQueue->uPacketLoss = iValue; + return(0); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function SocketPacketQueueStatus + + \Description + Get status of socket packet queue + + \Input *pPacketQueue - packet queue to get status of + \Input iStatus - status selector + + \Output + int32_t - selector result + + \Notes + iStatus can be one of the following: + + \verbatim + 'pful' - TRUE if queue is full, FALSE otherwise + 'pdrp' - number of packets overwritten due to queue overflow + 'pmax' - maximum number of packets in queue (high water) + 'pnum' - number of packets in queue + 'psiz' - queue size (in packets) + \endverbatim + + \Version 10/20/2015 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketPacketQueueStatus(SocketPacketQueueT *pPacketQueue, int32_t iStatus) +{ + if (iStatus == 'pful') + { + return(pPacketQueue->iMaxPackets == pPacketQueue->iNumPackets ? TRUE : FALSE); + } + if (iStatus == 'pdrp') + { + return((signed)pPacketQueue->uPacketDrop); + } + if (iStatus == 'pmax') + { + return((signed)pPacketQueue->uPacketMax); + } + if (iStatus == 'pnum') + { + return(pPacketQueue->iNumPackets); + } + if (iStatus == 'psiz') + { + return(pPacketQueue->iMaxPackets); + } + // invalid selector + return(-1); +} + +/* + Rate functions +*/ + +/*F********************************************************************************/ +/*! + \Function SocketRateUpdate + + \Description + Update socket data rate estimation + + \Input *pRate - state used to store rate estimation data + \Input iData - amount of data being sent/recv + \Input *pOpName - indicates send or recv (for debug use only) + + \Notes + Rate estimation is based on a rolling 16-deep history of ~100ms samples + of data rate (the actual period may vary slightly based on update rate + and tick resolution) covering a total of ~1600-1700ms. We also keep track + of the rate we are called at (excluding multiple calls within the same + tick) so we can estimate how much data we need to have sent by the next + time we are called. This is important because we will always be shooting + lower than our cap if we don't consider this factor, and the amount we + are shooting lower by increases the slower the update rate. + + \Version 08/20/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void SocketRateUpdate(SocketRateT *pRate, int32_t iData, const char *pOpName) +{ + const uint32_t uMaxIndex = (unsigned)(sizeof(pRate->aDataHist)/sizeof(pRate->aDataHist[0])); + uint32_t uCurTick = NetTick(), uOldTick; + uint32_t uIndex, uCallRate, uCallSum, uDataSum; + int32_t iTickDiff; + + // reserve tick=0 as uninitialized + if (uCurTick == 0) + { + uCurTick = 1; + } + // initialize update tick counters + if (pRate->uLastTick == 0) + { + pRate->uLastTick = NetTickDiff(uCurTick, 2); + // initialize rate tick counter to current-100 so as to force immediate history update + pRate->uLastRateTick = NetTickDiff(uCurTick, 100); + // start off at max rate + pRate->uCurRate = pRate->uNextRate = pRate->uMaxRate; + } + // exclude error results + if (iData < 0) + { + return; + } + + // update the data + pRate->aDataHist[pRate->uDataIndex] += iData; + /* update the call count, only only if time has elapsed since our last call, since we + want the true update rate) */ + if (NetTickDiff(uCurTick, pRate->uLastTick) > 1) + { + pRate->aCallHist[pRate->uDataIndex] += 1; + } + // update last update tick + pRate->uLastTick = uCurTick; + /* update timestamp, but only if it hasn't been updated already. we do this so we can + correctly update the rate calculation continuously with the current sample */ + if (pRate->aTickHist[pRate->uDataIndex] == 0) + { + pRate->aTickHist[pRate->uDataIndex] = uCurTick; + } + + // get oldest tick & sum recorded data & callcounts + for (uIndex = (pRate->uDataIndex + 1) % uMaxIndex, uOldTick = 0, uCallSum = 0, uDataSum = 0; ; uIndex = (uIndex + 1) % uMaxIndex) + { + // skip uninitialized tick values + if ((uOldTick == 0) && (pRate->aTickHist[uIndex] != 0)) + { + uOldTick = pRate->aTickHist[uIndex]; + } + // update call sum + uCallSum += pRate->aCallHist[uIndex]; + // update data sum + uDataSum += pRate->aDataHist[uIndex]; + // quit when we've hit every entry + if (uIndex == pRate->uDataIndex) + { + break; + } + } + + // update rate estimation + if ((iTickDiff = NetTickDiff(uCurTick, uOldTick)) > 0) + { + // calculate call rate + uCallRate = (uCallSum > 0) ? iTickDiff/uCallSum : 0; + // update current rate estimation + pRate->uCurRate = (uDataSum * 1000) / iTickDiff; + // update next rate estimation (fudge slightly by 2x as it gives us better tracking to our desired rate) + pRate->uNextRate = (uDataSum * 1000) / (iTickDiff+(uCallRate*2)); + + #if DIRTYNET_RATEDEBUG + if (pRate->uCurRate != 0) + { + NetPrintf(("dirtynet: [%p] rate=%4.2fkb nextrate=%4.2f callrate=%dms tickdiff=%d tick=%d\n", pRate, ((float)pRate->uCurRate)/1024.0f, ((float)pRate->uNextRate)/1024.0f, uCallRate, iTickDiff, uCurTick)); + } + #endif + } + + // move to next slot in history every 100ms + if (NetTickDiff(uCurTick, pRate->uLastRateTick) >= 100) + { + pRate->uDataIndex = (pRate->uDataIndex + 1) % uMaxIndex; + pRate->aDataHist[pRate->uDataIndex] = 0; + pRate->aTickHist[pRate->uDataIndex] = 0; + pRate->aCallHist[pRate->uDataIndex] = 0; + pRate->uLastRateTick = uCurTick; + + #if DIRTYNET_RATEDEBUG + if (pRate->uCurRate != 0) + { + NetPrintf(("dirtynet: [%p] %s=%5d rate=%4.2fkb/s indx=%2d tick=%08x diff=%d\n", pRate, pOpName, uDataSum, ((float)pRate->uCurRate)/1024.0f, pRate->uDataIndex, uCurTick, iTickDiff)); + } + #endif + } +} + +/*F********************************************************************************/ +/*! + \Function SocketRateThrottle + + \Description + Throttles data size to send or recv based on calculated data rate and + configured max rate. + + \Input *pRate - state used to calculate rate + \Input iSockType - socket type (SOCK_DGRAM, SOCK_STREAM, etc) + \Input iData - amount of data being sent/recv + \Input *pOpName - indicates send or recv (for debug use only) + + \Output + int32_t - amount of data to send/recv + + \Version 08/20/2014 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketRateThrottle(SocketRateT *pRate, int32_t iSockType, int32_t iData, const char *pOpName) +{ + int32_t iRateDiff; + + // max rate of zero means rate throttling is disabled + if ((pRate->uMaxRate == 0) || (iSockType != SOCK_STREAM)) + { + return(iData); + } + + // enforce min max update rate + if (pRate->uMaxRate < DIRTYNET_MIN_THROTTLE_RATE) + { + NetPrintf(("dirtynet: [%p] clamping max rate throttle of %d to %d", pRate->uMaxRate, DIRTYNET_MIN_THROTTLE_RATE)); + pRate->uMaxRate = DIRTYNET_MIN_THROTTLE_RATE; + } + + // if we're exceeding the max rate, update rate estimation and return no data + if ((iRateDiff = pRate->uMaxRate - pRate->uNextRate) <= 0) + { + NetPrintfVerbose((DIRTYNET_RATEDEBUG, 0, "dirtynet: [%p] exceeding max rate; clamping to zero\n", pRate)); + iData = 0; + } + else if (iData > iRateDiff) // return a limited amount of data so as not to exceed max rate + { + /* clamp in multiples of the typical TCP maximum segment size, so as not to generate fragmented packets + note that the TCP maximum segment size is equal to our minimum throttle rate, otherwise setting the + uMaxRate to less than this value would result in iData always being equal to 0. */ + iData = (iRateDiff / DIRTYNET_MIN_THROTTLE_RATE) * DIRTYNET_MIN_THROTTLE_RATE; + NetPrintfVerbose((DIRTYNET_RATEDEBUG, 0, "dirtynet: [%p] exceeding max rate; clamping to %d bytes from %d bytes\n", pRate, iRateDiff, pRate->uMaxRate - pRate->uNextRate)); + } + + // if we are returning no data, update the rate as the caller won't + if (iData == 0) + { + SocketRateUpdate(pRate, 0, pOpName); + } + + return(iData); +} + +/* + Send Callback functions +*/ + +/*F********************************************************************************/ +/*! + \Function SocketSendCallbackAdd + + \Description + Register a new socket send callback + + \Input aCbEntries - collection of callbacks to add to + \Input *pCbEntry - entry to be added + + \Output + int32_t - zero=success; negative=failure + + \Version 07/18/2014 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t SocketSendCallbackAdd(SocketSendCallbackEntryT aCbEntries[], SocketSendCallbackEntryT *pCbEntry) +{ + int32_t iRetCode = -1; // default to failure + int32_t iEntryIndex; + + for(iEntryIndex = 0; iEntryIndex < SOCKET_MAXSENDCALLBACKS; iEntryIndex++) + { + if (aCbEntries[iEntryIndex].pSendCallback == NULL) + { + aCbEntries[iEntryIndex].pSendCallback = pCbEntry->pSendCallback; + aCbEntries[iEntryIndex].pSendCallref = pCbEntry->pSendCallref; + + NetPrintf(("dirtynet: adding send callback (%p, %p)\n", pCbEntry->pSendCallback, pCbEntry->pSendCallref)); + + iRetCode = 0; // success + + break; + } + } + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function SocketSendCallbackRem + + \Description + Unregister a socket send callback that was already registered. + + \Input aCbEntries - collection of callbacks to remove from + \Input *pCbEntry - entry to be removed + + \Output + int32_t - zero=success; negative=failure + + \Version 07/18/2014 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t SocketSendCallbackRem(SocketSendCallbackEntryT aCbEntries[], SocketSendCallbackEntryT *pCbEntry) +{ + int32_t iRetCode = -1; // default to failure + int32_t iEntryIndex; + + for(iEntryIndex = 0; iEntryIndex < SOCKET_MAXSENDCALLBACKS; iEntryIndex++) + { + if (aCbEntries[iEntryIndex].pSendCallback == pCbEntry->pSendCallback && aCbEntries[iEntryIndex].pSendCallref == pCbEntry->pSendCallref) + { + NetPrintf(("dirtynet: removing send callback (%p, %p)\n", aCbEntries[iEntryIndex].pSendCallback, aCbEntries[iEntryIndex].pSendCallref)); + + aCbEntries[iEntryIndex].pSendCallback = NULL; + aCbEntries[iEntryIndex].pSendCallref = NULL; + + iRetCode = 0; // success + + break; + } + } + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function SocketSendCallbackInvoke + + \Description + Invoke all send callbacks in specified collection. Only one of the callback + is supposed to return "handled"... warn if not the case. + + \Input aCbEntries - collection of callbacks to be invoked + \Input pSocket - socket reference + \Input iType - socket type + \Input *pBuf - the data to be sent + \Input iLen - size of data + \Input *pTo - the address to send to (NULL=use connection address) + + \Output + int32_t - 0 = send not handled; >0 = send successfully handled (bytes sent); <0 = send handled but failed (SOCKERR_XXX) + + \Version 07/18/2014 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t SocketSendCallbackInvoke(SocketSendCallbackEntryT aCbEntries[], SocketT *pSocket, int32_t iType, const char *pBuf, int32_t iLen, const struct sockaddr *pTo) +{ + int32_t iRetCode = 0; // default to send not handled + int32_t iResult; + int32_t iEntryIndex; + + for (iEntryIndex = 0; iEntryIndex < SOCKET_MAXSENDCALLBACKS; iEntryIndex++) + { + // we expect zero or one send callback to handle the send, more than one indicates an invalid condition + if (aCbEntries[iEntryIndex].pSendCallback != NULL) + { + if((iResult = aCbEntries[iEntryIndex].pSendCallback(pSocket, iType, (const uint8_t *)pBuf, iLen, pTo, aCbEntries[iEntryIndex].pSendCallref)) != 0) + { + if (iRetCode == 0) + { + iRetCode = iResult; + } + else + { + NetPrintf(("dirtynet: critical error - send handled by more than one callback (iEntryIndex = %d, iResult = %d)\n", iEntryIndex, iResult)); + } + } + } + } + + return(iRetCode); +} + +/* + + Addr map functions + +*/ +#ifndef DIRTYCODE_NX +/*F*************************************************************************************/ +/*! + \Function _Sockaddr6SetAddrV4Mapped + + \Description + Set a V4-mapped IPv6 in6_addr + + \Input *pAddr6 - [out] storage for IPv4-mapped IPv6 address + \Input uAddr4 - IPv4 address to map + + \Version 03/01/2016 (jbrookes) +*/ +/************************************************************************************F*/ +static void _Sockaddr6SetAddrV4Mapped(struct in6_addr *pAddr6, uint32_t uAddr4) +{ + ds_memclr(pAddr6, sizeof(*pAddr6)); + pAddr6->s6_addr[10] = 0xff; + pAddr6->s6_addr[11] = 0xff; + pAddr6->s6_addr[12] = (uint8_t)(uAddr4 >> 24); + pAddr6->s6_addr[13] = (uint8_t)(uAddr4 >> 16); + pAddr6->s6_addr[14] = (uint8_t)(uAddr4 >> 8); + pAddr6->s6_addr[15] = (uint8_t)(uAddr4); +} + +/*F*************************************************************************************/ +/*! + \Function _Sockaddr6SetV4Mapped + + \Description + Set a V4-mapped IPv6 sockaddr6 + + \Input *pResult6 - [out] storage for IPv4-mapped IPv6 sockaddr + \Input *pSource4 - IPv4 address to map + + \Version 03/01/2016 (jbrookes) +*/ +/************************************************************************************F*/ +static void _Sockaddr6SetV4Mapped(struct sockaddr_in6 *pResult6, const struct sockaddr *pSource4) +{ + pResult6->sin6_family = AF_INET6; + pResult6->sin6_port = SocketNtohs(SockaddrInGetPort(pSource4)); + pResult6->sin6_flowinfo = 0; + _Sockaddr6SetAddrV4Mapped(&pResult6->sin6_addr, SockaddrInGetAddr(pSource4)); + pResult6->sin6_scope_id = 0; +} + +/*F*************************************************************************************/ +/*! + \Function _SocketAddrMapAlloc + + \Description + Alloc (or re-alloc) address map entry list + + \Input *pAddrMap - address map + \Input iNumEntries - new entry list size + \Input iMemGroup - memory group + \Input *pMemGroupUserData - memory group user data + + \Output + int32_t - negative=error, zero=success + + \Version 04/17/2013 (jbrookes) +*/ +/************************************************************************************F*/ +static int32_t _SocketAddrMapAlloc(SocketAddrMapT *pAddrMap, int32_t iNumEntries, int32_t iMemGroup, void *pMemGroupUserData) +{ + int32_t iOldEntryListSize = 0, iNewEntryListSize = iNumEntries*sizeof(SocketAddrMapEntryT); + SocketAddrMapEntryT *pNewEntries; + + // allocate new map memory + if ((pNewEntries = (SocketAddrMapEntryT *)DirtyMemAlloc(iNewEntryListSize, SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + return(-1); + } + // clear new map memory + ds_memclr(pNewEntries, iNewEntryListSize); + // copy previous map data + if (pAddrMap->pMapEntries != NULL) + { + iOldEntryListSize = pAddrMap->iNumEntries*sizeof(SocketAddrMapEntryT); + ds_memcpy(pNewEntries, pAddrMap->pMapEntries, iOldEntryListSize); + DirtyMemFree(pAddrMap->pMapEntries, SOCKET_MEMID, iMemGroup, pMemGroupUserData); + } + // update state + pAddrMap->iNumEntries = iNumEntries; + pAddrMap->pMapEntries = pNewEntries; + pAddrMap->iMemGroup = iMemGroup; + pAddrMap->pMemGroupUserData = pMemGroupUserData; + // return success + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketAddrMapGet + + \Description + Find a map entry, based on specified address + + \Input *pAddrMap - address map + \Input *pAddr - address to get entry for + \Input iAddrSize - size of address + + \Output + SockAddrMapEntryT * - pointer to map entry or NULL if not found + + \Version 04/17/2013 (jbrookes) +*/ +/************************************************************************************F*/ +static SocketAddrMapEntryT *_SocketAddrMapGet(const SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize) +{ + const struct sockaddr_in6 *pAddr6 = (const struct sockaddr_in6 *)pAddr; + SocketAddrMapEntryT *pMapEntry; + int32_t iMapEntry; + + for (iMapEntry = 0; iMapEntry < pAddrMap->iNumEntries; iMapEntry += 1) + { + pMapEntry = &pAddrMap->pMapEntries[iMapEntry]; + if ((pAddr->sa_family == AF_INET) && (pMapEntry->iVirtualAddress == SockaddrInGetAddr(pAddr))) + { + return(pMapEntry); + } + if ((pAddr->sa_family == AF_INET6) && (!memcmp(&pAddr6->sin6_addr, &pMapEntry->SockAddr6.sin6_addr, sizeof(pAddr6->sin6_addr)))) + { + return(pMapEntry); + } + } + return(NULL); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketAddrMapSet + + \Description + Initialize a map entry + + \Input *pAddrMap - address map + \Input *pMapEntry - entry to set + \Input *pAddr6 - IPv6 address to set + \Input iAddrSize - size of address + + \Version 04/17/2013 (jbrookes) +*/ +/************************************************************************************F*/ +static void _SocketAddrMapSet(SocketAddrMapT *pAddrMap, SocketAddrMapEntryT *pMapEntry, const struct sockaddr_in6 *pAddr6, int32_t iAddrSize) +{ + pMapEntry->iRefCount = 1; + pMapEntry->iVirtualAddress = pAddrMap->iNextVirtAddr; + pAddrMap->iNextVirtAddr = (pAddrMap->iNextVirtAddr + 1) & 0x00ffffff; + ds_memcpy(&pMapEntry->SockAddr6, pAddr6, iAddrSize); + NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] add map %A to %a\n", pMapEntry - pAddrMap->pMapEntries, pAddr6, pMapEntry->iVirtualAddress)); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketAddrMapRemap + + \Description + Alter the value of an initialized map entry + + \Input *pAddrMap - address map + \Input *pMapEntry - entry to set + \Input *pAddr6 - IPv6 address to set + \Input iAddrSize - size of address + + \Version 12/03/2015 (amakoukji) +*/ +/************************************************************************************F*/ +static void _SocketAddrMapRemap(const SocketAddrMapT *pAddrMap, SocketAddrMapEntryT *pMapEntry, const struct sockaddr_in6 *pAddr6, int32_t iAddrSize) +{ + if (memcmp(&pMapEntry->SockAddr6, pAddr6, iAddrSize) == 0) + { + NetPrintf(("dirtynet: [%d] attempt to remap %a to identical IPv6 address %A\n", pMapEntry - pAddrMap->pMapEntries, pMapEntry->iVirtualAddress, pAddr6)); + } + NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] remapped %A -> %A for virtual address %a\n", pMapEntry - pAddrMap->pMapEntries, &pMapEntry->SockAddr6, pAddr6, pMapEntry->iVirtualAddress)); + ds_memcpy(&pMapEntry->SockAddr6, pAddr6, iAddrSize); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketAddrMapDel + + \Description + Dereference (and clear, if no more references) a map entry + + \Input *pAddrMap - address map + \Input *pMapEntry - entry to del + + \Version 04/17/2013 (jbrookes) +*/ +/************************************************************************************F*/ +static void _SocketAddrMapDel(SocketAddrMapT *pAddrMap, SocketAddrMapEntryT *pMapEntry) +{ + NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] del map %A to %a (decremented refcount to %d)\n", + pMapEntry - pAddrMap->pMapEntries, &pMapEntry->SockAddr6, pMapEntry->iVirtualAddress, pMapEntry->iRefCount-1)); + if (--pMapEntry->iRefCount == 0) + { + ds_memclr(pMapEntry, sizeof(*pMapEntry)); + } +} + +/*F*************************************************************************************/ +/*! + \Function _SocketAddrMapAdd + + \Description + Add an IPv6 address to the address mapping table + + \Input *pAddrMap - address map + \Input *pAddr6 - address to add to the mapping table + \Input iAddrSize - size of address + + \Output + int32_t - SOCKMAP_ERROR=error, else virtual IPv4 address for newly mapped IPv6 address + + \Version 04/17/2013 (jbrookes) +*/ +/************************************************************************************F*/ +static int32_t _SocketAddrMapAdd(SocketAddrMapT *pAddrMap, const struct sockaddr_in6 *pAddr6, int32_t iAddrSize) +{ + int32_t iMapEntry; + + // find an empty slot + for (iMapEntry = 0; iMapEntry < pAddrMap->iNumEntries; iMapEntry += 1) + { + if (pAddrMap->pMapEntries[iMapEntry].iVirtualAddress == 0) + { + _SocketAddrMapSet(pAddrMap, &pAddrMap->pMapEntries[iMapEntry], pAddr6, iAddrSize); + return(pAddrMap->pMapEntries[iMapEntry].iVirtualAddress); + } + } + + // if no empty slot, realloc the array + if ((_SocketAddrMapAlloc(pAddrMap, pAddrMap->iNumEntries+8, pAddrMap->iMemGroup, pAddrMap->pMemGroupUserData)) < 0) + { + return(SOCKMAP_ERROR); + } + // try the add again + return(_SocketAddrMapAdd(pAddrMap, pAddr6, iAddrSize)); +} + +/*F*************************************************************************************/ +/*! + \Function SocketAddrMapInit + + \Description + Initialize a Socket Address Map + + \Input *pAddrMap - address map + \Input iMemGroup - memory group + \Input *pMemGroupUserData - memory group user data + + \Version 03/03/2016 (jbrookes) +*/ +/************************************************************************************F*/ +void SocketAddrMapInit(SocketAddrMapT *pAddrMap, int32_t iMemGroup, void *pMemGroupUserData) +{ + // set up address map + ds_memclr(pAddrMap, sizeof(*pAddrMap)); + pAddrMap->iMemGroup = iMemGroup; + pAddrMap->pMemGroupUserData = pMemGroupUserData; + // init ipv6 map virtual address + pAddrMap->iNextVirtAddr = NetTick() & 0x00ffffff; +} + +/*F*************************************************************************************/ +/*! + \Function SocketAddrMapShutdown + + \Description + Clean up a Socket Address Map + + \Input *pAddrMap - address map + + \Version 03/03/2016 (jbrookes) +*/ +/************************************************************************************F*/ +void SocketAddrMapShutdown(SocketAddrMapT *pAddrMap) +{ + if (pAddrMap->pMapEntries != NULL) + { + DirtyMemFree(pAddrMap->pMapEntries, SOCKET_MEMID, pAddrMap->iMemGroup, pAddrMap->pMemGroupUserData); + } +} + +/*F*************************************************************************************/ +/*! + \Function SocketAddrMapAddress + + \Description + Map an IPv6 address and return a virtual IPv4 address that can be used to + reference it. + + \Input *pAddrMap - address map + \Input *pAddr - address to add to the mapping table + \Input iAddrSize - size of address + + \Output + int32_t - SOCKMAP_ERROR=error, else virtual IPv4 address for newly mapped IPv6 address + + \Version 04/17/2013 (jbrookes) +*/ +/************************************************************************************F*/ +int32_t SocketAddrMapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize) +{ + const struct sockaddr_in6 *pAddr6 = (const struct sockaddr_in6 *)pAddr; + SocketAddrMapEntryT *pMapEntry; + + // if IPv4, just return the IPv4 address directly, without mapping + if (pAddr->sa_family == AF_INET) + { + return(SockaddrInGetAddr(pAddr)); + } + // we only map ipv6 addresses + if ((pAddr->sa_family != AF_INET6) || (iAddrSize < (signed)sizeof(struct sockaddr_in6))) + { + NetPrintf(("dirtynet: can't map address of type %d size=%d\n", pAddr->sa_family, iAddrSize)); + return(SOCKMAP_ERROR); + } + // force address size to in6 (is this necessary??) + iAddrSize = sizeof(struct sockaddr_in6); + // if it's an IPv4-mapped IPv6 address, return the IPv4 address + if (_SockaddrIn6IsIPv4(pAddr6) || _SockaddrIn6IsZero(pAddr6)) + { + return(SockaddrIn6GetAddr4(pAddr6)); + } + // see if this address is already mapped + if ((pMapEntry = _SocketAddrMapGet(pAddrMap, pAddr, iAddrSize)) != NULL) + { + pMapEntry->iRefCount += 1; + NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] map %A to %a (incremented refcount to %d)\n", pMapEntry - pAddrMap->pMapEntries, &pMapEntry->SockAddr6, pMapEntry->iVirtualAddress, pMapEntry->iRefCount)); + return(pMapEntry->iVirtualAddress); + } + // add it to the map + return(_SocketAddrMapAdd(pAddrMap, pAddr6, iAddrSize)); +} + +/*F*************************************************************************************/ +/*! + \Function SocketAddrRemapAddress + + \Description + Remap an existing IPv6 address and return a virtual IPv4 address that can be used to + reference it. + + \Input *pAddrMap - address map + \Input *pOldAddr - address that should be in mapping table + \Input *pNewAddr - address to update to the mapping table + \Input iAddrSize - size of address + + \Output + int32_t - SOCKMAP_ERROR=error, else virtual IPv4 address for remapped IPv6 address + + \Version 12/03/2015 (amakoukji) +*/ +/************************************************************************************F*/ +int32_t SocketAddrRemapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pOldAddr, const struct sockaddr *pNewAddr, int32_t iAddrSize) +{ + SocketAddrMapEntryT *pMapEntry; + + // if IPv4, just return the IPv4 address directly, without mapping + if (pNewAddr->sa_family == AF_INET) + { + return(SockaddrInGetAddr(pNewAddr)); + } + // we only map ipv6 addresses + if ((pNewAddr->sa_family != AF_INET6) || (iAddrSize < (signed)sizeof(struct sockaddr_in6))) + { + NetPrintf(("dirtynet: can't remap address of type %d size=%d\n", pNewAddr->sa_family, iAddrSize)); + return(SOCKMAP_ERROR); + } + iAddrSize = sizeof(struct sockaddr_in6); + // see if this address is already mapped + if ((pMapEntry = _SocketAddrMapGet(pAddrMap, (const struct sockaddr *)pOldAddr, iAddrSize)) != NULL) + { + NetPrintf(("dirtynet: attempting to remap virtual address %a, from %A to %A\n", pMapEntry->iVirtualAddress, pOldAddr, pNewAddr)); + _SocketAddrMapRemap(pAddrMap, pMapEntry, (const struct sockaddr_in6 *)pNewAddr, iAddrSize); + return(pMapEntry->iVirtualAddress); + } + + // did not find the address + NetPrintf(("dirtynet: attempt to remap unmapped address %A, attempting to add mapping instead\n", pNewAddr)); + return(SocketAddrMapAddress(pAddrMap, (const struct sockaddr *)pNewAddr, iAddrSize)); +} + +/*F*************************************************************************************/ +/*! + \Function SocketAddrUnmapAddress + + \Description + Removes an address mapping from the mapping table. + + \Input *pAddrMap - address map + \Input *pAddr - address to remove from the mapping table + \Input iAddrSize - size of address + + \Output + int32_t - negative=error, else zero + + \Version 04/18/2013 (jbrookes) +*/ +/************************************************************************************F*/ +int32_t SocketAddrUnmapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize) +{ + SocketAddrMapEntryT *pMapEntry; + + // we only map ipv6 addresses + if ((pAddr->sa_family != AF_INET6) || (iAddrSize < (signed)sizeof(struct sockaddr_in6))) + { + NetPrintf(("dirtynet: can't unmap address of type %d size=%d\n", pAddr->sa_family, iAddrSize)); + return(-1); + } + iAddrSize = sizeof(struct sockaddr_in6); + // get the map entry for this address + if ((pMapEntry = _SocketAddrMapGet(pAddrMap, (const struct sockaddr *)pAddr, iAddrSize)) == NULL) + { + NetPrintf(("dirtynet: address unmap operation on an address not in the table\n")); + return(-2); + } + // unmap it + _SocketAddrMapDel(pAddrMap, pMapEntry); + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function SocketAddrMapGet + + \Description + Check if an address is in the address map, and if so return its mapped equivalent + + \Input *pAddrMap - address map + \Input *pResult - [out] storage for result + \Input *pSource - source address + \Input *pNameLen - [out] storage for result length + + \Output + struct sockaddr * - pointer to result address, or NULL if not found + + \Version 04/18/2013 (jbrookes) +*/ +/************************************************************************************F*/ +struct sockaddr *SocketAddrMapGet(const SocketAddrMapT *pAddrMap, struct sockaddr *pResult, const struct sockaddr *pSource, int32_t *pNameLen) +{ + SocketAddrMapEntryT *pMapEntry = _SocketAddrMapGet(pAddrMap, pSource, *pNameLen); + struct sockaddr *pReturn = NULL; + + if (pMapEntry != NULL) + { + if (pResult->sa_family == AF_INET) + { + SockaddrInit(pResult, AF_INET); + SockaddrInSetAddr(pResult, pMapEntry->iVirtualAddress); + pReturn = pResult; + } + else if (pResult->sa_family == AF_INET6) + { + ds_memcpy_s(pResult, sizeof(*pResult), &pMapEntry->SockAddr6, sizeof(pMapEntry->SockAddr6)); + pReturn = pResult; + } + } + + return(pReturn); +} + +/*F*************************************************************************************/ +/*! + \Function SocketAddrMapTranslate + + \Description + Translate the following: + - IPv4 real address to IPV4-mapped IPv6 address + - IPv4 virtual address to IPv6 address + - IPv6 address to IPv4 address + + \Input *pAddrMap - address map + \Input *pResult - [out] storage for translated result + \Input *pSource - source address + \Input *pNameLen - [out] storage for result length + + \Output + struct sockaddr * - pointer to resulting IPv6 address + + \Version 04/15/2013 (jbrookes) +*/ +/************************************************************************************F*/ +struct sockaddr *SocketAddrMapTranslate(const SocketAddrMapT *pAddrMap, struct sockaddr *pResult, const struct sockaddr *pSource, int32_t *pNameLen) +{ + SocketAddrMapEntryT *pMapEntry; + struct sockaddr *pReturn = NULL; + + // handle broken use cases where the family is not specified + if ((pSource->sa_family != AF_INET) && (pSource->sa_family != AF_INET6)) + { + NetPrintf(("dirtynet: unsupported source family %d in SocketAddrMapTranslate(); assuming AF_INET\n", pSource->sa_family)); + ((struct sockaddr *)pSource)->sa_family = AF_INET; + } + if ((pResult->sa_family != AF_INET) && (pResult->sa_family != AF_INET6)) + { + NetPrintf(("dirtynet: unsupported result family %d in SocketAddrMapTranslate(); assuming AF_INET\n", pResult->sa_family)); + pResult->sa_family = AF_INET; + } + + // handle IPv4->IPv6 + if ((pSource->sa_family == AF_INET) && (pResult->sa_family == AF_INET6)) + { + struct sockaddr_in6 *pResult6 = (struct sockaddr_in6 *)pResult; + uint32_t uAddr = SockaddrInGetAddr(pSource); + // handle a regular IPv4 address + if ((uAddr == 0) || ((uAddr >> 24) != 0)) + { + ds_memclr(pResult, sizeof(*pResult)); + _Sockaddr6SetV4Mapped(pResult6, pSource); + *pNameLen = sizeof(*pResult6); + pReturn = pResult; + } + // get IPv6 address from virtual IPv4 + else if ((pMapEntry = _SocketAddrMapGet(pAddrMap, pSource, *pNameLen)) != NULL) + { + ds_memcpy_s(pResult6, sizeof(*pResult6), &pMapEntry->SockAddr6, sizeof(pMapEntry->SockAddr6)); + pResult6->sin6_port = SocketNtohs(SockaddrInGetPort(pSource)); + *pNameLen = sizeof(*pResult6); + pReturn = pResult; + } + else + { + NetPrintf(("dirtynet: could not find address for virtual address %a\n", SockaddrInGetAddr(pSource))); + } + } + // handle IPv6->IPv4 + else if ((pSource->sa_family == AF_INET6) && (pResult->sa_family == AF_INET)) + { + struct sockaddr_in6 *pSource6 = (struct sockaddr_in6 *)pSource; + // translate IPv6 to virtual IPv4 address + if ((pMapEntry = _SocketAddrMapGet(pAddrMap, pSource, *pNameLen)) != NULL) + { + struct sockaddr *pResult4 = (struct sockaddr *)pResult; + SockaddrInit(pResult4, AF_INET); + SockaddrInSetAddr(pResult4, pMapEntry->iVirtualAddress); + SockaddrInSetPort(pResult4, SocketHtons(pSource6->sin6_port)); + *pNameLen = sizeof(*pResult4); + pReturn = pResult; + } + // is this an IPv4-mapped IPv6 address? + else if (_SockaddrIn6IsIPv4(pSource6)) + { + struct sockaddr *pResult4 = (struct sockaddr *)pResult; + uint32_t uAddress = SockaddrIn6GetAddr4(pSource6); + SockaddrInit(pResult4, AF_INET); + SockaddrInSetAddr(pResult4, uAddress); + SockaddrInSetPort(pResult4, SocketHtons(pSource6->sin6_port)); + *pNameLen = sizeof(*pResult4); + pReturn = pResult; + } + } + else + { + *pNameLen = (pResult->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + ds_memcpy_s(pResult, *pNameLen, pSource, *pNameLen); + pReturn = pResult; + } + if (pReturn != NULL) + { + NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 2, "dirtynet: map translate %A->%A\n", pSource, pReturn)); + } + else + { + NetPrintf(("dirtynet: map translate %A failed\n", pSource)); + pReturn = (struct sockaddr *)pSource; + *pNameLen = sizeof(*pSource); + } + return(pReturn); +} +#endif diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtynetpriv.h b/src/thirdparty/dirtysdk/source/dirtysock/dirtynetpriv.h new file mode 100644 index 00000000..79bfb8fd --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtynetpriv.h @@ -0,0 +1,212 @@ +/*H*************************************************************************************/ +/*! + \File dirtynetpriv.h + + \Description + Private include for platform independent interface to network layers. + + \Copyright + Copyright (c) Electronic Arts 2002-2014 + + \Version 1.0 08/07/2014 (jbrookes) First version, split from dirtynet.h +*/ +/*************************************************************************************H*/ + +#ifndef _dirtynetpriv_h +#define _dirtynetpriv_h + +/*! +\Moduledef DirtyNetPriv DirtyNetPriv +\Modulemember DirtySock +*/ +//@{ + +/*** Include files *********************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtynet.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! socket hostname cache +typedef struct SocketHostnameCacheT SocketHostnameCacheT; + +//! socket packet queue +typedef struct SocketPacketQueueT SocketPacketQueueT; + +//! socket packet queue entry +typedef struct SocketPacketQueueEntryT +{ + int32_t iPacketSize; //!< packet size + struct sockaddr PacketAddr; //!< packet source + uint32_t uPacketTick; //!< tick packet was added to the queue + uint8_t aPacketData[SOCKET_MAXUDPRECV]; //!< packet data +} SocketPacketQueueEntryT; + +//! socket rate estimation +typedef struct SocketRateT +{ + uint32_t uMaxRate; //!< maximum transfer rate in bytes/sec (zero=no limit), minimum 1460 bytes/sec + uint32_t uCurRate; //!< current transfer rate in bytes/sec + uint32_t uNextRate; //!< estimated rate at next update + uint32_t uLastTick; //!< last update tick + uint32_t uLastRateTick; //!< tick count at last rate update + uint32_t aTickHist[16]; //!< tick history (when update was recorded) + uint32_t aDataHist[16]; //!< data history (how much data was sent during update) + uint8_t aCallHist[16]; //!< call history (how many times we were updated)) + uint8_t uDataIndex; //!< current update index + uint8_t _pad[3]; +} SocketRateT; + +// socket address map entry (private) +typedef struct SocketAddrMapEntryT SocketAddrMapEntryT; + +//! socket address map +typedef struct SocketAddrMapT +{ + int32_t iNumEntries; + int32_t iNextVirtAddr; + int32_t iMemGroup; + void *pMemGroupUserData; + SocketAddrMapEntryT *pMapEntries; //!< variable-length array +} SocketAddrMapT; + + +/*** Variables *************************************************************************/ + +/*** Functions *************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + HostName Cache functions +*/ + +// create hostname cache +SocketHostnameCacheT *SocketHostnameCacheCreate(int32_t iMemGroup, void *pMemGroupUserData); + +// destroy hostname cache +void SocketHostnameCacheDestroy(SocketHostnameCacheT *pCache); + +// add entry to hostname cache +void SocketHostnameCacheAdd(SocketHostnameCacheT *pCache, const char *pStrHost, uint32_t uAddress, int32_t iVerbose); + +// get entry from hostname cache +uint32_t SocketHostnameCacheGet(SocketHostnameCacheT *pCache, const char *pStrHost, int32_t iVerbose); + +// del entry from hostname cache +void SocketHostnameCacheDel(SocketHostnameCacheT *pCache, const char *pStrHost, uint32_t uAddress, int32_t iVerbose); + +// process cache entries to delete expired +void SocketHostnameCacheProcess(SocketHostnameCacheT *pCache, int32_t iVerbose); + +// check for refcounted (in-progress) hostname lookup then addref, or force using a new entry if bUseRef=false +HostentT *SocketHostnameAddRef(HostentT **ppHostList, HostentT *pHost, uint8_t bUseRef); + +// process hostname list, delete completed lookups +void SocketHostnameListProcess(HostentT **ppHostList, int32_t iMemGroup, void *pMemGroupUserData); + +/* + Packet Queue functions +*/ + +// create packet queue +SocketPacketQueueT *SocketPacketQueueCreate(int32_t iMaxPackets, int32_t iMemGroup, void *pMemGroupUserData); + +// destroy packet queue +void SocketPacketQueueDestroy(SocketPacketQueueT *pPacketQueue); + +// resize a packet queue +SocketPacketQueueT *SocketPacketQueueResize(SocketPacketQueueT *pPacketQueue, int32_t iMaxPackets, int32_t iMemGroup, void *pMemGroupUserData); + +// packet queue control function +int32_t SocketPacketQueueControl(SocketPacketQueueT *pPacketQueue, int32_t iControl, int32_t iValue); + +// packet queue status function +int32_t SocketPacketQueueStatus(SocketPacketQueueT *pPacketQueue, int32_t iStatus); + +// add to packet queue +int32_t SocketPacketQueueAdd(SocketPacketQueueT *pPacketQueue, const uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr); +int32_t SocketPacketQueueAdd2(SocketPacketQueueT *pPacketQueue, const uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr, uint32_t bPartialAllowed); + +// alloc packet queue entry +SocketPacketQueueEntryT *SocketPacketQueueAlloc(SocketPacketQueueT *pPacketQueue); + +// undo previous call to SocketPacketQueueAlloc() +void SocketPacketQueueAllocUndo(SocketPacketQueueT *pPacketQueue); + +// remove from packet queue +int32_t SocketPacketQueueRem(SocketPacketQueueT *pPacketQueue, uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr); + +// remove stream packet from packet queue +int32_t SocketPacketQueueRemStream(SocketPacketQueueT *pPacketQueue, uint8_t *pPacketData, int32_t iPacketSize); + +// get pointers to data associated with queue head +int32_t SocketPacketQueueGetHead(SocketPacketQueueT *pPacketQueue, uint8_t **ppPacketData, int32_t *pPacketSize, struct sockaddr **ppPacketAddr); + +// update queue's head entry such that it no longer includes successfully consumed portion of data +int32_t SocketPacketQueueTouchHead(SocketPacketQueueT *pPacketQueue, int32_t iConsumedSize); + +/* + Rate functions +*/ + +// update socket data rate calculations +void SocketRateUpdate(SocketRateT *pRate, int32_t iData, const char *pOpName); + +// throttle based on socket data rate calcuations and configured max rate +int32_t SocketRateThrottle(SocketRateT *pRate, int32_t iSockType, int32_t iData, const char *pOpName); + +/* + Send Callback functions +*/ + +// register a new socket send callback +int32_t SocketSendCallbackAdd(SocketSendCallbackEntryT aCbList[], SocketSendCallbackEntryT *pCbEntry); + +// removes a socket send callback previously registered +int32_t SocketSendCallbackRem(SocketSendCallbackEntryT aCbList[], SocketSendCallbackEntryT *pCbEntry); + +/* + + AddrMap functions + +*/ + +// initialize a socket address map +void SocketAddrMapInit(SocketAddrMapT *pAddrMap, int32_t iMemGroup, void *pMemGroupUserData); + +// cleanup a socket address map +void SocketAddrMapShutdown(SocketAddrMapT *pAddrMap); + +// check if source address is in address map, and if so return mapped address +struct sockaddr *SocketAddrMapGet(const SocketAddrMapT *pAddrMap, struct sockaddr *pResult, const struct sockaddr *pSource, int32_t *pNameLen); + +// translate between IPv4, vIPv4, and IPv6 addresses +struct sockaddr *SocketAddrMapTranslate(const SocketAddrMapT *pAddrMap, struct sockaddr *pResult, const struct sockaddr *pSource, int32_t *pNameLen); + +// map an IPv6 address and return a vIPv4 address that can be used to reference it +int32_t SocketAddrMapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize); + +// remap an existing IPv6 address and return a vIPv4 address that can be used to reference it +int32_t SocketAddrRemapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pOldAddr, const struct sockaddr *pNewAddr, int32_t iAddrSize); + +// removes an addres mapping from the mapping table +int32_t SocketAddrUnmapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize); + + +#ifdef __cplusplus +} +#endif + +//@} + +#endif // _dirtynetpriv_h + + diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtythread.cpp b/src/thirdparty/dirtysdk/source/dirtysock/dirtythread.cpp new file mode 100644 index 00000000..49518cbe --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtythread.cpp @@ -0,0 +1,262 @@ +/*H**************************************************************************************/ +/*! + \File dirtythread.cpp + + \Description + Provide threading library functions for use by network layer code. + + \Copyright + Copyright (c) Electronic Arts 2017 + + \Version 09/27/17 (eesponda) +*/ +/**************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include "eathread/eathread_condition.h" +#include "eathread/eathread_mutex.h" +#include "eathread/eathread_thread.h" + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/dirtysock/dirtythread.h" + +/*** Type Definitions ******************************************************************/ + +// data about your thread +typedef struct DirtyThreadT +{ + DirtyRunnableFunctionT *pFunction; //!< the function that implements the guts of the thread + void *pUserData; //!< user data passed along with the function + + int32_t iMemGroup; //!< memgroup identifier + void *pMemGroupUserData; //!< memgroup userdata +} DirtyThreadT; + +struct DirtyConditionRefT +{ + EA::Thread::Condition Condition; //!< condition state, must come first + + int32_t iMemGroup; //!< memory group identifier + void *pMemGroupUserdata; //!< user data passed along with allocation +}; + +/*** Private Functions *****************************************************************/ + +/*F**************************************************************************************/ +/*! + \Function _DirtyThreadWrapper + + \Description + Wrapper function that allows us to log and cleanup thread state + + \Input *pUserData - our threading state + + \Output + intptr_t - thread result + + \Version 09/28/2017 (eesponda) +*/ +/***************************************************************************************F*/ +EA_DISABLE_VC_WARNING(4702) +static intptr_t _DirtyThreadWrapper(void *pUserData) +{ + DirtyThreadT *pThread = (DirtyThreadT *)pUserData; + + // run the thread function + pThread->pFunction(pThread->pUserData); + + // clean up and return + DirtyMemFree(pThread, DIRTYTHREAD_MEMID, pThread->iMemGroup, pThread->pMemGroupUserData); + + return(0); +} +EA_RESTORE_VC_WARNING() + +/*** Public Functions *******************************************************************/ + + +/*F**************************************************************************************/ +/*! + \Function DirtyThreadCreate + + \Description + Allocate the thread state and start a thread, running it in our own wrapper function + + \Input *pFunction - function that is run in the thread + \Input *pUserData - user data passed along with that function + \Input *pConfig - addtional configuration needed + + \Output + int32_t - zero=success, negative=failure + + \Version 09/28/2017 (eesponda) +*/ +/***************************************************************************************F*/ +int32_t DirtyThreadCreate(DirtyRunnableFunctionT *pFunction, void *pUserData, const DirtyThreadConfigT *pConfig) +{ + /* we create the thread object on the stack here as it is not tied to the lifetime of the thread + just facilitates its creation */ + EA::Thread::Thread Thread; + EA::Thread::ThreadParameters Parameters; + DirtyThreadT *pThread; + int32_t iMemGroup; + void *pMemGroupUserData; + + // query memgroup info + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate thread state + if ((pThread = (DirtyThreadT *)DirtyMemAlloc(sizeof(*pThread), DIRTYTHREAD_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + return(-1); + } + ds_memclr(pThread, sizeof(*pThread)); + pThread->pFunction = pFunction; + pThread->pUserData = pUserData; + pThread->iMemGroup = iMemGroup; + pThread->pMemGroupUserData = pMemGroupUserData; + + Parameters.mnAffinityMask = (pConfig->iAffinity > 0) ? pConfig->iAffinity : EA::Thread::kThreadAffinityMaskAny; + Parameters.mnPriority = pConfig->iPriority; + Parameters.mnProcessor = EA::Thread::kProcessorAny; + Parameters.mpName = pConfig->pName; + + // start the thread + if (Thread.Begin(_DirtyThreadWrapper, pThread, &Parameters) == EA::Thread::kThreadIdInvalid) + { + DirtyMemFree(pThread, DIRTYTHREAD_MEMID, iMemGroup, pMemGroupUserData); + return(-2); + } + return(0); +} + +/*F**************************************************************************************/ +/*! + \Function DirtyThreadGetThreadId + + \Description + Gets the thread identifier that can be used for logging purposes + + \Input *pBuffer - [out] the output buffer for the thread id + \Input iBufSize - size of the output buffer + + \Output + const char *- pointer to the output buffer passed in + + \Version 02/04/2020 (eesponda) +*/ +/***************************************************************************************F*/ +const char *DirtyThreadGetThreadId(char *pBuffer, int32_t iBufSize) +{ + return(ds_strnzcpy(pBuffer, EAThreadSysThreadIdToString(EA::Thread::GetSysThreadId()), iBufSize)); +} + +/*F**************************************************************************************/ +/*! + \Function DirtyConditionCreate + + \Description + Creates a conditional variable with the given name + + \Input *pName - name for the conditional + + \Output + DirtyConditionRefT * - object ref on success, NULL otherwise + + \Version 09/27/2017 (eesponda) +*/ +/***************************************************************************************F*/ +DirtyConditionRefT *DirtyConditionCreate(const char *pName) +{ + DirtyConditionRefT *pCondition; + int32_t iMemGroup; + void *pMemGroupUserdata; + EA::Thread::ConditionParameters Params; + + // query memgroup info + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserdata); + + // setup parameters + ds_strnzcpy(Params.mName, pName, sizeof(Params.mName)); + Params.mbIntraProcess = true; + + // allocate the memory needed for condition state + if ((pCondition = (DirtyConditionRefT *)DirtyMemAlloc(sizeof(*pCondition), DIRTYTHREAD_MEMID, iMemGroup, pMemGroupUserdata)) == NULL) + { + NetPrintf(("dirtythread: unable to allocate state for condition variable\n")); + return(NULL); + } + ds_memclr(pCondition, sizeof(*pCondition)); + pCondition->iMemGroup = iMemGroup; + pCondition->pMemGroupUserdata = pMemGroupUserdata; + if (!pCondition->Condition.Init(&Params)) + { + NetPrintf(("dirtythread: [%p] condition init returned false\n", pCondition)); + DirtyConditionDestroy(pCondition); + return(NULL); + } + + return(pCondition); +} + +/*F**************************************************************************************/ +/*! + \Function DirtyConditionDestroy + + \Description + Destroys the condition variable + + \Input *pCondition - condition variable state + + \Version 09/27/2017 (eesponda) +*/ +/***************************************************************************************F*/ +void DirtyConditionDestroy(DirtyConditionRefT *pCondition) +{ + pCondition->Condition.~Condition(); + DirtyMemFree(pCondition, DIRTYTHREAD_MEMID, pCondition->iMemGroup, pCondition->pMemGroupUserdata); +} + +/*F**************************************************************************************/ +/*! + \Function DirtyConditionWait + + \Description + Waits on a condition given a crit (mutex) + + \Input *pCondition - condition variable state + \Input *pCrit - mutex used for wait + + \Version 09/27/2017 (eesponda) +*/ +/***************************************************************************************F*/ +void DirtyConditionWait(DirtyConditionRefT *pCondition, NetCritT *pCrit) +{ + // we can cast here since we know that the first field in crit is the mutex + EA::Thread::Mutex *pMutex = (EA::Thread::Mutex *)pCrit->pData; + pCondition->Condition.Wait(pMutex); +} + +/*F**************************************************************************************/ +/*! + \Function DirtyConditionSignal + + \Description + Signals a condition + + \Input *pCondition - condition variable state + + \Output + uint8_t - TRUE if successful, FALSE otherwsie + + \Version 09/27/2017 (eesponda) +*/ +/***************************************************************************************F*/ +uint8_t DirtyConditionSignal(DirtyConditionRefT *pCondition) +{ + return(pCondition->Condition.Signal()); +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/dirtyuser.c b/src/thirdparty/dirtysdk/source/dirtysock/dirtyuser.c new file mode 100644 index 00000000..945bc6f0 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/dirtyuser.c @@ -0,0 +1,117 @@ +/*H********************************************************************************/ +/*! + + \File dirtyuser.c + + \Description + Generic user functions. Given that all our supported platforms use + uint64_t, we can combine all our functions. + + \Copyright + Copyright (c) Electronic Arts 2020. ALL RIGHTS RESERVED. + + \Version 03/05/20 (eesponda) +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtyuser.h" +#include "DirtySDK/util/binary7.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function DirtyUserToNativeUser + + \Description + Convert a DirtyUserT to native format. + + \Input *pOutput - [out] storage for native format user + \Input iBufLen - length of output buffer + \Input *pUser - source user to convert + + \Output + uint32_t - TRUE if successful, else FALSE + + \Version 03/05/2020 (eesponda) +*/ +/********************************************************************************F*/ +uint32_t DirtyUserToNativeUser(void *pOutput, int32_t iBufLen, const DirtyUserT *pUser) +{ + // make sure output buffer is big enough + if (iBufLen < (signed)sizeof(uint64_t)) + { + return(FALSE); + } + // make sure the encoding is correct + if (*pUser->strNativeUser != '^') + { + return(FALSE); + } + + // decode contents of pUser + Binary7Decode((uint8_t *)pOutput, iBufLen, (const uint8_t *)pUser->strNativeUser+1); + + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function DirtyUserFromNativeUser + + \Description + Convert native format user data to a DirtyUserT. + + \Input *pUser - [out] storage for output DirtyUserT + \Input *pInput - pointer to native format user + + \Output + uint32_t - TRUE if successful, else FALSE + + \Version 03/05/2020 (eesponda) +*/ +/********************************************************************************F*/ +uint32_t DirtyUserFromNativeUser(DirtyUserT *pUser, const void *pInput) +{ + uint64_t *pPlayerId = (uint64_t *)pInput; + // clear output data + ds_memclr(pUser, sizeof(*pUser)); + + // encode input SceNpAccountId into pUser + pUser->strNativeUser[0] = '^'; + Binary7Encode((uint8_t *)pUser->strNativeUser+1, sizeof(*pUser)-1, (uint8_t *)pPlayerId, sizeof(*pPlayerId), TRUE); + + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function DirtyUserCompare + + \Description + Compare two DirtyUserT users for equality. + + \Input *pUser1 - user 1 + \Input *pUser2 - user 2 + + \Output + int32_t - TRUE if successful, else FALSE + + \Version 03/05/2020 (eesponda) +*/ +/********************************************************************************F*/ +int32_t DirtyUserCompare(DirtyUserT *pUser1, DirtyUserT *pUser2) +{ + return(strcmp(pUser1->strNativeUser, pUser2->strNativeUser) == 0); +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/netconn.c b/src/thirdparty/dirtysdk/source/dirtysock/netconn.c new file mode 100644 index 00000000..300e69be --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/netconn.c @@ -0,0 +1,611 @@ +/*H*************************************************************************************************/ +/*! + + \File netconn.c + + \Description + Provides network setup and teardown support. Does not actually create any connections. + + \Notes + None. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2001-2017. ALL RIGHTS RESERVED. + + \Version 1.0 03/12/01 (GWS) First Version + +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtycert.h" +#include "DirtySDK/dirtysock/netconn.h" + +/*** Defines **************************************************************************************/ + +//! maximum number of idle handlers that may be registered +#define NETCONN_MAXIDLEHANDLERS (64) + +/*** Macros ***************************************************************************************/ + +/*** Type Definitions *****************************************************************************/ + +/*** Function Prototypes **************************************************************************/ + +/*** Variables ************************************************************************************/ + +static struct +{ + void (*proc)(void *pData, uint32_t uTick); + void *data; +} _NETidle[NETCONN_MAXIDLEHANDLERS]; + +static const char _NetConn_HexEncode[16] = "0123456789abcdef"; + +static uint32_t _NetConn_uLastIdleTick = 0; +static uint8_t _NetConn_bTickInitialized = FALSE; +static uint8_t _NetConn_bNetConnIdleTiming = FALSE; +static uint32_t _NetConn_uMachineId = 0; + +/*** Private Functions ****************************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _NetConnIdleProcs + + \Description + Process any registered idle handlers. This is for internal use only. + + \Input uCurTick - current millisecond tick count + + \Output + None. + + \Version 05/26/2005 (doneill) +*/ +/********************************************************************************F*/ +static void _NetConnIdleProcs(uint32_t uCurTick) +{ + int32_t iProc; + + // call other idle handlers + for (iProc = 0; iProc < NETCONN_MAXIDLEHANDLERS; iProc++) + { + if (_NETidle[iProc].proc != NULL) + { + (_NETidle[iProc].proc)(_NETidle[iProc].data, uCurTick); + } + } +} + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _NetConnMonitorRate + + \Description + Monitor the rate at which NetconnIdle is called. Output diagnosis messages + + \Input uCurrTick - current millisecond tick count + iTickDiff - tick difference between previous call + + \Output + None. + + \Version 06/11/2009 (jrainy) +*/ +/********************************************************************************F*/ +static void _NetConnMonitorRate(uint32_t uCurrTick, int32_t iTickDiff) +{ + static int32_t iMaxDiff = 0; + static int32_t iMinDiff = 0; + static int32_t iRunningCount = 0; + static int32_t uStartTick = 0; + static int32_t uLastTick = 0; + + if ((iTickDiff < iMinDiff) || (iTickDiff > iMaxDiff) || (NetTickDiff(uCurrTick, uStartTick) > 2000)) + { + if (iRunningCount) + { + NetPrintf(("netconn: %d netconnidles of %d to %d ms between times %d and %d.\n", iRunningCount, iMinDiff, iMaxDiff, uStartTick, uLastTick)); + uStartTick = uLastTick; + } + else + { + uStartTick = uCurrTick; + } + iRunningCount = 1; + iMaxDiff = (iTickDiff * 15) / 10; + iMinDiff = (iTickDiff * 5) / 10; + } + else + { + iRunningCount++; + } + uLastTick = uCurrTick; +} +#endif + +/*** Public functions *****************************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function NetConnMAC + + \Description + Return the network MAC address string to the caller + + \Output + const char * - textual MAC address string + + \Version 1.0 04/29/01 (GWS) First Version +*/ +/*************************************************************************************************F*/ +const char *NetConnMAC(void) +{ + static union + { + unsigned char uMac[16]; + } Stack; + static char strMAC[16] = ""; + + // see if we need to query mac address + if (strMAC[0] == 0) + { + // get MAC address + if (NetConnStatus('macx', 0, Stack.uMac, sizeof(Stack.uMac)) >= 0) + { + // format into string + strMAC[0] = '$'; + strMAC[1] = _NetConn_HexEncode[Stack.uMac[0]>>4]; + strMAC[2] = _NetConn_HexEncode[Stack.uMac[0]&15]; + strMAC[3] = _NetConn_HexEncode[Stack.uMac[1]>>4]; + strMAC[4] = _NetConn_HexEncode[Stack.uMac[1]&15]; + strMAC[5] = _NetConn_HexEncode[Stack.uMac[2]>>4]; + strMAC[6] = _NetConn_HexEncode[Stack.uMac[2]&15]; + strMAC[7] = _NetConn_HexEncode[Stack.uMac[3]>>4]; + strMAC[8] = _NetConn_HexEncode[Stack.uMac[3]&15]; + strMAC[9] = _NetConn_HexEncode[Stack.uMac[4]>>4]; + strMAC[10] = _NetConn_HexEncode[Stack.uMac[4]&15]; + strMAC[11] = _NetConn_HexEncode[Stack.uMac[5]>>4]; + strMAC[12] = _NetConn_HexEncode[Stack.uMac[5]&15]; + strMAC[13] = 0; + } + } + + return(strMAC); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnElapsed + + \Description + Return elapsed time in milliseconds. The epoch (zero time) is not defined. + This function should be used to determine elapsed time by calling once, + saving the result, then later calling again and subtracting the original + result from the new result. This will give the elapsed time in millisecs. + + \Output + uint32_t - elapsed milliseconds + + \Version 1.0 03/10/01 (GWS) First Version +*/ +/*************************************************************************************************F*/ +uint32_t NetConnElapsed(void) +{ + return(NetTick()); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnIdleAdd + + \Description + Add an idle handler that will get called periodically. The frequency of calls + is not guarenteed, but should be 60Hz min. + + \Input *proc - callback function + \Input *data - reference data provided to callback function + + \Output + int32_t - negative=error, zero=success + + \Version 1.0 03/10/01 (GWS) First Version +*/ +/*************************************************************************************************F*/ +int32_t NetConnIdleAdd(void (*proc)(void *data, uint32_t tick), void *data) +{ + int32_t i; + + // locate slot and add + for (i = 0; i < NETCONN_MAXIDLEHANDLERS; ++i) + { + // make sure it's not already added + if ((_NETidle[i].proc == proc) && (_NETidle[i].data == data)) + { + NetPrintf(("netconn: ignoring add of an idle handler that is already registered\n")); + return(-1); + } + + // if there's space in the table, add the function + if (_NETidle[i].proc == NULL) + { + _NETidle[i].proc = proc; + _NETidle[i].data = data; + return(0); + } + } + + // warn the user that the add failed + NetPrintf(("netconn: unable to add new idle handler as table is full\n")); + + // no space in table + return(-2); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnIdleDel + + \Description + Remove a previously added idle handler. The data parameter must match what + was given when the function was added. + + \Input *proc - callback function + \Input *data - same value given to NetConnIdleAdd + + \Output + int32_t - negative=error, zero=success + + \Version 1.0 03/10/01 (GWS) First Version +*/ +/*************************************************************************************************F*/ +int32_t NetConnIdleDel(void (*proc)(void *data, uint32_t tick), void *data) +{ + int32_t i; + + // locate slot and del + for (i = 0; i < NETCONN_MAXIDLEHANDLERS; ++i) + { + if ((_NETidle[i].proc == proc) && (_NETidle[i].data == data)) + { + _NETidle[i].proc = NULL; + _NETidle[i].data = NULL; + return(0); + } + } + + // warn the user the handler did not exist + NetPrintf(("netconn: ignoring delete of an idle handler that is not registered\n")); + + // not in table + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnIdle + + \Description + Provide "life" to the network code. This function needs to be called periodically + (typically once per game loop, generally speaking 5-10hz minimum) for the network code to + function properly. + + \Version 1.0 03/10/01 (GWS) First Version +*/ +/*************************************************************************************************F*/ +void NetConnIdle(void) +{ + uint32_t uCurTick; + + // make sure module is available + if (NetConnStatus('open', 0, NULL, 0) == 0) + { + return; + } + + // get current tick count + uCurTick = NetTick(); + + // initialized last tick counter? + if (_NetConn_bTickInitialized == FALSE) + { + _NetConn_uLastIdleTick = uCurTick - 5; + _NetConn_bTickInitialized = TRUE; + } + + // debug timing of idle rate + #if DIRTYCODE_LOGGING + if (_NetConn_bNetConnIdleTiming) + { + int32_t iTickDiff = NetTickDiff(uCurTick, _NetConn_uLastIdleTick); + _NetConnMonitorRate(uCurTick, iTickDiff); + } + #endif + + _NetConn_uLastIdleTick = uCurTick; + + // call registered idle handlers + _NetConnIdleProcs(uCurTick); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnIdleShutdown + + \Description + Shut down the NetConnIdle handler. + This function is intended for internal use only. It should not be called by an application. + + \Version 1.0 06/16/04 (JLB) First Version +*/ +/*************************************************************************************************F*/ +void NetConnIdleShutdown(void) +{ + int32_t iProc; + + for (iProc = 0; iProc < NETCONN_MAXIDLEHANDLERS; iProc++) + { + if (_NETidle[iProc].proc != NULL) + { + NetPrintf(("netconn: removing idle handler at shutdown\n")); + _NETidle[iProc].proc = NULL; + _NETidle[iProc].data = NULL; + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnTiming + + \Description + Enable or disable the timing of netconnidles + + \Input uEnableTiming - on or off + + \Output + None. + + \Version 1.0 11/06/09 (jrainy) First Version +*/ +/*************************************************************************************************F*/ +void NetConnTiming(uint8_t uEnableTiming) +{ + _NetConn_bNetConnIdleTiming = uEnableTiming; +} + +#if DIRTYCODE_LOGGING +#define MONITOR_SIZE 200 +#define MONITOR_VARIABLES 64 +#define MONITOR_NAME_SIZE 64 +void NetConnMonitorValue(const char* pName, int32_t iValue) +{ + int32_t iIndex, iPrintIndex; + static char strNames[MONITOR_VARIABLES][MONITOR_NAME_SIZE] = {{0}}; + static int32_t iMonitoredValues[MONITOR_VARIABLES][MONITOR_SIZE] = {{0}}; + static int32_t iMonitoredCount[MONITOR_VARIABLES] = {0}; + + for(iIndex = 0; iIndex < MONITOR_VARIABLES; iIndex++) + { + if ((!ds_strnicmp(pName, strNames[iIndex], MONITOR_NAME_SIZE)) || (strNames[iIndex][0] == 0)) + { + break; + } + } + + if (iIndex == MONITOR_VARIABLES) + { + NetPrintf(("netconn: too many monitored variables\n")); + return; + } + + if (!strNames[iIndex][0]) + { + ds_strnzcpy(strNames[iIndex], pName, MONITOR_NAME_SIZE); + } + + iMonitoredValues[iIndex][iMonitoredCount[iIndex]++] = iValue; + + if (iMonitoredCount[iIndex] == MONITOR_SIZE) + { + NetPrintf(("NetConn: displaying monitored values \"%s\"\n", strNames[iIndex])); + for(iPrintIndex = 0; iPrintIndex < MONITOR_SIZE; iPrintIndex += 10) + { + NetPrintf(("%d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", + iMonitoredValues[iIndex][iPrintIndex + 0], + iMonitoredValues[iIndex][iPrintIndex + 1], + iMonitoredValues[iIndex][iPrintIndex + 2], + iMonitoredValues[iIndex][iPrintIndex + 3], + iMonitoredValues[iIndex][iPrintIndex + 4], + iMonitoredValues[iIndex][iPrintIndex + 5], + iMonitoredValues[iIndex][iPrintIndex + 6], + iMonitoredValues[iIndex][iPrintIndex + 7], + iMonitoredValues[iIndex][iPrintIndex + 8], + iMonitoredValues[iIndex][iPrintIndex + 9])); + } + iMonitoredCount[iIndex] = 0; + } + + + +} + +#endif + +/*F********************************************************************************/ +/*! + \Function NetConnMachineId + + \Description + Gets a unique id for this machine. + + \Output + uint32_t - machine id of this machine. + + \Version 01/31/2014 (cvienneau) +*/ +/********************************************************************************F*/ +uint32_t NetConnMachineId(void) +{ + return(_NetConn_uMachineId); +} + +/*F********************************************************************************/ +/*! + \Function NetConnSetMachineId + + \Description + Sets a unique id for this machine. + + \Input uMachineId - new value + + \Version 01/31/2014 (cvienneau) +*/ +/********************************************************************************F*/ +void NetConnSetMachineId(uint32_t uMachineId) +{ + _NetConn_uMachineId = uMachineId; + NetPrintf(("netconn: machineId set to %x\n", uMachineId)); +} + + +/*F********************************************************************************/ +/*! + \Function NetConnCopyParam + + \Description + Copy a command-line parameter. + + \Input *pDst - output buffer + \Input iDstLen - output buffer length + \Input *pParamName - name of parameter to check for + \Input *pSrc - input string to look for parameters in + \Input *pDefault - default string to use if paramname not found + + \Output + int32_t - number of bytes written + + \Version 07/18/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetConnCopyParam(char *pDst, int32_t iDstLen, const char *pParamName, const char *pSrc, const char *pDefault) +{ + int32_t iIndex; + + // find parameter + if ((pSrc = strstr(pSrc, pParamName)) == NULL) + { + // copy in default + ds_strnzcpy(pDst, pDefault, iDstLen); + return((int32_t)strlen(pDefault)); + } + + // skip parameter name + pSrc += strlen(pParamName); + + // make sure buffer has enough room + if (--iDstLen < 0) + { + return(0); + } + + // copy the string + for (iIndex = 0; (iIndex < iDstLen) && (pSrc[iIndex] != '\0') && (pSrc[iIndex] != ' '); iIndex++) + { + pDst[iIndex] = pSrc[iIndex]; + } + + // write null terminator and return number of bytes written + pDst[iIndex] = '\0'; + return(iIndex); +} + +/*F********************************************************************************/ +/*! + \Function NetConnDirtyCertCreate + + \Description + Create DirtyCert, intitialize service name if provided + + \Input *pParams - input params + + \Output + int32_t - negative=failure, else success + + \Version 03/28/2013 (jbrookes) + */ +/********************************************************************************F*/ +int32_t NetConnDirtyCertCreate(const char *pParams) +{ + char strServiceName[DIRTYCERT_SERVICENAME_SIZE]; + + // create the dirtycert module + if (DirtyCertCreate() != 0) + { + NetConnShutdown(0); + NetPrintf(("netconn: unable to create dirtycert\n")); + return(-1); + } + // check for servicename + if (strstr(pParams, "-servicename=") != NULL) + { + // get service name + NetConnCopyParam(strServiceName, sizeof(strServiceName), "-servicename=", pParams, ""); + // set service name in dirtycert + DirtyCertControl('snam', 0, 0, strServiceName); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetConnGetEnvStr + + \Description + Translate specified NETCONN_PLATENV_ define into an environment string + + \Output + const char * - pointer to env str + + \Version 07/09/2013 (jbrookes) + */ +/********************************************************************************F*/ +const char *NetConnGetEnvStr(void) +{ + const char *pEnv; + switch(NetConnStatus('envi', 0, NULL, 0)) + { + case NETCONN_PLATENV_DEV: + pEnv = "dev"; + break; + + case NETCONN_PLATENV_TEST: + pEnv = "test"; + break; + + case NETCONN_PLATENV_CERT: + pEnv = "cert"; + break; + + case NETCONN_PLATENV_PROD: + pEnv = "prod"; + break; + + default: + NetPrintf(("netconn: could not get env str\n")); + pEnv = "unkn"; + break; + } + return(pEnv); +} + diff --git a/src/thirdparty/dirtysdk/source/dirtysock/netconncommon.c b/src/thirdparty/dirtysdk/source/dirtysock/netconncommon.c new file mode 100644 index 00000000..7c5c695d --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/netconncommon.c @@ -0,0 +1,414 @@ +/*H********************************************************************************/ +/*! + \File netconncommon.c + + \Description + Cross-platform netconn data types and functions. + + \Copyright + Copyright (c) 2014 Electronic Arts Inc. + + \Version 05/21/2014 (mclouatre) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtycert.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protossl.h" +#include "netconncommon.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function NetConnCommonStartup + + \Description + Start up common functionality. + + \Input iNetConnRefSize - size of netconn ref to allocate + \Input *pParams - startup parameters + \Input *pRef - (out) netconn ref if successful; else NULL + + \Output + int32_t - 0 for success; negative for error + + \Version 05/21/2014 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t NetConnCommonStartup(int32_t iNetConnRefSize, const char *pParams, NetConnCommonRefT **pRef) +{ + NetConnCommonRefT *pCommonRef = *pRef; + int32_t iMemGroup; + void *pMemGroupUserData; + + // query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // refcount if already started + if (pCommonRef != NULL) + { + ++(pCommonRef->iRefCount); + NetPrintf(("netconncommon: NetConnStartup() called with params='%s' while the module is already started, refcounting to %d\n", pParams, pCommonRef->iRefCount)); + return(NETCONN_ERROR_ALREADY_STARTED); + } + + // alloc and init ref + if ((pCommonRef = (NetConnCommonRefT *)DirtyMemAlloc(iNetConnRefSize, NETCONN_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("netconncommon: unable to allocate module state\n")); + return(NETCONN_ERROR_NO_MEMORY); + } + ds_memclr(pCommonRef, iNetConnRefSize); + pCommonRef->iMemGroup = iMemGroup; + pCommonRef->pMemGroupUserData = pMemGroupUserData; + pCommonRef->iRefCount = 1; + pCommonRef->iDebugLevel = 1; + + // allocate external cleanup list + pCommonRef->iExternalCleanupListMax = NETCONN_EXTERNAL_CLEANUP_LIST_INITIAL_CAPACITY; + if ((pCommonRef->pExternalCleanupList = (NetConnExternalCleanupEntryT *)DirtyMemAlloc(pCommonRef->iExternalCleanupListMax * sizeof(NetConnExternalCleanupEntryT), NETCONN_MEMID, pCommonRef->iMemGroup, pCommonRef->pMemGroupUserData)) == NULL) + { + DirtyMemFree(pCommonRef, NETCONN_MEMID, pCommonRef->iMemGroup, pCommonRef->pMemGroupUserData); + NetPrintf(("netconncommmon: unable to allocate memory for initial external cleanup list\n")); + (*pRef) = NULL; + return(NETCONN_ERROR_NO_MEMORY); + } + ds_memclr(pCommonRef->pExternalCleanupList, pCommonRef->iExternalCleanupListMax * sizeof(NetConnExternalCleanupEntryT)); + + NetCritInit(&pCommonRef->crit, "netconnCrit"); + + // save ref + (*pRef) = pCommonRef; + + // successful completion + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetConnCommonShutdown + + \Description + Shutdown common functionality. + + \Input *pCommonRef - common module state + + \Version 05/21/2014 (mclouatre) +*/ +/********************************************************************************F*/ +void NetConnCommonShutdown(NetConnCommonRefT *pCommonRef) +{ + NetCritKill(&pCommonRef->crit); + + // free the cleanup list memory + if (pCommonRef->pExternalCleanupList != NULL) + { + DirtyMemFree(pCommonRef->pExternalCleanupList, NETCONN_MEMID, pCommonRef->iMemGroup, pCommonRef->pMemGroupUserData); + pCommonRef->pExternalCleanupList = NULL; + } + + // dispose of ref + DirtyMemFree(pCommonRef, NETCONN_MEMID, pCommonRef->iMemGroup, pCommonRef->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function NetConnCommonAddToExternalCleanupList + + \Description + Add an entry to the list of external module pending successful cleanup. + + \Input *pCommonRef - NetConnCommonRefT reference + \Input *pCleanupCb - cleanup callback + \Input *pCleanupData - data to be passed to cleanup callback + + \Output + uint32_t - 0 for success; -1 for failure. + + \Version 12/07/2009 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t NetConnCommonAddToExternalCleanupList(NetConnCommonRefT *pCommonRef, NetConnExternalCleanupCallbackT pCleanupCb, void *pCleanupData) +{ + NetCritEnter(&pCommonRef->crit); + + // if list if full, double its size. + if (pCommonRef->iExternalCleanupListCnt == pCommonRef->iExternalCleanupListMax) + { + NetConnExternalCleanupEntryT *pNewList; + + // allocate new external cleanup list + if ((pNewList = (NetConnExternalCleanupEntryT *) DirtyMemAlloc(2 * pCommonRef->iExternalCleanupListMax * sizeof(NetConnExternalCleanupEntryT), NETCONN_MEMID, pCommonRef->iMemGroup, pCommonRef->pMemGroupUserData)) == NULL) + { + NetPrintf(("netconncommon: unable to allocate memory for the external cleanup list\n")); + NetCritLeave(&pCommonRef->crit); + return(-1); + } + ds_memclr(pNewList, 2 * pCommonRef->iExternalCleanupListMax * sizeof(NetConnExternalCleanupEntryT)); + + // copy contents of old list in contents of new list + ds_memcpy(pNewList, pCommonRef->pExternalCleanupList, pCommonRef->iExternalCleanupListMax * sizeof(NetConnExternalCleanupEntryT)); + + // free old list and replace it with new list + DirtyMemFree(pCommonRef->pExternalCleanupList, NETCONN_MEMID, pCommonRef->iMemGroup, pCommonRef->pMemGroupUserData); + pCommonRef->pExternalCleanupList = pNewList; + pCommonRef->iExternalCleanupListMax = pCommonRef->iExternalCleanupListMax * 2; + } + + // add new entry to the list + pCommonRef->pExternalCleanupList[pCommonRef->iExternalCleanupListCnt].pCleanupCb = pCleanupCb; + pCommonRef->pExternalCleanupList[pCommonRef->iExternalCleanupListCnt].pCleanupData = pCleanupData; + pCommonRef->iExternalCleanupListCnt += 1; + + NetCritLeave(&pCommonRef->crit); + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetConnCommonProcessExternalCleanupList + + \Description + Walk external cleanup list and try to destroy each individual entry. + + \Input *pCommonRef - NetConnCommonRefT reference + + \Output + int32_t - number of valid entries in the cleanup list, negative integer upon failure + + \Version 03/13/2017 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t NetConnCommonProcessExternalCleanupList(NetConnCommonRefT *pCommonRef) +{ + int32_t iEntryIndex; + int32_t iEntryIndex2; + + // early exit if cleanup list not yet allocated + if (pCommonRef->pExternalCleanupList == NULL) + { + return(-1); + } + + // make sure we don't re-enter this code; some modules may call NetConnStatus() during their cleanup + if (!NetCritTry(&pCommonRef->crit)) + { + return(-2); + } + + for (iEntryIndex = 0; iEntryIndex < pCommonRef->iExternalCleanupListMax; iEntryIndex++) + { + if (pCommonRef->pExternalCleanupList[iEntryIndex].pCleanupCb == NULL) + { + // no more entry in list + break; + } + + if(pCommonRef->pExternalCleanupList[iEntryIndex].pCleanupCb(pCommonRef->pExternalCleanupList[iEntryIndex].pCleanupData) == 0) + { + pCommonRef->iExternalCleanupListCnt -= 1; + + NetPrintf(("netconncommon: successfully destroyed external module (cleanup data ptr = %p), external cleanup list count decremented to %d\n", + pCommonRef->pExternalCleanupList[iEntryIndex].pCleanupData, pCommonRef->iExternalCleanupListCnt)); + + // move all following entries one cell backward in the array + for(iEntryIndex2 = iEntryIndex; iEntryIndex2 < pCommonRef->iExternalCleanupListMax; iEntryIndex2++) + { + if (iEntryIndex2 == pCommonRef->iExternalCleanupListMax-1) + { + // last entry, reset to NULL + pCommonRef->pExternalCleanupList[iEntryIndex2].pCleanupCb = NULL; + pCommonRef->pExternalCleanupList[iEntryIndex2].pCleanupData = NULL; + } + else + { + pCommonRef->pExternalCleanupList[iEntryIndex2].pCleanupCb = pCommonRef->pExternalCleanupList[iEntryIndex2+1].pCleanupCb; + pCommonRef->pExternalCleanupList[iEntryIndex2].pCleanupData = pCommonRef->pExternalCleanupList[iEntryIndex2+1].pCleanupData; + } + } + } + } + + // clear re-enter block + NetCritLeave(&pCommonRef->crit); + + return(pCommonRef->iExternalCleanupListCnt); +} + +/*F********************************************************************************/ +/*! + \Function NetConnCommonCheckRef + + \Description + Decrement and verify the reference count. + + \Input *pCommonRef - NetConnCommonRefT reference + + \Output + int32_t - 0 if ready to shutdown, NETCONN_ERROR_ISACTIVE otherwise + + \Version 10/23/2017 (amakoukji) +*/ +/********************************************************************************F*/ +int32_t NetConnCommonCheckRef(NetConnCommonRefT *pCommonRef) +{ + // check the refcount + if (--(pCommonRef->iRefCount) > 0) + { + NetPrintf(("netconncommon: NetConnShutdown() called, new refcount is %d\n", pCommonRef->iRefCount)); + return(NETCONN_ERROR_ISACTIVE); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetConnCommonControl + + \Description + Set module behavior based on input selector. + + \Input *pCommonRef - netconncommon ref + \Input iControl - input selector + \Input iValue - selector input + \Input iValue2 - selector input + \Input *pValue - selector input + \Input *pValue2 - selector input + + \Output + int32_t - selector result + + \Notes + iControl can be one of the following: + + \verbatim + 'acid' - set the "Account Id" of user index iValue, via pValue as int64_t (iValue2 is buffer size) + 'peid' - set the "Persona Id" of user index iValue, via pValue as int64_t (iValue2 is buffer size) + \endverbatim + + Unhandled selectors are passed through to SocketControl() + + \Version 07/02/2019 (tcho) +*/ +/********************************************************************************F*/ +int32_t NetConnCommonControl(NetConnCommonRefT *pCommonRef, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue, void *pValue2) +{ + // set account id + if (iControl == 'acid') + { + if ((iValue >= 0) && (iValue < NETCONN_MAXLOCALUSERS) && (iValue2 == sizeof(int64_t))) + { + ds_memcpy(&pCommonRef->aAccountInfo[iValue].iAccountId, pValue, iValue2); + return(0); + } + else + { + NetPrintf(("netconncommon: error - NetConnControl('acid') called with invalid parameters.\n")); + return(-1); + } + } + + // set persona id + if (iControl == 'peid') + { + if ((iValue >= 0) && (iValue < NETCONN_MAXLOCALUSERS) && (iValue2 == sizeof(int64_t))) + { + ds_memcpy(&pCommonRef->aAccountInfo[iValue].iPersonaId, pValue, iValue2); + return(0); + } + else + { + NetPrintf(("netconncommon: error - NetConnControl('peid') called with invalid parameters.\n")); + return(-1); + } + } + + // pass through unhandled selectors to SocketControl() + return(SocketControl(NULL, iControl, iValue, pValue, pValue2)); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnCommonStatus + + \Description + Check general network connection status. Different selectors return + different status attributes. + + \Input *pCommonRef - netconncommon ref + \Input iKind - status selector + \Input iData - (optional) selector specific + \Input *pBuf - (optional) pointer to output buffer + \Input iBufSize - (optional) size of output buffer + + \Output + int32_t - selector specific + + \Notes + iKind can be one of the following: + + \verbatim + acid: get the "Account Id" for the user index at iData, via pBuf as an int64_t + peid: get the "Persona Id" for the user index at iData, via pBuf as an int64_t + \endverbatim + + Unhandled selectors are passed through to SocketInfo() + + \Version 07/02/2019 (tcho) +*/ +/*************************************************************************************************F*/ +int32_t NetConnCommonStatus(NetConnCommonRefT *pCommonRef, int32_t iKind, int32_t iData, void *pBuf, int32_t iBufSize) +{ + // get account id + if (iKind == 'acid') + { + if ((iData >= 0) && (iData < NETCONN_MAXLOCALUSERS) && (iBufSize == sizeof(int64_t))) + { + ds_memcpy(pBuf, &pCommonRef->aAccountInfo[iData].iAccountId, iBufSize); + return(0); + } + else + { + NetPrintf(("netconncommon: error - NetConnStatus('acid') called with invalid parameters.\n")); + return(-1); + } + } + + // get persona id + if (iKind == 'peid') + { + if ((iData >= 0) && (iData < NETCONN_MAXLOCALUSERS) && (iBufSize == sizeof(int64_t))) + { + ds_memcpy(pBuf, &pCommonRef->aAccountInfo[iData].iPersonaId, iBufSize); + return(0); + } + else + { + NetPrintf(("netconncommon: error - NetConnStatus('peid') called with invalid parameters.\n")); + return(-1); + } + } + + // pass unrecognized options to SocketInfo + return(SocketInfo(NULL, iKind, iData, pBuf, iBufSize)); +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/netconncommon.h b/src/thirdparty/dirtysdk/source/dirtysock/netconncommon.h new file mode 100644 index 00000000..192c9b9c --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/netconncommon.h @@ -0,0 +1,92 @@ +/*H********************************************************************************/ +/*! + \File netconncommon.h + + \Description + Cross-platform netconn data types and private functions. + + \Copyright + Copyright (c) 2014 Electronic Arts Inc. + + \Version 05/21/2009 (mclouatre) First Version +*/ +/********************************************************************************H*/ + +#ifndef _netconncommon_h +#define _netconncommon_h + +/*** Include files ****************************************************************/ +#include "DirtySDK/dirtysock/netconn.h" + +/*** Defines **********************************************************************/ + +// initial size of external cleanup list (in number of entries) +#define NETCONN_EXTERNAL_CLEANUP_LIST_INITIAL_CAPACITY (12) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! external cleanup callback function prototype +typedef int32_t(*NetConnExternalCleanupCallbackT)(void *pNetConnExternalCleanupData); + +typedef struct NetConnExternalCleanupEntryT +{ + void *pCleanupData; //!< pointer to data to be passed to the external cleanup callback + NetConnExternalCleanupCallbackT pCleanupCb;//!< external cleanup callback +} NetConnExternalCleanupEntryT; + +typedef struct NetConnCommonRefT +{ + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + int32_t iDebugLevel; + + int32_t iExternalCleanupListMax; //!< maximum number of entries in the array + int32_t iExternalCleanupListCnt; //!< number of valid entries in the array + NetConnExternalCleanupEntryT *pExternalCleanupList; //!< pointer to an array of entries pending external cleanup completion + + int32_t iRefCount; //!< module reference counter + + NetConnAccountInfoT aAccountInfo[NETCONN_MAXLOCALUSERS]; //!< account info array + + NetCritT crit; +} NetConnCommonRefT; + + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// handle common shutdown functionality +void NetConnCommonShutdown(NetConnCommonRefT *pCommonRef); + +// handle common startup functionality +int32_t NetConnCommonStartup(int32_t iNetConnRefSize, const char *pParams, NetConnCommonRefT **pRef); + +// add an entry to the list of external module pending successful cleanup +int32_t NetConnCommonAddToExternalCleanupList(NetConnCommonRefT *pCommonRef, NetConnExternalCleanupCallbackT pCleanupCb, void *pCleanupData); + +// walk external cleanup list and try to destroy each individual entry +int32_t NetConnCommonProcessExternalCleanupList(NetConnCommonRefT *pCommonRef); + +// decrement and verify the reference count for shutdown +int32_t NetConnCommonCheckRef(NetConnCommonRefT *pCommonRef); + +// set module behavior based on input selector +int32_t NetConnCommonControl(NetConnCommonRefT *pCommonRef, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue, void *pValue2); + +// check general network connection status (added param) +int32_t NetConnCommonStatus(NetConnCommonRefT *pCommonRef, int32_t iKind, int32_t iData, void *pBuf, int32_t iBufSize); +#ifdef __cplusplus +} +#endif + +#endif // _netconcommon_h + diff --git a/src/thirdparty/dirtysdk/source/dirtysock/netconnlocaluser.cpp b/src/thirdparty/dirtysdk/source/dirtysock/netconnlocaluser.cpp new file mode 100644 index 00000000..c9dc834b --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/netconnlocaluser.cpp @@ -0,0 +1,474 @@ +/*H********************************************************************************/ +/*! + \File netconnlocaluser.cpp + + \Description + Cross-platform netconn data types and functions. + + \Copyright + Copyright (c) 2014 Electronic Arts Inc. + + \Version 05/21/2014 (mclouatre) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "netconnlocaluser.h" + +#ifndef DIRTYCODE_PS5 +#include "IEAUser/IEAUser.h" +#endif + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +static NetConnLocalUserRefT *_NetConnLocalUser_pRef = NULL; //!< module state pointer + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _NetConnLocalUserEnqueueIEAUserEvent + + \Description + Add the specified entry at the head of the specified list. + + \Input *pUserEventEntry - event entry to be enqueued + \Input **pList - list to add the event to + + \Version 05/09/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _NetConnLocalUserEnqueueIEAUserEvent(NetConnIEAUserEventT *pUserEventEntry, NetConnIEAUserEventT **pList) +{ + if (*pList != NULL) + { + pUserEventEntry->pNext = *pList; + } + + *pList = pUserEventEntry; +} + +/*F********************************************************************************/ +/*! + \Function _NetConnLocalUserDequeueIEAUserEvent + + \Description + Get an event entry from the tail fo the specified list. + + \Input **pList - list to dequeue from + + \Output + NetConnIEAUserEventT * - pointer to free IEAUserEvent entry + + \Version 05/09/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static NetConnIEAUserEventT * _NetConnLocalUserDequeueIEAUserEvent(NetConnIEAUserEventT **pList) +{ + NetConnIEAUserEventT *pUserEventEntry = NULL; + + // find tail + if (*pList != NULL) + { + NetConnIEAUserEventT *pPrevious = NULL; + for (pUserEventEntry = *pList; pUserEventEntry->pNext != NULL; pUserEventEntry = pUserEventEntry->pNext) + { + pPrevious = pUserEventEntry; + } + + if (pPrevious) + { + pPrevious->pNext = NULL; + } + else + { + *pList = NULL; + } + } + + return(pUserEventEntry); +} + +/*F********************************************************************************/ +/*! + \Function _NetConnLocalUserGetFreeIEAUserEvent + + \Description + Get a free IEAUserEvent entry from the free list. + + \Input *pLocalUserRef - netconn module state + + \Output + NetConnIEAUserEventT * - pointer to free IEAUserEvent entry + + \Version 05/09/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static NetConnIEAUserEventT * _NetConnLocalUserGetFreeIEAUserEvent(NetConnLocalUserRefT *pLocalUserRef) +{ + NetConnIEAUserEventT *pUserEventEntry; + + // check if free list is empty + if (pLocalUserRef->pIEAUserFreeEventList == NULL) + { + int32_t iLoop = 0; + + // add 4 entries to the free list + for (iLoop = 0; iLoop < 4; iLoop++) + { + pUserEventEntry = (NetConnIEAUserEventT *) DirtyMemAlloc(sizeof(NetConnIEAUserEventT), NETCONN_MEMID, pLocalUserRef->iMemGroup, pLocalUserRef->pMemGroupUserData); + + if (pUserEventEntry) + { + ds_memclr(pUserEventEntry, sizeof(*pUserEventEntry)); + + _NetConnLocalUserEnqueueIEAUserEvent(pUserEventEntry, &pLocalUserRef->pIEAUserFreeEventList); + + NetPrintfVerbose((pLocalUserRef->iDebugLevel, 0, "netconnlocaluser: [%p] allocated a new free user event entry\n", pUserEventEntry)); + } + else + { + NetPrintf(("netconnlocaluser: failed to allocate a new user event entry\n")); + } + } + } + + pUserEventEntry = _NetConnLocalUserDequeueIEAUserEvent(&pLocalUserRef->pIEAUserFreeEventList); + + return(pUserEventEntry); +} + +/*F********************************************************************************/ +/*! + \Function _NetConnLocalUserAddIEAUserEvent + + \Description + Add an entry to the list of IEAUserEvents + + \Input *pLocalUserRef - netconn module state + \Input iLocalUserIndex - local user index + \Input *pIEAUser - pointer to IEAUser object + \Input eEvent - event type + + \Output + int32_t - 0 for success; negative for error + + \Version 05/09/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static int32_t _NetConnLocalUserAddIEAUserEvent(NetConnLocalUserRefT *pLocalUserRef, int32_t iLocalUserIndex, const EA::User::IEAUser *pIEAUser, NetConnIEAUserEventTypeE eEvent) +{ + int32_t iResult = 0; + + NetCritEnter(&pLocalUserRef->crit); + + NetConnIEAUserEventT *pUserEventEntry = _NetConnLocalUserGetFreeIEAUserEvent(pLocalUserRef); + + if (pUserEventEntry) + { + pUserEventEntry->eEvent = eEvent; + pUserEventEntry->iLocalUserIndex = iLocalUserIndex; + pUserEventEntry->pIEAUser = pIEAUser; +#ifndef DIRTYCODE_PS5 + pUserEventEntry->pIEAUser->AddRef(); +#endif + + _NetConnLocalUserEnqueueIEAUserEvent(pUserEventEntry, &pLocalUserRef->pIEAUserEventList); + +#ifndef DIRTYCODE_PS5 + NetPrintfVerbose((pLocalUserRef->iDebugLevel, 0, "netconnlocaluser: [%p] IEAUser event queued (local user index = %d, local user id = 0x%08x, event = %s)\n", + pUserEventEntry, iLocalUserIndex, (int32_t)pIEAUser->GetUserID(), (eEvent==NETCONN_EVENT_IEAUSER_ADDED?"added":"removed"))); +#endif + } + else + { + iResult = -1; + } + + NetCritLeave(&pLocalUserRef->crit); + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetConnLocalUserClearIEAUserEventList + + \Description + Clear list of pending IEAUser events. + + \Input *pLocalUserRef - netconn module state + + \Version 05/09/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _NetConnLocalUserClearIEAUserEventList(NetConnLocalUserRefT *pLocalUserRef) +{ + NetConnIEAUserEventT *pUserEventEntry; + + NetCritEnter(&pLocalUserRef->crit); + + while ((pUserEventEntry = _NetConnLocalUserDequeueIEAUserEvent(&pLocalUserRef->pIEAUserEventList)) != NULL) + { + // return event entry to free list +#ifndef DIRTYCODE_PS5 + pUserEventEntry->pIEAUser->Release(); +#endif + ds_memclr(pUserEventEntry, sizeof(*pUserEventEntry)); + _NetConnLocalUserEnqueueIEAUserEvent(pUserEventEntry, &pLocalUserRef->pIEAUserFreeEventList); + } + + NetCritLeave(&pLocalUserRef->crit); +} + +/*F********************************************************************************/ +/*! + \Function _NetConnLocalUserClearIEAUserFreeEventList + + \Description + Clear list of free IEAUser events. + + \Input *pLocalUserRef - netconn module state + + \Version 05/09/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _NetConnLocalUserClearIEAUserFreeEventList(NetConnLocalUserRefT *pLocalUserRef) +{ + NetConnIEAUserEventT *pUserEventEntry; + + NetCritEnter(&pLocalUserRef->crit); + + while ((pUserEventEntry = _NetConnLocalUserDequeueIEAUserEvent(&pLocalUserRef->pIEAUserFreeEventList)) != NULL) + { + DirtyMemFree(pUserEventEntry, NETCONN_MEMID, pLocalUserRef->iMemGroup, pLocalUserRef->pMemGroupUserData); + + NetPrintfVerbose((pLocalUserRef->iDebugLevel, 0, "netconnlocaluser: [%p] freed user event entry\n", pUserEventEntry)); + } + + NetCritLeave(&pLocalUserRef->crit); +} + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function NetConnLocalUserInit + + \Description + Start up local user functionality. + + \Input *pNetConn - parent NetConn reference + \Input *pAddUserCb - added user event handler + \Input *pRemoveUserCb - removed user event handler + + \Output + NetConnLocalUserRefT - local user reference on success; NULL on error + + \Version 10/24/2017 (amakoukji) +*/ +/********************************************************************************F*/ +NetConnLocalUserRefT* NetConnLocalUserInit(NetConnRefT *pNetConn, NetConnAddLocalUserCallbackT *pAddUserCb, NetConnRemoveLocalUserCallbackT *pRemoveUserCb) +{ + NetConnLocalUserRefT *pLocalUserRef = _NetConnLocalUser_pRef; + int32_t iMemGroup; + void *pMemGroupUserData; + + // query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // refcount if already started + if (pLocalUserRef != NULL) + { + NetPrintf(("netconnlocaluser: NetConnLocalUserInit() called while module is already active\n")); + return(NULL); + } + + // alloc and init ref + if ((pLocalUserRef = (NetConnLocalUserRefT*)DirtyMemAlloc(sizeof(NetConnLocalUserRefT), NETCONN_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("netconnlocaluser: unable to allocate module state\n")); + return(NULL); + } + ds_memclr(pLocalUserRef, sizeof(NetConnLocalUserRefT)); + pLocalUserRef->iMemGroup = iMemGroup; + pLocalUserRef->pMemGroupUserData = pMemGroupUserData; + pLocalUserRef->pAddUserCb = pAddUserCb; + pLocalUserRef->pRemoveUserCb = pRemoveUserCb; + pLocalUserRef->pNetConn = pNetConn; + + NetCritInit(&pLocalUserRef->crit, "netconnlocaluserCrit"); + + // save ref + _NetConnLocalUser_pRef = pLocalUserRef; + + // successful completion + return(pLocalUserRef); +} + +/*F********************************************************************************/ +/*! + \Function NetConnLocalUserDestroy + + \Description + Shutdown common functionality. + + \Input *pLocalUserRef - module state + + \Version 10/24/2017 (amakoukji) +*/ +/********************************************************************************F*/ +void NetConnLocalUserDestroy(NetConnLocalUserRefT *pLocalUserRef) +{ + _NetConnLocalUserClearIEAUserEventList(pLocalUserRef); + _NetConnLocalUserClearIEAUserFreeEventList(pLocalUserRef); + + NetCritKill(&pLocalUserRef->crit); + + // dispose of ref + DirtyMemFree(pLocalUserRef, NETCONN_MEMID, pLocalUserRef->iMemGroup, pLocalUserRef->pMemGroupUserData); + _NetConnLocalUser_pRef = NULL; +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnLocalUserAdd + + \Description + Use this function to tell netconn about a newly detected local user on the local console. + + \Input iLocalUserIndex - index at which DirtySDK needs to insert this user it its internal user array + \Input pLocalUser - pointer to associated IEAUser + + \Output + int32_t - 0 for success; negative for error + + \Version 01/16/2014 (mclouatre) +*/ +/*************************************************************************************************F*/ +int32_t NetConnLocalUserAdd(int32_t iLocalUserIndex, const EA::User::IEAUser *pLocalUser) +{ + NetConnLocalUserRefT *pLocalUserRef = _NetConnLocalUser_pRef; + int32_t iRetCode = 0; // default to success + + if ((iLocalUserIndex >= 0) && (iLocalUserIndex < NETCONN_MAXLOCALUSERS)) + { + iRetCode = _NetConnLocalUserAddIEAUserEvent(pLocalUserRef, iLocalUserIndex, pLocalUser, NETCONN_EVENT_IEAUSER_ADDED); + } + else + { + NetPrintf(("netconnlocaluser: NetConnLocalUserAddLocalUser() called with an invalid index (%d)\n", iLocalUserIndex)); + iRetCode = -1; + } + + return(iRetCode); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnLocalUserRemove + + \Description + Use this function to tell netconn about a local user that no longer exists + + \Input iLocalUserIndex - index in the internal DirtySDK user array at which the user needs to be cleared + pass -1 when the index is unknown and a lookup will be done + \Input pLocalUser - pointer to associated IEAUser + + \Output + int32_t - 0 for success; negative for error + + \Version 01/16/2014 (mclouatre) +*/ +/*************************************************************************************************F*/ +int32_t NetConnLocalUserRemove(int32_t iLocalUserIndex, const EA::User::IEAUser *pLocalUser) +{ + NetConnLocalUserRefT *pLocalUserRef = _NetConnLocalUser_pRef; + int32_t iRetCode = 0; // default to success + + if (iLocalUserIndex == -1) + { + NetCritEnter(&pLocalUserRef->crit); + iLocalUserIndex = NetConnStatus('usri', 0, (void *)pLocalUser, 0); + NetCritLeave(&pLocalUserRef->crit); + } + + if ((iLocalUserIndex >= 0) && (iLocalUserIndex < NETCONN_MAXLOCALUSERS)) + { + iRetCode = _NetConnLocalUserAddIEAUserEvent(pLocalUserRef, iLocalUserIndex, pLocalUser, NETCONN_EVENT_IEAUSER_REMOVED); + } + else + { + NetPrintf(("netconnlocaluser: NetConnLocalUserRemoveLocalUser() called with an invalid index (%d)\n", iLocalUserIndex)); + iRetCode = -1; + } + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function NetConnLocalUserUpdate + + \Description + Update our internally maintained array of NetConnUsers from the array + of IEAUsers + + \Input *pLocalUserRef - module reference + + \Version 01/18/2014 (mclouatre) +*/ +/********************************************************************************F*/ +void NetConnLocalUserUpdate(NetConnLocalUserRefT *pLocalUserRef) +{ + NetConnIEAUserEventT *pUserEventEntry; + + if (pLocalUserRef != NULL) + { + NetCritEnter(&pLocalUserRef->crit); + + // process events + while ((pUserEventEntry = _NetConnLocalUserDequeueIEAUserEvent(&pLocalUserRef->pIEAUserEventList)) != NULL) + { + if (pUserEventEntry->eEvent == NETCONN_EVENT_IEAUSER_ADDED) + { + if (pLocalUserRef->pAddUserCb != NULL) + { + pLocalUserRef->pAddUserCb(pLocalUserRef, pUserEventEntry->iLocalUserIndex, pUserEventEntry->pIEAUser); + } + } + else + { + if (pLocalUserRef->pRemoveUserCb != NULL) + { + pLocalUserRef->pRemoveUserCb(pLocalUserRef, pUserEventEntry->iLocalUserIndex, pUserEventEntry->pIEAUser); + } + } + +#ifndef DIRTYCODE_PS5 + NetPrintfVerbose((pLocalUserRef->iDebugLevel, 0, "netconnlocaluser: [%p] IEAUser event dequeued (local user index = %d, local user id = 0x%08x, event = %s)\n", + pUserEventEntry, pUserEventEntry->iLocalUserIndex, (int32_t)pUserEventEntry->pIEAUser->GetUserID(), (pUserEventEntry->eEvent == NETCONN_EVENT_IEAUSER_ADDED ? "added" : "removed"))); + + // return event entry to free list + pUserEventEntry->pIEAUser->Release(); +#endif + ds_memclr(pUserEventEntry, sizeof(*pUserEventEntry)); + _NetConnLocalUserEnqueueIEAUserEvent(pUserEventEntry, &pLocalUserRef->pIEAUserFreeEventList); + } + + NetCritLeave(&pLocalUserRef->crit); + } +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/netconnlocaluser.h b/src/thirdparty/dirtysdk/source/dirtysock/netconnlocaluser.h new file mode 100644 index 00000000..17192e6e --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/netconnlocaluser.h @@ -0,0 +1,94 @@ +/*H********************************************************************************/ +/*! + \File netconnlocaluser.h + + \Description + Wrapper for EA::User::IEAUser functionality + + \Copyright + Copyright (c) 2017 Electronic Arts Inc. + + \Version 10/24/2017 (amakoukji) First Version +*/ +/********************************************************************************H*/ + +#ifndef _netconnlocaluser_h +#define _netconnlocaluser_h + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ +struct NetConnRefT; + +//! user event callback function prototype +typedef void (NetConnAddLocalUserCallbackT)(struct NetConnLocalUserRefT *pCommonRef, int32_t iLocalUserIndex, const EA::User::IEAUser *pIEAUser); +typedef void (NetConnRemoveLocalUserCallbackT)(struct NetConnLocalUserRefT *pCommonRef, int32_t iLocalUserIndex, const EA::User::IEAUser *pIEAUser); + + +typedef enum NetConnIEAUserEventTypeE +{ + NETCONN_EVENT_IEAUSER_ADDED = 0, + NETCONN_EVENT_IEAUSER_REMOVED +} NetConnIEAUserEventTypeE; + +typedef struct NetConnIEAUserEventT +{ + struct NetConnIEAUserEventT *pNext; //!< linked list + const EA::User::IEAUser *pIEAUser; //!< IEAUser reference + NetConnIEAUserEventTypeE eEvent; //!< event type + int32_t iLocalUserIndex; //!< local user index +} NetConnIEAUserEventT; + +typedef struct NetConnLocalUserRefT +{ + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + int32_t iDebugLevel; + + NetConnIEAUserEventT *pIEAUserFreeEventList; //!< list of free IEAUser + NetConnIEAUserEventT *pIEAUserEventList; //!< list of pending NetConnIEAUserEvents - populated by customers with NetConnAddLocaUser()/NetConnRemoveUser() + + NetConnAddLocalUserCallbackT *pAddUserCb; + NetConnRemoveLocalUserCallbackT *pRemoveUserCb; + + NetConnRefT *pNetConn; //!< parent + + NetCritT crit; +} NetConnLocalUserRefT; + + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// handle shutdown functionality +void NetConnLocalUserDestroy(NetConnLocalUserRefT *pLocalUserRef); + +// handle startup functionality +NetConnLocalUserRefT* NetConnLocalUserInit(NetConnRefT *pNetConn, NetConnAddLocalUserCallbackT *pAddUserCb, NetConnRemoveLocalUserCallbackT *pRemoveUserCb); + +// handle add user functionality +int32_t NetConnLocalUserAdd(int32_t iLocalUserIndex, const EA::User::IEAUser *pLocalUser); + +// handle remove user functionality +int32_t NetConnLocalUserRemove(int32_t iLocalUserIndex, const EA::User::IEAUser *pLocalUser); + +// handle user update functionality +void NetConnLocalUserUpdate(NetConnLocalUserRefT *pLocalUserRef); + +#ifdef __cplusplus +} +#endif + +#endif // _netconnlocaluser_h + diff --git a/src/thirdparty/dirtysdk/source/dirtysock/pc/dirtyerrpc.c b/src/thirdparty/dirtysdk/source/dirtysock/pc/dirtyerrpc.c new file mode 100644 index 00000000..48892e7b --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/pc/dirtyerrpc.c @@ -0,0 +1,255 @@ +/*H********************************************************************************/ +/*! + \File dirtyerrpc.c + + \Description + Dirtysock debug error routines. + + \Copyright + Copyright (c) 2014 Electronic Arts Inc. + + \Version 07/01/2014 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtyerr.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +#if DIRTYSOCK_ERRORNAMES + +static DirtyErrT _DirtyErr_List[] = +{ + /* + WinError.h" + */ + DIRTYSOCK_ErrorName(ERROR_INVALID_FUNCTION), // 1 + DIRTYSOCK_ErrorName(ERROR_ACCESS_DENIED), // 5 + DIRTYSOCK_ErrorName(ERROR_NO_MORE_FILES), // 18 + DIRTYSOCK_ErrorName(ERROR_ALREADY_ASSIGNED), // 85 + DIRTYSOCK_ErrorName(ERROR_INVALID_PARAMETER), // 87 + DIRTYSOCK_ErrorName(ERROR_INSUFFICIENT_BUFFER), // 122 + DIRTYSOCK_ErrorName(ERROR_ALREADY_EXISTS), // 183 + DIRTYSOCK_ErrorName(ERROR_NO_DATA), // 232 + DIRTYSOCK_ErrorName(ERROR_INVALID_PORT_ATTRIBUTES), // 545 + DIRTYSOCK_ErrorName(ERROR_IO_INCOMPLETE), // 996 + DIRTYSOCK_ErrorName(ERROR_IO_PENDING), // 997 + DIRTYSOCK_ErrorName(ERROR_NOT_FOUND), // 1168 + DIRTYSOCK_ErrorName(ERROR_NO_MATCH), // 1169 + DIRTYSOCK_ErrorName(ERROR_CONNECTION_INVALID), // 1229 + DIRTYSOCK_ErrorName(ERROR_SERVICE_NOT_FOUND), // 1243 + DIRTYSOCK_ErrorName(ERROR_FUNCTION_FAILED), // 1627 + + DIRTYSOCK_ErrorName(RPC_S_CALL_FAILED), // 1726L + DIRTYSOCK_ErrorName(RPC_S_CALL_FAILED_DNE), // 1727L + + DIRTYSOCK_ErrorName(OR_INVALID_OXID), // 1910L -- the object exporter specified was not found + + // Misc other errors + DIRTYSOCK_ErrorName(ERROR_INVALID_STATE), // 5023L - The group or resource is not in the correct state to perform the requested operation + + /* + WinSock2.h + */ + // WinSock error codes from 10000-11999 + DIRTYSOCK_ErrorName(WSAEINTR), // 10004L - A blocking operation was interrupted by a call to WSACancelBlockingCall. + DIRTYSOCK_ErrorName(WSAEBADF), // 10009L - The file handle supplied is not valid. + DIRTYSOCK_ErrorName(WSAEACCES), // 10013L - An attempt was made to access a socket in a way forbidden by its access permissions. + DIRTYSOCK_ErrorName(WSAEFAULT), // 10014L - The system detected an invalid pointer address in attempting to use a pointer argument in a call. + DIRTYSOCK_ErrorName(WSAEINVAL), // 10022L - An invalid argument was supplied. + DIRTYSOCK_ErrorName(WSAEMFILE), // 10024L - Too many open sockets. + DIRTYSOCK_ErrorName(WSAEWOULDBLOCK), // 10035L - A non-blocking socket operation could not be completed immediately. + DIRTYSOCK_ErrorName(WSAEINPROGRESS), // 10036L - A blocking operation is currently executing. + DIRTYSOCK_ErrorName(WSAEALREADY), // 10037L - An operation was attempted on a non-blocking socket that already had an operation in progress. + DIRTYSOCK_ErrorName(WSAENOTSOCK), // 10038L - An operation was attempted on something that is not a socket. + DIRTYSOCK_ErrorName(WSAEDESTADDRREQ), // 10039L - A required address was omitted from an operation on a socket. + DIRTYSOCK_ErrorName(WSAEMSGSIZE), // 10040L - A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself. + DIRTYSOCK_ErrorName(WSAEPROTOTYPE), // 10041L - A protocol was specified in the socket function call that does not support the semantics of the socket type requested. + DIRTYSOCK_ErrorName(WSAENOPROTOOPT), // 10042L - An unknown, invalid, or unsupported option or level was specified in a getsockopt or setsockopt call. + DIRTYSOCK_ErrorName(WSAEPROTONOSUPPORT), // 10043L - The requested protocol has not been configured into the system, or no implementation for it exists. + DIRTYSOCK_ErrorName(WSAESOCKTNOSUPPORT), // 10044L - The support for the specified socket type does not exist in this address family. + DIRTYSOCK_ErrorName(WSAEOPNOTSUPP), // 10045L - The attempted operation is not supported for the type of object referenced. + DIRTYSOCK_ErrorName(WSAEPFNOSUPPORT), // 10046L - The protocol family has not been configured into the system or no implementation for it exists. + DIRTYSOCK_ErrorName(WSAEAFNOSUPPORT), // 10047L - An address incompatible with the requested protocol was used. + DIRTYSOCK_ErrorName(WSAEADDRINUSE), // 10048L - Only one usage of each socket address (protocol/network address/port) is normally permitted. + DIRTYSOCK_ErrorName(WSAEADDRNOTAVAIL), // 10049L - The requested address is not valid in its context. + DIRTYSOCK_ErrorName(WSAENETDOWN), // 10050L - A socket operation encountered a dead network. + DIRTYSOCK_ErrorName(WSAENETUNREACH), // 10051L - A socket operation was attempted to an unreachable network. + DIRTYSOCK_ErrorName(WSAENETRESET), // 10052L - The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress. + DIRTYSOCK_ErrorName(WSAECONNABORTED), // 10053L - An established connection was aborted by the software in your host machine. + DIRTYSOCK_ErrorName(WSAECONNRESET), // 10054L - An existing connection was forcibly closed by the remote host. + DIRTYSOCK_ErrorName(WSAENOBUFS), // 10055L - An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full. + DIRTYSOCK_ErrorName(WSAEISCONN), // 10056L - A connect request was made on an already connected socket. + DIRTYSOCK_ErrorName(WSAENOTCONN), // 10057L - A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied. + DIRTYSOCK_ErrorName(WSAESHUTDOWN), // 10058L - A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call. + DIRTYSOCK_ErrorName(WSAETOOMANYREFS), // 10059L - Too many references to some kernel object. + DIRTYSOCK_ErrorName(WSAETIMEDOUT), // 10060L - A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. + DIRTYSOCK_ErrorName(WSAECONNREFUSED), // 10061L - No connection could be made because the target machine actively refused it. + DIRTYSOCK_ErrorName(WSAELOOP), // 10062L - Cannot translate name. + DIRTYSOCK_ErrorName(WSAENAMETOOLONG), // 10063L - Name component or name was too long. + DIRTYSOCK_ErrorName(WSAEHOSTDOWN), // 10064L - A socket operation failed because the destination host was down. + DIRTYSOCK_ErrorName(WSAEHOSTUNREACH), // 10065L - A socket operation was attempted to an unreachable host. + DIRTYSOCK_ErrorName(WSAENOTEMPTY), // 10066L - Cannot remove a directory that is not empty. + DIRTYSOCK_ErrorName(WSAEPROCLIM), // 10067L - A Windows Sockets implementation may have a limit on the number of applications that may use it simultaneously. + DIRTYSOCK_ErrorName(WSAEUSERS), // 10068L - Ran out of quota. + DIRTYSOCK_ErrorName(WSAEDQUOT), // 10069L - Ran out of disk quota. + DIRTYSOCK_ErrorName(WSAESTALE), // 10070L - File handle reference is no longer available. + DIRTYSOCK_ErrorName(WSAEREMOTE), // 10071L - Item is not available locally. + DIRTYSOCK_ErrorName(WSASYSNOTREADY), // 10091L - WSAStartup cannot function at this time because the underlying system it uses to provide network services is currently unavailable. + DIRTYSOCK_ErrorName(WSAVERNOTSUPPORTED), // 10092L - The Windows Sockets version requested is not supported. + DIRTYSOCK_ErrorName(WSANOTINITIALISED), // 10093L - Either the application has not called WSAStartup, or WSAStartup failed. + DIRTYSOCK_ErrorName(WSAEDISCON), // 10101L - Returned by WSARecv or WSARecvFrom to indicate the remote party has initiated a graceful shutdown sequence. + DIRTYSOCK_ErrorName(WSAENOMORE), // 10102L - No more results can be returned by WSALookupServiceNext. + DIRTYSOCK_ErrorName(WSAECANCELLED), // 10103L - A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled. + DIRTYSOCK_ErrorName(WSAEINVALIDPROCTABLE), // 10104L - The procedure call table is invalid. + DIRTYSOCK_ErrorName(WSAEINVALIDPROVIDER), // 10105L - The requested service provider is invalid. + DIRTYSOCK_ErrorName(WSAEPROVIDERFAILEDINIT),// 10106L - The requested service provider could not be loaded or initialized. + DIRTYSOCK_ErrorName(WSASYSCALLFAILURE), // 10107L - A system call that should never fail has failed. + DIRTYSOCK_ErrorName(WSASERVICE_NOT_FOUND), // 10108L - No such service is known. The service cannot be found in the specified name space. + DIRTYSOCK_ErrorName(WSATYPE_NOT_FOUND), // 10109L - The specified class was not found. + DIRTYSOCK_ErrorName(WSA_E_NO_MORE), // 10110L - No more results can be returned by WSALookupServiceNext. + DIRTYSOCK_ErrorName(WSA_E_CANCELLED), // 10111L - A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled. + DIRTYSOCK_ErrorName(WSAEREFUSED), // 10112L - A database query failed because it was actively refused. + DIRTYSOCK_ErrorName(WSAHOST_NOT_FOUND), // 11001L - No such host is known. + + // NULL terminate + DIRTYSOCK_ListEnd() +}; + +#define DIRTYERR_NUMERRORS (sizeof(_DirtyErr_List) / sizeof(_DirtyErr_List[0])) + +#endif // #if DIRTYSOCK_ERRORNAMES + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function DirtyErrNameList + + \Description + This function takes as input a system-specific error code, and either + resolves it to its define name if it is recognized, or formats it as a hex + number if not. + + \Input *pBuffer - [out] pointer to output buffer to store result + \Input iBufSize - size of output buffer + \Input uError - error code to format + \Input *pList - error list to use + + \Version 06/13/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void DirtyErrNameList(char *pBuffer, int32_t iBufSize, uint32_t uError, const DirtyErrT *pList) +{ + static char strUnknown[16]; + + #if DIRTYSOCK_ERRORNAMES + int32_t iErr; + + // first try to match exactly + for (iErr = 0; pList[iErr].uError != DIRTYSOCK_LISTTERM; iErr++) + { + if ((pList[iErr].uError & ~0x80000000) == (uError & ~0x80000000)) + { + ds_snzprintf(pBuffer, iBufSize, "%s/0x%08x", pList[iErr].pErrorName, uError); + return; + } + } + + // if not found, try to match lower 16 bits of error code + for (iErr = 0; pList[iErr].uError != DIRTYSOCK_LISTTERM; iErr++) + { + if ((pList[iErr].uError & ~0xffff0000) == (uError & ~0xffff0000)) + { + ds_snzprintf(pBuffer, iBufSize, "%s/0x%08x", pList[iErr].pErrorName, uError); + return; + } + } + #endif + + ds_snzprintf(pBuffer, iBufSize, "0x%08x", uError); +} + +/*F********************************************************************************/ +/*! + \Function DirtyErrName + + \Description + This function takes as input a system-specific error code, and either + resolves it to its define name if it is recognized or formats it as a hex + number if not. + + \Input *pBuffer - [out] pointer to output buffer to store result + \Input iBufSize - size of output buffer + \Input uError - error code to format + + \Version 06/13/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void DirtyErrName(char *pBuffer, int32_t iBufSize, uint32_t uError) +{ + #if DIRTYSOCK_ERRORNAMES + DirtyErrNameList(pBuffer, iBufSize, uError, _DirtyErr_List); + #endif +} + +/*F********************************************************************************/ +/*! + \Function DirtyErrGetNameList + + \Description + This function takes as input a system-specific error code, and either + resolves it to its define name if it is recognized or formats it as a hex + number if not. + + \Input uError - error code to format + \Input *pList - error list to use + + \Output + const char *- pointer to error name or error formatted in hex + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +const char *DirtyErrGetNameList(uint32_t uError, const DirtyErrT *pList) +{ + static char strName[8][64]; + static uint8_t uLast = 0; + char *pName = strName[uLast++]; + if (uLast > 7) uLast = 0; + DirtyErrNameList(pName, sizeof(strName[0]), uError, pList); + return(pName); +} + +/*F********************************************************************************/ +/*! + \Function DirtyErrGetName + + \Description + This function takes as input a system-specific error code, and either + resolves it to its define name if it is recognized or formats it as a hex + number if not. + + \Input uError - error code to format + + \Output + const char *- pointer to error name or error formatted in hex + + \Version 06/13/2005 (jbrookes) +*/ +/********************************************************************************F*/ +const char *DirtyErrGetName(uint32_t uError) +{ + static char strName[128]; + DirtyErrName(strName, sizeof(strName), uError); + return(strName); +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/pc/dirtylibwin.c b/src/thirdparty/dirtysdk/source/dirtysock/pc/dirtylibwin.c new file mode 100644 index 00000000..d0c7bc76 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/pc/dirtylibwin.c @@ -0,0 +1,423 @@ +/*H********************************************************************************/ +/*! + + \File dirtylibwin.c + + \Description + Platform specific support library for network code. Suppplies + simple time, memory, and semaphore functions. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 1.0 01/02/02 (GWS) Initial version (ported from PS2 IOP) + +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#pragma warning(push,0) +#include +#pragma warning(pop) + +#include "DirtySDK/platform.h" + +#if !defined(DIRTYCODE_XBOXONE) +#include +#endif + +#include +#include +#include + +#include +#include "DirtySDK/dirtysock/dirtythread.h" +#include "DirtySDK/dirtysock/dirtylib.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Function Prototypes **********************************************************/ + +static uint32_t _NetLibGetTickCount2(void); + +/*** Variables ********************************************************************/ + +// Private variables + +// idle thread state +static volatile int32_t g_idlelife = -1; + +// queryperformancecounter frequency (static init so calls to NetTick() before NetLibCreate won't crash) +static LARGE_INTEGER _NetLib_lFreq = { 1 }; + +#if defined(DIRTYCODE_PC) || defined(DIRTYCODE_GDK) +uint32_t _NetLib_bUseHighResTimer = FALSE; +#else +uint32_t _NetLib_bUseHighResTimer = TRUE; +#endif + +// selected timer function +static uint32_t (*_NetLib_pTimerFunc)(void) = _NetLibGetTickCount2; + +// Public variables + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _NetLibThread + + \Description + Thread to handle special library tasks + + \Input _null - unused + + \Version 01/02/2002 (gschaefer) +*/ +/********************************************************************************F*/ +static void _NetLibThread(void *_null) +{ + char strThreadId[32]; + + // get the thread id + DirtyThreadGetThreadId(strThreadId, sizeof(strThreadId)); + + // show we are alive + NetPrintf(("dirtylibwin: idle thread running (thid=%s)\n", strThreadId)); + g_idlelife = 1; + + // run while we have sema + while (g_idlelife == 1) + { + // call idle functions + NetIdleCall(); + // wait for next tick + Sleep(50); + } + + // report termination + NetPrintf(("dirtylibwin: idle thread exiting\n")); + + // show we are dead + g_idlelife = 0; +} + +/*F********************************************************************************/ +/*! + \Function _NetLibGetTickCount + + \Description + Millisecond-accurate tick counter. + + \Output + uint32_t - millisecond tick counter + + \Version 11/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _NetLibGetTickCount(void) +{ + LARGE_INTEGER lCount; + QueryPerformanceCounter(&lCount); + return((uint32_t)((lCount.QuadPart*1000)/_NetLib_lFreq.QuadPart)); +} + +/*F********************************************************************************/ +/*! + \Function _NetLibGetTickCount2 + + \Description + Millisecond tick counter, with variable precision. + + \Output + uint32_t - millisecond tick counter + + \Version 11/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _NetLibGetTickCount2(void) +{ + #if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + return(_NetLibGetTickCount()); //$$TODO -- better method than querying high performance counter? + #else + return(timeGetTime()); + #endif +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function NetLibCreate + + \Description + Initialize the network library functions + + \Input iThreadPrio - priority to start the _NetLibThread with + \Input iThreadStackSize - stack size to start the _NetLibThread with (in bytes) + \Input iThreadCpuAffinity - cpu affinity to start the _NetLibThread with + + \Version 01/02/2002 (gschaefer) +*/ +/********************************************************************************F*/ +void NetLibCreate(int32_t iThreadPrio, int32_t iThreadStackSize, int32_t iThreadCpuAffinity) +{ + DirtyThreadConfigT ThreadConfig; + int32_t iResult; + + // init common netlib functionality + NetLibCommonInit(); + + // configure thread parameters + ds_memclr(&ThreadConfig, sizeof(ThreadConfig)); + ThreadConfig.pName = "NetLib"; + ThreadConfig.iPriority = iThreadPrio; + ThreadConfig.iAffinity = iThreadCpuAffinity; + ThreadConfig.iVerbosity = 1; + + // create a worker thread + if ((iResult = DirtyThreadCreate(_NetLibThread, NULL, &ThreadConfig)) != 0) + { + NetPrintf(("dirtylibwin: unable to create netidle thread (err=%d)\n", iResult)); + g_idlelife = 0; + } + + // set up high-performance timer, if available + if (QueryPerformanceFrequency(&_NetLib_lFreq) == 0) + { + NetPrintf(("dirtylibwin: high-frequency performance counter not available\n")); + } + // use high-performance timer for tick counter? + if (_NetLib_bUseHighResTimer) + { + _NetLib_pTimerFunc = _NetLibGetTickCount; + } +} + +/*F********************************************************************************/ +/*! + \Function NetLibDestroy + + \Description + Destroy the network lib + + \Input uShutdownFlags - NET_SHUTDOWN_* flags + + \Version 01/02/02 (gschaefer) +*/ +/********************************************************************************F*/ +void NetLibDestroy(uint32_t uShutdownFlags) +{ + // if the thread is running + if (g_idlelife == 1) + { + // signal a shutdown + g_idlelife = 2; + + // wait for thread to terminate + while (g_idlelife > 0) + { + Sleep(1); + } + } + + // shut down common functionality + NetLibCommonShutdown(); +} + +/*F********************************************************************************/ +/*! + \Function NetTick + + \Description + Return some kind of increasing tick count with millisecond scale (does + not need to have millisecond precision, but higher precision is better). + + \Output + uint32_t - millisecond tick count + + \Version 09/15/1999 (gschaefer) +*/ +/********************************************************************************F*/ +uint32_t NetTick(void) +{ + return(_NetLib_pTimerFunc()); +} + +/*F********************************************************************************/ +/*! + \Function NetTickUsec + + \Description + Return increasing tick count in microseconds. Used for performance timing + purposes. + + \Output + uint64_t - microsecond tick count + + \Version 01/30/2015 (jbrookes) +*/ +/********************************************************************************F*/ +uint64_t NetTickUsec(void) +{ + LARGE_INTEGER lCount; + QueryPerformanceCounter(&lCount); + return((uint64_t)((lCount.QuadPart*1000000)/_NetLib_lFreq.QuadPart)); +} + +/*F********************************************************************************/ +/*! + \Function NetLocalTime + + \Description + This converts the input GMT time to the local time as specified by the + system clock. This function follows the re-entrant localtime_r function + signature. + + \Input *pTm - storage for localtime output + \Input uElap - GMT time + + \Output + struct tm * - pointer to localtime result + + \Version 04/23/2008 (jbrookes) +*/ +/********************************************************************************F*/ +struct tm *NetLocalTime(struct tm *pTm, uint64_t uElap) +{ + time_t uTimeT = (time_t)uElap; + localtime_s(pTm, &uTimeT); + return(pTm); +} + +/*F********************************************************************************/ +/*! + \Function NetPlattimeToTime + + \Description + This converts the input platform-specific time data structure to the + generic time data structure. + + \Input *pTm - generic time data structure to be filled by the function + \Input *pPlatTime - pointer to the platform-specific data structure + + \Output + struct tm * - NULL=failure; else pointer to user-provided generic time data structure + + \Notes + pPlatTime is expected to point to a __timeb64 on PC platforms, and a + SYSTEMTIME on Xbox One. + + \Version 05/08/2010 (mclouatre) +*/ +/********************************************************************************F*/ +struct tm *NetPlattimeToTime(struct tm *pTm, void *pPlatTime) +{ + #if defined(DIRTYCODE_PC) + struct __timeb64 timebuffer = *(struct __timeb64 *)pPlatTime; + struct tm resultTm = *(_localtime64(&(timebuffer.time))); + + pTm->tm_sec = resultTm.tm_sec; + pTm->tm_min = resultTm.tm_min; + pTm->tm_hour = resultTm.tm_hour; + pTm->tm_mday = resultTm.tm_mday; + pTm->tm_mon = resultTm.tm_mon; + pTm->tm_year = resultTm.tm_year; + pTm->tm_wday = resultTm.tm_wday; + pTm->tm_yday = resultTm.tm_yday; + pTm->tm_isdst =resultTm.tm_isdst; + #else // XBOXONE + SYSTEMTIME systemTime = *(SYSTEMTIME *)pPlatTime; + + pTm->tm_sec = systemTime.wSecond; + pTm->tm_min = systemTime.wMinute; + pTm->tm_hour = systemTime.wHour; + pTm->tm_mday = systemTime.wDay; + pTm->tm_mon = systemTime.wMonth - 1; + pTm->tm_year = systemTime.wYear - 1900; + pTm->tm_wday = systemTime.wDayOfWeek; + pTm->tm_yday = 0; + pTm->tm_isdst = 0; + #endif + + return(pTm); +} + +/*F********************************************************************************/ +/*! + \Function NetPlattimeToTimeMs + + \Description + This function retrieves the current date time and fills in the + generic time data structure prrovided. It has the option of returning millisecond + which is not part of the generic time data structure + + \Input *pTm - generic time data structure to be filled by the function + \Input *pImsec - output param for milisecond to be filled by the function (optional can be NULL) + + \Output + struct tm * - NULL=failure; else pointer to user-provided generic time data structure + + \Version 09/16/2014 (tcho) +*/ +/********************************************************************************F*/ +struct tm *NetPlattimeToTimeMs(struct tm *pTm , int32_t *pImsec) +{ + void *pPlatTime; + int32_t iMsec; + + #if defined(DIRTYCODE_PC) + struct __timeb64 timebuffer; + _ftime64_s(&timebuffer); + iMsec = timebuffer.millitm; + pPlatTime = (void *)&timebuffer; + #else // XBOXONE + SYSTEMTIME systemTime; + GetLocalTime( &systemTime ); + iMsec = systemTime.wMilliseconds; + pPlatTime = (void *)&systemTime; + #endif + + if (pImsec != NULL) + { + *pImsec = iMsec; + } + + if (pTm == NULL) + { + return(NULL); + } + + return(NetPlattimeToTime(pTm, pPlatTime)); +} + +/*F********************************************************************************/ +/*! + \Function NetTime + + \Description + This function replaces the standard library time() function. Main + differences are the missing pointer parameter (not needed) and the uint64_t + return value. The function returns 0 on unsupported platforms vs time which + returns -1. + + \Output + uint64_t - number of elapsed seconds since Jan 1, 1970. + + \Version 01/12/2005 (gschaefer) +*/ +/********************************************************************************F*/ +uint64_t NetTime(void) +{ + return((uint64_t)time(NULL)); +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/pc/dirtynetwin.c b/src/thirdparty/dirtysdk/source/dirtysock/pc/dirtynetwin.c new file mode 100644 index 00000000..779be9f4 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/pc/dirtynetwin.c @@ -0,0 +1,3180 @@ +/*H*************************************************************************************/ +/*! + \File dirtynetwin.c + + \Description + Provide a wrapper that translates the Winsock network interface + into DirtySock calls. In the case of Winsock, little translation + is needed since it is based off BSD sockets (as is DirtySock). + + \Copyright + Copyright (c) Electronic Arts 1999-2018. + + \Version 1.0 01/02/2002 (gschaefer) First Version +*/ +/*************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 // avoid windows.h including extra stuff, including winsock.h which we don't want +#endif + +#include "DirtySDK/platform.h" + +#if defined(DIRTYCODE_XBOXONE) +#pragma warning(push,0) +#include +#pragma warning(pop) +#include +#include +#include // for tcp keep alive definitions +#else +#pragma warning(push,0) +#include +#include +#include // for tcp keep alive definitions +#pragma warning(pop) +#endif + +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtythread.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/dirtyvers.h" + +#include "dirtynetpriv.h" // private include for dirtynet common functions + +/*** Defines ***************************************************************************/ + +#define SOCKET_MAXEVENTS (64) + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! dirtysock connection socket structure +struct SocketT +{ + SocketT *pNext; //!< link to next active + SocketT *pKill; //!< link to next killed socket + + SOCKET uSocket; //!< winsock socket ref + + int32_t iFamily; //!< protocol family + int32_t iType; //!< protocol type + int32_t iProto; //!< protocol ident + + int8_t iOpened; //!< negative=error, zero=not open (connecting), positive=open + uint8_t uImported; //!< whether socket was imported or not + uint8_t bVirtual; //!< if true, socket is virtual + uint8_t uShutdown; //!< shutdown flag + + int32_t iLastError; //!< last socket error + + struct sockaddr LocalAddr; //!< local address + struct sockaddr RemoteAddr; //!< remote address + + uint16_t uVirtualPort; //!< virtual port, if set + uint8_t bAsyncRecv; //!< TRUE if async recv is enabled + int8_t iVerbose; //!< debug level + uint8_t bSendCbs; //!< TRUE if send cbs are enabled, false otherwise + uint8_t _pad0[3]; + + SocketRateT SendRate; //!< send rate estimation data + SocketRateT RecvRate; //!< recv rate estimation data + + uint32_t uCallMask; //!< valid callback events + uint32_t uCallLast; //!< last callback tick + uint32_t uCallIdle; //!< ticks between idle calls + void *pCallRef; //!< reference calback value + int32_t (*pCallback)(SocketT *pSocket, int32_t iFlags, void *pRef); + + WSAOVERLAPPED Overlapped; //!< overlapped i/o structure + NetCritT RecvCrit; //!< receive critical section + int32_t iAddrLen; //!< storage for async address length write by WSARecv + uint32_t uRecvFlag; //!< flags from recv operation + uint8_t bRecvInp; //!< if true, a receive operation is in progress + uint8_t bInCallback; //!< in a socket callback + uint8_t _pad1[2]; + + struct sockaddr RecvAddr; //!< receive address + struct sockaddr_in6 RecvAddr6; //!< receive address (ipv6) + + int32_t iPacketQueueResizePending; // -1 if no resize pending, new size otherwise + SocketPacketQueueT *pRecvQueue; + SocketPacketQueueEntryT *pRecvPacket; +}; + +//! local state +typedef struct SocketStateT +{ + SocketT *pSockList; //!< master socket list + SocketT *pSockKill; //!< list of killed sockets + HostentT *pHostList; //!< list of ongoing name resolution operations + + uint16_t aVirtualPorts[SOCKET_MAXVIRTUALPORTS]; //!< virtual port list + int32_t iMaxPacket; //!< maximum packet size + int32_t iFamily; //!< family to use for socket operations + + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + int32_t iVersion; //!< winsock version + int32_t iVerbose; //!< debug level + #if defined(DIRTYCODE_XBOXONE) + uint32_t uLocalAddr; //!< local ipv4 address + uint32_t uRandBindPort; + #else + uint32_t uAdapterAddress; //!< local interface used for SocketBind() operations if non-zero + #endif + + volatile int32_t iRecvLife; //!< receive thread alive indicator + WSAEVENT hEvent; //!< event used to wake up receive thread + NetCritT EventCrit; //!< event critical section (used when killing events) + + SocketAddrMapT AddrMap; //!< address map for translating ipv6 addresses to ipv4 virtual addresses and back + + SocketHostnameCacheT *pHostnameCache; //!< hostname cache + + SocketSendCallbackEntryT aSendCbEntries[SOCKET_MAXSENDCALLBACKS]; //!< collection of send callbacks +} SocketStateT; + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +//! module state ref +static SocketStateT *_Socket_pState = NULL; + +// Public variables + + +/*** Private Functions *****************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function _XlatError0 + + \Description + Translate a winsock error to dirtysock + + \Input iErr - return value from winsock call + \Input iWsaErr - winsock error (from WSAGetLastError()) + + \Output + int32_t - dirtysock error + + \Version 09/09/2004 (jbrookes) +*/ +/************************************************************************************F*/ +static int32_t _XlatError0(int32_t iErr, int32_t iWsaErr) +{ + if (iErr < 0) + { + iErr = iWsaErr; + if ((iErr == WSAEWOULDBLOCK) || (iErr == WSA_IO_PENDING)) + iErr = SOCKERR_NONE; + else if ((iErr == WSAENETUNREACH) || (iErr == WSAEHOSTUNREACH)) + iErr = SOCKERR_UNREACH; + else if (iErr == WSAENOTCONN) + iErr = SOCKERR_NOTCONN; + else if (iErr == WSAECONNREFUSED) + iErr = SOCKERR_REFUSED; + else if (iErr == WSAEINVAL) + iErr = SOCKERR_INVALID; + else if (iErr == WSAECONNRESET) + iErr = SOCKERR_CONNRESET; + else + { + NetPrintf(("dirtynetwin: error %s\n", DirtyErrGetName(iErr))); + iErr = SOCKERR_OTHER; + } + } + return(iErr); +} + +/*F*************************************************************************************/ +/*! + \Function _XlatError + + \Description + Translate the most recent winsock error to dirtysock + + \Input iErr - return value from winsock call + + \Output + int32_t - dirtysock error + + \Version 01/02/2002 (gschaefer) +*/ +/************************************************************************************F*/ +static int32_t _XlatError(int32_t iErr) +{ + return(_XlatError0(iErr, WSAGetLastError())); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketOpen + + \Description + Allocates a SocketT. If uSocket is INVALID_SOCKET, a WinSock socket ref is + created, otherwise uSocket is used. + + \Input uSocket - socket to use, or INVALID_SOCKET + \Input iFamily - address family + \Input iType - type (SOCK_DGRAM, SOCK_STREAM, ...) + \Input iProto - protocol + + \Output + SocketT * - pointer to new socket, or NULL + + \Version 03/03/2005 (jbrookes) +*/ +/************************************************************************************F*/ +static SocketT *_SocketOpen(SOCKET uSocket, int32_t iFamily, int32_t iType, int32_t iProto) +{ + SocketStateT *pState = _Socket_pState; + const uint32_t uTrue = 1, uFalse = 0; + const int32_t iQueueSize = (iType != SOCK_STREAM) ? 1 : 8; + SocketT *pSocket; + + // allocate memory + if ((pSocket = (SocketT *)DirtyMemAlloc(sizeof(*pSocket), SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynetwin: unable to allocate memory for socket\n")); + return(NULL); + } + ds_memclr(pSocket, sizeof(*pSocket)); + + // open a winsock socket + if ((uSocket == INVALID_SOCKET) && ((uSocket = WSASocketW(iFamily, iType, iProto, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)) + { + NetPrintf(("dirtynetwin: error %d creating socket\n", WSAGetLastError())); + DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + return(NULL); + } + + // create packet queue + if ((pSocket->pRecvQueue = SocketPacketQueueCreate(iQueueSize, pState->iMemGroup, pState->pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynetwin: failed to create socket queue for socket\n")); + closesocket(uSocket); + DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + return(NULL); + } + pSocket->iPacketQueueResizePending = -1; + + // save the socket + pSocket->uSocket = uSocket; + pSocket->iLastError = SOCKERR_NONE; + pSocket->iVerbose = 1; + + // set to non blocking + ioctlsocket(uSocket, FIONBIO, (u_long *)&uTrue); + + // if udp, allow broadcast + if (iType == SOCK_DGRAM) + { + setsockopt(uSocket, SOL_SOCKET, SO_BROADCAST, (char *)&uTrue, sizeof(uTrue)); + } + // if raw, set hdrincl + if (iType == SOCK_RAW) + { + setsockopt(uSocket, IPPROTO_IP, IP_HDRINCL, (char *)&uTrue, sizeof(uTrue)); + } + // disable IPv6 only (allow IPv4 use) + if (iFamily == AF_INET6) + { + setsockopt(uSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&uFalse, sizeof(uFalse)); + } + + // set family/proto info + pSocket->iFamily = iFamily; + pSocket->iType = iType; + pSocket->iProto = iProto; + pSocket->bAsyncRecv = ((iType == SOCK_DGRAM) || (iType == SOCK_RAW)) ? TRUE : FALSE; + pSocket->bSendCbs = TRUE; + + // create overlapped i/o event object + ds_memclr(&pSocket->Overlapped, sizeof(pSocket->Overlapped)); + pSocket->Overlapped.hEvent = WSACreateEvent(); + + // initialize receive critical section + NetCritInit(&pSocket->RecvCrit, "recvthread"); + + // install into list + NetCritEnter(NULL); + pSocket->pNext = pState->pSockList; + pState->pSockList = pSocket; + NetCritLeave(NULL); + + // return the socket + return(pSocket); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketClose + + \Description + Disposes of a SocketT, including removal from the global socket list and + disposal of the SocketT allocated memory. Does NOT dispose of the winsock + socket ref. + + \Input *pSocket - socket to close + \Input bShutdown - if TRUE, shutdown and close socket ref + + \Output + int32_t - negative=failure, zero=success + + \Version 01/14/2005 (jbrookes) +*/ +/************************************************************************************F*/ +static int32_t _SocketClose(SocketT *pSocket, uint32_t bShutdown) +{ + SocketStateT *pState = _Socket_pState; + uint8_t bSockInList = FALSE; + SocketT **ppSocket; + + // for access to socket list + NetCritEnter(NULL); + + // remove sock from linked list + for (ppSocket = &pState->pSockList; *ppSocket != NULL; ppSocket = &(*ppSocket)->pNext) + { + if (*ppSocket == pSocket) + { + *ppSocket = pSocket->pNext; + bSockInList = TRUE; + break; + } + } + + // release before NetIdleDone + NetCritLeave(NULL); + + // make sure the socket is in the socket list (and therefore valid) + if (!bSockInList) + { + NetPrintf(("dirtynetwin: warning, trying to close socket 0x%08x that is not in the socket list\n", (uintptr_t)pSocket)); + return(-1); + } + + // finish any idle call + NetIdleDone(); + + // acquire global critical section + NetCritEnter(NULL); + + // wake up out of WaitForMultipleEvents() + WSASetEvent(pState->hEvent); + + // acquire event critical section + NetCritEnter(&pState->EventCrit); + + // close event + WSACloseEvent(pSocket->Overlapped.hEvent); + pSocket->Overlapped.hEvent = WSA_INVALID_EVENT; + + // release event critical section + NetCritLeave(&pState->EventCrit); + + // release global critical section + NetCritLeave(NULL); + + // destroy packet queue + if (pSocket->pRecvQueue != NULL) + { + SocketPacketQueueDestroy(pSocket->pRecvQueue); + } + + // mark as closed + if (bShutdown && (pSocket->uSocket != INVALID_SOCKET)) + { + // close winsock socket + shutdown(pSocket->uSocket, 2); + closesocket(pSocket->uSocket); + } + pSocket->uSocket = INVALID_SOCKET; + pSocket->iOpened = 0; + + /* Put into killed list: + Usage of a kill list allows for postponing two things + * destruction of RecvCrit + * release of socket data structure memory + This ensures that RecvCrit is not freed while in-use by a running thread. + Such a scenario can occur when the receive callback invoked by _SocketRecvThread() + (while RecvCrit is entered) calls _SocketClose() */ + NetCritEnter(NULL); + pSocket->pKill = pState->pSockKill; + pState->pSockKill = pSocket; + NetCritLeave(NULL); + + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketIdle + + \Description + Call idle processing code to give connections time. + + \Input *_pState - module state + + \Version 10/15/1999 (gschaefer) +*/ +/************************************************************************************F*/ +static void _SocketIdle(void *_pState) +{ + SocketStateT *pState = (SocketStateT *)_pState; + SocketT *pSocket; + uint32_t uTick = NetTick(); + + // for access to socket list and kill list + NetCritEnter(NULL); + + // walk socket list and perform any callbacks + for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext) + { + // see if we should do callback + if ((pSocket->uCallIdle != 0) && (pSocket->pCallback != NULL) && (!pSocket->bInCallback) && (NetTickDiff(uTick, pSocket->uCallLast) > pSocket->uCallIdle)) + { + pSocket->bInCallback = TRUE; + pSocket->pCallback(pSocket, 0, pSocket->pCallRef); + pSocket->bInCallback = FALSE; + pSocket->uCallLast = uTick = NetTick(); + } + } + + // delete any killed sockets + while ((pSocket = pState->pSockKill) != NULL) + { + pState->pSockKill = pSocket->pKill; + + // release the socket's receive critical section + NetCritKill(&pSocket->RecvCrit); + + // free the socket memory + DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + + // process dns cache list, delete expired entries + SocketHostnameCacheProcess(pState->pHostnameCache, pState->iVerbose); + + // process hostname list, delete completed lookup requests + SocketHostnameListProcess(&pState->pHostList, pState->iMemGroup, pState->pMemGroupUserData); + + // release access to socket list and kill list + NetCritLeave(NULL); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketRecvfromPacketQueue + + \Description + Check if there is a pending inbound packet in the socket packet queue. + + \Input *pSocket - pointer to socket + \Input *pBuf - [out] buffer to receive data + \Input iLen - length of recv buffer + \Input *pFrom - [out] address data was received from (NULL=ignore) + \Input *pFromLen - [out] length of address + + \Output + int32_t - size of packet extracted from queue, 0 if no packet + + \Version 04/20/2016 (mclouatre) +*/ +/************************************************************************************F*/ +static int32_t _SocketRecvfromPacketQueue(SocketT *pSocket, const char *pBuf, int32_t iLen, struct sockaddr *pFrom, int32_t *pFromLen) +{ + int32_t iResult = 0; + + // make sure destination buffer is valid + if ((iLen > 0) && (pBuf != NULL)) + { + // get a packet + if (pSocket->iType != SOCK_STREAM) + { + iResult = SocketPacketQueueRem(pSocket->pRecvQueue, (uint8_t *)pBuf, iLen, &pSocket->RecvAddr); + } + else + { + iResult = SocketPacketQueueRemStream(pSocket->pRecvQueue, (uint8_t *)pBuf, iLen); + } + + if (iResult > 0) + { + if (pFrom != NULL) + { + ds_memcpy_s(pFrom, sizeof(*pFrom), &pSocket->RecvAddr, sizeof(pSocket->RecvAddr)); + *pFromLen = sizeof(*pFrom); + } + } + } + + return(iResult); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketRecvfrom + + \Description + Check if there is a pending inbound packet in the receive buffer of the + system socket + + \Input *pSocket - pointer to socket + \Input *pBuf - [out] buffer to receive data + \Input iLen - length of recv buffer + \Input *pFrom - [out] address data was received from (NULL=ignore) + \Input *pFromLen - [out] length of address + \Input *pRecvErr - [out] pointer to variable to be filled with recv err code + + \Output + int32_t - positive=data bytes received, else standard error code + + \Version 04/20/2016 (mclouatre) +*/ +/************************************************************************************F*/ +static int32_t _SocketRecvfrom(SocketT *pSocket, const char *pBuf, int32_t iLen, struct sockaddr *pFrom, int32_t *pFromLen, int32_t *pRecvErr) +{ + int32_t iResult = 0; + + // make sure socket ref is valid + if (pSocket->uSocket == INVALID_SOCKET) + { + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + + if (pFrom != NULL) + { + if (pSocket->iFamily == AF_INET) + { + iResult = recvfrom(pSocket->uSocket, (char *)pBuf, iLen, 0, pFrom, pFromLen); + } + if (pSocket->iFamily == AF_INET6) + { + struct sockaddr_in6 SockAddr6; + int32_t iFromLen6 = sizeof(SockAddr6); + SockAddr6.sin6_family = AF_INET; + if ((iResult = recvfrom(pSocket->uSocket, (char *)pBuf, iLen, 0, (struct sockaddr *)&SockAddr6, &iFromLen6)) > 0) + { + SocketAddrMapTranslate(&_Socket_pState->AddrMap, pFrom, (struct sockaddr *)&SockAddr6, pFromLen); + } + } + SockaddrInSetMisc(pFrom, NetTick()); + } + else + { + iResult = recv(pSocket->uSocket, (char *)pBuf, iLen, 0); + pFrom = &pSocket->RemoteAddr; + } + + // get most recent socket error + if ((*pRecvErr = WSAGetLastError()) == WSAEMSGSIZE) + { + /* if there was a message truncation, simply return the truncated size. this matches what we + do with the async recv thread version and also the linux behavior of recvfrom() */ + iResult = iLen; + } + + return(iResult); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketProcessQueueResize + + \Description + Resize socket packet queue if needed. + + \Input *pSocket - pointer to socket + + \Notes + This function is meant to be called from the _SocketRecvThread() only. + Do not use this function in code paths exercised by other threads. + + \Version 04/20/2016 (mclouatre) +*/ +/************************************************************************************F*/ +static void _SocketProcessQueueResize(SocketT *pSocket) +{ + SocketStateT *pState = _Socket_pState; + if ((pSocket->iPacketQueueResizePending != -1)) + { + pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, pSocket->iPacketQueueResizePending, pState->iMemGroup, pState->pMemGroupUserData); + pSocket->iPacketQueueResizePending = -1; + } +} + +/*F*************************************************************************************/ +/*! + \Function _SocketRecvfromAsyncComplete + + \Description + Called when data is received by _SocketRecvThread(). If there is a callback + registered, then the socket is passed to that callback so the data may be + consumed. + + \Input *pSocket - pointer to socket that has new data + \Input iBytesReceived - number of bytes received + + \Notes + This function is meant to be called from the _SocketRecvThread() only. + Do not use this function in code paths exercised by other threads. + + \Version 08/30/2004 (jbrookes) +*/ +/************************************************************************************F*/ +static void _SocketRecvfromAsyncComplete(SocketT *pSocket, int32_t iBytesReceived) +{ + // translate IPv6->IPv4, save receive timestamp + if (pSocket->iType != SOCK_STREAM) + { + if (pSocket->iFamily == AF_INET6) + { + int32_t iNameLen = sizeof(pSocket->RecvAddr); + SockaddrInit(&pSocket->RecvAddr, AF_INET); + SocketAddrMapTranslate(&_Socket_pState->AddrMap, &pSocket->RecvAddr, (struct sockaddr *)&pSocket->RecvAddr6, &iNameLen); + } + SockaddrInSetMisc(&pSocket->RecvAddr, NetTick()); + } + + // complete setup of packet queue entry (already has data) by updating the size and reception time + pSocket->pRecvPacket->iPacketSize = iBytesReceived; + pSocket->pRecvPacket->uPacketTick = NetTick(); + ds_memcpy_s(&pSocket->pRecvPacket->PacketAddr, sizeof(pSocket->pRecvPacket->PacketAddr), &pSocket->RecvAddr, sizeof(pSocket->RecvAddr)); + + // we are done with populating that specific packet queue entry, kill reference into packet queue + pSocket->pRecvPacket = NULL; + + /* This is a safe place to deal with pending packet queue resize because the recvcrit is guaranteed to be locked + and there is no async recv operation in progress on the queue (which implicitly means that pSocket->pRecvPacket is NOT + pointing to a buffer in the queue. + + It is important to execute this after pSocket->pRecvPacket (which points to an entry in the queue) is fully initialized + and befor the recv callback is invoked (because we want to resize to happen before SocketRecvfrom being potentially + invoked from the callback. */ + _SocketProcessQueueResize(pSocket); + + // see if we should issue callback + if ((!pSocket->bInCallback) && (pSocket->pCallback != NULL) && (pSocket->uCallMask & CALLB_RECV)) + { + pSocket->bInCallback = TRUE; + (pSocket->pCallback)(pSocket, 0, pSocket->pCallRef); + pSocket->bInCallback = FALSE; + pSocket->uCallLast = NetTick(); + } +} + +/*F*************************************************************************************/ +/*! + \Function _SocketRecvfromAsync + + \Description + Issue an overlapped recv call on the given socket. + + \Input *pSocket - pointer to socket to read from + + \Notes + This function is meant to be called from the _SocketRecvThread() only. + Do not use this function in code paths exercised by other threads. + + \Version 08/30/2004 (jbrookes) +*/ +/************************************************************************************F*/ +static void _SocketRecvfromAsync(SocketT *pSocket) +{ + int32_t iResult = 0; + int32_t iRecvErr; + WSABUF RecvBuf; + + /* Mark the operation as in progress. + For scenarios where the recv call returned 0 (meaning immediate completion), we enforce an execution + path similar to recv call not completing immediately. We asssume two things: + * _SocketRecvThread() will not wait on the next WSAWaitForMultipleEvents() because the event for this socket is signaled. + * The following call to WSAGetOverlappedResult() will then detect completion of the recv operation. */ + pSocket->bRecvInp = TRUE; + + // get a packet queue entry to receive into + pSocket->pRecvPacket = SocketPacketQueueAlloc(pSocket->pRecvQueue); + + // set up for recv call + RecvBuf.buf = (CHAR *)pSocket->pRecvPacket->aPacketData; + RecvBuf.len = sizeof(pSocket->pRecvPacket->aPacketData); + pSocket->uRecvFlag = 0; + + // try and receive some data + if (pSocket->iType == SOCK_DGRAM) + { + if (pSocket->iFamily == AF_INET) + { + pSocket->iAddrLen = sizeof(pSocket->RecvAddr); + iResult = WSARecvFrom(pSocket->uSocket, &RecvBuf, 1, NULL, (LPDWORD)&pSocket->uRecvFlag, (struct sockaddr *)&pSocket->RecvAddr, (LPINT)&pSocket->iAddrLen, &pSocket->Overlapped, NULL); + } + if (pSocket->iFamily == AF_INET6) + { + pSocket->iAddrLen = sizeof(pSocket->RecvAddr6); + iResult = WSARecvFrom(pSocket->uSocket, &RecvBuf, 1, NULL, (LPDWORD)&pSocket->uRecvFlag, (struct sockaddr *)&pSocket->RecvAddr6, (LPINT)&pSocket->iAddrLen, &pSocket->Overlapped, NULL); + } + } + else // pSocket->iType == SOCK_RAW + { + iResult = WSARecv(pSocket->uSocket, &RecvBuf, 1, NULL, (LPDWORD)&pSocket->uRecvFlag, &pSocket->Overlapped, NULL); + } + + // error? + if ((iResult == SOCKET_ERROR) && ((iRecvErr = WSAGetLastError()) != WSA_IO_PENDING)) + { + if (pSocket->iType != SOCK_STREAM) + { + NetPrintf(("dirtynetwin: [%p] error %s when trying to initiate async receive on socket\n", pSocket, DirtyErrGetName(iRecvErr))); + } + else + { + // in the testing done, winsock2 returns WSAECONNABORTED when the FIN comes in for the socket. in reviewing the error codes there was no other more suitable check to make + NetPrintf(("dirtynetwin: [%p] connection %s\n", pSocket, (iRecvErr == WSAECONNABORTED) ? "closed" : "failed")); + pSocket->iOpened = -1; + } + + // clean up resources that were reserved when the async recv was initiated + SocketPacketQueueAllocUndo(pSocket->pRecvQueue); + + // mark that receive operation is no longer in progress + pSocket->bRecvInp = FALSE; + } +} + +/*F*************************************************************************************/ +/*! + \Function _SocketRecvThread + + \Description + Wait for incoming data and deliver it immediately to the registered socket callback, + if any. + + \Input pUnused - unused + + \Version 08/30/2004 (jbrookes) +*/ +/************************************************************************************F*/ +static void _SocketRecvThread(void *pUnused) +{ + SocketStateT *pState = _Socket_pState; + WSAEVENT aEventList[SOCKET_MAXEVENTS]; + int32_t iNumEvents, iResult; + SocketT *pSocket; + char strThreadId[32]; + + // get the thread id + DirtyThreadGetThreadId(strThreadId, sizeof(strThreadId)); + + // show we are alive + NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: receive thread started (thid=%s)\n", strThreadId)); + + // clear event list + ds_memclr(aEventList, sizeof(aEventList)); + + // loop until done + while (pState->iRecvLife == 1) + { + // acquire global critical section for access to g_socklist + NetCritEnter(NULL); + + // add global event to list + iNumEvents = 0; + aEventList[iNumEvents++] = pState->hEvent; + + /* Walk the socket list and for each eligible socket do: + 1- If an async recv has recently completed, then finalize the recv operation and invoke the recv callback if registered. + Then mark the async recv operatoin has no longer in progress. + 2- If an async recv is not in progress, initiate one if n-deep packet queue is not full. Then mark the async recv operation + as in progress. + 3- If an async revc is in progress (old or newly initiated), then add the socket's event to the event list that the + thread later blocks on when calling WSAWaitForMultipleEvents(). */ + for (pSocket = pState->pSockList; (pSocket != NULL) && (iNumEvents < SOCKET_MAXEVENTS); pSocket = pSocket->pNext) + { + // only handle non-virtual sockets with asyncrecv true + if ((!pSocket->bVirtual) && (pSocket->uSocket != INVALID_SOCKET) && pSocket->bAsyncRecv) + { + // acquire socket critical section + NetCritEnter(&pSocket->RecvCrit); + + // is an asynchronous recv in progress on this socket? + if (pSocket->bRecvInp) + { + int32_t iRecvResult; + + // check for overlapped read completion + if (WSAGetOverlappedResult(pSocket->uSocket, &pSocket->Overlapped, (LPDWORD)&iRecvResult, FALSE, (LPDWORD)&pSocket->uRecvFlag) == TRUE) + { + // mark that receive operation is no longer in progress + pSocket->bRecvInp = FALSE; + + _SocketRecvfromAsyncComplete(pSocket, iRecvResult); + } + else if ((iResult = WSAGetLastError()) != WSA_IO_INCOMPLETE) + { + // clean up resources that were reserved when the async recv was initiated + SocketPacketQueueAllocUndo(pSocket->pRecvQueue); + + #if DIRTYCODE_LOGGING + if (iResult != WSAECONNRESET) + { + NetPrintf(("dirtynetwin: [%p] WSAGetOverlappedResult error %d\n", pSocket, iResult)); + } + #endif + + // mark that receive operation is no longer in progress + pSocket->bRecvInp = FALSE; + + /* This is a safe place to deal with pending packet queue resize because the recvcrit is guaranteed to be locked + and there is no async read operation in progress on the queue (which implicitly means that pSocket->pRecvPacket is NOT + pointing to a buffer in the queue. */ + _SocketProcessQueueResize(pSocket); + } + } + + /* Before proceeding, make sure that the user callback invoked in _SocketRecvThreadFinishAsyncRead() did not close the socket + with SocketClose(). We know that SocketClose() function resets the value of pSocket->socket to INVALID_SOCKET. Also, we + know that it does not destroy pSocket but it queues it in the global kill list. Since this list cannot be processed before + the code below, as it also runs in the context of the global critical section, the following code is thread-safe. */ + if ((pSocket->uSocket != INVALID_SOCKET) && ((pSocket->iType != SOCK_STREAM) || (pSocket->iOpened > 0))) + { + // should we initiate a new asynchronous recv on this socket? + if (!pSocket->bRecvInp && !SocketPacketQueueStatus(pSocket->pRecvQueue, 'pful')) + { + _SocketRecvfromAsync(pSocket); + } + + // if an asynchronous recv (old or newly initiated) is in progress, then add the associated event to event list + if (pSocket->bRecvInp) + { + aEventList[iNumEvents++] = pSocket->Overlapped.hEvent; + } + } + + // release socket critical section + NetCritLeave(&pSocket->RecvCrit); + } + } + + // protect against events being deleted + NetCritEnter(&pState->EventCrit); + + // release global critical section + NetCritLeave(NULL); + + // wait for an event to trigger + iResult = WSAWaitForMultipleEvents(iNumEvents, aEventList, FALSE, WSA_INFINITE, FALSE) - WSA_WAIT_EVENT_0; + + // reset the signaled event + if ((iResult >= 0) && (iResult < iNumEvents)) + { + WSAResetEvent(aEventList[iResult]); + } + + // leave event protected section + NetCritLeave(&pState->EventCrit); + } + + // indicate we are done + NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: receive thread exit\n")); + pState->iRecvLife = 0; +} + +/*F*************************************************************************************/ +/*! + \Function _SocketPoll + + \Description + Perform a blocking poll in nanoseconds + + \Input *pState - pointer to module state + \Input uPollUsec - time to perform the poll (select) for + \Input *ppSocketList - socket list or NULL + + \Output + int32_t - result of the select call + + \Version 04/17/2019 (eesponda) +*/ +/************************************************************************************F*/ +static int32_t _SocketPoll(SocketStateT *pState, uint32_t uPollUsec, SocketT **ppSocketList) +{ + fd_set FdRead, FdExcept; + struct timeval TimeVal; + SocketT *pTempSocket, **ppSocket; + int32_t iSocket, iResult; + int32_t iMaxSocket = 0; + + FD_ZERO(&FdRead); + FD_ZERO(&FdExcept); + TimeVal.tv_sec = uPollUsec/1000000; + TimeVal.tv_usec = uPollUsec%1000000; + + // if socket list specified, use it + if (ppSocketList != NULL) + { + // add sockets to select list (FD_SETSIZE is 64) + for (ppSocket = ppSocketList, iSocket = 0; (*ppSocket != NULL) && (iSocket < FD_SETSIZE); ppSocket++, iSocket++) + { + FD_SET((*ppSocket)->uSocket, &FdRead); + FD_SET((*ppSocket)->uSocket, &FdExcept); + iMaxSocket = DS_MAX((int32_t)((*ppSocket)->uSocket), iMaxSocket); + } + } + else + { + // get exclusive access to socket list + NetCritEnter(NULL); + // walk socket list and add all sockets + for (pTempSocket = pState->pSockList, iSocket = 0; (pTempSocket != NULL) && (iSocket < FD_SETSIZE); pTempSocket = pTempSocket->pNext, iSocket++) + { + if (pTempSocket->uSocket != INVALID_SOCKET) + { + FD_SET(pTempSocket->uSocket, &FdRead); + FD_SET(pTempSocket->uSocket, &FdExcept); + iMaxSocket = DS_MAX((int32_t)(pTempSocket->uSocket), iMaxSocket); + } + } + // for access to g_socklist and g_sockkill + NetCritLeave(NULL); + } + + // wait for input on the socket list for up to iData1 milliseconds + iResult = select(iMaxSocket + 1, &FdRead, NULL, &FdExcept, &TimeVal); + + // if any sockets have pending data, figure out which ones + if (iResult > 0) + { + // re-acquire critical section + NetCritEnter(NULL); + + // update sockets if there is data to be read or not + for (pTempSocket = pState->pSockList; pTempSocket != NULL; pTempSocket = pTempSocket->pNext) + { + if ((pTempSocket->uSocket != INVALID_SOCKET) && + (FD_ISSET(pTempSocket->uSocket, &FdRead)) && + (pTempSocket->pCallback != NULL) && + (pTempSocket->uCallMask & CALLB_RECV)) + { + pTempSocket->pCallback(pTempSocket, 0, pTempSocket->pCallRef); + pTempSocket->uCallLast = NetTick(); + } + } + + // release the critical section + NetCritLeave(NULL); + } + + // return number of file descriptors with pending data + return(iResult); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketInfoGlobal + + \Description + Return information about global state + + \Input iInfo - selector for desired information + \Input iData - selector specific + \Input *pBuf - [out] return buffer + \Input iLen - buffer length + + \Output + int32_t - size of returned data or error code (negative value) + + \Notes + These selectors need to be documented in SocketInfo() to allow our + documentation generation to pick them up. + + \Version 03/31/2017 (eesponda) +*/ +/************************************************************************************F*/ +static int32_t _SocketInfoGlobal(int32_t iInfo, int32_t iData, void *pBuf, int32_t iLen) +{ + SocketStateT *pState = _Socket_pState; + + if (iInfo == 'addr') + { + #if defined(DIRTYCODE_XBOXONE) + // force new acquisition of address? + if (iData == 1) + { + pState->uLocalAddr = 0; + } + #endif + return(SocketGetLocalAddr()); + } + // get socket bound to given port + if ( (iInfo == 'bind') || (iInfo == 'bndu') ) + { + SocketT *pSocket; + struct sockaddr BindAddr; + int32_t iFound = -1; + + // for access to socket list + NetCritEnter(NULL); + + // walk socket list and find matching socket + for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext) + { + // if iInfo is 'bndu', only consider sockets of type SOCK_DGRAM + // note: 'bndu' stands for "bind udp" + if ( (iInfo == 'bind') || ((iInfo == 'bndu') && (pSocket->iType == SOCK_DGRAM)) ) + { + // get socket info + SocketInfo(pSocket, 'bind', 0, &BindAddr, sizeof(BindAddr)); + if (SockaddrInGetPort(&BindAddr) == iData) + { + *(SocketT **)pBuf = pSocket; + iFound = 0; + break; + } + } + } + + // for access to g_socklist and g_sockkill + NetCritLeave(NULL); + return(iFound); + } + // check if specified ipv4 address is virtual and return associated ipv6 address if so + if (iInfo == '?ip6') + { + int32_t iResult = -1; + struct sockaddr_in6 SockAddr6, *pSockAddr6; + struct sockaddr SockAddr; + int32_t iNameLen; + + SockaddrInit(&SockAddr, AF_INET); + SockaddrInSetAddr(&SockAddr, iData); + + SockaddrInit6(&SockAddr6, AF_INET6); + pSockAddr6 = ((pBuf != NULL) && (iLen == sizeof(SockAddr6))) ? (struct sockaddr_in6 *)pBuf : &SockAddr6; + iNameLen = sizeof(SockAddr6); + + iResult = SocketAddrMapGet(&pState->AddrMap, (struct sockaddr *)pSockAddr6, &SockAddr, &iNameLen) ? 1 : 0; + return(iResult); + } + + #if !defined(DIRTYCODE_XBOXONE) + // get local address previously specified by user for subsequent SocketBind() operations + if (iInfo == 'ladr') + { + // If 'ladr' had not been set previously, address field of output sockaddr buffer + // will just be filled with 0. + SockaddrInSetAddr((struct sockaddr *)pBuf, pState->uAdapterAddress); + return(0); + } + #endif + // return max packet size + if (iInfo == 'maxp') + { + return(pState->iMaxPacket); + } + // get send callback function pointer (iData specifies index in array) + if (iInfo == 'sdcf') + { + if ((pBuf != NULL) && (iLen == sizeof(pState->aSendCbEntries[iData].pSendCallback))) + { + ds_memcpy(pBuf, &pState->aSendCbEntries[iData].pSendCallback, sizeof(pState->aSendCbEntries[iData].pSendCallback)); + return(0); + } + + NetPrintf(("dirtynetwin: 'sdcf' selector used with invalid paramaters\n")); + return(-1); + } + // get send callback user data pointer (iData specifies index in array) + if (iInfo == 'sdcu') + { + if ((pBuf != NULL) && (iLen == sizeof(pState->aSendCbEntries[iData].pSendCallref))) + { + ds_memcpy(pBuf, &pState->aSendCbEntries[iData].pSendCallref, sizeof(pState->aSendCbEntries[iData].pSendCallref)); + return(0); + } + + NetPrintf(("dirtynetwin: 'sdcu' selector used with invalid paramaters\n")); + return(-1); + } + // return global debug output level + if (iInfo == 'spam') + { + return(pState->iVerbose); + } + // unhandled + NetPrintf(("dirtynetwin: unhandled global SocketInfo() selector '%C'\n", iInfo)); + return(-1); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketControlGlobal + + \Description + Process a global control message (type specific operation) + + \Input iOption - the option to pass + \Input iData1 - message specific parm + \Input *pData2 - message specific parm + \Input *pData3 - message specific parm + + \Output + int32_t - message specific result (-1=unsupported message, -2=no such module) + + \Notes + These selectors need to be documented in SocketControl() to allow our + documentation generation to pick them up. + + \Version 03/31/2017 (eesponda) +*/ +/************************************************************************************F*/ +static int32_t _SocketControlGlobal(int32_t iOption, int32_t iData1, void *pData2, void *pData3) +{ + SocketStateT *pState = _Socket_pState; + + // set address family to use for socket operations + if (iOption == 'afam') + { + if ((iData1 != AF_INET) && (iData1 != AF_INET6)) + { + return(-1); + } + pState->iFamily = iData1; + NetPrintf(("dirtynetwin: address family used for socket operations set to %s\n", pState->iFamily == AF_INET ? "AF_INET" : "AF_INET6")); + return(0); + } + // handle connect message + if (iOption == 'conn') + { + return(0); + } + // handle disconnect message + if (iOption == 'disc') + { + return(0); + } + // set an ipv6 address into the mapping table + if (iOption == '+ip6') + { + return(SocketAddrMapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, iData1)); + } + // del an ipv6 address from the mapping table + if (iOption == '-ip6') + { + return(SocketAddrUnmapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, iData1)); + } + // remap an existing ipv6 address in the mapping table + if (iOption == '~ip6') + { + return(SocketAddrRemapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, (const struct sockaddr *)pData3, iData1)); + } + #if !defined(DIRTYCODE_XBOXONE) + // set local address? (used to select between multiple network interfaces) + if (iOption == 'ladr') + { + pState->uAdapterAddress = (unsigned)iData1; + return(0); + } + #endif + // set max udp packet size + if (iOption == 'maxp') + { + NetPrintf(("dirtynetwin: setting max udp packet size to %d\n", iData1)); + pState->iMaxPacket = iData1; + return(0); + } + // block waiting on input from socket list in milliseconds + if (iOption == 'poll') + { + return(_SocketPoll(pState, (unsigned)iData1*1000, pData2)); + } + if (iOption == 'poln') + { + return(_SocketPoll(pState, (unsigned)iData1, pData2)); + } + // set/unset send callback (iData1=TRUE for set - FALSE for unset, pData2=callback, pData3=callref) + if (iOption == 'sdcb') + { + SocketSendCallbackEntryT sendCbEntry; + sendCbEntry.pSendCallback = (SocketSendCallbackT *)pData2; + sendCbEntry.pSendCallref = pData3; + + if (iData1) + { + return(SocketSendCallbackAdd(&pState->aSendCbEntries[0], &sendCbEntry)); + } + else + { + return(SocketSendCallbackRem(&pState->aSendCbEntries[0], &sendCbEntry)); + } + } + // set debug level + if (iOption == 'spam') + { + // set module level debug level + pState->iVerbose = iData1; + return(0); + } + // mark a port as virtual + if (iOption == 'vadd') + { + int32_t iPort; + + // find a slot to add virtual port + for (iPort = 0; pState->aVirtualPorts[iPort] != 0; iPort++) + ; + if (iPort < SOCKET_MAXVIRTUALPORTS) + { + NetPrintfVerbose((pState->iVerbose, 1, "dirtynetwin: added port %d to virtual port list\n", iData1)); + pState->aVirtualPorts[iPort] = (uint16_t)iData1; + return(0); + } + } + // remove port from virtual port list + if (iOption == 'vdel') + { + int32_t iPort; + + // find virtual port in list + for (iPort = 0; (iPort < SOCKET_MAXVIRTUALPORTS) && (pState->aVirtualPorts[iPort] != (uint16_t)iData1); iPort++) + ; + if (iPort < SOCKET_MAXVIRTUALPORTS) + { + NetPrintfVerbose((pState->iVerbose, 1, "dirtynetwin: removed port %d from virtual port list\n", iData1)); + pState->aVirtualPorts[iPort] = 0; + return(0); + } + } + // unhandled + NetPrintf(("dirtynetwin: unhandled global SocketControl() option '%C'\n", iOption)); + return(-1); +} + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function SocketCreate + + \Description + Create new instance of socket interface module. Initializes all global + resources and makes module ready for use. + + \Input iThreadPrio - priority to start threads with + \Input iThreadStackSize - stack size to start threads with (in bytes) + \Input iThreadCpuAffinity - cpu affinity to start threads with + + \Output + int32_t - negative=error, zero=success + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketCreate(int32_t iThreadPrio, int32_t iThreadStackSize, int32_t iThreadCpuAffinity) +{ + SocketStateT *pState = _Socket_pState; + WSADATA WSAData; + int32_t iResult; + int32_t iMemGroup; + void *pMemGroupUserData; + DirtyThreadConfigT ThreadConfig; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // error if already started + if (pState != NULL) + { + NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: SocketCreate() called while module is already active\n")); + return(-1); + } + + // print version info + NetPrintf(("dirtynetwin: DirtySDK v%d.%d.%d.%d.%d\n", DIRTYSDK_VERSION_YEAR, DIRTYSDK_VERSION_SEASON, DIRTYSDK_VERSION_MAJOR, DIRTYSDK_VERSION_MINOR, DIRTYSDK_VERSION_PATCH)); + + // alloc and init state ref + if ((pState = (SocketStateT *)DirtyMemAlloc(sizeof(*pState), SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynetwin: unable to allocate module state\n")); + return(-2); + } + ds_memclr(pState, sizeof(*pState)); + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + pState->iMaxPacket = SOCKET_MAXUDPRECV; + pState->iFamily = AF_INET6; + pState->iVerbose = 1; + + // save global module ref + _Socket_pState = pState; + + // startup network libs + NetLibCreate(iThreadPrio, iThreadStackSize, iThreadCpuAffinity); + + // start winsock + ds_memclr(&WSAData, sizeof(WSAData)); + iResult = WSAStartup(MAKEWORD(2,2), &WSAData); + if (iResult != 0) + { + NetPrintf(("dirtynetwin: error %d loading winsock library\n", iResult)); + SocketDestroy((uint32_t)(-1)); + return(-3); + } + + // save the available version + pState->iVersion = (LOBYTE(WSAData.wVersion)<<8)|(HIBYTE(WSAData.wVersion)<<0); + + // create hostname cache + if ((pState->pHostnameCache = SocketHostnameCacheCreate(iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynetwin: unable to create hostname cache\n")); + SocketDestroy((uint32_t)(-1)); + return(-4); + } + + // add our idle handler + NetIdleAdd(&_SocketIdle, pState); + + // create a global event to notify recv thread of newly available sockets + pState->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (pState->hEvent == NULL) + { + NetPrintf(("dirtynetwin: error %d creating state event\n", GetLastError())); + SocketDestroy(0); + return(-5); + } + + // initialize event critical section + NetCritInit(&pState->EventCrit, "recv-event-crit"); + + /* if recvthread has been created but has no chance to run (upon failure or shutdown + immediately), there will be a problem. by setting iRecvLife to non-zero before + thread creation we ensure SocketDestroy work properly. */ + pState->iRecvLife = 1; // see _SocketRecvThread for more details + + // configure thread parameters + ds_memclr(&ThreadConfig, sizeof(ThreadConfig)); + ThreadConfig.pName = "SocketRecv"; + ThreadConfig.iPriority = iThreadPrio; + ThreadConfig.iAffinity = iThreadCpuAffinity; + ThreadConfig.iVerbosity = pState->iVerbose; + + // start up socket receive thread + if ((iResult = DirtyThreadCreate(_SocketRecvThread, NULL, &ThreadConfig)) != 0) + { + pState->iRecvLife = 0; // no recvthread was created, reset to 0 + NetPrintf(("dirtynetwin: error %d creating socket receive thread\n", iResult)); + SocketDestroy(0); + return(-6); + } + + // init socket address map + SocketAddrMapInit(&pState->AddrMap, pState->iMemGroup, pState->pMemGroupUserData); + + // return success + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function SocketDestroy + + \Description + Release resources and destroy module. + + \Input uShutdownFlags - shutdown flags + + \Output + int32_t - negative=error, zero=success + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketDestroy(uint32_t uShutdownFlags) +{ + SocketStateT *pState = _Socket_pState; + + // error if not active + if (pState == NULL) + { + NetPrintf(("dirtynetwin: SocketDestroy() called while module is not active\n")); + return(-1); + } + + NetPrintf(("dirtynetwin: shutting down\n")); + + // $$TODO- why don't we do this on XONE too? + #if !defined(DIRTYCODE_XBOXONE) + // wait until all lookup threads are done + while (pState->pHostList != NULL) + { + volatile HostentT **ppHost; + int32_t iSocketLookups; + + // check for lookup threads that are still active + for (ppHost = &pState->pHostList, iSocketLookups = 0; *ppHost != NULL; ppHost = (volatile HostentT **)&(*ppHost)->pNext) + { + iSocketLookups += (*ppHost)->thread ? 0 : 1; + } + // if no ongoing socket lookups, we're done + if (iSocketLookups == 0) + { + break; + } + Sleep(1); + } + #endif + + // kill idle callbacks + NetIdleDel(&_SocketIdle, pState); + + // let any idle event finish + NetIdleDone(); + + // tell receive thread to quit and wake it up (if running) + if (pState->iRecvLife == 1) + { + pState->iRecvLife = 2; + if (pState->hEvent != NULL) + { + WSASetEvent(pState->hEvent); + } + + // wait for thread to terminate + while (pState->iRecvLife > 0) + { + Sleep(1); + } + } + + // cleanup addr map, if allocated + SocketAddrMapShutdown(&pState->AddrMap); + + // close all sockets + NetCritEnter(NULL); + while (pState->pSockList != NULL) + { + SocketClose(pState->pSockList); + } + NetCritLeave(NULL); + + // clear the kill list + _SocketIdle(pState); + + // destroy hostname cache + if (pState->pHostnameCache != NULL) + { + SocketHostnameCacheDestroy(pState->pHostnameCache); + } + + // we rely on the Handle having been created to know if the critical section was created. + if (pState->hEvent != NULL) + { + // destroy event critical section + NetCritKill(&pState->EventCrit); + // delete global event + CloseHandle(pState->hEvent); + } + + // free the memory and clear global module ref + _Socket_pState = NULL; + DirtyMemFree(pState, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + + // shut down network libs + NetLibDestroy(0); + + // shutdown winsock, unless told otherwise + if (uShutdownFlags == 0) + { + WSACleanup(); + } + + NetPrintf(("dirtynetwin: shutdown complete\n")); + + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function SocketOpen + + \Description + Create a new transfer endpoint. A socket endpoint is required for any + data transfer operation. + + \Input iFamily - address family (AF_INET) [IGNORED] + \Input iType - socket type (SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, ...) + \Input iProto - protocol type for SOCK_RAW (unused by others) + + \Output + SocketT - socket reference + + \Notes + The address family specified here is ignored; instead it is dictated based on + the internal configuration setting which defaults to AF_INET6, but can be + overridden using the 'fam' SocketControl() selector. + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +SocketT *SocketOpen(int32_t iFamily, int32_t iType, int32_t iProto) +{ + return(_SocketOpen(INVALID_SOCKET, _Socket_pState->iFamily, iType, iProto)); +} + +/*F*************************************************************************************/ +/*! + \Function SocketClose + + \Description + Close a socket. Performs a graceful shutdown of connection oriented protocols. + + \Input *pSocket - socket reference + + \Output + int32_t - zero + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketClose(SocketT *pSocket) +{ + return(_SocketClose(pSocket, TRUE)); +} + +/*F*************************************************************************************/ +/*! + \Function SocketImport + + \Description + Import a socket. The given socket ref may be a SocketT, in which case a + SocketT pointer to the ref is returned, or it can be an actual Sony socket ref, + in which case a SocketT is created for the Sony socket ref. + + \Input uSockRef - socket reference + + \Output + SocketT * - pointer to imported socket, or NULL + + \Version 01/14/2005 (jbrookes) +*/ +/************************************************************************************F*/ +SocketT *SocketImport(intptr_t uSockRef) +{ + SocketStateT *pState = _Socket_pState; + int32_t iProto, iProtoSize; + SocketT *pSocket; + + // see if this socket is already in our socket list + NetCritEnter(NULL); + for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext) + { + if (pSocket == (SocketT *)uSockRef) + { + break; + } + } + NetCritLeave(NULL); + + // if socket is in socket list, just return it + if (pSocket != NULL) + { + return(pSocket); + } + + //$$ TODO - this assumes AF_INET + + // get info from socket ref + iProtoSize = sizeof(iProto); + if (getsockopt((SOCKET)uSockRef, 0, SO_TYPE, (char *)&iProto, &iProtoSize) != SOCKET_ERROR) + { + // create the socket (note: winsock socket types directly map to dirtysock socket types) + pSocket = _SocketOpen(uSockRef, pSocket->iFamily, iProto, 0); + + // update local and remote addresses + SocketInfo(pSocket, 'bind', 0, &pSocket->LocalAddr, sizeof(pSocket->LocalAddr)); + SocketInfo(pSocket, 'peer', 0, &pSocket->RemoteAddr, sizeof(pSocket->RemoteAddr)); + + // mark it as imported + pSocket->uImported = 1; + } + + return(pSocket); +} + +/*F*************************************************************************************/ +/*! + \Function SocketRelease + + \Description + Release an imported socket. + + \Input *pSocket - pointer to socket + + \Version 01/14/2005 (jbrookes) +*/ +/************************************************************************************F*/ +void SocketRelease(SocketT *pSocket) +{ + // if it wasn't imported, nothing to do + if (!pSocket->uImported) + { + return; + } + + // dispose of SocketT, but leave the sockref alone + _SocketClose(pSocket, FALSE); +} + +/*F*************************************************************************************/ +/*! + \Function SocketShutdown + + \Description + Perform partial/complete shutdown of socket indicating that either sending + and/or receiving is complete. + + \Input *pSocket - socket reference + \Input iHow - SOCK_NOSEND and/or SOCK_NORECV + + \Output + int32_t - zero + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketShutdown(SocketT *pSocket, int32_t iHow) +{ + // no shutdown for an invalid socket + if (pSocket->uSocket == INVALID_SOCKET) + { + return(0); + } + + // translate how + if (iHow == SOCK_NOSEND) + { + iHow = SD_SEND; + } + else if (iHow == SOCK_NORECV) + { + iHow = SD_RECEIVE; + } + else if (iHow == (SOCK_NOSEND|SOCK_NORECV)) + { + iHow = SD_BOTH; + } + + pSocket->uShutdown |= iHow; + shutdown(pSocket->uSocket, iHow); + + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function SocketBind + + \Description + Bind a local address/port to a socket. + + \Input *pSocket - socket reference + \Input *pName - local address/port + \Input iNameLen - length of name + + \Output + int32_t - standard network error code (SOCKERR_xxx) + + \Notes + If either address or port is zero, then they are filled in automatically. + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketBind(SocketT *pSocket, const struct sockaddr *pName, int32_t iNameLen) +{ + SocketStateT *pState = _Socket_pState; + struct sockaddr_in6 SockAddr6; + int32_t iResult; + + #if !defined(DIRTYCODE_XBOXONE) + struct sockaddr BindAddr; + + // bind to specific address? + if ((SockaddrInGetAddr(pName) == 0) && (pState->uAdapterAddress != 0)) + { + ds_memcpy_s(&BindAddr, sizeof(BindAddr), pName, sizeof(*pName)); + SockaddrInSetAddr(&BindAddr, pState->uAdapterAddress); + pName = &BindAddr; + } + #endif + + // is the bind port a virtual port? + if (pSocket->iType == SOCK_DGRAM) + { + int32_t iPort; + uint16_t uPort; + + if ((uPort = SockaddrInGetPort(pName)) != 0) + { + // find virtual port in list + for (iPort = 0; (iPort < SOCKET_MAXVIRTUALPORTS) && (pState->aVirtualPorts[iPort] != uPort); iPort++) + ; + if (iPort < SOCKET_MAXVIRTUALPORTS) + { + // acquire socket critical section + NetCritEnter(&pSocket->RecvCrit); + + // check to see if the socket is bound + if (pSocket->bVirtual && (pSocket->uVirtualPort != 0)) + { + NetPrintf(("dirtynetwin: [%p] failed to bind socket to %u which was already bound to port %u virtual\n", pSocket, uPort, pSocket->uVirtualPort)); + NetCritLeave(&pSocket->RecvCrit); + return(pSocket->iLastError = SOCKERR_INVALID); + } + + // close winsock socket + NetPrintf(("dirtynetwin: [%p] making socket bound to port %d virtual\n", pSocket, uPort)); + if (pSocket->uSocket != INVALID_SOCKET) + { + shutdown(pSocket->uSocket, SOCK_NOSEND); + closesocket(pSocket->uSocket); + pSocket->uSocket = INVALID_SOCKET; + } + /* increase socket queue size; this protects virtual sockets from having data pushed into + them and overwriting previous data that hasn't been read yet */ + pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, 4, pState->iMemGroup, pState->pMemGroupUserData); + // mark socket as virtual + pSocket->uVirtualPort = uPort; + pSocket->bVirtual = TRUE; + + // release socket critical section + NetCritLeave(&pSocket->RecvCrit); + + return(0); + } + } + } + + #if defined(DIRTYCODE_XBOXONE) + // set up IPv6 address (do not use an IPv4-mapped IPv6 here as it will disallow IPv6 address use) + ds_memclr(&SockAddr6, sizeof(SockAddr6)); + SockAddr6.sin6_family = AF_INET6; + SockAddr6.sin6_port = SocketNtohs(SockaddrInGetPort(pName)); + pName = (const struct sockaddr *)&SockAddr6; + iNameLen = sizeof(SockAddr6); + #else + // translate IPv4 -> IPv6 address (if needed) + if ((pSocket->iFamily == AF_INET6) && (pName->sa_family != AF_INET6)) + { + ds_memclr(&SockAddr6, sizeof(SockAddr6)); + SockAddr6.sin6_family = AF_INET6; + SockAddr6.sin6_port = SocketNtohs(SockaddrInGetPort(pName)); + pName = SocketAddrMapTranslate(&_Socket_pState->AddrMap, (struct sockaddr *)&SockAddr6, pName, &iNameLen); + } + #endif + + // execute the bind + iResult = _XlatError(bind(pSocket->uSocket, pName, iNameLen)); + + // notify read thread that socket is ready to be read from + if ((iResult == SOCKERR_NONE) && pSocket->bAsyncRecv) + { + WSASetEvent(pState->hEvent); + } + + // return result to caller + pSocket->iLastError = iResult; + return(pSocket->iLastError); +} + +/*F*************************************************************************************/ +/*! + \Function SocketConnect + + \Description + Initiate a connection attempt to a remote host. + + \Input *pSocket - socket reference + \Input *pName - pointer to name of socket to connect to + \Input iNameLen - length of name + + \Output + int32_t - standard network error code (SOCKERR_xxx) + + \Notes + Only has real meaning for stream protocols. For a datagram protocol, this + just sets the default remote host. + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketConnect(SocketT *pSocket, struct sockaddr *pName, int32_t iNameLen) +{ + struct sockaddr_in6 SockAddr6; + struct sockaddr SockAddr, *pSockAddr = NULL; + int32_t iResult, iSockAddrLen = 0; + + // mark as not open + pSocket->iOpened = 0; + + // save connect address + ds_memcpy_s(&pSocket->RemoteAddr, sizeof(pSocket->RemoteAddr), pName, sizeof(*pName)); + + // init sockaddr + if (pSocket->iFamily == AF_INET) + { + SockaddrInit(&SockAddr, AF_INET); + pSockAddr = &SockAddr; + iSockAddrLen = sizeof(SockAddr); + } + else if (pSocket->iFamily == AF_INET6) + { + // initialize family of SockAddr6 + SockaddrInit6(&SockAddr6, AF_INET6); + pSockAddr = (struct sockaddr *)&SockAddr6; + iSockAddrLen = sizeof(SockAddr6); + } + + #if !defined(DIRTYCODE_XBOXONE) + /* execute an explicit bind - this allows us to specify a non-zero local address + or port (see SocketBind()). a SOCKERR_INVALID result here means the socket has + already been bound, so we ignore that particular error */ + if (((iResult = SocketBind(pSocket, pSockAddr, iSockAddrLen)) < 0) && (iResult != SOCKERR_INVALID)) + { + pSocket->iLastError = iResult; + return(pSocket->iLastError); + } + #endif + + // translate to IPv6 if required + if (pSocket->iFamily == AF_INET6) + { + pName = SocketAddrMapTranslate(&_Socket_pState->AddrMap, pSockAddr, pName, &iNameLen); + } + + // execute the connect + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connecting to %A\n", pName)); + iResult = _XlatError(connect(pSocket->uSocket, pName, iNameLen)); + + // notify read thread that socket is ready to be read from + if ((iResult == SOCKERR_NONE) && pSocket->bAsyncRecv) + { + WSASetEvent(_Socket_pState->hEvent); + } + + // return result to caller + pSocket->iLastError = iResult; + return(pSocket->iLastError); +} + +/*F*************************************************************************************/ +/*! + \Function SocketListen + + \Description + Start listening for an incoming connection on the socket. The socket must already + be bound and a stream oriented connection must be in use. + + \Input *pSocket - socket reference to bound socket (see SocketBind()) + \Input iBackLog - number of pending connections allowed + + \Output + int32_t - standard network error code (SOCKERR_xxx) + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketListen(SocketT *pSocket, int32_t iBackLog) +{ + // do the listen + pSocket->iLastError = _XlatError(listen(pSocket->uSocket, iBackLog)); + return(pSocket->iLastError); +} + +/*F*************************************************************************************/ +/*! + \Function SocketAccept + + \Description + Accept an incoming connection attempt on a socket. + + \Input *pSocket - socket reference to socket in listening state (see SocketListen()) + \Input *pAddr - pointer to storage for address of the connecting entity, or NULL + \Input *pAddrLen - pointer to storage for length of address, or NULL + + \Output + SocketT * - the accepted socket, or NULL if not available + + \Notes + The integer pointed to by addrlen should on input contain the number of characters + in the buffer addr. On exit it will contain the number of characters in the + output address. + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +SocketT *SocketAccept(SocketT *pSocket, struct sockaddr *pAddr, int32_t *pAddrLen) +{ + SocketT *pOpen = NULL; + SOCKET iIncoming; + + pSocket->iLastError = SOCKERR_INVALID; + + // see if already connected + if (pSocket->uSocket == INVALID_SOCKET) + { + return(NULL); + } + + // make sure turn parm is valid + if ((pAddr != NULL) && (*pAddrLen < sizeof(struct sockaddr))) + { + return(NULL); + } + + #if !defined(DIRTYCODE_XBOXONE) + // perform inet accept + if (pSocket->iFamily == AF_INET) + { + iIncoming = accept(pSocket->uSocket, pAddr, pAddrLen); + if (iIncoming != INVALID_SOCKET) + { + pOpen = _SocketOpen(iIncoming, pSocket->iFamily, pSocket->iType, pSocket->iProto); + pSocket->iLastError = SOCKERR_NONE; + } + else + { + // use a negative error code to force _XlatError to internally call WSAGetLastError() and translate the obtained result + pSocket->iLastError = _XlatError(-99); + + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + NetPrintf(("dirtynetwin: accept() failed err=%d\n", WSAGetLastError())); + } + } + } + #endif + + // perform inet6 accept + if (pSocket->iFamily == AF_INET6) + { + struct sockaddr_in6 SockAddr6; + int32_t iAddrLen; + + SockaddrInit6(&SockAddr6, AF_INET6); + iAddrLen = sizeof(SockAddr6); + iIncoming = accept(pSocket->uSocket, (struct sockaddr *)&SockAddr6, &iAddrLen); + if (iIncoming != INVALID_SOCKET) + { + pOpen = _SocketOpen(iIncoming, pSocket->iFamily, pSocket->iType, pSocket->iProto); + pSocket->iLastError = SOCKERR_NONE; + // translate ipv6 to ipv4 virtual address + SocketAddrMapAddress(&_Socket_pState->AddrMap, (struct sockaddr *)&SockAddr6, sizeof(SockAddr6)); + + // save translated connecting info for caller + SockaddrInit(pAddr, AF_INET); + SocketAddrMapTranslate(&_Socket_pState->AddrMap, pAddr, (struct sockaddr *) &SockAddr6, pAddrLen); + } + else + { + // use a negative error code to force _XlatError to internally call WSAGetLastError() and translate the obtained result + pSocket->iLastError = _XlatError(-99); + + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + NetPrintf(("dirtynetwin: accept() failed err=%d\n", WSAGetLastError())); + } + } + } + + // return the socket + return(pOpen); +} + +/*F*************************************************************************************/ +/*! + \Function SocketSendto + + \Description + Send data to a remote host. The destination address is supplied along with + the data. Should only be used with datagram sockets as stream sockets always + send to the connected peer. + + \Input *pSocket - socket reference + \Input *pBuf - the data to be sent + \Input iLen - size of data + \Input iFlags - unused + \Input *pTo - the address to send to (NULL=use connection address) + \Input iToLen - length of address + + \Output + int32_t - standard network error code (SOCKERR_xxx) + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketSendto(SocketT *pSocket, const char *pBuf, int32_t iLen, int32_t iFlags, const struct sockaddr *pTo, int32_t iToLen) +{ + SocketStateT *pState = _Socket_pState; + int32_t iResult; + + if (pSocket->bSendCbs) + { + // if installed, give socket callback right of first refusal + if ((iResult = SocketSendCallbackInvoke(&pState->aSendCbEntries[0], pSocket, pSocket->iType, pBuf, iLen, pTo)) > 0) + { + return(iResult); + } + } + + // make sure socket ref is valid + if (pSocket->uSocket == INVALID_SOCKET) + { + #if DIRTYCODE_LOGGING + uint32_t uAddr = 0, uPort = 0; + if (pTo) + { + uAddr = SockaddrInGetAddr(pTo); + uPort = SockaddrInGetPort(pTo); + } + NetPrintf(("dirtynetwin: attempting to send to %a:%d on invalid socket\n", uAddr, uPort)); + #endif + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + + // handle optional data rate throttling + if ((iLen = SocketRateThrottle(&pSocket->SendRate, pSocket->iType, iLen, "send")) == 0) + { + return(0); + } + + // use appropriate version + if (pTo == NULL) + { + iResult = send(pSocket->uSocket, pBuf, iLen, 0); + pTo = &pSocket->RemoteAddr; + } + else + { + struct sockaddr_in6 SockAddr6; + if (pSocket->iFamily == AF_INET6) + { + SockaddrInit6(&SockAddr6, AF_INET6); + iToLen = sizeof(SockAddr6); + SocketAddrMapTranslate(&pState->AddrMap, (struct sockaddr *)&SockAddr6, pTo, &iToLen); + pTo = (struct sockaddr *)&SockAddr6; + } + iResult = sendto(pSocket->uSocket, pBuf, iLen, 0, pTo, iToLen); + } + + // update data rate estimation + SocketRateUpdate(&pSocket->SendRate, iResult, "send"); + + // return bytes sent + pSocket->iLastError = _XlatError(iResult); + return(pSocket->iLastError); +} + +/*F*************************************************************************************/ +/*! + \Function SocketRecvfrom + + \Description + Receive data from a remote host. If socket is a connected stream, then data can + only come from that source. A datagram socket can receive from any remote host. + + \Input *pSocket - socket reference + \Input *pBuf - buffer to receive data + \Input iLen - length of recv buffer + \Input iFlags - unused + \Input *pFrom - address data was received from (NULL=ignore) + \Input *pFromLen - length of address + + \Output + int32_t - positive=data bytes received, else standard error code + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketRecvfrom(SocketT *pSocket, char *pBuf, int32_t iLen, int32_t iFlags, struct sockaddr *pFrom, int32_t *pFromLen) +{ + int32_t iRecv = 0, iRecvErr = 0; + + // handle rate throttling, if enabled + if ((iLen = SocketRateThrottle(&pSocket->RecvRate, pSocket->iType, iLen, "recv")) == 0) + { + return(0); + } + + // handle if the socket was killed + if (pSocket->pKill != NULL) + { + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + + // sockets marked for async recv had actual receive operation take place in the thread + if (pSocket->bAsyncRecv) + { + uint32_t bSocketPacketQueueFull; + + // acquire socket receive critical section + NetCritEnter(&pSocket->RecvCrit); + + // remember if packet queue was full prior to calling _SocketRecvfromPacketQueue() + bSocketPacketQueueFull = SocketPacketQueueStatus(pSocket->pRecvQueue, 'pful'); + + /* given the socket could be either a TCP or UDP socket we handle the no data condition the same. + this is due to the below, when we do error conversion we override the system error with EWOULDBLOCK because + with TCP zero would be mean closed. if we are doing direct recv calls then the translation will + convert based on the errno returned after the call */ + if ((iRecv = _SocketRecvfromPacketQueue(pSocket, pBuf, iLen, pFrom, pFromLen)) == 0) + { + iRecv = -1; + iRecvErr = WSAEWOULDBLOCK; + } + + // when data is obtained from the packet queue, we lose visibility on system socket errors + pSocket->iLastError = SOCKERR_NONE; + + // release socket receive critical section + NetCritLeave(&pSocket->RecvCrit); + + /* If packet queue had reached "full" state, then _SocketRecvThread had not been + adding that socket to the event list anymore. Consequently, if _SocketRecvThread + is currently blocked on WSAWaitForMultipleEvents(), it may not wake up if there is + inbound traffic on this socket. To work around this, we wake it up explicitly here. */ + if (bSocketPacketQueueFull) + { + WSASetEvent(_Socket_pState->hEvent); + } + } + else + { + iRecv = _SocketRecvfrom(pSocket, pBuf, iLen, pFrom, pFromLen, &iRecvErr); + } + + // do error conversion + iRecv = (iRecv == 0) ? SOCKERR_CLOSED : _XlatError0(iRecv, iRecvErr); + + // update data rate estimation + SocketRateUpdate(&pSocket->RecvRate, pSocket->iLastError, "recv"); + + // return the error code + pSocket->iLastError = iRecv; + return(pSocket->iLastError); +} + +/*F*************************************************************************************/ +/*! + \Function SocketInfo + + \Description + Return information about an existing socket. + + \Input *pSocket - socket reference + \Input iInfo - selector for desired information + \Input iData - selector specific + \Input *pBuf - [out] return buffer + \Input iLen - buffer length + + \Output + int32_t - size of returned data or error code (negative value) + + \Notes + iInfo can be one of the following: + + \verbatim + 'addr' - return local address + 'conn' - who we are connecting to + 'bind' - return bind data (if pSocket == NULL, get socket bound to given port) + 'bndu' - return bind data (only with pSocket=NULL, get SOCK_DGRAM socket bound to given port) + '?ip6' - return TRUE if ipv4 address specified in iData is virtual, and fill in pBuf with ipv6 address if not NULL + 'ladr' - get local address previously specified by user for subsequent SocketBind() operations (pc only) + 'maxp' - return configured max packet size + 'maxr' - return configured max recv rate (bytes/sec; zero=uncapped) + 'maxs' - return configured max send rate (bytes/sec; zero=uncapped) + 'pdrp' - return socket packet queue number of packets dropped + 'peer' - peer info (only valid if connected) + 'pmax' - return socket packet queue max depth + 'pnum' - return socket packet queue current depth + 'ratr' - return current recv rate estimation (bytes/sec) + 'rats' - return current send rate estimation (bytes/sec) + 'sdcf' - get installed send callback function pointer (iData specifies index in array) + 'sdcu' - get installed send callback userdata pointer (iData specifies index in array) + 'serr' - last socket error + 'psiz' - return socket packet queue max size + 'sock' - return windows socket associated with the specified DirtySock socket + 'spam' - return debug level for debug output + 'stat' - TRUE if connected, else FALSE + 'virt' - TRUE if socket is virtual, else FALSE + \endverbatim + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketInfo(SocketT *pSocket, int32_t iInfo, int32_t iData, void *pBuf, int32_t iLen) +{ + SocketStateT *pState = _Socket_pState; + + // always zero results by default + #if defined(DIRTYCODE_XBOXONE) + if (pBuf != NULL) + #else + if ((pBuf != NULL) && (iInfo != 'ladr')) + #endif + { + ds_memclr(pBuf, iLen); + } + + // handle global socket options + if (pSocket == NULL) + { + return(_SocketInfoGlobal(iInfo, iData, pBuf, iLen)); + } + + // return local bind data + if (iInfo == 'bind') + { + int32_t iResult = -1; + if (pSocket->bVirtual) + { + SockaddrInit((struct sockaddr *)pBuf, AF_INET); + SockaddrInSetPort((struct sockaddr *)pBuf, pSocket->uVirtualPort); + iResult = 0; + } + else if (pSocket->uSocket != INVALID_SOCKET) + { + struct sockaddr_in6 SockAddr6; + iLen = sizeof(SockAddr6); + if ((iResult = getsockname(pSocket->uSocket, (struct sockaddr *)&SockAddr6, &iLen)) == 0) + { + SockaddrInit((struct sockaddr *)pBuf, AF_INET); + SockaddrInSetPort((struct sockaddr *)pBuf, SocketHtons(SockAddr6.sin6_port)); + SockaddrInSetAddr((struct sockaddr *)pBuf, SocketAddrMapAddress(&pState->AddrMap, (struct sockaddr *)&SockAddr6, sizeof(SockAddr6))); + } + iResult = _XlatError(iResult); + } + return(iResult); + } + + // return configured max recv rate + if (iInfo == 'maxr') + { + return(pSocket->RecvRate.uMaxRate); + } + + // return configured max send rate + if (iInfo == 'maxs') + { + return(pSocket->SendRate.uMaxRate); + } + + // return socket protocol + if (iInfo == 'prot') + { + return(pSocket->iProto); + } + + // return whether the socket is virtual or not + if (iInfo == 'virt') + { + return(pSocket->bVirtual); + } + + /* + make sure the socket is alive + ** AFTER THIS POINT WE ENSURE THE SOCKET DESCRIPTOR AND PACKET QUEUE ARE VALID ** + */ + if (pSocket->pKill != NULL) + { + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + + // return peer info (only valid if connected) + if ((iInfo == 'conn') || (iInfo == 'peer')) + { + getpeername(pSocket->uSocket, (struct sockaddr *)pBuf, &iLen); + //$$TODO this needs to be IPv6, translate to virtual address for output + return(0); + } + // get packet queue info + if ((iInfo == 'pdrp') || (iInfo == 'pmax') || (iInfo == 'psiz')) + { + int32_t iResult; + // acquire socket receive critical section + NetCritEnter(&pSocket->RecvCrit); + // get packet queue status + iResult = SocketPacketQueueStatus(pSocket->pRecvQueue, iInfo); + // release socket receive critical section + NetCritLeave(&pSocket->RecvCrit); + return(iResult); + } + + // return current recv rate estimation + if (iInfo == 'ratr') + { + return(pSocket->RecvRate.uCurRate); + } + + // return current send rate estimation + if (iInfo == 'rats') + { + return(pSocket->SendRate.uCurRate); + } + + // return last socket error + if (iInfo == 'serr') + { + return(pSocket->iLastError); + } + + // return windows socket identifier + if (iInfo == 'sock') + { + if (pBuf != NULL) + { + if (iLen == (int32_t)sizeof(pSocket->uSocket)) + { + ds_memcpy(pBuf, &pSocket->uSocket, sizeof(pSocket->uSocket)); + } + } + return((int32_t)pSocket->uSocket); + } + + // return socket status + if (iInfo == 'stat') + { + fd_set FdRead, FdWrite, FdExcept; + struct timeval TimeVal; + + // if not a connected socket, return TRUE + if (pSocket->iType != SOCK_STREAM) + { + return(1); + } + + // if not connected, use select to determine connect + if (pSocket->iOpened == 0) + { + // setup write/exception lists so we can select against the socket + FD_ZERO(&FdWrite); + FD_ZERO(&FdExcept); + FD_SET(pSocket->uSocket, &FdWrite); + FD_SET(pSocket->uSocket, &FdExcept); + TimeVal.tv_sec = TimeVal.tv_usec = 0; + if (select(pSocket->uSocket + 1, NULL, &FdWrite, &FdExcept, &TimeVal) > 0) + { + // if we got an exception, that means connect failed + if (FdExcept.fd_count > 0) + { + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection failed\n")); + pSocket->iOpened = -1; + } + // if socket is writable, that means connect succeeded + else if (FdWrite.fd_count > 0) + { + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection open\n")); + pSocket->iOpened = 1; + } + } + } + + /* if previously connected, make sure connect still valid. we only do this when not doing async receive for two + reasons + 1. there is a race condition between the poll waking up and querying the bytes available. if the bytes are + read on the receive thread between the poll and ioctl then it would think the socket is closed because the + socket has already been drained + 2. our reasoning behind using the async receive thread could be the cost of recv, plus other calls may be + expensive as well. the async receive thread will already set the correct state on the socket thus we can + skip the query and return iOpened back to the user */ + if (!pSocket->bAsyncRecv && (pSocket->iOpened > 0)) + { + FD_ZERO(&FdRead); + FD_ZERO(&FdExcept); + FD_SET(pSocket->uSocket, &FdRead); + FD_SET(pSocket->uSocket, &FdExcept); + TimeVal.tv_sec = TimeVal.tv_usec = 0; + if (select(pSocket->uSocket + 1, &FdRead, NULL, &FdExcept, &TimeVal) > 0) + { + // if we got an exception, that means connect failed (usually closed by remote peer) + if (FdExcept.fd_count > 0) + { + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection failure\n")); + pSocket->iOpened = -1; + } + else if (FdRead.fd_count > 0) + { + u_long uAvailBytes = 1; // u_long is required by ioctlsocket + // if socket is readable but there's no data available, connect was closed + // uAvailBytes might be less than actual bytes, so it can only be used for zero-test + if ((ioctlsocket(pSocket->uSocket, FIONREAD, &uAvailBytes) != 0) || (uAvailBytes == 0)) + { + pSocket->iLastError = SOCKERR_CLOSED; + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection closed (wsaerr=%d)\n", (uAvailBytes==0) ? WSAECONNRESET : WSAGetLastError())); + pSocket->iOpened = -1; + } + } + } + } + /* if we still have packets in the queue, tell the caller that we are still open. this makes sure they read the + complete stream of data */ + else if (pSocket->bAsyncRecv && (pSocket->iOpened < 0) && (SocketInfo(pSocket, 'pnum', 0, NULL, 0) != 0)) + { + return(1); + } + + // return connect status + return(pSocket->iOpened); + } + + return(-1); +} + +/*F*************************************************************************************/ +/*! + \Function SocketCallback + + \Description + Register a callback routine for notification of socket events. Also includes + timeout support. + + \Input *pSocket - socket reference + \Input iMask - valid callback events (CALLB_NONE, CALLB_SEND, CALLB_RECV) + \Input iIdle - if nonzero, specifies the number of ticks between idle calls + \Input *pRef - user data to be passed to proc + \Input *pProc - user callback + + \Output + int32_t - zero + + \Notes + A callback will reset the idle timer, so when specifying a callback and an + idle processing time, the idle processing time represents the maximum elapsed + time between calls. + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +#pragma optimize("", off) // make sure this block of code is not reordered +int32_t SocketCallback(SocketT *pSocket, int32_t iMask, int32_t iIdle, void *pRef, int32_t (*pProc)(SocketT *pSocket, int32_t iFlags, void *pRef)) +{ + pSocket->uCallIdle = iIdle; + pSocket->uCallMask = iMask; + pSocket->pCallRef = pRef; + pSocket->pCallback = pProc; + return(0); +} +#pragma optimize("", on) + +/*F*************************************************************************************/ +/*! + \Function SocketControl + + \Description + Process a control message (type specific operation) + + \Input *pSocket - socket to control, or NULL for module-level option + \Input iOption - the option to pass + \Input iData1 - message specific parm + \Input *pData2 - message specific parm + \Input *pData3 - message specific parm + + \Output + int32_t - message specific result (-1=unsupported message, -2=no such module) + + \Notes + iOption can be one of the following: + + \verbatim + 'afam' - set internet address family to use for socket operations (defaults to AF_INET6, AF_INET is also supported)) + 'arcv' - set async receive enable/disable (default enabled for DGRAM/RAW, disabled for TCP) + 'conn' - handle connect message + 'disc' - handle disconnect message + '+ip6' - add an IPv6 address into the mapping table and return a virtual IPv4 address to reference it + '-ip6' - del an IPv6 address from the mapping table + '~ip6' - remap an existing IPv6 address in the mapping table + 'keep' - set TCP keep-alive settings on PC (iData1=enable/disable, iData2=keep-alive time, iData3=keep-alive interval) + 'ladr' - set local address for subsequent SocketBind() operations (pc only) + 'maxp' - set max udp packet size + 'maxr' - set max recv rate (bytes/sec; zero=uncapped) + 'maxs' - set max send rate (bytes/sec; zero=uncapped) + 'nbio' - set nonblocking/blocking mode (TCP only, iData1=TRUE (nonblocking) or FALSE (blocking)) + 'ndly' - set TCP_NODELAY state for given stream socket (iData1=zero or one) + 'pdev' - set simulated packet deviation + 'plat' - set simulated packet latency + 'plos' - set simulated packet loss + 'poll' - execute blocking wait on given socket list (pData2) or all sockets (pData2=NULL), 64 max sockets in milliseconds + 'poln' - execute blocking wait on given socket list (pData2) or all sockets (pData2=NULL), 64 max sockets in microseconds + 'pque' - set socket packet queue depth + 'push' - push data into given socket (iData1=size, pData2=data ptr, pData3=sockaddr ptr) + 'rbuf' - set socket recv buffer size + 'sbuf' - set socket send buffer size + 'scbk' - enable/disable "send callbacks usage" on specified socket (defaults to enable) + 'sdcb' - set/unset send callback (iData1=TRUE for set - FALSE for unset, pData2=callback, pData3=callref) + 'soli' - set SO_LINGER On the specified socket, iData1 is timeout in seconds + 'spam' - set debug level for debug output + 'vadd' - add a port to virtual port list + 'vdel' - del a port from virtual port list + \endverbatim + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketControl(SocketT *pSocket, int32_t iOption, int32_t iData1, void *pData2, void *pData3) +{ + SocketStateT *pState = _Socket_pState; + int32_t iResult; + + // handle global control + if (pSocket == NULL) + { + return(_SocketControlGlobal(iOption, iData1, pData2, pData3)); + } + + // set async recv enable + if (iOption == 'arcv') + { + // set socket async recv flag + pSocket->bAsyncRecv = iData1 ? TRUE : FALSE; + // wake up recvthread to update socket polling + WSASetEvent(pState->hEvent); + return(0); + } + // set max recv rate + if (iOption == 'maxr') + { + NetPrintf(("dirtynetwin: setting max recv rate to %d bytes/sec\n", iData1)); + pSocket->RecvRate.uMaxRate = iData1; + return(0); + } + // set max send rate + if (iOption == 'maxs') + { + NetPrintf(("dirtynetwin: setting max send rate to %d bytes/sec\n", iData1)); + pSocket->SendRate.uMaxRate = iData1; + return(0); + } + // enable/disable "send callbacks usage" on specified socket (defaults to enable) + if (iOption == 'scbk') + { + if (pSocket->bSendCbs != (iData1?TRUE:FALSE)) + { + NetPrintf(("dirtynetwin: send callbacks usage changed from %s to %s for socket ref %p\n", (pSocket->bSendCbs?"ON":"OFF"), (iData1?"ON":"OFF"), pSocket)); + pSocket->bSendCbs = (iData1?TRUE:FALSE); + } + return(0); + } + // set debug level + if (iOption == 'spam') + { + // per-socket debug level + pSocket->iVerbose = iData1; + return(0); + } + + /* + make sure the socket is alive + ** AFTER THIS POINT WE ENSURE THE SOCKET DESCRIPTOR AND PACKET QUEUE ARE VALID ** + */ + if (pSocket->pKill != NULL) + { + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + + #if !defined(DIRTYCODE_XBOXONE) + // configure TCP keep-alive + if (iOption == 'keep') + { + struct tcp_keepalive TcpKeepAlive; + DWORD dwBytesReturned; + + if (pSocket->iType != SOCK_STREAM) + { + NetPrintf(("dirtynetwin: [%p] 'keep' selector can only be used on a SOCK_STREAM socket\n", pSocket)); + return(-1); + } + if (pSocket->uSocket == INVALID_SOCKET) + { + NetPrintf(("dirtynetwin: [%p] 'keep' selector used with an invalid socket\n", pSocket)); + return(-1); + } + + // initialize tcpkeep alive structure + ds_memclr(&TcpKeepAlive, sizeof(TcpKeepAlive)); + + TcpKeepAlive.onoff = (uint8_t)iData1; // on/off + if (TcpKeepAlive.onoff) + { + TcpKeepAlive.keepalivetime = *(uint32_t*)pData2; // timeout in ms + TcpKeepAlive.keepaliveinterval = *(uint32_t*)pData3; // interval in ms + } + else + { + TcpKeepAlive.keepalivetime = 0; // timeout in ms + TcpKeepAlive.keepaliveinterval = 0; // interval in ms + } + + if ((iResult = WSAIoctl(pSocket->uSocket, SIO_KEEPALIVE_VALS, (void *)&TcpKeepAlive, sizeof(TcpKeepAlive), NULL, 0, &dwBytesReturned, NULL, NULL)) == 0) + { + pSocket->iLastError = SOCKERR_NONE; + NetPrintfVerbose((pSocket->iVerbose, 1, "dirtynetwin: [%p] successfully %s the TCP keep-alive (timeout=%dms, interval=%dms)\n", pSocket, + iData1?"enabled":"disabled", TcpKeepAlive.keepalivetime, TcpKeepAlive.keepaliveinterval)); + } + else + { + pSocket->iLastError = _XlatError(iResult); + NetPrintf(("dirtynetwin: [%p] failed to %s TCP keep-alive for low-level socket (err = %d)\n", pSocket, + iData1?"enable":"disable", WSAGetLastError())); + } + + return(pSocket->iLastError); + } + #endif + // if a stream socket, set nonblocking/blocking mode + if ((iOption == 'nbio') && (pSocket->iType == SOCK_STREAM)) + { + uint32_t uNbio = (uint32_t)iData1; + iResult = ioctlsocket(pSocket->uSocket, FIONBIO, (u_long *)&uNbio); + pSocket->iLastError = _XlatError(iResult); + NetPrintf(("dirtynetwin: setting socket 0x%p to %s mode %s (LastError=%d).\n", pSocket, iData1 ? "nonblocking" : "blocking", iResult ? "failed" : "succeeded", pSocket->iLastError)); + return(pSocket->iLastError); + } + // if a stream socket, set TCP_NODELAY state + if ((iOption == 'ndly') && (pSocket->iType == SOCK_STREAM)) + { + iResult = setsockopt(pSocket->uSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&iData1, sizeof(iData1)); + pSocket->iLastError = _XlatError(iResult); + return(pSocket->iLastError); + } + // set simulated packet loss or packet latency + if ((iOption == 'pdev') || (iOption == 'plat') || (iOption == 'plos')) + { + // acquire socket receive critical section + NetCritEnter(&pSocket->RecvCrit); + // forward selector to packet queue + iResult = SocketPacketQueueControl(pSocket->pRecvQueue, iOption, iData1); + // release socket receive critical section + NetCritLeave(&pSocket->RecvCrit); + return(iResult); + } + + // change packet queue size + if (iOption == 'pque') + { + if (pSocket->bAsyncRecv) + { + pSocket->iPacketQueueResizePending = iData1; + } + else + { + // acquire socket receive critical section + NetCritEnter(&pSocket->RecvCrit); + // resize the queue + pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, iData1, pState->iMemGroup, pState->pMemGroupUserData); + // release socket receive critical section + NetCritLeave(&pSocket->RecvCrit); + } + // return success + return(0); + } + + // push data into receive buffer + if (iOption == 'push') + { + // acquire socket critical section + NetCritEnter(&pSocket->RecvCrit); + + // don't allow data that is too large (for the buffer) to be pushed + if (iData1 > SOCKET_MAXUDPRECV) + { + NetPrintf(("dirtynetwin: request to push %d bytes of data discarded (max=%d)\n", iData1, SOCKET_MAXUDPRECV)); + NetCritLeave(&pSocket->RecvCrit); + return(-1); + } + + // add packet to queue + SocketPacketQueueAdd(pSocket->pRecvQueue, (uint8_t *)pData2, iData1, (struct sockaddr *)pData3); + + // release socket critical section + NetCritLeave(&pSocket->RecvCrit); + + // see if we should issue callback + if ((pSocket->pCallback != NULL) && (pSocket->uCallMask & CALLB_RECV)) + { + pSocket->pCallback(pSocket, 0, pSocket->pCallRef); + } + return(0); + } + // set REUSEADDR + if (iOption == 'radr') + { + iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&iData1, sizeof(iData1)); + pSocket->iLastError = _XlatError(iResult); + return(pSocket->iLastError); + } + // set socket receive buffer size + if ((iOption == 'rbuf') || (iOption == 'sbuf')) + { + int32_t iOldSize, iNewSize, iOptLen=4; + int32_t iSockOpt = (iOption == 'rbuf') ? SO_RCVBUF : SO_SNDBUF; + + // get current buffer size + getsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (char *)&iOldSize, &iOptLen); + + // set new size + iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (const char *)&iData1, sizeof(iData1)); + pSocket->iLastError = _XlatError(iResult); + + // get new size + getsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (char *)&iNewSize, &iOptLen); + NetPrintf(("dirtynetwin: setsockopt(%s) changed buffer size from %d to %d\n", (iOption == 'rbuf') ? "SO_RCVBUF" : "SO_SNDBUF", + iOldSize, iNewSize)); + + return(pSocket->iLastError); + } + // set SO_LINGER + if (iOption == 'soli') + { + struct linger lingerOptions; + lingerOptions.l_onoff = TRUE; + lingerOptions.l_linger = iData1; + iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, SO_LINGER, (const char *)&lingerOptions, sizeof(lingerOptions)); + pSocket->iLastError = _XlatError(iResult); + return(pSocket->iLastError); + } + // unhandled + return(-1); +} + +/*F*************************************************************************************/ +/*! + \Function SocketGetLocalAddr + + \Description + Returns the "external" local address (ie, the address as a machine "out on + the Internet" would see as the local machine's address). + + \Output + uint32_t - local address + + \Version 07/28/2003 (jbrookes) +*/ +/************************************************************************************F*/ +uint32_t SocketGetLocalAddr(void) +{ + #if defined(DIRTYCODE_XBOXONE) + SocketStateT *pState = _Socket_pState; + struct sockaddr InetAddr, HostAddr; + + if ((pState->uLocalAddr == 0) || (pState->uLocalAddr == 0x7f000001)) + { + // create a remote internet address + ds_memclr(&InetAddr, sizeof(InetAddr)); + InetAddr.sa_family = AF_INET; + SockaddrInSetPort(&InetAddr, 79); + SockaddrInSetAddr(&InetAddr, 0x9f990000); + + // ask socket to give us local address that can connect to it + ds_memclr(&HostAddr, sizeof(HostAddr)); + SocketHost(&HostAddr, sizeof(HostAddr), &InetAddr, sizeof(InetAddr)); + + pState->uLocalAddr = SockaddrInGetAddr(&HostAddr); + } + return(pState->uLocalAddr); + #else + struct sockaddr InetAddr, HostAddr; + + // create a remote internet address + ds_memclr(&InetAddr, sizeof(InetAddr)); + InetAddr.sa_family = AF_INET; + SockaddrInSetPort(&InetAddr, 79); + SockaddrInSetAddr(&InetAddr, 0x9f990000); + + // ask socket to give us local address that can connect to it + ds_memclr(&HostAddr, sizeof(HostAddr)); + SocketHost(&HostAddr, sizeof(HostAddr), &InetAddr, sizeof(InetAddr)); + + return(SockaddrInGetAddr(&HostAddr)); + #endif +} + +/*F*************************************************************************************/ +/*! + \Function _SocketLookupDone + + \Description + Callback to determine if gethostbyname is complete. + + \Input *pHost - pointer to host lookup record + + \Output + int32_t - zero=in progess, neg=done w/error, pos=done w/success + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +static int32_t _SocketLookupDone(HostentT *pHost) +{ + // return current status + return(pHost->done); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketLookupFree + + \Description + Release resources used by gethostbyname. + + \Input *pHost - pointer to host lookup record + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +static void _SocketLookupFree(HostentT *pHost) +{ + // release resource + pHost->refcount -= 1; +} + +/*F*************************************************************************************/ +/*! + \Function _SocketLookupThread + + \Description + Socket lookup thread + + \Input *pUserData - pointer to host lookup record + + \Output + int32_t - zero + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +static void _SocketLookupThread(void *pUserData) +{ + SocketStateT *pState = _Socket_pState; + HostentT *pHost = (HostentT *)pUserData; + struct addrinfo Hints, *pList = NULL; + int32_t iResult; + + // setup lookup hints + ds_memclr(&Hints, sizeof(Hints)); + Hints.ai_family = AF_UNSPEC; + Hints.ai_socktype = SOCK_STREAM; // set specific socktype to limit to one result per type + Hints.ai_protocol = IPPROTO_TCP; // set specific proto to limit to one result per type + Hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; + + // perform the blocking dns query + if ((iResult = getaddrinfo(pHost->name, NULL, &Hints, &pList)) == 0) + { + struct addrinfo *pAddrInfo; + + // first loop we look for IPv4 addresses (prefer IPv4 to IPv6) + for (pAddrInfo = pList; pAddrInfo != NULL; pAddrInfo = pAddrInfo->ai_next) + { + // verbose logging of address info if spam settings warrant + NetPrintfVerbose((pState->iVerbose, 1, "dirtynetwin: addr=%A\n", pAddrInfo->ai_addr)); + NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin: ai_flags=0x%08x\n", pAddrInfo->ai_flags)); + NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin: ai_family=%d\n", pAddrInfo->ai_family)); + NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin: ai_socktype=%d\n", pAddrInfo->ai_socktype)); + NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin: ai_protocol=%d\n", pAddrInfo->ai_protocol)); + NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin: name=%s\n", pAddrInfo->ai_canonname)); + + // extract first IPv4 address we come across + if ((pHost->addr == 0) && (pAddrInfo->ai_family == AF_INET)) + { + pHost->addr = SocketAddrMapAddress(&pState->AddrMap, pAddrInfo->ai_addr, (int32_t)pAddrInfo->ai_addrlen); + } + } + + // if we haven't found one yet, look for any address, and pick the first one we come across + for (pAddrInfo = pList; (pAddrInfo != NULL) && (pHost->addr == 0); pAddrInfo = pAddrInfo->ai_next) + { + pHost->addr = SocketAddrMapAddress(&pState->AddrMap, pAddrInfo->ai_addr, (int32_t)pAddrInfo->ai_addrlen); + } + + // print selected address + NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: %s=%a\n", pHost->name, pHost->addr)); + + // mark success + pHost->done = 1; + + // add hostname to cache + SocketHostnameCacheAdd(pState->pHostnameCache, pHost->name, pHost->addr, pState->iVerbose); + + // release memory + freeaddrinfo(pList); + } + else + { + // unsuccessful + NetPrintf(("dirtynetwin: getaddrinfo('%s', ...) failed err=%d\n", pHost->name, iResult)); + pHost->done = -1; + } + + #if !defined(DIRTYCODE_XBOXONE) + // note thread completion + pHost->thread = 1; + #endif + // release thread-allocated refcount on hostname resource + pHost->refcount -= 1; +} + +/*F*************************************************************************************/ +/*! + \Function SocketLookup + + \Description + Lookup a host by name and return the corresponding Internet address. Uses + a callback/polling system since the socket library does not allow blocking. + + \Input *pText - pointer to null terminated address string + \Input iTimeout - number of milliseconds to wait for completion + + \Output + HostentT * - hostent struct that includes callback vectors + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +HostentT *SocketLookup(const char *pText, int32_t iTimeout) +{ + SocketStateT *pState = _Socket_pState; + int32_t iAddr, iResult; + HostentT *pHost, *pHostRef; + DirtyThreadConfigT ThreadConfig; + + NetPrintf(("dirtynetwin: looking up address for host '%s'\n", pText)); + + // dont allow negative timeouts + if (iTimeout < 0) + { + return(NULL); + } + + // create new structure + pHost = DirtyMemAlloc(sizeof(*pHost), SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + ds_memclr(pHost, sizeof(*pHost)); + + // setup callbacks + pHost->Done = &_SocketLookupDone; + pHost->Free = &_SocketLookupFree; + // copy over the target address + ds_strnzcpy(pHost->name, pText, sizeof(pHost->name)); + + // check for refcounted lookup + if ((pHostRef = SocketHostnameAddRef(&pState->pHostList, pHost, TRUE)) != NULL) + { + DirtyMemFree(pHost, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + return(pHostRef); + } + + // check for dot notation, then check hostname cache + if (((iAddr = SocketInTextGetAddr(pText)) != 0) || ((iAddr = SocketHostnameCacheGet(pState->pHostnameCache, pText, pState->iVerbose)) != 0)) + { + // save the data + pHost->addr = iAddr; + pHost->done = 1; + // mark as "thread complete" so _SocketLookupFree will release memory + pHost->thread = 1; + // return completed record + return(pHost); + } + + /* add an extra refcount for the thread; this ensures the host structure survives until the thread + is done with it. this must be done before thread creation. */ + pHost->refcount += 1; + + // configure dns thread parameters + ds_memclr(&ThreadConfig, sizeof(ThreadConfig)); + ThreadConfig.pName = "SocketLookup"; + ThreadConfig.iAffinity = NetConnStatus('affn', 0, NULL, 0); + ThreadConfig.iVerbosity = pState->iVerbose-1; + + // create dns lookup thread + if ((iResult = DirtyThreadCreate(_SocketLookupThread, pHost, &ThreadConfig)) != 0) + { + NetPrintf(("dirtynetwin: unable to create socket lookup thread (err=%d)\n", iResult)); + pHost->done = -1; + // remove refcount we just added + pHost->refcount -= 1; + } + + // return the host reference + return(pHost); +} + +/*F*************************************************************************************/ +/*! + \Function SocketHost + + \Description + Return the host address that would be used in order to communicate with the given + destination address. + + \Input *pHost - local sockaddr struct + \Input iHostLen - length of structure (sizeof(host)) + \Input *pDest - remote sockaddr struct + \Input iDestLen - length of structure (sizeof(dest)) + + \Output + int32_t - zero=success, negative=error + + \Version 10/04/1999 (gschaefer) +*/ +/************************************************************************************F*/ +int32_t SocketHost(struct sockaddr *pHost, int32_t iHostLen, const struct sockaddr *pDest, int32_t iDestLen) +{ + SocketStateT *pState = _Socket_pState; + struct sockaddr SockAddr; + char strHostName[256]; + int32_t iErr; + SOCKET iSock; + + // must be same kind of addresses + if (iHostLen != iDestLen) + { + return(-1); + } + + // do family specific lookup + if (pDest->sa_family == AF_INET) + { + // make them match initially + ds_memcpy(pHost, pDest, iHostLen); + + #if !defined(DIRTYCODE_XBOXONE) + // respect adapter override + if (pState->uAdapterAddress != 0) + { + SockaddrInSetAddr(pHost, pState->uAdapterAddress); + return(0); + } + #endif + + // zero the address portion + pHost->sa_data[2] = pHost->sa_data[3] = pHost->sa_data[4] = pHost->sa_data[5] = 0; + + // create a temp socket (must be datagram) + iSock = socket(AF_INET, SOCK_DGRAM, 0); + if (iSock != INVALID_SOCKET) + { + // use routing check if winsock 2.0 + if (pState->iVersion >= 0x200) + { + // get interface that would be used for this dest + ds_memclr(&SockAddr, sizeof(SockAddr)); + if (WSAIoctl(iSock, SIO_ROUTING_INTERFACE_QUERY, (void *)pDest, iDestLen, &SockAddr, sizeof(SockAddr), (LPDWORD)&iHostLen, NULL, NULL) < 0) + { + iErr = WSAGetLastError(); + NetPrintf(("dirtynetwin: WSAIoctl(SIO_ROUTING_INTERFACE_QUERY) returned %d\n", iErr)); + } + else + { + // copy over the result + ds_memcpy(pHost->sa_data+2, SockAddr.sa_data+2, 4); + } + + // convert loopback to requested address + if (SockaddrInGetAddr(pHost) == INADDR_LOOPBACK) + { + ds_memcpy(pHost->sa_data+2, pDest->sa_data+2, 4); + } + } + + // if no result, try connecting to socket then reading + // (this only works on some non-microsoft stacks) + if (SockaddrInGetAddr(pHost) == 0) + { + // connect to remote address + if (connect(iSock, pDest, iDestLen) == 0) + { + // query the host address + if (getsockname(iSock, &SockAddr, &iHostLen) == 0) + { + // just copy over the address portion + ds_memcpy(pHost->sa_data+2, SockAddr.sa_data+2, 4); + } + } + } + + // $$TODO evaluate possibility of unifying - (keep PC version) + #if defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) + // if still no result, use classic gethosthame/gethostbyname + // (works for microsoft, not for non-microsoft stacks) + if (SockaddrInGetAddr(pHost) == 0) + { + struct hostent *pHostent; + + // get machine name + gethostname(strHostName, sizeof(strHostName)); + // lookup ip info + pHostent = gethostbyname(strHostName); + if (pHostent != NULL) + { + ds_memcpy(pHost->sa_data+2, pHostent->h_addr_list[0], 4); + } + } + #else + // if still no result, use classic gethosthame/getaddrinfo + // (works for microsoft, not for non-microsoft stacks) + if (SockaddrInGetAddr(pHost) == 0) + { + int32_t iResult; + struct addrinfo Hints, *pList = NULL; + + // get machine name + ds_memclr(&Hints, sizeof(Hints)); + Hints.ai_family = AF_INET; + Hints.ai_socktype = SOCK_STREAM; + Hints.ai_protocol = IPPROTO_TCP; + gethostname(strHostName, sizeof(strHostName)); + // lookup ip info + if ((iResult = getaddrinfo(strHostName, NULL, &Hints, &pList)) == 0) + { + ds_memcpy(pHost->sa_data+2, pList->ai_addr->sa_data+2, 4); + } + else + { + NetPrintf(("dirtynetwin: getaddrinfo('%s', ...) failed err=%d\n", strHostName, iResult)); + } + + // release memory + freeaddrinfo(pList); + } + #endif + + // close the socket + closesocket(iSock); + } + return(0); + } + + // unsupported family + ds_memclr(pHost, iHostLen); + return(-3); +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/pc/netconnwin.c b/src/thirdparty/dirtysdk/source/dirtysock/pc/netconnwin.c new file mode 100644 index 00000000..c25f0c56 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/pc/netconnwin.c @@ -0,0 +1,1257 @@ +/*H************************************************************************************************* + + \File netconnwin.c + + \Description + Provides network setup and teardown support. Does not actually create any + kind of network connections (see NetPlay). + + \Copyright + Copyright (c) 2001-2010 Electronic Arts Inc. + + \Version 03/12/2001 (gschaefer) First version + \Version 12/16/2001 (sbevan) Edited config file hack in NetConnDevStart + \Version 06/23/2009 (mclouatre) _NetConFindAdapter() changed to _NetConnFindMacAddress() + +*************************************************************************************************H*/ + +/*** Include files ********************************************************************************/ + +#define WIN32_LEAN_AND_MEAN 1 // avoid extra stuff + +#pragma warning(push,0) +#include +#pragma warning(pop) + +#include +#include // for GetAdaptersAddresses() +#include // for PIP_ADAPTER_ADDRESSES +#include // for strtol() + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtyvers.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtycert.h" +#include "DirtySDK/dirtylang.h" // for locality macros and definitions +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/proto/protoupnp.h" +#include "netconncommon.h" + +/*** Defines **************************************************************************************/ + +// LOCALE_SNAME: defined in WinNls.h (WINVER >= 0x0600). This block is just there for the case where the define is absent. +#ifndef LOCALE_SNAME +#define LOCALE_SNAME 0x0000005c // locale name (ie: en-us) +#endif + +//! GetGeoInfo returns XX for unknown country +#define NETCONNWIN_COUNTRY_UNKNOWN_STR ("XX") + +//! UPNP port +#define NETCONN_DEFAULT_UPNP_PORT (3659) + +/*** Macros ***************************************************************************************/ + +/*** Type Definitions *****************************************************************************/ + +//! private module state +typedef struct NetConnRefT +{ + NetConnCommonRefT Common; //!< cross-platform netconn data (must come first!) + + enum + { + ST_INIT, + ST_CONN, + ST_IDLE, + } eState; + + int32_t iPeerPort; //!< peer port to be opened by upnp; if zero, still find upnp router but don't open a port + ProtoUpnpRefT *pProtoUpnp; //!< protoupnp module state + int32_t iThreadCpuAffinity; //!< cpu affinity we use for our internal threads +} NetConnRefT; + +/*** Function Prototypes **************************************************************************/ + +/*** Variables ************************************************************************************/ + +//! mapping table to map Windows Language IDs to DirtySock encodings +/* + Language Identifier Constants and Strings: http://msdn.microsoft.com/en-us/library/dd318693(v=vs.85).aspx + +1. In order to reduce the size of the mapping table: + if locality(Primary Id, SUBLANG_NEUTRAL) == locality(Primary Id, SUBLANG_DEFAULT), + then only locality(Primary Id, SUBLANG_DEFAULT) is included by the table. + That means if we can't find locality(Primary Id, SUBLANG_NEUTRAL) in the table, + we should search the table again with langid(Primary Id, SUBLANG_DEFAULT). + Defined in WinNT.h: + #define SUBLANG_NEUTRAL 0x00 // language neutral + #define SUBLANG_DEFAULT 0x01 // user default + #define SUBLANG_ARABIC_SAUDI_ARABIA 0x01 // Arabic (Saudi Arabia) + // normally, specific sublangs (ex. SUBLANG_ARABIC_SAUDI_ARABIA) are used by the table instead of using SUBLANG_DEFAULT. + + Another reason why we don't define all locality(Primary Id, SUBLANG_NEUTRAL) is: + Normally Windows API returns langid(Primary Id, specific_sublang) instead of langid(Primary Id, SUBLANG_NEUTRAL). + +2. The reason why we use a mapping table is because Windows API GetLocaleInfo(LOCALE_SNAME) is only available under Vista (or later). + +3. The result of GetLocaleInfo(LOCALE_SNAME) will be used to verify if the mapping table is good by _NetConnVerifyAllLocales. + (debug version only, Vista / Windows7 only) + +4. NetConnStatus('locl', 0, NULL, 0) can be used to obtain locality for local system. + The return value of NetConnStatus('locl', 0, NULL, 0) is similar to the return value of "locl" on the XBOX 360, ex. 'enUS'. +*/ +static uint16_t _NetConn_WindowsLocaleMap[214][3] = +{ + { MAKELANGID(LANG_AFRIKAANS, SUBLANG_AFRIKAANS_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_AFRIKAANS, LOBBYAPI_COUNTRY_SOUTH_AFRICA }, + { MAKELANGID(LANG_ALBANIAN, SUBLANG_ALBANIAN_ALBANIA), LOBBYAPI_LANGUAGE_ALBANIAN, LOBBYAPI_COUNTRY_ALBANIA }, + { MAKELANGID(LANG_AMHARIC, SUBLANG_AMHARIC_ETHIOPIA), LOBBYAPI_LANGUAGE_AMHARIC, LOBBYAPI_COUNTRY_ETHIOPIA }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_ALGERIA), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_ALGERIA }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_BAHRAIN), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_BAHRAIN }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_EGYPT), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_EGYPT }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_IRAQ), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_IRAQ }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_JORDAN), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_JORDAN }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_KUWAIT), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_KUWAIT }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_LEBANON), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_LEBANON }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_LIBYA), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_LIBYAN_ARAB_JAMAHIRIYA }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_MOROCCO), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_MOROCCO }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_OMAN), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_OMAN }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_QATAR), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_QATAR }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_SAUDI_ARABIA), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_SAUDI_ARABIA }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_SYRIA), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_SYRIAN_ARAB_REPUBLIC }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_TUNISIA), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_TUNISIA }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_UAE), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_UNITED_ARAB_EMIRATES }, + { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_YEMEN), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_YEMEN }, + { MAKELANGID(LANG_ARMENIAN, SUBLANG_ARMENIAN_ARMENIA), LOBBYAPI_LANGUAGE_ARMENIAN, LOBBYAPI_COUNTRY_ARMENIA }, + { MAKELANGID(LANG_ASSAMESE, SUBLANG_ASSAMESE_INDIA), LOBBYAPI_LANGUAGE_ASSAMESE, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_AZERI, SUBLANG_AZERI_CYRILLIC), LOBBYAPI_LANGUAGE_AZERBAIJANI, LOBBYAPI_COUNTRY_AZERBAIJAN }, + { MAKELANGID(LANG_AZERI, SUBLANG_AZERI_LATIN), LOBBYAPI_LANGUAGE_AZERBAIJANI, LOBBYAPI_COUNTRY_AZERBAIJAN }, + { MAKELANGID(LANG_BASHKIR, SUBLANG_BASHKIR_RUSSIA), LOBBYAPI_LANGUAGE_BASHKIR, LOBBYAPI_COUNTRY_RUSSIAN_FEDERATION }, + { MAKELANGID(LANG_BASQUE, SUBLANG_BASQUE_BASQUE), LOBBYAPI_LANGUAGE_BASQUE, LOBBYAPI_COUNTRY_SPAIN }, + { MAKELANGID(LANG_BELARUSIAN, SUBLANG_BELARUSIAN_BELARUS), LOBBYAPI_LANGUAGE_BYELORUSSIAN, LOBBYAPI_COUNTRY_BELARUS }, + { MAKELANGID(LANG_BENGALI, SUBLANG_BENGALI_BANGLADESH), LOBBYAPI_LANGUAGE_BENGALI, LOBBYAPI_COUNTRY_BANGLADESH }, + { MAKELANGID(LANG_BENGALI, SUBLANG_BENGALI_INDIA), LOBBYAPI_LANGUAGE_BENGALI, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_BOSNIAN, SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC), LOBBYAPI_LANGUAGE_BOSNIAN, LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA }, + { MAKELANGID(LANG_BOSNIAN, SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_LATIN), LOBBYAPI_LANGUAGE_BOSNIAN, LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA }, + { MAKELANGID(LANG_BRETON, SUBLANG_BRETON_FRANCE), LOBBYAPI_LANGUAGE_BRETON, LOBBYAPI_COUNTRY_FRANCE }, + { MAKELANGID(LANG_BULGARIAN, SUBLANG_BULGARIAN_BULGARIA), LOBBYAPI_LANGUAGE_BULGARIAN, LOBBYAPI_COUNTRY_BULGARIA }, + { MAKELANGID(LANG_CATALAN, SUBLANG_CATALAN_CATALAN), LOBBYAPI_LANGUAGE_CATALAN, LOBBYAPI_COUNTRY_SPAIN }, + { MAKELANGID(LANG_CHINESE, SUBLANG_NEUTRAL), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_CHINA }, + { MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_HONGKONG), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_HONG_KONG }, + { MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_MACAU), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_MACAU }, + { MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SINGAPORE), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_SINGAPORE }, + { MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_CHINA }, + { MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_TAIWAN }, + { MAKELANGID(LANG_CORSICAN, SUBLANG_CORSICAN_FRANCE), LOBBYAPI_LANGUAGE_CORSICAN, LOBBYAPI_COUNTRY_FRANCE }, + { MAKELANGID(LANG_CROATIAN, SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN), LOBBYAPI_LANGUAGE_CROATIAN, LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA }, + { MAKELANGID(LANG_CROATIAN, SUBLANG_CROATIAN_CROATIA), LOBBYAPI_LANGUAGE_CROATIAN, LOBBYAPI_COUNTRY_CROATIA }, + { MAKELANGID(LANG_CZECH, SUBLANG_CZECH_CZECH_REPUBLIC), LOBBYAPI_LANGUAGE_CZECH, LOBBYAPI_COUNTRY_CZECH_REPUBLIC }, + { MAKELANGID(LANG_DANISH, SUBLANG_DANISH_DENMARK), LOBBYAPI_LANGUAGE_DANISH, LOBBYAPI_COUNTRY_DENMARK }, + { MAKELANGID(LANG_DIVEHI, SUBLANG_DIVEHI_MALDIVES), LOBBYAPI_LANGUAGE_DIVEHI, LOBBYAPI_COUNTRY_MALDIVES }, + { MAKELANGID(LANG_DUTCH, SUBLANG_DUTCH_BELGIAN), LOBBYAPI_LANGUAGE_DUTCH, LOBBYAPI_COUNTRY_BELGIUM }, + { MAKELANGID(LANG_DUTCH, SUBLANG_DUTCH), LOBBYAPI_LANGUAGE_DUTCH, LOBBYAPI_COUNTRY_NETHERLANDS }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_AUS), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_AUSTRALIA }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_BELIZE), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_BELIZE }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_CAN), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_CANADA }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_CARIBBEAN), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_UNKNOWN }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_INDIA), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_EIRE), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_IRELAND }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_JAMAICA), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_JAMAICA }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_MALAYSIA), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_MALAYSIA }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_NZ), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_NEW_ZEALAND }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_PHILIPPINES), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_PHILIPPINES }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_SINGAPORE), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_SINGAPORE }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_SOUTH_AFRICA }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_TRINIDAD), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_TRINIDAD_AND_TOBAGO }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_UNITED_KINGDOM }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_UNITED_STATES }, + { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_ZIMBABWE), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_ZIMBABWE }, + { MAKELANGID(LANG_ESTONIAN, SUBLANG_ESTONIAN_ESTONIA), LOBBYAPI_LANGUAGE_ESTONIAN, LOBBYAPI_COUNTRY_ESTONIA }, + { MAKELANGID(LANG_FAEROESE, SUBLANG_FAEROESE_FAROE_ISLANDS), LOBBYAPI_LANGUAGE_FAEROESE, LOBBYAPI_COUNTRY_FAEROE_ISLANDS }, + { MAKELANGID(LANG_FINNISH, SUBLANG_FINNISH_FINLAND), LOBBYAPI_LANGUAGE_FINNISH, LOBBYAPI_COUNTRY_FINLAND }, + { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_BELGIAN), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_BELGIUM }, + { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_CANADIAN), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_CANADA }, + { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_FRANCE }, + { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_LUXEMBOURG), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_LUXEMBOURG }, + { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_MONACO), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_MONACO }, + { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_SWISS), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_SWITZERLAND }, + { MAKELANGID(LANG_FRISIAN, SUBLANG_FRISIAN_NETHERLANDS), LOBBYAPI_LANGUAGE_FRISIAN, LOBBYAPI_COUNTRY_NETHERLANDS }, + { MAKELANGID(LANG_GALICIAN, SUBLANG_GALICIAN_GALICIAN), LOBBYAPI_LANGUAGE_GALICIAN, LOBBYAPI_COUNTRY_SPAIN }, + { MAKELANGID(LANG_GEORGIAN, SUBLANG_GEORGIAN_GEORGIA), LOBBYAPI_LANGUAGE_GEORGIAN, LOBBYAPI_COUNTRY_GEORGIA }, + { MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_AUSTRIAN), LOBBYAPI_LANGUAGE_GERMAN, LOBBYAPI_COUNTRY_AUSTRIA }, + { MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN), LOBBYAPI_LANGUAGE_GERMAN, LOBBYAPI_COUNTRY_GERMANY }, + { MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_LIECHTENSTEIN), LOBBYAPI_LANGUAGE_GERMAN, LOBBYAPI_COUNTRY_LIECHTENSTEIN }, + { MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_LUXEMBOURG), LOBBYAPI_LANGUAGE_GERMAN, LOBBYAPI_COUNTRY_LUXEMBOURG }, + { MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_SWISS), LOBBYAPI_LANGUAGE_GERMAN, LOBBYAPI_COUNTRY_SWITZERLAND }, + { MAKELANGID(LANG_GREEK, SUBLANG_GREEK_GREECE), LOBBYAPI_LANGUAGE_GREEK, LOBBYAPI_COUNTRY_GREECE }, + { MAKELANGID(LANG_GREENLANDIC, SUBLANG_GREENLANDIC_GREENLAND), LOBBYAPI_LANGUAGE_GREENLANDIC, LOBBYAPI_COUNTRY_GREENLAND }, + { MAKELANGID(LANG_GUJARATI, SUBLANG_GUJARATI_INDIA), LOBBYAPI_LANGUAGE_GUJARATI, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_HAUSA, SUBLANG_HAUSA_NIGERIA_LATIN), LOBBYAPI_LANGUAGE_HAUSA, LOBBYAPI_COUNTRY_NIGERIA }, + { MAKELANGID(LANG_HEBREW, SUBLANG_HEBREW_ISRAEL), LOBBYAPI_LANGUAGE_HEBREW, LOBBYAPI_COUNTRY_ISRAEL }, + { MAKELANGID(LANG_HINDI, SUBLANG_HINDI_INDIA), LOBBYAPI_LANGUAGE_HINDI, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_HUNGARIAN, SUBLANG_HUNGARIAN_HUNGARY), LOBBYAPI_LANGUAGE_HUNGARIAN, LOBBYAPI_COUNTRY_HUNGARY }, + { MAKELANGID(LANG_ICELANDIC, SUBLANG_ICELANDIC_ICELAND), LOBBYAPI_LANGUAGE_ICELANDIC, LOBBYAPI_COUNTRY_ICELAND }, + { MAKELANGID(LANG_IGBO, SUBLANG_IGBO_NIGERIA), LOBBYAPI_LANGUAGE_IGBO, LOBBYAPI_COUNTRY_NIGERIA }, + { MAKELANGID(LANG_INDONESIAN, SUBLANG_INDONESIAN_INDONESIA), LOBBYAPI_LANGUAGE_INDONESIAN, LOBBYAPI_COUNTRY_INDONESIA }, + { MAKELANGID(LANG_INUKTITUT, SUBLANG_INUKTITUT_CANADA_LATIN), LOBBYAPI_LANGUAGE_INUKTITUT, LOBBYAPI_COUNTRY_CANADA }, + { MAKELANGID(LANG_INUKTITUT, SUBLANG_INUKTITUT_CANADA), LOBBYAPI_LANGUAGE_INUKTITUT, LOBBYAPI_COUNTRY_CANADA }, + { MAKELANGID(LANG_IRISH, SUBLANG_NEUTRAL), LOBBYAPI_LANGUAGE_IRISH, LOBBYAPI_COUNTRY_IRELAND }, + { MAKELANGID(LANG_IRISH, SUBLANG_IRISH_IRELAND), LOBBYAPI_LANGUAGE_IRISH, LOBBYAPI_COUNTRY_IRELAND }, + { MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN), LOBBYAPI_LANGUAGE_ITALIAN, LOBBYAPI_COUNTRY_ITALY }, + { MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN_SWISS), LOBBYAPI_LANGUAGE_ITALIAN, LOBBYAPI_COUNTRY_SWITZERLAND }, + { MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN), LOBBYAPI_LANGUAGE_JAPANESE, LOBBYAPI_COUNTRY_JAPAN }, + { MAKELANGID(LANG_KANNADA, SUBLANG_KANNADA_INDIA), LOBBYAPI_LANGUAGE_KANNADA, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_KAZAK, SUBLANG_KAZAK_KAZAKHSTAN), LOBBYAPI_LANGUAGE_KAZAKH, LOBBYAPI_COUNTRY_KAZAKHSTAN }, + { MAKELANGID(LANG_KHMER, SUBLANG_KHMER_CAMBODIA), LOBBYAPI_LANGUAGE_CAMBODIAN, LOBBYAPI_COUNTRY_CAMBODIA }, + { MAKELANGID(LANG_KINYARWANDA, SUBLANG_KINYARWANDA_RWANDA), LOBBYAPI_LANGUAGE_KINYARWANDA, LOBBYAPI_COUNTRY_RWANDA }, + { MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN), LOBBYAPI_LANGUAGE_KOREAN, LOBBYAPI_COUNTRY_KOREA_REPUBLIC_OF }, + { MAKELANGID(LANG_KYRGYZ, SUBLANG_KYRGYZ_KYRGYZSTAN), LOBBYAPI_LANGUAGE_KIRGHIZ, LOBBYAPI_COUNTRY_KYRGYZSTAN }, + { MAKELANGID(LANG_LAO, SUBLANG_LAO_LAO), LOBBYAPI_LANGUAGE_LAOTHIAN, LOBBYAPI_COUNTRY_LAO_PEOPLES_DEMOCRATIC_REPUBLIC }, + { MAKELANGID(LANG_LATVIAN, SUBLANG_LATVIAN_LATVIA), LOBBYAPI_LANGUAGE_LATVIAN_LETTISH, LOBBYAPI_COUNTRY_LATVIA }, + { MAKELANGID(LANG_LITHUANIAN, SUBLANG_LITHUANIAN), LOBBYAPI_LANGUAGE_LITHUANIAN, LOBBYAPI_COUNTRY_LITHUANIA }, + { MAKELANGID(LANG_LUXEMBOURGISH, SUBLANG_LUXEMBOURGISH_LUXEMBOURG), LOBBYAPI_LANGUAGE_LUXEMBOURGISH, LOBBYAPI_COUNTRY_LUXEMBOURG }, + { MAKELANGID(LANG_MACEDONIAN, SUBLANG_MACEDONIAN_MACEDONIA), LOBBYAPI_LANGUAGE_MACEDONIAN, LOBBYAPI_COUNTRY_MACEDONIA_THE_FORMER_YUGOSLAV_REPUBLIC_OF }, + { MAKELANGID(LANG_MALAY, SUBLANG_MALAY_BRUNEI_DARUSSALAM), LOBBYAPI_LANGUAGE_MALAY, LOBBYAPI_COUNTRY_BRUNEI_DARUSSALAM }, + { MAKELANGID(LANG_MALAY, SUBLANG_MALAY_MALAYSIA), LOBBYAPI_LANGUAGE_MALAY, LOBBYAPI_COUNTRY_MALAYSIA }, + { MAKELANGID(LANG_MALAYALAM, SUBLANG_MALAYALAM_INDIA), LOBBYAPI_LANGUAGE_MALAYALAM, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_MALTESE, SUBLANG_MALTESE_MALTA), LOBBYAPI_LANGUAGE_MALTESE, LOBBYAPI_COUNTRY_MALTA }, + { MAKELANGID(LANG_MAORI, SUBLANG_MAORI_NEW_ZEALAND), LOBBYAPI_LANGUAGE_MAORI, LOBBYAPI_COUNTRY_NEW_ZEALAND }, + { MAKELANGID(LANG_MARATHI, SUBLANG_MARATHI_INDIA), LOBBYAPI_LANGUAGE_MARATHI, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_MONGOLIAN, SUBLANG_MONGOLIAN_CYRILLIC_MONGOLIA), LOBBYAPI_LANGUAGE_MONGOLIAN, LOBBYAPI_COUNTRY_MONGOLIA }, + { MAKELANGID(LANG_MONGOLIAN, SUBLANG_MONGOLIAN_PRC), LOBBYAPI_LANGUAGE_MONGOLIAN, LOBBYAPI_COUNTRY_CHINA }, + { MAKELANGID(LANG_NEPALI, SUBLANG_NEPALI_NEPAL), LOBBYAPI_LANGUAGE_NEPALI, LOBBYAPI_COUNTRY_NEPAL }, + { MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL), LOBBYAPI_LANGUAGE_NORWEGIAN_BOKMAL, LOBBYAPI_COUNTRY_NORWAY }, + { MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK), LOBBYAPI_LANGUAGE_NORWEGIAN_NYNORSK, LOBBYAPI_COUNTRY_NORWAY }, + { MAKELANGID(LANG_OCCITAN, SUBLANG_OCCITAN_FRANCE), LOBBYAPI_LANGUAGE_OCCITAN, LOBBYAPI_COUNTRY_FRANCE }, + { MAKELANGID(LANG_ORIYA, SUBLANG_ORIYA_INDIA), LOBBYAPI_LANGUAGE_ORIYA, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_PASHTO, SUBLANG_PASHTO_AFGHANISTAN), LOBBYAPI_LANGUAGE_PASHTO_PUSHTO, LOBBYAPI_COUNTRY_AFGHANISTAN }, + { MAKELANGID(LANG_PERSIAN, SUBLANG_PERSIAN_IRAN), LOBBYAPI_LANGUAGE_PERSIAN, LOBBYAPI_COUNTRY_IRAN }, + { MAKELANGID(LANG_POLISH, SUBLANG_POLISH_POLAND), LOBBYAPI_LANGUAGE_POLISH, LOBBYAPI_COUNTRY_POLAND }, + { MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE_BRAZILIAN), LOBBYAPI_LANGUAGE_PORTUGUESE, LOBBYAPI_COUNTRY_BRAZIL }, + { MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE), LOBBYAPI_LANGUAGE_PORTUGUESE, LOBBYAPI_COUNTRY_PORTUGAL }, + { MAKELANGID(LANG_PUNJABI, SUBLANG_PUNJABI_INDIA), LOBBYAPI_LANGUAGE_PUNJABI, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_QUECHUA, SUBLANG_QUECHUA_BOLIVIA), LOBBYAPI_LANGUAGE_QUECHUA, LOBBYAPI_COUNTRY_BOLIVIA }, + { MAKELANGID(LANG_QUECHUA, SUBLANG_QUECHUA_ECUADOR), LOBBYAPI_LANGUAGE_QUECHUA, LOBBYAPI_COUNTRY_ECUADOR }, + { MAKELANGID(LANG_QUECHUA, SUBLANG_QUECHUA_PERU), LOBBYAPI_LANGUAGE_QUECHUA, LOBBYAPI_COUNTRY_PERU }, + { MAKELANGID(LANG_ROMANIAN, SUBLANG_ROMANIAN_ROMANIA), LOBBYAPI_LANGUAGE_ROMANIAN, LOBBYAPI_COUNTRY_ROMANIA }, + { MAKELANGID(LANG_ROMANSH, SUBLANG_ROMANSH_SWITZERLAND), LOBBYAPI_LANGUAGE_RHAETO_ROMANCE, LOBBYAPI_COUNTRY_SWITZERLAND }, + { MAKELANGID(LANG_RUSSIAN, SUBLANG_RUSSIAN_RUSSIA), LOBBYAPI_LANGUAGE_RUSSIAN, LOBBYAPI_COUNTRY_RUSSIAN_FEDERATION }, + { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_NORTHERN_FINLAND), LOBBYAPI_LANGUAGE_SAMI, LOBBYAPI_COUNTRY_FINLAND }, + { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_NORTHERN_NORWAY), LOBBYAPI_LANGUAGE_SAMI, LOBBYAPI_COUNTRY_NORWAY }, + { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_NORTHERN_SWEDEN), LOBBYAPI_LANGUAGE_SAMI, LOBBYAPI_COUNTRY_SWEDEN }, + { MAKELANGID(LANG_SANSKRIT, SUBLANG_SANSKRIT_INDIA), LOBBYAPI_LANGUAGE_SANSKRIT, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_CYRILLIC), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA }, + { MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_LATIN),LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA }, + { MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_CYRILLIC), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_SERBIA_AND_MONTENEGRO }, + { MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_LATIN), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_SERBIA_AND_MONTENEGRO }, + { MAKELANGID(LANG_SERBIAN, 0x0C), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_MONTENEGRO }, + { MAKELANGID(LANG_SERBIAN, 0x0B), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_MONTENEGRO }, + { MAKELANGID(LANG_SERBIAN, 0x0A), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_SERBIA }, + { MAKELANGID(LANG_SERBIAN, 0x09), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_SERBIA }, + { MAKELANGID(LANG_SINHALESE, SUBLANG_SINHALESE_SRI_LANKA), LOBBYAPI_LANGUAGE_SINGHALESE, LOBBYAPI_COUNTRY_SRI_LANKA }, + { MAKELANGID(LANG_SLOVAK, SUBLANG_SLOVAK_SLOVAKIA), LOBBYAPI_LANGUAGE_SLOVAK, LOBBYAPI_COUNTRY_SLOVAKIA }, + { MAKELANGID(LANG_SLOVENIAN, SUBLANG_SLOVENIAN_SLOVENIA), LOBBYAPI_LANGUAGE_SLOVENIAN, LOBBYAPI_COUNTRY_SLOVENIA }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_ARGENTINA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_ARGENTINA }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_BOLIVIA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_BOLIVIA }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_CHILE), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_CHILE }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_COLOMBIA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_COLOMBIA }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_COSTA_RICA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_COSTA_RICA }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_DOMINICAN_REPUBLIC), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_DOMINICAN_REPUBLIC }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_ECUADOR), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_ECUADOR }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_EL_SALVADOR), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_EL_SALVADOR }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_GUATEMALA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_GUATEMALA }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_HONDURAS), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_HONDURAS }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_MEXICAN), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_MEXICO }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_NICARAGUA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_NICARAGUA }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_PANAMA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_PANAMA }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_PARAGUAY), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_PARAGUAY }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_PERU), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_PERU }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_PUERTO_RICO), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_PUERTO_RICO }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_MODERN), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_SPAIN }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_SPAIN }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_US), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_UNITED_STATES }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_URUGUAY), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_URUGUAY }, + { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_VENEZUELA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_VENEZUELA }, + { MAKELANGID(LANG_SWAHILI, SUBLANG_DEFAULT), LOBBYAPI_LANGUAGE_SWAHILI, LOBBYAPI_COUNTRY_KENYA }, + { MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH_FINLAND), LOBBYAPI_LANGUAGE_SWEDISH, LOBBYAPI_COUNTRY_FINLAND }, + { MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH), LOBBYAPI_LANGUAGE_SWEDISH, LOBBYAPI_COUNTRY_SWEDEN }, + { MAKELANGID(LANG_TAJIK, SUBLANG_TAJIK_TAJIKISTAN), LOBBYAPI_LANGUAGE_TAJIK, LOBBYAPI_COUNTRY_TAJIKISTAN }, + { MAKELANGID(LANG_TAMIL, SUBLANG_TAMIL_INDIA), LOBBYAPI_LANGUAGE_TAMIL, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_TATAR, SUBLANG_TATAR_RUSSIA), LOBBYAPI_LANGUAGE_TATAR, LOBBYAPI_COUNTRY_RUSSIAN_FEDERATION }, + { MAKELANGID(LANG_TELUGU, SUBLANG_TELUGU_INDIA), LOBBYAPI_LANGUAGE_TELUGU, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_THAI, SUBLANG_THAI_THAILAND), LOBBYAPI_LANGUAGE_THAI, LOBBYAPI_COUNTRY_THAILAND }, + { MAKELANGID(LANG_TIBETAN, SUBLANG_TIBETAN_PRC), LOBBYAPI_LANGUAGE_TIBETAN, LOBBYAPI_COUNTRY_CHINA }, + { MAKELANGID(LANG_TSWANA, SUBLANG_TSWANA_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_SETSWANA, LOBBYAPI_COUNTRY_SOUTH_AFRICA }, + { MAKELANGID(LANG_TURKISH, SUBLANG_TURKISH_TURKEY), LOBBYAPI_LANGUAGE_TURKISH, LOBBYAPI_COUNTRY_TURKEY }, + { MAKELANGID(LANG_TURKMEN, SUBLANG_TURKMEN_TURKMENISTAN), LOBBYAPI_LANGUAGE_TURKMEN, LOBBYAPI_COUNTRY_TURKMENISTAN }, + { MAKELANGID(LANG_UIGHUR, SUBLANG_UIGHUR_PRC), LOBBYAPI_LANGUAGE_UIGHUR, LOBBYAPI_COUNTRY_CHINA }, + { MAKELANGID(LANG_UKRAINIAN, SUBLANG_UKRAINIAN_UKRAINE), LOBBYAPI_LANGUAGE_UKRAINIAN, LOBBYAPI_COUNTRY_UKRAINE }, + { MAKELANGID(LANG_URDU, SUBLANG_URDU_PAKISTAN), LOBBYAPI_LANGUAGE_URDU, LOBBYAPI_COUNTRY_PAKISTAN }, + { MAKELANGID(LANG_UZBEK, SUBLANG_UZBEK_CYRILLIC), LOBBYAPI_LANGUAGE_UZBEK, LOBBYAPI_COUNTRY_UZBEKISTAN }, + { MAKELANGID(LANG_UZBEK, SUBLANG_UZBEK_LATIN), LOBBYAPI_LANGUAGE_UZBEK, LOBBYAPI_COUNTRY_UZBEKISTAN }, + { MAKELANGID(LANG_VIETNAMESE, SUBLANG_VIETNAMESE_VIETNAM), LOBBYAPI_LANGUAGE_VIETNAMESE, LOBBYAPI_COUNTRY_VIETNAM }, + { MAKELANGID(LANG_WELSH, SUBLANG_WELSH_UNITED_KINGDOM), LOBBYAPI_LANGUAGE_WELSH, LOBBYAPI_COUNTRY_UNITED_KINGDOM }, + { MAKELANGID(LANG_WOLOF, SUBLANG_WOLOF_SENEGAL), LOBBYAPI_LANGUAGE_WOLOF, LOBBYAPI_COUNTRY_SENEGAL }, + { MAKELANGID(LANG_XHOSA, SUBLANG_XHOSA_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_XHOSA, LOBBYAPI_COUNTRY_SOUTH_AFRICA }, + { MAKELANGID(LANG_YI, SUBLANG_YI_PRC), LOBBYAPI_LANGUAGE_YI, LOBBYAPI_COUNTRY_CHINA }, + { MAKELANGID(LANG_YORUBA, SUBLANG_YORUBA_NIGERIA), LOBBYAPI_LANGUAGE_YORUBA, LOBBYAPI_COUNTRY_NIGERIA }, + { MAKELANGID(LANG_ZULU, SUBLANG_ZULU_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_ZULU, LOBBYAPI_COUNTRY_SOUTH_AFRICA }, + { MAKELANGID(0x91, SUBLANG_DEFAULT), LOBBYAPI_LANGUAGE_SCOTS_GAELIC, LOBBYAPI_COUNTRY_UNITED_KINGDOM }, + // the following are valid Windows locales, but not covered by ISO639-1, LOBBYAPI_LANGUAGE_UNKNOWN will be returned for them. + { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_INARI_FINLAND), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_FINLAND }, + { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_LULE_NORWAY), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_NORWAY }, + { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_LULE_SWEDEN), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_SWEDEN }, + { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_SKOLT_FINLAND), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_FINLAND }, + { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_SOUTHERN_NORWAY), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_NORWAY }, + { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_SOUTHERN_SWEDEN), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_SWEDEN }, + { MAKELANGID(LANG_UPPER_SORBIAN, SUBLANG_UPPER_SORBIAN_GERMANY), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_GERMANY }, + { MAKELANGID(LANG_LOWER_SORBIAN, SUBLANG_LOWER_SORBIAN_GERMANY), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_GERMANY }, + { MAKELANGID(LANG_KONKANI, SUBLANG_KONKANI_INDIA), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_INDIA }, + { MAKELANGID(LANG_SYRIAC, SUBLANG_DEFAULT), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_SYRIAN_ARAB_REPUBLIC }, + { MAKELANGID(LANG_TAMAZIGHT, SUBLANG_NEUTRAL), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_ALGERIA }, + { MAKELANGID(LANG_TAMAZIGHT, SUBLANG_TAMAZIGHT_ALGERIA_LATIN), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_ALGERIA }, + { MAKELANGID(LANG_FILIPINO, SUBLANG_FILIPINO_PHILIPPINES), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_PHILIPPINES }, + { MAKELANGID(LANG_SOTHO, SUBLANG_SOTHO_NORTHERN_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_SOUTH_AFRICA }, + { MAKELANGID(LANG_MAPUDUNGUN, SUBLANG_MAPUDUNGUN_CHILE), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_CHILE }, + { MAKELANGID(LANG_MOHAWK, SUBLANG_MOHAWK_MOHAWK), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_CANADA }, + { MAKELANGID(LANG_ALSATIAN, SUBLANG_ALSATIAN_FRANCE), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_FRANCE }, + { MAKELANGID(LANG_YAKUT, SUBLANG_YAKUT_RUSSIA), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_RUSSIAN_FEDERATION }, + { MAKELANGID(LANG_KICHE, SUBLANG_KICHE_GUATEMALA), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_GUATEMALA }, + { MAKELANGID(LANG_DARI, SUBLANG_DARI_AFGHANISTAN), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_AFGHANISTAN } +}; + +//! global module ref +static NetConnRefT *_NetConn_pRef = NULL; + +/*** Private Functions ****************************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _NetConnGetLocaleInfo + + \Description + To obtain locale information for specified lang id. + + \Input uLangId - the lang id used to extract locale info from + + \Output + int32_t - the same output to NetConnStatus('locl'), ex. enUS + + \Version 04/14/2011 (szhu) +*/ +/********************************************************************************F*/ +static int32_t _NetConnGetLocaleInfo(uint16_t uLangId) +{ + uint16_t uLanguage, uCountry; + uint32_t uIndex; + + // search the pre-defined mapping table + for (uIndex = 0; uIndex < (sizeof(_NetConn_WindowsLocaleMap)/sizeof(_NetConn_WindowsLocaleMap[0])); uIndex++) + { + // _NetConn_WindowsLocaleMap[uIndex][0]: LangId, [1]: language, [2]: COUNTRY + if (uLangId == _NetConn_WindowsLocaleMap[uIndex][0]) + { + uLanguage = _NetConn_WindowsLocaleMap[uIndex][1]; + uCountry = _NetConn_WindowsLocaleMap[uIndex][2]; + return(LOBBYAPI_LocalizerTokenCreate(uLanguage, uCountry)); + } + } + + // not found in pre-defined mapping table, search again with SUBLANG_DEFAULT (only for SUBLANG_NEUTRAL) + if ((SUBLANGID(uLangId) == SUBLANG_NEUTRAL) && (SUBLANG_NEUTRAL != SUBLANG_DEFAULT)) + { + uLangId = PRIMARYLANGID(uLangId); + return(_NetConnGetLocaleInfo(MAKELANGID(uLangId, SUBLANG_DEFAULT))); + } + + NetPrintf(("netconnwin: failed to obtain locale info for localid (0x%04x), %s is returned.\n", (uint32_t)uLangId, LOBBYAPI_LOCALITY_UNKNOWN_STR)); + // not found, zzZZ will be returned + return(LOBBYAPI_LOCALITY_UNKNOWN); +} + + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _NetConnVerifyLocale + + \Description + Check if a _NetConn_WindowsLocaleMap entry is good by comparing with Windows API (GetLocaleInfo)'s result. + + \Input *pLocale - a _NetConn_WindowsLocaleMap entry -- uint16_t[3] + \Input iAlwaysPrint - (boolean) TRUE, always prints debug trace; FALSE, prints debug trace upon error + + \Output + int32_t - (boolean) TRUE=success, FALSE=failure + + \Notes + For debugging purpose only. + + \Version 03/25/2011 (szhu) +*/ +/********************************************************************************F*/ +static int32_t _NetConnVerifyLocale(uint16_t *pLocale, int32_t iAlwaysPrint) +{ + int32_t iResult = FALSE; + char strLocale[LOCALE_NAME_MAX_LENGTH], strDefined[32]; + char *pTemp1, *pTemp2; + uint32_t uToken; + + // convert array to string like enUS + uToken = LOBBYAPI_LocalizerTokenCreate(pLocale[1], pLocale[2]); + LOBBYAPI_LocalizerTokenCreateLocalityString(strDefined, uToken); + + // compare between mapping table value and Windows API GetLocaleInfo(LOCALE_SNAME) to verify whether the mapping table value is good. + ds_memclr(strLocale, sizeof(strLocale)); + if (GetLocaleInfo(pLocale[0], LOCALE_SNAME, strLocale, LOCALE_NAME_MAX_LENGTH) > 0) + { + // GetLocaleInfo may return name-script-COUNTRY. If so, we remove the script: name-script-COUNTRY --> nameCOUNTRY + if ((pTemp1 = strchr(strLocale, '-')) != NULL) + { + // name-script-COUNTRY --> nameCOUNTRY + if ((pTemp2 = strchr(pTemp1 + 1, '-')) != NULL) + { + pTemp2++; // skip '-' + memmove(pTemp1, pTemp2, strlen(pTemp2) + 1); // plus '\0' + } + else // name-COUNTRY --> nameCOUNTRY + { + memmove(pTemp1, pTemp1 + 1, strlen(pTemp1 + 1) + 1); + } + } + + // GetLocaleInfo may return name-COUNTRY_xxxx. If so, we remove _xxxx: nameCOUNTRY_xxxx --> nameCOUNTRY + if ((pTemp1 = strchr(strLocale, '_')) != NULL) + { + pTemp1[0] = 0; + } + + // the language name should be in lowercase while the country name should be in uppercase. + // 1, enUS == enUS (perfect); 2, zzCA == xxxCA (not covered by ISO639-1); 3, xxCA == xxyCA (not covered by ISO639-1); 4, enZZ == enxxx; + if((strcmp(strDefined, strLocale) == 0) + || (strncmp(strDefined, LOBBYAPI_LANGUAGE_UNKNOWN_STR, 2) == 0 && strncmp(strDefined + 2, strLocale + 3, 2) == 0) + || (strncmp(strDefined, strLocale, 2) == 0 && strncmp(strDefined + 2, strLocale + 3, 2) == 0) + || (strncmp(strDefined, strLocale, 2) == 0 && strncmp(strDefined + 2, LOBBYAPI_COUNTRY_UNKNOWN_STR, 2) == 0 && strlen(strLocale) != 4)) + { + // check if _NetConnGetLocaleInfo behaves correctly + if ((uint32_t)_NetConnGetLocaleInfo(pLocale[0]) == uToken) + { + iResult = TRUE; + if (iAlwaysPrint != FALSE) + { + NetPrintf(("netconnwin: passed -- GetLocaleInfo(0x%04x) returned (%s)!\n", (uint32_t)pLocale[0], strLocale)); + } + // if not perfect (enUS == enUS), print debug trace + if (strcmp(strDefined, strLocale) != 0) + { + NetPrintf(("netconnwin: notice -- GetLocaleInfo(0x%04x) returned (%s) - (%s)!\n", (uint32_t)pLocale[0], strLocale, strDefined)); + } + } + } + } + + if (iResult == FALSE) + { + NetPrintf(("netconnwin: ERROR -- GetLocaleInfo(0x%04x) returned (%s) - (%s)!\n", (uint32_t)pLocale[0], strLocale, strDefined)); + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _NetConnVerifyAllLocales + + \Description + Verify if _NetConn_WindowsLocaleMap items are good. For debugging purpose only. + + \Notes + For unknown language, zz will be returned; + For unknown country, ZZ will be returned; + For unknown locality, zzZZ will be returned. + + \Version 03/25/2011 (szhu) +*/ +/********************************************************************************F*/ +static void _NetConnVerifyAllLocales(void) +{ + uint16_t aNeutral[3]; + uint32_t uIndex, uToken; + char strLocale[LOCALE_NAME_MAX_LENGTH]; + + for (uIndex = 0; uIndex < (sizeof(_NetConn_WindowsLocaleMap)/sizeof(_NetConn_WindowsLocaleMap[0])); uIndex++) + { + if (_NetConnVerifyLocale(_NetConn_WindowsLocaleMap[uIndex], FALSE) == FALSE) + { + NetPrintf(("netconnwin: error found in _NetConn_WindowsLocaleMap[%u] (0x%04x)!\n", uIndex, (uint32_t)_NetConn_WindowsLocaleMap[uIndex][0])); + DebugBreak(); + } + + // verify if langid(Primary ID, SUBLANG_NEUTRAL) works out + // exception 0x0404: 0x0404 --> ZH_TW, 0x0004 --> ZH_CN + if ((_NetConn_WindowsLocaleMap[uIndex][0] != 0x0404) && (SUBLANGID(_NetConn_WindowsLocaleMap[uIndex][0]) == SUBLANG_DEFAULT)) + { + aNeutral[0] = MAKELANGID(PRIMARYLANGID(_NetConn_WindowsLocaleMap[uIndex][0]), SUBLANG_NEUTRAL); + aNeutral[1] = _NetConn_WindowsLocaleMap[uIndex][1]; + aNeutral[2] = _NetConn_WindowsLocaleMap[uIndex][2]; + + if (_NetConnVerifyLocale(aNeutral, FALSE) == FALSE) + { + NetPrintf(("netconnwin: error found in _NetConn_WindowsLocaleMap[%u](Neutral) (0x%04x)!\n", uIndex, (uint32_t)aNeutral[0])); + DebugBreak(); + } + } + } + NetPrintf(("netconnwin: checked _NetConn_WindowsLocaleMap -- %u entries in total.\n", uIndex)); + + // print locale information + uToken = NetConnStatus('locl', 0, NULL, 0); + NetPrintf(("netconnwin: locality returned by NetConnStatus(locl,0): (%c%c%c%c).\n", LOBBYAPI_LocalizerTokenPrintCharArray(uToken))); + + // test if _NetConnGetLocaleInfo works fine when given an invalid langid + uToken = _NetConnGetLocaleInfo(0x9FFF); + NetPrintf(("netconnwin: _NetConnGetLocaleInfo(0x9FFF) returned: (%c%c%c%c) - (%s).\n", LOBBYAPI_LocalizerTokenPrintCharArray(uToken), LOBBYAPI_LOCALITY_UNKNOWN_STR)); + + // list locales that not covered by the mapping table + for (uIndex = 1; uIndex <= 0x8200; uIndex++) + { + if ((PRIMARYLANGID(uIndex) == LANG_NEUTRAL) + || (uIndex == MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)) + || (uIndex == MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT))) + { + continue; + } + + if ((GetLocaleInfo(uIndex, LOCALE_SNAME, strLocale, LOCALE_NAME_MAX_LENGTH) > 0) + && (_NetConnGetLocaleInfo(uIndex) == 'zzZZ')) + { + NetPrintf(("netconnwin: not covered (0x%04x): %s!\n", uIndex, strLocale)); + } + } +} +#endif //DIRTYCODE_LOGGING + +/*F********************************************************************************/ +/*! + \Function _NetConnIsValidMacAddress + + \Description + Checks to make sure the address is valid + + \Input *pAddr - The current address we are checking for validity + \Input bCheckIP - Should we check the IP Address of the NIC to make sure it is active + + \Output + uint8_t - TRUE=valid, FALSE=invalid +*/ +/********************************************************************************F*/ +static uint8_t _NetConnIsValidMacAddress(PIP_ADAPTER_ADDRESSES pAddr, uint8_t bCheckIP) +{ + PIP_ADAPTER_UNICAST_ADDRESS pUnicastAddr; + + // If the address is not 6-byte long it is invalid + if (pAddr->PhysicalAddressLength != 6) + { + return(FALSE); + } + + // If we are not checking the IP address then the MAC is valid + // as far as we are concerned + if (bCheckIP == FALSE) + { + return(TRUE); + } + + // Make sure the address has a valid IP in multiple NIC situation + for (pUnicastAddr = pAddr->FirstUnicastAddress; pUnicastAddr != NULL; pUnicastAddr = pUnicastAddr->Next) + { + if ((pUnicastAddr->Flags & IP_ADAPTER_ADDRESS_DNS_ELIGIBLE) == IP_ADAPTER_ADDRESS_DNS_ELIGIBLE) + { + return(TRUE); + } + } + + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function _NetConnFindMacAddress + + \Description + Scans through adapter list and finds an active adapter. + + \Input *pMacAddr - user-provided buffer to be filled with MAC address + \Input iAddrSize - The size of the buffer + \Input bCheckIP - If we should check for a valid IP on the NIC + + \Output + int32_t - Negative=error, zero=success + + \Version 06/23/2009 (mclouatre) +*/ +/********************************************************************************F*/ +static int32_t _NetConnFindMacAddress(uint8_t *pMacAddr, int32_t iAddrSize, uint8_t bCheckIP) +{ + NetConnRefT *pRef = _NetConn_pRef; + DWORD dwMsRetVal; + ULONG ulOutBufLen; + ULONG ulDummyBuffer; + PIP_ADAPTER_ADDRESSES pAdaptersAddrBuf; + PIP_ADAPTER_ADDRESSES pCurrAddr; + +#if DIRTYCODE_LOGGING + uint32_t uByteIndex; + char strPrintBuf[32]; + int32_t iCurrLen; +#endif + + int32_t iRetVal = 0; + + // make an initial call to GetAdaptersAddresses() to get the + // size needed into the ulOutBufLen variable + ulOutBufLen = 1; // make sure that size provided is too small to store the info. + dwMsRetVal = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, (PIP_ADAPTER_ADDRESSES) &ulDummyBuffer, &ulOutBufLen); + if (dwMsRetVal != ERROR_BUFFER_OVERFLOW) + { + // if windows does not return an overflow error, something is wrong... + NetPrintf(("netconnwin: GetAdaptersAddresses() failed with err %d when querying for output buffer size\n", dwMsRetVal)); + return(-1); + } + + /* + allocate a big enough buffer. + note: + * it is not a good idea to preallocate this as we will have to seriously overallocate to support rare cases + with a long list of IP_ADAPTER_ADDRESSES returned. MS doc states that Windows will "typically" not + require a buffer larger than 15 Kbytes http://msdn.microsoft.com/en-us/library/aa365915(VS.85).aspx + * it is not a good idea to allocate this buffer on the stack as it can be quite huge and could lead to a + stack overflow. + */ + if ((pAdaptersAddrBuf = DirtyMemAlloc(ulOutBufLen, NETCONN_MEMID, pRef->Common.iMemGroup, pRef->Common.pMemGroupUserData)) == NULL) + { + NetPrintf(("netconnwin: unable to allocate output buffer required to call GetAdaptersAddresses()\n")); + return(-2); + } + + // make a second call to GetAdaptersAddresses() to get the actual data we want + dwMsRetVal = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAdaptersAddrBuf, &ulOutBufLen); + if (dwMsRetVal == NO_ERROR) + { + // loop through the adapters and match first entry with valid MAC address + pCurrAddr = pAdaptersAddrBuf; + while (pCurrAddr) + { + // make sure interface type can have a MAC address + if ((pCurrAddr->IfType == IF_TYPE_ETHERNET_CSMACD) || (pCurrAddr->IfType == IF_TYPE_IEEE80211)) + { + // make sure physical address is 6-byte long and has a valid ip address; if not, it is not a MAC address + if (_NetConnIsValidMacAddress(pCurrAddr, bCheckIP) == TRUE) + { + ds_memcpy_s(pMacAddr, iAddrSize, pCurrAddr->PhysicalAddress, pCurrAddr->PhysicalAddressLength); + + #if DIRTYCODE_LOGGING + // We need to use sprintf here because if we try to NetPrintf we will + // have timestamps inbetween where we are printing out the mac address + + ds_memclr(strPrintBuf, sizeof(strPrintBuf)); + iCurrLen = 0; + + for (uByteIndex = 0; uByteIndex < (int32_t) pCurrAddr->PhysicalAddressLength; uByteIndex++) + { + if (uByteIndex == (pCurrAddr->PhysicalAddressLength - 1)) + { + iCurrLen += ds_snzprintf(strPrintBuf + iCurrLen, sizeof(strPrintBuf) - iCurrLen, "%.2X", (int32_t)pCurrAddr->PhysicalAddress[uByteIndex]); + } + else + { + iCurrLen += ds_snzprintf(strPrintBuf + iCurrLen, sizeof(strPrintBuf) - iCurrLen, "%.2X-", (int32_t)pCurrAddr->PhysicalAddress[uByteIndex]); + } + } + + NetPrintf(("netconnwin: valid MAC address found --> %s\n", strPrintBuf)); + #endif + + // valid MAC address found! exit the function with success + break; + } + } + + pCurrAddr = pCurrAddr->Next; + } + + if (pCurrAddr == NULL) + { + NetPrintf(("netconnwin: could not find any valid adapters to get a MAC address from\n")); + iRetVal = -3; + } + } + else + { + NetPrintf(("netconnwin: GetAdaptersAddresses() failed with err %d when querying for adapters data size\n", dwMsRetVal)); + iRetVal = -4; + } + + // free buffer used with GetAdaptersAddresses + DirtyMemFree(pAdaptersAddrBuf, NETCONN_MEMID, pRef->Common.iMemGroup, pRef->Common.pMemGroupUserData); + + return(iRetVal); +} + +/*F********************************************************************************/ +/*! + \Function _NetConnShutdownInternal + + \Description + Shutdown the network code and return to idle state for internal use + + \Input pRef - netconn ref + \Input bStopEE - ignored in PC implementation + + \Output + int32_t - negative=error, else zero + + \Version 1/13/2020 (tcho) +*/ +/********************************************************************************F*/ +int32_t _NetConnShutdownInternal(NetConnRefT *pRef, uint32_t bStopEE) +{ + int32_t iResult = 0; + + // decrement and check the refcount + if ((iResult = NetConnCommonCheckRef((NetConnCommonRefT*)pRef)) < 0) + { + return(iResult); + } + + if (_NetConn_pRef != NULL) + { + NetConnDisconnect(); + } + + // destroy the upnp ref + if (pRef->pProtoUpnp != NULL) + { + ProtoUpnpDestroy(pRef->pProtoUpnp); + pRef->pProtoUpnp = NULL; + } + + // shut down protossl + ProtoSSLShutdown(); + + // shut down dirtycert + DirtyCertDestroy(); + + // shut down Idle handler + NetConnIdleShutdown(); + + // shut down dirtysock + SocketDestroy(0); + + // common shutdown (must come last as this frees the memory) + NetConnCommonShutdown(&pRef->Common); + _NetConn_pRef = NULL; + + return(0); +} + +/*** Public functions *****************************************************************************/ + +/*F********************************************************************************/ +/*! + \Function NetConnStartup + + \Description + Bring the network connection module to life. Does not actually start any + network activity. + + \Input *pParams - startup parameters + + \Output + int32_t - zero=success, negative=failure + + \Notes + NetConnRefT::iRefCount serves as a counter for the number of times + NetConnStartup has been called. This allows us to track how many modules + are using it and how many times we expect NetConnShutdown to the called. + In the past we only allowed a single call to NetConnStartup but some + libraries may need to networking without a guarentee that the game has + already started it. + + pParams can contain the following terms: + + \verbatim + -noupnp : disable UPNP + -servicename= : set servicename required for SSL use + -affinity= : cpu affinity we set for our internal threads + \endverbatim + + \Version 3/10/01 (GWS) +*/ +/********************************************************************************F*/ +int32_t NetConnStartup(const char *pParams) +{ + NetConnRefT *pRef = _NetConn_pRef; + int32_t iResult = 0; + char strThreadCpuAffinity[16]; + + // allow NULL params + if (pParams == NULL) + { + pParams = ""; + } + + // debug display of input params + NetPrintf(("netconnwin: startup params='%s'\n", pParams)); + + // common startup + // pRef shall hold the address of the NetConnRefT after completion if no error occured + iResult = NetConnCommonStartup(sizeof(*pRef), pParams, (NetConnCommonRefT**)(&pRef)); + + // treat the result of the common startup, if already started simply early out + if (iResult == NETCONN_ERROR_ALREADY_STARTED) + { + return(0); + } + // otherwise, if an error occured report it + else if (iResult < 0) + { + return(iResult); + } + + pRef->eState = ST_INIT; + pRef->iPeerPort = NETCONN_DEFAULT_UPNP_PORT; + + // get the cpu affinity string from our startup params, defaulting to 0x0 + ds_memclr(strThreadCpuAffinity, sizeof(strThreadCpuAffinity)); + NetConnCopyParam(strThreadCpuAffinity, sizeof(strThreadCpuAffinity), "-affinity=", pParams, "0x0"); + pRef->iThreadCpuAffinity = strtol(strThreadCpuAffinity, NULL, 16); + + // start up dirtysock + if (SocketCreate(THREAD_PRIORITY_HIGHEST, 0, pRef->iThreadCpuAffinity) != 0) + { + _NetConnShutdownInternal(pRef, FALSE); + NetPrintf(("netconnwin: unable to start up dirtysock\n")); + return(NETCONN_ERROR_SOCKET_CREATE); + } + + // create and configure dirtycert + if (NetConnDirtyCertCreate(pParams)) + { + _NetConnShutdownInternal(pRef, FALSE); + NetPrintf(("netconnwin: unable to create dirtycert\n")); + return(NETCONN_ERROR_DIRTYCERT_CREATE); + } + + // start up protossl + if (ProtoSSLStartup() < 0) + { + _NetConnShutdownInternal(pRef, FALSE); + NetPrintf(("netconnwin: unable to start up protossl\n")); + return(NETCONN_ERROR_PROTOSSL_CREATE); + } + + if (strstr(pParams, "-noupnp") == NULL) + { + pRef->pProtoUpnp = ProtoUpnpCreate(); + if (pRef->pProtoUpnp == NULL) + { + _NetConnShutdownInternal(pRef, FALSE); + NetPrintf(("netconnwin: unable to start up protoupnp\n")); + return(NETCONN_ERROR_PROTOUPNP_CREATE); + } + } + + // save ref + _NetConn_pRef = pRef; + + #if DIRTYCODE_LOGGING + // if we made changes to the mapping table (_NetConn_WindowsLocaleMap), + // uncomment the following line and build-debug-run to verify if the changes are good. + //_NetConnVerifyAllLocales(); + #endif + + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnQuery + + \Description + Query the list of available connection configurations. This list is loaded + from the specified device. The list is returned in a simple fixed width + array with one string per array element. The caller can find the user portion + of the config name via strchr(item, '#')+1. + + \Input *pDevice - device to scan (mc0:, mc1:, pfs0:, pfs1:) + \Input *pList - buffer to store output array in + \Input iSize - length of buffer in bytes + + \Output + int32_t - negative=error, else number of configurations + + \Version 01/18/02 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetConnQuery(const char *pDevice, NetConfigRecT *pList, int32_t iSize) +{ + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnConnect + + \Description + Used to bring the networking online with a specific configuration. Uses the + configuration returned by NetConnQuery. + + \Input *pConfig - the configuration entry from NetConnQuery + \Input *pOption - asciiz list of config parameters + "peerport=" to specify peer port to be opened by upnp. + \Input iData - platform-specific + + \Output + int32_t - negative=error, zero=success + + \Version 01/18/02 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetConnConnect(const NetConfigRecT *pConfig, const char *pOption, int32_t iData) +{ + NetConnRefT *pRef = _NetConn_pRef; + + // check connection options, if present + if (pOption != NULL) + { + const char *pOpt; + // check for specification of peer port + if ((pOpt = strstr(pOption, "peerport=")) != NULL) + { + pRef->iPeerPort = strtol(pOpt+9, NULL, 10); + } + } + NetPrintf(("netconnwin: upnp peerport=%d %s\n", + pRef->iPeerPort, (pRef->iPeerPort == NETCONN_DEFAULT_UPNP_PORT ? "(default)" : "(selected via netconnconnect param)"))); + + // make sure we aren't already connected + if (pRef->eState == ST_INIT) + { + // connecting + pRef->eState = ST_CONN; + + // discover upnp router information + if (pRef->pProtoUpnp != NULL) + { + if (pRef->iPeerPort != 0) + { + ProtoUpnpControl(pRef->pProtoUpnp, 'port', pRef->iPeerPort, 0, NULL); + ProtoUpnpControl(pRef->pProtoUpnp, 'macr', 'upnp', 0, NULL); + } + else + { + ProtoUpnpControl(pRef->pProtoUpnp, 'macr', 'dscg', 0, NULL); + } + } + } + else + { + NetPrintf(("netconnwin: NetConnConnect() ignored because already connected!\n")); + } + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnDisconnect + + \Description + Used to bring down the network connection. After calling this, it would + be necessary to call NetConnConnect to bring the connection back up or + NetConnShutdown to completely shutdown all network support. + + \Output + int32_t - negative=error, zero=success + + \Version 02/09/02 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetConnDisconnect(void) +{ + NetConnRefT *pRef = _NetConn_pRef; + + if (pRef->eState == ST_CONN) + { + // shutdown the interfaces (but leave API running) + pRef->eState = ST_INIT; + } + + // abort upnp operations + if (pRef->pProtoUpnp != NULL) + { + ProtoUpnpControl(pRef->pProtoUpnp, 'abrt', 0, 0, NULL); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetConnControl + + \Description + Set module behavior based on input selector. + + \Input iControl - input selector + \Input iValue - selector input + \Input iValue2 - selector input + \Input *pValue - selector input + \Input *pValue2 - selector input + + \Output + int32_t - selector result + + \Notes + iControl can be one of the following: + + \verbatim + snam: set DirtyCert service name + \endverbatim + + Unhandled selectors are passed through to NetConnCommonControl() + + \Version 04/27/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetConnControl(int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue, void *pValue2) +{ + NetConnRefT *pRef = _NetConn_pRef; + + // make sure module is started before allowing any other control calls + if (pRef == NULL) + { + NetPrintf(("netconnwin: warning - calling NetConnControl() while module is not initialized\n")); + return(-1); + } + + // set dirtycert service name + if (iControl == 'snam') + { + return(DirtyCertControl('snam', 0, 0, pValue)); + } + + // pass through unhandled selectors to NetConnCommon + return(NetConnCommonControl(&pRef->Common, iControl, iValue, iValue2, pValue, pValue2)); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnStatus + + \Description + Check general network connection status. Different selectors return + different status attributes. + + \Input iKind - status selector + \Input iData - (optional) selector specific + \Input *pBuf - (optional) pointer to output buffer + \Input iBufSize - (optional) size of output buffer + + \Output + int32_t - selector specific + + \Notes + iKind can be one of the following: + + \verbatim + addr: ip address of client + affn: get the thread cpu affinity setting + bbnd: broadband true or false + conn: true/false indication of whether connection in progress + ethr: mac address of adapter based on iData (0=first, otherwise=first active) (returned in pBuf), 0=success, negative=error + locl: return locality (Windows locale) for local system, ex. 'enUS' + For unrecognized locale, 'zzZZ' will be returned. + locn: return location (Windows location) for local system, ex. 'zzCA' + The language part of the return value should be ignored by the caller. + If local system has 'no location' set, 'zzZZ' will be returned. + macx: mac address of adapter based on iData (0=first, otherwise=first active) (returned in pBuf), 0=success, negative=error + onln: true/false indication of whether network is operational + open: true/false indication of whether network code running + type: NETCONN_IFTYPE_* + upnp: return protoupnp port info, if available + vers: return DirtySDK version + \endverbatim + + Unhandled selectors are passed through to NetConnCommonStatus() + + \Version 03/10/2001 (gschaefer) +*/ +/*************************************************************************************************F*/ +int32_t NetConnStatus(int32_t iKind, int32_t iData, void *pBuf, int32_t iBufSize) +{ + NetConnRefT *pRef = _NetConn_pRef; + + // see if network code is initialized + if (iKind == 'open') + { + return(pRef != NULL); + } + // return DirtySDK version + if (iKind == 'vers') + { + return(DIRTYSDK_VERSION); + } + + // make sure module is started before allowing any other status calls + if (pRef == NULL) + { + NetPrintf(("netconn: warning - calling NetConnStatus() while module is not initialized\n")); + return(-1); + } + + // return client ip address + if (iKind == 'addr') + { + return(SocketGetLocalAddr()); + } + + // return the thread cpu affinity + if (iKind == 'affn') + { + return(pRef->iThreadCpuAffinity); + } + + // return whether we are broadband or not + if (iKind == 'bbnd') + { + // assume broadband + return(TRUE); + } + + // return connection status + if (iKind == 'conn') + { + return((pRef->eState == ST_CONN) ? '+onl' : '-dsc'); + } + + // return host mac address + if ((iKind == 'ethr') || (iKind == 'macx')) + { + if (pBuf != NULL) + { + // clear out the buffer + ds_memclr(pBuf, iBufSize); + + // find a valid MAC address + return(_NetConnFindMacAddress(pBuf, iBufSize, iData == 0 ? FALSE : TRUE)); + } + } + + // see if connected to ISP/LAN + if (iKind == 'onln') + { + return(pRef->eState == ST_CONN); + } + + // return what type of interface we are connected with + if (iKind == 'type') + { + // assume broadband + return(NETCONN_IFTYPE_ETHER); + } + + // return upnp addportmap info, if available + if (iKind == 'upnp') + { + // if protoupnp is available, and we've added a port map, return the external port for the port mapping + if ((pRef->pProtoUpnp != NULL) && (ProtoUpnpStatus(pRef->pProtoUpnp, 'stat', NULL, 0) & PROTOUPNP_STATUS_ADDPORTMAP)) + { + return(ProtoUpnpStatus(pRef->pProtoUpnp, 'extp', NULL, 0)); + } + } + + // return Windows locale information + if (iKind == 'locl') + { + return(_NetConnGetLocaleInfo(GetUserDefaultLangID())); + } + + // return Windows location information + if (iKind == 'locn') + { + GEOID iGeoID; + char strCountry[8]; // ISO2, [8] is enough + uint16_t uLanguage = LOBBYAPI_LANGUAGE_UNKNOWN, // language is always set to zz + uCountry = LOBBYAPI_COUNTRY_UNKNOWN; // default to ZZ + + // check if local system has location set + if ((iGeoID = GetUserGeoID(GEOCLASS_NATION)) == GEOID_NOT_AVAILABLE) + { + return(LOBBYAPI_LOCALITY_UNKNOWN); + } + + ds_memclr(strCountry, sizeof(strCountry)); + if ((GetGeoInfoA(iGeoID, GEO_ISO2, strCountry, sizeof(strCountry), 0) > 0) + && (strlen(strCountry) == sizeof(uint16_t)) + && (ds_stricmp(strCountry, NETCONNWIN_COUNTRY_UNKNOWN_STR) != 0)) + { + // a valid country code: 2-letter, and not equal to XX + uCountry = LOBBYAPI_LocalizerTokenGetShortFromString(strCountry); + } + + return(LOBBYAPI_LocalizerTokenCreate(uLanguage, uCountry)); + } + + // pass unrecognized options to NetConnCommon + return(NetConnCommonStatus(&pRef->Common, iKind, iData, pBuf, iBufSize)); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnShutdown + + \Description + Shutdown the network code and return to idle state. + + \Input bStopEE - ignored in PC implementation + + \Output + negative=error, zero=success + + \Version 3/10/01 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetConnShutdown(uint32_t bStopEE) +{ + NetConnRefT *pRef = _NetConn_pRef; + + // error see if already stopped + if (pRef == NULL) + { + return(NETCONN_ERROR_NOTACTIVE); + } + + return(_NetConnShutdownInternal(pRef, bStopEE)); +} + +/*F*************************************************************************************************/ +/*! + \Function NetConnSleep + + \Description + Sleep the application (yield thread) for some number of milliseconds. + + \Input iMilliSecs - number of milliseconds to block for + + \Output + None + + \Version 04/30/02 (GWS) +*/ +/*************************************************************************************************F*/ +void NetConnSleep(int32_t iMilliSecs) +{ + Sleep(iMilliSecs); +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/unix/dirtyerrunix.c b/src/thirdparty/dirtysdk/source/dirtysock/unix/dirtyerrunix.c new file mode 100644 index 00000000..87f12252 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/unix/dirtyerrunix.c @@ -0,0 +1,364 @@ +/*H********************************************************************************/ +/*! + \File dirtyerrunix.c + + \Description + Dirtysock debug error routines. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 10/04/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include // included so we know where to find network headers +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtyerr.h" + +#if defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_APPLEOSX) +#include +#include +#endif + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +#if DIRTYSOCK_ERRORNAMES +static DirtyErrT _DirtyErr_List[] = +{ + DIRTYSOCK_ErrorName(EPERM), // 1 Operation not permitted + DIRTYSOCK_ErrorName(ENOENT), // 2 No such file or directory + DIRTYSOCK_ErrorName(ESRCH), // 3 No such process + DIRTYSOCK_ErrorName(EINTR), // 4 Interrupted system call + DIRTYSOCK_ErrorName(EIO), // 5 I/O error + DIRTYSOCK_ErrorName(ENXIO), // 6 No such device or address + DIRTYSOCK_ErrorName(E2BIG), // 7 Arg list too long + DIRTYSOCK_ErrorName(ENOEXEC), // 8 Exec format error + DIRTYSOCK_ErrorName(EBADF), // 9 Bad file number + DIRTYSOCK_ErrorName(ECHILD), // 10 No child processes + //DIRTYSOCK_ErrorName(EAGAIN), // 11 Try again + DIRTYSOCK_ErrorName(EWOULDBLOCK), // 11 Operation would block + DIRTYSOCK_ErrorName(ENOMEM), // 12 Out of memory + DIRTYSOCK_ErrorName(EACCES), // 13 Permission denied + DIRTYSOCK_ErrorName(EFAULT), // 14 Bad address + DIRTYSOCK_ErrorName(ENOTBLK), // 15 Block device required + DIRTYSOCK_ErrorName(EBUSY), // 16 Device or resource busy + DIRTYSOCK_ErrorName(EEXIST), // 17 File exists + DIRTYSOCK_ErrorName(EXDEV), // 18 Cross-device link + DIRTYSOCK_ErrorName(ENODEV), // 19 No such device + DIRTYSOCK_ErrorName(ENOTDIR), // 20 Not a directory + DIRTYSOCK_ErrorName(EISDIR), // 21 Is a directory + DIRTYSOCK_ErrorName(EINVAL), // 22 Invalid argument + DIRTYSOCK_ErrorName(ENFILE), // 23 File table overflow + DIRTYSOCK_ErrorName(EMFILE), // 24 Too many open files + DIRTYSOCK_ErrorName(ENOTTY), // 25 Not a typewriter + DIRTYSOCK_ErrorName(ETXTBSY), // 26 Text file busy + DIRTYSOCK_ErrorName(EFBIG), // 27 File too large + DIRTYSOCK_ErrorName(ENOSPC), // 28 No space left on device + DIRTYSOCK_ErrorName(ESPIPE), // 29 Illegal seek + DIRTYSOCK_ErrorName(EROFS), // 30 Read-only file system + DIRTYSOCK_ErrorName(EMLINK), // 31 Too many links + DIRTYSOCK_ErrorName(EPIPE), // 32 Broken pipe + DIRTYSOCK_ErrorName(EDOM), // 33 Math argument out of domain of func + DIRTYSOCK_ErrorName(ERANGE), // 34 Math result not representable + DIRTYSOCK_ErrorName(EDEADLK), // 35 Resource deadlock would occur + DIRTYSOCK_ErrorName(ENAMETOOLONG), // 36 File name too long + DIRTYSOCK_ErrorName(ENOLCK), // 37 No record locks available + DIRTYSOCK_ErrorName(ENOSYS), // 38 Function not implemented + DIRTYSOCK_ErrorName(ENOTEMPTY), // 39 Directory not empty + DIRTYSOCK_ErrorName(ELOOP), // 40 Too many symbolic links encountered + DIRTYSOCK_ErrorName(ENOMSG), // 42 No message of desired type + DIRTYSOCK_ErrorName(EIDRM), // 43 Identifier removed +#if defined(DIRTYCODE_LINUX) + DIRTYSOCK_ErrorName(ECHRNG), // 44 Channel number out of range + DIRTYSOCK_ErrorName(EL2NSYNC), // 45 Level 2 not synchronized + DIRTYSOCK_ErrorName(EL3HLT), // 46 Level 3 halted + DIRTYSOCK_ErrorName(EL3RST), // 47 Level 3 reset + DIRTYSOCK_ErrorName(ELNRNG), // 48 Link number out of range + DIRTYSOCK_ErrorName(EUNATCH), // 49 Protocol driver not attached + DIRTYSOCK_ErrorName(ENOCSI), // 50 No CSI structure available + DIRTYSOCK_ErrorName(EL2HLT), // 51 Level 2 halted + DIRTYSOCK_ErrorName(EBADE), // 52 Invalid exchange + DIRTYSOCK_ErrorName(EBADR), // 53 Invalid request descriptor + DIRTYSOCK_ErrorName(EXFULL), // 54 Exchange full + DIRTYSOCK_ErrorName(ENOANO), // 55 No anode + DIRTYSOCK_ErrorName(EBADRQC), // 56 Invalid request code + DIRTYSOCK_ErrorName(EBADSLT), // 57 Invalid slot + DIRTYSOCK_ErrorName(EDEADLOCK), // 58 File locking deadlock error + DIRTYSOCK_ErrorName(EBFONT), // 59 Bad font file format +#endif + DIRTYSOCK_ErrorName(ENOSTR), // 60 Device not a stream + DIRTYSOCK_ErrorName(ENODATA), // 61 No data available + DIRTYSOCK_ErrorName(ETIME), // 62 Timer expired + DIRTYSOCK_ErrorName(ENOSR), // 63 Out of streams resources +#if defined(DIRTYCODE_LINUX) + DIRTYSOCK_ErrorName(ENONET), // 64 Machine is not on the network + DIRTYSOCK_ErrorName(ENOPKG), // 65 Package not installed +#endif + DIRTYSOCK_ErrorName(EREMOTE), // 66 Object is remote + DIRTYSOCK_ErrorName(ENOLINK), // 67 Link has been severed +#if defined(DIRTYCODE_LINUX) + DIRTYSOCK_ErrorName(EADV), // 68 Advertise error + DIRTYSOCK_ErrorName(ESRMNT), // 69 Srmount error + DIRTYSOCK_ErrorName(ECOMM), // 70 Communication error on send +#endif + DIRTYSOCK_ErrorName(EPROTO), // 71 Protocol error + DIRTYSOCK_ErrorName(EMULTIHOP), // 72 Multihop attempted +#if defined(DIRTYCODE_LINUX) + DIRTYSOCK_ErrorName(EDOTDOT), // 73 RFS specific error +#endif + DIRTYSOCK_ErrorName(EBADMSG), // 74 Not a data message + DIRTYSOCK_ErrorName(EOVERFLOW), // 75 Value too large for defined data type +#if defined(DIRTYCODE_LINUX) + DIRTYSOCK_ErrorName(ENOTUNIQ), // 76 Name not unique on network + DIRTYSOCK_ErrorName(EBADFD), // 77 File descriptor in bad state + DIRTYSOCK_ErrorName(EREMCHG), // 78 Remote address changed + DIRTYSOCK_ErrorName(ELIBACC), // 79 Can not access a needed shared library + DIRTYSOCK_ErrorName(ELIBBAD), // 80 Accessing a corrupted shared library + DIRTYSOCK_ErrorName(ELIBSCN), // 81 .lib section in a.out corrupted + DIRTYSOCK_ErrorName(ELIBMAX), // 82 Attempting to link in too many shared libraries + DIRTYSOCK_ErrorName(ELIBEXEC), // 83 Cannot exec a shared library directly +#endif + DIRTYSOCK_ErrorName(EILSEQ), // 84 Illegal byte sequence +#if defined(DIRTYCODE_LINUX) + DIRTYSOCK_ErrorName(ERESTART), // 85 Interrupted system call should be restarted + DIRTYSOCK_ErrorName(ESTRPIPE), // 86 Streams pipe error +#endif + DIRTYSOCK_ErrorName(EUSERS), // 87 Too many users + DIRTYSOCK_ErrorName(ENOTSOCK), // 88 Socket operation on non-socket + DIRTYSOCK_ErrorName(EDESTADDRREQ), // 89 Destination address required + DIRTYSOCK_ErrorName(EMSGSIZE), // 90 Message too long + DIRTYSOCK_ErrorName(EPROTOTYPE), // 91 Protocol wrong type for socket + DIRTYSOCK_ErrorName(ENOPROTOOPT), // 92 Protocol not available + DIRTYSOCK_ErrorName(EPROTONOSUPPORT), // 93 Protocol not supported + DIRTYSOCK_ErrorName(ESOCKTNOSUPPORT), // 94 Socket type not supported + DIRTYSOCK_ErrorName(EOPNOTSUPP), // 95 Operation not supported on transport endpoint + DIRTYSOCK_ErrorName(EPFNOSUPPORT), // 96 Protocol family not supported + DIRTYSOCK_ErrorName(EAFNOSUPPORT), // 97 Address family not supported by protocol + DIRTYSOCK_ErrorName(EADDRINUSE), // 98 Address already in use + DIRTYSOCK_ErrorName(EADDRNOTAVAIL), // 99 Cannot assign requested address + DIRTYSOCK_ErrorName(ENETDOWN), // 100 Network is down + DIRTYSOCK_ErrorName(ENETUNREACH), // 101 Network is unreachable + DIRTYSOCK_ErrorName(ENETRESET), // 102 Network dropped connection because of reset + DIRTYSOCK_ErrorName(ECONNABORTED), // 103 Software caused connection abort + DIRTYSOCK_ErrorName(ECONNRESET), // 104 Connection reset by peer + DIRTYSOCK_ErrorName(ENOBUFS), // 105 No buffer space available + DIRTYSOCK_ErrorName(EISCONN), // 106 Transport endpoint is already connected + DIRTYSOCK_ErrorName(ENOTCONN), // 107 Transport endpoint is not connected + DIRTYSOCK_ErrorName(ESHUTDOWN), // 108 Cannot send after transport endpoint shutdown + DIRTYSOCK_ErrorName(ETOOMANYREFS), // 109 Too many references: cannot splice + DIRTYSOCK_ErrorName(ETIMEDOUT), // 110 Connection timed out + DIRTYSOCK_ErrorName(ECONNREFUSED), // 111 Connection refused + DIRTYSOCK_ErrorName(EHOSTDOWN), // 112 Host is down + DIRTYSOCK_ErrorName(EHOSTUNREACH), // 113 No route to host + DIRTYSOCK_ErrorName(EALREADY), // 114 Operation already in progress + DIRTYSOCK_ErrorName(EINPROGRESS), // 115 Operation now in progress + DIRTYSOCK_ErrorName(ESTALE), // 116 Stale NFS file handle +#if defined(DIRTYCODE_LINUX) + DIRTYSOCK_ErrorName(EUCLEAN), // 117 Structure needs cleaning + DIRTYSOCK_ErrorName(ENOTNAM), // 118 Not a XENIX named type file + DIRTYSOCK_ErrorName(ENAVAIL), // 119 No XENIX semaphores available + DIRTYSOCK_ErrorName(EISNAM), // 120 Is a named type file + DIRTYSOCK_ErrorName(EREMOTEIO), // 121 Remote I/O error +#endif + DIRTYSOCK_ErrorName(EDQUOT), // 122 Quota exceeded + +#if defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_APPLEOSX) + DIRTYSOCK_ErrorName(errSecUnimplemented), // -4 Function or operation not implemented. + DIRTYSOCK_ErrorName(errSecIO), // -35 I/O error (bummers) + DIRTYSOCK_ErrorName(errSecParam), // -50 One or more parameters passed to a function were not valid. + DIRTYSOCK_ErrorName(errSecAllocate), // -108 Failed to allocate memory. + DIRTYSOCK_ErrorName(errSecUserCanceled), // -128 User canceled the operation. + DIRTYSOCK_ErrorName(errSecBadReq), // -909 Bad parameter or invalid state for operation. + + DIRTYSOCK_ErrorName(errSSLProtocol), // -9800 SSL protocol error + DIRTYSOCK_ErrorName(errSSLNegotiation), // -9801 Cipher Suite negotiation failure + DIRTYSOCK_ErrorName(errSSLFatalAlert), // -9802 Fatal alert + DIRTYSOCK_ErrorName(errSSLWouldBlock), // -9803 I/O would block (not fatal) + DIRTYSOCK_ErrorName(errSSLSessionNotFound), // -9804 attempt to restore an unknown session + DIRTYSOCK_ErrorName(errSSLClosedGraceful), // -9805 connection closed gracefully + DIRTYSOCK_ErrorName(errSSLClosedAbort), // -9806 connection closed via error + DIRTYSOCK_ErrorName(errSSLXCertChainInvalid), // -9806 invalid certificate chain + DIRTYSOCK_ErrorName(errSSLBadCert), // -9808 bad certificate format + DIRTYSOCK_ErrorName(errSSLCrypto), // -9809 underlying cryptographic error + DIRTYSOCK_ErrorName(errSSLInternal), // -9810 Internal error + DIRTYSOCK_ErrorName(errSSLModuleAttach), // -9811 module attach failure + DIRTYSOCK_ErrorName(errSSLUnknownRootCert), // -9812 valid cert chain, untrusted root + DIRTYSOCK_ErrorName(errSSLNoRootCert), // -9813 cert chain not verified by root + DIRTYSOCK_ErrorName(errSSLCertExpired), // -9814 chain had an expired cert + DIRTYSOCK_ErrorName(errSSLCertNotYetValid), // -9815 chain had a cert not yet valid + DIRTYSOCK_ErrorName(errSSLClosedNoNotify), // -9816 server closed session with no notification + DIRTYSOCK_ErrorName(errSSLBufferOverflow), // -9817 insufficient buffer provided + DIRTYSOCK_ErrorName(errSSLBadCipherSuite), // -9818 bad SSLCipherSuite + + DIRTYSOCK_ErrorName(errSSLPeerUnexpectedMsg), // -9819 unexpected message received + DIRTYSOCK_ErrorName(errSSLPeerBadRecordMac), // -9820 bad MAC + DIRTYSOCK_ErrorName(errSSLPeerDecryptionFail), // -9821 decryption failed + DIRTYSOCK_ErrorName(errSSLPeerRecordOverflow), // -9822 record overflow + DIRTYSOCK_ErrorName(errSSLPeerDecompressFail), // -9823 decompression failure + DIRTYSOCK_ErrorName(errSSLPeerHandshakeFail), // -9824 handshake failure + DIRTYSOCK_ErrorName(errSSLPeerBadCert), // -9825 misc. bad certificate + DIRTYSOCK_ErrorName(errSSLPeerUnsupportedCert), // -9826 bad unsupported cert format + DIRTYSOCK_ErrorName(errSSLPeerCertRevoked), // -9827 certificate revoked + DIRTYSOCK_ErrorName(errSSLPeerCertExpired), // -9828 certificate expired + DIRTYSOCK_ErrorName(errSSLPeerCertUnknown), // -9829 unknown certificate + DIRTYSOCK_ErrorName(errSSLIllegalParam), // -9830 illegal parameter + DIRTYSOCK_ErrorName(errSSLPeerUnknownCA), // -9831 unknown Cert Authority + DIRTYSOCK_ErrorName(errSSLPeerAccessDenied), // -9832 access denied + DIRTYSOCK_ErrorName(errSSLPeerDecodeError), // -9833 decoding error + DIRTYSOCK_ErrorName(errSSLPeerDecryptError), // -9834 decryption error + DIRTYSOCK_ErrorName(errSSLPeerExportRestriction), // -9835 export restriction + DIRTYSOCK_ErrorName(errSSLPeerProtocolVersion), // -9836 bad protocol version + DIRTYSOCK_ErrorName(errSSLPeerInsufficientSecurity),// -9837 insufficient security + DIRTYSOCK_ErrorName(errSSLPeerInternalError), // -9838 internal error + DIRTYSOCK_ErrorName(errSSLPeerUserCancelled), // -9839 user canceled + DIRTYSOCK_ErrorName(errSSLPeerNoRenegotiation), // -9840 no renegotiation allowed + + DIRTYSOCK_ErrorName(errSSLPeerAuthCompleted), // -9841 peer cert is valid, or was ignored if verification disabled + DIRTYSOCK_ErrorName(errSSLClientCertRequested), // -9842 server has requested a client cert + + DIRTYSOCK_ErrorName(errSSLHostNameMismatch), // -9843 peer host name mismatch + DIRTYSOCK_ErrorName(errSSLConnectionRefused), // -9844 peer dropped connection before responding + DIRTYSOCK_ErrorName(errSSLDecryptionFail), // -9845 decryption failure + DIRTYSOCK_ErrorName(errSSLBadRecordMac), // -9846 bad MAC + DIRTYSOCK_ErrorName(errSSLRecordOverflow), // -9847 record overflow + DIRTYSOCK_ErrorName(errSSLBadConfiguration), // -9848 configuration error + DIRTYSOCK_ErrorName(errSSLUnexpectedRecord), // -9849 unexpected (skipped) record in DTLS + DIRTYSOCK_ErrorName(errSSLWeakPeerEphemeralDHKey), // -9850 weak ephemeral dh key + + DIRTYSOCK_ErrorName(errSSLClientHelloReceived), // -9851 SNI + + DIRTYSOCK_ErrorName(errSecNotAvailable), // -25291 No keychain is available. You may need to restart your computer. + DIRTYSOCK_ErrorName(errSecAuthFailed), // -25293 The user name or passphrase you entered is not correct. + DIRTYSOCK_ErrorName(errSecDuplicateItem), // -25299 The specified item already exists in the keychain. + DIRTYSOCK_ErrorName(errSecItemNotFound), // -25300 The specified item could not be found in the keychain. + DIRTYSOCK_ErrorName(errSecInteractionNotAllowed), // -25308 User interaction is not allowed. + DIRTYSOCK_ErrorName(errSecDecode), // -26275 Unable to decode the provided data. + + DIRTYSOCK_ErrorName(errSecVerifyFailed), // -67808 A cryptographic verification failure has occured. +#endif + + // NULL terminate + DIRTYSOCK_ListEnd() +}; +#endif + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function DirtyErrNameList + + \Description + This function takes as input a system-specific error code, and either + resolves it to its define name if it is recognized or formats it as a hex + number if not. + + \Input *pBuffer - [out] pointer to output buffer to store result + \Input iBufSize - size of output buffer + \Input uError - error code to format + \Input *pList - error list to use + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void DirtyErrNameList(char *pBuffer, int32_t iBufSize, uint32_t uError, const DirtyErrT *pList) +{ + #if DIRTYSOCK_ERRORNAMES + int32_t iErr; + + for (iErr = 0; pList[iErr].uError != DIRTYSOCK_LISTTERM; iErr++) + { + if (pList[iErr].uError == uError) + { + ds_strnzcpy(pBuffer, pList[iErr].pErrorName, iBufSize); + return; + } + } + #endif + + ds_snzprintf(pBuffer, iBufSize, "0x%08x", uError); +} + +/*F********************************************************************************/ +/*! + \Function DirtyErrName + + \Description + This function takes as input a system-specific error code, and either + resolves it to its define name if it is recognized or formats it as a hex + number if not. + + \Input *pBuffer - [out] pointer to output buffer to store result + \Input iBufSize - size of output buffer + \Input uError - error code to format + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void DirtyErrName(char *pBuffer, int32_t iBufSize, uint32_t uError) +{ + #if DIRTYSOCK_ERRORNAMES + DirtyErrNameList(pBuffer, iBufSize, uError, _DirtyErr_List); + #endif +} + +/*F********************************************************************************/ +/*! + \Function DirtyErrGetNameList + + \Description + This function takes as input a system-specific error code, and either + resolves it to its define name if it is recognized or formats it as a hex + number if not. + + \Input uError - error code to format + \Input *pList - error list to use + + \Output + const char *- pointer to error name or error formatted in hex + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +const char *DirtyErrGetNameList(uint32_t uError, const DirtyErrT *pList) +{ + static char strName[8][64]; + static uint8_t uLast = 0; + char *pName = strName[uLast++]; + if (uLast > 7) uLast = 0; + DirtyErrNameList(pName, sizeof(strName[0]), uError, pList); + return(pName); +} + +/*F********************************************************************************/ +/*! + \Function DirtyErrGetName + + \Description + This function takes as input a system-specific error code, and either + resolves it to its define name if it is recognized or formats it as a hex + number if not. + + \Input uError - error code to format + + \Output + const char *- pointer to error name or error formatted in hex + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +const char *DirtyErrGetName(uint32_t uError) +{ + static char strName[64]; + DirtyErrName(strName, sizeof(strName), uError); + return(strName); +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/unix/dirtylibunix.c b/src/thirdparty/dirtysdk/source/dirtysock/unix/dirtylibunix.c new file mode 100644 index 00000000..67635321 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/unix/dirtylibunix.c @@ -0,0 +1,360 @@ +/*H********************************************************************************/ +/*! + \File dirtylibunix.c + + \Description + Platform-specific support library for network code. Supplies simple time, + debug printing, and mutex functions. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 10/04/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtythread.h" +#include "DirtySDK/dirtysock/netconn.h" + +/*** Defines **********************************************************************/ + + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +// idle thread state +static volatile int32_t g_idlelife = -1; +uint8_t _NetLib_bSingleThreaded = FALSE; + +#if DIRTYCODE_LOGGING +// static printf buffer to avoid using dynamic allocation +static char _NetLib_aPrintfMem[4096]; +#endif + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _NetLibThread + + \Description + Thread to handle special library tasks. + + \Input *pArg - pointer to argument + + \Version 06/21/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _NetLibThread(void *pArg) +{ + char strThreadId[32]; + + // get the thread id + DirtyThreadGetThreadId(strThreadId, sizeof(strThreadId)); + + // show we are running + NetPrintf(("dirtylibunix: idle thread running (thid=%s)\n", strThreadId)); + g_idlelife = 1; + + // run while we have sema + while (g_idlelife == 1) + { + // call idle functions + NetIdleCall(); + + // wait for next tick + usleep(50*1000); + } + + // report termination + NetPrintf(("dirtylibunix: idle thread exiting\n")); + + // show we are dead + g_idlelife = 0; +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function NetLibCreate + + \Description + Initialize the network library module. + + \Input iThreadPrio - priority to start the _NetLibThread with + \Input iThreadStackSize - stack size to start the _NetLibThread with (in bytes) + \Input iThreadCpuAffinity - cpu affinity to start the _NetLibThread with + + \Version 06/21/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void NetLibCreate(int32_t iThreadPrio, int32_t iThreadStackSize, int32_t iThreadCpuAffinity) +{ + int32_t iResult; + + if (iThreadPrio < 0) + { + NetPrintf(("dirtylibunix: starting in single-threaded mode\n")); + _NetLib_bSingleThreaded = TRUE; + } + + // init common functionality + NetLibCommonInit(); + + // init idlelife tracker + g_idlelife = -1; + + // create a worker thread + if (!_NetLib_bSingleThreaded) + { + DirtyThreadConfigT ThreadConfig; + + // configure the threading + ds_memclr(&ThreadConfig, sizeof(ThreadConfig)); + ThreadConfig.pName = "NetLib"; + ThreadConfig.iAffinity = iThreadCpuAffinity; + ThreadConfig.iPriority = iThreadPrio; + ThreadConfig.iVerbosity = 1; + + if ((iResult = DirtyThreadCreate(_NetLibThread, NULL, &ThreadConfig)) == 0) + { + // wait for thread startup + while (g_idlelife == -1) + { + usleep(100); + } + } + else + { + NetPrintf(("dirtylibunix: unable to create idle thread (err=%d)\n", iResult)); + g_idlelife = 0; + } + + } + + #if DIRTYCODE_LOGGING + // set explicit printf buffer to avoid dynamic malloc() use + setvbuf(stdout, _NetLib_aPrintfMem, _IOLBF, sizeof(_NetLib_aPrintfMem)); + #endif +} + +/*F********************************************************************************/ +/*! + \Function NetLibDestroy + + \Description + Destroy the network library module. + + \Input uShutdownFlags - NET_SHUTDOWN_* flags + + \Version 06/21/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void NetLibDestroy(uint32_t uShutdownFlags) +{ + // signal a shutdown when the thread is running + if ((!_NetLib_bSingleThreaded) && (g_idlelife == 1)) + { + g_idlelife = 2; + + // wait for thread to terminate + while (g_idlelife > 0) + { + usleep(1); + } + } + + // shut down common functionality + NetLibCommonShutdown(); +} + +/*F********************************************************************************/ +/*! + \Function NetTick + + \Description + Return some kind of increasing tick count with millisecond scale (does + not need to have millisecond precision, but higher precision is better). + + \Output + uint32_t - millisecond tick count + + \Version 06/21/2006 (jbrookes) +*/ +/********************************************************************************F*/ +uint32_t NetTick(void) +{ + uint32_t uCurTick; + struct timeval now; + gettimeofday(&now, 0); + uCurTick = (uint32_t)(((uint64_t)now.tv_sec*1000) + (uint64_t)now.tv_usec/1000); + return(uCurTick); +} + +/*F********************************************************************************/ +/*! + \Function NetTickUsec + + \Description + Return increasing tick count in microseconds. Used for performance timing + purposes. + + \Output + uint64_t - microsecond tick count + + \Version 01/30/2015 (jbrookes) +*/ +/********************************************************************************F*/ +uint64_t NetTickUsec(void) +{ + struct timeval now; + gettimeofday(&now, 0); + return(((uint64_t)now.tv_sec*1000000) + (uint64_t)now.tv_usec); +} + +/*F********************************************************************************/ +/*! + \Function NetLocalTime + + \Description + This converts the input GMT time to the local time as specified by the + system clock. This function follows the re-entrant localtime_r function + signature. + + \Input *pTm - storage for localtime output + \Input uElap - GMT time + + \Output + struct tm * - pointer to localtime result + + \Version 04/23/2008 (jbrookes) +*/ +/********************************************************************************F*/ +struct tm *NetLocalTime(struct tm *pTm, uint64_t uElap) +{ + time_t uTimeT = (time_t)uElap; + return(localtime_r(&uTimeT, pTm)); +} + +/*F********************************************************************************/ +/*! + \Function NetPlattimeToTime + + \Description + This converts the input platform-specific time data structure to the + generic time data structure. + + \Input *pTm - generic time data structure to be filled by the function + \Input *pPlatTime - pointer to the platform-specific data structure + + \Output + struct tm * - NULL=failure; else pointer to user-provided generic time data structure + + \Notes + pPlatTime is expected to point to a timespec on Unix platforms + + \Version 05/08/2010 (mclouatre) +*/ +/********************************************************************************F*/ +struct tm *NetPlattimeToTime(struct tm *pTm, void *pPlatTime) +{ + #if defined(DIRTYCODE_LINUX) || defined(DIRTYCODE_ANDROID) + struct timespec timeSpec = *(struct timespec *)pPlatTime; + localtime_r(&timeSpec.tv_sec, pTm); + #elif defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_APPLEOSX) + struct timeval timeVal = *(struct timeval *)pPlatTime; + localtime_r(&timeVal.tv_sec, pTm); + #else + pTm = NULL; + #endif + + return(pTm); +} + +/*F********************************************************************************/ +/*! + \Function NetPlattimeToTimeMs + + \Description + This function retrieves the current date time and fills in the + generic time data structure prrovided. It has the option of returning millisecond + which is not part of the generic time data structure + + \Input *pTm - generic time data structure to be filled by the function + \Input *pImSec - output param for milisecond to be filled by the function (optional can be NULL) + + \Output + struct tm * - NULL=failure; else pointer to user-provided generic time data structure + + \Version 09/16/2014 (tcho) +*/ +/********************************************************************************F*/ +struct tm *NetPlattimeToTimeMs(struct tm *pTm , int32_t *pImSec) +{ + void *pPlatTime; + int32_t iMsec; + + #if defined(DIRTYCODE_LINUX) || defined(DIRTYCODE_ANDROID) + struct timespec timeSpec; + clock_gettime(CLOCK_REALTIME, &timeSpec); + iMsec = timeSpec.tv_nsec/1000000; + pPlatTime = (void *)&timeSpec; + #elif defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_APPLEOSX) + struct timeval timeVal; + gettimeofday(&timeVal, NULL); + + iMsec = timeVal.tv_usec/1000; + pPlatTime = (void*)&timeVal; + #else + return (NULL); + #endif + + if (pImSec != NULL) + { + *pImSec = iMsec; + } + + if (pTm == NULL) + { + return(NULL); + } + + return(NetPlattimeToTime(pTm, pPlatTime)); +} + +/*F********************************************************************************/ +/*! + \Function NetTime + + \Description + This function replaces the standard library time() function. Main + differences are the missing pointer parameter (not needed) and the uint64_t + return value. The function returns 0 on unsupported platforms vs time which + returns -1. + + \Output + uint64_t - number of elapsed seconds since Jan 1, 1970. + + \Version 01/12/2005 (gschaefer) +*/ +/********************************************************************************F*/ +uint64_t NetTime(void) +{ + return((uint64_t)time(NULL)); +} diff --git a/src/thirdparty/dirtysdk/source/dirtysock/unix/dirtynetunix.c b/src/thirdparty/dirtysdk/source/dirtysock/unix/dirtynetunix.c new file mode 100644 index 00000000..c0c2332a --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/unix/dirtynetunix.c @@ -0,0 +1,3513 @@ +/*H********************************************************************************/ +/*! + \File dirtynetunix.c + + \Description + Provides a wrapper that translates the Unix network interface to the + DirtySock portable networking interface. + + \Copyright + Copyright (c) 2010 Electronic Arts Inc. + + \Version 04/05/2010 (jbrookes) First version; a vanilla port to Linux from PS3 +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DirtySDK/platform.h" + +#if defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_APPLEOSX) + #include + #include + #include + #include +#else + #include + #include // struct ifreq + #include // ioctl +#endif + +#if defined(DIRTYCODE_APPLEIOS) + #include + #include + #include + #include +#endif + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtyvers.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtythread.h" +#include "DirtySDK/dirtysock/netconn.h" + +#include "dirtynetpriv.h" // private include for dirtynet common functions + +/*** Defines **********************************************************************/ + +#define INVALID_SOCKET (-1) + +#define SOCKET_MAXPOLL (32) + +#define SOCKET_VERBOSE (DIRTYCODE_DEBUG && FALSE) + +/*** Type Definitions *************************************************************/ + +//! private socketlookup structure containing extra data +typedef struct SocketLookupPrivT +{ + HostentT Host; //!< must come first! +} SocketLookupPrivT; + +//! dirtysock connection socket structure +struct SocketT +{ + SocketT *pNext; //!< link to next active + SocketT *pKill; //!< link to next killed socket + + int32_t iFamily; //!< protocol family + int32_t iType; //!< protocol type + int32_t iProto; //!< protocol ident + + int8_t iOpened; //!< negative=error, zero=not open (connecting), positive=open + uint8_t bImported; //!< whether socket was imported or not + uint8_t bVirtual; //!< if true, socket is virtual + uint8_t bHasData; //!< zero if no data, else has data ready to be read + uint8_t bInCallback; //!< in a socket callback + uint8_t uBrokenFlag; //!< 0 or 1=might not be broken, >1=broken socket + uint8_t bAsyncRecv; //!< if true, async recv is enabled + uint8_t bSendCbs; //!< TRUE if send cbs are enabled, false otherwise + int8_t iVerbose; //!< debug level + uint8_t __pad[3]; + + int32_t uSocket; //!< unix socket ref + int32_t iLastError; //!< last socket error + + struct sockaddr LocalAddr; //!< local address + struct sockaddr RemoteAddr; //!< remote address + + uint16_t uVirtualPort; //!< virtual port, if set + uint16_t uPollIdx; //!< index in blocking poll() operation + + SocketRateT SendRate; //!< send rate estimation data + SocketRateT RecvRate; //!< recv rate estimation data + + int32_t iCallMask; //!< valid callback events + uint32_t uCallLast; //!< last callback tick + uint32_t uCallIdle; //!< ticks between idle calls + void *pCallRef; //!< reference calback value + int32_t (*pCallback)(SocketT *pSocket, int32_t iFlags, void *pRef); + + NetCritT RecvCrit; //!< receive critical section + int32_t iRecvErr; //!< last error that occurred + uint32_t uRecvFlag; //!< flags from recv operation + int32_t iRbufSize; //!< read buffer size (bytes) + int32_t iSbufSize; //!< send buffer size (bytes) + + struct sockaddr RecvAddr; //!< receive address + + struct sockaddr_in6 RecvAddr6; //!< receive address (ipv6) + + SocketPacketQueueT *pRecvQueue; + SocketPacketQueueEntryT *pRecvPacket; +}; + +//! standard ipv4 packet header (see RFC791) +typedef struct HeaderIpv4 +{ + uint8_t verslen; //!< version and length fields (4 bits each) + uint8_t service; //!< type of service field + uint8_t length[2]; //!< total packet length (header+data) + uint8_t ident[2]; //!< packet sequence number + uint8_t frag[2]; //!< fragmentation information + uint8_t time; //!< time to live (remaining hop count) + uint8_t proto; //!< transport protocol number + uint8_t check[2]; //!< header checksum + uint8_t srcaddr[4]; //!< source ip address + uint8_t dstaddr[4]; //!< dest ip address +} HeaderIpv4; + +//! local state +typedef struct SocketStateT +{ + SocketT *pSockList; //!< master socket list + SocketT *pSockKill; //!< list of killed sockets + HostentT *pHostList; //!< list of ongoing name resolution operations + + uint16_t aVirtualPorts[SOCKET_MAXVIRTUALPORTS]; //!< virtual port list + + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + uint32_t uConnStatus; //!< current connection status + uint32_t uLocalAddr; //!< local internet address for active interface + int32_t iMaxPacket; //!< maximum packet size + + uint8_t aMacAddr[6]; //!< MAC address for active interface + uint8_t bSingleThreaded; //!< TRUE if in single-threaded mode + int8_t iVerbose; //!< debug output verbosity + + volatile int32_t iRecvLife; + + SocketAddrMapT AddrMap; //!< address map for translating ipv6 addresses to ipv4 virtual addresses and back + + SocketHostnameCacheT *pHostnameCache; //!< hostname cache + + SocketSendCallbackEntryT aSendCbEntries[SOCKET_MAXSENDCALLBACKS]; //!< collection of send callbacks +} SocketStateT; + +/*** Variables ********************************************************************/ + +//! module state ref +static SocketStateT *_Socket_pState = NULL; + +#if DIRTYSOCK_ERRORNAMES +//! getaddrinfo() error result table +static const DirtyErrT _GAI_ErrList[] = +{ + DIRTYSOCK_ErrorName(EAI_BADFLAGS), // -1; Invalid value for 'ai_flags' field. + DIRTYSOCK_ErrorName(EAI_NONAME), // -2; NAME or SERVICE is unknown. + DIRTYSOCK_ErrorName(EAI_AGAIN), // -3; Temporary failure in name resolution. + DIRTYSOCK_ErrorName(EAI_FAIL), // -4; Non-recoverable failure in name res. + #if defined(__USE_GNU) || DIRTYCODE_ANDROID + DIRTYSOCK_ErrorName(EAI_NODATA), // -5: No address associated with NAME. + #endif + DIRTYSOCK_ErrorName(EAI_FAMILY), // -6; 'ai_family' not supported. + DIRTYSOCK_ErrorName(EAI_SOCKTYPE), // -7; 'ai_socktype' not supported. + DIRTYSOCK_ErrorName(EAI_SERVICE), // -8; SERVICE not supported for 'ai_socktype'. + #if defined(__USE_GNU) || DIRTYCODE_ANDROID + DIRTYSOCK_ErrorName(EAI_ADDRFAMILY), // -9; Address family for NAME not supported. + #endif + DIRTYSOCK_ErrorName(EAI_MEMORY), // -10; Memory allocation failure. + DIRTYSOCK_ErrorName(EAI_SYSTEM), // -11; System error returned in `errno'. + DIRTYSOCK_ErrorName(EAI_OVERFLOW), // -12; Argument buffer overflow. + #if DIRTYCODE_LINUX && defined(__USE_GNU) + DIRTYSOCK_ErrorName(EAI_INPROGRESS), // -100; Processing request in progress. + DIRTYSOCK_ErrorName(EAI_CANCELED), // -101; Request canceled. + DIRTYSOCK_ErrorName(EAI_NOTCANCELED), // -102; Request not canceled. + DIRTYSOCK_ErrorName(EAI_ALLDONE), // -103; All requests done. + DIRTYSOCK_ErrorName(EAI_INTR), // -104; Interrupted by a signal. + DIRTYSOCK_ErrorName(EAI_IDN_ENCODE), // -105; IDN encoding failed. + #endif + DIRTYSOCK_ListEnd() +}; +#endif + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _SocketTranslateError2 + + \Description + Translate a BSD error to dirtysock + + \Input iErr - BSD error code + \Input iErrno - errno or override of the value + + \Output + int32_t - dirtysock error (SOCKERR_*) + + \Version 06/21/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _SocketTranslateError2(int32_t iErr, int32_t iErrno) +{ + if (iErr < 0) + { + iErr = iErrno; + if ((iErr == EWOULDBLOCK) || (iErr == EINPROGRESS)) + iErr = SOCKERR_NONE; + else if (iErr == EHOSTUNREACH) + iErr = SOCKERR_UNREACH; + else if (iErr == ENOTCONN) + iErr = SOCKERR_NOTCONN; + else if (iErr == ECONNREFUSED) + iErr = SOCKERR_REFUSED; + else if (iErr == ECONNRESET) + iErr = SOCKERR_CONNRESET; + else if ((iErr == EBADF) || (iErr == EPIPE)) + iErr = SOCKERR_BADPIPE; + else + iErr = SOCKERR_OTHER; + } + return(iErr); +} + +/*F********************************************************************************/ +/*! + \Function _SocketTranslateError + + \Description + Translate a BSD error to dirtysock + + \Input iErr - BSD error code + + \Output + int32_t - dirtysock error (SOCKERR_*) + + \Version 06/19/2020 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _SocketTranslateError(int32_t iErr) +{ + return(_SocketTranslateError2(iErr, errno)); +} + +#if defined(DIRTYCODE_LINUX) || defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_ANDROID) +/*F********************************************************************************/ +/*! + \Function _SocketDisableSigpipe + + \Description + Disable SIGPIPE signal. + + \Version 12/08/2003 (sbevan) +*/ +/********************************************************************************F*/ +static void _SocketDisableSigpipe(void) +{ + struct sigaction sa; + ds_memclr(&sa, sizeof(sa)); + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGPIPE, &sa, 0); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _SocketCreateSocket + + \Description + Create a system level socket. + + \Input iAddrFamily - address family (AF_INET) + \Input iType - socket type (SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, ...) + \Input iProto - protocol type for SOCK_RAW (unused by others) + + \Output + int32_t - socket handle + + \Version 16/02/2012 (szhu) +*/ +/********************************************************************************F*/ +static int32_t _SocketCreateSocket(int32_t iAddrFamily, int32_t iType, int32_t iProto) +{ + int32_t iSocket; + // create socket + if ((iSocket = socket(iAddrFamily, iType, iProto)) >= 0) + { + const uint32_t uTrue = 1, uFalse = 0; + // if dgram, allow broadcast + if (iType == SOCK_DGRAM) + { + setsockopt(iSocket, SOL_SOCKET, SO_BROADCAST, &uTrue, sizeof(uTrue)); + } + // if a raw socket, set header include + if (iType == SOCK_RAW) + { + setsockopt(iSocket, IPPROTO_IP, IP_HDRINCL, &uTrue, sizeof(uTrue)); + } + // set nonblocking operation + if (fcntl(iSocket, F_SETFL, O_NONBLOCK) < 0) + { + NetPrintf(("dirtynetunix: error trying to make socket non-blocking (err=%d)\n", errno)); + } + // disable IPV6 only + setsockopt(iSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&uFalse, sizeof(uFalse)); + + #if defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_APPLEOSX) + // ignore SIGPIPE; we ignore this signal globally, but that doesn't work on iOS/MacX + if (setsockopt(iSocket, SOL_SOCKET, SO_NOSIGPIPE, &uTrue, sizeof(uTrue)) < 0) + { + NetPrintf(("dirtynetunix: unable to set NOSIGPIPE on socket (err=%s)\n", DirtyErrGetName(errno))); + } + #endif + } + else + { + NetPrintf(("dirtynetunix: socket() failed (err=%s)\n", DirtyErrGetName(errno))); + } + return(iSocket); +} + +/*F********************************************************************************/ +/*! + \Function _SocketOpen + + \Description + Create a new transfer endpoint. A socket endpoint is required for any + data transfer operation. If iSocket != -1 then used existing socket. + + \Input iSocket - Socket descriptor to use or -1 to create new + \Input iAddrFamily - address family (AF_INET) + \Input iType - socket type (SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, ...) + \Input iProto - protocol type for SOCK_RAW (unused by others) + \Input iOpened - 0=not open (connecting), 1=open + + \Output + SocketT * - socket reference + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static SocketT *_SocketOpen(int32_t iSocket, int32_t iAddrFamily, int32_t iType, int32_t iProto, int32_t iOpened) +{ + SocketStateT *pState = _Socket_pState; + const int32_t iQueueSize = (iType != SOCK_STREAM) ? 1 : 8; + SocketT *pSocket; + + // allocate memory + if ((pSocket = DirtyMemAlloc(sizeof(*pSocket), SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynetunix: unable to allocate memory for socket\n")); + return(NULL); + } + ds_memclr(pSocket, sizeof(*pSocket)); + + // open a socket (force to AF_INET6 if we are opening it) + if ((iSocket == -1) && ((iSocket = _SocketCreateSocket(iAddrFamily = AF_INET6, iType, iProto)) < 0)) + { + NetPrintf(("dirtynetunix: error %s creating socket\n", DirtyErrGetName(iSocket))); + DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + return(NULL); + } + + // create packet queue + if ((pSocket->pRecvQueue = SocketPacketQueueCreate(iQueueSize, pState->iMemGroup, pState->pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynetunix: failed to create socket queue for socket\n")); + close(iSocket); + DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + return(NULL); + } + + // set family/proto info + pSocket->iFamily = iAddrFamily; + pSocket->iType = iType; + pSocket->iProto = iProto; + pSocket->uSocket = iSocket; + pSocket->iOpened = iOpened; + pSocket->iLastError = SOCKERR_NONE; + pSocket->bAsyncRecv = ((pState->bSingleThreaded == FALSE) && ((iType == SOCK_DGRAM) || (iType == SOCK_RAW))) ? TRUE : FALSE; + pSocket->bSendCbs = TRUE; + pSocket->iVerbose = 1; + + // inititalize critical section + NetCritInit(&pSocket->RecvCrit, "inet-recv"); + + // install into list + NetCritEnter(NULL); + pSocket->pNext = pState->pSockList; + pState->pSockList = pSocket; + NetCritLeave(NULL); + + // return the socket + return(pSocket); +} + +/*F********************************************************************************/ +/*! + \Function _SocketReopen + + \Description + Recreate a socket endpoint. + + \Input *pSocket - socket ref + + \Output + SocketT * - socket ref on success, NULL for error + + \Notes + This function should not be called from the async/idle thread, + so socket recreation cannot happen in SocketRecvfrom. + + \Version 16/02/2012 (szhu) +*/ +/********************************************************************************F*/ +static SocketT *_SocketReopen(SocketT *pSocket) +{ + // we need recvcrit to prevent the socket from being used by the async/idle thread + // for non-virtual non-tcp sockets only + if (!pSocket->bVirtual && ((pSocket->iType == SOCK_DGRAM) || (pSocket->iType == SOCK_RAW))) + { + SocketT *pResult = NULL; + // acquire socket receive critical section + NetCritEnter(&pSocket->RecvCrit); + NetPrintf(("dirtynetunix: trying to recreate the socket handle for %x->%d\n", pSocket, pSocket->uSocket)); + + // close existing socket handle + if (pSocket->uSocket != INVALID_SOCKET) + { + close(pSocket->uSocket); + pSocket->uSocket = INVALID_SOCKET; + } + + // create socket + if ((pSocket->uSocket = _SocketCreateSocket(pSocket->iFamily, pSocket->iType, pSocket->iProto)) >= 0) + { + // set socket buffer size + if (pSocket->iRbufSize > 0) + { + SocketControl(pSocket, 'rbuf', pSocket->iRbufSize, NULL, 0); + } + if (pSocket->iSbufSize > 0) + { + SocketControl(pSocket, 'sbuf', pSocket->iSbufSize, NULL, 0); + } + // bind if previous socket was bound to a specific port + if (SockaddrInGetPort(&pSocket->LocalAddr) != 0) + { + int32_t iResult; + // always set reuseaddr flag for socket recreation + SocketControl(pSocket, 'radr', 1, NULL, 0); + // we don't call SocketBind() here (to avoid virtual socket translation) + if ((iResult = bind(pSocket->uSocket, &pSocket->LocalAddr, sizeof(pSocket->LocalAddr))) < 0) + { + pSocket->iLastError = _SocketTranslateError(iResult); + NetPrintf(("dirtynetunix: bind() to %a:%d failed (err=%s)\n", + SockaddrInGetAddr(&pSocket->LocalAddr), + SockaddrInGetPort(&pSocket->LocalAddr), + DirtyErrGetName(errno))); + } + } + // connect if previous socket was connected to a specific remote + if (SockaddrInGetPort(&pSocket->RemoteAddr) != 0) + { + struct sockaddr SockAddr; + // make a copy of remote + ds_memcpy_s(&SockAddr, sizeof(SockAddr), &pSocket->RemoteAddr, sizeof(pSocket->RemoteAddr)); + SocketConnect(pSocket, &SockAddr, sizeof(SockAddr)); + } + // success + pSocket->uBrokenFlag = 0; + pResult = pSocket; + NetPrintf(("dirtynetunix: socket recreation (%x->%d) succeeded.\n", pSocket, pSocket->uSocket)); + } + else + { + pSocket->iLastError = _SocketTranslateError(pSocket->uSocket); + NetPrintf(("dirtynetunix: socket recreation (%x) failed.\n", pSocket)); + } + + // release socket receive critical section + NetCritLeave(&pSocket->RecvCrit); + return(pResult); + } + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function _SocketClose + + \Description + Disposes of a SocketT, including disposal of the SocketT allocated memory. Does + NOT dispose of the unix socket ref. + + \Input *pSocket - socket to close + + \Output + int32_t - negative=error, else zero + + \Version 06/21/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _SocketClose(SocketT *pSocket) +{ + SocketStateT *pState = _Socket_pState; + uint32_t bSockInList; + SocketT **ppSocket; + + // remove sock from linked list + NetCritEnter(NULL); + for (ppSocket = &pState->pSockList, bSockInList = FALSE; *ppSocket != NULL; ppSocket = &(*ppSocket)->pNext) + { + if (*ppSocket == pSocket) + { + *ppSocket = pSocket->pNext; + bSockInList = TRUE; + break; + } + } + NetCritLeave(NULL); + + // make sure the socket is in the socket list (and therefore valid) + if (bSockInList == FALSE) + { + NetPrintf(("dirtynetunix: warning, trying to close socket 0x%08x that is not in the socket list\n", (intptr_t)pSocket)); + return(-1); + } + + // finish any idle call + NetIdleDone(); + + // mark as closed + pSocket->uSocket = INVALID_SOCKET; + pSocket->iOpened = FALSE; + + // kill critical section + NetCritKill(&pSocket->RecvCrit); + + // destroy packet queue + if (pSocket->pRecvQueue != NULL) + { + SocketPacketQueueDestroy(pSocket->pRecvQueue); + } + + // put into killed list + NetCritEnter(NULL); + pSocket->pKill = pState->pSockKill; + pState->pSockKill = pSocket; + NetCritLeave(NULL); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _SocketIdle + + \Description + Call idle processing code to give connections time. + + \Input *pData - pointer to socket state + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SocketIdle(void *pData) +{ + SocketStateT *pState = (SocketStateT *)pData; + SocketT *pSocket; + uint32_t uTick; + + // for access to g_socklist and g_sockkill + NetCritEnter(NULL); + + // get current tick + uTick = NetTick(); + + // walk socket list and perform any callbacks + for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext) + { + // see if we should do callback + if ((pSocket->uCallIdle != 0) && + (pSocket->pCallback != NULL) && + (!pSocket->bInCallback) && + (NetTickDiff(uTick, pSocket->uCallLast) > (signed)pSocket->uCallIdle)) + { + pSocket->bInCallback = TRUE; + (pSocket->pCallback)(pSocket, 0, pSocket->pCallRef); + pSocket->bInCallback = FALSE; + pSocket->uCallLast = uTick = NetTick(); + } + } + + // delete any killed sockets + while ((pSocket = pState->pSockKill) != NULL) + { + pState->pSockKill = pSocket->pKill; + DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + + // process dns cache list, delete expired entries + SocketHostnameCacheProcess(pState->pHostnameCache, pState->iVerbose); + + // process hostname list, delete completed lookup requests + SocketHostnameListProcess(&pState->pHostList, pState->iMemGroup, pState->pMemGroupUserData); + + // for access to g_socklist and g_sockkill + NetCritLeave(NULL); +} + +/*F*************************************************************************************/ +/*! + \Function _SocketRecvfromPacketQueue + + \Description + Check if there is a pending inbound packet in the socket packet queue. + + \Input *pSocket - pointer to socket + \Input *pBuf - [out] buffer to receive data + \Input iLen - length of recv buffer + \Input *pFrom - [out] address data was received from (NULL=ignore) + \Input *pFromLen - [out] length of address + + \Output + int32_t - size of packet extracted from queue, 0 if no packet + + \Version 04/20/2016 (mclouatre) +*/ +/************************************************************************************F*/ +static int32_t _SocketRecvfromPacketQueue(SocketT *pSocket, const char *pBuf, int32_t iLen, struct sockaddr *pFrom, int32_t *pFromLen) +{ + int32_t iResult = 0; + + // make sure destination buffer is valid + if ((iLen > 0) && (pBuf != NULL)) + { + // get a packet + if (pSocket->iType != SOCK_STREAM) + { + iResult = SocketPacketQueueRem(pSocket->pRecvQueue, (uint8_t *)pBuf, iLen, &pSocket->RecvAddr); + } + else + { + iResult = SocketPacketQueueRemStream(pSocket->pRecvQueue, (uint8_t *)pBuf, iLen); + } + + if (iResult > 0) + { + if (pFrom != NULL) + { + ds_memcpy_s(pFrom, sizeof(*pFrom), &pSocket->RecvAddr, sizeof(pSocket->RecvAddr)); + *pFromLen = sizeof(*pFrom); + } + } + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _SocketPoll + + \Description + Execute a blocking poll on input of all sockets. + + \Input pState - pointer to module state + \Input uPollNsec - maximum number of nanoseconds to block in poll + + \Output + int32_t - result of the ppoll call + + \Version 04/02/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _SocketPoll(SocketStateT *pState, uint32_t uPollNsec) +{ + struct pollfd aPollFds[1024]; + #if defined(DIRTYCODE_APPLEOSX) || defined(DIRTYCODE_APPLEIOS) + int32_t iPollTime = (int32_t)(uPollNsec/1000000); + #else + struct timespec PollTime = { .tv_sec = uPollNsec/1000000000, .tv_nsec = uPollNsec%1000000000 }; + #endif + int32_t iPoll, iMaxPoll, iResult; + SocketT *pSocket; + + // for access to socket list + NetCritEnter(NULL); + + // walk socket list and find matching socket + for (pSocket = pState->pSockList, iPoll = 0, iMaxPoll = 1024; (pSocket != NULL) && (iPoll < iMaxPoll); pSocket = pSocket->pNext) + { + // skip invalid sockets or sockets we have an error on + if ((pSocket->uSocket == INVALID_SOCKET) || (pSocket->bHasData & 0x80)) + { + pSocket->uPollIdx = iMaxPoll; // mark the socket as 'not in the poll array' + continue; + } + + // add socket to poll array + aPollFds[iPoll].fd = pSocket->uSocket; + aPollFds[iPoll].events = POLLIN; + aPollFds[iPoll].revents = 0; + + // remember poll index + pSocket->uPollIdx = iPoll++; + } + + // release critical section + NetCritLeave(NULL); + + // execute the poll + #if defined(DIRTYCODE_APPLEOSX) || defined(DIRTYCODE_APPLEIOS) + iResult = poll(aPollFds, iPoll, iPollTime); + #else + iResult = ppoll(aPollFds, iPoll, &PollTime, NULL); + #endif + + // if any sockets have pending data, figure out which ones + if (iResult > 0) + { + uint32_t uCurTick; + + // re-acquire critical section + NetCritEnter(NULL); + + // update sockets if there is data to be read or not + for (pSocket = pState->pSockList, uCurTick = NetTick(); pSocket != NULL; pSocket = pSocket->pNext) + { + /* skip socket if it was not in poll array submitted to poll()/ppoll() + only sockets explicitely added to the poll array have uPollIdx in the valid range [0, iMaxPoll[ */ + if (pSocket->uPollIdx >= iMaxPoll) + { + continue; + } + + pSocket->bHasData += aPollFds[pSocket->uPollIdx].revents & POLLIN ? 1 : 0; + if (pSocket->bHasData + && !pSocket->bInCallback + && (pSocket->pCallback != NULL) + && (pSocket->iCallMask & CALLB_RECV)) + { + pSocket->bInCallback = TRUE; + pSocket->pCallback(pSocket, 0, pSocket->pCallRef); + pSocket->bInCallback = FALSE; + pSocket->uCallLast = uCurTick; + } + + // if we have a socket error, remove from future poll events + if (aPollFds[pSocket->uPollIdx].revents & (POLLERR|POLLHUP)) + { + pSocket->bHasData |= 0x80; + } + } + + // release the critical section + NetCritLeave(NULL); + } + else if (iResult < 0) + { + NetPrintf(("dirtynetunix: poll() failed (err=%s)\n", DirtyErrGetName(errno))); + } + + // return number of file descriptors with pending data + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _SocketLookupDone + + \Description + Callback to determine if gethostbyname is complete. + + \Input *pHost - pointer to host lookup record + + \Output + int32_t - zero=in progess, neg=done w/error, pos=done w/success + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _SocketLookupDone(HostentT *pHost) +{ + // return current status + return(pHost->done); +} + +/*F********************************************************************************/ +/*! + \Function _SocketLookupFree + + \Description + Release resources used by SocketLookup() + + \Input *pHost - pointer to host lookup record + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SocketLookupFree(HostentT *pHost) +{ + // release resource + pHost->refcount -= 1; +} + +/*F********************************************************************************/ +/*! + \Function _SocketLookupThread + + \Description + Socket lookup thread + + \Input *_pRef - thread argument (hostent record) + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SocketLookupThread(void *_pRef) +{ + SocketStateT *pState = _Socket_pState; + HostentT *pHost = (HostentT *)_pRef; + int32_t iResult; + struct addrinfo Hints, *pList; + int32_t iPreferredFamily; + char strThreadId[32]; + + // if state is null the module has been shut down + if (pState == NULL) + { + return; + } + + // get the thread id + DirtyThreadGetThreadId(strThreadId, sizeof(strThreadId)); + + // setup lookup hints + ds_memclr(&Hints, sizeof(Hints)); + Hints.ai_family = AF_UNSPEC; + Hints.ai_socktype = SOCK_STREAM; // set specific socktype to limit to one result per type + Hints.ai_protocol = IPPROTO_TCP; // set specific proto to limit to one result per type + Hints.ai_flags = AI_ADDRCONFIG; + + #if !defined(DIRTYCODE_ANDROID) + Hints.ai_flags |= AI_V4MAPPED; + #endif + + #if defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_APPLEOSX) + iPreferredFamily = AF_INET6; + #else + iPreferredFamily = AF_INET; + #endif + + // start lookup + NetPrintf(("dirtynetunix: lookup thread start; name=%s (thid=%s)\n", pHost->name, strThreadId)); + if ((iResult = getaddrinfo(pHost->name, NULL, &Hints, &pList)) == 0) + { + struct addrinfo *pAddrInfo; + + // first loop we look for addresses matching our preferred address family + for (pAddrInfo = pList; pAddrInfo != NULL; pAddrInfo = pAddrInfo->ai_next) + { + // verbose logging of address info if spam settings warrant + NetPrintfVerbose((pState->iVerbose, 1, "dirtynetunix: addr=%A\n", pAddrInfo->ai_addr)); + NetPrintfVerbose((pState->iVerbose, 2, "dirtynetunix: ai_flags=0x%08x\n", pAddrInfo->ai_flags)); + NetPrintfVerbose((pState->iVerbose, 2, "dirtynetunix: ai_family=%d\n", pAddrInfo->ai_family)); + NetPrintfVerbose((pState->iVerbose, 2, "dirtynetunix: ai_socktype=%d\n", pAddrInfo->ai_socktype)); + NetPrintfVerbose((pState->iVerbose, 2, "dirtynetunix: ai_protocol=%d\n", pAddrInfo->ai_protocol)); + NetPrintfVerbose((pState->iVerbose, 2, "dirtynetunix: name=%s\n", pAddrInfo->ai_canonname)); + + // extract first IPv6 address we come across + if ((pHost->addr == 0) && (pAddrInfo->ai_family == iPreferredFamily)) + { + pHost->addr = SocketAddrMapAddress(&pState->AddrMap, pAddrInfo->ai_addr, (int32_t)pAddrInfo->ai_addrlen); + } + } + + // if we haven't found one yet, look for any address, and pick the first one we come across + for (pAddrInfo = pList; (pAddrInfo != NULL) && (pHost->addr == 0); pAddrInfo = pAddrInfo->ai_next) + { + pHost->addr = SocketAddrMapAddress(&pState->AddrMap, pAddrInfo->ai_addr, (int32_t)pAddrInfo->ai_addrlen); + } + + // print selected address + NetPrintf(("dirtynetunix: %s=%a\n", pHost->name, pHost->addr)); + + // mark success + pHost->done = 1; + + // add hostname to cache + SocketHostnameCacheAdd(pState->pHostnameCache, pHost->name, pHost->addr, pState->iVerbose); + + // release memory + freeaddrinfo(pList); + } + else + { + // unsuccessful + NetPrintf(("dirtynetunix: getaddrinfo('%s', ...) failed err=%s\n", pHost->name, DirtyErrGetNameList(iResult, _GAI_ErrList))); + pHost->done = -1; + } + + // note thread completion + pHost->thread = 1; + + NetPrintf(("dirtynetunix: lookup thread exit; name=%s (thid=%s)\n", pHost->name, strThreadId)); + + // release thread-allocated refcount on hostname resource + pHost->refcount -= 1; +} + +/*F********************************************************************************/ +/*! + \Function _SocketRecvfrom + + \Description + Receive data from a remote host on a datagram socket. + + \Input *pSocket - socket reference + \Input *pBuf - buffer to receive data + \Input iLen - length of recv buffer + \Input *pFrom - address data was received from (NULL=ignore) + \Input *pFromLen - length of address + + \Output + int32_t - positive=data bytes received, else error + + \Version 09/10/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _SocketRecvfrom(SocketT *pSocket, char *pBuf, int32_t iLen, struct sockaddr *pFrom, int32_t *pFromLen) +{ + int32_t iResult; + + // make sure socket ref is valid + if (pSocket->uSocket == INVALID_SOCKET) + { + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + + if (pFrom != NULL) + { + // do the receive + struct sockaddr_in6 SockAddr6; + SockaddrInit6(&SockAddr6, AF_INET6); + *pFromLen = sizeof(SockAddr6); + SockaddrInit(pFrom, AF_INET); + if ((iResult = (int32_t)recvfrom(pSocket->uSocket, pBuf, iLen, 0, (struct sockaddr *)&SockAddr6, (socklen_t *)pFromLen)) > 0) + { + SocketAddrMapTranslate(&_Socket_pState->AddrMap, pFrom, (struct sockaddr *)&SockAddr6, pFromLen); + SockaddrInSetMisc(pFrom, NetTick()); + } + } + else + { + iResult = (int32_t)recv(pSocket->uSocket, pBuf, iLen, 0); + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _SocketRecvToPacketQueue + + \Description + Attempt to receive data from the given socket and to push it directly + in the packet queue. + + \Input *pState - pointer to module state + \Input *pSocket - pointer to socket to read from + + \Output + int32_t - receive result + + \Version 10/21/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SocketRecvToPacketQueue(SocketStateT *pState, SocketT *pSocket) +{ + int32_t iRecvResult; + + // early exit if packet queue is full or this is a virtual socket + if (SocketPacketQueueStatus(pSocket->pRecvQueue, 'pful') || (pSocket->bVirtual == TRUE)) + { + return; + } + + // get a packet queue entry to receive into + pSocket->pRecvPacket = SocketPacketQueueAlloc(pSocket->pRecvQueue); + + // try and receive some data + if ((pSocket->iType == SOCK_DGRAM) || (pSocket->iType == SOCK_RAW)) + { + int32_t iFromLen = sizeof(pSocket->RecvAddr); + iRecvResult = _SocketRecvfrom(pSocket, (char *)pSocket->pRecvPacket->aPacketData, sizeof(pSocket->pRecvPacket->aPacketData), &pSocket->RecvAddr, &iFromLen); + } + else + { + iRecvResult = _SocketRecvfrom(pSocket, (char *)pSocket->pRecvPacket->aPacketData, sizeof(pSocket->pRecvPacket->aPacketData), NULL, 0); + } + + // if the read completed successfully, save the originator address, packet size and reception time; forward data to socket callback if needed + if (iRecvResult > 0) + { + pSocket->pRecvPacket->iPacketSize = iRecvResult; + pSocket->pRecvPacket->uPacketTick = NetTick(); + ds_memcpy_s(&pSocket->pRecvPacket->PacketAddr, sizeof(pSocket->pRecvPacket->PacketAddr), &pSocket->RecvAddr, sizeof(pSocket->RecvAddr)); + + // see if we should issue callback + if ((pSocket->uCallLast != (unsigned)-1) && (pSocket->pCallback != NULL) && (pSocket->iCallMask & CALLB_RECV)) + { + pSocket->uCallLast = (unsigned)-1; + (pSocket->pCallback)(pSocket, 0, pSocket->pCallRef); + pSocket->uCallLast = NetTick(); + } + } + else + { + if (errno != EAGAIN) + { + // if we are using a TCP socket and we didn't receive positive bytes, we are closed + if ((pSocket->iType == SOCK_STREAM) && (iRecvResult <= 0)) + { + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetunix: [%p] connection %s\n", pSocket, (iRecvResult == 0) ? "closed" : "failed")); + pSocket->iOpened = -1; + } + else + { + NetPrintf(("dirtynetunix: [%p] _SocketRecvfrom() to packet queue returned %d (err=%s)\n", pSocket, iRecvResult, DirtyErrGetName(errno))); + } + } + + // clean up resources that were reserved for the receive operation + SocketPacketQueueAllocUndo(pSocket->pRecvQueue); + } +} + +/*F********************************************************************************/ +/*! + \Function _SocketRecvThread + + \Description + Wait for incoming data and deliver it immediately to the socket callback, + if registered. + + \Input *pArg - pointer to Socket module state + + \Version 10/21/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SocketRecvThread(void *pArg) +{ + typedef struct PollListT + { + SocketT *aSockets[SOCKET_MAXPOLL]; + struct pollfd aPollFds[SOCKET_MAXPOLL]; + int32_t iCount; + } PollListT; + + PollListT pollList, previousPollList; + SocketT *pSocket; + int32_t iListIndex, iResult; + SocketStateT *pState = (SocketStateT *)pArg; + char strThreadId[32]; + + // get the thread id + DirtyThreadGetThreadId(strThreadId, sizeof(strThreadId)); + + // show we are alive + pState->iRecvLife = 1; + NetPrintf(("dirtynetunix: recv thread running (thid=%s)\n", strThreadId)); + + // reset contents of pollList + ds_memclr(&pollList, sizeof(pollList)); + + // loop until done + while(pState->iRecvLife == 1) + { + // reset contents of previousPollList + ds_memclr(&previousPollList, sizeof(previousPollList)); + + // make a copy of the poll list used for the last poll() call + for (iListIndex = 0; iListIndex < pollList.iCount; iListIndex++) + { + // copy entry from pollList to previousPollList + previousPollList.aSockets[iListIndex] = pollList.aSockets[iListIndex]; + previousPollList.aPollFds[iListIndex] = pollList.aPollFds[iListIndex]; + } + previousPollList.iCount = pollList.iCount; + + // reset contents of pollList in preparation for the next poll() call + ds_memclr(&pollList, sizeof(pollList)); + + // acquire global critical section for access to socket list + NetCritEnter(NULL); + + // walk the socket list and do two things: + // 1- if the socket is ready for reading, perform the read operation + // 2- if the buffer in which inbound data is saved is empty, initiate a new low-level read operation for that socket + for (pSocket = pState->pSockList; (pSocket != NULL) && (pollList.iCount < SOCKET_MAXPOLL); pSocket = pSocket->pNext) + { + // only handle non-virtual sockets with asyncrecv enabled + if ((pSocket->bVirtual == FALSE) && (pSocket->uSocket != INVALID_SOCKET) && (pSocket->bAsyncRecv == TRUE)) + { + // acquire socket critical section + NetCritEnter(&pSocket->RecvCrit); + + // was this socket in the poll list of the previous poll() call + for (iListIndex = 0; iListIndex < previousPollList.iCount; iListIndex++) + { + if (previousPollList.aSockets[iListIndex] == pSocket) + { + // socket was in previous poll list! + // now check if poll() notified that this socket is ready for reading + if (previousPollList.aPollFds[iListIndex].revents & POLLIN) + { + /* + Note: + The poll() doc states that some error codes returned by the function + may only apply to one of the sockets in the poll list. For this reason, + we check the polling result for all entries in the list regardless + of the return value of poll(). + */ + + // ready for reading, so go ahead and read + _SocketRecvToPacketQueue(pState, previousPollList.aSockets[iListIndex]); + } + /* + POLLNVAL: The file descriptor is not open. we need to exclude this socket + handle from being added to the poll list, otherwise the poll() will keep + returning 1 at once. + + Due to the race-condition (the main thread might have recreated the socket + before we reach here), 'brokenflag' is accumulated rather simply set to TRUE, and + the socket will be excluded from the poll list only if it's marked as broken + twice or more ('brokenflag' is reset to 0 when recreating the socket, so actually + the max possible value of 'brokenflag' is 2). + */ + else if (previousPollList.aPollFds[iListIndex].revents & POLLNVAL) + { + NetPrintf(("dirtynetunix: marking socket (%x->%d) as broken upon POLLNVAL\n", pSocket, pSocket->uSocket)); + pSocket->uBrokenFlag++; + } + break; + } + } + + /* if the socket is not virtual, the socket is open (TCP) and if there is room in the recv queue, + then add this socket to the poll list to be used by the next poll() call */ + if (!SocketPacketQueueStatus(pSocket->pRecvQueue, 'pful') && (pSocket->uSocket != INVALID_SOCKET) && (pSocket->uBrokenFlag <= 1) && ((pSocket->iType != SOCK_STREAM) || (pSocket->iOpened > 0))) + { + // add socket to poll list + pollList.aSockets[pollList.iCount] = pSocket; + pollList.aPollFds[pollList.iCount].fd = pSocket->uSocket; + pollList.aPollFds[pollList.iCount].events = POLLIN; + pollList.iCount += 1; + } + + // release socket critical section + NetCritLeave(&pSocket->RecvCrit); + } + } + + // release global critical section + NetCritLeave(NULL); + + // any sockets? + if (pollList.iCount > 0) + { + // poll for data (wait up to 50ms) + iResult = poll(pollList.aPollFds, pollList.iCount, 50); + + if (iResult < 0) + { + NetPrintf(("dirtynetunix: poll() failed (err=%s)\n", DirtyErrGetName(errno))); + + // stall for 50ms because experiment shows that next call to poll() may not block + // internally if a socket is alreay in error. + usleep(50*1000); + } + } + else + { + // no sockets, so stall for 50ms + usleep(50*1000); + } + } + + // indicate we are done + NetPrintf(("dirtynetunix: receive thread exit\n")); + pState->iRecvLife = 0; +} + +/*F********************************************************************************/ +/*! + \Function _SocketGetMacAddress + + \Description + Attempt to retreive MAC address of the system. + + \Input *pState - pointer to module state + + \Output + uint8_t - TRUE if MAC address found, FALSE otherwise + + \Notes + Usage of getifaddrs() is preferred over usage of ioctl() with a socket to save + the socket creation step. However, not all platforms support the AF_LINK address + family. In those cases, usage of ioctl() can't be avoided. + + \Version 05/12/2004 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t _SocketGetMacAddress(SocketStateT *pState) +{ + int32_t iResult; + uint8_t bFound = FALSE; + +#if defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_APPLEOSX) + struct ifaddrs* pIntfList = NULL; + struct ifaddrs* pIntf = NULL; + + // retrieve the current interfaces - returns 0 on success + iResult = getifaddrs(&pIntfList); + if (iResult == 0) + { + // loop through linked list of interfaces + pIntf = pIntfList; + while(pIntf != NULL) + { + // "en0" is the name of the wifi adapter on the iPhone + if ((pIntf->ifa_addr->sa_family == AF_LINK) && strcmp(pIntf->ifa_name, "en0") == 0) + { + struct sockaddr_dl* pDataLinkSockAddr = (struct sockaddr_dl *)(pIntf->ifa_addr); + + if (pDataLinkSockAddr && pDataLinkSockAddr->sdl_alen == 6) + { + ds_memcpy(pState->aMacAddr, LLADDR(pDataLinkSockAddr), 6); + + NetPrintf(("dirtynetunix: mac address - %X:%X:%X:%X:%X:%X\n", + (uint32_t)pState->aMacAddr[0], (uint32_t)pState->aMacAddr[1], (uint32_t)pState->aMacAddr[2], + (uint32_t)pState->aMacAddr[3], (uint32_t)pState->aMacAddr[4], (uint32_t)pState->aMacAddr[5])); + + bFound = TRUE; + + break; + } + } + + pIntf = pIntf->ifa_next; + } + + // free interface list returned by getifaddrs() + freeifaddrs(pIntfList); + } + else + { + NetPrintf(("dirtynetunix: getifaddrs() returned nonzero status: %d\n", iResult)); + } +#else + struct ifreq req; + int32_t fd; + int32_t iIfIndex; + + const char *aIfrName[] = + { + "eth0", + "eth1", + "wlan0", + "end-of-interface-array" + }; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0) + { + for (iIfIndex = 0; strcmp(aIfrName[iIfIndex],"end-of-interface-array"); iIfIndex++) + { + strncpy(req.ifr_name, aIfrName[iIfIndex], IFNAMSIZ); + if ((iResult = ioctl(fd, SIOCGIFHWADDR, &req)) >= 0) + { + ds_memcpy(pState->aMacAddr, req.ifr_hwaddr.sa_data, 6); + bFound = TRUE; + break; + } + else + { + NetPrintf(("dirtynetunix: (%s) failed to query MAC address - SIOCGIFHWADDR ioctl failure %d\n", aIfrName[iIfIndex], errno)); + } + } + + close(fd); + } + else + { + NetPrintf(("dirtynetunix: can't open socket %d for MAC address query with ioctl(SIOCGIFHWADDR)\n", errno)); + } +#endif + + return(bFound); +} + +/*F********************************************************************************/ +/*! + \Function _SocketInfoGlobal + + \Description + Return information about global state + + \Input iInfo - selector for desired information + \Input iData - selector specific + \Input *pBuf - return buffer + \Input iLen - buffer length + + \Output + int32_t - selector-specific + + \Notes + These selectors need to be documented in SocketInfo() to allow our + documentation generation to pick them up. + + \Version 03/31/2017 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _SocketInfoGlobal(int32_t iInfo, int32_t iData, void *pBuf, int32_t iLen) +{ + SocketStateT *pState = _Socket_pState; + + if (iInfo == 'addr') + { + #if defined(DIRTYCODE_APPLEIOS) //$$TODO -- evaluate + if (pState->uLocalAddr == 0) + { + // get local address here, or possibly at network startup + struct ifaddrs* interfaces = NULL; + + NetPrintf(("dirtynetunix: querying interfaces\n")); + + int error = getifaddrs(&interfaces); + if (error == 0) + { + struct ifaddrs *currentAddress; + for (currentAddress = interfaces; currentAddress; currentAddress = currentAddress->ifa_next) + { + // only consider live inet interfaces and return first valid non-loopback address + if (((currentAddress->ifa_flags & (IFF_LOOPBACK | IFF_UP)) == IFF_UP)) + { + struct sockaddr *pHostAddr = (struct sockaddr *)currentAddress->ifa_addr; + if ((currentAddress->ifa_addr->sa_family == AF_INET) && (pState->uLocalAddr == 0)) + { + pState->uLocalAddr = SockaddrInGetAddr(pHostAddr); + NetPrintf(("dirtynetunix: found local address %a\n", pState->uLocalAddr)); + } + else if (currentAddress->ifa_addr->sa_family == AF_INET6) + { + NetPrintf(("dirtynetunix: found interface %A\n", (struct sockaddr *)pHostAddr)); + } + } + } + + freeifaddrs(interfaces); + } + else + { + NetPrintf(("dirtynetunix: error %d querying interfaces\n", errno)); + } + } + return(pState->uLocalAddr); + #else + struct sockaddr HostAddr, DestAddr; + SockaddrInit(&DestAddr, AF_INET); + SockaddrInSetAddr(&DestAddr, (uint32_t)iData); + if (SocketHost(&HostAddr, sizeof(HostAddr), &DestAddr, sizeof(DestAddr)) != -1) + { + return(SockaddrInGetAddr(&HostAddr)); + } + else + { + return(-1); + } + #endif + } + // get socket bound to given port + if ((iInfo == 'bind') || (iInfo == 'bndu')) + { + SocketT *pSocket; + struct sockaddr BindAddr; + int32_t iFound = -1; + + // for access to socket list + NetCritEnter(NULL); + + // walk socket list and find matching socket + for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext) + { + // if iInfo is 'bndu', only consider sockets of type SOCK_DGRAM + // note: 'bndu' stands for "bind udp" + if ((iInfo == 'bind') || ((iInfo == 'bndu') && (pSocket->iType == SOCK_DGRAM))) + { + // get socket info + SocketInfo(pSocket, 'bind', 0, &BindAddr, sizeof(BindAddr)); + if (SockaddrInGetPort(&BindAddr) == iData) + { + *(SocketT **)pBuf = pSocket; + iFound = 0; + break; + } + } + } + + // for access to g_socklist and g_sockkill + NetCritLeave(NULL); + return(iFound); + } + + if (iInfo == 'conn') + { + return(pState->uConnStatus); + } + + #ifdef DIRTYCODE_ANDROID + if (iInfo == 'eth0' || iInfo == 'wan0') + { + int32_t iRet = -1; + int32_t fd = socket(AF_INET, SOCK_DGRAM, 0); + struct ifreq ifr; + + if (fd == -1) + { + NetPrintf(("dirtynetunix: SocketInfo('eth0/wan0') cannot create socket descriptor.\n")); + iRet = -2; + } + else + { + if (iInfo == 'eth0') + { + strncpy(ifr.ifr_name, "eth0", IFNAMSIZ); + } + else + { + strncpy(ifr.ifr_name, "wlan0", IFNAMSIZ); + } + + if (ioctl(fd, SIOCGIFADDR, &ifr) == -1) + { + close(fd); + NetPrintf(("dirtynetunix: SocketInfo('%c') cannot find an IP address for device %s. Errno %s\n", iInfo, ifr.ifr_name, strerror(errno))); + iRet = -3; + } + else + { + close(fd); + struct sockaddr_in* ipaddr = (struct sockaddr_in*)&ifr.ifr_addr; + if (ipaddr->sin_addr.s_addr != 0) + { + NetPrintf(("dirtynetunix: SocketInfo('%c') network address found for %s.\n", iInfo, ifr.ifr_name)); + iRet = 0; + } + else + { + NetPrintf(("dirtynetunix: SocketInfo('%c') cannot find an IP address for device %s.\n", iInfo, ifr.ifr_name)); + iRet = -4; + } + } + } + + return(iRet); + } + #endif + + // get MAC address + if ((iInfo == 'ethr') || (iInfo == 'macx')) + { + uint8_t aZeros[6] = { 0, 0, 0, 0, 0, 0 }; + uint8_t bFound = TRUE; + + // early exit if user-provided buffer not correct + if ((pBuf == NULL) && (iLen < (signed)sizeof(pState->aMacAddr))) + { + return(-1); + } + + // try to get mac address if we don't already have it + if (!memcmp(pState->aMacAddr, aZeros, sizeof(pState->aMacAddr))) + { + bFound = _SocketGetMacAddress(pState); + } + + if (bFound) + { + // copy MAC address in user-provided buffer and signal success + ds_memcpy(pBuf, &pState->aMacAddr, sizeof(pState->aMacAddr)); + return(0); + } + + // signal failure - no MAC address found + return(-1); + } + + // check if specified ipv4 address is virtual and return associated ipv6 address if so + if (iInfo == '?ip6') + { + int32_t iResult = -1; + struct sockaddr_in6 SockAddr6, *pSockAddr6; + struct sockaddr SockAddr; + int32_t iNameLen; + + SockaddrInit(&SockAddr, AF_INET); + SockaddrInSetAddr(&SockAddr, iData); + + SockaddrInit6(&SockAddr6, AF_INET6); + pSockAddr6 = ((pBuf != NULL) && (iLen == sizeof(SockAddr6))) ? (struct sockaddr_in6 *)pBuf : &SockAddr6; + iNameLen = sizeof(SockAddr6); + + iResult = SocketAddrMapGet(&pState->AddrMap, (struct sockaddr *)pSockAddr6, &SockAddr, &iNameLen) ? 1 : 0; + return(iResult); + } + + // return max packet size + if (iInfo == 'maxp') + { + return(pState->iMaxPacket); + } + + // get send callback function pointer (iData specifies index in array) + if (iInfo == 'sdcf') + { + if ((pBuf != NULL) && (iLen == sizeof(pState->aSendCbEntries[iData].pSendCallback))) + { + ds_memcpy(pBuf, &pState->aSendCbEntries[iData].pSendCallback, sizeof(pState->aSendCbEntries[iData].pSendCallback)); + return(0); + } + + NetPrintf(("dirtynetunix: 'sdcf' selector used with invalid paramaters\n")); + return(-1); + } + // get send callback user data pointer (iData specifies index in array) + if (iInfo == 'sdcu') + { + if ((pBuf != NULL) && (iLen == sizeof(pState->aSendCbEntries[iData].pSendCallref))) + { + ds_memcpy(pBuf, &pState->aSendCbEntries[iData].pSendCallref, sizeof(pState->aSendCbEntries[iData].pSendCallref)); + return(0); + } + + NetPrintf(("dirtynetunix: 'sdcu' selector used with invalid paramaters\n")); + return(-1); + } + // return global debug output level + if (iInfo == 'spam') + { + return(pState->iVerbose); + } + + NetPrintf(("dirtynetunix: unhandled global SocketInfo() selector '%C'\n", iInfo)); + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _SocketControlGlobal + + \Description + Process a global control message (type specific operation) + + \Input iOption - the option to pass + \Input iData1 - message specific parm + \Input *pData2 - message specific parm + \Input *pData3 - message specific parm + + \Output + int32_t - message specific result (-1=unsupported message) + + \Notes + These selectors need to be documented in SocketControl() to allow our + documentation generation to pick them up. + + \Version 03/31/2017 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _SocketControlGlobal(int32_t iOption, int32_t iData1, void *pData2, void *pData3) +{ + SocketStateT *pState = _Socket_pState; + + // init network stack and bring up interface + if (iOption == 'conn') + { + #if defined(DIRTYCODE_APPLEIOS) //$$TODO - evaluate + CFStringRef URLString = CFStringCreateWithCString(kCFAllocatorDefault, "http://gos.ea.com/util/test.jsp", kCFStringEncodingASCII); + CFStringRef getString = CFStringCreateWithCString(kCFAllocatorDefault, "GET", kCFStringEncodingASCII); + CFURLRef baseURL = NULL; + CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, URLString, baseURL); + CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, getString, url, kCFHTTPVersion1_1); + CFMutableDataRef data = CFDataCreateMutable(NULL, 0); + CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(NULL, request); + + if (CFReadStreamOpen(readStream)) + { + char done = FALSE; + do + { + const int BUFSIZE = 4096; + unsigned char buf[BUFSIZE]; + int bytesRead = (int)CFReadStreamRead(readStream, buf, BUFSIZE); + if (bytesRead > 0) + { + CFDataAppendBytes(data, buf, bytesRead); + } + else if (bytesRead == 0) + { + done = TRUE; + } + else + { + done = TRUE; + } + } while (!done); + } + + CFReadStreamClose(readStream); + CFRelease(readStream); + readStream = nil; + + CFRelease(url); + url = NULL; + + CFRelease(data); + data = NULL; + + CFRelease(URLString); + URLString = NULL; + + CFRelease(getString); + getString = NULL; + #endif + + pState->uConnStatus = '+onl'; + return(0); + } + // bring down interface + if (iOption == 'disc') + { + NetPrintf(("dirtynetunix: disconnecting from network\n")); + pState->uConnStatus = '-off'; + return(0); + } + // set an ipv6 address into the mapping table + if (iOption == '+ip6') + { + return(SocketAddrMapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, iData1)); + } + // del an ipv6 address from the mapping table + if (iOption == '-ip6') + { + return(SocketAddrUnmapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, iData1)); + } + // remap an existing ipv6 address in the mapping table + if (iOption == '~ip6') + { + return(SocketAddrRemapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, (const struct sockaddr *)pData3, iData1)); + } + // handle any idle processing required + if (iOption == 'idle') + { + // in single-threaded mode, we have to give life to the network idle process + if (pState->bSingleThreaded) + { + NetIdleCall(); + } + return(0); + } + // set max udp packet size + if (iOption == 'maxp') + { + NetPrintf(("dirtynetunix: setting max udp packet size to %d\n", iData1)); + pState->iMaxPacket = iData1; + return(0); + } + // block waiting on input from socket list + if (iOption == 'poll') + { + return(_SocketPoll(pState, (unsigned)iData1*1000000)); + } + if (iOption == 'poln') + { + return(_SocketPoll(pState, (unsigned)iData1*1000)); + } + // set/unset send callback (iData1=TRUE for set - FALSE for unset, pData2=callback, pData3=callref) + if (iOption == 'sdcb') + { + SocketSendCallbackEntryT sendCbEntry; + sendCbEntry.pSendCallback = (SocketSendCallbackT *)pData2; + sendCbEntry.pSendCallref = pData3; + + if (iData1) + { + return(SocketSendCallbackAdd(&pState->aSendCbEntries[0], &sendCbEntry)); + } + else + { + return(SocketSendCallbackRem(&pState->aSendCbEntries[0], &sendCbEntry)); + } + } + // set debug spam level + if (iOption == 'spam') + { + // module level debug level + pState->iVerbose = iData1; + return(0); + } + // mark a port as virtual + if (iOption == 'vadd') + { + int32_t iPort; + + // find a slot to add virtual port + for (iPort = 0; pState->aVirtualPorts[iPort] != 0; iPort++) + ; + if (iPort < SOCKET_MAXVIRTUALPORTS) + { + NetPrintfVerbose((pState->iVerbose, 1, "dirtynetunix: added port %d to virtual port list\n", iData1)); + pState->aVirtualPorts[iPort] = (uint16_t)iData1; + return(0); + } + } + // remove port from virtual port list + if (iOption == 'vdel') + { + int32_t iPort; + + // find virtual port in list + for (iPort = 0; (iPort < SOCKET_MAXVIRTUALPORTS) && (pState->aVirtualPorts[iPort] != (uint16_t)iData1); iPort++) + ; + if (iPort < SOCKET_MAXVIRTUALPORTS) + { + NetPrintfVerbose((pState->iVerbose, 1, "dirtynetunix: removed port %d from virtual port list\n", iData1)); + pState->aVirtualPorts[iPort] = 0; + return(0); + } + } + // unhandled + NetPrintf(("dirtynetunix: unhandled global SocketControl() option '%C'\n", iOption)); + return(-1); +} + +/*** Public Functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function SocketCreate + + \Description + Create new instance of socket interface module. Initializes all global + resources and makes module ready for use. + + \Input iThreadPrio - priority to start threads with + \Input iThreadStackSize - stack size to start threads with (in bytes) + \Input iThreadCpuAffinity - cpu affinity to start threads with + + \Output + int32_t - negative=error, zero=success + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketCreate(int32_t iThreadPrio, int32_t iThreadStackSize, int32_t iThreadCpuAffinity) +{ + SocketStateT *pState = _Socket_pState; + int32_t iMemGroup; + void *pMemGroupUserData; + int32_t iResult; + + // Query mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // error if already started + if (pState != NULL) + { + NetPrintf(("dirtynetunix: SocketCreate() called while module is already active\n")); + return(-1); + } + + // print version info + NetPrintf(("dirtynetunix: DirtySDK v%d.%d.%d.%d.%d\n", DIRTYSDK_VERSION_YEAR, DIRTYSDK_VERSION_SEASON, DIRTYSDK_VERSION_MAJOR, DIRTYSDK_VERSION_MINOR, DIRTYSDK_VERSION_PATCH)); + + // alloc and init state ref + if ((pState = DirtyMemAlloc(sizeof(*pState), SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynetunix: unable to allocate module state\n")); + return(-2); + } + ds_memclr(pState, sizeof(*pState)); + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + pState->iMaxPacket = SOCKET_MAXUDPRECV; + pState->iVerbose = 1; + + if (iThreadPrio < 0) + { + pState->bSingleThreaded = TRUE; + } + + // disable SIGPIPE (Linux-based system only) + #if defined(DIRTYCODE_LINUX) || defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_ANDROID) + _SocketDisableSigpipe(); + #endif + + // startup network libs + NetLibCreate(iThreadPrio, iThreadStackSize, iThreadCpuAffinity); + + // create hostname cache + if ((pState->pHostnameCache = SocketHostnameCacheCreate(iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtynetunix: unable to create hostname cache\n")); + SocketDestroy((uint32_t)(-1)); + return(-3); + } + + // add our idle handler + NetIdleAdd(&_SocketIdle, pState); + + // create high-priority receive thread + if (!pState->bSingleThreaded) + { + DirtyThreadConfigT ThreadConfig; + + // configure threading + ds_memclr(&ThreadConfig, sizeof(ThreadConfig)); + ThreadConfig.pName = "SocketRecv"; + ThreadConfig.iAffinity = iThreadCpuAffinity; + ThreadConfig.iPriority = iThreadPrio; + ThreadConfig.iVerbosity = pState->iVerbose; + + if ((iResult = DirtyThreadCreate(_SocketRecvThread, pState, &ThreadConfig)) == 0) + { + // wait for receive thread startup + while (pState->iRecvLife == 0) + { + usleep(100); + } + } + else + { + NetPrintf(("dirtynetunix: unable to create recv thread (err=%d)\n", iResult)); + pState->iRecvLife = 0; + } + } + + // init socket address map + SocketAddrMapInit(&pState->AddrMap, pState->iMemGroup, pState->pMemGroupUserData); + + // save state + _Socket_pState = pState; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function SocketDestroy + + \Description + Release resources and destroy module. + + \Input uShutdownFlags - shutdown flags + + \Output + int32_t - negative=error, zero=success + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketDestroy(uint32_t uShutdownFlags) +{ + SocketStateT *pState = _Socket_pState; + + // error if not active + if (pState == NULL) + { + NetPrintf(("dirtynetunix: SocketDestroy() called while module is not active\n")); + return(-1); + } + + NetPrintf(("dirtynetunix: shutting down\n")); + + // wait until all lookup threads are done + while (pState->pHostList != NULL) + { + volatile HostentT **ppHost; + int32_t iSocketLookups; + + // check for lookup threads that are still active + for (ppHost = (volatile HostentT **)&pState->pHostList, iSocketLookups = 0; *ppHost != NULL; ppHost = (volatile HostentT **)&(*ppHost)->pNext) + { + iSocketLookups += (*ppHost)->thread ? 0 : 1; + } + // if no ongoing socket lookups, we're done + if (iSocketLookups == 0) + { + break; + } + NetConnSleep(1); + } + + // kill idle callbacks + NetIdleDel(&_SocketIdle, pState); + + // let any idle event finish + NetIdleDone(); + + if ((!pState->bSingleThreaded) && (pState->iRecvLife == 1)) + { + // tell receive thread to quit + pState->iRecvLife = 2; + // wait for thread to terminate + while (pState->iRecvLife > 0) + { + usleep(1*1000); + } + } + + // cleanup addr map, if allocated + SocketAddrMapShutdown(&pState->AddrMap); + + // close any remaining sockets + NetCritEnter(NULL); + while (pState->pSockList != NULL) + { + SocketClose(pState->pSockList); + } + NetCritLeave(NULL); + + // clear the kill list + _SocketIdle(pState); + + // destroy hostname cache + if (pState->pHostnameCache != NULL) + { + SocketHostnameCacheDestroy(pState->pHostnameCache); + } + + // shut down network libs + NetLibDestroy(0); + + // dispose of state + DirtyMemFree(pState, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + _Socket_pState = NULL; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function SocketOpen + + \Description + Create a new transfer endpoint. A socket endpoint is required for any + data transfer operation. + + \Input iAddrFamily - address family (AF_INET) + \Input iType - socket type (SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, ...) + \Input iProto - protocol type for SOCK_RAW (unused by others) + + \Output + SocketT * - socket reference + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +SocketT *SocketOpen(int32_t iAddrFamily, int32_t iType, int32_t iProto) +{ + return(_SocketOpen(-1, iAddrFamily, iType, iProto, 0)); +} + +/*F********************************************************************************/ +/*! + \Function SocketClose + + \Description + Close a socket. Performs a graceful shutdown of connection oriented protocols. + + \Input *pSocket - socket reference + + \Output + int32_t - negative=error, else zero + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketClose(SocketT *pSocket) +{ + int32_t iSocket = pSocket->uSocket; + + // stop sending + SocketShutdown(pSocket, SOCK_NOSEND); + + // dispose of SocketT + if (_SocketClose(pSocket) < 0) + { + return(-1); + } + + // close unix socket if allocated + if (iSocket >= 0) + { + // close socket + if (close(iSocket) < 0) + { + NetPrintf(("dirtynetunix: close() failed (err=%s)\n", DirtyErrGetName(errno))); + } + } + + // success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function SocketImport + + \Description + Import a socket. The given socket ref may be a SocketT, in which case a + SocketT pointer to the ref is returned, or it can be an actual unix socket ref, + in which case a SocketT is created for the unix socket ref. + + \Input uSockRef - socket reference + + \Output + SocketT * - pointer to imported socket, or NULL + + \Version 01/14/2005 (jbrookes) +*/ +/********************************************************************************F*/ +SocketT *SocketImport(intptr_t uSockRef) +{ + SocketStateT *pState = _Socket_pState; + socklen_t iProtoSize; + int32_t iProto; + SocketT *pSock; + + // see if this socket is already in our socket list + NetCritEnter(NULL); + for (pSock = pState->pSockList; pSock != NULL; pSock = pSock->pNext) + { + if (pSock == (SocketT *)uSockRef) + { + break; + } + } + NetCritLeave(NULL); + + // if socket is in socket list, just return it + if (pSock != NULL) + { + return(pSock); + } + + // get info from socket ref + iProtoSize = sizeof(iProto); + if (getsockopt((int32_t)uSockRef, SOL_SOCKET, SO_TYPE, &iProto, &iProtoSize) == 0) + { + // create the socket + pSock = _SocketOpen((int32_t)uSockRef, AF_INET, iProto, 0, 0); + + // update local and remote addresses + SocketInfo(pSock, 'bind', 0, &pSock->LocalAddr, sizeof(pSock->LocalAddr)); + SocketInfo(pSock, 'peer', 0, &pSock->RemoteAddr, sizeof(pSock->RemoteAddr)); + + // mark it as imported + pSock->bImported = TRUE; + } + else + { + NetPrintf(("dirtynetunix: getsockopt(SO_TYPE) failed (err=%s)\n", DirtyErrGetName(errno))); + } + + return(pSock); +} + +/*F********************************************************************************/ +/*! + \Function SocketRelease + + \Description + Release an imported socket. + + \Input *pSocket - pointer to socket + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void SocketRelease(SocketT *pSocket) +{ + // if it wasn't imported, nothing to do + if (pSocket->bImported == FALSE) + { + return; + } + + // dispose of SocketT, but leave the sockref alone + _SocketClose(pSocket); +} + +/*F********************************************************************************/ +/*! + \Function SocketShutdown + + \Description + Perform partial/complete shutdown of socket indicating that either sending + and/or receiving is complete. + + \Input *pSocket - socket reference + \Input iHow - SOCK_NOSEND and/or SOCK_NORECV + + \Output + int32_t - negative=error, else zero + + \Version 09/10/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketShutdown(SocketT *pSocket, int32_t iHow) +{ + int32_t iResult=0; + + // only shutdown a connected socket + if (pSocket->iType != SOCK_STREAM) + { + pSocket->iLastError = SOCKERR_NONE; + return(pSocket->iLastError); + } + + // make sure socket ref is valid + if (pSocket->uSocket == INVALID_SOCKET) + { + pSocket->iLastError = SOCKERR_NONE; + return(pSocket->iLastError); + } + + // translate how + if (iHow == SOCK_NOSEND) + { + iHow = SHUT_WR; + } + else if (iHow == SOCK_NORECV) + { + iHow = SHUT_RD; + } + else if (iHow == (SOCK_NOSEND|SOCK_NORECV)) + { + iHow = SHUT_RDWR; + } + + // do the shutdown + if (shutdown(pSocket->uSocket, iHow) < 0) + { + iResult = errno; + + // log only useful messages + if (iResult != ENOTCONN) + { + NetPrintf(("dirtynetunix: shutdown() failed (err=%s)\n", DirtyErrGetName(iResult))); + } + } + + pSocket->iLastError = _SocketTranslateError(iResult); + return(pSocket->iLastError); +} + +/*F********************************************************************************/ +/*! + \Function SocketBind + + \Description + Bind a local address/port to a socket. + + \Input *pSocket - socket reference + \Input *pName - local address/port + \Input iNameLen - length of name + + \Output + int32_t - standard network error code (SOCKERR_xxx) + + \Notes + If either address or port is zero, then they are filled in automatically. + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketBind(SocketT *pSocket, const struct sockaddr *pName, int32_t iNameLen) +{ + SocketStateT *pState = _Socket_pState; + struct sockaddr_in6 SockAddr6; + int32_t iResult; + + // make sure socket is valid + if (pSocket->uSocket < 0) + { + NetPrintf(("dirtynetunix: attempt to bind invalid socket\n")); + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + + // save local address + ds_memcpy_s(&pSocket->LocalAddr, sizeof(pSocket->LocalAddr), pName, sizeof(*pName)); + + // is the bind port a virtual port? + if (pSocket->iType == SOCK_DGRAM) + { + int32_t iPort; + uint16_t uPort; + + if ((uPort = SockaddrInGetPort(pName)) != 0) + { + // find virtual port in list + for (iPort = 0; (iPort < SOCKET_MAXVIRTUALPORTS) && (pState->aVirtualPorts[iPort] != uPort); iPort++) + ; + if (iPort < SOCKET_MAXVIRTUALPORTS) + { + // acquire socket critical section + NetCritEnter(&pSocket->RecvCrit); + + // check to see if the socket is bound + if (pSocket->bVirtual && (pSocket->uVirtualPort != 0)) + { + NetPrintf(("dirtynetunix: [%p] failed to bind socket to %u which was already bound to port %u virtual\n", pSocket, uPort, pSocket->uVirtualPort)); + NetCritLeave(&pSocket->RecvCrit); + return(pSocket->iLastError = SOCKERR_INVALID); + } + + // close winsock socket + NetPrintf(("dirtynetunix: [%p] making socket bound to port %d virtual\n", pSocket, uPort)); + if (pSocket->uSocket != INVALID_SOCKET) + { + shutdown(pSocket->uSocket, SOCK_NOSEND); + close(pSocket->uSocket); + pSocket->uSocket = INVALID_SOCKET; + } + /* increase socket queue size; this protects virtual sockets from having data pushed into + them and overwriting previous data that hasn't been read yet */ + pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, 4, pState->iMemGroup, pState->pMemGroupUserData); + // mark socket as virtual + pSocket->uVirtualPort = uPort; + pSocket->bVirtual = TRUE; + + // release socket critical section + NetCritLeave(&pSocket->RecvCrit); + return(0); + } + } + } + + // translate IPv4 -> IPv6 address (if needed) + if (pName->sa_family != AF_INET6) + { + ds_memclr(&SockAddr6, sizeof(SockAddr6)); + SockAddr6.sin6_family = AF_INET6; + SockAddr6.sin6_port = SocketHtons(SockaddrInGetPort(pName)); + pName = SocketAddrMapTranslate(&pState->AddrMap, (struct sockaddr *)&SockAddr6, pName, &iNameLen); + } + + // do the bind + if ((iResult = bind(pSocket->uSocket, pName, iNameLen)) < 0) + { + NetPrintf(("dirtynetunix: bind() to port %d failed (err=%s)\n", SockaddrInGetPort(pName), DirtyErrGetName(errno))); + } + else if (SockaddrInGetPort(&pSocket->LocalAddr) == 0) + { + iNameLen = sizeof(pSocket->LocalAddr); + iResult = getsockname(pSocket->uSocket, &pSocket->LocalAddr, (socklen_t *)&iNameLen); + NetPrintf(("dirtynetunix: bind(port=0) succeeded, local address=%a:%d.\n", + SockaddrInGetAddr(&pSocket->LocalAddr), + SockaddrInGetPort(&pSocket->LocalAddr))); + } + + pSocket->iLastError = _SocketTranslateError(iResult); + return(pSocket->iLastError); +} + +/*F********************************************************************************/ +/*! + \Function SocketConnect + + \Description + Initiate a connection attempt to a remote host. + + \Input *pSocket - socket reference + \Input *pName - pointer to name of socket to connect to + \Input iNameLen - length of name + + \Output + int32_t - standard network error code (SOCKERR_xxx) + + \Notes + Only has real meaning for stream protocols. For a datagram protocol, this + just sets the default remote host. + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketConnect(SocketT *pSocket, struct sockaddr *pName, int32_t iNameLen) +{ + struct sockaddr_in6 SockAddr6; + int32_t iResult; + + // initialize family of Sockaddr6 + SockaddrInit6(&SockAddr6, AF_INET6); + + // translate to IPv6 if required + pName = SocketAddrMapTranslate(&_Socket_pState->AddrMap, (struct sockaddr *)&SockAddr6, pName, &iNameLen); + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetunix: connecting to %A\n", pName)); + + // do the connect + pSocket->iOpened = 0; + if ((iResult = connect(pSocket->uSocket, pName, iNameLen)) == 0) + { + // if connect succeeded (usually for udp sockets) or attempting to establish a non-blocking connection save correct address + ds_memcpy_s(&pSocket->RemoteAddr, sizeof(pSocket->RemoteAddr), pName, sizeof(*pName)); + } + else if (errno == EHOSTUNREACH) + { + /* if host is unreachable, purge from hostname cache (if present). this is helpful in + situations where the addressing scheme has changed; for example if a device switches + from an IPv4 hosted connection to an IPv6 connection. in such a case the old (now + invalid) address is purged from the cache more quickly than if we waited for it to + expire from the normal cache timeout */ + SocketHostnameCacheDel(_Socket_pState->pHostnameCache, NULL, SockaddrInGetAddr(pName), _Socket_pState->iVerbose); + } + else if (errno != EINPROGRESS) + { + NetPrintf(("dirtynetunix: connect() failed (err=%s)\n", DirtyErrGetName(errno))); + } + + pSocket->iLastError = _SocketTranslateError(iResult); + return(pSocket->iLastError); +} + +/*F********************************************************************************/ +/*! + \Function SocketListen + + \Description + Start listening for an incoming connection on the socket. The socket must already + be bound and a stream oriented connection must be in use. + + \Input *pSocket - socket reference to bound socket (see SocketBind()) + \Input iBacklog - number of pending connections allowed + + \Output + int32_t - standard network error code (SOCKERR_xxx) + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketListen(SocketT *pSocket, int32_t iBacklog) +{ + int32_t iResult; + + // do the listen + if ((iResult = listen(pSocket->uSocket, iBacklog)) < 0) + { + NetPrintf(("dirtynetunix: listen() failed (err=%s)\n", DirtyErrGetName(errno))); + } + + pSocket->iLastError = _SocketTranslateError(iResult); + return(pSocket->iLastError); +} + +/*F********************************************************************************/ +/*! + \Function SocketAccept + + \Description + Accept an incoming connection attempt on a socket. + + \Input *pSocket - socket reference to socket in listening state (see SocketListen()) + \Input *pAddr - pointer to storage for address of the connecting entity, or NULL + \Input *pAddrLen - pointer to storage for length of address, or NULL + + \Output + SocketT * - the accepted socket, or NULL if not available + + \Notes + The integer pointed to by addrlen should on input contain the number of characters + in the buffer addr. On exit it will contain the number of characters in the + output address. + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +SocketT *SocketAccept(SocketT *pSocket, struct sockaddr *pAddr, int32_t *pAddrLen) +{ + SocketT *pOpen = NULL; + int32_t iIncoming; + + pSocket->iLastError = SOCKERR_INVALID; + + // make sure we have a socket + if (pSocket->uSocket == INVALID_SOCKET) + { + NetPrintf(("dirtynetunix: accept() called on invalid socket\n")); + return(NULL); + } + + // make sure turn parm is valid + if ((pAddr != NULL) && (*pAddrLen < (signed)sizeof(struct sockaddr))) + { + NetPrintf(("dirtynetunix: accept() called with invalid address\n")); + return(NULL); + } + + // perform inet6 accept + if (pSocket->iFamily == AF_INET6) + { + struct sockaddr_in6 SockAddr6; + socklen_t iAddrLen; + + SockaddrInit6(&SockAddr6, AF_INET6); + SockAddr6.sin6_port = SocketNtohs(SockaddrInGetPort(pAddr)); + SockAddr6.sin6_addr = in6addr_any; + iAddrLen = sizeof(SockAddr6); + + iIncoming = accept(pSocket->uSocket, (struct sockaddr *)&SockAddr6, &iAddrLen); + if (iIncoming != -1) + { + // Allocate socket structure and install in list + pOpen = _SocketOpen(iIncoming, pSocket->iFamily, pSocket->iType, pSocket->iProto, 1); + pSocket->iLastError = SOCKERR_NONE; + + #if defined(DIRTYCODE_ANDROID) || defined(DIRTYCODE_LINUX) + /* http://linux.die.net/man/2/accept: + On Linux, the new socket returned by accept() does not inherit file status flags + such as O_NONBLOCK and O_ASYNC from the listening socket. This behaviour differs + from the canonical BSD sockets implementation. */ + // set nonblocking operation + if (fcntl(iIncoming, F_SETFL, O_NONBLOCK) < 0) + { + NetPrintf(("dirtynetunix: error trying to make socket non-blocking (err=%d)\n", errno)); + } + #endif + // translate ipv6 to ipv4 virtual address + SocketAddrMapAddress(&_Socket_pState->AddrMap, (const struct sockaddr *)&SockAddr6, sizeof(SockAddr6)); + // save translated connecting info for caller + SockaddrInit(pAddr, AF_INET); + SocketAddrMapTranslate(&_Socket_pState->AddrMap, pAddr, (struct sockaddr *)&SockAddr6, pAddrLen); + } + else + { + pSocket->iLastError = _SocketTranslateError(iIncoming); + if (errno != EWOULDBLOCK) + { + NetPrintf(("dirtynetunix: accept() failed (err=%s)\n", DirtyErrGetName(errno))); + } + } + } + + // return the socket + return(pOpen); +} + +/*F********************************************************************************/ +/*! + \Function SocketSendto + + \Description + Send data to a remote host. The destination address is supplied along with + the data. Should only be used with datagram sockets as stream sockets always + send to the connected peer. + + \Input *pSocket - socket reference + \Input *pBuf - the data to be sent + \Input iLen - size of data + \Input iFlags - unused + \Input *pTo - the address to send to (NULL=use connection address) + \Input iToLen - length of address + + \Output + int32_t - standard network error code (SOCKERR_xxx) + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketSendto(SocketT *pSocket, const char *pBuf, int32_t iLen, int32_t iFlags, const struct sockaddr *pTo, int32_t iToLen) +{ + SocketStateT *pState = _Socket_pState; + int32_t iResult = -1; + + if (pSocket->bSendCbs) + { + // if installed, give socket callback right of first refusal + if ((iResult = SocketSendCallbackInvoke(&pState->aSendCbEntries[0], pSocket, pSocket->iType, pBuf, iLen, pTo)) > 0) + { + return(iResult); + } + } + + // make sure socket ref is valid + if (pSocket->uSocket < 0) + { + #if DIRTYCODE_LOGGING + uint32_t uAddr = 0, uPort = 0; + if (pTo) + { + uAddr = SockaddrInGetAddr(pTo); + uPort = SockaddrInGetPort(pTo); + } + NetPrintf(("dirtynetunix: attempting to send to %a:%d on invalid socket\n", uAddr, uPort)); + #endif + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + + // handle optional data rate throttling + if ((iLen = SocketRateThrottle(&pSocket->SendRate, pSocket->iType, iLen, "send")) == 0) + { + return(0); + } + + // use appropriate version + if (pTo == NULL) + { + if ((iResult = (int32_t)send(pSocket->uSocket, pBuf, iLen, 0)) < 0) + { + if (errno != EWOULDBLOCK) + NetPrintf(("dirtynetunix: send() failed (err=%s)\n", DirtyErrGetName(errno))); + } + } + else + { + struct sockaddr_in6 SockAddr6; + struct sockaddr *pTo6; + + // do the send + #if SOCKET_VERBOSE + NetPrintf(("dirtynetunix: sending %d bytes to %a:%d\n", iLen, SockaddrInGetAddr(pTo), SockaddrInGetPort(pTo))); + #endif + + SockaddrInit6(&SockAddr6, AF_INET6); + iToLen = sizeof(SockAddr6); + pTo6 = SocketAddrMapTranslate(&pState->AddrMap, (struct sockaddr *)&SockAddr6, pTo, &iToLen); + if ((iResult = (int32_t)sendto(pSocket->uSocket, pBuf, iLen, 0, pTo6, iToLen)) < 0) + { + NetPrintf(("dirtynetunix: sendto(%A) failed (err=%s)\n", pTo6, DirtyErrGetName(errno))); + } + } + // translate error + pSocket->iLastError = iResult = _SocketTranslateError(iResult); + if (iResult == SOCKERR_BADPIPE) + { + // recreate socket (iLastError will be set in _SocketReopen) + if (_SocketReopen(pSocket)) + { + // success, re-send (should either work or error out) + return(SocketSendto(pSocket, pBuf, iLen, iFlags, pTo, iToLen)); + } + // failed to recreate the socket, error + } + + // update data rate estimation + SocketRateUpdate(&pSocket->SendRate, iResult, "send"); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function SocketRecvfrom + + \Description + Receive data from a remote host. If socket is a connected stream, then data can + only come from that source. A datagram socket can receive from any remote host. + + \Input *pSocket - socket reference + \Input *pBuf - buffer to receive data + \Input iLen - length of recv buffer + \Input iFlags - unused + \Input *pFrom - address data was received from (NULL=ignore) + \Input *pFromLen - length of address + + \Output + int32_t - positive=data bytes received, else standard error code + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketRecvfrom(SocketT *pSocket, char *pBuf, int32_t iLen, int32_t iFlags, struct sockaddr *pFrom, int32_t *pFromLen) +{ + int32_t iRecv = -1, iErrno = 0; + + // clear "hasdata" hint + pSocket->bHasData = 0; + + // handle rate throttling, if enabled + if ((iLen = SocketRateThrottle(&pSocket->RecvRate, pSocket->iType, iLen, "recv")) == 0) + { + return(0); + } + // handle if the socket was killed + if (pSocket->pKill != NULL) + { + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + + /* sockets marked for async recv had actual receive operation take place in the thread. sockets marked as virtual have the + packet pushed into them (specific to unix as we disable async receive for singlethreaded mode). */ + if ((pSocket->bAsyncRecv == TRUE) || (pSocket->bVirtual == TRUE)) + { + // acquire socket receive critical section + NetCritEnter(&pSocket->RecvCrit); + + /* given the socket could be either a TCP or UDP socket we handle the no data condition the same. + this is due to the below, when we do error conversion we override the system error with EWOULDBLOCK because + with TCP zero would be mean closed. if we are doing direct recv calls then the translation will + convert based on the errno returned after the call */ + if ((iRecv = _SocketRecvfromPacketQueue(pSocket, pBuf, iLen, pFrom, pFromLen)) == 0) + { + iRecv = -1; + iErrno = EWOULDBLOCK; + } + + // when data is obtained from the packet queue, we lose visibility on system socket errors + pSocket->iLastError = SOCKERR_NONE; + + // release socket receive critical section + NetCritLeave(&pSocket->RecvCrit); + } + else // non-async recvthread socket + { + // do direct recv call + if (((iRecv = _SocketRecvfrom(pSocket, pBuf, iLen, pFrom, pFromLen)) < 0) && ((iErrno = errno) != EAGAIN)) + { + NetPrintf(("dirtynetunix: _SocketRecvfrom() failed on a SOCK_STREAM socket (err=%s)\n", DirtyErrGetName(iErrno))); + } + } + + // do error conversion + iRecv = (iRecv == 0) ? SOCKERR_CLOSED : _SocketTranslateError2(iRecv, iErrno); + + // update data rate estimation + SocketRateUpdate(&pSocket->RecvRate, iRecv, "recv"); + + // return the error code + pSocket->iLastError = iRecv; + return(iRecv); +} + +/*F********************************************************************************/ +/*! + \Function SocketInfo + + \Description + Return information about an existing socket. + + \Input *pSocket - socket reference + \Input iInfo - selector for desired information + \Input iData - selector specific + \Input *pBuf - return buffer + \Input iLen - buffer length + + \Output + int32_t - selector-specific + + \Notes + iInfo can be one of the following: + + \verbatim + 'addr' - returns interface address; iData=destination address for routing + 'bind' - return bind data (if pSocket == NULL, get socket bound to given port) + 'bndu' - return bind data (only with pSocket=NULL, get SOCK_DGRAM socket bound to given port) + 'conn' - connection status + 'eth0' - returns 0 if we have a valid ip for eth0 device. negative if error or no address found (Android Only) + 'ethr'/'macx' - local ethernet address (returned in pBuf), 0=success, negative=error + '?ip6' - return TRUE if ipv4 address specified in iData is virtual, and fill in pBuf with ipv6 address if not NULL + 'maxp' - return configured max packet size + 'maxr' - return configured max recv rate (bytes/sec; zero=uncapped) + 'maxs' - return configured max send rate (bytes/sec; zero=uncapped) + 'pdrp' - return socket packet queue number of packets dropped + 'peer' - peer info (only valid if connected) + 'pmax' - return socket packet queue max depth + 'pnum' - return socket packet queue current depth + 'ratr' - return current recv rate estimation (bytes/sec) + 'rats' - return current send rate estimation (bytes/sec) + 'read' - return if socket has data available for reading + 'sdcf' - get installed send callback function pointer (iData specifies index in array) + 'sdcu' - get installed send callback userdata pointer (iData specifies index in array) + 'serr' - last socket error + 'psiz' - return socket packet queue max size + 'sock' - return socket associated with the specified DirtySock socket + 'spam' - return debug level for debug output + 'stat' - socket status + 'virt' - TRUE if socket is virtual, else FALSE + 'wan0' - returns true if we have a valid ip for wlan0 device. Negative if error or no address found (Android Only) + \endverbatim + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketInfo(SocketT *pSocket, int32_t iInfo, int32_t iData, void *pBuf, int32_t iLen) +{ + SocketStateT *pState = _Socket_pState; + + // always zero results by default + if (pBuf != NULL) + { + ds_memclr(pBuf, iLen); + } + + // handle global socket options + if (pSocket == NULL) + { + return(_SocketInfoGlobal(iInfo, iData, pBuf, iLen)); + } + + // return local bind data + if (iInfo == 'bind') + { + int32_t iResult = -1; + if (pSocket->bVirtual == TRUE) + { + SockaddrInit((struct sockaddr *)pBuf, AF_INET); + SockaddrInSetPort((struct sockaddr *)pBuf, pSocket->uVirtualPort); + iResult = 0; + } + else if (pSocket->uSocket != INVALID_SOCKET) + { + struct sockaddr_in6 SockAddr6; + iLen = sizeof(SockAddr6); + if ((iResult = getsockname(pSocket->uSocket, (struct sockaddr *)&SockAddr6, (socklen_t *)&iLen)) == 0) + { + SockaddrInit((struct sockaddr *)pBuf, AF_INET); + SockaddrInSetPort((struct sockaddr *)pBuf, SocketHtons(SockAddr6.sin6_port)); + SockaddrInSetAddr((struct sockaddr *)pBuf, SocketAddrMapAddress(&pState->AddrMap, (struct sockaddr *)&SockAddr6, sizeof(SockAddr6))); + } + iResult = _SocketTranslateError(iResult); + } + return(iResult); + } + + // return configured max recv rate + if (iInfo == 'maxr') + { + return(pSocket->RecvRate.uMaxRate); + } + + // return configured max send rate + if (iInfo == 'maxs') + { + return(pSocket->SendRate.uMaxRate); + } + + // return whether the socket is virtual or not + if (iInfo == 'virt') + { + return(pSocket->bVirtual); + } + + /* + make sure the socket is alive + ** AFTER THIS POINT WE ENSURE THE SOCKET DESCRIPTOR AND PACKET QUEUE ARE VALID ** + */ + if (pSocket->pKill != NULL) + { + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + + // return local peer data + if ((iInfo == 'conn') || (iInfo == 'peer')) + { + if (iLen >= (signed)sizeof(pSocket->LocalAddr)) + { + getpeername(pSocket->uSocket, pBuf, (socklen_t *)&iLen); + } + return(0); + } + + // get packet queue info + if ((iInfo == 'pdrp') || (iInfo == 'pmax') || (iInfo == 'pnum') || (iInfo == 'psiz')) + { + int32_t iResult; + // acquire socket receive critical section + NetCritEnter(&pSocket->RecvCrit); + // get packet queue status + iResult = SocketPacketQueueStatus(pSocket->pRecvQueue, iInfo); + // release socket receive critical section + NetCritLeave(&pSocket->RecvCrit); + // return success + return(iResult); + } + + // return current recv rate estimation + if (iInfo == 'ratr') + { + return(pSocket->RecvRate.uCurRate); + } + + // return current send rate estimation + if (iInfo == 'rats') + { + return(pSocket->SendRate.uCurRate); + } + + // return if socket has data + if (iInfo == 'read') + { + return(pSocket->bHasData); + } + + // return last socket error + if (iInfo == 'serr') + { + return(pSocket->iLastError); + } + + // return unix socket ref + if (iInfo == 'sock') + { + return(pSocket->uSocket); + } + + // return socket status + if (iInfo == 'stat') + { + struct pollfd PollFd; + + // if not a connected socket, return TRUE + if (pSocket->iType != SOCK_STREAM) + { + return(1); + } + + // if not connected, use poll to determine connect + if (pSocket->iOpened == 0) + { + ds_memclr(&PollFd, sizeof(PollFd)); + PollFd.fd = pSocket->uSocket; + PollFd.events = POLLOUT; + if (poll(&PollFd, 1, 0) != 0) + { + /* + Experimentation shows that on connect failed: + Android: (only) POLLERR is returned. + iOS5: (only) POLLHUP is returned. + Linux: both POLLERR and POLLHUP are returned (POLLERR|POLLHUP). + + To make the code work on all platforms, we test if any of POLLERR and POLLHUP was set. + */ + if ((PollFd.revents & POLLERR) || (PollFd.revents & POLLHUP)) + { + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetunix: read exception on connect\n")); + pSocket->iOpened = -1; + } + // if socket is writable, that means connect succeeded + else if (PollFd.revents & POLLOUT) + { + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetunix: connection open\n")); + pSocket->iOpened = 1; + } + } + } + + /* if previously connected, make sure connect still valid. we only do this when not doing async receive for two + reasons + 1. there is a race condition between the poll waking up and querying the bytes available. if the bytes are + read on the receive thread between the poll and ioctl then it would think the socket is closed because the + socket has already been drained + 2. our reasoning behind using the async receive thread could be the cost of recv, plus other calls may be + expensive as well. the async receive thread will already set the correct state on the socket thus we can + skip the query and return iOpened back to the user */ + if (!pSocket->bAsyncRecv && (pSocket->iOpened > 0)) + { + ds_memclr(&PollFd, sizeof(PollFd)); + PollFd.fd = pSocket->uSocket; + PollFd.events = POLLIN; + if (poll(&PollFd, 1, 0) != 0) + { + // if we got an exception, that means connect failed (usually closed by remote peer) + if ((PollFd.revents & POLLERR) || (PollFd.revents & POLLHUP)) + { + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetunix: connection failure\n")); + pSocket->iOpened = -1; + } + else if (PollFd.revents & POLLIN) + { + int32_t iAvailBytes = 1; + // get number of bytes for read (might be less than actual bytes, so it can only be used for zero-test) + if (ioctl(pSocket->uSocket, FIONREAD, &iAvailBytes) != 0) + { + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetunix: ioctl(FIONREAD) failed (err=%s).\n", DirtyErrGetName(errno))); + } + // if socket is readable but there's no data available for read, connect was closed + else if (iAvailBytes == 0) + { + pSocket->iLastError = SOCKERR_CLOSED; + NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetunix: connection closed\n")); + pSocket->iOpened = -1; + } + } + } + } + /* if we still have packets in the queue, tell the caller that we are still open. this makes sure they read the + complete stream of data */ + else if (pSocket->bAsyncRecv && (pSocket->iOpened < 0) && (SocketInfo(pSocket, 'pnum', 0, NULL, 0) != 0)) + { + return(1); + } + + // return connect status + return(pSocket->iOpened); + } + + // unhandled option? + NetPrintf(("dirtynetunix: unhandled SocketInfo() option '%C'\n", iInfo)); + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function SocketCallback + + \Description + Register a callback routine for notification of socket events. Also includes + timeout support. + + \Input *pSocket - socket reference + \Input iMask - valid callback events (CALLB_NONE, CALLB_SEND, CALLB_RECV) + \Input iIdle - if nonzero, specifies the number of ticks between idle calls + \Input *pRef - user data to be passed to proc + \Input *pProc - user callback + + \Output + int32_t - zero + + \Notes + A callback will reset the idle timer, so when specifying a callback and an + idle processing time, the idle processing time represents the maximum elapsed + time between calls. + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketCallback(SocketT *pSocket, int32_t iMask, int32_t iIdle, void *pRef, int32_t (*pProc)(SocketT *pSock, int32_t iFlags, void *pRef)) +{ + pSocket->uCallIdle = iIdle; + pSocket->iCallMask = iMask; + pSocket->pCallRef = pRef; + pSocket->pCallback = pProc; + pSocket->uCallLast = NetTick() - iIdle; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function SocketControl + + \Description + Process a control message (type specific operation) + + \Input *pSocket - socket to control, or NULL for module-level option + \Input iOption - the option to pass + \Input iData1 - message specific parm + \Input *pData2 - message specific parm + \Input *pData3 - message specific parm + + \Output + int32_t - message specific result (-1=unsupported message) + + \Notes + iOption can be one of the following: + + \verbatim + 'arcv' - set async receive enable/disable (default enabled for DGRAM/RAW, disabled for TCP) + 'conn' - init network stack + 'disc' - bring down network stack + 'idle' - perform any network connection related processing + '+ip6' - add an IPv6 address into the mapping table and return a virtual IPv4 address to reference it + '-ip6' - del an IPv6 address from the mapping table + '~ip6' - remap an existing IPv6 address in the mapping table + 'keep' - set TCP keep-alive settings on Linux (iData1=enable/disable, iData2=keep-alive time, iData3=keep-alive interval) + 'maxp' - set max udp packet size + 'maxr' - set max recv rate (bytes/sec; zero=uncapped) + 'maxs' - set max send rate (bytes/sec; zero=uncapped) + 'nbio' - set nonblocking/blocking mode (TCP only, iData1=TRUE (nonblocking) or FALSE (blocking)) + 'ndly' - set TCP_NODELAY state for given stream socket (iData1=zero or one) + 'pdev' - set simulated packet deviation + 'plat' - set simulated packet latency + 'plos' - set simulated packet loss + 'poll' - block waiting on input from socket list (iData1=ms to block) + 'poln' - block waiting on input from socket list (iData1=us to block), unsupported on apple platforms fallback to 'poll' behavior + 'pque' - set socket packet queue depth + 'push' - push data into given socket receive buffer (iData1=size, pData2=data ptr, pData3=sockaddr ptr) + 'radr' - set SO_REUSEADDR On the specified socket + 'rbuf' - set socket recv buffer size + 'sbuf' - set socket send buffer size + 'scbk' - enable/disable "send callbacks usage" on specified socket (defaults to enable) + 'sdcb' - set/unset send callback (iData1=TRUE for set - FALSE for unset, pData2=callback, pData3=callref) + 'soli' - set SO_LINGER On the specified socket, iData1 is timeout in seconds + 'spam' - set debug level for debug output + 'vadd' - add a port to virtual port list + 'vdel' - del a port from virtual port list + \endverbatim + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t SocketControl(SocketT *pSocket, int32_t iOption, int32_t iData1, void *pData2, void *pData3) +{ + SocketStateT *pState = _Socket_pState; + int32_t iResult; + + // handle global controls + if (pSocket == NULL) + { + return(_SocketControlGlobal(iOption, iData1, pData2, pData3)); + } + + // set async recv enable + if (iOption == 'arcv') + { + // set socket async recv flag + pSocket->bAsyncRecv = iData1 ? TRUE : FALSE; + return(0); + } + + // set max recv rate + if (iOption == 'maxr') + { + NetPrintf(("dirtynetunix: setting max recv rate to %d bytes/sec\n", iData1)); + pSocket->RecvRate.uMaxRate = iData1; + return(0); + } + // set max send rate + if (iOption == 'maxs') + { + NetPrintf(("dirtynetunix: setting max send rate to %d bytes/sec\n", iData1)); + pSocket->SendRate.uMaxRate = iData1; + return(0); + } + // enable/disable "send callbacks usage" on specified socket (defaults to enable) + if (iOption == 'scbk') + { + if (pSocket->bSendCbs != (iData1?TRUE:FALSE)) + { + NetPrintf(("dirtynetunix: send callbacks usage changed from %s to %s for socket ref %p\n", (pSocket->bSendCbs?"ON":"OFF"), (iData1?"ON":"OFF"), pSocket)); + pSocket->bSendCbs = (iData1?TRUE:FALSE); + } + return(0); + } + // set debug spam level + if (iOption == 'spam') + { + // per-socket debug level + pSocket->iVerbose = iData1; + return(0); + } + + /* + make sure the socket is alive + ** AFTER THIS POINT WE ENSURE THE SOCKET DESCRIPTOR AND PACKET QUEUE ARE VALID ** + */ + if (pSocket->pKill != NULL) + { + pSocket->iLastError = SOCKERR_INVALID; + return(pSocket->iLastError); + } + +#if defined(DIRTYCODE_LINUX) + // configure TCP keep-alive + if (iOption == 'keep') + { + uint32_t bKeepAlive, uKeepAliveTime, uKeepAliveInterval; + + if (pSocket->iType != SOCK_STREAM) + { + NetPrintf(("dirtynetunix: [%p] 'keep' control can only be used on a SOCK_STREAM socket\n", pSocket)); + return(-1); + } + + bKeepAlive = (uint8_t)iData1; //!< enable/disable keep-alive option + uKeepAliveTime = bKeepAlive ? *(uint32_t *)pData2 / 1000 : 0; //!< get keep-alive time and convert to seconds + uKeepAliveInterval = bKeepAlive ? *(uint32_t *)pData3 / 1000 : 0; //!< get keep-alive interval and convert to seconds + + if ((iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, SO_KEEPALIVE, &bKeepAlive, sizeof(bKeepAlive))) != 0) + { + pSocket->iLastError = _SocketTranslateError(iResult); + NetPrintf(("dirtynetunix: [%p] failed to set SO_KEEPALIVE to %s (err=%d)\n", pSocket, bKeepAlive ? "true" : "false", pSocket->iLastError)); + } + else if ((iResult = setsockopt(pSocket->uSocket, SOL_TCP, TCP_KEEPIDLE, &uKeepAliveTime, sizeof(uKeepAliveTime))) != 0) + { + pSocket->iLastError = _SocketTranslateError(iResult); + NetPrintf(("dirtynetunix: [%p] failed to set TCP_KEEPIDLE to %ums (err=%d)\n", pSocket, uKeepAliveTime*1000, pSocket->iLastError)); + } + else if ((iResult = setsockopt(pSocket->uSocket, SOL_TCP, TCP_KEEPINTVL, &uKeepAliveInterval, sizeof(uKeepAliveInterval))) != 0) + { + pSocket->iLastError = _SocketTranslateError(iResult); + NetPrintf(("dirtynetunix: [%p] failed to set TCP_KEEPINTVL to %ums (err=%d)\n", pSocket, uKeepAliveInterval*1000, pSocket->iLastError)); + } + else + { + pSocket->iLastError = SOCKERR_NONE; + + NetPrintfVerbose((pState->iVerbose, 1, "dirtynetunix: [%p] successfully set the TCP keep-alive options (enabled=%s, timeout=%ums, interval=%ums)\n", + pSocket, bKeepAlive ? "true" : "false", uKeepAliveTime*1000, uKeepAliveInterval*1000)); + } + + return(pSocket->iLastError); + } +#endif + // if a stream socket, set nonblocking/blocking mode + if ((iOption == 'nbio') && (pSocket->iType == SOCK_STREAM)) + { + int32_t iVal = fcntl(pSocket->uSocket, F_GETFL, O_NONBLOCK); + iVal = iData1 ? (iVal | O_NONBLOCK) : (iVal & ~O_NONBLOCK); + iResult = fcntl(pSocket->uSocket, F_SETFL, iVal); + pSocket->iLastError = _SocketTranslateError(iResult); + NetPrintf(("dirtynetunix: setting socket:0x%x to %s mode %s (LastError=%d).\n", pSocket, iData1 ? "nonblocking" : "blocking", iResult ? "failed" : "succeeded", pSocket->iLastError)); + return(pSocket->iLastError); + } + // if a stream socket, set TCP_NODELAY state + if ((iOption == 'ndly') && (pSocket->iType == SOCK_STREAM)) + { + iResult = setsockopt(pSocket->uSocket, IPPROTO_TCP, TCP_NODELAY, &iData1, sizeof(iData1)); + pSocket->iLastError = _SocketTranslateError(iResult); + return(pSocket->iLastError); + } + // set simulated packet loss or packet latency + if ((iOption == 'pdev') || (iOption == 'plat') || (iOption == 'plos')) + { + // acquire socket receive critical section + NetCritEnter(&pSocket->RecvCrit); + // forward selector to packet queue + iResult = SocketPacketQueueControl(pSocket->pRecvQueue, iOption, iData1); + // release socket receive critical section + NetCritLeave(&pSocket->RecvCrit); + return(iResult); + } + + // change packet queue size + if (iOption == 'pque') + { + // acquire socket receive critical section + NetCritEnter(&pSocket->RecvCrit); + // resize the queue + pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, iData1, pState->iMemGroup, pState->pMemGroupUserData); + // release socket receive critical section + NetCritLeave(&pSocket->RecvCrit); + // return success + return(0); + } + + // push data into receive buffer + if (iOption == 'push') + { + // acquire socket critical section + NetCritEnter(&pSocket->RecvCrit); + + // don't allow data that is too large (for the buffer) to be pushed + if (iData1 > SOCKET_MAXUDPRECV) + { + NetPrintf(("dirtynetunix: request to push %d bytes of data discarded (max=%d)\n", iData1, SOCKET_MAXUDPRECV)); + NetCritLeave(&pSocket->RecvCrit); + return(-1); + } + + // add packet to queue + SocketPacketQueueAdd(pSocket->pRecvQueue, (uint8_t *)pData2, iData1, (struct sockaddr *)pData3); + // remember we have data + pSocket->bHasData = 1; + + // release socket critical section + NetCritLeave(&pSocket->RecvCrit); + + // see if we should issue callback + if ((pSocket->pCallback != NULL) && (pSocket->iCallMask & CALLB_RECV)) + { + pSocket->pCallback(pSocket, 0, pSocket->pCallRef); + } + return(0); + } + // set SO_REUSEADDR + if (iOption == 'radr') + { + iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&iData1, sizeof(iData1)); + pSocket->iLastError = _SocketTranslateError(iResult); + return(pSocket->iLastError); + } + // set socket receive buffer size + if ((iOption == 'rbuf') || (iOption == 'sbuf')) + { + int32_t iOldSize, iNewSize; + int32_t iSockOpt = (iOption == 'rbuf') ? SO_RCVBUF : SO_SNDBUF; + socklen_t uOptLen = 4; + + // get current buffer size + getsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (char *)&iOldSize, &uOptLen); + + // set new size + iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (const char *)&iData1, sizeof(iData1)); + if ((pSocket->iLastError = _SocketTranslateError(iResult)) == SOCKERR_NONE) + { + // save new buffer size + if (iOption == 'rbuf') + { + pSocket->iRbufSize = iData1; + } + else + { + pSocket->iSbufSize = iData1; + } + } + + // get new size + getsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (char *)&iNewSize, &uOptLen); + #if defined(DIRTYCODE_LINUX) + /* as per SO_RCVBUF/SO_SNDBUF documentation: "The kernel doubles the value (to allow space for bookkeeping + overhead) when it is set using setsockopt(), and this doubled value is returned by getsockopt()." To + account for this we halve the getsockopt() value so it matches what we requested. */ + iNewSize /= 2; + #endif + NetPrintf(("dirtynetunix: setsockopt(%s) changed buffer size from %d to %d\n", (iOption == 'rbuf') ? "SO_RCVBUF" : "SO_SNDBUF", + iOldSize, iNewSize)); + + return(pSocket->iLastError); + } + // set SO_LINGER + if (iOption == 'soli') + { + struct linger lingerOptions; + lingerOptions.l_onoff = TRUE; + lingerOptions.l_linger = iData1; + iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, SO_LINGER, &lingerOptions, sizeof(lingerOptions)); + pSocket->iLastError = _SocketTranslateError(iResult); + return(pSocket->iLastError); + } + // unhandled + NetPrintf(("dirtynetunix: unhandled control option '%C'\n", iOption)); + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function SocketGetLocalAddr + + \Description + Returns the "external" local address (ie, the address as a machine "out on + the Internet" would see as the local machine's address). + + \Output + uint32_t - local address + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +uint32_t SocketGetLocalAddr(void) +{ + SocketStateT *pState = _Socket_pState; + if (pState->uLocalAddr == 0) + { + pState->uLocalAddr = SocketInfo(NULL, 'addr', 0, NULL, 0); + } + return(pState->uLocalAddr); +} + +/*F********************************************************************************/ +/*! + \Function SocketLookup + + \Description + Lookup a host by name and return the corresponding Internet address. Uses + a callback/polling system since the socket library does not allow blocking. + + \Input *pText - pointer to null terminated address string + \Input iTimeout - number of milliseconds to wait for completion + + \Output + HostentT * - hostent struct that includes callback vectors + + \Version 06/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +HostentT *SocketLookup(const char *pText, int32_t iTimeout) +{ + SocketStateT *pState = _Socket_pState; + SocketLookupPrivT *pPriv; + int32_t iAddr, iResult; + HostentT *pHost, *pHostRef; + DirtyThreadConfigT ThreadConfig; + + NetPrintf(("dirtynetunix: looking up address for host '%s'\n", pText)); + + // dont allow negative timeouts + if (iTimeout < 0) + { + return(NULL); + } + + // create new structure + pPriv = DirtyMemAlloc(sizeof(*pPriv), SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + ds_memclr(pPriv, sizeof(*pPriv)); + pHost = &pPriv->Host; + + // setup callbacks + pHost->Done = &_SocketLookupDone; + pHost->Free = &_SocketLookupFree; + // copy over the target address + ds_strnzcpy(pHost->name, pText, sizeof(pHost->name)); + + // look for refcounted lookup + if ((pHostRef = SocketHostnameAddRef(&pState->pHostList, &pPriv->Host, TRUE)) != NULL) + { + DirtyMemFree(pPriv, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + return(pHostRef); + } + + // check for dot notation, then check hostname cache + if (((iAddr = SocketInTextGetAddr(pText)) != 0) || ((iAddr = SocketHostnameCacheGet(pState->pHostnameCache, pText, pState->iVerbose)) != 0)) + { + // we've got a dot-notation address + pHost->addr = iAddr; + pHost->done = 1; + // return completed record + return(pHost); + } + + /* add an extra refcount for the thread; this ensures the host structure survives until the thread + is done with it. this must be done before thread creation. */ + pHost->refcount += 1; + + // configure threading + ds_memclr(&ThreadConfig, sizeof(ThreadConfig)); + ThreadConfig.pName = "SocketLookup"; + ThreadConfig.iAffinity = NetConnStatus('affn', 0, NULL, 0); + ThreadConfig.iVerbosity = pState->iVerbose-1; + + // create dns lookup thread + if ((iResult = DirtyThreadCreate(_SocketLookupThread, pPriv, &ThreadConfig)) < 0) + { + NetPrintf(("dirtynetunix: failed to create lookup thread (err=%d)\n", iResult)); + pPriv->Host.done = -1; + // remove refcount we just added + pHost->refcount -= 1; + } + + // return the host reference + return(pHost); +} + +/*F********************************************************************************/ +/*! + \Function SocketHost + + \Description + Return the host address that would be used in order to communicate with + the given destination address. + + \Input *pHost - [out] local sockaddr struct + \Input iHostlen - length of structure (sizeof(host)) + \Input *pDest - remote sockaddr struct + \Input iDestlen - length of structure (sizeof(dest)) + + \Output + int32_t - zero=success, negative=error + + \Version 12/12/2003 (sbevan) +*/ +/********************************************************************************F*/ +int32_t SocketHost(struct sockaddr *pHost, int32_t iHostlen, const struct sockaddr *pDest, int32_t iDestlen) +{ +#if defined(DIRTYCODE_APPLEIOS) + SocketStateT *pState = _Socket_pState; + + // must be same kind of addresses + if (iHostlen != iDestlen) + { + return(-1); + } + + // do family specific lookup + if (pDest->sa_family == AF_INET) + { + // special case destination of zero or loopback to return self + if ((SockaddrInGetAddr(pDest) == 0) || (SockaddrInGetAddr(pDest) == 0x7f000000)) + { + ds_memcpy(pHost, pDest, iHostlen); + return(0); + } + else + { + ds_memclr(pHost, iHostlen); + pHost->sa_family = AF_INET; + SockaddrInSetAddr(pHost, pState->uLocalAddr); + return(0); + } + } + + // unsupported family + ds_memclr(pHost, iHostlen); + return(-3); +#elif defined(DIRTYCODE_LINUX) || defined(DIRTYCODE_ANDROID) + struct sockaddr_in HostAddr; + struct sockaddr_in DestAddr; + uint32_t uSource = 0, uTarget; + int32_t iSocket; +#if DIRTYCODE_LOGGING + SocketStateT *pState = _Socket_pState; +#endif + // get target address + uTarget = SockaddrInGetAddr(pDest); + + // create a temp socket (must be datagram) + iSocket = socket(AF_INET, SOCK_DGRAM, 0); + if (iSocket != INVALID_SOCKET) + { + int32_t iIndex; + int32_t iCount; + struct ifreq EndpRec[16]; + struct ifconf EndpList; + uint32_t uAddr; + uint32_t uMask; + + // request list of interfaces + ds_memclr(&EndpList, sizeof(EndpList)); + EndpList.ifc_req = EndpRec; + EndpList.ifc_len = sizeof(EndpRec); + if (ioctl(iSocket, SIOCGIFCONF, &EndpList) >= 0) + { + // figure out number and walk the list + iCount = EndpList.ifc_len / sizeof(EndpRec[0]); + for (iIndex = 0; iIndex < iCount; ++iIndex) + { + // extract the individual fields + ds_memcpy(&HostAddr, &EndpRec[iIndex].ifr_addr, sizeof(HostAddr)); + uAddr = ntohl(HostAddr.sin_addr.s_addr); + ioctl(iSocket, SIOCGIFNETMASK, &EndpRec[iIndex]); + ds_memcpy(&DestAddr, &EndpRec[iIndex].ifr_broadaddr, sizeof(DestAddr)); + uMask = ntohl(DestAddr.sin_addr.s_addr); + ioctl(iSocket, SIOCGIFFLAGS, &EndpRec[iIndex]); + + NetPrintfVerbose((pState->iVerbose, 1, "dirtynetunix: checking interface name=%s, fam=%d, flags=%04x, addr=%08x, mask=%08x\n", + EndpRec[iIndex].ifr_name, HostAddr.sin_family, + EndpRec[iIndex].ifr_flags, uAddr, uMask)); + + // only consider live inet interfaces + if ((HostAddr.sin_family == AF_INET) && ((EndpRec[iIndex].ifr_flags & (IFF_LOOPBACK+IFF_UP)) == (IFF_UP))) + { + // if target is within address range, must be hit + if ((uAddr & uMask) == (uTarget & uMask)) + { + uSource = uAddr; + break; + } + // if in a private address space and nothing else found + if (((uAddr & 0xff000000) == 0x0a000000) || ((uAddr & 0xffff0000) == 0xc0a80000)) + { + if (uSource == 0) + { + uSource = uAddr; + } + } + // always take a public address + else + { + uSource = uAddr; + } + } + } + } + // close the socket + close(iSocket); + } + + // populate dest addr + SockaddrInit(pHost, AF_INET); + SockaddrInSetAddr(pHost, uSource); + + // return result + return((uSource != 0) ? 0 : -1); +#else + return(-1); +#endif +} + diff --git a/src/thirdparty/dirtysdk/source/dirtysock/unix/netconnunix.c b/src/thirdparty/dirtysdk/source/dirtysock/unix/netconnunix.c new file mode 100644 index 00000000..428ea2ea --- /dev/null +++ b/src/thirdparty/dirtysdk/source/dirtysock/unix/netconnunix.c @@ -0,0 +1,895 @@ +/*H********************************************************************************/ +/*! + \File netconnunix.c + + \Description + Provides network setup and teardown support. Does not actually create any + kind of network connections. + + \Copyright + Copyright (c) 2010 Electronic Arts Inc. + + \Version 04/05/2010 (jbrookes) First version; a vanilla port to Unix from PS3 +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtyvers.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtycert.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/proto/protoupnp.h" +#include "netconncommon.h" + +#ifdef DIRTYCODE_APPLEIOS + +#include "DirtySDK/dirtysock/iphone/netconnios.h" + +#endif + +/*** Defines **********************************************************************/ + +//! UPNP port +#define NETCONN_DEFAULT_UPNP_PORT (3659) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! private module state +typedef struct NetConnRefT +{ + NetConnCommonRefT Common; //!< cross-platform netconn data (must come first!) + + enum + { + ST_INIT, //!< initialization + ST_CONN, //!< bringing up network interface + ST_IDLE, //!< active + } eState; //!< internal connection state + + uint32_t uConnStatus; //!< connection status (surfaced to user) + + ProtoUpnpRefT *pProtoUpnp; //!< protoupnp module state + int32_t iPeerPort; //!< peer port to be opened by upnp; if zero, still find upnp router but don't open a port + int32_t iNumProcCores; //!< number of processor cores on the system + int32_t iThreadCpuAffinity; //!< cpu affinity used for our internal threads +} NetConnRefT; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +//! global module ref +static NetConnRefT *_NetConn_pRef = NULL; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _NetConnUpdateConnStatus + + \Description + Update the Connection Status and provide logging of status changes + + \Input *pRef - pointer to net NetConn module ref + \Input *uNewConnStatus - the new conn status + + \Version 01/19/2015 (tcho) +*/ +/********************************************************************************F*/ +static void _NetConnUpdateConnStatus(NetConnRefT *pRef, uint32_t uNewConnStatus) +{ + #if DIRTYCODE_LOGGING + int32_t iIndex; + char strConnStatus[5]; + + for (iIndex = 0; iIndex < 4; ++iIndex) + { + strConnStatus[iIndex] = ((char *) &uNewConnStatus)[3 - iIndex]; + } + + strConnStatus[4] = 0; + + NetPrintf(("netconnunix: netconn status changed to %s\n", strConnStatus)); + #endif + + pRef->uConnStatus = uNewConnStatus; +} + +#if defined(DIRTYCODE_LINUX) +/*F********************************************************************************/ +/*! + \Function _NetConnGetProcLine + + \Description + Parse a single line of the processor entry file. + + \Input *pLine - pointer to line to parse + \Input *pName - [out] buffer to store parsed field name + \Input iNameLen - size of name buffer + \Input *pData - [out] buffer to store parsed field data + \Input iDataLen - size of data buffer + + \Version 04/26/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static void _NetConnGetProcLine(const char *pLine, char *pName, int32_t iNameLen, char *pData, int32_t iDataLen) +{ + const char *pParse; + + // skip to end of field name + for (pParse = pLine; (*pParse != '\t') && (*pParse != ':'); pParse += 1) + ; + + // copy field name + ds_strsubzcpy(pName, iNameLen, pLine, pParse - pLine); + + // skip to field value + for ( ; (*pParse == ' ') || (*pParse == '\t') || (*pParse == ':'); pParse += 1) + ; + + // find end of field value + for (pLine = pParse; (*pParse != '\n') && (*pParse != '\0'); pParse += 1) + ; + + // copy field value + ds_strsubzcpy(pData, iDataLen, pLine, pParse - pLine); +} +#endif + +#if defined(DIRTYCODE_LINUX) +/*F********************************************************************************/ +/*! + \Function _NetConnGetProcRecord + + \Description + Parse a single proc record. + + \Input *pProcFile - proc record file pointer + + \Output + int32_t - one=success, zero=no more entries + + \Version 04/26/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetConnGetProcRecord(FILE *pProcFile) +{ + char strBuf[1024], strName[32], strValue[128], *pLine; + while (((pLine = fgets(strBuf, sizeof(strBuf), pProcFile)) != NULL) && (strBuf[0] != '\n')) + { + _NetConnGetProcLine(strBuf, strName, sizeof(strName), strValue, sizeof(strValue)); + } + return(pLine != NULL); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _NetConnGetNumProcs + + \Description + Get the number of processors on the system. + + \Output + int32_t - number of processors in system, or <=0 on failure + + \Version 04/26/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _NetConnGetNumProcs(void) +{ + #if defined(DIRTYCODE_LINUX) + FILE *pFile = fopen("/proc/cpuinfo", "r"); + int32_t iNumProcs = -1; + + if (pFile != NULL) + { + for (iNumProcs = 0; _NetConnGetProcRecord(pFile) != 0; iNumProcs += 1) + ; + fclose(pFile); + + NetPrintf(("netconnunix: parsed %d processor cores from cpuinfo\n", iNumProcs)); + } + else + { + NetPrintf(("netconnunix: could not open proc file\n")); + } + return(iNumProcs); + #else + return(-1); + #endif +} + +/*F********************************************************************************/ +/*! + \Function _NetConnGetInterfaceType + + \Description + Get interface type and return it to caller. + + \Input *pRef - module state + + \Output + uint32_t - interface type bitfield (NETCONN_IFTYPE_*) + + \Version 10/08/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _NetConnGetInterfaceType(NetConnRefT *pRef) +{ + uint32_t uIfType = NETCONN_IFTYPE_ETHER; + +#if defined(DIRTYCODE_ANDROID) + + uIfType = NETCONN_IFTYPE_NONE; + + if (SocketInfo(NULL, 'eth0', 0, NULL, 0) == 0) + { + uIfType = NETCONN_IFTYPE_WIRELESS; + } + + if (SocketInfo(NULL, 'wan0', 0, NULL, 0) == 0) + { + uIfType = NETCONN_IFTYPE_CELL; + } + +#elif defined(DIRTYCODE_APPLEIOS) + + uIfType = NetConnStatusIos(&(_NetConn_pRef->Common), 'type', 0, NULL, 0); + +#endif + + return(uIfType); +} + +/*F********************************************************************************/ +/*! + \Function _NetConnUpdate + + \Description + Update status of NetConn module. This function is called by NetConnIdle. + + \Input *pData - pointer to NetConn module ref + \Input uTick - current tick counter + + \Version 07/18/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _NetConnUpdate(void *pData, uint32_t uTick) +{ + NetConnRefT *pRef = (NetConnRefT *)pData; + + // perform idle processing + SocketControl(NULL, 'idle', uTick, NULL, NULL); + + // wait for network active status + if (pRef->eState == ST_CONN) + { + uint32_t uSocketConnStatus = SocketInfo(NULL, 'conn', 0, NULL, 0); + + if (pRef->uConnStatus != uSocketConnStatus) + { + _NetConnUpdateConnStatus(pRef, uSocketConnStatus); + } + + if (pRef->uConnStatus == '+onl') + { + // discover upnp router information + if (pRef->pProtoUpnp != NULL) + { + if (pRef->iPeerPort != 0) + { + ProtoUpnpControl(pRef->pProtoUpnp, 'port', pRef->iPeerPort, 0, NULL); + ProtoUpnpControl(pRef->pProtoUpnp, 'macr', 'upnp', 0, NULL); + } + else + { + ProtoUpnpControl(pRef->pProtoUpnp, 'macr', 'dscg', 0, NULL); + } + } + + pRef->eState = ST_IDLE; + } + } + + // update connection status while idle + if (pRef->eState == ST_IDLE) + { + // update connection status if not already in an error state + if ((pRef->uConnStatus >> 24) != '-') + { + uint32_t uSocketConnStat = SocketInfo(NULL, 'conn', 0, NULL, 0); + + if (pRef->uConnStatus != uSocketConnStat) + { + _NetConnUpdateConnStatus(pRef, uSocketConnStat); + } + } + } + + // if error status, go to idle state from any other state + if ((pRef->eState != ST_IDLE) && (pRef->uConnStatus >> 24 == '-')) + { + pRef->eState = ST_IDLE; + } +} + +/*F********************************************************************************/ +/*! + \Function _NetConnShutdownInternal + + \Description + Shutdown the network code and return to idle state for internal use + + \Input pRef - netconn ref + \Input uShutdownFlags - shutdown configuration flags + + \Output + int32_t - negative=error, else zero + + \Version 1/13/2020 (tcho) +*/ +/********************************************************************************F*/ +int32_t _NetConnShutdownInternal(NetConnRefT *pRef, uint32_t uShutdownFlags) +{ + int32_t iResult = 0; + + // decrement and check the refcount + if ((iResult = NetConnCommonCheckRef((NetConnCommonRefT*)pRef)) < 0) + { + return(iResult); + } + + if (_NetConn_pRef != NULL) + { + // disconnect network interfaces + NetConnDisconnect(); + } + + // destroy the upnp ref + if (pRef->pProtoUpnp != NULL) + { + ProtoUpnpDestroy(pRef->pProtoUpnp); + pRef->pProtoUpnp = NULL; + } + + // destroy the dirtycert module + DirtyCertDestroy(); + + // shut down protossl + ProtoSSLShutdown(); + + // remove netconn idle task + NetConnIdleDel(_NetConnUpdate, pRef); + + // shut down Idle handler + NetConnIdleShutdown(); + + // shutdown the network code + SocketDestroy(0); + + #ifdef DIRTYCODE_APPLEIOS + + //call ios NetConnShutdown + NetConnShutdownIos(uShutdownFlags); + + #endif + + // common shutdown (must come last as this frees the memory) + NetConnCommonShutdown(&pRef->Common); + _NetConn_pRef = NULL; + + return(0); +} + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function NetConnStartup + + \Description + Bring the network connection module to life. Creates connection with IOP + resources and gets things ready to go. Puts all device drivers into "probe" + mode so they look for appropriate hardware. Does not actually start any + network activity. + + \Input *pParams - startup parameters + + \Output + int32_t - zero=success, negative=failure + + \Notes + NetConnRefT::iRefCount serves as a counter for the number of times + NetConnStartup has been called. This allows us to track how many modules + are using it and how many times we expect NetConnShutdown to the called. + In the past we only allowed a single call to NetConnStartup but some + libraries may need to networking without a guarentee that the game has + already started it. + + pParams can contain the following terms: + + \verbatim + -noupnp - disable upnp support + -servicename - set servicename required for SSL use + -singlethreaded - start DirtySock in single-threaded mode (typically when used in servers) + -affinity= - the cpu affinity for our internal threads + \endverbatim + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetConnStartup(const char *pParams) +{ + NetConnRefT *pRef = _NetConn_pRef; + int32_t iThreadPrio = 10; + int32_t iRet = 0; + int32_t iResult = 0; + char strThreadCpuAffinity[16]; + + // allow NULL params + if (pParams == NULL) + { + pParams = ""; + } + + // debug display of input params + NetPrintf(("netconnunix: startup params='%s'\n", pParams)); + + // common startup + // pRef shall hold the address of the NetConnRefT after completion if no error occured + iResult = NetConnCommonStartup(sizeof(*pRef), pParams, (NetConnCommonRefT**)(&pRef)); + + // treat the result of the common startup, if already started simply early out + if (iResult == NETCONN_ERROR_ALREADY_STARTED) + { + return(0); + } + // otherwise, if an error occured report it + else if (iResult < 0) + { + return(iResult); + } + + pRef->eState = ST_INIT; + pRef->iPeerPort = NETCONN_DEFAULT_UPNP_PORT; + + // check for singlethreaded mode + if (strstr(pParams, "-singlethreaded")) + { + iThreadPrio = -1; + } + + // get the thread cpu affinity setting from our startup params, defaulting to 0x0 + ds_memclr(strThreadCpuAffinity, sizeof(strThreadCpuAffinity)); + NetConnCopyParam(strThreadCpuAffinity, sizeof(strThreadCpuAffinity), "-affinity=", pParams, "0x0"); + pRef->iThreadCpuAffinity =(int32_t) strtol(strThreadCpuAffinity, NULL, 16); + + // create network instance + if (SocketCreate(iThreadPrio, 0, pRef->iThreadCpuAffinity) != 0) + { + NetPrintf(("netconnunix: unable to start up dirtysock\n")); + _NetConnShutdownInternal(pRef, 0); + return(NETCONN_ERROR_SOCKET_CREATE); + } + + // create and configure dirtycert + if (NetConnDirtyCertCreate(pParams)) + { + _NetConnShutdownInternal(pRef, 0); + NetPrintf(("netconnunix: unable to create dirtycert\n")); + return(NETCONN_ERROR_DIRTYCERT_CREATE); + } + + // start up protossl + if (ProtoSSLStartup() < 0) + { + _NetConnShutdownInternal(pRef, 0); + NetPrintf(("netconnunix: unable to start up protossl\n")); + return(NETCONN_ERROR_PROTOSSL_CREATE); + } + + // create the upnp module + if (!strstr(pParams, "-noupnp")) + { + pRef->pProtoUpnp = ProtoUpnpCreate(); + if (pRef->pProtoUpnp == NULL) + { + _NetConnShutdownInternal(pRef, 0); + NetPrintf(("netconnunix: unable to start up protoupnp\n")); + return(NETCONN_ERROR_PROTOUPNP_CREATE); + } + } + + // add netconn task handle + if (NetConnIdleAdd(_NetConnUpdate, pRef) < 0) + { + _NetConnShutdownInternal(pRef, 0); + NetPrintf(("netconnunix: unable to add netconn task handler\n")); + return(NETCONN_ERROR_INTERNAL); + } + +#ifdef DIRTYCODE_APPLEIOS + //call ios netconn startup + if ((iRet = NetConnStartupIos(pParams)) < 0) + { + _NetConnShutdownInternal(pRef, 0); + return(iRet); + } +#endif + + // save ref + _NetConn_pRef = pRef; + + return(iRet); +} + +/*F********************************************************************************/ +/*! + \Function NetConnQuery + + \Description + Query the list of available connection configurations. This list is loaded + from the specified device. The list is returned in a simple fixed width + array with one string per array element. The caller can find the user portion + of the config name via strchr(item, '#')+1. + + \Input *pDevice - device to scan (mc0:, mc1:, pfs0:, pfs1:) + \Input *pList - buffer to store output array in + \Input iSize - length of buffer in bytes + + \Output + int32_t - negative=error, else number of configurations + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetConnQuery(const char *pDevice, NetConfigRecT *pList, int32_t iSize) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetConnConnect + + \Description + Used to bring the networking online with a specific configuration. Uses a + configuration returned by NetConnQuery. + + \Input *pConfig - unused + \Input *pOption - asciiz list of config parameters + "peerport=" to specify peer port to be opened by upnp. + \Input iData - platform-specific + + \Output + int32_t - negative=error, zero=success + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetConnConnect(const NetConfigRecT *pConfig, const char *pOption, int32_t iData) +{ + int32_t iResult = 0; + NetConnRefT *pRef = _NetConn_pRef; + + // check connection options, if present + if (pRef->eState == ST_INIT) + { + // check for connect options + if (pOption != NULL) + { + const char *pOpt; + + // check for specification of peer port + if ((pOpt = strstr(pOption, "peerport=")) != NULL) + { + pRef->iPeerPort = (int32_t)strtol(pOpt+9, NULL, 10); + } + } + NetPrintf(("netconnunix: upnp peerport=%d %s\n", + pRef->iPeerPort, (pRef->iPeerPort == NETCONN_DEFAULT_UPNP_PORT ? "(default)" : "(selected via netconnconnect param)"))); + + // start up network interface + SocketControl(NULL, 'conn', 0, NULL, NULL); + + // transition to connecting state + pRef->eState = ST_CONN; + } + else + { + NetPrintf(("netconnunix: NetConnConnect() ignored because already connected!\n")); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function NetConnDisconnect + + \Description + Used to bring down the network connection. After calling this, it would + be necessary to call NetConnConnect to bring the connection back up or + NetConnShutdown to completely shutdown all network support. + + \Output + int32_t - negative=error, zero=success + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetConnDisconnect(void) +{ + NetConnRefT *pRef = _NetConn_pRef; + + // shut down networking + if (pRef->eState != ST_INIT) + { + // bring down network interface + SocketControl(NULL, 'disc', 0, NULL, NULL); + + // reset status + pRef->eState = ST_INIT; + pRef->uConnStatus = 0; + } + + // abort upnp operations + if (pRef->pProtoUpnp != NULL) + { + ProtoUpnpControl(pRef->pProtoUpnp, 'abrt', 0, 0, NULL); + } + + // done + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetConnControl + + \Description + Set module behavior based on input selector. + + \Input iControl - input selector + \Input iValue - selector input + \Input iValue2 - selector input + \Input *pValue - selector input + \Input *pValue2 - selector input + + \Output + int32_t - selector result + + \Notes + iControl can be one of the following: + + \verbatim + snam: set DirtyCert service name + \endverbatim + + Unhandled selectors are passed through to NetConnCommonControl() + + \Version 1.0 04/27/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetConnControl(int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue, void *pValue2) +{ + NetConnRefT *pRef = _NetConn_pRef; + + // make sure module is started before allowing any other control calls + if (pRef == NULL) + { + NetPrintf(("netconnunix: warning - calling NetConnControl() while module is not initialized\n")); + return(-1); + } + + // set dirtycert service name + if (iControl == 'snam') + { + return(DirtyCertControl('snam', 0, 0, pValue)); + } + + // pass through unhandled selectors to NetConnCommon + return(NetConnCommonControl(&pRef->Common, iControl, iValue, iValue2, pValue, pValue2)); +} + +/*F********************************************************************************/ +/*! + \Function NetConnStatus + + \Description + Check general network connection status. Different selectors return + different status attributes. + + \Input iKind - status selector ('open', 'conn', 'onln') + \Input iData - (optional) selector specific + \Input *pBuf - (optional) pointer to output buffer + \Input iBufSize - (optional) size of output buffer + + \Output + int32_t - selector specific + + \Notes + iKind can be one of the following: + + \verbatim + addr: ip address of client + affn: thread cpu affinity setting + bbnd: TRUE if broadband, else FALSE + conn: connection status: +onl=online, ~=in progress, -=NETCONN_ERROR_* + hwid: (IOS only) This will return the vendor id in pBuf. pBuf must be at least 17 byte. + macx: MAC address of client (returned in pBuf) + ncon: returns the network connectivity level (iOS and Android Only) + onln: true/false indication of whether network is operational + open: true/false indication of whether network code running + proc: number of processor cores on the system (Linux only) + type: connection type: NETCONN_IFTYPE_* bitfield + upnp: return protoupnp external port info, if available + vers: return DirtySDK version + \endverbatim + + Unhandled selectors are passed through to NetConnCommonStatus() + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetConnStatus(int32_t iKind, int32_t iData, void *pBuf, int32_t iBufSize) +{ + NetConnRefT *pRef = _NetConn_pRef; + + // initialize output buffer + if (pBuf != NULL) + { + ds_memclr(pBuf, iBufSize); + } + + // see if network code is initialized + if (iKind == 'open') + { + return(pRef != NULL); + } + // return DirtySDK version + if (iKind == 'vers') + { + return(DIRTYSDK_VERSION); + } + + // make sure module is started before allowing any other status calls + if (pRef == NULL) + { + NetPrintf(("netconnunix: warning - calling NetConnStatus() while module is not initialized\n")); + return(-1); + } + + // return the thread cpu affinity setting + if (iKind == 'affn') + { + return(pRef->iThreadCpuAffinity); + } + + // return broadband (TRUE/FALSE) + if (iKind == 'bbnd') + { + return(TRUE); + } + // connection status + if (iKind == 'conn') + { + return(pRef->uConnStatus); + } + // see if connected to ISP/LAN + if (iKind == 'onln') + { + return(pRef->uConnStatus == '+onl'); + } + + // return number of processor cores + if (iKind == 'proc') + { + if (pRef->iNumProcCores == 0) + { + pRef->iNumProcCores = _NetConnGetNumProcs(); + } + return(pRef->iNumProcCores); + } + // return interface type (more verbose) + if (iKind == 'type') + { + return(_NetConnGetInterfaceType(pRef)); + } + // return upnp addportmap info, if available + if (iKind == 'upnp') + { + // if protoupnp is available, and we've added a port map, return the external port for the port mapping + if ((pRef->pProtoUpnp != NULL) && (ProtoUpnpStatus(pRef->pProtoUpnp, 'stat', NULL, 0) & PROTOUPNP_STATUS_ADDPORTMAP)) + { + return(ProtoUpnpStatus(pRef->pProtoUpnp, 'extp', NULL, 0)); + } + } +#ifdef DIRTYCODE_ANDROID + + if (iKind == 'ncon') + { + if ((SocketInfo(NULL, 'eth0', 0, NULL, 0) == 0) || SocketInfo(NULL, 'wan0', 0, NULL, 0) == 0) + { + return(TRUE); + } + else + { + return(FALSE); + } + } + +#endif + + +#ifdef DIRTYCODE_APPLEIOS + + //pass unrecgnized options to NetConnStatusIos and Socket Info + return(NetConnStatusIos(&pRef->Common, iKind, iData, pBuf, iBufSize)); + +#else + + // pass unrecognized options to NetConnCommon + return(NetConnCommonStatus(&pRef->Common, iKind, iData, pBuf, iBufSize)); + +#endif + +} + +/*F********************************************************************************/ +/*! + \Function NetConnShutdown + + \Description + Shutdown the network code and return to idle state. + + \Input uShutdownFlags - shutdown configuration flags + + \Output + int32_t - negative=error, else zero + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetConnShutdown(uint32_t uShutdownFlags) +{ + NetConnRefT *pRef = _NetConn_pRef; + + // make sure we've been started + if (pRef == NULL) + { + return(NETCONN_ERROR_NOTACTIVE); + } + + return(_NetConnShutdownInternal(pRef, uShutdownFlags)); +} + +/*F********************************************************************************/ +/*! + \Function NetConnSleep + + \Description + Sleep the application for some number of milliseconds. + + \Input iMilliSecs - number of milliseconds to block for + + \Version 10/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void NetConnSleep(int32_t iMilliSecs) +{ + usleep(iMilliSecs*1000); +} + diff --git a/src/thirdparty/dirtysdk/source/game/connapi.c b/src/thirdparty/dirtysdk/source/game/connapi.c new file mode 100644 index 00000000..cfd923b1 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/game/connapi.c @@ -0,0 +1,4180 @@ +/*H********************************************************************************/ +/*! + \File connapi.c + + \Description + ConnApi is a high-level connection manager, that packages the "connect to + peer" process into a single module. Both game connections and voice + connections can be managed. Multiple peers are supported in a host/client + model for the game connection, and a peer/peer model for the voice + connections. + + \Copyright + Copyright (c) Electronic Arts 2005. ALL RIGHTS RESERVED. + + \Notes + About ConnApi using ProtoMangle: + + For plaforms other than xboxone: + * ConnApi uses ProtoMangle to exercice connection demangling logic + with a central demangler service. + * When demangler is enabled, a typical connection state transition is: + success: INIT --> CONN --> ACTV + success after demangling: INIT --> CONN --> MNGL --> INIT --> CONN --> ACTV + * For each connection, the boolean ConnApiConnInfoT::bDemangling is used to + track "demangling being in progress" across the "MNGL --> INIT --> CONN --> ACTV" + state sequence such that ProtoMangleReport() gets called appropriately + once post-demangling conn attempt finishes. + * Additionally, the boolean ConnApiConnInfoT::bDemangling is used to "serialize" + demangling attempts as interaction with the demangler is limited to one session + at a time. + + For xboxone: + * The central demangling service is never used, but the secure device association + creation is mapped to the protomangle metaphore. + * A typical connection state transition is: + p2p conn (SecureAssoc needed): MNGL --> INIT --> CONN --> ACTV + server conn (SecureAssoc not needed): INIT --> CONN --> ACTV + * For each connection, the boolean ConnApiConnInfoT::bDemangling no longer means + "demangling being in progress" across states. It's rather used in such way that, + for server conns, if the conn attempt fails, then the MGNL state is not entered. + + + \Version 01/04/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtynames.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/proto/protomangle.h" +#include "DirtySDK/proto/prototunnel.h" +#include "DirtySDK/voip/voip.h" +#include "DirtySDK/voip/voipgroup.h" + +#include "DirtySDK/game/connapi.h" + +/*** Defines **********************************************************************/ + +//! define if we're using Xbox networking +#if defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) +#define CONNAPI_XBOX_NETWORKING 1 +#else +#define CONNAPI_XBOX_NETWORKING 0 +#endif + +//! default connapi timeout +#define CONNAPI_TIMEOUT_DEFAULT (15*1000) + +//! connapi connection timeout +#define CONNAPI_CONNTIMEOUT_DEFAULT (10*1000) + +//! connapi default demangler timeout (per user) +#if CONNAPI_XBOX_NETWORKING +#define CONNAPI_DEMANGLER_TIMEOUT (2*CONNAPI_CONNTIMEOUT_DEFAULT) // experimentation showed that creation of security association can be significantly long +#define CONNAPI_DEMANGLER_WITH_FAILOVER_TIMEOUT (CONNAPI_CONNTIMEOUT_DEFAULT) // however for scenarios involving CC assistance, we don't wait that long +#else +#define CONNAPI_DEMANGLER_TIMEOUT (CONNAPI_CONNTIMEOUT_DEFAULT) +#define CONNAPI_DEMANGLER_WITH_FAILOVER_TIMEOUT (CONNAPI_DEMANGLER_TIMEOUT) +#endif + +//! default GameLink buffer size +#define CONNAPI_LINKBUFDEFAULT (1024) + +//! test demangling +#define CONNAPI_DEMANGLE_TEST (DIRTYCODE_DEBUG && FALSE) + +//! random demangle local port? +#define CONNAPI_RNDLCLDEMANGLEPORT (0) // used to be enabled for WII revolution platform + +//! max number of registered callbacks +#define CONNAPI_MAX_CALLBACKS (8) + +//! connapi client flags +#define CONNAPI_CLIENTFLAG_REMOVE (1) // remove client from clientlist +#define CONNAPI_CLIENTFLAG_TUNNELPORTDEMANGLED (2) // tunnel port has been demangled +#define CONNAPI_CLIENTFLAG_SECUREADDRRESOLVED (4) // secure address has been resolved (xboxone-specific) + +//! debug flags +#define CONNAPI_CLIENTFLAG_P2PFAILDBG (128) // set to force P2P connections to fail + +/*** Type Definitions *************************************************************/ +struct ConnApiRefT +{ + //! connapi user callback info + ConnApiCallbackT *pCallback[CONNAPI_MAX_CALLBACKS]; + void *pUserData[CONNAPI_MAX_CALLBACKS]; + + //! dirtymem memory group + int32_t iMemGroup; + void *pMemGroupUserData; + + //! game port to connect on + uint16_t uGamePort; + + //! voip port to connect on + uint16_t uVoipPort; + + //! game connection flags + uint16_t uConnFlags; + + //! netmask, used for external address comparisons + uint32_t uNetMask; + + //! game name + char strGameName[32]; + + //! game link buffer size + int32_t iLinkBufSize; + + //! master game util ref (used for advertising) + NetGameUtilRefT *pGameUtilRef; + + //! protomangle ref + ProtoMangleRefT *pProtoMangle; + + //! prototunnel ref + ProtoTunnelRefT *pProtoTunnel; + + //! prototunnel port + int32_t iTunnelPort; + + //! do we own tunnel ref? + int32_t bTunnelOwner; + + //! protomangle server name + char strDemanglerServer[48]; + + //! voip ref + VoipRefT *pVoipRef; + + //! voipgroup ref + VoipGroupRefT *pVoipGroupRef; + + //! comm construct function + CommAllConstructT *pCommConstruct; + + //! our address + DirtyAddrT SelfAddr; + + //! our unique identifier + uint32_t uSelfId; + + //! index of ourself in client list + int32_t iSelf; + + //! session identifier + int32_t iSessId; + + //! connection timeout value + int32_t iConnTimeout; + + //! timeout value + int32_t iTimeout; + + //! demangler timeouts + int32_t iConfigMnglTimeout; // configured timeout for cases where CC assistance are not applicable + int32_t iConfigMnglTimeoutFailover; // configured timeout for cases where CC assistance are applicable + int32_t iCurrentMnglTimeout; // effective timeout value to be passed down to ProtoMangle + + //! gamelink configuration - input packet queue size + int32_t iGameMinp; + + //! gamelink configuration - output packet queue size + int32_t iGameMout; + + //! gamelink configuration - max packet width + int32_t iGameMwid; + + //! gamelink configuration - unacknowledged packet window + int32_t iGameUnackLimit; + + //! Connection Concierge mode (CONNAPI_CCMODE_*) + int32_t iCcMode; + + //! session information + char strSession[128]; + + //! demangler reporting? + uint8_t bReporting; + + //! is demangler enabled? + uint8_t bDemanglerEnabled; + + //! is tunnel enabled? + uint8_t bTunnelEnabled; + + //! inside of a callback? + uint8_t bInCallback; + + //! disc callback on client removal? + uint8_t bRemoveCallback; + + //! auto-update enabled? + uint8_t bAutoUpdate; + + #if CONNAPI_XBOX_NETWORKING + //! 'exsn', the external session name - passed to the demangler + char strExternalSessionName[128]; + + //! 'estn', the external session template name - passed to the demangler + char strExternalSessionTemplateName[128]; + + //! 'scid', the service configuration id - passed to the demangler + char strScid[128]; + #endif + + //! 'adve', TRUE if ProtoAdvt advertising enabled + uint8_t bDoAdvertising; + + //! 'meta', TRUE if commudp metadata enabled + uint8_t bCommUdpMetadata; + + //! game socket ref, if available + uintptr_t uGameSockRef; + + //! voip socket ref, if available + uintptr_t uVoipSockRef; + + //! tunnel socket ref, if available + uintptr_t uTunlSockRef; + + //! game host + int32_t iGameHostIndex; + //! voip host + int32_t iVoipHostIndex; + + #if DIRTYCODE_DEBUG + //! force direct connection to fail ? + uint8_t bFailP2PConnect; + #endif + + uint32_t uGameTunnelFlag; + uint32_t uGameTunnelFlagOverride; + + int32_t iQosDuration; + int32_t iQosInterval; + int32_t iQosPacketSize; + + //! client state + enum + { + ST_IDLE, //!< idle + ST_INGAME //!< hosting or joined a game + } eState; + + //! game network topology + ConnApiGameTopologyE eGameTopology; + //! voip network topology + ConnApiVoipTopologyE eVoipTopology; + + //! client list - must come last in ref as it is variable length + ConnApiClientListT ClientList; +}; + +/*** Variables ********************************************************************/ + +//! number of voipgroups we allocate in the manager +static int8_t _ConnApi_iMaxVoipGroups = 8; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _ConnApiDefaultCallback + + \Description + Default ConnApi user callback. On a debug build, displays state transition + information. + + \Input *pConnApi - connection manager ref + \Input *pCbInfo - connection info + \Input *pUserData - user callback data + + \Version 01/17/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiDefaultCallback(ConnApiRefT *pConnApi, ConnApiCbInfoT *pCbInfo, void *pUserData) +{ + #if DIRTYCODE_LOGGING + static const char *_StateNames[CONNAPI_NUMSTATUSTYPES] = + { + "CONNAPI_STATUS_INIT", + "CONNAPI_STATUS_CONN", + "CONNAPI_STATUS_MNGL", + "CONNAPI_STATUS_ACTV", + "CONNAPI_STATUS_DISC" + }; + static const char *_TypeNames[CONNAPI_NUMCBTYPES] = + { + "CONNAPI_CBTYPE_GAMEEVENT", + "CONNAPI_CBTYPE_DESTEVENT", + "CONNAPI_CBTYPE_VOIPEVENT", + }; + + // display state change + NetPrintf(("connapi: [%p] client %d) [%s] %s -> %s\n", pConnApi, pCbInfo->iClientIndex, _TypeNames[pCbInfo->eType], + _StateNames[pCbInfo->eOldStatus], _StateNames[pCbInfo->eNewStatus])); + #endif +} + +#if CONNAPI_RNDLCLDEMANGLEPORT +/*F********************************************************************************/ +/*! + \Function _ConnApiGenerateDemanglePort + + \Description + Generate a "random" demangle port to use, actually generated based on the + two lowest bytes of the MAC address and multiplied by a small prime number, + and then modded into a small range of ports. The intent here is to generate + ports that will both not be reused on successive attempts and also be unlikely + to collide with other client choices, in case there are other clients operating + the same code behind the same NAT device. + + \Input *pConnApi - connection manager ref + + \Output + uint16_t - generated demangle port to use + + \Version 03/02/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static uint16_t _ConnApiGenerateDemanglePort(ConnApiRefT *pConnApi) +{ + static const uint16_t _uPortBase = 10000; + static const uint16_t _uPortRange = 1000; + static uint16_t _uPortOffset = 0xffff; + + // initialize port offset based on mac address to avoid collisions with other clients behind the same NAT + if (_uPortOffset == 0xffff) + { + uint8_t aMacAddr[6]; + if (NetConnStatus('macx', 0, aMacAddr, sizeof(aMacAddr)) >= 0) + { + // generate mac-based port within specified range + NetPrintf(("connapi: [%p] generating port offset based on MAC addr '%s'\n", pConnApi, NetConnMAC())); + _uPortOffset = aMacAddr[4]; + _uPortOffset = (_uPortOffset << 8) + aMacAddr[5]; + _uPortOffset *= 7; + } + else + { + NetPrintf(("connapi: [%p] unable to acquire mac address\n", pConnApi)); + _uPortOffset = 0; + } + } + + // generate new port + _uPortOffset = (_uPortOffset + 1) % _uPortRange; + return(_uPortBase + _uPortOffset); +} +#endif + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _ConnApiDisplayClientInfo + + \Description + Debug-only function to print the given client info to debug output. + + \Input *pClient - pointer to client to display + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiDisplayClientInfo(ConnApiClientT *pClient, int32_t iClient) +{ + if (pClient->bAllocated) + { + NetPrintf(("connapi: %d) id:0x%08x lid:0x%08x rid:0x%08x ip:%a hosted:%s qos:%s dirtyaddr:%s\n", + iClient, pClient->ClientInfo.uId, pClient->ClientInfo.uLocalClientId, pClient->ClientInfo.uRemoteClientId, + pClient->ClientInfo.uAddr, pClient->ClientInfo.bIsConnectivityHosted ? "TRUE" : "FALSE", + pClient->ClientInfo.bEnableQos ? "TRUE" : "FALSE", pClient->ClientInfo.DirtyAddr.strMachineAddr)); + } + else + { + NetPrintf(("connapi: %d) empty\n", iClient)); + } +} +#endif + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _ConnApiDisplayClientList + + \Description + Debug-only function to print the given client list to debug output. + + \Input *pClientList - pointer to client list to display + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiDisplayClientList(ConnApiClientListT *pClientList) +{ + int32_t iClient; + + NetPrintf(("connapi: clientlist display\n")); + for (iClient = 0; iClient < pClientList->iMaxClients; iClient++) + { + _ConnApiDisplayClientInfo(&pClientList->Clients[iClient], iClient); + } +} +#endif + +/*F********************************************************************************/ +/*! + \Function _ConnApiClientReleaseProtoMangleResources + + \Description + When both voip and game are in DISC state, let ProtoMangle know that it + can release resources associated with this client. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to init + + \Notes + 1- Really needed for Xbox One only. Ends up being a no-op on other platform. + + 2- For dirtycast-based scenarios, the client entry for the gameserver always has + pClient->VoipInfo.eStatus = CONNAPI_STATUS_INIT, so the call to ProtoMangleContro() + is never exercised. + + \Version 09/13/2013 (mclouatre) +*/ +/********************************************************************************F*/ +static void _ConnApiClientReleaseProtoMangleResources(ConnApiRefT *pConnApi, ConnApiClientT *pClient) +{ + if ((pConnApi->bDemanglerEnabled == TRUE) && (pClient->GameInfo.eStatus == CONNAPI_STATUS_DISC) && (pClient->VoipInfo.eStatus == CONNAPI_STATUS_DISC)) + { + ProtoMangleControl(pConnApi->pProtoMangle, 'remv', (int32_t)(pClient - pConnApi->ClientList.Clients), 0, NULL); + } +} + + +#if CONNAPI_XBOX_NETWORKING +/*F********************************************************************************/ +/*! + \Function _ConnApiInitClientConnectionState + + \Description + Initialize client's game and voip connection states based on selected game and voip topology. + + \Input *pConnApi - connection manager ref + \Input *pClient - pointer to client to init + \Input iClientIndex - index of client + \Input uConnMode - bit mask to specify what connection state needs to be initialized (CONNAPI_CONNFLAG_XXXCONN) + + \Version 11/08/2013 (mclouatre) +*/ +/********************************************************************************F*/ +static void _ConnApiInitClientConnectionState(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, uint32_t uConnMode) +{ + /* + $todo - revisit this logics to make it more explicit + --> p2p game connections that do not need to be demangled are skipped in _ConnApiUpdateConnections() because + they don't have the CONNAPI_CONNFLAG_GAMECONN flag set + --> the same trick is not feasible with p2p voip connections because they need to reach the CONNAPI_STATUS_ACTV state + even if routed through a server; so we make sure they do not get demangled by defaulting straight + to CONNAPI_STATUS_INIT here. + */ + + if (uConnMode & CONNAPI_CONNFLAG_GAMECONN) + { + // if we're a xboxone client in phxc mode and we are currently dealing with the dedicated server client entry or we are connectivity hosted, + // then make sure we do not attempt to demangle with protomanglexboxone + if (!pConnApi->bDemanglerEnabled || pClient->ClientInfo.bIsConnectivityHosted || (iClientIndex == pConnApi->iSelf) || + ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED) && (iClientIndex == pConnApi->iGameHostIndex))) + { + pClient->GameInfo.eStatus = CONNAPI_STATUS_INIT; + pClient->GameInfo.bDemangling = TRUE; // make sure demangling will not be attempted if first connection attempt to server fails + } + else + { + pClient->GameInfo.eStatus = CONNAPI_STATUS_MNGL; + + // if secure address is already known, don't clear it - it will be reused + if ((pClient->uFlags & CONNAPI_CLIENTFLAG_SECUREADDRRESOLVED) == 0) + { + pClient->ClientInfo.uAddr = 0; + pClient->ClientInfo.uLocalAddr = 0; + } + } + } + + if (uConnMode & CONNAPI_CONNFLAG_VOIPCONN) + { + // if voip is routed through a dedicated server, game is routed through a dedicated server but voip is not or when connectivity hosted, then make sure we do + // not attempt to demangle voip connections with protomanglexboxone. There is no need to check to see if the client is a voip topology host (host will never have conn mode CONNAPI_CONNFLAG_VOIPCONN) + if (!pConnApi->bDemanglerEnabled || pClient->ClientInfo.bIsConnectivityHosted || (iClientIndex == pConnApi->iSelf) || + (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) || + ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED) && (iClientIndex == pConnApi->iGameHostIndex))) + { + pClient->VoipInfo.eStatus = CONNAPI_STATUS_INIT; + pClient->VoipInfo.bDemangling = TRUE; // make sure demangling will not be attempted if first connection attempt to server fails + } + else + { + pClient->VoipInfo.eStatus = CONNAPI_STATUS_MNGL; + + // if secure address is already known, don't clear it - it will be reused + if ((pClient->uFlags & CONNAPI_CLIENTFLAG_SECUREADDRRESOLVED) == 0) + { + pClient->ClientInfo.uAddr = 0; + pClient->ClientInfo.uLocalAddr = 0; + } + } + } +} +#endif + +/*F********************************************************************************/ +/*! + \Function _ConnApiInitClient + + \Description + Initialize a single client based on input user info. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to init + \Input *pClientInfo - pointer to user info to init client with + \Input iClientIndex - index of client + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiInitClient(ConnApiRefT *pConnApi, ConnApiClientT *pClient, ConnApiClientInfoT *pClientInfo, int32_t iClientIndex) +{ + // initialize new client structure and save input user info + ds_memclr(pClient, sizeof(*pClient)); + ds_memcpy_s(&pClient->ClientInfo, sizeof(pClient->ClientInfo), pClientInfo, sizeof(*pClientInfo)); + + // initialize default voip values + pClient->iVoipConnId = VOIP_CONNID_NONE; + + // set up remote (connect) port info + pClient->GameInfo.uMnglPort = (pClient->ClientInfo.uGamePort == 0) ? pConnApi->uGamePort : pClient->ClientInfo.uGamePort; + pClient->VoipInfo.uMnglPort = (pClient->ClientInfo.uVoipPort == 0) ? pConnApi->uVoipPort : pClient->ClientInfo.uVoipPort; + + // set up local (bind) port info + pClient->GameInfo.uLocalPort = ((pClient->ClientInfo.uLocalGamePort == 0) || (pConnApi->bTunnelEnabled == TRUE)) ? pConnApi->uGamePort : pClient->ClientInfo.uLocalGamePort; + pClient->VoipInfo.uLocalPort = ((pClient->ClientInfo.uLocalVoipPort == 0) || (pConnApi->bTunnelEnabled == TRUE)) ? pConnApi->uVoipPort : pClient->ClientInfo.uLocalVoipPort; + + // set unique client identifier if not already supplied + if (pClient->ClientInfo.uId == 0) + { + pClient->ClientInfo.uId = (uint32_t)iClientIndex + 1; + } + + if ((pClient->ClientInfo.bEnableQos == TRUE) && (iClientIndex != pConnApi->iSelf)) + { + NetPrintf(("connapi: [%p] delaying voip connection for player %d because of QoS validation\n", pConnApi, iClientIndex)); + } + else + { + // Voip is not delayed when bEnableQos is FALSE + pClient->bEstablishVoip = TRUE; + } + + #if CONNAPI_XBOX_NETWORKING + _ConnApiInitClientConnectionState(pConnApi, pClient, iClientIndex, CONNAPI_CONNFLAG_GAMECONN|CONNAPI_CONNFLAG_VOIPCONN); + #endif + + // mark as allocated + pClient->bAllocated = TRUE; +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiUpdateClientFlags + + \Description + Update client flags based on game mode and game flags. + + \Input *pConnApi - pointer to module state + + \Version 05/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiUpdateClientFlags(ConnApiRefT *pConnApi) +{ + ConnApiClientT *pClient; + int32_t iClient; + + for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) + { + pClient = &pConnApi->ClientList.Clients[iClient]; + if (!pClient->bAllocated || (iClient == pConnApi->iSelf)) + { + continue; + } + pClient->uConnFlags = pConnApi->uConnFlags; + + // when not in a peer web game topology, only establish a game connection to the host + if ((iClient != pConnApi->iGameHostIndex) && (pConnApi->iGameHostIndex != pConnApi->iSelf) && (pConnApi->eGameTopology != CONNAPI_GAMETOPOLOGY_PEERWEB)) + { + pClient->uConnFlags &= ~CONNAPI_CONNFLAG_GAMECONN; + } + if (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) + { + // when in a server hosted voip topology, don't establish a voip connection to the host + if (iClient == pConnApi->iVoipHostIndex) + { + pClient->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; + } + } + else + { + /* if we are in a game with a server hosted and the voip topology is not server hosted, don't establish + connections to that host */ + if ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED) && (iClient == pConnApi->iGameHostIndex)) + { + pClient->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; + } + } + // if voip needs to be disabled while we do QoS measurements, update the conn flags accordingly + if (pClient->bEstablishVoip == FALSE) + { + pClient->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiGenerateSessionKey + + \Description + Generate session key for demangling session. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to peer + \Input iClientIndex - index of peer + \Input *pSess - [out] pointer to session buffer + \Input iSessSize - size of session buffer + \Input *pSessType - type of session - "game" or "voip" + + \Version 01/13/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiGenerateSessionKey(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, char *pSess, int32_t iSessSize, const char *pSessType) +{ + uint32_t uIdA, uIdB, uTemp; + + uIdA = pClient->ClientInfo.uLocalClientId; + uIdB = pClient->ClientInfo.uRemoteClientId; + if (uIdB < uIdA) + { + uTemp = uIdA; + uIdA = uIdB; + uIdB = uTemp; + } + + ds_snzprintf(pSess, iSessSize, "%08x-%08x-%s-%08x", uIdA, uIdB, pSessType, pConnApi->iSessId); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiTunnelAlloc + + \Description + Allocate a tunnel for the given client. + + \Input *pConnApi - pointer to module state + \Input *pClient - client to connect to + \Input iClientIndex - index of client + \Input uRemoteAddr - remote address to tunnel to + \Input bLocalAddr - TRUE if we are using local address, else FALSE + + \Version 12/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiTunnelAlloc(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, uint32_t uRemoteAddr, uint8_t bLocalAddr) +{ + ProtoTunnelInfoT TunnelInfo; + + // if connectivity is hosted and if the tunnel to the hosting server was already created in the connection flow of another client + // then skip tunnel creation and reuse that tunnel + if (pClient->ClientInfo.bIsConnectivityHosted) + { + int32_t iClient; + + for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) + { + if ( (pClient != &pConnApi->ClientList.Clients[iClient]) && + (pClient->ClientInfo.uHostingServerId == pConnApi->ClientList.Clients[iClient].ClientInfo.uHostingServerId) && + (pConnApi->ClientList.Clients[iClient].iTunnelId != 0) ) + { + pClient->iTunnelId = pConnApi->ClientList.Clients[iClient].iTunnelId; + NetPrintf(("connapi: [%p] client %d is reusing tunnel 0x%08x to hosting server 0x%08x\n", pConnApi, iClientIndex, pClient->iTunnelId, pClient->ClientInfo.uHostingServerId)); + return; + } + } + } + + // set up tunnel info + ds_memclr(&TunnelInfo, sizeof(TunnelInfo)); + + TunnelInfo.uRemoteClientId = pClient->ClientInfo.bIsConnectivityHosted ? pClient->ClientInfo.uHostingServerId : pClient->ClientInfo.uRemoteClientId; + TunnelInfo.uRemoteAddr = uRemoteAddr; + TunnelInfo.uRemotePort = bLocalAddr ? pClient->ClientInfo.uLocalTunnelPort : pClient->ClientInfo.uTunnelPort; + TunnelInfo.aRemotePortList[0] = pClient->GameInfo.uMnglPort; + TunnelInfo.aRemotePortList[1] = pClient->VoipInfo.uMnglPort; + TunnelInfo.aPortFlags[0] = (pConnApi->uGameTunnelFlagOverride) ? (pConnApi->uGameTunnelFlag) : (PROTOTUNNEL_PORTFLAG_ENCRYPTED|PROTOTUNNEL_PORTFLAG_AUTOFLUSH); + TunnelInfo.aPortFlags[1] = PROTOTUNNEL_PORTFLAG_ENCRYPTED; + + NetPrintf(("connapi: [%p] setting client %d clientId=0x%08x TunnelInfo.uRemotePort %d %s\n", pConnApi, iClientIndex, TunnelInfo.uRemoteClientId, TunnelInfo.uRemotePort, + pClient->ClientInfo.bIsConnectivityHosted ? "(hosted connection)" : "(direct connection)")); + + // allocate tunnel, set the local client id and return to caller + pClient->iTunnelId = ProtoTunnelAlloc(pConnApi->pProtoTunnel, &TunnelInfo, pClient->ClientInfo.strTunnelKey); + if (pClient->iTunnelId >= 0) + { + ProtoTunnelControl(pConnApi->pProtoTunnel, 'tcid', pClient->iTunnelId, (int32_t)pClient->ClientInfo.uLocalClientId, NULL); + } +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiVoipTunnelAlloc + + \Description + Allocate a tunnel for voip for the given client depending on voip settings. + + \Input *pConnApi - pointer to module state + \Input *pClient - client to connect to + \Input iClientIndex - index of client + \Input uRemoteAddr - remote address to tunnel to + \Input bLocalAddr - TRUE if we are using local address, else FALSE + + \Version 12/23/2008 (jrainy) +*/ +/********************************************************************************F*/ +static void _ConnApiVoipTunnelAlloc(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, uint32_t uRemoteAddr, uint8_t bLocalAddr) +{ + int32_t iTunnelId = 0; + + // if doing redirect via host, check for previously created tunnel for re-use + if (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) + { + iTunnelId = pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex].iTunnelId; + } + + // if no reused tunnel, create one + if (iTunnelId == 0) + { + _ConnApiTunnelAlloc(pConnApi, pClient, iClientIndex, uRemoteAddr, bLocalAddr); + } + else + { + pClient->iTunnelId = iTunnelId; + } +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiTunnelFree + + \Description + Free the given client's tunnel. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to allocate tunnel for + + \Version 12/20/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiTunnelFree(ConnApiRefT *pConnApi, ConnApiClientT *pClient) +{ + // tunnel active? + if (!pConnApi->bTunnelEnabled) + { + return; + } + + // if voip to this client is redirected via the host in a C/S game, + // and we're trying to free a client, but not the host itself, skip tunnel destruction. + if (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) + { + if ((pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex].iTunnelId == pClient->iTunnelId) && + (&pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex] != pClient)) + { + pClient->iTunnelId = 0; + return; + } + } + + // if connectivity is hosted and if the tunnel to the hosting server is still used for another client + // then skip tunnel destruction + if (pClient->ClientInfo.bIsConnectivityHosted) + { + int32_t iClient; + + for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) + { + if ( (pClient != &pConnApi->ClientList.Clients[iClient]) && + (pClient->ClientInfo.uHostingServerId == pConnApi->ClientList.Clients[iClient].ClientInfo.uHostingServerId) && + (pClient->iTunnelId == pConnApi->ClientList.Clients[iClient].iTunnelId) ) + { + #if DIRTYCODE_LOGGING + ConnApiClientT *pFirstClient = &pConnApi->ClientList.Clients[0]; + NetPrintf(("connapi: [%p] freeing tunnel 0x%08x to hosting server 0x%08x is skipped for client %d because tunnel still used by at least client %d\n", + pConnApi, pClient->iTunnelId, pClient->ClientInfo.uHostingServerId, (pClient - pFirstClient), iClient)); + #endif + pClient->iTunnelId = 0; + return; + } + } + } + + if (pClient->iTunnelId != 0) + { + ProtoTunnelFree2(pConnApi->pProtoTunnel, pClient->iTunnelId, pClient->ClientInfo.strTunnelKey, pClient->ClientInfo.uAddr); + pClient->iTunnelId = 0; + } +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiGetConnectAddr + + \Description + Selects between internal and internal address to use for connection, + based on whether the external addresses are equal or not. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to connect to + \Input *pLocalAddr - [out] storage for boolean indicating whether local address was used or not + \Input uConnMode - game conn or voip conn + \Input **pClientUsedRet - the pointer to use to return the client actually used + + \Output + int32_t - ip address to be used to connnect to the specified client + + \Version 01/11/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ConnApiGetConnectAddr(ConnApiRefT *pConnApi, ConnApiClientT *pClient, uint8_t *pLocalAddr, const uint32_t uConnMode, ConnApiClientT **pClientUsedRet) +{ + ConnApiClientT *pSelf; + int32_t uAddr; + uint8_t bLocalAddr = FALSE; + ConnApiClientT *pClientUsed = NULL; + + #if DIRTYCODE_LOGGING + const char *pConn = (uConnMode == CONNAPI_CONNFLAG_GAMECONN) ? "game" : "voip"; + #endif + + // ref local client info + pSelf = &pConnApi->ClientList.Clients[pConnApi->iSelf]; + + if ((uConnMode == CONNAPI_CONNFLAG_VOIPCONN) && (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED)) + { + uAddr = pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex].ClientInfo.uAddr; + pClientUsed = &pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex]; + NetPrintf(("connapi: [%s] using host address to connect to (or disconnect from) client 0x%08x\n", pConn, pClient->ClientInfo.uId)); + } + else + { + if (pClient->ClientInfo.uAddr == 0) + { + /* we get into passive mode when we don't have an address for our peer and we are expecting the incoming traffic to help us choose the correct remote address. + for clarity of how this works, we can look at the behavior of commudp, given that the game traffic is what comes first in the establishing the connections. + when commudp sees that the address is 0.0.0.0, it will normally not send POKE packets to the peer. when the INIT packets come in the remote address gets updated based + on the source of the traffic. after which point we can complete the connection with our peer having the updated address. + + NOTE: when we introduced prototunnel to the equation the passive functionality was broken because it using a virtual address, not understanding what the real address is. + this means that we will send invalid POKE packets to 0.0.0.0 via the tunnel which can be seen as errors in the logs. when we receive incoming traffic the connection + can still complete successfully using the same logic explained above. + + this behavior is only expected in a connection between an xbox client (using xbox secure networking) and dedicated server (from the standpoint of the server). + if this behavior is seen anywhere else there is a problem in the assigning of address to connapi. dirtysock will recover as long as one side has a correct address. + if both sides have invalid addresses you could expect the connection to fail */ + NetPrintf(("connapi: [%s] using %a address to connect to (or disconnect from) client 0x%08x in passive mode\n", pConn, pClient->ClientInfo.uAddr, pClient->ClientInfo.uId)); + uAddr = pClient->ClientInfo.uAddr; + pClientUsed = pClient; + } + // if external addresses match, use local address + else if ((pSelf->ClientInfo.uAddr & pConnApi->uNetMask) == (pClient->ClientInfo.uAddr & pConnApi->uNetMask)) + { + NetPrintf(("connapi: [%s] using local address to connect to (or disconnect from) client 0x%08x\n", pConn, pClient->ClientInfo.uId)); + uAddr = pClient->ClientInfo.uLocalAddr; + pClientUsed = pClient; + bLocalAddr = TRUE; + } + else + { + NetPrintf(("connapi: [%s] using peer address to connect to (or disconnect from) client 0x%08x\n", pConn, pClient->ClientInfo.uId)); + uAddr = pClient->ClientInfo.uAddr; + + pClientUsed = pClient; + } + + #if DIRTYCODE_DEBUG + if (pClient->ClientInfo.bIsConnectivityHosted == FALSE) + { + if (pConnApi->bFailP2PConnect) + { + // global P2P fail flag is set + NetPrintf(("connapi: [%s] !! P2P CONNECTION FAILURE TRICK !! - global P2P fail - peer address for client 0x%08x replaced with unreachable value\n", pConn, pClient->ClientInfo.uId)); + uAddr = 0; + } + else if (pClient->uFlags & CONNAPI_CLIENTFLAG_P2PFAILDBG) + { + NetPrintf(("connapi: [%s] !! P2P CONNECTION FAILURE TRICK !! - remote P2P fail flag - peer address for client 0x%08x replaced with unreachable value\n", pConn, pClient->ClientInfo.uId)); + uAddr = 0; + } + else if (pConnApi->ClientList.Clients[pConnApi->iSelf].uFlags & CONNAPI_CLIENTFLAG_P2PFAILDBG) + { + NetPrintf(("connapi: [%s] !! P2P CONNECTION FAILURE TRICK !! - self P2P fail flag - peer address for client 0x%08x replaced with unreachable value\n", pConn, pClient->ClientInfo.uId)); + uAddr = 0; + } + } + #endif + } + + *pLocalAddr = bLocalAddr; + + if (pClientUsedRet) + { + *pClientUsedRet = pClientUsed; + } + + return(uAddr); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiVoipGroupConnSharingCallback + + \Description + Use to invoke VoipGroupResume() and VoipGroupSuspend when notified + by the VoipGroup module. + + \Input *pVoipGroup - voip group ref + \Input eCbType - event identifier + \Input iConnId - connection ID + \Input *pUserData - user callback data + \Input bSending - client sending flag + \Input bReceiving - client receiving flag + + \Version 11/11/2009 (mclouatre) +*/ +/********************************************************************************F*/ +static void _ConnApiVoipGroupConnSharingCallback(VoipGroupRefT *pVoipGroup, ConnSharingCbTypeE eCbType, int32_t iConnId, void *pUserData, uint8_t bSending, uint8_t bReceiving) +{ + ConnApiRefT *pConnApi = (ConnApiRefT *)pUserData; + ConnApiClientT *pClient = &pConnApi->ClientList.Clients[iConnId]; + ConnApiClientT *pClientUsed; + int32_t iConnectAddr; + uint8_t bLocalAddr; + + iConnectAddr = _ConnApiGetConnectAddr(pConnApi, pClient, &bLocalAddr, CONNAPI_CONNFLAG_VOIPCONN, &pClientUsed); + + if (eCbType == VOIPGROUP_CBTYPE_CONNSUSPEND) + { + NetPrintf(("connapi: [%p] suspending voip connection to client 0x%08x:%a at %d\n", pConnApi, pClient->ClientInfo.uId, iConnectAddr, NetTick())); + VoipGroupSuspend(pVoipGroup, iConnId); + } + else if (eCbType == VOIPGROUP_CBTYPE_CONNRESUME) + { + // do we have a tunnel to this client? + if (pClientUsed->iTunnelId > 0) + { + NetPrintf(("connapi: [%p] we have a tunnel for client %d; using virtual address %a\n", pConnApi, + iConnId, pClientUsed->iTunnelId)); + iConnectAddr = pClientUsed->iTunnelId; + } + + NetPrintf(("connapi: [%p] resuming voip connection to client 0x%08x:%a at %d\n", pConnApi, pClient->ClientInfo.uId, iConnectAddr, NetTick())); + VoipGroupResume(pVoipGroup, iConnId, iConnectAddr, pClient->VoipInfo.uMnglPort, pClient->VoipInfo.uLocalPort, pClient->ClientInfo.uId, pConnApi->iSessId, pClient->ClientInfo.bIsConnectivityHosted); + #if DIRTYCODE_LOGGING + if (bSending != bReceiving) + { + NetPrintf(("connapi: [%p] warning - send and receive mute flags are different for client 0x%08x:%a\n", pConnApi, pClient->ClientInfo.uId, iConnectAddr)); + } + #endif + VoipGroupMuteByConnId(pVoipGroup, iConnId, bSending); + } + else + { + NetPrintf(("connapi: [%p] critical error - unknown connection sharing event type\n", pConnApi)); + } +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiGetConnectParms + + \Description + Gets connection parameters. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to connect to + \Input *pConnName - [out] storage for connection name + \Input iNameSize - size of output buffer + + \Output + int32_t - connection flags (NEGAME_CONN_*) + + \Version 04/21/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ConnApiGetConnectParms(ConnApiRefT *pConnApi, ConnApiClientT *pClient, char *pConnName, int32_t iNameSize) +{ + uint32_t uAddrA, uAddrB, uAddrT; + uint32_t bHosting = TRUE; + int32_t iConnFlags; + + // reference unique address strings + uAddrA = pClient->ClientInfo.uRemoteClientId; + uAddrB = pClient->ClientInfo.uLocalClientId; + + /* determine if we are "hosting" or not, in NetGame terms (one peer + must listen and one peer must connect for each connection) */ + if (pConnApi->eGameTopology != CONNAPI_GAMETOPOLOGY_PEERWEB) + { + // if we're client/server, server listens and clients connect + bHosting = (pConnApi->iGameHostIndex == pConnApi->iSelf); + } + else + { + // if we're peer-web, compare addresses to choose listener and connector + bHosting = uAddrA < uAddrB; + } + + /* set up parms based on whether we are "hosting" or not. the connection name is the + unique address of the "host" concatenated with the unique address of the "client" */ + if (bHosting == TRUE) + { + // swap names + uAddrT = uAddrB; + uAddrB = uAddrA; + uAddrA = uAddrT; + + // set conn flags + iConnFlags = NETGAME_CONN_LISTEN; + } + else + { + iConnFlags = NETGAME_CONN_CONNECT; + } + + // format connection name and return connection flags + ds_snzprintf(pConnName, iNameSize, "%x-%x", uAddrA, uAddrB); + return(iConnFlags); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiUpdateCallback + + \Description + Trigger user callback if the state has changed. + + \Input *pConnApi - pointer to module state + \Input iClientIndex - index of client + \Input eType - type of connection (CONNAPI_CBTYPE_*) + \Input eOldStatus - previous status + \Input eNewStatus - current status + + \Output + int32_t - one=active, zero=disconnected + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiUpdateCallback(ConnApiRefT *pConnApi, int32_t iClientIndex, ConnApiCbTypeE eType, ConnApiConnStatusE eOldStatus, ConnApiConnStatusE eNewStatus) +{ + ConnApiCbInfoT CbInfo; + ConnApiConnInfoT *pConnInfo = NULL; + int32_t iIndex; + + // if no change, no callback + if (eOldStatus == eNewStatus) + { + return; + } + + // otherwise, fire off a callback + CbInfo.iClientIndex = iClientIndex; + CbInfo.eType = eType; + CbInfo.eOldStatus = eOldStatus; + CbInfo.eNewStatus = eNewStatus; + CbInfo.pClient = &pConnApi->ClientList.Clients[iClientIndex]; + + if (eType == CONNAPI_CBTYPE_GAMEEVENT) + { + pConnInfo = (ConnApiConnInfoT *)&CbInfo.pClient->GameInfo; + } + else if (eType == CONNAPI_CBTYPE_VOIPEVENT) + { + pConnInfo = (ConnApiConnInfoT *)&CbInfo.pClient->VoipInfo; + } + + // update connection timers + if (pConnInfo != NULL) + { + // finished demangling + if (eOldStatus == CONNAPI_STATUS_MNGL) + { + if (pConnInfo->iConnStart != 0) + { + #if CONNAPI_XBOX_NETWORKING + pConnInfo->ConnTimers.uCreateSATime = NetTickDiff(NetTick(), pConnInfo->iConnStart); + #else + pConnInfo->ConnTimers.uDemangleTime = NetTickDiff(NetTick(), pConnInfo->iConnStart); + #endif + } + else + { + #if CONNAPI_XBOX_NETWORKING + pConnInfo->ConnTimers.uCreateSATime = 0; + #else + pConnInfo->ConnTimers.uDemangleTime = 0; + #endif + } + } + // it went from some state to active or disconnect log connect time + else if ((eOldStatus == CONNAPI_STATUS_CONN) && ((eNewStatus == CONNAPI_STATUS_ACTV) || (eNewStatus == CONNAPI_STATUS_DISC))) + { + if (pConnInfo->uConnFlags & CONNAPI_CONNFLAG_DEMANGLED) + { + if (pConnInfo->iConnStart != 0) + { + pConnInfo->ConnTimers.uDemangleConnectTime = NetTickDiff(NetTick(), pConnInfo->iConnStart); + } + else + { + pConnInfo->ConnTimers.uDemangleConnectTime = 0; + } + } + else + { + if (pConnInfo->iConnStart != 0) + { + pConnInfo->ConnTimers.uConnectTime = NetTickDiff(NetTick(), pConnInfo->iConnStart); + } + else + { + pConnInfo->ConnTimers.uConnectTime = 0; + } + } + } + } + + // call the callback + pConnApi->bInCallback = TRUE; + _ConnApiDefaultCallback(pConnApi, &CbInfo, NULL); + for(iIndex = 0; iIndex < CONNAPI_MAX_CALLBACKS; iIndex++) + { + if (pConnApi->pCallback[iIndex] != NULL) + { + pConnApi->pCallback[iIndex](pConnApi, &CbInfo, pConnApi->pUserData[iIndex]); + } + } + + pConnApi->bInCallback = FALSE; +} + + +/*F********************************************************************************/ +/*! + \Function _ConnApiDestroyGameConnection + + \Description + Destroy game link to given client. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to close game connection for + \Input iClientIndex - index of client in client array + \Input *pReason - reason connection is being closed (for debug use) + + \Version 01/12/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiDestroyGameConnection(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, const char *pReason) +{ + // if refs are about to be destroyed, notify application + if ((pClient->pGameDistRef != NULL) || (pClient->pGameLinkRef != NULL)) + { + _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_DESTEVENT, CONNAPI_STATUS_ACTV, CONNAPI_STATUS_DISC); + } + + // destroy the refs + NetPrintf(("connapi: [%p] destroying game connection to client 0x%08x: %s at %d\n", pConnApi, pClient->ClientInfo.uId, pReason, NetTick())); + if (pClient->pGameLinkRef != NULL) + { + NetGameLinkDestroy(pClient->pGameLinkRef); + pClient->pGameLinkRef = NULL; + } + if (pClient->pGameUtilRef != NULL) + { + NetGameUtilDestroy(pClient->pGameUtilRef); + pClient->pGameUtilRef = NULL; + } + + pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; + _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiDestroyVoipConnection + + \Description + Destroy voip link to given client. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to close game connection for + \Input *pReason - reason connection is being closed (for debug use) + + \Version 01/12/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiDestroyVoipConnection(ConnApiRefT *pConnApi, ConnApiClientT *pClient, const char *pReason) +{ + if (pClient->iVoipConnId >= 0) + { + NetPrintf(("connapi: destroying voip connection to client 0x%08x: %s at %d\n", pClient->ClientInfo.uId, pReason, NetTick())); + VoipGroupDisconnect(pConnApi->pVoipGroupRef, pClient->iVoipConnId); + pClient->iVoipConnId = VOIP_CONNID_NONE; + } + pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; + + _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiDisconnectClient + + \Description + Disconnect a client. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to disconnect + \Input iClientIndex - client index + \Input *pReason - reason for the close + + \Version 01/12/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiDisconnectClient(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, const char *pReason) +{ + _ConnApiDestroyGameConnection(pConnApi, pClient, iClientIndex, pReason); + _ConnApiDestroyVoipConnection(pConnApi, pClient, pReason); + _ConnApiTunnelFree(pConnApi, pClient); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiInitClientList + + \Description + Initialize client list based on input client list. + + \Input *pConnApi - pointer to module state + \Input *pClientList - list of client addresses + \Input iNumClients - number of clients in list + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiInitClientList(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientList, int32_t iNumClients) +{ + ConnApiClientT *pClient; + int32_t iClient; + + // make sure client count is below max + if (iNumClients > pConnApi->ClientList.iMaxClients) + { + NetPrintf(("connapi: [%p] cannot host %d clients; clamping to %d\n", pConnApi, iNumClients, pConnApi->ClientList.iMaxClients)); + iNumClients = pConnApi->ClientList.iMaxClients; + } + + // find our index + pConnApi->iSelf = -1; // init so we can check after setup to make sure we're in the list + for (iClient = 0, pConnApi->ClientList.iNumClients = 0; iClient < iNumClients; iClient++) + { + // remember our index in list + if (pClientList[iClient].uId == pConnApi->uSelfId) + { + pConnApi->iSelf = iClient; + } + } + + // copy input client list + for (iClient = 0, pConnApi->ClientList.iNumClients = 0; iClient < iNumClients; iClient++) + { + // ref client structure + pClient = &pConnApi->ClientList.Clients[iClient]; + + // need to check to see if the client passed it is valid. + if (pClientList[iClient].uId != 0) + { + // init client structure and copy user info + _ConnApiInitClient(pConnApi, pClient, &pClientList[iClient], iClient); + + // if us, update dirtyaddr + if (iClient == pConnApi->iSelf) + { + // update dirtyaddr and save ref + ds_memcpy_s(&pConnApi->SelfAddr, sizeof(pConnApi->SelfAddr), &pClient->ClientInfo.DirtyAddr, sizeof(pClient->ClientInfo.DirtyAddr)); + } + + // increment client count + pConnApi->ClientList.iNumClients += 1; + } + } + + // make sure iSelf is valid before we continue + if (pConnApi->iSelf >= 0) + { + // ref local client + pClient = &pConnApi->ClientList.Clients[pConnApi->iSelf]; + + // set local user + if (pConnApi->pVoipRef != NULL) + { + VoipGroupControl(pConnApi->pVoipGroupRef, 'clid', pClient->ClientInfo.uId, 0, NULL); + } + + // set prototunnel user + if (pConnApi->pProtoTunnel != NULL) + { + ProtoTunnelControl(pConnApi->pProtoTunnel, 'clid', pClient->ClientInfo.uId, 0, NULL); + } + + // if the voip is server hosted don't ever establish voip from the host + if ((pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) && (pConnApi->iVoipHostIndex == pConnApi->iSelf)) + { + pConnApi->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; + } + } + + // set initial client flags + _ConnApiUpdateClientFlags(pConnApi); + + #if DIRTYCODE_LOGGING + // make sure we're in the list + if (pConnApi->iSelf == -1) + { + NetPrintf(("connapi: local client 0x%08x not found in client list\n", pConnApi->uSelfId)); + } + // display client list + _ConnApiDisplayClientList(&pConnApi->ClientList); + #endif +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiParseAdvertField + + \Description + Parse a field from an advertisement. + + \Input *pOutBuf - output buffer for field + \Input iOutSize - size of output buffer + \Input *pInpBuf - pointer to start of field in advertisement buffer + \Input cTerminator - field termination character + + \Output + char * - pointer to next field + + \Version 01/10/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_ConnApiParseAdvertField(char *pOutBuf, int32_t iOutSize, char *pInpBuf, char cTerminator) +{ + char *pEndPtr; + + pEndPtr = strchr(pInpBuf, cTerminator); + *pEndPtr = '\0'; + + ds_strnzcpy(pOutBuf, pInpBuf, iOutSize); + return(pEndPtr+1); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiCheckAdvert + + \Description + Scan current adverts for any adverts that are broadcast by clients we are + connecting to. + + \Input *pConnApi - pointer to module state + + \Version 01/10/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiCheckAdvert(ConnApiRefT *pConnApi) +{ + char strAdvtList[512], *pAdvt, *pTemp; + char strName[32], strNote[32], strAddr[32]; + ConnApiClientT *pClient; + int32_t iAdvt, iNumAdvt, iClient; + uint32_t uAdvtId; + uint32_t uLocalAddr; + + // see if there are any advertisements + iNumAdvt = NetGameUtilQuery(pConnApi->pGameUtilRef, pConnApi->strGameName, strAdvtList, sizeof(strAdvtList)); + NetPrintf(("connapi: [%p] found %d advertisements\n", pConnApi, iNumAdvt)); + + // parse any advertisements + for (pAdvt = strAdvtList, iAdvt = 0; iAdvt < iNumAdvt; iAdvt++) + { + // extract info from advertisement + pAdvt = _ConnApiParseAdvertField(strName, sizeof(strName), pAdvt, '\t'); + pAdvt = _ConnApiParseAdvertField(strNote, sizeof(strNote), pAdvt, '\t'); + pAdvt = _ConnApiParseAdvertField(strAddr, sizeof(strAddr), pAdvt+4, '\n'); + + sscanf(strName, "%u", &uAdvtId); + + // extract address from addr field + pTemp = strchr(strAddr, ':'); + *pTemp = '\0'; + uLocalAddr = SocketInTextGetAddr(strAddr); + + // does the name match one of our client's names? + for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) + { + pClient = &pConnApi->ClientList.Clients[iClient]; + + if ((uAdvtId == pClient->ClientInfo.uId) && (pClient->ClientInfo.uLocalAddr != uLocalAddr)) + { + NetPrintf(("connapi: updating local address of machine Id %u from %a to %a\n", uAdvtId, pClient->ClientInfo.uAddr, uLocalAddr)); + pClient->ClientInfo.uLocalAddr = uLocalAddr; + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiSetGamelinkOpt + + \Description + Set a gamelink option, if it isn't the default. + + \Input *pUtilRef - pointer to util ref for game link + \Input iOpt - gamelink option to set + \Input iValue - value to set + + \Version 06/03/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiSetGamelinkOpt(NetGameUtilRefT *pUtilRef, int32_t iOpt, int32_t iValue) +{ + if (iValue != 0) + { + NetGameUtilControl(pUtilRef, iOpt, iValue); + } +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiDemangleReport + + \Description + Initiate a demangler report to indicate connection success or failure. + + \Input *pConnApi - pointer to module state + \Input *pInfo - connection info + \Input eStatus - connection result (success/fail) + + \Version 01/17/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiDemangleReport(ConnApiRefT *pConnApi, ConnApiConnInfoT *pInfo, ProtoMangleStatusE eStatus) +{ + if (pInfo->bDemangling != TRUE) + { + // not demangling, nothing to report + return; + } + + ProtoMangleReport(pConnApi->pProtoMangle, eStatus, -1); + pConnApi->bReporting = TRUE; + pInfo->bDemangling = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiRemoveClient + + \Description + Remove a current client from a game. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to remove + \Input iClientIndex - index of client to remove + + \Version 04/08/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiRemoveClient(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex) +{ + ConnApiConnStatusE eGameStatus = pClient->GameInfo.eStatus; + ConnApiConnStatusE eVoipStatus = pClient->VoipInfo.eStatus; + ConnApiConnStatusE eNewGameStatus; + ConnApiConnStatusE eNewVoipStatus; + + // disconnect them + _ConnApiDisconnectClient(pConnApi, pClient, iClientIndex, "removal"); + + eNewGameStatus = pClient->GameInfo.eStatus; + eNewVoipStatus = pClient->VoipInfo.eStatus; + + // if we were demangling them, abort demangling + #if CONNAPI_XBOX_NETWORKING + if ((pClient->GameInfo.eStatus == CONNAPI_STATUS_MNGL) || (pClient->VoipInfo.eStatus == CONNAPI_STATUS_MNGL)) + #else + if ((pClient->GameInfo.bDemangling == TRUE) || (pClient->VoipInfo.bDemangling == TRUE)) + #endif + { + NetPrintf(("connapi: aborting demangle of client 0x%08x - being removed from client list\n", pClient->ClientInfo.uId)); + ProtoMangleControl(pConnApi->pProtoMangle, 'abrt', iClientIndex, 0, NULL); + } + + // decrement overall count + pConnApi->ClientList.iNumClients -= 1; + + ds_memclr(&pConnApi->ClientList.Clients[iClientIndex], sizeof(ConnApiClientT)); + + // send a disconnect event + if (pConnApi->bRemoveCallback == TRUE) + { + if ((pConnApi->uConnFlags & CONNAPI_CONNFLAG_GAMECONN) != 0) + { + _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_GAMEEVENT, eGameStatus, eNewGameStatus); + } + if ((pConnApi->uConnFlags & CONNAPI_CONNFLAG_VOIPCONN) != 0) + { + _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_VOIPEVENT, eVoipStatus, eNewVoipStatus); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiUpdateGameClient + + \Description + Process game connection associated with this client. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to update + \Input iClientIndex - index of client + + \Output + int32_t - one=active, zero=disconnected + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ConnApiUpdateGameClient(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex) +{ + ConnApiConnStatusE eStatus = pClient->GameInfo.eStatus; + uint8_t bLocalAddr; + int32_t iConnTimeout; + + // are game connections disabled? + if ((pConnApi->uConnFlags & CONNAPI_CONNFLAG_GAMECONN) == 0) + { + // if we're not connected, just bail + if ((pClient->GameInfo.eStatus == CONNAPI_STATUS_INIT) || (pClient->GameInfo.eStatus == CONNAPI_STATUS_DISC)) + { + return(0); + } + + // if we're voip only and not already disconnected, kill the connection + _ConnApiDestroyGameConnection(pConnApi, pClient, iClientIndex, "connection closed (voiponly)"); + } + + // handle initial connection state + if (pClient->GameInfo.eStatus == CONNAPI_STATUS_INIT) + { + ConnApiClientT *pClientUsed; + + // get address to connect with + int32_t iConnectAddr = _ConnApiGetConnectAddr(pConnApi, pClient, &bLocalAddr, CONNAPI_CONNFLAG_GAMECONN, &pClientUsed); + + NetPrintf(("connapi: [%p] establishing game connection to client 0x%08x:%a at %d\n", pConnApi, pClient->ClientInfo.uId, iConnectAddr, NetTick())); + + // create tunnel? + if ((pConnApi->bTunnelEnabled) && (pClientUsed->iTunnelId == 0)) + { + _ConnApiTunnelAlloc(pConnApi, pClientUsed, iClientIndex, iConnectAddr, bLocalAddr); + } + + // do we have a tunnel to this client? + if (pClientUsed->iTunnelId > 0) + { + NetPrintf(("connapi: [%p] tunnel allocated for client %d (local id 0x%08x, remote id 0x%08x); switching to use virtual address %a\n", pConnApi, + iClientIndex, pClient->ClientInfo.uLocalClientId, + (pClient->ClientInfo.bIsConnectivityHosted ? pClient->ClientInfo.uHostingServerId : pClient->ClientInfo.uRemoteClientId), pClientUsed->iTunnelId)); + iConnectAddr = pClientUsed->iTunnelId; + } + + // try to create game connection + DirtyMemGroupEnter(pConnApi->iMemGroup, pConnApi->pMemGroupUserData); + pClient->pGameUtilRef = NetGameUtilCreate(); + DirtyMemGroupLeave(); + if (pClient->pGameUtilRef != NULL) + { + char strConn[128], strConnName[64]; + int32_t iConnFlags; + + // set game link options + _ConnApiSetGamelinkOpt(pClient->pGameUtilRef, 'minp', pConnApi->iGameMinp); + _ConnApiSetGamelinkOpt(pClient->pGameUtilRef, 'mout', pConnApi->iGameMout); + _ConnApiSetGamelinkOpt(pClient->pGameUtilRef, 'mwid', pConnApi->iGameMwid); + if (pConnApi->iGameUnackLimit != 0) + { + _ConnApiSetGamelinkOpt(pClient->pGameUtilRef, 'ulmt', pConnApi->iGameUnackLimit); + } + + // set our client id (used by gameserver to uniquely identify us) + NetGameUtilControl(pClient->pGameUtilRef, 'clid', pClient->ClientInfo.uLocalClientId); + NetGameUtilControl(pClient->pGameUtilRef, 'rcid', pClient->ClientInfo.uRemoteClientId); + + // determine connection parameters + iConnFlags = _ConnApiGetConnectParms(pConnApi, pClient, strConnName, sizeof(strConnName)); + + // format connect string + ds_snzprintf(strConn, sizeof(strConn), "%a:%d:%d#%s", iConnectAddr, + pClient->GameInfo.uLocalPort, pClient->GameInfo.uMnglPort, strConnName); + + // start the connection attempt + NetGameUtilConnect(pClient->pGameUtilRef, iConnFlags, strConn, pConnApi->pCommConstruct); + pClient->GameInfo.eStatus = CONNAPI_STATUS_CONN; + + // remember connection start time + pClient->GameInfo.iConnStart = NetTick(); + + NetGameUtilControl(pClient->pGameUtilRef, 'meta', (pConnApi->bCommUdpMetadata || (pClient->ClientInfo.bIsConnectivityHosted)) ? 1 : 0); + } + else + { + NetPrintf(("connapi: unable to allocate util ref for connection %d to client 0x%08x\n", iClientIndex, pClient->ClientInfo.uId)); + pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; + _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); + } + } + + // waiting for connection + if (pClient->GameInfo.eStatus == CONNAPI_STATUS_CONN) + { + void *pCommRef; + + if (pClient->pGameLinkRef == NULL) + { + // check for established connection + if ((pCommRef = NetGameUtilComplete(pClient->pGameUtilRef)) != NULL) + { + DirtyMemGroupEnter(pConnApi->iMemGroup, pConnApi->pMemGroupUserData); + pClient->pGameLinkRef = NetGameLinkCreate(pCommRef, FALSE, pConnApi->iLinkBufSize); + DirtyMemGroupLeave(); + if (pClient->pGameLinkRef != NULL) + { + NetPrintf(("connapi: game connection %d to client 0x%08x established at %d\n", iClientIndex, pClient->ClientInfo.uId, NetTick())); + + if (pClient->ClientInfo.bEnableQos) + { + NetPrintf(("connapi: enabling QoS over NetGameLink on connection %d to client 0x%08x\n", iClientIndex, pClient->ClientInfo.uId)); + NetGameLinkControl(pClient->pGameLinkRef, 'sqos', pConnApi->iQosDuration, &pConnApi->iQosInterval); + NetGameLinkControl(pClient->pGameLinkRef, 'lqos', pConnApi->iQosPacketSize, NULL); + } + + // if we were demangling, report success + _ConnApiDemangleReport(pConnApi, &pClient->GameInfo, PROTOMANGLE_STATUS_CONNECTED); + + // save socket ref for multi-demangle if we need it + if (!pConnApi->bTunnelEnabled) + { + NetGameUtilStatus(pClient->pGameUtilRef, 'sock', &pConnApi->uGameSockRef, sizeof(pConnApi->uGameSockRef)); + } + + // indicate we've connected + pClient->GameInfo.uConnFlags |= CONNAPI_CONNFLAG_CONNECTED; + } + else + { + NetPrintf(("connapi: unable to allocate link ref for connection %d to client 0x%08x\n", iClientIndex, pClient->ClientInfo.uId)); + pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; + _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); + } + } + } + + // check for gamelink saying we're connected + if (pClient->pGameLinkRef != NULL) + { + NetGameLinkStatT Stat; + + // give time to NetGameLink to run any connection-related processes + NetGameLinkUpdate(pClient->pGameLinkRef); + + // get link stats + NetGameLinkStatus(pClient->pGameLinkRef, 'stat', 0, &Stat, sizeof(NetGameLinkStatT)); + + // see if we're open + if (Stat.isopen == TRUE) + { + // mark as active + NetPrintf(("connapi: game connection %d to client 0x%08x is active at %d\n", iClientIndex, pClient->ClientInfo.uId, NetTick())); + pClient->GameInfo.eStatus = CONNAPI_STATUS_ACTV; + } + } + + // check for connection timeout + iConnTimeout = pConnApi->iConnTimeout; + + // check for connection timeout + // The connection timeout, and a subsequent disconnection, should only occur if we still have not connected to the peer. + // If we have a pGameLinkRef, then that means we MUST have established a connection to the peer, but are still doing QoS. + if ((pClient->GameInfo.eStatus == CONNAPI_STATUS_CONN) && (pClient->pGameLinkRef == NULL) && (NetTickDiff(NetTick(), pClient->GameInfo.iConnStart) > iConnTimeout)) + { + _ConnApiDestroyGameConnection(pConnApi, pClient, iClientIndex, "connection timeout"); + + // on xboxone, we never want to go back to MNGL state from CONN state + #if CONNAPI_XBOX_NETWORKING + NetPrintf(("connapi: game connection to client 0x%08x failed\n", pClient->ClientInfo.uId)); + if (pClient->GameInfo.bDemangling) + { + _ConnApiDemangleReport(pConnApi, &pClient->GameInfo, PROTOMANGLE_STATUS_FAILED); + } + + pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; + #else + // initial attempt to connect failed + if (pClient->GameInfo.bDemangling == FALSE) + { + if ((pConnApi->bDemanglerEnabled) && (pClient->ClientInfo.bIsConnectivityHosted == FALSE) && (pConnApi->eGameTopology != CONNAPI_GAMETOPOLOGY_SERVERHOSTED)) + { + NetPrintf(("connapi: game status=mngl for connection %d to client 0x%08x\n", iClientIndex, pClient->ClientInfo.uId)); + pClient->GameInfo.eStatus = CONNAPI_STATUS_MNGL; + } + else + { + if (pConnApi->bDemanglerEnabled && pClient->ClientInfo.bIsConnectivityHosted == TRUE) + { + NetPrintf(("connapi: demangling skipped for connection %d to client 0x%08x because the client is connectivity hosted\n", iClientIndex, pClient->ClientInfo.uId)); + } + + pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; + } + } + else + { + NetPrintf(("connapi: game connection to client 0x%08x after demangle failed\n", pClient->ClientInfo.uId)); + _ConnApiDemangleReport(pConnApi, &pClient->GameInfo, PROTOMANGLE_STATUS_FAILED); + + pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; + } + #endif + } // timeout + + // set the packet receive flag if a packet was received + if ((pClient->pGameUtilRef != NULL) && (NetGameUtilStatus(pClient->pGameUtilRef, 'pkrc', NULL, 0) == TRUE)) + { + pClient->GameInfo.uConnFlags |= CONNAPI_CONNFLAG_PKTRECEIVED; + } + } // waiting for connection + + // update connection status during active phase + if (pClient->GameInfo.eStatus == CONNAPI_STATUS_ACTV) + { + // get link stats + NetGameLinkStatT Stat; + NetGameLinkStatus(pClient->pGameLinkRef, 'stat', 0, &Stat, sizeof(NetGameLinkStatT)); + + // make sure connection is still open + if (Stat.isopen == FALSE) + { + _ConnApiDestroyGameConnection(pConnApi, pClient, iClientIndex, "connection closed"); + } + // see if we've timed out + else if (NetTickDiff(Stat.tick, Stat.rcvdlast) > pConnApi->iTimeout) + { + _ConnApiDestroyGameConnection(pConnApi, pClient, iClientIndex, "connection timed out"); + } + } + + // trigger callback if state change + _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_GAMEEVENT, eStatus, (ConnApiConnStatusE)pClient->GameInfo.eStatus); + + // return active or inactive + return(pClient->GameInfo.eStatus != CONNAPI_STATUS_DISC); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiUpdateVoipClient + + \Description + Process voip connection associated with this connection. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to connection to update + \Input iClientIndex - index of connection + + \Output + int32_t - one=active, zero=disconnected + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ConnApiUpdateVoipClient(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex) +{ + ConnApiConnStatusE eStatus = pClient->VoipInfo.eStatus; + int32_t iVoipConnId, iVoipStatus = 0; + uint8_t bLocalAddr; + + // are voip connections disabled? + if ((pConnApi->uConnFlags & CONNAPI_CONNFLAG_VOIPCONN) == 0) + { + // if we're not connected, just bail + if ((pClient->VoipInfo.eStatus == CONNAPI_STATUS_INIT) || (pClient->VoipInfo.eStatus == CONNAPI_STATUS_DISC)) + { + return(0); + } + + // if we're game only and not already disconnected, close the connection + _ConnApiDestroyVoipConnection(pConnApi, pClient, "connection closed (gameonly)"); + } + + // handle initial connection state + if (pClient->VoipInfo.eStatus == CONNAPI_STATUS_INIT) + { + ConnApiClientT *pClientUsed; + int32_t iAdjustedVoipClientIndex = (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) ? iClientIndex - 1 : iClientIndex; + + // get address to connect with + int32_t iConnectAddr = _ConnApiGetConnectAddr(pConnApi, pClient, &bLocalAddr, CONNAPI_CONNFLAG_VOIPCONN, &pClientUsed); + + NetPrintf(("connapi: [%p] establishing voip connection to client 0x%08x:%a at %d\n", pConnApi, + pClient->ClientInfo.uId, iConnectAddr, NetTick())); + + // create tunnel? + if ((pConnApi->bTunnelEnabled) && (pClientUsed->iTunnelId == 0)) + { + _ConnApiVoipTunnelAlloc(pConnApi, pClientUsed, iClientIndex, iConnectAddr, bLocalAddr); + } + + // do we have a tunnel to this client? + if (pClientUsed->iTunnelId > 0) + { + NetPrintf(("connapi: [%p] tunnel allocated for client %d; switching to use virtual address %a\n", pConnApi, + iClientIndex, pClientUsed->iTunnelId)); + iConnectAddr = pClientUsed->iTunnelId; + } + + // initiate connection attempt + // $$todo - deprecate 'vcid' and add a new uLocalClientId parameter to VoipGroupConnect() - the current implementation can be problematic if + // the return conn id does not correspond to the one set with 'vcid' + VoipGroupControl(pConnApi->pVoipGroupRef, 'vcid', iAdjustedVoipClientIndex, 0, &pClient->ClientInfo.uLocalClientId); + iVoipConnId = VoipGroupConnect(pConnApi->pVoipGroupRef, iAdjustedVoipClientIndex, iConnectAddr, pClient->VoipInfo.uMnglPort, pClient->VoipInfo.uLocalPort, + pClient->ClientInfo.uId, pConnApi->iSessId, pClient->ClientInfo.bIsConnectivityHosted, pClient->ClientInfo.uRemoteClientId); + + if (iVoipConnId >= 0) + { + pClient->iVoipConnId = iVoipConnId; + pClient->VoipInfo.eStatus = CONNAPI_STATUS_CONN; + pClient->VoipInfo.iConnStart = NetTick(); + } + else + { + NetPrintf(("connapi: unable to init voip for client index %d (client id: 0x%08x)\n", iClientIndex, pClient->ClientInfo.uId)); + pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; + _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); + } + } + + // get connection status + if ((pClient->VoipInfo.eStatus == CONNAPI_STATUS_CONN) || (pClient->VoipInfo.eStatus == CONNAPI_STATUS_ACTV)) + { + iVoipStatus = VoipGroupConnStatus(pConnApi->pVoipGroupRef, pClient->iVoipConnId); + } + + // for both cases below, CONNAPI_STATUS_CONN and CONNAPI_STATUS_ACTV, when we detect a disconnection, + // we won't set the iVoipConnId to NONE nor trigger a disconnection. This is because the voipgroup code, + // in order to provide correct voip connection sharing, needs to be told only of authoritative "connect" + // and "disconnect" by the higher-level lobby (plasma, lobbysdk, blazesdk, ...) techonology. + + // update connection status during connect phase + if (pClient->VoipInfo.eStatus == CONNAPI_STATUS_CONN) + { + // check for established connection + if (iVoipStatus & VOIP_CONN_CONNECTED) + { + NetPrintf(("connapi: voip connection for client index %d (client id: 0x%08x) established at %d\n", iClientIndex, pClient->ClientInfo.uId, NetTick())); + + // if we were demangling, report success + _ConnApiDemangleReport(pConnApi, &pClient->VoipInfo, PROTOMANGLE_STATUS_CONNECTED); + + // save socket ref for multi-demangle if we need it + if (!pConnApi->bTunnelEnabled) + { + VoipGroupStatus(pConnApi->pVoipGroupRef, 'sock', 0, &pConnApi->uVoipSockRef, sizeof(pConnApi->uVoipSockRef)); + } + + // mark as active + pClient->VoipInfo.eStatus = CONNAPI_STATUS_ACTV; + pClient->VoipInfo.uConnFlags |= CONNAPI_CONNFLAG_CONNECTED; + } + else if (iVoipStatus & VOIP_CONN_STOPPED) + { + NetPrintf(("connapi: voip connection attempt to client 0x%08x failed at %d\n", pClient->ClientInfo.uId, NetTick())); + + // on xboxone, we never want to go back to MNGL state from CONN state + #if CONNAPI_XBOX_NETWORKING + NetPrintf(("connapi: voip connection attempt to client 0x%08x failed\n", pClient->ClientInfo.uId)); + if (pClient->VoipInfo.bDemangling == TRUE) + { + _ConnApiDemangleReport(pConnApi, &pClient->VoipInfo, PROTOMANGLE_STATUS_FAILED); + } + + pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; + #else + if (pClient->VoipInfo.bDemangling == TRUE) + { + NetPrintf(("connapi: voip connection attempt to client 0x%08x after demangle failed\n", pClient->ClientInfo.uId)); + _ConnApiDemangleReport(pConnApi, &pClient->VoipInfo, PROTOMANGLE_STATUS_FAILED); + + pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; + } + else + { + if (pConnApi->bDemanglerEnabled && pClient->ClientInfo.bIsConnectivityHosted == FALSE) + { + NetPrintf(("connapi: voip status=mngl for client index %d (client id: 0x%08x)\n", iClientIndex, pClient->ClientInfo.uId)); + pClient->VoipInfo.eStatus = CONNAPI_STATUS_MNGL; + } + else + { + if (pConnApi->bDemanglerEnabled && pClient->ClientInfo.bIsConnectivityHosted == TRUE) + { + NetPrintf(("connapi: voip connection demangling skipped for client index %d (client id: 0x%08x) because the client is connectivity hosted\n", iClientIndex, pClient->ClientInfo.uId)); + } + + pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; + } + } // bDemangling = FALSE + #endif + } // REMOTE_DISCONNECTED + } // STATUS_CONN + + // update client in active state + if (pClient->VoipInfo.eStatus == CONNAPI_STATUS_ACTV) + { + if (iVoipStatus & VOIP_CONN_STOPPED) + { + NetPrintf(("connapi: voip connection to client 0x%08x terminated at %d\n", pClient->ClientInfo.uId, NetTick())); + pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; + _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); + } + } + + // trigger callback if state change + _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_VOIPEVENT, eStatus, (ConnApiConnStatusE)pClient->VoipInfo.eStatus); + + // return active or inactive + return(pClient->VoipInfo.eStatus != CONNAPI_STATUS_DISC); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiUpdateDemangleReport + + \Description + Update demangler during the Report phase. + + \Input *pConnApi - pointer to module state + + \Output + uint32_t - TRUE if reporting is in progress, FALSE otherwise + + \Version 01/17/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _ConnApiUpdateDemangleReport(ConnApiRefT *pConnApi) +{ + // if not reporting, don't process + if (pConnApi->bReporting != FALSE) + { + #if CONNAPI_XBOX_NETWORKING + // there is no reporting phase on xbox one... so we alway fake that reporting is complete + pConnApi->bReporting = FALSE; + #else + // update client + ProtoMangleUpdate(pConnApi->pProtoMangle); + + // check for completion + if (ProtoMangleComplete(pConnApi->pProtoMangle, NULL, NULL) != 0) + { + pConnApi->bReporting = FALSE; + } + #endif + } + + return(pConnApi->bReporting); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiUpdateDemangle + + \Description + Update client connection in CONNAPI_STATUS_MNGL state. + + \Input *pConnApi - pointer to module state + \Input *pClient - pointer to client to update + \Input iClientIndex - index of client + \Input *pConnInfo - pointer to connection info (game or voip) + \Input iType - type of connection (zero=game, one=voip) + + \Version 01/13/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiUpdateDemangle(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, ConnApiConnInfoT *pConnInfo, int32_t iType) +{ + static const char _Types[2][5] = { "game", "voip" }; + static uint32_t _Flags[2] = { CONNAPI_CONNFLAG_GAMECONN, CONNAPI_CONNFLAG_VOIPCONN }; + ConnApiCbTypeE eCbType; + + #if !CONNAPI_XBOX_NETWORKING + int32_t iClient; + #endif + + // initialize eType + if (iType == 0) + { + eCbType = CONNAPI_CBTYPE_GAMEEVENT; + } + else + { + eCbType = CONNAPI_CBTYPE_VOIPEVENT; + } + + // ignore game/voip demangle if we're not doing game/voip connections + if ((_Flags[iType] & pConnApi->uConnFlags) == 0) + { + return; + } + + #if !CONNAPI_XBOX_NETWORKING + // if anyone is in a connecting state, wait to demangle + for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) + { + if ((pConnApi->ClientList.Clients[iClient].GameInfo.eStatus == CONNAPI_STATUS_CONN) || + (pConnApi->ClientList.Clients[iClient].VoipInfo.eStatus == CONNAPI_STATUS_CONN)) + { + NetPrintf(("connapi: [%p] deferring demangle until there are no ongoing connection attempts\n", pConnApi)); + return; + } + } + #endif + + // tunnel-specific processing + if (pConnApi->bTunnelEnabled) + { + // if we've already demangled the tunnel port, use previous demangle result + if (pClient->uFlags & CONNAPI_CLIENTFLAG_TUNNELPORTDEMANGLED) + { + ConnApiConnStatusE eStatus = pConnInfo->eStatus; + pConnInfo->eStatus = CONNAPI_STATUS_INIT; + pConnInfo->uConnFlags |= CONNAPI_CONNFLAG_DEMANGLED; + + NetPrintf(("connapi: [%p] reusing previously demangled tunnel port\n", pConnApi)); + + // trigger callback if state change + _ConnApiUpdateCallback(pConnApi, iClientIndex, eCbType, eStatus, (ConnApiConnStatusE)pConnInfo->eStatus); + return; + } + } + else + { + // if we've already resolved the secure address, use it + if (pClient->uFlags & CONNAPI_CLIENTFLAG_SECUREADDRRESOLVED) + { + ConnApiConnStatusE eStatus = pConnInfo->eStatus; + pConnInfo->eStatus = CONNAPI_STATUS_INIT; + pConnInfo->uConnFlags |= CONNAPI_CONNFLAG_DEMANGLED; + + NetPrintf(("connapi: [%p] reusing previously resolved secure address\n", pConnApi)); + + // trigger callback if state change + _ConnApiUpdateCallback(pConnApi, iClientIndex, eCbType, eStatus, (ConnApiConnStatusE)pConnInfo->eStatus); + return; + } + } + + // are we in an idle state? + if (ProtoMangleStatus(pConnApi->pProtoMangle, 'idle', NULL, iClientIndex)) + { + char strSess[64]; + intptr_t uSockRef; + uint32_t uDemanglePort = 0; + + // show we're demangling + pConnInfo->bDemangling = TRUE; + + // create session id + _ConnApiGenerateSessionKey(pConnApi, pClient, iClientIndex, strSess, sizeof(strSess), _Types[iType]); + + // get socket ref + if (pConnApi->bTunnelEnabled) + { + uSockRef = pConnApi->uTunlSockRef; + } + else if (iType == 0) + { + uSockRef = pConnApi->uGameSockRef; + } + else + { + uSockRef = pConnApi->uVoipSockRef; + } + + // if socket ref is null, connect normally + if (uSockRef == 0) + { + #if !CONNAPI_RNDLCLDEMANGLEPORT + uDemanglePort = (iType == 0) ? pConnApi->uGamePort : pConnApi->uVoipPort; + uDemanglePort = (pConnApi->bTunnelEnabled) ? (unsigned)pConnApi->iTunnelPort : uDemanglePort; + #else + uDemanglePort = _ConnApiGenerateDemanglePort(pConnApi); + if (pConnApi->bTunnelEnabled) + { + // if we're tunneling, we need to recreate the tunnel socket bound to the new port + ProtoTunnelControl(pConnApi->pProtoTunnel, 'bind', uDemanglePort, 0, NULL); + } + else + { + // if not tunneling, we need to update the local port info with the new local (bind) demangle port + pConnInfo->uLocalPort = (uint16_t)uDemanglePort; + } + #endif + // if tunneling we always want to use the tunnel socket ref for demangling + if (pConnApi->bTunnelEnabled) + { + ProtoTunnelStatus(pConnApi->pProtoTunnel, 'sock', 0, &pConnApi->uTunlSockRef, sizeof(pConnApi->uTunlSockRef)); + uSockRef = pConnApi->uTunlSockRef; + } + } + + if (iType == 0) + { + pClient->GameInfo.iConnStart = NetTick(); + } + else + { + pClient->VoipInfo.iConnStart = NetTick(); + } + + #if CONNAPI_XBOX_NETWORKING + { + // since the SecureDeviceAddr fits in the DirtyAddrT, it is safe to use a buffer as large as a DirtyAddrT + char aSecureDeviceAddressBlob[DIRTYADDR_MACHINEADDR_MAXLEN]; + int32_t iBlobSize; + + if (DirtyAddrGetInfoXboxOne(&pConnApi->ClientList.Clients[iClientIndex].ClientInfo.DirtyAddr, NULL, aSecureDeviceAddressBlob, &iBlobSize)) + { + NetPrintf(("connapi: initiating %s secure address resolution of client 0x%08x at %d\n", _Types[iType], pClient->ClientInfo.uId, NetTick())); + ProtoMangleConnect2(pConnApi->pProtoMangle, iClientIndex, aSecureDeviceAddressBlob, iBlobSize); + } + else + { + ConnApiConnStatusE eStatus = pConnInfo->eStatus; + NetPrintf(("connapi: failed to initiate the %s secure address resolution of client 0x%08x due to device address being invalid\n", _Types[iType], pClient->ClientInfo.uId)); + pConnInfo->eStatus = CONNAPI_STATUS_DISC; + _ConnApiUpdateCallback(pConnApi, iClientIndex, eCbType, eStatus, (ConnApiConnStatusE)pConnInfo->eStatus); + return; + } + } + #else + // before demangling, flush the tunnel to make sure there are no buffered packets + if (pConnApi->bTunnelEnabled) + { + ProtoTunnelControl(pConnApi->pProtoTunnel, 'flsh', pClient->iTunnelId, 0, NULL); + } + + // kick off demangling process + if (uSockRef == 0) + { + NetPrintf(("connapi: initiating %s:%d demangle of client 0x%08x at %d\n", _Types[iType], uDemanglePort, pClient->ClientInfo.uId, NetTick())); + ProtoMangleConnect(pConnApi->pProtoMangle, uDemanglePort, strSess); + + } + else + { + NetPrintf(("connapi: initiating %s demangle of client 0x%08x using sockref 0x%08x at %d\n", _Types[iType], pClient->ClientInfo.uId, uSockRef, NetTick())); + ProtoMangleConnectSocket(pConnApi->pProtoMangle, uSockRef, strSess); + } + #endif + } + else + { + if (pConnInfo->bDemangling != FALSE) + { + int32_t iAddr, iPort, iResult; + + // update client + ProtoMangleUpdate(pConnApi->pProtoMangle); + + // check for completion + #if CONNAPI_XBOX_NETWORKING + iPort = iClientIndex; + #endif + if ((iResult = ProtoMangleComplete(pConnApi->pProtoMangle, &iAddr, &iPort)) != 0) + { + ConnApiConnStatusE eStatus = pConnInfo->eStatus; + + if (eStatus != CONNAPI_STATUS_ACTV) + { + if (iResult > 0) + { + #if CONNAPI_XBOX_NETWORKING + NetPrintf(("connapi: %s secure address resolution for user 0x%08x is successful ipaddr=%a at %d\n", + _Types[iType], pClient->ClientInfo.uId, iAddr, NetTick())); + iPort = pConnInfo->uMnglPort; + #else + NetPrintf(("connapi: %s demangle of client 0x%08x successful port=%d at %d\n", _Types[iType], pClient->ClientInfo.uId, iPort, NetTick())); + #endif + + pClient->ClientInfo.uAddr = pClient->ClientInfo.uLocalAddr = iAddr; + if (pConnApi->bTunnelEnabled) + { + // for xboxone, clients do not yet have a valid tunnel id at this point because client enters to MNGL state before INIT state (unlike other platforms) + #if !CONNAPI_XBOX_NETWORKING + ProtoTunnelControl(pConnApi->pProtoTunnel, 'rprt', pClient->iTunnelId, iPort, NULL); + #endif + + pClient->uFlags |= CONNAPI_CLIENTFLAG_TUNNELPORTDEMANGLED; + } + else + { + pConnInfo->uMnglPort = (uint16_t)iPort; + + #if CONNAPI_XBOX_NETWORKING + pClient->uFlags |= CONNAPI_CLIENTFLAG_SECUREADDRRESOLVED; + #endif + } + pConnInfo->eStatus = CONNAPI_STATUS_INIT; + pConnInfo->uConnFlags |= CONNAPI_CONNFLAG_DEMANGLED; + } + else + { + NetPrintf(("connapi: %s demangle of client 0x%08x failed (timeout=%s)\n", _Types[iType], pClient->ClientInfo.uId, + ProtoMangleStatus(pConnApi->pProtoMangle, 'time', NULL, iClientIndex) ? "true" : "false")); + pConnInfo->eStatus = CONNAPI_STATUS_DISC; + } + } + else + { + NetPrintf(("connapi: [%p] %s demangle of client 0x%08x finished with %d but ignored because connection aleady active (timeout=%s)\n", pConnApi, + _Types[iType], pClient->ClientInfo.uId, iResult, ProtoMangleStatus(pConnApi->pProtoMangle, 'time', NULL, iClientIndex) ? "true" : "false")); + } + + // trigger callback if state change + _ConnApiUpdateCallback(pConnApi, iClientIndex, eCbType, eStatus, (ConnApiConnStatusE)pConnInfo->eStatus); + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiUpdateRemoval + + \Description + Scan through client list and remove clients marked for removal. + + \Input *pConnApi - pointer to module state + + \Version 04/11/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiUpdateRemoval(ConnApiRefT *pConnApi) +{ + ConnApiClientT *pClient; + int32_t iClientIndex; + + for (iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) + { + // ref client + pClient = &pConnApi->ClientList.Clients[iClientIndex]; + + // if client needs to be removed, remove them + if (pClient->uFlags & CONNAPI_CLIENTFLAG_REMOVE) + { + _ConnApiRemoveClient(pConnApi, pClient, iClientIndex); + } + } +} + + +/*F********************************************************************************/ +/*! + \Function _ConnApiRemoveClientSetup + + \Description + Set up a client for removal from the game. + + \Input *pConnApi - pointer to module state + \Input iClientIndex - index of client to remove (used if pClientName is NULL) + \Input uFlags - client removal flags + + \Notes + If this function is called inside of a ConnApi callback, the removal will + be deferred until the next time NetConnIdle() is called. Otherwise, the + removal will happen immediately. + + \Version 04/08/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiRemoveClientSetup(ConnApiRefT *pConnApi, int32_t iClientIndex, uint16_t uFlags) +{ + ConnApiClientT *pClient; + + // don't allow self removal + if (iClientIndex == pConnApi->iSelf) + { + NetPrintf(("connapi: [%p] can't remove self from game\n", pConnApi)); + return; + } + + // ref the client and mark them for removal + pClient = &pConnApi->ClientList.Clients[iClientIndex]; + pClient->uFlags |= uFlags; + + // if we're not in a callback, do the removal immediately + if (pConnApi->bInCallback == FALSE) + { + _ConnApiUpdateRemoval(pConnApi); + } +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiUpdateConnections + + \Description + Update ConnApi connections. + + \Input *pConnApi - pointer to module state + + \Output + int32_t - number of connections that are not in the DISC state + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ConnApiUpdateConnections(ConnApiRefT *pConnApi) +{ + ConnApiClientT *pClient; + int32_t iActive, iClientIndex; + uint32_t bDemangling; + + // update game connections + for (iActive = 0, iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) + { + // ref connection + pClient = &pConnApi->ClientList.Clients[iClientIndex]; + + // don't update if iClientIndex is us or unallocated + if (!pClient->bAllocated) + { + continue; + } + + if (pClient->uConnFlags & CONNAPI_CONNFLAG_GAMECONN) + { + // process game connection + iActive += _ConnApiUpdateGameClient(pConnApi, pClient, iClientIndex); + } + } + + // update voip connections + if (pConnApi->pVoipRef != NULL) + { + for (iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) + { + // ref connection + pClient = &pConnApi->ClientList.Clients[iClientIndex]; + + // don't update if iClientIndex is us or unallocated + if (!pClient->bAllocated) + { + continue; + } + + if (pClient->uConnFlags & CONNAPI_CONNFLAG_VOIPCONN) + { + // process voip connection + iActive += _ConnApiUpdateVoipClient(pConnApi, pClient, iClientIndex); + } + } + } + + // update reporting + bDemangling = _ConnApiUpdateDemangleReport(pConnApi); + + // update game demangling + for (iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) + { + // ref connection + pClient = &pConnApi->ClientList.Clients[iClientIndex]; + + // don't update if iClientIndex is us or unallocated + if ((iClientIndex == pConnApi->iSelf) || !pClient->bAllocated) + { + continue; + } + + // demangle game connection? +#if CONNAPI_XBOX_NETWORKING + // on xboxone, we don't serialize demangling of different clients, we want them occurring in parallel + if ((pClient->GameInfo.eStatus == CONNAPI_STATUS_MNGL) && (pClient->uConnFlags & CONNAPI_CONNFLAG_GAMECONN)) +#else + if ((pClient->GameInfo.eStatus == CONNAPI_STATUS_MNGL) && (pClient->uConnFlags & CONNAPI_CONNFLAG_GAMECONN) && (bDemangling == FALSE)) +#endif + + { + _ConnApiUpdateDemangle(pConnApi, pClient, iClientIndex, &pClient->GameInfo, 0); + } + bDemangling |= pClient->GameInfo.bDemangling; + } + + // update voip demangling + for (iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) + { + // ref connection + pClient = &pConnApi->ClientList.Clients[iClientIndex]; + + // don't update if iClientIndex is us or unallocated + if ((iClientIndex == pConnApi->iSelf) || !pClient->bAllocated) + { + continue; + } + + // demangle voip connection? +#if CONNAPI_XBOX_NETWORKING + // on xboxone, we don't serialize demangling of different clients, we want them occurring in parallel + if ((pClient->VoipInfo.eStatus == CONNAPI_STATUS_MNGL)) +#else + if ((pClient->VoipInfo.eStatus == CONNAPI_STATUS_MNGL) && (bDemangling == FALSE)) +#endif + + { + _ConnApiUpdateDemangle(pConnApi, pClient, iClientIndex, &pClient->VoipInfo, 1); + } + } + + // update tunnel + if ((pConnApi->bTunnelEnabled != 0) && (pConnApi->pProtoTunnel != NULL)) + { + ProtoTunnelUpdate(pConnApi->pProtoTunnel); + } + + return(iActive); +} + +/*F********************************************************************************/ +/*! + \Function _ConnApiIdle + + \Description + NetConn idle function to update the ConnApi module. + + \Input *pData - pointer to module state + \Input uTick - current tick count + + \Notes + This function is installed as a NetConn Idle function. NetConnIdle() + must be regularly polled for this function to be called. + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ConnApiIdle(void *pData, uint32_t uTick) +{ + ConnApiRefT *pConnApi = (ConnApiRefT *)pData; + + if (pConnApi->bAutoUpdate == TRUE) + { + ConnApiUpdate(pConnApi); + } +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ConnApiCreate2 + + \Description + Create the module state. + + \Input iGamePort - game connection port + \Input iMaxClients - maximum number of clients allowed + \Input *pCallback - pointer to user callback + \Input *pUserData - pointer to user data + \Input *pConstruct - comm construct function + + \Output + ConnApiRefT * - pointer to module state, or NULL + + \Version 01/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +ConnApiRefT *ConnApiCreate2(int32_t iGamePort, int32_t iMaxClients, ConnApiCallbackT *pCallback, void *pUserData, CommAllConstructT *pConstruct) +{ + ConnApiRefT *pConnApi; + int32_t iMemGroup; + void *pMemGroupUserData; + int32_t iSize; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // calculate size of module state + iSize = sizeof(*pConnApi) + (sizeof(ConnApiClientT) * (iMaxClients - 1)); + + // allocate and init module state + if ((pConnApi = DirtyMemAlloc(iSize, CONNAPI_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("connapi: could not allocate module state... connapi initialization aborted!\n")); + return(NULL); + } + ds_memclr(pConnApi, iSize); + pConnApi->iMemGroup = iMemGroup; + pConnApi->pMemGroupUserData = pMemGroupUserData; + + if ((pConnApi->pVoipGroupRef = VoipGroupCreate(_ConnApi_iMaxVoipGroups)) == NULL) + { + // release module memory + DirtyMemFree(pConnApi, CONNAPI_MEMID, pConnApi->iMemGroup, pConnApi->pMemGroupUserData); + + NetPrintf(("connapi: [%p] no more voip groups available... connapi initialization aborted!\n", pConnApi)); + return(NULL); + } + + // register connection sharing callback with underlying voip group instance + VoipGroupSetConnSharingEventCallback(pConnApi->pVoipGroupRef, _ConnApiVoipGroupConnSharingCallback, pConnApi); + + // save info + pConnApi->uGamePort = (uint16_t)iGamePort; + pConnApi->uVoipPort = VOIP_PORT; + pConnApi->ClientList.iMaxClients = iMaxClients; + pConnApi->pCallback[0] = (pCallback != NULL) ? pCallback : _ConnApiDefaultCallback; + pConnApi->pUserData[0] = pUserData; + pConnApi->pCommConstruct = pConstruct; + + // set default values + pConnApi->uConnFlags = CONNAPI_CONNFLAG_GAMEVOIP; + pConnApi->iLinkBufSize = CONNAPI_LINKBUFDEFAULT; + pConnApi->iConnTimeout = CONNAPI_CONNTIMEOUT_DEFAULT; + pConnApi->iTimeout = CONNAPI_TIMEOUT_DEFAULT; + pConnApi->iConfigMnglTimeout = CONNAPI_DEMANGLER_TIMEOUT; + pConnApi->iConfigMnglTimeoutFailover = CONNAPI_DEMANGLER_WITH_FAILOVER_TIMEOUT; + pConnApi->iCurrentMnglTimeout = 0; + pConnApi->iTunnelPort = 3658; + pConnApi->bDemanglerEnabled = TRUE; + pConnApi->bAutoUpdate = TRUE; +#if !CONNAPI_XBOX_NETWORKING + pConnApi->bDoAdvertising = TRUE; +#endif + + pConnApi->uNetMask = 0xffffffff; + + pConnApi->iQosDuration = 0; // QoS is disabled by default + pConnApi->iQosInterval = 0; + pConnApi->iQosPacketSize = 0; + pConnApi->iGameHostIndex = -1; + pConnApi->iVoipHostIndex = -1; + + // set default demangler server and create demangler + ds_strnzcpy(pConnApi->strDemanglerServer, PROTOMANGLE_SERVER, sizeof(pConnApi->strDemanglerServer)); + + // add update function to netconn idle handler + NetConnIdleAdd(_ConnApiIdle, pConnApi); + + // return module state to caller + return(pConnApi); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiOnline + + \Description + This function should be called once the user has logged on and the input + parameters are available + + \Input *pConnApi - pointer to module state + \Input *pGameName - pointer to game resource string (eg cso/NCAA-2006/na) + \Input uSelfId - unique identifier for the local connapi client + \Input eGameTopology- type of game + \Input eVoipTopology- type of voip + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ConnApiOnline(ConnApiRefT *pConnApi, const char *pGameName, uint32_t uSelfId, ConnApiGameTopologyE eGameTopology, ConnApiVoipTopologyE eVoipTopology) +{ + char strAdvt[32]; + + NetPrintf(("connapi: [%p] ConnApiOnline() invoked with uSelfId=0x%08x and pGameName=%s\n", pConnApi, uSelfId, pGameName)); + + // save info + ds_strnzcpy(pConnApi->strGameName, pGameName, sizeof(pConnApi->strGameName)); + pConnApi->uSelfId = uSelfId; + + // if voip was disabled before online was called change the topology to disabled + if ((pConnApi->uConnFlags & CONNAPI_CONNFLAG_VOIPCONN) == 0) + { + NetPrintf(("connapi: [%p] requested voip topology ignored because voip globally disabled\n", pConnApi)); + eVoipTopology = CONNAPI_VOIPTOPOLOGY_DISABLED; + } + + // save the topology and set the connection flags accordingly + if ((pConnApi->eGameTopology = eGameTopology) == CONNAPI_GAMETOPOLOGY_DISABLED) + { + pConnApi->uConnFlags &= ~CONNAPI_CONNFLAG_GAMECONN; + } + if ((pConnApi->eVoipTopology = eVoipTopology) == CONNAPI_VOIPTOPOLOGY_DISABLED) + { + pConnApi->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; + } + + // get VoIP ref + if ((pConnApi->eVoipTopology != CONNAPI_VOIPTOPOLOGY_DISABLED) && (pConnApi->pVoipRef == NULL)) + { + if ((pConnApi->pVoipRef = VoipGetRef()) == NULL) + { + NetPrintf(("connapi: [%p] critical error! ConnApiOnline() is invoked on a voip-enabled ConnApi before VoipStartup() was called externally.\n", pConnApi)); + return; + } + } + + // set memory grouping, this requires DirtyMemGroupLeave() to be called before return + DirtyMemGroupEnter(pConnApi->iMemGroup, pConnApi->pMemGroupUserData); + + // create util ref for subnet advertising + if (pConnApi->bDoAdvertising) + { + NetPrintf(("connapi: [%p] creating NetGameUtil ref used for advertising purposes\n", pConnApi)); + if (pConnApi->pGameUtilRef == NULL) + { + if ((pConnApi->pGameUtilRef = NetGameUtilCreate()) == NULL) + { + NetPrintf(("connapi: [%p] failed to create the NetGameUtil ref used for advertising purposes\n", pConnApi)); + DirtyMemGroupLeave(); + return; + } + } + else + { + NetPrintf(("connapi: [%p] can't create the NetGameUtil ref used for advertising purposes because there already exists one\n", pConnApi)); + DirtyMemGroupLeave(); + return; + } + } + else + { + NetPrintf(("connapi: [%p] skipped creation of the NetGameUtil ref used for advertising purposes\n", pConnApi)); + } + + // on non-XboxOne platforms handle the cases where we should disable the demangler + #if !CONNAPI_XBOX_NETWORKING + if ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED) && (pConnApi->eVoipTopology != CONNAPI_VOIPTOPOLOGY_PEERWEB)) + { + NetPrintf(("connapi: [%p] internally disabling demangler for mesh involving server-based game/voip topologies\n", pConnApi)); + pConnApi->bDemanglerEnabled = FALSE; + } + /* for CC-assisted scenarios, we don't allow demangling for meshes with potentially + more than 2 players because connapi serializes the demangling attempts internally, and multiple + back-to-back failing demangling attempts can induce a long wait time that will result in + some clients not even having a chance to attempt CC-assisted path. */ + else if ((pConnApi->ClientList.iMaxClients > 2) && (pConnApi->iCcMode != CONNAPI_CCMODE_PEERONLY)) + { + NetPrintf(("connapi: [%p] internally disabling demangler for CC-assisted mesh with a max player count (%d) larger than 2\n", pConnApi, pConnApi->ClientList.iMaxClients)); + pConnApi->bDemanglerEnabled = FALSE; + } + #endif + + // create demangler + if ((pConnApi->bDemanglerEnabled) && (pConnApi->pProtoMangle == NULL)) + { + #if CONNAPI_XBOX_NETWORKING + NetPrintf(("connapi: [%p] creating demangler ref with max clients = %d\n", pConnApi, pConnApi->ClientList.iMaxClients)); + if ((pConnApi->pProtoMangle = ProtoMangleCreate(pConnApi->strDemanglerServer, pConnApi->ClientList.iMaxClients, pConnApi->strGameName, "")) == NULL) + #else + NetPrintf(("connapi: [%p] creating demangler ref with gamename=%s and server=%s\n", pConnApi, pConnApi->strGameName, pConnApi->strDemanglerServer)); + if ((pConnApi->pProtoMangle = ProtoMangleCreate(pConnApi->strDemanglerServer, PROTOMANGLE_PORT, pConnApi->strGameName, "")) == NULL) + #endif + { + NetPrintf(("connapi: [%p] unable to create ProtoMangle module\n", pConnApi)); + pConnApi->bDemanglerEnabled = FALSE; + } + else + { + // now that we have a valid ProtoMangle, immediately make sure that ConnApi-driven demangler timeout is passed down to it + ConnApiControl(pConnApi, 'dtim', pConnApi->iConfigMnglTimeout, 0, NULL); + ConnApiControl(pConnApi, 'dtif', pConnApi->iConfigMnglTimeoutFailover, 0, NULL); + } + } + + // create tunnel module + if ((pConnApi->bTunnelEnabled) && (pConnApi->pProtoTunnel == NULL)) + { + if ((pConnApi->pProtoTunnel = ProtoTunnelCreate(pConnApi->ClientList.iMaxClients-1, pConnApi->iTunnelPort)) == NULL) + { + // unable to create, so disable the tunnel + pConnApi->bTunnelEnabled = FALSE; + } + else + { + // we own the tunnel + pConnApi->bTunnelOwner = TRUE; + } + } + + // set voip/gamelink timeouts + ConnApiControl(pConnApi, 'time', pConnApi->iTimeout, 0, NULL); + + #if CONNAPI_XBOX_NETWORKING + // set external session name and scid (calls into ProtoMangle) + ConnApiControl(pConnApi, 'exsn', 0, 0, pConnApi->strExternalSessionName); + ConnApiControl(pConnApi, 'exst', 0, 0, pConnApi->strExternalSessionTemplateName); + ConnApiControl(pConnApi, 'scid', 0, 0, pConnApi->strScid); + #endif + + ds_snzprintf(strAdvt, sizeof(strAdvt), "%u", pConnApi->uSelfId); + + // start advertising + if (pConnApi->pGameUtilRef != NULL) + { + NetGameUtilAdvert(pConnApi->pGameUtilRef, pConnApi->strGameName, strAdvt, ""); + } + + // leave memory group + DirtyMemGroupLeave(); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiDestroy + + \Description + Destroy the module state. + + \Input *pConnApi - pointer to module state + + \Version 01/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ConnApiDestroy(ConnApiRefT *pConnApi) +{ + // disconnect + ConnApiDisconnect(pConnApi); + + // remove idle handler + NetConnIdleDel(_ConnApiIdle, pConnApi); + + VoipGroupDestroy(pConnApi->pVoipGroupRef); + + // destroy advertising gameutil ref + if (pConnApi->pGameUtilRef != NULL) + { + NetGameUtilDestroy(pConnApi->pGameUtilRef); + } + + // destroy tunnel, if present and we are the owner + if ((pConnApi->pProtoTunnel != NULL) && (pConnApi->bTunnelOwner == TRUE)) + { + ProtoTunnelDestroy(pConnApi->pProtoTunnel); + } + + // destroy demangler + if (pConnApi->pProtoMangle != NULL) + { + ProtoMangleDestroy(pConnApi->pProtoMangle); + } + + // release module memory + DirtyMemFree(pConnApi, CONNAPI_MEMID, pConnApi->iMemGroup, pConnApi->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiConnect + + \Description + Connect to a game. + + \Input *pConnApi - pointer to module state + \Input *pClientList - list of clients in game session + \Input iClientListSize - number of clients in list + \Input iGameHostIndex - index in the client list who will be serving as game host + \Input iVoipHostIndex - index in the clinet list who will be serving as voip host + \Input iSessId - unique session identifier + + \Notes + ConnApi supports invalid entries in the client list. Invalid clients are detected with a DirtyAddr that is zeroed out + + \Version 09/29/2009 (cvienneau) +*/ +/********************************************************************************F*/ +void ConnApiConnect(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientList, int32_t iClientListSize, int32_t iGameHostIndex, int32_t iVoipHostIndex, int32_t iSessId) +{ + NetPrintf(("connapi: [%p] ConnApiConnect() called with listSize=%d, gamehost=%d, voiphost=%d, sessionId=%d\n", + pConnApi, iClientListSize, iGameHostIndex, iVoipHostIndex, iSessId)); + + // make sure we're idle + if (pConnApi->eState != ST_IDLE) + { + NetPrintf(("connapi: [%p] can't host or connect to a game when not in idle state\n", pConnApi)); + return; + } + + // save session identifier + pConnApi->iSessId = iSessId; + + // virtualize ports if tunneling + if (pConnApi->bTunnelEnabled == TRUE) + { + NetConnControl('vadd', pConnApi->uGamePort, 0, NULL, NULL); + + if (pConnApi->eVoipTopology != CONNAPI_VOIPTOPOLOGY_DISABLED) + { + NetConnControl('vadd', pConnApi->uVoipPort, 0, NULL, NULL); + } + } + + // initialize the game / voip host index only in topologies they apply + if ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_PEERHOSTED) || (pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED)) + { + pConnApi->iGameHostIndex = iGameHostIndex; + } + if (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) + { + pConnApi->iVoipHostIndex = iVoipHostIndex; + } + + // init client list + _ConnApiInitClientList(pConnApi, pClientList, iClientListSize); + + // let the voipgroup know if we are hosting or not + VoipGroupControl(pConnApi->pVoipGroupRef, 'serv', (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED), 0, NULL); + + #if CONNAPI_XBOX_NETWORKING + if (pConnApi->bDemanglerEnabled == TRUE) + { + // let protomanglexboxone know what our index is + ProtoMangleControl(pConnApi->pProtoMangle, 'self', pConnApi->iSelf, 0, NULL); + } + #endif + + // check for advertisement if necessary + if (pConnApi->pGameUtilRef != NULL) + { + _ConnApiCheckAdvert(pConnApi); + } + + pConnApi->eState = ST_INGAME; +} + +/*F********************************************************************************/ +/*! + \Function ConnApiMigrateGameHost + + \Description + Reopen all connections, using the host specified. + This is for host migration to a different host in non-peerweb, needing new + connections for everyone. + + \Input *pConnApi - pointer to module state + \Input iNewGameHostIndex - index of the new game host + + \Version 09/21/2007 (jrainy) +*/ +/********************************************************************************F*/ +void ConnApiMigrateGameHost(ConnApiRefT *pConnApi, int32_t iNewGameHostIndex) +{ + ConnApiClientT *pClient; + int32_t iClientIndex; + ConnApiConnStatusE eStatus; + + pConnApi->iGameHostIndex = iNewGameHostIndex; + pConnApi->eState = ST_INGAME; + + for (iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) + { + pClient = &pConnApi->ClientList.Clients[iClientIndex]; + if (pClient->bAllocated && (pClient->GameInfo.eStatus != CONNAPI_STATUS_ACTV)) + { + eStatus = pClient->GameInfo.eStatus; + + #if CONNAPI_XBOX_NETWORKING + _ConnApiInitClientConnectionState(pConnApi, pClient, iClientIndex, CONNAPI_CONNFLAG_GAMECONN); + #else + pClient->GameInfo.eStatus = CONNAPI_STATUS_INIT; + #endif + + _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_GAMEEVENT, eStatus, (ConnApiConnStatusE)pClient->GameInfo.eStatus); + } + } +} + +/*F********************************************************************************/ +/*! + \Function ConnApiAddClient + + \Description + Add a new client to a pre-existing game at the specified index. + + \Input *pConnApi - pointer to module state + \Input *pClientInfo - info on joining user + \Input iClientIndex - index to add client to + + \Output + 0 if successful, error code otherwise. + + \Notes + This function should be called by all current members of a game while + ConnApiConnect() is called by the joining client. + + \Version 06/16/2008 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ConnApiAddClient(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientInfo, int32_t iClientIndex) +{ + ConnApiClientT *pClient; + + // make sure we're not idle + if (pConnApi->eState == ST_IDLE) + { + NetPrintf(("connapi: [%p] can't add a connection to a game in idle state\n", pConnApi)); + return(CONNAPI_ERROR_INVALID_STATE); + } + + // make sure there is room + if (pConnApi->ClientList.iNumClients == pConnApi->ClientList.iMaxClients) + { + NetPrintf(("connapi: [%p] can't add a connection to the game because it is full\n", pConnApi)); + return(CONNAPI_ERROR_CLIENTLIST_FULL); + } + + // make sure the selected slot is valid + if ((iClientIndex < 0) || (iClientIndex >= pConnApi->ClientList.iMaxClients)) + { + NetPrintf(("connapi: [%p] can't add a connection to the game in slot %d because valid slot range 0-%d\n", pConnApi, iClientIndex, pConnApi->ClientList.iMaxClients-1)); + return(CONNAPI_ERROR_SLOT_OUT_OF_RANGE); + } + + // get pointer to new client structure to fill in, and increment client count + pClient = &pConnApi->ClientList.Clients[iClientIndex]; + + // check slot and make sure it is uninitialized + if (pClient->bAllocated == TRUE) + { + NetPrintf(("connapi: [%p] slot %d already allocated; cannot add a new client in this slot\n", pConnApi, iClientIndex)); + return(CONNAPI_ERROR_SLOT_USED); + } + + // add client to list + _ConnApiInitClient(pConnApi, pClient, pClientInfo, iClientIndex); + + // display client info + #if DIRTYCODE_LOGGING + NetPrintf(("connapi: [%p] adding client to clientlist\n", pConnApi)); + _ConnApiDisplayClientInfo(&pConnApi->ClientList.Clients[iClientIndex], iClientIndex); + #endif + + // increment client count + pConnApi->ClientList.iNumClients += 1; + + // check for advertisement if necessary + if (pConnApi->pGameUtilRef != NULL) + { + _ConnApiCheckAdvert(pConnApi); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiFindClient + + \Description + Returns the ConnApiClientT of a given client, if found by id. + + \Input *pConnApi - pointer to module state + \Input *pClientInfo - info on searched user + \Input *pOutClient - used to return the ClientT structure of the client + + \Output + uint8_t - TRUE if the client is found, FALSE otherwise + + \Version 06/05/2008 (jbrookes) +*/ +/********************************************************************************F*/ +uint8_t ConnApiFindClient(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientInfo, ConnApiClientT *pOutClient) +{ + int32_t iClient; + + for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) + { + if (pConnApi->ClientList.Clients[iClient].ClientInfo.uId == pClientInfo->uId) + { + ds_memcpy_s(pOutClient, sizeof(*pOutClient), &pConnApi->ClientList.Clients[iClient], sizeof(pConnApi->ClientList.Clients[iClient])); + return(TRUE); + } + } + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiRemoveClient + + \Description + Remove a current client from a game. + + \Input *pConnApi - pointer to module state + \Input iClientIndex - index of client to remove (used if pClientName is NULL) + + \Notes + If this function is called inside of a ConnApi callback, the removal will + be deferred until the next time NetConnIdle() is called. Otherwise, the + removal will happen immediately. + + \Version 06/14/2008 (jbrookes) +*/ +/********************************************************************************F*/ +void ConnApiRemoveClient(ConnApiRefT *pConnApi, int32_t iClientIndex) +{ + // make sure the select slot is valid + if ((iClientIndex < 0) || (iClientIndex >= pConnApi->ClientList.iMaxClients)) + { + NetPrintf(("connapi: [%p] can't remove a connection from the game in slot %d because valid slot range is 0-%d\n", pConnApi, iClientIndex, pConnApi->ClientList.iMaxClients-1)); + return; + } + + _ConnApiRemoveClientSetup(pConnApi, iClientIndex, CONNAPI_CLIENTFLAG_REMOVE); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiDisconnect + + \Description + Stop game, disconnect from clients, and reset client list. + + \Input *pConnApi - pointer to module state + + \Notes + Any NetGameDistRefs created by the application that references a NetGameUtil/ + NeGameLink combination created by ConnApi must destroy the DistRef(s) before + calling this function. + + \Version 01/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ConnApiDisconnect(ConnApiRefT *pConnApi) +{ + ConnApiClientT *pClient; + int32_t iClient; + + NetPrintf(("connapi: [%p] disconnecting\n", pConnApi)); + + // make sure we're not idle + if (pConnApi->eState == ST_IDLE) + { + NetPrintf(("connapi: [%p] can't disconnect when in idle state\n", pConnApi)); + return; + } + + // walk client list + for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) + { + // ref client + pClient = &pConnApi->ClientList.Clients[iClient]; + + // if it's not us and was allocated, disconnect from them + if ((iClient != pConnApi->iSelf) && pClient->bAllocated) + { + _ConnApiDisconnectClient(pConnApi, &pConnApi->ClientList.Clients[iClient], iClient, "disconnect"); + } + } + + // reset client list + pConnApi->ClientList.iNumClients = 0; + ds_memclr(&pConnApi->ClientList.Clients, pConnApi->ClientList.iMaxClients * sizeof(ConnApiClientT)); + + // devirtualize ports if tunneling is enabled + if (pConnApi->bTunnelEnabled == TRUE) + { + NetConnControl('vdel', pConnApi->uGamePort, 0, NULL, NULL); + + if (pConnApi->eVoipTopology != CONNAPI_VOIPTOPOLOGY_DISABLED) + { + NetConnControl('vdel', pConnApi->uVoipPort, 0, NULL, NULL); + } + } + + // clear socket refs + pConnApi->uTunlSockRef = 0; + pConnApi->uGameSockRef = 0; + pConnApi->uVoipSockRef = 0; + + // go to idle state + pConnApi->eState = ST_IDLE; +} + +/*F********************************************************************************/ +/*! + \Function ConnApiGetClientList + + \Description + Get a list of current connections. + + \Input *pConnApi - pointer to module state + + \Output + ConnApiClientListT * - pointer to client list + + \Version 01/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +const ConnApiClientListT *ConnApiGetClientList(ConnApiRefT *pConnApi) +{ + return(&pConnApi->ClientList); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiStatus + + \Description + Get status information. + + \Input *pConnApi - pointer to module state + \Input iSelect - status selector + \Input *pBuf - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iSelect can be one of the following: + + \verbatim + 'cbfp' - return current callback function pointer in output buffer + 'cbup' - return current callback data pointer in output buffer + 'ctim' - returns the connection timeout + 'dtim' - get effective demangler timeout (in milliseconds) + 'ghst' - return the the host index ConnApiClientT (via pBuf) for the game host in peer hosted and server hosted games + 'gprt' - return game port + 'gsrv' - return whether the game is server hosted (ConnApiClientT returned via pBuf) + 'ingm' - currently 'in game' (connecting to or connected to one or more peers) + 'lbuf' - returns GameLink buffer allocation size + 'lclt' - returns one past the index of the last allocated player + 'lcon' - returns the number of consoles (excluding the game server) allocated in the client list + 'minp' - returns GameLink input buffer queue length (zero=default) + 'mngl' - returns whether demangler is enabled or not + 'mout' - returns GameLink output buffer queue length (zero=default) + 'mplr' - *deprecated - replaced by 'lclt'* returns one past the index of the last allocated player + 'mwid' - returns GameLink max packet size (zero=default) + 'nmsk' - returns current netmask + 'peer' - returns game conn peer-web enable/disable status + 'self' - returns index of local user in client list + 'sock' - copy socket ref to pBuf + 'sess' - copies session information into output buffer + 'time' - returns the timeout + 'tprt' - returns port tunnel has been bound to (if available) + 'tref' - returns the prototunnel ref used + 'tunl' - returns whether tunnel is enabled or not + 'type' - returns current connection type (CONNAPI_CONNFLAG_*) + 'vhst' - return the host index and ConnApiClientT (via pBuf) for the voip host in server hosted voip + 'vprt' - return voip port + \endverbatim + + \Version 01/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ConnApiStatus(ConnApiRefT *pConnApi, int32_t iSelect, void *pBuf, int32_t iBufSize) +{ + return(ConnApiStatus2(pConnApi, iSelect, NULL, pBuf, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiStatus2 + + \Description + Get status information. + + \Input *pConnApi - pointer to module state + \Input iSelect - status selector + \Input *pData - input data + \Input *pBuf - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iSelect can be one of the following: + + \verbatim + 'cadr' - stands for Connection Address, ip address used at the platform socket level to reach this client (regardless of tunneling being used or not) + 'cbfp' - return current callback function pointer in output buffer + 'cbup' - return current callback data pointer in output buffer + 'cprt' - stands for Connection Port, UDP port used at the platform socket level to reach this client + 'ctim' - returns the connection timeout + 'dtim' - get effective demangler timeout (in milliseconds) + 'ghst' - return the the host index ConnApiClientT (via pBuf) for the game host in peer hosted and server hosted games + 'gprt' - return game port + 'gsrv' - return whether the game is server hosted (ConnApiClientT returned via pBuf) + 'host' - returns whether hosting or not, plus copies host name to buffer + 'ingm' - currently 'in game' (connecting to or connected to one or more peers) + 'lbuf' - returns GameLink buffer allocation size + 'lclt' - returns one past the index of the last allocated player + 'lcon' - returns the number of consoles (excluding the game server) allocated in the client list + 'minp' - returns GameLink input buffer queue length (zero=default) + 'mngl' - returns whether demangler is enabled or not + 'mout' - returns GameLink output buffer queue length (zero=default) + 'mplr' - *deprecated - replaced by 'lclt'* returns one past the index of the last allocated player + 'mvtm' - return true if multiple virtual machine mode is active + 'mwid' - returns GameLink max packet size (zero=default) + 'nmsk' - returns current netmask + 'peer' - returns game conn peer-web enable/disable status + 'self' - returns index of local user in client list + 'sock' - copy socket ref to pBuf + 'sess' - copies session information into output buffer + 'time' - returns the timeout + 'tprt' - returns port tunnel has been bound to (if available) + 'tref' - returns the prototunnel ref used + 'tunl' - returns whether tunnel is enabled or not + 'tunr' - returns receive protunnel stats as a ProtoTunnelStatT in pBuf for a given client pData + 'tuns' - returns send prototunnel stats as a ProtoTunnelStatT in pBuf for a given client pData + 'type' - returns current connection type (CONNAPI_CONNFLAG_*) + 'vgrp' - returns voipgroup pointer in pBuf + 'vhst' - return the host index and ConnApiClientT (via pBuf) for the voip host in server hosted voip + 'vprt' - return voip port + \endverbatim + + \Version 01/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ConnApiStatus2(ConnApiRefT *pConnApi, int32_t iSelect, void *pData, void *pBuf, int32_t iBufSize) +{ + if ((iSelect == 'cadr') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(uint32_t))) + { + uint8_t bLocalAddr; + ConnApiClientT *pClientUsed; + ConnApiClientT *pClient = (ConnApiClientT *)pData; + struct sockaddr addr; + + *(uint32_t*)pBuf = _ConnApiGetConnectAddr(pConnApi, pClient, &bLocalAddr, CONNAPI_CONNFLAG_GAMECONN, &pClientUsed); + + // if we are using the tunnel return the tunnel address + if (pConnApi->pProtoTunnel != NULL) + { + ProtoTunnelStatus(pConnApi->pProtoTunnel, 'vtop', pClient->iTunnelId, &addr, sizeof(addr)); + *(uint32_t*)pBuf = SockaddrInGetAddr(&addr); + } + + if (pClientUsed->GameInfo.eStatus != CONNAPI_STATUS_ACTV) + { + return(-1); + } + + return(0); + } + if ((iSelect == 'cbfp') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(pConnApi->pCallback[0]))) + { + ds_memcpy(pBuf, &(pConnApi->pCallback[0]), sizeof(pConnApi->pCallback[0])); + return(0); + } + if ((iSelect == 'cbup') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(pConnApi->pUserData[0]))) + { + ds_memcpy(pBuf, &(pConnApi->pUserData[0]), sizeof(pConnApi->pUserData[0])); + return(0); + } + if ((iSelect == 'cprt') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(uint16_t))) + { + uint8_t bLocalAddr; + ConnApiClientT *pClientUsed; + ConnApiClientT *pClient = (ConnApiClientT *)pData; + + _ConnApiGetConnectAddr(pConnApi, pClient, &bLocalAddr, CONNAPI_CONNFLAG_GAMECONN, &pClientUsed); + + if (pClientUsed->GameInfo.eStatus != CONNAPI_STATUS_ACTV) + { + return(-1); + } + + if (pClientUsed->iTunnelId > 0) + { + ProtoTunnelStatus(pConnApi->pProtoTunnel, 'rprt', pClientUsed->iTunnelId, pBuf, iBufSize); + } + else + { + *(uint16_t*)pBuf = pClientUsed->GameInfo.uMnglPort; + } + return(0); + } + if (iSelect == 'ctim') + { + return(pConnApi->iConnTimeout); + } + if (iSelect == 'dtim') + { + return(pConnApi->iCurrentMnglTimeout); + } + if ((iSelect == 'ghst') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(ConnApiClientT))) + { + if ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_PEERHOSTED) || (pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED)) + { + ds_memcpy(pBuf, &pConnApi->ClientList.Clients[pConnApi->iGameHostIndex], sizeof(ConnApiClientT)); + return(pConnApi->iGameHostIndex); + } + return(-1); + } + if (iSelect == 'gprt') + { + return(pConnApi->uGamePort); + } + if (iSelect == 'gsrv') + { + uint8_t bHostIsGameServer; + if ((bHostIsGameServer = (pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED)) == TRUE) + { + if ((pBuf != NULL) && (iBufSize >= (signed)sizeof(ConnApiClientT))) + { + ds_memcpy(pBuf, &pConnApi->ClientList.Clients[pConnApi->iGameHostIndex], sizeof(ConnApiClientT)); + } + } + return(bHostIsGameServer); + } + if (iSelect == 'ingm') + { + return(pConnApi->eState != ST_IDLE); + } + if (iSelect == 'lbuf') + { + return(pConnApi->iLinkBufSize); + } + if (iSelect == 'lcon') + { + int32_t iNumberPlayers = ConnApiStatus2(pConnApi, 'lclt', pData, pBuf, iBufSize); + + // if the game is server hosted we do not want to include the server + if (ConnApiStatus(pConnApi, 'gsrv', NULL, 0)) + { + --iNumberPlayers; + } + + return(iNumberPlayers); + } + if (iSelect == 'minp') + { + return(pConnApi->iGameMinp); + } + if (iSelect == 'mngl') + { + return(pConnApi->bDemanglerEnabled); + } + if (iSelect == 'mout') + { + return(pConnApi->iGameMout); + } + if ((iSelect == 'mplr') || (iSelect == 'lclt')) + { + int32_t iClient; + int32_t iNumberPlayers = 0; + for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) + { + if (pConnApi->ClientList.Clients[iClient].bAllocated) + { + iNumberPlayers = iClient + 1; + } + } + return(iNumberPlayers); + } + if (iSelect == 'mwid') + { + return(pConnApi->iGameMwid); + } + if (iSelect == 'nmsk') + { + return(pConnApi->uNetMask); + } + if (iSelect == 'peer') + { + return(pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_PEERWEB); + } + if (iSelect == 'self') + { + return(pConnApi->iSelf); + } + if (iSelect == 'sess') + { + ds_strnzcpy((char *)pBuf, pConnApi->strSession, iBufSize); + return(0); + } + if (iSelect == 'sock') + { + if (iBufSize >= (signed)sizeof(intptr_t)) + { + if (pConnApi->bTunnelEnabled) + { + ProtoTunnelStatus(pConnApi->pProtoTunnel, 'sock', 0, pBuf, iBufSize); + } + else if ((pConnApi->iGameHostIndex >= 0) && (pConnApi->ClientList.Clients[pConnApi->iGameHostIndex].pGameUtilRef != NULL)) + { + NetGameUtilStatus(pConnApi->ClientList.Clients[pConnApi->iGameHostIndex].pGameUtilRef, 'sock', pBuf, iBufSize); + } + else + { + NetPrintf(("connapi: [%p] ConnApiStatus('sock') failed because game socket not yet available (tunnel enabled = %s)\n", pConnApi, + pConnApi->bTunnelEnabled?"true":"false")); + return(-2); + } + return(0); + } + else + { + NetPrintf(("connapi: [%p] ConnApiStatus('sock') failed because size (%d) of user-provided buffer is too small (required: %d)\n", pConnApi, + iBufSize, sizeof(intptr_t))); + return(-1); + } + } + if (iSelect == 'time') + { + return(pConnApi->iTimeout); + } + if ((iSelect == 'tprt') && (pConnApi->pProtoTunnel != NULL)) + { + return(ProtoTunnelStatus(pConnApi->pProtoTunnel, 'lprt', 0, NULL, 0)); + } + if (iSelect == 'tref' && (pConnApi->pProtoTunnel != NULL)) + { + if (iBufSize >= (signed)sizeof(ProtoTunnelRefT*)) + { + ds_memcpy(pBuf, &pConnApi->pProtoTunnel, sizeof(ProtoTunnelRefT*)); + return(0); + } + else + { + NetPrintf(("connapi: [%p] ConnApiStatus('tref') failed because size (%d) of user-provided buffer is too small (required: %d)\n", pConnApi, + iBufSize, sizeof(ProtoTunnelRefT*))); + return(-1); + } + } + if (iSelect == 'tunl') + { + return(pConnApi->bTunnelEnabled); + } + if (iSelect == 'tunr') + { + if ((pData != NULL) && (pBuf != NULL) && (iBufSize == sizeof(ProtoTunnelStatT))) + { + ConnApiClientT *pClient = (ConnApiClientT *)pData; + ProtoTunnelStatus(pConnApi->pProtoTunnel, 'rcvs', pClient->iTunnelId, pBuf, iBufSize); + return(0); + } + else + { + NetPrintf(("connapi: [%p] ConnApiStatus('tunr') failed due to invalid arguments\n", pConnApi)); + return(-1); + } + } + if (iSelect == 'tuns') + { + if ((pData != NULL) && (pBuf != NULL) && (iBufSize == sizeof(ProtoTunnelStatT))) + { + ConnApiClientT *pClient = (ConnApiClientT *)pData; + ProtoTunnelStatus(pConnApi->pProtoTunnel, 'snds', pClient->iTunnelId, pBuf, iBufSize); + return(0); + } + else + { + NetPrintf(("connapi: [%p] ConnApiStatus('tuns') failed due to invalid arguments\n", pConnApi)); + return(-1); + } + } + if (iSelect == 'type') + { + return(pConnApi->uConnFlags); + } + if (iSelect == 'ulmt') + { + return(pConnApi->iGameUnackLimit); + } + if (iSelect == 'vgrp') + { + if (iBufSize >= (int32_t)sizeof(pConnApi->pVoipGroupRef)) + { + ds_memcpy(pBuf, &pConnApi->pVoipGroupRef, sizeof(pConnApi->pVoipGroupRef)); + return(0); + } + return(-1); + } + if ((iSelect == 'vhst') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(ConnApiClientT))) + { + if (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) + { + ds_memcpy(pBuf, &pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex], sizeof(ConnApiClientT)); + return(pConnApi->iVoipHostIndex); + } + return(-1); + } + if (iSelect == 'vprt') + { + return(pConnApi->uVoipPort); + } + // unhandled + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiControl + + \Description + Control behavior of module. + + \Input *pConnApi - pointer to module state + \Input iControl - status selector + \Input iValue - control value + \Input iValue2 - control value + \Input *pValue - control value + + \Output + int32_t - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + 'adve' - set to enable ProtoAdvt advertising + 'auto' - set auto-update enable/disable - iValue=TRUE or FALSE (default TRUE) + 'cbfp' - set callback function pointer - pValue=pCallback + 'cbup' - set callback user data pointer - pValue=pUserData + 'ccmd' - set the CC mode (CONNAPI_CCMODE_*) + 'ctim' - set connection timeout - iValue=timeout (minimum & default 10000 ms) + 'dist' - set dist ref - iValue=index of client or 'gsrv' for host user, pValue=dist ref + 'dsrv' - set demangler server - pValue=pointer to demangler server name (default demangler.ea.com) + 'dtif' - set demangler timeout for scenarios involving or CC assistance - iValue=timeout in milliseconds + 'dtim' - set demangler timeout - iValue=timeout in milliseconds + 'exsn' - set the external session name for the MultiplayerSessionReference (xbox one only) + 'exst' - set the external session template name for the MultiplayerSessionReference (xbox one only) + 'estv' - enable flag to establish voip for a client after it was delayed, iValue=client index + 'gprt' - set game port to use - iValue=port + 'lbuf' - set game link buffer size - iValue=size (default 1024) + 'lqos' - set QoS packet size used when creating NetGameLinks. iValue=packet size in bytes + 'maxg' - set maximum number of voipgroups we support + 'meta' - enable/disable commudp metadata + 'minp' - set GameLink input buffer queue length - iValue=length (default 32) + 'mngl' - set demangler enable/disable - iValue=TRUE/FALSE (default TRUE) + 'mout' - set GameLink output buffer queue length - iValue=length (default 32) + 'mvtm' - set multiple virtual machine mode enable/disable - iValue=TRUE/FALSE (default FALSE) + 'mwid' - set GameLink max packet size - iValue=size (default NETGAME_DATAPKT_DEFSIZE, max NETGAME_DATAPKT_MAXSIZE) + 'nmsk' - set netmask used for external address comparisons - iValue=mask (default 0xffffffff) + 'rcbk' - set enable of disc callback on removal - iValue=TRUE/FALSE (default FALSE) + 'scid' - set the service configuration id for the MultiplayerSessionReference (xbox one only) + 'sqos' - set QoS settings used when creating NetGameLinks. iValue=QoS duration (0 disables QoS), iValue2=QoS packet interval + 'stun' - set prototunnel ref + 'tctl' - set prototunnel control data + 'time' - set timeout - iValue=timeout in ms (default 15 seconds) + 'tgam' - enable the override of game tunnel flags - iValue=falgs, iValue2=boolean(overrides or not) + 'tunl' - set tunnel parms: + iValue = TRUE/FALSE to enable/disable or negative to ignore + iValue2 = tunnel port, or negative to ignore + 'type' - set connection type - iValue = CONNAPI_CONNFLAG_* + 'voig' - pass-through to VoipGroupControl() - 'getr' VoipGroupControl selector has been deprecated please switch to ConnApiStatus('vgrp') instead + 'voip' - pass-through to VoipControl() - iValue=VoIP iControl, iValue2= VoIP iValue + 'vprt' - set voip port to use - iValue=port + 'vset' - set voip enable/disable (default=TRUE; call before ConnApiOnline()) + '!res' - force secure address resolution to fail, to simulate p2p connection failure. + iValue = TRUE/FALSE to enable/disable + iValue2 = index of user to force fail or -1 for all users or local index for all users + \endverbatim + + \Version 01/04/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ConnApiControl(ConnApiRefT *pConnApi, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iControl == 'adve') + { + #if CONNAPI_XBOX_NETWORKING + NetPrintf(("connapi: ProtoAdvt advertising cannot be enabled on Xbox One\n")); + #else + NetPrintf(("connapi: [%p] ProtoAdvt advertising %s\n", pConnApi, (iValue?"enabled":"disabled"))); + pConnApi->bDoAdvertising = iValue; + // if disabling advertising and we already have an advertising ref, kill it + if ((pConnApi->bDoAdvertising == FALSE) && (pConnApi->pGameUtilRef != NULL)) + { + NetGameUtilDestroy(pConnApi->pGameUtilRef); + pConnApi->pGameUtilRef = NULL; + } + #endif + return(0); + } + if (iControl == 'auto') + { + pConnApi->bAutoUpdate = iValue; + return(0); + } + if (iControl == 'ccmd') + { + #if DIRTYCODE_LOGGING + static const char *_ConnApiCcModeNames[] = + { + "CONNAPI_CCMODE_PEERONLY", + "CONNAPI_CCMODE_HOSTEDONLY", + "CONNAPI_CCMODE_HOSTEDFALLBACK" + }; + #endif + + switch (iValue) + { + case (CONNAPI_CCMODE_PEERONLY): + VoipGroupControl(pConnApi->pVoipGroupRef, 'ccmd', VOIPGROUP_CCMODE_PEERONLY, 0, NULL); + break; + case (CONNAPI_CCMODE_HOSTEDONLY): + VoipGroupControl(pConnApi->pVoipGroupRef, 'ccmd', VOIPGROUP_CCMODE_HOSTEDONLY, 0, NULL); + break; + case (CONNAPI_CCMODE_HOSTEDFALLBACK): + VoipGroupControl(pConnApi->pVoipGroupRef, 'ccmd', VOIPGROUP_CCMODE_HOSTEDFALLBACK, 0, NULL); + break; + default: + NetPrintf(("connapi: [%p] unsupported CC mode %d\n", pConnApi, iValue)); + return(-1); + } + NetPrintf(("connapi: [%p] CC mode = %d (%s)\n", pConnApi, iValue, _ConnApiCcModeNames[iValue])); + pConnApi->iCcMode = iValue; + return(0); + } + if (iControl == 'cbfp') + { + // set callback function pointer + pConnApi->pCallback[0] = ((pValue != NULL) ? (ConnApiCallbackT *)pValue : _ConnApiDefaultCallback); + return(0); + } + if (iControl == 'cbup') + { + // set callback user data pointer + pConnApi->pUserData[0] = pValue; + return(0); + } + if ((iControl == 'ctim') && (iValue >= CONNAPI_CONNTIMEOUT_DEFAULT)) + { + NetPrintf(("connapi: [%p] setting connection timeout to %d\n", pConnApi, iValue)); + pConnApi->iConnTimeout = iValue; + return(0); + } + if (iControl == 'dist') + { + // set dist ref for specified client + + // special value to signify the host + if (iValue == 'gsrv') + { + iValue = pConnApi->iGameHostIndex; + } + + if ((iValue >= 0) && (iValue < pConnApi->ClientList.iMaxClients)) + { + pConnApi->ClientList.Clients[iValue].pGameDistRef = (NetGameDistRefT *)pValue; + return(0); + } + } + if (iControl == 'dsrv') + { + // set demangler server + ds_strnzcpy(pConnApi->strDemanglerServer, (const char *)pValue, sizeof(pConnApi->strDemanglerServer)); + return(0); + } + if ((iControl == 'dtim') || (iControl == 'dtif')) + { + // set demangler timeout + int32_t iNewMnglTimeout; + #if DIRTYCODE_LOGGING + uint8_t bIsFailoverPossible = FALSE; + #endif + + // special case to allow using a different timeout value for connapi involving CC assistance + if (iControl == 'dtif') + { + NetPrintf(("connapi: [%p] changing demangler_with_failover timeout config (%d ms --> %d ms)\n", pConnApi, pConnApi->iConfigMnglTimeoutFailover, iValue)); + pConnApi->iConfigMnglTimeoutFailover = iValue; + } + else + { + NetPrintf(("connapi: [%p] changing demangler timeout config (%d ms --> %d ms)\n", pConnApi, pConnApi->iConfigMnglTimeout, iValue)); + pConnApi->iConfigMnglTimeout = iValue; + } + + // check if the special timeout value for cc scenarios is applicable + if (pConnApi->iCcMode != CONNAPI_CCMODE_PEERONLY) + { + #if DIRTYCODE_LOGGING + bIsFailoverPossible = TRUE; + #endif + iNewMnglTimeout = pConnApi->iConfigMnglTimeoutFailover; + } + else + { + iNewMnglTimeout = pConnApi->iConfigMnglTimeout; + } + + // pass new effective demangler timeout value down to ProtoMangle + if ((pConnApi->pProtoMangle != NULL) && (iNewMnglTimeout != pConnApi->iCurrentMnglTimeout)) + { + NetPrintf(("connapi: [%p] applying new demangler timeout (%d ms --> %d ms) for a demangling scenario %s\n", + pConnApi, pConnApi->iCurrentMnglTimeout, iNewMnglTimeout, (bIsFailoverPossible?"with":"without"))); + pConnApi->iCurrentMnglTimeout = iNewMnglTimeout; + ProtoMangleControl(pConnApi->pProtoMangle, 'time', pConnApi->iCurrentMnglTimeout, 0, NULL); + } + + return(0); + } + #if CONNAPI_XBOX_NETWORKING + if (iControl == 'exsn') + { + if ((pValue == NULL) || (*(char*)pValue == '\0')) + { + NetPrintf(("connapi: [%p] 'exsn', invalid external session name\n", pConnApi)); + } + else + { + if (pConnApi->strExternalSessionName != pValue) + { + ds_strnzcpy(pConnApi->strExternalSessionName, (char*)pValue, sizeof(pConnApi->strExternalSessionName)); + NetPrintf(("connapi: [%p] 'exsn', external session name saved as (%s)\n", pConnApi, pConnApi->strExternalSessionName)); + } + if (pConnApi->pProtoMangle != NULL) + { + return(ProtoMangleControl(pConnApi->pProtoMangle, 'exsn', 0, 0, pConnApi->strExternalSessionName)); + } + return(0); + } + } + if (iControl == 'exst') + { + if ((pValue == NULL) || (*(char*)pValue == '\0')) + { + NetPrintf(("connapi: [%p] 'exst', invalid external session template name\n", pConnApi)); + } + else + { + if (pConnApi->strExternalSessionTemplateName != pValue) + { + ds_strnzcpy(pConnApi->strExternalSessionTemplateName, (char*)pValue, sizeof(pConnApi->strExternalSessionTemplateName)); + NetPrintf(("connapi: [%p] 'exst', external session template name saved as (%s)\n", pConnApi, pConnApi->strExternalSessionTemplateName)); + } + if (pConnApi->pProtoMangle != NULL) + { + return(ProtoMangleControl(pConnApi->pProtoMangle, 'exst', 0, 0, pConnApi->strExternalSessionTemplateName)); + } + return(0); + } + } + #endif + if (iControl == 'gprt') + { + NetPrintf(("connapi: [%p] using game port %d\n", pConnApi, iValue)); + pConnApi->uGamePort = (uint16_t)iValue; + return(0); + } + if (iControl == 'lbuf') + { + // set game link buffer size + pConnApi->iLinkBufSize = iValue; + return(0); + } + if (iControl == 'maxg') + { + // set maximum number of voipgroups + NetPrintf(("connapi: changing maximum number of voipgroups from %d to %d\n", _ConnApi_iMaxVoipGroups, iValue)); + _ConnApi_iMaxVoipGroups = (int8_t)iValue; + return(0); + } + if (iControl == 'meta') + { + // enable/disable commudp metadata + NetPrintf(("connapi: [%p] commudp metadata %s\n", pConnApi, iValue ? "enabled" : "disabled")); + pConnApi->bCommUdpMetadata = iValue; + return(0); + } + if (iControl == 'minp') + { + // set gamelink input packet queue length + pConnApi->iGameMinp = iValue; + return(0); + } + if (iControl == 'mngl') + { + #if CONNAPI_XBOX_NETWORKING + NetPrintf(("connapi: [%p] demangler cannot be disabled on Xbox One\n", pConnApi)); + #else + // set demangler enable/disable + NetPrintf(("connapi: [%p] demangling %s\n", pConnApi, iValue ? "enabled" : "disabled")); + pConnApi->bDemanglerEnabled = iValue; + #endif + return(0); + } + if (iControl == 'mout') + { + // set gamelink output packet queue length + pConnApi->iGameMout = iValue; + return(0); + } + if (iControl == 'mwid') + { + // set gamelink packet length + pConnApi->iGameMwid = iValue; + return(0); + } + if (iControl == 'nmsk') + { + // set netmask + pConnApi->uNetMask = (unsigned)iValue; + return(0); + } + if (iControl == 'rcbk') + { + // enable disc callback on removal + pConnApi->bRemoveCallback = iValue; + return(0); + } + #if CONNAPI_XBOX_NETWORKING + if (iControl == 'scid') + { + if ((pValue == NULL) || (*(char*)pValue == '\0')) + { + NetPrintf(("connapi: [%p] 'scid', invalid service configuration id\n", pConnApi)); + } + else + { + if (pConnApi->strScid != pValue) + { + ds_strnzcpy(pConnApi->strScid, (char*)pValue, sizeof(pConnApi->strScid)); + NetPrintf(("connapi: [%p] 'scid', service configuration name saved as (%s)\n", pConnApi, pConnApi->strScid)); + } + if (pConnApi->pProtoMangle != NULL) + { + return(ProtoMangleControl(pConnApi->pProtoMangle, 'scid', 0, 0, pConnApi->strScid)); + } + return(0); + } + } + #endif + if ((iControl == 'stun') && (pConnApi->pProtoTunnel == NULL) && (pValue != NULL)) + { + // set prototunnel ref + pConnApi->pProtoTunnel = (ProtoTunnelRefT *)pValue; + return(0); + } + if (iControl == 'time') + { + // set timeout + pConnApi->iTimeout = iValue; + VoipGroupControl(pConnApi->pVoipGroupRef, 'time', iValue, 0, NULL); + return(0); + } + if ((iControl == 'tctl') && (pConnApi->pProtoTunnel != NULL)) + { + return(ProtoTunnelControl(pConnApi->pProtoTunnel, iValue, iValue2, 0, pValue)); + } + if (iControl == 'tgam') + { + pConnApi->uGameTunnelFlag = iValue; + pConnApi->uGameTunnelFlagOverride = iValue2; + } + if (iControl == 'tunl') + { + // set tunnel status + if (iValue >= 0) + { + pConnApi->bTunnelEnabled = iValue; + VoipGroupControl(pConnApi->pVoipGroupRef, 'tunl', iValue, 0, NULL); + } + if (iValue2 > 0) + { + pConnApi->iTunnelPort = iValue2; + } + return(0); + } + if (iControl == 'type') + { + // set connection flags (CONNAPI_CONNFLAG_*) + NetPrintf(("connapi: [%p] connflag change from 0x%02x to 0x%02x\n", pConnApi, pConnApi->uConnFlags, iValue)); + pConnApi->uConnFlags = (uint16_t)iValue; + return(0); + } + if (iControl == 'ulmt') + { + // set gamelink unack window size + pConnApi->iGameUnackLimit = iValue; + return(0); + } + if (iControl == 'voig') + { + if (iValue == 'getr') //$$todo remove 'getr' support in future release + { + return(ConnApiStatus(pConnApi, 'vgrp', pValue, sizeof(pConnApi->pVoipGroupRef))); + } + VoipGroupControl(pConnApi->pVoipGroupRef, iValue, iValue2, 0, pValue); + return(0); + } + if (iControl == 'voip') + { + if(pConnApi->pVoipRef != NULL) + { + VoipControl(pConnApi->pVoipRef, iValue, iValue2, pValue); + return(0); + } + + NetPrintf(("connapi: [%p] - WARNING - ConnApiControl(): processing of 'voip' selector failed because of an uninitialized VOIP module reference!\n", pConnApi)); + } + if (iControl == 'vprt') + { + NetPrintf(("connapi: [%p] using voip port %d\n", pConnApi, iValue)); + pConnApi->uVoipPort = (uint16_t)iValue; + return(0); + } + if (iControl == 'vset') + { + uint8_t bVoipEnabled; + // if disabling VoIP, set game flags appropriately + if ((bVoipEnabled = iValue) == FALSE) + { + NetPrintf(("connapi: [%p] 'vset' used to globally disable voip\n", pConnApi)); + pConnApi->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; + } + return(0); + } + #if DIRTYCODE_DEBUG + if (iControl == '!res') + { + ConnApiClientT *pClient; + if (iValue2 >= 0 && iValue2 < pConnApi->ClientList.iMaxClients) + { + pClient = &pConnApi->ClientList.Clients[iValue2]; + if (pClient->bAllocated == TRUE) + { + if (iValue > 0) + { + NetPrintf(("connapi: [%p] setting debug FailP2P flag for user at index %d\n", pConnApi, iValue2)); + pConnApi->ClientList.Clients[iValue2].uFlags |= CONNAPI_CLIENTFLAG_P2PFAILDBG; + if (iValue2 == pConnApi->iSelf) + { + NetPrintf(("connapi: [%p] setting debug FailP2P flag with our own index, disabling P2P for all users for all users\n", pConnApi)); + } + } + else + { + NetPrintf(("connapi: [%p] resetting debug FailP2P flag for user at index %d\n", pConnApi, iValue2)); + pConnApi->ClientList.Clients[iValue2].uFlags &= ~CONNAPI_CLIENTFLAG_P2PFAILDBG; + } + } + else + { + NetPrintf(("connapi: [%p] failed to set debug FailP2P flag for user at index %d\n", pConnApi, iValue2)); + return(-1); + } + } + else if (iValue2 < 0) + { + pConnApi->bFailP2PConnect = iValue; + NetPrintf(("connapi: [%p] setting debug FailP2P flag to %d for all users\n", pConnApi, iValue)); + } + else + { + NetPrintf(("connapi: [%p] cannot set debug FailP2P flag to %d for user at index %d\n", pConnApi, iValue, iValue2)); + } + + return(0); + } + #endif + if (iControl == 'sqos') + { + pConnApi->iQosDuration = iValue; + NetPrintf(("connapi: [%p] total duration of QoS characterization over netgamelinks --> %d ms %s\n", + pConnApi, pConnApi->iQosDuration, ((pConnApi->iQosDuration == 0) ? "(QoS over NetGameLink disabled)" : ""))); + + pConnApi->iQosInterval = iValue2; + NetPrintf(("connapi: [%p] send interval used for QoS characterization over netgamelinks --> %d ms\n", pConnApi, pConnApi->iQosInterval)); + + return(0); + } + if (iControl == 'lqos') + { + NetPrintf(("connapi: [%p] packet size used for QoS characterization over netgamelinks --> %d bytes\n", pConnApi, iValue)); + pConnApi->iQosPacketSize = iValue; + return(0); + } + if (iControl == 'estv') + { + ConnApiClientT *pClient; + if (iValue < pConnApi->ClientList.iMaxClients) + { + pClient = &pConnApi->ClientList.Clients[iValue]; + if (pClient->bAllocated == TRUE) + { + // no-op if it is already established + // in the case of the local user, he will always have bEstablishVoip == TRUE so let's not spam with extra logs about it + if (pClient->bEstablishVoip == FALSE) + { + NetPrintf(("connapi: [%p] activating delayed voip for client %d\n", pConnApi, iValue)); + pClient->bEstablishVoip = TRUE; + return(0); + } + } + else + { + NetPrintf(("connapi: [%p] activating delayed voip failed because client %d not allocated\n", pConnApi, iValue)); + return(-1); + } + } + else + { + NetPrintf(("connapi: [%p] activating delayed voip failed because of client index of range\n", pConnApi)); + return(-1); + } + } + + // unhandled + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiUpdate + + \Description + Update the ConnApi module (must be called directly if auto-update is disabled) + + \Input *pConnApi - pointer to module state + + \Notes + By default, ConnApiUpdate() is called internally via a NetConnIdle() callback + (auto-update). If auto-update is disabled via ConnApiControl('auto'), + ConnApiUpdate must be polled by the application instead. + + \Version 01/06/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ConnApiUpdate(ConnApiRefT *pConnApi) +{ + // update client flags + _ConnApiUpdateClientFlags(pConnApi); + + // update connapi connections + _ConnApiUpdateConnections(pConnApi); + + #if CONNAPI_XBOX_NETWORKING + /* + update protomangle outside the demangling phase context + required for protomangle to be pumped after a call to ProtoMangleControl('remv') + */ + if (pConnApi->bDemanglerEnabled == TRUE) + { + ProtoMangleUpdate(pConnApi->pProtoMangle); + } + #endif + + // handle removal of clients from client list, if requested + _ConnApiUpdateRemoval(pConnApi); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiAddCallback + + \Description + Register a new callback + + \Input *pConnApi - pointer to module state + \Input *pCallback - the callback to add + \Input *pUserData - the user data that will be passed back + + \Output + int32_t - negative means error. 0 or greater: slot used + + \Version 09/18/2008 (jrainy) +*/ +/********************************************************************************F*/ +int32_t ConnApiAddCallback(ConnApiRefT *pConnApi, ConnApiCallbackT *pCallback, void *pUserData) +{ + int32_t iIndex; + + // skip the first (0th, which is reserved for 'cbfp' and 'cbup' backward compatibility. + for(iIndex = 1; iIndex < CONNAPI_MAX_CALLBACKS; iIndex++) + { + if (pConnApi->pCallback[iIndex] == NULL) + { + pConnApi->pCallback[iIndex] = pCallback; + pConnApi->pUserData[iIndex] = pUserData; + + return(iIndex); + } + } + return(CONNAPI_CALLBACKS_FULL); +} + +/*F********************************************************************************/ +/*! + \Function ConnApiRemoveCallback + + \Description + Unregister a callback + + \Input *pConnApi - pointer to module state + \Input *pCallback - the callback to remove + \Input *pUserData - the user data that was originally passed in + + \Output + int32_t - negative means error. 0 or greater: slot freed + + \Version 09/18/2008 (jrainy) +*/ +/********************************************************************************F*/ +int32_t ConnApiRemoveCallback(ConnApiRefT *pConnApi, ConnApiCallbackT *pCallback, void *pUserData) +{ + int32_t iIndex; + + // skip the first (0th, which is reserved for 'cbfp' and 'cbup' backward compatibility. + for(iIndex = 1; iIndex < CONNAPI_MAX_CALLBACKS; iIndex++) + { + if ((pConnApi->pCallback[iIndex] == pCallback) && (pConnApi->pUserData[iIndex] == pUserData)) + { + pConnApi->pCallback[iIndex] = NULL; + pConnApi->pUserData[iIndex] = NULL; + return(iIndex); + } + } + return(CONNAPI_CALLBACK_NOT_FOUND); +} diff --git a/src/thirdparty/dirtysdk/source/game/netgamedist.c b/src/thirdparty/dirtysdk/source/game/netgamedist.c new file mode 100644 index 00000000..252a9914 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/game/netgamedist.c @@ -0,0 +1,2579 @@ +/*H*************************************************************************************************/ +/*! + + \File netgamedist.c + + \Description + This file provides some upper layer protocol abstractions such as controller packet + buffering and exchange logic. + + \Notes + \verbatim + + From a "client" perspective: + + pRef->InpBufData is the inbound queue where inbound multipackets from server are accumulated. + * pRef->InpBufData.iBeg identifies the oldest entry in that queue + (i.e. the next entry to be consumed locally) + * pRef->InpBufData.iEnd identifies the next free entry in that queue + (i.e. the next spot to write into when we read from the socket) + * An overflow in that queue is always detected by comparing pRef->InpBufData.iBeg and pRef->InpBufData.iEnd. + + pRef->OutBufData is the outbound queue where outbound packets are accumulated pending transmission to the server. + * pRef->OutBufData.iBeg identifies the oldest entry in that queue + (i.e. the next entry to be sent over the network) + * pRef->OutBufData.iEnd identifies the next free entry in that queue + (i.e. the next spot to write into when user submits data) + * SPECIFICITY: When pRef->OutBufData.iBeg is advanced, the entry that it used to point to is NOT YET invalidated + because it still needs to be surfaced up to the user later when paired with a inbound bundle from the server. + To track those "pending entries" located before the position of pRef->OutBufData.iBeg, this third pointer + is used: "pRef->InpBufData.iBeg+pRef->iIOOffset". Notice that it consists of a pointer tracking the + other queue + an adjustment offset. + * An overflow in that queue is always detected by comparing "pRef->InpBufData.iBeg+pRef->iIOOffset" + and pRef->OutBufData.iEnd. + + From a "server" perspective: + + pRef->InpBufData is the inbound queue where inbound packets from a specific client are accumulated. + * pRef->InpBufData.iBeg identifies the oldest entry in that queue + (i.e. the next entry to be consumed locally) + * pRef->InpBufData.iEnd identifies the next free entry in that queue + (i.e. the next spot to write into when we read from the socket) + * An overflow in that queue is always detected by comparing pRef->InpBufData.iBeg and pRef->InpBufData.iEnd. + + pRef->OutBufData is the outbound queue where outbound multipackets are accumulated pending transmission to a specific client. + * pRef->OutBufData.iBeg identifies the oldest entry in that queue + (i.e. the next entry to be sent over the network) + * pRef->OutBufData.iEnd identifies the next free entry in that queue + (i.e. the next spot to write into when the server has a bundle of paired inputs to submit for transmission) + * An overflow in that queue is always detected by comparing pRef->OutBufData.iBeg and pRef->OutBufData.iEnd + + \endverbatim + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2000-2018. ALL RIGHTS RESERVED. + + \Version 1.0 12/20/00 (GWS) Based on split of GmClient.c + \Version 1.1 12/31/01 (GWS) Cleaned up and made really platform independent + \Version 1.2 12/03/09 (mclouatre) Added configurable run-time verbosity +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/game/netgamepkt.h" +#include "DirtySDK/game/netgamelink.h" +#include "DirtySDK/game/netgamedist.h" + +/*** Defines ***************************************************************************/ + +#define NETGAMEDIST_VERBOSITY (2) + +#define TIMING_DEBUG (0) +#define PING_DEBUG (0) +#define INPUTCHECK_LOGGING_DELAY (15) // 15 msec +#define GMDIST_META_ARRAY_SIZE (32) // how many past versions of sparse multipacket to keep + +// PACKET_WINDOW can be overriden at build time with the nant global property called dirtysdk-distpktwindow-size +#ifndef PACKET_WINDOW +#define PACKET_WINDOW (64) +#endif + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! Describe an entry in the flat buffer. +typedef struct GameBufferLookupT +{ + uint32_t uInsertTime; //!< time when packet was queued + uint32_t uPos; //!< indexes into OutBufData.pControllerIO or InpBufData.pControllerIO + uint16_t uLen; + uint16_t uLenSize; +} GameBufferLookupT; + +//! Flat buffer structure. Implements a wrapping queue of packets. +typedef struct GameBufferDataT +{ + //! incoming and outgoing controller packets + unsigned char *pControllerIO; + //! length of the io buffer + uint32_t uBufLen; + // input/output lookup, addresses into pControllerIO + GameBufferLookupT IOLookUp[PACKET_WINDOW]; + //! index of first packet to send/process in pControllerIO + int32_t iBeg; + //! index of last packet to send/process in pControllerIO + int32_t iEnd; +} GameBufferDataT; + +//! Describes one version of multipacket. +typedef struct NetGameDistMetaInfoT +{ + //! which entries are used + uint32_t uMask; + //! number of players used + uint8_t uPlayerCount; + //! version number + uint8_t uVer; +} NetGameDistMetaInfoT; + +//! netgamedist internal state +struct NetGameDistRefT +{ + //! module memory group + int32_t iMemGroup; + void *pMemGroupUserData; + + //! output buffer for packets and lookup table + GameBufferDataT OutBufData; + //! input buffer for packets and lookup table + GameBufferDataT InpBufData; + + //! offset between the input and output queues + int32_t iIOOffset; + + //! local sequence number + uint32_t uLocalSeq; + //! global sequence number + uint32_t uGlobalSeq; + + //! external status monitoring + NetGameLinkStatT NetGameLinkStats; + + //! current exchange rate + int32_t iInputRate; + //! input exchange window + int32_t iInputWind; + //! clamp the min window size + int32_t iInputMini; + //! clamp the max window size + int32_t iInputMaxi; + + //! when to recalc window + uint32_t uInpCalc; + //! when packet was last send + uint32_t uInpNext; + + //! netgamelink ref + void *pNetGameLinkRef; + //! netgamelink stat func + NetGameDistStatProc *pStatProc; + //! netgame send function + NetGameDistSendProc *pSendProc; + //! netgame recv function + NetGameDistRecvProc *pRecvProc; + + NetGameDistDropProc *pDropProc; + NetGameDistPeekProc *pPeekProc; + + NetGameDistLinkCtrlProc *pLinkCtrlProc; + + //! when bActAsServer is true the index is of the player that owns the dist + //! if bActAsServer is false the index is of the local player in the dist game + uint32_t uDistIndex; + + //! the total number of players + uint32_t uTotalPlyrs; + + //! true if we are receiving multi packets from dirtycast in OTP mode + //! false if when inbound packets originated from the client in a 2 player mode + uint32_t bRecvMulti; + + //! whether we are acting as the server. + uint32_t bActAsServer; + + //! a max packet for use by input + NetGameMaxPacketT MaxPkt; + + //! a max packet for use by input + char aMultiBuf[NETGAME_DATAPKT_MAXSIZE]; + + //! the number of writes to a position in the input queue, (dropproc) + int32_t aPacketId[PACKET_WINDOW]; + + //! latest stats received by each pClient + NetGameDistStatT aRecvStats[GMDIST_MAX_CLIENTS]; + + //! Error condition. Set during calls like update, if an error occurs. + int32_t iErrCond; + int32_t iLastSentDelta; + uint32_t uSkippedInputCheckLogCount; + uint32_t uLastInputCheckLogTick; + + int32_t iInboundDropPktCnt; + int32_t iInboundPktCnt; + int32_t iOutboundPktCnt; + + //! total wait time in input queue + int32_t iWaitTimeTotal; + + //! total Input deqeued count + int32_t iInboundDequeueCnt; + + //! total dist processing time + int32_t iDistProcTimeTotal; + + //! total dist inputs processed + int32_t iDistProcCnt; + + char strErrorText[GMDIST_ERROR_SIZE]; + + //! whether we must update the flow control flags to the game server + uint8_t bUpdateFlowCtrl; + + //! the range of meta info we must update the + uint8_t uUpdateMetaInfoBeg; + uint8_t uUpdateMetaInfoEnd; + + //! whether we are ready to send or not + uint8_t bRdySend; + + //! whether we are ready to receive or not + uint8_t bRdyRecv; + + //! whether remote is ready to send or not + uint8_t bRdySendRemote; + + //! whether remote is ready to receive or not + uint8_t bRdyRecvRemote; + + uint32_t uLocalCRC; + uint8_t bLocalCRCValid; + + uint32_t uRemoteCRC; + uint8_t bRemoteCRCValid; + + //! debug output verbosity + uint8_t uVerbose; + + //! boolean indicating whether we want to surface CRC cahllenges from the GS + uint8_t bCRCChallenges; + + //! boolean indicating whether we received meta information on the packet layout + uint8_t bGotMetaInfo; + + //! boolean indicating whether we are sending sparse multi-packets + uint8_t bSparse; + + //! Meta information about sparse multi-packets to send + NetGameDistMetaInfoT aMetaInfoToSend[GMDIST_META_ARRAY_SIZE]; + + //! Received meta information about sparse multi-packets (wraps around) + NetGameDistMetaInfoT aMetaInfo[GMDIST_META_ARRAY_SIZE]; + + //! The version meta information from the last peeked or queried packet + uint32_t uLastQueriedVersion; +}; + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +#if DIRTYCODE_LOGGING +// The following is meant to be indexed with the corresponding constants defined in netgamedist.h. +// Array contents need to be tailored if constants are removed, modified or added. +static char _strNetGameDistDataTypes[6][32] = + {"INVALID", + "GMDIST_DATA_NONE", + "GMDIST_DATA_INPUT", + "GMDIST_DATA_INPUT_DROPPABLE", + "GMDIST_DATA_DISCONNECT", + "GMDIST_DATA_NODATA"}; +#endif + +// Public variables + + +/*** Private Functions *****************************************************************/ + + +#if PING_DEBUG +/*F*************************************************************************************************/ +/*! + \Function _PingHistory + + \Description + Display the uPing history [DEBUG only] + + \Input *pStats - pointer to NetGameLinkStat struct + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +static void _PingHistory(const NetGameLinkStatT *pStats) +{ + int32_t iPing; + int32_t iIndex; + char strMin[64]; + char strMax[64]; + char strAvg[64]; + char strCnt[64]; + const NetGameLinkHistT *pHist; + static uint32_t uPrev = 0; + + // see if its time + if (pStats->pingslot == uPrev) + { + return; + } + uPrev = pStats->pingslot; + + iPing = 0; + + for (iIndex = 0; iIndex < PING_HISTORY; ++iIndex) + { + pHist = pStats->pinghist + ((pStats->pingslot - iIndex) & (PING_HISTORY-1)); + strMin[iIndex] = '0'+pHist->min/50; + strMax[iIndex] = '0'+pHist->max/50; + strAvg[iIndex] = '0'+pHist->avg/50; + strCnt[iIndex] = '0'+pHist->cnt; + + iPing += pHist->avg; + } + strMin[iIndex] = 0; + strMax[iIndex] = 0; + strAvg[iIndex] = 0; + strCnt[iIndex] = 0; + + iPing /= PING_HISTORY; + + NetPrintf(("history(%d/%d): ping=%d, late=%d, calc=%d\n", pStats->pingslot, pStats->pingtick, pStats->ping, pStats->late, iPing)); + NetPrintf((" %s\n", strMin)); + NetPrintf((" %s\n", strMax)); + NetPrintf((" %s\n", strAvg)); + NetPrintf((" %s\n", strCnt)); +} +#endif + +/*F*************************************************************************************************/ +/*! + \Function _SetDataPtrTry + + \Description + returns the position in the input or output buffer for addition of a packet of length uLength. + + \Input *pRef - reference pointer + \Input *pBuffer - buffer pointer + \Input uLength - length of the next packet to be stored + + \Output + int32_t - pos if a space was found. -1 if the buffer cannot accomodate uLength + + \Version 02/08/07 (jrainy) +*/ +/*************************************************************************************************F*/ +static int32_t _SetDataPtrTry(NetGameDistRefT *pRef, GameBufferDataT *pBuffer, uint16_t uLength) +{ + uint32_t uIndexA, uIndexB; + uint32_t uPosA; + + if (uLength > pBuffer->uBufLen) + { + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "overflow in _SetDataPtrTry. requested length (%d) > buffer size (%d), ", uLength, pBuffer->uBufLen); + NetPrintf(("netgamedist: [%p] critical failure in _SetDataPtrTry()\n", pRef)); + return(-1); + } + + // identify the IOLookup index that points to the last-written entry (uIndexA), and find out where the corresponding next free byte exactly is (uPosA) + uIndexA = GMDIST_Modulo(pBuffer->iEnd - 1, PACKET_WINDOW); + uPosA = pBuffer->IOLookUp[uIndexA].uPos + pBuffer->IOLookUp[uIndexA].uLen; + uPosA = ((uPosA + 3)&~3); // aligns posA to the next 4-byte boundary + + // identify the IOLookup index that points to the oldest valid entry (uIndexB) + if ((pBuffer == &pRef->InpBufData) || pRef->bActAsServer) + { + /* for the input queue (inbound data from either server or client) or the server output queue (outbound data to client) + the iBeg index identifies the oldest valid data */ + uIndexB = pBuffer->iBeg; + } + else + { + /* For the client output queue (outbound data to server), the iBeg index identifies the next packet to be + sent to the server not the oldest valid data. The oldest valid data, i.e. pending data to be + notified as paired by the game server, is rather always identified using a combination of + the "inbound" iBeg and the iOOffset. */ + uIndexB = GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW); + } + + // if the buffer is empty then we can just start filling it in from the beginning + if (uIndexB == (unsigned)pBuffer->iEnd) + { + return(0); + } + else + { + /* note: uIndexB can be used to access valid data in IOLookup[] only if uIndexB != pBuffer->iEnd + because pBuffer->iEnd points to the next "free" entry... so it contains no valid data yet. */ + + // find out where the last valid byte exactly is (uPosB) + uint32_t uPosB = pBuffer->IOLookUp[uIndexB].uPos; + + if (uPosA >= uPosB) + { + // if we can't fit at the end, let's retry from the beginning + if ((uPosA + uLength) > pBuffer->uBufLen) + { + uPosA = 0; + // fall through to the next 'if' + } + else + { + return(uPosA); + } + } + if (uPosB >= uPosA) + { + if ((uPosA + uLength) >= uPosB) + { + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "overflow in _SetDataPtrTry. indexA was %d, indexB was %d, posA was %d, posB was %d, ", uIndexA, uIndexB, pBuffer->IOLookUp[uIndexA].uPos + pBuffer->IOLookUp[uIndexA].uLen, uPosB); + + NetPrintf(("netgamedist: [%p] %s buffer full.\n", pRef, ((pBuffer == &pRef->InpBufData)?"input":"output"))); + return(-1); + } + else + { + return(uPosA); + } + } + } + + // unreachable code. If we get here something went terribly wrong. + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "_SetDataPtrTry reached unreachable code."); + NetPrintf(("netgamedist: [%p] critical failure in _SetDataPtrTry() - unreachable code\n", pRef)); + + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function _SetDataPtr + + \Description + Prepares the input or output buffer for addition of a packet of length uLength. + + \Input *pRef - reference pointer + \Input *pBuffer - buffer pointer + \Input uLength - length of the next packet to be stored + + \Output + uint8_t - TRUE if a space was found. FALSE if the buffer cannot accomodate uLength + + \Version 02/08/07 (jrainy) +*/ +/*************************************************************************************************F*/ +static uint8_t _SetDataPtr(NetGameDistRefT *pRef, GameBufferDataT *pBuffer, uint16_t uLength) +{ + int32_t uPos = _SetDataPtrTry(pRef, pBuffer, uLength); + + if (uPos != -1) + { + pBuffer->IOLookUp[pBuffer->iEnd].uPos = uPos; + return(TRUE); + } + + return(FALSE); +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameDistCheckWindow + + \Description + Handle the window stretching. If we have too many unacknowledged packets, we start sending less often + + \Input *pRef - The NetGameDist ref + \Input iRemain - Remaining frame time (before window stretching) + \Input uTick - Current tick + + \Version 12/21/09 (jrainy) +*/ +/*************************************************************************************************F*/ +static void _NetGameDistCheckWindow(NetGameDistRefT *pRef, int32_t iRemain, uint32_t uTick) +{ + int32_t iQueue; + + if (!pRef->bRecvMulti) + { + // stretch cycle if we are over window + iQueue = GMDIST_Modulo(pRef->OutBufData.iEnd - GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW), PACKET_WINDOW); + if ((iQueue > pRef->iInputWind) && (iRemain < pRef->iInputRate /2)) + { + #if TIMING_DEBUG + // dont show single cycle adjustments + if (uTick+pRef->iInputRate/2-pRef->uInpNext != 1) + { + NetPrintf(("netgamedist: [%p] stretching cycle (que=%d, win=%d, tick=%d, add=%d)\n", + pRef, iQueue, pRef->iInputWind, uTick, (uTick+pRef->iInputRate/2)-pRef->uInpNext)); + } + #endif + // stretch the next send + pRef->uInpNext = uTick+pRef->iInputRate /2; + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameDistUpdateSendTime + + \Description + Update the next send time after sending each packet. In p2p mode, clamp the next send to + no earlier than half a frame after *now* and no later than two frames after *now* + + \Input *pRef - The NetGameDist ref + + \Version 12/21/09 (jrainy) +*/ +/*************************************************************************************************F*/ +static void _NetGameDistUpdateSendTime(NetGameDistRefT *pRef) +{ + uint32_t uTick; + int32_t iNext; + // get current time + + uTick = NetTick(); + // record the send time so we can schedule next packet + pRef->uInpNext += pRef->iInputRate; + + if (!pRef->bRecvMulti) + { + // figure time till next send + iNext = pRef->uInpNext - uTick; + // clamp the time range to half/double rate + if (iNext < pRef->iInputRate / 2) + { + #if TIMING_DEBUG + NetPrintf(("send clamping to half (was %d)\n", iNext)); + #endif + pRef->uInpNext = uTick + pRef->iInputRate /2; + } + if (iNext > pRef->iInputRate * 2) + { + #if TIMING_DEBUG + NetPrintf(("send clamping to double (was %d)\n", iNext)); + #endif + pRef->uInpNext = uTick + pRef->iInputRate *2; + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameDistSendInput + + \Description + Provide local input data + + \Input *pRef - reference pointer + \Input *pBuffer - controller data + \Input iLength - data length + \Input iLengthSize - size of length buffer + + \Output + int32_t - negative=error (including GMDIST_OVERFLOW=overflow), positive=packet successfully sent or saved to NetGameDist send buffer + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +static int32_t _NetGameDistSendInput(NetGameDistRefT *pRef, void *pBuffer, int32_t iLength, int32_t iLengthSize) +{ + int32_t iNext, iResult, iBeg; + unsigned char *pData; + + // add packet to queue + if (pBuffer != NULL) + { + // verify length is valid + if (iLength < 0) + { + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: _NetGameDistSendInput with iLength %d.", iLength); + NetPrintf(("netgamedist: invalid buffer length passed to _NetGameDistSendInput()!\n")); + return(GMDIST_INVALID); + } + + // see if room to buffer packet + iNext = GMDIST_Modulo(pRef->OutBufData.iEnd+1, PACKET_WINDOW); + // the - 1 steals a spot but prevents: + // input local prechanging iooffset triggering this + iBeg = GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset - 1, PACKET_WINDOW); + if (!pRef->bActAsServer && (iNext == iBeg)) + { + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: _NetGameDistSendInput with iNext %d and iBeg %d.", iNext, iBeg); + return(GMDIST_OVERFLOW); + } + + if (iNext == pRef->OutBufData.iBeg) + { + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: _NetGameDistSendInput with iNext %d and OutBufData.iBeg %d.", iNext, pRef->OutBufData.iBeg); + return(GMDIST_OVERFLOW); + } + + // point to buffer + if (!_SetDataPtr(pRef, &pRef->OutBufData, iLength)) + { + return(GMDIST_OVERFLOW); + } + pData = pRef->OutBufData.pControllerIO+pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uPos; + + // copy data into buffer + pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uLen = iLength; + pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uLenSize = iLengthSize; + ds_memcpy(pData, pBuffer, iLength); + + // save down the time + pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uInsertTime = NetTick(); + + // incorporate into buffer + pRef->OutBufData.iEnd = iNext; + + // moved from inside the 'while' below + // this will update the send time on send attempt, not on actual send. + // i.e. "queue packets at regular interval", not "queue packets so that + // they send at regular interval", which is not really doable anyway. + _NetGameDistUpdateSendTime(pRef); + } + + // try and send packet + // prevent sending if we have pending metainfo to send as it will affect the format + while ((pRef->OutBufData.iBeg != pRef->OutBufData.iEnd) && (pRef->uUpdateMetaInfoBeg == pRef->uUpdateMetaInfoEnd)) + { + // point to buffer + pData = pRef->OutBufData.pControllerIO+pRef->OutBufData.IOLookUp[pRef->OutBufData.iBeg].uPos; + + // setup a data input packet + if (pRef->bActAsServer) + { + if (pRef->OutBufData.IOLookUp[pRef->OutBufData.iBeg].uLenSize == 2) + { + pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_MULTI_FAT; + } + else + { + pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_MULTI; + } + } + else + { + pRef->MaxPkt.head.kind = GAME_PACKET_INPUT; + } + + pRef->MaxPkt.head.len = pRef->OutBufData.IOLookUp[pRef->OutBufData.iBeg].uLen; + ds_memcpy(pRef->MaxPkt.body.data, pData, pRef->MaxPkt.head.len); + + // try and send + iResult = pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1); + if (iResult > 0) + { + // all is well -- remove from buffer + pRef->OutBufData.iBeg = GMDIST_Modulo(pRef->OutBufData.iBeg+1, PACKET_WINDOW); + } + else if (iResult < 0) + { + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: GMDIST_SENDPROC_FAILED result is %d.", iResult); + + pRef->iErrCond = GMDIST_SENDPROC_FAILED; + NetPrintf(("netgamedist: sendproc failed in _NetGameDistSendInput()!\n")); + return(GMDIST_SENDPROC_FAILED); + } + else + { + // lower-level transport is out of send buffer space at the moment exit for now and try again later. + break; + } + } + + // packet was either buffered and/or sent + return(1); +} + +/*F*************************************************************************************************/ +/*! + \Function _ProcessPacketType + + \Description + Whenever a packet of a given type is received, this function is called + to do any specific process. + + \Input *pRef - reference pointer + \Input uPacketType - the packet type + + \Version 02/22/07 (jrainy) +*/ +/*************************************************************************************************F*/ +static void _ProcessPacketType(NetGameDistRefT *pRef, uint8_t uPacketType) +{ + if (!pRef->bActAsServer && ((uPacketType == GAME_PACKET_INPUT_MULTI) || (uPacketType == GAME_PACKET_INPUT_MULTI_FAT))) + { + pRef->bRecvMulti = TRUE; + } +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameDistSendFlowUpdate + + \Description + Sends a packet to the server enabling or disabling flow control + + \Input *pRef - reference pointer + + \Version 09/29/09 (jrainy) +*/ +/*************************************************************************************************F*/ +static void _NetGameDistSendFlowUpdate(NetGameDistRefT *pRef) +{ + int32_t iResult; + + pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_FLOW; + pRef->MaxPkt.head.len = 7; + + pRef->MaxPkt.body.data[0] = pRef->bRdySend; + pRef->MaxPkt.body.data[1] = pRef->bRdyRecv; + + pRef->MaxPkt.body.data[2] = pRef->bLocalCRCValid; + pRef->MaxPkt.body.data[3] = (pRef->uLocalCRC >> 24); + pRef->MaxPkt.body.data[4] = (pRef->uLocalCRC >> 16); + pRef->MaxPkt.body.data[5] = (pRef->uLocalCRC >> 8); + pRef->MaxPkt.body.data[6] = pRef->uLocalCRC; + + pRef->bLocalCRCValid = FALSE; + + iResult = pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1); + + if (iResult < 0) + { + NetPrintf(("netgamedist: [%p] Flow update failed (error=%d)!\n", pRef, iResult)); + pRef->bUpdateFlowCtrl = FALSE; + } + else if (iResult > 0) + { + pRef->bUpdateFlowCtrl = FALSE; + } + else + { + NetPrintf(("netgamedist: [%p] Flow update deferred (error=overflow)!\n", pRef)); + // nothing to do here, the caller - NetGameDistUpdate - will try again later. + } +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameDistSendMetaInfo + + \Description + Sends a packet to the client describing the format of upcoming packets + + \Input *pRef - reference pointer + + \Version 09/29/09 (jrainy) +*/ +/*************************************************************************************************F*/ +static void _NetGameDistSendMetaInfo(NetGameDistRefT *pRef) +{ + int32_t iResult; + NetGameDistMetaInfoT *pMetaInfo; + + while(pRef->uUpdateMetaInfoBeg != pRef->uUpdateMetaInfoEnd) + { + pMetaInfo = &pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoBeg]; + + pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_META; + pRef->MaxPkt.head.len = 5; + + pRef->MaxPkt.body.data[0] = pMetaInfo->uVer; + pRef->MaxPkt.body.data[1] = pMetaInfo->uMask >> 24; + pRef->MaxPkt.body.data[2] = pMetaInfo->uMask >> 16; + pRef->MaxPkt.body.data[3] = pMetaInfo->uMask >> 8; + pRef->MaxPkt.body.data[4] = pMetaInfo->uMask; + + iResult = pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1); + + if (iResult < 0) + { + NetPrintf(("netgamedist: [%p] Meta info update failed (error=%d)!\n", pRef, iResult)); + pRef->uUpdateMetaInfoBeg = GMDIST_Modulo(pRef->uUpdateMetaInfoBeg + 1, GMDIST_META_ARRAY_SIZE); + } + else if (iResult > 0) + { + NetPrintf(("netgamedist: [%p] Meta info update sent %d 0x%08x !\n", pRef, pMetaInfo->uVer, pMetaInfo->uMask)); + pRef->uUpdateMetaInfoBeg = GMDIST_Modulo(pRef->uUpdateMetaInfoBeg + 1, GMDIST_META_ARRAY_SIZE); + } + else + { + NetPrintf(("netgamedist: [%p] Meta info update deferred (error=overflow)!\n", pRef)); + // nothing to do here, the caller - NetGameDistUpdate - will try again later. + + break; + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameDistCountBits + + \Description + Count the number of bits set in a given uint32_t variable + + \Input *pRef - reference pointer + \Input uMask - mask + + \Output + uint32_t - the number of bits set + + \Version 09/26/09 (jrainy) +*/ +/*************************************************************************************************F*/ +static uint32_t _NetGameDistCountBits(NetGameDistRefT *pRef, uint32_t uMask) +{ + uint32_t uCount = 0; + + while(uMask) + { + uCount += (uMask & 1); + uMask /= 2; + }; + + return(uCount); +} + +#if DIRTYCODE_LOGGING +/*F*************************************************************************************************/ +/*! + \Function _NetGameDistInputCheckLog + + \Description + Logs the values returned by NetGameDistInputCheck. + + \Input *pRef - reference pointer + \Input *pSend - the pointer to pSend the client passed to InputCheck + \Input *pRecv - the pointer to pRecv the client passed to InputCheck + + \Version 12/21/09 (jrainy) +*/ +/*************************************************************************************************F*/ +static void _NetGameDistInputCheckLog(NetGameDistRefT *pRef, int32_t *pSend, int32_t *pRecv) +{ + uint32_t uCurrentTick = NetTick(); + uint32_t uDelay; + + // Calculate delay since last log + uDelay = NetTickDiff(uCurrentTick, pRef->uLastInputCheckLogTick); + + // Check if condition to display the trace is met. + // Condition is: meaningful send/recv info ready OR skip timeout expire + if ( (pSend && (*pSend==0)) || (pRecv && (*pRecv!=0)) || // check meaningful send/recv + (uDelay >= INPUTCHECK_LOGGING_DELAY) ) // check skip timeout + { + if (pSend && pRecv) + { + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, *pSend=%d, *pRecv=%d logskipped_count=%d\n", + pRef, uCurrentTick, *pSend, *pRecv, pRef->uSkippedInputCheckLogCount)); + } + else if (pSend && !pRecv) + { + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, *pSend=%d, pRecv=NULL logskipped_count=%d\n", + pRef, uCurrentTick, *pSend, pRef->uSkippedInputCheckLogCount)); + } + else if (!pSend && pRecv) + { + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, pSend=NULL, *pRecv=%d logskipped_count=%d\n", + pRef, uCurrentTick, *pRecv, pRef->uSkippedInputCheckLogCount)); + } + else + { + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, pSend=NULL, pRecv=NULL logskipped_count=%d\n", + pRef, uCurrentTick, pRef->uSkippedInputCheckLogCount)); + } + + // Re-initialize variables used to skip some logs + pRef->uSkippedInputCheckLogCount = 0; + pRef->uLastInputCheckLogTick = uCurrentTick; + } + else + { + pRef->uSkippedInputCheckLogCount++; + } +} +#endif + +/*F*************************************************************************************************/ +/*! + \Function _NetGameDistInputPeekRecv + + \Description + Behaves just as a receive function, taking data from netgamelink. However, we peek first and + if an overflow is detected, we do not take the packet from netgamelink. In this case, we + return 0, as if doing was ready to be received + + \Input *pRef - reference pointer + \Input *pBuf - buffer to receive into + \Input iLen - available length in buf + + \Output + int32_t - recvproc return value, or forced to 0 if we'd be in overflow + + \Version 08/15/11 (jrainy) +*/ +/*************************************************************************************************F*/ +static int32_t _NetGameDistInputPeekRecv(NetGameDistRefT *pRef, NetGamePacketT *pBuf, int32_t iLen) +{ + NetGamePacketT* pPeekPacket = NULL; + + if (pRef->pPeekProc) + { + uint32_t uDistMask = (1 << GAME_PACKET_INPUT) | + (1 << GAME_PACKET_INPUT_MULTI) | + (1 << GAME_PACKET_INPUT_MULTI_FAT); + + (*pRef->pPeekProc)(pRef->pNetGameLinkRef, &pPeekPacket, uDistMask); + + // ok, we have a mean to see what is coming, and we got something. + if (pPeekPacket) + { + // let's check if we have space for it. + // for completeness, we may check buffer overflow on slots and dropproc, but this seems overkill + if (_SetDataPtrTry(pRef, &pRef->InpBufData, pPeekPacket->head.len + 1) < 0) + { + NetPrintf(("netgamedist: [%p] not receiving from link layer a packet of type %d and length %d because our buffer is full\n", pRef, pPeekPacket->head.kind, pPeekPacket->head.len)); + // act as if the recvproc had nothing. + return(0); + } + } + } + + return((*pRef->pRecvProc)(pRef->pNetGameLinkRef, pBuf, iLen, TRUE)); +} +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistCreate + + \Description + Create the game pClient + + \Input *pNetGameLinkRef - netgamelink module ref + \Input *pStatProc - netgamelink stat callback + \Input *pSendProc - game send func + \Input *pRecvProc - game recv func + \Input uInBufferSize - input buffer size, plan around PACKET_WINDOW*n*packets + \Input uOutBufferSize - output buffer size, plan around PACKET_WINDOW*packets + + \Output + NetGameDistRefT * - pointer to module state + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +NetGameDistRefT *NetGameDistCreate(void *pNetGameLinkRef, NetGameDistStatProc *pStatProc, NetGameDistSendProc *pSendProc, NetGameDistRecvProc *pRecvProc, uint32_t uInBufferSize, uint32_t uOutBufferSize ) +{ + NetGameDistRefT *pRef; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pRef = DirtyMemAlloc(sizeof(*pRef), NETGAMEDIST_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("netgamedist: [%p] unable to allocate module state\n", pRef)); + return(NULL); + } + ds_memclr(pRef, sizeof(*pRef)); + pRef->iMemGroup = iMemGroup; + pRef->pMemGroupUserData = pMemGroupUserData; + pRef->iLastSentDelta = -1; + + // Allocate the input and output buffers + pRef->InpBufData.pControllerIO = DirtyMemAlloc( uInBufferSize, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData ); + pRef->OutBufData.pControllerIO = DirtyMemAlloc( uOutBufferSize, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData ); + + ds_memclr(pRef->InpBufData.pControllerIO, uInBufferSize); + ds_memclr(pRef->OutBufData.pControllerIO, uOutBufferSize); + + pRef->InpBufData.uBufLen = uInBufferSize; + pRef->OutBufData.uBufLen = uOutBufferSize; + + // save the link layer callbacks + pRef->pNetGameLinkRef = pNetGameLinkRef; + pRef->pStatProc = pStatProc; + pRef->pSendProc = pSendProc; + pRef->pRecvProc = pRecvProc; + + // set default controller exchange rate + pRef->iInputRate = 50; + // set the defaults + pRef->iInputMini = 1; + pRef->iInputMaxi = 10; + + // Defaults to two players so that the non-multi version behaves as it used to. + pRef->uTotalPlyrs = 2; + + // Set default verbosity level + pRef->uVerbose = 1; + + // init check tick counter + pRef->uLastInputCheckLogTick = NetTick(); + + NetPrintf(("netgamedist: module created with PACKET_WINDOW = %d\n", PACKET_WINDOW)); + + return(pRef); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistDestroy + + \Description + Destroy the game pClient + + \Input *pRef - reference pointer + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameDistDestroy(NetGameDistRefT *pRef) +{ + DirtyMemFree( pRef->InpBufData.pControllerIO, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData ); + DirtyMemFree( pRef->OutBufData.pControllerIO, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData ); + pRef->InpBufData.pControllerIO = NULL; + pRef->OutBufData.pControllerIO = NULL; + + // free our memory + DirtyMemFree(pRef, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistStatus + + \Description + Get status information + + \Input *pRef - reference pointer + \Input iSelect - selector + \Input iValue - input value + \Input *pBuf - output buffer + \Input iBufSize - output buffer size + + \Output + int32_t - selector specific return value + + \Notes + This is read-only data which can be read at any time. This reference becomes + invalid when the module is destroyed. + + The count of [?snd..?out] is the number of packets ready to send. The min of + [?cmp..?inp] and [?cmp..?out] is the number of packets ready to process (because + you need a packet from each peer in order to perform processing). + + Selectors are: + + \verbatim + SELECTOR RETURN RESULT + 'dcnt' returns the total inbound dequeued count + 'drop' returns the total input packet dropped + 'icnt' returns the total inbound packet count + 'late' returns the latency from the link stats + 'mult' returns the number of players and writes their stats to pBuf + 'ocnt' returns the total outbound packet count + 'pcnt' returns the total packet processed count (full round trip) + 'plat' returns the end-to-end latency in number of packets + 'prti' returns the total packet processed time (full round trip) + 'pwin' returns PACKET_WINDOW configured for netgamedist + 'qver' returns the last queried packet version + 'rate' returns current exchange rate + 'rcrc' if iValue==0 returns whether we have a value otherwise returns the value itself + 'rrcv' returns if remote is ready to receive or not + 'rsnd' returns if remote is ready to send or not + 'stat' returns the link stats via pBuf + 'wait' returns total inbound packet wait time; + 'wind' returns the current exchange window + '?cmp' returns the offset of first packet to process in input buffer + '?cws' returns if we can send. FALSE if sending would overflow the send queue, iValue is length + '?inp' returns the offset of last packet to process in input buffer + '?out' returns the offset of last packet to send in output buffer + '?snd' returns the offset of first packet to send in output buffer + \endverbatim + + Unhandled selectors are passed on to NetGameLink + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetGameDistStatus(NetGameDistRefT *pRef, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + if (iSelect == 'dcnt') + { + return(pRef->iInboundDequeueCnt); + } + if (iSelect == 'drop') + { + return(pRef->iInboundDropPktCnt); + } + if (iSelect == 'icnt') + { + return(pRef->iInboundPktCnt); + } + if (iSelect == 'late') + { + return(pRef->NetGameLinkStats.late); + } + if (iSelect == 'mult') + { + // Make user-provide buffer is large enough to receive a pointer + if ((pBuf != NULL) && (iBufSize >= (int32_t)(sizeof(NetGameDistStatT) * pRef->uTotalPlyrs))) + { + ds_memcpy(pBuf, pRef->aRecvStats, sizeof(NetGameDistStatT) * pRef->uTotalPlyrs); + return(pRef->uTotalPlyrs); + } + else + { + // unhandled + return(-1); + } + } + if (iSelect == 'ocnt') + { + return(pRef->iOutboundPktCnt); + } + if (iSelect == 'plat') + { + return(GMDIST_Modulo(pRef->OutBufData.iEnd - GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW), PACKET_WINDOW)); + } + if (iSelect == 'pcnt') + { + return(pRef->iDistProcCnt); + } + if (iSelect == 'prti') + { + return(pRef->iDistProcTimeTotal); + } + if (iSelect == 'pwin') + { + return(PACKET_WINDOW); + } + if (iSelect == 'qver') + { + return(pRef->uLastQueriedVersion); + } + if (iSelect == 'rate') + { + return(pRef->iInputRate); + } + if (iSelect == 'rcrc') + { + if (iValue) + { + int32_t ret = pRef->uRemoteCRC; + pRef->uRemoteCRC = 0; + pRef->bRemoteCRCValid = FALSE; + return(ret); + } + else + { + return(pRef->bRemoteCRCValid); + } + } + if (iSelect == 'rrcv') + { + return(pRef->bRdyRecvRemote); + } + if (iSelect == 'rsnd') + { + return(pRef->bRdySendRemote); + } + if (iSelect == 'stat') + { + (pRef->pStatProc)(pRef->pNetGameLinkRef, iSelect, iValue, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT)); + + // Make user-provide buffer is large enough to receive a pointer + if ((pBuf != NULL) && (iBufSize >= (int32_t)sizeof(NetGameLinkStatT))) + { + ds_memcpy(pBuf, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT)); + return(0); + } + else + { + // unhandled + return(-1); + } + } + if (iSelect == 'wait') + { + return(pRef->iWaitTimeTotal); + } + if (iSelect == 'wind') + { + return(pRef->iInputWind); + } + if (iSelect == '?cmp') + { + return(pRef->InpBufData.iBeg); + } + if (iSelect == '?cws') + { + int32_t iNext; + if (iValue < 0) + { + return(FALSE); + } + + iNext = GMDIST_Modulo(pRef->OutBufData.iEnd +1, PACKET_WINDOW); + + // mclouatre April 26th 2018 - unclear to me what the -1 is for in the condition below + if (!pRef->bActAsServer && (iNext == GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset - 1, PACKET_WINDOW))) + { + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "'?cws' failure while comparing indices"); + return(FALSE); + } + if (_SetDataPtrTry(pRef, &pRef->OutBufData, iValue) == -1) + { + return(FALSE); + } + + return(TRUE); + } + if (iSelect == '?inp') + { + return(pRef->InpBufData.iEnd); + } + if (iSelect == '?out') + { + return(pRef->OutBufData.iEnd); + } + if (iSelect == '?snd') + { + return(pRef->OutBufData.iBeg); + } + + // fallthrough to NetGameLinkStatus + return((pRef->pStatProc)(pRef->pNetGameLinkRef, iSelect, iValue, pBuf, iBufSize)); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistSetServer + + \Description + Get pointer to status counters + + \Input *pRef - reference pointer + \Input bActAsServer - boolean, whether NetGameDist should send multi-packets + + \Version 02/26/07 (jrainy) +*/ +/*************************************************************************************************F*/ +void NetGameDistSetServer(NetGameDistRefT *pRef, uint8_t bActAsServer) +{ + // the trigraph is there to make sure we don't store non-{0,1} value in. + pRef->bActAsServer = bActAsServer ? TRUE : FALSE; +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistMultiSetup + + \Description + Tells NetGameDist how many players there are in the game, and which one we are. + Will only affect the order packets are received from NetGameDistInputQueryMulti. + + \Input *pRef - netgamelink module ref + \Input iDistIndex - which player we are + \Input iTotPlrs - total number of players + + \Version 02/08/07 (jrainy) +*/ +/*************************************************************************************************F*/ +void NetGameDistMultiSetup(NetGameDistRefT *pRef, int32_t iDistIndex, int32_t iTotPlrs) +{ + NetPrintf(("netgamedist: [%p] NetGameDistMultiSetup index: %d, total players: %d\n", pRef, iDistIndex, iTotPlrs)); + + pRef->uTotalPlyrs = iTotPlrs; + pRef->uDistIndex = iDistIndex; +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistMetaSetup + + \Description + Tells NetGameDist whether to enable meta-information sending, and + sets the mask & the version number for the meta-information. + + \Input *pRef - netgamelink module ref + \Input bSparse - enable or disable meta-information sending (disabling after enabling is currently not-supported on the client) + \Input uMask - set the mask for the meta-information + \Input uVersion - set the version number for the meta-information. + + \Version 08/29/11 (szhu) +*/ +/*************************************************************************************************F*/ +void NetGameDistMetaSetup(NetGameDistRefT *pRef, uint8_t bSparse, uint32_t uMask, uint32_t uVersion) +{ + NetPrintf(("netgamedist: [%p] NetGameDistMetaSetup Enabled: %s, Mask: 0x%x, Version: %d\n", + pRef, bSparse?"TRUE":"FALSE", uMask, uVersion)); + + pRef->bSparse = bSparse; + pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoEnd].uMask = uMask; + pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoEnd].uPlayerCount = _NetGameDistCountBits(pRef, uMask); + pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoEnd].uVer = GMDIST_Modulo(uVersion, GMDIST_META_ARRAY_SIZE); + pRef->uUpdateMetaInfoEnd = GMDIST_Modulo(pRef->uUpdateMetaInfoEnd + 1, GMDIST_META_ARRAY_SIZE); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistInputPeek + + \Description + See if a completed packet is ready + + \Input *pRef - reference pointer + \Input *pType - will be filled with data type + \Input *pPeer - buffer sent by peer + \Input *pPlen - length of data in peer buffer. Must pass in the length of pPeer + + \Output + int32_t - zero=no data pending, negative=error, positive=data returned + + \Version 02/05/2007 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t NetGameDistInputPeek(NetGameDistRefT *pRef, uint8_t *pType, void *pPeer, int32_t *pPlen) +{ + uint8_t *pPsrc; + int32_t iLength; + uint8_t uInputKind; + int16_t iLengthSize = 1; + uint8_t uOffsetLengths; + uint8_t uOffsetBuffer; + uint8_t uOffsetTypes; + uint8_t uOffsetVersion; + uint16_t uPlayerCount = pRef->uTotalPlyrs; + uint8_t uVer = 0; + + + // if no remote data, do an update + if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd) + { + NetGameDistUpdate(pRef); + } + + // no packets from peer? + if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd) + { + return(0); + } + + // point to data buffer + pPsrc = pRef->InpBufData.pControllerIO+pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uPos; + + uInputKind = pPsrc[pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - 1]; + + if (uInputKind == GAME_PACKET_INPUT_MULTI_FAT) + { + iLengthSize = 2; + } + + // find the location to lookup the version + uOffsetVersion = pRef->bRecvMulti ? 1 : 0; + + // extract the version number of the packet, and player count, if the server ever gave us metadata + if (pRef->bGotMetaInfo && pRef->bRecvMulti) + { + uVer = GMDIST_Modulo(pPsrc[uOffsetVersion], GMDIST_META_ARRAY_SIZE); + uPlayerCount = pRef->aMetaInfo[uVer].uPlayerCount; + pRef->uLastQueriedVersion = uVer; + } + + // always pack packets as if there was at least 2 players (to avoid negatively-sized fields) + if (pRef->bRecvMulti && (pRef->uTotalPlyrs == 1)) + { + uPlayerCount = 2; + } + + // compute offsets to various parts + uOffsetTypes = uOffsetVersion + (pRef->bGotMetaInfo ? 1 : 0); + uOffsetLengths = uOffsetTypes + (pRef->bRecvMulti ? (uPlayerCount / 2) : 1); + uOffsetBuffer = uOffsetLengths + (pRef->bRecvMulti ? (iLengthSize * (uPlayerCount - 2)) : 0); + + // copy over the data, -1 to skip the packet kind tacked at the end + iLength = pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - uOffsetBuffer - 1; + if (pPeer != NULL) + { + //check the passed-in length to prevent overflow + if (iLength > *pPlen) + { + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "NetGameDistInputPeek error. length is %d, *pLen is %d.", iLength, *pPlen); + + // fail if the buffer is insufficient + *pPlen = iLength; + return(GMDIST_OVERFLOW); + } + + ds_memcpy(pPeer, pPsrc + uOffsetBuffer, iLength); + } + + *pPlen = iLength; + + if (pType != NULL) + { + *pType = *(pPsrc + (pRef->bRecvMulti ? 1 : 0)); + } + + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] peeked type %d len %d\n", pRef, pType?*pType:0, pPlen?*pPlen:0)); + #endif + + // return success + return(pRef->aPacketId[pRef->InpBufData.iBeg]); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistInputQuery + + \Description + See if a completed packet is ready + + \Input *pRef - reference pointer + \Input *pOurs - buffer previously sent with NetGameDistInputLocal + \Input *pOlen - length of data in ours buffer + \Input *pPeer - buffer sent by peer + \Input *pPlen - length of data in peer buffer + + \Output + int32_t - zero=no data pending, negative=error, positive=data returned + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetGameDistInputQuery(NetGameDistRefT *pRef, void *pOurs, int32_t *pOlen, void *pPeer, int32_t *pPlen) +{ + void* aInputs[2]; + int32_t aLengths[2]; + uint8_t aTypes[2]; + int32_t iRet; + + if ( pRef->uTotalPlyrs != 2 ) + { + return(GMDIST_BADSETUP); + } + + // for a 2 player setup our index is always 0 and the peer is always at 1 + aInputs[0] = pOurs; + aInputs[1] = pPeer; + + iRet = NetGameDistInputQueryMulti(pRef, aTypes, aInputs, aLengths); + + *pOlen = aLengths[0]; + *pPlen = aLengths[1]; + + return(iRet); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistInputQueryMulti + + \Description + See if a completed packet is ready + + \Input *pRef - reference pointer + \Input *pDataTypes - array of data types (GMDIST_DATA_NONE, GMDIST_DATA_INPUT, GMDIST_DATA_INPUT_DROPPABLE, GMDIST_DATA_DISCONNECT). + \Input **ppInputs - array of pointers to inputs to receive. + \Input *pLen - array of lengths from the data received. + + \Output + int32_t - zero=no data pending, negative=error, positive=data returned + + \Version 02/08/07 (jrainy) +*/ +/*************************************************************************************************F*/ +int32_t NetGameDistInputQueryMulti(NetGameDistRefT *pRef, uint8_t *pDataTypes, void **ppInputs, int32_t *pLen) +{ + unsigned char *pOsrc; + uint32_t uIndex, uCount, uPos, uRecvLen; + unsigned char *pRecvBuf; + uint8_t *pLengths; + uint8_t *pTypePos; + uint8_t uDelta; + uint8_t uOffsetLengths; + uint8_t uOffsetBuffer; + uint8_t uOffsetTypes; + uint8_t uOffsetVersion; + uint32_t uOurInp; + uint16_t uPlayerCount = pRef->uTotalPlyrs; + uint8_t bCRCRequest = FALSE; + uint8_t uVer = 0; + + // if waiting on remote data, do an update + if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd) + { + NetGameDistUpdate(pRef); + } + + // check for inputs ready to process; in 2p mode we also require a non-empty output buffer to pair with + if ((pRef->InpBufData.iBeg == pRef->InpBufData.iEnd) || (!pRef->bActAsServer && !pRef->bRecvMulti && (GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW) == pRef->OutBufData.iEnd))) + { + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputQueryMulti() retval=0 tick=%d len=n/a type=n/a\n", pRef, NetTick())); + #endif + + return(0); + } + + if (ppInputs) + { + // access the first byte after the received data + uint8_t* pPacketKind = pRef->InpBufData.pControllerIO + pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uPos + pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - 1; + int16_t iLengthSize = 1; + + if (*pPacketKind == GAME_PACKET_INPUT_MULTI_FAT) + { + iLengthSize = 2; + } + + // Go through the received packet and fill all the input data except ours, then copy our own from outlkup + + uCount = 0; + uPos = 0; + uRecvLen = pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - 1; + pRecvBuf = pRef->InpBufData.pControllerIO+pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uPos; + uDelta = pRef->bRecvMulti ? pRecvBuf[0] : 1; + + uOffsetVersion = pRef->bRecvMulti ? 1 : 0; + + // extract the version number of the packet, and player count, if the server ever gave us metadata + if (pRef->bGotMetaInfo && pRef->bRecvMulti) + { + uVer = GMDIST_Modulo(((char *)pRecvBuf)[uOffsetVersion], GMDIST_META_ARRAY_SIZE); + uPlayerCount = pRef->aMetaInfo[uVer].uPlayerCount; + pRef->uLastQueriedVersion = uVer; + } + + // always pack packets as if there was at least 2 players (to avoid negatively-sized fields) + if (pRef->bRecvMulti && (uPlayerCount == 1)) + { + uPlayerCount = 2; + } + + // compute offsets to various parts + uOffsetTypes = uOffsetVersion + (pRef->bGotMetaInfo ? 1 : 0); + uOffsetLengths = uOffsetTypes + (pRef->bRecvMulti ? (uPlayerCount / 2) : 1); + uOffsetBuffer = uOffsetLengths + (pRef->bRecvMulti ? (iLengthSize * (uPlayerCount - 2)) : 0); + + pLengths = pRecvBuf + uOffsetLengths; + pTypePos = pRecvBuf + uOffsetTypes; + pRecvBuf += uOffsetBuffer; + + for (uIndex = 0; uIndex < pRef->uTotalPlyrs; uIndex++) + { + /* if this is not our own input and we are either + sending for everyone + or this is a player we are including in the current sparse multi-packet + then, add it in */ + if (uIndex != pRef->uDistIndex) + { + if (!pRef->bGotMetaInfo || (pRef->aMetaInfo[uVer].uMask & (1 << uIndex))) + { + /* compute the length of packet for player i. + last length is known (ours) second to last is implicit (total - known ones) */ + if (uCount == (uPlayerCount - 2u)) + { + pLen[uIndex] = uRecvLen - uPos - uOffsetBuffer; + } + else + { + if (iLengthSize == 2) + { + pLen[uIndex] = (pLengths[uCount * iLengthSize] * 256) + pLengths[uCount * iLengthSize + 1]; + } + else + { + pLen[uIndex] = pLengths[uCount]; + } + } + + // prevent invalid sizes if bogus data is received + if (pLen[uIndex] < 0) + { + pLen[uIndex] = 0; + } + + // clamp the total read length to the received buffer, to prevent overflow. + if (pLen[uIndex] + uPos + uOffsetBuffer > uRecvLen) + { + NetPrintfVerbose((pRef->uVerbose, 0, "netgamedist: Warning ! Buffer overrun. Packet trimmed.\n")); + pLen[uIndex] = uRecvLen - uPos - uOffsetBuffer; + } + + // compute the type of packet for player i. (nibbles) + if (uCount % 2) + { + pDataTypes[uIndex] = *pTypePos >> 4; + pTypePos++; + } + else + { + pDataTypes[uIndex] = *pTypePos & 0x0f; + } + + // copy the packet for player i. + if (((pDataTypes[uIndex] & ~GMDIST_DATA_CRC_REQUEST) == GMDIST_DATA_INPUT) || + ((pDataTypes[uIndex] & ~GMDIST_DATA_CRC_REQUEST) == GMDIST_DATA_INPUT_DROPPABLE)) + { + ds_memcpy(ppInputs[uIndex], pRecvBuf + uPos, pLen[uIndex]); + uPos += pLen[uIndex]; + } + else + { + pLen[uIndex] = 0; + } + + // if crc-checking is disabled locally, clear the "crc request" bit + if (!pRef->bCRCChallenges) + { + pDataTypes[uIndex] &= ~GMDIST_DATA_CRC_REQUEST; + } + + /* set a local variable if any of the input has a crc request in. + this is to set the "crc request" bit in local input too, to ease our customers job. */ + if (pDataTypes[uIndex] & GMDIST_DATA_CRC_REQUEST) + { + bCRCRequest = TRUE; + } + + uCount++; + } + else + { + pDataTypes[uIndex] = GMDIST_DATA_NODATA; + pLen[uIndex] = 0; + } + } + } + + if (uDelta) + { + uint8_t uDropIndex; + + // update dist process times for dropped inputs (inputs dropped are considered processed) + for (uDropIndex = 0; uDropIndex < (uDelta - 1) ; uDropIndex++, pRef->iDistProcCnt++) + { + uint32_t uCurInp = GMDIST_Modulo(pRef->InpBufData.iBeg + uDropIndex, PACKET_WINDOW); + pRef->iDistProcTimeTotal += NetTickDiff(NetTick(), pRef->OutBufData.IOLookUp[uCurInp].uInsertTime); + } + + // point to data buffers. Our local storage has just 1-byte type field + pRef->iIOOffset += (uDelta - 1); + uOurInp = GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW); + pOsrc = pRef->OutBufData.pControllerIO + pRef->OutBufData.IOLookUp[uOurInp].uPos + 1; + + // copy over the data + if (pRef->OutBufData.IOLookUp[uOurInp].uLen != 0) + { + pLen[pRef->uDistIndex] = pRef->OutBufData.IOLookUp[uOurInp].uLen - 1; + ds_memcpy(ppInputs[pRef->uDistIndex], pOsrc, pLen[pRef->uDistIndex]); + pRef->iDistProcTimeTotal += NetTickDiff(NetTick(), pRef->OutBufData.IOLookUp[uOurInp].uInsertTime); + pRef->iDistProcCnt++; + pDataTypes[pRef->uDistIndex] = *(pRef->OutBufData.pControllerIO + pRef->OutBufData.IOLookUp[uOurInp].uPos); + } + else + { + NetPrintf(("netgamedist: detected invalid pairing index between input and output queue; ignoring")); + pLen[pRef->uDistIndex] = 0; + pDataTypes[pRef->uDistIndex] = GMDIST_DATA_NONE; + } + } + else + { + pRef->iIOOffset = GMDIST_Modulo(pRef->iIOOffset - 1, PACKET_WINDOW); + pDataTypes[pRef->uDistIndex] = GMDIST_DATA_NONE; + pLen[pRef->uDistIndex] = 0; + } + + if (bCRCRequest) + { + pDataTypes[pRef->uDistIndex] |= GMDIST_DATA_CRC_REQUEST; + } + } + + // calculate time in queue and add it to the total + pRef->iWaitTimeTotal += NetTickDiff(NetTick(), pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uInsertTime); + pRef->iInboundDequeueCnt++; + + // return item from buffer + pRef->InpBufData.iBeg = GMDIST_Modulo(pRef->InpBufData.iBeg + 1, PACKET_WINDOW); + // update global sequence number + pRef->uGlobalSeq += 1; + + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputQueryMulti() retval=%d tick=%d\n", pRef, pRef->uGlobalSeq, NetTick())); + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, " ==== Dumping player-specific data returned in [OUT] params\n")); + if (ppInputs) + { + for (uIndex = 0; uIndex < pRef->uTotalPlyrs; uIndex++) + { + uint8_t uDataType = (pDataTypes[uIndex] & ~GMDIST_DATA_CRC_REQUEST); + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, " == Player %u --> len=%d type=%s\n", uIndex, pLen[uIndex], + _strNetGameDistDataTypes[(((uDataType >= GMDIST_DATA_NONE) && (uDataType <= GMDIST_DATA_NODATA)) ? uDataType : 0)])); + } + } + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, " ==== End of player-specific data dump\n")); + #endif + + // return the sequence number + return(pRef->uGlobalSeq); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistInputLocal + + \Description + Provide local input data + + \Input *pRef - reference pointer + \Input *pBuffer - controller data + \Input iLength - data length + + \Output + int32_t - negative=error (including GMDIST_OVERFLOW=overflow), positive=successfully sent or queued + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetGameDistInputLocal(NetGameDistRefT *pRef, void *pBuffer, int32_t iLength) +{ + if (!pBuffer) + { + return(_NetGameDistSendInput(pRef, NULL, iLength, 0)); + } + pRef->aMultiBuf[0] = GMDIST_DATA_INPUT; + ds_memcpy(pRef->aMultiBuf + 1, pBuffer, iLength); + return(_NetGameDistSendInput(pRef, pRef->aMultiBuf, iLength + 1, 1)); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistInputLocalMulti + + \Description + Provide local input data for all players, including ours, which will be discarded + + \Input *pRef - reference pointer + \Input *pTypesArray - array of data types (GMDIST_DATA_INPUT_DROPPABLE, GMDIST_DATA_INPUT) + \Input **ppBuffer - array of controller data + \Input *pLengthsArray - array of data length + \Input iDelta - game team should pass in 1, other values only usable on server + + \Output + int32_t - negative=error (including GMDIST_OVERFLOW*=overflow), positive=successfully sent or queued + + \Version 02/08/07 (jrainy) +*/ +/*************************************************************************************************F*/ +int32_t NetGameDistInputLocalMulti(NetGameDistRefT *pRef, uint8_t *pTypesArray, void **ppBuffer, int32_t *pLengthsArray, int32_t iDelta) +{ + uint32_t uIndex, uPos, uCount; + uint8_t uOffsetLengths; + uint8_t uOffsetBuffer; + uint8_t uOffsetTypes; + uint8_t uOffsetVersion; + uint8_t *pLengths; + uint8_t *pTypePos; + uint8_t uOurType, uType; + uint32_t uSendSize; + int16_t iLengthSize = 1; + uint16_t uPlayerCount = pRef->bActAsServer ? pRef->uTotalPlyrs : 1; + uint16_t uLastPlayer = uPlayerCount; + int32_t iResult; + + // check if any input forces us to send fat multi-packets. + for (uIndex = 0; uIndex < uPlayerCount; uIndex++) + { + if (pLengthsArray[uIndex] > 0xFF) + { + iLengthSize = 2; + } + } + + // if we are sending sparse multipacket, adjust the player count accordingly + if (pRef->bSparse && pRef->bActAsServer) + { + uPlayerCount = pRef->aMetaInfoToSend[GMDIST_Modulo(pRef->uUpdateMetaInfoEnd - 1, GMDIST_META_ARRAY_SIZE)].uPlayerCount; + } + + // always pack multipacket as if there was at least 2 players (simplify the logic) + if (pRef->bActAsServer && (uPlayerCount == 1)) + { + uLastPlayer = 2; + uPlayerCount = 2; + } + + // compute the offset to various parts of the multipacket + uOffsetVersion = pRef->bActAsServer ? 1 : 0; + uOffsetTypes = uOffsetVersion + ((pRef->bSparse && pRef->bActAsServer) ? 1 : 0); + uOffsetLengths = uOffsetTypes + (pRef->bActAsServer ? (uPlayerCount / 2) : 1); + uOffsetBuffer = uOffsetLengths + (pRef->bActAsServer ? (iLengthSize * (uPlayerCount - 2)) : 0); + + // if server mode + if (pRef->bActAsServer) + { + // compute the total packet size + uSendSize = uOffsetBuffer; + for (uIndex = 0; uIndex < pRef->uTotalPlyrs; uIndex++) + { + if (uIndex != pRef->uDistIndex) + { + uSendSize += pLengthsArray[uIndex]; + } + } + + // bail out if the packet would overflow + if (uSendSize >= NETGAME_DATAPKT_MAXSIZE) + { + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "Multipacket bigger than NETGAME_DATAPKT_MAXSIZE (%d). Discarding and reporting overflow.\n", NETGAME_DATAPKT_MAXSIZE); + NetPrintf(("netgamedist: [%p] Multipacket bigger than NETGAME_DATAPKT_MAXSIZE (%d). Discarding and reporting overflow.\n", pRef, NETGAME_DATAPKT_MAXSIZE)); + return(GMDIST_OVERFLOW_MULTI); + } + + // mark the version, if we are sending meta information + if (pRef->bSparse) + { + pRef->aMultiBuf[uOffsetVersion] = GMDIST_Modulo(pRef->aMetaInfoToSend[GMDIST_Modulo(pRef->uUpdateMetaInfoEnd - 1, GMDIST_META_ARRAY_SIZE)].uVer, GMDIST_META_ARRAY_SIZE); + } + + uOurType = (pTypesArray[pRef->uDistIndex] & ~GMDIST_DATA_CRC_REQUEST); + if (uOurType == GMDIST_DATA_NONE) + { + pRef->aMultiBuf[0] = 0; + } + else + { + if (((iDelta - 1) - pRef->iLastSentDelta) >= PACKET_WINDOW) + { + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "GMDIST_OVERFLOW_WINDOW (pRef->m_packetid[pRef->InpBufData.iBeg]-1) is %d, pRef->iLastSentDelta is %d.\n", (pRef->aPacketId[pRef->InpBufData.iBeg]-1), pRef->iLastSentDelta); + return(GMDIST_OVERFLOW_WINDOW); + } + + pRef->aMultiBuf[0] = (iDelta-1) - pRef->iLastSentDelta; + + pRef->iLastSentDelta = (iDelta-1); + } + pRef->aPacketId[pRef->InpBufData.iBeg] = 0; + + if (uOurType == GMDIST_DATA_NONE) + { + pRef->iIOOffset = GMDIST_Modulo(pRef->iIOOffset + 1, PACKET_WINDOW); + } + } + + pLengths = (unsigned char *)pRef->aMultiBuf + uOffsetLengths; + pTypePos = (unsigned char *)pRef->aMultiBuf + uOffsetTypes; + + uPos = uOffsetBuffer; + uCount = 0; + + for (uIndex = 0; uIndex < uLastPlayer; uIndex++) + { + uType = (pTypesArray[uIndex] & ~GMDIST_DATA_CRC_REQUEST); + + if (((uType == GMDIST_DATA_NONE) || (uType == GMDIST_DATA_DISCONNECT)) && !pRef->bActAsServer) + { + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY,"netgamedist: [%p] Exiting NetGameDistInputLocalMulti() because of invalid data type.\n", pRef)); + #endif + return(GMDIST_INVALID); + } + + // if sending multipackets, dont send input back to user who originally sent it + if (pRef->bActAsServer && (uIndex == pRef->uDistIndex)) + { + continue; + } + // if sending sparse multipackets, skip sending blank inputs for users that aren't in the game + if (pRef->bSparse && ((pRef->aMetaInfoToSend[GMDIST_Modulo(pRef->uUpdateMetaInfoEnd - 1, GMDIST_META_ARRAY_SIZE)].uMask & (1 << uIndex)) == 0)) + { + continue; + } + + // put the data in + ds_memcpy(pRef->aMultiBuf + uPos, ppBuffer[uIndex], pLengthsArray[uIndex]); + uPos += pLengthsArray[uIndex]; + + if (pRef->bActAsServer) + { + if (uCount < (uPlayerCount - 2u)) + { + // we assign the length here for normal packets + if (iLengthSize == 1) + { + pLengths[uCount] = pLengthsArray[uIndex]; + } + else + { + // and for fat multipackets + pLengths[uCount * 2] = pLengthsArray[uIndex] / 256; + pLengths[uCount * 2 + 1] = pLengthsArray[uIndex] % 256; + } + } + } + + if (uCount % 2) + { + *pTypePos = (*pTypePos & 0x0f) | ((pTypesArray[uIndex] << 4) & 0xf0); + pTypePos++; + } + else + { + *pTypePos = (pTypesArray[uIndex] & 0x0f); + } + uCount++; + } + + if (!NetGameDistStatus(pRef, '?cws', uPos, NULL, 0)) + { + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] Exiting NetGameDistInputLocalMulti() with return value GMDIST_OVERFLOW.\n", pRef)); + #endif + return(GMDIST_OVERFLOW); + } + + #if DIRTYCODE_LOGGING + { + uint8_t uDataType = (pTypesArray[0] & ~GMDIST_DATA_CRC_REQUEST); + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] NetGameDistInputLocalMulti() called with tick=%d len=%d type=%s\n", + pRef, NetTick(), pLengthsArray[0], _strNetGameDistDataTypes[(((uDataType >= GMDIST_DATA_NONE) && (uDataType <= GMDIST_DATA_NODATA)) ? uDataType : 0)])); + + } + #endif + + // increment output packet count + if ((iResult = _NetGameDistSendInput(pRef, pRef->aMultiBuf, uPos, iLengthSize)) > 0) + { + pRef->iOutboundPktCnt++; + } + + // pass the size of length used to the send input function + return(iResult); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistInputCheck + + \Description + Check input status (see how long till next) + + \Input *pRef - reference pointer + \Input *pSend - (optional) stores time until next packet should be sent + \Input *pRecv - (optional) stores whether data is available + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameDistInputCheck(NetGameDistRefT *pRef, int32_t *pSend, int32_t *pRecv) +{ + int32_t iRemain; + uint32_t uTick; + int32_t iInData; + int32_t iOutData; + + // call the status proc + (pRef->pStatProc)(pRef->pNetGameLinkRef, 'stat', 0, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT)); + + // get current time + uTick = NetTick(); + + // make sure next send time is initialized + if (pRef->uInpNext == 0) + { + pRef->uInpNext = uTick; + } + + // see if its time to recalc network parms + if (uTick > pRef->uInpCalc) + { + // set the number of unacknowledged packets permitted + // (this number must be large enough to cover the network latency + pRef->iInputWind = (pRef->NetGameLinkStats.late+pRef->iInputRate)/pRef->iInputRate; + + if (pRef->bRecvMulti) + { + pRef->iInputWind *= 2; + } + + if (pRef->iInputWind < pRef->iInputMini) + { + pRef->iInputWind = pRef->iInputMini; + } + if (pRef->iInputWind > pRef->iInputMaxi) + { + pRef->iInputWind = pRef->iInputMaxi; + } + if (pRef->iInputWind > 500/pRef->iInputRate) + { + pRef->iInputWind = 500/pRef->iInputRate; + } + // determine time till next update + pRef->uInpCalc = uTick + pRef->iInputRate; + } + + // figure out time remaining until next send + iRemain = pRef->uInpNext - uTick; + + // clamp to valid rate + if (iRemain < 0) + { + iRemain = 0; + } + if (iRemain > pRef->iInputRate *2) + { + iRemain = pRef->iInputRate *2; + } + + _NetGameDistCheckWindow(pRef, iRemain, uTick); + + // figure out time till next send + iRemain = pRef->uInpNext - uTick; + // clamp to valid rate + if (iRemain < 0) + { + iRemain = 0; + } + + // return time until next packet should be sent + if (pSend != NULL) + { + // make sure rate is set + if (pRef->iInputRate == 0) + { + // data is not initialized -- just return a delay + *pSend = 50; + } + else if ((pRef->OutBufData.iBeg != pRef->OutBufData.iEnd) || (!pRef->bRdyRecvRemote)) + { + // dont send while something in output queue + *pSend = 10; + } + else + { + *pSend = iRemain; + } + } + + // indicate if a packet is waiting + if (pRecv != NULL) + { + // if waiting on remote data, do an update + if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd) + { + NetGameDistUpdate(pRef); + } + // get input data queue length + iInData = GMDIST_Modulo(pRef->InpBufData.iEnd - pRef->InpBufData.iBeg, PACKET_WINDOW); + // get output data queue length (in multi-mode we assume one so we don't gate receiving due to an empty output queue) + iOutData = !pRef->bRecvMulti ? GMDIST_Modulo(pRef->OutBufData.iEnd - GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW), PACKET_WINDOW) : 1; + // write if data is available (non-zero=available) + *pRecv = (iInData < iOutData) ? iInData : iOutData; + } + + #if DIRTYCODE_LOGGING + _NetGameDistInputCheckLog(pRef, pSend, pRecv); + #endif +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistInputRate + + \Description + Set the input rate + + \Input *pRef - reference pointer + \Input iRate - new input rate + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameDistInputRate(NetGameDistRefT *pRef, int32_t iRate) +{ + // save rate if changed + if (iRate > 0) + { + pRef->iInputRate = iRate; + } +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistInputClear + + \Description + Flush the input queue. + + \Input *pRef - reference pointer + + \Notes + This must be done with independent synchronization before and after to avoid issues. + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameDistInputClear(NetGameDistRefT *pRef) +{ + // reset buffer pointers + pRef->OutBufData.iEnd = 0; + pRef->OutBufData.iBeg = 0; + pRef->InpBufData.iEnd = 0; + pRef->InpBufData.iBeg = 0; + + // reset sequence numbers + pRef->uLocalSeq = 0; + pRef->uGlobalSeq = 0; + + // reset the iooffset between the two queues. + pRef->iIOOffset = 0; + + // reset the send time + pRef->uInpNext = NetTick(); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistControl + + \Description + Set NetGameDist operation parameters + + \Input *pRef - reference pointer + \Input iSelect - item to tweak + \Input iValue - tweak value + \Input pValue - pointer tweak value + + \Output + int32_t - selector specific + + \Notes + + Selectors are: + + \verbatim + SELECTOR DESCRIPTION + 'clri' clear the local queue of inputs + 'crcs' enable/disable the reception of CRC challenges + 'lcrc' to provide the current CRC as request by NetGameDist + 'lrcv' local recv flow control. Set whether we are ready to receive or not. + 'lsnd' local send flow control. Set whether we are ready to send or not. + 'maxi' max window size clamp + 'mini' min window size clamp + 'spam' sets debug output verbosity (0...n) + \endverbatim + + \Version 11/18/08 (jrainy) +*/ +/*************************************************************************************************F*/ +int32_t NetGameDistControl(NetGameDistRefT *pRef, int32_t iSelect, int32_t iValue, void *pValue) +{ + if (iSelect == 'clri') + { + NetGameDistInputClear(pRef); + return(0); + } + if (iSelect == 'crcs') + { + pRef->bCRCChallenges = iValue; + return(0); + } + if (iSelect == 'lcrc') + { + pRef->bUpdateFlowCtrl = TRUE; + pRef->uLocalCRC = iValue; + pRef->bLocalCRCValid = TRUE; + return(0); + } + if (iSelect == 'lrcv') + { + if (iValue != pRef->bRdyRecv) + { + pRef->bUpdateFlowCtrl = TRUE; + pRef->bRdyRecv = iValue; + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] flow control change -> %s to receive\n", pRef, (pRef->bRdyRecv ? "ready" : "not ready"))); + } + return(pRef->bRdyRecv); + } + if (iSelect == 'lsnd') + { + if (iValue != pRef->bRdySend) + { + pRef->bUpdateFlowCtrl = TRUE; + pRef->bRdySend = iValue; + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] flow control change -> %s to send\n", pRef, (pRef->bRdySend ? "ready" : "not ready"))); + } + return(pRef->bRdySend); + } + if (iSelect == 'maxi') + { + if (iValue >= 0) + { + pRef->iInputMaxi = iValue; + } + return(pRef->iInputMaxi); + } + if (iSelect == 'mini') + { + if (iValue >= 0) + { + pRef->iInputMini = iValue; + } + return(pRef->iInputMini); + } + if (iSelect == 'spam') + { + pRef->uVerbose = iValue; + return(0); + } + // no action + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistUpdate + + \Description + Perform periodic tasks. + + \Input *pRef - reference pointer + + \Output + uint32_t - current sequence number + + \Notes + Application must call this every 100ms or so. + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +uint32_t NetGameDistUpdate(NetGameDistRefT *pRef) +{ + NetGamePacketT* pPacket = (NetGamePacketT*)&pRef->MaxPkt; + unsigned char *pData; + int32_t iNext, iCurrent; + uint32_t uIndex, uPos; + unsigned char *pExisting; + unsigned char *pIncoming; + + // update the input rate if needed + NetGameDistInputRate(pRef, 0); + + // send any pending game packets + NetGameDistInputLocal(pRef, NULL, 0); + + // grab incoming packets + while (( GMDIST_Modulo(pRef->InpBufData.iEnd + 1, PACKET_WINDOW) != pRef->InpBufData.iBeg) && (_NetGameDistInputPeekRecv(pRef, pPacket, 1) > 0)) + { + // dispatch the packet according to type + if ((pPacket->head.kind == GAME_PACKET_INPUT) || + (pPacket->head.kind == GAME_PACKET_INPUT_MULTI) || + (pPacket->head.kind == GAME_PACKET_INPUT_MULTI_FAT)) + { + pRef->iInboundPktCnt++; + + iNext = GMDIST_Modulo(pRef->InpBufData.iEnd + 1, PACKET_WINDOW); + if (pRef->InpBufData.iBeg != pRef->InpBufData.iEnd && !pRef->bRecvMulti && pRef->bActAsServer && pRef->pDropProc) + { + iCurrent = GMDIST_Modulo(pRef->InpBufData.iEnd - 1, PACKET_WINDOW); + pExisting = (pRef->InpBufData.pControllerIO + pRef->InpBufData.IOLookUp[iCurrent].uPos); + pIncoming = pPacket->body.data; + + if (pRef->pDropProc(pRef, pExisting + 1, pIncoming + 1, pExisting[0], pIncoming[0])) + { + iNext = pRef->InpBufData.iEnd; + pRef->iInboundDropPktCnt++; + pRef->InpBufData.iEnd = GMDIST_Modulo(pRef->InpBufData.iEnd - 1, PACKET_WINDOW); + } + } + + _ProcessPacketType(pRef, pPacket->head.kind); + // check for overflow + if (iNext == pRef->InpBufData.iBeg) + { + // ignore the packet + NetPrintf(("netgamedist: [%p] NetGameDistUpdate() - buffer overflow (queue full)!\n", pRef)); + ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "NetGameDistUpdate, buffer overflow (queue full)."); + + pRef->iErrCond = GMDIST_QUEUE_FULL; + return((unsigned)(-1)); + } + else + { + // point to data buffer + + // we currently write one byte past the buffer after reserving one extra byte + // the len set in the data structure includes this extra byte past the packet data + if (_SetDataPtr(pRef, &pRef->InpBufData, pPacket->head.len + 1)) + { + pData = pRef->InpBufData.pControllerIO+pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].uPos; + pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].uLen = pPacket->head.len + 1; + // copy into buffer + ds_memcpy(pData, pPacket->body.data, pPacket->head.len); + pData[pPacket->head.len] = pPacket->head.kind; + + pRef->aPacketId[pRef->InpBufData.iEnd] = pRef->iInboundPktCnt; + + // update queue time + pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].uInsertTime = pPacket->head.when; + + // add item to buffer + pRef->InpBufData.iEnd = iNext; + + // display number of packets in buffer + #if TIMING_DEBUG + NetPrintf(("netgamedist: [%p] NetGameDistUpdate() inpbuf=%d packets\n", pRef, + GMDIST_Modulo(pRef->InpBufData.iEnd - pRef->InpBufData.iBeg, PACKET_WINDOW))); + #endif + } + else + { + NetPrintf(("netgamedist: [%p] NetGameDistUpdate() - buffer overflow (memory)!\n", pRef)); + pRef->iErrCond = GMDIST_QUEUE_MEMORY; + return((unsigned)(-1)); + } + } + } + else if (pPacket->head.kind == GAME_PACKET_STATS) + { + // clear the contents, if a player leaves the game we don't want their stats to be misrepresented + ds_memclr(pRef->aRecvStats, sizeof(pRef->aRecvStats)); + + for (uPos = 0, uIndex = 0; uPos < pPacket->head.len; uIndex += 1) + { + ds_memcpy(&pRef->aRecvStats[uIndex], pPacket->body.data + uPos, sizeof(*pRef->aRecvStats) ); + uPos += sizeof(*pRef->aRecvStats); + + pRef->aRecvStats[uIndex].late = SocketNtohs(pRef->aRecvStats[uIndex].late); + pRef->aRecvStats[uIndex].bps = SocketNtohs(pRef->aRecvStats[uIndex].bps); + } + } + else if (pPacket->head.kind == GAME_PACKET_INPUT_FLOW) + { + pRef->bRdySendRemote = pPacket->body.data[0]; + pRef->bRdyRecvRemote = pPacket->body.data[1]; + + if (pPacket->head.len >= 7) + { + if (pRef->MaxPkt.body.data[2]) + { + pRef->bRemoteCRCValid = TRUE; + pRef->uRemoteCRC = (pRef->MaxPkt.body.data[6] | + (pRef->MaxPkt.body.data[5] << 8) | + (pRef->MaxPkt.body.data[4] << 16) | + (pRef->MaxPkt.body.data[3] << 24)); + } + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] bRemoteCRCValid is %d, uRemoteCRC is %d\n", pRef, pRef->bRemoteCRCValid, pRef->uRemoteCRC)); + #endif + } + + NetPrintf(("netgamedist: [%p] Got GAME_PACKET_INPUT_FLOW (send %d recv %d)\n", pRef, pRef->bRdySendRemote, pRef->bRdyRecvRemote)); + } + else if (pPacket->head.kind == GAME_PACKET_INPUT_META) + { + // process meta-information about sparse multi-packets + uint8_t uVer; + uint32_t uMask; + + pRef->bGotMetaInfo = TRUE; + + uVer = GMDIST_Modulo(pPacket->body.data[0], GMDIST_META_ARRAY_SIZE); + uMask = ((pPacket->body.data[1] << 24) + + (pPacket->body.data[2] << 16) + + (pPacket->body.data[3] << 8) + + (pPacket->body.data[4])); + + NetPrintf(("netgamedist: Got GAME_PACKET_INPUT_META (version %d mask 0x%08x)\n", uVer, uMask)); + + pRef->aMetaInfo[uVer].uMask = uMask; + pRef->aMetaInfo[uVer].uPlayerCount = _NetGameDistCountBits(pRef, uMask); + pRef->aMetaInfo[uVer].uVer = uVer; + } + } + + // update link status + NetGameDistStatus(pRef, 'stat', 0, NULL, 0); + + // show the history + #if PING_DEBUG + _PingHistory(&pRef->NetGameLinkStats); + #endif + + // send meta-information as needed + if (pRef->uUpdateMetaInfoBeg != pRef->uUpdateMetaInfoEnd) + { + _NetGameDistSendMetaInfo(pRef); + } + + // send flow control updates as needed + if (pRef->bUpdateFlowCtrl) + { + _NetGameDistSendFlowUpdate(pRef); + } + + // return current sequence number + return(pRef->uGlobalSeq); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistSetProc + + \Description + Sets or override the various procedure pointers. + + \Input *pRef - reference pointer + \Input iKind - kind + \Input *pProc - proc + + \Notes + 'drop' should only be used on game servers + + \Version 02/27/07 (jrainy) +*/ +/*************************************************************************************************F*/ +void NetGameDistSetProc(NetGameDistRefT *pRef, int32_t iKind, void *pProc) +{ + switch(iKind) + { + case 'drop': + pRef->pDropProc = (NetGameDistDropProc *)pProc; + break; + case 'peek': + pRef->pPeekProc = (NetGameDistPeekProc *)pProc; + break; + case 'recv': + pRef->pRecvProc = (NetGameDistRecvProc *)pProc; + break; + case 'send': + pRef->pSendProc = (NetGameDistSendProc *)pProc; + break; + case 'stat': + pRef->pStatProc = (NetGameDistStatProc *)pProc; + break; + case 'link': + pRef->pLinkCtrlProc = (NetGameDistLinkCtrlProc *)pProc; + break; + } +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistSendStats + + \Description + Send stats. Meant to be used by the game server to send stats regarding all clients + + \Input *pRef - reference pointer + \Input *pStats - uTotalPlyrs-sized array of stats + + \Version 03/14/07 (jrainy) +*/ +/*************************************************************************************************F*/ +void NetGameDistSendStats(NetGameDistRefT *pRef, NetGameDistStatT *pStats) +{ + uint32_t uIndex; + int32_t iPos; + + pRef->MaxPkt.head.kind = GAME_PACKET_STATS; + pRef->MaxPkt.head.len = (uint16_t)sizeof(NetGameDistStatT) * pRef->uTotalPlyrs; + + iPos = 0; + for (uIndex = 0; uIndexuTotalPlyrs; uIndex++) + { + ds_memcpy(pRef->MaxPkt.body.data + iPos, &pStats[uIndex], sizeof(NetGameDistStatT)); + iPos += sizeof(NetGameDistStatT); + } + + pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistGetError + + \Description + return non-zero if there ever was an overflow + + \Input *pRef - reference pointer + + \Output + int32_t - 0 if there was no error, positive >0 otherwise. + + \Version 03/23/07 (jrainy) +*/ +/*************************************************************************************************F*/ +int32_t NetGameDistGetError(NetGameDistRefT *pRef) +{ + return(pRef->iErrCond); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistGetErrorText + + \Description + copies into the passed buffer the last error text. + + \Input *pRef - reference pointer + \Input *pErrorBuffer - buffer to write into + \Input iBufSize - buf size + + \Version 08/15/08 (jrainy) +*/ +/*************************************************************************************************F*/ +void NetGameDistGetErrorText(NetGameDistRefT *pRef, char *pErrorBuffer, int32_t iBufSize) +{ + int32_t iMinSize = sizeof(pRef->strErrorText); + + if (iBufSize < iMinSize) + { + iMinSize = iBufSize; + } + + strncpy(pErrorBuffer, pRef->strErrorText, iMinSize); + pErrorBuffer[iMinSize - 1] = 0; +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameDistResetError + + \Description + reset the overflow error condition to 0. + + \Input *pRef - reference pointer + + \Version 03/23/07 (jrainy) +*/ +/*************************************************************************************************F*/ +void NetGameDistResetError(NetGameDistRefT *pRef) +{ + pRef->iErrCond = 0; +} + + diff --git a/src/thirdparty/dirtysdk/source/game/netgamedistserv.c b/src/thirdparty/dirtysdk/source/game/netgamedistserv.c new file mode 100644 index 00000000..86e56fa2 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/game/netgamedistserv.c @@ -0,0 +1,1651 @@ +/*H********************************************************************************/ +/*! + \File netgamedistserv.c + + \Description + Server module to handle 2+ NetGameDist connections in a client/server + architecture. + + \Copyright + Copyright (c) 2007 Electronic Arts Inc. + + \Version 02/01/2007 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/game/netgamepkt.h" +#include "DirtySDK/game/netgamedistserv.h" + +/*** Defines **********************************************************************/ + +#define MONITOR_HIGHWATER (0) +#define DISTSERV_MAX_MULTIPACKET_LENGTH (1100) +#define DISTSERV_MAX_FIXEDRATE_MULTIPLE (3) +#define DISTSERV_DEFAULT_FIXEDRATE (33) + +/*** Type Definitions *************************************************************/ + +//! client state +typedef struct NetGameDistServClientT +{ + NetGameDistRefT *pGameDist; //!< client dist ref + char strName[32]; //!< client name + uint8_t aLocalInput[NETGAME_DATAPKT_MAXSIZE]; //!< local input buffer (TODO - this will be a queue) + int32_t iLocalInputSize; //!< size of input data + int32_t iPeekKey; //!< delta at the time of our peek. + uint8_t bLocalInput; //!< whether we have local input or not + uint8_t iLocalInputType; //!< type of input + uint8_t bInitialized; //!< true if client is initialized + uint8_t bDisconnected; //!< whether the client is disconnected or not + int32_t iDisconnectReason; //!< why the disconnect + int32_t iCount; //!< total number of packets received + int32_t iCRC; //!< latest client-side CRC received + uint8_t uCRCValid; //!< whether we got a CRC response to the last request + uint8_t bRemoteRcv; //!< last 'rrcv' value (is client ready to recv) + uint8_t bRemoteSnd; //!< last 'rsnd' value (is client ready to send) + uint8_t bReallyPeeked; //!< indicate whether we peeked this client before InputLocalMulti this frame (to address delayed input scenarios) + uint32_t uNakSent; //!< previous nak sent stat + uint32_t uPackLost; //!< previous packet loss stat + char strDiscReason[GMDIST_ERROR_SIZE]; +} NetGameDistServClientT; + +//! client list +typedef struct NetGameDistServClientListT +{ + int32_t iNumClients; //!< current number of clients in list + int32_t iMaxClients; //!< max number of clients in list + NetGameDistServClientT Clients[1]; //!< variable-length array of clients +} NetGameDistServClientListT; + +//! module state +struct NetGameDistServT +{ + //! module memory group + int32_t iMemGroup; + void *pMemGroupUserData; + + //! verbose debug output level + int32_t iVerbosity; + + //! Last stats update time + uint32_t uLastStatsUpdate; + + // debugging + uint32_t aPeekTimes[32]; + uint32_t aRecvTimes[32]; + uint32_t aSendTimes[32]; + uint32_t aEscalate[32]; + int32_t iFixedRate; + + uint32_t uNbFramesNoData; + + uint32_t uCRCRate; //!< number of frames between challenges + uint32_t uCRCResponseLimit; //!< number of frames allowed to receive response + uint32_t uCRCRemainingFrames; //!< number of frames remaining until next challenge + uint32_t uCRCResponseCountdown; //!< number of frames remaining to accept CRC responses + + uint8_t uSendThreshold; + + #if MONITOR_HIGHWATER + int32_t iHighWaterInputQueue; + int32_t iHighWaterOutputQueue; + uint8_t uHighWaterChanged; + #endif + + int32_t iFirstClient; + uint32_t uLastFlowUpdateTime; + uint32_t uOutputMultiPacketCount; //!< total output dist multi packet + uint32_t uCurStatClientCount; //!< current stat client count + uint8_t bClientCountChanged; //!< did the client count change between stat sampling interval? + uint8_t bFlowEnabled; //!< whether all clients are ready to receive. + uint8_t bFlowEnabledChanged; //!< did flow control change between stat sampling interval? + uint8_t bNoInputMode; //!< TRUE means: outbound multipackets are no longer generated because inbound packets have not been coming in for multiple frames + uint8_t bNoInputModeChanged; //!< did no input mode change between stat sampling interval? + uint8_t uMultiPacketVersion; //!< sparse multi-packet version number. Changes when player list changes + uint8_t _pad[2]; + + NetGameDistServLoggingCbT *pLoggingCb; //!< logging callback + void *pUserData; //!< logging userdata + + //! variable-length array of clients -- must come last! + NetGameDistServClientListT ClientList; +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _NetGameDistServLogPrintf + + \Description + Log printing for netgamedistserv module + + \Input *pDistServ - module state + \Input *pFormat - printf format string + \Input ... - variable argument list + + \Version 06/26/2019 (eesponda) +*/ +/********************************************************************************F*/ +static void _NetGameDistServLogPrintf(NetGameDistServT *pDistServ, const char *pFormat, ...) +{ + char strText[2048]; + int32_t iOffset = 0; + va_list Args; + + // format prefix + iOffset += ds_snzprintf(strText+iOffset, sizeof(strText)-iOffset, "netgamedistserv: "); + + // format output + va_start(Args, pFormat); + iOffset += ds_vsnprintf(strText+iOffset, sizeof(strText)-iOffset, pFormat, Args); + va_end(Args); + + // forward to callback if registered + if ((pDistServ != NULL) && (pDistServ->pLoggingCb != NULL)) + { + pDistServ->pLoggingCb(strText, pDistServ->pUserData); + } + else + { + NetPrintf(("%s", strText)); + } +} + +/*F********************************************************************************/ +/*! + \Function _NetGameDistServLogPrintfVerbose + + \Description + Log printing for netgamedistserv module at varying verbosity levels + + \Input *pDistServ - module state + \Input iCheckLevel - the level we are checking our internal iVerbosity against + \Input *pFormat - printf format string + \Input ... - variable argument list + + \Version 06/26/2019 (eesponda) +*/ +/********************************************************************************F*/ +static void _NetGameDistServLogPrintfVerbose(NetGameDistServT *pDistServ, int32_t iCheckLevel, const char *pFormat, ...) +{ + char strText[2048]; + va_list Args; + + // no-op + if (pDistServ->iVerbosity <= iCheckLevel) + { + return; + } + + // format output + va_start(Args, pFormat); + ds_vsnprintf(strText, sizeof(strText), pFormat, Args); + va_end(Args); + + // format to the logging function + _NetGameDistServLogPrintf(pDistServ, "%s", strText); +} + +/*F********************************************************************************/ +/*! + \Function _NetGameDistServSanityCheckIn + + \Description + Perform sanity checking on input. + + \Input *pDistServ - module state + \Input iClient - client to check + \Input iRet - result from NetGameDistInputPeek() + + \Version 01/01/2007 (jrainy) +*/ +/********************************************************************************F*/ +static void _NetGameDistServSanityCheckIn(NetGameDistServT *pDistServ, int32_t iClient, int32_t iRet) +{ + int32_t iDelay; + uint32_t uCurTick; + + uCurTick = NetTick(); + + // no need to perform sanity checking on input when flow is disabled + if (pDistServ->bFlowEnabled) + { + iDelay = uCurTick - pDistServ->aRecvTimes[iClient]; + if (iRet > 0) + { + pDistServ->aRecvTimes[iClient] = uCurTick; + + if (iDelay < 200) + { + pDistServ->aEscalate[iClient] = 0; + } + } + if ((iDelay > 200) && pDistServ->aRecvTimes[iClient] && (pDistServ->aEscalate[iClient] < 1)) + { + _NetGameDistServLogPrintfVerbose(pDistServ, 0, "no input in 200 ms from client %d\n", iClient); + pDistServ->aEscalate[iClient] = 1; + } + else if ((iDelay > 500) && pDistServ->aRecvTimes[iClient] && (pDistServ->aEscalate[iClient] < 2)) + { + _NetGameDistServLogPrintfVerbose(pDistServ, 0, "no input in 500 ms from client %d\n", iClient); + pDistServ->aEscalate[iClient] = 2; + } + else if ((iDelay > 1000) && pDistServ->aRecvTimes[iClient]) + { + _NetGameDistServLogPrintfVerbose(pDistServ, 0, "no input in 1000 ms from client %d\n", iClient); + pDistServ->aRecvTimes[iClient] = uCurTick; + pDistServ->aEscalate[iClient] = 0; + } + + iDelay = uCurTick - pDistServ->aPeekTimes[iClient]; + pDistServ->aPeekTimes[iClient] = uCurTick; + if (iDelay > (pDistServ->iFixedRate * DISTSERV_MAX_FIXEDRATE_MULTIPLE)) + { + _NetGameDistServLogPrintfVerbose(pDistServ, 0, "no peek in %d ms from client %d\n", iDelay, iClient); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _NetGameDistServSanityCheckOut + + \Description + Perform sanity checking on output. + + \Input *pDistServ - module state + \Input iClient - client to check + \Input iRet - result from NetGameDistInputLocalMulti() + + \Version 01/01/2007 (jrainy) +*/ +/********************************************************************************F*/ +static void _NetGameDistServSanityCheckOut(NetGameDistServT *pDistServ, int32_t iClient, int32_t iRet) +{ + int32_t iDelay; + uint32_t uCurTick; + + uCurTick = NetTick(); + + iDelay = uCurTick - pDistServ->aSendTimes[iClient]; + if (iRet == 1) + { + pDistServ->aSendTimes[iClient] = uCurTick; + } + if ((iDelay > (pDistServ->iFixedRate * DISTSERV_MAX_FIXEDRATE_MULTIPLE)) && pDistServ->aSendTimes[iClient]) + { + _NetGameDistServLogPrintfVerbose(pDistServ, 0, "no send in %d ms to client %d\n", iDelay, iClient); + pDistServ->aSendTimes[iClient] = uCurTick; + } +} + +/*F********************************************************************************/ +/*! + \Function _NetGameDistServFlowControl + + \Description + Send flow control update from the server as appropriate + + \Input *pDistServ - module state + + \Version 12/03/2007 (jrainy) +*/ +/********************************************************************************F*/ +static void _NetGameDistServFlowControl(NetGameDistServT *pDistServ) +{ + int32_t iClient; + NetGameDistServClientT *pDistClient; + uint8_t bPrevFlowEnabled; //!< previous reported stat flow enabled boolean + + bPrevFlowEnabled = pDistServ->bFlowEnabled; + pDistServ->bFlowEnabled = TRUE; + + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + + if (pDistClient->bInitialized && !pDistClient->bDisconnected) + { + if (!pDistClient->bRemoteRcv) + { + pDistServ->bFlowEnabled = FALSE; + } + } + } + + _NetGameDistServLogPrintf(pDistServ, "sending flow update lrcv %d\n", pDistServ->bFlowEnabled); + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + + if (pDistClient->bInitialized && !pDistClient->bDisconnected) + { + NetGameDistControl(pDistClient->pGameDist, 'lrcv', pDistServ->bFlowEnabled, NULL); + } + } + + /* If flow control changed, flag the boolean used to remember that it happened at least once + during the metrics sampling interval. */ + if (bPrevFlowEnabled != pDistServ->bFlowEnabled) + { + if (pDistServ->bFlowEnabledChanged == FALSE) + { + _NetGameDistServLogPrintfVerbose(pDistServ, 1, "metric sampling interval marked invalid for idpps and inputdrop because flow control changed (flow enabled = %d)\n", pDistServ->bFlowEnabled); + } + pDistServ->bFlowEnabledChanged = TRUE; + } +} + +/*F********************************************************************************/ +/*! + \Function _NetGameDistServDiscClient + + \Description + Destroy game dist refs. + + \Input *pDistServ - module state + \Input *pDistClient - client to disconnect from + \Input iDistClient - index of client + \Input iReason - disconnection reason + + \Version 04/26/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static void _NetGameDistServDiscClient(NetGameDistServT *pDistServ, NetGameDistServClientT *pDistClient, int32_t iDistClient, int32_t iReason) +{ + // mark as disconnected + if (!pDistClient->bDisconnected) + { + _NetGameDistServLogPrintf(pDistServ, "disconnecting from client %d\n", iDistClient); + pDistClient->bDisconnected = TRUE; + pDistClient->iDisconnectReason = iReason; + } + else if (pDistClient->pGameDist != NULL) + { + _NetGameDistServLogPrintf(pDistServ, "warning -- client %d has gamedist but is disconnected\n", iDistClient); + } + + // destroy dist + if (pDistClient->pGameDist != NULL) + { + NetGameDistGetErrorText(pDistClient->pGameDist, pDistClient->strDiscReason, sizeof(pDistClient->strDiscReason)); + + _NetGameDistServLogPrintfVerbose(pDistServ, 0, "deleting dist ref for client %s/%d\n", pDistClient->strName, iDistClient); + NetGameDistDestroy(pDistClient->pGameDist); + pDistClient->pGameDist = NULL; + } + + _NetGameDistServFlowControl(pDistServ); +} + +/*F********************************************************************************/ +/*! + \Function _NetGameDistUpdateMulti + + \Description + Update Multi configuration when a client is added or removed + + \Input *pDistServ - module state + + \Version 02/13/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static void _NetGameDistUpdateMulti(NetGameDistServT *pDistServ) +{ + NetGameDistServClientT *pDistClient; + int32_t iClient; + int32_t iLastClient = 0; + uint32_t uMask = 0; + + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + if (pDistClient->bInitialized) + { + iLastClient = iClient; + + // set the bits in the mask for all initialized players + uMask |= (1 << iClient); + } + } + + // increment the multipacket version + pDistServ->uMultiPacketVersion++; + + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + if (pDistClient->pGameDist != NULL) + { + _NetGameDistServLogPrintf(pDistServ, "issuing NetGameDistMultiSetup %d %d\n", iClient, iLastClient + 1); + NetGameDistMultiSetup(pDistClient->pGameDist, iClient, iLastClient + 1); + + _NetGameDistServLogPrintf(pDistServ, "issuing NetGameDistMetaSetup 1 0x%0x %d\n", uMask, pDistServ->uMultiPacketVersion); + NetGameDistMetaSetup(pDistClient->pGameDist, 1, uMask, pDistServ->uMultiPacketVersion); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _NetGameDistServDrop + + \Description + Default implementation of drop function. + + \Input *pLinkRef - module state + \Input *pExisting - input packet at end of the input queue + \Input *pIncoming - newly received input packet + \Input uTypeExisting - type of the packet in the queue + \Input uTypeIncoming - type of the new packet + + \Output + int8_t - TRUE if the input packet can be dropped, FALSE otherwise + + \Version 02/27/2007 (jrainy) +*/ +/********************************************************************************F*/ +static int8_t _NetGameDistServDrop(void *pLinkRef, void *pExisting, void *pIncoming, uint8_t uTypeExisting, uint8_t uTypeIncoming) +{ + return(uTypeExisting == GMDIST_DATA_INPUT_DROPPABLE); +} + +/*F********************************************************************************/ +/*! + \Function _NetGameDistServHandleCRCResponses + + \Description + Handle the CRC responses. Check the current response status of all clients + + \Input *pDistServ - module state + + \Notes + Can only compare 64 different CRCs. If we have over 64 clients, the function + will still function correctly, as long as there is more than 64 different + CRCs being returned by the clients (highly unlikely). If there's too many + different CRCs received, we'll pick the one with majority from the 64 first + clients. + + \Version 03/23/2009 (jrainy) +*/ +/********************************************************************************F*/ +static void _NetGameDistServHandleCRCResponses(NetGameDistServT *pDistServ) +{ + NetGameDistServClientT *pDistClient; + int32_t iClient,iIndex; + uint8_t uWaiting = FALSE; + uint8_t uTied = FALSE; + int32_t iCRCs[64] = {0}; + int32_t iOccurences[64] = {0}; + int32_t iBestCRC = 0; + int32_t iBestOccurences = 0; + + // for all the current clients + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + + // skip uninitialized/disconnected clients + if (!pDistClient->bInitialized || pDistClient->bDisconnected) + { + continue; + } + + // mark us waiting if this client didn't send its CRC yet + if (!pDistClient->uCRCValid) + { + uWaiting = TRUE; + } + else + { + // For all the CRC seen already + for(iIndex = 0; iIndex < 64; iIndex++) + { + // count the repeats + if (pDistClient->iCRC == iCRCs[iIndex]) + { + iOccurences[iIndex]++; + + if (iOccurences[iIndex] > iBestOccurences) + { + iBestOccurences = iOccurences[iIndex]; + iBestCRC = iCRCs[iIndex]; + } + + break; + } + else if (iOccurences[iIndex] == 0) + { + // remember the newly seen CRCs + iCRCs[iIndex] = pDistClient->iCRC; + iOccurences[iIndex] = 1; + + if (iOccurences[iIndex] > iBestOccurences) + { + iBestOccurences = iOccurences[iIndex]; + iBestCRC = iCRCs[iIndex]; + } + + break; + } + } + } + } + + // if the wait is over, (received all responses, or number of frames) + if (!uWaiting || (pDistServ->uCRCResponseCountdown == 0)) + { + for(iIndex = 0; iIndex < 64; iIndex++) + { + if ((iOccurences[iIndex] == iBestOccurences) && (iCRCs[iIndex] != iBestCRC)) + { + uTied = TRUE; + } + } + + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + + // skip disconnected clients + if (!pDistClient->bInitialized || pDistClient->bDisconnected) + { + continue; + } + + if (!pDistClient->uCRCValid || (pDistClient->iCRC != iBestCRC) || uTied) + { + _NetGameDistServLogPrintf(pDistServ, "client %d failed CRC challenge: %d %d %d %d\n", iClient, pDistClient->uCRCValid, pDistClient->iCRC, iBestCRC, uTied); + + if (uTied) + { + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_DESYNCED_ALL_PLAYERS); + } + else + { + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_DESYNCED); + } + } + else + { + _NetGameDistServLogPrintfVerbose(pDistServ, 2, "client %d passed CRC challenge: %d\n", iClient, pDistClient->iCRC); + } + + pDistClient->uCRCValid = FALSE; + pDistClient->iCRC = 0; + } + + // go back to challenge mode + pDistServ->uCRCResponseCountdown = 0; + pDistServ->uCRCRemainingFrames = pDistServ->uCRCRate; + } +} + +/*F********************************************************************************/ +/*! + \Function _NetGameDistServPrepareInputs + + \Description + Prepare the inputs for sending a multipacket + + \Input *pDistServ - module state + \Input **pInputs - array of pointers to set to data buffers + \Input *pInputSizes - array of sizes to set + \Input *pInputTypes - array of types to set + \Input *pInputUsed - array of booleans, indicates whether a given player's input was used + \Input *pInputDropped - array of booleans, indicates whether a given player's input was dropped + + \Output + uint8_t - indicates whether any data was available and prepared + + \Version 09/22/2009 (jrainy) +*/ +/********************************************************************************F*/ +static uint8_t _NetGameDistServPrepareInputs(NetGameDistServT *pDistServ, void** pInputs, int32_t* pInputSizes, uint8_t* pInputTypes, uint8_t* pInputUsed, uint8_t* pInputDropped) +{ + int32_t iClient, iIndex; + int32_t iRunningLength, iRunningLengthDroppable; + int32_t iAllowed, iAllowedDroppable; + uint8_t bDataAvailable; + + NetGameDistServClientT *pDistClient; + + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pInputSizes[iClient] = 0; + pInputTypes[iClient] = GMDIST_DATA_NONE; + pInputUsed[iClient] = FALSE; + } + + iRunningLength = 0; + iRunningLengthDroppable = 0; + + for (iIndex = 0, bDataAvailable = FALSE; iIndex < pDistServ->ClientList.iMaxClients; iIndex++) + { + iClient = (iIndex + pDistServ->iFirstClient) % pDistServ->ClientList.iMaxClients; + pDistClient = &pDistServ->ClientList.Clients[iClient]; + + if (pDistClient->bLocalInput) + { + if (pDistClient->iLocalInputType == GMDIST_DATA_INPUT_DROPPABLE) + { + iRunningLengthDroppable += pDistClient->iLocalInputSize; + } + else + { + if (iRunningLength + pDistClient->iLocalInputSize < DISTSERV_MAX_MULTIPACKET_LENGTH) + { + iRunningLength += pDistClient->iLocalInputSize; + } + } + } + } + + iAllowed = iRunningLength; + iAllowedDroppable = DISTSERV_MAX_MULTIPACKET_LENGTH - iAllowed; + iRunningLength = 0; + iRunningLengthDroppable = 0; + + for (iIndex = 0, bDataAvailable = FALSE; iIndex < pDistServ->ClientList.iMaxClients; iIndex++) + { + iClient = (iIndex + pDistServ->iFirstClient) % pDistServ->ClientList.iMaxClients; + pDistClient = &pDistServ->ClientList.Clients[iClient]; + pInputDropped[iClient] = FALSE; + + pInputs[iClient] = pDistClient->aLocalInput; + if (!pDistClient->bInitialized || pDistClient->bDisconnected) + { + pInputSizes[iClient] = 0; + pInputTypes[iClient] = GMDIST_DATA_DISCONNECT; + } + else if (pDistClient->bLocalInput) + { + if (pDistClient->iLocalInputType == GMDIST_DATA_INPUT_DROPPABLE) + { + iRunningLengthDroppable += pDistClient->iLocalInputSize; + if (iRunningLengthDroppable > iAllowedDroppable) + { + pInputDropped[iClient] = TRUE; + + _NetGameDistServLogPrintfVerbose(pDistServ, 1, "dropping packet for iClient %d, iIndex %d\n", iClient, iIndex); + continue; + } + } + else + { + iRunningLength += pDistClient->iLocalInputSize; + if (iRunningLength > iAllowed) + { + _NetGameDistServLogPrintfVerbose(pDistServ, 1, "delaying packet for iClient %d, iIndex %d\n", iClient, iIndex); + continue; + } + } + + pInputSizes[iClient] = pDistClient->iLocalInputSize; + pInputTypes[iClient] = pDistClient->iLocalInputType; + bDataAvailable = TRUE; + } + + pInputUsed[iClient] = TRUE; + + pDistClient->iCount += pDistClient->iPeekKey; + } + + return(bDataAvailable); +} + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function NetGameDistServCreate + + \Description + Create the NetGameDistServ module. + + \Input iMaxClients - maximum number of clients supported + \Input iVerbosity - debug verbose level + + \Output + NetGameDistServT * - module state, or NULL if create failed + + \Version 02/01/2007 (jbrookes) +*/ +/********************************************************************************F*/ +NetGameDistServT *NetGameDistServCreate(int32_t iMaxClients, int32_t iVerbosity) +{ + NetGameDistServT *pDistServ; + int32_t iMemSize; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // calculate memory size based on number of clients + iMemSize = sizeof(*pDistServ) + (sizeof(pDistServ->ClientList.Clients[0]) * (iMaxClients - 1)); + + // allocate and init module state + if ((pDistServ = DirtyMemAlloc(iMemSize, NETGAMEDISTSERV_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + _NetGameDistServLogPrintf(NULL, "could not allocate module state\n"); + return(NULL); + } + ds_memclr(pDistServ, iMemSize); + pDistServ->iMemGroup = iMemGroup; + pDistServ->pMemGroupUserData = pMemGroupUserData; + + // init other module state + pDistServ->ClientList.iMaxClients = iMaxClients; + pDistServ->iVerbosity = iVerbosity; + pDistServ->uSendThreshold = 4; + pDistServ->uLastFlowUpdateTime = NetTick(); + pDistServ->uCRCResponseCountdown = 0; + pDistServ->uCRCRemainingFrames = pDistServ->uCRCRate; + pDistServ->iFixedRate = DISTSERV_DEFAULT_FIXEDRATE; + + // return module ref to caller + return(pDistServ); +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServDestroy + + \Description + Destroy the NetGameDistServ module. + + \Input *pDistServ - module state + + \Version 02/01/2007 (jbrookes) +*/ +/********************************************************************************F*/ +void NetGameDistServDestroy(NetGameDistServT *pDistServ) +{ + DirtyMemFree(pDistServ, NETGAMEDISTSERV_MEMID, pDistServ->iMemGroup, pDistServ->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServAddClient + + \Description + Add a client to the client list + + \Input *pDistServ - module state + \Input iClient - index of slot to add client to + \Input *pLinkRef - link ref to create dist with + \Input *pClientName - name of client + + \Output + int32_t - negative=error, else success + + \Version 02/05/2007 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetGameDistServAddClient(NetGameDistServT *pDistServ, int32_t iClient, NetGameLinkRefT *pLinkRef, const char *pClientName) +{ + // ref given client index + NetGameDistServClientT *pDistClient = &pDistServ->ClientList.Clients[iClient]; + + // make sure this slot isn't already taken + if (pDistClient->bInitialized && !pDistClient->bDisconnected) + { + _NetGameDistServLogPrintf(pDistServ, "skipping add of client to slot %d when slot is not empty\n", iClient); + return(-1); + } + + // save info and mark as initialized + ds_memclr(pDistClient, sizeof(*pDistClient)); + ds_strnzcpy(pDistClient->strName, pClientName, sizeof(pDistClient->strName)); + pDistClient->bInitialized = TRUE; + + // create dist ref for client + _NetGameDistServLogPrintfVerbose(pDistServ, 0, "creating dist ref for client %s\n", pDistClient->strName); + DirtyMemGroupEnter(pDistServ->iMemGroup, pDistServ->pMemGroupUserData); + pDistClient->pGameDist = NetGameDistCreate(pLinkRef, + (NetGameDistStatProc *)NetGameLinkStatus, + (NetGameDistSendProc *)NetGameLinkSend, + (NetGameDistRecvProc *)NetGameLinkRecv, + GMDIST_DEFAULT_BUFFERSIZE_IN * 20, + GMDIST_DEFAULT_BUFFERSIZE_OUT * 20); + DirtyMemGroupLeave(); + + NetGameDistSetServer(pDistClient->pGameDist, TRUE); + NetGameDistSetProc(pDistClient->pGameDist, 'drop', (void *)_NetGameDistServDrop); + + // update client count + pDistServ->ClientList.iNumClients++; + + // this call is required for dirtycast to announce "no longer receiving" upon join-in-progress + // DirtyCast will reenter the "receiving" state when the joiner explicitly uses NetGameDistControl('lrcv') + _NetGameDistServFlowControl(pDistServ); + + // update dist multi configuration + _NetGameDistUpdateMulti(pDistServ); + + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServDelClient + + \Description + Delete a client from the client list + + \Input *pDistServ - module state + \Input iClient - index of slot to delete client from + + \Output + int32_t - negative=error, else success + + \Version 02/05/2007 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetGameDistServDelClient(NetGameDistServT *pDistServ, int32_t iClient) +{ + // ref given client index + NetGameDistServClientT *pDistClient; + + // make sure this is a valid index + if (iClient >= pDistServ->ClientList.iMaxClients) + { + _NetGameDistServLogPrintf(pDistServ, "skipping delete of client %d not in dist list\n", iClient); + return(-1); + } + + // ref client + pDistClient = &pDistServ->ClientList.Clients[iClient]; + + // disconnect from client + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_DELETED); + + // clear slot in client list + ds_memclr(pDistClient, sizeof(*pDistClient)); + + // update client count + pDistServ->ClientList.iNumClients--; + + // this used to be done only while game was not started + // with meta-information, we can now do it at any time + _NetGameDistUpdateMulti(pDistServ); + + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServDiscClient + + \Description + Mark the specified client as disconnect. This will prevent sends and update + + \Input *pDistServ - module state + \Input iClient - index of slot holding client to update + + \Output + int32_t - negative=error, else success + + \Version 03/15/2007 (jrainy) +*/ +/********************************************************************************F*/ +int32_t NetGameDistServDiscClient(NetGameDistServT *pDistServ, int32_t iClient) +{ + // make sure this is a valid index + if (iClient >= pDistServ->ClientList.iMaxClients) + { + _NetGameDistServLogPrintf(pDistServ, "skipping disc of client %d not in dist list\n", iClient); + return(-1); + } + + // destroy connection refs and mark as disconnected + _NetGameDistServDiscClient(pDistServ, &pDistServ->ClientList.Clients[iClient], iClient, GMDIST_DISCONNECTED); + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServUpdateClient + + \Description + Update the Dist ref for the specified client. + + \Input *pDistServ - module state + \Input iClient - index of slot holding client to update + + \Output + int32_t - negative=disconnected, else zero + + \Version 02/05/2007 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t NetGameDistServUpdateClient(NetGameDistServT *pDistServ, int32_t iClient) +{ + NetGameDistServClientT *pDistClient = &pDistServ->ClientList.Clients[iClient]; + int32_t iDistErr=0; + + // don't update uninitialized slots + if (!pDistClient->bInitialized) + { + return(0); + } + + // don't update disconnected clients + if (pDistClient->bDisconnected) + { + return(pDistClient->iDisconnectReason ? pDistClient->iDisconnectReason : -1); + } + + // update client's dist ref + NetGameDistUpdate(pDistClient->pGameDist); + + // check for error + iDistErr = NetGameDistGetError(pDistClient->pGameDist); + if (iDistErr != 0) + { + _NetGameDistServLogPrintf(pDistServ, "NetGameDistGetError() returned %d\n", iDistErr); + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, iDistErr); + return(iDistErr); + } + + // return status + return(0); +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServUpdate + + \Description + Update the DistServ module. This function is expected to be called at the + desired output rate. + + \Input *pDistServ - module state + + \Version 02/12/2007 (jrainy) +*/ +/********************************************************************************F*/ +void NetGameDistServUpdate(NetGameDistServT *pDistServ) +{ + NetGameDistServClientT *pDistClient; + int32_t iClient, iResult; + int32_t aInputSizes[GMDIST_MAX_CLIENTS]; + uint8_t aInputTypes[GMDIST_MAX_CLIENTS]; + uint8_t aInputUsed[GMDIST_MAX_CLIENTS]; + uint8_t aInputDropped[GMDIST_MAX_CLIENTS]; + NetGameDistStatT aStats[GMDIST_MAX_CLIENTS]; + NetGameLinkStatT Stat; + uint8_t bDataAvailable; + uint8_t bRemoteRcv, bRemoteSnd; + uint8_t uCRCevent = FALSE; + uint32_t uSendThreshold; + void *pInputs[GMDIST_MAX_CLIENTS]; + + int32_t iPeekLength[GMDIST_MAX_CLIENTS]; + int32_t iPeekResult[GMDIST_MAX_CLIENTS] = {0}; + uint8_t iType[GMDIST_MAX_CLIENTS]; + static char aLocalInput[GMDIST_MAX_CLIENTS][NETGAME_DATAPKT_MAXSIZE]; + uint8_t uNewFlow[GMDIST_MAX_CLIENTS]; + uint32_t uNumClientsInitConn = 0; + uint32_t uNow = NetTick(); + uint8_t bPrevNoInputMode; //!< previous no input mode + + // every 100 sends, send stats to clients + if ((pDistServ->uLastStatsUpdate == 0) || (NetTickDiff(uNow, pDistServ->uLastStatsUpdate) > 2000)) + { + pDistServ->uLastStatsUpdate = uNow; + + // gather stats for all connected clients + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + if (pDistClient->bInitialized && !pDistClient->bDisconnected) + { + NetGameDistStatus(pDistClient->pGameDist, 'stat', 0, &Stat, sizeof(NetGameLinkStatT)); + aStats[iClient].late = SocketHtons(Stat.late); + aStats[iClient].bps = SocketHtons(Stat.outbps); + aStats[iClient].pps = (uint8_t)Stat.outpps; + aStats[iClient].slen = (uint8_t)NetGameDistStatus(pDistClient->pGameDist, 'slen', 0, NULL, 0); + aStats[iClient].naksent = (uint8_t)(Stat.lnaksent - pDistClient->uNakSent); + aStats[iClient].plost = (uint8_t)(Stat.lpacklost - pDistClient->uPackLost); + pDistClient->uNakSent = Stat.lnaksent; + pDistClient->uPackLost = Stat.lpacklost; + } + else + { + ds_memclr(&aStats[iClient], sizeof(aStats[0])); + } + } + + // broadcast updated stat info to all connected clients + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + if(pDistClient->bInitialized && !pDistClient->bDisconnected) + { + NetGameDistSendStats(pDistClient->pGameDist, aStats); + } + } + } + + #if MONITOR_HIGHWATER + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + int32_t iRet; + + pDistClient = &pDistServ->ClientList.Clients[iClient]; + if(pDistClient->bInitialized && !pDistClient->bDisconnected) + { + iRet = GMDIST_Modulo(NetGameDistStatus(pDistClient->pGameDist, '?inp', 0, NULL, 0) - NetGameDistStatus(pDistClient->pGameDist, '?cmp', 0, NULL, 0), NetGameDistStatus(NULL, 'pwin', 0, NULL, 0)); + if (iRet > pDistServ->iHighWaterInputQueue) + { + pDistServ->iHighWaterInputQueue = iRet; + pDistServ->uHighWaterChanged = TRUE; + } + + iRet = GMDIST_Modulo(NetGameDistStatus(pDistClient->pGameDist, '?out', 0, NULL, 0) - NetGameDistStatus(pDistClient->pGameDist, '?snd', 0, NULL, 0), NetGameDistStatus(NULL, 'pwin', 0, NULL, 0)); + if (iRet > pDistServ->iHighWaterOutputQueue) + { + pDistServ->iHighWaterOutputQueue = iRet; + pDistServ->uHighWaterChanged = TRUE; + } + } + } + #endif + + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + + if (pDistClient->bInitialized && !pDistClient->bDisconnected) + { + if (!pDistClient->bLocalInput) + { + int32_t iLength = sizeof(pDistClient->aLocalInput); + // check for incoming data on this dist + iResult = NetGameDistInputPeek(pDistClient->pGameDist, &pDistClient->iLocalInputType, pDistClient->aLocalInput, &iLength); + pDistClient->bReallyPeeked = TRUE; + _NetGameDistServSanityCheckIn(pDistServ, iClient, iResult); + + if (iResult > 0) + { + pDistClient->iLocalInputSize = iLength; + pDistClient->bLocalInput = TRUE; + pDistClient->iPeekKey = iResult; + } + else if (iResult < 0) + { + _NetGameDistServLogPrintf(pDistServ, "peek error for client %d\n", iClient); + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_PEEK_ERROR); + continue; + } + } + + bRemoteRcv = NetGameDistStatus(pDistClient->pGameDist,'rrcv', 0, NULL, 0); + bRemoteSnd = NetGameDistStatus(pDistClient->pGameDist,'rsnd', 0, NULL, 0); + + if ((bRemoteRcv != pDistClient->bRemoteRcv) || (bRemoteSnd != pDistClient->bRemoteSnd)) + { + pDistClient->bRemoteRcv = bRemoteRcv; + pDistClient->bRemoteSnd = bRemoteSnd; + + _NetGameDistServLogPrintf(pDistServ, "client %d got GAME_PACKET_INPUT_FLOW (send %d, recv %d)\n", iClient, pDistClient->bRemoteSnd, pDistClient->bRemoteRcv); + _NetGameDistServFlowControl(pDistServ); + pDistServ->uLastFlowUpdateTime = NetTick(); + } + } + } + + bDataAvailable = _NetGameDistServPrepareInputs(pDistServ, pInputs, aInputSizes, aInputTypes, aInputUsed, aInputDropped); + + if (!bDataAvailable) + { + pDistServ->uNbFramesNoData++; + } + else + { + pDistServ->uNbFramesNoData = 0; + } + + uSendThreshold = pDistServ->uSendThreshold; + + if (!pDistServ->bFlowEnabled) + { + uSendThreshold = 1; + } + + // don't send if there was no data available for a while + if ((uSendThreshold == 0) || (pDistServ->uNbFramesNoData < uSendThreshold)) + { + if (pDistServ->uCRCRemainingFrames) + { + if (pDistServ->bFlowEnabled) + { + pDistServ->uCRCRemainingFrames--; + } + if (pDistServ->uCRCRemainingFrames == 0) + { + int32_t iClientCount = 0; + + // if we happen to send to only one client, below, + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + if (pDistClient->bInitialized && (!pDistClient->bDisconnected)) + { + iClientCount++; + } + } + + if (iClientCount <= 1) + { + // don't send CRC request to single players + pDistServ->uCRCRemainingFrames = pDistServ->uCRCRate; + pDistServ->uCRCResponseCountdown = 0; + } + else + { + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + aInputTypes[iClient] |= GMDIST_DATA_CRC_REQUEST; + _NetGameDistServLogPrintfVerbose(pDistServ, 2, "setting GMDIST_DATA_CRC_REQUEST in aInputTypes[%d]\n", iClient); + } + + pDistServ->uCRCResponseCountdown = pDistServ->uCRCResponseLimit; + } + } + } + + bPrevNoInputMode = pDistServ->bNoInputMode; + pDistServ->bNoInputMode = FALSE; + + // scan through client list, and queue all inputs for sending + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + uNewFlow[iClient] = FALSE; + + pDistClient = &pDistServ->ClientList.Clients[iClient]; + + // skip disconnected clients + if (!pDistClient->bInitialized || pDistClient->bDisconnected) + { + continue; + } + + uNumClientsInitConn++; + + if (pDistServ->uCRCResponseCountdown) + { + if (NetGameDistStatus(pDistClient->pGameDist, 'rcrc', 0, NULL, 0)) + { + pDistClient->iCRC = NetGameDistStatus(pDistClient->pGameDist, 'rcrc', 1, NULL, 0); + pDistClient->uCRCValid = TRUE; + + _NetGameDistServLogPrintfVerbose(pDistServ, 2, "got CRC response %d back from client %d\n", pDistClient->iCRC, iClient); + + uCRCevent = TRUE; + } + } + + if (!pDistClient->bReallyPeeked) + { + iPeekLength[iClient] = sizeof(aLocalInput[iClient]); + iPeekResult[iClient] = NetGameDistInputPeek(pDistClient->pGameDist, &(iType[iClient]), &(aLocalInput[iClient]), &(iPeekLength[iClient])); + + if (iPeekResult[iClient] > 0) + { + uNewFlow[iClient] = TRUE; + } + } + + pDistClient->bReallyPeeked = FALSE; + + // queue input into client + if ((iResult = NetGameDistInputLocalMulti(pDistClient->pGameDist, aInputTypes, pInputs, aInputSizes, pDistClient->iPeekKey)) == 1) + { + pDistServ->uOutputMultiPacketCount++; + _NetGameDistServLogPrintfVerbose(pDistServ, 2, "queued input into client %s\n", pDistClient->strName); + } + else + { + _NetGameDistServLogPrintf(pDistServ, "NetGameDistInputLocal() failed for client %s (err=%d)\n", pDistClient->strName, iResult); + + if (iResult == GMDIST_INVALID) + { + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTLOCAL_FAILED_INVALID); + } + else if (iResult == GMDIST_OVERFLOW_MULTI) + { + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTLOCAL_FAILED_MULTI); + } + else if (iResult == GMDIST_OVERFLOW_WINDOW) + { + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTLOCAL_FAILED_WINDOW); + } + else + { + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTLOCAL_FAILED); + } + continue; + } + + if (aInputUsed[iClient] || aInputDropped[iClient]) + { + pDistClient->bLocalInput = FALSE; + pDistClient->iPeekKey = 0; + } + + // sanity check output + _NetGameDistServSanityCheckOut(pDistServ, iClient, iResult); + + // advance the state + if ((iResult = NetGameDistInputQueryMulti(pDistClient->pGameDist, NULL, NULL, NULL)) > 0) + { + _NetGameDistServLogPrintfVerbose(pDistServ, 2, "sent input %d\n", iResult); + } + else if (iResult) + { + _NetGameDistServLogPrintf(pDistServ, "NetGameDistInputQueryMulti() failed (err=%d)\n", iResult); + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTQUERY_FAILED); + continue; + } + + if (uNewFlow[iClient] && !aInputUsed[iClient]) + { + _NetGameDistServLogPrintf(pDistServ, "we're getting behind on large packet delaying for client %d\n", iClient); + _NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTQUERY_FAILED); + continue; + } + } + + /* If the client count changed, flag the boolean used to remember that it happened at least once + during the metrics sampling interval. */ + if (pDistServ->uCurStatClientCount != uNumClientsInitConn) + { + if (pDistServ->bClientCountChanged == FALSE) + { + _NetGameDistServLogPrintfVerbose(pDistServ, 1, "metric sampling interval marked invalid for idpps, odmpps and inputdrop because client count changed (client count = %d)\n", pDistServ->uCurStatClientCount); + } + pDistServ->bClientCountChanged = TRUE; + pDistServ->uCurStatClientCount = uNumClientsInitConn; + } + + for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++) + { + pDistClient = &pDistServ->ClientList.Clients[iClient]; + + if (uNewFlow[iClient]) + { + pDistClient->iLocalInputType = iType[iClient]; + pDistClient->iLocalInputSize = iPeekLength[iClient]; + pDistClient->iPeekKey = iPeekResult[iClient]; + pDistClient->bLocalInput = TRUE; + + ds_memcpy_s(pDistClient->aLocalInput, sizeof(pDistClient->aLocalInput), aLocalInput[iClient], sizeof(aLocalInput[iClient])); + + _NetGameDistServSanityCheckIn(pDistServ, iClient, iPeekResult[iClient]); + } + } + + // if something occured, or we have a active countdown + if (pDistServ->uCRCResponseCountdown || uCRCevent) + { + // countdown + if (pDistServ->bFlowEnabled && pDistServ->uCRCResponseCountdown) + { + pDistServ->uCRCResponseCountdown--; + } + + // if something occured or we just reached zero. + if (uCRCevent || (pDistServ->uCRCResponseCountdown == 0)) + { + _NetGameDistServHandleCRCResponses(pDistServ); + } + } + } + else + { + bPrevNoInputMode = pDistServ->bNoInputMode; + pDistServ->bNoInputMode = TRUE; + } + + /* If 'no input mode' changed, flag the boolean used to remember that it happened at least once + during the metrics sampling interval. */ + if (bPrevNoInputMode != pDistServ->bNoInputMode) + { + if (pDistServ->bNoInputModeChanged == FALSE) + { + _NetGameDistServLogPrintfVerbose(pDistServ, 1, "metric sampling interval marked invalid for odmpps because no input mode changed (no input mode = %d)\n", pDistServ->bNoInputMode); + } + pDistServ->bNoInputModeChanged = TRUE; + } + + // $$ jrainy -- fixed up the below code to work without collapsing but introduced a very slight + // side-effect if we have a list with a big empty area (Alice, [Empty], [Empty], [Empty], [Empty], Bob) + // Bob will be able to get his oversized packets through more easily than Alice. + // $$ TODO: A better change would be to increment iFirstclient to the next allocated/initialized + // spot instead of by just 1. (minor for now) + if (pDistServ->ClientList.iMaxClients != 0) + { + pDistServ->iFirstClient = (pDistServ->iFirstClient + 1) % pDistServ->ClientList.iMaxClients; + } +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServHighWaterChanged + + \Description + Return whether the highwater mark changed, and the current highwater values. + + \Input *pDistServ - module state + \Input pHighWaterInputQueue - input queue + \Input pHighWaterOutputQueue - output queue + + \Output + uint8_t - whether the highwater mark changed since last call + + \Version 06/05/2008 (jrainy) +*/ +/********************************************************************************F*/ +uint8_t NetGameDistServHighWaterChanged(NetGameDistServT *pDistServ, int32_t* pHighWaterInputQueue, int32_t* pHighWaterOutputQueue) +{ + #if MONITOR_HIGHWATER + uint8_t bChanged = pDistServ->uHighWaterChanged; + + if (pHighWaterInputQueue != NULL) + { + *pHighWaterInputQueue = pDistServ->iHighWaterInputQueue; + } + if (pHighWaterOutputQueue != NULL) + { + *pHighWaterOutputQueue = pDistServ->iHighWaterOutputQueue; + } + + pDistServ->uHighWaterChanged = FALSE; + return(bChanged); + #else + return(FALSE); + #endif +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServExplainError + + \Description + return the lastest error reported by netgamedist, for client iClient + + \Input *pDistServ - module state + \Input iClient - client to get the error for. + + \Output + char * - the latest netgamedist error + + \Version 08/15/2008 (jrainy) +*/ +/********************************************************************************F*/ +char* NetGameDistServExplainError(NetGameDistServT *pDistServ, int32_t iClient) +{ + return(pDistServ->ClientList.Clients[iClient].strDiscReason); +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServControl + + \Description + Control behavior of module. + + \Input *pDistServ - pointer to module state + \Input iControl - status selector + \Input iValue - control value + \Input iValue2 - control value + \Input *pValue - control value + + \Notes + iControl can be one of the following: + + \verbatim + 'crcc' - set the CRC challenge send rate in number of frames (0 to disable) + 'crcr' - set the CRC max response times in number of frames (0 makes it infinite) + 'mpty' - set the number(iValue) of empty frames needed to pause traffic + 'rate' - set the fixed rate the server has been configured to run at via iValue + 'rsta' - reset stat variables (flow control/no input mode bool) + \endverbatim + + \Version 03/17/2009 (jrainy) +*/ +/********************************************************************************F*/ +int32_t NetGameDistServControl(NetGameDistServT *pDistServ, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iControl == 'crcc') + { + if ((uint32_t)iValue != pDistServ->uCRCRate) + { + //set the rate + pDistServ->uCRCRate = iValue; + + //start counting down + pDistServ->uCRCRemainingFrames = iValue; + _NetGameDistServLogPrintf(pDistServ, "setting CRC rate to %d\n", iValue); + + } + return(0); + } + if (iControl == 'crcr') + { + pDistServ->uCRCResponseLimit = iValue; + return(0); + } + if (iControl == 'mpty') + { + pDistServ->uSendThreshold = iValue; + return(0); + } + if (iControl == 'rate') + { + pDistServ->iFixedRate = iValue; + return(0); + } + if (iControl == 'rsta') + { + pDistServ->bClientCountChanged = FALSE; + pDistServ->bFlowEnabledChanged = FALSE; + pDistServ->bNoInputModeChanged = FALSE; + + return(0); + } + + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServStatus2 + + \Description + Get status information. + + \Input *pDistServ - pointer to module state + \Input iSelect - status selector + \Input iValue - selector specific + \Input *pBuf - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iSelect can be one of the following: + + \verbatim + 'clnu' - returns the current client count used for stat (note this not the same as iNumClients in client list) + 'drop' - returns total dropped input packet count in pValue (uint32_t) for client index in iValue, the value is invalid if GameServerDistStatus() return -1; + 'ftim' - flow time. Time (ms) since the last flow control packet was received. + 'icnt' - returns total input dist packet count in pValue (uint32_t) for client index in iValue, the value is invalid if GameServerDistStatus() return -1; + 'ninp' - whether we are currently in the mode where no inputs are sent due to stall in input arrival + 'ocnt' - reutrns output dist multi packet total in pValue (uint32_t), the value is invalid if GameServerDistStatus() return -1; + \endverbatim + + \Version 09/18/2019 (tcho) +*/ +/********************************************************************************F*/ +int32_t NetGameDistServStatus2(NetGameDistServT *pDistServ, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + // total number of clients counted in the update loop + if (iSelect == 'clnu') + { + return(pDistServ->uCurStatClientCount); + } + + // total input packet dropped count for a client or total input dist packet count per client + if ((iSelect == 'drop') || (iSelect == 'icnt')) + { + uint32_t uInputDropCount; + uint32_t uInputCount; + NetGameDistServClientT *pDistClient; + + if ((iValue < 0) || (iValue >= pDistServ->ClientList.iMaxClients)) + { + // invalid index + return(-1); + } + + if ((pBuf == NULL) || (iBufSize < (int32_t)sizeof(uint32_t))) + { + // invalid output parameter + return(-2); + } + + pDistClient = &pDistServ->ClientList.Clients[iValue]; + + // only report a value if the client is initialized and connected + if (pDistClient->bInitialized && !pDistClient->bDisconnected) + { + if (iSelect == 'drop') + { + uInputDropCount = NetGameDistStatus(pDistClient->pGameDist, 'drop', 0, NULL, 0); + ds_memcpy(pBuf, &uInputDropCount, sizeof(uint32_t)); + } + + if (iSelect == 'icnt') + { + uInputCount = NetGameDistStatus(pDistClient->pGameDist, 'icnt', 0, NULL, 0); + ds_memcpy(pBuf, &uInputCount, sizeof(uint32_t)); + } + + if ((pDistServ->bClientCountChanged == TRUE) || (pDistServ->bFlowEnabledChanged == TRUE) || (pDistServ->bFlowEnabled == FALSE)) + { + /* value copied in pBuf is valid but sampling interval should be ignored because conditions are not met + to properly calculate per-client rate of drops or per-client rate of inbound inputs. */ + return(1); + } + + return(0); + } + else + { + ds_memclr(pBuf, iBufSize); + return(-3); + } + } + + if (iSelect == 'ftim') + { + return(NetTickDiff(NetTick(), pDistServ->uLastFlowUpdateTime)); + } + + if (iSelect == 'ninp') + { + return(pDistServ->bNoInputMode); + } + + // total output multi dist count per server + if (iSelect == 'ocnt') + { + if ((iValue < 0) || (iValue >= pDistServ->ClientList.iMaxClients)) + { + // invalid index + return(-1); + } + + if ((pBuf == NULL) || (iBufSize < (int32_t)sizeof(uint32_t))) + { + // invalid output parameter + return(-2); + } + + ds_memcpy(pBuf, &pDistServ->uOutputMultiPacketCount, sizeof(uint32_t)); + + if ((pDistServ->uCurStatClientCount == 0) || (pDistServ->bClientCountChanged == TRUE) || (pDistServ->bNoInputModeChanged == TRUE) || (pDistServ->bNoInputMode == TRUE)) + { + /* value copied in pBuf is valid but sampling interval should be ignored because conditions are not met + to properly calculate rate of outbound multipackets */ + return(1); + } + + return(0); + } + + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServStatus + + \Description + Get status information. + + \Input *pDistServ - pointer to module state + \Input iSelect - status selector + \Input *pBuf - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + falls through to NetGameDistServStatus2() + + \Version 24/09/2012 (jrainy) first version +*/ +/********************************************************************************F*/ +int32_t NetGameDistServStatus(NetGameDistServT *pDistServ, int32_t iSelect, void *pBuf, int32_t iBufSize) +{ + return(NetGameDistServStatus2(pDistServ, iSelect, 0, pBuf, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function NetGameDistServSetLoggingCallback + + \Description + Set the logging callback + + \Input *pDistServ - pointer to module state + \Input *pLoggingCb - logging callback + \Input *pUserData - logging userdata + + \Version 06/26/2019 (eesponda) +*/ +/********************************************************************************F*/ +void NetGameDistServSetLoggingCallback(NetGameDistServT *pDistServ, NetGameDistServLoggingCbT *pLoggingCb, void *pUserData) +{ + pDistServ->pLoggingCb = pLoggingCb; + pDistServ->pUserData = pUserData; +} diff --git a/src/thirdparty/dirtysdk/source/game/netgamelink.c b/src/thirdparty/dirtysdk/source/game/netgamelink.c new file mode 100644 index 00000000..247aa0b2 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/game/netgamelink.c @@ -0,0 +1,2193 @@ +/*H*************************************************************************************************/ +/*! + + \File netgamelink.c + + \Description + This module provides a packet layer peer-peer interface which utilizes + a lower layer "comm" module. This module is used either by netgamedist + (if it is being used) or can be accessed directly if custom online game + logic is being used. This module also calculates latency (ping) and + maintains statistics about the connection (number of bytes send/received). + The direct application interface is currently weak but will be improved. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 1.0 12/19/00 (GWS) First Version + +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include /* memset/ds_memcpy */ + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/game/netgamepkt.h" +#include "DirtySDK/game/netgamelink.h" +#include "DirtySDK/comm/commall.h" + +/*** Defines ***************************************************************************/ + +#define NETGAMELINK_KEEPALIVE_TIME (500) + +//! min/max values for NetGameLink QoS settings +#define NETGAMELINK_QOS_DURATION_MIN (0) +#define NETGAMELINK_QOS_DURATION_MAX (10000) +#define NETGAMELINK_QOS_INTERVAL_MIN (10) +#define NETGAMELINK_QOS_PACKETSIZE_MIN (50) + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! netgamelink internal state +struct NetGameLinkRefT +{ + //! port used to communicate with server + CommRef *pPort; + //! flag to indicate if we own port (if we need to destroy when done) + int32_t iPortOwner; + + //! module memory group + int32_t iMemGroup; + void *pMemGroupUserData; + + //! tick at which last packet was sent to server (in ms) + uint32_t uLastSend; + //! tick at which last sync packet was received from server (in ms) + uint32_t uLastRecv; + //! tick at which last sync packet contributed to latency average calculation + uint32_t uLastAvg; + //! last echo requested by server (used for rtt calc) + uint32_t uLastEcho; + //! last time a sync packet was sent + uint32_t uLastSync; + //! last time the history slot changed + uint32_t uLastHist; + + //! the time weighted rtt average + int32_t iRttAvg; + //! the time weighted rtt deviation + int32_t iRttDev; + + //! external status monitoring + NetGameLinkStatT NetGameLinkStats; + + //! stats over a one second period + int32_t iInpBytes; //!< input bytes + int32_t iInpPackets; //!< input packets + int32_t iInpRawBytes; //!< input raw bytes + int32_t iInpRawPackets; //!< input raw packets + int32_t iOverhead; //!< send overhead + int32_t iRcvOverhead; //!< recv overhead + int32_t iRcvRawBytes; //!< recv raw bytes + int32_t iRcvRawPackets; //!< recv raw packet (commudp recvs) + int32_t iRcvPackets; //!< recv packets + int32_t iRcvBytes; //!< recv bytes + + // track packets sent/received for sending to peer in sync packets + + //! stats - raw packets sent + int32_t iPackSent; + //! stats - raw packets received + int32_t iPackRcvd; + //! stats - remote->local packets lost + int32_t iR2LPackLost; + //! stats - naks sent + int32_t iNakSent; + + //! point to receive buffer + char *pBuffer; + //! length of data in receive buffer + int32_t iBufLen; + //! size of receive buffer + int32_t iBufMax; + + //! protect resources + NetCritT Crit; + //! count of missed accessed + int32_t iProcess; + + //! data for m_callproc + void *pCallData; + //! callback for incoming packet notification + void (*pCallProc)(void *pCalldata, int32_t iKind); + //! callback pending + int32_t iCallPending; + + //! count calls to NetGameLinkRecv() to limit callback access + int32_t iRecvProcCnt; + + //! send enable/disable + int32_t bSendEnabled; + + //! sync enable/disable + int32_t bSyncEnabled; + + //! list of active streams + NetGameLinkStreamT *m_stream; + + int32_t iStrmMaxPktSize; + + //! used for tracking QoS packets + uint32_t uQosSendInterval; + uint32_t uQosLastSendTime; + uint32_t uQosStartTime; + uint16_t uQosPacketsToSend; + uint16_t uQosPacketsToRecv; + uint16_t uQosPacketSize; + int32_t iQosCumulLate; + int32_t iQosLateCount; + int32_t iQosAvgLate; + + //! verbosity + int32_t iVerbosity; +}; + +typedef struct GameStreamT +{ + int32_t iKind; //!< block type + uint32_t uSync; //!< sync sequence number + int32_t iSent; //!< amount sent so far + int32_t iSize; //!< total block size + int32_t iSubchan; //!< subchannel index/ +} GameStreamT; + + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +// Public variables + + +/*** Private Functions *****************************************************************/ + + + +/*F*************************************************************************************/ +/*! + \Function _NetGameLinkUpdateLocalPacketLoss + + \Description + Calculate local packet loss based on a difference of remote packets sent and + local packets received. + + \Input uLPackRcvd - count of packets received locally + \Input uRPackSent - count of packets sent by remote peer (cumulative psnt + values of all received sync packets) + \Input uOldLPackLost - old count of packets lost + + \Output + uint32_t - updated count of packets lost + + \Notes + \verbatim + This function returns the number of packets lost at this end of the connection + since the beginning. It is obtained by subtracting uRPackSent (the number of + packets sent by the remote end - cumulative psnt values of all received sync + packets) from uLPackRcvd (the number of packets received locally). + + The following two scenarios indicate that a sync packet itself suffered packet + loss, and it relied on the packet resending mechanisms of commudp to finally + make it to us: + 1 - uLPackRcvd is larger than uRPackSent (subtracting them would return a + negative value) + 2 - uLPackRcvd is smaller than uRPackSent, but the last saved count of + packets lost is larger than the new one. + + For both scenarios, we skip updating the packets lost count to avoid a + possibly negative or under-valued packet loss calculation, and we deal with + that iteration as if no packet loss was detected. + + The assumption here is that the next sync packet that makes it to us without + being "resent" will end up updating the packet lost counters with a "coherent" + value as its "psnt" field will include all packets that were used for packet + retransmission. At the end, this is guaranteeing the coherency of the packets + lost count (non negative value and only increasing over time) by delaying its + update. + \endverbatim + + \Version 09/02/14 (mclouatre) +*/ +/*************************************************************************************F*/ +static uint32_t _NetGameLinkUpdateLocalPacketLoss(uint32_t uLPackRcvd, uint32_t uRPackSent, uint32_t uOldLPackLost) +{ + uint32_t uUpdatedLPackLost = uOldLPackLost; + + // only proceed if packet loss is not negative + if (uRPackSent > uLPackRcvd) + { + // only proceed if packet loss is larger than last calculated value + if ((uRPackSent - uLPackRcvd) > uOldLPackLost) + { + uUpdatedLPackLost = uRPackSent - uLPackRcvd; + } + } + + return(uUpdatedLPackLost); +} + +/*F*************************************************************************************/ +/*! + \Function _NetGameLinkGetSync + + \Description + Extract sync packet from buffer, and return a pointer to the sync packet location in the buffer + + \Input *pPacket - pointer to packet structure + \Input *pPacketData - pointer to packet data (used instead of packet buffer ref to allow split use) + \Input *pSync - [out] output buffer for extracted sync packet + + \Output + NetGamePacketSyncT * - pointer to start of sync packet in buffer, or NULL if invalid + + \Version 09/09/11 (jbrookes) +*/ +/*************************************************************************************F*/ +static NetGamePacketSyncT *_NetGameLinkGetSync(NetGamePacketT *pPacket, uint8_t *pPacketData, NetGamePacketSyncT *pSync) +{ + uint32_t uSyncSize=0; + // validate sync length byte is available + if (pPacket->head.len < 1) + { + NetPrintf(("netgamelink: received a sync packet with no data\n")); + return(NULL); + } + // validate sync size + if ((uSyncSize = (uint32_t)pPacketData[pPacket->head.len-1]) != sizeof(*pSync)) + { + NetPrintf(("netgamelink: received a sync with an invalid size (got=%d, expected=%d)\n", uSyncSize, sizeof(*pSync))); + return(NULL); + } + // validate packet is large enough to hold sync + if (pPacket->head.len < uSyncSize) + { + NetPrintf(("netgamelink: received a sync too large for packet (len=%d)\n", pPacket->head.len)); + return(NULL); + } + // locate sync at end of packet & subtract from packet length, copy to output + pPacket->head.len -= uSyncSize; + ds_memcpy(pSync, pPacketData+pPacket->head.len, uSyncSize); + // return sync packet pointer to caller + return((NetGamePacketSyncT *)(pPacketData+pPacket->head.len)); +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkSendPacket + + \Description + Send a packet to the server + + \Input *pRef - reference pointer + \Input *pPacket - pointer to completed packet + \Input uCurrTick- current tick + + \Output + int32_t - bad error, zero=unable to send now, positive=sent + + \Notes + Adds timestamp, rtt and duplex information to packet + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +static int32_t _NetGameLinkSendPacket(NetGameLinkRefT *pRef, NetGamePacketT *pPacket, uint32_t uCurrTick) +{ + int32_t iResult; + int32_t iSynced = 0; + NetGamePacketSyncT sync; + uint32_t uPacketFlags, uPacketType; + uint32_t uPackSent=0, uPackRcvd=0, uLPktLost=0, uNakSent=0; + + // sending enabled? + if (pRef->bSendEnabled == FALSE) + { + NetPrintf(("netgamelink: warning -- trying to send packet over a sending-disabled link (%d bytes)\n", pPacket->head.len)); + return(1); + } + + // return error for oversized packets + if ((pPacket->head.len + NETGAME_DATAPKT_MAXTAIL) > pRef->pPort->maxwid) + { + NetPrintf(("netgamelink: oversized packet send (%d bytes)\n", pPacket->head.len)); + return(-1); + } + + /* see if we should add sync info: + if _NetGameLinkSendPacket was called explicitly to send a sync packet there is no need to make any further checks + otherwise if a reliable packet game was being sent and it is time to send a sync packet */ + if (((pPacket->head.kind & GAME_PACKET_SYNC) || ((pPacket->head.kind != GAME_PACKET_USER_UNRELIABLE) && (uCurrTick-pRef->uLastSync > NETGAMELINK_KEEPALIVE_TIME/2))) && (pRef->bSyncEnabled == TRUE)) + { + // build the sync packet + ds_memclr(&sync, sizeof(sync)); + + sync.size = sizeof(sync); + sync.echo = SocketHtonl(uCurrTick); + sync.repl = SocketHtonl(pRef->uLastEcho+(uCurrTick-pRef->uLastRecv)); + sync.late = SocketHtons((int16_t)((pRef->iRttAvg+pRef->iRttDev+1)/2)); + + uLPktLost = _NetGameLinkUpdateLocalPacketLoss(pRef->NetGameLinkStats.lpackrcvd, pRef->NetGameLinkStats.rpacksent, (unsigned)pRef->iR2LPackLost); + uPackSent = pRef->pPort->packsent; + uNakSent = pRef->pPort->naksent; + uPackRcvd = pRef->pPort->packrcvd; + + sync.plost = (uint8_t)(uLPktLost - pRef->iR2LPackLost); + sync.psnt = (uint8_t)(uPackSent - pRef->iPackSent); + sync.nsnt = (uint8_t)(uNakSent - pRef->iNakSent); + sync.prcvd = (uint8_t)(uPackRcvd - pRef->iPackRcvd); + + // if we have not received a sync packet from the remote peer, our repl field is not valid, so we tell the remote peer to ignore it + if (pRef->NetGameLinkStats.rpacksent == 0) + { + sync.flags |= NETGAME_SYNCFLAG_REPLINVALID; + } + + // piggyback on existing packet + ds_memcpy(pPacket->body.data+pPacket->head.len, &sync, sizeof(sync)); + pPacket->head.len += sizeof(sync); + pPacket->head.kind |= GAME_PACKET_SYNC; + iSynced = 1; + } + + // put type as last byte + pPacket->body.data[pPacket->head.len] = pPacket->head.kind; + // determine packet flags + uPacketType = pPacket->head.kind & ~GAME_PACKET_SYNC; + if (((uPacketType <= GAME_PACKET_ONE_BEFORE_FIRST) || (uPacketType >= GAME_PACKET_ONE_PAST_LAST)) && (pPacket->head.kind != GAME_PACKET_SYNC)) + { + NetPrintf(("netgamelink: warning -- send unrecognized packet kind %d\n", pPacket->head.kind)); + } + if (uPacketType == GAME_PACKET_USER_UNRELIABLE) + { + uPacketFlags = COMM_FLAGS_UNRELIABLE; + } + else if (uPacketType == GAME_PACKET_USER_BROADCAST) + { + uPacketFlags = COMM_FLAGS_UNRELIABLE|COMM_FLAGS_BROADCAST; + } + else + { + uPacketFlags = COMM_FLAGS_RELIABLE; + } + + // send the packet + iResult = pRef->pPort->Send(pRef->pPort, pPacket->body.data, pPacket->head.len + 1, uPacketFlags); + + // if we added sync info, remove it now + if (iSynced) + { + pPacket->head.len -= sizeof(sync); + pPacket->head.kind ^= GAME_PACKET_SYNC; + } + + // make sure send succeeded + if (iResult > 0) + { + // record the send time + pRef->uLastSend = uCurrTick; + // if it was a sync packet, update sync info + if (iSynced) + { + pRef->uLastSync = uCurrTick; + pRef->iPackSent = uPackSent; + pRef->iNakSent = uNakSent; + pRef->iPackRcvd = uPackRcvd; + pRef->iR2LPackLost = uLPktLost; + } + + // update the stats + pRef->iInpBytes += pPacket->head.len; + pRef->iInpPackets += 1; + pRef->NetGameLinkStats.sent += pPacket->head.len; + pRef->NetGameLinkStats.sentlast = pRef->uLastSend; + + // see if we should turn off send light + if ((pRef->NetGameLinkStats.sentshow != 0) && (pRef->uLastSend -pRef->NetGameLinkStats.sentshow > 100)) + { + pRef->NetGameLinkStats.sentshow = 0; + pRef->NetGameLinkStats.senthide = pRef->uLastSend; + } + + // see if we should turn on send light + if ((pRef->NetGameLinkStats.senthide != 0) && (pRef->uLastSend -pRef->NetGameLinkStats.senthide > 100)) + { + pRef->NetGameLinkStats.senthide = 0; + pRef->NetGameLinkStats.sentshow = pRef->uLastSend; + } + } + else + { +// NetPrintf(("GmLink: send failed! (kind=%d, len=%d, synced=%d)\n", +// packet->head.kind, packet->head.len, iSynced)); + } + + // return the result code + return(iResult); +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkRecvPacket0 + + \Description + Process incoming data packet + + \Input *pRef - reference pointer + \Input uCurrTick- current tick + + \Output + int32_t - zero=no packet processed, positive=packet processed + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +static int32_t _NetGameLinkRecvPacket0(NetGameLinkRefT *pRef, uint32_t uCurrTick) +{ + int32_t iSize; + int16_t iDelta; + uint32_t uWhen, uKind; + NetGamePacketT *pPacket; + NetGameLinkHistT *pHistory; + + // calculate buffer space free, making sure to include packet header overhead (not sent, but queued) + if ((iSize = pRef->iBufMax -pRef->iBufLen -sizeof(pPacket->head)) <= 0) + { + return(0); + } + else if (iSize > pRef->pPort->maxwid) + { + iSize = pRef->pPort->maxwid; + } + + // setup packet buffer + pPacket = (NetGamePacketT *)(pRef->pBuffer +pRef->iBufLen); + + // see if packet is available + iSize = pRef->pPort->Recv(pRef->pPort, &pPacket->body, iSize, &uWhen); + if ((iSize <= 0) || (iSize > pRef->pPort->maxwid)) + { + #if DIRTYCODE_LOGGING + if (iSize != COMM_NODATA) + { + NetPrintf(("netgamelink: Recv() returned %d\n", iSize)); + } + #endif + return(0); + } + pPacket->head.len = iSize; + pPacket->head.size = 0; + pPacket->head.when = uWhen; + + pRef->NetGameLinkStats.tick = uCurrTick; + pRef->NetGameLinkStats.tickseqn += 1; + + // update the stats + pRef->NetGameLinkStats.rcvdlast = uCurrTick; + pRef->NetGameLinkStats.rcvd += pPacket->head.len; + pRef->iRcvBytes += pPacket->head.len; + pRef->iRcvPackets += 1; + + // see if we should turn off receive light + if ((pRef->NetGameLinkStats.rcvdshow != 0) && (uCurrTick-pRef->NetGameLinkStats.rcvdshow > 100)) + { + pRef->NetGameLinkStats.rcvdshow = 0; + pRef->NetGameLinkStats.rcvdhide = uCurrTick; + } + + // see if we should turn on receive light + if ((pRef->NetGameLinkStats.rcvdhide != 0) && (uCurrTick-pRef->NetGameLinkStats.rcvdhide > 100)) + { + pRef->NetGameLinkStats.rcvdhide = 0; + pRef->NetGameLinkStats.rcvdshow = uCurrTick; + } + + // extract the kind field and fix the length + pPacket->head.len -= 1; + pPacket->head.kind = pPacket->body.data[pPacket->head.len]; + + // get packet kind + uKind = pPacket->head.kind & ~GAME_PACKET_SYNC; + + // warn if kind is invalid + if (((uKind <= GAME_PACKET_ONE_BEFORE_FIRST) || (uKind >= GAME_PACKET_ONE_PAST_LAST)) && (pPacket->head.kind != GAME_PACKET_SYNC)) + { + NetPrintf(("netgamelink: warning -- recv unrecognized packet kind %d\n", pPacket->head.kind)); + return(1); + } + + // see if this packet contains timing info + if (pPacket->head.kind & GAME_PACKET_SYNC) + { + // get sync packet info + NetGamePacketSyncT Sync; + if (_NetGameLinkGetSync(pPacket, pPacket->body.data, &Sync) == NULL) + { + return(1); + } + + // remove sync bit + pPacket->head.kind ^= GAME_PACKET_SYNC; + + // update the latency stat + pRef->NetGameLinkStats.late = SocketNtohs(Sync.late); + + // calculate instantaneous rtt + if ((Sync.flags & NETGAME_SYNCFLAG_REPLINVALID) == 0) + { + /* delta = recvtime - sendtime + 1 + The +1 is needed because the peer may think the packet stayed in + its queue for 1ms while we may think it was returned within the + same millisecond. This happens because the clocks are not precisely + synchronized. Adding 1 avoids the issue without compromising precision. */ + iDelta = (int16_t)(uWhen - SocketNtohl(Sync.repl) + 1); + } + else + { + // remote peer has indicated their repl field is invalid, so we ignore sync latency info + iDelta = -1; + } + if ((iDelta >= 0) && (iDelta <= 2500)) + { + // figure out elapsed time since last packet + int32_t iElapsed = uWhen-pRef->uLastAvg; + if (iElapsed < 10) + { + iElapsed = 10; + } + pRef->uLastAvg = uWhen; + + // perform rtt calc using weighted time average + if (iElapsed < RTT_SAMPLE_PERIOD) + { + // figure out weight of existing data + int32_t iWeight = RTT_SAMPLE_PERIOD-iElapsed; + // figure deviation first since it uses average + int32_t iDeviate = iDelta - pRef->iRttAvg; + if (iDeviate < 0) + iDeviate = -iDeviate; + // calc weighted deviation + pRef->iRttDev = (iWeight*pRef->iRttDev+iElapsed*iDeviate)/RTT_SAMPLE_PERIOD; + // calc weighted average + pRef->iRttAvg = (iWeight*pRef->iRttAvg+iElapsed*iDelta)/RTT_SAMPLE_PERIOD; + } + else + { + // if more than our scale has elapsed, use this data + pRef->iRttAvg = iDelta; + pRef->iRttDev = 0; + } + + // save copy of ping in stats table + pRef->NetGameLinkStats.ping = pRef->iRttAvg+pRef->iRttDev; + pRef->NetGameLinkStats.pingavg = pRef->iRttAvg; + pRef->NetGameLinkStats.pingdev = pRef->iRttDev; + + // see if this is a new recod + if (uWhen-pRef->uLastHist >= PING_LENGTH) + { + // remember the update time + pRef->uLastHist = uWhen; + // advance to next ping slot + pRef->NetGameLinkStats.pingslot = (pRef->NetGameLinkStats.pingslot + 1) % PING_HISTORY; + pHistory = pRef->NetGameLinkStats.pinghist + pRef->NetGameLinkStats.pingslot; + + // save the information + pHistory->min = iDelta; + pHistory->max = iDelta; + pHistory->avg = iDelta; + pHistory->cnt = 1; + } + else + { + // update the information + pHistory = pRef->NetGameLinkStats.pinghist + pRef->NetGameLinkStats.pingslot; + if (iDelta < pHistory->min) + pHistory->min = iDelta; + if (iDelta > pHistory->max) + pHistory->max = iDelta; + pHistory->avg = ((pHistory->avg * pHistory->cnt) + iDelta) / (pHistory->cnt+1); + pHistory->cnt += 1; + } + + // save this update time + pRef->NetGameLinkStats.pingtick = uWhen; + } + else if ((Sync.flags & NETGAME_SYNCFLAG_REPLINVALID) == 0) + { + NetPrintf(("netgamelink: sync delta %d is out of range (when=0x%08x,sync.repl=0x%08x)\n", iDelta, uWhen, SocketNtohl(Sync.repl) )); + } + + // extract timing information + pRef->uLastRecv = uWhen; + pRef->uLastEcho = SocketNtohl(Sync.echo); + + // update remote peer's packets sent/recv stat tracker + if (pRef->NetGameLinkStats.rpacksent == 0) + { + /* + bias initial update by one to account for the commdup packet conveying this NetGameLinkSyncT packet not being in the count (but it should really be) + no need to do so afterwards because the count will include the previous packet for which the count is already biased + */ + pRef->NetGameLinkStats.rpacksent += 1; + } + pRef->NetGameLinkStats.rpacksent += Sync.psnt; + pRef->NetGameLinkStats.rnaksent += Sync.nsnt; + pRef->NetGameLinkStats.rpackrcvd += Sync.prcvd; + pRef->NetGameLinkStats.rpacklost += Sync.plost; + + // update packets received, packets saved, packets sent and packets lost; this is done here to keep in sync with rpacksent and rpackrcvd + pRef->NetGameLinkStats.lnaksent = pRef->pPort->naksent; + pRef->NetGameLinkStats.lpackrcvd = pRef->pPort->packrcvd; + pRef->NetGameLinkStats.lpacksaved = pRef->pPort->packsaved; + pRef->NetGameLinkStats.lpacksent = pRef->pPort->packsent; + pRef->NetGameLinkStats.lpacklost = _NetGameLinkUpdateLocalPacketLoss(pRef->NetGameLinkStats.lpackrcvd, pRef->NetGameLinkStats.rpacksent, pRef->NetGameLinkStats.lpacklost); + } + + // save packet if something remains (was not just sync) + if (pPacket->head.kind != 0) + { + pPacket->head.size = (sizeof(pPacket->head)+pPacket->head.len+3) & 0x7ffc; + pRef->iBufLen += pPacket->head.size; + } + return(1); +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkRecvPacket + + \Description + Call _NetGameLinkRecvPacket0 if we haven't already + + \Input *pRef - reference pointer + \Input uCurrTick- current tick + + \Output + int32_t - zero=no packet processed, positive=packet processed + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +static int32_t _NetGameLinkRecvPacket(NetGameLinkRefT *pRef, uint32_t uCurrTick) +{ + int32_t iRetVal; + + // limit call depth to prevent a callback from calling us more than once + if (pRef->iRecvProcCnt > 0) + { + #if DIRTYCODE_LOGGING && 0 + NetPrintf(("netgamelink: m_recvprocct=%d, unable to call _NetGameLinkRecvPacket0\n", pRef->m_recvprocct)); + #endif + return(0); + } + + pRef->iRecvProcCnt = 1; + iRetVal = _NetGameLinkRecvPacket0(pRef, uCurrTick); + pRef->iRecvProcCnt = 0; + + return(iRetVal); +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkProcess + + \Description + Main send/receive processing loop + + \Input *pRef - reference pointer + \Input uCurrTick- current tick + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +static void _NetGameLinkProcess(NetGameLinkRefT *pRef, uint32_t uCurrTick) +{ + uint32_t uRange; + + // grab any pending packets + while (_NetGameLinkRecvPacket(pRef, uCurrTick) > 0) + { + pRef->iCallPending += 1; + } + + // mark as processing complete + pRef->iProcess = 0; + + // handle stats update (once per second) + pRef->NetGameLinkStats.tick = uCurrTick; + pRef->NetGameLinkStats.tickseqn += 1; + uRange = uCurrTick - pRef->NetGameLinkStats.stattick; + if (uRange >= 1000) + { + // calc bytes per second, raw bytes per second, and network bytes per second + pRef->NetGameLinkStats.outbps = (pRef->iInpBytes *1000)/uRange; + pRef->NetGameLinkStats.outrps = (pRef->pPort->datasent-pRef->iInpRawBytes)*1000/uRange; + pRef->NetGameLinkStats.outnps = ((pRef->pPort->datasent-pRef->iInpRawBytes)+(pRef->pPort->overhead-pRef->iOverhead))*1000/uRange; + pRef->NetGameLinkStats.inrps = ((pRef->pPort->datarcvd-pRef->iRcvRawBytes)*1000)/uRange; + pRef->NetGameLinkStats.inbps = (pRef->iRcvBytes * 1000)/uRange; + pRef->NetGameLinkStats.innps = ((pRef->pPort->datarcvd-pRef->iRcvRawBytes)+(pRef->pPort->rcvoverhead-pRef->iRcvOverhead))*1000/uRange; + // calculate packets per second and raw packets per second + pRef->NetGameLinkStats.outpps = ((pRef->iInpPackets *1000)+500)/uRange; + pRef->NetGameLinkStats.outrpps = ((pRef->pPort->packsent-pRef->iInpRawPackets)*1000+500)/uRange; + pRef->NetGameLinkStats.inpps = ((pRef->iRcvPackets *1000)+500)/uRange; + pRef->NetGameLinkStats.inrpps = ((pRef->pPort->packrcvd - pRef->iRcvRawPackets)*1000+500)/uRange; + // reset tracking variables + pRef->iInpBytes = pRef->iInpPackets = 0; + pRef->iRcvBytes = pRef->iRcvPackets = 0; + pRef->iInpRawBytes = pRef->pPort->datasent; + pRef->iInpRawPackets = pRef->pPort->packsent; + pRef->iRcvRawPackets = pRef->pPort->packrcvd; + pRef->iRcvRawBytes = pRef->pPort->datarcvd; + pRef->iOverhead = pRef->pPort->overhead; + pRef->iRcvOverhead = pRef->pPort->rcvoverhead; + + // remember stat update time + pRef->NetGameLinkStats.stattick = uCurrTick; + } + + // see if we should turn off send light + if ((pRef->NetGameLinkStats.sentshow != 0) && (uCurrTick-pRef->NetGameLinkStats.sentshow > 100)) + { + pRef->NetGameLinkStats.sentshow = 0; + pRef->NetGameLinkStats.senthide = uCurrTick; + } + + // see if we should turn on send light + if ((pRef->NetGameLinkStats.senthide != 0) && (uCurrTick-pRef->NetGameLinkStats.senthide > 100) && (uCurrTick-pRef->NetGameLinkStats.sentlast < 50)) + { + pRef->NetGameLinkStats.senthide = 0; + pRef->NetGameLinkStats.sentshow = uCurrTick; + } + + // see if we should turn off receive light + if ((pRef->NetGameLinkStats.rcvdshow != 0) && (uCurrTick-pRef->NetGameLinkStats.rcvdshow > 100)) + { + pRef->NetGameLinkStats.rcvdshow = 0; + pRef->NetGameLinkStats.rcvdhide = uCurrTick; + } + + // see if we should turn on receive light + if ((pRef->NetGameLinkStats.rcvdhide != 0) && (uCurrTick-pRef->NetGameLinkStats.rcvdhide > 100) && (uCurrTick-pRef->NetGameLinkStats.rcvdlast < 50)) + { + pRef->NetGameLinkStats.rcvdhide = 0; + pRef->NetGameLinkStats.rcvdshow = uCurrTick; + } + + // send keepalive/sync if needed + if ((uCurrTick-pRef->uLastSend > NETGAMELINK_KEEPALIVE_TIME) && (pRef->bSyncEnabled == TRUE)) + { + // make sure we do not overflow output queue + int32_t iQueue = pRef->pPort->Send(pRef->pPort, NULL, 0, COMM_FLAGS_RELIABLE); + // the exact buffer limit is unimportant, but it needs something + // to avoid overrun during a semi-persistant failure + if ((iQueue > 0) && (iQueue < (pRef->pPort->maxout/4))) + { + // send sync packet + NetGamePacketT spacket; + spacket.head.kind = GAME_PACKET_SYNC; + spacket.head.len = 0; + _NetGameLinkSendPacket(pRef, &spacket, uCurrTick); + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkPrintStats + + \Description + Prints the current NetGameLink stats. + + \Input *pRef - reference pointer + + \Version 09/03/14 (mcorcoran) +*/ +/*************************************************************************************************F*/ +static void _NetGameLinkPrintStats(NetGameLinkRefT *pRef) +{ + NetGameLinkStatT NetGameLinkStats; + NetGameLinkStatus(pRef, 'stat', 0, &NetGameLinkStats, sizeof(NetGameLinkStats)); + + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: QoS Results ------------------------------------------------------------------------------\n")); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: latency over QoS period %d\n", pRef->iQosAvgLate)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: latency over last sampling period %d (sampl. prd = %d ms)\n", NetGameLinkStats.late, RTT_SAMPLE_PERIOD)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: when connection established %d\n", NetGameLinkStats.conn)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: when data most recently sent %d\n", NetGameLinkStats.sentlast)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: when data most recently received %d\n", NetGameLinkStats.rcvdlast)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: ping deviation %d\n", NetGameLinkStats.pingdev)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: ping average %d\n", NetGameLinkStats.pingavg)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of bytes sent %d\n", NetGameLinkStats.sent)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of bytes received %d\n", NetGameLinkStats.rcvd)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of packets sent to peer since start (at time = last inbound sync pkt) %d\n", NetGameLinkStats.lpacksent)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of packets sent to peer since start (at time = now) %d\n", NetGameLinkStats.lpacksent_now)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of packets received from peer since start %d\n", NetGameLinkStats.lpackrcvd)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of packets sent by peer (to us) since start %d\n", NetGameLinkStats.rpacksent)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of packets received by peer (from us) since start %d\n", NetGameLinkStats.rpackrcvd)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: local->remote packets lost: number of packets (from us) lost by peer since start %d\n", NetGameLinkStats.rpacklost)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: packets saved: number of packets recovered by via redundancy mechanisms %d\n", NetGameLinkStats.lpacksaved)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of NAKs sent by peer (to us) since start %d\n", NetGameLinkStats.rnaksent)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: packets per sec sent (user packets) %d\n", NetGameLinkStats.outpps)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: raw packets per sec sent (packets sent to network) %d\n", NetGameLinkStats.outrpps)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: bytes per sec sent (user data) %d\n", NetGameLinkStats.outbps)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: raw bytes per sec sent (data sent to network) %d\n", NetGameLinkStats.outrps)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: network bytes per sec sent (inrps + estimated UDP/Eth frame overhead) %d\n", NetGameLinkStats.outnps)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: packets per sec received (user packets) %d\n", NetGameLinkStats.inpps)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: raw packets per sec received (packets sent to network) %d\n", NetGameLinkStats.inrpps)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: bytes per sec received (user data) %d\n", NetGameLinkStats.inbps)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: raw bytes per sec received (data sent to network) %d\n", NetGameLinkStats.inrps)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: network bytes per sec received (inrps + estimated UDP/Eth frame overhead) %d\n", NetGameLinkStats.innps)); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: ------------------------------------------------------------------------------------------\n")); +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkNotify + + \Description + Main notification from lower layer + + \Input *pCommRef- generic comm pointer + \Input iEvent - event type + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +static void _NetGameLinkNotify(CommRef *pCommRef, int32_t iEvent) +{ + NetGameLinkRefT *pRef = (NetGameLinkRefT *)pCommRef->refptr; + + // make sure we are exclusive + if (NetCritTry(&pRef->Crit)) + { + // do the processing + _NetGameLinkProcess(pRef, NetTick()); + + // do callback if needed + if ((pRef->iBufLen > 0) && (pRef->pCallProc != NULL) && (pRef->iCallPending > 0)) + { + pRef->iCallPending = 0; + (pRef->pCallProc)(pRef->pCallData, 1); + } + + // free access + NetCritLeave(&pRef->Crit); + } + else + { + // count the miss + pRef->iProcess += 1; + if (pRef->iProcess > 0) + { +// NetPrintf(("netgamelink: missed %d events\n", pRef->iProcess)); + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkSendCallback + + \Description + Handle notification of send (or re-send) of data from comm layer. + + \Input *pComm - pointer to comm ref + \Input *pPacket - packet data that is being sent + \Input iPacketLen - packet length + \Input uCurrTick - current timestamp + + \Version 09/09/11 (jbrookes) +*/ +/*************************************************************************************************F*/ +static void _NetGameLinkSendCallback(CommRef *pComm, void *pPacket, int32_t iPacketLen, uint32_t uCurrTick) +{ + uint8_t *pPacketData = (uint8_t *)pPacket; + NetGamePacketSyncT Sync, *pSync; + NetGamePacketHeadT Head; + uint32_t uTickDiff; + + // does this packet include a sync packet? + if ((pPacketData[iPacketLen-1] & GAME_PACKET_SYNC) == 0) + { + return; + } + + // extract sync packet + Head.kind = pPacketData[iPacketLen-1]; + Head.len = iPacketLen-1; + if ((pSync = _NetGameLinkGetSync((NetGamePacketT *)&Head, pPacketData, &Sync)) != NULL) + { + // compare currtick with current tick + uTickDiff = NetTickDiff(uCurrTick, SocketNtohl(Sync.echo)); + + // refresh echo and repl and write back sync packet + Sync.echo = SocketHtonl(uCurrTick); + Sync.repl = SocketHtonl((SocketNtohl(Sync.repl) + uTickDiff)); + ds_memcpy(pSync, &Sync, sizeof(Sync)); + + /* Note: + Unlike echo and repl, other sync packet fields (psnt, plost, nsnt, prcvd,...) are intentionally not + refreshed here because they convey update of counts between two sync packets (they are delta values). + Any update in the values of these counters not included in this sync packet (regardless of this + packet being resent or not) will be safely covered by the next sync packet. */ + } +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkPollStream + + \Description + Poll to see if we should send stream data + + \Input *pRef - reference pointer + + \Output + int32_t - send count + + \Version 01/11/10 (jrainy) +*/ +/*************************************************************************************************F*/ +static int32_t _NetGameLinkPollStream(NetGameLinkRefT *pRef) +{ + int32_t iCount = 0, iSize, iResult; + NetGameMaxPacketT Packet; + GameStreamT Block; + NetGameLinkStreamT *pStream; + + // see if any stream has pending data + for (pStream = pRef->m_stream; pStream != NULL; pStream = pStream->pNext) + { + // see if anything to send + while (pStream->iOutProg < pStream->iOutSize) + { + int32_t iLimit; + + // get data block + ds_memcpy(&Block, pStream->pOutData+pStream->iOutProg, sizeof(Block)); + // setup data packet + Packet.head.kind = GAME_PACKET_LINK_STRM; + // set up packet 'size' field, which is size | subchannel + iSize = (Block.iSize & ~0xff000000) | ((Block.iSubchan & 0xff) << 24); + // store stream header fields in network order + Packet.body.strm.ident = SocketHtonl(pStream->iIdent); + Packet.body.strm.kind = SocketHtonl(Block.iKind); + Packet.body.strm.size = SocketHtonl(iSize); + iSize = Block.iSize -Block.iSent; + + // don't overflow our buffer + iLimit = (signed)sizeof(Packet.body.strm.data); + if (iSize > iLimit) + { + iSize = iLimit; + } + + // don't overflow the underlying layer max packet size + iLimit = pRef->iStrmMaxPktSize; + if (iSize > iLimit) + { + iSize = iLimit; + } + + ds_memcpy(Packet.body.strm.data, pStream->pOutData+pStream->iOutProg+sizeof(Block)+Block.iSent, iSize); + Packet.head.len = (uint16_t)(sizeof(Packet.body.strm)-sizeof(Packet.body.strm.data)+iSize); + // try and queue/send the packet + iResult = NetGameLinkSend(pRef, (NetGamePacketT *)&Packet, 1); + if (iResult < 0) + { + NetPrintf(("netgamelink: [%p] stream send failed for stream 0x%08x!\n", pRef, pStream)); + break; + } + else if (iResult == 0) + { + break; + } + // advance the sent count + Block.iSent += iSize; + if (Block.iSent != Block.iSize) + { + // save back updated block for next time (could just update .sent, but this is easier due to alignment) + ds_memcpy(pStream->pOutData+pStream->iOutProg, &Block, sizeof(Block)); + } + else + { + pStream->iOutProg += sizeof(Block)+Block.iSize; + // see if we should shift the buffer (more than 75% full and shift would gain >33%) + if ((pStream->iOutProg < pStream->iOutSize) && + (pStream->iOutSize*4 > pStream->iOutMaxm*3) && + (pStream->iOutProg*3 > pStream->iOutMaxm*1)) + { + // do the shift + memmove(pStream->pOutData, pStream->pOutData+pStream->iOutProg, pStream->iOutSize-pStream->iOutProg); + pStream->iOutSize -= pStream->iOutProg; + pStream->iOutProg = 0; + } + } + // count the send + ++iCount; + } + } + + // return send count + return(iCount); +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkSendStream + + \Description + Let user queue up a buffer to send + + \Input *pStream - stream to send on + \Input iSubChan - subchannel stream is to be received on + \Input iKind - kind of data (used to determine if sync) + \Input *pBuffer - pointer to data to send + \Input iLength - length of data to send + + \Output + int32_t - negative=error, zero=busy (send again later), positive=sent + + \Version 01/11/10 (jrainy) +*/ +/*************************************************************************************************F*/ +static int32_t _NetGameLinkSendStream(NetGameLinkStreamT *pStream, int32_t iSubChan, int32_t iKind, void *pBuffer, int32_t iLength) +{ + GameStreamT Block; + + // if this is an orphaned stream, return an error + if (pStream->pClient == NULL) + { + return(-1); //todo + } + + // allow negative length to mean strlen(data)+1 + if (iLength < 0) + { + for (iLength = 0; ((char *)pBuffer)[iLength] != '\0'; ++iLength) + ; + ++iLength; + } + + // make sure send buffer does not exceed total input buffer + // (do this AFTER negative length check) + if (iLength >= pStream->iInpMaxm) + { + return(-1); + } + + // see if we can reset buffer pointers + if (pStream->iOutProg >= pStream->iOutSize) + { + pStream->iOutProg = pStream->iOutSize = 0; + } + + // make sure send buffer does not exceed current output buffer + if (iLength+sizeof(GameStreamT) >= (unsigned)pStream->iOutMaxm-pStream->iOutSize) + { + return(0); + } + + // if this is a sync, make sure we have space in sync buffer + if ((iKind > '~ ') && (iLength+sizeof(GameStreamT) >= (unsigned)pStream->iSynMaxm-pStream->iSynSize)) + { + return(0); + } + + // see if this is a length query + if (pBuffer == NULL) + { + // calc main buffer space + iLength = pStream->iOutMaxm-pStream->iOutSize; + // if this is sync, limit based on sync buffer + if ((iKind > '~ ') && (iLength > pStream->iSynMaxm-pStream->iSynSize)) + { + iLength = pStream->iSynMaxm-pStream->iSynSize; + } + // subtract packet header size + iLength -= sizeof(GameStreamT); + if (iLength < 0) + { + iLength = 0; + } + // return available size + return(iLength); + } + + // setup the header block + Block.iKind = iKind; + Block.uSync = 0; + Block.iSent = 0; + Block.iSize = iLength; + Block.iSubchan = iSubChan; + + // if sync, copy into sync buffer + if (iKind > '~ ') + { + // set sync sequence number + Block.uSync = 0; + // copy into sync byffer + ds_memcpy(pStream->pSynData+pStream->iSynSize, &Block, sizeof(Block)); + ds_memcpy(pStream->pSynData+pStream->iSynSize+sizeof(Block), pBuffer, iLength); + pStream->iSynSize += sizeof(Block)+iLength; + } + + // do a memmove just in case data source is already in this buffer + // (this is an optimization used for database access) + memmove(pStream->pOutData+pStream->iOutSize+sizeof(Block), pBuffer, iLength); + ds_memcpy(pStream->pOutData+pStream->iOutSize, &Block, sizeof(Block)); + pStream->iOutSize += sizeof(Block)+iLength; + + if (pStream->iOutSize > pStream->iHighWaterUsed) + { + pStream->iHighWaterUsed = pStream->iOutSize; + } + if ((pStream->iOutSize - pStream->iOutProg) > pStream->iHighWaterNeeded) + { + pStream->iHighWaterNeeded = pStream->iOutSize - pStream->iOutProg; + } + + // attempt immediate send + _NetGameLinkPollStream(pStream->pClient); + + // return send status + return(iLength); +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkRecvStream + + \Description + Process a received stream packet + + \Input *pRef - reference pointer + \Input *pPacket - packet to process + + \Output + int32_t - -1=no match, -2=overflow error, 0=valid packet received + + \Version 01/11/10 (jrainy) +*/ +/*************************************************************************************************F*/ +static int32_t _NetGameLinkRecvStream(NetGameLinkRefT *pRef, NetGamePacketT *pPacket) +{ + int32_t iRes = -1, iSize; + NetGameLinkStreamT *pStream; + NetGameLinkStreamInpT *pInp; + int32_t iSubChannel; + + // see if this packet matches any of the streams + for (pStream = pRef->m_stream; pStream != NULL; pStream = pStream->pNext) + { + // see if this is the matching stream + if (pStream->iIdent == pPacket->body.strm.ident) + { + // extract size/subchannel from 'size' member + iSize = pPacket->body.strm.size & ~0xff000000; + iSubChannel = (unsigned)pPacket->body.strm.size >> 24; + if ((iSubChannel < 0) || (iSubChannel >= (pStream->iSubchan+1))) + { + NetPrintf(("netgamelink: [%p] warning, received packet on stream '%C' with invalid packet subchannel %d\n", pRef, pPacket->body.strm.ident, iSubChannel)); + return(-1); + } + // ref channel + pInp = pStream->pInp + iSubChannel; + // default to overflow error + iRes = GMDIST_OVERFLOW; + // validate the packet size + if (iSize <= pStream->iInpMaxm) + { + /* auto-clear packet if anything looks inconsistant -- note that this is expected behavior + for the first packet fragment in a sequence of packet fragments */ + if ((pPacket->body.strm.kind != pInp->iInpKind) || (iSize != pInp->iInpSize)) + { + // reset progress + pInp->iInpProg = 0; + // save basic packet header + pInp->iInpKind = pPacket->body.strm.kind; + pInp->iInpSize = iSize; + } + // figure out size of data in packet + iSize = pPacket->head.len-(sizeof(pPacket->body.strm)-sizeof(pPacket->body.strm.data)); + if (iSize > pInp->iInpSize-pInp->iInpProg) + { + NetPrintf(("netgamelink: [%p] clamped stream packet size from %d to %d on stream '%C'\n", pRef, iSize, pInp->iInpSize-pInp->iInpProg, pPacket->body.strm.ident)); + iSize = pInp->iInpSize-pInp->iInpProg; + } + // append to existing packet + ds_memcpy(pInp->pInpData+pInp->iInpProg, pPacket->body.strm.data, iSize); + pInp->iInpProg += iSize; + // see if packet is complete + if (pInp->iInpProg == pInp->iInpSize) + { + // deliver packet + if (pStream->Recv != NULL) + { + pStream->Recv(pStream, iSubChannel, pInp->iInpKind, pInp->pInpData, pInp->iInpSize); + } + else + { + NetPrintf(("netgamelink: [%p] no registered stream recv handler on stream '%C'\n", pRef, pPacket->body.strm.ident)); + } + // clear from buffer + pInp->iInpProg = 0; + } + // packet was valid + iRes = 0; + } + else + { + NetPrintf(("netgamelink: [%p] invalid stream packet size (%d >= %d) on stream '%C'\n", + pRef, pPacket->body.strm.size, pStream->iInpMaxm, pPacket->body.strm.ident)); + } + return(iRes); + } + } + + // didn't find the stream + NetPrintf(("netgamelink: [%p] could not find stream for stream packet with iIdent '%C'\n", pRef, pPacket->body.strm.ident)); + + // return result + return(iRes); +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkUpdateQos + + \Description + Send and receive QoS packets. + + \Input *pRef - reference pointer + + \Version 09/03/14 (mcorcoran) +*/ +/*************************************************************************************************F*/ +static void _NetGameLinkUpdateQos(NetGameLinkRefT *pRef) +{ + NetGameMaxPacketT QosPacket; + uint32_t uNow = NetTick(); + + // save QoS start time + if (pRef->uQosStartTime == 0) + { + pRef->uQosStartTime = uNow; + } + + // init QoS send time + if ((pRef->uQosPacketsToSend > 0) && (pRef->uQosLastSendTime == 0)) + { + /* We are now expecting QoS packet from the other end of the link. + Make this value non-zero to satisfy the while() condition below. */ + pRef->uQosPacketsToRecv = 1; + pRef->uQosLastSendTime = uNow - (pRef->uQosSendInterval+1); + } + + // check if it's time to send a QoS packet. + if ((pRef->uQosPacketsToSend > 0) && (NetTickDiff(uNow, pRef->uQosLastSendTime) > (signed)pRef->uQosSendInterval)) + { + // set qos last send time (reserve zero for uninitialized) + pRef->uQosLastSendTime = (uNow != 0) ? uNow : uNow-1; + pRef->uQosPacketsToSend -= 1; + + // create and send the packet + QosPacket.head.kind = GAME_PACKET_USER; + QosPacket.head.len = pRef->uQosPacketSize; + + ds_memclr(QosPacket.body.data, QosPacket.head.len); + + // the other end of the link needs to know how many more packets to expect. + QosPacket.body.data[0] = (uint8_t)(pRef->uQosPacketsToSend >> 8); + QosPacket.body.data[1] = (uint8_t)(pRef->uQosPacketsToSend >> 0); + + NetGameLinkSend(pRef, (NetGamePacketT*)&QosPacket, 1); + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: [%p] sent QoS packet, size(%d), remaining packets to send (%d)\n", pRef, QosPacket.head.len, pRef->uQosPacketsToSend)); + } + + while ((pRef->uQosPacketsToRecv > 0) && NetGameLinkRecv(pRef, (NetGamePacketT*)&QosPacket, 1, FALSE)) + { + // the other end of the link tells us how many more packets to expect (wait for before transitioning to active) + pRef->uQosPacketsToRecv = + (QosPacket.body.data[0] << 8) | + (QosPacket.body.data[1] << 0); + + NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: [%p] received QoS packet, size(%d), remaining packets to recv (%d)\n", pRef, QosPacket.head.len, pRef->uQosPacketsToRecv)); + } + + // have we finished sending and receiving everything yet? + if ((pRef->uQosPacketsToSend == 0) && (pRef->uQosPacketsToRecv == 0)) + { + // we're done, print available stats + _NetGameLinkPrintStats(pRef); + } + + // only start averaging latency when 1 sample period is complete + if (NetTickDiff(uNow, pRef->uQosStartTime) > RTT_SAMPLE_PERIOD) + { + pRef->iQosCumulLate += pRef->NetGameLinkStats.late; + pRef->iQosLateCount++; + pRef->iQosAvgLate = pRef->iQosCumulLate / pRef->iQosLateCount; + } +} + +/*F*************************************************************************************************/ +/*! + \Function _NetGameLinkDrainRecvStream + + \Description + Need for streams handling. Received all data on the stream. + + \Input *pRef - reference pointer + + \Version 09/03/14 (mcorcoran) +*/ +/*************************************************************************************************F*/ +static void _NetGameLinkDrainRecvStream(NetGameLinkRefT *pRef) +{ + NetGameMaxPacketT Packet; + + while (NetGameLinkRecv2(pRef, (NetGamePacketT*)&Packet, 1, 1 << GAME_PACKET_LINK_STRM)) + { + // convert stream header from network to host order + Packet.body.strm.ident = SocketNtohl(Packet.body.strm.ident); + Packet.body.strm.kind = SocketNtohl(Packet.body.strm.kind); + Packet.body.strm.size = SocketNtohl(Packet.body.strm.size); + + // process the packet + _NetGameLinkRecvStream(pRef, (NetGamePacketT *)&Packet); + } +} + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkCreate + + \Description + Construct the game client + + \Input *pCommRef - the connection from NetGameUtilComplete() + \Input iOwner - if TRUE, NetGameLink will assume ownership of the port (ie, delete it when done) + \Input iBufLen - length of input buffer + + \Output + NetGameLinkRefT * - pointer to new NetGameLinkRef + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +NetGameLinkRefT *NetGameLinkCreate(void *pCommRef, int32_t iOwner, int32_t iBufLen) +{ + int32_t iIndex; + uint32_t uTick; + CommRef *pPort = pCommRef; + NetGameLinkRefT *pRef; + int32_t iMemGroup; + void *pMemGroupUserData; + NetGameMaxPacketT Packet; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pRef = DirtyMemAlloc(sizeof(*pRef), NETGAMELINK_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("netgamelink: unable to allocate module state\n")); + return(NULL); + } + ds_memclr(pRef, sizeof(*pRef)); + pRef->iMemGroup = iMemGroup; + pRef->pMemGroupUserData = pMemGroupUserData; + + // assign port info + pRef->pPort = pPort; + pRef->iPortOwner = iOwner; + + // allocate input buffer + if (iBufLen < 4096) + { + iBufLen = 4096; + } + pRef->pBuffer = DirtyMemAlloc(iBufLen, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + pRef->iBufMax = iBufLen; + + // reset the timing info + pRef->iRttAvg = 0; + pRef->iRttDev = 0; + + // setup connection time for reference + uTick = NetTick(); + pRef->NetGameLinkStats.conn = uTick; + pRef->NetGameLinkStats.senthide = uTick; + pRef->NetGameLinkStats.rcvdhide = uTick; + pRef->NetGameLinkStats.rcvdlast = uTick; + pRef->NetGameLinkStats.sentlast = uTick; + pRef->NetGameLinkStats.stattick = uTick; + pRef->NetGameLinkStats.isconn = FALSE; + pRef->NetGameLinkStats.isopen = FALSE; + + // fill ping history with bogus starting data + for (iIndex = 0; iIndex < PING_HISTORY; ++iIndex) + { + pRef->NetGameLinkStats.pinghist[iIndex].min = PING_DEFAULT; + pRef->NetGameLinkStats.pinghist[iIndex].max = PING_DEFAULT; + pRef->NetGameLinkStats.pinghist[iIndex].avg = PING_DEFAULT; + pRef->NetGameLinkStats.pinghist[iIndex].cnt = 1; + } + + // setup critical section + NetCritInit(&pRef->Crit, "netgamelink"); + + // setup for callbacks + pPort->refptr = pRef; + pPort->Callback(pPort, _NetGameLinkNotify); + pPort->SendCallback = _NetGameLinkSendCallback; + + // default sending and syncs to enabled + pRef->bSendEnabled = TRUE; + pRef->bSyncEnabled = TRUE; + + // calculate the max packet size based on the cudp max width (minus the stream overhead) + pRef->iStrmMaxPktSize = pPort->maxwid - (sizeof(Packet.body.strm) - sizeof(Packet.body.strm.data)); + + // other QoS items memset() to zero above + pRef->uQosSendInterval = NETGAMELINK_QOS_INTERVAL_MIN; + pRef->uQosPacketSize = NETGAME_DATAPKT_MAXSIZE; + + pRef->iVerbosity = 1; + + return(pRef); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkDestroy + + \Description + Destruct the game client + + \Input *pRef - reference pointer + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameLinkDestroy(NetGameLinkRefT *pRef) +{ + // dont need callback + pRef->pPort->Callback(pRef->pPort, NULL); + + // we own the port -- get rid of it + if (pRef->iPortOwner) + pRef->pPort->Destroy(pRef->pPort); + + // free receive buffer + DirtyMemFree(pRef->pBuffer, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + + // free critical section + NetCritKill(&pRef->Crit); + + // free our memory + DirtyMemFree(pRef, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkCallback + + \Description + Register a callback function + + \Input *pRef - reference pointer + \Input *pCallData - caller reference data + \Input *pCallProc - callback function + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameLinkCallback(NetGameLinkRefT *pRef, void *pCallData, void (*pCallProc)(void *pCallData, int32_t iKind)) +{ + pRef->pCallData = pCallData; + pRef->pCallProc = pCallProc; +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkStatus + + \Description + Return current link status + + \Input *pRef - reference pointer + \Input iSelect - selector + \Input iValue - input value + \Input pBuf - output buffer + \Input iBufSize - output buffer size + + \Output + int32_t - selector specific return value + + \Notes + iSelect can be one of the following: + + \verbatim + 'mwid' - returns the commudp packet max width field + 'qlat' - average latency calculated during the initial qos phase + 'sinf' - returns SocketInfo() with iValue being the status selector (iData param is unsupported) + 'slen' - returns current length of send queue + 'sque' - returns TRUE if send queue is empty, else FALSE + 'stat' - Fills out a NetGameLinkStatT with QoS info. pBuf=&NetGameLinkStatT, iBufSize=sizeof(NetGameLinkStatT) + \endverbatim + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetGameLinkStatus(NetGameLinkRefT *pRef, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + // read commudp mwid field + if (iSelect == 'mwid') + { + return(pRef->pPort->maxwid); + } + if (iSelect == 'qlat') + { + return(pRef->iQosAvgLate); + } + if ((iSelect == 'sinf') && (pRef->pPort != NULL) && (pRef->pPort->sockptr != NULL)) + { + return(SocketInfo(pRef->pPort->sockptr, iValue, 0, pBuf, iBufSize)); + } + if ((iSelect == 'sque') || (iSelect == 'slen')) + { + // get output queue position + int32_t iQueue = pRef->pPort->Send(pRef->pPort, NULL, 0, COMM_FLAGS_RELIABLE); + + if (iSelect == 'sque') + { + // if output queue position is one send queue is empty (assume error=empty) + iQueue = (iQueue > 0) ? (iQueue == 1) : TRUE; + } + return(iQueue); + } + if (iSelect == 'stat') + { + volatile uint32_t uSeqn; + uint32_t uPortStat; + + do + { + // remember pre-update ticks + uSeqn = pRef->NetGameLinkStats.tickseqn; + + // update tick is same as movement tick + pRef->NetGameLinkStats.tick = NetTick(); + + // if things changed during out assignment, do it again + } while (pRef->NetGameLinkStats.tickseqn != uSeqn); + + // update port status + uPortStat = pRef->pPort->Status(pRef->pPort); + pRef->NetGameLinkStats.isopen = ((uPortStat == COMM_CONNECTING) || (uPortStat == COMM_ONLINE)) ? TRUE : FALSE; + pRef->NetGameLinkStats.isopen = pRef->NetGameLinkStats.isopen && (pRef->uQosPacketsToSend == 0) && (pRef->uQosPacketsToRecv == 0); + + // update packet sent counter + pRef->NetGameLinkStats.lpacksent_now = pRef->pPort->packsent; + + // make sure user-provided buffer is large enough to receive a pointer + if ((pBuf != NULL) && (iBufSize >= (int32_t)sizeof(NetGameLinkStatT))) + { + ds_memcpy(pBuf, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT)); + return(0); + } + else + { + // unhandled + return(-1); + } + } + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkSend + + \Description + Handle incoming packet stream from upper layer + + \Input *pRef - reference pointer + \Input *pPkt - packet list (1 or more) + \Input iLen - size packet list (1=one packet) + + \Output + int32_t - negative=bad error, zero=unable to send now (overflow), positive=bytes sent + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetGameLinkSend(NetGameLinkRefT *pRef, NetGamePacketT *pPkt, int32_t iLen) +{ + int32_t iCnt = 0, iErr; + uint32_t uCurrTick = NetTick(); + + // get exclusive access + NetCritEnter(&pRef->Crit); + + // see if we need to handle missed event + if (pRef->iProcess > 0) + { + NetPrintf(("netgamelink: processing missed event\n")); + _NetGameLinkProcess(pRef, uCurrTick); + } + + // walk the packet list + while (iLen > 0) + { + if ((iErr = _NetGameLinkSendPacket(pRef, pPkt, uCurrTick)) <= 0) + { + // don't spam on overflow + if (iErr != 0) + { + NetPrintf(("netgamelink: error %d sending packet\n", iErr)); + } + // should _NetGameLinkSendPacket fail at first send attempt, return err to caller. + if (iCnt == 0) + { + iCnt = iErr; + } + break; + } + + // calc packet size for them + pPkt->head.size = (sizeof(pPkt->head)+pPkt->head.len+sizeof(NetGamePacketSyncT)+3)&0x7ffc; + // count the packet size + iCnt += pPkt->head.size; + // stop if this was single packet + if (iLen == 1) + { + break; + } + + // skip to next packet + iLen -= pPkt->head.size; + pPkt = (NetGamePacketT *)(((char *)pPkt)+pPkt->head.size); + } + + // release exclusive access + NetCritLeave(&pRef->Crit); + + // return bytes sent + return(iCnt); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkPeek2 + + \Description + Peek into the buffer, for the first packet type matching mask + + \Input *pRef - reference pointer + \Input **ppPkt - (optional) storage for pointer to current packet data + \Input uMask - which packet types we want. bitmask, addressed by GAME_PACKET values + + \Output + int32_t - buffer size + + \Version 08/08/11 (jrainy) +*/ +/*************************************************************************************************F*/ +int32_t NetGameLinkPeek2(NetGameLinkRefT *pRef, NetGamePacketT **ppPkt, uint32_t uMask) +{ + int32_t iBufLen = pRef->iBufLen; + int32_t iIndex, iKind; + NetGamePacketT *pPkt0; + int32_t iPktSize = 0; + + // I do not understand the reason for && (iBufLen > 0). + // Kept to maintain current behaviour + // TODO: investigate + if ((ppPkt != NULL) && (iBufLen > 0)) + { + // while going through our buffer of received packets + for (iIndex = 0; iIndex < pRef->iBufLen;) + { + // extract size of current head packet + pPkt0 = (NetGamePacketT *)(pRef->pBuffer + iIndex); + iPktSize = pPkt0->head.size; + + // if the current packet matches what we want + + iKind = (((NetGamePacketT *)(pRef->pBuffer + iIndex))->head.kind) & ~GAME_PACKET_SYNC; + + if (uMask & (1 << iKind)) + { + *ppPkt = pPkt0; + break; + } + else + { + // skip over the non-matching packets. + iIndex += iPktSize; + } + } + } + else if (ppPkt != NULL) + { + *ppPkt = NULL; + } + + + // return buffer size + return(iBufLen); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkPeek + + \Description + Peek into the buffer + + \Input *pRef - reference pointer + \Input **ppPkt - (optional) storage for pointer to current packet data + + \Output + int32_t - buffer size + + \Version 12/19/00 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetGameLinkPeek(NetGameLinkRefT *pRef, NetGamePacketT **ppPkt) +{ + return(NetGameLinkPeek2(pRef, ppPkt, (unsigned)~0)); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkRecv2 + + \Description + Outgoing packet stream to upper layer + + \Input *pRef - reference pointer + \Input *pBuf - storage for packet list (1 or more) + \Input iLen - size packet list (1=one packet) + \Input uMask - which packet types we wanted. bitmask, addressed by GAME_PACKET values + + \Output + int32_t - 0=no data available, else number of packets received + + \Version 01/11/10 (jrainy) +*/ +/*************************************************************************************************F*/ +int32_t NetGameLinkRecv2(NetGameLinkRefT *pRef, NetGamePacketT *pBuf, int32_t iLen, uint32_t uMask) +{ + NetGamePacketT *pPkt; + uint32_t uCurrTick = NetTick(); + int32_t iIndex, iKind; + int32_t iLenRead = 0; + int32_t iPktSize = 0; + + // disable callback + NetCritEnter(&pRef->Crit); + + // if no data in queue, check with lower layer + if (pRef->iBufLen == 0) + { + while (_NetGameLinkRecvPacket(pRef, uCurrTick) > 0) + ; + } + + // see if there is anything to process + if (pRef->iBufLen > 0) + { + // while going through our buffer of received packets + for (iIndex = 0; iIndex < pRef->iBufLen;) + { + // extract size of current head packet + pPkt = (NetGamePacketT *)(pRef->pBuffer + iIndex); + iPktSize = pPkt->head.size; + + // don't access pkt past here, as the memmove will overwrite it + + // if the current packet matches what we want + + iKind = (((NetGamePacketT *)(pRef->pBuffer + iIndex))->head.kind) & ~GAME_PACKET_SYNC; + + if (uMask & (1 << iKind)) + { + // if we can fit it in + if ((iPktSize <= iLen) || (iLen == 1)) + { + // copy the packet to return buffer + ds_memcpy(pBuf, pRef->pBuffer + iIndex, iPktSize); + iLen -= iPktSize; + pBuf = (NetGamePacketT *)(((char*)pBuf) + iPktSize); + iLenRead += iPktSize; + + // remove from our list of received packets + memmove(pRef->pBuffer + iIndex, pRef->pBuffer + iIndex + iPktSize, pRef->iBufLen - iIndex - iPktSize); + pRef->iBufLen -= iPktSize; + + // if len was 1, it's now negative, we got our packet, bail out + if (iLen < 0) + { + break; + } + } + else + { + // bail out, we got all we could + break; + } + } + else + { + // skip over the non-matching packets. + iIndex += iPktSize; + } + } + } + + // enable access + NetCritLeave(&pRef->Crit); + + // return length + return(iLenRead); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkRecv + + \Description + Outgoing packet stream to upper layer + + \Input *pRef - reference pointer + \Input *pBuf - storage for packet list (1 or more) + \Input iLen - size packet list (1=one packet) + \Input bDist - whether dist packets are to be received. Use FALSE. + + \Output + int32_t - 0=no data available, else number of packets received + + \Version 01/11/10 (jrainy) +*/ +/*************************************************************************************************F*/ +int32_t NetGameLinkRecv(NetGameLinkRefT *pRef, NetGamePacketT *pBuf, int32_t iLen, uint8_t bDist) +{ + uint32_t uDistMask = (1 << GAME_PACKET_INPUT) | + (1 << GAME_PACKET_INPUT_MULTI) | + (1 << GAME_PACKET_INPUT_MULTI_FAT) | + (1 << GAME_PACKET_STATS) | + (1 << GAME_PACKET_INPUT_FLOW) | + (1 << GAME_PACKET_INPUT_META); + + return(NetGameLinkRecv2(pRef, pBuf, iLen, (bDist ? uDistMask : ~uDistMask) & ~(1 << GAME_PACKET_LINK_STRM))); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkControl + + \Description + NetGameLink control function. Different selectors control different behaviors. + + \Input *pRef - reference pointer + \Input iControl - control selector + \Input iValue - selector-specific value + \Input pValue - selector-specific pointer + + \Output + int32_t - selector specific, or negative if failure + + \Notes + iControl can be one of the following: + + \verbatim + lqos: set individual QoS packet size + rlmt: set redundant packet size limit + send: enable/disable sending + spam: set debug verbosity level + sqos: set QoS duration and interval + sync: enable/disable sync packets + \endverbatim + + Unhandled selectors are passed through to the underlying comm module + + \Version 07/07/03 (jbrookes) +*/ +/*************************************************************************************************F*/ +int32_t NetGameLinkControl(NetGameLinkRefT *pRef, int32_t iControl, int32_t iValue, void *pValue) +{ + // set individual QoS packet size + if (iControl == 'lqos') + { + if ((iValue < NETGAMELINK_QOS_PACKETSIZE_MIN) || (iValue > NETGAME_DATAPKT_MAXSIZE)) + { + NetPrintf(("netgamelink: [%p] invalid QoS packet size specified (%d). QoS packets must be >= %d and <= %d\n", pRef, iValue, NETGAMELINK_QOS_PACKETSIZE_MIN, NETGAME_DATAPKT_MAXSIZE)); + iValue = (iValue < NETGAMELINK_QOS_PACKETSIZE_MIN ? NETGAMELINK_QOS_PACKETSIZE_MIN : iValue); + iValue = (iValue > NETGAME_DATAPKT_MAXSIZE ? NETGAME_DATAPKT_MAXSIZE : iValue); + } + NetPrintf(("netgamelink: [%p] QoS packet size set to %d\n", pRef, iValue)); + pRef->uQosPacketSize = iValue; + return(0); + } + // set redundant packet size limit + if (iControl == 'rlmt') + { + NetPrintf(("netgamelink: [%p] updating redundant packet size limit for underlying comm (%d)\n", pRef, iValue)); + return(pRef->pPort->Control(pRef->pPort, iControl, iValue, pValue)); + } + // set send/recv value + if (iControl == 'send') + { + pRef->bSendEnabled = iValue; + return(1); + } + // set debug verbosity level + if (iControl == 'spam') + { + if (iValue >= 0 && iValue <= 5) + { + pRef->iVerbosity = iValue; + return(1); + } + return(0); + } + // set QoS duration and interval + if (iControl == 'sqos') + { + int32_t iValue2 = *(int32_t*)pValue; + + if (pRef->uQosLastSendTime != 0) + { + NetPrintf(("netgamelink: [%p] cannot change QoS duration or interval while QoS is in progress or is already finished.", pRef)); + return(-1); + } + + if ((iValue < NETGAMELINK_QOS_DURATION_MIN) || (iValue > NETGAMELINK_QOS_DURATION_MAX)) + { + NetPrintf(("netgamelink: [%p] invalid QoS duration period provided (%d). QoS duration must be >= %d and <= %s ms\n", pRef, iValue, NETGAMELINK_QOS_DURATION_MIN, NETGAMELINK_QOS_DURATION_MAX)); + iValue = (iValue < NETGAMELINK_QOS_DURATION_MIN ? NETGAMELINK_QOS_DURATION_MIN : iValue); + iValue = (iValue > NETGAMELINK_QOS_DURATION_MAX ? NETGAMELINK_QOS_DURATION_MAX : iValue); + } + if ((iValue != 0) && ((iValue2 < NETGAMELINK_QOS_INTERVAL_MIN) || (iValue2 > iValue))) + { + NetPrintf(("netgamelink: [%p] invalid QoS interval provided (%d). QoS interval must be >= %d and <= 'QoS duration period'\n", pRef, iValue2, NETGAMELINK_QOS_INTERVAL_MIN)); + iValue2 = (iValue2 < NETGAMELINK_QOS_INTERVAL_MIN ? NETGAMELINK_QOS_INTERVAL_MIN : iValue2); + iValue2 = (iValue2 > iValue ? iValue : iValue2); + } + NetPrintf(("netgamelink: [%p] QoS duration period set to %d ms %s\n", pRef, iValue, ((iValue == 0) ? "(QoS disabled)" : ""))); + + pRef->uQosPacketsToSend = (iValue == 0 ? 0 : iValue / iValue2); + NetPrintf(("netgamelink: [%p] QoS packet count set to %d packets %s\n", pRef, pRef->uQosPacketsToSend, ((pRef->uQosPacketsToSend == 0) ? "(QoS disabled)" : ""))); + + pRef->uQosSendInterval = iValue2; + NetPrintf(("netgamelink: [%p] QoS interval set to %d ms\n", pRef, pRef->uQosSendInterval)); + + return(0); + } + // queue checks (Deprecated use netgamelinkstatus versions instead) $$todo Remove in future release + if ((iControl == 'sque') || (iControl == 'slen')) + { + // get output queue position + int32_t iQueue = pRef->pPort->Send(pRef->pPort, NULL, 0, COMM_FLAGS_RELIABLE); + + if (iControl == 'sque') + { + // if output queue position is one send queue is empty (assume error=empty) + iQueue = (iQueue > 0) ? (iQueue == 1) : TRUE; + } + return(iQueue); + } + // enable/disable sync packets + if (iControl == 'sync') + { + pRef->bSyncEnabled = iValue; + return(0); + } + // unhandled; pass through to comm module if available + return((pRef->pPort != NULL) ? pRef->pPort->Control(pRef->pPort, iControl, iValue, pValue) : -1); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkUpdate + + \Description + Provides NetGameLink with time, at regular interval. Need for streams handling. + Also handles QoS stage when during link startup. + + \Input *pRef - reference pointer + + \Output + uint32_t - zero + + \Version 01/11/10 (jrainy) +*/ +/*************************************************************************************************F*/ +uint32_t NetGameLinkUpdate(NetGameLinkRefT *pRef) +{ + // Are we currently in the QoS phase? + if ((pRef->uQosPacketsToSend > 0) || (pRef->uQosPacketsToRecv > 0)) + { + // Send and receive QoS packets + _NetGameLinkUpdateQos(pRef); + } + else + { + // Handle normal link data + _NetGameLinkDrainRecvStream(pRef); + + _NetGameLinkPollStream(pRef); + } + + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkCreateStream + + \Description + Allocate a stream + + \Input *pRef - module state reference + \Input iSubChan - number of subchannels (zero for a normal stream) + \Input iIdent - unique stream iIdent + \Input iInpLen - size of input buffer + \Input iOutLen - size of output buffer + \Input iSynLen - size of sync buffer + + \Output + NetGameLinkStreamT * - new stream, or NULL if the iIdent was not unique + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +NetGameLinkStreamT *NetGameLinkCreateStream(NetGameLinkRefT *pRef, int32_t iSubChan, int32_t iIdent, int32_t iInpLen, int32_t iOutLen, int32_t iSynLen) +{ + NetGameLinkStreamT *pStream; + int32_t iInpSize; + char *pInpData; + + // make sure the pipe identifier is unique + for (pStream = pRef->m_stream; pStream != NULL; pStream = pStream->pNext) + { + // dont create a duplicate stream + if (pStream->iIdent == iIdent) + { + NetPrintf(("netgamelink: [%p] error -- attempting to create duplicate stream '%c%c%c%c'\n", + pRef, (uint8_t)(iIdent>>24), (uint8_t)(iIdent>>16), (uint8_t)(iIdent>>8), (uint8_t)iIdent)); + return(NULL); + } + } + + // allocate a pipe record + if ((pStream = (NetGameLinkStreamT *) DirtyMemAlloc(sizeof(*pStream), NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData)) == NULL) + { + NetPrintf(("netgamelink: [%p] unable to allocate stream\n", pRef)); + return(NULL); + } + ds_memclr(pStream, sizeof(*pStream)); + + // setup the data fields + pStream->pClient = pRef; + pStream->iIdent = iIdent; + pStream->iSubchan = iSubChan; + pStream->Send = _NetGameLinkSendStream; + pStream->Recv = NULL; + + // allocate input buffer plus input buffer tracking structure(s) + pStream->iInpMaxm = iInpLen; + iInpSize = sizeof(*pStream->pInp) * (pStream->iSubchan + 1); + pStream->pInp = DirtyMemAlloc(iInpSize + (iInpLen * (pStream->iSubchan + 1)), NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + ds_memclr(pStream->pInp, iInpSize); + for (iSubChan = 0, pInpData = (char *)pStream->pInp + iInpSize; iSubChan < pStream->iSubchan+1; iSubChan++) + { + pStream->pInp[iSubChan].pInpData = pInpData; + pInpData += iInpLen; + } + + // allocate output buffer + if (iOutLen < iInpLen) + { + iOutLen = iInpLen; + } + pStream->iOutMaxm = iOutLen; + pStream->pOutData = DirtyMemAlloc(iOutLen, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + + // allocate sync buffer + pStream->iSynMaxm = iSynLen; + if (iSynLen > 0) + { + pStream->pSynData = DirtyMemAlloc(iSynLen, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + } + + // put into list + pStream->pNext = pRef->m_stream; + pRef->m_stream = pStream; + return(pStream); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameLinkDestroyStream + + \Description + Destroy a stream + + \Input *pRef - reference pointer + \Input *pStream - pointer to stream to destroy + + \Version 12/20/00 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameLinkDestroyStream(NetGameLinkRefT *pRef, NetGameLinkStreamT *pStream) +{ + NetGameLinkStreamT **ppLink; + + // make sure stream is valid + if (pStream != NULL) + { + // if stream is active, remove the link + if (pStream->pClient != NULL) + { + // locate the stream in the list for removal + for (ppLink = &pStream->pClient->m_stream; *ppLink != pStream; ppLink = &((*ppLink)->pNext)) + { + // see if at end of list + if (*ppLink == NULL) + { + return; + } + } + // remove stream from list + *ppLink = pStream->pNext; + } + + // now the tricky part-- make sure nobody is still processing this stream + + // release the resources + if (pStream->pSynData != NULL) + { + DirtyMemFree(pStream->pSynData, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + } + DirtyMemFree(pStream->pInp, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + DirtyMemFree(pStream->pOutData, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + DirtyMemFree(pStream, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + } +} + diff --git a/src/thirdparty/dirtysdk/source/game/netgameutil.c b/src/thirdparty/dirtysdk/source/game/netgameutil.c new file mode 100644 index 00000000..a9fae798 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/game/netgameutil.c @@ -0,0 +1,711 @@ +/*H*************************************************************************************************/ +/*! + + \File netgameutil.c + + \Description + This module provides the setup required to bring peer-peer networking + online. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2001-2002. ALL RIGHTS RESERVED. + + \Version 1.0 01/09/01 (GWS) First Version + +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/comm/commall.h" +#include "DirtySDK/comm/commudp.h" +#include "DirtySDK/comm/commsrp.h" +#include "DirtySDK/proto/protoadvt.h" +#include "DirtySDK/game/netgameutil.h" +#include "DirtySDK/game/netgamepkt.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! netgameutil internal state +struct NetGameUtilRefT +{ + //! module memory group + int32_t memgroup; + void *memgrpusrdata; + + //! mac->name translation table + char *table; + //! class (unique to app) + char kind[32]; + //! service address list (64-->128 7/25/05 to fix ConnApi overrun GWS) + char addr[128]; + + //! advert ref, for broadcasting hosting info + ProtoAdvtRef *advt; + //! advertising ref, for connecting + ProtoAdvtRef *find; + + //! hosting status: 0=hosting, 1=joining + int32_t hosting; + + //! host ip + uint32_t hostip; + //! host port + uint32_t hostport; + //! peer ip + uint32_t peerip; + //! peer port + uint32_t peerport; + + //! socket ref + SocketT *pSocket; + + //! max packet width + int32_t maxwid; + + //! size of send buffer in packets + int32_t maxout; + + //! size of receive buffer in packets + int32_t maxinp; + + //! unacknowledged packet window + int32_t unacklimit; + + //! advertising frequency, in seconds + int32_t advtfreq; + + //! client identifier (zero == none) + int32_t clientid; + + //! remote client identifier + int32_t rclientid; + + //! metatype + int32_t metatype; + + //! construct function (used for AUTO mode) + CommAllConstructT *pConstruct; + + //! commref of connection, or NULL if no connection + CommRef *comm; + + uint8_t uLocalAdvt; +}; + + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +// Constants + +// Public variables + + +/*** Private Functions *****************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function _NetGameUtilAdvtConstruct + + \Description + Create the ProtoAdvt module. + + \Input *pRef - pointer to module state + \Input iSize - ProtoAdvtConstruct() parameter + \Input bConnect - TRUE if constructing advt ref for connecting, FALSE if constructing advt ref for broadcasting hosting info + + \Output + ProtoAdvtRefT * - pointer to new advertising module + + \Version 10/13/2005 (jbrookes) +*/ +/*************************************************************************************F*/ +static ProtoAdvtRef *_NetGameUtilAdvtConstruct(NetGameUtilRefT *pRef, int32_t iSize, uint32_t bConnect) +{ + ProtoAdvtRef **ppAdvt = bConnect ? &pRef->find : &pRef->advt; + + if (*ppAdvt == NULL) + { + DirtyMemGroupEnter(pRef->memgroup, pRef->memgrpusrdata); + *ppAdvt = ProtoAdvtConstruct(iSize); + DirtyMemGroupLeave(); + } + return(*ppAdvt); +} + + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilCreate + + \Description + Construct the game setup module + + \Output + NetGameUtilRefT * - reference pointer + + \Version 01/09/01 (GWS) +*/ +/*************************************************************************************************F*/ +NetGameUtilRefT *NetGameUtilCreate(void) +{ + NetGameUtilRefT *pRef; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pRef = DirtyMemAlloc(sizeof(*pRef), NETGAMEUTIL_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("netgameutil: unable to allocate module state\n")); + return(NULL); + } + ds_memclr(pRef, sizeof(*pRef)); + pRef->memgroup = iMemGroup; + pRef->memgrpusrdata = pMemGroupUserData; + + // set default comm buffer parameters + NetGameUtilControl(pRef, 'mwid', NETGAME_DATAPKT_DEFSIZE); + NetGameUtilControl(pRef, 'minp', NETGAME_DATABUF_MAXSIZE); + NetGameUtilControl(pRef, 'mout', NETGAME_DATABUF_MAXSIZE); + + // return state + return(pRef); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilDestroy + + \Description + Destroy the game setup module + + \Input *ref - reference pointer + + \Version 01/09/01 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameUtilDestroy(NetGameUtilRefT *ref) +{ + // reset + NetGameUtilReset(ref); + // done with local state + DirtyMemFree(ref, NETGAMEUTIL_MEMID, ref->memgroup, ref->memgrpusrdata); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilReset + + \Description + Reset the game setup module + + \Input *ref - reference pointer + + \Version 01/09/01 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameUtilReset(NetGameUtilRefT *ref) +{ + // release comm module + if (ref->comm != NULL) + { + ref->comm->Destroy(ref->comm); + ref->comm = NULL; + } + // kill advertisements + if (ref->find != NULL) + { + ProtoAdvtDestroy(ref->find); + ref->find = NULL; + } + if (ref->advt != NULL) + { + ProtoAdvtDestroy(ref->advt); + ref->advt = NULL; + } + + // clear construct ref, if any + ref->pConstruct = NULL; +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilControl + + \Description + Set internal GameUtil parameters. + + \Input *pRef - reference pointer + \Input iKind - selector + \Input iValue - value to set + + \Notes + Selectors: + + \verbatim + 'advf': set advertising frequency, in seconds (call before calling NetGameUtilAdvert) + 'clid': set client identifier* (for use with gameservers) + 'locl': enable or disable query of local adverts + 'meta': set metatype (default=0) + 'minp': set receive buffer size, in packets* + 'mout': set send buffer size, in packets* + 'mwid': set maximum packet width (must be <= NETGAME_DATAPKT_MAXSIZE)* + * must be set before NetGameLinkConnect() is called to be effective. + \endverbatim + + \Version 11/12/03 (JLB) +*/ +/*************************************************************************************************F*/ +void NetGameUtilControl(NetGameUtilRefT *pRef, int32_t iKind, int32_t iValue) +{ + if (iKind == 'clid') + { + pRef->clientid = iValue; + NetPrintf(("netgameutil: setting clid=0x%08x\n", pRef->clientid)); + } + if (iKind == 'rcid') + { + pRef->rclientid = iValue; + NetPrintf(("netgameutil: setting rcid=0x%08x\n", pRef->rclientid)); + } + if (iKind == 'locl') + { + pRef->uLocalAdvt = iValue; + NetPrintf(("netgameutil: setting uLocalAdvt=%d\n", pRef->uLocalAdvt)); + } + if (iKind == 'meta') + { + pRef->metatype = iValue; + NetPrintf(("netgameutil: setting meta=0x%08x\n", pRef->metatype)); + } + if (iKind == 'mwid') + { + if (iValue <= NETGAME_DATAPKT_MAXSIZE) + { + pRef->maxwid = iValue+NETGAME_DATAPKT_MAXTAIL; + NetPrintf(("netgameutil: setting mwid=%d\n", pRef->maxwid)); + } + else + { + NetPrintf(("netgameutil: mwid value of %d is too large\n", iValue)); + } + } + if (iKind == 'minp') + { + pRef->maxinp = iValue; + NetPrintf(("netgameutil: setting minp=%d\n", pRef->maxinp)); + } + if (iKind == 'mout') + { + pRef->maxout = iValue; + NetPrintf(("netgameutil: setting mout=%d\n", pRef->maxout)); + } + if (iKind == 'ulmt') + { + pRef->unacklimit = iValue; + NetPrintf(("netgameutil: setting unacklimit=%d\n", pRef->unacklimit)); + } + + if (iKind == 'advf') + { + pRef->advtfreq = iValue; + NetPrintf(("netgameutil: setting advf=%d\n", pRef->advtfreq)); + } + + // if selector is unhandled, and a comm func is available, pass it on down + if ((pRef->comm != NULL) && (pRef->comm->Control != NULL)) + { + pRef->comm->Control(pRef->comm, iKind, iValue, NULL); + } +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilConnect + + \Description + Establish a connection (connect/listen) + + \Input *ref - reference pointer + \Input conn - connect mode (NETGAME_CONN_*) | comm type (NETGAME_CONN_*) + \Input *addr - service address list + \Input *pConstruct - comm construct function + + \Output + int32_t - zero=success, negative=failure + + \Version 01/09/01 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetGameUtilConnect(NetGameUtilRefT *ref, int32_t conn, const char *addr, CommAllConstructT *pConstruct) +{ + int32_t iErr = 0; + + // make sure user specified connect and/or listen, and a valid protocol + if (((conn & NETGAME_CONN_AUTO) == 0) || (pConstruct == NULL)) + { + NetPrintf(("netgameutil: invalid conn param\n")); + return(-100); // using hundred range to not conflic with COMM_* error codes that iErr can be assigned with. + } + + // save the address for later + ds_strnzcpy(ref->addr, addr, sizeof(ref->addr)); + // save host/join mode + ref->hosting = ((conn & NETGAME_CONN_CONNECT) ? 1 : 0); + + NetPrintf(("netgameutil: connect %d %s\n", conn, addr)); + + // release previous comm module + if (ref->comm != NULL) + { + ref->comm->Destroy(ref->comm); + } + + // handle auto mode + if ((conn & NETGAME_CONN_AUTO) == NETGAME_CONN_AUTO) + { + // save construct ref + ref->pConstruct = pConstruct; + // make sure advertising is running + if (_NetGameUtilAdvtConstruct(ref, 8, TRUE) == NULL) + { + NetPrintf(("netgameutil: NetGameUtilConnect() failed to create the ProtoAdvt module.\n")); + return(-101); // using hundred range to not conflic with COMM_* error codes that iErr can be assigned with. + } + ProtoAdvtAnnounce(ref->find, "GmUtil", addr, "", "TCP:~1:1024\tUDP:~1:1024", 0); + return(0); + } + + // mark modules as created by us with our memgroup + DirtyMemGroupEnter(ref->memgroup, ref->memgrpusrdata); + + // create comm module + ref->comm = pConstruct(ref->maxwid, ref->maxinp, ref->maxout); + + // start connect/listen + if (ref->comm != NULL) + { + if (ref->comm->Control != NULL) + { + ref->comm->Control(ref->comm, 'clid', ref->clientid, NULL); + ref->comm->Control(ref->comm, 'rcid', ref->rclientid, NULL); + ref->comm->Control(ref->comm, 'meta', ref->metatype, NULL); + + if (ref->unacklimit != 0) + { + ref->comm->Control(ref->comm, 'ulmt', ref->unacklimit, NULL); + } + } + if (conn & NETGAME_CONN_CONNECT) + { + iErr = ref->comm->Connect(ref->comm, addr); + } + else if (conn & NETGAME_CONN_LISTEN) + { + iErr = ref->comm->Listen(ref->comm, addr); + } + ref->pSocket = ref->comm->sockptr; + // get host ip/port info from commref + ref->hostip = ref->comm->hostip; + ref->hostport = ref->comm->hostport; + } + + DirtyMemGroupLeave(); + return(iErr); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilComplete + + \Description + Check for connection complete + + \Input *ref - reference pointer + + \Output + void * - connection pointer (NULL is no connection) + + \Version 01/09/01 (GWS) +*/ +/*************************************************************************************************F*/ +void *NetGameUtilComplete(NetGameUtilRefT *ref) +{ + // see if we are in find mode + if (ref->find != NULL) + { + // if not connecting, see if we can locate someone + if (ref->comm == NULL) + { + char text[256]; + uint32_t peer, host; + peer = ProtoAdvtLocate(ref->find, "GmUtil", ref->addr, &host, 0); + if (peer != 0) + { + NetPrintf(("netgameutil: located peer=%08x, host=%08x\n", peer, host)); + if (peer > host) + { + ref->hostip = peer; + ref->peerip = host; + ds_snzprintf(text, sizeof(text), "%d.%d.%d.%d%s", + (unsigned char)(peer>>24), (unsigned char)(peer>>16), + (unsigned char)(peer>> 8), (unsigned char)(peer>>0), + ref->addr); + NetGameUtilConnect(ref, NETGAME_CONN_CONNECT, text, ref->pConstruct); + } + else + { + ref->hostip = host; + ref->peerip = peer; + ds_strnzcpy(text, ref->addr, sizeof(text)); + NetGameUtilConnect(ref, NETGAME_CONN_LISTEN, text, ref->pConstruct); + } + } + } + } + + // check for a connect + if ((ref->comm != NULL) && (ref->comm->Status(ref->comm) == COMM_ONLINE)) + { + // get peer ip/port info from commref + ref->peerip = ref->comm->peerip; + ref->peerport = ref->comm->peerport; + + // stop any advertising + if (ref->advt != NULL) + { + ProtoAdvtDestroy(ref->advt); + ref->advt = NULL; + } + if (ref->find != NULL) + { + ProtoAdvtDestroy(ref->find); + ref->find = NULL; + } + + NetPrintf(("netgameutil: connection complete\n")); + return(ref->comm); + } + else + { + return(NULL); + } +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilStatus + + \Description + Return status info + + \Input *ref - reference pointer + \Input iSelect - info selector + \Input *pBuf - [out] output buffer + \Input iBufSize - size of output buffer + + \Output + int32_t - status info + + \Notes + iSelect can be one of the following: + + \verbatim + 'host' - TRUE if hosting, else FALSE + 'join' - TRUE if joining, else FALSE + 'hoip' - host ip + 'hprt' - host port + 'peip' - peer ip + 'pprt' - peer port + 'pkrc' - packet received + 'sock' - SocketT socket pointer (in pBuf) + \endverbatim + + \Version 01/09/01 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetGameUtilStatus(NetGameUtilRefT *ref, int32_t iSelect, void *pBuf, int32_t iBufSize) +{ + // return host status + if (iSelect == 'host') + { + return(ref->hosting == 0); + } + if (iSelect == 'join') + { + return(ref->hosting == 1); + } + if (iSelect == 'hoip') + { + return(ref->hostip); + } + if (iSelect == 'hprt') + { + return(ref->hostport); + } + if (iSelect == 'pkrc') + { + return(ref->comm->bpackrcvd); + } + if (iSelect == 'peip') + { + return(ref->peerip); + } + if (iSelect == 'pprt') + { + return(ref->peerport); + } + if ((iSelect == 'sock') && (iBufSize == (signed)sizeof(ref->pSocket))) + { + ds_memcpy(pBuf, &ref->pSocket, sizeof(ref->pSocket)); + return(sizeof(ref->pSocket)); + } + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilAdvert + + \Description + Send out an advertisement + + \Input *ref - reference pointer + \Input *kind - class (unique to app) + \Input *name - name to broadcast + \Input *note - notes + + \Version 01/09/01 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameUtilAdvert(NetGameUtilRefT *ref, const char *kind, const char *name, const char *note) +{ + // see if we need to create module + if (_NetGameUtilAdvtConstruct(ref, 16, FALSE) == NULL) + { + NetPrintf(("netgameutil: NetGameUtilAdvert() failed to create the ProtoAdvt module.\n")); + return; + } + + // save the kind for future queries + ds_strnzcpy(ref->kind, kind, sizeof(ref->kind)); + + // start advertising + ProtoAdvtAnnounce(ref->advt, kind, name, note, "TCP:~1:1024\tUDP:~1:1024", ref->advtfreq); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilWithdraw + + \Description + Withdraw given advertisement + + \Input *ref - reference pointer + \Input *kind - advert kind + \Input *name - advert name + + \Version 01/09/01 (GWS) +*/ +/*************************************************************************************************F*/ +void NetGameUtilWithdraw(NetGameUtilRefT *ref, const char *kind, const char *name) +{ + if (ref->advt != NULL) + { + ProtoAdvtCancel(ref->advt, kind, name); + } +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilLocate + + \Description + Find ip address of a specific advertisement + + \Input *ref - reference pointer + \Input *kind - class (unique to app) + \Input *name - advertisement to look for + + \Output + uint32_t - ip address of advertiser or zero if no match + + \Version 01/09/01 (GWS) +*/ +/*************************************************************************************************F*/ +uint32_t NetGameUtilLocate(NetGameUtilRefT *ref, const char *kind, const char *name) +{ + // auto-create the advert module if needed + if (_NetGameUtilAdvtConstruct(ref, 16, FALSE) == NULL) + { + NetPrintf(("netgameutil: NetGameUtilLocate() failed to create the ProtoAdvt module.\n")); + return(0); + } + + // allow use of default kind + if (kind == NULL) + { + kind = ref->kind; + } + + // pass to advertising module + return(ProtoAdvtLocate(ref->advt, kind, name, NULL, 0)); +} + +/*F*************************************************************************************************/ +/*! + \Function NetGameUtilQuery + + \Description + Return a list of all advertisements + + \Input *ref - reference pointer + \Input *kind - class (unique to app) + \Input *buf - target buffer + \Input max - target buffer length + + \Output + int32_t - number of matching ads + + \Version 01/09/01 (GWS) +*/ +/*************************************************************************************************F*/ +int32_t NetGameUtilQuery(NetGameUtilRefT *ref, const char *kind, char *buf, int32_t max) +{ + // auto-create the advert module if needed + if (_NetGameUtilAdvtConstruct(ref, 16, FALSE) == NULL) + { + NetPrintf(("netgameutil: NetGameUtilQuery() failed to create the ProtoAdvt module.\n")); + return(0); + } + + // allow use of default kind + if (kind == NULL) + { + kind = ref->kind; + } + + // pass to advert module + return(ProtoAdvtQuery(ref->advt, kind, "", buf, max, ref->uLocalAdvt)); +} diff --git a/src/thirdparty/dirtysdk/source/graph/dirtygif.c b/src/thirdparty/dirtysdk/source/graph/dirtygif.c new file mode 100644 index 00000000..3f22514e --- /dev/null +++ b/src/thirdparty/dirtysdk/source/graph/dirtygif.c @@ -0,0 +1,1063 @@ +/*H********************************************************************************/ +/*! + \File dirtygif.c + + \Description + Routines to decode a GIF into a raw image and palette. These routines + were written from scratch based on the published specifications and visual + inspection of some public-domain decoders. + + \Notes + References + GIF specification: https://www.w3.org/Graphics/GIF/spec-gif89a.txt + + \Copyright + Copyright (c) 2003-2020 Electronic Arts Inc. + + \Version 11/13/2003 (jbrookes) First Version + \Version 01/28/2020 (jbrookes) Added multiframe (animated) support +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/graph/dirtygif.h" + +/*** Defines **********************************************************************/ + +#define DIRTYGIF_VERBOSE (DIRTYCODE_LOGGING && FALSE) //!< enable verbose logging + +#define DIRTYGIF_MAXBITS (12) //!< max LZW code size +#define DIRTYGIF_MAXCODE ((1 << DIRTYGIF_MAXBITS) - 1) //!< max code value + +/*** Macros ***********************************************************************/ + +//! macro to validate that enough space exists in the file for a particular header +#define DIRTYGIF_Validate(_pData, _pEnd, _size, _retcode) \ +{ \ + if (((_pEnd) - (_pData)) < (_size)) \ + { \ + return(_retcode); \ + } \ +} + +#define DIRTYGIF_ClrCode(_iSize) (1 << (iSize) + 0) +#define DIRTYGIF_EndCode(_iSize) (1 << (iSize) + 1) +#define DIRTYGIF_NewCode(_iSize) (1 << (iSize) + 2) + +/*** Type Definitions *************************************************************/ + +//! decoder state +typedef struct DirtyGifDecoderT +{ + int32_t iCurCodeSize; //!< current code size, in bits + int32_t iClearCode; //!< value of a clear code + int32_t iEndingCode; //!< value of an ending code + int32_t iNewCodes; //!< first available code + int32_t iTopSlot; //!< highest code for current code size + int32_t iSlot; //!< last read code + + int32_t iBytesLeft; //!< number of bytes left in block + int32_t iBitsLeft; //!< number of bits left in block + uint8_t uCurByte; //!< current byte + const uint8_t *pByteStream; //!< pointer to byte stream + + uint8_t uSuffixTable[DIRTYGIF_MAXCODE+1]; //!< suffix table + uint32_t uPrefixTable[DIRTYGIF_MAXCODE+1]; //!< prefix table + + uint8_t uStack[DIRTYGIF_MAXCODE+1]; //!< code stack + uint8_t *pStackPtr; //!< current stack pointer + uint8_t *pStackEnd; //!< end of stack + + int32_t iCode; //!< most recent table code + int32_t iCodeBits; //!< number of bits used for code + int32_t iCode1; + int32_t iCode0; + int32_t iCodeRaw; //!< raw code read from data + + const uint8_t *pCodeData; //!< current image data pointer + const uint8_t *pEndCodeData; //!< end of image data + int32_t uHeight; //!< image height + int32_t uWidth; //!< image width + uint8_t *pScanLine; //!< start of current scan line + uint8_t *pScanLineEnd; //!< end of current scan line + uint8_t *pBufferEnd; //!< end of current buffer line + int32_t iPass; //!< pass for interlaced images + int32_t iIndex; //!< which row within pass + int32_t iStep; //!< interlace step + int32_t iBufHeight; //!< output buffer height + uint8_t *pImageData; //!< output image buffer (byte array) + +} DirtyGifDecoderT; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +static const int32_t stepsize[] = { 8, 8, 4, 2, 0, 1, 0 }; +static const int32_t startrow[] = { 0, 4, 2, 1, 0, 0, 0 }; + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _DirtyGifParseColorTable + + \Description + Parse the color table in GIF header. + + \Input *ppColorTable- [out] storage for color table pointer + \Input *pNumColors - [out] storage for color table size + \Input *pGifData - pointer to current location parsing GIF data + \Input *pGifEnd - pointer past end of GIF data + \Input uBits - byte containing color table info + + \Output + const uint8_t * - pointer to gif data after color table (if present) + + \Version 11/13/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_DirtyGifParseColorTable(const uint8_t **ppColorTable, uint16_t *pNumColors, const uint8_t *pGifData, const uint8_t *pGifEnd, uint32_t uBits) +{ + if (uBits & 0x80) + { + // get number of colors + *pNumColors = 2 << (uBits & 0x7); + + // validate color table + if ((pGifEnd-pGifData) < (signed)(*pNumColors*3)) + { + return(NULL); + } + + // ref color table + NetPrintfVerbose((DIRTYGIF_VERBOSE, 0, "dirtygif: pColorTable=%p\n", pGifData)); + *ppColorTable = pGifData; + pGifData += *pNumColors*3; + } + else + { + *ppColorTable = NULL; + *pNumColors = 0; + } + + return(pGifData); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyGifParseHeader + + \Description + Parse the GIF header. + + \Input *pGifHdr - [out] pointer to GIF header to fill in + \Input *pGifData - pointer to GIF data + \Input *pGifEnd - pointer past the end of GIF data + \Input *pFrames - [out] storage for list of frames or NULL if none + \Input uNumFrames - size of output frame array or zero if no frames output + + \Output + int32_t - nonnegative=success, negative=error + + \Version 01/22/2020 (jbrookes) Rewrote for more advanced parsing +*/ +/********************************************************************************F*/ +static int32_t _DirtyGifParseHeader(DirtyGifHdrT *pGifHdr, const uint8_t *pGifData, const uint8_t *pGifEnd, DirtyGifHdrT *pFrames, uint32_t uNumFrames) +{ + uint8_t uSize, uKind; + uint16_t uDelay; + uint8_t uTransColor=0, bHasAlpha=FALSE; + + // iClearCode header + ds_memclr(pGifHdr, sizeof(*pGifHdr)); + + // validate and skip signature + DIRTYGIF_Validate(pGifData, pGifEnd, 6, -1); + if (memcmp(pGifData, "GIF87a", 6) && memcmp(pGifData, "GIF89a", 6)) + { + return(-2); + } + pGifData += 6; + + // get logical screen width and height + pGifHdr->uWidth = pGifData[0] | pGifData[1] << 8; + pGifHdr->uHeight = pGifData[2] | pGifData[3] << 8; + NetPrintfVerbose((DIRTYGIF_VERBOSE, 0, "dirtygif: logical screen w=%d h=%d\n", pGifHdr->uWidth, pGifHdr->uHeight)); + + // validate logical screen descriptor + DIRTYGIF_Validate(pGifData, pGifEnd, 7, -3); + + // parse global color table info from logical screen descriptor + pGifData = _DirtyGifParseColorTable(&pGifHdr->pColorTable, &pGifHdr->uNumColors, pGifData+7, pGifEnd, pGifData[4]); + + // process blocks + for (uSize = 0, uDelay = 0; ((pGifEnd - pGifData) > 2); ) + { + uKind = pGifData[0]; + + // parse extension + if (uKind == 0x21) + { + uKind = pGifData[1]; + uSize = pGifData[2]; + pGifData += 3; + + NetPrintfVerbose((DIRTYGIF_VERBOSE, 0, "dirtygif: parsing extension block type 0x%02x (size=%d)\n", uKind, uSize)); + + // validate extension + DIRTYGIF_Validate(pGifData, pGifEnd, uSize + 1, -5); + + // parse graphic control extension block + if (uKind == 0xf9) + { + // get alpha + bHasAlpha = pGifData[0] & 0x1; + uTransColor = pGifData[3]; + // get delay time and convert from hundreths of a second to milliseconds + uDelay = (pGifData[1]|(pGifData[2]<<8))*10; + NetPrintfVerbose((DIRTYGIF_VERBOSE, 0, "dirtygif: delay=%d alpha=%d color=%d\n", uDelay, bHasAlpha, uTransColor)); + // skip size plus trailer + pGifData += uSize + 1; + } + // skip application extension block + else if (uKind == 0xff) + { + for (; uSize != 0; uSize = *pGifData++) + { + pGifData += uSize; + } + } + // unhandled extension... just skip it + else + { + pGifData += uSize + 1; + } + } + // parse image block + else if (uKind == 0x2c) + { + DirtyGifHdrT FrameInfo; + uint32_t uBlockCount; + uint8_t uBlockSize; + + DIRTYGIF_Validate(pGifData, pGifEnd, 10, -7); + + // get width and height from image descriptor + ds_memclr(&FrameInfo, sizeof(FrameInfo)); + FrameInfo.uLeft = pGifData[1] | pGifData[2] << 8; + FrameInfo.uTop = pGifData[3] | pGifData[4] << 8; + FrameInfo.uWidth = pGifData[5] | pGifData[6] << 8; + FrameInfo.uHeight = pGifData[7] | pGifData[8] << 8; + FrameInfo.uDelay = uDelay; + FrameInfo.bHasAlpha = bHasAlpha; + FrameInfo.uTransColor = uTransColor; + + // determine if it is an interlaced image + FrameInfo.bInterlaced = (pGifData[9] & 0x40) != 0; + + // parse local color table info from image descriptor + pGifData = _DirtyGifParseColorTable(&FrameInfo.pColorTable, &FrameInfo.uNumColors, &pGifData[10], pGifEnd, pGifData[9]); + + // save image start + FrameInfo.pImageData = pGifData++; + + // parse through image blocks to find end + for (uBlockSize = 0xff, uBlockCount = 0; (pGifData < pGifEnd) && (uBlockSize != 0); pGifData += uBlockSize) + { + uBlockSize = *pGifData++; + } + // save image end + FrameInfo.pImageEnd = pGifData; + + // if first image, copy frame info to main gif header info + if (pGifHdr->uNumFrames == 0) + { + pGifHdr->pImageData = FrameInfo.pImageData; + pGifHdr->pImageEnd = FrameInfo.pImageEnd; + pGifHdr->uWidth = FrameInfo.uWidth; + pGifHdr->uHeight = FrameInfo.uHeight; + pGifHdr->bInterlaced = FrameInfo.bInterlaced; + pGifHdr->uTransColor = FrameInfo.uTransColor; + pGifHdr->bHasAlpha = FrameInfo.bHasAlpha; + } + + // if we have a frames list, write to that + if ((pFrames != NULL) && (pGifHdr->uNumFrames < uNumFrames)) + { + ds_memcpy_s(&pFrames[pGifHdr->uNumFrames], sizeof(*pFrames), &FrameInfo, sizeof(FrameInfo)); + } + + // log frame + NetPrintfVerbose((DIRTYGIF_VERBOSE, 0, "dirtygif: frame %02d] t=%d l=%d w=%d h=%d i=%d bc=%d\n", pGifHdr->uNumFrames, FrameInfo.uTop, FrameInfo.uLeft, FrameInfo.uWidth, FrameInfo.uHeight, FrameInfo.bInterlaced, uBlockCount)); + + // track frame count + pGifHdr->uNumFrames += 1; + } + else + { + // invalid block kind + NetPrintf(("dirtygif: invalid block kind 0x%02x\n", uKind)); + return(-6); + } + } + + NetPrintfVerbose((DIRTYGIF_VERBOSE, 0, "dirtygif: nFrames=%d\n", pGifHdr->uNumFrames)); + + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyGifResetDecoder + + \Description + Reset the LZW decoder. + + \Input *pDecoder - pointer to decoder state + + \Version 11/20/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static void _DirtyGifResetDecoder(DirtyGifDecoderT *pDecoder) +{ + pDecoder->iCurCodeSize = pDecoder->iCodeBits + 1; + pDecoder->iSlot = pDecoder->iNewCodes; + pDecoder->iTopSlot = 1 << pDecoder->iCurCodeSize; + pDecoder->iCode = 0; +} + +/*F********************************************************************************/ +/*! + \Function _DirtyGifInitDecoder + + \Description + Init the LZW decoder. + + \Input *pDecoder - pointer to decoder state + \Input *pGifHdr - pointer to GIF header + \Input *pImageData - pointer to output pixel buffer + \Input iStep - output step size + \Input iHeight - output buffer height + + \Version 11/20/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _DirtyGifInitDecoder(DirtyGifDecoderT *pDecoder, DirtyGifHdrT *pGifHdr, uint8_t *pImageData, int32_t iStep, int32_t iHeight) +{ + ds_memclr(pDecoder, sizeof(*pDecoder)); + pDecoder->pCodeData = pGifHdr->pImageData; + pDecoder->pEndCodeData = pGifHdr->pImageEnd; + pDecoder->uHeight = pGifHdr->uHeight; + pDecoder->uWidth = pGifHdr->uWidth; + pDecoder->iStep = iStep; + pDecoder->iBufHeight = iHeight; + pDecoder->pImageData = pImageData; + + // set starting imaging location + pDecoder->iPass = (pGifHdr->bInterlaced ? 0 : 5); + pDecoder->iIndex = startrow[pDecoder->iPass]; + pDecoder->pScanLine = NULL; + + // get the initial code length + if (pDecoder->pCodeData == pDecoder->pEndCodeData) + { + return(-2); + } + pDecoder->iCodeBits = *pDecoder->pCodeData++; + if ((pDecoder->iCodeBits < 2) || (pDecoder->iCodeBits > 9)) + { + return(-2); + } + + // setup for decoding + pDecoder->iBytesLeft = pDecoder->iBitsLeft = 0; + pDecoder->iClearCode = 1 << pDecoder->iCodeBits; + pDecoder->iEndingCode = pDecoder->iClearCode + 1; + pDecoder->iNewCodes = pDecoder->iEndingCode + 1; + _DirtyGifResetDecoder(pDecoder); + + // protect against missing iClearCode code + pDecoder->iCode0 = pDecoder->iCode1 = 0; + + // init stack + pDecoder->pStackPtr = pDecoder->uStack; + pDecoder->pStackEnd = pDecoder->uStack+sizeof(pDecoder->uStack); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyGifGetByte + + \Description + Get next byte from current block. + + If the current block is exhausted, this function advances to the next block, and + returns the first byte from that block. + + \Input *pDecoder - pointer to decoder state + + \Output + int32_t - zero + + \Version 11/20/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _DirtyGifGetByte(DirtyGifDecoderT *pDecoder) +{ + // have we consumed all bytes in this block? + if (pDecoder->iBytesLeft <= 0) + { + // get byte length of next block + pDecoder->iBytesLeft = *pDecoder->pCodeData++; + + // validate block + DIRTYGIF_Validate(pDecoder->pCodeData, pDecoder->pEndCodeData, pDecoder->iBytesLeft, -1); + + // point to bytestream for this block + pDecoder->pByteStream = pDecoder->pCodeData; + + // skip decode pointer past block + pDecoder->pCodeData += pDecoder->iBytesLeft; + } + + // get next byte from block + pDecoder->uCurByte = *pDecoder->pByteStream++; + pDecoder->iBytesLeft--; + + // return success to caller + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyGifGetNextCode + + \Description + Get next code from the current block. + + \Input *pDecoder - pointer to decoder state + + \Output + int32_t - TRUE if we should continue decoding, else FALSE if we've + reached the end code. + + \Version 11/20/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _DirtyGifGetNextCode(DirtyGifDecoderT *pDecoder) +{ + // out of bits? + if (pDecoder->iBitsLeft == 0) + { + if (_DirtyGifGetByte(pDecoder) < 0) + { + return(-1); + } + + pDecoder->iBitsLeft += 8; + } + + // add from bit bucket + pDecoder->iCodeRaw = pDecoder->uCurByte >> (8 - pDecoder->iBitsLeft); + + // fill out the code + while (pDecoder->iCurCodeSize > pDecoder->iBitsLeft) + { + if (_DirtyGifGetByte(pDecoder) < 0) + { + return(-1); + } + + pDecoder->iCodeRaw |= pDecoder->uCurByte << pDecoder->iBitsLeft; + pDecoder->iBitsLeft += 8; + } + + // update bits left, and mask to code range + pDecoder->iBitsLeft -= pDecoder->iCurCodeSize; + pDecoder->iCodeRaw &= (1 << pDecoder->iCurCodeSize) - 1; + + // check for end of data + return((pDecoder->iCodeRaw == pDecoder->iEndingCode) ? 0 : 1); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyGifUpdateDecoder + + \Description + Update decoder state. + + \Input *pDecoder - pointer to decoder state + + \Version 11/20/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static void _DirtyGifUpdateDecoder(DirtyGifDecoderT *pDecoder) +{ + // set up for decode + pDecoder->iCode = pDecoder->iCodeRaw; + + // if we get a bogus code use the last valid code read instead + if (pDecoder->iCode >= pDecoder->iSlot) + { + pDecoder->iCode = pDecoder->iCode0; + if (pDecoder->pStackPtr < pDecoder->pStackEnd) + { + *pDecoder->pStackPtr++ = (uint8_t)pDecoder->iCode1; + } + } + + // push characters onto stack + while (pDecoder->iCode >= pDecoder->iNewCodes) + { + if (pDecoder->pStackPtr >= pDecoder->pStackEnd) + { + // overflow error + break; + } + *pDecoder->pStackPtr++ = pDecoder->uSuffixTable[pDecoder->iCode]; + pDecoder->iCode = pDecoder->uPrefixTable[pDecoder->iCode]; + } + + // push last char onto the uStack + if (pDecoder->pStackPtr < pDecoder->pStackEnd) + { + *pDecoder->pStackPtr++ = (uint8_t)pDecoder->iCode; + } + + // set up new prefix and uSuffixTable + if (pDecoder->iSlot < pDecoder->iTopSlot) + { + pDecoder->iCode1 = pDecoder->iCode; + pDecoder->uSuffixTable[pDecoder->iSlot] = (uint8_t)pDecoder->iCode1; + pDecoder->uPrefixTable[pDecoder->iSlot++] = pDecoder->iCode0; + pDecoder->iCode0 = pDecoder->iCodeRaw; + } + + // if required iSlot number is greater than bit size allows, increase bit size (up to 12 bits) + if ((pDecoder->iSlot >= pDecoder->iTopSlot) && (pDecoder->iCurCodeSize < 12)) + { + pDecoder->iTopSlot <<= 1; + pDecoder->iCurCodeSize++; + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyGifUpdateBitmap + + \Description + Write decoded string to output bitmap buffer. + + \Input *pDecoder - pointer to decoder state + \Input bVflip - if TRUE, flip image vertically + + \Output + int32_t - one to continue, zero to abort + + \Version 11/20/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _DirtyGifUpdateBitmap(DirtyGifDecoderT *pDecoder, uint32_t bVflip) +{ + uint8_t cPixel; + + // pop decoded string off stack into output buffer + for ( ; pDecoder->pStackPtr > pDecoder->uStack; ) + { + // see if we need to calc scanline start + if (pDecoder->pScanLine == NULL) + { + // if we've hit the output buffer height, quit early + if (pDecoder->iIndex == pDecoder->iBufHeight) + { + return(0); + } + + // calc the new line start + if (bVflip) + { + pDecoder->pScanLine = pDecoder->pImageData + ((pDecoder->uHeight-1) * pDecoder->iStep); + if (pDecoder->iIndex < pDecoder->uHeight) + { + pDecoder->pScanLine -= pDecoder->iStep*((pDecoder->uHeight-1)-pDecoder->iIndex); + } + } + else + { + pDecoder->pScanLine = pDecoder->pImageData; + if (pDecoder->iIndex < pDecoder->uHeight) + { + pDecoder->pScanLine += pDecoder->iStep*((pDecoder->uHeight-1)-pDecoder->iIndex); + } + } + + pDecoder->pScanLineEnd = pDecoder->pScanLine+pDecoder->uWidth; + pDecoder->pBufferEnd = pDecoder->pScanLine+pDecoder->iStep; + } + + // read pixel from stack + cPixel = *(--pDecoder->pStackPtr); + + // put translated pixel into output buffer + if (pDecoder->pScanLine < pDecoder->pBufferEnd) + { + *pDecoder->pScanLine = cPixel; + } + pDecoder->pScanLine += 1; + + // see if we are at end of scanline + if (pDecoder->pScanLine == pDecoder->pScanLineEnd) + { + // if width and step size differ, zero fill extra + if (pDecoder->uWidth < pDecoder->iStep) + { + *pDecoder->pScanLine++ = 0x00; + } + + // see if we are done with this pass + if (((pDecoder->iIndex += stepsize[pDecoder->iPass]) >= pDecoder->uHeight) && (stepsize[pDecoder->iPass] > 0)) + { + pDecoder->iIndex = startrow[++pDecoder->iPass]; + } + + // invalidate the scanline pointer + pDecoder->pScanLine = NULL; + } + } + + // normal return condition + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyGifDecodeImage32 + + \Description + Decode a GIF image into a 32bit ARGB direct-color bitmap + + \Input *pGifHdr - pointer to header describing gif to decode + \Input *pImageData - [out] pointer to buffer to write decoded image data to + \Input *pImageDataPrev - pointer to buffer with previous image, or NULL if first image + \Input *p8BitImage - pointer to scratch buffer for 8bit image decoding, or NULL + \Input iBufWidth - width of output buffer in pixels + \Input iBufHeight - height of output buffer in pixels + \Input bVflip - if TRUE, flip image vertically + + \Output + int32_t - positive=number of bytes decoded, negative=error + + \Version 01/28/2020 (jbrookes) Rewrote to handle animated gif frames +*/ +/********************************************************************************F*/ +static int32_t _DirtyGifDecodeImage32(DirtyGifHdrT *pGifHdr, uint8_t *pImageData, const uint8_t *pImageDataPrev, uint8_t *p8BitImage, int32_t iBufWidth, int32_t iBufHeight, uint32_t bVflip) +{ + uint8_t aPaletteData[256][4]; + uint8_t *pSrc, *pDst; + const uint8_t *pSrc32; + int32_t i8BitSize, iWidth, iHeight; + uint8_t bAlpha, bInFrame; + int32_t iError; + + // first, decode palette info + if ((iError = DirtyGifDecodePalette(pGifHdr, (uint8_t *)aPaletteData, (uint8_t *)aPaletteData + sizeof(aPaletteData), 0xff)) < 0) + { + return(iError); + } + + // if we didn't get an 8bit decode buffer, put it at the end of the output buffer + if (p8BitImage == NULL) + { + // calculate size of decoded 8bit image + i8BitSize = pGifHdr->uWidth * pGifHdr->uHeight; + // locate 8bit image at end of buffer + p8BitImage = pImageData + (iBufWidth * iBufHeight * 4) - i8BitSize; + } + + // decode the image + if ((iError = DirtyGifDecodeImage(pGifHdr, p8BitImage, pGifHdr->uWidth, pGifHdr->uHeight, bVflip)) < 0) + { + return(iError); + } + + // now translate the 8bit image to 32bits + for (pSrc = p8BitImage, iHeight = 0; iHeight < iBufHeight; iHeight += 1) + { + for (iWidth = 0; iWidth < iBufWidth; iWidth += 1) + { + // get palette index and read if we have an alpha + bAlpha = pGifHdr->bHasAlpha && (*pSrc == pGifHdr->uTransColor); + // see if our pixel is in frame or not + bInFrame = ((iWidth >= pGifHdr->uLeft) && (iWidth < (pGifHdr->uLeft + pGifHdr->uWidth)) && + (iHeight >= pGifHdr->uTop) && (iHeight < (pGifHdr->uTop + pGifHdr->uHeight))) || + (pImageDataPrev == NULL); + // locate output + pDst = pImageData + ((iHeight * iBufWidth) + iWidth) * 4; + // write output + if (bInFrame && !bAlpha) + { + pDst[0] = aPaletteData[*pSrc][3]; + pDst[1] = aPaletteData[*pSrc][0]; + pDst[2] = aPaletteData[*pSrc][1]; + pDst[3] = aPaletteData[*pSrc][2]; + } + else if (pImageData != pImageDataPrev) + { + pSrc32 = pImageDataPrev + ((iHeight * iBufWidth) + iWidth) * 4; + pDst[0] = pSrc32[0]; + pDst[1] = pSrc32[1]; + pDst[2] = pSrc32[2]; + pDst[3] = pSrc32[3]; + } + + // advance source pointer if we're in frame + if (bInFrame) + { + pSrc += 1; + } + } + } + + // return success + return(0); +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function DirtyGifIdentify + + \Description + Identify if input image is a GIF image. + + \Input *pImageData - pointer to image data + \Input uImageLen - size of image data + + \Output + int32_t - TRUE if a GIF, else FALSE + + \Version 03/09/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGifIdentify(const uint8_t *pImageData, uint32_t uImageLen) +{ + // make sure we have enough data + if (uImageLen < 6) + { + return(0); + } + // see of we're a GIF + if (memcmp(pImageData, "GIF87a", 6) && memcmp(pImageData, "GIF89a", 6)) + { + return(0); + } + return(1); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGifParse + + \Description + Parse GIF header. + + \Input *pGifHdr - [out] pointer to GIF header to fill in + \Input *pGifData - pointer to GIF data + \Input *pGifEnd - pointer past the end of GIF data + + \Version 11/13/2003 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGifParse(DirtyGifHdrT *pGifHdr, const uint8_t *pGifData, const uint8_t *pGifEnd) +{ + return(_DirtyGifParseHeader(pGifHdr, pGifData, pGifEnd, NULL, 0)); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGifParseEx + + \Description + Parse GIF header, with extended info + + \Input *pGifHdr - [out] pointer to GIF header to fill in + \Input *pGifData - pointer to GIF data + \Input *pGifEnd - pointer past the end of GIF data + \Input *pFrames - [out] storage for list of frames + \Input uNumFrames - size of output frame array; zero if unknown + + \Version 01/16/2020 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGifParseEx(DirtyGifHdrT *pGifHdr, const uint8_t *pGifData, const uint8_t *pGifEnd, DirtyGifHdrT *pFrames, uint32_t uNumFrames) +{ + return(_DirtyGifParseHeader(pGifHdr, pGifData, pGifEnd, pFrames, uNumFrames)); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGifDecodePalette + + \Description + Decode a GIF palette into an RGBA palette. + + \Input *pGifHdr - pointer to GIF header + \Input *pPalette - [out] pointer to output for RGBA palette + \Input *pPaletteEnd - pointer past end of RGBA output buffer + \Input uAlpha - alpha value to use for normal pixels + + \Version 11/13/2003 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGifDecodePalette(DirtyGifHdrT *pGifHdr, uint8_t *pPalette, uint8_t *pPaletteEnd, uint8_t uAlpha) +{ + const uint8_t *pColorTable; + uint8_t *pPalStart = pPalette; + + // validate parameters + if ((pGifHdr->pColorTable == NULL) || (pPalette == NULL)) + { + return(-1); + } + + // extract palette colors + for (pColorTable = pGifHdr->pColorTable; pPalette < pPaletteEnd; pPalette += 4, pColorTable += 3) + { + pPalette[0] = pColorTable[0]; + pPalette[1] = pColorTable[1]; + pPalette[2] = pColorTable[2]; + pPalette[3] = uAlpha; + } + + // handle alpha transparency + if (pGifHdr->bHasAlpha) + { + uint8_t *pAlphaColor = pPalStart + (pGifHdr->uTransColor * 4); + if (pAlphaColor < pPaletteEnd) + { + pAlphaColor[3] = 0x00; + } + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGifDecodeImage + + \Description + Decode a GIF image into an 8bit paletteized bitmap. + + \Input *pGifHdr - pointer to header describing gif to decode + \Input *pImageData - [out] pointer to buffer to write decoded image data to + \Input iBufWidth - width of output buffer + \Input iBufHeight - height of output buffer + \Input bVflip - if TRUE, flip image vertically + + \Output + int32_t - positive=number of bytes decoded, negative=error + + \Version 11/13/2003 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGifDecodeImage(DirtyGifHdrT *pGifHdr, uint8_t *pImageData, int32_t iBufWidth, int32_t iBufHeight, uint32_t bVflip) +{ + DirtyGifDecoderT Decoder, *pDecoder = &Decoder; + + // init the decoder + if (_DirtyGifInitDecoder(pDecoder, pGifHdr, pImageData, iBufWidth, iBufHeight) < 0) + { + return(-1); + } + + // decode the image + for (;;) + { + // get next code + if (_DirtyGifGetNextCode(pDecoder) <= 0) + { + break; + } + + // iClearCode code? + if (pDecoder->iCodeRaw == pDecoder->iClearCode) + { + // reset the decoder + _DirtyGifResetDecoder(pDecoder); + + // get code following iClearCode code + if (_DirtyGifGetNextCode(pDecoder) <= 0) + { + break; + } + + // if code is out of range, set code=0 to protect against broken encoders + if (pDecoder->iCodeRaw >= pDecoder->iSlot) + { + pDecoder->iCodeRaw = 0; + } + + // update + pDecoder->iCode0 = pDecoder->iCode1 = pDecoder->iCodeRaw; + + // push the single value onto stack + if (pDecoder->pStackPtr < pDecoder->pStackEnd) + { + *pDecoder->pStackPtr++ = (uint8_t)pDecoder->iCodeRaw; + } + } + else + { + // decode code to stack and update decoder + _DirtyGifUpdateDecoder(pDecoder); + } + + // write any decoded codes into bitmap + if (_DirtyGifUpdateBitmap(pDecoder, bVflip) == 0) + { + // buffer is too small in height - bail early + break; + } + } + + // number number of bytes processed + return((int32_t)(pDecoder->pCodeData - pGifHdr->pImageData)); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGifDecodeImage32 + + \Description + Decode a GIF image into a 32bit ARGB direct-color bitmap. + + \Input *pGifHdr - pointer to header describing gif to decode + \Input *pImageData - [out] pointer to buffer to write decoded image data to + \Input iBufWidth - width of output buffer in pixels + \Input iBufHeight - height of output buffer in pixels + \Input bVflip - if TRUE, flip image vertically + + \Output + int32_t - positive=number of bytes decoded, negative=error + + \Notes + This version may not always decode the first frame of a multi-frame GIF + correctly; use DirtyGifDecodeImage32Multi() with iNumFrames=1 instead. + + \Version 03/09/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGifDecodeImage32(DirtyGifHdrT *pGifHdr, uint8_t *pImageData, int32_t iBufWidth, int32_t iBufHeight, uint32_t bVflip) +{ + return(_DirtyGifDecodeImage32(pGifHdr, pImageData, NULL, NULL, iBufWidth, iBufHeight, bVflip)); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGifDecodeImage32Multi + + \Description + Decode a GIF image into a multiple 32bit ARGB direct-color bitmaps + + \Input *pGifHdr - pointer to header describing gif to decode + \Input *pFrameInfo - frame info + \Input *pImageData - [out] pointer to buffer to write decoded images to + \Input iBufWidth - width of output buffer in pixels + \Input iBufHeight - height of output buffer in pixels + \Input iNumFrames - number of image frames to decode + \Input bVflip - if TRUE, flip image vertically + + \Output + int32_t - positive=number of bytes decoded, negative=error + + \Version 01/28/2020 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGifDecodeImage32Multi(DirtyGifHdrT *pGifHdr, DirtyGifHdrT *pFrameInfo, uint8_t *pImageData, int32_t iBufWidth, int32_t iBufHeight, int32_t iNumFrames, uint32_t bVflip) +{ + int32_t iFrame, iFrameSize, iResult; + DirtyGifHdrT GifHdr; + uint8_t *pImageDataPrev; + + for (iFrame = 0, iResult = 0, iFrameSize = iBufWidth*iBufHeight*4, pImageDataPrev = NULL; (iFrame < iNumFrames) && (iResult == 0); iFrame += 1) + { + // copy frame info + ds_memcpy_s(&GifHdr, sizeof(GifHdr), &pFrameInfo[iFrame], sizeof(pFrameInfo[iFrame])); + + // use global color table if not set for this frame + if (pFrameInfo[iFrame].pColorTable == NULL) + { + GifHdr.pColorTable = pGifHdr->pColorTable; + GifHdr.uNumColors = pGifHdr->uNumColors; + } + + // decode to current buffer + iResult = _DirtyGifDecodeImage32(&GifHdr, pImageData + iFrame*iFrameSize, pImageDataPrev, NULL, iBufWidth, iBufHeight, bVflip); + + // remember current frame for next decode + pImageDataPrev = pImageData + iFrame*iFrameSize; + } + + // return most recent result code + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGifDecodeImage32Frame + + \Description + Decode a specific frame of a GIF image into a single 32bit ARGB direct-color + bitmap. The frame order must be from the first frame to the last in sequence + and the image data output buffer must be preserved between calls, except when + decoding the first frame. + + \Input *pGifHdr - pointer to header describing gif to decode + \Input *pFrameInfo - frame info + \Input *pImageData - [out] pointer to buffer to write decoded image to + \Input *p8BitImage - pointer to scratch buffer for 8bit image decoding + \Input iBufWidth - width of output buffer in pixels + \Input iBufHeight - height of output buffer in pixels + \Input iFrame - frame to decode + \Input iNumFrames - number of image frames to decode + \Input bVflip - if TRUE, flip image vertically + + \Output + int32_t - positive=numb + + \Version 02/06/2020 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGifDecodeImage32Frame(DirtyGifHdrT *pGifHdr, DirtyGifHdrT *pFrameInfo, uint8_t *pImageData, uint8_t *p8BitImage, int32_t iBufWidth, int32_t iBufHeight, int32_t iFrame, int32_t iNumFrames, uint32_t bVflip) +{ + DirtyGifHdrT GifHdr; + + // copy frame info for the frame we want to decode + ds_memcpy_s(&GifHdr, sizeof(GifHdr), &pFrameInfo[iFrame], sizeof(pFrameInfo[iFrame])); + + // use global color table if not set for this frame + if (pFrameInfo[iFrame].pColorTable == NULL) + { + GifHdr.pColorTable = pGifHdr->pColorTable; + GifHdr.uNumColors = pGifHdr->uNumColors; + } + + // decode to current buffer and return result to caller + return(_DirtyGifDecodeImage32(&GifHdr, pImageData, pImageData, p8BitImage, iBufWidth, iBufHeight, bVflip)); +} + diff --git a/src/thirdparty/dirtysdk/source/graph/dirtygraph.c b/src/thirdparty/dirtysdk/source/graph/dirtygraph.c new file mode 100644 index 00000000..400948e4 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/graph/dirtygraph.c @@ -0,0 +1,416 @@ +/*H********************************************************************************/ +/*! + \File dirtygraph.c + + \Description + Routines for decoding an encoded graphics image. + + \Copyright + Copyright (c) 2006-2020 Electronic Arts Inc. + + \Version 03/09/2006 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/graph/dirtygraph.h" +#include "DirtySDK/graph/dirtygif.h" +#include "DirtySDK/graph/dirtyjpg.h" +#include "DirtySDK/graph/dirtypng.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct DirtyGraphBufferT +{ + uint8_t *pBuffer; + int32_t iBufWidth; + int32_t iBufHeight; +} DirtyGraphBufferT; + +struct DirtyGraphRefT +{ + //! module memory group + int32_t iMemGroup; + void *pMemGroupUserData; + + DirtyGifHdrT *pGifFrameInfo; + DirtyGraphBufferT GifImage8; + DirtyJpgStateT *pJpg; + DirtyPngStateT *pPng; + DirtyGifHdrT GifHdr; + DirtyJpgHdrT JpgHdr; + DirtyPngHdrT PngHdr; +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function DirtyGraphCreate + + \Description + Create the DirtyGraph module. + + \Output + DirtyGraphStateT * - module state, or NULL if unable to create + + \Version 03/09/2006 (jbrookes) First Version +*/ +/********************************************************************************F*/ +DirtyGraphRefT *DirtyGraphCreate(void) +{ + DirtyGraphRefT *pDirtyGraph; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pDirtyGraph = DirtyMemAlloc(sizeof(*pDirtyGraph), DIRTYGRAPH_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtygraph: could not allocate module state\n")); + return(NULL); + } + ds_memclr(pDirtyGraph, sizeof(*pDirtyGraph)); + pDirtyGraph->iMemGroup = iMemGroup; + pDirtyGraph->pMemGroupUserData = pMemGroupUserData; + + // allocate jpeg module state + if ((pDirtyGraph->pJpg = DirtyJpgCreate()) == NULL) + { + NetPrintf(("dirtygraph: unable to allocate jpg module state\n")); + } + + // allocate png module state + if ((pDirtyGraph->pPng = DirtyPngCreate()) == NULL) + { + NetPrintf(("dirtygraph: unable to allocate png module state\n")); + } + + // return module state ref + return(pDirtyGraph); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGraphDestroy + + \Description + Destroy the DirtyGraph module. + + \Input *pDirtyGraph - pointer to module state + + \Output + None. + + \Version 03/09/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void DirtyGraphDestroy(DirtyGraphRefT *pDirtyGraph) +{ + // destroy gif 8bit frame buffer? + if (pDirtyGraph->GifImage8.pBuffer != NULL) + { + DirtyMemFree(pDirtyGraph->GifImage8.pBuffer, DIRTYGRAPH_MEMID, pDirtyGraph->iMemGroup, pDirtyGraph->pMemGroupUserData); + } + // destroy gif frame info? + if (pDirtyGraph->pGifFrameInfo != NULL) + { + DirtyMemFree(pDirtyGraph->pGifFrameInfo, DIRTYGRAPH_MEMID, pDirtyGraph->iMemGroup, pDirtyGraph->pMemGroupUserData); + } + // destroy jpeg module? + if (pDirtyGraph->pJpg != NULL) + { + DirtyJpgDestroy(pDirtyGraph->pJpg); + } + // destroy png module? + if (pDirtyGraph->pPng != NULL) + { + DirtyPngDestroy(pDirtyGraph->pPng); + } + // free module state + DirtyMemFree(pDirtyGraph, DIRTYGRAPH_MEMID, pDirtyGraph->iMemGroup, pDirtyGraph->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGraphDecodeHeader + + \Description + Decode an image header + + \Input *pDirtyGraph - pointer to module state + \Input *pInfo - [out] storage for image info + \Input *pImageData - pointer to input image info + \Input uImageLen - size of input image + + \Output + int32_t - negative=error, else success + + \Version 03/09/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGraphDecodeHeader(DirtyGraphRefT *pDirtyGraph, DirtyGraphInfoT *pInfo, const uint8_t *pImageData, uint32_t uImageLen) +{ + int32_t iError, iResult=-1; + + // init info structure + ds_memclr(pInfo, sizeof(*pInfo)); + pInfo->pImageData = pImageData; + pInfo->uLength = uImageLen; + + // try and identify image + if (DirtyGifIdentify(pImageData, uImageLen)) + { + if ((iError = DirtyGifParse(&pDirtyGraph->GifHdr, pImageData, pImageData+uImageLen)) == 0) + { + pInfo->uType = DIRTYGRAPH_IMAGETYPE_GIF; + pInfo->iWidth = pDirtyGraph->GifHdr.uWidth; + pInfo->iHeight = pDirtyGraph->GifHdr.uHeight; + pInfo->uNumFrames = pDirtyGraph->GifHdr.uNumFrames; + // if we have a previous gif frame buffer, destroy it now + if (pDirtyGraph->pGifFrameInfo != NULL) + { + DirtyMemFree(pDirtyGraph->pGifFrameInfo, DIRTYGRAPH_MEMID, pDirtyGraph->iMemGroup, pDirtyGraph->pMemGroupUserData); + } + // now allocate and parse frame data + if ((pDirtyGraph->pGifFrameInfo = DirtyMemAlloc(sizeof(*pDirtyGraph->pGifFrameInfo)*pInfo->uNumFrames, DIRTYGRAPH_MEMID, pDirtyGraph->iMemGroup, pDirtyGraph->pMemGroupUserData)) != NULL) + { + iResult = DirtyGifParseEx(&pDirtyGraph->GifHdr, pImageData, pImageData+uImageLen, pDirtyGraph->pGifFrameInfo, pInfo->uNumFrames); + } + else + { + NetPrintf(("dirtygraph: unable to allocate memory for gif frames list\n")); + iResult = -1; + } + } + else + { + NetPrintf(("dirtygraph: error %d parsing gif image\n", iError)); + } + + } + else if ((pDirtyGraph->pJpg != NULL) && DirtyJpgIdentify(pDirtyGraph->pJpg, pImageData, uImageLen)) + { + if ((iError = DirtyJpgDecodeHeader(pDirtyGraph->pJpg, &pDirtyGraph->JpgHdr, pImageData, uImageLen)) == DIRTYJPG_ERR_NONE) + { + pInfo->uType = DIRTYGRAPH_IMAGETYPE_JPG; + pInfo->iWidth = pDirtyGraph->JpgHdr.uWidth; + pInfo->iHeight = pDirtyGraph->JpgHdr.uHeight; + pInfo->uNumFrames = 1; + iResult = 0; + } + else + { + NetPrintf(("dirtygraph: error %d parsing jpg image\n", iError)); + } + } + else if ((pDirtyGraph->pPng != NULL) && DirtyPngIdentify(pImageData, uImageLen)) + { + if ((iError = DirtyPngParse(pDirtyGraph->pPng, &pDirtyGraph->PngHdr, pImageData, pImageData+uImageLen)) == DIRTYPNG_ERR_NONE) + { + pInfo->uType = DIRTYGRAPH_IMAGETYPE_PNG; + pInfo->iWidth = pDirtyGraph->PngHdr.uWidth; + pInfo->iHeight = pDirtyGraph->PngHdr.uHeight; + pInfo->uNumFrames = 1; + iResult = 0; + } + else + { + NetPrintf(("dirtygraph: error %d parsing png image\n", iError)); + } + } + else + { + NetPrintf(("dirtygraph: cannot parse image of unknown type\n")); + pInfo->uType = DIRTYGRAPH_IMAGETYPE_UNKNOWN; + pInfo->pImageData = NULL; + pInfo->uLength = 0; + } + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGraphGetImageInfo + + \Description + Get extended information about an image + + \Input *pDirtyGraph - pointer to module state + \Input *pInfo - image information (filled in by DirtyGraphDecodeHeader) + \Input iSelect - info selector + \Input *pBuffer - [out] selector output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector return + + \Notes + Selectors are: + + \verbatim + 'anim' fills buffer with int32_t anim info (delay) in milliseconds, and returns number of frames + \endverbatim + + \Version 01/27/2020 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGraphGetImageInfo(DirtyGraphRefT *pDirtyGraph, DirtyGraphInfoT *pInfo, int32_t iSelect, void *pBuffer, int32_t iBufSize) +{ + if ((iSelect == 'anim') && (pInfo->uType == DIRTYGRAPH_IMAGETYPE_GIF) && (pBuffer != NULL) && (iBufSize == (signed)(pInfo->uNumFrames*sizeof(int32_t)))) + { + int32_t *pDelayBuf = (int32_t *)pBuffer, iFrame; + // collect delay info in integer output array + for (iFrame = 0; iFrame < pInfo->uNumFrames; iFrame += 1) + { + pDelayBuf[iFrame] = pDirtyGraph->pGifFrameInfo[iFrame].uDelay; + } + // return count of delay frame values copied to output buffer + return(iFrame); + } + // unhandled + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function DirtyGraphDecodeImage + + \Description + Decode an image into the given output buffer. + + \Input *pDirtyGraph - pointer to module state + \Input *pInfo - image information (filled in by DirtyGraphDecodeHeader) + \Input *pImageBuf - [out] pointer to buffer to store decoded image + \Input iBufWidth - width of output buffer + \Input iBufHeight - height of output buffer + + \Output + int32_t - negative=error, else success + + \Version 03/09/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGraphDecodeImage(DirtyGraphRefT *pDirtyGraph, DirtyGraphInfoT *pInfo, uint8_t *pImageBuf, int32_t iBufWidth, int32_t iBufHeight) +{ + if (pInfo->uType == DIRTYGRAPH_IMAGETYPE_GIF) + { + return(DirtyGifDecodeImage32Multi(&pDirtyGraph->GifHdr, pDirtyGraph->pGifFrameInfo, pImageBuf, iBufWidth, iBufHeight, 1, TRUE)); + } + else if (pInfo->uType == DIRTYGRAPH_IMAGETYPE_JPG) + { + return(DirtyJpgDecodeImage(pDirtyGraph->pJpg, &pDirtyGraph->JpgHdr, pImageBuf, iBufWidth, iBufHeight)); + } + else if (pInfo->uType == DIRTYGRAPH_IMAGETYPE_PNG) + { + return(DirtyPngDecodeImage(pDirtyGraph->pPng, &pDirtyGraph->PngHdr, pImageBuf, iBufWidth, iBufHeight)); + } + else + { + NetPrintf(("dirtygraph: cannot decode image of unknown type\n")); + return(-1); + } +} + +/*F********************************************************************************/ +/*! + \Function DirtyGraphDecodeImageMulti + + \Description + Decode a multiframe image into an array of images in the given output buffer. + + \Input *pDirtyGraph - pointer to module state + \Input *pInfo - image information (filled in by DirtyGraphDecodeHeader) + \Input *pImageBuf - [out] pointer to buffer to store decoded images + \Input iBufWidth - width of output buffer + \Input iBufHeight - height of output buffer + + \Output + int32_t - negative=error, else success + + \Version 01/28/2020 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGraphDecodeImageMulti(DirtyGraphRefT *pDirtyGraph, DirtyGraphInfoT *pInfo, uint8_t *pImageBuf, int32_t iBufWidth, int32_t iBufHeight) +{ + if ((pInfo->uType == DIRTYGRAPH_IMAGETYPE_GIF) && (pDirtyGraph->pGifFrameInfo != NULL)) + { + return(DirtyGifDecodeImage32Multi(&pDirtyGraph->GifHdr, pDirtyGraph->pGifFrameInfo, pImageBuf, iBufWidth, iBufHeight, pDirtyGraph->GifHdr.uNumFrames, TRUE)); + } + else + { + NetPrintf(("dirtygraph: multi-image decoding not supported for image type\n")); + return(-1); + } +} + +/*F********************************************************************************/ +/*! + \Function DirtyGraphDecodeImageFrame + + \Description + Decode a multiframe image into the given output buffer. The frame order must + be from the first frame to the last in sequence and the image data output + buffer must be preserved between calls, except when decoding the first frame. + + \Input *pDirtyGraph - pointer to module state + \Input *pInfo - image information (filled in by DirtyGraphDecodeHeader) + \Input *pImageBuf - [out] pointer to buffer to store decoded image + \Input iBufWidth - width of output buffer + \Input iBufHeight - height of output buffer + \Input iFrame - frame to decode + + \Output + int32_t - negative=error, else success + + \Version 02/06/2020 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyGraphDecodeImageFrame(DirtyGraphRefT *pDirtyGraph, DirtyGraphInfoT *pInfo, uint8_t *pImageBuf, int32_t iBufWidth, int32_t iBufHeight, int32_t iFrame) +{ + if ((pInfo->uType == DIRTYGRAPH_IMAGETYPE_GIF) && (pDirtyGraph->pGifFrameInfo != NULL)) + { + // if eight-bit frame cache exists but the frame size has changed, kill it + if ((pDirtyGraph->GifImage8.pBuffer != NULL) && ((pDirtyGraph->GifImage8.iBufWidth != iBufWidth) || (pDirtyGraph->GifImage8.iBufHeight != iBufHeight))) + { + DirtyMemFree(pDirtyGraph->GifImage8.pBuffer, DIRTYGRAPH_MEMID, pDirtyGraph->iMemGroup, pDirtyGraph->pMemGroupUserData); + pDirtyGraph->GifImage8.pBuffer = NULL; + } + // allocate eight-bit frame cache + if (pDirtyGraph->GifImage8.pBuffer == NULL) + { + if ((pDirtyGraph->GifImage8.pBuffer = DirtyMemAlloc(iBufWidth * iBufHeight, DIRTYGRAPH_MEMID, pDirtyGraph->iMemGroup, pDirtyGraph->pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtygraph: could not allocate 8bit decode buffer\n")); + return(-1); + } + pDirtyGraph->GifImage8.iBufWidth = iBufWidth; + pDirtyGraph->GifImage8.iBufHeight = iBufHeight; + } + // decode the frame and return result to caller + return(DirtyGifDecodeImage32Frame(&pDirtyGraph->GifHdr, pDirtyGraph->pGifFrameInfo, pImageBuf, pDirtyGraph->GifImage8.pBuffer, iBufWidth, iBufHeight, iFrame, pDirtyGraph->GifHdr.uNumFrames, TRUE)); + } + else + { + NetPrintf(("dirtygraph: multi-image decoding not supported for image type\n")); + return(-1); + } +} \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/source/graph/dirtyjpg.c b/src/thirdparty/dirtysdk/source/graph/dirtyjpg.c new file mode 100644 index 00000000..1fd40654 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/graph/dirtyjpg.c @@ -0,0 +1,1967 @@ +/*H********************************************************************************/ +/*! + \File dirtyjpg.c + + \Description + Implements JFIF/EXIF image decoding to an output 32bit buffer. This decoder + does not support progressive, lossless, or arithmetic options. + + \Notes + References: + [1] http://www.w3.org/Graphics/JPEG/jfif3.pdf + [2] http://www.w3.org/Graphics/JPEG/itu-t81.pdf + [3] http://class.ece.iastate.edu/ee528/Reading%20material/JPEG_File_Format.pdf + [4] http://www.bsdg.org/SWAG/GRAPHICS/0143.PAS.html + [5] http://www.exif.org/Exif2-2.PDF + + \Copyright + Copyright (c) 2006 Electronic Arts Inc. + + \Version 02/23/2006 (jbrookes) First version, based on gshaefer code +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/graph/dirtyjpg.h" + +/*** Defines **********************************************************************/ + +#define DIRTYJPG_DEBUG (TRUE) +#define DIRTYJPG_VERBOSE (DIRTYJPG_DEBUG && FALSE) +#define DIRTYJPG_DEBUG_HUFF (DIRTYJPG_DEBUG && FALSE) +#define DIRTYJPG_DEBUG_IDCT (DIRTYJPG_DEBUG && FALSE) + +#if !DIRTYJPG_DEBUG_IDCT + #define _PrintMatrix16(_pMatrix, _pTitle) {;} +#endif + +#define MCU_SIZE (8*8*4) // 8x8 32-bit +#define MCU_MAXSIZE (MCU_SIZE*4*4) // max size is 4x4 blocks + +// LLM constants +#define DCTSIZE 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#define FIX_0_298631336 ((int32_t) 2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((int32_t) 3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((int32_t) 4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((int32_t) 6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((int32_t) 7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((int32_t) 9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((int32_t) 12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((int32_t) 15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((int32_t) 16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((int32_t) 16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((int32_t) 20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((int32_t) 25172) /* FIX(3.072711026) */ + +/*** Type Definitions *************************************************************/ + +//! quantization/matrix +typedef uint16_t QuantTableT[64]; + +//! huffman decode table head +typedef struct HuffHeadT +{ + uint16_t uShift; //!< number of shifts to normalize + uint16_t uMinCode; //!< min code in this table + uint16_t uMaxCode; //!< max code in this table + uint16_t uOffset; //!< offset to value/shift data +} HuffHeadT; + +//! huffman decode table +typedef struct HuffTableT +{ + HuffHeadT Head[10]; //!< table code range + uint8_t Code[512]; //!< code lookup + uint8_t Step[512]; //!< code length lookup +} HuffTableT; + +//! component table +typedef struct CompTableT +{ + uint16_t uMcuHorz; //!< number of 8x8 blocks horizontally in an mcu + uint16_t uMcuVert; //!< number of 8x8 blocks vertically in an mcu + uint16_t uCompHorz; //!< horizontal sampling factor + uint16_t uCompVert; //!< vertical sampling factor + uint16_t uExpHorz; //!< horizontal expansion factor + uint16_t uExpVert; //!< vertical expansion factor + + uint32_t uStepHorz0; //!< horizontal step + uint32_t uStepVert0; //!< vertical step + uint32_t uStepHorz1; //!< horizontal scaled step + uint32_t uStepVert1; //!< vertical scaled stemp + uint32_t uStepHorz8; //!< horizontal step*8 + uint32_t uStepVert8; //!< horizontal step*8 + uint32_t uStepHorzM; //!< horizontal mcu step + uint32_t uStepVertM; //!< vertical mcu step + + uint16_t uQuantNum; //!< quant table to use + + HuffTableT *pACHuff; //!< pointer to current AC huffman decode table + HuffTableT *pDCHuff; //!< pointer to current DC huffman decode table + + void (*pExpand)(DirtyJpgStateT *pState, struct CompTableT *pCompTable, uint32_t uOutOffset); + void (*pInvert)(DirtyJpgStateT *pState, struct CompTableT *pCompTable, uint32_t uOutOffset); + + QuantTableT Matrix; //!< working matrix for decoding this component + QuantTableT Quant; //!< current quant table + +} CompTableT; + +//! module state +struct DirtyJpgStateT +{ + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + int32_t iLastError; //!< previous error + uint32_t uLastOffset; //!< offset where decoding previously stopped + + uint16_t uVersion; //!< JFIF file version + uint16_t uAspectRatio; //!< image aspect ratio + uint16_t uAspectHorz; //!< horizontal aspect ratio + uint16_t uAspectVert; //!< vertical aspect ratio + + uint16_t uImageWidth; //!< width of encoded image + uint16_t uImageHeight; //!< height of encoded image + + uint16_t uMcuHorz; //!< maxumum horizontal MCU, out of all components + uint16_t uMcuHorzM; //!< number of MCUs to cover image horizontall + uint16_t uMcuVert; //!< maximum vertical MCU, out of all components + uint16_t uMcuVertM; //!< number of MCUs to cover image vertically + + uint32_t uBitfield; //!< current bit buffer + uint32_t uBitsAvail; //!< number of bits in bit buffer + uint32_t uBitsConsumed; //!< number of bits consumed from bitstream + uint32_t uBytesRead; //!< number of bytes read from bitstream + + CompTableT CompTable[4]; //!< component tables + QuantTableT QuantTable[16]; //!< quantization tables + HuffTableT HuffTable[8]; //!< huffman decode tables + uint8_t aMCU[MCU_MAXSIZE]; //!< mcu decode buffer + + const uint8_t *pCompData; //!< pointer to compressed component data + + uint8_t *pImageBuf; //!< pointer to output image buffer (allocated by caller) + const uint8_t *pSrcFinal; + + uint32_t uBufWidth; //!< width of buffer to decode into + uint32_t uBufHeight; //!< height of buffer to decode into + + uint8_t bRestart; //!< if TRUE, a restart marker has been processed + uint8_t _pad[3]; +}; + +/*** Variables ********************************************************************/ + +//! helper table to negate a value based on its bitsize +static const uint16_t _ZagAdj_adjust[] = +{ + 0x0000, 0x0001, 0x0003, 0x0007, + 0x000f, 0x001f, 0x003f, 0x007f, + 0x00ff, 0x01ff, 0x03ff, 0x07ff, + 0x0fff, 0x1fff, 0x3fff, 0x7fff +}; + +//! jpeg zig-zag sequence table +static const uint16_t _ZagAdj_zag[] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + +static const int16_t _Mult07_14h[] = +{ + 1462, 1451, 1439, 1428, 1416, 1405, 1394, 1382, 1371, 1359, 1348, 1336, 1325, 1314, 1302, 1291, + 1279, 1268, 1256, 1245, 1234, 1222, 1211, 1199, 1188, 1176, 1165, 1154, 1142, 1131, 1119, 1108, + 1096, 1085, 1074, 1062, 1051, 1039, 1028, 1016, 1005, 994, 982, 971, 959, 948, 936, 925, + 914, 902, 891, 879, 868, 856, 845, 834, 822, 811, 799, 788, 776, 765, 754, 742, + 731, 719, 708, 697, 685, 674, 662, 651, 639, 628, 617, 605, 594, 582, 571, 559, + 548, 537, 525, 514, 502, 491, 479, 468, 457, 445, 434, 422, 411, 399, 388, 377, + 365, 354, 342, 331, 319, 308, 297, 285, 274, 262, 251, 239, 228, 217, 205, 194, + 182, 171, 159, 148, 137, 125, 114, 102, 91, 79, 68, 57, 45, 34, 22, 11, + 0, -11, -22, -34, -45, -57, -68, -79, -91, -102, -114, -125, -137, -148, -159, -171, + -182, -194, -205, -217, -228, -239, -251, -262, -274, -285, -297, -308, -319, -331, -342, -354, + -365, -377, -388, -399, -411, -422, -434, -445, -457, -468, -479, -491, -502, -514, -525, -537, + -548, -559, -571, -582, -594, -605, -617, -628, -639, -651, -662, -674, -685, -697, -708, -719, + -731, -742, -754, -765, -776, -788, -799, -811, -822, -834, -845, -856, -868, -879, -891, -902, + -914, -925, -936, -948, -959, -971, -982, -994,-1005,-1016,-1028,-1039,-1051,-1062,-1074,-1085, +-1096,-1108,-1119,-1131,-1142,-1154,-1165,-1176,-1188,-1199,-1211,-1222,-1234,-1245,-1256,-1268, +-1279,-1291,-1302,-1314,-1325,-1336,-1348,-1359,-1371,-1382,-1394,-1405,-1416,-1428,-1439,-1451 +}; + +static const int16_t _Mult07_14l[] = +{ + -179, -178, -176, -175, -173, -172, -171, -169, -168, -166, -165, -164, -162, -161, -159, -158, + -157, -155, -154, -152, -151, -150, -148, -147, -145, -144, -143, -141, -140, -138, -137, -135, + -134, -133, -131, -130, -128, -127, -126, -124, -123, -121, -120, -119, -117, -116, -114, -113, + -112, -110, -109, -107, -106, -105, -103, -102, -100, -99, -98, -96, -95, -93, -92, -91, + -89, -88, -86, -85, -84, -82, -81, -79, -78, -77, -75, -74, -72, -71, -70, -68, + -67, -65, -64, -63, -61, -60, -58, -57, -56, -54, -53, -51, -50, -49, -47, -46, + -44, -43, -42, -40, -39, -37, -36, -35, -33, -32, -30, -29, -28, -26, -25, -23, + -22, -21, -19, -18, -16, -15, -14, -12, -11, -9, -8, -7, -5, -4, -2, -1, + 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 14, 15, 16, 18, 19, 21, + 22, 23, 25, 26, 28, 29, 30, 32, 33, 35, 36, 37, 39, 40, 42, 43, + 44, 46, 47, 49, 50, 51, 53, 54, 56, 57, 58, 60, 61, 63, 64, 65, + 67, 68, 70, 71, 72, 74, 75, 77, 78, 79, 81, 82, 84, 85, 86, 88, + 89, 91, 92, 93, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, + 112, 113, 114, 116, 117, 119, 120, 121, 123, 124, 126, 127, 128, 130, 131, 133, + 134, 135, 137, 138, 140, 141, 143, 144, 145, 147, 148, 150, 151, 152, 154, 155, + 157, 158, 159, 161, 162, 164, 165, 166, 168, 169, 171, 172, 173, 175, 176, 178 +}; + +static const int16_t _Mult17_03h[] = +{ + -226, -225, -223, -221, -219, -217, -216, -214, -212, -210, -209, -207, -205, -203, -202, -200, + -198, -196, -194, -193, -191, -189, -187, -186, -184, -182, -180, -178, -177, -175, -173, -171, + -170, -168, -166, -164, -163, -161, -159, -157, -155, -154, -152, -150, -148, -147, -145, -143, + -141, -139, -138, -136, -134, -132, -131, -129, -127, -125, -124, -122, -120, -118, -116, -115, + -113, -111, -109, -108, -106, -104, -102, -101, -99, -97, -95, -93, -92, -90, -88, -86, + -85, -83, -81, -79, -77, -76, -74, -72, -70, -69, -67, -65, -63, -62, -60, -58, + -56, -54, -53, -51, -49, -47, -46, -44, -42, -40, -38, -37, -35, -33, -31, -30, + -28, -26, -24, -23, -21, -19, -17, -15, -14, -12, -10, -8, -7, -5, -3, -1, + 0, 1, 3, 5, 7, 8, 10, 12, 14, 15, 17, 19, 21, 23, 24, 26, + 28, 30, 31, 33, 35, 37, 38, 40, 42, 44, 46, 47, 49, 51, 53, 54, + 56, 58, 60, 62, 63, 65, 67, 69, 70, 72, 74, 76, 77, 79, 81, 83, + 85, 86, 88, 90, 92, 93, 95, 97, 99, 101, 102, 104, 106, 108, 109, 111, + 113, 115, 116, 118, 120, 122, 124, 125, 127, 129, 131, 132, 134, 136, 138, 139, + 141, 143, 145, 147, 148, 150, 152, 154, 155, 157, 159, 161, 163, 164, 166, 168, + 170, 171, 173, 175, 177, 178, 180, 182, 184, 186, 187, 189, 191, 193, 194, 196, + 198, 200, 202, 203, 205, 207, 209, 210, 212, 214, 216, 217, 219, 221, 223, 225 +}; + +static const int16_t _Mult17_03l[] = +{ + 704, 699, 693, 688, 682, 677, 671, 666, 660, 655, 649, 644, 638, 633, 627, 622, + 616, 611, 605, 600, 594, 589, 583, 578, 572, 567, 561, 556, 550, 545, 539, 534, + 528, 523, 517, 512, 506, 501, 495, 490, 484, 479, 473, 468, 462, 457, 451, 446, + 440, 434, 429, 423, 418, 412, 407, 401, 396, 390, 385, 379, 374, 368, 363, 357, + 352, 346, 341, 335, 330, 324, 319, 313, 308, 302, 297, 291, 286, 280, 275, 269, + 264, 258, 253, 247, 242, 236, 231, 225, 220, 214, 209, 203, 198, 192, 187, 181, + 176, 170, 165, 159, 154, 148, 143, 137, 132, 126, 121, 115, 110, 104, 99, 93, + 88, 82, 77, 71, 66, 60, 55, 49, 44, 38, 33, 27, 22, 16, 11, 5, + 0, -5, -11, -16, -22, -27, -33, -38, -44, -49, -55, -60, -66, -71, -77, -82, + -88, -93, -99, -104, -110, -115, -121, -126, -132, -137, -143, -148, -154, -159, -165, -170, + -176, -181, -187, -192, -198, -203, -209, -214, -220, -225, -231, -236, -242, -247, -253, -258, + -264, -269, -275, -280, -286, -291, -297, -302, -308, -313, -319, -324, -330, -335, -341, -346, + -352, -357, -363, -368, -374, -379, -385, -390, -396, -401, -407, -412, -418, -423, -429, -434, + -440, -446, -451, -457, -462, -468, -473, -479, -484, -490, -495, -501, -506, -512, -517, -523, + -528, -534, -539, -545, -550, -556, -561, -567, -572, -578, -583, -589, -594, -600, -605, -611, + -616, -622, -627, -633, -638, -644, -649, -655, -660, -666, -671, -677, -682, -688, -693, -699 +}; + +//! 8-bit table for clamping range [-256...511]->[0...255] +static const uint8_t _Ranger8[] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +//! offset clamp table to zero +static const uint8_t *_pRanger8 = _Ranger8+256; + + +/*** Private Functions ************************************************************/ + +#if DIRTYJPG_DEBUG_IDCT +/*F********************************************************************************/ +/*! + \Function _PrintMatrix16 + + \Description + Print 16bit 8x8 matrix to debug output. + + \Input *pMatrix - pointer to matrix to print + \Input *pTitle - output title + + \Version 03/09/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _PrintMatrix16(int16_t *pMatrix, const char *pTitle) +{ + int32_t ctr; + + NetPrintf(("%s\n", pTitle)); + for (ctr = 0; ctr < 64; ctr += 8) + { + NetPrintf(("%+10d %+10d %+10d %+10d %+10d %+10d %+10d %+10d\n", + pMatrix[ctr+0], pMatrix[ctr+1], pMatrix[ctr+2], pMatrix[ctr+3], + pMatrix[ctr+4], pMatrix[ctr+5], pMatrix[ctr+6], pMatrix[ctr+7])); + } +} +#endif + +/* + + Huffman decoding routines + +*/ + +/*F********************************************************************************/ +/*! + \Function _ReadByte + + \Description + Read a byte into the bitbuffer, handling any dle bytes. + + \Input *pState - state ref + \Input *pData - compressed data base pointer + \Input *pValue - byte read + + \Output + uint32_t - number of bytes consumed + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _ReadByte(DirtyJpgStateT *pState, const uint8_t *pData, uint8_t *pValue) +{ + uint32_t uValue, uOffset=0; + + // protect against reading past the end of the buffer, in which case we just return a null byte + if ((pData+uOffset) == pState->pSrcFinal) + { + *pValue = 0; + return(1); + } + + // read a byte + *pValue = uValue = pData[uOffset++]; + + // eat marker data + while (uValue == 0xff) + { + uValue = pData[uOffset++]; + pState->uBitsConsumed += 8; + + // restart marker? + if ((uValue >= 0xd0) && (uValue <= 0xd7)) + { + #if DIRTYJPG_DEBUG_HUFF + NetPrintf(("dirtyjpg: restart marker %d\n", uValue&0xf)); + #endif + // remember that there was a restart marker + pState->bRestart = TRUE; + } + } + + return(uOffset); +} + +/*F********************************************************************************/ +/*! + \Function _ExtractBits + + \Description + Read bits from bitstream. + + \Input *pState - state ref + \Input *pData - compressed data base pointer + \Input uBitSize - number of bits to read + \Input *pSign - positive if leading bit of extracted data is not set, else negative + \Input bAdvance - if TRUE, advance the bit offset + + \Output + uint32_t - extracted value + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _ExtractBits(DirtyJpgStateT *pState, const uint8_t *pData, uint32_t uBitSize, int32_t *pSign, uint8_t bAdvance) +{ + uint32_t uValue, uBytesRead; + uint8_t uByte; + + // load data into bit buffer + while ((pState->uBitsAvail < 16) && (pState->bRestart != TRUE)) + { + uBytesRead = _ReadByte(pState, pData+pState->uBytesRead, &uByte); + pState->uBytesRead += uBytesRead; + pState->uBitfield |= uByte << (24-pState->uBitsAvail); + pState->uBitsAvail += 8; + } + + // determine sign of extracted value + if (pSign) + { + *pSign = (pState->uBitfield & 0x80000000) ? -1 : 1; + } + + // take top bits + uValue = pState->uBitfield >> (32-uBitSize); + if (bAdvance) + { + pState->uBitfield <<= uBitSize; + pState->uBitsAvail -= uBitSize; + pState->uBitsConsumed += uBitSize; + } + + // return extracted value to caller + return(uValue); +} + +/*F********************************************************************************/ +/*! + \Function _HuffDecode + + \Description + Decode a huffman-encoded value + + \Input *pState - state ref + \Input *pHuffTable - pointer to huffman table to use for decode + \Input *pData - compressed data base pointer + + \Output + uint32_t - decoded value + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _HuffDecode(DirtyJpgStateT *pState, HuffTableT *pHuffTable, const uint8_t *pData) +{ + HuffHeadT *pHuffHead; + uint32_t uValue; + + // extract 16 bits, but don't advance bit offset + uValue = _ExtractBits(pState, pData, 16, NULL, FALSE); + + // find table range for this value + for (pHuffHead = pHuffTable->Head; uValue >= pHuffHead->uMaxCode; pHuffHead += 1) + { + if (pHuffHead->uShift == 0) + { + NetPrintf(("dirtyjpg: decode error\n")); + return(0xffffffff); + } + } + #if DIRTYJPG_DECODE_HUFF + NetPrintf(("dirtyjpg: using range %d\n", pHuffHead-pHuffTable->Head)); + #endif + + // subtract mincode to get offset into table + uValue -= pHuffHead->uMinCode; + // shift down to discard extra bits + uValue >>= pHuffHead->uShift; + // offset by the start of this table into code/step buffer + uValue += pHuffHead->uOffset; + + // lookup skip value and consume bits + _ExtractBits(pState, pData, pHuffTable->Step[uValue], NULL, TRUE); + + #if DIRTYJPG_DEBUG_HUFF + NetPrintf(("_decode: 0x%02x->s%d,c%02x\n", uValue, pHuffTable->Step[uValue], pHuffTable->Code[uValue])); + #endif + + // lookup code value + uValue = pHuffTable->Code[uValue]; + + // return decoded value to caller + return(uValue); +} + +/*F********************************************************************************/ +/*! + \Function _UnpackComp + + \Description + Unnpack the next sample into a component record's matrix + + \Input *pState - state ref + \Input *pCompTable - pointer to component table + \Input *pData - compressed data base pointer + + \Output + int32_t - negative=error, else success + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _UnpackComp(DirtyJpgStateT *pState, CompTableT *pCompTable, const uint8_t *pData) +{ + uint16_t *pMatrix = (uint16_t *)&pCompTable->Matrix; + uint16_t *pQuant = (uint16_t *)&pCompTable->Quant; + uint32_t uCodeLen, uElemIdx, uValue=0; + int32_t iSign = 0; + + // decode a token (get code length) + if ((uCodeLen = _HuffDecode(pState, pCompTable->pDCHuff, pData)) == 0xffffffff) + { + return(uCodeLen); + } + else if (uCodeLen != 0) + { + // get the raw value + uValue = _ExtractBits(pState, pData, uCodeLen, &iSign, TRUE); + // if non-negative, adjust to negative value + if (iSign > 0) + { + uValue -= _ZagAdj_adjust[uCodeLen]; + } + #if DIRTYJPG_DEBUG_HUFF + NetPrintf(("dc=0x%02x\n", uValue)); + #endif + // quantify the difference + uValue *= pQuant[0]; + } + + // accumulate and save the dc coefficient + uValue += pMatrix[0]; + pMatrix[0] = uValue; + + // clear the rest of the matrix + ds_memclr(&pMatrix[1], sizeof(pCompTable->Matrix)-sizeof(pMatrix[0])); + + // decode ac coefficients + for (uElemIdx = 1; uElemIdx < 64; ) + { + // decode ac code + if ((uValue = _HuffDecode(pState, pCompTable->pACHuff, pData)) == 0xffffffff) + { + return(uValue); + } + + // get vli length + if ((uCodeLen = uValue & 0xf) == 0) + { + // end of block? + if (uValue != 0xf0) + { + return(0); + } + // skip 16 (zero skip) + uElemIdx += 16; + } + else + { + // skip count + uValue >>= 4; + // advance matrix offset (zero skip) + uElemIdx = (uElemIdx+uValue)&63; + // get the raw value and advance the bit offset + uValue = _ExtractBits(pState, pData, uCodeLen, &iSign, TRUE); + // if non-negative, adjust to negative value + if (iSign > 0) + { + uValue -= _ZagAdj_adjust[uCodeLen]; + } + #if DIRTYJPG_DEBUG_HUFF + NetPrintf(("ac=0x%02x\n", uValue)); + #endif + // quantify the value + uValue *= pQuant[uElemIdx]; + // store the quantized value, with zag + pMatrix[_ZagAdj_zag[uElemIdx]] = uValue; + // increment index + uElemIdx += 1; + } + } + + // return success + return(0); +} + + +/* + + Image coefficient expansion routines + +*/ + +/*F********************************************************************************/ +/*! + \Function _Expand0 + + \Description + Expand buffered component scan line data (no expansion). + + \Input *pState - state ref + \Input *pCompTable - pointer to component table + \Input uOutOffset - offset in mcu buffer + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _Expand0(DirtyJpgStateT *pState, CompTableT *pCompTable, uint32_t uOutOffset) +{ +} + +/*F********************************************************************************/ +/*! + \Function _Expand3hv + + \Description + Expand buffered component scan line data (2x2 special case). + + \Input *pState - state ref + \Input *pCompTable - pointer to component table + \Input uOutOffset - offset in mcu buffer + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _Expand3hv(DirtyJpgStateT *pState, CompTableT *pCompTable, uint32_t uOutOffset) +{ + uint32_t uStepV0 = pCompTable->uStepVert0; + uint32_t uStepVM = pCompTable->uStepVertM; + uint32_t uStepH0 = pCompTable->uStepHorz0; + uint8_t *pData = pState->aMCU + uOutOffset; + uint8_t *pEndRow, *pEndCol; + uint32_t uData; + + for (pEndRow = pData + uStepVM; pData != pEndRow;) + { + for (pEndCol = pData + uStepV0; pData != pEndCol;) + { + uData = *pData; // get upper left + pData[uStepV0] = uData; // save lower-left + pData += uStepH0; + pData[0] = uData; // save upper-right + pData[uStepV0] = uData; // save lower-right + pData += uStepH0; + } + + // skip extra col + pData += uStepV0; + } +} + +/*F********************************************************************************/ +/*! + \Function _ExpandAny + + \Description + Expand buffered component scan line data (general case). + + \Input *pState - state ref + \Input *pCompTable - pointer to component table + \Input uOutOffset - offset in mcu buffer + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ExpandAny(DirtyJpgStateT *pState, CompTableT *pCompTable, uint32_t uOutOffset) +{ + uint32_t uData, uExpHorz, uExpVert, uRptCnt; + uint32_t uStepV0 = pCompTable->uStepVert0; + uint32_t uStepVM = pCompTable->uStepVertM; + uint32_t uStepH0 = pCompTable->uStepHorz0; + uint8_t *pData, *pEndRow, *pEndCol; + + // get zero-relative vertical expansion factor + if ((uExpVert = pCompTable->uExpVert - 1) != 0) + { + pData = pState->aMCU + uOutOffset; + for (pEndCol = pData + uStepV0; pData < pEndCol; pData += pCompTable->uStepHorz1) + { + uint8_t *pTemp = pData; + for (pEndRow = pData + uStepVM; pData < pEndRow; ) + { + // get source data and index past it + uData = *pData; + pData += uStepV0; + + // expand the data + for (uRptCnt = 0; uRptCnt < uExpVert; uRptCnt += 1, pData += uStepV0) + { + *pData = uData; + } + } + pData = pTemp; + } + } + + // get zero-relative horizontal expansion factor + if ((uExpHorz = pCompTable->uExpHorz - 1) != 0) + { + pData = pState->aMCU + uOutOffset; + for (pEndRow = pData + uStepVM; pData < pEndRow; ) + { + // get source data and index past it + uData = *pData; + pData += uStepH0; + + // expand the data + for (uRptCnt = 0; uRptCnt < uExpHorz; uRptCnt += 1, pData += uStepH0) + { + *pData = uData; + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function _TransLLM + + \Description + Perform iDCT and quantify (component record matrix -> mcu buffer). + + \Input *pState - state ref + \Input *pCompTable - pointer to component table + \Input uOutOffset - offset in mcu buffer + + \Version 03/03/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _TransLLM(DirtyJpgStateT *pState, CompTableT *pCompTable, uint32_t uOutOffset) +{ + uint32_t uStepH1, uStepV1, uRow; + uint8_t *pDataDest; + QuantTableT WorkingMatrix; + #if DIRTYJPG_DEBUG_IDCT + QuantTableT WorkingMatrix2; + #endif + int16_t *pWorking, *pMatrix; + const uint8_t *pRanger8 = _pRanger8 + 128; + int32_t tmp0, tmp1, tmp2, tmp3; + int32_t tmp10, tmp11, tmp12, tmp13; + int32_t z1, z2, z3, z4, z5; + + // point to dst data + if ((pDataDest = pState->aMCU) == NULL) + { + return; + } + + // increment to offset + pDataDest += uOutOffset; + + // ref step values + uStepH1 = pCompTable->uStepHorz1; + uStepV1 = pCompTable->uStepVert1; + + // debug input + _PrintMatrix16((int16_t *)&pCompTable->Matrix, "iDCT input matrix"); + + // do eight columns + for (uRow = 0, pWorking=(int16_t*)&WorkingMatrix, pMatrix=(int16_t*)&pCompTable->Matrix; uRow < 8; uRow++) + { + /* Due to quantization, we will usually find that many of the input + coefficients are zero, especially the AC terms. We can exploit this + by short-circuiting the IDCT calculation for any column in which all + the AC terms are zero. In that case each output is equal to the + DC coefficient (with scale factor as needed). + With typical images and quantization tables, half or more of the + column DCT calculations can be simplified this way. */ + if ((pMatrix[DCTSIZE*1] | pMatrix[DCTSIZE*2] | pMatrix[DCTSIZE*3] | + pMatrix[DCTSIZE*4] | pMatrix[DCTSIZE*5] | pMatrix[DCTSIZE*6] | + pMatrix[DCTSIZE*7]) == 0) + { + /* AC terms all zero */ + int16_t dcval = (int16_t) (pMatrix[DCTSIZE*0] << PASS1_BITS); + + pWorking[DCTSIZE*0] = dcval; + pWorking[DCTSIZE*1] = dcval; + pWorking[DCTSIZE*2] = dcval; + pWorking[DCTSIZE*3] = dcval; + pWorking[DCTSIZE*4] = dcval; + pWorking[DCTSIZE*5] = dcval; + pWorking[DCTSIZE*6] = dcval; + pWorking[DCTSIZE*7] = dcval; + + pMatrix++; // advance pointers to next column + pWorking++; + continue; + } + + // Even part: reverse the even part of the forward DCT. The rotator is sqrt(2)*c(-6). + z2 = pMatrix[DCTSIZE*2]; + z3 = pMatrix[DCTSIZE*6]; + + z1 = (z2 + z3) * FIX_0_541196100; + tmp2 = z1 + (z3 * -FIX_1_847759065); + tmp3 = z1 + (z2 * FIX_0_765366865); + + z2 = pMatrix[DCTSIZE*0]; + z3 = pMatrix[DCTSIZE*4]; + + tmp0 = (z2 + z3) << CONST_BITS; + tmp1 = (z2 - z3) << CONST_BITS; + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Odd part per figure 8; the matrix is unitary and hence its + transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. */ + tmp0 = pMatrix[DCTSIZE*7]; + tmp1 = pMatrix[DCTSIZE*5]; + tmp2 = pMatrix[DCTSIZE*3]; + tmp3 = pMatrix[DCTSIZE*1]; + + z1 = tmp0 + tmp3; + z2 = tmp1 + tmp2; + z3 = tmp0 + tmp2; + z4 = tmp1 + tmp3; + z5 = (z3 + z4) * FIX_1_175875602; // sqrt(2) * c3 + + tmp0 = tmp0 * FIX_0_298631336; // sqrt(2) * (-c1+c3+c5-c7) + tmp1 = tmp1 * FIX_2_053119869; // sqrt(2) * ( c1+c3-c5+c7) + tmp2 = tmp2 * FIX_3_072711026; // sqrt(2) * ( c1+c3+c5-c7) + tmp3 = tmp3 * FIX_1_501321110; // sqrt(2) * ( c1+c3-c5-c7) + z1 = z1 * -FIX_0_899976223; // sqrt(2) * (c7-c3) + z2 = z2 * -FIX_2_562915447; // sqrt(2) * (-c1-c3) + z3 = z3 * -FIX_1_961570560; // sqrt(2) * (-c3-c5) + z4 = z4 * -FIX_0_390180644; // sqrt(2) * (c5-c3) + + z3 += z5; + z4 += z5; + + tmp0 += z1 + z3; + tmp1 += z2 + z4; + tmp2 += z2 + z3; + tmp3 += z1 + z4; + + // Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 + pWorking[DCTSIZE*0] = (int16_t) ((tmp10 + tmp3) >> (CONST_BITS-PASS1_BITS)); + pWorking[DCTSIZE*7] = (int16_t) ((tmp10 - tmp3) >> (CONST_BITS-PASS1_BITS)); + pWorking[DCTSIZE*1] = (int16_t) ((tmp11 + tmp2) >> (CONST_BITS-PASS1_BITS)); + pWorking[DCTSIZE*6] = (int16_t) ((tmp11 - tmp2) >> (CONST_BITS-PASS1_BITS)); + pWorking[DCTSIZE*2] = (int16_t) ((tmp12 + tmp1) >> (CONST_BITS-PASS1_BITS)); + pWorking[DCTSIZE*5] = (int16_t) ((tmp12 - tmp1) >> (CONST_BITS-PASS1_BITS)); + pWorking[DCTSIZE*3] = (int16_t) ((tmp13 + tmp0) >> (CONST_BITS-PASS1_BITS)); + pWorking[DCTSIZE*4] = (int16_t) ((tmp13 - tmp0) >> (CONST_BITS-PASS1_BITS)); + + pMatrix++; // advance pointers to next column + pWorking++; + } + + // debug first pass + _PrintMatrix16((int16_t *)&WorkingMatrix, "iDCT pass one"); + + /* Pass 2: process rows from work array, store into output array. + Note that we must descale the results by a factor of 8 == 2**3, + and also undo the PASS1_BITS scaling. */ + for (uRow = 0, pWorking=(int16_t*)&WorkingMatrix; uRow < DCTSIZE; uRow++, pDataDest += uStepV1) + { + // Even part: reverse the even part of the forward DCT. The rotator is sqrt(2)*c(-6). + z2 = (int32_t) pWorking[2]; + z3 = (int32_t) pWorking[6]; + + z1 = (z2 + z3) * FIX_0_541196100; + tmp2 = z1 + (z3 * -FIX_1_847759065); + tmp3 = z1 + (z2 * FIX_0_765366865); + + tmp0 = ((int32_t) pWorking[0] + (int32_t) pWorking[4]) << CONST_BITS; + tmp1 = ((int32_t) pWorking[0] - (int32_t) pWorking[4]) << CONST_BITS; + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Odd part per figure 8; the matrix is unitary and hence its + transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. */ + tmp0 = (int32_t) pWorking[7]; + tmp1 = (int32_t) pWorking[5]; + tmp2 = (int32_t) pWorking[3]; + tmp3 = (int32_t) pWorking[1]; + + z1 = tmp0 + tmp3; + z2 = tmp1 + tmp2; + z3 = tmp0 + tmp2; + z4 = tmp1 + tmp3; + z5 = (z3 + z4) * FIX_1_175875602; // sqrt(2) * c3 + + tmp0 = tmp0 * FIX_0_298631336; // sqrt(2) * (-c1+c3+c5-c7) + tmp1 = tmp1 * FIX_2_053119869; // sqrt(2) * ( c1+c3-c5+c7) + tmp2 = tmp2 * FIX_3_072711026; // sqrt(2) * ( c1+c3+c5-c7) + tmp3 = tmp3 * FIX_1_501321110; // sqrt(2) * ( c1+c3-c5-c7) + z1 = z1 * -FIX_0_899976223; // sqrt(2) * (c7-c3) + z2 = z2 * -FIX_2_562915447; // sqrt(2) * (-c1-c3) + z3 = z3 * -FIX_1_961570560; // sqrt(2) * (-c3-c5) + z4 = z4 * -FIX_0_390180644; // sqrt(2) * (c5-c3) + + z3 += z5; + z4 += z5; + + tmp0 += z1 + z3; + tmp1 += z2 + z4; + tmp2 += z2 + z3; + tmp3 += z1 + z4; + + #if DIRTYJPG_DEBUG_IDCT + WorkingMatrix2[0+(uRow*8)] = (((tmp10 + tmp3) >> (CONST_BITS+PASS1_BITS+3))); + WorkingMatrix2[7+(uRow*8)] = (((tmp10 - tmp3) >> (CONST_BITS+PASS1_BITS+3))); + WorkingMatrix2[1+(uRow*8)] = (((tmp11 + tmp2) >> (CONST_BITS+PASS1_BITS+3))); + WorkingMatrix2[6+(uRow*8)] = (((tmp11 - tmp2) >> (CONST_BITS+PASS1_BITS+3))); + WorkingMatrix2[2+(uRow*8)] = (((tmp12 + tmp1) >> (CONST_BITS+PASS1_BITS+3))); + WorkingMatrix2[5+(uRow*8)] = (((tmp12 - tmp1) >> (CONST_BITS+PASS1_BITS+3))); + WorkingMatrix2[3+(uRow*8)] = (((tmp13 + tmp0) >> (CONST_BITS+PASS1_BITS+3))); + WorkingMatrix2[4+(uRow*8)] = (((tmp13 - tmp0) >> (CONST_BITS+PASS1_BITS+3))); + #endif + + pDataDest[0*uStepH1] = pRanger8[(((tmp10 + tmp3) >> (CONST_BITS+PASS1_BITS+3)))]; + pDataDest[7*uStepH1] = pRanger8[(((tmp10 - tmp3) >> (CONST_BITS+PASS1_BITS+3)))]; + pDataDest[1*uStepH1] = pRanger8[(((tmp11 + tmp2) >> (CONST_BITS+PASS1_BITS+3)))]; + pDataDest[6*uStepH1] = pRanger8[(((tmp11 - tmp2) >> (CONST_BITS+PASS1_BITS+3)))]; + pDataDest[2*uStepH1] = pRanger8[(((tmp12 + tmp1) >> (CONST_BITS+PASS1_BITS+3)))]; + pDataDest[5*uStepH1] = pRanger8[(((tmp12 - tmp1) >> (CONST_BITS+PASS1_BITS+3)))]; + pDataDest[3*uStepH1] = pRanger8[(((tmp13 + tmp0) >> (CONST_BITS+PASS1_BITS+3)))]; + pDataDest[4*uStepH1] = pRanger8[(((tmp13 - tmp0) >> (CONST_BITS+PASS1_BITS+3)))]; + + pWorking += DCTSIZE; // advance pointer to next row + } + + // debug second pass + _PrintMatrix16((int16_t *)&WorkingMatrix2, "iDCT pass two"); +} + +/*F********************************************************************************/ +/*! + \Function _InitComp + + \Description + Init component table. + + \Input *pState - state ref + \Input *pFrame - pointer to SOS (start of scan) frame + + \Version 03/01/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _InitComp(DirtyJpgStateT *pState, const uint8_t *pFrame) +{ + CompTableT *pCompTable; + QuantTableT *pQuantTable; + uint32_t uExpFunc; + + // ref comp table + pCompTable = &pState->CompTable[pFrame[0]]; + + // ref huffman tables + pCompTable->pDCHuff = &pState->HuffTable[0+(pFrame[1]>>4)]; + pCompTable->pACHuff = &pState->HuffTable[4+(pFrame[1]&3)]; + #if DIRTYJPG_DEBUG_HUFF + NetPrintf(("dirtyjpg: using dc=%d ac=%d\n", pFrame[1]>>4, pFrame[1]&3)); + #endif + + // reset differential encoding + pCompTable->Matrix[0] = 0; + + // determine expansion function + uExpFunc = ((pCompTable->uExpVert - 1) << 2) + (pCompTable->uExpHorz - 1); + + // ref expansion table + if (uExpFunc == 0) + { + pCompTable->pExpand = _Expand0; + } + else if (uExpFunc == 0x0101) + { + pCompTable->pExpand = _Expand3hv; + } + else + { + pCompTable->pExpand = _ExpandAny; + } + + // get quantify table pointer + pQuantTable = &pState->QuantTable[pCompTable->uQuantNum]; + + // copy the quantify table + ds_memcpy_s(pCompTable->Quant, sizeof(pCompTable->Quant), pQuantTable, sizeof(*pQuantTable)); + + // ref inverse function + pCompTable->pInvert = _TransLLM; +} + +/*F********************************************************************************/ +/*! + \Function _PutColor + + \Description + Convert an MCU from YCbCr to ARGB ($$tmp assume color) + + \Input *pState - state ref + \Input *pCompTable - pointer to component table + \Input uDstHOff - horizontal mcu offset + \Input uDstVOff - vertical mcu offset + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _PutColor(DirtyJpgStateT *pState, CompTableT *pCompTable, uint32_t uDstHOff, uint32_t uDstVOff) +{ + uint32_t uSrcH, uSrcV, uStepSrcH, uStepSrcV, uMaxSrcH, uMaxSrcV; + uint32_t uDstH, uDstV, uStepDstH, uStepDstV, uMaxDstH, uMaxDstV; + const uint8_t *pDataSrc; + uint8_t *pDataDst; + int16_t Y, Cb_hi, Cb_lo, Cr_hi, Cr_lo; + uint32_t R, G, B; + + // ref source step values + uStepSrcH = pCompTable->uStepHorz0; + uMaxSrcH = pCompTable->uStepHorzM; + uStepSrcV = pCompTable->uStepVert0; + uMaxSrcV = pCompTable->uStepVertM; + + // ref dest step values + uStepDstH = 4; // ARGB + uMaxDstH = uStepDstH * pState->uBufWidth; + uStepDstV = uStepDstH * pState->uBufWidth; + uMaxDstV = uStepDstV * pState->uBufHeight; + + // scale mcu step values to get pixel offsets + uDstHOff *= 8*4*pState->uMcuHorz; + uDstVOff *= uMaxDstH * 8 * pState->uMcuVert; + + // transform the data + for (uSrcV = 0, uDstV = uDstVOff; (uSrcV < uMaxSrcV) && (uDstV < uMaxDstV); uSrcV += uStepSrcV, uDstV += uStepDstV) + { + for (uSrcH = 0, uDstH = uDstHOff; (uSrcH < uMaxSrcH) && (uDstH < uMaxDstH); uSrcH += uStepSrcH, uDstH += uStepDstH) + { + pDataSrc = pState->aMCU + uSrcH + uSrcV; + pDataDst = pState->pImageBuf + uDstH + uDstV; + + // get YCbCr components + Y = pDataSrc[0]; + Cb_hi = _Mult17_03h[pDataSrc[1]]; // get 1.772(Cb-128) + Cb_lo = _Mult17_03l[pDataSrc[1]]; // get -0.34414(Cb-128) + Cr_hi = _Mult07_14h[pDataSrc[2]]; // get -0.71414(Cr-128) + Cr_lo = _Mult07_14l[pDataSrc[2]]; // get 1.402(Cr-128) + + // convert to RGB +#ifdef __SNC__ + R = *((uint8_t *)(_pRanger8 + Y + Cr_lo)); // Y + 1.402(Cr-128) + G = *((uint8_t *)(_pRanger8 + Y + ((Cr_hi + Cb_lo) >> 4))); // Y - 0.71414(Cr-128) - 0.34414(Cb-128) + B = *((uint8_t *)(_pRanger8 + Y + Cb_hi)); // Y + 1.772(Cb-128) +#else + R = _pRanger8[Y + Cr_lo]; // Y + 1.402(Cr-128) + G = _pRanger8[Y + ((Cr_hi + Cb_lo) >> 4)]; // Y - 0.71414(Cr-128) - 0.34414(Cb-128) + B = _pRanger8[Y + Cb_hi]; // Y + 1.772(Cb-128) +#endif + // store in output buffer + pDataDst[0] = 0xff; + pDataDst[1] = R; + pDataDst[2] = G; + pDataDst[3] = B; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _DoRST + + \Description + Restart decoder after an RST marker was encountered. + + \Input *pState - state ref + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _DoRST(DirtyJpgStateT *pState) +{ + uint32_t uComp; + + // reset bitbuffer + pState->uBitsConsumed += pState->uBitsAvail; + pState->uBitsAvail = 0; + pState->uBitfield = 0; + + // reset dc for all components + for (uComp = 0; uComp < sizeof(pState->CompTable)/sizeof(pState->CompTable[0]); uComp++) + { + pState->CompTable[uComp].Matrix[0] = 0; + } + + // done the restart, clear the flag + pState->bRestart = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function _GetMCU + + \Description + Decode an MCU into MCU buffer. + + \Input *pState - state ref + \Input *pFrame - pointer to SOS (start of scan) component frame data + \Input uCompCount - number of components (must be 1 or 3) + + \Output + uint32_t - number of bits consumed from the bitstream, or -1 + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _GetMCU(DirtyJpgStateT *pState, const uint8_t *pFrame, uint32_t uCompCount) +{ + uint32_t uCompIdx, uCompVert, uCompHorz, uOutOffset; + CompTableT *pCompTable; + + // unpack and translate all components + for (uCompIdx = 0; uCompIdx < uCompCount; uCompIdx += 1) + { + // ref comp table + uint32_t uIndex = pFrame[uCompIdx*2]; + if (uIndex > 3) + { + continue; + } + pCompTable = &pState->CompTable[uIndex]; + + // unpack and translate component + for (uOutOffset = uCompIdx, uCompVert = 0; uCompVert < pCompTable->uCompVert; uCompVert += 1) + { + // save start of row + uint32_t uTmpOffset = uOutOffset; + + // unpack and translate row + for (uCompHorz = 0; uCompHorz < pCompTable->uCompHorz; uCompHorz += 1) + { + // unpack a component into record matrix + if (_UnpackComp(pState, pCompTable, pState->pCompData) == 0xffffffff) + { + return(0xffffffff); + } + + // translate the component into mcu buffer + pCompTable->pInvert(pState, pCompTable, uOutOffset); + + // increment to next component + uOutOffset += pCompTable->uStepHorz8; + } + + // increment to next output row + uOutOffset = uTmpOffset + pCompTable->uStepVert8; + } + + // expand component + pCompTable->pExpand(pState, pCompTable, uCompIdx); + } + + // if there was a restart, do it now + if (pState->bRestart) + { + _DoRST(pState); + } + + // return updated bit offset + return(pState->uBitsConsumed); +} + +/*F********************************************************************************/ +/*! + \Function _ParseDQT + + \Description + Parse a DQT (quantization) table. + + \Input *pState - state ref + \Input *pFrame - pointer to DQT frame data + \Input *pFinal - end of DQT frame + + \Output + int32_t - DIRTYJPG_ERR_* + + \Version 02/23/2006 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _ParseDQT(DirtyJpgStateT *pState, const uint8_t *pFrame, const uint8_t *pFinal) +{ + uint32_t uCount; + uint32_t uIndex; + uint32_t uMask = 0; + uint32_t uShift = 0; + uint32_t uSkip = 1; + + // process tables + while (pFrame < pFinal) + { + // decode index + uIndex = pFrame[0] & 15; + + // if 16-bit encoded, setup to shift first byte and unmask second + if (pFrame[0] >= 16) + { + uMask = 255; + uShift = 8; + uSkip = 2; + } + + // skip the header + pFrame += 1; + + // decode and copy the table + for (uCount = 0; uCount < 64; ++uCount) + { + pState->QuantTable[uIndex][uCount] = (pFrame[0]<HuffTable[(pFrame[0]>>2) | (pFrame[0]&3)]; + + // offset to value/shift lookup + uOffset = 0; + + // figure out max code based on first 8 huffman tables (1-8 bits consolidated) + for (uSize=1, uCode=0; uSize < 8; uSize++) + { + uCode += pFrame[uSize]; + uCode += uCode; + } + uCode += pFrame[uSize]; + + // setup first table entry + pHead = &pTable->Head[0]; + pHead->uOffset = uOffset; + // initial table is larger than the rest (based on code size) + uOffset += uCode; + pHead->uMaxCode = uCode; + pHead->uShift = uSize; + pHead += 1; + + // do remaining tables + while (++uSize < 17) + { + uCode += uCode; + uCode += pFrame[uSize]; + + // only add header if it contains data + if (pFrame[uSize] > 0) + { + pHead->uOffset = uOffset; + // other tables are sized based on exact code counts + uOffset += pFrame[uSize]; + pHead->uMaxCode = uCode; + pHead->uShift = uSize; + pHead += 1; + } + } + + // terminate header list + pHead->uShift = 0; + pHead->uMaxCode = uCode*2; + pHead->uOffset = uOffset; // so we can calc length of final table + + // previous max code + uCode = 0; + // start with one-bit codes + uSize = 1; + // copy value/shift data to all the tables + pData = pFrame+17; + for (pHead = &pTable->Head[0]; pHead->uShift != 0; ++pHead) + { + // change from size to shift count + uShift = pHead->uShift; + pHead->uShift = 16-uShift; + + // save the previous max code as our min code + pHead->uMinCode = uCode; + // get current max code + uCode = pHead->uMaxCode; + // right align the bits + uCode <<= (16-uShift); + // save the new right-aligned max code + pHead->uMaxCode = uCode; + + // fill out the data portion (walk current data offset to next data offset) + for (uCount = pHead[0].uOffset; uCount < pHead[1].uOffset;) + { + // copy over the huffman values + for (uIndex = 0; uIndex < pFrame[uSize]; ++uIndex) + { + // repeat count is variable in table 0 but always 1 for tables 1+ + uint32_t uRepeat = (1 << uShift) >> uSize; + // copy the huffman value into the appropriate slots + ds_memset(pTable->Code+uCount, *pData, uRepeat); + // copy the corresponding shift counts into the matching slots + ds_memset(pTable->Step+uCount, uSize, uRepeat); + uCount += uRepeat; + pData += 1; + } + uSize += 1; + } + } + + // move to end of data + pFrame = pData; + } + + // make sure all data was processed + return(pFrame == pFinal ? 0 : DIRTYJPG_ERR_BADDHT); +} + +/*F********************************************************************************/ +/*! + \Function _ParseSOF0 + + \Description + Parse a SOF0 (start of frame 0) frame. + + \Input *pState - state ref + \Input *pFrame - pointer to sof0 frame data + \Input *pFinal - end of sof0 frame + + \Output + int32_t - DIRTYJPG_ERR_* + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ParseSOF0(DirtyJpgStateT *pState, const uint8_t *pFrame, const uint8_t *pFinal) +{ + uint32_t uComp, uCompCount; + uint32_t uHorzSampFactor, uVertSampFactor; + CompTableT *pCompTable; + const uint8_t *pComp; + + // validate 8bits per component + if (pFrame[0] != 8) + { + return(DIRTYJPG_ERR_BITDEPTH); + } + + pState->uImageHeight = (pFrame[1]<<8)|(pFrame[2]<<0); + pState->uImageWidth = (pFrame[3]<<8)|(pFrame[4]<<0); + uCompCount = pFrame[5]; + + // only support three components + if (uCompCount > 3) + { + return(DIRTYJPG_ERR_BADSOF0); + } + + // parse components to find image mcu + for (pComp = pFrame+6, uComp = 0; uComp < uCompCount; uComp++, pComp += 3) + { + uVertSampFactor = pComp[1] & 0xf; + if (pState->uMcuVert < uVertSampFactor) + { + pState->uMcuVert = uVertSampFactor; + } + uHorzSampFactor = pComp[1] >> 4; + if (pState->uMcuHorz < uHorzSampFactor) + { + pState->uMcuHorz = uHorzSampFactor; + } + } + + // calculate image dimensions in terms of number of mcu blocks + pState->uMcuHorzM = (pState->uImageWidth + (8*pState->uMcuHorz)-1) / (pState->uMcuHorz*8); + pState->uMcuVertM = (pState->uImageHeight + (8*pState->uMcuVert)-1) / (pState->uMcuVert*8); + + // parse components to fill in comp table + for (pComp = pFrame+6, uComp = 0; uComp < uCompCount; uComp++, pComp += 3) + { + // skip invalid tabls + if (pComp[0] > 3) + { + NetPrintf(("dirtyjpg: skipping invalid comp table index\n")); + continue; + } + + // ref comp table + pCompTable = &pState->CompTable[pComp[0]]; + + // init mcu + pCompTable->uMcuHorz = pState->uMcuHorz; + pCompTable->uMcuVert = pState->uMcuVert; + + // get sampling factors + pCompTable->uCompVert = pComp[1] & 0xf; + pCompTable->uCompHorz = pComp[1] >> 4; + + // compute expansion factors + pCompTable->uExpHorz = pCompTable->uMcuHorz / pCompTable->uCompHorz; + pCompTable->uExpVert = pCompTable->uMcuVert / pCompTable->uCompVert; + + // calculate step based on aligned size + pCompTable->uStepHorz0 = 4; + pCompTable->uStepVert0 = pCompTable->uMcuHorz * 8 * 4; + + // compute scaled sample step sizes + pCompTable->uStepHorz1 = pCompTable->uStepHorz0 * pCompTable->uExpHorz; + pCompTable->uStepVert1 = pCompTable->uStepVert0 * pCompTable->uExpVert; + + // compute step * 8 + pCompTable->uStepHorz8 = pCompTable->uStepHorz1 * 8; + pCompTable->uStepVert8 = pCompTable->uStepVert1 * 8; + + // compute mcu step sizes + pCompTable->uStepHorzM = (pCompTable->uCompHorz * 8) * pCompTable->uStepHorz1; + pCompTable->uStepVertM = (pCompTable->uCompVert * 8) * pCompTable->uStepVert1; + + // reference quantization table + pCompTable->uQuantNum = pComp[2]&0x3; + } + + // index past comp table + pFrame = pComp; + + return(pFrame == pFinal ? 0 : DIRTYJPG_ERR_BADSOF0); +} + +/*F********************************************************************************/ +/*! + \Function _ParseSOS + + \Description + Parse the SOS (Start of Scan) frame + + \Input *pState - state ref + \Input *pFrame - pointer to SOS (start of scan) frame data + \Input *pFinal - end of frame data + + \Output + uint8_t * - pointer past end of frame, or NULL if there was an error + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_ParseSOS(DirtyJpgStateT *pState, const uint8_t *pFrame, const uint8_t *pFinal) +{ + uint32_t uCompIdx, uCompCount, uMcuH, uMcuV; + const uint8_t *pCompData; + + // point to compressed data + pCompData = pFrame - 2; + pCompData = pCompData + ((pCompData[0] << 8) | pCompData[1]); + + // save compressed data + pState->pCompData = pCompData; + + // get component count and skip to next byte + uCompCount = *pFrame++; + + // init component tables + for (uCompIdx = 0; uCompIdx < uCompCount; uCompIdx++) + { + _InitComp(pState, &pFrame[uCompIdx*2]); + } + + // decode mcus and blit them to the output buffer + for (uMcuV = 0; uMcuV < pState->uMcuVertM; uMcuV++) + { + for (uMcuH = 0; uMcuH < pState->uMcuHorzM; uMcuH++) + { + // process an MCU + if (_GetMCU(pState, pFrame, uCompCount) == 0xffffffff) + { + return(NULL); + } + + // put the colors into buffer + _PutColor(pState, &pState->CompTable[1], uMcuH, uMcuV); + } + } + + // return number of bytes consumed + return(pState->pCompData + (pState->uBitsConsumed+7)/8); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyJpgValidate + + \Description + Validate we have a supported jpeg file + + \Input *pState - state ref + \Input *pImage - pointer to image data + \Input uLength - size of image data + + \Output + int32_t - DIRTYJPG_ERR_* + + \Version 07/30/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _DirtyJpgValidate(DirtyJpgStateT *pState, const uint8_t *pImage, uint32_t uLength) +{ + // validate SOI header + if ((uLength < 16) || (pImage[0] != 0xff) || (pImage[1] != 0xd8)) + { + return(DIRTYJPG_ERR_TOOSHORT); + } + // validate app header + if ((pImage[2] != 0xff) || ((pImage[3] != 0xe0) && (pImage[3] != 0xe1))) + { + return(DIRTYJPG_ERR_NOMAGIC); + } + // validate format marker + if (memcmp(pImage+6, "JFIF", 4) && memcmp(pImage+6, "Exif", 4)) + { + return(DIRTYJPG_ERR_NOFORMAT); + } + return(DIRTYJPG_ERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyJpgDecodeParse + + \Description + Parse the JFIF image data. + + \Input *pState - state ref + \Input *pImage - pointer to image data + \Input uLength - size of image data + \Input bIdentify - identify if this is a JFIF image or not + + \Output + int32_t - DIRTYJPG_ERR_* + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _DirtyJpgDecodeParse(DirtyJpgStateT *pState, const uint8_t *pImage, uint32_t uLength, uint8_t bIdentify) +{ + int32_t iError = 0; + const uint8_t *pStart=pImage, *pFinal = pImage+uLength; + uint8_t uType; + uint32_t uSize; + + // save pointer past end of input + pState->pSrcFinal = pFinal; + + // decode all the frames + while (pImage < pFinal) + { + // get the frame info + uType = *pImage++; + + // gobble alignment byte + if (uType == 0xff) + { + continue; + } + + // get size (no size for eoi) + uSize = (uType != 0xd9) ? (pImage[0]<<8)|(pImage[1]<<0) : 0; + + // validate the frame size + if ((pImage+uSize) > pFinal) + { + iError = DIRTYJPG_ERR_ENDOFDATA; + break; + } + + #if DIRTYJPG_VERBOSE + NetPrintf(("dirtyjpg: frame=0x%02x size=%d\n", uType, uSize)); + #endif + + // parse the frame + switch (uType) + { + // app0 frame + case 0xe0: + { + // verify its got JFIF header + if ((pImage[2] == 'J') && (pImage[3] == 'F') && (pImage[4] == 'I') && (pImage[5] == 'F')) + { + // extract the version and make sure we support it + pState->uVersion = (pImage[7]<<8)|(pImage[8]>>0); + if ((pState->uVersion < 0x0100) || (pState->uVersion > 0x0102)) + { + iError = DIRTYJPG_ERR_BADVERS; + break; + } + + // extract & save aspect info + pState->uAspectRatio = pImage[9]; + pState->uAspectHorz = (pImage[10]<<8)|(pImage[11]<<0); + pState->uAspectVert = (pImage[12]<<8)|(pImage[13]<<0); + + // if just doing identification, bail + if (bIdentify) + { + return(DIRTYJPG_ERR_NONE); + } + break; + } + } + // dqt frame + case 0xdb: + { + iError = _ParseDQT(pState, pImage+2, pImage+uSize); + break; + } + // dht frame + case 0xc4: + { + iError = _ParseDHT(pState, pImage+2, pImage+uSize); + break; + } + // sof0 frame (baseline jpeg) + case 0xc0: + { + iError = _ParseSOF0(pState, pImage+2, pImage+uSize); + break; + } + // sof2 frame (progressive jpeg) + case 0xc2: + NetPrintf(("dirtyjpg: warning; SOF2 frame indicates a progressively encoded image, which is not supported\n")); + iError = DIRTYJPG_ERR_NOSUPPORT; + break; + + // sos (start of scan) frame + case 0xda: + { + if (pState->pImageBuf != NULL) + { + pImage = _ParseSOS(pState, pImage+2, pImage+uSize); + if (pImage == NULL) + { + iError = DIRTYJPG_ERR_DECODER; + } + } + else + { + iError = DIRTYJPG_ERR_NOBUFFER; + pState->uLastOffset = (uint32_t)(pImage-pStart); + } + break; + } + // skip "no action" frames + case 0xd9: // eoi frame + case 0xfe: // com frame + { + break; + } + + default: + if ((uType & 0xf0) != 0xe0) + { + NetPrintf(("dirtyjpg: ignoring unrecognized frame type 0x%02x\n", uType)); + } + #if DIRTYJPG_VERBOSE + else + { + NetPrintf(("dirtyjpg: ignoring application-specific frame type 0x%02x\n", uType)); + } + #endif + break; + } + + // bail if we got an error + if (iError != 0) + { + break; + } + + // move to next record + pImage += uSize; + } + + // save last error + pState->iLastError = iError; + return(iError); +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function DirtyJpgCreate + + \Description + Create the DirtyJpg module state + + \Output + DirtyJpgStateT * - pointer to new ref, or NULL if error + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +DirtyJpgStateT *DirtyJpgCreate(void) +{ + DirtyJpgStateT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pState = DirtyMemAlloc(sizeof(*pState), DIRTYJPG_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtyjpg: error allocating module state\n")); + return(NULL); + } + ds_memclr(pState, sizeof(*pState)); + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + + // return the state pointer + return(pState); +} + +/*F********************************************************************************/ +/*! + \Function DirtyJpgReset + + \Description + Reset the DirtyJpg module state + + \Input *pState - pointer to module state + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void DirtyJpgReset(DirtyJpgStateT *pState) +{ + pState->uLastOffset = 0; + + pState->uBitfield = 0; + pState->uBitsAvail = 0; + pState->uBitsConsumed = 0; + pState->uBytesRead = 0; + + // clear all comp tables + ds_memclr(pState->CompTable, sizeof(pState->CompTable)); + + // clear all quant tables + ds_memclr(pState->QuantTable, sizeof(pState->QuantTable)); + + // clear all huffman tables + ds_memclr(pState->HuffTable, sizeof(pState->HuffTable)); + + // set the image buffer to NULL + pState->pImageBuf = NULL; + + pState->bRestart = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function DirtyJpgDestroy + + \Description + Destroy the DirtyJpg module + + \Input *pState - pointer to module state + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void DirtyJpgDestroy(DirtyJpgStateT *pState) +{ + DirtyMemFree(pState, DIRTYJPG_MEMID, pState->iMemGroup, pState->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function DirtyJpgIdentify + + \Description + Identify if input image is a JPG (JFIF) image. + + \Input *pState - jpg module state + \Input *pImage - pointer to image data + \Input uLength - size of image data + + \Output + int32_t - TRUE if a JPG, else FALSE + + \Version 03/09/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyJpgIdentify(DirtyJpgStateT *pState, const uint8_t *pImage, uint32_t uLength) +{ + int32_t iResult; + + // make sure it's a supported jpeg format + if ((iResult = _DirtyJpgValidate(pState, pImage, uLength)) == DIRTYJPG_ERR_NONE) + { + // run the parser + iResult = _DirtyJpgDecodeParse(pState, pImage+2, uLength-2, TRUE); + } + + // return true if it's a JFIF jpg, else false + return((iResult == DIRTYJPG_ERR_NOBUFFER) || (iResult == DIRTYJPG_ERR_NONE)); +} + +/*F********************************************************************************/ +/*! + \Function DirtyJpgDecodeHeader + + \Description + Parse JPG header. + + \Input *pState - pointer to module state + \Input *pJpgHdr - pointer to jpg header + \Input *pImage - pointer to image buf + \Input uLength - size of input image + + \Output + int32_t - DIRTYJPG_ERR_* + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyJpgDecodeHeader(DirtyJpgStateT *pState, DirtyJpgHdrT *pJpgHdr, const uint8_t *pImage, uint32_t uLength) +{ + int32_t iResult; + + // reset internal state + DirtyJpgReset(pState); + + // make sure it's a supported jpeg format + if ((iResult = _DirtyJpgValidate(pState, pImage, uLength)) < 0) + { + return(iResult); + } + + // run the parser -- we should get a no buffer error + if ((iResult = _DirtyJpgDecodeParse(pState, pImage+2, uLength-2, FALSE)) == DIRTYJPG_ERR_NOBUFFER) + { + // this is expected during header parsing -- save info and return no error + pJpgHdr->pImageData = pImage; + pJpgHdr->uLength = uLength; + pJpgHdr->uWidth = pState->uImageWidth; + pJpgHdr->uHeight = pState->uImageHeight; + iResult = 0; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function DirtyJpgDecodeImage + + \Description + Parse JPG image. + + \Input *pState - pointer to module state + \Input *pJpgHdr - pointer to jpg header + \Input *pImageBuf - pointer to image buf + \Input iBufWidth - image buf width + \Input iBufHeight - image buf height + + \Output + int32_t - DIRTYJPG_ERR_* + + \Version 02/23/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t DirtyJpgDecodeImage(DirtyJpgStateT *pState, DirtyJpgHdrT *pJpgHdr, uint8_t *pImageBuf, int32_t iBufWidth, int32_t iBufHeight) +{ + int32_t iError; + + // make sure we are in proper state + if (pState->iLastError != DIRTYJPG_ERR_NOBUFFER) + { + return(DIRTYJPG_ERR_BADSTATE); + } + + // setup the output buffer + pState->pImageBuf = pImageBuf; + pState->uBufWidth = (unsigned)iBufWidth; + pState->uBufHeight = (unsigned)iBufHeight; + + // resume parsing at last spot + iError = _DirtyJpgDecodeParse(pState, pJpgHdr->pImageData + pState->uLastOffset, pJpgHdr->uLength-pState->uLastOffset, FALSE); + return(iError); +} diff --git a/src/thirdparty/dirtysdk/source/graph/dirtypng.c b/src/thirdparty/dirtysdk/source/graph/dirtypng.c new file mode 100644 index 00000000..8af861e8 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/graph/dirtypng.c @@ -0,0 +1,2829 @@ +/*H********************************************************************************/ +/*! + \File dirtypng.c + + \Description + Routines to decode a PNG into a raw image and palette. These routines + were written from scratch based on the published specifications. The + functions and style were heavily based on the dirtygif.c and dirtyjpeg.c + files coded by James Brookes. + + \Notes + References: + Info page: http://www.libpng.org/pub/png/ + Sample images: http://www.libpng.org/pub/png/pngsuite.html + W3C Specification: http://www.libpng.org/pub/png/spec/iso/index-object.html + + \Copyright + Copyright (c) 2007 Electronic Arts Inc. + + \Version 05/10/2007 (cadam) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/graph/dirtypng.h" + +/*** Defines **********************************************************************/ + +#define DIRTYPNG_DEBUG (FALSE) +#define DIRTYPNG_DEBUG_CHUNKS (DIRTYPNG_DEBUG && FALSE) +#define DIRTYPNG_DEBUG_ZLIB (DIRTYPNG_DEBUG && FALSE) +#define DIRTYPNG_DEBUG_DEFLATE (DIRTYPNG_DEBUG && FALSE) +#define DIRTYPNG_DEBUG_DYNAMIC (DIRTYPNG_DEBUG && FALSE) +#define DIRTYPNG_DEBUG_INFLATE (DIRTYPNG_DEBUG && FALSE) +#define DIRTYPNG_DEBUG_LDCODES (DIRTYPNG_DEBUG && FALSE) + +//! define set lengths +#define TYPESTART 4 +#define DATASTART 8 +#define HEADER_LEN 25 +#define SIGNATURE_LEN 8 + +//! little endian of the 32-bit CRC polynomial 1110 1101 1011 1000 1000 0011 0010 0000 (1) +#define POLYNOMIAL 0xedb88320 + +//! define max values for HLIT + 257, HDIST + 1, and HCLEN + 4 +#define MAXLIT 286 +#define MAXDIST 32 +#define MAXCLEN 19 + +/*** Macros ***********************************************************************/ + +//! macro to validate that enough space exists in the file for a particular header +#define DIRTYPNG_Validate(_pData, _pEnd, _size, _retcode) \ +{ \ + if (((_pEnd) - (_pData)) < (_size)) \ + { \ + return(_retcode); \ + } \ +} + +#define DIRTYPNG_GetInt32(_pData, _pOffset) \ + ((_pData[_pOffset] << 24) | (_pData[_pOffset+1] << 16) | (_pData[_pOffset+2] << 8) | _pData[_pOffset+3]) + +/*** Type Definitions *************************************************************/ + +//! huffman decode tables +typedef struct HuffLitTableT +{ + uint16_t uNoCodes; + uint16_t uMinCode; + uint16_t uMaxCode; + uint16_t Codes[MAXLIT]; +} HuffLitTableT; + +typedef struct HuffDistTableT +{ + uint16_t uNoCodes; + uint16_t uMinCode; + uint16_t uMaxCode; + uint16_t Codes[MAXDIST]; +} HuffDistTableT; + +typedef struct HuffCLenTableT +{ + uint16_t uNoCodes; + uint16_t uMinCode; + uint16_t uMaxCode; + uint16_t Codes[MAXCLEN]; +} HuffCLenTableT; + +typedef struct PngPaletteT +{ + uint32_t uNumColors; + uint8_t aColor[256][3]; +} PngPaletteT; + +//! module state +struct DirtyPngStateT +{ + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData;//!< user data associated with mem group + + uint32_t uBufWidth; //!< width of buffer to decode into + uint32_t uBufHeight; //!< height of buffer to decode into + + uint32_t uWidthOffset; //!< offset for which byte of the current scanline we are at + uint32_t uHeightOffset; //!< offset for which scanline of the buffer we are at + uint32_t uImageOffset; //!< current offset of the output buffer + + uint32_t uDataBits; //!< bits grabbed from the IDAT data + uint32_t uByteOffset; //!< byte offset from which the next data should be read + uint32_t uIdatStart; //!< byte offset from which the current IDAT data started + uint32_t uIdatLength; //!< length of the current IDAT chunk data being read + + uint16_t uCountIdat; //!< number of IDAT chunks parsed + + uint16_t uHLit; //!< number of literal/length codes + uint16_t uHDist; //!< number of distance codes + uint16_t uHCLen; //!< number of code length codes + + uint16_t uWindowLength; //!< current length of the window + uint16_t uWindowOffset; //!< current offset in the window + uint16_t uWindowSize; //!< window size to work with + + uint16_t uScanlineWidth; //!< width of the scanline in bytes + uint16_t uCurrentScanline; //!< current scanline that is to be written to the output buffer + + uint8_t uFilterType; //!< filter type + + uint8_t uCurrentPass; //!< current pass of the image being parsed + + uint8_t uPixelWidth; //!< number of bytes per pixel + + uint8_t uWindowLog; //!< log base 2 of the window size minus 8 + + uint8_t uChunkTime; //!< tIME chunk parsed + uint8_t uChunkPhys; //!< pHYs chunk parsed + uint8_t uChunkIccp; //!< iCCP or sRGB chunk parsed + uint8_t uChunkSbit; //!< sBIT chunk parsed + uint8_t uChunkGama; //!< gAMA chunk parsed + uint8_t uChunkChrm; //!< cHRM chunk parsed + uint8_t uChunkPlte; //!< PLTE chunk parsed + uint8_t uChunkTrns; //!< tRNS chunk parsed + uint8_t uChunkHist; //!< hIST chunk parsed + uint8_t uChunkBkgd; //!< bKGD chunk parsed + uint8_t uChunkIdat; //!< IDAT chunk parsed + uint8_t uParsingIdat; //!< last chunk parsed was an IDAT chunk + + uint8_t uBType; //!< compression type for the block + uint8_t uBlockEnd; //!< done reading the block + uint8_t uBitsLeft; //!< bits left in uDataBits + + uint8_t uLitMinBits; //!< smallest bit length of the literal/length codes + uint8_t uDistMinBits; //!< smallest bit length of the distance codes + + PngPaletteT Palette; + + uint8_t uCRCTableGenerated; //!< CRC table generated + uint32_t CRCTable[256]; //!< CRC table + + uint32_t InterlacePassPixels[7]; //!< number of pixels per scanline for each pass + uint32_t InterlaceScanlines[7]; //!< number of scanlines per pass + + uint8_t SlidingWindow[32768]; //!< sliding window + + HuffLitTableT LitTables[16]; //!< huffman tables for the literal/length codes + HuffDistTableT DistTables[16]; //!< huffman tables for the distance codes + HuffCLenTableT CLenTables[8]; //!< huffman tables for the code length alphabet + + uint8_t *pPrevScanline; //!< pointer to the previous scanline + uint8_t *pCurrScanline; //!< pointer to the current scanline + uint8_t *pScanlines[2]; //!< pointers to the two scanlines + + uint8_t *pImageBuf; //!< pointer to output image buffer (allocated by caller) +}; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +//! code lengths and how many extra bits to grab +static const uint16_t _HLIT_Codes[] = +{ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, + 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 +}; +static const uint8_t _HLIT_Bits[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, + 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 +}; + +//! code start distances and how many extra bits to grab +static const uint16_t _HDIST_Codes[] = +{ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, + 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, + 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 +}; +static const uint8_t _HDIST_Bits[] = +{ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, + 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 +}; + +//! code length value order for the code length alphabet +static const uint8_t _HCLEN_Codes[] = +{ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, + 11, 4, 12, 3, 13, 2, 14, 1, 15 +}; + +/*** Private Functions ************************************************************/ + +#if DIRTYPNG_DEBUG_CHUNKS +/*F********************************************************************************/ +/*! + \Function _DirtyPngPrintChunk + + \Description + Display the Chunk Information. Used only for debugging. + + \Input *pPngData - pointer to the PNG data + \Input uLength - length of the chunk to print + \Input uShowData - boolean (show data or not) + + Version 02/06/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngPrintChunk(const uint8_t *pPngData, uint32_t uLength, uint8_t uShowData) +{ + NetPrintf(("dirtypng: chunk type=%c%c%c%c, len=%d, crc=0x%08x\n", pPngData[TYPESTART], pPngData[TYPESTART+1], pPngData[TYPESTART+2], pPngData[TYPESTART+3], + uLength, DIRTYPNG_GetInt32(pPngData, uLength+DATASTART))); + if (uShowData != 0) + { + NetPrintMem(pPngData+DATASTART, uLength, "chunk data"); + } +} +#endif + +#if DIRTYPNG_DEBUG_DYNAMIC +/*F********************************************************************************/ +/*! + \Function _DirtyPngPrintLitTables + + \Description + Print the Literal/Length Tables. Used only for debugging. + + \Input *pState - pointer to the module state + + Version 02/09/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngPrintLitTables(DirtyPngStateT *pState) +{ + uint16_t u, v, uNumCodes; + + NetPrintf(("dirtypng: Literal/Length Table\n")); + for (u = 0; u < 16; u++) + { + if (pState->LitTables[u].uNoCodes) + { + NetPrintf(("dirtypng: NULL\n")); + } + else + { + uNumCodes = pState->LitTables[u].uMaxCode - pState->LitTables[u].uMinCode + 1; + + NetPrintf(("dirtypng: uMinCode %u : ", pState->LitTables[u].uMinCode)); + NetPrintf(("uMaxCode %u : ", pState->LitTables[u].uMaxCode)); + for (v = 0; v < uNumCodes; v++) + { + NetPrintf(("%u ", pState->LitTables[u].Codes[v])); + } + NetPrintf(("\n")); + } + } + NetPrintf(("\n")); +} +#endif + +#if DIRTYPNG_DEBUG_DYNAMIC +/*F********************************************************************************/ +/*! + \Function _DirtyPngPrintDistTables + + \Description + Print the Distance Tables. Used only for debugging. + + \Input *pState - pointer to the module state + + Version 02/09/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngPrintDistTables(DirtyPngStateT *pState) +{ + uint16_t u, v, uNumCodes; + + NetPrintf(("dirtypng: Distance Table\n")); + for (u = 0; u < 16; u++) + { + if (pState->DistTables[u].uNoCodes) + { + NetPrintf(("dirtypng: NULL\n")); + } + else + { + uNumCodes = pState->DistTables[u].uMaxCode - pState->DistTables[u].uMinCode + 1; + + NetPrintf(("dirtypng: uMinCode %u : ", pState->DistTables[u].uMinCode)); + NetPrintf(("uMaxCode %u : ", pState->DistTables[u].uMaxCode)); + for (v = 0; v < uNumCodes; v++) + { + NetPrintf(("%u ", pState->DistTables[u].Codes[v])); + } + NetPrintf(("\n")); + } + } + NetPrintf(("\n")); +} +#endif + +#if DIRTYPNG_DEBUG_DYNAMIC +/*F********************************************************************************/ +/*! + \Function _DirtyPngPrintCLenTables + + \Description + Print the Code Length Tables. Used only for debugging. + + \Input *pState - pointer to the module state + + \Output + None. + + Version 02/09/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngPrintCLenTables(DirtyPngStateT *pState) +{ + uint16_t u, v, uNumCodes; + + NetPrintf(("dirtypng: Code Length Table\n")); + for (u = 0; u < 8; u++) + { + if (pState->CLenTables[u].uNoCodes) + { + NetPrintf(("dirtypng: NULL\n")); + } + else + { + uNumCodes = pState->CLenTables[u].uMaxCode - pState->CLenTables[u].uMinCode + 1; + + NetPrintf(("dirtypng: uMinCode %u : ", pState->CLenTables[u].uMinCode)); + NetPrintf(("uMaxCode %u : ", pState->CLenTables[u].uMaxCode)); + for (v = 0; v < uNumCodes; v++) + { + NetPrintf(("%u ", pState->CLenTables[u].Codes[v])); + } + NetPrintf(("\n")); + } + } + NetPrintf(("\n")); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _DirtyPngGenerateCRCTable + + \Description + Generate the CRC Table by calculating the CRC for all byte values possible. + + \Input *pState - pointer to the module state + + Version 05/02/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngGenerateCRCTable(DirtyPngStateT *pState) +{ + uint32_t uCRC, uByteValue, uBit; + + for (uByteValue = 0; uByteValue < 256; uByteValue++) + { + uCRC = uByteValue; + + for (uBit = 0; uBit < 8; uBit++) + { + if (uCRC & 1) + { + uCRC = (uCRC >> 1) ^ POLYNOMIAL; + } + else + { + uCRC >>= 1; + } + } + + pState->CRCTable[uByteValue] = uCRC; + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngCheckCRC + + \Description + Calculate the CRC value for the chunk and compare to the received CRC. + + \Input *pState - pointer to the module state + \Input *pPngData - pointer to the PNG data + \Input uLength - length of the chunk being checked + + \Output + int32_t - nonnegative=success, negative=error + + Version 02/06/2007 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _DirtyPngCheckCRC(DirtyPngStateT *pState, const uint8_t *pPngData, uint32_t uLength) +{ + uint32_t uLen, uChunkCRC; + // PNG requires the CRC initially be set to all 1s + uint32_t uCRC = 0xFFFFFFFF; + + for (uLen = 0; uLen < uLength+TYPESTART; uLen++) + { + uCRC = uCRC ^ pPngData[uLen+TYPESTART]; + + uCRC = pState->CRCTable[uCRC & 0x000000FF] ^ (uCRC >> 8); + } + + uChunkCRC = DIRTYPNG_GetInt32(pPngData, uLength+DATASTART); + + // PNG requires that the calculated CRC's 1s complement is what gets stored for each chunk + if (~uCRC == uChunkCRC) + { + return(TRUE); + } + else + { + return(FALSE); + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngReset + + \Description + Reset the DirtyPngState structure. + + \Input *pState - pointer to the module state + + Version 05/02/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngReset(DirtyPngStateT *pState) +{ + pState->uWidthOffset = 0; + pState->uHeightOffset = 0; + pState->uImageOffset = 0; + + pState->uDataBits = 0; + + pState->uWindowLength = 0; + pState->uWindowOffset = 0; + + pState->uCurrentScanline = 0; + + pState->uFilterType = (uint8_t)-1; + + pState->uCurrentPass = 0; + + pState->uChunkTime = FALSE; + pState->uChunkPhys = FALSE; + pState->uChunkIccp = FALSE; + pState->uChunkSbit = FALSE; + pState->uChunkGama = FALSE; + pState->uChunkChrm = FALSE; + pState->uChunkPlte = FALSE; + pState->uChunkTrns = FALSE; + pState->uChunkHist = FALSE; + pState->uChunkBkgd = FALSE; + pState->uChunkIdat = FALSE; + pState->uParsingIdat = FALSE; + + pState->uBitsLeft = 0; + + ds_memclr(pState->InterlacePassPixels, sizeof(pState->InterlacePassPixels)); + ds_memclr(pState->InterlaceScanlines, sizeof(pState->InterlaceScanlines)); + + ds_memclr(pState->LitTables, sizeof(pState->LitTables)); + ds_memclr(pState->DistTables, sizeof(pState->DistTables)); + ds_memclr(pState->CLenTables, sizeof(pState->CLenTables)); + + if (pState->uCRCTableGenerated == 0) + { + _DirtyPngGenerateCRCTable(pState); + pState->uCRCTableGenerated = 1; + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngParseHeader + + \Description + Parse the PNG header. + + \Input pState - module state + \Input *pPngHdr - [out] pointer to PNG header to fill in + \Input *pPngData - pointer to PNG data + \Input *pPngEnd - pointer past the end of PNG data + + \Output + int32_t - nonnegative=success, negative=error + + \Version 02/06/2007 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _DirtyPngParseHeader(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr, const uint8_t *pPngData, const uint8_t *pPngEnd) +{ + // iClearCode header + ds_memclr(pPngHdr, sizeof(*pPngHdr)); + + // skip the PNG signature + pPngData += SIGNATURE_LEN; + + // validate the IHDR chunk + DIRTYPNG_Validate(pPngData, pPngEnd, HEADER_LEN, DIRTYPNG_ERR_TOOSHORT); + + // make sure the length field specifies a chunk length of 13 + if (DIRTYPNG_GetInt32(pPngData, 0) != HEADER_LEN-12) + { + return(DIRTYPNG_ERR_TOOSHORT); + } + + #if DIRTYPNG_DEBUG_CHUNKS + _DirtyPngPrintChunk(pPngData, HEADER_LEN-12, 1); + #endif + + // check that the CRC value of the chunk is valid + if (_DirtyPngCheckCRC(pState, pPngData, HEADER_LEN-12) != TRUE) + { + return(DIRTYPNG_ERR_BADCRC); + } + + // verify that the chunk type is IHDR + if (memcmp(pPngData+4, "IHDR", 4)) + { + return(DIRTYPNG_ERR_BADTYPE); + } + + // get width and height from image descriptor + pPngHdr->uWidth = DIRTYPNG_GetInt32(pPngData, DATASTART); + pPngHdr->uHeight = DIRTYPNG_GetInt32(pPngData, DATASTART+4); + + // get bit depth and colour type from the image descriptor + pPngHdr->iBitDepth = pPngData[DATASTART+8]; + pPngHdr->iColourType = pPngData[DATASTART+9]; + + // verify that the Bit Depth and Colour type combination is valid + // check Table 11.1 of the PNG Specification for valid combinations + switch(pPngHdr->iColourType) + { + case 0: + pState->uPixelWidth = 1; + if ((pPngHdr->iBitDepth != 1) && (pPngHdr->iBitDepth != 2) && (pPngHdr->iBitDepth != 4) && + (pPngHdr->iBitDepth != 8)) + { + return(DIRTYPNG_ERR_BADDEPTH); + } + break; + case 2: + pState->uPixelWidth = 3; + if (pPngHdr->iBitDepth != 8) + { + return(DIRTYPNG_ERR_BADDEPTH); + } + break; + case 3: + pState->uPixelWidth = 1; + break; + case 4: + pState->uPixelWidth = 2; + if (pPngHdr->iBitDepth != 8) + { + return(DIRTYPNG_ERR_BADDEPTH); + } + break; + case 6: + pState->uPixelWidth = 4; + if (pPngHdr->iBitDepth != 8) + { + return(DIRTYPNG_ERR_BADDEPTH); + } + break; + default: + return(DIRTYPNG_ERR_BADCOLOR); + } + + // get compression, filter, and interlace methods from the image descriptor + pPngHdr->iCompression = pPngData[DATASTART+10]; + pPngHdr->iFilter = pPngData[DATASTART+11]; + pPngHdr->iInterlace = pPngData[DATASTART+12]; + + // verify that each method specified is valid + if (pPngHdr->iCompression != 0) + { + return(DIRTYPNG_ERR_BADCOMPR); + } + if (pPngHdr->iFilter != 0) + { + return(DIRTYPNG_ERR_BADFILTR); + } + if ((pPngHdr->iInterlace != 0) && (pPngHdr->iInterlace != 1)) + { + return(DIRTYPNG_ERR_BADINTRL); + } + + // setup the interlace arrays + if (pPngHdr->iInterlace == 0) + { + pState->InterlacePassPixels[0] = pPngHdr->uWidth; + pState->InterlaceScanlines[0] = pPngHdr->uHeight; + } + else + { + // compute how many pixels per scanline for each pass + pState->InterlacePassPixels[0] = (pPngHdr->uWidth + 7) / 8; + pState->InterlacePassPixels[1] = (pPngHdr->uWidth + 3) / 8; + pState->InterlacePassPixels[2] = (pPngHdr->uWidth + 3) / 4; + pState->InterlacePassPixels[3] = (pPngHdr->uWidth + 1) / 4; + pState->InterlacePassPixels[4] = (pPngHdr->uWidth + 1) / 2; + pState->InterlacePassPixels[5] = pPngHdr->uWidth / 2; + pState->InterlacePassPixels[6] = pPngHdr->uWidth; + + // compute how many scanlines per pass + pState->InterlaceScanlines[0] = (pPngHdr->uHeight + 7) / 8; + pState->InterlaceScanlines[1] = (pPngHdr->uHeight + 7) / 8; + pState->InterlaceScanlines[2] = (pPngHdr->uHeight + 3) / 8; + pState->InterlaceScanlines[3] = (pPngHdr->uHeight + 3) / 4; + pState->InterlaceScanlines[4] = (pPngHdr->uHeight + 1) / 4; + pState->InterlaceScanlines[5] = (pPngHdr->uHeight + 1) / 2; + pState->InterlaceScanlines[6] = pPngHdr->uHeight / 2; + } + + // return success + return(DIRTYPNG_ERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngParseChunks + + \Description + Parse the PNG chunks. + + \Input *pState - pointer to the module state + \Input *pPngHdr - [out] pointer to PNG header to fill in + \Input *pPngData - pointer to PNG data + \Input *pPngEnd - pointer past the end of PNG data + + \Output + int32_t - nonnegative=success, negative=error + + \Version 02/06/2007 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _DirtyPngParseChunks(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr, const uint8_t *pPngData, const uint8_t *pPngEnd) +{ + char strChunkType[5]; + uint32_t uLength; + int32_t iEndReached = FALSE; + + pPngData += SIGNATURE_LEN + HEADER_LEN; + + do + { + //! make sure there is enough data left for another chunk + DIRTYPNG_Validate(pPngData, pPngEnd, 12, DIRTYPNG_ERR_TOOSHORT); + + // get the chunk length + uLength = DIRTYPNG_GetInt32(pPngData, 0); + + DIRTYPNG_Validate(pPngData, pPngEnd, (int32_t)uLength+12, DIRTYPNG_ERR_TOOSHORT); + + // copy the chunk type value from the PNG byte stream + ds_memclr(strChunkType, sizeof(strChunkType)); + ds_strnzcpy(strChunkType, (const char *)pPngData+TYPESTART, (int32_t)sizeof(strChunkType)); + + // calculate the chunk CRC and compare with the received CRC + if (_DirtyPngCheckCRC(pState, pPngData, uLength) == FALSE) + { + return(DIRTYPNG_ERR_BADCRC); + } + + #if DIRTYPNG_DEBUG_CHUNKS + if (memcmp(strChunkType, "IDAT", 4) == 0) + { + _DirtyPngPrintChunk(pPngData, uLength, 0); + } + else + { + _DirtyPngPrintChunk(pPngData, uLength, 1); + } + #endif + + // check the chunk type and call its parser if required + // checks to make sure the chunks occur in order and duplicates + // of chunks where multiples are not allowed do not exist + if (memcmp(strChunkType, "IDAT", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: parsing chunk type = %s\n", strChunkType)); + #endif + + // make sure the IDAT chunks are sequential + if (pState->uChunkIdat && !pState->uParsingIdat) + { + return(DIRTYPNG_ERR_BADORDER); + } + + if (!pState->uChunkIdat) + { + pPngHdr->pImageData = pPngData; + pState->uChunkIdat = TRUE; + pState->uCountIdat = 1; + pState->uParsingIdat = TRUE; + } + else + { + pState->uCountIdat++; + } + } + else if (memcmp(strChunkType, "IEND", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: parsing chunk type = %s\n", strChunkType)); + #endif + + // make sure there was at least one IDAT chunk + if (!pState->uChunkIdat) + { + return(DIRTYPNG_ERR_TYPEMISS); + } + + if (pState->uParsingIdat) + { + pPngHdr->pImageEnd = pPngData; + pState->uParsingIdat = FALSE; + } + + iEndReached = TRUE; + } + else if (memcmp(strChunkType, "PLTE", 4) == 0) + { + if (pState->uChunkIdat || pState->uChunkTrns || pState->uChunkBkgd) + { + return(DIRTYPNG_ERR_BADORDER); + } + if (pState->uChunkPlte) + { + return(DIRTYPNG_ERR_TYPEDUPL); + } + // calculate palette size + pState->Palette.uNumColors = uLength/3; + // palette length must be a multiple of three and no larger than 256 + if ((((pState->Palette.uNumColors)*3) != uLength) || (uLength > (256*3))) + { + NetPrintf(("dirtypng: palette size of %d is invalid\n", uLength)); + return(DIRTYPNG_ERR_BADDEPTH); + } + // copy palette data + ds_memcpy_s(pState->Palette.aColor, sizeof(pState->Palette.aColor), pPngData+DATASTART, uLength); + // mark as parsed, but only if this is a paletted image; otherwise the palette is optional and we treat the image as nonpaletted + if (pState->uPixelWidth == 1) + { + pState->uChunkPlte = TRUE; + } + } + else if (memcmp(strChunkType, "tRNS", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uChunkIdat) + { + return(DIRTYPNG_ERR_BADORDER); + } + if (pState->uChunkTrns) + { + return(DIRTYPNG_ERR_TYPEDUPL); + } + + pState->uChunkTrns = TRUE; + } + else if (memcmp(strChunkType, "cHRM", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uChunkIdat || pState->uChunkPlte) + { + return(DIRTYPNG_ERR_BADORDER); + } + if (pState->uChunkChrm) + { + return(DIRTYPNG_ERR_TYPEDUPL); + } + + pState->uChunkChrm = TRUE; + } + else if (memcmp(strChunkType, "gAMA", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uChunkIdat || pState->uChunkPlte) + { + return(DIRTYPNG_ERR_BADORDER); + } + if (pState->uChunkGama) + { + return(DIRTYPNG_ERR_TYPEDUPL); + } + + pState->uChunkGama = TRUE; + } + else if (memcmp(strChunkType, "iCCP", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uChunkIdat || pState->uChunkPlte) + { + return(DIRTYPNG_ERR_BADORDER); + } + if (pState->uChunkIccp) + { + return(DIRTYPNG_ERR_TYPEDUPL); + } + + pState->uChunkIccp = TRUE; + } + else if (memcmp(strChunkType, "sBIT", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uChunkIdat || pState->uChunkPlte) + { + return(DIRTYPNG_ERR_BADORDER); + } + if (pState->uChunkSbit) + { + return(DIRTYPNG_ERR_TYPEDUPL); + } + + pState->uChunkSbit = TRUE; + } + else if (memcmp(strChunkType, "sRGB", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uChunkIdat || pState->uChunkPlte) + { + return(DIRTYPNG_ERR_BADORDER); + } + if (pState->uChunkIccp) + { + return(DIRTYPNG_ERR_TYPEDUPL); + } + + pState->uChunkIccp = TRUE; + } + else if (memcmp(strChunkType, "tEXt", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uParsingIdat) + { + pPngHdr->pImageEnd = pPngData; + pState->uParsingIdat = FALSE; + } + } + else if (memcmp(strChunkType, "zTXt", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uParsingIdat) + { + pPngHdr->pImageEnd = pPngData; + pState->uParsingIdat = FALSE; + } + } + else if (memcmp(strChunkType, "iTXt", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uParsingIdat) + { + pPngHdr->pImageEnd = pPngData; + pState->uParsingIdat = FALSE; + } + } + else if (memcmp(strChunkType, "bKGD", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uChunkIdat) + { + return(DIRTYPNG_ERR_BADORDER); + } + if (pState->uChunkBkgd) + { + return(DIRTYPNG_ERR_TYPEDUPL); + } + + pState->uChunkBkgd = TRUE; + } + else if (memcmp(strChunkType, "hIST", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (!pState->uChunkPlte) + { + return(DIRTYPNG_ERR_TYPEMISS); + } + if (pState->uChunkIdat) + { + return(DIRTYPNG_ERR_BADORDER); + } + } + else if (memcmp(strChunkType, "pHYs", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uChunkIdat) + { + return(DIRTYPNG_ERR_BADORDER); + } + if (pState->uChunkPhys) + { + return(DIRTYPNG_ERR_TYPEDUPL); + } + + pState->uChunkPhys = TRUE; + } + else if (memcmp(strChunkType, "sPLT", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uChunkIdat) + { + return(DIRTYPNG_ERR_BADORDER); + } + } + else if (memcmp(strChunkType, "tIME", 4) == 0) + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unsupported chunk type = %s\n", strChunkType)); + #endif + + if (pState->uParsingIdat) + { + pPngHdr->pImageEnd = pPngData; + pState->uParsingIdat = FALSE; + } + + pState->uChunkTime = TRUE; + } + else + { + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: unknown chunk type = %s\n", strChunkType)); + #endif + + // if the 5th bit of the first byte of the chunk is 0 it is + // a critical chunk and it cannot be skipped, return an error + if (!(strChunkType[0] & 0x20)) + { + return(DIRTYPNG_ERR_UNKNCRIT); + } + } + + pPngData += uLength + 12; + } while (!iEndReached); + + #if DIRTYPNG_DEBUG_CHUNKS + NetPrintf(("dirtypng: Parsing Complete\n\n")); + #endif + + // return success + return(DIRTYPNG_ERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngGetNextBytes + + \Description + Check if there is another iBytes bytes available and add it to DataBits. + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + \Input iBytes - byte count + + \Output + int32_t - nonnegative=success, negative=error + + \Version 02/14/2007 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _DirtyPngGetNextBytes(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr, int32_t iBytes) +{ + int32_t i; + + for (i = 0; i < iBytes; i++) + { + pState->uByteOffset++; + + if (pState->uByteOffset == pState->uIdatStart + pState->uIdatLength) + { + pState->uCountIdat--; + + if (!pState->uCountIdat && !pState->uBlockEnd) + { + return(DIRTYPNG_ERR_NOBLKEND); + } + else if (pState->uCountIdat) + { + pState->uIdatLength = DIRTYPNG_GetInt32(pPngHdr->pImageData, pState->uByteOffset+4); + pState->uIdatStart = pState->uByteOffset = pState->uByteOffset + 12; + } + } + + pState->uDataBits += ((uint32_t)(pPngHdr->pImageData[pState->uByteOffset]) << pState->uBitsLeft); + pState->uBitsLeft += 8; + } + + return(DIRTYPNG_ERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngRemoveBits + + \Description + Remove the specified number of bits from the DataBits. + + \Input *pState - pointer to the module state + \Input iBits - bit count + + \Version 02/14/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngRemoveBits(DirtyPngStateT *pState, int32_t iBits) +{ + pState->uDataBits >>= iBits; + pState->uBitsLeft -= iBits; +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngReverseBits + + \Description + Reverse the bits in a uint16_t. + + \Input uValue - uint16_t to reverse + \Input uBits - number of bits to reverse + + \Output + uint16_t - the reversed value of the passed in uint16_t + + \Version 02/14/2007 (cadam) +*/ +/********************************************************************************F*/ +static uint16_t _DirtyPngReverseBits(uint16_t uValue, uint16_t uBits) +{ + uint16_t u, uReversed = 0; + + uReversed = uValue & 1; + + for (u = 1; u < uBits; u++) + { + uReversed <<= 1; + uValue >>= 1; + uReversed |= (uValue & 1); + } + + return(uReversed); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngParseZlibInfo + + \Description + Parse the zlib CMF and FLG bytes. + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + + \Output + int32_t - nonnegative=success, negative=error + + \Version 02/13/2007 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _DirtyPngParseZlibInfo(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr) +{ + #if DIRTYPNG_DEBUG_ZLIB + NetPrintf(("\n\ndirtypng: parsing zlib info\n")); + #endif + + // make sure there are at least enough space for the CMF and FLG bytes + if ((pState->uIdatLength = DIRTYPNG_GetInt32(pPngHdr->pImageData, 0)) < 2) + { + return(DIRTYPNG_ERR_TOOSHORT); + } + + pState->uIdatStart = DATASTART; + + // make sure bits 0 to 3 of the CMF byte (CM) contains a value of 8 (Most-Significant Bit first) + if ((pPngHdr->pImageData[DATASTART] & 0x0f) != 8) + { + return(DIRTYPNG_ERR_BADCM); + } + + #if DIRTYPNG_DEBUG_ZLIB + NetPrintf(("dirtypng: CM=%u\n", pPngHdr->pImageData[DATASTART] & 0x0f)); + #endif + + // make sure bits 4 to 7 of the CMF byte (CINFO) specifies a value of 7 or less + pState->uWindowLog = pPngHdr->pImageData[DATASTART] >> 4; + if (pState->uWindowLog >= 8) + { + return(DIRTYPNG_ERR_BADCI); + } + pState->uWindowSize = 1 << (pState->uWindowLog + 8); + + #if DIRTYPNG_DEBUG_ZLIB + NetPrintf(("dirtypng: CINFO=%u\n\n", pPngHdr->pImageData[DATASTART] >> 4)); + #endif + + // the CMF byte * 256 + the FLG byte should be a multiple of 31 + if ((((uint16_t)pPngHdr->pImageData[DATASTART]) << 8 | pPngHdr->pImageData[DATASTART+1]) % 31 != 0) + { + return(DIRTYPNG_ERR_BADFLG); + } + + if (pPngHdr->pImageData[DATASTART+1] & 0x20) + { + return(DIRTYPNG_ERR_FDICTSET); + } + + pState->uByteOffset = DATASTART + 1; + + return(DIRTYPNG_ERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngParseDeflateInfo + + \Description + Parse the deflate information. + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + + \Output + int32_t - nonnegative=success, negative=error + + \Version 02/13/2007 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _DirtyPngParseDeflateInfo(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr) +{ + int32_t iRetVal; + + #if DIRTYPNG_DEBUG_DEFLATE + NetPrintf(("dirtypng: parsing deflate info\n")); + #endif + + if (pState->uBitsLeft < 3) + { + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, 1)) != DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + } + + iRetVal = pState->uDataBits & 1; + _DirtyPngRemoveBits(pState, 1); + + pState->uBType = pState->uDataBits & ((1 << 2) - 1); + _DirtyPngRemoveBits(pState, 2); + + #if DIRTYPNG_DEBUG_DEFLATE + NetPrintf(("dirtypng: BFINAL=%u\n", iRetVal)); + NetPrintf(("dirtypng: BTYPE=%u\n\n", pState->uBType)); + #endif + + if (pState->uBType == 3) + { + iRetVal = DIRTYPNG_ERR_BADBTYPE; + } + + return(iRetVal); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngWriteScanline + + \Description + Write the original scanline into the output buffer. + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + + \Version 05/11/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngWriteScanline(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr) +{ + uint32_t u = 0, uScanlineOffset = 0, uScanlineWidth, uPixelWidth; + uint8_t uShiftsPerByte = 8 / pPngHdr->iBitDepth; + uint8_t uShift = uShiftsPerByte - 1; + uint8_t uBits = (1 << pPngHdr->iBitDepth) - 1; + + uPixelWidth = !pState->uChunkPlte ? pState->uPixelWidth : 4; + uScanlineWidth = uPixelWidth * pState->InterlacePassPixels[pState->uCurrentPass]; + + if (pPngHdr->iInterlace == 1) + { + uint32_t uA7HeightOffset = 0, uA7WidthOffset = 0, uA7WidthShift = 0; + + // setup the width shift, width offset, and height offset for the current Adam7 scanline + switch(pState->uCurrentPass) + { + case 0: + uA7HeightOffset = 8 * pState->uCurrentScanline * uPixelWidth * pPngHdr->uWidth; + uA7WidthShift = 7 * uPixelWidth; + break; + case 1: + uA7WidthOffset = 4 * uPixelWidth; + uA7HeightOffset = 8 * pState->uCurrentScanline * uPixelWidth * pPngHdr->uWidth; + uA7WidthShift = 7 * uPixelWidth; + break; + case 2: + uA7HeightOffset = (8 * pState->uCurrentScanline + 4) * uPixelWidth * pPngHdr->uWidth; + uA7WidthShift = 3 * uPixelWidth; + break; + case 3: + uA7WidthOffset = 2 * uPixelWidth; + uA7HeightOffset = 4 * pState->uCurrentScanline * uPixelWidth * pPngHdr->uWidth; + uA7WidthShift = 3 * uPixelWidth; + break; + case 4: + uA7HeightOffset = (4 * pState->uCurrentScanline + 2 )* uPixelWidth * pPngHdr->uWidth; + uA7WidthShift = uPixelWidth; + break; + case 5: + uA7WidthOffset = 1 * uPixelWidth; + uA7HeightOffset = 2 * pState->uCurrentScanline * uPixelWidth * pPngHdr->uWidth; + uA7WidthShift = uPixelWidth; + break; + case 6: + uA7HeightOffset = (2 * pState->uCurrentScanline + 1)* uPixelWidth * pPngHdr->uWidth; + break; + } + + while (u < uScanlineWidth) + { + uint32_t uValue = ((pState->pCurrScanline[uScanlineOffset] >> (pPngHdr->iBitDepth * uShift)) & uBits) * (255 / uBits); + uint32_t uOffset = uA7HeightOffset + uA7WidthOffset; + + if (!pState->uChunkPlte) + { + pState->pImageBuf[uOffset] = uValue; + pState->uImageOffset += 1; + uA7WidthOffset += 1; + u += 1; + } + else + { + pState->pImageBuf[uOffset+0] = 0xff; + pState->pImageBuf[uOffset+1] = pState->Palette.aColor[uValue][0]; + pState->pImageBuf[uOffset+2] = pState->Palette.aColor[uValue][1]; + pState->pImageBuf[uOffset+3] = pState->Palette.aColor[uValue][2];; + pState->uImageOffset += 4; + uA7WidthOffset += 4; + u += 4; + } + + if (uShift == 0) + { + uShift = uShiftsPerByte - 1; + uScanlineOffset++; + } + else + { + uShift--; + } + + // for each pixel written make sure to shift to the next pixel offset for the current Adam7 scanline + if (u % uPixelWidth == 0) + { + uA7WidthOffset += uA7WidthShift; + } + } + } + else + { + while (u < uScanlineWidth) + { + uint8_t uValue = ((pState->pCurrScanline[uScanlineOffset] >> (pPngHdr->iBitDepth * uShift)) & uBits) * (255 / uBits); + + if (!pState->uChunkPlte) + { + pState->pImageBuf[pState->uImageOffset] = uValue; + pState->uImageOffset += 1; + u += 1; + } + else + { + pState->pImageBuf[pState->uImageOffset+0] = 0xff; + pState->pImageBuf[pState->uImageOffset+1] = pState->Palette.aColor[uValue][0]; + pState->pImageBuf[pState->uImageOffset+2] = pState->Palette.aColor[uValue][1]; + pState->pImageBuf[pState->uImageOffset+3] = pState->Palette.aColor[uValue][2]; + pState->uImageOffset += 4; + u += 4; + } + + if (uShift == 0) + { + uShift = uShiftsPerByte - 1; + uScanlineOffset++; + } + else + { + uShift--; + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngGetPaeth + + \Description + Get the distance total from the bits after the length code. + + \Input a - the byte one pixel to the left of x + \Input b - the byte at the offset of x in the previous scanline + \Input c - the byte at the offset of a in the previous scanline + + \Output + uint8_t - nonnegative=success, zero=error + + \Version 02/13/2007 (cadam) +*/ +/********************************************************************************F*/ +static uint8_t _DirtyPngGetPaeth(uint8_t a, uint8_t b, uint8_t c) +{ + int16_t p, pa, pb, pc; + + p = (int16_t)a + (int16_t)b - (int16_t)c; + pa = p - (int16_t)a; + pb = p - (int16_t)b; + pc = p - (int16_t)c; + + if (pa < 0) + { + pa = -pa; + } + if (pb < 0) + { + pb = -pb; + } + if (pc < 0) + { + pc = -pc; + } + + if (pa <= pb && pa <= pc) + { + return(a); + } + else if (pb <= pc) + { + return(b); + } + else + { + return(c); + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngFilterLine + + \Description + Reconstruct the original line. + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + + \Version 05/07/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngFilterLine(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr) +{ + uint8_t *pTempScanline; + uint32_t u, uScanlineEnd, uOffsetX = 0; + + uScanlineEnd = (pState->uPixelWidth * pState->InterlacePassPixels[pState->uCurrentPass] * pPngHdr->iBitDepth + 8 - pPngHdr->iBitDepth) / 8; + + switch(pState->uFilterType) + { + case 1: + uOffsetX = pState->uPixelWidth; + while (uOffsetX != uScanlineEnd) + { + pState->pCurrScanline[uOffsetX] = pState->pCurrScanline[uOffsetX] + pState->pCurrScanline[uOffsetX - pState->uPixelWidth]; + uOffsetX++; + } + break; + case 2: + while (uOffsetX != uScanlineEnd) + { + pState->pCurrScanline[uOffsetX] = pState->pCurrScanline[uOffsetX] + pState->pPrevScanline[uOffsetX]; + uOffsetX++; + } + break; + case 3: + for (u = 0; u < pState->uPixelWidth; u++) + { + pState->pCurrScanline[uOffsetX] = pState->pCurrScanline[uOffsetX] + (pState->pPrevScanline[uOffsetX] >> 1); + uOffsetX++; + } + while (uOffsetX != uScanlineEnd) + { + pState->pCurrScanline[uOffsetX] = pState->pCurrScanline[uOffsetX] + (uint8_t)(((uint16_t)pState->pCurrScanline[uOffsetX - pState->uPixelWidth] + + (uint16_t)pState->pPrevScanline[uOffsetX]) >> 1); + uOffsetX++; + } + break; + case 4: + for (u = 0; u < pState->uPixelWidth; u++) + { + pState->pCurrScanline[uOffsetX] = pState->pCurrScanline[uOffsetX] + pState->pPrevScanline[uOffsetX]; + uOffsetX++; + } + while (uOffsetX != uScanlineEnd) + { + pState->pCurrScanline[uOffsetX] = pState->pCurrScanline[uOffsetX] + _DirtyPngGetPaeth(pState->pCurrScanline[uOffsetX - pState->uPixelWidth], + pState->pPrevScanline[uOffsetX], + pState->pPrevScanline[uOffsetX - pState->uPixelWidth]); + uOffsetX++; + } + break; + }; + + // write the scanline to the image buffer + _DirtyPngWriteScanline(pState, pPngHdr); + + // swap the scanlines then clear the current + pTempScanline = pState->pPrevScanline; + pState->pPrevScanline = pState->pCurrScanline; + pState->pCurrScanline = pTempScanline; + ds_memclr(pState->pCurrScanline, pState->uScanlineWidth); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngNoCompBlock + + \Description + Parse the length field and validate, then copy the data to the image buffer + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + + \Output + int32_t - nonnegative=success, negative=error + + \Version 05/02/2007 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _DirtyPngNoCompBlock(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr) +{ + int32_t iRetVal; + uint16_t u, uBlockLen; + + _DirtyPngRemoveBits(pState, pState->uBitsLeft); + + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, 4)) != DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + + uBlockLen = (uint16_t)(pState->uDataBits & 0xffff); + _DirtyPngRemoveBits(pState, 16); + + if (uBlockLen != (uint16_t)~pState->uDataBits) + { + return(DIRTYPNG_ERR_BADBLKLEN); + } + + _DirtyPngRemoveBits(pState, pState->uBitsLeft); + + for (u = 0; u < uBlockLen; u++) + { + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, 1)) != DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + + pState->SlidingWindow[pState->uWindowOffset++] = (uint8_t)pState->uDataBits; + + // wrap around the window if needed + if (pState->uWindowOffset == pState->uWindowSize) + { + pState->uWindowOffset = 0; + } + + // increment the uWindowLength if we haven't already reached the max length + if (pState->uWindowLength < pState->uWindowSize) + { + pState->uWindowLength++; + } + + // skip the filter byte + if (pState->uWidthOffset == 0) + { + pState->uFilterType = (int8_t)pState->uDataBits; + // make sure the filter type is valid + if (pState->uFilterType > 4) + { + return(DIRTYPNG_ERR_BADTYPE); + } + pState->uWidthOffset++; + } + else + { + pState->pCurrScanline[pState->uWidthOffset++ - 1] = (uint8_t)pState->uDataBits; + + if (pState->uWidthOffset - 1 == (pState->uPixelWidth * pState->InterlacePassPixels[pState->uCurrentPass] * pPngHdr->iBitDepth + 8 - pPngHdr->iBitDepth) / 8) + { + pState->uWidthOffset = 0; + _DirtyPngFilterLine(pState, pPngHdr); + + pState->InterlaceScanlines[pState->uCurrentPass]--; + pState->uCurrentScanline++; + + if (pState->InterlaceScanlines[pState->uCurrentPass] == 0) + { + pState->uCurrentPass++; + pState->uCurrentScanline = 0; + ds_memclr(pState->pPrevScanline, pState->uScanlineWidth); + } + while ((pState->uCurrentPass < 7) && (pState->InterlaceScanlines[pState->uCurrentPass] == 0)) + { + pState->uCurrentPass++; + } + + if ((pState->uCurrentPass == 7) && (u + 1 != uBlockLen)) + { + return(DIRTYPNG_ERR_INVFILE); + } + } + } + + _DirtyPngRemoveBits(pState, pState->uBitsLeft); + } + + return(DIRTYPNG_ERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngFixedBlock + + \Description + Use the default huffman codes. These codes are specified in section 3.2.6 of rfc1951. + Literal Information: + 0-143 00110000->10111111 (8 bits) + 144-255 110010000->111111111 (9 bits) + 256-279 0000000->0010111 (7 bits) + 280-287 11000000->11000111 (8 bits) + Distance Information: + 0-31 00000->11111 (5 bits) + Distance codes 30 and 31 will never appear in the compressed data + + \Input *pState - pointer to the module state + + \Output + int32_t - nonnegative=success, negative=error + + \Version 02/13/2007 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _DirtyPngFixedBlock(DirtyPngStateT *pState) +{ + // form the table with the fixed codes-literal value combinations + // found in section 3.2.6 of RFC1951 + uint16_t u, uCodeValue; + + pState->uLitMinBits = 7; + pState->uDistMinBits = 5; + + // set no codes to 1 for all bit lengths. 7, 8 and 9 bit codes will get set to zero later. + for (u = 0; u < 16; u++) + { + pState->LitTables[u].uNoCodes = 1; + pState->DistTables[u].uNoCodes = 1; + } + + // setup the literal/length table + pState->LitTables[7].uNoCodes = 0; + pState->LitTables[7].uMinCode = 0; + pState->LitTables[7].uMaxCode = 23; + + uCodeValue = 256; + for (u = 0; u < 24; u++) + { + pState->LitTables[7].Codes[u] = uCodeValue++; + } + + pState->LitTables[8].uNoCodes = 0; + pState->LitTables[8].uMinCode = 48; + pState->LitTables[8].uMaxCode = 199; + + uCodeValue = 0; + for (u = 0; u < 144; u++) + { + pState->LitTables[8].Codes[u] = uCodeValue++; + } + uCodeValue = 280; + for (; u < 152; u++) + { + pState->LitTables[8].Codes[u] = uCodeValue++; + } + + pState->LitTables[9].uNoCodes = 0; + pState->LitTables[9].uMinCode = 400; + pState->LitTables[9].uMaxCode = 511; + + uCodeValue = 144; + for (u = 0; u < 112; u++) + { + pState->LitTables[9].Codes[u] = uCodeValue++; + } + + // setup the distance table + pState->DistTables[5].uNoCodes = 0; + pState->DistTables[5].uMinCode = 0; + pState->DistTables[5].uMaxCode = 29; // 29 since 30 and 31 shouldn't occur + + for (u = 0; u < 30; u++) + { + pState->DistTables[5].Codes[u] = u; + } + + return(DIRTYPNG_ERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngDynamicBlock + + \Description + Parse and setup the dynamic huffman table + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + + \Output + int32_t - nonnegative=success, negative=error + + \Version 02/13/2007 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _DirtyPngDynamicBlock(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr) +{ + int32_t iRetVal; + uint16_t u, v, uReversed, uMinimumBits = 0; + uint16_t uCodeFound, uCodeOffset, uCodeOverlap = 0, uCodeRepeat = 0, uCodeValue, uPrevCode = 0; + uint8_t CodeLength[MAXCLEN]; + uint16_t CodeCount[MAXCLEN]; + + ds_memclr(CodeLength, MAXCLEN); + ds_memclr(CodeCount, MAXCLEN * 2); + + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, ((14 - pState->uBitsLeft) >> 3) + 1)) != DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + + pState->uHLit = (pState->uDataBits & 0x1f) + 257; + _DirtyPngRemoveBits(pState, 5); + + pState->uHDist = (pState->uDataBits & 0x1f) + 1; + _DirtyPngRemoveBits(pState, 5); + + pState->uHCLen = (pState->uDataBits & 0x0f) + 4; + _DirtyPngRemoveBits(pState, 4); + + if (pState->uHLit > 286) + { + return(DIRTYPNG_ERR_MAXCODES); + } + + #if DIRTYPNG_DEBUG_DYNAMIC + NetPrintf(("dirtypng: parsing Dynamic Block Info\n")); + NetPrintf(("dirtypng: HLit=%u\n", pState->uHLit)); + NetPrintf(("dirtypng: HDist=%u\n", pState->uHDist)); + NetPrintf(("dirtypng: HCLen=%u\n\n", pState->uHCLen)); + #endif + + // get the length codes for the code length alphabet + for (u = 0; u < pState->uHCLen; u++) + { + if (pState->uBitsLeft < 3) + { + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, 1)) != DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + } + + CodeLength[_HCLEN_Codes[u]] = pState->uDataBits & 7; + _DirtyPngRemoveBits(pState, 3); + CodeCount[CodeLength[_HCLEN_Codes[u]]]++; + } + for ( ; u < 19; u++) + { + CodeLength[_HCLEN_Codes[u]] = 0; + } + + uCodeValue = 0; + // zero code lengths are ignored + pState->CLenTables[0].uNoCodes = 1; + for (u = 1; u < 8; u++) + { + if (CodeCount[u] > 0) + { + if (uMinimumBits == 0) + { + uMinimumBits = u; + } + pState->CLenTables[u].uMinCode = uCodeValue; + pState->CLenTables[u].uMaxCode = uCodeValue - 1; + uCodeValue = (uCodeValue + CodeCount[u]) << 1; + pState->CLenTables[u].uNoCodes = 0; + } + else + { + uCodeValue <<= 1; + pState->CLenTables[u].uNoCodes = 1; + } + } + + // if there are no code lengths set return a NOCODES error + if (uMinimumBits == 0) + { + return(DIRTYPNG_ERR_NOCODES); + } + + for (u = 0; u < MAXCLEN; u++) + { + if (CodeLength[u] > 0) + { + uCodeOffset = pState->CLenTables[CodeLength[u]].uMaxCode - pState->CLenTables[CodeLength[u]].uMinCode + 1; + pState->CLenTables[CodeLength[u]].Codes[uCodeOffset] = u; + pState->CLenTables[CodeLength[u]].uMaxCode++; + } + } + + #if DIRTYPNG_DEBUG_DYNAMIC + NetPrintf(("dirtypng: CodeLength")); + for (u = 0; u < MAXCLEN; u++) + { + if (u % 5 == 0) + { + NetPrintf(("\ndirtypng: ")); + } + + NetPrintf(("%u ", CodeLength[u])); + } + NetPrintf(("\n\n")); + + _DirtyPngPrintCLenTables(pState); + #endif + + // reset the code count + ds_memclr(CodeCount, MAXCLEN * 2); + + // construct the literal/length and distance tables + for (u = 0; u < pState->uHLit + pState->uHDist; u++) + { + v = uMinimumBits; + uCodeFound = 0; + + do + { + while ((pState->CLenTables[v].uNoCodes == 1) && (v < 8)) + { + v++; + } + if (v == 8) + { + return(DIRTYPNG_ERR_BADCODE); + } + + if (pState->uBitsLeft < v) + { + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, 1)) != DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + } + + uReversed = _DirtyPngReverseBits(pState->uDataBits, v); + + if ((uReversed >= pState->CLenTables[v].uMinCode) && (uReversed <= pState->CLenTables[v].uMaxCode)) + { + // code found so remove the bits + _DirtyPngRemoveBits(pState, v); + + uCodeFound = 1; + + uCodeValue = pState->CLenTables[v].Codes[uReversed - pState->CLenTables[v].uMinCode]; + + // details of the code length values found in RFC1951 + if ((uCodeValue > 0) && (uCodeValue < 16)) + { + if (u < pState->uHLit) + { + pState->LitTables[uCodeValue].Codes[CodeCount[uCodeValue]++] = u; + } + else + { + if (uCodeOverlap == 0) + { + // set the min/max codes for the literal/length Tables + uCodeOffset = 0; + pState->uLitMinBits = 0; + pState->LitTables[0].uNoCodes = 1; + for (v = 1; v < 16; v++) + { + if (CodeCount[v] != 0) + { + if (pState->uLitMinBits == 0) + { + pState->uLitMinBits = v; + } + pState->LitTables[v].uMinCode = uCodeOffset; + pState->LitTables[v].uMaxCode = CodeCount[v] + uCodeOffset - 1; + uCodeOffset = (CodeCount[v] + uCodeOffset) << 1; + pState->LitTables[v].uNoCodes = 0; + } + else + { + uCodeOffset <<= 1; + pState->LitTables[v].uNoCodes = 1; + } + } + + // reset the code count + ds_memclr(CodeCount, MAXCLEN * 2); + + uCodeOverlap = 1; + } + + pState->DistTables[uCodeValue].Codes[CodeCount[uCodeValue]++] = u - pState->uHLit; + } + + uPrevCode = uCodeValue; + } + else if (uCodeValue == 16) + { + // a code length of 16 cannot occur on the first code + if (u == 0) + { + return(DIRTYPNG_ERR_BADCODE); + } + + if (pState->uBitsLeft < 2) + { + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, 1)) != DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + } + + uCodeRepeat = 3 + (pState->uDataBits & ((1 << 2) - 1)); + _DirtyPngRemoveBits(pState, 2); + } + else if (uCodeValue == 17) + { + if (pState->uBitsLeft < 3) + { + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, 1)) != DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + } + + uPrevCode = 0; + uCodeRepeat = 3 + (pState->uDataBits & ((1 << 3) - 1)); + _DirtyPngRemoveBits(pState, 3); + } + else if (uCodeValue == 18) + { + if (pState->uBitsLeft < 7) + { + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, 1)) != DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + } + + uPrevCode = 0; + uCodeRepeat = 11 + (pState->uDataBits & ((1 << 7) - 1)); + _DirtyPngRemoveBits(pState, 7); + } + + if (uCodeRepeat > 0) + { + // check to make sure the repeat value is valid + if ((u + uCodeRepeat) > (pState->uHLit + pState->uHDist)) + { + return(DIRTYPNG_ERR_BADCODE); + } + + if (uPrevCode == 0) + { + u += uCodeRepeat - 1; + uCodeRepeat = 0; + } + else + { + while (uCodeRepeat != 0) + { + if (u < pState->uHLit) + { + pState->LitTables[uPrevCode].Codes[CodeCount[uPrevCode]++] = u; + } + else + { + if (uCodeOverlap == 0) + { + // set the min/max codes for the literal/length Tables + uCodeOffset = 0; + pState->uLitMinBits = 0; + pState->LitTables[0].uNoCodes = 1; + for (v = 1; v < 16; v++) + { + if (CodeCount[v] != 0) + { + if (pState->uLitMinBits == 0) + { + pState->uLitMinBits = v; + } + pState->LitTables[v].uMinCode = uCodeOffset; + pState->LitTables[v].uMaxCode = CodeCount[v] + uCodeOffset - 1; + uCodeOffset = (CodeCount[v] + uCodeOffset) << 1; + pState->LitTables[v].uNoCodes = 0; + } + else + { + uCodeOffset <<= 1; + pState->LitTables[v].uNoCodes = 1; + } + } + + // there should always be at least one code + if (pState->uLitMinBits == 0) + { + return(DIRTYPNG_ERR_NOCODES); + } + + pState->DistTables[uPrevCode].Codes[0] = u - pState->uHLit; + + // reset the code count + ds_memclr(CodeCount, MAXCLEN * 2); + + CodeCount[uPrevCode] = 1; + uCodeOverlap = 1; + } + else + { + pState->DistTables[uPrevCode].Codes[CodeCount[uPrevCode]++] = u - pState->uHLit; + } + } + + uCodeRepeat--; + u++; + } + + // make sure to decrement u by one since the loop increments it at the end + u--; + } + } + } + + v++; + } while ((uCodeFound == 0) && (v < 8)); + + if (uCodeFound == 0) + { + return(DIRTYPNG_ERR_BADCODE); + } + } + + // set the min/max codes for the distance tables + uCodeOffset = 0; + pState->uDistMinBits = 0; + pState->DistTables[0].uNoCodes = 1; + for (u = 1; u < 16; u++) + { + if (CodeCount[u] != 0) + { + if (pState->uDistMinBits == 0) + { + pState->uDistMinBits = u; + } + pState->DistTables[u].uMinCode = uCodeOffset; + pState->DistTables[u].uMaxCode = CodeCount[u] + uCodeOffset - 1; + uCodeOffset = (CodeCount[u] + uCodeOffset) << 1; + pState->DistTables[u].uNoCodes = 0; + } + else + { + uCodeOffset <<= 1; + pState->DistTables[u].uNoCodes = 1; + } + } + + if (pState->uDistMinBits == 0) + { + return(DIRTYPNG_ERR_NOCODES); + } + + #if DIRTYPNG_DEBUG_DYNAMIC + _DirtyPngPrintLitTables(pState); + _DirtyPngPrintDistTables(pState); + #endif + + return(DIRTYPNG_ERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngGetDistance + + \Description + Get the distance total from the bits after the length code. + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + + \Output + uint32_t - nonnegative=success, zero=error + + \Version 02/13/2007 (cadam) +*/ +/********************************************************************************F*/ +static uint32_t _DirtyPngGetDistance(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr) +{ + uint16_t u, uDistance, uExtraBits, uParsedCode; + + u = pState->uDistMinBits; + + do + { + while ((pState->DistTables[u].uNoCodes == 1) && (u < 16)) + { + u++; + } + if (u == 16) + { + return(0); + } + + if (pState->uBitsLeft < u) + { + if (_DirtyPngGetNextBytes(pState, pPngHdr, (((u - pState->uBitsLeft) >> 3) + 1)) != DIRTYPNG_ERR_NONE) + { + return(0); + } + } + + uParsedCode = _DirtyPngReverseBits(pState->uDataBits, u); + + if ((uParsedCode >= pState->DistTables[u].uMinCode) && (uParsedCode <= pState->DistTables[u].uMaxCode)) + { + _DirtyPngRemoveBits(pState, u); + + uParsedCode = pState->DistTables[u].Codes[uParsedCode - pState->DistTables[u].uMinCode]; + + #if DIRTYPNG_DEBUG_LDCODES + NetPrintf(("\ndistance: code=%u", uParsedCode)); + #endif + + if (uParsedCode > 29) + { + return(0); + } + + uDistance = _HDIST_Codes[uParsedCode]; + uExtraBits = _HDIST_Bits[uParsedCode]; + + #if DIRTYPNG_DEBUG_LDCODES + NetPrintf((" base=%u bits=%u", uDistance, uExtraBits)); + #endif + + if (uExtraBits > 0) + { + if (pState->uBitsLeft < uExtraBits) + { + if (_DirtyPngGetNextBytes(pState, pPngHdr, (((uExtraBits - pState->uBitsLeft) >> 3) + 1)) != + DIRTYPNG_ERR_NONE) + { + return(0); + } + } + + uDistance += (pState->uDataBits & ((1 << uExtraBits) - 1)); + _DirtyPngRemoveBits(pState, uExtraBits); + } + + #if DIRTYPNG_DEBUG_LDCODES + NetPrintf((" total=%u\n", uDistance)); + #endif + + return(uDistance); + } + + u++; + } while (u < 16); + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngCompBlock + + \Description + Parse the compressed codes, then copy the data to the image buffer + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + + \Output + int32_t - nonnegative=success, negative=error + + \Version 05/04/2007 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _DirtyPngCompBlock(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr) +{ + int32_t iRetVal = -1; + uint16_t u, v, uExtraBits, uFoundCode = 0, uLength, uParsedCode; + uint16_t uDistance, uDistStart, uDistEnd, uDistOffset; + + #if DIRTYPNG_DEBUG_INFLATE + uint16_t uCountFound = 0; + #endif + + pState->uBlockEnd = 0; + + #if DIRTYPNG_DEBUG_INFLATE + NetPrintf(("dirtypng: byte offset=%u\n", pState->uByteOffset)); + #endif + + do + { + u = pState->uLitMinBits; + + uFoundCode = 0; + + do + { + while ((pState->LitTables[u].uNoCodes == 1) && (u < 16)) + { + u++; + } + if (u == 16) + { + return(DIRTYPNG_ERR_BADCODE); + } + + if (pState->uBitsLeft < u) + { + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, (((u - pState->uBitsLeft) >> 3) + 1))) != + DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + } + + uParsedCode = _DirtyPngReverseBits(pState->uDataBits, u); + + if ((uParsedCode >= pState->LitTables[u].uMinCode) && (uParsedCode <= pState->LitTables[u].uMaxCode)) + { + _DirtyPngRemoveBits(pState, u); + + uFoundCode = 1; + + uParsedCode = pState->LitTables[u].Codes[uParsedCode - pState->LitTables[u].uMinCode]; + + if (uParsedCode < 256) + { + if (pState->uCurrentPass == 7) + { + return(DIRTYPNG_ERR_INVFILE); + } + + #if DIRTYPNG_DEBUG_INFLATE + if (uCountFound % 20 == 0) + { + NetPrintf(("\ndirtypng: ")); + } + NetPrintf(("%u ", uParsedCode)); + uCountFound++; + #endif + + pState->SlidingWindow[pState->uWindowOffset++] = (uint8_t)(uParsedCode & 0xff); + + // wrap around the window if needed + if (pState->uWindowOffset == pState->uWindowSize) + { + pState->uWindowOffset = 0; + } + + // increment the uWindowLength if we haven't already reached the max length + if (pState->uWindowLength < pState->uWindowSize) + { + pState->uWindowLength++; + } + + // skip the filter byte + if (pState->uWidthOffset == 0) + { + pState->uFilterType = (uint8_t)(uParsedCode & 0xff); + // make sure the filter type is valid + if (pState->uFilterType > 4) + { + return(DIRTYPNG_ERR_BADTYPE); + } + pState->uWidthOffset++; + } + else + { + pState->pCurrScanline[pState->uWidthOffset++ - 1] = (uint8_t)(uParsedCode & 0xff); + + if (pState->uWidthOffset - 1 == (pState->uPixelWidth * pState->InterlacePassPixels[pState->uCurrentPass] * pPngHdr->iBitDepth + 8 - pPngHdr->iBitDepth) / 8) + { + pState->uWidthOffset = 0; + _DirtyPngFilterLine(pState, pPngHdr); + + pState->InterlaceScanlines[pState->uCurrentPass]--; + pState->uCurrentScanline++; + + if (pState->InterlaceScanlines[pState->uCurrentPass] == 0) + { + pState->uCurrentPass++; + pState->uCurrentScanline = 0; + ds_memclr(pState->pPrevScanline, pState->uScanlineWidth); + } + while ((pState->uCurrentPass < 7) && (pState->InterlaceScanlines[pState->uCurrentPass] == 0)) + { + pState->uCurrentPass++; + } + } + } + } + else if ((uParsedCode > 256) && (uParsedCode < 286)) + { + if (pState->uCurrentPass == 7) + { + return(DIRTYPNG_ERR_INVFILE); + } + + #if DIRTYPNG_DEBUG_LDCODES + NetPrintf(("\nlength: code=%u", uParsedCode)); + #endif + + uParsedCode -= 257; + + uLength = _HLIT_Codes[uParsedCode]; + uExtraBits = _HLIT_Bits[uParsedCode]; + + #if DIRTYPNG_DEBUG_LDCODES + NetPrintf((" base=%u bits=%u", uLength, uExtraBits)); + #endif + + if (uExtraBits > 0) + { + if (pState->uBitsLeft < uExtraBits) + { + if ((iRetVal = _DirtyPngGetNextBytes(pState, pPngHdr, 1)) != DIRTYPNG_ERR_NONE) + { + return(iRetVal); + } + } + + uLength += (pState->uDataBits & ((1 << uExtraBits) - 1)); + _DirtyPngRemoveBits(pState, uExtraBits); + } + + #if DIRTYPNG_DEBUG_LDCODES + NetPrintf((" total=%u", uLength)); + #endif + + if ((uDistance = _DirtyPngGetDistance(pState, pPngHdr)) == 0) + { + return(DIRTYPNG_ERR_BADCODE); + } + + if (uDistance > pState->uWindowLength) + { + return(DIRTYPNG_ERR_BADCODE); + } + + uDistEnd = pState->uWindowOffset; + if (pState->uWindowOffset < uDistance) + { + uDistOffset = pState->uWindowSize + pState->uWindowOffset - uDistance; + } + else + { + uDistOffset = pState->uWindowOffset - uDistance; + } + uDistStart = uDistOffset; + + for (v = 0; v < uLength; v++) + { + if (pState->uCurrentPass == 7) + { + return(DIRTYPNG_ERR_INVFILE); + } + + #if DIRTYPNG_DEBUG_INFLATE + if (uCountFound % 20 == 0) + { + NetPrintf(("\ndirtypng: ")); + } + NetPrintf(("%u ", pState->SlidingWindow[uDistOffset])); + uCountFound++; + #endif + + pState->SlidingWindow[pState->uWindowOffset++] = pState->SlidingWindow[uDistOffset]; + + if (pState->uWindowOffset == pState->uWindowSize) + { + pState->uWindowOffset = 0; + } + + // increment the uWindowLength if we haven't already reached the max length + if (pState->uWindowLength < pState->uWindowSize) + { + pState->uWindowLength++; + } + + // skip the filter byte + if (pState->uWidthOffset == 0) + { + pState->uFilterType = pState->SlidingWindow[uDistOffset++]; + // make sure the filter type is valid + if (pState->uFilterType > 4) + { + return(DIRTYPNG_ERR_BADTYPE); + } + pState->uWidthOffset++; + } + else + { + pState->pCurrScanline[pState->uWidthOffset++ - 1] = pState->SlidingWindow[uDistOffset++]; + + if (pState->uWidthOffset == (pState->uPixelWidth * pState->InterlacePassPixels[pState->uCurrentPass] * pPngHdr->iBitDepth + 8 - pPngHdr->iBitDepth) / 8 + 1) + { + pState->uWidthOffset = 0; + _DirtyPngFilterLine(pState, pPngHdr); + + pState->InterlaceScanlines[pState->uCurrentPass]--; + pState->uCurrentScanline++; + + if (pState->InterlaceScanlines[pState->uCurrentPass] == 0) + { + pState->uCurrentPass++; + pState->uCurrentScanline = 0; + ds_memclr(pState->pPrevScanline, pState->uScanlineWidth); + } + while ((pState->uCurrentPass < 7) && (pState->InterlaceScanlines[pState->uCurrentPass] == 0)) + { + pState->uCurrentPass++; + } + } + } + + if (uDistOffset == pState->uWindowSize) + { + uDistOffset = 0; + } + if (uDistOffset == uDistEnd) + { + uDistOffset = uDistStart; + } + } + } + else if (uParsedCode >= 286) + { + return(DIRTYPNG_ERR_BADCODE); + } + } + + u++; + } while ((uFoundCode == 0) && (u < 16)); + + if (uFoundCode == 0) + { + return(DIRTYPNG_ERR_BADCODE); + } + } while (uParsedCode != 256); + + pState->uBlockEnd = 1; + + #if DIRTYPNG_DEBUG_INFLATE + NetPrintf(("dirtypng: byte offset=%u\n", pState->uByteOffset)); + #endif + + return(DIRTYPNG_ERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngConvertImage + + \Description + Convert the image to the expected ordering of DirtyGraph. + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + + \Version 02/13/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngConvertImage(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr) +{ + uint8_t u = 0, uTemp = 0; + uint32_t uInOffset, uOutOffset, uPixelWidth = pState->uPixelWidth; + + switch(pPngHdr->iColourType) + { + case 0: + uInOffset = pState->uImageOffset - 1; + uPixelWidth += 3; + uOutOffset = pState->uBufHeight * uPixelWidth * pState->uBufWidth - 1; + + while (uInOffset > 0) + { + pState->pImageBuf[uOutOffset-2] = pState->pImageBuf[uOutOffset-1] = pState->pImageBuf[uOutOffset] = pState->pImageBuf[uInOffset--]; + pState->pImageBuf[uOutOffset-3] = 0xff; + + uOutOffset -= 4; + } + + break; + case 2: + uInOffset = pState->uImageOffset - 1; + uPixelWidth++; + uOutOffset = pState->uBufHeight * uPixelWidth * pState->uBufWidth - 1; + + while (uInOffset > 0) + { + if (uOutOffset % uPixelWidth == 0) + { + pState->pImageBuf[uOutOffset] = 0xff; + } + else + { + pState->pImageBuf[uOutOffset] = pState->pImageBuf[uInOffset--]; + } + + uOutOffset--; + } + + pState->pImageBuf[0] = 0xff; + break; + case 4: + uInOffset = pState->uImageOffset - 1; + uPixelWidth += 2; + uOutOffset = pState->uBufHeight * uPixelWidth * pState->uBufWidth - 1; + + while (uInOffset > 0) + { + uTemp = pState->pImageBuf[uInOffset--]; + pState->pImageBuf[uOutOffset-2] = pState->pImageBuf[uOutOffset-1] = pState->pImageBuf[uOutOffset] = pState->pImageBuf[uInOffset]; + pState->pImageBuf[uOutOffset-3] = uTemp; + + if (uInOffset == 0) + { + break; + } + + uInOffset--; + uOutOffset -= 4; + } + + break; + case 6: + uOutOffset = pState->uBufHeight * uPixelWidth * pState->uBufWidth - 1; + + while (uOutOffset > 0) + { + uTemp = pState->pImageBuf[uOutOffset]; + for (u = 0; u < uPixelWidth - 1; u++) + { + pState->pImageBuf[uOutOffset] = pState->pImageBuf[uOutOffset - 1]; + uOutOffset--; + } + pState->pImageBuf[uOutOffset] = uTemp; + + if (uOutOffset == 0) + { + break; + } + uOutOffset--; + } + break; + } +} + +/*F********************************************************************************/ +/*! + \Function _DirtyPngFreeScanlines + + \Description + Free the scanline data + + \Input *pState - pointer to module state + + \Version 05/22/2007 (cadam) +*/ +/********************************************************************************F*/ +static void _DirtyPngFreeScanlines(DirtyPngStateT *pState) +{ + if (pState->pPrevScanline != NULL) + { + DirtyMemFree(pState->pPrevScanline, DIRTYPNG_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->pPrevScanline = NULL; + } + if (pState->pCurrScanline != NULL) + { + DirtyMemFree(pState->pCurrScanline, DIRTYPNG_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->pCurrScanline = NULL; + } +} + +/*** Public Functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function DirtyPngCreate + + \Description + Create the DirtyPng module state + + \Output + DirtyPngStateT * - pointer to new ref, or NULL if error + + \Version 02/07/2007 (cadam) +*/ +/********************************************************************************F*/ +DirtyPngStateT *DirtyPngCreate(void) +{ + DirtyPngStateT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pState = DirtyMemAlloc(sizeof(*pState), DIRTYPNG_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtypng: error allocating module state\n")); + return(NULL); + } + ds_memclr(pState, sizeof(*pState)); + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + + // return the state pointer + return(pState); +} + +/*F********************************************************************************/ +/*! + \Function DirtyPngDestroy + + \Description + Destroy the DirtyPng module + + \Input *pState - pointer to module state + + \Version 05/01/2007 (cadam) +*/ +/********************************************************************************F*/ +void DirtyPngDestroy(DirtyPngStateT *pState) +{ + DirtyMemFree(pState, DIRTYPNG_MEMID, pState->iMemGroup, pState->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function DirtyPngIdentify + + \Description + Identify if input image is a PNG image. + + \Input *pImageData - pointer to image data + \Input uImageLen - size of image data + + \Output + int32_t - TRUE if a PNG, else FALSE + + \Version 02/05/2007 (cadam) +*/ +/********************************************************************************F*/ +int32_t DirtyPngIdentify(const uint8_t *pImageData, uint32_t uImageLen) +{ + // make sure we have enough data + if (uImageLen < SIGNATURE_LEN) + { + return(FALSE); + } + // see of we're a PNG + // the first eight bytes of a PNG file contain the decimal values: 137 80 78 71 13 10 26 10 + if ((pImageData[0] != 0x89) || (pImageData[1] != 0x50) || (pImageData[2] != 0x4E) || (pImageData[3] != 0x47) || + (pImageData[4] != 0x0D) || (pImageData[5] != 0x0A) || (pImageData[6] != 0x1A) || (pImageData[7] != 0x0A)) + { + return(FALSE); + } + + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function DirtyPngParse + + \Description + Parse PNG header. + + \Input *pState - pointer to the module state + \Input *pPngHdr - [out] pointer to PNG header to fill in + \Input *pPngData - pointer to PNG data + \Input *pPngEnd - pointer past the end of PNG data + + \Version 02/06/2007 (cadam) +*/ +/********************************************************************************F*/ +int32_t DirtyPngParse(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr, const uint8_t *pPngData, const uint8_t *pPngEnd) +{ + int32_t iRetVal; + + // reset internal state + _DirtyPngReset(pState); + + iRetVal = _DirtyPngParseHeader(pState, pPngHdr, pPngData, pPngEnd); + + if (iRetVal == 0) + { + iRetVal = _DirtyPngParseChunks(pState, pPngHdr, pPngData, pPngEnd); + } + + return(iRetVal); +} + + +/*F********************************************************************************/ +/*! + \Function DirtyPngDecodeImage + + \Description + Decode a PNG image into an 8bit paletteized bitmap. + + \Input *pState - pointer to the module state + \Input *pPngHdr - pointer to header describing png to decode + \Input *pImageData - [out] pointer to buffer to write decoded image data to + \Input iBufWidth - width of output buffer + \Input iBufHeight - height of output buffer + + \Output + int32_t - positive=number of bytes decoded, negative=error + + \Version 05/01/2007 (cadam) +*/ +/********************************************************************************F*/ +int32_t DirtyPngDecodeImage(DirtyPngStateT *pState, DirtyPngHdrT *pPngHdr, uint8_t *pImageData, int32_t iBufWidth, int32_t iBufHeight) +{ + int32_t iLastBlock, iResult; + + pState->pImageBuf = pImageData; + pState->uBufWidth = (uint32_t)iBufWidth; + pState->uBufHeight = (uint32_t)iBufHeight; + + if ((iResult = _DirtyPngParseZlibInfo(pState, pPngHdr)) != DIRTYPNG_ERR_NONE) + { + return(iResult); + } + + // allocate memory for the previous and current scanlines + pState->uScanlineWidth = (pState->uPixelWidth * pPngHdr->uWidth * pPngHdr->iBitDepth + 8 - pPngHdr->iBitDepth) / 8; + if ((pState->pScanlines[0] = DirtyMemAlloc(pState->uScanlineWidth, DIRTYPNG_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtypng: error allocating previous scanline\n")); + return(DIRTYPNG_ERR_ALLOCFAIL); + } + ds_memclr(pState->pScanlines[0], pState->uScanlineWidth); + pState->pPrevScanline = pState->pScanlines[0]; + if ((pState->pScanlines[1] = DirtyMemAlloc(pState->uScanlineWidth, DIRTYPNG_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL) + { + NetPrintf(("dirtypng: error allocating current scanline\n")); + DirtyMemFree(pState->pPrevScanline, DIRTYPNG_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->pPrevScanline = NULL; + return(DIRTYPNG_ERR_ALLOCFAIL); + } + ds_memclr(pState->pScanlines[1], pState->uScanlineWidth); + pState->pCurrScanline = pState->pScanlines[1]; + + do + { + iLastBlock = _DirtyPngParseDeflateInfo(pState, pPngHdr); + + if (iLastBlock < 0) + { + _DirtyPngFreeScanlines(pState); + return(iLastBlock); + } + + switch(pState->uBType) + { + case 0: + if ((iResult = _DirtyPngNoCompBlock(pState, pPngHdr)) != DIRTYPNG_ERR_NONE) + { + _DirtyPngFreeScanlines(pState); + return(iResult); + } + break; + case 1: + if ((iResult = _DirtyPngFixedBlock(pState)) != DIRTYPNG_ERR_NONE) + { + _DirtyPngFreeScanlines(pState); + return(iResult); + } + if ((iResult = _DirtyPngCompBlock(pState, pPngHdr)) != DIRTYPNG_ERR_NONE) + { + _DirtyPngFreeScanlines(pState); + return(iResult); + } + break; + case 2: + if ((iResult = _DirtyPngDynamicBlock(pState, pPngHdr)) != DIRTYPNG_ERR_NONE) + { + _DirtyPngFreeScanlines(pState); + return(iResult); + } + if ((iResult = _DirtyPngCompBlock(pState, pPngHdr)) != DIRTYPNG_ERR_NONE) + { + _DirtyPngFreeScanlines(pState); + return(iResult); + } + break; + } + } while (iLastBlock != TRUE); + + _DirtyPngConvertImage(pState, pPngHdr); + + // _DirtyPngCheckAlder() - Needs to be coded; + + // free the previous and current scanlines + _DirtyPngFreeScanlines(pState); + + return(DIRTYPNG_ERR_NONE); +} diff --git a/src/thirdparty/dirtysdk/source/misc/qosclient.c b/src/thirdparty/dirtysdk/source/misc/qosclient.c new file mode 100644 index 00000000..20a02f43 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/misc/qosclient.c @@ -0,0 +1,2728 @@ +/*H********************************************************************************/ +/*! + \File qosclient.c + + \Description + This module implements the client API for the quality of service. + + \Copyright + Copyright (c) 2017 Electronic Arts Inc. + + \Version 2.0 06/05/2017 (cvienneau) Re-write of qosapi +*/ +/********************************************************************************H*/ + +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/misc/qoscommon.h" +#include "DirtySDK/misc/qosclient.h" +#include "DirtySDK/proto/protoname.h" +#include "DirtySDK/proto/protohttp2.h" + +/*** Defines **********************************************************************/ +//!< qosclient default listen port +#if !DIRTYCODE_XBOXONE +#define QOS_CLIENT_DEFAULT_LISTENPORT (7673) +#else +#define QOS_CLIENT_DEFAULT_LISTENPORT (0) +#endif + +#define QOS_CLIENT_SIMULATE_PACKET_LOSS (0) //!< for testing it can often be handy to lose some probes + +//!< passed to SocketControl 'rbuf' +#define QOS_CLIENT_DEFAULT_RECEIVE_BUFF_SIZE (16*1024) + +//!< passed to SocketControl 'sbuf' +#define QOS_CLIENT_DEFAULT_SEND_BUFF_SIZE (16*1024) + +/*! passed to SocketControl 'pque' + By default non-virtual sockets have a 1-deep packet queue. When _QosClientRecvCB() fails + to enter pQosClient->ThreadCrit we need space to buffer the packet. If extracting packets + from the socket receive buffer is delayed, then rtt time calculated from those probe replies + ends up being incorrectly higher than it should be. + + Testing has shown that with 6 ping sites providing ~equal ping times a packet queue of 2 is sometimes required. + If pQosClient->ThreadCrit fails to enter 50% of the time, approximately a 'pque' of 6 is required. + With pQosClient->ThreadCrit failing 100% of the time, an 11 deep 'pque' successfully allows the updating via NetConnIdle to handle it.*/ +#define QOS_CLIENT_DEFAULT_PACKET_QUEUE_SIZE (12) + +//!< qosclient timeout for socket idle callback (0 to disable the idle callback) +#define QOS_CLIENT_SOCKET_IDLE_RATE (0) + +//!< the minimum amount of time to have passed since the last update for us to consider our updates to have been stalled +#define QOS_CLIENT_DEFAULT_STALL_WINDOW (500) + +//!< the maximum amount of time to have passed since we started the qos process before we just bail out +#define QOS_CLIENT_DEFAULT_MAX_QOS_PROCESS_TIME (60000) + +//!< easily switch from https to http for testing, prod will always be https +#define QOS_CLIENT_DEFAULT_USE_HTTPS (TRUE) + +//!< every module state transition in DIRTYAPI_QOS should have a unique entry here and later can be found in the hResult +enum +{ + QOS_CLIENT_MODULE_STATUS_INIT = 2000, + QOS_CLIENT_MODULE_STATUS_PROCESS_STARTED, + QOS_CLIENT_MODULE_STATUS_TEST_CONFIG_RECIEVED, + QOS_CLIENT_MODULE_STATUS_RAW_RESULTS_READY, + QOS_CLIENT_MODULE_STATUS_RESULTS_RECEIVED, + QOS_CLIENT_MODULE_STATUS_REPORT_COMPLETE, + + QOS_CLIENT_MODULE_STATUS_UNSET = 0, + + QOS_CLIENT_MODULE_STATUS_ERROR_TEST_CONFIG_UNUSABLE = -2000, + QOS_CLIENT_MODULE_STATUS_ERROR_RPC_DECODE, + QOS_CLIENT_MODULE_STATUS_ERROR_RPC_ENCODE, + QOS_CLIENT_MODULE_STATUS_ERROR_UNKOWN_STATE, + QOS_CLIENT_MODULE_STATUS_ERROR_TEST_CONFIG_PRODUCED_NO_REQUESTS, + QOS_CLIENT_MODULE_STATUS_ERROR_FAILED_ALLOC_RECV_BUFFER, + QOS_CLIENT_MODULE_STATUS_ERROR_RECV_BUFFER_NEEDED_TOO_LARGE, + QOS_CLIENT_MODULE_STATUS_ERROR_FAILED_ALLOC_SEND_BUFFER, + QOS_CLIENT_MODULE_STATUS_ERROR_FAILED_ALLOC_SEND_BUFFER_FATAL, + QOS_CLIENT_MODULE_STATUS_ERROR_SEND_BUFFER_NEEDED_TOO_LARGE, + QOS_CLIENT_MODULE_STATUS_ERROR_SERIALIZED_PACKET_UNEXPECTED_SIZE, + QOS_CLIENT_MODULE_STATUS_ERROR_TIMEOUT, + QOS_CLIENT_MODULE_STATUS_ERROR_UNRECOVERED_ERROR + +} eQosClientModuleStatus; + +//!< every request state transition in DIRTYAPI_QOS should have a unique entry here and later can be found in the hResult +enum +{ + QOS_CLIENT_REQUEST_STATUS_INIT = 1000, + QOS_CLIENT_REQUEST_STATUS_SOCKET_LOOKUP_SUCCESS, + QOS_CLIENT_REQUEST_STATUS_INIT_SYNC_SUCCESS, + QOS_CLIENT_REQUEST_STATUS_SEND_NEXT_PROBE, + QOS_CLIENT_REQUEST_STATUS_SEND_LOST_PROBES, + QOS_CLIENT_REQUEST_STATUS_SEND_SUCCESS, + QOS_CLIENT_REQUEST_STATUS_SEND_TRY_AGAIN, + QOS_CLIENT_REQUEST_STATUS_COMPLETE_ACCEPTABLE, + QOS_CLIENT_REQUEST_STATUS_COMPLETE, + //add new positive states here + + QOS_CLIENT_REQUEST_STATUS_UNSET = 0, //unused valued + + QOS_CLIENT_REQUEST_STATUS_ERROR_SOCKET_LOOKUP_ALLOC = -1000, + QOS_CLIENT_REQUEST_STATUS_ERROR_SOCKET_LOOKUP_UPDATE, + QOS_CLIENT_REQUEST_STATUS_ERROR_TIMEOUT, + QOS_CLIENT_REQUEST_STATUS_ERROR_SENT_MAX_PROBES, + QOS_CLIENT_REQUEST_STATUS_ERROR_SITE_INVALID, + QOS_CLIENT_REQUEST_STATUS_ERROR_TEST_INVALID, + QOS_CLIENT_REQUEST_STATUS_ERROR_EXTERNAL_ADDRESS_MISMATCH, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_VERSION, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_UP_TOO_SMALL, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_DOWN_TOO_SMALL, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_UP_TOO_LARGE, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_DOWN_TOO_LARGE, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_MY_ADDRESS, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_SEVER_TIME, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_DOWN_TOO_MANY, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_UP_TOO_MANY, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_SERVICE_REQUEST_ID, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_ALREADY_COMPLETE, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_ALREADY_RECEIVED, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_HMAC, + QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_PROTOCOL, + QOS_CLIENT_REQUEST_STATUS_ERROR_TIMEOUT_PARTIAL + //add new negative states here + +} eQosClientRequestStatus; + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef enum QosClientRequestStateE +{ + REQUEST_STATE_INIT = 0, //!< prepare configuration, usually DNS resolve the server address + REQUEST_STATE_INIT_SYNC, //!< attempt to start all tests at the same time, wait for other tests to finish their init before proceeding to the send state + REQUEST_STATE_SEND, //!< send a series of probes to the target + REQUEST_STATE_RECEIVE, //!< wait for probes/response from target + REQUEST_STATE_COMPLETE, //!< succeeded or failed, callback the user and clean up + REQUEST_STATE_ERROR //!< equivalent to REQUEST_STATE_COMPLETE, but sets failure hResult error code bit +} QosClientRequestStateE; + +typedef enum QosClientModuleStateE +{ + MODULE_STATE_IDLE = 0, //!< do nothing + MODULE_STATE_INIT_COORDINATOR_COMM, //!< send a message to the coordinator announcing who we are, and any results we have so far + MODULE_STATE_UPDATE_COORDINATOR_COMM, //!< wait for response from the coordinator + MODULE_STATE_PROBE, //!< perform the tests that the coordinator has requested, once tests are completed typically communicate with the coordinator again to provide results and get next steps + MODULE_STATE_REPORT, //!< the coordinator is done with us, report results to the client + MODULE_STATE_ERROR, //!< an error has happened preventing us from doing our tests, report to the coordinator immediately + MODULE_STATE_FATAL //!< an unrecoverable error has happened, such as coordinator communication failure, halt all further actions +} QosClientModuleStateE; + +//! A QOS test is used to gather various metrics for a given ping site +typedef struct QosClientRequestT +{ + struct QosClientRequestT *pNext; //!< link to the next record + + //info about the target the test is being performed against + QosCommonSiteT site; //!< site we want to send the QOS probes to + HostentT *pQosServerHost; //!< host name lookup for the QOS server + struct sockaddr serverAddr; //!< address we want to send the QOS probes to + + // info about how the test is to be performed + QosCommonTestT test; //!< parameters describing the test + uint32_t uServiceRequestID; //!< id from the server to distinguish different clients + uint16_t uClientRequestID; //!< id generated by this client to distinguish different requests + + //storage for the results + QosCommonRawResultsT rawResults; //!< we'll store all the info we are going to send back to the coordinator here + QosCommonAddrT probeExternalAddr; //!< clients external address, and the location the server will be sending probes to. Start with to clientAddressFromCoordinator, but update to ClientAddressFromServer if available, they will likely be the same. + + //request status + QosClientRequestStateE eState; //!< the current state the request is in + uint32_t uProbesToSend; //!< the number of probes we will send total, including retries + uint32_t uWhenStarted; //!< time we started working on this request + uint32_t uWhenLastSent; //!< the last time we sent a probe for this request + uint32_t uWhenCompleted; //!< time we finished working on this request + uint32_t uCreationCount; //!< typically 0, but if a request generates a new request we increment + int16_t iLastProbeValidationError; //!< what was the last thing to go wrong when receiving probes for this request +} QosClientRequestT; + +//! current module state +struct QosClientRefT +{ + // module memory group + int32_t iMemGroup; //!< module memory group id + void *pMemGroupUserData; //!< user data associated with memory group + + // return results to the user of QosClient + QosClientCallbackT *pCallback; //!< callback function to call to when results are available + void *pUserData; //!< user specified data to pass to the callback + + // QosCoordinator communication + char strCoordinatorAddr[QOS_COMMON_MAX_URL_LENGTH]; //!< the url for the coordinator + QosCommonClientToCoordinatorRequestT rawResultStore; //!< contains the data that needs to be provided to the coordinator + QosCommonProcessedResultsT finalResults; //!< contains the result data which is ready for consumption by the client + QosCommonAddrT clientAddressFromCoordinator; //!< external address from the coordinators prospective + ProtoHttp2RefT *pProtoHttp; //!< http module, used for service requests to the QOS server + int32_t iHttpStreamId; //!< stream identifier generated on post + uint8_t *pHttpRecvBuff; //!< buffer we will receive http responses into, this need to be a member since sometimes the whole message can't come in one call (PROTOHTTP_RECVWAIT) + uint8_t *pHttpSendBuff; //!< buffer we will send http messages from, typically it will be in one shot, but if the message is particularly large multiple ProtoHttpSend calls may be needed + uint8_t *pHttpSendProgress; //!< pointer into the send buffer where rpc data is currently being sent from + uint32_t uHttpRecvBuffSize; //!< the current allocated size of pHttpRecvBuff + uint32_t uCurrentHttpRecvSize; //!< the amount of data stored in pHttpRecvBuff + uint32_t uHttpSendBuffSize; //!< the current allocated size of pHttpSendBuff + uint32_t uHttpBytesToSend; //!< total rpc body size to send to the coordinator + int32_t iHttpSoLinger; //!< passed down to the socket as a SO_LINGER option if its >= 0, a 0 value will abort the socket on disconnection with no TIME_WAIT state + uint16_t uCoordinatorPort; //!< port of the coordinator + uint8_t bUseHttps; //!< true if communication is done via https otherwise http + uint8_t bIgnoreSSLErrors; //!< if true ssl cert errors will be ignored + + // udp probe socket management + SocketT *pUdpSocket; //!< socket pointer + QosCommonAddrT localUdpAddrInfo; //!< information about the local address we bound to + uint32_t uUdpReceiveBuffSize; //!< passed to SocketControl 'rbuf' + uint32_t uUdpSendBuffSize; //!< passed to SocketControl 'sbuf' + uint32_t uUdpPacketQueueSize; //!< passed to SocketControl 'pque' defaults QOS_CLIENT_DEFAULT_PACKET_QUEUE_SIZE (12) + uint16_t uUdpRequestedListenPort; //!< port we want to listen on, we receive probes from the server here + uint16_t uUdpCurrentListenPort; //!< port we are currently listening on (we may not get the port we wanted to listen on) + int8_t bDeferredRecv; //!< data available for reading, _QosClientUpdate should take care of it + + // request management + QosClientRequestT *pRequestQueue; //!< linked list of requests pending + NetCritT ThreadCrit; //!< critical section, we lock receive thread, update, and public APIs to ensure safety with one accessors to the module at a time, since everything access the pRequestQueue + QosClientModuleStateE eModuleState; //!< what stage of the QOS procedure are we currently in + uint32_t uInitSyncTime; //!< the time when a series of tests was initialized, used a base time to see if initialization is taking a long time + uint32_t uFinalResultsRecvTime; //!< the time when the coordinator processed results arrived on this console, used to time if we want to retry based off uTimeTillRetry + uint32_t uErrorMessageCount; //!< the number of consecutive messages we received from the coordinator that resulted in an error state. + uint16_t uNextClientRequestID; //!< current request id, incremented every time a new request is made + + // update management + uint32_t uStallWindow; //!< the amount of time to pass between updates before we consider it stalled + uint32_t uQosProcessStartTime; //!< the amount of time to have passed since calling QosClientStart + uint32_t uMaxQosProcessTime; //!< the maximum amount of time to let qos run before bailing out completely + uint32_t uLastUpdateTime; //!< NetTick of the last time update was called for this module + + int16_t iSpamValue; //!< the current logging level +}; + +/*** Function Prototypes **********************************************************/ + +static int32_t _QosClientRecvCB(SocketT *pSocket, int32_t iFlags, void *pData); +static uint8_t* _QosClientAllocHttpRecvBuff(QosClientRefT *pQosClient, uint32_t uSize); +static uint8_t* _QosClientAllocHttpSendBuff(QosClientRefT *pQosClient, uint32_t uSize); +static int32_t _QosClientDestroyRequest(QosClientRefT *pQosClient, uint32_t uRequestId); + +/*** Variables ********************************************************************/ +#if DIRTYCODE_LOGGING +const char *_strQosClientRequestState[] = //!< keep in sync with QosClientRequestStateE +{ + "REQUEST_STATE_INIT ", + "REQUEST_STATE_INIT_SYNC", + "REQUEST_STATE_SEND ", + "REQUEST_STATE_RECEIVE ", + "REQUEST_STATE_COMPLETE ", + "REQUEST_STATE_ERROR " +}; + +const char *_strQosClientModuleState[] = //!< keep in sync with QosClientModuleStateE +{ + "MODULE_STATE_IDLE ", + "MODULE_STATE_INIT_COORDINATOR_COMM ", + "MODULE_STATE_UPDATE_COORDINATOR_COMM ", + "MODULE_STATE_PROBE ", + "MODULE_STATE_REPORT", + "MODULE_STATE_ERROR ", + "MODULE_STATE_FATAL " +}; + +const char *_strQosCommonFirewallType[] = //!< keep in sync with QosCommonFirewallTypeE +{ + "QOS_COMMON_FIREWALL_UNKNOWN ", + "QOS_COMMON_FIREWALL_OPEN ", + "QOS_COMMON_FIREWALL_MODERATE ", + "QOS_COMMON_FIREWALL_STRICT ", + "QOS_COMMON_FIREWALL_NUMNATTYPES" +}; +#endif + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _QosClientRequestStateTransition + + \Description + Update the requests state, status and current error code. + + \Input *pQosClient - module state + \Input *pRequest - pointer to the request + \Input eState - new state to transition to, REQUEST_STATE_* + \Input uModule - module producing the hResult code, DIRTYAPI_* + \Input iCode - status/error code to use in hResult + \Input iLogLevel - the log level this state transition should be logged at, equivalent to NetPrintfVerbose((pQosClient->iSpamValue, iLogLevel, ... + + \Version 09/19/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientRequestStateTransition(QosClientRefT *pQosClient, QosClientRequestT *pRequest, QosClientRequestStateE eState, uint32_t uModule, int16_t iCode, int32_t iLogLevel) +{ + pRequest->rawResults.hResult = DirtyErrGetHResult(uModule, iCode, eState == REQUEST_STATE_ERROR); + NetPrintfVerbose((pQosClient->iSpamValue, iLogLevel, "qosclient: request[%02d] transitioning from %s-> %s (0x%08x, %s, %s)\n", pRequest->uClientRequestID, _strQosClientRequestState[pRequest->eState], _strQosClientRequestState[eState], pRequest->rawResults.hResult, pRequest->site.strSiteName, pRequest->test.strTestName)); + + if (eState == REQUEST_STATE_ERROR) + { + pRequest->eState = REQUEST_STATE_COMPLETE; + } + else + { + pRequest->eState = eState; + } +} + +/*F********************************************************************************/ +/*! + \Function _QosClientModuleStateTransition + + \Description + Update the modules hResult, and transition to another state. + + \Input *pQosClient - module state + \Input eState - new state to transition to, MODULE_STATE_* + \Input uModule - module producing the hResult code, DIRTYAPI_* + \Input iCode - status/error code to use in hResult + \Input iLogLevel - the log level this state transition should be logged at, equivalent to NetPrintfVerbose((pQosClient->iSpamValue, iLogLevel, ... + + \Version 09/19/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientModuleStateTransition(QosClientRefT *pQosClient, QosClientModuleStateE eState, uint32_t uModule, int16_t iCode, int32_t iLogLevel) +{ + pQosClient->rawResultStore.clientModifiers.hResult = DirtyErrGetHResult(uModule, iCode, ((eState == MODULE_STATE_ERROR) || (eState == MODULE_STATE_FATAL))); + NetPrintfVerbose((pQosClient->iSpamValue, iLogLevel, "qosclient: process transitioning from %s-> %s (0x%08x)\n", _strQosClientModuleState[pQosClient->eModuleState], _strQosClientModuleState[eState], pQosClient->rawResultStore.clientModifiers.hResult, pQosClient->rawResultStore.clientModifiers.hResult)); + + // if we were sending to the coordinator, and we aren't anymore, we no longer need the send buffer + if ((pQosClient->eModuleState == MODULE_STATE_INIT_COORDINATOR_COMM) && (eState != MODULE_STATE_INIT_COORDINATOR_COMM)) + { + _QosClientAllocHttpSendBuff(pQosClient, 0); + } + + // if we were receiving/reporting results from the coordinator, and we aren't anymore, we no longer need the receive buffer + if ((pQosClient->eModuleState == MODULE_STATE_REPORT) && (eState != MODULE_STATE_REPORT)) + { + _QosClientAllocHttpRecvBuff(pQosClient, 0); + } + + // if we are no longer active, give the port back to the system, if we do qos again we'll attempt to obtain it again + if ((pQosClient->eModuleState != MODULE_STATE_IDLE) && (eState == MODULE_STATE_IDLE)) + { + if (pQosClient->pUdpSocket != NULL) + { + SocketClose(pQosClient->pUdpSocket); + pQosClient->pUdpSocket = NULL; + } + } + + // fatal errors clean up and call the callback, we can't talk to the coordinator anymore + if (eState == MODULE_STATE_FATAL) + { + //free any of our buffers, they would likely need to be re-created anyways + _QosClientAllocHttpSendBuff(pQosClient, 0); + _QosClientAllocHttpRecvBuff(pQosClient, 0); + + //if the module has gone into an error state we probably don't have any requests (because most module level errors happen before we would) + // but if we did we should get rid of them since the only course of action would be to start the qos process again. + while (pQosClient->pRequestQueue != NULL) + { + _QosClientDestroyRequest(pQosClient, pQosClient->pRequestQueue->uClientRequestID); + } + pQosClient->eModuleState = MODULE_STATE_REPORT; + return; + } + + // errors tell the coordinator about it right away, keep whatever results we can and let the coordinator figgure out what to do + if (eState == MODULE_STATE_ERROR) + { + pQosClient->eModuleState = MODULE_STATE_INIT_COORDINATOR_COMM; + return; + } + + //if nothing else has returned set the new state + pQosClient->eModuleState = eState; +} + +/*F********************************************************************************/ +/*! + \Function _QosClientUpdateLocalAddress + + \Description + Update the local address used for udp communication. + + \Input *pQosClient - pointer to module state + + \Version 09/06/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientUpdateLocalAddress(QosClientRefT *pQosClient) +{ +#if DIRTYCODE_LOGGING + char addrBuff[128]; +#endif + // if we haven't set the local address information, do so now + if (pQosClient->localUdpAddrInfo.uFamily == 0) + { + //xbox and ps4 http don't have the api to read the local addr from protohttp, but they also don't support ipv6 or have multiple nics so we can do something simpler + #if defined(DIRTYCODE_PS4) || defined(DIRTYCODE_XBOXONE) + pQosClient->localUdpAddrInfo.uFamily = AF_INET; + pQosClient->localUdpAddrInfo.addr.v4 = SocketGetLocalAddr(); + #else + struct sockaddr LocalAddr; + if (ProtoHttp2Status(pQosClient->pProtoHttp, 0, 'ladd', &LocalAddr, sizeof(LocalAddr)) == 0) + { + //if the address family of the cached value is 0, it hasn't actually been cached yet + if (LocalAddr.sa_family != 0) + { + // cache off the info about the address we are bound to, this will be sent to the coordinator as our pQosClient->rawResultStore.clientAddressInternal + // it needs caching since we will clear the rawResultStore if multiple tests are done, but we only bind the socket once + // technically this is the address used for the http communication, but it can be difficult to get the udp address so we will presume they will be the same. + QosCommonConvertAddr(&pQosClient->localUdpAddrInfo, &LocalAddr); + } + } + else + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: localUdpAddrInfo failed to get cached local address.\n")); + } + #endif + + // change the info to be shared if we have updated the local address info + if (pQosClient->localUdpAddrInfo.uFamily != 0) + { + pQosClient->localUdpAddrInfo.uPort = pQosClient->uUdpCurrentListenPort; //override the port to the one we know the udp address was bound to + pQosClient->rawResultStore.clientModifiers.clientAddressInternal = pQosClient->localUdpAddrInfo; //set the local address information to be sent on next coordinator communication + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: localUdpAddrInfo address updated to %s\n", QosCommonAddrToString(&pQosClient->localUdpAddrInfo, addrBuff, sizeof(addrBuff)))); + } + } + + // if the port from the udp socket doesn't match what we have cached update it + if (pQosClient->localUdpAddrInfo.uPort != pQosClient->uUdpCurrentListenPort) + { + pQosClient->localUdpAddrInfo.uPort = pQosClient->uUdpCurrentListenPort; //override the port to the one we know the udp address was bound to + pQosClient->rawResultStore.clientModifiers.clientAddressInternal = pQosClient->localUdpAddrInfo; //set the local address information to be sent on next coordinator communication + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: localUdpAddrInfo port updated to %s\n", QosCommonAddrToString(&pQosClient->localUdpAddrInfo, addrBuff, sizeof(addrBuff)))); + } +} + +/*F********************************************************************************/ +/*! + \Function _QosClientSocketOpen + + \Description + Open QosClient socket + + \Input *pQosClient - pointer to module state + + \Output + SocketT * - pointer to socket + + \Version 09/13/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static SocketT *_QosClientSocketOpen(QosClientRefT *pQosClient) +{ + struct sockaddr LocalAddr; + int32_t iResult; + SocketT *pSocket; + + // set the port to the default value if 0 + if (pQosClient->uUdpRequestedListenPort == 0) + { + pQosClient->uUdpRequestedListenPort = QOS_CLIENT_DEFAULT_LISTENPORT; + } + + // create the socket + if ((pSocket = SocketOpen(AF_INET, SOCK_DGRAM, 0)) == NULL) + { + NetPrintf(("qosclient: could not allocate socket\n")); + return(NULL); + } + + // make sure socket receive buffer is large enough to queue up + // multiple probe responses (worst case: 10 probes * 1200 bytes) + SocketControl(pSocket, 'rbuf', pQosClient->uUdpReceiveBuffSize, NULL, NULL); + SocketControl(pSocket, 'sbuf', pQosClient->uUdpSendBuffSize, NULL, NULL); + SocketControl(pSocket, 'pque', pQosClient->uUdpPacketQueueSize, NULL, NULL); + + SockaddrInit(&LocalAddr, AF_INET); + SockaddrInSetPort(&LocalAddr, pQosClient->uUdpRequestedListenPort); + + // bind the socket + if ((iResult = SocketBind(pSocket, &LocalAddr, sizeof(LocalAddr))) != SOCKERR_NONE) + { + NetPrintf(("qosclient: error %d binding socket to port %d, trying random\n", iResult, pQosClient->uUdpRequestedListenPort)); + SockaddrInSetPort(&LocalAddr, 0); + if ((iResult = SocketBind(pSocket, &LocalAddr, sizeof(LocalAddr))) != SOCKERR_NONE) + { + NetPrintf(("qosclient: error %d binding socket to listen\n", iResult)); + SocketClose(pSocket); + return(NULL); + } + } + + // set the current listen port + SocketInfo(pSocket, 'bind', 0, &LocalAddr, sizeof(LocalAddr)); + pQosClient->uUdpCurrentListenPort = SockaddrInGetPort(&LocalAddr); + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: bound probe socket to port %u\n", pQosClient->uUdpCurrentListenPort)); + + //update the clients local address, as new info becomes available (we just discovered the port) + _QosClientUpdateLocalAddress(pQosClient); + + // set the callback + SocketCallback(pSocket, CALLB_RECV, QOS_CLIENT_SOCKET_IDLE_RATE, pQosClient, &_QosClientRecvCB); + // return to caller + return(pSocket); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientDestroyRequest + + \Description + Remove request from queue and free any memory associated with the request. + + \Input *pQosClient - pointer to module state + \Input uRequestId - id of the request to destroy + + \Output + int32_t - 0 if successful, negative otherwise + + \Version 04/11/2008 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _QosClientDestroyRequest(QosClientRefT *pQosClient, uint32_t uRequestId) +{ + QosClientRequestT *pQosClientRequest, **ppQosClientRequest; + + // find request in queue + for (ppQosClientRequest = &pQosClient->pRequestQueue; *ppQosClientRequest != NULL; ppQosClientRequest = &(*ppQosClientRequest)->pNext) + { + // found the request to destroy? + if ((*ppQosClientRequest)->uClientRequestID == uRequestId) + { + // set the request + pQosClientRequest = *ppQosClientRequest; + + // dequeue + *ppQosClientRequest = (*ppQosClientRequest)->pNext; + + // destroy host name lookup if we have one in progress + if (pQosClientRequest->pQosServerHost != NULL) + { + pQosClientRequest->pQosServerHost->Free(pQosClientRequest->pQosServerHost); + pQosClientRequest->pQosServerHost = NULL; + } + + // free memory + DirtyMemFree(pQosClientRequest, QOS_CLIENT_MEMID, pQosClient->iMemGroup, pQosClient->pMemGroupUserData); + + return(0); + } + } + + // specified request not found + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientGetRequest + + \Description + Get a request by request ID. + + \Input *pQosClient - pointer to module state + \Input uRequestId - id of the request to get + + \Output + QosClientRequestT * - pointer to request or NULL if not found + + \Version 08/16/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static QosClientRequestT *_QosClientGetRequest(QosClientRefT *pQosClient, uint32_t uRequestId) +{ + QosClientRequestT *pRequest; + + // find request in queue + for (pRequest = pQosClient->pRequestQueue; pRequest != NULL; pRequest = pRequest->pNext) + { + // found the request? + if (pRequest->uClientRequestID == uRequestId) + { + return(pRequest); + } + } + // did not find the request + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientValidateTest + + \Description + Validate that a test contains all the required information. + + \Input *pQosClient - pointer to module state + \Input *pTest - pointer test to be validated + + \Output + int32_t - 0 if valid test, negative otherwise + + \Version 06/05/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _QosClientValidateTest(QosClientRefT *pQosClient, QosCommonTestT *pTest) +{ + if ((strcmp(pTest->strSiteName, "") == 0) || (strcmp(pTest->strTestName, "") == 0)) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: test %s skipped, incomplete information, strSiteName=%s.\n", pTest->strTestName, pTest->strSiteName)); + return(-1); + } + + if (!((pTest->uProbeCountUp == 1) || (pTest->uProbeCountDown == 1))) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: test %s skipped, uProbeCountUp(%d) or uProbeCountDown(%d) must be 1, we do not support many to many probes on this client.\n", pTest->strTestName, pTest->uProbeCountUp, pTest->uProbeCountDown)); + return(-2); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientValidateSite + + \Description + Validate that a site contains all the required information. + + \Input *pQosClient - pointer to module state + \Input *pSite - pointer site to be validated + + \Output + int32_t - 0 if valid site, negative otherwise + + \Version 06/05/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _QosClientValidateSite(QosClientRefT *pQosClient, QosCommonSiteT *pSite) +{ + // validate that the site has all the info we require + if ((pSite->uProbePort == 0) || (strcmp(pSite->strSiteName, "") == 0) || (strcmp(pSite->strProbeAddr, "") == 0)) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: site skipped, did not have complete information; site:%s, addr:%s:%d\n", pSite->strSiteName, pSite->strProbeAddr, pSite->uProbePort)); + return(-1); + } + + if (!QosCommonIsCompatibleProbeVersion(pSite->uProbeVersion)) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: site provided doesn't have a compatible version. %d, vs %d.\n", pSite->uProbeVersion, QOS_COMMON_PROBE_VERSION)); + return(-1); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientControlStrToControlInt + + \Description + Convert a string to a DirtySDK control selector, ie "abcd" = 'abcd'. + + \Input *pStr - string to convert + + \Output + int32_t - the integer value of the four character code + + \Version 06/05/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _QosClientControlStrToControlInt(const char* pStr) +{ + //control configs must be four characters long + if (strlen(pStr) == 4) + { + int32_t controlInt = 0; + // extract the type + controlInt = pStr[3]; + controlInt |= pStr[2] << 8; + controlInt |= pStr[1] << 16; + controlInt |= pStr[0] << 24; + return(controlInt); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientApplyControlConfigs + + \Description + Apply any QosClientControl selector overrides that coordinator has requested. + + \Input *pQosClient - pointer to module state + \Input *pConfig - collection of config overrides provided by the coordinator + + \Output + int32_t - number of config overrides applied. + + \Version 06/05/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _QosClientApplyControlConfigs(QosClientRefT *pQosClient, QosCommonClientConfigT *pConfig) +{ + int32_t i; + for (i = 0; i < pConfig->uNumControlConfigs; i++) + { + uint32_t controlInt = _QosClientControlStrToControlInt(pConfig->aControlConfigs[i].strControl); + QosClientControl(pQosClient, controlInt, pConfig->aControlConfigs[i].iValue, pConfig->aControlConfigs[i].strValue); + } + return(i); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientCreateRequest + + \Description + Enqueue a test to be performed against a ping site + + \Input *pQosClient - module state + \Input *pSite - information on what ping site to perform the test against + \Input *pTest - information on what settings to use while doing the test + \Input uServiceRequestID - id for a batch of requests, provided by the coordinator + + \Output + int32_t - request id, negative on failure. + + \Version 03/31/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _QosClientCreateRequest(QosClientRefT *pQosClient, QosCommonSiteT *pSite, QosCommonTestT *pTest, uint32_t uServiceRequestID) +{ + QosClientRequestT *pRequest, **ppQosClientRequest; + + NetCritEnter(&pQosClient->ThreadCrit); + // if the module's socket hasn't yet been allocated (do this a bit lazy so we can possibly configure the socket after creation) + if ((pQosClient->pUdpSocket == NULL) && ((pQosClient->pUdpSocket = _QosClientSocketOpen(pQosClient)) == NULL)) + { + NetPrintf(("qosclient: error opening listening socket.\n")); + NetCritLeave(&pQosClient->ThreadCrit); + return(-1); + } + + // allocate a request + if ((pRequest = (QosClientRequestT*)DirtyMemAlloc(sizeof(*pRequest), QOS_CLIENT_MEMID, pQosClient->iMemGroup, pQosClient->pMemGroupUserData)) == NULL) + { + NetPrintf(("qosclient: error allocating request.\n")); + NetCritLeave(&pQosClient->ThreadCrit); + return(-1); + } + ds_memclr(pRequest, sizeof(*pRequest)); + + // find end of queue and append + for (ppQosClientRequest = &pQosClient->pRequestQueue; *ppQosClientRequest != NULL; ppQosClientRequest = &(*ppQosClientRequest)->pNext) + { + } + *ppQosClientRequest = pRequest; + + // set the request values + pRequest->uServiceRequestID = uServiceRequestID; + pRequest->uClientRequestID = pQosClient->uNextClientRequestID++; + + pRequest->site = *pSite; + pRequest->test = *pTest; + + pRequest->uProbesToSend = pRequest->test.uProbeCountUp; + pRequest->uWhenStarted = NetTick(); + ds_strnzcpy(pRequest->rawResults.strSiteName, pRequest->site.strSiteName, sizeof(pRequest->rawResults.strSiteName)); + ds_strnzcpy(pRequest->rawResults.strTestName, pRequest->test.strTestName, sizeof(pRequest->rawResults.strTestName)); + + if (_QosClientValidateSite(pQosClient, &pRequest->site) < 0) + { + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_ERROR_SITE_INVALID, 0); + } + else if (_QosClientValidateTest(pQosClient, &pRequest->test) < 0) + { + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_ERROR_TEST_INVALID, 0); + } + else + { + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_INIT, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_INIT, 0); + } + + NetCritLeave(&pQosClient->ThreadCrit); + return(pRequest->uClientRequestID); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientGetOsFirewallType + + \Description + Queries DirtySDK to see if there is a NAT override to use in place of the value obtained by QOS + + \Output + QosCommonFirewallTypeE - NAT type. + + \Version 06/05/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static QosCommonFirewallTypeE _QosClientGetOsFirewallType(void) +{ + #if defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) + int32_t iResult = 0; + int32_t iNatType; + + iResult = NetConnStatus('natt', 0, &iNatType, sizeof(iNatType)); + if (iResult < 0) + { + NetPrintf(("qosclient: _QosClientGetOsFirewallType() error retrieving NAT type\n")); + return(QOS_COMMON_FIREWALL_UNKNOWN); + } + + if (iNatType == 0) + { + return(QOS_COMMON_FIREWALL_OPEN); + } + else if (iNatType == 1) + { + return(QOS_COMMON_FIREWALL_MODERATE); + } + else if (iNatType == 2) + { + return(QOS_COMMON_FIREWALL_STRICT); + } + + NetPrintf(("qosclient: _QosClientGetOsFirewallType() unknown error\n")); + return(QOS_COMMON_FIREWALL_UNKNOWN); + #else + return(QOS_COMMON_FIREWALL_UNKNOWN); + #endif +} + +/*F********************************************************************************/ +/*! + \Function _QosClientOverrideFinalResults + + \Description + Give the QosClient an opportunity to do final tweaks to the results before + providing the results to the game client. Currently not used. + + \Input *pQosClient - module state + + \Version 03/31/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientOverrideFinalResults(QosClientRefT *pQosClient) +{ +} + +/*F********************************************************************************/ +/*! + \Function _QosClientPurgeCurrentResults + + \Description + Clear any raw results we might be hanging on to + + \Input *pQosClient - pointer to module state + + \Version 03/16/2018 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientPurgeCurrentResults(QosClientRefT *pQosClient) +{ + pQosClient->rawResultStore.uNumResults = 0; + ds_memclr(pQosClient->rawResultStore.aResults, sizeof(pQosClient->rawResultStore.aResults)); + pQosClient->rawResultStore.clientModifiers.uStallCountCoordinator = 0; + pQosClient->rawResultStore.clientModifiers.uStallCountProbe = 0; + pQosClient->rawResultStore.clientModifiers.uStallDurationCoordinator = 0; + pQosClient->rawResultStore.clientModifiers.uStallDurationProbe = 0; +} + +/*F********************************************************************************/ +/*! + \Function _QosClientProcessCoordinatorResponse + + \Description + Parses the rpc response from the coordinator. + + \Input *pQosClient - pointer to module state + + \Output + int32_t - negative on error, 0 on success + + \Version 03/31/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _QosClientProcessCoordinatorResponse(QosClientRefT *pQosClient) +{ + int32_t iRet = 0; + QosCommonCoordinatorToClientResponseT response; + + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: <-- http response:\n")); + #if DIRTYCODE_LOGGING + if (pQosClient->iSpamValue >= 2) + { + NetPrintMem(pQosClient->pHttpRecvBuff, pQosClient->uCurrentHttpRecvSize, "CoordinatorToClientResponse"); + } + #endif + + if (QosCommonCoordinatorToClientResponseDecode(&response, pQosClient->pHttpRecvBuff, pQosClient->uCurrentHttpRecvSize) == 0) + { + int32_t testIdx, siteIdx; + pQosClient->uInitSyncTime = NetTick(); + + // detailed print + QosCommonCoordinatorToClientPrint(&response, pQosClient->iSpamValue); + + //validate the service id is what we expect it to be + if ((pQosClient->rawResultStore.uServiceRequestID != 0) && (pQosClient->rawResultStore.uServiceRequestID != response.uServiceRequestID)) + { + NetPrintf(("qosclient: warning, expected uServiceRequestID %d, but got %d.\n", pQosClient->rawResultStore.uServiceRequestID, response.uServiceRequestID)); + } + pQosClient->rawResultStore.uServiceRequestID = response.uServiceRequestID; + pQosClient->clientAddressFromCoordinator = response.configuration.clientAddressFromCoordinator; + + //apply the client configs if there are any, this may change many behaviors + if (response.configuration.uNumControlConfigs > 0) + { + _QosClientApplyControlConfigs(pQosClient, &response.configuration); + } + + if (response.results.uNumResults > 0) + { + //read our results and complete the QOS process + pQosClient->finalResults = response.results; + pQosClient->uFinalResultsRecvTime = NetTick(); + _QosClientOverrideFinalResults(pQosClient); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_REPORT, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_RESULTS_RECEIVED, 0); + } + else if ((response.configuration.uNumQosTests > 0) && (response.configuration.uNumSites > 0)) + { + // get rid of existing raw results, so when the new tests finish we don't send the old raw results to the coordinator again + if (pQosClient->rawResultStore.uNumResults > 0) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: purging %d existing raw results, continuing with multi-phased qos test.\n", pQosClient->rawResultStore.uNumResults)); + _QosClientPurgeCurrentResults(pQosClient); + } + + //convert this collection of information we have into a bunch of discrete request + for (testIdx = 0; testIdx < response.configuration.uNumQosTests; testIdx++) + { + for (siteIdx = 0; siteIdx < response.configuration.uNumSites; siteIdx++) + { + // if the test is against all sties, or if the site is contained in the list of sites the test should be performed against + if ((strcmp(response.configuration.aQosTests[testIdx].strSiteName, "ALL") == 0) || (strstr(response.configuration.aQosTests[testIdx].strSiteName, response.configuration.aSites[siteIdx].strSiteName) != NULL)) + { + _QosClientCreateRequest(pQosClient, &response.configuration.aSites[siteIdx], &response.configuration.aQosTests[testIdx], response.uServiceRequestID); + } + } + } + if (pQosClient->pRequestQueue != NULL) + { + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_PROBE, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_TEST_CONFIG_RECIEVED, 0); + } + else + { + NetPrintf(("qosclient: error, message from QosCoordinator did not result in any actionable requests.\n")); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_TEST_CONFIG_PRODUCED_NO_REQUESTS, 0); + iRet = -1; + } + } + else + { + NetPrintf(("qosclient: error, message from QosCoordinator was not actionable; uNumQosTests:%d, uNumSites:%d.\n", response.configuration.uNumQosTests, response.configuration.uNumSites)); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_TEST_CONFIG_UNUSABLE, 0); + iRet = -1; + } + } + else + { + NetPrintf(("qosclient: error, QosCommonCoordinatorToClientResponseDecode failed.\n")); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_RPC_DECODE, 0); + iRet = -1; + } + return(iRet); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientRequestInit + + \Description + Do any preparations such as the dns resolve of the QOS server before beginning + to send probes. + + \Input *pQosClient - pointer to module state + \Input *pRequest - pointer to the request which contains the site info + + \Version 03/31/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientRequestInit(QosClientRefT *pQosClient, QosClientRequestT *pRequest) +{ + // check if we need to do a lookup of the address + if (pRequest->pQosServerHost == 0) + { + if ((pRequest->pQosServerHost = SocketLookup(pRequest->site.strProbeAddr, 30*1000)) == NULL) + { + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_ERROR, DIRTYAPI_PROTO_HTTP, QOS_CLIENT_REQUEST_STATUS_ERROR_SOCKET_LOOKUP_ALLOC, 0); + } + } + else // we are in the process of looking up the address + { + // check if we are done resolving the host name + if (pRequest->pQosServerHost->Done(pRequest->pQosServerHost) == TRUE) + { + if (pRequest->pQosServerHost->addr != 0) + { + // we finished successfully, update the address from the lookup + SockaddrInit(&pRequest->serverAddr, AF_INET); + SockaddrInSetAddr(&pRequest->serverAddr, pRequest->pQosServerHost->addr); + SockaddrInSetPort(&pRequest->serverAddr, pRequest->site.uProbePort); + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_INIT_SYNC, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_SOCKET_LOOKUP_SUCCESS, 0); + } + else + { + // we finished but we don't have a valid address + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_ERROR, DIRTYAPI_PROTO_HTTP, QOS_CLIENT_REQUEST_STATUS_ERROR_SOCKET_LOOKUP_UPDATE, 0); + } + + // we are done with the address lookup, free the resources + pRequest->pQosServerHost->Free(pRequest->pQosServerHost); + pRequest->pQosServerHost = NULL; + } + } + +} + +/*F********************************************************************************/ +/*! + \Function _QosClientRequestWaitForInit + + \Description + Transition to the send state if no other request are in the init state. + We prefer to start all tests at the same time because if the users connection + suffers from periodic lag spikes, this will produce a more "fair" result. Otherwise what + might be the best ping site can suffer from a random occurrence, and a less ideal ping + site which was lucky enough to avoid the lag spike will come out as the best ping site. + Now all ping sites should avoid or suffer from the same lag spike since they are occurring + at the same time. + + \Input *pQosClient - pointer to module state + \Input *pRequest - pointer to the request + + \Version 08/11/2016 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientRequestWaitForInit(QosClientRefT *pQosClient, QosClientRequestT *pRequest) +{ + QosClientRequestT *pRequestToCheck; + + if (NetTickDiff(NetTick(), pQosClient->uInitSyncTime) > pRequest->test.uInitSyncTimeout) //too much time passed + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] giving up on waiting for init, %dms have already passed.\n", pRequest->uClientRequestID, pRequest->test.uInitSyncTimeout)); + } + else + { + for (pRequestToCheck = pQosClient->pRequestQueue; pRequestToCheck != NULL; pRequestToCheck = pRequestToCheck->pNext) + { + if (pRequestToCheck->eState == REQUEST_STATE_INIT) + { + return;//one of the requests is still waiting to resolve the target address, lets continue to wait so that we can all measure connection metrics at the same time + } + } + } + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_SEND, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_INIT_SYNC_SUCCESS, 0); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientRequestSendProbes + + \Description + Sends probes to the request's target as necessary. + + \Input *pQosClient - pointer to module state + \Input *pRequest - pointer to the request + + \Version 11/07/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientRequestSendProbes(QosClientRefT *pQosClient, QosClientRequestT *pRequest) +{ + uint32_t uSendCounter; + int32_t iResult = 0; + uint32_t uProbeCountToSend = 1; //send probes one at a time, unless uMinTimeBetwenProbes == 0 + uint8_t aSendBuff[QOS_COMMON_MAX_PACKET_SIZE]; + QosCommonProbePacketT probe; + + // figure out what external address best describes where the server will be sending its probes to, for security + // server will verify the probes are coming from this address, we switch to address from server if its available + if (pRequest->rawResults.clientAddressFromServer.uFamily == 0) + { + pRequest->probeExternalAddr = pQosClient->clientAddressFromCoordinator; + } + else + { + pRequest->probeExternalAddr = pRequest->rawResults.clientAddressFromServer; + } + + ds_memclr(&probe, sizeof(probe)); + ds_memclr(aSendBuff, sizeof(aSendBuff)); + + probe.uProtocol = QOS_COMMON_PROBE_PROTOCOL_ID; + probe.uVersion = QOS_COMMON_PROBE_VERSION; + probe.uServiceRequestId = pRequest->uServiceRequestID; + //probe.uServerReceiveTime; server use only + //probe.uServerSendDuration; server use only + probe.uProbeSizeUp = pRequest->test.uProbeSizeUp; + probe.uProbeSizeDown = pRequest->test.uProbeSizeDown; + //probe.uProbeCountUp = pRequest->test.uProbeCountUp; //modified for each probe in loop below + probe.uProbeCountDown = pRequest->test.uProbeCountDown; + probe.uClientRequestId = pRequest->uClientRequestID; + probe.clientAddressFromService = pRequest->probeExternalAddr; + probe.uExpectedProbeCountUp = pRequest->test.uProbeCountUp; + //probe.additionalBytes filled with memset send buffer + + // should we send all the probes in a burst? + if (pRequest->test.uMinTimeBetwenProbes == 0) + { + uProbeCountToSend = pRequest->uProbesToSend - pRequest->rawResults.uProbeCountUp; //target number of probes to send - the number of probes we already sent + } + + // send probes + for (uSendCounter = 0; uSendCounter < uProbeCountToSend; uSendCounter++) + { + if (pRequest->rawResults.uProbeCountUp >= QOS_COMMON_MAX_PROBE_COUNT) + { + //this can happen in a BW up test since we send a burst, but if we do lets bail + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] max probes have already been sent, not sending additional probes.\n", pRequest->uClientRequestID, &pRequest->serverAddr)); + //we don't really want to error immediately, better let it timeout so we can have a chance to receive any incoming probes (though likely is going to be a timeout scenario) + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_RECEIVE, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_ERROR_SENT_MAX_PROBES, 0); + pRequest->uWhenLastSent = NetTick(); + return; + } + + probe.uProbeCountUp = pRequest->rawResults.uProbeCountUp; + + //probe is now complete, serialize it and sign it, into the send buffer (a place where there will be extra space for the additional bandwidth bytes) + if (QosCommonSerializeProbePacket(aSendBuff, sizeof(aSendBuff), &probe, pRequest->site.aSecureKey) != QOS_COMMON_MIN_PACKET_SIZE) + { + NetPrintf(("qosclient: error serialized packet is not the expected size.\n")); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_SERIALIZED_PACKET_UNEXPECTED_SIZE, 0); + return; + } + + iResult = SocketSendto(pQosClient->pUdpSocket, (const char *)aSendBuff, probe.uProbeSizeUp, 0, &pRequest->serverAddr, sizeof(pRequest->serverAddr)); + NetPrintfVerbose((pQosClient->iSpamValue, 2, "qosclient: request[%02d] sent probe %d to %A (result=%d)\n", pRequest->uClientRequestID, pRequest->rawResults.uProbeCountUp, &pRequest->serverAddr, iResult)); + + if (iResult == probe.uProbeSizeUp) //check to see that we were successful in our last send + { + pRequest->rawResults.aProbeResult[pRequest->rawResults.uProbeCountUp].uClientSendTime = NetTick(); + pRequest->rawResults.uProbeCountUp++; + if (pRequest->rawResults.uProbeCountUp >= QOS_COMMON_MAX_PROBE_COUNT) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] max probes have now been sent, no additional probes will be sent.\n", pRequest->uClientRequestID, &pRequest->serverAddr)); + } + } + else + { + break; //some failure deal with it below + } + } + + if (iResult == probe.uProbeSizeUp) //check to see that we were successful in our last send + { + NetPrintfVerbose((pQosClient->iSpamValue, 1, "qosclient: request[%02d] sent %d probes to address %A\n", pRequest->uClientRequestID, uProbeCountToSend, &pRequest->serverAddr)); + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_RECEIVE, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_SEND_SUCCESS, 1); + pRequest->uWhenLastSent = NetTick(); + } + else if (iResult == 0) // need to try again + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] needs to try again to send probe to %A\n", pRequest->uClientRequestID, &pRequest->serverAddr)); + // transition anyways, let the re-issue logic handle the missing probes + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_RECEIVE, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_SEND_TRY_AGAIN, 0); + pRequest->uWhenLastSent = NetTick(); + } + else if (iResult < 0) // an error + { + NetPrintf(("qosclient: request[%02d] failed to send probe %d to address %A (err=%d)\n", pRequest->uClientRequestID, pRequest->rawResults.uProbeCountUp, &pRequest->serverAddr, iResult)); + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_ERROR, DIRTYAPI_SOCKET, iResult, 0); + } + else // unexpected result + { + NetPrintf(("qosclient: request[%02d] unexpected result for probe %d to address %A (err=%d)\n", pRequest->uClientRequestID, pRequest->rawResults.uProbeCountUp, &pRequest->serverAddr, iResult)); + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_ERROR, DIRTYAPI_SOCKET, iResult, 0); + } +} + +/*F********************************************************************************/ +/*! + \Function _QosClientValidatePacketQueue + + \Description + Ensure the packet queue 'pque' is in a healthy state, if its not print warnings. + + \Input *pQosClient - pointer to module state + + \Output + uint32_t - amount of space left in packet queue + + \Version 03/31/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static uint32_t _QosClientValidatePacketQueue(QosClientRefT *pQosClient) +{ + if (pQosClient->pUdpSocket != NULL) + { + //validate that the packet queue is healthy, it can be difficult to detect so we have special logging to check + int32_t iQueueSize = SocketInfo(pQosClient->pUdpSocket, 'psiz', 0, NULL, 0); + int32_t iQueueHighWater = SocketInfo(pQosClient->pUdpSocket, 'pmax', 0, NULL, 0); + +#if DIRTYCODE_LOGGING + int32_t iPacketDrop = SocketInfo(pQosClient->pUdpSocket, 'pdrp', 0, NULL, 0); + + //if we are getting close to using all of the packet queue complain always + if (iQueueHighWater >= (iQueueSize - 2)) + { + NetPrintf(("qosclient: warning %d/%d packet queue high watermark, lost packets %d.\n", iQueueHighWater, iQueueSize, iPacketDrop)); + } + else + { + NetPrintfVerbose((pQosClient->iSpamValue, 2, "qosclient: %d/%d packet queue high watermark, lost packets %d.\n", iQueueHighWater, iQueueSize, iPacketDrop)); + } +#endif + return(iQueueSize - iQueueHighWater); + } + + //if we don't have a socket, just return the default + return(QOS_CLIENT_DEFAULT_PACKET_QUEUE_SIZE); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientAllocHttpSendBuff + + \Description + Allocate a send buffer of the specified size. + + \Input *pQosClient - module state + \Input uSize - the size the send buffer should be + + \Output + char* - success, pointer to pQosClient->pHttpSendBuff; fail, NULL + + \Version 11/07/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static uint8_t* _QosClientAllocHttpSendBuff(QosClientRefT *pQosClient, uint32_t uSize) +{ + DirtyMemGroupEnter(pQosClient->iMemGroup, pQosClient->pMemGroupUserData); + + // if the user is asking for a 0 size buffer they really just want to deallocate it + if (uSize == 0) + { + if (pQosClient->pHttpSendBuff != NULL) + { + DirtyMemFree(pQosClient->pHttpSendBuff, QOS_CLIENT_MEMID, pQosClient->iMemGroup, pQosClient->pMemGroupUserData); + pQosClient->pHttpSendBuff = NULL; + } + DirtyMemGroupLeave(); + pQosClient->uHttpSendBuffSize = 0; + pQosClient->uHttpBytesToSend = 0; + return(NULL); + } + + // if the current size is good enough just use it + if (pQosClient->uHttpSendBuffSize >= uSize) + { + DirtyMemGroupLeave(); + return(pQosClient->pHttpSendBuff); + } + + // if we are going to allocate something, deallocate what we already have, if we had something + _QosClientAllocHttpSendBuff(pQosClient, 0); //this doesn't move the old data to the new buffer, but that isn't behavior we need right now + + // check to see the amount of space being asked for is sane + if (uSize > QOS_COMMON_MAX_RPC_BODY_SIZE) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: purging %d existing raw results, the required send buffer is too large.\n", pQosClient->rawResultStore.uNumResults)); + _QosClientPurgeCurrentResults(pQosClient); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_SEND_BUFFER_NEEDED_TOO_LARGE, 0); + DirtyMemGroupLeave(); + return(NULL); + } + + // allocate the amount of space that was asked for + if ((pQosClient->pHttpSendBuff = DirtyMemAlloc(uSize, QOS_CLIENT_MEMID, pQosClient->iMemGroup, pQosClient->pMemGroupUserData)) == NULL) + { + //try again with no results + if (pQosClient->rawResultStore.uNumResults > 0) + { + NetPrintf(("qosclient: purging %d existing raw results, error failed to allocate send buffer(%d)\n", pQosClient->rawResultStore.uNumResults, uSize)); + _QosClientPurgeCurrentResults(pQosClient); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_FAILED_ALLOC_SEND_BUFFER, 0); + } + else // if we can't allocate a buffer with no resutls, its fatal + { + NetPrintf(("qosclient: error failed to allocate send buffer(%d)\n", uSize)); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_FATAL, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_FAILED_ALLOC_SEND_BUFFER_FATAL, 0); + } + DirtyMemGroupLeave(); + return(NULL); + } + pQosClient->uHttpSendBuffSize = uSize; + pQosClient->uHttpBytesToSend = 0; + DirtyMemGroupLeave(); + return(pQosClient->pHttpSendBuff); +} + + +/*F********************************************************************************/ +/*! + \Function _QosClientAutoStart + + \Description + The coordinator may have told us to run a tests again after some delay, + if it wasn't satisfied with the results it generated. + + \Input *pQosClient - pointer to module state + + \Version 08/02/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientAutoStart(QosClientRefT *pQosClient) +{ + // see if the coordinator has told us to start a QOS process ourselves + if (pQosClient->finalResults.uTimeTillRetry != 0) + { + if (NetTickDiff(NetTick(), pQosClient->uFinalResultsRecvTime) > (int32_t)pQosClient->finalResults.uTimeTillRetry) + { + //save off the last profile name to pass back in + char strQosProfile[QOS_COMMON_MAX_RPC_STRING]; + ds_snzprintf(strQosProfile, sizeof(strQosProfile), "%s", pQosClient->rawResultStore.strQosProfile); + QosClientStart(pQosClient, pQosClient->strCoordinatorAddr, pQosClient->uCoordinatorPort, strQosProfile); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _QosClientMessageCoordinator + + \Description + Send message to the coordinator letting it know the current state of the + QoS process. + + \Input *pQosClient - pointer to module state + + \Version 03/31/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientMessageCoordinator(QosClientRefT *pQosClient) +{ + char strUrl[QOS_COMMON_MAX_URL_LENGTH]; + + ProtoHttp2Update(pQosClient->pProtoHttp); + + // we don't have a send in progress yet + if (pQosClient->uHttpBytesToSend == 0) + { + // get a send buffer + if (_QosClientAllocHttpSendBuff(pQosClient, QosCommonClientToCoordinatorEstimateEncodedSize(&pQosClient->rawResultStore)) == NULL) + { + //_QosClientAllocHttpSendBuff will have state transitioned if it failed + return; + } + + // add any last minute data + pQosClient->rawResultStore.clientModifiers.uPacketQueueRemaining = _QosClientValidatePacketQueue(pQosClient); // check if packet queue sizes are becoming a problem + pQosClient->rawResultStore.clientModifiers.eOSFirewallType = _QosClientGetOsFirewallType(); // tell the coordinator what the client thinks about the firewall + + // encode the rpc into the send buffer + if ((pQosClient->pHttpSendProgress = QosCommonClientToCoordinatorRequestEncode(&pQosClient->rawResultStore, pQosClient->pHttpSendBuff, pQosClient->uHttpSendBuffSize, &pQosClient->uHttpBytesToSend)) != NULL) + { + int32_t iResult; + const char *pStrHttpMode = "https"; + if (pQosClient->bUseHttps != TRUE) + { + pStrHttpMode = "http"; + } + ds_snzprintf(strUrl, sizeof(strUrl), "%s://%s:%u%s", pStrHttpMode, pQosClient->strCoordinatorAddr, pQosClient->uCoordinatorPort, QOS_COMMON_CLIENT_URL); + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: --> sending http request:\n", strUrl)); + #if DIRTYCODE_LOGGING + if (pQosClient->iSpamValue >= 2) + { + NetPrintMem(pQosClient->pHttpSendBuff, pQosClient->uHttpBytesToSend, "ClientToCoordinatorRequest"); + } + QosCommonClientToCoordinatorPrint(&pQosClient->rawResultStore, pQosClient->iSpamValue); + #endif + + // we shouldn't have an active stream right now, but its possible through some error paths that it hasn't been cleaned up + if (pQosClient->iHttpStreamId != 0) + { + NetPrintf(("qosclient: discarding stream %d.\n", pQosClient->iHttpStreamId)); + ProtoHttp2StreamFree(pQosClient->pProtoHttp, pQosClient->iHttpStreamId); + pQosClient->iHttpStreamId = 0; + } + + // start the send + if ((iResult = ProtoHttp2Request(pQosClient->pProtoHttp, strUrl, NULL, PROTOHTTP2_STREAM_BEGIN, PROTOHTTP_REQUESTTYPE_POST, &pQosClient->iHttpStreamId)) >= 0) + { + pQosClient->pHttpSendProgress += iResult; + pQosClient->uHttpBytesToSend -= iResult; + //if uHttpBytesToSend is 0 we are done, otherwise there is still more data that needs to be sent, so stay in this state to do more ProtoHttpSends + if (pQosClient->uHttpBytesToSend == 0) + { + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_UPDATE_COORDINATOR_COMM, DIRTYAPI_PROTO_HTTP, iResult, 0); + } + } + else + { + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_FATAL, DIRTYAPI_PROTO_HTTP, iResult, 0); + } + } + else + { + NetPrintf(("qosclient: error, unable to QosCommonClientToCoordinatorRequestEncode.\n")); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_FATAL, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_RPC_ENCODE, 0); + } + } + //proceed with sending of stream + else + { + int32_t iResult; + + if ((iResult = ProtoHttp2Send(pQosClient->pProtoHttp, pQosClient->iHttpStreamId, pQosClient->pHttpSendProgress, pQosClient->uHttpBytesToSend)) >= 0) + { + pQosClient->pHttpSendProgress += iResult; + pQosClient->uHttpBytesToSend -= iResult; + //if uHttpBytesToSend is 0 we are done, otherwise there is still more data that needs to be sent, so stay in this state to do more ProtoHttpSends + if (pQosClient->uHttpBytesToSend == 0) + { + ProtoHttp2Send(pQosClient->pProtoHttp, pQosClient->iHttpStreamId, NULL, PROTOHTTP2_STREAM_END); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_UPDATE_COORDINATOR_COMM, DIRTYAPI_PROTO_HTTP, iResult, 0); + } + } + else + { + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_FATAL, DIRTYAPI_PROTO_HTTP, iResult, 0); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _QosClientValidateProbe + + \Description + Validate that a probe packet meets all the requirements to be processed. + + \Input *pQosClient - pointer to module state + \Input *pProbe - pointer probe to be validated + + \Output + QosClientRequestT * - the request the probe belongs to, or NULL if validation fails. + + \Version 06/05/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static QosClientRequestT* _QosClientValidateProbe(QosClientRefT *pQosClient, QosCommonProbePacketT* pProbe) +{ + QosClientRequestT *pRequest = NULL; + + // get request matching id + if ((pRequest = _QosClientGetRequest(pQosClient, pProbe->uClientRequestId)) == NULL) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] could not be found, ignoring.\n", pProbe->uClientRequestId)); + return(NULL); + } + + if (pProbe->uProtocol != QOS_COMMON_PROBE_PROTOCOL_ID) + { + NetPrintf(("qosclient: error probe packet not expected protocol (%d).\n", pProbe->uProtocol)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_PROTOCOL; + return(NULL); + } + + if (!QosCommonIsCompatibleProbeVersion(pProbe->uVersion)) + { + NetPrintf(("qosclient: error probe packet not compatible version (%d).\n", pProbe->uVersion)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_VERSION; + return(NULL); + } + + // make sure the probe isn't smaller than could be possible + if (pProbe->uProbeSizeUp < QOS_COMMON_MIN_PACKET_SIZE) + { + NetPrintf(("qosclient: error uProbeSizeUp=%d, is smaller than QOS_COMMON_MIN_PACKET_SIZE=%d\n", pProbe->uProbeSizeUp, QOS_COMMON_MIN_PACKET_SIZE)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_UP_TOO_SMALL; + return(NULL); + } + + if (pProbe->uProbeSizeDown < QOS_COMMON_MIN_PACKET_SIZE) + { + NetPrintf(("qosclient: error uProbeSizeDown=%d, is smaller than QOS_COMMON_MIN_PACKET_SIZE=%d\n", pProbe->uProbeSizeDown, QOS_COMMON_MIN_PACKET_SIZE)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_DOWN_TOO_SMALL; + return(NULL); + } + + // make sure the probe isn't larger than could be possible + if (pProbe->uProbeSizeUp > QOS_COMMON_MAX_PACKET_SIZE) + { + NetPrintf(("qosclient: error uProbeSizeUp=%d, is larger than QOS_COMMON_MAX_PACKET_SIZE=%d\n", pProbe->uProbeSizeUp, QOS_COMMON_MAX_PACKET_SIZE)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_UP_TOO_LARGE; + return(NULL); + } + + if (pProbe->uProbeSizeDown > QOS_COMMON_MAX_PACKET_SIZE) + { + NetPrintf(("qosclient: error uProbeSizeDown=%d, is larger than QOS_COMMON_MAX_PACKET_SIZE=%d\n", pProbe->uProbeSizeDown, QOS_COMMON_MAX_PACKET_SIZE)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_DOWN_TOO_LARGE; + return(NULL); + } + + // make sure the incoming probe doesn't contain values we don't expect + if (!(pProbe->clientAddressFromService.uFamily == AF_INET || pProbe->clientAddressFromService.uFamily == AF_INET6)) + { + NetPrintf(("qosclient: error unexpected externalAddress.\n")); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_MY_ADDRESS; + return(NULL); + } + + if (pProbe->uServerReceiveTime == 0) + { + NetPrintf(("qosclient: error, expected incoming probe expected uServerReceiveTime(%d), to be set.\n", pProbe->uServerReceiveTime)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_SEVER_TIME; + return(NULL); + } + + // make sure the probe counts are within expected limits + if (pProbe->uProbeCountDown > QOS_COMMON_MAX_PROBE_COUNT) + { + NetPrintf(("qosclient: error, uProbeCountDown(%d) higher than expected.\n", pProbe->uProbeCountDown)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_DOWN_TOO_MANY; + return(NULL); + } + + if (pProbe->uProbeCountUp > QOS_COMMON_MAX_PROBE_COUNT) + { + NetPrintf(("qosclient: error, uProbeCountUp(%d) higher than expected.\n", pProbe->uProbeCountUp)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_UP_TOO_MANY; + return(NULL); + } + + // validate that this request is part of the right service request + if (pRequest->uServiceRequestID != pProbe->uServiceRequestId) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] uServiceRequestId's did not match request(%d), probe(%d).\n", pProbe->uClientRequestId)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_SERVICE_REQUEST_ID; + return(NULL); + } + + // make sure it's not failed or timed out + if (pRequest->eState == REQUEST_STATE_COMPLETE) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] response received but ignored, since the request is already completed.\n", pRequest->uClientRequestID)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_ALREADY_COMPLETE; + return(NULL); + } + + // validate we haven't already received the probe for this slot (we will only know the server receive time if we have received it here on the client) + if (pRequest->rawResults.aProbeResult[pProbe->uProbeCountDown].uServerReceiveTime != 0) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] response received but ignored, because we already received a probe to match the one sent uProbeCount(%d).\n", pRequest->uClientRequestID, pProbe->uProbeCountDown)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_ALREADY_RECEIVED; + return(NULL); + } + + return(pRequest); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientSaveProbeResults + + \Description + Store the results from receiving a probe, when all probes have been received the + request is done. Once all requests are done results will be shared with the + coordinator. + + \Input *pQosClient - module state + \Input *pRequest - request associated to the probe + \Input *pProbe - incoming probe + \Input *pRecvAddr - address the probe came from (QOS server's address) + + \Version 06/05/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientSaveProbeResults(QosClientRefT *pQosClient, QosClientRequestT *pRequest, QosCommonProbePacketT *pProbe, struct sockaddr *pRecvAddr) +{ + //update the result of the request + pRequest->rawResults.aProbeResult[pProbe->uProbeCountDown].uServerReceiveTime = pProbe->uServerReceiveTime; + pRequest->rawResults.aProbeResult[pProbe->uProbeCountDown].uServerSendDelta = pProbe->uServerSendDelta; + + // we didn't actually send a probe to match this receive, so use the send time of the first probe + if (pRequest->test.uProbeCountUp == 1) + { + pRequest->rawResults.aProbeResult[pProbe->uProbeCountDown].uClientSendTime = pRequest->rawResults.aProbeResult[0].uClientSendTime; + pRequest->rawResults.aProbeResult[pProbe->uProbeCountDown].uClientReceiveDelta = NetTickDiff(SockaddrInGetMisc(pRecvAddr), pRequest->rawResults.aProbeResult[0].uClientSendTime); + } + else + { + pRequest->rawResults.aProbeResult[pProbe->uProbeCountDown].uClientReceiveDelta = NetTickDiff(SockaddrInGetMisc(pRecvAddr), pRequest->rawResults.aProbeResult[pProbe->uProbeCountDown].uClientSendTime); + } + + pRequest->rawResults.clientAddressFromServer = pProbe->clientAddressFromService; //this would be the same from every probe for a given requests + + pRequest->rawResults.uProbeCountDown++; + if (pRequest->rawResults.uProbeCountHighWater < pProbe->uProbeCountDown) + { + pRequest->rawResults.uProbeCountHighWater = pProbe->uProbeCountDown; + } + + if (QosCommonIsAddrEqual(&pRequest->rawResults.clientAddressFromServer, &pRequest->probeExternalAddr) == FALSE) + { +#if DIRTYCODE_LOGGING + char addr1Buff[128]; + char addr2Buff[128]; +#endif + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] QOS server does not agree that client external address is %s, it should be %s.\n", pRequest->uClientRequestID, QosCommonAddrToString(&pRequest->rawResults.clientAddressFromServer, addr1Buff, sizeof(addr1Buff)), QosCommonAddrToString(&pRequest->probeExternalAddr, addr2Buff, sizeof(addr2Buff)))); + // the coordinator and the QOS server disagree on what the external address of the client is, this will be ok for many test types + // however the QOS server will not take part in sending multiple packets or large packets to a target that it isn't confident it has the right address + // in case of ip/spoofing packet replay is attempting to generate a DDOS attack + // if we would like to do something like check our down bandwidth we must update our external address to agree with the QOS server who is performing the test + if ((pRequest->test.uProbeCountDown > 1) || (pRequest->test.uProbeSizeDown > QOS_COMMON_MIN_PACKET_SIZE)) + { + // if we want to update to the correct address, lets just make a whole new request so that we don't potentially have older probes on the wire interfering with our new results + // but in case there is some real world internet scenario where the address never match up and we get caught in a loop creating requests, we should only do this max once per request + if (pRequest->uCreationCount == 0) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] creating new request with updated external address(%s->%s).\n", pRequest->uClientRequestID, QosCommonAddrToString(&pRequest->rawResults.clientAddressFromServer, addr1Buff, sizeof(addr1Buff)), QosCommonAddrToString(&pRequest->probeExternalAddr, addr2Buff, sizeof(addr2Buff)))); + + // mostly the new request is a copy of the old request + int32_t iRequestID = _QosClientCreateRequest(pQosClient, &pRequest->site, &pRequest->test, pRequest->uServiceRequestID); + QosClientRequestT *pNewRequest = _QosClientGetRequest(pQosClient, iRequestID); + + //the probes will be sent containing the address the QOS server expects to see, probeExternalAddr selects clientAddressFromServer over clientAddressFromCoordinator + pNewRequest->rawResults.clientAddressFromServer = pRequest->rawResults.clientAddressFromServer; + + // so we don't do this again if it fails again for some reason + pNewRequest->uCreationCount++; + } + + // let the old request complete early, this should prevent us from processing more of the probes from the old request + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_ERROR_EXTERNAL_ADDRESS_MISMATCH, 0); + } + } + + //now that we've processed the probe, determine if we are ready to move on to the next state + if (pRequest->rawResults.uProbeCountDown >= (pRequest->test.uProbeCountDown * pRequest->test.uProbeCountUp)) + { + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_COMPLETE, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_COMPLETE, 0); + } +} + +/*F********************************************************************************/ +/*! + \Function _QosClientRecvResponse + + \Description + Process a receive response. + + \Input *pQosClient - module state + \Input *pProbe - received probe + \Input *pRecvAddr - source of packet data + + \Version 08/15/2011 (jbrookes) re-factored from _QosClientRecv() +*/ +/********************************************************************************F*/ +static void _QosClientRecvResponse(QosClientRefT *pQosClient, QosCommonProbePacketT *pProbe, struct sockaddr *pRecvAddr) +{ + QosClientRequestT *pRequest; + +#if QOS_CLIENT_SIMULATE_PACKET_LOSS + static uint32_t uTestCount=0; + uTestCount++; + if ((uTestCount%4) == 0) + { + NetPrintf(("qosclient: request[%02d] pretending probe %d was lost.\n", pProbe->uClientRequestId, pProbe->uProbeCountDown)); + return; + } +#endif + + // make sure the probe is valid and get matching request + if ((pRequest = _QosClientValidateProbe(pQosClient, pProbe)) == NULL) + { + //Validation function would have printed + return; + } + + //save the timing info from the probe and state transition if necessary + _QosClientSaveProbeResults(pQosClient, pRequest, pProbe, pRecvAddr); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientRecv + + \Description + Process QosClient socket data. + + \Input *pSocket - pointer to module socket + \Input *pData - pointer to module state + + \Output + int32_t - zero + + \Version 08/08/2011 (szhu) +*/ +/********************************************************************************F*/ +static int32_t _QosClientRecv(SocketT *pSocket, void *pData) +{ + QosClientRefT *pQosClient = (QosClientRefT *)pData; + struct sockaddr_storage recvAddr; + int32_t iAddrLen, iRecvLen; + uint8_t aRecvBuff[QOS_COMMON_MAX_PACKET_SIZE]; + SockaddrInit((struct sockaddr*)&recvAddr, AF_INET); + + NetCritEnter(&pQosClient->ThreadCrit); + while ((iRecvLen = SocketRecvfrom(pSocket, (char*)aRecvBuff, sizeof(aRecvBuff), 0, (struct sockaddr*)&recvAddr, &iAddrLen)) > 0) + { + if (iRecvLen >= QOS_COMMON_MIN_PACKET_SIZE) + { + QosClientRequestT *pRequest = NULL; + QosCommonProbePacketT probe; + uint16_t uClientRequestID = QosCommonDeserializeClientRequestId(aRecvBuff); //we need to get the client request id from the packet so we can look up the secure key to authenticate the rest of the probe + NetPrintfVerbose((pQosClient->iSpamValue, 2, "qosclient: request[%02d] received probe (iRecvLen=%d).\n", uClientRequestID, iRecvLen)); + + if ((pRequest = _QosClientGetRequest(pQosClient, uClientRequestID)) != NULL) + { + if (QosCommonDeserializeProbePacket(&probe, aRecvBuff, pRequest->site.aSecureKey, NULL) == 0) + { + _QosClientRecvResponse(pQosClient, &probe, (struct sockaddr*)&recvAddr); + } + else + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] hmac test failed.\n", pRequest->uClientRequestID)); + pRequest->iLastProbeValidationError = QOS_CLIENT_REQUEST_STATUS_ERROR_PROBE_HMAC; + } + } + else + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] QosCommonDeserializeClientRequestId() could not be found, ignoring.\n", uClientRequestID)); + } + } + else + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: Discarding probe packet that was too small, %d bytes.\n", iRecvLen)); + } + } + NetCritLeave(&pQosClient->ThreadCrit); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientRecvCB + + \Description + QosClient socket callback. + + \Input *pSocket - pointer to module socket + \Input iFlags - ignored + \Input *pData - pointer to module state + + \Output + int32_t - zero + + \Version 04/11/2008 (cadam) +*/ +/********************************************************************************F*/ +static int32_t _QosClientRecvCB(SocketT *pSocket, int32_t iFlags, void *pData) +{ + QosClientRefT *pQosClient = (QosClientRefT *)pData; + // make sure we own resources + if (NetCritTry(&pQosClient->ThreadCrit)) + { + _QosClientRecv(pSocket, pData); + // release resources + NetCritLeave(&pQosClient->ThreadCrit); + } + else + { + pQosClient->bDeferredRecv = TRUE; + } + return(0); +} + + +/*F********************************************************************************/ +/*! + \Function _QosClientUpdateUdpProbes + + \Description + Receive any incoming probes + + \Input *pQosClient - module state + + \Version 09/19/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientUpdateUdpProbes(QosClientRefT *pQosClient) +{ + /* + check if we need to poll for data. we need to do this if: + 1. this platform does not implement an async receive thread + 2. there's a deferred receive call issued by async receive thread + */ + if (pQosClient->pUdpSocket != NULL) + { + if (pQosClient->bDeferredRecv) + { + pQosClient->bDeferredRecv = FALSE; + _QosClientRecv(pQosClient->pUdpSocket, pQosClient); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _QosClientAllocHttpRecvBuff + + \Description + Allocate a receive buffer of the specified size. + + \Input *pQosClient - module state + \Input uSize - the size the receive buffer should be + + \Output + char* - success, pointer to pQosClient->pHttpRecvBuff; fail, NULL + + \Version 11/07/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static uint8_t* _QosClientAllocHttpRecvBuff(QosClientRefT *pQosClient, uint32_t uSize) +{ + uint8_t *pTemp; + DirtyMemGroupEnter(pQosClient->iMemGroup, pQosClient->pMemGroupUserData); + + // if the user is asking for a 0 size buffer they really just want to deallocate it + if (uSize == 0) + { + if (pQosClient->pHttpRecvBuff != NULL) + { + DirtyMemFree(pQosClient->pHttpRecvBuff, QOS_CLIENT_MEMID, pQosClient->iMemGroup, pQosClient->pMemGroupUserData); + pQosClient->pHttpRecvBuff = NULL; + } + DirtyMemGroupLeave(); + pQosClient->uHttpRecvBuffSize = 0; + pQosClient->uCurrentHttpRecvSize = 0; + return(NULL); + } + + // if the current size is good enough just use it + if (pQosClient->uHttpRecvBuffSize >= uSize) + { + DirtyMemGroupLeave(); + return(pQosClient->pHttpRecvBuff); + } + + // check to see the amount of space being asked for is sane + if (uSize > QOS_COMMON_MAX_RPC_BODY_SIZE) + { + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_RECV_BUFFER_NEEDED_TOO_LARGE, 0); + DirtyMemGroupLeave(); + return(NULL); + } + + + // allocate the amount of space that was asked for to a temporary variable + if ((pTemp = DirtyMemAlloc(uSize, QOS_CLIENT_MEMID, pQosClient->iMemGroup, pQosClient->pMemGroupUserData)) == NULL) + { + NetPrintf(("qosclient: error failed to allocate receive buffer(%d)\n", uSize)); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_FATAL, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_FAILED_ALLOC_RECV_BUFFER, 0); + DirtyMemGroupLeave(); + return(NULL); + } + + // move old data to the new buffer, and get rid of the old buffer (if there was one) + if (pQosClient->pHttpRecvBuff) + { + ds_memcpy(pTemp, pQosClient->pHttpRecvBuff, pQosClient->uHttpRecvBuffSize); + _QosClientAllocHttpRecvBuff(pQosClient, 0); + } + + pQosClient->pHttpRecvBuff = pTemp; + pQosClient->uHttpRecvBuffSize = uSize; + DirtyMemGroupLeave(); + return(pQosClient->pHttpRecvBuff); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientUpdateHttp + + \Description + Receive any incoming http responses + + \Input *pQosClient - module state + + \Version 11/07/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientUpdateHttp(QosClientRefT *pQosClient) +{ + int32_t iResult; + + + //service http module since we are expecting bytes to come in + ProtoHttp2Update(pQosClient->pProtoHttp); + + //update the clients local address, as new info becomes available + _QosClientUpdateLocalAddress(pQosClient); + + if (_QosClientAllocHttpRecvBuff(pQosClient, QOS_COMMON_DEFAULT_RPC_BODY_SIZE) == NULL) + { + // _QosClientAllocHttpRecvBuff will state transition on an error + return; + } + + if ((iResult = ProtoHttp2RecvAll(pQosClient->pProtoHttp, pQosClient->iHttpStreamId, pQosClient->pHttpRecvBuff, pQosClient->uHttpRecvBuffSize)) >= 0) + { + int32_t iHttpStatusCode = ProtoHttp2Status(pQosClient->pProtoHttp, pQosClient->iHttpStreamId, 'code', NULL, 0); + if (iHttpStatusCode == PROTOHTTP_RESPONSE_SUCCESSFUL) + { + pQosClient->uCurrentHttpRecvSize = iResult; + // parse the http response + if (_QosClientProcessCoordinatorResponse(pQosClient) >= 0) + { + //we state transition inside _QosClientProcessCoordinatorResponse + ProtoHttp2StreamFree(pQosClient->pProtoHttp, pQosClient->iHttpStreamId); + pQosClient->iHttpStreamId = 0; + pQosClient->uErrorMessageCount = 0; + } + else + { + pQosClient->uErrorMessageCount++; + if (pQosClient->uErrorMessageCount > 1) + { + NetPrintf(("qosclient: error we have had %d consecutive errors from the coordinator, going fatal.\n", pQosClient->uErrorMessageCount)); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_FATAL, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_UNRECOVERED_ERROR, 0); + } + } + } + else + { + NetPrintf(("qosclient: error http status %d\n", iHttpStatusCode)); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_FATAL, DIRTYAPI_PROTO_HTTP, iHttpStatusCode, 0); + } + } + else if (iResult == PROTOHTTP2_RECVWAIT) + { + NetPrintfVerbose((pQosClient->iSpamValue, 1, "qosclient: [%p] Waiting for complete http response to arrive.\n", pQosClient)); + } + else if (iResult == PROTOHTTP2_RECVBUFF) + { + uint32_t uNewBuffSize = 2 * pQosClient->uHttpRecvBuffSize; + NetPrintfVerbose((pQosClient->iSpamValue, 1, "qosclient: [%p] Insufficient buffer space to receive, increasing space from %u to %u.\n", pQosClient, pQosClient->uHttpRecvBuffSize, uNewBuffSize)); + _QosClientAllocHttpRecvBuff(pQosClient, uNewBuffSize); + } + else if (iResult < 0) + { + int32_t hResult = ProtoHttp2Status(pQosClient->pProtoHttp, pQosClient->iHttpStreamId, 'hres', NULL, 0); + if (hResult >= 0) //in this case there was no socket or ssl error + { + NetPrintf(("qosclient: error ProtoHttpRecvAll failed iResult=%d\n", iResult)); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_FATAL, DIRTYAPI_PROTO_HTTP, iResult, 0); + } + else + { + int16_t iCode = 0; + + //we just want to get the error code from the hResult so we can include it in the hResult generated in _QosClientModuleStateTransition + DirtyErrDecodeHResult(hResult, NULL, &iCode, NULL, NULL); + + NetPrintf(("qosclient: error ProtoHttpRecvAll failed hResult=%p\n", hResult)); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_FATAL, DIRTYAPI_PROTO_HTTP, iCode, 0); + } + } +} + + +/*F********************************************************************************/ +/*! + \Function _QosClientRequestReceiveUpdate + + \Description + Check to see if we should send a probe or we need to re-send any probes. + If so transition back into the send state. + + \Input *pQosClient - module state + \Input *pRequest - request being processes + + \Version 11/07/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientRequestReceiveUpdate(QosClientRefT *pQosClient, QosClientRequestT *pRequest) +{ + // don't bother sending anything more if we already have sent all we can, unless the responses are already on the wire this will likely timeout + if (pRequest->rawResults.uProbeCountUp < QOS_COMMON_MAX_PROBE_COUNT) + { + uint32_t currentTime = NetTick(); + // there are two reasons why we might transition back to REQUEST_STATE_SEND + // 1 the common case is that enough time has lapsed in between probes + if ((pRequest->rawResults.uProbeCountUp < pRequest->uProbesToSend) && //we haven't sent all probes yet + (NetTickDiff(currentTime, pRequest->uWhenLastSent) > (int32_t)pRequest->test.uMinTimeBetwenProbes) //too much time passed + ) + { + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_SEND, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_SEND_NEXT_PROBE, 1); + } + // 2 too much time may have laps before receiving responses in which case we want to send more probes then initially planned (we likely lost probes on the wire) + if ((pRequest->rawResults.uProbeCountDown < (pRequest->test.uProbeCountUp * pRequest->test.uProbeCountDown)) && //we haven't received the number of responses we want, and + (NetTickDiff(currentTime, pRequest->uWhenLastSent) > (int32_t)pRequest->test.uTimeTillResend) //too much time passed + ) + { + int32_t iMissingProbes = ((pRequest->test.uProbeCountUp * pRequest->test.uProbeCountDown) - pRequest->rawResults.uProbeCountDown); + if (iMissingProbes > pRequest->test.uAcceptableLostProbeCount) + { + int32_t iAdditionalProbes = (iMissingProbes / pRequest->test.uProbeCountDown); // we expect uProbeCountDown per uProbesToSend + if (iAdditionalProbes <= 0) //there needs to be at least one missing probe + { + iAdditionalProbes = 1; + } + pRequest->uProbesToSend += (iAdditionalProbes + pRequest->test.uResendExtraProbeCount); //the test may say to send even more probes if missing probes are found + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_SEND, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_SEND_LOST_PROBES, 0); + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] re-sending %d new probes.\n", pRequest->uClientRequestID, iAdditionalProbes)); + } + else + { + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_COMPLETE, DIRTYAPI_QOS, QOS_CLIENT_REQUEST_STATUS_COMPLETE_ACCEPTABLE, 0); + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function _QosClientRequestComplete + + \Description + Finish the request; call back the user, and delete the request. + + \Input *pQosClient - module ref + \Input *pRequest - request being completed + + \Version 11/07/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientRequestComplete(QosClientRefT *pQosClient, QosClientRequestT *pRequest) +{ + pRequest->uWhenCompleted = NetTick(); + pQosClient->rawResultStore.aResults[pQosClient->rawResultStore.uNumResults] = pRequest->rawResults; + pQosClient->rawResultStore.uNumResults++; + + _QosClientDestroyRequest(pQosClient, pRequest->uClientRequestID); + + // see if there are currently no requests left, if so tell the coordinator about what happened + if (pQosClient->pRequestQueue == NULL) + { + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_INIT_COORDINATOR_COMM, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_RAW_RESULTS_READY, 0); + } +} + +/*F********************************************************************************/ +/*! + \Function _QosClientCheckRequestForTimeout + + \Description + Check to see if the request should be completed due to timeout, if so transition it to timeout. + + \Input *pQosClient - module ref + \Input *pRequest - request being checked + + \Version 11/07/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientCheckRequestForTimeout(QosClientRefT *pQosClient, QosClientRequestT *pRequest) +{ + //check to see if the request has timed out + if (NetTickDiff(NetTick(), pRequest->uWhenStarted) > (int32_t)pRequest->test.uTimeout) + { + int16_t iErrorCode = QOS_CLIENT_REQUEST_STATUS_ERROR_TIMEOUT; + if (pRequest->rawResults.uProbeCountDown > 0) + { + iErrorCode = QOS_CLIENT_REQUEST_STATUS_ERROR_TIMEOUT_PARTIAL; + } + if (pRequest->iLastProbeValidationError != 0) + { + iErrorCode = pRequest->iLastProbeValidationError; + } + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: request[%02d] timed out (%d ms), possible cause %d.\n", pRequest->uClientRequestID, NetTickDiff(NetTick(), pRequest->uWhenStarted), iErrorCode)); + _QosClientRequestStateTransition(pQosClient, pRequest, REQUEST_STATE_ERROR, DIRTYAPI_QOS, iErrorCode, 0); + } +} + +/*F********************************************************************************/ +/*! + \Function _QosClientReport + + \Description + Display a summary report and pass the info on to the client + + \Input *pQosClient - module ref + + \Version 09/15/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientReport(QosClientRefT *pQosClient) +{ + //save the hResult that was generated while doing this request to pass to the user + pQosClient->finalResults.hResult = pQosClient->rawResultStore.clientModifiers.hResult; + #if DIRTYCODE_LOGGING + uint32_t uSiteIndex; + char addrBuff[128]; + + uint32_t uProcessTime = NetTickDiff(NetTick(), pQosClient->uQosProcessStartTime); + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: CLIENT(hResult:0x%08x, external addr:%s, fw:%s, time:%ums)\n", + pQosClient->finalResults.hResult, + QosCommonAddrToString(&pQosClient->finalResults.clientExternalAddress, addrBuff, sizeof(addrBuff)), + _strQosCommonFirewallType[pQosClient->finalResults.eFirewallType], + uProcessTime)); + + for (uSiteIndex = 0; uSiteIndex < pQosClient->finalResults.uNumResults; uSiteIndex++) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: SITE(%d, %8s) RESULT(hResult:0x%08x, upbps:%9u, downbps:%9u, rtt:%11u)\n", + uSiteIndex, + pQosClient->finalResults.aTestResults[uSiteIndex].strSiteName, + pQosClient->finalResults.aTestResults[uSiteIndex].hResult, + pQosClient->finalResults.aTestResults[uSiteIndex].uUpbps, + pQosClient->finalResults.aTestResults[uSiteIndex].uDownbps, + pQosClient->finalResults.aTestResults[uSiteIndex].uMinRTT + )); + } + #endif + + pQosClient->uQosProcessStartTime = 0; + if (pQosClient->pCallback) + { + pQosClient->pCallback(pQosClient, &pQosClient->finalResults, pQosClient->pUserData); + } + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_IDLE, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_REPORT_COMPLETE, 0); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientUpdateRequest + + \Description + Process the request for any given state. + + \Input *pQosClient - module ref + \Input *pRequest - request being updated + + \Output + int8_t - TRUE if the request has been destroyed. + + \Version 11/07/2014 (cvienneau) +*/ +/********************************************************************************F*/ +static uint8_t _QosClientUpdateRequest(QosClientRefT *pQosClient, QosClientRequestT *pRequest) +{ + if (pRequest->eState == REQUEST_STATE_INIT) + { + _QosClientRequestInit(pQosClient, pRequest); + } + else if (pRequest->eState == REQUEST_STATE_INIT_SYNC) + { + _QosClientRequestWaitForInit(pQosClient, pRequest); + } + else if (pRequest->eState == REQUEST_STATE_SEND) + { + _QosClientRequestSendProbes(pQosClient, pRequest); + } + else if (pRequest->eState == REQUEST_STATE_RECEIVE) + { + _QosClientRequestReceiveUpdate(pQosClient, pRequest); + } + else if (pRequest->eState == REQUEST_STATE_COMPLETE) + { + _QosClientRequestComplete(pQosClient, pRequest); + return(TRUE); + } + else + { + NetPrintf(("qosclient: request[%02d] error request in unexpected state.\n", pRequest->uClientRequestID)); + } + + _QosClientCheckRequestForTimeout(pQosClient, pRequest); + + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function _QosClientCheckUpdateTimings + + \Description + See if we are being pumped frequently enough, if we aren't it can adversely + effect the timeouts. + + \Input *pQosClient - module ref + + \Version 11/28/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosClientCheckUpdateTimings(QosClientRefT *pQosClient) +{ + uint32_t uCurrentTime = NetTick(); + + //check to see if we are being updated at a reasonable rate + uint32_t uUpateElapsedTime = NetTickDiff(uCurrentTime, pQosClient->uLastUpdateTime); + if (uUpateElapsedTime >= pQosClient->uStallWindow) + { + if ((pQosClient->eModuleState == MODULE_STATE_INIT_COORDINATOR_COMM) || (pQosClient->eModuleState == MODULE_STATE_UPDATE_COORDINATOR_COMM)) + { + pQosClient->rawResultStore.clientModifiers.uStallCountCoordinator++; + pQosClient->rawResultStore.clientModifiers.uStallDurationCoordinator += uUpateElapsedTime; + + } + else if (pQosClient->eModuleState == MODULE_STATE_PROBE) + { + pQosClient->rawResultStore.clientModifiers.uStallCountProbe++; + pQosClient->rawResultStore.clientModifiers.uStallDurationProbe += uUpateElapsedTime; + } + NetPrintfVerbose((pQosClient->iSpamValue, 1, "qosclient: an update stall of %dms was detected.\n", uUpateElapsedTime)); + } + pQosClient->uLastUpdateTime = uCurrentTime; + + //check for overall timeout + if (pQosClient->uQosProcessStartTime != 0) + { + if (NetTickDiff(uCurrentTime, pQosClient->uQosProcessStartTime) > (int32_t)pQosClient->uMaxQosProcessTime) + { + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: the qos process has timed out after %ums.\n", pQosClient->uMaxQosProcessTime)); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_FATAL, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_TIMEOUT, 0); + } + } +} + + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function QosClientUpdate + + \Description + Allow the module to do processing. + + \Input *pQosClient - point to module state + + \Version 06/21/2017 (cvienneau) +*/ +/********************************************************************************F*/ +void QosClientUpdate(QosClientRefT *pQosClient) +{ + QosClientRequestT *pRequest; + + if (!NetCritTry(&pQosClient->ThreadCrit)) + { + return; + } + + _QosClientCheckUpdateTimings(pQosClient); + + if (pQosClient->eModuleState == MODULE_STATE_IDLE) + { + _QosClientAutoStart(pQosClient); + } + else if (pQosClient->eModuleState == MODULE_STATE_INIT_COORDINATOR_COMM) + { + //tell the coordinator where we are at, this could be just who we are, or may include the results we have so far + _QosClientMessageCoordinator(pQosClient); + } + else if (pQosClient->eModuleState == MODULE_STATE_UPDATE_COORDINATOR_COMM) + { + // if there is any pending http action process it now + _QosClientUpdateHttp(pQosClient); + } + else if (pQosClient->eModuleState == MODULE_STATE_PROBE) + { + // receive data for any incoming probes + // note that probes may also be received by the socket receive thread + _QosClientUpdateUdpProbes(pQosClient); + + //update the state of all the request + for (pRequest = pQosClient->pRequestQueue; pRequest != NULL; pRequest = pRequest->pNext) + { + // update based off recent communication udp or http + if(_QosClientUpdateRequest(pQosClient, pRequest) == TRUE) + { + break;//on TRUE a request has been destroyed the list is no longer valid + } + } + } + else if (pQosClient->eModuleState == MODULE_STATE_REPORT) + { + _QosClientReport(pQosClient); + } + else + { + NetPrintf(("qosclient: error unknown eModuleState state.\n")); + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_ERROR, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_ERROR_UNKOWN_STATE, 0); + } + + NetCritLeave(&pQosClient->ThreadCrit); +} + +/*F********************************************************************************/ +/*! + \Function QosClientCreate + + \Description + Create the QosClient module. + + \Input *pCallback - callback function to use when a status change is detected + \Input *pUserData - use data to be set for the callback + \Input uListenPort - set the port incoming probes will go to, pass 0 to use defaults + + \Output + QosClientRefT * - state pointer on success, NULL on failure + + \Version 04/07/2008 (cadam) +*/ +/********************************************************************************F*/ +QosClientRefT *QosClientCreate(QosClientCallbackT *pCallback, void *pUserData, uint16_t uListenPort) +{ + QosClientRefT *pQosClient; + void *pMemGroupUserData; + int32_t iMemGroup; + + // Query current memory group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + DirtyMemGroupEnter(iMemGroup, pMemGroupUserData); + + // allocate and init module state + if ((pQosClient = (QosClientRefT*)DirtyMemAlloc(sizeof(*pQosClient), QOS_CLIENT_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + DirtyMemGroupLeave(); + NetPrintf(("qosclient: could not allocate module state\n")); + return(NULL); + } + + //init member variables + ds_memclr(pQosClient, sizeof(*pQosClient)); + pQosClient->iMemGroup = iMemGroup; + pQosClient->pMemGroupUserData = pMemGroupUserData; + pQosClient->iSpamValue = 1; + pQosClient->uUdpRequestedListenPort = uListenPort; + pQosClient->iHttpSoLinger = -1; + pQosClient->bUseHttps = QOS_CLIENT_DEFAULT_USE_HTTPS; + pQosClient->pCallback = pCallback; + pQosClient->pUserData = pUserData; + pQosClient->uUdpReceiveBuffSize = QOS_CLIENT_DEFAULT_RECEIVE_BUFF_SIZE; + pQosClient->uUdpSendBuffSize = QOS_CLIENT_DEFAULT_SEND_BUFF_SIZE; + pQosClient->uUdpPacketQueueSize = QOS_CLIENT_DEFAULT_PACKET_QUEUE_SIZE; + pQosClient->uStallWindow = QOS_CLIENT_DEFAULT_STALL_WINDOW; + pQosClient->uMaxQosProcessTime = QOS_CLIENT_DEFAULT_MAX_QOS_PROCESS_TIME; + pQosClient->uLastUpdateTime = NetTick(); + //transition to starting state + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_IDLE, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_INIT, 0); + + //setup protoHttp for communication with the coordinator + if ((pQosClient->pProtoHttp = ProtoHttp2Create(4096)) == NULL) //we shouldn't need much space + { + DirtyMemGroupLeave(); + NetPrintf(("qosclient: could not create the protohttp module.\n")); + QosClientDestroy(pQosClient); + return(NULL); + } + DirtyMemGroupLeave(); + ProtoHttp2Control(pQosClient->pProtoHttp, pQosClient->iHttpStreamId, 'apnd', 0, 0, NULL); + ProtoHttp2Control(pQosClient->pProtoHttp, pQosClient->iHttpStreamId, 'apnd', 0, 0, (void *)"te: trailers\r\ncontent-type: application/grpc\r\n"); + + // need to sync access to data + NetCritInit(&pQosClient->ThreadCrit, "qosclient"); + + // return module state to caller + return(pQosClient); +} + +/*F********************************************************************************/ +/*! + \Function QosClientStart + + \Description + Request that the QOS coordinator provide setting to the client, the client will + then perform tests based off those settings. Results will be provided to game code + via the callback set in QosClientCreate. + + \Input *pQosClient - module ref + \Input *pStrCoordinatorAddr - url of the QOS coordinator + \Input uPort - port of the QOS coordinator + \Input *pStrQosProfile - what profile should the QOS coordinator use for settings, likely a service name like "fifa-pc-20147" + + \Version 11/07/2014 (cvienneau) +*/ +/********************************************************************************F*/ +void QosClientStart(QosClientRefT *pQosClient, const char *pStrCoordinatorAddr, uint16_t uPort, const char *pStrQosProfile) +{ + NetCritEnter(&pQosClient->ThreadCrit); + + //only start if we haven't already got something on the go + if (pQosClient->eModuleState == MODULE_STATE_IDLE) + { + //clear out any existing state + ds_memclr(&pQosClient->finalResults, sizeof(pQosClient->finalResults)); + ds_memclr(&pQosClient->rawResultStore, sizeof(pQosClient->rawResultStore)); + pQosClient->uHttpBytesToSend = 0; + pQosClient->uErrorMessageCount = 0; + pQosClient->uQosProcessStartTime = NetTick(); + + //set info about the client + ds_snzprintf(pQosClient->rawResultStore.strQosProfile, sizeof(pQosClient->rawResultStore.strQosProfile), pStrQosProfile); + pQosClient->rawResultStore.uProbeVersion = QOS_COMMON_PROBE_VERSION; + pQosClient->rawResultStore.clientModifiers.clientAddressInternal = pQosClient->localUdpAddrInfo; + ds_snzprintf(pQosClient->rawResultStore.clientModifiers.strPlatform, sizeof(pQosClient->rawResultStore.clientModifiers.strPlatform), DIRTYCODE_PLATNAME_SHORT); + + //set info about the coordinator + ds_snzprintf(pQosClient->strCoordinatorAddr, sizeof(pQosClient->strCoordinatorAddr), pStrCoordinatorAddr); + pQosClient->uCoordinatorPort = uPort; + + //make use of any control selectors that were set before we start + ProtoHttp2Control(pQosClient->pProtoHttp, pQosClient->iHttpStreamId, 'spam', pQosClient->iSpamValue + 1, 0, NULL); + ProtoHttp2Control(pQosClient->pProtoHttp, pQosClient->iHttpStreamId, 'ncrt', pQosClient->bIgnoreSSLErrors, 0, NULL); + + //start communication with the coordinator + _QosClientModuleStateTransition(pQosClient, MODULE_STATE_INIT_COORDINATOR_COMM, DIRTYAPI_QOS, QOS_CLIENT_MODULE_STATUS_PROCESS_STARTED, 0); + } + else + { + NetPrintf(("qosclient: QosClientStart() skipped because the QosClient is already busy.\n")); + } + NetCritLeave(&pQosClient->ThreadCrit); +} + +/*F********************************************************************************/ +/*! + \Function QosClientControl + + \Description + QosClient control function. Different selectors control different behaviors. + + \Input *pQosClient - module state + \Input iControl - control selector + \Input iValue - selector specific data + \Input pValue - selector specific data + + \Output + int32_t - control specific + + \Notes + iControl can be one of the following: + + \verbatim + 'cbfp' - set callback function pointer - pValue=pCallback + 'lprt' - set the port to use for the QoS listen port (must be set before calling listen/request), same as the value passed to QosClientCreate + 'maxt' - set the amount of ms before we give up on the current qos process + 'ncrt' - passed to ProtoHttpControl, if TRUE will proceed even if ssl cert errors are detected (defaults FALSE) + 'pque' - passed to SocketControl 'pque' if not 0, otherwise the number of packets will be passed to SocketControl 'pque' + 'rbuf' - passed to SocketControl 'rbuf' + 'sbuf' - passed to SocketControl 'sbuf' + 'soli' - passed down to the socket as a SO_LINGER option if its >= 0, a 0 value will abort the socket on disconnection with no TIME_WAIT state + 'spam' - set the verbosity of the module, default 1 (0 errors, 1 debug info, 2 extended debug info, 3 per probe info) + 'stwi' - set the amount of ms before a stall is counted + 'ussl' - if TRUE use https else http for setup communication (defaults TRUE) + + \endverbatim + + \Version 04/07/2008 (cadam) +*/ +/********************************************************************************F*/ +int32_t QosClientControl(QosClientRefT *pQosClient, int32_t iControl, int32_t iValue, void *pValue) +{ + int32_t iRet = 0; + NetPrintfVerbose((pQosClient->iSpamValue, 0, "qosclient: QosClientControl('%C'), (%d)\n", iControl, iValue)); + + NetCritEnter(&pQosClient->ThreadCrit); + + if (iControl == 'cbfp') + { + // set callback function pointer + pQosClient->pCallback = (QosClientCallbackT *)pValue; + } + else if (iControl == 'lprt') + { + pQosClient->uUdpRequestedListenPort = iValue; + } + else if (iControl == 'maxt') + { + pQosClient->uMaxQosProcessTime = iValue; + } + else if (iControl == 'ncrt') + { + pQosClient->bIgnoreSSLErrors = iValue; + } + else if (iControl == 'pque') + { + pQosClient->uUdpPacketQueueSize = iValue; + } + else if (iControl == 'rbuf') + { + pQosClient->uUdpReceiveBuffSize = iValue; + } + else if (iControl == 'sbuf') + { + pQosClient->uUdpSendBuffSize = iValue; + } + else if (iControl == 'soli') + { + pQosClient->iHttpSoLinger = iValue; + } + else if (iControl == 'spam') + { + pQosClient->iSpamValue = iValue; + } + else if (iControl == 'stwi') + { + pQosClient->uStallWindow = iValue; + } + else if (iControl == 'ussl') + { + pQosClient->bUseHttps = iValue; + } + else + { + iRet = -1; + } + + NetCritLeave(&pQosClient->ThreadCrit); + return(iRet); +} + +/*F********************************************************************************/ +/*! + \Function QosClientStatus + + \Description + Return quality of service information. + + \Input *pQosClient - module state + \Input iSelect - output selector + \Input iData - selector specific + \Input *pBuf - [out] pointer to output buffer + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iSelect can be one of the following: + + \verbatim + 'clpt' - get the current listen port of the QoS socket + 'hres' - get the current hResult value for the module + 'lprt' - get the port to use for the QoS listen port + 'maxt' - get the maximum time we will allow the qos process to run + 'ncrt' - if TRUE we will ignore ssl errors for setup communication + 'pque' - get the value passed to SocketControl 'pque' + 'rbuf' - get the value passed to SocketControl 'rbuf' + 'rpro' - get the processed results, the same results that are returned in the callback, pass in a QosCommonProcessedResultsT + 'rraw' - get the raw results, the results that are analyzed by the coordinator, pass in a QosCommonClientToCoordinatorRequestT + 'rrpc' - get the result rpc data, the processed results from the coordinator, only valid during the completion callback. returns length of data, and populates pBuf if a buffer of enough space is provided. + 'sbuf' - get the value passed to SocketControl 'sbuf' + 'soli' - get the value passed to ProtoHttp 'soli' + 'spam' - get the verbosity of the module + 'stwi' - get the duration of time to pass, to consider a updating to have been stalled + 'ussl' - if TRUE use https else http for setup communication + + \endverbatim + + \Version 04/07/2008 (cadam) +*/ +/********************************************************************************F*/ +int32_t QosClientStatus(QosClientRefT *pQosClient, int32_t iSelect, int32_t iData, void *pBuf, int32_t iBufSize) +{ + int32_t iRet = 0; + NetPrintfVerbose((pQosClient->iSpamValue, 1, "qosclient: QosClientStatus('%C'), (%d)\n", iSelect, iData)); + + NetCritEnter(&pQosClient->ThreadCrit); + + if (iSelect == 'clpt') + { + iRet = pQosClient->localUdpAddrInfo.uPort; + } + if (iSelect == 'hres') + { + iRet = pQosClient->rawResultStore.clientModifiers.hResult; + } + else if (iSelect == 'lprt') + { + iRet = pQosClient->uUdpRequestedListenPort; + } + else if (iSelect == 'maxt') + { + iRet = pQosClient->uMaxQosProcessTime; + } + else if (iSelect == 'ncrt') + { + iRet = pQosClient->bIgnoreSSLErrors; + } + else if (iSelect == 'pque') + { + iRet = pQosClient->uUdpPacketQueueSize; + } + else if (iSelect == 'rbuf') + { + iRet = pQosClient->uUdpReceiveBuffSize; + } + else if ((iSelect == 'rpro') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(QosCommonProcessedResultsT))) + { + ds_memcpy(pBuf, &pQosClient->finalResults, sizeof(QosCommonProcessedResultsT)); + iRet = 1; + } + else if ((iSelect == 'rraw') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(QosCommonClientToCoordinatorRequestT))) + { + ds_memcpy(pBuf, &pQosClient->rawResultStore, sizeof(QosCommonClientToCoordinatorRequestT)); + iRet = 1; + } + else if (iSelect == 'rrpc') + { + iRet = 0; + if (pQosClient->pHttpRecvBuff) + { + iRet = (int32_t)pQosClient->uCurrentHttpRecvSize; + if ((pBuf != NULL) && (iBufSize >= iRet)) + { + ds_memcpy(pBuf, pQosClient->pHttpRecvBuff, iRet); + } + } + } + else if (iSelect == 'sbuf') + { + iRet = pQosClient->uUdpSendBuffSize; + } + else if (iSelect == 'soli') + { + iRet = pQosClient->iHttpSoLinger; + } + else if (iSelect == 'spam') + { + iRet = pQosClient->iSpamValue; + } + else if (iSelect == 'stwi') + { + iRet = pQosClient->uStallWindow; + } + else if (iSelect == 'ussl') + { + iRet = pQosClient->bUseHttps; + } + else + { + iRet = -1; + } + + NetCritLeave(&pQosClient->ThreadCrit); + return(iRet); +} + +/*F********************************************************************************/ +/*! + \Function QosClientDestroy + + \Description + Destroy the QosClient module. + + \Input *pQosClient - module state + + \Version 04/07/2008 (cadam) +*/ +/********************************************************************************F*/ +void QosClientDestroy(QosClientRefT *pQosClient) +{ + NetCritEnter(&pQosClient->ThreadCrit); + + if (pQosClient->pUdpSocket != NULL) + { + SocketClose(pQosClient->pUdpSocket); + pQosClient->pUdpSocket = NULL; + } + + if (pQosClient->pProtoHttp != NULL) + { + // close the socket asap when destroyed (necessary for stress testing to not leak resources too long) + if (pQosClient->iHttpSoLinger >= 0) + { + ProtoHttp2Control(pQosClient->pProtoHttp, pQosClient->iHttpStreamId, 'soli', pQosClient->iHttpSoLinger, 0, NULL); + } + ProtoHttp2Destroy(pQosClient->pProtoHttp); + pQosClient->pProtoHttp = NULL; + + _QosClientAllocHttpRecvBuff(pQosClient, 0); + _QosClientAllocHttpSendBuff(pQosClient, 0); + } + + while (pQosClient->pRequestQueue != NULL) + { + _QosClientDestroyRequest(pQosClient, pQosClient->pRequestQueue->uClientRequestID); + } + + NetCritLeave(&pQosClient->ThreadCrit); + NetCritKill(&pQosClient->ThreadCrit); + + // release module memory + DirtyMemFree(pQosClient, QOS_CLIENT_MEMID, pQosClient->iMemGroup, pQosClient->pMemGroupUserData); +} + diff --git a/src/thirdparty/dirtysdk/source/misc/qoscommon.c b/src/thirdparty/dirtysdk/source/misc/qoscommon.c new file mode 100644 index 00000000..aaa94753 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/misc/qoscommon.c @@ -0,0 +1,1335 @@ +/*H********************************************************************************/ +/*! + \File qoscommon.c + + \Description + This code implements shared items between client and server of the QOS system. + + \Copyright + Copyright (c) 2016 Electronic Arts Inc. + +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/misc/qoscommon.h" +#include "DirtySDK/util/binary7.h" +#include "DirtySDK/crypt/crypthash.h" +#include "DirtySDK/crypt/crypthmac.h" + +#include "DirtySDK/util/protobufcommon.h" +#include "DirtySDK/util/protobufwrite.h" +#include "DirtySDK/util/protobufread.h" + +/*** Defines **********************************************************************/ + +//!< Limits put on values when decoding rpcs + +#define QOS_COMMON_MAX_INIT_SYNC_TIME (5000) //!< max time to wait for all the tests to start at the same time, after this time the tests will continue without synchronizing +#define QOS_COMMON_MAX_TIME_BETWEEN_PROBES (1000) //!< time between each probe send for a given test +#define QOS_COMMON_MIN_PROBE_COUNT (1) //!< the min amount of probes used for a given test, note QOS_COMMON_MAX_PROBE_COUNT is defined in qoscommon.h +#define QOS_COMMON_MAX_RESEND_EXTRA_PROBES (QOS_COMMON_MAX_PROBE_COUNT) //!< if we think we lost probes, should we send some extras to reduce the chances of losing some of those too +#define QOS_COMMON_MIN_TIMEOUT (7000) //!< min qosclient request timeout period, the test is considered failed if this much time passed +#define QOS_COMMON_MAX_TIMEOUT (20000) //!< max qosclient request timeout period, the test is considered failed if this much time passed +#define QOS_COMMON_MIN_RESEND_TIME (200) //!< qosclient time till we decide probes have been lost, so send more probes then initially planned +#define QOS_COMMON_MAX_RESEND_TIME (QOS_COMMON_MAX_TIMEOUT) //!< qosclient time till we decide probes have been lost, so send more probes then initially planned +#define QOS_COMMON_MAX_ACCEPTABLE_LOST_PROBES (QOS_COMMON_MAX_PROBE_COUNT) //!< don't bother resend new probes if we've only lost x number of probes +#define QOS_COMMON_RPC_HEADER_SIZE (1) //! one byte is needed to make a grpc header on top of what is done in protobuff +#define QOS_COMMON_ENABLE_HMAC (1) //! be sure that hmac is enabled except for special debug cases + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ +// Note that all of the below enums must be kept in sync with the proto files +// that belong to the coordinator. + +//message ClientRequest +enum +{ + CLIENT_TO_COORDINATOR_REQUEST_ARESULTS = 1, + CLIENT_TO_COORDINATOR_REQUEST_PROFILE, + CLIENT_TO_COORDINATOR_REQUEST_SERVICE_ID, + CLIENT_TO_COORDINATOR_REQUEST_PROBE_VERSION, + CLIENT_TO_COORDINATOR_REQUEST_MODIFIERS +}eClientToCoordinatorRequestFields; + +//message ClientModifierFields +enum +{ + CLIENT_MODIFIER_INTERNAL_ADDRESS = 1, + CLIENT_MODIFIER_PLATFORM, + CLIENT_MODIFIER_OS_FIREWALL, + CLIENT_MODIFIER_PACKET_QUEUE_REMAINING, + CLIENT_MODIFIER_STALL_COUNT_COMM, + CLIENT_MODIFIER_STALL_DURATION_COMM, + CLIENT_MODIFIER_STALL_COUNT_PROBE, + CLIENT_MODIFIER_STALL_DURATION_PROBE, + CLIENT_MODIFIER_HRESULT +}eQosCommonClientModifiersFields; + +//message ClientRawResult +enum +{ + RAW_RESULTS_ARESULT = 1, + RAW_RESULTS_CLIENT_ADDRESS_FROM_SERVER, + RAW_RESULTS_SITE_NAME, + RAW_RESULTS_TEST_NAME, + RAW_RESULTS_HRESULT +}eQosCommonRawResultsFields; + +//message ProbeResult +enum +{ + PROBE_RESULT_CLIENT_SEND_TIME = 1, + PROBE_RESULT_SERVER_RECV_TIME, + PROBE_RESULT_SERVER_SEND_DELTA, + PROBE_RESULT_CLIENT_RECV_DELTA +}eQosCommonProbeResultFields; + +//message ClientResponse +enum +{ + COORDINATOR_TO_CLIENT_RESPONSE_STATUS = 1, + COORDINATOR_TO_CLIENT_RESPONSE_SERVICE_ID, + COORDINATOR_TO_CLIENT_RESPONSE_CLIENT_ADDR_FROM_COORDINATOR, + COORDINATOR_TO_CLIENT_RESPONSE_FIREWALL_TYPE, + COORDINATOR_TO_CLIENT_RESPONSE_TIME_TILL_RETRY, + COORDINATOR_TO_CLIENT_RESPONSE_CLIENT_EXTERNAL_ADDRESS, + COORDINATOR_TO_CLIENT_RESPONSE_APINGSITES, + COORDINATOR_TO_CLIENT_RESPONSE_ACONTROL_CONFIGS, + COORDINATOR_TO_CLIENT_RESPONSE_ATESTS, + COORDINATOR_TO_CLIENT_RESPONSE_ASITE_RESULTS +}eCoordinatorToClientResponseFields; + +//message PingSite +enum +{ + SITE_NAME = 1, + SITE_ADDR, + SITE_PORT, + SITE_KEY, + SITE_PROBE_VERSION +}eQosCommonSiteFields; + +//message ControlConfig +enum +{ + CONTROL_CONFIG_NAME = 1, + CONTROL_CONFIG_I_VALUE, + CONTROL_CONFIG_STR_VALUE +}eQosCommonControlConfigFields; + +//message QosTest +enum +{ + TEST_NAME = 1, + TEST_SITE, + TEST_PROBE_COUNT_UP, + TEST_PROBE_COUNT_DOWN, + TEST_PROBE_SIZE_UP, + TEST_PROBE_SIZE_DOWN, + TEST_TIMEOUT, + TEST_MINTIME_BETWEEN_PROBES, + TEST_TIME_TILL_RESEND, + TEST_RESEND_EXTRA_PROBE_COUNT, + TEST_ACCEPTABLE_PROBE_LOSS, + TEST_INIT_SYNC_TIME +}eQosCommonTestFields; + +//message TestResult +enum +{ + TEST_RESULT_SITE = 1, + TEST_RESULT_RTT, + TEST_RESULT_UP_BPS, + TEST_RESULT_DOWN_BPS, + TEST_RESULT_HRESULT +}eQosCommonTestResultFields; + +//message ServerRegistrationRequest +enum +{ + SERVER_TO_COORDINATOR_REQUEST_SITE = 1, + SERVER_TO_COORDINATOR_REQUEST_ADDR, + SERVER_TO_COORDINATOR_REQUEST_POOL, + SERVER_TO_COORDINATOR_REQUEST_KEY, + SERVER_TO_COORDINATOR_REQUEST_PORT, + SERVER_TO_COORDINATOR_REQUEST_CAPCAITY_SEC, + SERVER_TO_COORDINATOR_REQUEST_LAST_LOAD_SEC, + SERVER_TO_COORDINATOR_REQUEST_PROBE_VERSION, + SERVER_TO_COORDINATOR_REQUEST_UPDATE_INTERVAL, + SERVER_TO_COORDINATOR_REQUEST_SHUTTING_DOWN +}eServerToCoordinatorRequestFields; + +enum +{ + COORDINATOR_TO_SERVER_RESPONSE_STATUS = 1, + COORDINATOR_TO_SERVER_RESPONSE_REGISTRATION_MESSAGE, + COORDINATOR_TO_SERVER_RESPONSE_MIN_SERVICE_ID +}eCoordinatorToServerResponseFields; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _QosCommonScrubProbeResults + + \Description + Time values are kind of meaningless outside of the box they are generated on + here we try to make them more readable and take less space by trimming them to + lower values. + + \Input *pIn - input values + \Input *pOut - [out] output values + \Input uHightWater - max array index used + + \Version 08/02/2017 (cvienneau) +*/ +/********************************************************************************F*/ +static void _QosCommonScrubProbeResults(const QosCommonProbeResultT* pIn, QosCommonProbeResultT* pOut, uint32_t uHightWater) +{ + //scan data for lowest client side time and lowest server side time + uint32_t uMinClientTime = (uint32_t)-1; + uint32_t uMinServerTime = (uint32_t)-1; + uint32_t uProbeIndex; + for (uProbeIndex = 0; uProbeIndex <= uHightWater; uProbeIndex++) + { + if ((pIn[uProbeIndex].uClientSendTime != 0) && (pIn[uProbeIndex].uClientSendTime < uMinClientTime)) + { + uMinClientTime = pIn[uProbeIndex].uClientSendTime; + } + + if ((pIn[uProbeIndex].uServerReceiveTime != 0) && (pIn[uProbeIndex].uServerReceiveTime < uMinServerTime)) + { + uMinServerTime = pIn[uProbeIndex].uServerReceiveTime; + } + } + + //zeros are used when probes are missing, so we want to use 1 instead. + uMinClientTime--; + uMinServerTime--; + + //pass over data again to reduce values + for (uProbeIndex = 0; uProbeIndex <= uHightWater; uProbeIndex++) + { + if (pIn[uProbeIndex].uClientSendTime != 0) + { + pOut[uProbeIndex].uClientSendTime = pIn[uProbeIndex].uClientSendTime - uMinClientTime; + } + else + { + pOut[uProbeIndex].uClientSendTime = UINT32_MAX; + } + + if (pIn[uProbeIndex].uServerReceiveTime != 0) + { + pOut[uProbeIndex].uServerReceiveTime = pIn[uProbeIndex].uServerReceiveTime - uMinServerTime; + //copy the delta values out of the original array deltas don't need re-basing + pOut[uProbeIndex].uClientReceiveDelta = pIn[uProbeIndex].uClientReceiveDelta; + pOut[uProbeIndex].uServerSendDelta = pIn[uProbeIndex].uServerSendDelta; + } + else + { + //if the server didn't receive the probe put error sentinel values in for everything + pOut[uProbeIndex].uServerReceiveTime = UINT32_MAX; + pOut[uProbeIndex].uClientReceiveDelta = UINT16_MAX; + pOut[uProbeIndex].uServerSendDelta = UINT16_MAX; + } + + } +} + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function QosCommonClientToCoordinatorEstimateEncodedSize + + \Description + Estimate how much space will be needed to encode the QosCommonClientToCoordinatorRequestT. + + \Input *pClientToCoordinatorRequest - structure to read values from + + \Output + int32_t - negative on error or, size on success + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +int32_t QosCommonClientToCoordinatorEstimateEncodedSize(const QosCommonClientToCoordinatorRequestT *pClientToCoordinatorRequest) +{ + // the basic introduction message with no results fits under 200 bytes, lets say 1k as a min anyways + int32_t iEstimatedSize = 1024; + // my own local test shows ~600 bytes for 3 results, approximately 200 bytes per result, but if the tests used more probes (i used 10) + // i could see this easily be 600b, lets just say 1k per result for plenty of wiggle room + iEstimatedSize = iEstimatedSize + (1024 * pClientToCoordinatorRequest->uNumResults); + + return(iEstimatedSize); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonClientToCoordinatorRequestEncode + + \Description + Encode the QosCommonClientToCoordinatorRequestT into a buffer, which + gives the coordinator information on who the client is and what test + results it might already have. + + \Input *pClientToCoordinatorRequest - structure to read values from + \Input *pBuffer - [out] buffer we are writing to + \Input uBuffSize - max size of buffer + \Input *pOutSize - [out] output size of encoded data + + \Output + uint8_t* - NULL on error or, pointer to filled buffer on success + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t* QosCommonClientToCoordinatorRequestEncode(const QosCommonClientToCoordinatorRequestT *pClientToCoordinatorRequest, uint8_t *pBuffer, uint32_t uBuffSize, uint32_t *pOutSize) +{ + char strTemp[QOS_COMMON_MAX_RPC_STRING]; + uint32_t uIndex, uIndexProbe; + int32_t iError = 0; + + ds_memclr(pBuffer, QOS_COMMON_RPC_HEADER_SIZE); + ProtobufWriteRefT *pEncoder = ProtobufWriteCreate(pBuffer + QOS_COMMON_RPC_HEADER_SIZE, uBuffSize - QOS_COMMON_RPC_HEADER_SIZE, TRUE); + + if (pEncoder) + { + iError |= ProtobufWriteString(pEncoder, pClientToCoordinatorRequest->strQosProfile, (int32_t)strlen(pClientToCoordinatorRequest->strQosProfile), CLIENT_TO_COORDINATOR_REQUEST_PROFILE); + iError |= ProtobufWriteVarint(pEncoder, pClientToCoordinatorRequest->uServiceRequestID, CLIENT_TO_COORDINATOR_REQUEST_SERVICE_ID); + iError |= ProtobufWriteVarint(pEncoder, pClientToCoordinatorRequest->uProbeVersion, CLIENT_TO_COORDINATOR_REQUEST_PROBE_VERSION); + + iError |= ProtobufWriteMessageBegin(pEncoder, CLIENT_TO_COORDINATOR_REQUEST_MODIFIERS); + QosCommonAddrToString(&pClientToCoordinatorRequest->clientModifiers.clientAddressInternal, strTemp, sizeof(strTemp)); + iError |= ProtobufWriteString(pEncoder, strTemp, (int32_t)strlen(strTemp), CLIENT_MODIFIER_INTERNAL_ADDRESS); + iError |= ProtobufWriteString(pEncoder, pClientToCoordinatorRequest->clientModifiers.strPlatform, (int32_t)strlen(pClientToCoordinatorRequest->clientModifiers.strPlatform), CLIENT_MODIFIER_PLATFORM); + iError |= ProtobufWriteVarint(pEncoder, pClientToCoordinatorRequest->clientModifiers.eOSFirewallType, CLIENT_MODIFIER_OS_FIREWALL); + iError |= ProtobufWriteVarint(pEncoder, pClientToCoordinatorRequest->clientModifiers.uPacketQueueRemaining, CLIENT_MODIFIER_PACKET_QUEUE_REMAINING); + iError |= ProtobufWriteVarint(pEncoder, pClientToCoordinatorRequest->clientModifiers.uStallCountCoordinator, CLIENT_MODIFIER_STALL_COUNT_COMM); + iError |= ProtobufWriteVarint(pEncoder, pClientToCoordinatorRequest->clientModifiers.uStallCountProbe, CLIENT_MODIFIER_STALL_COUNT_PROBE); + iError |= ProtobufWriteVarint(pEncoder, pClientToCoordinatorRequest->clientModifiers.uStallDurationCoordinator, CLIENT_MODIFIER_STALL_DURATION_COMM); + iError |= ProtobufWriteVarint(pEncoder, pClientToCoordinatorRequest->clientModifiers.uStallDurationProbe, CLIENT_MODIFIER_STALL_DURATION_PROBE); + iError |= ProtobufWriteVarint(pEncoder, pClientToCoordinatorRequest->clientModifiers.hResult, CLIENT_MODIFIER_HRESULT); + iError |= ProtobufWriteMessageEnd(pEncoder); //end of CLIENT_TO_COORDINATOR_REQUEST_MODIFIERS + + // add all current results + if ((iError == 0) && (pClientToCoordinatorRequest->uNumResults > 0)) + { + for (uIndex = 0; uIndex < pClientToCoordinatorRequest->uNumResults; uIndex++) + { + iError |= ProtobufWriteMessageBegin(pEncoder, CLIENT_TO_COORDINATOR_REQUEST_ARESULTS); + iError |= ProtobufWriteVarint(pEncoder, pClientToCoordinatorRequest->aResults[uIndex].hResult, RAW_RESULTS_HRESULT); + iError |= ProtobufWriteString(pEncoder, pClientToCoordinatorRequest->aResults[uIndex].strSiteName, (int32_t)strlen(pClientToCoordinatorRequest->aResults[uIndex].strSiteName), RAW_RESULTS_SITE_NAME); + iError |= ProtobufWriteString(pEncoder, pClientToCoordinatorRequest->aResults[uIndex].strTestName, (int32_t)strlen(pClientToCoordinatorRequest->aResults[uIndex].strTestName), RAW_RESULTS_TEST_NAME); + QosCommonAddrToString(&pClientToCoordinatorRequest->aResults[uIndex].clientAddressFromServer, strTemp, sizeof(strTemp)); + iError |= ProtobufWriteString(pEncoder, strTemp, (int32_t)strlen(strTemp), RAW_RESULTS_CLIENT_ADDRESS_FROM_SERVER); + + if ((iError == 0) && (pClientToCoordinatorRequest->aResults[uIndex].uProbeCountDown > 0)) + { + //make an array the same size as the one contained in the struct + QosCommonProbeResultT aScrubedProbeResults[(sizeof(pClientToCoordinatorRequest->aResults[uIndex].aProbeResult) / sizeof(pClientToCoordinatorRequest->aResults[uIndex].aProbeResult[0]))]; + + //lower the values used to something more human readable + _QosCommonScrubProbeResults(pClientToCoordinatorRequest->aResults[uIndex].aProbeResult, aScrubedProbeResults, pClientToCoordinatorRequest->aResults[uIndex].uProbeCountHighWater); + // info on individual probes + for (uIndexProbe = 0; uIndexProbe <= pClientToCoordinatorRequest->aResults[uIndex].uProbeCountHighWater; uIndexProbe++) //this list may have spaces if some probes were not received + { + iError |= ProtobufWriteMessageBegin(pEncoder, RAW_RESULTS_ARESULT); + iError |= ProtobufWriteVarint(pEncoder, aScrubedProbeResults[uIndexProbe].uClientSendTime, PROBE_RESULT_CLIENT_SEND_TIME); + iError |= ProtobufWriteVarint(pEncoder, aScrubedProbeResults[uIndexProbe].uServerReceiveTime, PROBE_RESULT_SERVER_RECV_TIME); + iError |= ProtobufWriteVarint(pEncoder, aScrubedProbeResults[uIndexProbe].uServerSendDelta, PROBE_RESULT_SERVER_SEND_DELTA); + iError |= ProtobufWriteVarint(pEncoder, aScrubedProbeResults[uIndexProbe].uClientReceiveDelta, PROBE_RESULT_CLIENT_RECV_DELTA); + iError |= ProtobufWriteMessageEnd(pEncoder); //end of probe result + } + } + iError |= ProtobufWriteMessageEnd(pEncoder); //end of result + } + } + *pOutSize = ProtobufWriteDestroy(pEncoder) + QOS_COMMON_RPC_HEADER_SIZE; + } + else + { + iError |= -1; + *pOutSize = 0; + } + return((iError == 0) ? pBuffer : NULL); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonClientToCoordinatorPrint + + \Description + Print the QosCommonClientToCoordinatorRequestT to tty. + + \Input *pClientToCoordinatorRequest - structure to print + \Input uLogLevel - log level to control spam + + \Version 03/03/2018 (cvienneau) +*/ +/********************************************************************************F*/ +void QosCommonClientToCoordinatorPrint(const QosCommonClientToCoordinatorRequestT *pClientToCoordinatorRequest, uint32_t uLogLevel) +{ +#if DIRTYCODE_LOGGING + char strTemp[QOS_COMMON_MAX_RPC_STRING]; + uint32_t uIndex, uIndexProbe; + + NetPrintfVerbose((uLogLevel, 0, "qoscommon: cl2co.strQosProfile: %s\n", pClientToCoordinatorRequest->strQosProfile)); + NetPrintfVerbose((uLogLevel, 0, "qoscommon: cl2co.uServiceRequestID: %u\n", pClientToCoordinatorRequest->uServiceRequestID)); + NetPrintfVerbose((uLogLevel, 1, "qoscommon: cl2co.uProbeVersion: %u\n", pClientToCoordinatorRequest->uProbeVersion)); + + QosCommonAddrToString(&pClientToCoordinatorRequest->clientModifiers.clientAddressInternal, strTemp, sizeof(strTemp)); + NetPrintfVerbose((uLogLevel, 0, "qoscommon: cl2co.cm.clientAddressInternal: %s\n", strTemp)); + NetPrintfVerbose((uLogLevel, 1, "qoscommon: cl2co.cm.strPlatform: %s\n", pClientToCoordinatorRequest->clientModifiers.strPlatform)); + NetPrintfVerbose((uLogLevel, 0, "qoscommon: cl2co.cm.eOSFirewallType: %u\n", pClientToCoordinatorRequest->clientModifiers.eOSFirewallType)); + NetPrintfVerbose((uLogLevel, 0, "qoscommon: cl2co.cm.uPacketQueueRemaining: %u\n", pClientToCoordinatorRequest->clientModifiers.uPacketQueueRemaining)); + NetPrintfVerbose((uLogLevel, 1, "qoscommon: cl2co.cm.uStallCountCoordinator: %u\n", pClientToCoordinatorRequest->clientModifiers.uStallCountCoordinator)); + NetPrintfVerbose((uLogLevel, 1, "qoscommon: cl2co.cm.uStallCountProbe: %u\n", pClientToCoordinatorRequest->clientModifiers.uStallCountProbe)); + NetPrintfVerbose((uLogLevel, 0, "qoscommon: cl2co.cm.uStallDurationCoordinator: %u\n", pClientToCoordinatorRequest->clientModifiers.uStallDurationCoordinator)); + NetPrintfVerbose((uLogLevel, 0, "qoscommon: cl2co.cm.uStallDurationProbe: %u\n", pClientToCoordinatorRequest->clientModifiers.uStallDurationProbe)); + NetPrintfVerbose((uLogLevel, 0, "qoscommon: cl2co.cm.hResult: 0x%08x\n", pClientToCoordinatorRequest->clientModifiers.hResult)); + + // add all current results + for (uIndex = 0; uIndex < pClientToCoordinatorRequest->uNumResults; uIndex++) + { + NetPrintfVerbose((uLogLevel, 0, "qoscommon: cl2co.aResults[%u] site: %s, test: %s, hresult: 0x%08x, up: %u, down: %u, high: %u, addr: %s\n", + uIndex, + pClientToCoordinatorRequest->aResults[uIndex].strSiteName, + pClientToCoordinatorRequest->aResults[uIndex].strTestName, + pClientToCoordinatorRequest->aResults[uIndex].hResult, + pClientToCoordinatorRequest->aResults[uIndex].uProbeCountUp, + pClientToCoordinatorRequest->aResults[uIndex].uProbeCountDown, + pClientToCoordinatorRequest->aResults[uIndex].uProbeCountHighWater, + QosCommonAddrToString(&pClientToCoordinatorRequest->aResults[uIndex].clientAddressFromServer, strTemp, sizeof(strTemp)) + )); + + if (pClientToCoordinatorRequest->aResults[uIndex].uProbeCountDown > 0) + { + //make an array the same size as the one contained in the struct + QosCommonProbeResultT aScrubedProbeResults[(sizeof(pClientToCoordinatorRequest->aResults[uIndex].aProbeResult) / sizeof(pClientToCoordinatorRequest->aResults[uIndex].aProbeResult[0]))]; + + //lower the values used to something more human readable + _QosCommonScrubProbeResults(pClientToCoordinatorRequest->aResults[uIndex].aProbeResult, aScrubedProbeResults, pClientToCoordinatorRequest->aResults[uIndex].uProbeCountHighWater); + // info on individual probes + for (uIndexProbe = 0; uIndexProbe <= pClientToCoordinatorRequest->aResults[uIndex].uProbeCountHighWater; uIndexProbe++) //this list may have spaces if some probes were not received + { + // only print the first probe, unless we have higher logging level set. + int32_t uLocalLogLevel = 0; + if (uIndexProbe > 0) + { + uLocalLogLevel = 1; + } + NetPrintfVerbose((uLogLevel, uLocalLogLevel, "qoscommon: cl2co.aResults[%d].aProbeResult[%d] csend: %u, srcv: %u, sdelta: %u, cdelta: %u\n", uIndex, uIndexProbe, + aScrubedProbeResults[uIndexProbe].uClientSendTime, + aScrubedProbeResults[uIndexProbe].uServerReceiveTime, + aScrubedProbeResults[uIndexProbe].uServerSendDelta, + aScrubedProbeResults[uIndexProbe].uClientReceiveDelta)); + } + } + } +#endif +} + + +/*F********************************************************************************/ +/*! + \Function QosCommonCoordinatorToClientResponseDecode + + \Description + Decodes the buffer, into a QosCommonCoordinatorToClientResponseT, which + gives the client instructions on what it should be doing next. + + \Input *pResponse - [out] structure to write values to + \Input *pBuffer - buffer we are reading from + \Input uBuffSize - max size of pBuffer + + \Output + int32_t - -1 on error 0 otherwise + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +int32_t QosCommonCoordinatorToClientResponseDecode(QosCommonCoordinatorToClientResponseT *pResponse, const uint8_t *pBuffer, uint32_t uBuffSize) +{ + int32_t iMsgSize; + uint8_t bError = FALSE; + + ds_memclr(pResponse, sizeof(QosCommonCoordinatorToClientResponseT)); + pBuffer = ProtobufCommonReadSize(pBuffer + QOS_COMMON_RPC_HEADER_SIZE, uBuffSize - QOS_COMMON_RPC_HEADER_SIZE, &iMsgSize); + + if (pBuffer) + { + ProtobufReadT Reader, ReaderValue; + const uint8_t *pCurrentRepeat; + char strTempAddr[QOS_COMMON_MAX_RPC_STRING]; + ProtobufReadInit(&Reader, pBuffer, iMsgSize); + + pResponse->uServiceRequestID = (uint32_t)ProtobufReadVarint(&Reader, ProtobufReadFind(&Reader, COORDINATOR_TO_CLIENT_RESPONSE_SERVICE_ID)); + ProtobufReadString(&Reader, ProtobufReadFind(&Reader, COORDINATOR_TO_CLIENT_RESPONSE_CLIENT_ADDR_FROM_COORDINATOR), strTempAddr, sizeof(strTempAddr)); + QosCommonStringToAddr(strTempAddr, &pResponse->configuration.clientAddressFromCoordinator); + + //most will only be valid if results have been calculated, but we don't know what magic the coordinator might use + pResponse->results.eFirewallType = (QosCommonFirewallTypeE)ProtobufReadVarint(&Reader, ProtobufReadFind(&Reader, COORDINATOR_TO_CLIENT_RESPONSE_FIREWALL_TYPE)); + pResponse->results.uTimeTillRetry = (uint32_t)ProtobufReadVarint(&Reader, ProtobufReadFind(&Reader, COORDINATOR_TO_CLIENT_RESPONSE_TIME_TILL_RETRY)); + ProtobufReadString(&Reader, ProtobufReadFind(&Reader, COORDINATOR_TO_CLIENT_RESPONSE_CLIENT_EXTERNAL_ADDRESS), strTempAddr, sizeof(strTempAddr)); + QosCommonStringToAddr(strTempAddr, &pResponse->results.clientExternalAddress); + + pResponse->configuration.uNumSites = 0; + pCurrentRepeat = NULL; + while ((pCurrentRepeat = ProtobufReadFind2(&Reader, COORDINATOR_TO_CLIENT_RESPONSE_APINGSITES, pCurrentRepeat)) != NULL) + { + if (pResponse->configuration.uNumSites >= QOS_COMMON_MAX_SITES) + { + bError = TRUE; + break; + } + pCurrentRepeat = ProtobufReadMessage(&Reader, pCurrentRepeat, &ReaderValue); + ProtobufReadString(&ReaderValue, ProtobufReadFind(&ReaderValue, SITE_NAME), pResponse->configuration.aSites[pResponse->configuration.uNumSites].strSiteName, sizeof(pResponse->configuration.aSites[pResponse->configuration.uNumSites].strSiteName)); + ProtobufReadString(&ReaderValue, ProtobufReadFind(&ReaderValue, SITE_ADDR), pResponse->configuration.aSites[pResponse->configuration.uNumSites].strProbeAddr, sizeof(pResponse->configuration.aSites[pResponse->configuration.uNumSites].strProbeAddr)); + ProtobufReadBytes(&ReaderValue, ProtobufReadFind(&ReaderValue, SITE_KEY), pResponse->configuration.aSites[pResponse->configuration.uNumSites].aSecureKey, sizeof(pResponse->configuration.aSites[pResponse->configuration.uNumSites].aSecureKey)); + pResponse->configuration.aSites[pResponse->configuration.uNumSites].uProbePort = ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, SITE_PORT)); + pResponse->configuration.aSites[pResponse->configuration.uNumSites].uProbeVersion = ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, SITE_PROBE_VERSION)); + pResponse->configuration.uNumSites++; + } + + pResponse->configuration.uNumControlConfigs = 0; + pCurrentRepeat = NULL; + while ((pCurrentRepeat = ProtobufReadFind2(&Reader, COORDINATOR_TO_CLIENT_RESPONSE_ACONTROL_CONFIGS, pCurrentRepeat)) != NULL) + { + if (pResponse->configuration.uNumControlConfigs >= QOS_COMMON_MAX_CONTROL_CONFIGS) + { + bError = TRUE; + break; + } + pCurrentRepeat = ProtobufReadMessage(&Reader, pCurrentRepeat, &ReaderValue); + ProtobufReadString(&ReaderValue, ProtobufReadFind(&ReaderValue, CONTROL_CONFIG_NAME), pResponse->configuration.aControlConfigs[pResponse->configuration.uNumControlConfigs].strControl, sizeof(pResponse->configuration.aControlConfigs[pResponse->configuration.uNumControlConfigs].strControl)); + pResponse->configuration.aControlConfigs[pResponse->configuration.uNumControlConfigs].iValue = (int32_t)ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, CONTROL_CONFIG_I_VALUE)); + ProtobufReadString(&ReaderValue, ProtobufReadFind(&ReaderValue, CONTROL_CONFIG_STR_VALUE), pResponse->configuration.aControlConfigs[pResponse->configuration.uNumControlConfigs].strValue, sizeof(pResponse->configuration.aControlConfigs[pResponse->configuration.uNumControlConfigs].strValue)); + pResponse->configuration.uNumControlConfigs++; + } + + pResponse->configuration.uNumQosTests = 0; + pCurrentRepeat = NULL; + while ((pCurrentRepeat = ProtobufReadFind2(&Reader, COORDINATOR_TO_CLIENT_RESPONSE_ATESTS, pCurrentRepeat)) != NULL) + { + if (pResponse->configuration.uNumQosTests >= QOS_COMMON_MAX_TESTS) + { + bError = TRUE; + break; + } + pCurrentRepeat = ProtobufReadMessage(&Reader, pCurrentRepeat, &ReaderValue); + ProtobufReadString(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_NAME), pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].strTestName, sizeof(pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].strTestName)); + ProtobufReadString(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_SITE), pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].strSiteName, sizeof(pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].strSiteName)); + pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].uProbeCountUp = DS_CLAMP(ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_PROBE_COUNT_UP)), QOS_COMMON_MIN_PROBE_COUNT, QOS_COMMON_MAX_PROBE_COUNT); + pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].uProbeCountDown = DS_CLAMP(ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_PROBE_COUNT_DOWN)), QOS_COMMON_MIN_PROBE_COUNT, QOS_COMMON_MAX_PROBE_COUNT); + pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].uProbeSizeUp = DS_CLAMP(ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_PROBE_SIZE_UP)), QOS_COMMON_MIN_PACKET_SIZE, QOS_COMMON_MAX_PACKET_SIZE); + pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].uProbeSizeDown = DS_CLAMP(ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_PROBE_SIZE_DOWN)), QOS_COMMON_MIN_PACKET_SIZE, QOS_COMMON_MAX_PACKET_SIZE); + pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].uTimeout = DS_CLAMP(ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_TIMEOUT)), QOS_COMMON_MIN_TIMEOUT, QOS_COMMON_MAX_TIMEOUT); + pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].uMinTimeBetwenProbes = DS_MIN(ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_MINTIME_BETWEEN_PROBES)), QOS_COMMON_MAX_TIME_BETWEEN_PROBES); + pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].uTimeTillResend = DS_CLAMP(ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_TIME_TILL_RESEND)), QOS_COMMON_MIN_RESEND_TIME, QOS_COMMON_MAX_RESEND_TIME); + pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].uResendExtraProbeCount = DS_MIN(ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_RESEND_EXTRA_PROBE_COUNT)), QOS_COMMON_MAX_RESEND_EXTRA_PROBES); + pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].uAcceptableLostProbeCount = DS_MIN(ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_ACCEPTABLE_PROBE_LOSS)), QOS_COMMON_MAX_ACCEPTABLE_LOST_PROBES); + pResponse->configuration.aQosTests[pResponse->configuration.uNumQosTests].uInitSyncTimeout = DS_MIN(ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_INIT_SYNC_TIME)), QOS_COMMON_MAX_INIT_SYNC_TIME); + pResponse->configuration.uNumQosTests++; + } + + pResponse->results.uNumResults = 0; + pCurrentRepeat = NULL; + while ((pCurrentRepeat = ProtobufReadFind2(&Reader, COORDINATOR_TO_CLIENT_RESPONSE_ASITE_RESULTS, pCurrentRepeat)) != NULL) + { + if (pResponse->results.uNumResults >= QOS_COMMON_MAX_SITES) + { + bError = TRUE; + break; + } + pCurrentRepeat = ProtobufReadMessage(&Reader, pCurrentRepeat, &ReaderValue); + ProtobufReadString(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_RESULT_SITE), pResponse->results.aTestResults[pResponse->results.uNumResults].strSiteName, sizeof(pResponse->results.aTestResults[pResponse->results.uNumResults].strSiteName)); + pResponse->results.aTestResults[pResponse->results.uNumResults].uMinRTT = (uint32_t)ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_RESULT_RTT)); + pResponse->results.aTestResults[pResponse->results.uNumResults].uUpbps = (uint32_t)ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_RESULT_UP_BPS)); + pResponse->results.aTestResults[pResponse->results.uNumResults].uDownbps = (uint32_t)ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_RESULT_DOWN_BPS)); + pResponse->results.aTestResults[pResponse->results.uNumResults].hResult = (uint32_t)ProtobufReadVarint(&ReaderValue, ProtobufReadFind(&ReaderValue, TEST_RESULT_HRESULT)); + pResponse->results.uNumResults++; + } + } + else + { + bError = TRUE; + } + + return(bError ? -1 : 0); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonCoordinatorToClientPrint + + \Description + Print the QosCommonCoordinatorToClientResponseT to tty. + + \Input *pResponse - structure to print + \Input uLogLevel - log level to control spam + + \Version 03/03/2018 (cvienneau) +*/ +/********************************************************************************F*/ +void QosCommonCoordinatorToClientPrint(const QosCommonCoordinatorToClientResponseT *pResponse, uint32_t uLogLevel) +{ +#if DIRTYCODE_LOGGING + char strTemp[QOS_COMMON_MAX_RPC_STRING]; + uint32_t uIndex; + + //todo set proper log levels of these prints + + NetPrintfVerbose((uLogLevel, 0, "qoscommon: co2cl.uServiceRequestID: %u\n", pResponse->uServiceRequestID)); + NetPrintfVerbose((uLogLevel, 0, "qoscommon: co2cl.configuration.clientAddressFromCoordinator: %s\n", QosCommonAddrToString(&pResponse->configuration.clientAddressFromCoordinator, strTemp, sizeof(strTemp)))); + NetPrintfVerbose((uLogLevel, 1, "qoscommon: co2cl.results.eFirewallType: %u\n", pResponse->results.eFirewallType)); + NetPrintfVerbose((uLogLevel, 0, "qoscommon: co2cl.results.uTimeTillRetry: %u\n", pResponse->results.uTimeTillRetry)); + NetPrintfVerbose((uLogLevel, 1, "qoscommon: co2cl.results.clientExternalAddress: %s\n", QosCommonAddrToString(&pResponse->results.clientExternalAddress, strTemp, sizeof(strTemp)))); + + for (uIndex = 0; uIndex < pResponse->configuration.uNumSites; uIndex++) + { + NetPrintfVerbose((uLogLevel, 0, "qoscommon: co2cl.configuration.aSites[%d] %s, %s:%u, %u\n", + uIndex, + pResponse->configuration.aSites[uIndex].strSiteName, + pResponse->configuration.aSites[uIndex].strProbeAddr, + pResponse->configuration.aSites[uIndex].uProbePort, + pResponse->configuration.aSites[uIndex].uProbeVersion)); + //NetPrintfVerbose((uLogLevel, 0, "qoscommon: co2cl.configuration.aSites[%d].aSecureKey: X\n", pResponse->configuration.aSites[uIndex].aSecureKey)); + } + + for (uIndex = 0; uIndex < pResponse->configuration.uNumControlConfigs; uIndex++) + { + NetPrintfVerbose((uLogLevel, 0, "qoscommon: co2cl.configuration.aControlConfigs[%u]: '%s', %d, %s\n", + uIndex, + pResponse->configuration.aControlConfigs[uIndex].strControl, + pResponse->configuration.aControlConfigs[uIndex].iValue, + pResponse->configuration.aControlConfigs[uIndex].strValue)); + } + + for (uIndex = 0; uIndex < pResponse->configuration.uNumQosTests; uIndex++) + { + NetPrintfVerbose((uLogLevel, 0, "qoscommon: co2cl.configuration.aQosTests[%u]: %s, %s, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u\n", + uIndex, + pResponse->configuration.aQosTests[uIndex].strTestName, + pResponse->configuration.aQosTests[uIndex].strSiteName, + pResponse->configuration.aQosTests[uIndex].uProbeCountUp, + pResponse->configuration.aQosTests[uIndex].uProbeCountDown, + pResponse->configuration.aQosTests[uIndex].uProbeSizeUp, + pResponse->configuration.aQosTests[uIndex].uProbeSizeDown, + pResponse->configuration.aQosTests[uIndex].uTimeout, + pResponse->configuration.aQosTests[uIndex].uMinTimeBetwenProbes, + pResponse->configuration.aQosTests[uIndex].uTimeTillResend, + pResponse->configuration.aQosTests[uIndex].uResendExtraProbeCount, + pResponse->configuration.aQosTests[uIndex].uAcceptableLostProbeCount, + pResponse->configuration.aQosTests[uIndex].uInitSyncTimeout)); + } + + for (uIndex = 0; uIndex < pResponse->results.uNumResults; uIndex++) + { + NetPrintfVerbose((uLogLevel, 1, "qoscommon: co2cl.results.aTestResults[%d]: %s, %u, %u, %u, 0x%08x\n", + uIndex, + pResponse->results.aTestResults[uIndex].strSiteName, + pResponse->results.aTestResults[uIndex].uMinRTT, + pResponse->results.aTestResults[uIndex].uUpbps, + pResponse->results.aTestResults[uIndex].uDownbps, + pResponse->results.aTestResults[uIndex].hResult)); + } +#endif +} + + +/*F********************************************************************************/ +/*! + \Function QosCommonServerToCoordinatorRequestEncode + + \Description + Encode QosCommonServerToCoordinatorRequestT into a buffer, which either + tells the coordinator we are a new server to be added to the available pool + or as a heartbeat and update of the secure key. + + \Input *pServerRegistrationRequest - structure to read values from + \Input *pBuffer - [out] buffer we are writing to + \Input uBuffSize - size of buffer + \Input *pOutSize - [out] output size of encoded data + + \Output + uint8_t* - NULL on error, pointer to filled buffer on success + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t* QosCommonServerToCoordinatorRequestEncode(const QosCommonServerToCoordinatorRequestT *pServerRegistrationRequest, uint8_t *pBuffer, uint32_t uBuffSize, uint32_t *pOutSize) +{ + int32_t iError = 0; + + //first byte is grpc header + ds_memclr(pBuffer, QOS_COMMON_RPC_HEADER_SIZE); + ProtobufWriteRefT *pEncoder = ProtobufWriteCreate(pBuffer + QOS_COMMON_RPC_HEADER_SIZE, uBuffSize - QOS_COMMON_RPC_HEADER_SIZE, TRUE); + + if (pEncoder) + { + iError |= ProtobufWriteString(pEncoder, pServerRegistrationRequest->strSiteName, (int32_t)strlen(pServerRegistrationRequest->strSiteName), SERVER_TO_COORDINATOR_REQUEST_SITE); + iError |= ProtobufWriteString(pEncoder, pServerRegistrationRequest->strPool, (int32_t)strlen(pServerRegistrationRequest->strPool), SERVER_TO_COORDINATOR_REQUEST_POOL); + iError |= ProtobufWriteString(pEncoder, pServerRegistrationRequest->strAddr, (int32_t)strlen(pServerRegistrationRequest->strAddr), SERVER_TO_COORDINATOR_REQUEST_ADDR); + iError |= ProtobufWriteBytes(pEncoder, pServerRegistrationRequest->aSecureKey, sizeof(pServerRegistrationRequest->aSecureKey), SERVER_TO_COORDINATOR_REQUEST_KEY); + iError |= ProtobufWriteVarint(pEncoder, pServerRegistrationRequest->uPort, SERVER_TO_COORDINATOR_REQUEST_PORT); + iError |= ProtobufWriteVarint(pEncoder, pServerRegistrationRequest->uCapacityPerSec, SERVER_TO_COORDINATOR_REQUEST_CAPCAITY_SEC); + iError |= ProtobufWriteVarint(pEncoder, pServerRegistrationRequest->uLastLoadPerSec, SERVER_TO_COORDINATOR_REQUEST_LAST_LOAD_SEC); + iError |= ProtobufWriteVarint(pEncoder, pServerRegistrationRequest->uProbeVersion, SERVER_TO_COORDINATOR_REQUEST_PROBE_VERSION); + iError |= ProtobufWriteVarint(pEncoder, pServerRegistrationRequest->uUpdateInterval, SERVER_TO_COORDINATOR_REQUEST_UPDATE_INTERVAL); + iError |= ProtobufWriteVarint(pEncoder, pServerRegistrationRequest->bShuttingDown, SERVER_TO_COORDINATOR_REQUEST_SHUTTING_DOWN); + *pOutSize = ProtobufWriteDestroy(pEncoder) + QOS_COMMON_RPC_HEADER_SIZE; + } + else + { + iError |= -1; + *pOutSize = 0; + } + return((iError == 0) ? pBuffer : NULL); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonCoordinatorToServerResponseDecode + + \Description + Decode buffer into a QosCommonCoordinatorToServerResponseT, which + tells the server any registration status. + + \Input *pServerRegistrationResponse - [out] structure to write values to + \Input *pBuffer - buffer we are reading from + \Input uBuffSize - size of buffer + + \Output + int32_t - -1 on err, 0 on success + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +int32_t QosCommonCoordinatorToServerResponseDecode(QosCommonCoordinatorToServerResponseT *pServerRegistrationResponse, const uint8_t *pBuffer, uint32_t uBuffSize) +{ + int32_t iMsgSize; + uint8_t bError = FALSE; + + ds_memclr(pServerRegistrationResponse, sizeof(QosCommonCoordinatorToServerResponseT)); + pBuffer = ProtobufCommonReadSize(pBuffer + QOS_COMMON_RPC_HEADER_SIZE, uBuffSize - QOS_COMMON_RPC_HEADER_SIZE, &iMsgSize); + + if (pBuffer) + { + ProtobufReadT Reader; + ProtobufReadInit(&Reader, pBuffer, iMsgSize); + //note we don't do anything with COORDINATOR_TO_SERVER_RESPONSE_STATUS + ProtobufReadString(&Reader, ProtobufReadFind(&Reader, COORDINATOR_TO_SERVER_RESPONSE_REGISTRATION_MESSAGE), pServerRegistrationResponse->strRegistrationMessage, sizeof(pServerRegistrationResponse->strRegistrationMessage)); + pServerRegistrationResponse->uMinServiceRequestID = (uint32_t)ProtobufReadVarint(&Reader, ProtobufReadFind(&Reader, COORDINATOR_TO_SERVER_RESPONSE_MIN_SERVICE_ID)); + } + else + { + bError = TRUE; + } + + return(bError ? -1 : 0); +} + + +/*F********************************************************************************/ +/*! + \Function QosCommonAddrToString + + \Description + Convert a address into a human readable easily pars-able string. + + \Input *pAddr - address to convert + \Input *pBuffer - [out] buffer to write to + \Input iBufSize - size of buffer + + \Output + char* - the pBuffer pointer that was written to + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +char* QosCommonAddrToString(const QosCommonAddrT *pAddr, char *pBuffer, int32_t iBufSize) +{ + //we build a sockaddr, just so we can use our library print function, which saves us some headache + // if we wanted more details take a look at _ds_sockaddrtostr() + if (pAddr->uFamily == AF_INET) + { + struct sockaddr sockAddr; + SockaddrInit(&sockAddr, AF_INET); + SockaddrInSetAddr(&sockAddr, pAddr->addr.v4); + SockaddrInSetPort(&sockAddr, pAddr->uPort); + ds_snzprintf(pBuffer, iBufSize, "v4%A", &sockAddr); + } + #ifndef DIRTYCODE_NX + else if (pAddr->uFamily == AF_INET6) + { + struct sockaddr_in6 sockAddr6; + SockaddrInit6(&sockAddr6, AF_INET6); + ds_memcpy(&sockAddr6.sin6_addr, &(pAddr->addr.v6), sizeof(sockAddr6.sin6_addr)); + sockAddr6.sin6_port = pAddr->uPort; + ds_snzprintf(pBuffer, iBufSize, "v6%A", &sockAddr6); + } + #endif + else + { + ds_snzprintf(pBuffer, iBufSize, "na[0]:0"); + } + return(pBuffer); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonStringToAddr + + \Description + Convert a string generated by QosCommonAddrToString into a QosCommonAddrT + + \Input *pStrIn - text to convert + \Input *pOutAddr - [out] address to write to + + \Output + int32_t - -1 on err, 0 on success + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +int32_t QosCommonStringToAddr(char *pStrIn, QosCommonAddrT *pOutAddr) +{ + int32_t iRet = -1; + int32_t iCurrentToken = 0; + char *pSave = NULL; + char *pTokenTemp = NULL; + char *pToken0 = NULL; + char *pToken1 = NULL; + char *pToken2 = NULL; + + ds_memclr(pOutAddr, sizeof(QosCommonAddrT)); + pTokenTemp = ds_strtok_r(pStrIn, "[]", &pSave); + while (pTokenTemp != NULL) + { + //process the token into a variable based off the token count + if (iCurrentToken == 0) + { + pToken0 = pTokenTemp; + } + else if (iCurrentToken == 1) + { + pToken1 = pTokenTemp; + } + else if (iCurrentToken == 2) + { + pToken2 = pTokenTemp; + } + pTokenTemp = ds_strtok_r(NULL, "[]", &pSave); + iCurrentToken++; + } + + if ((pToken0 != NULL) && (ds_strnicmp(pToken0, "v4", 2) == 0)) + { + pOutAddr->uFamily = AF_INET; + if (pToken1 != NULL) + { + struct sockaddr tempAddr; //doing this so i can make use of SockaddrInSetAddrText + SockaddrInit(&tempAddr, AF_INET); + SockaddrInSetAddrText(&tempAddr, pToken1); + pOutAddr->addr.v4 = SockaddrInGetAddr(&tempAddr); + iRet = 0; + } + if (pToken2 != NULL) + { + pOutAddr->uPort = atoi(pToken2 + 1); // +1 to move past the ':' character + } + } + #ifndef DIRTYCODE_NX + else if ((pToken0 != NULL) && (ds_strnicmp(pToken0, "v6", 2) == 0)) + { + pOutAddr->uFamily = AF_INET6; + if (pToken1 != NULL) + { + struct sockaddr_in6 tempAddr; //doing this so i can make use of SockaddrInSetAddrText + SockaddrInit6(&tempAddr, AF_INET6); + SockaddrInSetAddrText((struct sockaddr *)&tempAddr, pToken1); //this will translate the string into the bytes it needs to be + ds_memcpy(&(pOutAddr->addr.v6), &tempAddr.sin6_addr, sizeof(pOutAddr->addr.v6)); //write the bytes into our structure + iRet = 0; + } + if (pToken2 != NULL) + { + pOutAddr->uPort = atoi(pToken2 + 1); // +1 to move past the ':' character + } + } + #endif + return(iRet); +} +/*F********************************************************************************/ +/*! + \Function QosCommonConvertAddr + + \Description + Determine if pSourceAddr is remapped, if so fetch the real info and + copy the common fields from pSourceAddr to pTargetAddr. + + \Input *pTargetAddr - [out] structure to store the fields we are interested in + \Input *pSourceAddr - address to copy from + + \Version 06/27/2017 (cvienneau) +*/ +/********************************************************************************F*/ +void QosCommonConvertAddr(QosCommonAddrT *pTargetAddr, struct sockaddr *pSourceAddr) +{ + #ifndef DIRTYCODE_NX + struct sockaddr_in6 SockAddr6; + + //check to see if the ipv4 address coming in is a remapped ipv6 addr, if it is get the real ipv6 info + if (pSourceAddr->sa_family == AF_INET) + { + uint32_t uAddr = SockaddrInGetAddr(pSourceAddr); + if (SocketInfo(NULL, '?ip6', uAddr, &SockAddr6, sizeof(SockAddr6)) == 1) + { + pSourceAddr = (struct sockaddr *)&SockAddr6; //it is an ipv6 remapped addr, use the ipv6 info below. + } + } + #endif + + pTargetAddr->uFamily = pSourceAddr->sa_family; + + if (pTargetAddr->uFamily == AF_INET) + { + pTargetAddr->addr.v4 = SockaddrInGetAddr(pSourceAddr); + pTargetAddr->uPort = SockaddrInGetPort(pSourceAddr); + } + + #ifndef DIRTYCODE_NX + else if (pTargetAddr->uFamily == AF_INET6) + { + struct sockaddr_in6 * pAddr6 = (struct sockaddr_in6 *)pSourceAddr; + ds_memcpy(&(pTargetAddr->addr.v6), &pAddr6->sin6_addr, sizeof(pTargetAddr->addr.v6)); + pTargetAddr->uPort = pAddr6->sin6_port; + } + #endif +} + + +/*F********************************************************************************/ +/*! + \Function QosCommonIsAddrEqual + + \Description + Compare if two addresses belong to the same machine (ignore port). + + \Input *pAddr1 - first address to compare + \Input *pAddr2 - second address to compare + + \Output + uint8_t - TRUE if addresses are equal + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t QosCommonIsAddrEqual(QosCommonAddrT *pAddr1, QosCommonAddrT *pAddr2) +{ + if (pAddr1->uFamily == pAddr2->uFamily) + { + if (pAddr1->uFamily == AF_INET) + { + if (pAddr1->addr.v4 == pAddr2->addr.v4) + { + return(TRUE); + } + } + #ifndef DIRTYCODE_NX + else if (pAddr1->uFamily == AF_INET6) + { + //just compare the address portion of the struct + if (memcmp(&pAddr1->addr.v6, + &pAddr2->addr.v6, + sizeof(pAddr1->addr.v6)) == 0) + { + return(TRUE); + } + } + #endif + } + + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonIsRemappedAddrEqual + + \Description + Retrieve the address remap for pAddr1 if it exists (would be the case for ipv6) + and compare it to the v4 or already remapped pAddr2 to see if they belong to the same + machine. + + \Input *pAddr1 - first address to compare + \Input *pAddr2 - second address to compare + + \Output + uint8_t - TRUE if addresses are equal + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t QosCommonIsRemappedAddrEqual(QosCommonAddrT *pAddr1, struct sockaddr *pAddr2) +{ + uint32_t uAddr1v4; + uint32_t uAddr2v4; + + if (pAddr2->sa_family != AF_INET) + { + return(FALSE); //we expect this address to be ipv4 or a remapped ipv6, so we shouldn't see anything other than AF_INET + } + uAddr2v4 = SockaddrInGetAddr(pAddr2); + + if (pAddr1->uFamily == AF_INET) + { + uAddr1v4 = pAddr1->addr.v4; + } + #ifndef DIRTYCODE_NX + else if (pAddr1->uFamily == AF_INET6) + { + struct sockaddr_in6 addr6; + SockaddrInit6(&addr6, AF_INET6); + ds_memcpy(&addr6.sin6_addr, &(pAddr1->addr.v6), sizeof(addr6.sin6_addr)); + addr6.sin6_port = pAddr1->uPort; + uAddr1v4 = SocketControl(NULL, '+ip6', sizeof(addr6), &addr6, NULL); + //todo when will things be removed from the address map? + } + #endif + else + { + return(FALSE); // we don't know the type, they're not equal + } + + if (uAddr1v4 == uAddr2v4) + { + return(TRUE); + } + + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonSerializeProbePacket + + \Description + Write the contents of a QosCommonProbePacketT struct to a buffer, sign it for authenticity + in preparation for sending it on the wire. + + \Input *pOutBuff - [out] buffer the packet will be written to + \Input uBuffSize - size of the buffer the packet is being written to, must be at least QOS_COMMON_MIN_PACKET_SIZE + \Input *pInPacket - the structure containing the probe packet information + \Input *pSecureKey- the secure key used to sign the probe as being authentic + + \Output + uint8_t - number of bytes written to the buffer + + \Version 07/06/2017 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t QosCommonSerializeProbePacket(uint8_t *pOutBuff, uint32_t uBuffSize, const QosCommonProbePacketT *pInPacket, uint8_t *pSecureKey) +{ + /* + This is the order we are going to serialize in + uint32_t uProtocol; //!< QOS 2.0 packets will always contain 'qos2' for easy identification. + uint16_t uVersion; //!< uniquely identifies protocol + uint32_t uServiceRequestId; //!< provided by the QosCoordinator, unique to the client doing multiple QOS actions, used to identify which server resources this client is using + uint16_t uClientRequestId; //!< provided by the client, unique to a particular QOS action, used to pair request and responses together + uint32_t uServerReceiveTime; //!< time the server received this probe from the client + uint16_t uServerSendDelta; //!< delta before the server sent this probe response + uint16_t uProbeSizeUp; //!< indicates how big this probe is, including any padding for bandwidth + uint16_t uProbeSizeDown; //!< indicates how big this probe is, including any padding for bandwidth + uint8_t uProbeCountUp; //!< count index of this probe + uint8_t uProbeCountDown; //!< count index of this probe + QosCommonAddrT clientAddressFromService; //!< initially the client address from coordinator, however address from server prospective takes precedence, used to authenticate the packet is coming from the address that generated it + uint8_t aHmac[QOS_COMMON_HMAC_SIZE];//!< when combined with the secure key identifies this packet as coming from a real QOS client + */ + if (uBuffSize >= QOS_COMMON_MIN_PACKET_SIZE) + { + uint32_t uTemp32; + uint16_t uTemp16; + uint8_t *pWrite = pOutBuff; + + uTemp32 = SocketHtonl(pInPacket->uProtocol); ds_memcpy(pWrite, &uTemp32, sizeof(uint32_t)); pWrite += sizeof(uint32_t); //4 + uTemp16 = SocketHtons(pInPacket->uVersion); ds_memcpy(pWrite, &uTemp16, sizeof(uint16_t)); pWrite += sizeof(uint16_t); //2 + uTemp32 = SocketHtonl(pInPacket->uServiceRequestId); ds_memcpy(pWrite, &uTemp32, sizeof(uint32_t)); pWrite += sizeof(uint32_t); //4 + uTemp16 = SocketHtons(pInPacket->uClientRequestId); ds_memcpy(pWrite, &uTemp16, sizeof(uint16_t)); pWrite += sizeof(uint16_t); //2 + uTemp32 = SocketHtonl(pInPacket->uServerReceiveTime); ds_memcpy(pWrite, &uTemp32, sizeof(uint32_t)); pWrite += sizeof(uint32_t); //4 + uTemp16 = SocketHtons(pInPacket->uServerSendDelta); ds_memcpy(pWrite, &uTemp16, sizeof(uint16_t)); pWrite += sizeof(uint16_t); //2 + uTemp16 = SocketHtons(pInPacket->uProbeSizeUp); ds_memcpy(pWrite, &uTemp16, sizeof(uint16_t)); pWrite += sizeof(uint16_t); //2 + uTemp16 = SocketHtons(pInPacket->uProbeSizeDown); ds_memcpy(pWrite, &uTemp16, sizeof(uint16_t)); pWrite += sizeof(uint16_t); //2 + /*pInPacket->uProbeCountUp;*/ ds_memcpy(pWrite, &pInPacket->uProbeCountUp, sizeof(uint8_t)); pWrite += sizeof(uint8_t); //1 + /*pInPacket->uProbeCountDown;*/ ds_memcpy(pWrite, &pInPacket->uProbeCountDown, sizeof(uint8_t)); pWrite += sizeof(uint8_t); //1 + /*pInPacket->uExpectedProbeCountUp;*/ ds_memcpy(pWrite, &pInPacket->uExpectedProbeCountUp, sizeof(uint8_t)); pWrite += sizeof(uint8_t); //1 + uTemp16 = SocketHtons(pInPacket->clientAddressFromService.uFamily); ds_memcpy(pWrite, &uTemp16, sizeof(uint16_t)); pWrite += sizeof(uint16_t); //2 + uTemp16 = SocketHtons(pInPacket->clientAddressFromService.uPort); ds_memcpy(pWrite, &uTemp16, sizeof(uint16_t)); pWrite += sizeof(uint16_t); //2 + uTemp32 = SocketHtonl(pInPacket->clientAddressFromService.addr.v6.aDwords[0]); ds_memcpy(pWrite, &uTemp32, sizeof(uint32_t)); pWrite += sizeof(uint32_t); //4 + uTemp32 = SocketHtonl(pInPacket->clientAddressFromService.addr.v6.aDwords[1]); ds_memcpy(pWrite, &uTemp32, sizeof(uint32_t)); pWrite += sizeof(uint32_t); //4 + uTemp32 = SocketHtonl(pInPacket->clientAddressFromService.addr.v6.aDwords[2]); ds_memcpy(pWrite, &uTemp32, sizeof(uint32_t)); pWrite += sizeof(uint32_t); //4 + uTemp32 = SocketHtonl(pInPacket->clientAddressFromService.addr.v6.aDwords[3]); ds_memcpy(pWrite, &uTemp32, sizeof(uint32_t)); pWrite += sizeof(uint32_t); //4 + //total 45 bytes == QOS_COMMON_SIZEOF_PROBE_DATA + //generate hmac, it is just bytes, we don't need to hton them + //hash the probe values with the secure key to sign the probe as being authentic + CryptHmacCalc(pWrite, QOS_COMMON_HMAC_SIZE, pOutBuff, QOS_COMMON_SIZEOF_PROBE_DATA, pSecureKey, QOS_COMMON_SECURE_KEY_LENGTH, QOS_COMMON_HMAC_TYPE); + pWrite += QOS_COMMON_HMAC_SIZE; + return(pWrite - pOutBuff); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonDeserializeClientRequestId + + \Description + Read the uClientRequestId from the packet without doing any validation. + + \Input *pInBuff - buffer the packet will be read from + + \Output + uint16_t - the uClientRequestId field from a serialized QosCommonProbePacketT + + \Version 07/06/2017 (cvienneau) + */ +/********************************************************************************F*/ +uint16_t QosCommonDeserializeClientRequestId(uint8_t *pInBuff) +{ + //uClientRequestId is the 10'th byte in the serialized packet + return(SocketNtohs(*(uint16_t*)(pInBuff+10))); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonDeserializeServiceRequestId + + \Description + Read the uServiceRequestId from the packet without doing any validation. + + \Input *pInBuff - buffer the packet will be read from to + + \Output + uint32_t - the uServiceRequestId field from a serialized QosCommonProbePacketT + + \Version 07/06/2017 (cvienneau) +*/ +/********************************************************************************F*/ +uint32_t QosCommonDeserializeServiceRequestId(uint8_t *pInBuff) +{ + //uServiceRequestId is the 6'th byte in the serialized packet + return(SocketNtohl(*(uint32_t*)(pInBuff + 6))); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonDeserializeProbePacket + + \Description + Authenticate and read a QosCommonProbePacketT from the provided buffer. + + \Input *pOutPacket - [out] struct to contain the deserailzed probe + \Input *pInBuff - buffer the probe is read from + \Input *pSecureKey1 - secure key used to authenticate the probe data + \Input *pSecureKey2 - optional alternative, secure key used to authenticate the probe data + + \Output + uint32_t - 0=first secure key succeeded, 1=second secure key succeeded, else failure + + \Version 07/06/2017 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t QosCommonDeserializeProbePacket(QosCommonProbePacketT *pOutPacket, uint8_t *pInBuff, uint8_t *pSecureKey1, uint8_t *pSecureKey2) +{ + uint8_t uRet = 0; + +#if QOS_COMMON_ENABLE_HMAC + uint8_t aHmac[QOS_COMMON_HMAC_SIZE]; + + //validate the hmac, which is at the end of the packet + //generate what we think the hmac should be + CryptHmacCalc(aHmac, QOS_COMMON_HMAC_SIZE, pInBuff, QOS_COMMON_SIZEOF_PROBE_DATA, pSecureKey1, QOS_COMMON_SECURE_KEY_LENGTH, QOS_COMMON_HMAC_TYPE); + if (memcmp(aHmac, pInBuff + QOS_COMMON_SIZEOF_PROBE_DATA, QOS_COMMON_HMAC_SIZE) != 0) + { + //first secure key failed, try with the backup secure key if one was provided + if (pSecureKey2 != NULL) + { + CryptHmacCalc(aHmac, QOS_COMMON_HMAC_SIZE, pInBuff, QOS_COMMON_SIZEOF_PROBE_DATA, pSecureKey2, QOS_COMMON_SECURE_KEY_LENGTH, QOS_COMMON_HMAC_TYPE); + if (memcmp(aHmac, pInBuff + QOS_COMMON_SIZEOF_PROBE_DATA, QOS_COMMON_HMAC_SIZE) != 0) + { + return(2); //failed authentication + } + else + { + uRet = 1; //first hmac didn't succeed but second one did + } + } + else + { + return(2); //failed authentication + } + } +#endif + + QosCommonDeserializeProbePacketInsecure(pOutPacket, pInBuff); + return(uRet); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonDeserializeProbePacketInsecure + + \Description + Read a QosCommonProbePacketT from the provided buffer. + + \Input *pOutPacket - [out] struct to contain the deserailzed probe + \Input *pInBuff - buffer the probe is read from + + \Version 07/06/2017 (cvienneau) +*/ +/********************************************************************************F*/ +void QosCommonDeserializeProbePacketInsecure(QosCommonProbePacketT *pOutPacket, uint8_t *pInBuff) +{ + /* + This is the order we are going to deserialize from + uint32_t uProtocol; //!< QOS 2.0 packets will always contain 'qos2' for easy identification. + uint16_t uVersion; //!< uniquely identifies protocol + uint32_t uServiceRequestId; //!< provided by the QosCoordinator, unique to the client doing multiple QOS actions, used to identify which server resources this client is using + uint16_t uClientRequestId; //!< provided by the client, unique to a particular QOS action, used to pair request and responses together + uint32_t uServerReceiveTime; //!< time the server received this probe from the client + uint32_t uServerSendDelta; //!< delta before the server sent this probe response + uint16_t uProbeSizeUp; //!< indicates how big this probe is, including any padding for bandwidth + uint16_t uProbeSizeDown; //!< indicates how big this probe is, including any padding for bandwidth + uint8_t uProbeCountUp; //!< count index of this probe + uint8_t uProbeCountDown; //!< count index of this probe + QosCommonAddrT clientAddressFromService; //!< initially the client address from coordinator, however address from server prospective takes precedence, used to authenticate the packet is coming from the address that generated it + uint8_t aHmac[QOS_COMMON_HMAC_SIZE];//!< when combined with the secure key identifies this packet as coming from a real QOS client + */ + pOutPacket->uProtocol = SocketNtohl(*(uint32_t*)pInBuff); pInBuff += sizeof(uint32_t); //4 + pOutPacket->uVersion = SocketNtohs(*(uint16_t*)pInBuff); pInBuff += sizeof(uint16_t); //2 + pOutPacket->uServiceRequestId = SocketNtohl(*(uint32_t*)pInBuff); pInBuff += sizeof(uint32_t); //4 + pOutPacket->uClientRequestId = SocketNtohs(*(uint16_t*)pInBuff); pInBuff += sizeof(uint16_t); //2 + pOutPacket->uServerReceiveTime = SocketNtohl(*(uint32_t*)pInBuff); pInBuff += sizeof(uint32_t); //4 + pOutPacket->uServerSendDelta = SocketNtohl(*(uint16_t*)pInBuff); pInBuff += sizeof(uint16_t); //2 + pOutPacket->uProbeSizeUp = SocketNtohs(*(uint16_t*)pInBuff); pInBuff += sizeof(uint16_t); //2 + pOutPacket->uProbeSizeDown = SocketNtohs(*(uint16_t*)pInBuff); pInBuff += sizeof(uint16_t); //2 + pOutPacket->uProbeCountUp = *pInBuff; pInBuff += sizeof(uint8_t); //1 + pOutPacket->uProbeCountDown = *pInBuff; pInBuff += sizeof(uint8_t); //1 + pOutPacket->uExpectedProbeCountUp = *pInBuff; pInBuff += sizeof(uint8_t); //1 + pOutPacket->clientAddressFromService.uFamily = SocketNtohs(*(uint16_t*)pInBuff); pInBuff += sizeof(uint16_t); //2 + pOutPacket->clientAddressFromService.uPort = SocketNtohs(*(uint16_t*)pInBuff); pInBuff += sizeof(uint16_t); //2 + pOutPacket->clientAddressFromService.addr.v6.aDwords[0] = SocketNtohl(*(uint32_t*)pInBuff); pInBuff += sizeof(uint32_t); //4 + pOutPacket->clientAddressFromService.addr.v6.aDwords[1] = SocketNtohl(*(uint32_t*)pInBuff); pInBuff += sizeof(uint32_t); //4 + pOutPacket->clientAddressFromService.addr.v6.aDwords[2] = SocketNtohl(*(uint32_t*)pInBuff); pInBuff += sizeof(uint32_t); //4 + pOutPacket->clientAddressFromService.addr.v6.aDwords[3] = SocketNtohl(*(uint32_t*)pInBuff); pInBuff += sizeof(uint32_t); //4 +} + +/*F********************************************************************************/ +/*! + \Function QosCommonMakeVersion + + \Description + Make a version number out of a major and minor version parts + + \Input uMajor - major byte, indicates non-backwards compatible changes + \Input uMinor - minor byte, indicates bug fixes + + \Output + uint16_t - 2 bytes representing a version. + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +uint16_t QosCommonMakeVersion(uint8_t uMajor, uint8_t uMinor) +{ + uint16_t uVersion = (uMajor << 8) + uMinor; + return(uVersion); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonGetVersion + + \Description + Split a version number into major and minor version parts + + \Input uVersion - full version, to be split into major and minor components + \Input *uMajor - [out] major byte, indicates non-backwards compatible changes + \Input *uMinor - [out] minor byte, indicates bug fixes + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +void QosCommonGetVersion(uint16_t uVersion, uint8_t *uMajor, uint8_t *uMinor) +{ + *uMajor = (uVersion >> 8); + *uMinor = (uVersion & 0x00FF); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonIsCompatibleVersion + + \Description + Compare the major portion of two version number to see if they are compatible. + + \Input uVersion1 - first version + \Input uVersion2 - second version + + \Output + uint8_t - TRUE if they are compatible + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t QosCommonIsCompatibleVersion(uint16_t uVersion1, uint16_t uVersion2) +{ + uint8_t uMajor1 = (uVersion1 >> 8); + uint8_t uMajor2 = (uVersion2 >> 8); + if (uMajor1 == uMajor2) + { + return(TRUE); + } + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function QosCommonIsCompatibleProbeVersion + + \Description + Compare if probes of the passed in version are compatible with our version of code. + + \Input uVersion - version of probe + + \Output + uint8_t - TRUE if they are compatible + + \Version 12/09/2016 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t QosCommonIsCompatibleProbeVersion(uint16_t uVersion) +{ + uint8_t uMajor = (uVersion >> 8); + if (uMajor == QOS_COMMON_PROBE_VERSION_MAJOR) + { + return(TRUE); + } + return(FALSE); +} diff --git a/src/thirdparty/dirtysdk/source/misc/userapi.c b/src/thirdparty/dirtysdk/source/misc/userapi.c new file mode 100644 index 00000000..2f2a7c80 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/misc/userapi.c @@ -0,0 +1,1052 @@ +/*H*************************************************************************************************/ +/*! + \File userapi.c + + \Description + Expose first party players' information + + \Copyright + Copyright (c) Electronic Arts 2001-2013 + + \Version 05/10/2013 (mcorcoran) First Version +*/ +/*************************************************************************************************H*/ + + +/*** Include files ********************************************************************************/ + +#include + +#include "DirtySDK/misc/userapi.h" +#include "userapipriv.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +/*** Defines **************************************************************************************/ + +/*** Macros ***************************************************************************************/ + +/*** Type Definitions *****************************************************************************/ + +/*** Function Prototypes **************************************************************************/ + +static void _ClearContext(UserApiRefT *pRef, int32_t iUserIndex); + +/*** Variables ************************************************************************************/ + +/*** Private Functions ****************************************************************************/ + +/*F*************************************************************************************************/ +/*! + \Function _UserApiFreeCallback + + \Description + Callback registered with the netconn external cleanup mechanism. It proceeds with destroying + the UserApi instance only when there is no longer an internal async operation in progress + and the memory can safely be freed. + + \Input *pMem - pointer to the UserApi memory buffer + + \Output + int32_t - zero=success; -1=try again; other negative=error + + \Version 09/25/2013 (amakoukji) +*/ +/*************************************************************************************************F*/ +static int32_t _UserApiFreeCallback(void *pMem) +{ + UserApiRefT *pRef = (UserApiRefT*)pMem; + + if ((pRef->pPlatformData != NULL) && (UserApiPlatDestroyData(pRef, pRef->pPlatformData) < 0)) + { + return(-1); + } + + NetCritKill(&pRef->crit); + NetCritKill(&pRef->postCrit); + DirtyMemFree(pRef, USERAPI_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function _ClearContext + + \Description + Clears a user context + + \Input *pRef - pointer to module ref if successful, NULL otherwise. + \Input iUserIndex - user index + + \Version 12/11/2013 (amakoukji) - First version +*/ +/*************************************************************************************************F*/ +void _ClearContext(UserApiRefT *pRef, int32_t iUserIndex) +{ + NetCritEnter(&pRef->crit); + pRef->UserContextList[iUserIndex].iTotalRequested = 0; + pRef->UserContextList[iUserIndex].iTotalReceived = 0; + pRef->UserContextList[iUserIndex].iTotalErrors = 0; + + pRef->UserPresenceList[iUserIndex].iTotalRequested = 0; + pRef->UserPresenceList[iUserIndex].iTotalReceived = 0; + pRef->UserPresenceList[iUserIndex].iTotalErrors = 0; + + pRef->UserRichPresenceList[iUserIndex].iTotalRequested = 0; + pRef->UserRichPresenceList[iUserIndex].iTotalReceived = 0; + pRef->UserRichPresenceList[iUserIndex].iTotalErrors = 0; + + pRef->iLookupUsersLength[iUserIndex] = -1; + pRef->iLookupUserIndex[iUserIndex] = -1; + pRef->iLookupsSent[iUserIndex] = 0; + pRef->bLookupUserAvailable[iUserIndex] = TRUE; + + pRef->bAvailableDataIndex[iUserIndex] = FALSE; + pRef->bAvailableDataIndexPresence[iUserIndex] = FALSE; + pRef->bAvailableDataIndexRichPresence[iUserIndex] = FALSE; + + if (pRef->aLookupUsers[iUserIndex]) + { + DirtyMemFree(pRef->aLookupUsers[iUserIndex], USERAPI_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + pRef->aLookupUsers[iUserIndex] = NULL; + } + NetCritLeave(&pRef->crit); +} + +/*F*************************************************************************************************/ +/*! + \Function _UserApiTriggerCallback + + \Description + Called by update function in the platform specific UserApi modules when a profile + or error is ready. + + \Input *pRef - pointer to UserApiT module reference. + \Input *uUserIndex - The index of the user associated with this profile request. + \Input *eError - A UserApiEventErrorE indicating success for the failure reason. + \Input eType - type of callback + \Input *pUserData - pointer to a populated UserApiUserDataT + + \Output + void + + \Version 05/10/2013 (mcorcoran) - First version + \Version 12/10/2013 (amakoukji) - Second version for UserApi second pass +*/ +/*************************************************************************************************F*/ +void _UserApiTriggerCallback(UserApiRefT *pRef, uint32_t uUserIndex, UserApiEventErrorE eError, UserApiEventTypeE eType, UserApiUserDataT *pUserData) +{ + UserApiEventDataT EventData; + NetCritT *pCrit = &pRef->crit; + + NetCritEnter(pCrit); + + EventData.eError = eError; + EventData.eEventType = eType; + EventData.uUserIndex = (uint32_t)uUserIndex; + ds_memcpy(&EventData.EventDetails.UserData, pUserData, sizeof(UserApiUserDataT)); + + if (eType == USERAPI_EVENT_END_OF_LIST) + { + EventData.EventDetails.EndOfList.iTotalRequested = pRef->UserContextList[uUserIndex].iTotalRequested + pRef->UserPresenceList[uUserIndex].iTotalRequested + pRef->UserRichPresenceList[uUserIndex].iTotalRequested; + EventData.EventDetails.EndOfList.iTotalReceived = pRef->UserContextList[uUserIndex].iTotalReceived + pRef->UserPresenceList[uUserIndex].iTotalReceived + pRef->UserRichPresenceList[uUserIndex].iTotalReceived; + EventData.EventDetails.EndOfList.iTotalErrors = pRef->UserContextList[uUserIndex].iTotalErrors + pRef->UserPresenceList[uUserIndex].iTotalErrors + pRef->UserRichPresenceList[uUserIndex].iTotalErrors; + + if (EventData.EventDetails.EndOfList.iTotalErrors > 0) + { + EventData.eError = USERAPI_ERROR_REQUEST_FAILED; + } + } + + // the actual callback + if (pRef->pUserCallback[uUserIndex] != NULL) + { + pRef->pUserCallback[uUserIndex](pRef, &EventData, pRef->pUserData[uUserIndex]); + } + + NetCritLeave(pCrit); +} + + +/*** Public Functions *****************************************************************************/ + +/*F*************************************************************************************************/ +/*! + \Function UserApiCreate + + \Description + Starts UserApi. + + \Output + UserApiRefT* - pointer to module ref if successful, NULL otherwise. + + \Version 05/10/2013 (mcorcoran) - First version +*/ +/*************************************************************************************************F*/ +UserApiRefT * UserApiCreate(void) +{ + UserApiRefT *pRef; + int32_t iMemGroup; + void *pMemGroupUserData; + uint32_t iIndex; + + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pRef = (UserApiRefT*)DirtyMemAlloc(sizeof(*pRef), USERAPI_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("userapi: [%p] failed to allocate module state.\n", pRef)); + return(NULL); + } + ds_memclr(pRef, sizeof(*pRef)); + pRef->iMemGroup = iMemGroup; + pRef->pMemGroupUserData = pMemGroupUserData; + + pRef->bShuttingDown = FALSE; + + for (iIndex = 0; iIndex < NETCONN_MAXLOCALUSERS; ++iIndex) + { + pRef->iLookupUsersLength[iIndex] = -1; + pRef->iLookupUserIndex[iIndex] = -1; + pRef->bLookupUserAvailable[iIndex] = TRUE; + pRef->aLookupUsers[iIndex] = NULL; + + pRef->pUserCallback[iIndex] = NULL; + pRef->pPostCallback[iIndex] = NULL; + pRef->pUserData[iIndex] = NULL; + pRef->pUserDataPost[iIndex] = NULL; + pRef->uUserDataMask[iIndex] = 0; + pRef->bAvailableDataIndex[iIndex] = 0; + pRef->bAvailableDataIndexPresence[iIndex] = 0; + pRef->bAvailableDataIndexRichPresence[iIndex] = 0; + pRef->bAvailableDataIndexRMP[iIndex] = FALSE; + } + + for (iIndex = 0; iIndex < USERAPI_NOTIFY_LIST_MAX_SIZE; ++iIndex) + { + pRef->PresenceNotification[iIndex].pCallback = NULL; + pRef->PresenceNotification[iIndex].pUserData = NULL; + pRef->TitleNotification[iIndex].pCallback = NULL; + pRef->TitleNotification[iIndex].pUserData = NULL; + pRef->RichPresenceNotification[iIndex].pCallback = NULL; + pRef->RichPresenceNotification[iIndex].pUserData = NULL; + pRef->ProfileUpdateNotification[iIndex].pCallback = NULL; + pRef->ProfileUpdateNotification[iIndex].pUserData = NULL; + } + pRef->bPresenceNotificationStarted = FALSE; + pRef->bTitleNotificationStarted = FALSE; + pRef->bRichPresenceNotificationStarted = FALSE; + pRef->bProfileUpdateNotificationStarted = FALSE; + + NetCritInit(&pRef->crit, "UserApi"); + NetCritInit(&pRef->postCrit, "UserApiPost"); + + pRef->pPlatformData = UserApiPlatCreateData(pRef); + if (pRef->pPlatformData == NULL) + { + NetPrintf(("userapi: [%p] failed to create platform data.\n", pRef)); + UserApiDestroy(pRef); + return(NULL); + } + + // return the module ref + return(pRef); +} + +/*F*************************************************************************************************/ +/*! + \Function UserApiDestroy + + \Description + Starts shutting down module. Module will not accept new requests, and will abort ongoing ones. Also, + registered UserApiCallbackT callback will not be called even if there is data available for processing. + + \Input *pRef - pointer to UserApiT module reference + + \Output + int32_t - 0 if successful. Otherwise -1 + + \Version 05/10/2013 (mcorcoran) - First version +*/ +/*************************************************************************************************F*/ +int32_t UserApiDestroy(UserApiRefT *pRef) +{ + int32_t iIndex = 0; + pRef->bShuttingDown = TRUE; + NetCritEnter(&pRef->crit); + + for (; iIndex < NETCONN_MAXLOCALUSERS; ++iIndex) + { + pRef->bAvailableDataIndex[iIndex] = 0; + pRef->bAvailableDataIndexPresence[iIndex] = 0; + pRef->bAvailableDataIndexRichPresence[iIndex] = 0; + pRef->bAvailableDataIndexRMP[iIndex] = FALSE; + } + + NetCritLeave(&pRef->crit); + + if (_UserApiFreeCallback((void*)pRef) < 0) + { + NetPrintf(("userapi: [%p] destroy deferred to netconn due to pending async operation.\n", pRef)); + NetConnControl('recu', 0, 0, (void *)_UserApiFreeCallback, pRef); + } + + // $todo: change return type to void and remove the return statement + return(0); +} + +/*F********************************************************************************/ +/*! + \Function UserApiStatus + + \Description + Get status information. + + \Input *pRef - pointer to module state + \Input iSelect - status selector + \Input *pBuf - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - Return 0 if successful. Otherwise a selector specific error + + \Notes + There is currently nothing to query with this module. This is a placeholder for future implementations. + + \verbatim + \endverbatim + + \Version 05/10/2013 (mcorcoran) - First version +*/ +/********************************************************************************F*/ +int32_t UserApiStatus(UserApiRefT *pRef, int32_t iSelect, void *pBuf, int32_t iBufSize) +{ + NetPrintf(("userapi: unhandled status selector '%C'\n", iSelect)); + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function UserApiControl + + \Description + Control behavior of module. + + \Input *pRef - pointer to module state + \Input iControl - status selector + \Input iValue - control value + \Input iValue2 - control value + \Input *pValue - control value + + \Output + int32_t - Return 0 if successful. Otherwise a selector specific error + + \Notes + iControl can be one of the following: + + \verbatim + 'abrt' - Abort the current request associated with the user at index iValue + 'avsz' - (PS4 only) Set which avatar size will be retrieved. iValue = 's' for small, 'm' for medium and 'l' for big. 's' is the default value, and this is just functional for PS4. + \endverbatim + + \Version 05/10/2013 (mcorcoran) - First version +*/ +/********************************************************************************F*/ +int32_t UserApiControl(UserApiRefT *pRef, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iControl == 'abrt') + { + if ((iValue < 0) || (iValue >= NETCONN_MAXLOCALUSERS)) + { + NetPrintf(("userapi: [%p] iValue(%d) is not a valid user index\n", pRef, iValue)); + return(-1); + } + + pRef->pUserCallback[iValue] = NULL; + pRef->pUserData[iValue] = NULL; + + UserApiPlatAbortRequests(pRef, iValue); + + pRef->UserContextList[iValue].iTotalRequested = 0; + pRef->UserContextList[iValue].iTotalReceived = 0; + pRef->UserContextList[iValue].iTotalErrors = 0; + + pRef->iLookupUsersLength[iValue] = -1; + pRef->iLookupUserIndex[iValue] = -1; + pRef->bLookupUserAvailable[iValue] = TRUE; + if (pRef->aLookupUsers[iValue]) + { + DirtyMemFree(pRef->aLookupUsers[iValue], USERAPI_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + pRef->aLookupUsers[iValue] = NULL; + } + + return(0); + } + else + { + int32_t iRet; + if ((iRet = UserApiPlatControl(pRef, iControl, iValue, iValue2, pValue)) < 0) + { + NetPrintf(("userapi: unhandled control selector '%C'\n", iControl)); + } + + return(iRet); + } +} + +/*F*************************************************************************************************/ +/*! + \Function UserApiRequestProfilesAsync + + \Description + Starts the process to retrieve user information of players. pLookupUsers is a pointer to the first DirtyUserT, and iLookupUsersLength is the number of DirtyUserTs. + + \Input *pRef - pointer to UserApiT module reference + \Input uUserIndex - Index used to specify which local user owns the request + \Input *pLookupUsers - Pointer to first DityUserT in an array of iLookupUsersLength elements + \Input iLookupUsersLength - Number of elements in the pLookupUsers array + \Input *pCallback - Callback that is going to be called when responses for these requests are received + \Input *pUserData - This pointer is going to be passed to the callback when it is called. This parameter can be NULL + + \Output + int32_t - Return 0 if successful, -1 otherwise. + + \Version 05/10/2013 (mcorcoran) - First version + \Version 09/12/2013 (amakoukji) - Second pass with presence +*/ +/*************************************************************************************************F*/ +int32_t UserApiRequestProfilesAsync(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUsers, int32_t iLookupUsersLength, UserApiCallbackT *pCallback, void *pUserData) +{ + int32_t iRet; + + if (pLookupUsers == NULL || iLookupUsersLength <= 0) + { + NetPrintf(("userapi: [%p] invalid pointer to DirtyUserT list\n", pRef)); + return(-1); + } + + if (pCallback == NULL) + { + NetPrintf(("userapi: [%p] invalid pointer to callback\n", pRef)); + return(-3); + } + + NetCritEnter(&pRef->crit); + + if (pRef->UserContextList[uUserIndex].iTotalRequested > 0 || pRef->UserPresenceList[uUserIndex].iTotalRequested > 0) + { + NetPrintf(("userapi: [%p] module is already handling a request for user (%u)\n", pRef, uUserIndex)); + NetCritLeave(&pRef->crit); + return(-2); + } + + pRef->UserContextList[uUserIndex].iTotalRequested = iLookupUsersLength; + pRef->UserContextList[uUserIndex].iTotalReceived = 0; + pRef->UserContextList[uUserIndex].iTotalErrors = 0; + pRef->pUserCallback[uUserIndex] = pCallback; + pRef->pPostCallback[uUserIndex] = NULL; + pRef->pUserData[uUserIndex] = pUserData; + pRef->uUserDataMask[uUserIndex] = USERAPI_MASK_PROFILES; // Mark as batch + + iRet = UserApiPlatRequestProfile(pRef, uUserIndex, pLookupUsers, iLookupUsersLength); + + // clear the context if the request was not successful + if (iRet < 0) + { + pRef->UserContextList[uUserIndex].iTotalRequested = 0; + pRef->UserContextList[uUserIndex].iTotalReceived = 0; + pRef->UserContextList[uUserIndex].iTotalErrors = 0; + pRef->pUserCallback[uUserIndex] = NULL; + pRef->pPostCallback[uUserIndex] = NULL; + pRef->pUserData[uUserIndex] = NULL; + pRef->uUserDataMask[uUserIndex] = 0; + } + + NetCritLeave(&pRef->crit); + + return(iRet); +} + +/*F*************************************************************************************************/ +/*! + \Function UserApiRequestProfileAsync + + \Description + Starts the process to retrieve profile information of players. pLookupUser is a pointer to DirtyUserT. + + \Input *pRef - pointer to UserApiT module reference + \Input uUserIndex - Index used to specify which local user owns the request + \Input *pLookupUser - Pointer to DityUserT + \Input *pCallback - Callback that is going to be called when responses for these requests are received + \Input uUserDataMask - A mask value defining which elements are needed; USERAPI_MASK_* + \Input *pUserData - This pointer is going to be passed to the callback when it is called. This parameter can be NULL + + \Output + int32_t - Return 0 if successful, -1 otherwise. + + \Version 09/12/2013 (amakoukji) - First version +*/ +/*************************************************************************************************F*/ +int32_t UserApiRequestProfileAsync(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUser, UserApiCallbackT *pCallback, uint32_t uUserDataMask, void *pUserData) +{ + return(UserApiRequestPresenceAsync(pRef, uUserIndex, pLookupUser, pCallback, uUserDataMask, pUserData)); +} + +/*F*************************************************************************************************/ +/*! + \Function UserApiRequestPresenceAsync + + \Description + Starts the process to retrieve profile information of players. pLookupUser is a pointer to DirtyUserT. + + \Input *pRef - pointer to UserApiT module reference + \Input uUserIndex - Index used to specify which local user owns the request + \Input *pLookupUser - Pointer to DityUserT + \Input *pCallback - Callback that is going to be called when responses for these requests are received + \Input uUserDataMask - A mask value defining which elements are needed; USERAPI_MASK_* + \Input *pUserData - This pointer is going to be passed to the callback when it is called. This parameter can be NULL + + \Output + int32_t - Return 0 if successful, -1 otherwise. + + \Version 09/12/2013 (amakoukji) - First version +*/ +/*************************************************************************************************F*/ +int32_t UserApiRequestPresenceAsync(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUser, UserApiCallbackT *pCallback, uint32_t uUserDataMask, void *pUserData) +{ + // Start 2 seperate queries, one for a single profile and another for presence / rich presence if necessary + int32_t iReturn = 0; + if (uUserDataMask == 0) + { + // nothing to do + NetPrintf(("userapi: [%p] query without a mask value submitted (user %u)\n", pRef, uUserIndex)); + return(-4); + } + + if (pLookupUser == NULL) + { + NetPrintf(("userapi: [%p] invalid pointer to DirtyUserT list\n", pRef)); + return(-5); + } + + if (pCallback == NULL) + { + NetPrintf(("userapi: [%p] invalid pointer to callback\n", pRef)); + return(-3); + } + + + NetCritEnter(&pRef->crit); + + if (pRef->UserContextList[uUserIndex].iTotalRequested > 0 || pRef->UserPresenceList[uUserIndex].iTotalRequested > 0) + { + NetPrintf(("userapi: [%p] module is already handling a request for user (%u)\n", pRef, uUserIndex)); + NetCritLeave(&pRef->crit); + return(-2); + } + + pRef->pUserCallback[uUserIndex] = pCallback; + pRef->pPostCallback[uUserIndex] = NULL; + pRef->pUserData[uUserIndex] = pUserData; + pRef->uUserDataMask[uUserIndex] = uUserDataMask; + + if ( uUserDataMask & USERAPI_MASK_PROFILE) + { + pRef->UserContextList[uUserIndex].iTotalRequested = 1; + pRef->UserContextList[uUserIndex].iTotalReceived = 0; + pRef->UserContextList[uUserIndex].iTotalErrors = 0; + + if ((iReturn = UserApiPlatRequestProfile(pRef, uUserIndex, pLookupUser, 1)) < 0) + { + _ClearContext(pRef, (int32_t)uUserIndex); + NetCritLeave(&pRef->crit); + return(iReturn); + } + } + + if ( (uUserDataMask & USERAPI_MASK_PRESENCE)) + { + pRef->UserPresenceList[uUserIndex].iTotalRequested = 1; + pRef->UserPresenceList[uUserIndex].iTotalReceived = 0; + pRef->UserPresenceList[uUserIndex].iTotalErrors = 0; + + if ((iReturn = UserApiPlatRequestPresence(pRef, uUserIndex, pLookupUser)) < 0) + { + _ClearContext(pRef, (int32_t)uUserIndex); + NetCritLeave(&pRef->crit); + return(iReturn); + } + } + + if ((uUserDataMask & USERAPI_MASK_RICH_PRESENCE)) + { + pRef->UserRichPresenceList[uUserIndex].iTotalRequested = 1; + pRef->UserRichPresenceList[uUserIndex].iTotalReceived = 0; + pRef->UserRichPresenceList[uUserIndex].iTotalErrors = 0; + + if ((iReturn = UserApiPlatRequestRichPresence(pRef, uUserIndex, pLookupUser)) < 0) + { + _ClearContext(pRef, (int32_t)uUserIndex); + NetCritLeave(&pRef->crit); + return(iReturn); + } + } + + NetCritLeave(&pRef->crit); + + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function UserApiRequestRichPresenceAsync + + \Description + Starts the process to retrieve rich presence information of players. + + \Input *pRef - pointer to UserApiT module reference + \Input uUserIndex - Index used to specify which local user owns the request + \Input *pLookupUser - Pointer to DirtyUserT + \Input *pCallback - Callback that is going to be called when responses for these requests are received + \Input uUserDataMask - A mask value defining which elements are needed; USERAPI_MASK_* + \Input *pUserData - This pointer is going to be passed to the callback when it is called. This parameter can be NULL + + \Output + int32_t - Return 0 if successful, -1 otherwise. + + \Notes + For PS4: + strData contains GameStatus + pData == NULL means delete the rich presence + For XB1: + strData contains the Rich Presence String as defined in your Service Config Workbook + More complex rich presence requests for XB1 will need to be set through their API directly + + \Version 09/12/2013 (amakoukji) - First version +*/ +/*************************************************************************************************F*/ +int32_t UserApiRequestRichPresenceAsync(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUser, UserApiCallbackT *pCallback, uint32_t uUserDataMask, void *pUserData) +{ + return(UserApiRequestProfileAsync(pRef, uUserIndex, pLookupUser, pCallback, uUserDataMask , pUserData)); +} + +/*F*************************************************************************************************/ +/*! + \Function UserApiPostRecentlyMetAsync + + \Description + Starts the process to post that a certain player was recently encountered. + + \Input *pRef - pointer to UserApiT module reference + \Input uUserIndex - Index used to specify which local user owns the request + \Input *pPlayerMet - Pointer to the DityUserT to add to the recently met players list + \Input *pAdditionalInfo - Pointer to additional info container which a platform may require + \Input *pCallback - Callback that is going to be called when responses for these requests are received + \Input *pUserData - This pointer is going to be passed to the callback when it is called. This parameter can be NULL + + \Output + int32_t - Return 0 if successful, -1 otherwise. + + \Version 06/16/2013 (amakoukji) - First version +*/ +/*************************************************************************************************F*/ +int32_t UserApiPostRecentlyMetAsync(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pPlayerMet, void *pAdditionalInfo, UserApiPostCallbackT *pCallback, void *pUserData) +{ + int32_t result = 0; + if (pRef == NULL) + { + NetPrintf(("userapi: UserApiRecentlyMetAsync() invalid reference pointer\n")); + return(-1); + } + + if (uUserIndex >= NETCONN_MAXLOCALUSERS) + { + NetPrintf(("userapi: [%p] module, invalid user index (%d)\n", pRef, uUserIndex)); + return(-1); + } + + if (pPlayerMet == NULL) + { + NetPrintf(("userapi: [%p] module, UserApiRecentlyMetAsync() invalid DirtyUserT pointer [%p]\n", pRef, pPlayerMet)); + return(-1); + } + + NetCritEnter(&pRef->postCrit); + + if (pRef->UserRmpList[uUserIndex].iTotalRequested > 0) + { + NetPrintf(("userapi: [%p] module is already handling a recently met player request for user (%d)\n", pRef, uUserIndex)); + NetCritLeave(&pRef->postCrit); + return(-2); + } + + pRef->UserRmpList[uUserIndex].iTotalRequested = 1; + pRef->UserRmpList[uUserIndex].iTotalReceived = 0; + pRef->UserRmpList[uUserIndex].iTotalErrors = 0; + pRef->pPostCallback[uUserIndex] = pCallback; + pRef->pUserDataPost[uUserIndex] = pUserData; + + result = UserApiPlatRequestRecentlyMet(pRef, (int32_t)uUserIndex, pPlayerMet, pAdditionalInfo); + + if (result < 0) + { + // An error occured, reset + pRef->UserRmpList[uUserIndex].iTotalRequested = 0; + pRef->UserRmpList[uUserIndex].iTotalReceived = 0; + pRef->UserRmpList[uUserIndex].iTotalErrors = 0; + pRef->pPostCallback[uUserIndex] = NULL; + pRef->pUserDataPost[uUserIndex] = NULL; + } + + NetCritLeave(&pRef->postCrit); + + return(result); +} + +/*F*************************************************************************************************/ +/*! + \Function UserApiPostRichPresenceAsync + + \Description + Starts the process to post that a certain player was recently encountered. + + \Input *pRef - pointer to UserApiT module reference + \Input uUserIndex - Index used to specify which local user owns the request + \Input *pData - Rich Presence to post + \Input *pCallback - Callback that is going to be called when responses for these requests are received + \Input *pUserData - This pointer is going to be passed to the callback when it is called. This parameter can be NULL + + \Output + int32_t - Return 0 if successful, -1 otherwise. + + \Version 06/16/2013 (amakoukji) - First version +*/ +/*************************************************************************************************F*/ +int32_t UserApiPostRichPresenceAsync(UserApiRefT *pRef, uint32_t uUserIndex, UserApiRichPresenceT *pData, UserApiPostCallbackT *pCallback, void *pUserData) +{ + int32_t result = 0; + if (pRef == NULL) + { + NetPrintf(("userapi: UserApiPostRichPresenceAsync() invalid reference pointer\n")); + return(-1); + } + + if (uUserIndex >= NETCONN_MAXLOCALUSERS) + { + NetPrintf(("userapi: [%p] module, invalid user index (%d)\n", pRef, uUserIndex)); + return(-2); + } + + NetCritEnter(&pRef->postCrit); + + if (pRef->UserRmpList[uUserIndex].iTotalRequested > 0) + { + NetPrintf(("userapi: [%p] module is already handling a recently met player request for user (%d)\n", pRef, uUserIndex)); + NetCritLeave(&pRef->postCrit); + return(-2); + } + + pRef->UserRmpList[uUserIndex].iTotalRequested = 1; + pRef->UserRmpList[uUserIndex].iTotalReceived = 0; + pRef->UserRmpList[uUserIndex].iTotalErrors = 0; + pRef->pPostCallback[uUserIndex] = pCallback; + pRef->pUserDataPost[uUserIndex] = pUserData; + + result = UserApiPlatRequestPostRichPresence(pRef, (int32_t)uUserIndex, pData); + + NetCritLeave(&pRef->postCrit); + + return(result); +} + +/*F*************************************************************************************************/ +/*! + \Function UserApiRegisterUpdateEvent + + \Description + Register for notifications from first part + + \Input *pRef - pointer to UserApiT module reference + \Input uUserIndex - Index used to specify which local user owns the request + \Input eType - type of event to register for + \Input *pNotifyCb - Callback that is going to be called when responses for these requests are received + \Input *pUserData - This pointer is going to be passed to the callback when it is called. This parameter can be NULL + + \Output + int32_t - Return 0 if successful, -1 otherwise. + + \Version 06/16/2013 (amakoukji) - First version +*/ +/*************************************************************************************************F*/ +int32_t UserApiRegisterUpdateEvent(UserApiRefT *pRef, uint32_t uUserIndex, UserApiNotifyTypeE eType, UserApiUpdateCallbackT *pNotifyCb, void *pUserData) +{ + int32_t iReturn = USERAPI_ERROR_OK; + UserApiNotificationT (*pList)[USERAPI_NOTIFY_LIST_MAX_SIZE] = NULL; + uint8_t bNeedsInit = TRUE; + int32_t i = 0; + + if (eType == USERAPI_NOTIFY_PRESENCE_UPDATE) + { + pList = &pRef->PresenceNotification; + if (pRef->bPresenceNotificationStarted) + { + bNeedsInit = FALSE; + } + pRef->bPresenceNotificationStarted = TRUE; + } + else if (eType == USERAPI_NOTIFY_TITLE_UPDATE) + { + pList = &pRef->TitleNotification; + if (pRef->bTitleNotificationStarted) + { + bNeedsInit = FALSE; + } + pRef->bTitleNotificationStarted = TRUE; + } + else if (eType == USERAPI_NOTIFY_RICH_PRESENCE_UPDATE) + { + pList = &pRef->RichPresenceNotification; + if (pRef->bRichPresenceNotificationStarted) + { + bNeedsInit = FALSE; + } + pRef->bRichPresenceNotificationStarted = TRUE; + } + else if (eType == USERAPI_NOTIFY_PROFILE_UPDATE) + { + pList = &pRef->ProfileUpdateNotification; + if (pRef->bProfileUpdateNotificationStarted) + { + bNeedsInit = FALSE; + } + pRef->bProfileUpdateNotificationStarted = TRUE; + } + + else + { + return(USERAPI_ERROR_UNSUPPORTED); + } + + if (pList == NULL) + { + return(USERAPI_ERROR_FULL); + } + + if (bNeedsInit) + { + iReturn = UserApiPlatRegisterUpdateEvent(pRef, uUserIndex, eType); + } + + if (iReturn >= 0) + { + for (i = 0; i < USERAPI_NOTIFY_LIST_MAX_SIZE; ++i) + { + if ((*pList)[i].pCallback == pNotifyCb && (*pList)[i].pUserData == pUserData && (*pList)[i].uUserIndex == uUserIndex) + { + // callback already setup + break; + } + + if ((*pList)[i].pCallback == NULL) + { + (*pList)[i].pCallback = pNotifyCb; + (*pList)[i].pUserData = pUserData; + (*pList)[i].uUserIndex = uUserIndex; + break; + } + } + } + + return(iReturn); +} + +/*F*************************************************************************************************/ +/*! + \Function UserApiCancel + + \Description + Cancel all queries to the 1st party. + + \Input *pRef - pointer to UserApiT module reference + \Input uUserIndex - index of user associated with this request + + \Output + int32_t - result of abort request; see UserApiPlatAbortRequests() + + \Version 05/10/2013 (mcorcoran) - First version +*/ +/*************************************************************************************************F*/ +int32_t UserApiCancel(UserApiRefT *pRef, uint32_t uUserIndex) +{ + _ClearContext(pRef, (int32_t)uUserIndex); + pRef->pUserCallback[uUserIndex] = NULL; + pRef->pPostCallback[uUserIndex] = NULL; + pRef->pUserDataPost[uUserIndex] = NULL; + return(UserApiPlatAbortRequests(pRef, (int32_t)uUserIndex)); +} + +/*F*************************************************************************************************/ +/*! + \Function UserApiUpdate + + \Description + Update the internal state of the module, and call registered UserApiCallbackT callback if there are GamerCard/Profile responses available. This function should be called periodically. + + \Input *pRef - pointer to UserApiT module reference + + \Version 05/10/2013 (mcorcoran) - First version +*/ +/*************************************************************************************************F*/ +void UserApiUpdate(UserApiRefT *pRef) +{ + UserApiUserDataT UserData; + UserApiProfileT *pProfileData = &UserData.Profile; + UserApiPresenceT *pPresenceData = &UserData.Presence; + UserApiRichPresenceT *pRichPresenceData = &UserData.RichPresence; + int32_t iProcessingResult = 0; + int32_t i = 0; + int32_t iInnerLoop = 0; + + if (pRef == NULL) + { + NetPrintf(("userapi: invalid reference pointer\n")); + return; + } + + UserApiPlatUpdate(pRef); + + for (i = 0; i < NETCONN_MAXLOCALUSERS; ++i) + { + NetCritEnter(&pRef->crit); + + ds_memclr(&UserData, sizeof(UserData)); + UserData.uUserDataMask = pRef->uUserDataMask[i]; + + // Check for batch profile fetch. + // This is treated seperately because it never needs to wait for other 1st party requests to finish + if (pRef->uUserDataMask[i] == USERAPI_MASK_PROFILES) + { + if (pRef->bAvailableDataIndex[i] > 0) + { + iProcessingResult = _UserApiProcessProfileResponse(pRef, i, TRUE, pProfileData, &UserData); + + // If the processing failed report it and cancel all further 1st party requests + if (iProcessingResult < 0) + { + _UserApiTriggerCallback(pRef, i, iProcessingResult, USERAPI_EVENT_END_OF_LIST, &UserData); + _ClearContext(pRef, i); + } + // If all the requested results have been accounted for send the "list end" callback + else if (pRef->UserContextList[i].iTotalRequested == (pRef->UserContextList[i].iTotalReceived + pRef->UserContextList[i].iTotalErrors)) + { + _UserApiTriggerCallback(pRef, i, USERAPI_ERROR_OK, USERAPI_EVENT_END_OF_LIST, &UserData); + _ClearContext(pRef, i); + } + // If all requests are not complete for the user, mark the lookup available as true in order to continue + else + { + pRef->bLookupUserAvailable[i] = TRUE; + } + + pRef->bAvailableDataIndex[i] = FALSE; + pRef->bAvailableDataIndexPresence[i] = FALSE; + pRef->bAvailableDataIndexRichPresence[i] = FALSE; + } + } + + // then check individual requests which may need to wait for several 1st party queries to finish + else if ((pRef->uUserDataMask[i] & USERAPI_MASK_PROFILE) || (pRef->uUserDataMask[i] & USERAPI_MASK_PRESENCE) || (pRef->uUserDataMask[i] & USERAPI_MASK_RICH_PRESENCE)) + { + if ( ((pRef->bAvailableDataIndex[i] > 0 && (pRef->uUserDataMask[i] & USERAPI_MASK_PROFILE)) || !(pRef->uUserDataMask[i] & USERAPI_MASK_PROFILE)) + && ((pRef->bAvailableDataIndexPresence[i] > 0 && (pRef->uUserDataMask[i] & USERAPI_MASK_PRESENCE)) || !(pRef->uUserDataMask[i] & USERAPI_MASK_PRESENCE)) + && ((pRef->bAvailableDataIndexRichPresence[i] > 0 && (pRef->uUserDataMask[i] & USERAPI_MASK_RICH_PRESENCE)) || !(pRef->uUserDataMask[i] & USERAPI_MASK_RICH_PRESENCE)) ) + { + // At this point all 1st party queries are complete + // Parse the data, report it to the user and reset + iProcessingResult = 0; + + if (pRef->uUserDataMask[i] & USERAPI_MASK_PROFILE) + { + iProcessingResult = _UserApiProcessProfileResponse(pRef, i, FALSE, pProfileData, &UserData); + } + + if (pRef->uUserDataMask[i] & USERAPI_MASK_PRESENCE && iProcessingResult >= 0) + { + iProcessingResult = _UserApiProcessPresenceResponse(pRef, i, pPresenceData, &UserData); + } + + if (pRef->uUserDataMask[i] & USERAPI_MASK_RICH_PRESENCE && iProcessingResult >= 0) + { + iProcessingResult = _UserApiProcessRichPresenceResponse(pRef, i, pRichPresenceData, &UserData); + } + + // Callback + _UserApiTriggerCallback(pRef, i, (iProcessingResult >= 0) ? USERAPI_ERROR_OK : iProcessingResult, + (iProcessingResult >= 0) ? USERAPI_EVENT_DATA : USERAPI_EVENT_END_OF_LIST, &UserData); + + // Clean up + _ClearContext(pRef, i); + pRef->bAvailableDataIndex[i] = FALSE; + pRef->bAvailableDataIndexPresence[i] = FALSE; + pRef->bAvailableDataIndexRichPresence[i] = FALSE; + } + } + + NetCritLeave(&pRef->crit); + } + + // Handle Posted responses + NetCritEnter(&pRef->postCrit); + for (i = 0; i < NETCONN_MAXLOCALUSERS; ++i) + { + + if (pRef->bAvailableDataIndexRMP[i] != FALSE) + { + _UserApiTriggerPostCallback(pRef, i); + + // Clear context + pRef->UserRmpList[i].iTotalRequested = 0; + pRef->UserRmpList[i].iTotalReceived = 0; + pRef->UserRmpList[i].iTotalErrors = 0; + pRef->bAvailableDataIndexRMP[i] = FALSE; + } + } + + // finally process notifications from Sony + if (pRef->UserApiNotifyEvent[0].pNotificationData != NULL) + { + int32_t iOuterLoop = 0; + // at least one notification is in the list, do processing + for (; iOuterLoop < USERAPI_MAX_QUEUED_NOTIFICATIONS; ++iOuterLoop) + { + if (pRef->UserApiNotifyEvent[iOuterLoop].pNotificationData != NULL) + { + uint32_t uNotficationUserId = pRef->UserApiNotifyEvent[iOuterLoop].uUserIndex; + // dispatch notifications + for (iInnerLoop = 0; iInnerLoop < USERAPI_NOTIFY_LIST_MAX_SIZE; ++iInnerLoop) + { + if ((*(pRef->UserApiNotifyEvent[iOuterLoop].pNotificationList))[iInnerLoop].pCallback != NULL) + { + // if the notification is for the requestor of the callback + if (uNotficationUserId == (*(pRef->UserApiNotifyEvent[iOuterLoop].pNotificationList))[iInnerLoop].uUserIndex) + { + (*(pRef->UserApiNotifyEvent[iOuterLoop].pNotificationList))[iInnerLoop].pCallback(pRef, + pRef->UserApiNotifyEvent[iOuterLoop].pNotificationType, + pRef->UserApiNotifyEvent[iOuterLoop].pNotificationData, + (*(pRef->UserApiNotifyEvent[iOuterLoop].pNotificationList))[iInnerLoop].pUserData); + } + } + } + + // clean up + DirtyMemFree(pRef->UserApiNotifyEvent[iOuterLoop].pNotificationData, USERLISTAPI_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + pRef->UserApiNotifyEvent[iOuterLoop].pNotificationData = NULL; + pRef->UserApiNotifyEvent[iOuterLoop].pNotificationList = NULL; + } + } + } + + NetCritLeave(&pRef->postCrit); + +} diff --git a/src/thirdparty/dirtysdk/source/misc/userapipriv.h b/src/thirdparty/dirtysdk/source/misc/userapipriv.h new file mode 100644 index 00000000..a59b2635 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/misc/userapipriv.h @@ -0,0 +1,149 @@ +/*H*************************************************************************************************/ +/*! + + \File userapipriv.h + + \Description + Expose first party player information + + \Notes + None. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2001-2013. ALL RIGHTS RESERVED. + + \Version 05/10/2013 (mcorcoran) First Version + +*/ +/*************************************************************************************************H*/ + +#ifndef _userapipriv_h +#define _userapipriv_h + +/*** Include files ********************************************************************************/ + +#include "DirtySDK/misc/userapi.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/netconn.h" + +/*** Defines **************************************************************************************/ + +#define USERAPI_MAX_QUEUED_NOTIFICATIONS (100) + +/*** Macros ***************************************************************************************/ + +/*** Type Definitions *****************************************************************************/ + +typedef struct UserApiUserContextT +{ + int32_t iTotalRequested; //!< the total number of user profiles that are being looked up + int32_t iTotalReceived; //!< the number of user profiles received + int32_t iTotalErrors; //!< the number erros that have occured +} UserApiUserContextT; + +typedef struct UserApiUserContextRMPT +{ + int32_t iTotalRequested; //!< the total number of user profiles that are being looked up + int32_t iTotalReceived; //!< the number of user profiles received + int32_t iTotalErrors; //!< the number erros that have occured + UserApiPostCallbackT *pUserCallback; //!< callback to user code that will be called when data is available during a call to UserApiUpdate() + void *pUserData; //!< user data for the callback +} UserApiUserContextRMPT; + +typedef struct UserApiNotificationT +{ + UserApiUpdateCallbackT *pCallback; //!< function address + void *pUserData; //!< user data to return + uint32_t uUserIndex; //!< user index of the requester +} UserApiNotificationT; + +typedef struct UserApiNotifyEventT +{ + UserApiNotifyDataT *pNotificationData; + UserApiNotificationT (*pNotificationList)[]; + UserApiNotifyTypeE pNotificationType; + uint32_t uUserIndex; +} UserApiNotifyEventT; + +typedef struct UserApiPlatformDataT UserApiPlatformDataT; + +struct UserApiRefT +{ + int32_t iMemGroup; //!< dirtymem memory group + void *pMemGroupUserData; //!< dirtymem memory group user data + NetCritT crit; //!< sychronize shared data between the threads for profiles + NetCritT postCrit; //!< sychronize shared data between the threads for POSTing data + uint8_t bShuttingDown; + + UserApiUserContextT UserContextList[NETCONN_MAXLOCALUSERS]; //!< per local user data for profile requests + UserApiUserContextT UserPresenceList[NETCONN_MAXLOCALUSERS]; //!< per local user data for presence requests + UserApiUserContextT UserRichPresenceList[NETCONN_MAXLOCALUSERS]; //!< per local user data for presence requests + UserApiUserContextT UserRmpList[NETCONN_MAXLOCALUSERS]; //!< per local user data for recently met player requests + UserApiCallbackT *pUserCallback[NETCONN_MAXLOCALUSERS]; //!< callback to user code that will be called when data is available during a call to UserApiUpdate() + UserApiPostCallbackT *pPostCallback[NETCONN_MAXLOCALUSERS]; //!< callback to user code that will be called when data is available for POSTs + void *pUserData[NETCONN_MAXLOCALUSERS]; //!< user data for the callback + void *pUserDataPost[NETCONN_MAXLOCALUSERS]; //!< user data for the callback for POSTs + uint32_t uUserDataMask[NETCONN_MAXLOCALUSERS]; //!> request mask + + volatile uint8_t bAvailableDataIndex[NETCONN_MAXLOCALUSERS]; //!< mask denoting which user has data waiting to pick up + volatile uint8_t bAvailableDataIndexPresence[NETCONN_MAXLOCALUSERS]; //!< mask denoting which user has data waiting to pick up + volatile uint8_t bAvailableDataIndexRichPresence[NETCONN_MAXLOCALUSERS]; //!< mask denoting which user has data waiting to pick up + volatile uint8_t bAvailableDataIndexRMP[NETCONN_MAXLOCALUSERS]; //!< mask denoting which user has data waiting to pick up + UserApiPlatformDataT *pPlatformData; + + DirtyUserT *aLookupUsers[NETCONN_MAXLOCALUSERS]; + int32_t iLookupUsersLength[NETCONN_MAXLOCALUSERS]; + int32_t iLookupUserIndex[NETCONN_MAXLOCALUSERS]; + uint8_t bLookupUserAvailable[NETCONN_MAXLOCALUSERS]; + int32_t iLookupsSent[NETCONN_MAXLOCALUSERS]; + uint8_t bLookupRmpAvailable[NETCONN_MAXLOCALUSERS]; + + // Callbacks + UserApiNotificationT PresenceNotification[USERAPI_NOTIFY_LIST_MAX_SIZE]; + UserApiNotificationT TitleNotification[USERAPI_NOTIFY_LIST_MAX_SIZE]; + UserApiNotificationT RichPresenceNotification[USERAPI_NOTIFY_LIST_MAX_SIZE]; + UserApiNotificationT ProfileUpdateNotification[USERAPI_NOTIFY_LIST_MAX_SIZE]; + uint8_t bPresenceNotificationStarted; + uint8_t bTitleNotificationStarted; + uint8_t bRichPresenceNotificationStarted; + uint8_t bProfileUpdateNotificationStarted; + UserApiNotifyEventT UserApiNotifyEvent[USERAPI_MAX_QUEUED_NOTIFICATIONS]; +}; + +/*** Function Prototypes **************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +UserApiPlatformDataT *UserApiPlatCreateData(UserApiRefT *pRef); +int32_t UserApiPlatDestroyData(UserApiRefT *pRef, UserApiPlatformDataT *pPlatformData); +int32_t UserApiPlatRequestProfile(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUsers, int32_t iLookupUsersLength); +int32_t UserApiPlatAbortRequests(UserApiRefT *pRef, uint32_t uUserIndex); +int32_t UserApiPlatRequestPresence(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUsers); +int32_t UserApiPlatRequestRichPresence(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pLookupUser); +int32_t UserApiPlatRequestRecentlyMet(UserApiRefT *pRef, uint32_t uUserIndex, DirtyUserT *pPlayerMet, void *pAdditionalInfo); +int32_t UserApiPlatRequestPostRichPresence(UserApiRefT *pRef, uint32_t uUserIndex, UserApiRichPresenceT *pData); + +int32_t _UserApiPlatAbortPostRequests(UserApiRefT *pRef, uint32_t uUserIndex); + +int32_t _UserApiProcessProfileResponse(UserApiRefT *pRef, int32_t uUserIndex, uint8_t bBatch, UserApiProfileT *ProfileData, UserApiUserDataT *pUserData); +int32_t _UserApiProcessPresenceResponse(UserApiRefT *pRef, int32_t uUserIndex, UserApiPresenceT *pPresenceData, UserApiUserDataT *pUserData); +int32_t _UserApiProcessRichPresenceResponse(UserApiRefT *pRef, int32_t uUserIndex, UserApiRichPresenceT *pRichPresenceData, UserApiUserDataT *pUserData); +int32_t _UserApiProcessRmpResponse(UserApiRefT *pRef, uint32_t uUserIndex); + +void _UserApiTriggerCallback(UserApiRefT *pRef, uint32_t uUserIndex, UserApiEventErrorE eError, UserApiEventTypeE eType, UserApiUserDataT *pUserData); +void _UserApiTriggerPostCallback(UserApiRefT *pRef, uint32_t uUserIndex); + +int32_t UserApiPlatRegisterUpdateEvent(UserApiRefT *pRef, uint32_t uUserIndex, UserApiNotifyTypeE eType); + +//!< Control behavior of module +int32_t UserApiPlatControl(UserApiRefT *pRef, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); + +int32_t UserApiPlatUpdate(UserApiRefT *pRef); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/thirdparty/dirtysdk/source/misc/weblog.c b/src/thirdparty/dirtysdk/source/misc/weblog.c new file mode 100644 index 00000000..4c1199bc --- /dev/null +++ b/src/thirdparty/dirtysdk/source/misc/weblog.c @@ -0,0 +1,448 @@ +/*H********************************************************************************/ +/*! + \File weblog.c + + \Description + Captures DirtySDK debug output and posts it to a webserver where the output + can be retrieved. This is useful when debugging on a system with no + debugging capability or in a "clean room" environment, for example. Two + basic mechanisms are employed; the first is a NetPrint debug hook to capture + all debug output, the second a stand-alone WebOfferPrintf() function that + can be used more selectively. + + \Notes + A small local buffer is required to store the output before it is submitted + to ProtoHttp for sending. This is to avoid reentrancy issues where debug + output from ProtoHttp or lower-level modules (ProtoSSL, Crypt*, etc) would + put us into an infinite recursion. Instead the text is simply buffered and + flushed at regular intervals by the WebLogUpdate() function. + + \Copyright + Copyright (c) 2008 Electronic Arts Inc. + + \Version 05/06/2008 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/misc/weblog.h" +#include "DirtySDK/proto/protohttp.h" + + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +struct WebLogRefT +{ + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + ProtoHttpRefT *pProtoHttp; //!< http module ref + NetCritT WebCrit; //!< critical section to guard weblog buffer + int32_t iBufLen; //!< string buffer length + char strWebHost[128]; //!< webhost to post to + char strWebUrl[128]; //!< weburl for post + + uint8_t bLogging; //!< TRUE if logging is enabled, else FALSE + uint8_t bPosting; //!< TRUE if in posting state, else FALSE + uint8_t bUpdating; //!< TRUE if in WebLogUpdate(), else FALSE + uint8_t _pad; + + char strText[1]; //!< variable-length string buffer (must come last!) +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _WebLogFlush + + \Description + Flush weblog text to ProtoHttp. + + \Input *pWebLog - module state + + \Output + None. + + \Version 05/06/2008 (jbrookes) +*/ +/********************************************************************************F*/ +static void _WebLogFlush(WebLogRefT *pWebLog) +{ + int32_t iSendSize = (int32_t)strlen(pWebLog->strText), iSentSize; + + // if nothing to do, don't send + if (iSendSize == 0) + { + return; + } + + // if logging is enabled but we aren't posting yet, start posting + if ((pWebLog->bLogging == TRUE) && (pWebLog->bPosting == FALSE)) + { + char strUrl[256]; + // format url + ds_snzprintf(strUrl, sizeof(strUrl), "http://%s%s", pWebLog->strWebHost, pWebLog->strWebUrl); + // make client timeout very long (one hour) + ProtoHttpControl(pWebLog->pProtoHttp, 'time', 60*60*1000, 0, NULL); + //$$ hack -- set keepalive to 2 so we don't get a Connection: Close header + ProtoHttpControl(pWebLog->pProtoHttp, 'keep', 2, 0, NULL); + // start the post request + ProtoHttpPost(pWebLog->pProtoHttp, strUrl, NULL, -1, FALSE); + pWebLog->bPosting = TRUE; + } + + // send the data + iSentSize = ProtoHttpSend(pWebLog->pProtoHttp, pWebLog->strText, iSendSize); + + // remove sent data from buffer + if (iSentSize > 0) + { + if (iSentSize == iSendSize) + { + // clear the buffer + pWebLog->strText[0] = '\0'; + + // if we aren't logging, end the transaction + if (pWebLog->bLogging == FALSE) + { + NetPrintf(("weblog: ending streaming transmission\n")); + ProtoHttpSend(pWebLog->pProtoHttp, NULL, 0); + } + } + else + { + // contract buffer (include null character) + memmove(pWebLog->strText, pWebLog->strText + iSentSize, iSendSize - iSentSize + 1); + } + } + else if (iSentSize < 0) + { + NetPrintf(("weblog: error %d trying to send; resetting state to try a new post operation\n", iSentSize)); + pWebLog->bPosting = FALSE; + } +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function WebLogCreate + + \Description + Create the WebLog module. + + \Input iBufSize - local text buffer size + + \Output + WebLogRefT * - pointer to module state, or NULL + + \Version 05/06/2008 (jbrookes) +*/ +/********************************************************************************F*/ +WebLogRefT *WebLogCreate(int32_t iBufSize) +{ + WebLogRefT *pWebLog; + int32_t iModuleSize; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // enforce minimum buffer size + if (iBufSize < 4096) + { + iBufSize = 4096; + } + + // allocate and init module state + iModuleSize = sizeof(*pWebLog) + iBufSize - 1; + if ((pWebLog = DirtyMemAlloc(iModuleSize, WEBLOG_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("weblog: could not allocate module state\n")); + return(NULL); + } + ds_memclr(pWebLog, iModuleSize); + pWebLog->iMemGroup = iMemGroup; + pWebLog->pMemGroupUserData = pMemGroupUserData; + pWebLog->iBufLen = iBufSize; + + /* Set default webhost -- easo.stest.ea.com is the VIP address for the EAWS stest web cluster + and should be accessible from any environment. Port 8080 on the VIP is configured to forward + directly to port 8001, which hits Tomcat directly (and bypasses Apache). This is required + because the EAWS version of Apache has a bug when proxying a chunked upload that causes the + chunked encoding to be stripped but a Content-Length: not applied to the forwarded upload */ + ds_strnzcpy(pWebLog->strWebHost, "easo.stest.ea.com:8080", sizeof(pWebLog->strWebHost)); + #if 0 + // stesteasoweb01 bypasses the VIP but is an internal address + ds_strnzcpy(pWebLog->strWebHost, "stesteasoweb01.pt.abn-iad.ea.com:8001", sizeof(pWebLog->strWebHost)); + // eggplant is the dev EAWS server + ds_strnzcpy(pWebLog->strWebHost, "eggplant.online.ea.com", sizeof(pWebLog->strWebHost)); + // eggplant:8001 bypasses Apache and goes directly to Tomcat + ds_strnzcpy(pWebLog->strWebHost, "eggplant.online.ea.com:8001", sizeof(pWebLog->strWebHost)); + #endif + + // set default URL + ds_strnzcpy(pWebLog->strWebUrl, "/tool/easo/logrequest.jsp", sizeof(pWebLog->strWebUrl)); + + // init critical section + NetCritInit(&pWebLog->WebCrit, "WebLog"); + + // create ProtoHttp ref + if ((pWebLog->pProtoHttp = ProtoHttpCreate(4*1024)) == NULL) + { + NetPrintf(("weblog: could not allocate http ref\n")); + WebLogDestroy(pWebLog); + return(NULL); + } + + // return module state to caller + return(pWebLog); +} + +/*F********************************************************************************/ +/*! + \Function WebLogConfigure + + \Description + Configure weblog parameters + + \Input *pWebLog - weblog module state + \Input *pServer - server to post log to (NULL to retain current value) + \Input *pUrl - url to post log to (NULL to retain current value) + + \Output + None. + + \Version 05/14/2008 (jbrookes) +*/ +/********************************************************************************F*/ +void WebLogConfigure(WebLogRefT *pWebLog, const char *pServer, const char *pUrl) +{ + if (pServer != NULL) + { + ds_strnzcpy(pWebLog->strWebHost, pServer, sizeof(pWebLog->strWebHost)); + } + if (pUrl != NULL) + { + ds_strnzcpy(pWebLog->strWebUrl, pUrl, sizeof(pWebLog->strWebUrl)); + } +} + +/*F********************************************************************************/ +/*! + \Function WebLogStart + + \Description + Starts logging + + \Input *pWebLog - weblog module state + + \Output + None. + + \Version 05/06/2008 (jbrookes) +*/ +/********************************************************************************F*/ +void WebLogStart(WebLogRefT *pWebLog) +{ + // see if we are already started + if (pWebLog->bLogging) + { + NetPrintf(("weblog: start called when already started\n")); + return; + } + + // start the logging + pWebLog->bLogging = TRUE; + NetPrintf(("weblog: starting log operation\n")); +} + +/*F********************************************************************************/ +/*! + \Function WebLogStop + + \Description + Stop logging and close close the current WebLog transaction (if any) + + \Input *pWebLog - weblog module state + + \Output + None. + + \Version 05/06/2008 (jbrookes) +*/ +/********************************************************************************F*/ +void WebLogStop(WebLogRefT *pWebLog) +{ + // see if we are already stopped + if (!pWebLog->bLogging) + { + NetPrintf(("weblog: stop called when already stopped\n")); + return; + } + + // stop the logging + NetPrintf(("weblog: stopping log operation\n")); + pWebLog->bLogging = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function WebLogDestroy + + \Description + Destroy the WebLog module. + + \Input *pWebLog - pointer to weblog module to destroy + + \Output + None. + + \Version 05/06/2008 (jbrookes) +*/ +/********************************************************************************F*/ +void WebLogDestroy(WebLogRefT *pWebLog) +{ + NetCritKill(&pWebLog->WebCrit); + if (pWebLog->pProtoHttp != NULL) + { + ProtoHttpDestroy(pWebLog->pProtoHttp); + } + DirtyMemFree(pWebLog, WEBLOG_MEMID, pWebLog->iMemGroup, pWebLog->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function WebLogDebugHook + + \Description + WebLog NetPrintf debug hook. + + \Input *pUserData - user data + \Input *pText - debug text + + \Output + int32_t - zero to suppress debug output, else do not suppress + + \Version 05/06/2008 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t WebLogDebugHook(void *pUserData, const char *pText) +{ + WebLogRefT *pWebLog = (WebLogRefT *)pUserData; + WebLogPrintf(pWebLog, "%s", pText); + return(1); +} + +/*F********************************************************************************/ +/*! + \Function WebLogPrintf + + \Description + Print into the weblog buffer. + + \Input *pWebLog - weblog module state + \Input *pFormat - format string + \Input ... - variable argument listing + + \Output + None. + + \Version 05/06/2008 (jbrookes) +*/ +/********************************************************************************F*/ +void WebLogPrintf(WebLogRefT *pWebLog, const char *pFormat, ...) +{ + static char strText[4096]; + va_list pFmtArgs; + + // ensure serial access to text buffer and weblog buffer + NetCritEnter(&pWebLog->WebCrit); + + // format text + va_start(pFmtArgs, pFormat); + ds_vsnzprintf(strText, sizeof(strText), pFormat, pFmtArgs); + va_end(pFmtArgs); + + // queue the text for sending if we are logging + if (pWebLog->bLogging && !pWebLog->bUpdating) + { + ds_strnzcat(pWebLog->strText, strText, pWebLog->iBufLen); + } + + // release mutex + NetCritLeave(&pWebLog->WebCrit); +} + +/*F********************************************************************************/ +/*! + \Function WebLogControl + + \Description + Control weblog behavior + + \Input *pWebLog - weblog module state + \Input iSelect - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + Unhandled selectors are passed through to ProtoHttpControl() + + \Version 05/13/2008 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t WebLogControl(WebLogRefT *pWebLog, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue) +{ + return(ProtoHttpControl(pWebLog->pProtoHttp, iSelect, iValue, iValue2, pValue)); +} + +/*F********************************************************************************/ +/*! + \Function WebLogUpdate + + \Description + Update the WebLog module + + \Input *pWebLog - weblog module state + + \Output + None. + + \Version 05/06/2008 (jbrookes) +*/ +/********************************************************************************F*/ +void WebLogUpdate(WebLogRefT *pWebLog) +{ + NetCritEnter(&pWebLog->WebCrit); + // remember we are updating so weblog doesn't log itself + pWebLog->bUpdating = TRUE; + // flush data to protohttp + _WebLogFlush(pWebLog); + // update ProtoHttp + ProtoHttpUpdate(pWebLog->pProtoHttp); + // no longer updating + pWebLog->bUpdating = FALSE; + NetCritLeave(&pWebLog->WebCrit); +} diff --git a/src/thirdparty/dirtysdk/source/platform/plat-str.c b/src/thirdparty/dirtysdk/source/platform/plat-str.c new file mode 100644 index 00000000..098a50d0 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/platform/plat-str.c @@ -0,0 +1,1877 @@ +/*H********************************************************************************/ +/*! + \File plat-str.c + + \Description + This file provides platform independent versions of some standard-C + functions that are not "standard" across platforms and/or fixes + implementation problems with the standard versions (such as consistent + termination). + + \Copyright + Copyright (c) 2005-2011 Electronic Arts Inc. + + \Version 01/11/2005 (gschaefer) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include +#include // tolower + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtynet.h" // sockaddr + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +//! lowercase hex translation table +static const char _ds_strhexlower[16] = "0123456789abcdef"; +//! uppercase hex translation table +static const char _ds_strhexupper[16] = "0123456789ABCDEF"; + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _ds_writechar + + \Description + Write a character to output buffer, if there is room + + \Input *pBuffer - [out] output buffer + \Input iLength - size of output buffer + \Input cWrite - character to write to output + \Input iOutput - offset in output buffer to write to + + \Output + int32_t - iOutput+1 + + \Version 02/14/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ds_writechar(char *pBuffer, int32_t iLength, char cWrite, int32_t iOutput) +{ + if (iOutput < iLength) + { + pBuffer[iOutput] = cWrite; + } + return(iOutput+1); +} + +/*F********************************************************************************/ +/*! + \Function _ds_writestr + + \Description + Write string to output buffer, supporting a source of chars or wchars and a + destination of chars. As per C99 specifications, if the output does not fit in + the given buffer, this function will return the number of characters that would + have been written if the buffer were big enough, but will truncate the string to + preserve buffer integrity. + + \Input *pBuffer - [out] output buffer + \Input iLength - size of output buffer + \Input *pString - string to print + \Input iOutput - offset in output buffer to print to + \Input bWideChar - if TRUE, input string uses wide (wchar_t) chars + + \Output + int32_t - number of characters written to output buffer + + \Version 12/14/2011 (jbrookes) Extracted from _ds_printstr(), added wchar support +*/ +/********************************************************************************F*/ +static int32_t _ds_writestr(char *pBuffer, int32_t iLength, const char *pString, int32_t iOutput, uint8_t bWideChar) +{ + const wchar_t *pWideStr; + int32_t iIndex; + + if (!bWideChar) + { + for (iIndex = 0; pString[iIndex] != '\0'; iIndex += 1) + { + iOutput = _ds_writechar(pBuffer, iLength, pString[iIndex], iOutput); + } + } + else + { + for (iIndex = 0, pWideStr = (const wchar_t *)pString; pWideStr[iIndex] != '\0\0'; iIndex += 1) + { + iOutput = _ds_writechar(pBuffer, iLength, (char)pWideStr[iIndex], iOutput); + } + } + return(iOutput); +} + +/*F********************************************************************************/ +/*! + \Function _ds_printstr + + \Description + Print string to output buffer, with leading character/justification/padding. + As per C99 specifications, if the output does not fit in the given buffer, + this function will return the number of characters that would have been written + if the buffer were big enough, but will truncate the string to preserve buffer + integrity. + + \Input *pBuffer - [out] output buffer + \Input iLength - size of output buffer + \Input *pString - string to print + \Input iOutput - offset in output buffer to print to + \Input iWidth - field width + \Input bRightJust - if TRUE, right-justify output + \Input bWideChar - if TRUE, input string uses wide (wchar_t) chars + \Input cLead - leading character or zero if none + \Input cPad - character to pad output with if width is larger than string length + + \Output + int32_t - number of characters written to output buffer + + \Version 10/28/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ds_printstr(char *pBuffer, int32_t iLength, const char *pString, int32_t iOutput, int32_t iWidth, uint8_t bRightJust, uint8_t bWideChar, char cPad, char cLead) +{ + int32_t iStrLen; + // allow null pointer for source string + if (pString == NULL) + { + pString = "(null)"; + } + // calculate field width + if (iWidth > 0) + { + iStrLen = (int32_t) strlen(pString); + iWidth = (iStrLen < iWidth) ? iWidth - iStrLen : 0; + } + // handle right justification + if (bRightJust) + { + // handle lead character + if (cLead != 0) + { + // only add the lead character if we are not padding with spaces + if (cPad != ' ') + { + iOutput = _ds_writechar(pBuffer, iLength, cLead, iOutput); + cLead = 0; + } + // always decrement width + if (iWidth > 0) + { + iWidth -= 1; + } + } + for ( ; iWidth > 0; iWidth -= 1) + { + iOutput = _ds_writechar(pBuffer, iLength, cPad, iOutput); + } + } + // handle lead character + if (cLead != 0) + { + iOutput = _ds_writechar(pBuffer, iLength, cLead, iOutput); + // only decrement width if we are not right justfied, in which case we've already done it + if ((bRightJust == FALSE) && (iWidth > 0)) + { + iWidth -= 1; + } + } + // write string to buffer + iOutput = _ds_writestr(pBuffer, iLength, pString, iOutput, bWideChar); + // handle left justification + for ( ; iWidth > 0; iWidth -= 1) + { + iOutput = _ds_writechar(pBuffer, iLength, cPad, iOutput); + } + return(iOutput); +} + +/*F********************************************************************************/ +/*! + \Function _ds_uinttostr + + \Description + Convert an input unsigned integer into a string. + + \Input *pBuffer - [out] output buffer + \Input iBufSize - size of output buffer + \Input uInteger - unsigned integer to print + \Input uBase - output integer base + \Input *pHexTbl - pointer to hex conversion table (upper or lower case) + + \Output + char * - pointer to output string + + \Version 10/28/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_ds_uinttostr(char *pBuffer, int32_t iBufSize, uint64_t uInteger, uint32_t uBase, const char *pHexTbl) +{ + int32_t iIndex = iBufSize-1; + uint32_t uDigit; + + pBuffer[iIndex--] = '\0'; + + if (uInteger != 0) + { + for ( ; ; iIndex -= 1) + { + uDigit = uInteger % uBase; + pBuffer[iIndex] = pHexTbl[uDigit]; + if ((uInteger /= uBase) == 0) + { + break; + } + } + } + else + { + pBuffer[iIndex] = '0'; + } + + return(pBuffer+iIndex); +} + +/*F********************************************************************************/ +/*! + \Function _ds_inttostr + + \Description + Convert an input integer into a string. + + \Input *pBuffer - [out] output buffer + \Input iBufSize - size of output buffer + \Input iInteger - integer to print + \Input uBase - output integer base + \Input *pHexTbl - pointer to hex conversion table (upper or lower case) + \Input bPrintPlus - if TRUE, include + character on positive integers + \Input *pLead - [out] leading character to print (+/- or none) + + \Output + char * - pointer to output string + + \Version 10/28/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_ds_inttostr(char *pBuffer, int32_t iBufSize, int64_t iInteger, uint32_t uBase, const char *pHexTbl, uint8_t bPrintPlus, char *pLead) +{ + uint8_t bNegative = FALSE; + if (iInteger < 0) + { + bNegative = TRUE; + iInteger = -iInteger; + } + pBuffer = _ds_uinttostr(pBuffer, iBufSize, (uint64_t)iInteger, uBase, pHexTbl); + if (bNegative) + { + *pLead = '-'; + } + else if (bPrintPlus) + { + *pLead = '+'; + } + return(pBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _ds_fcvt + + \Description + Convert a double into a string (like fcvt()). + + \Input *pBuffer - [out] output buffer + \Input iBufSize - size of output buffer + \Input fDouble - double to print + \Input iPrecision - number of digits to include in mantissa + \Input *pDecimalPos - [out] storage for decimal point offset + \Input *pSign - [out] 0=positive, 1=negative + + \Output + char * - pointer to output string + + \Notes + Format of a double + seeeeeee eeeeffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff (1 11 52) + + \Version 05/03/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_ds_fcvt(char *pBuffer, int32_t iBufSize, double fDouble, int32_t iPrecision, int32_t *pDecimalPos, int32_t *pSign) +{ + char strFloat[128] = "0", strInt[128], *pStrInt, *pStrFloat, *pStrFloatRnd, *pStrFloatStrt; + uint64_t uDouble, uMantissa, uDivisor, uDigit = 0; + int32_t iExponent, iFloatBufSize = sizeof(strFloat) - 1, iShift; + uint32_t uSign, uUpper; + + ds_memcpy_s(&uDouble, sizeof(uDouble), &fDouble, sizeof(fDouble)); + uUpper = (uint32_t)(uDouble >> 32); + iExponent = (int32_t)((uUpper >> 20) & 0x7ff) - 1023; + uMantissa = ((uDouble << 12) >> 12) | ((uint64_t)1 << 52); + uSign = uUpper >> 31; + + *pSign = uSign; + *pDecimalPos = 0; + pStrFloat = pStrFloatStrt = &strFloat[1]; + + // right-bias mantissa + for (iShift = 0; (uMantissa & 1) != 1 && (52-iExponent-iShift) > 0; iShift += 1) + { + uMantissa >>= 1; + } + + // give us a little more room to handle very large numbers + if (iExponent > 52) + { + if ((iShift = iExponent - 52) > 11) + { + // too big to fit in a 64bit integer + ds_strnzcpy(pBuffer, "(BIG)", iBufSize); + return(pBuffer); + } + // left-bias mantissa to reduce exponent + uMantissa <<= iShift; + iExponent -= iShift; + iShift = 0; + } + + // for very small numbers; right-shift (losing precision) until our integer divisor is within range + for ( ; (52-iExponent-iShift) > 62; iShift += 1) + { + uMantissa >>= 1; + } + + uDivisor = (uint64_t)1 << (52-iExponent-iShift); + + // integer part + if (uMantissa >= uDivisor) + { + uDigit = uMantissa / uDivisor; + pStrInt = _ds_uinttostr(strInt, sizeof(strInt), uDigit, 10, _ds_strhexlower); + ds_strnzcpy(pStrFloat, pStrInt, iFloatBufSize); + *pDecimalPos = (int32_t)strlen(pStrInt); + pStrFloat += *pDecimalPos; + uMantissa -= uDigit*uDivisor; + uDigit = 0; + } + + // fractional part + for ( ; uMantissa > 0; iPrecision -= 1) + { + uMantissa = uMantissa * 10; + uDigit = uMantissa / uDivisor; + if (iPrecision == 0) + { + break; + } + *pStrFloat++ = (uint8_t)(uDigit + '0'); + uMantissa -= uDigit*uDivisor; + } + + // round? + if ((iPrecision == 0) && (uDigit >= 5)) + { + // null-terminate + *pStrFloat = '\0'; + + for (pStrFloatRnd = pStrFloat - 1; ; pStrFloatRnd -= 1) + { + *pStrFloatRnd += 1; + if (*pStrFloatRnd <= '9') + { + break; + } + *pStrFloatRnd = '0'; + } + + if (pStrFloatRnd == strFloat) + { + pStrFloatStrt = strFloat; + *pDecimalPos += 1; + } + } + else + { + // pad with zeros + for ( ; iPrecision > 0; iPrecision -= 1) + { + *pStrFloat++ = '0'; + } + + // null-terminate + *pStrFloat++ = '\0'; + } + + // copy to output and return to caller + ds_strnzcpy(pBuffer, pStrFloatStrt, iBufSize); + return(pBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _ds_floattostr + + \Description + Convert an 32bit floating point number into a string. + + \Input *pBuffer - [out] output buffer + \Input iBufSize - size of output buffer + \Input fDouble - double to print + \Input iPrecision - number of digits to include in mantissa + \Input bPrintPlus - if TRUE, include + character on positive integers + \Input *pLead - [out] leading character to print (+/- or none) + + \Output + char * - pointer to output string + + \Version 05/03/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_ds_floattostr(char *pBuffer, int32_t iBufSize, double fDouble, int32_t iPrecision, uint32_t bPrintPlus, char *pLead) +{ + int32_t iDecimalPos, iSign, iIndex = 0; + char strFloat[128], strFmtFloat[128]; + + // default precision + if (iPrecision < 0) + { + iPrecision = 6; //c99 default + } + + // float to string +#if 0 + ds_strnzcpy(strFloat, fcvt(fDouble, iPrecision, &iDecimalPos, &iSign), sizeof(strFloat)); +#else + _ds_fcvt(strFloat, sizeof(strFloat), fDouble, iPrecision, &iDecimalPos, &iSign); +#endif + + // handle sign + if (iSign != 0) + { + *pLead = '-'; + } + else if (bPrintPlus) + { + *pLead = '+'; + } + + // copy integer portion + if (iDecimalPos > 0) + { + ds_strsubzcpy(&strFmtFloat[iIndex], sizeof(strFmtFloat) - iIndex, strFloat, iDecimalPos); + iIndex += iDecimalPos; + } + else + { + strFmtFloat[iIndex++] = '0'; + } + + // add decimal point and fractional portion + if (strFloat[iDecimalPos] != '\0') + { + strFmtFloat[iIndex++] = '.'; + } + for ( ; iDecimalPos < 0 && iPrecision > 0; iDecimalPos += 1, iPrecision -= 1) + { + strFmtFloat[iIndex++] = '0'; + } + if (iDecimalPos >= 0) + { + ds_strnzcpy(&strFmtFloat[iIndex], &strFloat[iDecimalPos], sizeof(strFmtFloat)-iIndex); + } + else + { + strFmtFloat[iIndex++] = '\0'; + } + + // copy to buffer + ds_strnzcpy(pBuffer, strFmtFloat, iBufSize); + return(pBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _ds_ptrtostr + + \Description + Convert an input integer into a string. + + \Input *pBuffer - [out] output buffer + \Input iBufSize - size of output buffer + \Input *pPointer - pointer value to print + + \Output + char * - pointer to output string + + \Version 10/28/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_ds_ptrtostr(char *pBuffer, int32_t iBufSize, void *pPointer) +{ + if (pPointer == NULL) + { + ds_strnzcpy(pBuffer, "(null)", iBufSize); + return(pBuffer); + } + pBuffer = _ds_uinttostr(pBuffer, iBufSize, (uintptr_t)pPointer, 16, _ds_strhexlower); + return(pBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _ds_sockaddrtostr + + \Description + Convert an input sockaddr into string format (supports AF_INET and AF_INET6) + Important on the nx only AF_INET addresses arew supported + + \Input *pBuffer - [out] output buffer + \Input iBufSize - size of output buffer + \Input *pSockAddr - address to print + + \Output + char * - pointer to output string + + \Notes + Reference: http://tools.ietf.org/html/rfc5952#section-4 + + \Version 04/24/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_ds_sockaddrtostr(char *pBuffer, int32_t iBufSize, struct sockaddr *pSockAddr) +{ + char *pBufStart = pBuffer; + char strPort[16] = "", strAddr[48]; + uint16_t uPort = 0; + + // format port string, if non-zero + #ifdef DIRTYCODE_NX + uPort = SockaddrInGetPort(pSockAddr); + #else + struct sockaddr_in6 *pSockAddr6 = (struct sockaddr_in6 *)pSockAddr; + uPort = (pSockAddr->sa_family == AF_INET) ? SockaddrInGetPort(pSockAddr) : SocketHtons(pSockAddr6->sin6_port); + #endif + + if (uPort != 0) + { + ds_snzprintf(strPort, sizeof(strPort), ":%d", uPort); + } + // format the address + ds_snzprintf(pBuffer, iBufSize, "[%s]%s", SockaddrInGetAddrText(pSockAddr, strAddr, sizeof(strAddr)), strPort); + + // return buffer to caller + return(pBufStart); +} + + +/*F********************************************************************************/ +/*! + \Function _ds_fourcctostr + + \Description + Convert an input fourcc code into a string. Unprintable characters + are converted to asterisks ('*'). + + \Input *pBuffer - [out] output buffer + \Input iBufSize - size of output buffer + \Input uFourCC - fourcc code to print + + \Output + char * - pointer to output string + + \Version 10/28/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_ds_fourcctostr(char *pBuffer, int32_t iBufSize, uint32_t uFourCC) +{ + int32_t iCode; + for (iCode = 3; iCode >= 0; iCode -= 1) + { + pBuffer[iCode] = (char)(uFourCC & 0xff); + uFourCC >>= 8; + if (!isprint((uint8_t)pBuffer[iCode])) + { + pBuffer[iCode] = '*'; + } + } + pBuffer[4] = '\0'; + return(pBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _ds_vsnprintf + + \Description + Replacement for vsnprintf, with some modifications. + + \Input *pBuffer - output buffer + \Input iLength - size of output buffer + \Input *pFormat - format string + \Input Args - variable argument list + + \Output + int32_t - number of characters formatted; if > than the length, output + is truncated and the buffer is null-terminated unless iLength<=0 + + \Notes + Unlike a standard implementation of vsnprintf(), this version always + null-terminates unless the buffer size is zero. Overflow semantics follow + the C99 standard. + + \verbatim + Most ISO printf functionality is supported, with the following exceptions: + + - Floating point is supported with the following limitations: + - All floating-point formatters behave like 'f' + - Very large (> 2^63) floating point numbers will return (BIG) + - The # flag is not supported, although it is consumed. + - Length is not supported, although length specifiers are consumed. + + Additionally, the following extension types replace their normal meaning: + + - %a: print 32-bit address in dot notation (32-bit integer) + - %A: print a sockaddr (IPv4 or IPv6) + - %C: print fourcc code (32-bit integer) + \endverbatim + + \Version 10/28/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ds_vsnprintf(char *pBuffer, int32_t iLength, const char *pFormat, va_list Args) +{ + int32_t iInput, iOutput, iTerm; + char strTempBuf[64], *pString; + char cInput; + + // handle format list + for (iInput = 0, iOutput = 0; ; ) + { + // read input character + if ((cInput = pFormat[iInput++]) == '\0') + { + break; + } + + // process a format specifier + if (cInput == '%') + { + int32_t iPrecision = -1, iSize = 4, iWidth = 0; + uint8_t bRightJust = TRUE, bPrintPlus = FALSE, bWideChar = FALSE; + char cPad = ' ', cLead = 0; + + // handle end of string + if (pFormat[iInput] == '\0') + { + break; + } + + // process %% + if (pFormat[iInput] == '%') + { + iOutput = _ds_writechar(pBuffer, iLength, pFormat[iInput], iOutput); + iInput += 1; + continue; + } + // process flags + if (pFormat[iInput] == '-') + { + iInput += 1; + bRightJust = FALSE; + } + if (pFormat[iInput] == '+') + { + iInput += 1; + bPrintPlus = TRUE; + } + if (pFormat[iInput] == '#') + { + // unsupported + iInput += 1; + } + if (pFormat[iInput] == '0') + { + iInput += 1; + cPad = '0'; + } + // process width + if (pFormat[iInput] == '*') + { + iInput += 1; + iWidth = va_arg(Args, int); + } + for ( ; (pFormat[iInput] >= '0') && (pFormat[iInput] <= '9'); iInput += 1) + { + iWidth *= 10; + iWidth += pFormat[iInput] - '0'; + } + + // precision specifier? + if (pFormat[iInput] == '.') + { + // process period and precision width + for (iPrecision = 0, iInput += 1; (pFormat[iInput] >= '0') && (pFormat[iInput] <= '9'); iInput += 1) + { + iPrecision *= 10; + iPrecision += pFormat[iInput] - '0'; + } + + // process asterisk (arg width) if present + if (pFormat[iInput] == '*') + { + iInput += 1; + iWidth = va_arg(Args, int); + } + } + + // check for length specifier + cInput = pFormat[iInput++]; + if ((cInput == 'h') || (cInput == 'l') || (cInput == 'L') || (cInput == 'z') || (cInput == 'j') || (cInput == 't') || (cInput == 'q') || (cInput == 'I')) + { + if (cInput == 'h') + { + iSize = 2; + } + if (cInput == 'q') + { + iSize = 8; + } + if ((cInput == 'l') && (pFormat[iInput] == 's')) + { + iSize = 8; + } + if (cInput == 'I') + { + if ((pFormat[iInput] == '6') && (pFormat[iInput+1] == '4')) + { + iSize = 8; + } + iInput += 2; + } + cInput = pFormat[iInput++]; + if ((cInput == 'h') || (cInput == 'l')) + { + if (cInput == 'h') + { + iSize = 1; + } + if (cInput == 'l') + { + iSize = 8; + } + cInput = pFormat[iInput++]; + } + } + + // process type specifier + switch(cInput) + { + case 'a': + { + uint32_t uAddress = va_arg(Args, unsigned int); + pString = SocketInAddrGetText(uAddress, strTempBuf, sizeof(strTempBuf)); + iOutput = _ds_printstr(pBuffer, iLength, pString, iOutput, iWidth, bRightJust, bWideChar, cPad, 0); + } + break; + + case 'A': + { + struct sockaddr *pSockAddr = va_arg(Args, struct sockaddr *); + pString = _ds_sockaddrtostr(strTempBuf, sizeof(strTempBuf), pSockAddr); + iOutput = _ds_printstr(pBuffer, iLength, pString, iOutput, iWidth, bRightJust, bWideChar, cPad, 0); + } + break; + + case 'c': + { + strTempBuf[0] = (char)va_arg(Args, int); + strTempBuf[1] = '\0'; + iOutput = _ds_printstr(pBuffer, iLength, strTempBuf, iOutput, iWidth, bRightJust, bWideChar, cPad, 0); + } + break; + + case 'C': + { + uint32_t uFourCC = va_arg(Args, unsigned int); + pString = _ds_fourcctostr(strTempBuf, sizeof(strTempBuf), uFourCC); + iOutput = _ds_printstr(pBuffer, iLength, pString, iOutput, iWidth, bRightJust, bWideChar, cPad, 0); + } + break; + + case 'd': + case 'i': + { + int64_t iInteger = (iSize <= 4) ? va_arg(Args, int) : va_arg(Args, long long); + pString = _ds_inttostr(strTempBuf, sizeof(strTempBuf), iInteger, 10, _ds_strhexlower, bPrintPlus, &cLead); + iOutput = _ds_printstr(pBuffer, iLength, pString, iOutput, iWidth, bRightJust, bWideChar, cPad, cLead); + } + break; + + case 'n': + { + int *pInteger = va_arg(Args, int *); + *pInteger = iOutput; + } + break; + + case 'o': + { + uint64_t uInteger = (iSize <= 4) ? va_arg(Args, unsigned int) : va_arg(Args, unsigned long long); + pString = _ds_uinttostr(strTempBuf, sizeof(strTempBuf), uInteger, 8, _ds_strhexlower); + iOutput = _ds_printstr(pBuffer, iLength, pString, iOutput, iWidth, bRightJust, bWideChar, cPad, 0); + } + break; + + case 'p': + { + void *pPointer = va_arg(Args, void *); + if (pPointer != NULL) + { + iWidth = sizeof(void *) * 2 + 1; // always print full width (+1 for the leading '$') + cLead = '$'; + } + pString = _ds_ptrtostr(strTempBuf, sizeof(strTempBuf), pPointer); + iOutput = _ds_printstr(pBuffer, iLength, pString, iOutput, iWidth, bRightJust, bWideChar, '0', cLead); // always print leading zeros + } + break; + + case 'S': + iSize = 8; + case 's': + { + pString = va_arg(Args, char *); + bWideChar = (iSize == 8) ? TRUE : FALSE; + iOutput = _ds_printstr(pBuffer, iLength, pString, iOutput, iWidth, bRightJust, bWideChar, cPad, 0); + } + break; + + case 'u': + { + uint64_t uInteger = (iSize <= 4) ? va_arg(Args, unsigned int) : va_arg(Args, unsigned long long); + pString = _ds_uinttostr(strTempBuf, sizeof(strTempBuf), uInteger, 10, _ds_strhexlower); + iOutput = _ds_printstr(pBuffer, iLength, pString, iOutput, iWidth, bRightJust, bWideChar, cPad, 0); + } + break; + + case 'x': + case 'X': + { + uint64_t uInteger = (iSize <= 4) ? va_arg(Args, unsigned int) : va_arg(Args, unsigned long long); + pString = _ds_uinttostr(strTempBuf, sizeof(strTempBuf), uInteger, 16, cInput == 'x' ? _ds_strhexlower : _ds_strhexupper); + iOutput = _ds_printstr(pBuffer, iLength, pString, iOutput, iWidth, bRightJust, bWideChar, cPad, 0); + } + break; + + // all floating-point handled like 'f' + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + { + double fDouble = va_arg(Args, double); + pString = _ds_floattostr(strTempBuf, sizeof(strTempBuf), fDouble, iPrecision, bPrintPlus, &cLead); + iOutput = _ds_printstr(pBuffer, iLength, pString, iOutput, iWidth, bRightJust, bWideChar, cPad, cLead); + } + break; + + default: + { + // should not hit this; unsupported type specifier? + } + break; + } + } + else + { + iOutput = _ds_writechar(pBuffer, iLength, cInput, iOutput); + } + } + + // locate terminating null + iTerm = (iOutput < iLength) ? iOutput : iLength-1; + + // terminate if possible + if (iLength > 0) + { + pBuffer[iTerm] = '\0'; + } + // return length of output to caller + return(iOutput); +} + +/*F********************************************************************************/ +/*! + \Function _ds_strcmpwc + + \Description + String compare with wildcard matching ('*' only). + + \Input *pString1 - string to match + \Input *pStrWild - wildcard string + \Input bNoCase - TRUE if a case-insensitive comparison should be performed + + \Output + int32_t - see stricmp + + \Version 09/18/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ds_strcmpwc(const char *pString1, const char *pStrWild, uint8_t bNoCase) +{ + int32_t r; + char c1, c2; + + do { + c1 = *pString1++; + c2 = *pStrWild; + if (bNoCase) + { + if ((c1 >= 'A') && (c1 <= 'Z')) + { + c1 ^= 32; + } + if ((c2 >= 'A') && (c2 <= 'Z')) + { + c2 ^= 32; + } + } + if ((c2 == '*') && (c1 != '\0')) + { + if ((r = _ds_strcmpwc(pString1, pStrWild+1, bNoCase)) == 0) + { + break; + } + r = 0; + } + else + { + pStrWild += 1; + r = c1-c2; + } + } while ((c1 != '\0') && (c2 != '\0') && (r == 0)); + + return(r); +} + +/*F********************************************************************************/ +/*! + \Function _ds_fmtoctstring + + \Description + Format a binary blob as hexadecmial text + + \Input *pOutput - [out] outbuffer for text + \Input iOutLen - size of output buffer + \Input *pInput - input binary data + \Input iInpLen - size of input data + \Input *pHexTbl - hex table to use for translation + + \Output + char * - pointer to output buffer + + \Version 04/11/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_ds_fmtoctstring(char *pOutput, int32_t iOutLen, const uint8_t *pInput, int32_t iInpLen, const char *pHexTbl) +{ + int32_t iInput, iOutput; + + for (iInput = 0, iOutput = 0, iOutLen -= 1; (iInput < iInpLen) && (iOutput < (iOutLen-1)); iInput += 1, iOutput += 2) + { + uint8_t cByte = pInput[iInput]; + pOutput[iOutput+0] = pHexTbl[cByte >> 4]; + pOutput[iOutput+1] = pHexTbl[cByte & 0xf]; + } + pOutput[iOutput] = '\0'; + + return(pOutput); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ds_strnlen + + \Description + Replacement for strnlen. + + \Input *pBuffer - buffer + \Input iLength - size of buffer + + \Output + int32_t - number of characters or iLength if no '\0' is present. + + \Version 10/28/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_strnlen(const char *pBuffer, int32_t iLength) +{ + int32_t iLen = 0; + + while (iLen < iLength && *(pBuffer+iLen) != '\0') + { + ++iLen; + } + + return(iLen); +} + +/*F********************************************************************************/ +/*! + \Function ds_vsnprintf + + \Description + Replacement for vsnprintf, with some modifications. + + \Input *pBuffer - output buffer + \Input iLength - size of output buffer + \Input *pFormat - format string + \Input Args - variable argument list + + \Output + int32_t - number of characters formatted; if > than the length, output + is truncated and the buffer is null-terminated unless iLength<=0. + + \Notes + Unlike standard vsnprintf, this version always null-terminates if the buffer + is not zero-sized. + + \Version 10/28/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_vsnprintf(char *pBuffer, int32_t iLength, const char *pFormat, va_list Args) +{ + return(_ds_vsnprintf(pBuffer, iLength, pFormat, Args)); +} + +/*F********************************************************************************/ +/*! + \Function ds_vsnzprintf + + \Description + Replacement for vsnprintf that always includes a terminator at the end of + the string. + + \Input *pBuffer - output buffer + \Input iLength - size of output buffer + \Input *pFormat - format string + \Input Args - variable argument list + + \Output + int32_t - number of characters formatted; if > than the length, output + is truncated and the buffer is null-terminated unless iLength<=0. + + \Version 02/15/2011 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_vsnzprintf(char *pBuffer, int32_t iLength, const char *pFormat, va_list Args) +{ + return(_ds_vsnprintf(pBuffer, iLength, pFormat, Args)); +} + +/*F********************************************************************************/ +/*! + \Function ds_snzprintf + + \Description + Replacement for snprintf that always includes a terminator at the end of + the string. + + \Input *pBuffer - output buffer + \Input iLength - size of output buffer + \Input *pFormat - format string + \Input ... - variable argument list + + \Output + int32_t - number of characters formatted; if > than the length, output + is truncated and the buffer is null-terminated unless iLength<=0. + + \Version 02/15/2011 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_snzprintf(char *pBuffer, int32_t iLength, const char *pFormat, ...) +{ + int32_t iResult; + va_list args; + + // format the text + va_start(args, pFormat); + iResult = _ds_vsnprintf(pBuffer, iLength, pFormat, args); + va_end(args); + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ds_fmtoctstring + + \Description + Format a binary blob as hexadecmial text + + \Input *pOutput - [out] outbuffer for text + \Input iOutLen - size of output buffer + \Input *pInput - input binary data + \Input iInpLen - size of input data + + \Output + char * - pointer to output buffer + + \Version 04/11/2017 (jbrookes) +*/ +/********************************************************************************F*/ +char *ds_fmtoctstring(char *pOutput, int32_t iOutLen, const uint8_t *pInput, int32_t iInpLen) +{ + return(_ds_fmtoctstring(pOutput, iOutLen, pInput, iInpLen, "0123456789ABCDEF")); +} + +/*F********************************************************************************/ +/*! + \Function ds_fmtoctstring_lc + + \Description + Format a binary blob as hexadecmial text, with the alphabetic characters + being lowercase + + \Input *pOutput - [out] outbuffer for text + \Input iOutLen - size of output buffer + \Input *pInput - input binary data + \Input iInpLen - size of input data + + \Output + char * - pointer to output buffer + + \Version 04/11/2017 (jbrookes) +*/ +/********************************************************************************F*/ +char *ds_fmtoctstring_lc(char *pOutput, int32_t iOutLen, const uint8_t *pInput, int32_t iInpLen) +{ + return(_ds_fmtoctstring(pOutput, iOutLen, pInput, iInpLen, "0123456789abcdef")); +} + +/*F********************************************************************************/ +/*! + \Function ds_strtolower + + \Description + Convert given string to lower case + + \Input *pString - string to convert + + \Output + char * - pointer to string + + \Version 12/28/2019 (jbrookes) +*/ +/********************************************************************************F*/ +char *ds_strtolower(char *pString) +{ + char *pStrStart = pString; + for (; *pString != '\0'; pString += 1) + { + if ((*pString >= 'A') && (*pString <= 'Z')) + { + *pString += 0x20; + } + } + return(pStrStart); +} + +/*F********************************************************************************/ +/*! + \Function ds_strtoupper + + \Description + Convert given string to upper case + + \Input *pString - string to convert + + \Output + char * - pointer to string + + \Version 12/28/2019 (jbrookes) +*/ +/********************************************************************************F*/ +char *ds_strtoupper(char *pString) +{ + char *pStrStart = pString; + for (; *pString != '\0'; pString += 1) + { + if ((*pString >= 'a') && (*pString <= 'z')) + { + *pString -= 0x20; + } + } + return(pStrStart); +} + +/*F********************************************************************************/ +/*! + \Function ds_strtrim + + \Description + Removes excess white space before and after values, and converts sequential + spaces to a single space. + + \Input *pString - [out] string to trim + + \Output + char * - pointer to string + + \Version 12/28/2018 (jbrookes) +*/ +/********************************************************************************F*/ +char *ds_strtrim(char *pString) +{ + char *pStrStart = pString, *pStrTrim; + // skip leading whitespace + for (; *pString == ' '; pString += 1) + ; + // copy characters, converting sequential spaces into a single space + for (pStrTrim = pString; *pString != '\0'; ) + { + *pStrTrim++ = *pString; + if (*pString == ' ') + { + // eat multiple spaces + for (; *pString == ' '; pString += 1) + ; + } + else + { + pString += 1; + } + } + // terminate string + *pString = '\0'; + // return start of string + return(pStrStart); +} + +/*F********************************************************************************/ +/*! + \Function ds_strlistinsert + + \Description + Insert string into string list buffer in sorted order, terminated by + the specified terminator. The list terminator may not be nul and it must + be a character that does not appear in strings inserted in the list. + + \Input *pStrList - [in/out] buffer to hold string list + \Input iListBufLen - length of list buffer + \Input *pString - string to insert in string list + \Input cTerm - string terminator + + \Output + int32_t - positive on success, zero if string could not be inserted + + \Version 12/29/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_strlistinsert(char *pStrList, int32_t iListBufLen, const char *pString, char cTerm) +{ + char *pStrInsert, *pStrSkip; + int32_t iStrLen, iListLen; + + // get length of string we're inserting + iStrLen = (int32_t)strlen(pString); + // get length of strlist + iListLen = (int32_t)strlen(pStrList); + // make sure we have room to insert + if (iListBufLen < (iListLen+iStrLen+1)) + { + return(0); + } + + // find insertion point + for (pStrInsert = pStrSkip = pStrList; pStrSkip != NULL; ) + { + if (ds_stricmp(pStrInsert, pString) >= 0) + { + break; + } + pStrInsert = pStrSkip; + if ((pStrSkip = strchr(pStrSkip, cTerm)) != NULL) + { + pStrSkip += 1; + } + } + + // make room for formatted header if necessary + if (*pStrInsert != '\0') + { + // get length of list from here to end of the buffer + iListLen = (int32_t)strlen(pStrInsert); + // make room for string, plus terminator character + memmove(pStrInsert+iStrLen+1, pStrInsert, iListLen); + // point to insertion point + iListBufLen -= pStrInsert-pStrList; + pStrList = pStrInsert; + } + else + { + // end of list, so null terminate + pStrInsert[iStrLen+1] = '\0'; + } + + // copy formatted header into list and terminate it + ds_memcpy_s(pStrInsert, iListBufLen, pString, iStrLen); + pStrInsert[iStrLen] = cTerm; + return(1); +} + +/*F********************************************************************************/ +/*! + \Function ds_stristr + + \Description + Case insensitive substring search + + \Input pHaystack - see stristr + \Input pNeedle - see stristr + + \Output see stristr + + \Version 01/25/2005 (gschaefer) +*/ +/********************************************************************************F*/ +char *ds_stristr(const char *pHaystack, const char *pNeedle) +{ + int32_t iFirst; + int32_t iIndex; + + // make sure strings are valid + if ((pHaystack != NULL) && (*pHaystack != 0) && (pNeedle != NULL) && (*pNeedle != 0)) + { + iFirst = tolower((unsigned char)*pNeedle); + for (; *pHaystack != 0; ++pHaystack) + { + // small optimization on first character search + if (tolower((unsigned char)*pHaystack) == iFirst) + { + for (iIndex = 1;; ++iIndex) + { + if (pNeedle[iIndex] == 0) + { + return((char *)pHaystack); + } + if (pHaystack[iIndex] == 0) + { + break; + } + if (tolower((unsigned char)pHaystack[iIndex]) != tolower((unsigned char)pNeedle[iIndex])) + { + break; + } + } + } + } + } + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function ds_stricmp + + \Description + Case insensitive string compare + + \Input *pString1 - see stricmp + \Input *pString2 - see stricmp + + \Output see stricmp + + \Version 01/25/2005 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ds_stricmp(const char *pString1, const char *pString2) +{ + int32_t r; + char c1, c2; + + do { + c1 = *pString1++; + if ((c1 >= 'A') && (c1 <= 'Z')) + c1 ^= 32; + c2 = *pString2++; + if ((c2 >= 'A') && (c2 <= 'Z')) + c2 ^= 32; + r = c1-c2; + } while ((c1 != 0) && (r == 0)); + + return(r); +} + +/*F********************************************************************************/ +/*! + \Function ds_strnicmp + + \Description + Case insensitive string compare of first N characters + + \Input *pString1 - see strnicmp + \Input *pString2 - see strnicmp + \Input uCount - see strnicmp + + \Output see strnicmp + + \Version 01/25/2005 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ds_strnicmp(const char *pString1, const char *pString2, uint32_t uCount) +{ + int32_t r; + char c1, c2; + uint32_t uPos; + + if (uCount == 0) + return(0); + + uPos = 0; + do { + c1 = *pString1++; + if ((c1 >= 'A') && (c1 <= 'Z')) + c1 ^= 32; + c2 = *pString2++; + if ((c2 >= 'A') && (c2 <= 'Z')) + c2 ^= 32; + r = c1-c2; + uPos++; + } while ((c1 != 0) && (r == 0) && (uPos < uCount)); + + return(r); +} + +/*F********************************************************************************/ +/*! + \Function ds_strcmpwc + + \Description + String compare with wildcard matching ('*' only). + + \Input *pString1 - see stricmp + \Input *pStrWild - match string including wildcard(s) + + \Output see stricmp + + \Version 09/18/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_strcmpwc(const char *pString1, const char *pStrWild) +{ + return(_ds_strcmpwc(pString1, pStrWild, FALSE)); +} + +/*F********************************************************************************/ +/*! + \Function ds_stricmpwc + + \Description + String compare with wildcard matching ('*' only). + + \Input *pString1 - see stricmp + \Input *pStrWild - match string including wildcard(s) + + \Output see stricmp + + \Version 09/18/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_stricmpwc(const char *pString1, const char *pStrWild) +{ + return(_ds_strcmpwc(pString1, pStrWild, TRUE)); +} + +/*F********************************************************************************/ +/*! + \Function ds_strnzcpy + + \Description + Always terminated strncpy function + + \Input *pDest - [out] output for new string + \Input *pSource - pointer to input string to copy from + \Input iCount - length of output buffer + + \Output + char * - pointer to output buffer + + \Notes + Unlike strncpy(), the destination buffer is not filled with zeros if the + string being copied does not fill it. + + \Version 01/11/2017 (jbrookes) +*/ +/********************************************************************************F*/ +char *ds_strnzcpy(char *pDest, const char *pSource, int32_t iCount) +{ + int32_t iIndex; + + // make sure buffer has enough room + if (--iCount < 0) + { + return(0); + } + + // copy the string + for (iIndex = 0; (iIndex < iCount) && (pSource[iIndex] != '\0'); iIndex += 1) + { + pDest[iIndex] = pSource[iIndex]; + } + + // write null terminator and return number of bytes written + pDest[iIndex] = '\0'; + return(pDest); +} + +/*F********************************************************************************/ +/*! + \Function ds_strsubzcpy + + \Description + Copy a substring from pSrc into the output buffer. The output string + is guaranteed to be null-terminated. + + \Input *pDst - [out] output for new string + \Input iDstLen - length of output buffer + \Input *pSrc - pointer to input string to copy from + \Input iSrcLen - number of characters to copy from input string + + \Output + int32_t - number of characters written, excluding null character + + \Version 09/27/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_strsubzcpy(char *pDst, int32_t iDstLen, const char *pSrc, int32_t iSrcLen) +{ + int32_t iIndex; + + // make sure buffer has enough room + if (--iDstLen < 0) + { + return(0); + } + + // copy the string + for (iIndex = 0; (iIndex < iSrcLen) && (iIndex < iDstLen) && (pSrc[iIndex] != '\0'); iIndex++) + { + pDst[iIndex] = pSrc[iIndex]; + } + + // write null terminator and return number of bytes written + pDst[iIndex] = '\0'; + return(iIndex); +} + +/*F********************************************************************************/ +/*! + \Function ds_strnzcat + + \Description + Concatenate the string pointed to by pSrc to the string pointed to by pDst. + A maximum of iDstLen-1 characters are copied, and the resulting string is + guaranteed to be null-terminated. + + \Input *pDst - [out] output for new string + \Input *pSrc - pointer to input string to copy from + \Input iDstLen - size of output buffer + + \Output + int32_t - number of characters in pDst, excluding null character + + \Version 09/27/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_strnzcat(char *pDst, const char *pSrc, int32_t iDstLen) +{ + int32_t iDst, iSrc; + + // make sure buffer has enough room + if (--iDstLen < 0) + { + return(0); + } + + // find end of string + for (iDst = 0; (iDst < iDstLen) && (pDst[iDst] != '\0'); iDst++) + ; + + // copy the string + for (iSrc = 0; (iDst < iDstLen) && (pSrc[iSrc] != '\0'); iSrc++, iDst++) + { + pDst[iDst] = pSrc[iSrc]; + } + + // write null terminator and return updated length of string + pDst[iDst] = '\0'; + return(iDst); +} + +/*F********************************************************************************/ +/*! + \Function ds_strsubzcat + + \Description + Concatenate the substring pointed to by pSrc and with a length of iSrcLen + to the string pointed to by pDst. A maximum of iDstLen-1 or iSrcLen + characters are copied, whichever is smaller, and the resulting string is + guaranteed to be null-terminated. + + \Input *pDst - [out] output for new string + \Input iDstLen - size of output buffer + \Input *pSrc - pointer to input string to copy from + \Input iSrcLen - size of substring pointed to by pSrc. + + \Output + int32_t - number of characters in pDst, excluding null character + + \Version 09/27/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_strsubzcat(char *pDst, int32_t iDstLen, const char *pSrc, int32_t iSrcLen) +{ + int32_t iDst, iSrc; + + // make sure buffer has enough room + if (--iDstLen < 0) + { + return(0); + } + + // find end of string + for (iDst = 0; (iDst < iDstLen) && (pDst[iDst] != '\0'); iDst++) + ; + + // copy the string + for (iSrc = 0; (iDst < iDstLen) && (iSrc < iSrcLen) && (pSrc[iSrc] != '\0'); iSrc++, iDst++) + { + pDst[iDst] = pSrc[iSrc]; + } + + // write null terminator and return updated length of string + pDst[iDst] = '\0'; + return(iDst); + +} + +/*F********************************************************************************/ +/*! + \Function ds_strsplit + + \Description + Splits string on a delimiter + + \Input *pSrc - the string we are splitting + \Input cDelimiter - delimited to split on + \Input *pDst - where we are writing the data split + \Input iDstSize - size of dest string + \Input **pNewSrc - used to save position of iteration + + \Output + int32_t - length of destination or zero if nothing parsed + + \Version 08/04/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ds_strsplit(const char *pSrc, char cDelimiter, char *pDst, int32_t iDstSize, const char **pNewSrc) +{ + const char *pLocation; + int32_t iSrcSize; + + // check for validity of inputs + if ((pSrc == NULL) || (*pSrc == '\0')) + { + return(0); + } + iSrcSize = (int32_t)strlen(pSrc); + + /* if we start with delimiter skip it + this happens when you do multiple parses */ + if (pSrc[0] == cDelimiter) + { + pSrc += 1; + iSrcSize -= 1; + (*pNewSrc) += 1; + } + + // terminate the destination + if (pDst != NULL && iDstSize > 0) + { + pDst[0] = '\0'; + } + + /* write the value to the destination (if not null) + when length is less than max, add 1 for nul terminator */ + if ((pLocation = strchr(pSrc, cDelimiter)) != NULL) + { + const int32_t iCount = (int32_t)(pLocation - pSrc); + (*pNewSrc) += iCount; + if (pDst != NULL) + { + ds_strnzcpy(pDst, pSrc, DS_MIN(iDstSize, iCount+1)); + } + return(iCount); + } + else + { + (*pNewSrc) += iSrcSize; + if (pDst != NULL) + { + ds_strnzcpy(pDst, pSrc, DS_MIN(iDstSize, iSrcSize+1)); + } + return(iSrcSize); + } +} + +/*F********************************************************************************/ +/*! + \Function ds_strtok_r + + \Description + Reentrant version of strtok + + \Input *pStr - input string + \Input *pDelim - delimiter + \Input **ppSavePtr - variable used to save down the state + + \Output + char* - reutrns the pointer to the next token + + \Version 09/07/2018 (tcho) +*/ +/********************************************************************************F*/ +char* ds_strtok_r(char *pStr, const char *pDelim, char **ppSavePtr) +{ + #if defined(DIRTYCODE_NX) + char *pEnd; + + if (pStr == NULL) + { + pStr = *ppSavePtr; + } + + if (*pStr == '\0') + { + *ppSavePtr = pStr; + return(NULL); + } + + // scan leading character + pStr += strspn(pStr, pDelim); + if (*pStr == '\0') + { + *ppSavePtr = pStr; + return(NULL); + } + + // find end of token + pEnd = pStr + strcspn(pStr, pDelim); + if (*pEnd == '\0') + { + *ppSavePtr = pEnd; + return(pStr); + } + + *pEnd = '\0'; + *ppSavePtr = pEnd + 1; + return pStr; + #elif defined(DIRTYCODE_PC) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + return strtok_s(pStr, pDelim, ppSavePtr); + #else + return strtok_r(pStr, pDelim, ppSavePtr); + #endif +} +/*F********************************************************************************/ +/*! + \Function ds_memcpy + + \Description + Replacement for memcpy. + + \Input *pDst - [out] output for new string + \Input *pSrc - pointer to input string to copy from + \Input iDstLen - size of substring pointed to by pSrc. + + \Version 07/15/2014 (amakoukji) +*/ +/********************************************************************************F*/ +void ds_memcpy(void *pDst, const void *pSrc, int32_t iDstLen) +{ + memcpy(pDst, pSrc, iDstLen); +} + +/*F********************************************************************************/ +/*! + \Function ds_memcpy_s + + \Description + Replacement for memcpy_s. + + \Input *pDst - [out] output for new string + \Input iDstLen - size of output buffer + \Input *pSrc - pointer to input string to copy from + \Input iSrcLen - size of substring pointed to by pSrc. + + \Version 07/15/2014 (amakoukji) +*/ +/********************************************************************************F*/ +void ds_memcpy_s(void *pDst, int32_t iDstLen, const void *pSrc, int32_t iSrcLen) +{ + iDstLen = DS_MIN(iDstLen, iSrcLen); + memcpy(pDst, pSrc, iDstLen); +} + +/*F********************************************************************************/ +/*! + \Function ds_memclr + + \Description + Memset that always clears the memory with zero + + \Input *pMem - memory that is being cleared + \Input iCount - the number of bytes to clear + + \Version 01/06/2017 (eesponda) +*/ +/********************************************************************************F*/ +void ds_memclr(void *pMem, int32_t iCount) +{ + memset(pMem, 0, iCount); +} + +/*F********************************************************************************/ +/*! + \Function ds_memset + + \Description + Replacement for memset + + \Input *pMem - memory that is being cleared + \Input iValue - value to clear the memory with + \Input iCount - the number of bytes to clear + + \Version 01/06/2017 (eesponda) +*/ +/********************************************************************************F*/ +void ds_memset(void *pMem, int32_t iValue, int32_t iCount) +{ + memset(pMem, iValue, iCount); +} diff --git a/src/thirdparty/dirtysdk/source/platform/plat-time.c b/src/thirdparty/dirtysdk/source/platform/plat-time.c new file mode 100644 index 00000000..3cab94b9 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/platform/plat-time.c @@ -0,0 +1,720 @@ +/*H********************************************************************************/ +/*! + \File plat-time.c + + \Description + This module implements variations on the standard library time functions. + The originals had a number of problems with internal static storage, + bizarre parameter passing (pointers to input values instead of the + actual value) and time_t which is different on different platforms. + + All these functions work with uint32_t values which provide a time + range of 1970-2107. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 01/11/2005 (gschaefer) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" + +#if defined(DIRTYCODE_PS4) +#include +#elif defined(DIRTYCODE_PC) || defined(DIRTYCODE_XBOXONE) +#include +#include +#include +#include +#elif defined (DIRTYCODE_LINUX) || defined(DIRTYCODE_ANDROID) || defined(DIRTYCODE_APPLEIOS) || defined(DIRTYCODE_APPLEOSX) +#include +#endif + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +static const char *_PlatTime_strWday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL }; +static const char *_PlatTime_strMonth[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; + +/*** Private functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _ds_strtoint + + \Description + Converts a string number to an integer. Does not support sign. + + \Input *pStr - pointer to int to read + \Input *pValue - [out] storage for result + \Input iMaxDigits - max number of digits to convert + + \Output + const char * - pointer past end of number + + \Version 12/13/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_ds_strtoint(const char *pStr, int32_t *pValue, int32_t iMaxDigits) +{ + int32_t iNum, iDigit; + for (iNum = 0, iDigit = 0; ((*pStr >= '0') && (*pStr <= '9') && (iDigit < iMaxDigits)); pStr += 1, iDigit += 1) + { + iNum = (iNum * 10) + (*pStr & 15); + } + *pValue = iNum; + return(pStr); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ds_timeinsecs + + \Description + Return time in seconds, or zero if not available + + \Output uint64_t - number of elapsed seconds since Jan 1, 1970 + + \Version 01/12/2005 (gschaefer) +*/ +/********************************************************************************F*/ +uint64_t ds_timeinsecs(void) +{ + return(NetTime()); +} + +/*F********************************************************************************/ +/*! + \Function ds_timezone + + \Description + This function returns the current system timezone as its offset from GMT in + seconds. There is no direct equivilent function in the standard C libraries. + + \Output int32_t - local timezone offset from GMT in seconds + + \Notes + The intent is to determine the timezone, so the offset intentionally does + not take daylight savings into account. + Do not use for GMT time determination. + + \Version 11/06/2002 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ds_timezone(void) +{ + time_t iGmt, iLoc; + static int32_t iZone = -1; + + // just calc the timezone one time + if (iZone == -1) + { + time_t uTime = time(0); + struct tm *pTm, TmTime; + + // convert to gmt time + pTm = gmtime(&uTime); + iGmt = (uint32_t)mktime(pTm); + + // convert to local time + pTm = ds_localtime(&TmTime,(uint32_t)uTime); + iLoc = (uint32_t)mktime(pTm); + + // calculate timezone difference + iZone = (int32_t)(iLoc - iGmt); + } + + // return the timezone offset in seconds + return(iZone); +} + +/*F********************************************************************************/ +/*! + \Function ds_localtime + + \Description + This converts the input GMT time to the local time as specified by the + system clock. This function follows the re-entrant localtime_r function + signature. + + \Input *pTm - storage for localtime output + \Input elap - GMT time + + \Output + struct tm * - pointer to localtime result + + \Version 04/23/2008 (jbrookes) +*/ +/********************************************************************************F*/ +struct tm *ds_localtime(struct tm *pTm, uint64_t elap) +{ + return(NetLocalTime(pTm, elap)); +} + +/*F********************************************************************************/ +/*! + \Function ds_secstotime + + \Description + Convert elapsed seconds to discrete time components. This is essentially a + ds_localtime() replacement with better syntax that is available on all platforms. + + \Input *pTm - target component record + \Input elap - epoch time input + + \Output struct tm * - returns tm if successful, NULL if failed + + \Version 01/23/2000 (gschaefer) +*/ +/********************************************************************************F*/ +struct tm *ds_secstotime(struct tm *pTm, uint64_t elap) +{ + int32_t year, leap, next, days, secs; + const int32_t *mon; + + // table to find days per month + static const int32_t dayspermonth[24] = { + 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, // leap + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 // norm + }; + // table to find day of the week + static const int32_t wday[] = { + 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 + }; + + // divide out secs within day and days + // (we ignore leap-seconds cause it requires tables and nasty stuff) + days = (int32_t)(elap / (24*60*60)); + secs = (int32_t)(elap % (24*60*60)); + + // store the time info + pTm->tm_sec = secs % 60; + secs /= 60; + pTm->tm_min = secs % 60; + secs /= 60; + pTm->tm_hour = secs; + + // determine what year we are in + for (year = 1970;; year = next) { + // calc the length of the year in days + leap = (((year & 3) == 0) && (((year % 100 != 0) || ((year % 400) == 0))) ? 366 : 365); + // see if date is within this year + if (days < leap) + break; + + // estimate target year assuming every year is a leap year + // (this may underestimate the year, but will never overestimate) + next = year + (days / 366); + + // make sure year got changed and force if it did not + /// (can happen on dec 31 of non-leap year) + if (next == year) + ++next; + + // subtract out normal days/year + days -= (next - year) * 365; + // add in leap years from previous guess + days += ((year-1)/4 - (year-1)/100 + (year-1)/400); + // subtract out leap years since new guess + days -= ((next-1)/4 - (next-1)/100 + (next-1)/400); + } + + // save the year and day within year + pTm->tm_year = year - 1900; + pTm->tm_yday = days; + // calc the month + mon = dayspermonth + 12*(leap&1); + for (pTm->tm_mon = 0; days >= *mon; pTm->tm_mon += 1) + days -= *mon++; + // save the days + pTm->tm_mday = days + 1; + // calculate weekday using Sakamoto's algorithm, adjusted for m=[0...11] + year -= pTm->tm_mon < 2; + pTm->tm_wday = (year + year/4 - year/100 + year/400 + wday[pTm->tm_mon] + pTm->tm_mday) % 7; + // clear dst + pTm->tm_isdst = 0; + + // return pointer to argument to make it closer to ds_localtime() + return(pTm); +} + +/*F********************************************************************************/ +/*! + \Function ds_timetosecs + + \Description + Convert discrete components to elapsed time. + + \Input *pTm - source component record + + \Output + uint32_t - zero=failure, else epoch time + + \Version 01/23/2000 (gschaefer) +*/ +/********************************************************************************F*/ +uint64_t ds_timetosecs(const struct tm *pTm) +{ + uint64_t min, max, mid; + struct tm cmp; + int32_t res; + + /* do a binary search using ds_secstotime to encode prospective time_t values. + though iterative, it requires a max of 32 iterations which is actually pretty + good considering the complexity of the calculation (this allows all the + time nastiness to remain in a single function). */ + + /* perform binary search (set max to 1/1/3000 to reduce 64-bit search space + and prevent overflow in 32bit days/secs calculations in ds_secstotime */ + for (min = 0, mid = 0, res = 0, max = 32503680000; min <= max; ) + { + /* test at midpoint -- since these are large unsigned values, they can overflow + in the (min+max)/2 case hense the individual divide and lost bit recovery */ + mid = (min/2)+(max/2)+(min&max&1); + // do the time conversion + ds_secstotime(&cmp, mid); + // do the compare + if ((res = (cmp.tm_year - pTm->tm_year)) == 0) { + if ((res = (cmp.tm_mon - pTm->tm_mon)) == 0) { + if ((res = (cmp.tm_mday - pTm->tm_mday)) == 0) { + if ((res = (cmp.tm_hour - pTm->tm_hour)) == 0) { + if ((res = (cmp.tm_min - pTm->tm_min)) == 0) { + if ((res = cmp.tm_sec - pTm->tm_sec) == 0) { + // got an exact match! + break; + } + } + } + } + } + } + + // force break once min/max converge (cannot do this within for condition as res will not be setup correctly) + if (min == max) + break; + + // narrow the search range + if (res > 0) + max = mid-1; + else + min = mid+1; + } + + // return converted time or zero if failed + return((res == 0) ? mid : 0); +} + +/*F********************************************************************************/ +/*! + \Function ds_plattimetotime + + \Description + This converts the input platform-specific time data structure to the + generic time data structure. + + \Input *pTm - generic time data structure to be filled by the function + \Input *pPlatTime - pointer to the platform-specific data structure + + \Output + struct tm * - NULL=failure; else pointer to user-provided generic time data structure + + \Notes + See NetPlattimeToTime() for input data format + + \Version 05/08/2010 (mclouatre) +*/ +/********************************************************************************F*/ +struct tm *ds_plattimetotime(struct tm *pTm, void *pPlatTime) +{ + return(NetPlattimeToTime(pTm, pPlatTime)); +} + +/*F********************************************************************************/ +/*! + \Function ds_plattimetotimems + + \Description + This function retrieves the current date time and fills in the + generic time data structure prrovided. It has the option of returning millisecond + which is not part of the generic time data structure + + \Input *pTm - generic time data structure to be filled by the function + \Input *pImSec - output param for milisecond to be filled by the function (optional can be NULL) + + \Output + struct tm * - NULL=failure; else pointer to user-provided generic time data structure + + \Version 09/16/2014 (tcho) +*/ +/********************************************************************************F*/ +struct tm *ds_plattimetotimems(struct tm *pTm , int32_t *pImSec) +{ + return(NetPlattimeToTimeMs(pTm, pImSec)); +} + +/*F********************************************************************************/ +/*! + \Function ds_timetostr + + \Description + Converts a date formatted in a number of common Unix and Internet formats + and convert to a struct tm. + + \Input *pTm - input time stucture to be converted to string + \Input eConvType - user-selected conversion type + \Input bLocalTime - whether input time is local time or UTC 0 offset time. + \Input *pStrBuf - user-provided buffer to be filled with datetime string + \Input iBufSize - size of output buffer (must be at least 18 bytes to receive null-terminated yyyy-MM-ddTHH:mm:ssZ + + \Output + char * - zero=failure, else epoch time + + \Version 07/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +char *ds_timetostr(const struct tm *pTm, TimeToStringConversionTypeE eConvType, uint8_t bLocalTime, char *pStrBuf, int32_t iBufSize) +{ + switch(eConvType) + { + case TIMETOSTRING_CONVERSION_ISO_8601: + ds_snzprintf(pStrBuf, iBufSize, "%04d-%02d-%02dT%02d:%02d:%02d%s", + pTm->tm_year+1900, pTm->tm_mon+1, pTm->tm_mday, + pTm->tm_hour, pTm->tm_min, pTm->tm_sec, + !bLocalTime ? "Z" : ""); + break; + case TIMETOSTRING_CONVERSION_ISO_8601_BASIC: + ds_snzprintf(pStrBuf, iBufSize, "%04d%02d%02dT%02d%02d%02d%s", + pTm->tm_year+1900, pTm->tm_mon+1, pTm->tm_mday, + pTm->tm_hour, pTm->tm_min, pTm->tm_sec, + !bLocalTime ? "Z" : ""); + break; + case TIMETOSTRING_CONVERSION_RFC_0822: // e.g. Wed, 15 Nov 1995 04:58:08 GMT + { + uint32_t tm_wday = ((unsigned)pTm->tm_wday < 7) ? pTm->tm_wday : 7; + uint32_t tm_mon = ((unsigned)pTm->tm_mon < 12) ? pTm->tm_mon : 12; + ds_snzprintf(pStrBuf, iBufSize, "%s, %2d %s %4d %02d:%02d:%02d GMT", _PlatTime_strWday[tm_wday], pTm->tm_mday, + _PlatTime_strMonth[tm_mon], pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec); + break; + } + default: + // unsupported conversion type + pStrBuf = NULL; + break; + } + + return(pStrBuf); +} + +/*F********************************************************************************/ +/*! + \Function ds_secstostr + + \Description + Converts a datetime in epoch format to string + + \Input elap - epoch time to convert to string + \Input eConvType - user-selected conversion type + \Input bLocalTime - whether input time is local time or UTC 0 offset time. + \Input *pStrBuf - user-provided buffer to be filled with datetime string + \Input iBufSize - size of output buffer (must be at least 18 bytes to receive null-terminated yyyy-MM-ddTHH:mm:ssZ + + \Output + char * - zero=failure, else epoch time + + \Version 02/26/2014 (jbrookes) +*/ +/********************************************************************************F*/ +char *ds_secstostr(uint64_t elap, TimeToStringConversionTypeE eConvType, uint8_t bLocalTime, char *pStrBuf, int32_t iBufSize) +{ + struct tm TmTime; + return(ds_timetostr(ds_secstotime(&TmTime, elap), eConvType, bLocalTime, pStrBuf, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function ds_strtotime + + \Description + Converts a date formatted in a number of common Unix and Internet formats + and convert to a struct tm. + + \Input *pStr - textual date string + + \Output uint32_t - zero=failure, else epoch time + + \Notes + \verbatim + The following time formats are supported: + Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + + The following format is mentioned as needing to be readable by HTTP + but is not currently supported by this function: + Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + \endverbatim + + \Version 11/01/2002 (gschaefer) +*/ +/********************************************************************************F*/ +uint64_t ds_strtotime(const char *pStr) +{ + int32_t i; + const char *s; + struct tm tm; + + // reset the fields + ds_memset(&tm, -1, sizeof(tm)); + + // skip past any white space + while ((*pStr != 0) && (*pStr <= ' ')) + { + pStr++; + } + + // see if starts with day of week (RFC 822/1123, asctime) + for (i = 0; (s=_PlatTime_strWday[i]) != 0; ++i) + { + if ((pStr[0] == s[0]) && (pStr[1] == s[1]) && (pStr[2] == s[2])) + { + tm.tm_wday = i; + // skip name of weekday + while ((*pStr != ',') && (*pStr != ' ') && (*pStr != 0)) + ++pStr; + // skip the divider + while ((*pStr == ',') || (*pStr == ' ')) + ++pStr; + break; + } + } + + // check for mmm dd (asctime) + if ((*pStr < '0') || (*pStr > '9')) + { + for (i = 0; (s=_PlatTime_strMonth[i]) != 0; ++i) + { + // look for a match + if ((pStr[0] != s[0]) || (pStr[1] != s[1]) || (pStr[2] != s[2])) + continue; + // found the month + tm.tm_mon = i; + // skip to the digits + while ((*pStr != 0) && ((*pStr < '0') || (*pStr > '9'))) + ++pStr; + // get the day of month + for (i = 0; ((*pStr >= '0') && (*pStr <= '9')); ++pStr) + i = (i * 10) + (*pStr & 15); + if (i > 0) + tm.tm_mday = i; + break; + } + } + + // check for dd mmm (RFC 822/1123) + if ((tm.tm_mon < 0) && (pStr[0] >= '0') && (pStr[0] <= '9') && + ((pStr[1] > '@') || (pStr[2] > '@') || (pStr[3] > '@'))) + { + // get the day + for (i = 0; ((*pStr >= '0') && (*pStr <= '9')); ++pStr) + i = (i * 10) + (*pStr & 15); + tm.tm_mday = i; + while (*pStr < '@') + ++pStr; + // get the month + for (i = 0; (s=_PlatTime_strMonth[i]) != 0; ++i) + { + // look for a match + if ((pStr[0] != s[0]) || (pStr[1] != s[1]) || (pStr[2] != s[2])) + continue; + tm.tm_mon = i; + while ((*pStr != 0) && (*pStr != ' ')) + ++pStr; + break; + } + } + + // check for xx/xx or xx/xx/xx (???) + if ((*pStr >= '0') && (*pStr <= '9') && (tm.tm_mon < 0)) + { + // get the month + for (i = 0; ((*pStr >= '0') && (*pStr <= '9')); ++pStr) + i = (i * 10) + (*pStr & 15); + tm.tm_mon = i - 1; + if (*pStr != 0) + ++pStr; + // get the day + for (i = 0; ((*pStr >= '0') && (*pStr <= '9')); ++pStr) + i = (i * 10) + (*pStr & 15); + tm.tm_mday = i; + if (*pStr != 0) + ++pStr; + } + + // check for year (RFC 822/1123) + while ((*pStr != 0) && ((*pStr < '0') || (*pStr > '9'))) + ++pStr; + // see if the year is here + if ((pStr[0] >= '0') && (pStr[0] <= '9') && (pStr[1] != ':') && (pStr[2] != ':')) + { + for (i = 0; ((*pStr >= '0') && (*pStr <= '9')); ++pStr) + i = (i * 10) + (*pStr & 15); + if (i > 999) + tm.tm_year = i; + else if (i >= 50) + tm.tm_year = 1900+i; + else + tm.tm_year = 2000+i; + // find next digit sequence + while ((*pStr != 0) && ((*pStr < '0') || (*pStr > '9'))) + ++pStr; + } + + // save the hour (RFC 822/1123, asctime) + if ((*pStr >= '0') && (*pStr <= '9')) + { + i = (*pStr++ & 15); + if ((*pStr >= '0') && (*pStr <= '9')) + i = (i * 10) + (*pStr++ & 15); + tm.tm_hour = i; + if (*pStr == ':') + ++pStr; + } + + // save the minute (RFC 822/1123, asctime) + if ((*pStr >= '0') && (*pStr <= '9')) + { + i = (*pStr++ & 15); + if ((*pStr >= '0') && (*pStr <= '9')) + i = (i * 10) + (*pStr++ & 15); + tm.tm_min = i; + if (*pStr == ':') + ++pStr; + } + + // save the second (if present) (RFC 822/1123, asctime) + if ((*pStr >= '0') && (*pStr <= '9')) + { + i = (*pStr++ & 15); + if ((*pStr >= '0') && (*pStr <= '9')) + i = (i * 10) + (*pStr++ & 15); + tm.tm_sec = i; + } + + // see if year is still remaining (asctime) + if (tm.tm_year < 0) + { + // see if any digits left + while ((*pStr != 0) && ((*pStr < '0') || (*pStr > '9'))) + ++pStr; + for (i = 0; ((*pStr >= '0') && (*pStr <= '9')); ++pStr) + i = (i * 10) + (*pStr & 15); + if (i > 999) + tm.tm_year = i; + } + + // make year relative to 1900 (really dumb) + if (tm.tm_year > 1900) + tm.tm_year -= 1900; + + // convert from struct tm to uint32_t and return to caller + return(ds_timetosecs(&tm)); +} + +/*F********************************************************************************/ +/*! + \Function ds_strtotime2 + + \Description + Converts a date formatted in a number of common Unix and Internet formats + and convert to a struct tm. + + \Input *pStr - textual date string + \Input eConvType - time format to convert from + + \Output + uint32_t - zero=failure, else epoch time + + \Notes + For supported conversion types other than ISO_8601, see documentation + for ds_strtotime(). + + \Version 12/13/2012 (jbrookes) +*/ +/********************************************************************************F*/ +uint64_t ds_strtotime2(const char *pStr, TimeToStringConversionTypeE eConvType) +{ + uint64_t uTime = 0; + struct tm tm; + + if (eConvType == TIMETOSTRING_CONVERSION_ISO_8601) + { + // format: YYYY-MM-DDTHH:MM:SSZ + if (strlen(pStr) < 19) // 'Z' is optional and ignored + { + return(0); + } + // read the date/time + pStr = _ds_strtoint(pStr, &tm.tm_year, 4) + 1; // get the year, skip hyphen + pStr = _ds_strtoint(pStr, &tm.tm_mon, 2) + 1; // get the month, skip hyphen + pStr = _ds_strtoint(pStr, &tm.tm_mday, 2) + 1; // get the day, skip 'T' + pStr = _ds_strtoint(pStr, &tm.tm_hour, 2) + 1; // get the hour, skip ':' + pStr = _ds_strtoint(pStr, &tm.tm_min, 2) + 1; // get the minute, skip ':' + _ds_strtoint(pStr, &tm.tm_sec, 2); // get the second + // adjust year and month + tm.tm_year -= 1900; + tm.tm_mon -= 1; + // convert from struct tm to uint32_t and return to caller + uTime = ds_timetosecs(&tm); + } + else if ((eConvType == TIMETOSTRING_CONVERSION_ASN1_UTCTIME) || (eConvType == TIMETOSTRING_CONVERSION_ASN1_GENTIME)) + { + /* GeneralizedTime: .fff is optional + Local Time: YYYYMMDDHHMMSS.fff + UTC Time: YYYYMMDDHHMMSS.fffZ + Difference Time: YYYYMMDDHHMMSS.fff+-HHMM + UTCTime: like GeneralizedTime, but accuracy is to one minute or one second (no .fff, SS is optional, Z is implicit and not included). + Local Time: YYYYMMDDHHMMSS + UTC Time: YYMMDDHHMMSS + Difference Time: YYYYMMDDHHMMSS+-HHMM + Note: Fractional time, difference time, and UTC character, if present, are ignored */ + if (eConvType == TIMETOSTRING_CONVERSION_ASN1_UTCTIME) + { + pStr = _ds_strtoint(pStr, &tm.tm_year, 2); + tm.tm_year += (tm.tm_year < 70) ? 2000 : 1900; // 2-year UTC time represents from 1970 to 2069 + } + else + { + pStr = _ds_strtoint(pStr, &tm.tm_year, 4); + } + pStr = _ds_strtoint(pStr, &tm.tm_mon, 2); + pStr = _ds_strtoint(pStr, &tm.tm_mday, 2); + pStr = _ds_strtoint(pStr, &tm.tm_hour, 2); + pStr = _ds_strtoint(pStr, &tm.tm_min, 2); + _ds_strtoint(pStr, &tm.tm_sec, 2); + // adjust year and month + tm.tm_year -= 1900; + tm.tm_mon -= 1; + // convert from struct tm to uint32_t and return to caller + uTime = ds_timetosecs(&tm); + } + else + { + uTime = ds_strtotime(pStr); + } + return(uTime); +} + diff --git a/src/thirdparty/dirtysdk/source/proto/protoadvt.c b/src/thirdparty/dirtysdk/source/proto/protoadvt.c new file mode 100644 index 00000000..6ce21eee --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protoadvt.c @@ -0,0 +1,918 @@ +/*H*************************************************************************************/ +/*! + \File protoadvt.c + + \Description + This advertising module provides a relatively simple multi-protocol + distributed name server architecture utilizing the broadcast capabilities + of UDP and IPX. Once the module is instantiated, it can be used as both + an advertiser (server) and watcher (client) simultaneously. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 1.0 02/17/99 (GWS) Original version + \Version 1.1 02/25/99 (GWS) Alpha release + \Version 1.2 07/27/99 (GWS) Initial release + \Version 1.3 10/28/99 (GWS) Message queue elimination + \Version 1.4 07/10/00 (GWS) Windock2 dependency removal + \Version 1.5 12/11/00 (GWS) Ported to Dirtysock + \Version 2.0 03/28/03 (GWS) Made more robust with double-broadcast sends + \Version 2.1 06/16/03 (JLB) Made string comparisons case-insensitive +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/proto/protoadvt.h" + +#if defined(DIRTYCODE_PS4) +#include +#endif +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +//! raw broadcast packet format +typedef struct ProtoAdvtPacketT +{ + //! static packet identifier + uint8_t ident[3]; + //! single char freq (avoid byte ordering issues) + uint8_t freq; + //! advertisement sequence number + uint8_t seqn[4]; + //! the kind of service + char kind[32]; + //! the name of this particular service + char name[32]; + //! misc notes about service + char note[192]; + //! list of service addresses + char addr[120]; +} ProtoAdvtPacketT; + +//! a list of services +typedef struct ServListT +{ + //! actual packet data + ProtoAdvtPacketT packet; + //! timeout until item expires or is rebroadcast + uint32_t timeout; + //! last internet address associated with service + char inetdot[16]; + uint32_t inetval; + uint32_t hostval; + //! flag to indicate if packet has local origin + int32_t local; + //! link to next item in list + struct ServListT *next; +} ServListT; + +//! local module reference +struct ProtoAdvtRef +{ + //! control access to resources + NetCritT crit; + //! list of services to announce + ServListT *announce; + //! known service list + ServListT *snoopbuf; + ServListT *snoopend; + //! dirtysock memory group + int32_t memgroup; + void *memgrpusrdata; + //! seeding is needed + int32_t seed; + //! number of users + int32_t usage; + //! active socket + SocketT *sock; + //! broadcast address + struct sockaddr addr; + //! indicate that snoop buffer changed + int32_t snoop; +}; + + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +static int32_t g_count = 0; +static ProtoAdvtRef *g_ref = NULL; + +// Public variables + + +/*** Private Functions *****************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function _ProtoAdvtSendPacket + + \Description + Sends a ProtoAdvt packet. + + \Input *pRef - module ref + \Input *pPacket - packet to send + + \Output + int32_t - return result from SocketSendto() + + \Version 11/30/2006 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _ProtoAdvtSendPacket(ProtoAdvtRef *pRef, const ProtoAdvtPacketT *pPacket) +{ + int32_t iPacketSize = sizeof(*pPacket); + return(SocketSendto(pRef->sock, (const char *)pPacket, iPacketSize, 0, &pRef->addr, sizeof(pRef->addr))); +} + +/*F*************************************************************************************/ +/*! + \Function _ProtoAdvtRecvPacket + + \Description + Receives a ProtoAdvt packet. Variable-length string fields are unpacked from + the compacted sent packet to produce the fixed-length result packet. + + \Input *pRef - module ref + \Input *pPacket - packet buffer to receive to + \Input *pFrom - sender's address + + \Output + int32_t - return result from SocketRecvfrom() + + \Version 11/30/2006 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _ProtoAdvtRecvPacket(ProtoAdvtRef *pRef, ProtoAdvtPacketT *pPacket, struct sockaddr *pFrom) +{ + int32_t iFromLen = sizeof(*pFrom); + return(SocketRecvfrom(pRef->sock, (char *)pPacket, sizeof(*pPacket), 0, pFrom, &iFromLen)); +} + +/*F*************************************************************************************/ +/*! + \Function _ProtoAdvtRequestSeed + + \Description + Send a seed request + + \Input *pRef - module ref + + \Version 03/28/03 (GWS) +*/ +/*************************************************************************************F*/ +static void _ProtoAdvtRequestSeed(ProtoAdvtRef *pRef) +{ + ProtoAdvtPacketT Packet; + + // setup a query packet + ds_memclr(&Packet, sizeof(Packet)); + Packet.ident[0] = ADVERT_PACKET_IDENTIFIER[0]; + Packet.ident[1] = ADVERT_PACKET_IDENTIFIER[1]; + Packet.ident[2] = ADVERT_PACKET_IDENTIFIER[2]; + Packet.kind[0] = '?'; + + // do the send + _ProtoAdvtSendPacket(pRef, &Packet); +} + + +/*F*************************************************************************************/ +/*! + \Function _ProtoAdvtCallback + + \Description + Main worker callback + + \Input *sock - connection socket pointer + \Input flags - unused + \Input *_ref - ProtoAdvtRef pointer + + \Output int32_t - zero + + \Version 11/01/02 (GWS) +*/ +/*************************************************************************************F*/ +static int32_t _ProtoAdvtCallback(SocketT *sock, int32_t flags, void *_ref) +{ + int32_t len; + int32_t local; + struct sockaddr base; + struct sockaddr from; + ServListT *list, *item, **link; + ProtoAdvtPacketT packet; + ProtoAdvtRef *ref = (ProtoAdvtRef *) _ref; + uint32_t tick; + + // make sure we own resources + if (!NetCritTry(&ref->crit)) + { + NetPrintf(("protoadvt: could not acquire critical section\n")); + return(0); + } + + // process all pending packets + while (ref->sock != NULL) + { + // request seeding if needed + if (ref->seed > 0) + { + ref->seed = 0; + _ProtoAdvtRequestSeed(ref); + } + + // try and receive a packet + packet.kind[0] = '\0'; + SockaddrInit(&from, AF_INET); + len = _ProtoAdvtRecvPacket(ref, &packet, &from); + if ((len <= 0) || (packet.kind[0] == '\0')) + { + break; + } + + // get local address we would have used + SockaddrInit(&base, AF_INET); + SocketHost(&base, sizeof(base), &from, sizeof(from)); + + // see if packet was sent locally + local = 0; + if ((base.sa_family == from.sa_family) && (from.sa_family == AF_INET)) + local = (memcmp(&from.sa_data, &base.sa_data, 6) == 0); + + // ignore packets without proper identifier + if ((packet.ident[0] != ADVERT_PACKET_IDENTIFIER[0]) || + (packet.ident[1] != ADVERT_PACKET_IDENTIFIER[1]) || + (packet.ident[2] != ADVERT_PACKET_IDENTIFIER[2])) + { + continue; + } + + // handle seed request first + if ((packet.kind[0] == '?') && (packet.kind[1] == 0)) + { + tick = NetTick() + 1000; + // reduce timeouts for outgoing packets + for (list = ref->announce; list != NULL; list = list->next) + { + // reduce timeout down to 1 second + // (helps prevent multiple seed requests from causing storm) + if (list->timeout > tick) + list->timeout = tick; + } + // get next packet + continue; + } + + // make sure name & kind are set + if (packet.name[0] == 0) + { + continue; + } + + // process the packet + item = NULL; + for (list = ref->snoopbuf; list != ref->snoopend; ++list) + { + // check for unused block + if ((item == NULL) && (list->packet.name[0] == 0)) + { + item = list; + } + // see if we already have this packet + if ((ds_stricmp(packet.kind, list->packet.kind) == 0) && + (ds_stricmp(packet.name, list->packet.name) == 0) && + (memcmp(packet.seqn, list->packet.seqn, sizeof(packet.seqn)) == 0)) + { + break; + } + } + + // see if this is a new packet + if ((list == ref->snoopend) && (item != NULL)) + { + list = item; + ds_memclr(list, sizeof(*list)); + // populate with kind/name/seqn (never change) + ds_memcpy_s(list->packet.seqn, sizeof(list->packet.seqn), packet.seqn, sizeof(packet.seqn)); + ds_strnzcpy(list->packet.kind, packet.kind, sizeof(list->packet.kind)); + ds_strnzcpy(list->packet.name, packet.name, sizeof(list->packet.name)); + ds_strnzcpy(list->packet.note, packet.note, sizeof(list->packet.note)); + // indicate snoop buffer changed + ref->snoop += 1; + NetPrintf(("protoadvt: added new advert name=%s freq=%d addr=%a\n", list->packet.name, packet.freq, SockaddrInGetAddr(&from))); + } + + // update the record (assuming we found/allocated) + if (list != ref->snoopend) + { + ds_strnzcpy(list->packet.addr, packet.addr, sizeof(list->packet.addr)); + ds_strnzcpy(list->packet.note, packet.note, sizeof(list->packet.note)); + list->timeout = NetTick()+2*(packet.freq*1000)+1000; + list->local = local; + list->inetval = SockaddrInGetAddr(&from); + list->hostval = SockaddrInGetAddr(&base); + SockaddrInGetAddrText(&from, list->inetdot, sizeof(list->inetdot)); + // indicate snoop buffer changed + ref->snoop += 1; + } + // loop checks for another packet + } + + // check for expired services + tick = NetTick(); + for (list = ref->snoopbuf; list != ref->snoopend; ++list) + { + if (list->packet.name[0] != 0) + { + // see if anything has expired + if ((list->packet.addr[0] == 0) || (list->timeout == 0) || (tick > list->timeout)) + { + NetPrintf(("protoadvt: expiring advert name=%s\n", list->packet.name)); + list->packet.name[0] = 0; + list->packet.kind[0] = 0; + // indicate snoop buffer changed + ref->snoop += 1; + } + } + } + + // check for expired announcements + for (link = &ref->announce; (*link) != NULL;) + { + // see if it has expired + if ((*link)->timeout == 0) + { + item = *link; + *link = (*link)->next; + // send out a cancelation notice + NetPrintf(("protoadvt: canceling announcement name=%s\n", item->packet.name)); + item->packet.addr[0] = '\0'; + // send out notices + _ProtoAdvtSendPacket(ref, &item->packet); + // done with the record + DirtyMemFree(item, PROTOADVT_MEMID, ref->memgroup, ref->memgrpusrdata); + } + else + { + // move to next item + link = &(*link)->next; + } + } + + // see if anything needs to be sent + tick = NetTick(); + for (list = ref->announce; list != NULL; list = list->next) + { + // see if its time to send + if (tick > list->timeout) + { + // broadcast packet + _ProtoAdvtSendPacket(ref, &list->packet); + // update send time + list->timeout = tick + list->packet.freq*1000; + } + } + + // done with update + NetCritLeave(&ref->crit); + return(0); +} + + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function ProtoAdvtAnnounce + + \Description + Advertise a service as available + + \Input *ref - reference pointer + \Input *kind - service class (max 32 characters including NUL) + \Input *name - service name (usually game name, max 32 characters including NUL) + \Input *note - service note (max 32 characters including NUL) + \Input *addr - service address list (max 120 characters, including NUL) + \Input freq - announcement frequency, in seconds (can be in the range [2...250]) + + \Output + int32_t - <0 = error, 0=ok + + \Version 11/01/02 (GWS) +*/ +/*************************************************************************************F*/ +int32_t ProtoAdvtAnnounce(ProtoAdvtRef *ref, const char *kind, const char *name, + const char *note, const char *addr, int32_t freq) +{ + ServListT *list; + ServListT *item; + + // clamp the freqeuncy to valid range + if (freq == 0) + freq = 30; + if (freq < 2) + freq = 2; + if (freq > 250) + freq = 250; + + // validate input strings + if ((kind == NULL) || (kind[0] == '\0')) + { + NetPrintf(("protoadvt: error, invalid kind\n")); + return(-1); + } + if ((name == NULL) || (name[0] == '\0')) + { + NetPrintf(("protoadvt: error, invalid name\n")); + return(-2); + } + if (note == NULL) + { + NetPrintf(("protoadvt: error, invalid note\n")); + return(-3); + } + if (addr == NULL) + { + NetPrintf(("protoadvt: error, invalid addr\n")); + return(-4); + } + + // see if service is already listed + for (list = ref->announce; list != NULL; list = list->next) + { + // check for dupl + if ((ds_stricmp(kind, list->packet.kind) == 0) && + (ds_stricmp(name, list->packet.name) == 0)) + { + // update the addr field if necessary + if (ds_stricmp(addr, list->packet.addr) != 0) + { + // update address & force immediate broadcast of new info + ds_strnzcpy(list->packet.addr, addr, sizeof(list->packet.addr)); + list->timeout = NetTick()-1; + } + + // update the note field if necessary + if (ds_stricmp(note, list->packet.note) != 0) + { + // update note & force immediate broadcast of new info + ds_strnzcpy(list->packet.note, note, sizeof(list->packet.note)); + list->timeout = NetTick()-1; + } + // all done + return(0); + } + } + + // build a packet + item = DirtyMemAlloc(sizeof(*item), PROTOADVT_MEMID, ref->memgroup, ref->memgrpusrdata); + ds_memclr(item, sizeof(*item)); + item->timeout = NetTick(); + item->packet.ident[0] = ADVERT_PACKET_IDENTIFIER[0]; + item->packet.ident[1] = ADVERT_PACKET_IDENTIFIER[1]; + item->packet.ident[2] = ADVERT_PACKET_IDENTIFIER[2]; + item->packet.freq = (unsigned char) freq; + item->packet.seqn[0] = (unsigned char)(item->timeout >> 24); + item->packet.seqn[1] = (unsigned char)(item->timeout >> 16); + item->packet.seqn[2] = (unsigned char)(item->timeout >> 8); + item->packet.seqn[3] = (unsigned char)(item->timeout >> 0); + ds_strnzcpy(item->packet.kind, kind, sizeof(item->packet.kind)); + ds_strnzcpy(item->packet.name, name, sizeof(item->packet.name)); + ds_strnzcpy(item->packet.addr, addr, sizeof(item->packet.addr)); + ds_strnzcpy(item->packet.note, note, sizeof(item->packet.note)); + NetPrintf(("protoadvt: broadcasting new announcement name=%s freq=%d\n", item->packet.name, item->packet.freq)); + + // send an immediate copy + _ProtoAdvtSendPacket(ref, &item->packet); + // schedule next send in 250ms + item->timeout = NetTick()+250; + + // make sure we own resources + NetCritEnter(&ref->crit); + // add new item to list + item->next = ref->announce; + ref->announce = item; + // release resources + NetCritLeave(&ref->crit); + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function ProtoAdvtCancel + + \Description + Cancel server advertisement + + \Input *ref - reference pointer + \Input *kind - service kind + \Input *name - service name (usually game name, NULL=wildcard) + + \Output + int32_t - <0=error, 0=ok + + \Version 11/01/02 (GWS) +*/ +/*************************************************************************************F*/ +int32_t ProtoAdvtCancel(ProtoAdvtRef *ref, const char *kind, const char *name) +{ + ServListT *list; + + // make sure we own resources + NetCritEnter(&ref->crit); + + // locate the item in the announcement list + for (list = ref->announce; list != NULL; list = list->next) + { + // see if we got a match + if (((kind == NULL) || (ds_stricmp(kind, list->packet.kind) == 0)) && + ((name == NULL) || (ds_stricmp(name, list->packet.name) == 0))) + { + // mark as deleted + list->timeout = 0; + break; + } + } + + // release resources + NetCritLeave(&ref->crit); + + // was item found? + return(list == NULL ? -1 : 0); +} + + +/*F*************************************************************************************/ +/*! + \Function ProtoAdvtQuery + + \Description + Query for available services + + \Input *ref - reference pointer + \Input *kind - service kind + \Input *proto - protocol + \Input *buffer - target buffer + \Input buflen - target length + \Input local - if zero, exclude local lists + + \Output + int32_t - number of matches + + \Version 11/01/02 (GWS) +*/ +/*************************************************************************************F*/ +int32_t ProtoAdvtQuery(ProtoAdvtRef *ref, const char *kind, const char *proto, + char *buffer, int32_t buflen, int32_t local) +{ + char *s, *t, *u; + char addr[256] = ""; + char record[512]; + int32_t count = 0; + ServListT *list; + + // establish min buffer size + if (buflen < 5) + return(-1); + + // default to empty buffer + buffer[0] = 0; + + // see what matching services we know about + for (list = ref->snoopbuf; list != ref->snoopend; ++list) + { + // skip empty entries + if (list->packet.name[0] == 0) + continue; + // see if the kind matches + if (ds_stricmp(kind, list->packet.kind) != 0) + continue; + // exclude locals if not wanted + if ((!local) && (list->local)) + continue; + // parse the address choices + for (s = list->packet.addr; *s != 0;) + { + // parse out the address + for (t = addr; (*s != 0) && (*s != '\t');) + *t++ = *s++; + *t++ = 0; + if (*s == '\t') + ++s; + // make sure address looks valid + if ((strlen(addr) < 5) || (addr[3] != ':')) + continue; + // see if this protocol type is wanted by caller + addr[3] = 0; + if ((proto[0] != 0) && (strstr(proto, addr) == NULL)) + continue; + addr[3] = ':'; + // compile data into a record + ds_strnzcpy(record, list->packet.name, sizeof(record)); + for (t = record; *t != 0; ++t) + ; + *t++ = '\t'; + // copy over notes field + strcpy(t, list->packet.note); + while (*t != 0) + ++t; + *t++ = '\t'; + // append the address + for (u = addr; *u != 0; ++u) + { + // check for inet substitution + if ((u[0] == '~') && (u[1] == '1')) + { + // make sure inet address is known + if (list->inetdot[0] == 0) + break; + // copy over the address + strcpy(t, list->inetdot); + while (*t != 0) + ++t; + ++u; + continue; + } + // check for ipx substitution + if ((u[0] == '~') && (u[1] == '2')) + { + // no ipx support + break; + } + // raw copy + *t++ = *u; + } + // see if copy was canceled (substitution error) + if (*u != 0) + continue; + // terminate the record + *t++ = '\n'; + *t = 0; + // see if the record will fit into output buffer + if (strlen(record)+5 > (unsigned) buflen) + { + // indicate an overflow + *buffer++ = '.'; + *buffer++ = '.'; + *buffer++ = '.'; + *buffer++ = '\n'; + *buffer = 0; + return(count); + } + // append the record to the buffer + strcpy(buffer, record); + buffer += (int32_t)strlen(record); + buflen -= (int32_t)strlen(record); + ++count; + } + } + + // return count of items + return(count); +} + + +/*F*************************************************************************************/ +/*! + \Function ProtoAdvtLocate + + \Description + Locate a specific advertisement and return advertisers address (UDP only) + + \Input *ref - reference pointer + \Input *kind - service kind (NULL=any) + \Input *name - service name (usually game name, NULL=wildcard) + \Input *host - pointer to buffer for host (our) address, or NULL + \Input defval - value returned if module is not set + + \Output + uint32_t - defval if ref is NULL, else first matching internet address + + \Version 11/01/02 (GWS) +*/ +/*************************************************************************************F*/ +uint32_t ProtoAdvtLocate(ProtoAdvtRef *ref, const char *kind, const char *name, + uint32_t *host, uint32_t defval) +{ + ServListT *list; + + // just return default if module is not set + if (ref == NULL) + return(defval); + + // see what matching services we know about + for (list = ref->snoopbuf; list != ref->snoopend; ++list) + { + // make sure service is valid + if (list->packet.name[0] == 0) + continue; + // exclude locals + if (list->local) + continue; + // see if the kind matches + if ((kind != NULL) && (kind[0] != 0) && (ds_stricmp(kind, list->packet.kind) != 0)) + continue; + // make sure the name matches + if ((name != NULL) && (name[0] != 0) && (ds_stricmp(list->packet.name, name) != 0)) + continue; + // make sure there is an internet address + if (list->inetval == 0) + continue; + // return host (our) address if they want it + if (host != NULL) + *host = list->hostval; + // return the internet address + defval = list->inetval; + break; + } + + // return default value + return(defval); +} + + +/*F*************************************************************************************/ +/*! + \Function ProtoAdvtConstruct + + \Description + Construct an advertising agent + + \Input limit - size of snoop buffer (min 4) + + \Output + ProtoAdvtRef * - construct ref (passed to other routines) + + \Version 11/01/02 (GWS) +*/ +/*************************************************************************************F*/ +ProtoAdvtRef *ProtoAdvtConstruct(int32_t limit) +{ + SocketT *sock; + struct sockaddr bindaddr; + struct sockaddr peeraddr; + ProtoAdvtRef *ref; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // see if already allocated + if (g_ref != NULL) + { + ++g_count; + return(g_ref); + } + + // allocate class storage + ref = DirtyMemAlloc(sizeof(*ref), PROTOADVT_MEMID, iMemGroup, pMemGroupUserData); + if (ref == NULL) + return(NULL); + ds_memclr(ref, sizeof(*ref)); + ref->memgroup = iMemGroup; + ref->memgrpusrdata = pMemGroupUserData; + + // allocate snooped advertisement buffer + if (limit < 4) + limit = 4; + ref->snoopbuf = DirtyMemAlloc(limit*sizeof(ref->snoopbuf[0]), PROTOADVT_MEMID, ref->memgroup, ref->memgrpusrdata); + if (ref->snoopbuf == NULL) + { + DirtyMemFree(ref, PROTOADVT_MEMID, ref->memgroup, ref->memgrpusrdata); + return(NULL); + } + ref->snoopend = ref->snoopbuf+limit; + ds_memclr(ref->snoopbuf, (int32_t)((char *)ref->snoopend-(char *)ref->snoopbuf)); + + // set initial revision value + ref->snoop = 1; + + // create the actual socket + sock = SocketOpen(AF_INET, SOCK_DGRAM, 0); + if (sock == NULL) + { + DirtyMemFree(ref, PROTOADVT_MEMID, ref->memgroup, ref->memgrpusrdata); + return(NULL); + } + + // mark as available for sharing + g_ref = ref; + g_count = 1; + + // need to sync access to data + NetCritInit(&ref->crit, "protoadvt"); + // limit access during setup + NetCritEnter(&ref->crit); + + // bind with local address + SockaddrInit(&bindaddr, AF_INET); + SockaddrInSetPort(&bindaddr, ADVERT_BROADCAST_PORT_UDP); + SocketBind(sock, &bindaddr, sizeof(bindaddr)); + + // connect to remote address + SockaddrInit(&peeraddr, AF_INET); + SockaddrInSetPort(&peeraddr, ADVERT_BROADCAST_PORT_UDP); + SockaddrInSetAddr(&peeraddr, INADDR_BROADCAST); + + // note: though it would be easier to use connect to bind the peer address + // to the socket, it will not work because winsock will not deliver an + // incoming packet to a socket connected to the sending port. therefore, + // the address must be maintained separately and used with sendto. + // (logic unchanged for dirtysock since it is already coded this way) + ds_memcpy_s(&ref->addr, sizeof(ref->addr), &peeraddr, sizeof(peeraddr)); + + // broadcast request for available server info (seed database) + ref->seed = 1; + // make the socket available + ref->sock = sock; + // bind the callback function + SocketCallback(ref->sock, CALLB_RECV, 100, ref, &_ProtoAdvtCallback); + + // done with setup + NetCritLeave(&ref->crit); + + // immediately send a seed request + // (the idle process will send a second within 100ms) + _ProtoAdvtRequestSeed(ref); + return(ref); +} + + +/*F*************************************************************************************/ +/*! + \Function ProtoAdvtDestroy + + \Description + Destruct an advertising agent + + \Input *ref - construct ref + + \Version 11/01/02 (GWS) +*/ +/*************************************************************************************F*/ +void ProtoAdvtDestroy(ProtoAdvtRef *ref) +{ + SocketT *sock; + + // make sure what is valid + if (ref == NULL) + { + return; + } + + // see if we are last + if (g_count > 1) + { + --g_count; + return; + } + + // destroy the class + g_ref = NULL; + g_count = 0; + + // cancel all announcements + while (ref->announce != NULL) + { + ProtoAdvtPacketT *packet = &ref->announce->packet; + ProtoAdvtCancel(ref, packet->kind, packet->name); + _ProtoAdvtCallback(ref->sock, 0, ref); + } + + // make sure we own resources + NetCritEnter(&ref->crit); + + // do the shutdown + sock = ref->sock; + ref->sock = NULL; + + // release resources + NetCritLeave(&ref->crit); + + // dispose of socket + SocketClose(sock); + + // done with semaphore + NetCritKill(&ref->crit); + + // done with ref + DirtyMemFree(ref->snoopbuf, PROTOADVT_MEMID, ref->memgroup, ref->memgrpusrdata); + DirtyMemFree(ref, PROTOADVT_MEMID, ref->memgroup, ref->memgrpusrdata); +} + diff --git a/src/thirdparty/dirtysdk/source/proto/protohttp.c b/src/thirdparty/dirtysdk/source/proto/protohttp.c new file mode 100644 index 00000000..27d11f25 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protohttp.c @@ -0,0 +1,3363 @@ +/*H********************************************************************************/ +/*! + \File protohttp.c + + \Description + This module implements an HTTP client that can perform basic transactions + (get/put) with an HTTP server. It conforms to but does not fully implement + the 1.1 HTTP spec (http://www.w3.org/Protocols/rfc2616/rfc2616.html), and + allows for secure HTTP transactions as well as insecure transactions. + + \Copyright + Copyright (c) Electronic Arts 2000-2004. ALL RIGHTS RESERVED. + + \Version 0.5 02/21/2000 (gschaefer) First Version + \Version 1.0 12/07/2000 (gschaefer) Added PS2/Dirtysock support + \Version 1.1 03/03/2004 (sbevan) Rewrote to use ProtoSSL, added limited Post support. + \Version 1.2 11/18/2004 (jbrookes) Refactored, updated to HTTP 1.1, added full Post support. +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtyvers.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/proto/protohttp.h" + +/*** Defines **********************************************************************/ + +//! default ProtoHttp timeout +#define PROTOHTTP_TIMEOUT_DEFAULT (30*1000) + +//! default maximum allowed redirections +#define PROTOHTTP_MAXREDIRECT (3) + +//! size of "last-received" header cache +#define PROTOHTTP_HDRCACHESIZE (1024) + +//! protohttp revision number (maj.min) +#define PROTOHTTP_VERSION (0x0103) // update this for major bug fixes or protocol additions/changes + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! http module state +struct ProtoHttpRefT +{ + ProtoSSLRefT *pSsl; //!< ssl module + + ProtoHttpCustomHeaderCbT *pCustomHeaderCb; //!< global callback for modifying request header + ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb; //!< global callback for viewing recv header on recepit + void *pCallbackRef; //!< user ref for callback + + ProtoHttpWriteCbT *pWriteCb; //!< optional data write callback + ProtoHttpCustomHeaderCbT *pReqCustomHeaderCb; //!< optional request custom header callback + ProtoHttpReceiveHeaderCbT *pReqReceiveHeaderCb; //!< optional request receive header callback + void *pUserData; //!< user data for callback + + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData;//!< user data associated with mem group + + NetCritT HttpCrit; //!< critical section for guarding update from send/recv + + ProtoHttpRequestTypeE eRequestType; //!< request type of current request + int32_t iPort; //!< server port + int32_t iBasePort; //!< base port (used for partial urls) + int32_t iProxiedPort; //!< port of proxied host + int32_t iSecure; //!< secure connection + int32_t iBaseSecure; //!< base security setting (used for partial urls) + int32_t iProxiedSecure; //!< true if proxied connection is secure + + enum + { + ST_IDLE, //!< idle + ST_CONN, //!< connecting + ST_SEND, //!< sending buffered data + ST_RESP, //!< waiting for initial response (also sending any data not buffered if POST or PUT) + ST_HEAD, //!< getting header + ST_BODY, //!< getting body + ST_DONE, //!< transaction success + ST_FAIL //!< transaction failed + } eState; //!< current state + + int32_t iSslFail; //!< ssl failure code, if any + int32_t iHresult; //!< ssl hresult code, if any + int32_t iHdrCode; //!< result code + int32_t iHdrDate; //!< last modified date + + int32_t iHeadSize; //!< size of head data + int64_t iPostSize; //!< amount of data being sent in a POST or PUT operation + int64_t iBodySize; //!< size of body data + int64_t iBodyRcvd; //!< size of body data received by caller + int32_t iRecvSize; //!< amount of data received by ProtoHttpRecvAll + int32_t iRecvRslt; //!< last receive result + + char *pInpBuf; //!< input buffer + int32_t iInpMax; //!< maximum buffer size + int32_t iInpOff; //!< offset into buffer + int32_t iInpLen; //!< total length in buffer + int64_t iInpCnt; //!< ongoing count + int32_t iInpOvr; //!< input overflow amount + int32_t iChkLen; //!< chunk length (if chunked encoding) + int32_t iHdrLen; //!< length of header(s) queued for sending + int32_t iHdrOff; //!< temp offset used when receiving header + + char *pInpBufTmp; //!< temp storage for input buffer pointer when using connect flow + int32_t iInpLenTmp; //!< temp storage for input buffer length when using connect flow + + int32_t iNumRedirect; //!< number of redirections processed + int32_t iMaxRedirect; //!< maximum number of redirections allowed + + uint32_t uTimeout; //!< protocol timeout + uint32_t uTimer; //!< timeout timer + int32_t iKeepAlive; //!< indicate if we should try to use keep-alive + int32_t iKeepAliveDflt; //!< keep-alive default (keep-alive will be reset to this value; can be overridden by user) + + char *pAppendHdr; //!< append header buffer pointer + int32_t iAppendLen; //!< size of append header buffer + + char strHdr[PROTOHTTP_HDRCACHESIZE]; //!< storage for most recently received HTTP header + char strRequestHdr[PROTOHTTP_HDRCACHESIZE]; //!< storage for most recent HTTP request header + char strConnectHdr[256]; //!< temp storage for connect header when using connect flow + char strHost[256]; //!< server name + char strBaseHost[256]; //!< base server name (used for partial urls) + char strProxy[256]; //!< proxy server name/address (including port) + char strProxiedHost[256]; //!< hostname of server we are connecting to through proxy + + uint8_t bTimeout; //!< boolean indicating whether a timeout occurred or not + uint8_t bChunked; //!< if TRUE, transfer is chunked + uint8_t bHeadOnly; //!< if TRUE, only get header + uint8_t bCloseHdr; //!< server wants close after this + uint8_t bClosed; //!< connection has been closed + uint8_t bConnOpen; //!< connection is open + uint8_t iVerbose; //!< debug output verbosity + uint8_t bVerifyHdr; //!< perform header type verification + uint8_t bHttp1_0; //!< TRUE if HTTP/1.0, else FALSE + uint8_t bCompactRecv; //!< compact receive buffer + uint8_t bInfoHdr; //!< TRUE if a new informational header has been cached; else FALSE + uint8_t bNewConnection; //!< TRUE if a new connection should be used, else FALSE (if using keep-alive) + uint8_t bPipelining; //!< TRUE if pipelining is enabled, else FALSE + uint8_t bPipeGetNext; //!< TRUE if we should proceed to next pipelined result, else FALSE + int8_t iPipedRequests; //!< number of pipelined requests + uint8_t bPipedRequestsLost; //!< TRUE if pipelined requests were lost due to a premature close + uint8_t bReuseOnPost; //!< TRUE if reusing a previously established connection on PUT/POST is allowed, else FALSE + uint8_t bConnProxy; //!< if true, executing secure proxy connect flow + uint8_t bUpgradeSSL; //!< upgrade connection to SSL after connect + +}; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Private variables + +// update this when PROTOHTTP_NUMREQUESTTYPES changes +static const char _ProtoHttp_strRequestNames[PROTOHTTP_NUMREQUESTTYPES][16] = +{ + "HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "CONNECT" +}; + +//! global proxy; if this is set all ProtoHttp refs will use this as their proxy +static char _ProtoHttp_strGlobalProxy[256] = ""; + + +// Public variables + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpApplyBaseUrl + + \Description + Apply base url elements (if set) to any url elements not specified (relative + url support). + + \Input *pState - module state + \Input *pKind - parsed http kind ("http" or "https") + \Input *pHost - [in/out] parsed URL host + \Input iHostSize - size of pHost buffer + \Input *pPort - [in/out] parsed port + \Input *pSecure - [in/out] parsed security (0 or 1) + \Input bPortSpecified - TRUE if a port is explicitly specified in the url, else FALSE + + \Output + uint32_t - non-zero if changed, else zero + + \Version 02/03/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _ProtoHttpApplyBaseUrl(ProtoHttpRefT *pState, const char *pKind, char *pHost, int32_t iHostSize, int32_t *pPort, int32_t *pSecure, uint8_t bPortSpecified) +{ + uint8_t bChanged = FALSE; + if ((*pHost == '\0') && (pState->strBaseHost[0] != '\0')) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: host not present; setting to %s\n", pState->strBaseHost)); + ds_strnzcpy(pHost, pState->strBaseHost, iHostSize); + bChanged = TRUE; + } + if ((bPortSpecified == FALSE) && (pState->iBasePort != 0)) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: port not present; setting to %d\n", pState->iBasePort)); + *pPort = pState->iBasePort; + bChanged = TRUE; + } + if (*pKind == '\0') + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: kind (protocol) not present; setting to %d\n", pState->iBaseSecure)); + *pSecure = pState->iBaseSecure; + // if our port setting is default and incompatible with our security setting, override it + if (((*pPort == 80) && (*pSecure == 1)) || ((*pPort == 443) && (*pSecure == 0))) + { + *pPort = *pSecure ? 443 : 80; + } + bChanged = TRUE; + } + return(bChanged); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpClose + + \Description + Close connection to server, if open. + + \Input *pState - module state + \Input *pReason - reason connection is being closed (for debug output) + + \Output + None. + + \Version 10/07/2005 (jbrookes) First Version +*/ +/********************************************************************************F*/ +static void _ProtoHttpClose(ProtoHttpRefT *pState, const char *pReason) +{ + if (pState->bClosed) + { + // already issued disconnect, don't need to do it again + return; + } + + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] closing connection: %s\n", pState, pReason)); + ProtoSSLDisconnect(pState->pSsl); + pState->bCloseHdr = FALSE; + pState->bConnOpen = FALSE; + pState->bClosed = TRUE; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpReset + + \Description + Reset state before a transaction request. + + \Input *pState - reference pointer + + \Output + None. + + \Version 11/22/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +static void _ProtoHttpReset(ProtoHttpRefT *pState) +{ + ds_memclr(pState->strHdr, sizeof(pState->strHdr)); + ds_memclr(pState->strRequestHdr, sizeof(pState->strRequestHdr)); + pState->eState = ST_IDLE; + pState->iSslFail = 0; + pState->iHresult = 0; + pState->iHdrCode = -1; + pState->iHdrDate = 0; + pState->iHeadSize = 0; + pState->iBodySize = pState->iBodyRcvd = 0; + pState->iRecvSize = 0; + pState->iInpOff = 0; + pState->iInpLen = 0; + pState->iInpOvr = 0; + pState->iChkLen = 0; + pState->bTimeout = FALSE; + pState->bChunked = FALSE; + pState->bClosed = FALSE; + pState->bHeadOnly = FALSE; + pState->bPipeGetNext = FALSE; + pState->bPipedRequestsLost = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpFreeInputBuf + + \Description + Free input buffer + + \Input *pState - reference pointer + + \Version 06/20/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoHttpFreeInputBuf(ProtoHttpRefT *pState) +{ + char *pInpBuf = (pState->pInpBuf != pState->strConnectHdr) ? pState->pInpBuf : pState->pInpBufTmp; + DirtyMemFree(pInpBuf, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpSetAppendHeader + + \Description + Set given string as append header, allocating memory as required. + + \Input *pState - reference pointer + \Input *pAppendHdr - append header string + + \Output + int32_t - zero=success, else error + + \Version 11/11/2004 (jbrookes) Split/combined from ProtoHttpGet() and ProtoHttpPost() +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpSetAppendHeader(ProtoHttpRefT *pState, const char *pAppendHdr) +{ + int32_t iAppendBufLen, iAppendStrLen; + + // check for empty append string, in which case we free the buffer + if ((pAppendHdr == NULL) || (*pAppendHdr == '\0')) + { + if (pState->pAppendHdr != NULL) + { + DirtyMemFree(pState->pAppendHdr, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->pAppendHdr = NULL; + } + pState->iAppendLen = 0; + return(0); + } + + // check to see if append header is already set + if ((pState->pAppendHdr != NULL) && (!strcmp(pAppendHdr, pState->pAppendHdr))) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: ignoring set of append header '%s' that is already set\n", pAppendHdr)); + return(0); + } + + // get append header length + iAppendStrLen = (int32_t)strlen(pAppendHdr); + // append buffer size includes null and space for \r\n if not included by submitter + iAppendBufLen = iAppendStrLen + 3; + + // see if we need to allocate a new buffer + if (iAppendBufLen > pState->iAppendLen) + { + if (pState->pAppendHdr != NULL) + { + DirtyMemFree(pState->pAppendHdr, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + if ((pState->pAppendHdr = DirtyMemAlloc(iAppendBufLen, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) != NULL) + { + pState->iAppendLen = iAppendBufLen; + } + else + { + NetPrintf(("protohttp: could not allocate %d byte buffer for append header\n", iAppendBufLen)); + pState->iAppendLen = 0; + return(-1); + } + } + + // copy append header + ds_strnzcpy(pState->pAppendHdr, pAppendHdr, iAppendStrLen+1); + + // if append header is not \r\n terminated, do it here + if (pAppendHdr[iAppendStrLen-2] != '\r' || pAppendHdr[iAppendStrLen-1] != '\n') + { + ds_strnzcat(pState->pAppendHdr, "\r\n", pState->iAppendLen); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpFormatRequestHeader + + \Description + Format a request header based on given input data. + + \Input *pState - reference pointer + \Input *pUrl - pointer to user-supplied url + \Input *pHost - pointer to hostname + \Input iPort - port, or zero if unspecified + \Input iSecure - 1=enabled, 0=disabled + \Input *pRequest - pointer to request type ("GET", "HEAD", "POST", "PUT") + \Input iDataLen - size of included data; zero if none, negative if streaming put/post + + \Output + int32_t - zero=success, else error + + \Version 11/11/2004 (jbrookes) Split/combined from ProtoHttpGet() and ProtoHttpPost() +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpFormatRequestHeader(ProtoHttpRefT *pState, const char *pUrl, const char *pHost, int32_t iPort, int32_t iSecure, const char *pRequest, int64_t iDataLen) +{ + int32_t iInpMax, iOffset = 0; + const char *pUrlSlash; + char *pInpBuf; + ProtoHttpCustomHeaderCbT *pCustomHeaderCb; + void *pUserData; + + // if url is empty or isn't preceded by a slash, put one in + pUrlSlash = (*pUrl != '/') ? "/" : ""; + + // set up for header formatting + pInpBuf = pState->pInpBuf + pState->iInpLen; + iInpMax = pState->iInpMax - pState->iInpLen; + if (pState->iInpLen != 0) + { + pState->iPipedRequests += 1; + } + + // format request header + iOffset += ds_snzprintf(pInpBuf+iOffset, iInpMax-iOffset, "%s %s%s HTTP/1.1\r\n", pRequest, pUrlSlash, pUrl); + if ((iSecure && (iPort == 443)) || (iPort == 80)) + { + iOffset += ds_snzprintf(pInpBuf+iOffset, iInpMax-iOffset, "Host: %s\r\n", pHost); + } + else + { + iOffset += ds_snzprintf(pInpBuf+iOffset, iInpMax-iOffset, "Host: %s:%d\r\n", pHost, iPort); + } + if (iDataLen == -1) + { + iOffset += ds_snzprintf(pInpBuf+iOffset, iInpMax-iOffset, "Transfer-Encoding: chunked\r\n"); + } + else if ((iDataLen > 0) || (pState->eRequestType == PROTOHTTP_REQUESTTYPE_PUT) || (pState->eRequestType == PROTOHTTP_REQUESTTYPE_PATCH) || (pState->eRequestType == PROTOHTTP_REQUESTTYPE_POST)) + { + iOffset += ds_snzprintf(pInpBuf+iOffset, iInpMax-iOffset, "Content-Length: %qd\r\n", iDataLen); + } + if (pState->iKeepAlive == 0) + { + iOffset += ds_snzprintf(pInpBuf+iOffset, iInpMax-iOffset, "Connection: Close\r\n"); + } + if ((pState->pAppendHdr == NULL) || !ds_stristr(pState->pAppendHdr, "User-Agent:")) + { + iOffset += ds_snzprintf(pInpBuf+iOffset, iInpMax-iOffset, "User-Agent: ProtoHttp %d.%d/DS %d.%d.%d.%d.%d (" DIRTYCODE_PLATNAME ")\r\n", + (PROTOHTTP_VERSION>>8)&0xff, PROTOHTTP_VERSION&0xff, DIRTYSDK_VERSION_YEAR, DIRTYSDK_VERSION_SEASON, DIRTYSDK_VERSION_MAJOR, DIRTYSDK_VERSION_MINOR, DIRTYSDK_VERSION_PATCH); + } + if ((pState->pAppendHdr == NULL) || (pState->pAppendHdr[0] == '\0')) + { + iOffset += ds_snzprintf(pInpBuf+iOffset, iInpMax-iOffset, "Accept: */*\r\n"); + } + else + { + iOffset += ds_snzprintf(pInpBuf+iOffset, iInpMax-iOffset, "%s", pState->pAppendHdr); + } + + // request level callback takes priority to global + if ((pCustomHeaderCb = pState->pReqCustomHeaderCb) != NULL) + { + pUserData = pState->pUserData; + } + else + { + pCustomHeaderCb = pState->pCustomHeaderCb; + pUserData = pState->pCallbackRef; + } + + // call custom header format callback, if specified + if (pCustomHeaderCb != NULL) + { + if ((iOffset = pCustomHeaderCb(pState, pInpBuf, iInpMax, NULL, 0, pUserData)) < 0) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: custom header callback error %d\n", iOffset)); + return(iOffset); + } + if (iOffset == 0) + { + iOffset = (int32_t)strlen(pInpBuf); + } + } + + // append header terminator and return header length + iOffset += ds_snzprintf(pInpBuf+iOffset, iInpMax-iOffset, "\r\n"); + + // make sure we were able to complete the header + if (iOffset > iInpMax) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: not enough buffer to format request header (have %d, need %d)\n", iInpMax, iOffset)); + pState->iInpOvr = iOffset; + return(PROTOHTTP_MINBUFF); + } + + // save a copy of the header + ds_strnzcpy(pState->strRequestHdr, pInpBuf, sizeof(pState->strRequestHdr)); + + // update buffer size + pState->iInpLen += iOffset; + + // save updated header size + pState->iHdrLen = pState->iInpLen; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpFormatConnectHeader + + \Description + Format a proxy connect request header as per + https://tools.ietf.org/html/rfc7231#section-4.3.6. + + \Input *pState - reference pointer + \Input *pStrHost - pointer to proxy hostname + \Input iPort - proxy port + + \Version 05/30/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoHttpFormatConnectHeader(ProtoHttpRefT *pState, const char *pStrHost, int32_t iPort) +{ + int32_t iOffset = 0, iInpMax; + + // save current input buffer info + pState->pInpBufTmp = pState->pInpBuf; + pState->iInpLenTmp = pState->iInpLen; + + // point to temp connect header + pState->pInpBuf = pState->strConnectHdr; + iInpMax = sizeof(pState->strConnectHdr); + + // format request header + iOffset += ds_snzprintf(pState->pInpBuf + iOffset, iInpMax - iOffset, "%s %s:%d HTTP/1.1\r\n", _ProtoHttp_strRequestNames[PROTOHTTP_REQUESTTYPE_CONNECT], pStrHost, iPort); + iOffset += ds_snzprintf(pState->pInpBuf + iOffset, iInpMax - iOffset, "Host: %s:%d\r\n", pStrHost, iPort); + // append header terminator and return header length + iOffset += ds_snzprintf(pState->pInpBuf + iOffset, iInpMax - iOffset, "\r\n"); + // update buffer size + pState->iInpLen = iOffset; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpFormatRequest + + \Description + Format a request into the local buffer. + + \Input *pState - reference pointer + \Input *pUrl - pointer to user-supplied url + \Input *pData - pointer to data to include with request, or NULL + \Input iDataLen - size of data pointed to by pData, or zero if no data + \Input eRequestType - type of request (PROTOHTTP_REQUESTTYPE_*) + + \Output + int32_t - bytes of userdata included in request + + \Version 10/07/2005 (jbrookes) Split/combined from ProtoHttpGet() and ProtoHttpPost() +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpFormatRequest(ProtoHttpRefT *pState, const char *pUrl, const char *pData, int64_t iDataLen, ProtoHttpRequestTypeE eRequestType) +{ + char strHost[sizeof(pState->strHost)], strKind[8]; + int32_t iPort, iResult, iSecure; + int32_t eState = pState->eState; + uint8_t bPortSpecified; + const char *pStrProxy; + + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] %s %s\n", pState, _ProtoHttp_strRequestNames[eRequestType], pUrl)); + pState->eRequestType = eRequestType; + + // reset various state + if (pState->eState != ST_IDLE) + { + _ProtoHttpReset(pState); + } + + // restore input buffer, if set up for proxy + if (pState->pInpBuf == pState->strConnectHdr) + { + pState->pInpBuf = pState->pInpBufTmp; + } + + // use global proxy if set, otherwise use state-local proxy + pStrProxy = (_ProtoHttp_strGlobalProxy[0] != '\0') ? _ProtoHttp_strGlobalProxy : pState->strProxy; + + // assume we don't want a new connection to start with (if this is a pipelined request, don't override the original selection) + if (pState->iInpLen == 0) + { + pState->bNewConnection = FALSE; + } + + // parse the url for kind, host, and port + if (pStrProxy[0] == '\0') + { + pUrl = ProtoHttpUrlParse2(pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure, &bPortSpecified); + } + else + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: using proxy server %s\n", pStrProxy)); + ProtoHttpUrlParse2(pStrProxy, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure, &bPortSpecified); + } + + // fill in any missing info (relative url) if available + if (_ProtoHttpApplyBaseUrl(pState, strKind, strHost, sizeof(strHost), &iPort, &iSecure, bPortSpecified) != 0) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] %s %s://%s:%d%s\n", pState, _ProtoHttp_strRequestNames[eRequestType], + iSecure ? "https" : "http", strHost, iPort, pUrl)); + } + + // determine if host, port, or security settings have changed since the previous request + if ((iSecure != pState->iSecure) || (ds_stricmp(strHost, pState->strHost) != 0) || (iPort != pState->iPort)) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] requesting new connection -- url change to %s\n", pState, strHost)); + + // reset keep-alive + pState->iKeepAlive = pState->iKeepAliveDflt; + + // save new server/port/security state + ds_strnzcpy(pState->strHost, strHost, sizeof(pState->strHost)); + pState->iPort = iPort; + pState->iSecure = iSecure; + + // make sure we use a new connection + pState->bNewConnection = TRUE; + } + + // check to see if previous connection (if any) is still active + if ((pState->bNewConnection == FALSE) && (ProtoSSLStat(pState->pSsl, 'stat', NULL, 0) < 0)) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] requesting new connection -- previous connection was closed\n", pState)); + pState->bNewConnection = TRUE; + } + + // check to make sure we are in a known valid state + if ((pState->bNewConnection == FALSE) && (eState != ST_IDLE) && (eState != ST_DONE)) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] requesting new connection -- current state of %d does not allow connection reuse\n", pState, eState)); + pState->bNewConnection = TRUE; + } + + // if executing put/post, check to see if connection reuse on request is allowed + if ((pState->bNewConnection == FALSE) && (pState->bReuseOnPost == FALSE) && ((eRequestType == PROTOHTTP_REQUESTTYPE_PUT) || (eRequestType == PROTOHTTP_REQUESTTYPE_PATCH) || (eRequestType == PROTOHTTP_REQUESTTYPE_POST))) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] requesting new connection -- reuse on put/post disabled\n", pState)); + pState->bNewConnection = TRUE; + } + + // if using a proxy server, parse original url to get target host and port for Host header + if (pStrProxy[0] != '\0') + { + pUrl = ProtoHttpUrlParse2(pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure, &bPortSpecified); + if ((ds_stricmp(pState->strProxiedHost, strHost)) || (pState->iProxiedPort != iPort) || (pState->iProxiedSecure != iSecure)) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] requesting new connection -- proxy change\n", pState)); + pState->bNewConnection = TRUE; + } + ds_strnzcpy(pState->strProxiedHost, strHost, sizeof(pState->strProxiedHost)); + pState->iProxiedPort = iPort; + pState->iProxiedSecure = iSecure; + } + else + { + pState->strProxiedHost[0] = '\0'; + } + + // format the request header + if ((iResult = _ProtoHttpFormatRequestHeader(pState, pUrl, strHost, iPort, iSecure, _ProtoHttp_strRequestNames[eRequestType], iDataLen)) < 0) + { + return(iResult); + } + + // append data to header? + if ((pData != NULL) && (iDataLen > 0)) + { + // see how much data will fit into the buffer + if (iDataLen > (pState->iInpMax - pState->iInpLen)) + { + iDataLen = (pState->iInpMax - pState->iInpLen); + } + + // copy data into buffer (must happen after _ProtoHttpFormatRequestHeader()) + ds_memcpy(pState->pInpBuf + pState->iInpLen, pData, (int32_t)iDataLen); + pState->iInpLen += iDataLen; + } + else if (iDataLen < 0) + { + // for a streaming post, return no data written + iDataLen = 0; + } + + // set headonly status + pState->bHeadOnly = (eRequestType == PROTOHTTP_REQUESTTYPE_HEAD) ? TRUE : FALSE; + + // handle connect flow when using a secure proxy + if ((pStrProxy[0] != '\0') && iSecure) + { + _ProtoHttpFormatConnectHeader(pState, strHost, iPort); + pState->bConnProxy = TRUE; + } + else + { + pState->bConnProxy = FALSE; + } + + return((int32_t)iDataLen); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpSendRequest + + \Description + Send a request (already formatted in buffer) to the server. + + \Input *pState - reference pointer + + \Output + None. + + \Version 05/19/2009 (jbrookes) Split from _ProtoHttpFormatRequest() +*/ +/********************************************************************************F*/ +static void _ProtoHttpSendRequest(ProtoHttpRefT *pState) +{ + int32_t iResult; + char cTest; + + /* if we still want to reuse the current connection, try and receive on it and + make sure it is in a valid state (not an error state and no data to be read) */ + if (pState->bNewConnection == FALSE) + { + if ((iResult = ProtoSSLRecv(pState->pSsl, &cTest, sizeof(cTest))) > 0) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] requesting new connection -- receive on previous connection returned data (0x%02x)\n", pState, (uint8_t)cTest)); + pState->bNewConnection = TRUE; + } + else if (iResult < 0) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] requesting new connection -- received %d error response from previous connection\n", pState, iResult)); + pState->bNewConnection = TRUE; + } + } + + // handle proxy connections + if (pState->bConnProxy) + { + // restore user request if we're in the proxy connect flow and we're already connected + if (!pState->bNewConnection) + { + NetPrintf(("protohttp: [%p] bypassing proxy connect (already connected)\n", pState)); + pState->pInpBuf = pState->pInpBufTmp; + pState->iInpLen = pState->iInpLenTmp; + pState->bUpgradeSSL = FALSE; + } + else + { + // if a new proxy connection, mark for SSL upgrade + NetPrintf(("protohttp: [%p] new proxy connection\n", pState)); + pState->bUpgradeSSL = TRUE; + } + } + + // set connection timeout + pState->uTimer = NetTick() + pState->uTimeout; + + // see if we need a new connection + if (pState->bNewConnection == TRUE) + { + // close the existing connection, if not already closed + _ProtoHttpClose(pState, "new connection"); + + // start connect + NetPrintfVerbose((pState->iVerbose, 2, "protohttp: [%p] connect start (tick=%u)\n", pState, NetTick())); + ProtoSSLConnect(pState->pSsl, pState->iSecure, pState->strHost, 0, pState->iPort); + pState->eState = ST_CONN; + pState->bClosed = FALSE; + } + else + { + // advance state + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] reusing previous connection (keep-alive)\n", pState)); + pState->eState = ST_SEND; + } + + // if we requested a connection close, the server may not tell us, so remember it here + if (pState->iKeepAlive == 0) + { + pState->bCloseHdr = TRUE; + } + + // count the attempt + pState->iKeepAlive += 1; + + // call the update routine just in case operation can complete + ProtoHttpUpdate(pState); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpRetrySendRequest + + \Description + If the connection was a keep-alive connection and the request method was + idempotent (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.2 + for a definition of idempotent), we automatically re-issue the request one + time on a fresh connection. + + \Input *pState - reference pointer + + \Output + uint32_t - zero=did not reissue request, else we did + + \Version 07/14/2009 (jbrookes) Split from ProtoHttpUpdate() +*/ +/********************************************************************************F*/ +static uint32_t _ProtoHttpRetrySendRequest(ProtoHttpRefT *pState) +{ + // if this was not a keep-alive connection, we do not retry + if (pState->bNewConnection == TRUE) + { + return(0); + } + // if this was a POST request, we do not retry as the method is not idempotent + if (pState->eRequestType == PROTOHTTP_REQUESTTYPE_POST) + { + NetPrintf(("protohttp: cannot execute automatic retry of post request on keep-alive connection\n")); + return(0); + } + + // retry the connection + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] request failure on keep-alive connection; retrying\n", pState)); + _ProtoHttpClose(pState, "retry"); + + // rewind buffer pointers to resend header + pState->iInpLen = pState->iHdrLen; + pState->iInpOff = 0; + + /* set keep-alive so we don't try another reconnect attempt, but we do + request keep-alive on any further requests if this one succeeds */ + pState->iKeepAlive = 1; + + // reconnect + ProtoSSLConnect(pState->pSsl, pState->iSecure, pState->strHost, 0, pState->iPort); + pState->eState = ST_CONN; + pState->bClosed = FALSE; + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpResizeBuffer + + \Description + Resize the buffer + + \Input *pState - reference pointer + \Input iBufMax - new buffer size + + \Output + int32_t - zero=success, else failure + + \Version 02/21/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpResizeBuffer(ProtoHttpRefT *pState, int32_t iBufMax) +{ + int32_t iCopySize; + char *pInpBuf; + + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] resizing input buffer from %d to %d bytes\n", pState, pState->iInpMax, iBufMax)); + if ((pInpBuf = DirtyMemAlloc(iBufMax, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL) + { + NetPrintf(("protohttp: [%p] could not resize input buffer\n", pState)); + return(-1); + } + + // calculate size of data to copy from old buffer to new + if ((iCopySize = pState->iInpLen - pState->iInpOff) > iBufMax) + { + NetPrintf(("protohttp: [%p] warning; resize of input buffer is losing %d bytes of data\n", pState, iCopySize - iBufMax)); + iCopySize = iBufMax; + } + // copy valid contents of input buffer, if any, to new buffer + ds_memcpy(pInpBuf, pState->pInpBuf+pState->iInpOff, iCopySize); + + // dispose of input buffer + _ProtoHttpFreeInputBuf(pState); + + // update buffer variables + pState->pInpBuf = pInpBuf; + pState->iInpOff = 0; + pState->iInpLen = iCopySize; + pState->iInpMax = iBufMax; + + // clear overflow count + pState->iInpOvr = 0; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpCompactBuffer + + \Description + Compact the buffer + + \Input *pState - reference pointer + + \Output + int32_t - amount of space freed by compaction + + \Version 07/02/2009 (jbrookes) Extracted from ProtoHttpRecv() +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpCompactBuffer(ProtoHttpRefT *pState) +{ + int32_t iCompacted = 0; + // make sure it needs compacting + if (pState->iInpOff > 0) + { + // compact the buffer + if (pState->iInpOff < pState->iInpLen) + { + memmove(pState->pInpBuf, pState->pInpBuf+pState->iInpOff, pState->iInpLen-pState->iInpOff); + iCompacted = pState->iInpOff; + } + pState->iInpLen -= pState->iInpOff; + pState->iInpOff = 0; + pState->bCompactRecv = FALSE; + } + return(iCompacted); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpParseHeader + + \Description + Parse incoming HTTP header. The HTTP header is presumed to be at the + beginning of the input buffer. + + \Input *pState - reference pointer + + \Output + int32_t - negative=not ready or error, else success + + \Version 11/12/2004 (jbrookes) First Version. +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpParseHeader(ProtoHttpRefT *pState) +{ + char *s = pState->pInpBuf; + char *t = pState->pInpBuf+pState->iInpLen-3; + char strTemp[128]; + ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb; + void *pUserData; + + // scan for blank line marking body start + while ((s != t) && ((s[0] != '\r') || (s[1] != '\n') || (s[2] != '\r') || (s[3] != '\n'))) + { + s++; + } + if (s == t) + { + // header is incomplete + return(-1); + } + + // save the head size + pState->iHeadSize = (int32_t)(s+4-pState->pInpBuf); + // terminate header for easy parsing + s[2] = s[3] = 0; + + // make sure the header is valid + if (pState->bVerifyHdr) + { + if (strncmp(pState->pInpBuf, "HTTP", 4)) + { + // header is invalid + NetPrintf(("protohttp: [%p] invalid result type\n", pState)); + pState->eState = ST_FAIL; + return(-2); + } + } + + // detect if it is a 1.0 response + pState->bHttp1_0 = !strncmp(pState->pInpBuf, "HTTP/1.0", 8); + + // parse header code + pState->iHdrCode = ProtoHttpParseHeaderCode(pState->pInpBuf); + + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] received %d response (%d bytes)\n", pState, pState->iHdrCode, pState->iHeadSize)); + if (pState->iVerbose > 1) + { + NetPrintWrap(pState->pInpBuf, 80); + } + #endif + + // parse content-length field + if (ProtoHttpGetHeaderValue(pState, pState->pInpBuf, "content-length", strTemp, sizeof(strTemp), NULL) != -1) + { + pState->iBodySize = ds_strtoll(strTemp, NULL, 10); + pState->bChunked = FALSE; + } + else + { + pState->iBodySize = -1; + } + + // parse last-modified header + if (ProtoHttpGetHeaderValue(pState, pState->pInpBuf, "last-modified", strTemp, sizeof(strTemp), NULL) != -1) + { + pState->iHdrDate = (int32_t)ds_strtotime(strTemp); + } + else + { + pState->iHdrDate = 0; + } + + // parse transfer-encoding header + if (ProtoHttpGetHeaderValue(pState, pState->pInpBuf, "transfer-encoding", strTemp, sizeof(strTemp), NULL) != -1) + { + pState->bChunked = !ds_stricmp(strTemp, "chunked"); + } + + // parse connection header + if (pState->bCloseHdr == FALSE) + { + ProtoHttpGetHeaderValue(pState, pState->pInpBuf, "connection", strTemp, sizeof(strTemp), NULL); + pState->bCloseHdr = !ds_stricmp(strTemp, "close"); + // if server is closing the connection and we are expecting subsequent piped results, we should not expect to get them + if (pState->bCloseHdr && (pState->iPipedRequests > 0)) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] lost %d piped requests due to server connection-close request\n", pState, pState->iPipedRequests)); + pState->iPipedRequests = 0; + pState->bPipedRequestsLost = TRUE; + } + } + + // note if it is an informational header + pState->bInfoHdr = PROTOHTTP_GetResponseClass(pState->iHdrCode) == PROTOHTTP_RESPONSE_INFORMATIONAL; + + // copy header to header cache + ds_strnzcpy(pState->strHdr, pState->pInpBuf, sizeof(pState->strHdr)); + + // request level callback takes priority to global + if ((pReceiveHeaderCb = pState->pReqReceiveHeaderCb) != NULL) + { + pUserData = pState->pUserData; + } + else + { + pReceiveHeaderCb = pState->pReceiveHeaderCb; + pUserData = pState->pCallbackRef; + } + + // trigger recv header user callback, if specified (and if this wasn't a proxy connect request) + if ((pReceiveHeaderCb != NULL) && (!pState->bConnProxy || (pState->pInpBufTmp == NULL))) + { + pReceiveHeaderCb(pState, pState->pInpBuf, (uint32_t)strlen(pState->pInpBuf), pUserData); + } + + // remove header from input buffer + pState->iInpOff = pState->iHeadSize; + pState->iInpCnt = pState->iInpLen - pState->iHeadSize; + + // header successfully parsed + return(0); +} + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _ProtoHttpDisplayHeader + + \Description + Display header to debug output [DEBUG ONLY] + + \Input *pState - reference pointer + + \Version 05/03/2010 (jbrookes) Refactored out of ProtoHttpUpdate() +*/ +/********************************************************************************F*/ +static void _ProtoHttpDisplayHeader(ProtoHttpRefT *pState) +{ + // if we just sent a header, display header to debug output + if (pState->iVerbose > 1) + { + int32_t iRequestType; + for (iRequestType = 0; iRequestType < PROTOHTTP_NUMREQUESTTYPES; iRequestType += 1) + { + if (!strncmp(pState->pInpBuf, _ProtoHttp_strRequestNames[iRequestType], strlen(_ProtoHttp_strRequestNames[iRequestType]))) + { + char *pHdrEnd = pState->pInpBuf + pState->iHdrLen; + char cHdrChr = *pHdrEnd; + *pHdrEnd = '\0'; + NetPrintf(("protohttp: [%p] sent request:\n", pState)); + NetPrintfVerbose((pState->iVerbose, 2, "protohttp: [%p] tick=%u\n", pState, NetTick())); + NetPrintWrap(pState->pInpBuf, 80); + *pHdrEnd = cHdrChr; + break; + } + } + } +} +#endif + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpProcessInfoHeader + + \Description + Handles an informational response header (response code=1xx) + + \Input *pState - reference pointer + + \Output + None. + + \Version 05/15/2008 (jbrookes) First Version. +*/ +/********************************************************************************F*/ +static void _ProtoHttpProcessInfoHeader(ProtoHttpRefT *pState) +{ + // ignore the response + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] ignoring %d response from server\n", pState, pState->iHdrCode)); + + // remove header from input buffer + memmove(pState->pInpBuf, pState->pInpBuf+pState->iInpOff, pState->iInpLen-pState->iInpOff); + pState->iInpLen -= pState->iInpOff; + // reset processing offset + pState->iInpOff = 0; + + // reset state to process next header + pState->eState = ST_HEAD; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpProcessRedirect + + \Description + Handle redirection header (response code=3xx) + + \Input *pState - reference pointer + + \Notes + A maximum of PROTOHTTP_MAXREDIRECT redirections is allowed. Any further + redirection attempts will result in a failure state. A redirection + Location url is limited based on the size of the http receive buffer. + + Auto-redirection is implemented as specified by RFC: + (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3); + if auto-redirection is not performed, processing ends and it is the + responsibility of the application to recognize the 3xx result code + and handle it accordingly. + + Auto-redirection can be disabled by setting the maximum number of + redirections allowed to zero. + + \Version 11/12/2004 (jbrookes) First Version. +*/ +/********************************************************************************F*/ +static void _ProtoHttpProcessRedirect(ProtoHttpRefT *pState) +{ + char strHost[sizeof(pState->strHost)], strKind[PROTOHTTPUTIL_SCHEME_MAX]; + int32_t iPort, iResult, iSecure, iUrlLen; + char *pUrlBuf; + + // do not auto-redirect multiplechoices or notmodified responses + if ((pState->iHdrCode == PROTOHTTP_RESPONSE_MULTIPLECHOICES) || (pState->iHdrCode == PROTOHTTP_RESPONSE_NOTMODIFIED)) + { + return; + } + // do not auto-redirect responses that are not head or get requests, and are not a SEEOTHER response + if ((pState->eRequestType != PROTOHTTP_REQUESTTYPE_GET) && (pState->eRequestType != PROTOHTTP_REQUESTTYPE_HEAD)) + { + /* As per HTTP 1.1 RFC, 303 SEEOTHER POST requests may be auto-redirected to a GET requset. 302 FOUND responses + to a POST request are not supposed to be auto-redirected; however, there is a note in the RFC indicating that + this is a common client behavior, and it is additionally a common server behavior to use 302 even when automatic + redirection is intended, as some clients do not support 303 SEEOTHER. Therefore, we perform auto-redirection + on 302 FOUND responses to POST requests with a GET request for compatibility with servers that choose this + behavior */ + if ((pState->iHdrCode == PROTOHTTP_RESPONSE_FOUND) || (pState->iHdrCode == PROTOHTTP_RESPONSE_SEEOTHER)) + { + pState->eRequestType = PROTOHTTP_REQUESTTYPE_GET; + pState->iPostSize = 0; + } + else + { + return; + } + } + + // get size of location header + if ((iUrlLen = ProtoHttpGetLocationHeader(pState, pState->pInpBuf, NULL, 0, NULL)) <= 0) + { + NetPrintf(("protohttp: [%p] no location included in redirect header\n", pState)); + pState->eState = ST_FAIL; + return; + } + + // get url at the end of input buffer + pUrlBuf = pState->pInpBuf + pState->iInpMax - iUrlLen; + if (ProtoHttpGetLocationHeader(pState, pState->pInpBuf, pUrlBuf, iUrlLen, NULL) != 0) + { + NetPrintf(("protohttp: [%p] failed to get location header url", pState)); + pState->eState = ST_FAIL; + return; + } + + // parse url for protocol + ProtoHttpUrlParse(pUrlBuf, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure); + // only auto-redirect if http/s protocol + if ((ds_stricmp(strKind, "https") != 0) && (ds_stricmp(strKind, "http") != 0)) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] skipping auto-redirection of non-http protocol '%s'\n", pState, strKind)); + return; + } + + // process based on max redirections allowed; zero=disabled + if (pState->iMaxRedirect == 0) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] auto-redirection disabled\n", pState)); + return; + } + else if (++pState->iNumRedirect > pState->iMaxRedirect) + { + NetPrintf(("protohttp: [%p] maximum number of redirections (%d) exceeded\n", pState, pState->iMaxRedirect)); + pState->eState = ST_FAIL; + return; + } + + // close connection? + if (pState->bCloseHdr) + { + _ProtoHttpClose(pState, "server request"); + } + + // clear piped result count + pState->iPipedRequests = 0; + pState->bPipedRequestsLost = FALSE; + + // format redirection request + if ((iResult = _ProtoHttpFormatRequest(pState, pUrlBuf, NULL, 0, pState->eRequestType)) < 0) + { + NetPrintf(("protohttp: redirect header format request failed\n")); + pState->eState = ST_FAIL; + return; + } + // send redirection request + _ProtoHttpSendRequest(pState); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpChunkProcess + + \Description + Process output if chunked encoding. + + \Input *pState - reference pointer + \Input iBufMax - maximum number of bytes to return (buffer size) + + \Output + int32_t - number of bytes available + + \Notes + Does not support anything but chunked encoding. Does not support optional + end-transfer header (anything past the terminating 0 chunk is discarded). + + \Version 04/05/2005 (jbrookes) First Version. +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpChunkProcess(ProtoHttpRefT *pState, int32_t iBufMax) +{ + int32_t iChkSize, iLen, iLenInt; + + // if no new data, bail + if (pState->iInpLen == pState->iInpOff) + { + return(0); + } + + // see if we are starting a new chunk + if (pState->iChkLen == 0) + { + char *s = pState->pInpBuf+pState->iInpOff, *s2; + char *t = pState->pInpBuf+pState->iInpLen-1; + + // make sure we have a complete chunk header + for (s2=s; (s2 < t) && ((s2[0] != '\r') || (s2[1] != '\n')); s2++) + ; + if (s2 == t) + { + if (pState->iInpLen == pState->iInpMax) + { + // tell ProtoHttpRecv() to compact recv buffer next time around + pState->bCompactRecv = TRUE; + } + return(0); + } + + // get the chunk length + if ((pState->iChkLen = (int32_t)strtol(s, NULL, 16)) == 0) + { + // terminating chunk - clear the buffer and set state to done + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] parsed end chunk\n", pState)); + pState->iInpOff += s2-s+4; // remove chunk header plus terminating crlf + pState->iBodySize = pState->iBodyRcvd; + pState->eState = ST_DONE; + + // return no data + return(0); + } + else + { + NetPrintfVerbose((pState->iVerbose, 2, "protohttp: [%p] parsed chunk size=%d\n", pState, pState->iChkLen)); + } + + // remove header from input + pState->iInpOff += s2-s+2; + } + + // calculate length + iLenInt = pState->iInpLen - pState->iInpOff; + iLen = (iLenInt > iBufMax) ? iBufMax : iLenInt; + + // have we got all the data? + if (iLen >= pState->iChkLen) + { + /* got chunk and trailer, so consume it. we use the internal data size rather than the data size + that is constrained by the user buffer size as the user isn't going to read the chunk data and + therefore must not be limited by a requirement to read it. not doing so results in the user not + being able to consume the whole chunk if their buffer does not have room for the chunk trailer */ + if (iLenInt >= (pState->iChkLen+2)) + { + // reset chunk length + iChkSize = pState->iChkLen; + pState->iChkLen = 0; + } + else + { + if (pState->iChkLen > 1) + { + /* consume data and compact recv buffer to make room for the trailer; however we leave one + byte unread to cover the case where the chunk trailer spans a recv boundary, and where the + next byte is not available to read from the network. in such a case we wait until the chunk + trailer is completely available before finishing the chunk, otherwise we end up reading the + one byte of chunk trailer as if it were the next chunk size */ + iChkSize = pState->iChkLen-1; + pState->iChkLen -= iChkSize; + } + else + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] waiting for chunk trailer\n", pState)); + iChkSize = 0; + } + // tell ProtoHttpRecv() to compact recv buffer next time around + pState->bCompactRecv = TRUE; + } + } + else + { + iChkSize = iLen; + pState->iChkLen -= iChkSize; + } + + return(iChkSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpSend + + \Description + Try and send some data. If data is sent, the timout value is updated. + + \Input *pState - reference pointer + \Input *pStrBuf - pointer to buffer to send from + \Input iSize - amount of data to try and send + + \Output + int32_t - negative=error, else number of bytes sent + + \Version 11/18/2004 (jbrookes) First Version. +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpSend(ProtoHttpRefT *pState, const char *pStrBuf, int32_t iSize) +{ + int32_t iResult; + + // try and send some data + if ((iResult = ProtoSSLSend(pState->pSsl, pStrBuf, iSize)) > 0) + { + #if DIRTYCODE_LOGGING + if (pState->iVerbose > 2) + { + NetPrintf(("protohttp: [%p] sent %d bytes\n", pState, iResult)); + } + if (pState->iVerbose > 3) + { + NetPrintMem(pStrBuf, iResult, "http-send"); + } + #endif + + // sent data, so update timeout + pState->uTimer = NetTick() + pState->uTimeout; + } + else if (iResult < 0) + { + NetPrintf(("protohttp: [%p] error %d sending %d bytes\n", pState, iResult, iSize)); + pState->eState = ST_FAIL; + pState->iSslFail = ProtoSSLStat(pState->pSsl, 'fail', NULL, 0); + pState->iHresult = ProtoSSLStat(pState->pSsl, 'hres', NULL, 0); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpSendBuff + + \Description + Send data queued in buffer. + + \Input *pState - reference pointer + + \Output + int32_t - negative=error, else number of bytes sent + + \Version 01/29/2010 (jbrookes) First Version. +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpSendBuff(ProtoHttpRefT *pState) +{ + int32_t iResult, iSentSize = 0; + if ((iResult = _ProtoHttpSend(pState, pState->pInpBuf+pState->iInpOff, pState->iInpLen)) > 0) + { + pState->iInpOff += iResult; + pState->iInpLen -= iResult; + if (pState->iInpLen == 0) + { + pState->iInpOff = 0; + } + iSentSize = iResult; + } + else if (iResult < 0) + { + NetPrintf(("protohttp: [%p] failed to send request data (err=%d)\n", pState, iResult)); + pState->iInpLen = 0; + pState->eState = ST_FAIL; + iSentSize = -1; + } + return(iSentSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpFormatChunk + + \Description + Format source data into chunked format, ready to be sent. + + \Input *pState - reference pointer + \Input *pStrBuf - pointer to buffer to send from + \Input iSize - amount of data to try and send (zero for stream completion) + + \Output + int32_t - negative=space required, else number of bytes of user data sent + + \Notes + Space is reserved for an end chunk to be buffered so the ProtoHttpSend() + call indicating the stream is complete does not ever need to be retried. + + \Version 03/21/2014 (jbrookes) Refactored from _ProtoHttpSendChunk() +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpFormatChunk(ProtoHttpRefT *pState, const char *pStrBuf, int32_t iSize) +{ + char *pInpBuf = pState->pInpBuf + pState->iInpLen + pState->iInpOff; + int32_t iInpMax = pState->iInpMax - pState->iInpLen - pState->iInpOff; + int32_t iSendSize, iSentSize; + + // make sure we have enough room for a max chunk header, chunk data, *and* possible end chunk + if ((iSendSize = iSize) > 0) + { + if (iSendSize > (iInpMax - (6+2+2 + 1+2+2))) + { + iSendSize = (iInpMax - (6+2+2 + 1+2+2)); + } + } + else + { + pState->iPostSize = 0; + } + + // if we have room to buffer chunk data, or this is the end chunk, do it + if ((iSendSize > 0) || (iSize == 0)) + { + // format chunk into buffer + iSentSize = ds_snzprintf(pInpBuf, iInpMax, "%x\r\n", iSendSize); + if (iSendSize > 0) + { + ds_memcpy(pInpBuf+iSentSize, pStrBuf, iSendSize); + iSentSize += iSendSize; + } + iSentSize += ds_snzprintf(pInpBuf+iSentSize, iInpMax, "\r\n"); + + // add chunk to length + pState->iInpLen += iSentSize; + } + else + { + iSendSize = 0; + } + // return size of data buffered to caller + return(iSendSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpSendChunk + + \Description + Send the specified data using chunked transfer encoding. + + \Input *pState - reference pointer + \Input *pStrBuf - pointer to buffer to send from + \Input iSize - amount of data to try and send (zero for stream completion) + + \Output + int32_t - negative=error, else number of bytes of user data sent + + \Notes + $$TODO - update + Unlike _ProtoHttpSend(), which simply passes the data to ProtoSSL and returns + the amount of data that was accepted, this function buffers one or more chunks + of data, up to the buffer limit. It tries to send the buffered data immediately, + however if the band the data is sent by ProtoHttpUpdate(). + This guarantees that a chunk will be sent correctly even if a partial send + occurs. + + \Version 05/07/2008 (jbrookes) First Version. +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpSendChunk(ProtoHttpRefT *pState, const char *pStrBuf, int32_t iSize) +{ + int32_t iBuffSize, iCompSize; + + // try to buffer chunk data + if ((iBuffSize = _ProtoHttpFormatChunk(pState, pStrBuf, iSize)) < 0) + { + // try compacting send buffer to make room for more data + if ((iCompSize = _ProtoHttpCompactBuffer(pState)) > 0) + { + // buffer was compacted, retry + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] compacted send buffer (%d bytes)\n", pState, iCompSize)); + // try to buffer chunk data again + iBuffSize = _ProtoHttpFormatChunk(pState, pStrBuf, iSize); + } + } + + // try to send any buffered data + _ProtoHttpSendBuff(pState); + + // return buffered data size to caller + return((iBuffSize > 0) ? iBuffSize : 0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpRecvData + + \Description + Try and receive some data. If data is received, the timout value is + updated. + + \Input *pState - reference pointer + \Input *pStrBuf - [out] pointer to buffer to receive into + \Input iSize - amount of data to try and receive + + \Output + int32_t - negative=error, else success + + \Version 11/12/2004 (jbrookes) First Version. +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpRecvData(ProtoHttpRefT *pState, char *pStrBuf, int32_t iSize) +{ + // if we have no buffer space, don't try to receive + if (iSize == 0) + { + return(0); + } + + // try and receive some data + if ((pState->iRecvRslt = ProtoSSLRecv(pState->pSsl, pStrBuf, iSize)) > 0) + { + #if DIRTYCODE_LOGGING + if (pState->iVerbose > 2) + { + NetPrintf(("protohttp: [%p] recv %d bytes\n", pState, pState->iRecvRslt)); + } + if (pState->iVerbose > 3) + { + NetPrintMem(pStrBuf, pState->iRecvRslt, "http-recv"); + } + #endif + + // received data, so update timeout + pState->uTimer = NetTick() + pState->uTimeout; + } + return(pState->iRecvRslt); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpHeaderRecvFirstLine + + \Description + Try and receive the first line of the response header. We receive into + the header cache buffer directly so we can avoid conflicting with possible + use of the input buffer sending streaming data. This allows us to receive + while we are sending, which is useful in detecting situations where the + server responsds with an error in the middle of a streaming post transaction. + + \Input *pState - reference pointer + + \Output + int32_t - negative=error, zero=pending, else success + + \Version 01/13/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpHeaderRecvFirstLine(ProtoHttpRefT *pState) +{ + int32_t iResult; + for (iResult = 1; (iResult == 1) && (pState->iHdrOff < 64); ) // hard-coded max limit in case this is not http + { + if ((iResult = _ProtoHttpRecvData(pState, pState->strHdr+pState->iHdrOff, 1)) == 1) + { + pState->iHdrOff += 1; + if ((pState->strHdr[pState->iHdrOff-2] == '\r') && (pState->strHdr[pState->iHdrOff-1] == '\n')) + { + // we've received the first line of the response header... get response code + int32_t iHdrCode = ProtoHttpParseHeaderCode(pState->strHdr); + + /* if this is a streaming post, check the response code. we do this because a server might + abort a streaming upload prematurely if the file size is too large and this allows the + client to stop sending gracefully; typically the server will issue a forceful reset if + the client keeps sending data after being sent notification */ + if ((pState->iPostSize == -1) && (iHdrCode != PROTOHTTP_RESPONSE_CONTINUE) && (PROTOHTTP_GetResponseClass(iHdrCode) != PROTOHTTP_RESPONSE_SUCCESSFUL)) + { + NetPrintf(("protohttp [%p] got unexpected response %d during streaming post; aborting send\n", pState, iHdrCode)); + pState->iPostSize = 0; + } + break; + } + } + } + // return result unless we are in the middle of a streaming post + return((pState->iPostSize != -1) ? iResult : 0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpHeaderProcess + + \Description + Check for header completion and process header data + + \Input *pState - reference pointer + + \Version 05/03/2012 (jbrookes) Refactored out of ProtoHttpUpdate() +*/ +/********************************************************************************F*/ +static void _ProtoHttpHeaderProcess(ProtoHttpRefT *pState) +{ + // try parsing header + if (_ProtoHttpParseHeader(pState) < 0) + { + // was there a prior SOCKERR_CLOSED error? + if (pState->iRecvRslt < 0) + { + NetPrintf(("protohttp: [%p] ST_HEAD append got ST_FAIL (err=%d, len=%d)\n", pState, pState->iRecvRslt, pState->iInpLen)); + pState->eState = ST_FAIL; + } + // if the buffer is full, we don't have enough room to receive the header + if (pState->iInpLen == pState->iInpMax) + { + if (pState->iInpOvr == 0) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] input buffer too small for response header\n", pState)); + } + pState->iInpOvr = pState->iInpLen+1; + } + return; + } + + /* workaround for non-compliant 1.0 servers - some 1.0 servers specify a + Content Length of zero invalidly. if the response is a 1.0 response + and the Content Length is zero, and we've gotten body data, we set + the Content Length to -1 (indeterminate) */ + if ((pState->bHttp1_0 == TRUE) && (pState->iBodySize == 0) && (pState->iInpCnt > 0)) + { + pState->iBodySize = -1; + } + + // handle final processing + if ((pState->bHeadOnly == TRUE) || (pState->iHdrCode == PROTOHTTP_RESPONSE_NOCONTENT) || (pState->iHdrCode == PROTOHTTP_RESPONSE_NOTMODIFIED)) + { + // only needed the header (or response did not include a body; see HTTP RFC 1.1 section 4.4) -- all done + pState->eState = ST_DONE; + } + else if ((pState->iBodySize >= 0) && (pState->iInpCnt >= pState->iBodySize)) + { + // we got entire body with header -- all done + pState->eState = ST_DONE; + } + else + { + // wait for rest of body + pState->eState = ST_BODY; + } + + // handle response code? + if (PROTOHTTP_GetResponseClass(pState->iHdrCode) == PROTOHTTP_RESPONSE_INFORMATIONAL) + { + _ProtoHttpProcessInfoHeader(pState); + } + else if (PROTOHTTP_GetResponseClass(pState->iHdrCode) == PROTOHTTP_RESPONSE_REDIRECTION) + { + _ProtoHttpProcessRedirect(pState); + } + + /* handle upgrade to ssl after connect; note we have to check state because a redirection processed immediately + above can issue a new proxy connection requiring an upgrade, but it won't be in the right state yet */ + if (pState->bUpgradeSSL && (pState->eState == ST_BODY)) + { + if (ProtoSSLControl(pState->pSsl, 'secu', 0, 0, NULL) < 0) + { + NetPrintf(("protossl: failed to upgrade connection to SSL\n")); + pState->eState = ST_FAIL; + return; + } + + NetPrintf(("protossl: upgrading connection to SSL\n")); + ProtoSSLControl(pState->pSsl, 'host', 0, 0, pState->strProxiedHost); + + // restore and send user request + pState->pInpBuf = pState->pInpBufTmp; + pState->iInpLen = pState->iInpLenTmp; + pState->iInpOff = 0; + pState->pInpBufTmp = NULL; + pState->iInpLenTmp = 0; + + // send the request + pState->bNewConnection = FALSE; + pState->eState = ST_CONN; + + // upgrade completed + pState->bUpgradeSSL = FALSE; + // set keep-alive + pState->iKeepAlive += 1; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpRecvBody + + \Description + Attempt to recevive and process body data + + \Input *pState - reference pointer + + \Output + int32_t - zero=no data available + + \Version 05/03/2012 (jbrookes) Refactored out of ProtoHttpUpdate() +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpRecvBody(ProtoHttpRefT *pState) +{ + int32_t iResult; + + // reset pointers if needed + if ((pState->iInpLen > 0) && (pState->iInpOff == pState->iInpLen)) + { + pState->iInpOff = pState->iInpLen = 0; + } + + // check for data + iResult = pState->iInpMax - pState->iInpLen; + if (iResult <= 0) + { + // always return zero bytes since buffer is full + iResult = 0; + } + else + { + // try to add to buffer + iResult = _ProtoHttpRecvData(pState, pState->pInpBuf+pState->iInpLen, iResult); + } + if (iResult == 0) + { + return(0); + } + + // check for connection close + if ((iResult == SOCKERR_CLOSED) && ((pState->iBodySize == -1) || (pState->iBodySize == pState->iInpCnt))) + { + if (!pState->bChunked) + { + pState->iBodySize = pState->iInpCnt; + } + pState->bCloseHdr = TRUE; + pState->eState = ST_DONE; + } + else if (iResult > 0) + { + // add to buffer + pState->iInpLen += iResult; + pState->iInpCnt += iResult; + + // check for end of body + if ((pState->iBodySize >= 0) && (pState->iInpCnt >= pState->iBodySize)) + { + pState->eState = ST_DONE; + } + } + else if (iResult < 0) + { + NetPrintf(("protohttp: [%p] ST_FAIL (err=%d)\n", (uintptr_t)pState, iResult)); + pState->eState = ST_FAIL; + pState->iSslFail = ProtoSSLStat(pState->pSsl, 'fail', NULL, 0); + pState->iHresult = ProtoSSLStat(pState->pSsl, 'hres', NULL, 0); + } + + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpRecv + + \Description + Return the actual url data. + + \Input *pState - reference pointer + \Input *pBuffer - buffer to store data in + \Input iBufMin - minimum number of bytes to return (returns zero if this number is not available) + \Input iBufMax - maximum number of bytes to return (buffer size) + + \Output + int32_t - negative=error, zero=no data available or bufmax <= 0, positive=number of bytes read + + \Version 04/12/2000 (gschaefer) First Version +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpRecv(ProtoHttpRefT *pState, char *pBuffer, int32_t iBufMin, int32_t iBufMax) +{ + int32_t iLen; + + // early out for failure result + if (pState->eState == ST_FAIL) + { + int32_t iResult = PROTOHTTP_RECVFAIL; + if (pState->iNumRedirect > pState->iMaxRedirect) + { + iResult = PROTOHTTP_RECVRDIR; + } + else if (pState->bTimeout) + { + iResult = PROTOHTTP_TIMEOUT; + } + return(iResult); + } + // check for input buffer too small for header + if (pState->iInpOvr > 0) + return(PROTOHTTP_MINBUFF); + // waiting for data + if ((pState->eState != ST_BODY) && (pState->eState != ST_DONE)) + return(PROTOHTTP_RECVWAIT); + + // if they only wanted head, thats all they get + if (pState->bHeadOnly == TRUE) + return(PROTOHTTP_RECVHEAD); + + // if they are querying only for done state when no more data is available to be read + if((iBufMax == 0) && ((pState->eState == ST_DONE) && (pState->iBodyRcvd == pState->iBodySize))) + return(PROTOHTTP_RECVDONE); + + // make sure range is valid + if (iBufMax < 1) + return(0); + + // clamp the range + if (iBufMin < 1) + iBufMin = 1; + if (iBufMax < iBufMin) + iBufMax = iBufMin; + if (iBufMin > pState->iInpMax) + iBufMin = pState->iInpMax; + if (iBufMax > pState->iInpMax) + iBufMax = pState->iInpMax; + + // see if we need to shift buffer + if ((iBufMin > pState->iInpMax-pState->iInpOff) || (pState->bCompactRecv == TRUE)) + { + // compact receive buffer + _ProtoHttpCompactBuffer(pState); + // give chance to get more data + _ProtoHttpRecvBody(pState); + } + + // figure out how much data is available + if (pState->bChunked == TRUE) + { + iLen = _ProtoHttpChunkProcess(pState, iBufMax); + } + else if ((iLen = (pState->iInpLen - pState->iInpOff)) > iBufMax) + { + iLen = iBufMax; + } + + // check for end of data + if ((iLen == 0) && (pState->eState == ST_DONE)) + { + return(PROTOHTTP_RECVDONE); + } + + // special check for responses with trailing piped data + if (pState->iPipedRequests > 0) + { + // check for end of this transaction + if (pState->iBodyRcvd == pState->iBodySize) + { + return(PROTOHTTP_RECVDONE); + } + // clamp available data to body size + if ((pState->iBodySize != -1) && (iLen > (int32_t)(pState->iBodySize - pState->iBodyRcvd))) + { + iLen = (int32_t)(pState->iBodySize - pState->iBodyRcvd); + } + } + + // see if there is enough to return + if ((iLen >= iBufMin) || (pState->iInpCnt == pState->iBodySize)) + { + // return data to caller + if (pBuffer != NULL) + { + ds_memcpy(pBuffer, pState->pInpBuf+pState->iInpOff, iLen); + + #if DIRTYCODE_LOGGING + if (pState->iVerbose > 3) + { + NetPrintf(("protohttp: [%p] read %d bytes\n", pState, iLen)); + } + if (pState->iVerbose > 4) + { + NetPrintMem(pBuffer, iLen, "http-read"); + } + #endif + } + pState->iInpOff += iLen; + pState->iBodyRcvd += iLen; + + // if we're at the end of a chunk, skip trailing crlf + if ((pState->bChunked == TRUE) && (pState->iChkLen == 0)) + { + pState->iInpOff += 2; + } + + // return bytes read + return(iLen); + } + + // nothing available + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpWriteCbProcess + + \Description + User write callback processing, if write callback is set + + \Input *pState - reference pointer + + \Version 05/03/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoHttpWriteCbProcess(ProtoHttpRefT *pState) +{ + ProtoHttpWriteCbInfoT WriteCbInfo; + int32_t iResult; + + ds_memclr(&WriteCbInfo, sizeof(WriteCbInfo)); + WriteCbInfo.eRequestType = pState->eRequestType; + WriteCbInfo.eRequestResponse = (ProtoHttpResponseE)pState->iHdrCode; + + /* download more data when the following are true: we are in the body state or we are in the done state but we haven't received + everything. note, the body size is negative for chunked transfers when we haven't processed the end chunk. */ + if ((pState->eState == ST_BODY) || ((pState->eState == ST_DONE) && ((pState->iBodySize < 0) || (pState->iBodyRcvd < pState->iBodySize)))) + { + char strTempRecv[1024]; + while ((iResult = _ProtoHttpRecv(pState, strTempRecv, 1, sizeof(strTempRecv))) > 0) + { + pState->pWriteCb(pState, &WriteCbInfo, strTempRecv, iResult, pState->pUserData); + } + } + + if (pState->eState > ST_BODY) + { + if (pState->eState == ST_DONE) + { + pState->pWriteCb(pState, &WriteCbInfo, "", pState->bHeadOnly ? PROTOHTTP_HEADONLY : PROTOHTTP_RECVDONE, pState->pUserData); + } + if (pState->eState == ST_FAIL) + { + pState->pWriteCb(pState, &WriteCbInfo, "", pState->bTimeout ? PROTOHTTP_TIMEOUT : PROTOHTTP_RECVFAIL, pState->pUserData); + } + pState->pWriteCb = NULL; + pState->pReqCustomHeaderCb = NULL; + pState->pReqReceiveHeaderCb = NULL; + pState->pUserData = NULL; + } +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ProtoHttpCreate + + \Description + Allocate module state and prepare for use + + \Input iBufSize - length of receive buffer + + \Output + ProtoHttpRefT * - pointer to module state, or NULL + + \Version 04/12/2000 (gschaefer) First Version +*/ +/********************************************************************************F*/ +ProtoHttpRefT *ProtoHttpCreate(int32_t iBufSize) +{ + ProtoHttpRefT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // clamp the buffer size + if (iBufSize < 4096) + { + iBufSize = 4096; + } + + // allocate the resources + if ((pState = DirtyMemAlloc(sizeof(*pState), PROTOHTTP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protohttp: unable to allocate module state\n")); + return(NULL); + } + ds_memclr(pState, sizeof(*pState)); + // save memgroup (will be used in ProtoHttpDestroy) + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + + // allocate ssl module + if ((pState->pSsl = ProtoSSLCreate()) == NULL) + { + NetPrintf(("protohttp: [%p] unable to allocate ssl module\n", pState)); + ProtoHttpDestroy(pState); + return(NULL); + } + if ((pState->pInpBuf = DirtyMemAlloc(iBufSize, PROTOHTTP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protohttp: [%p] unable to allocate protohttp buffer\n", pState)); + ProtoHttpDestroy(pState); + return(NULL); + } + + // init crit + NetCritInit(&pState->HttpCrit, "ProtoHttp"); + + // save parms & set defaults + pState->eState = ST_IDLE; + pState->iInpMax = iBufSize; + pState->uTimeout = PROTOHTTP_TIMEOUT_DEFAULT; + pState->bVerifyHdr = TRUE; + pState->iVerbose = 1; + pState->iMaxRedirect = PROTOHTTP_MAXREDIRECT; + + // return the state + return(pState); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpCallback + + \Description + Set header callbacks. + + \Input *pState - module state + \Input *pCustomHeaderCb - pointer to custom send header callback (may be NULL) + \Input *pReceiveHeaderCb- pointer to recv header callback (may be NULL) + \Input *pCallbackRef - user-supplied callback ref (may be NULL) + + \Notes + The ProtHttpCustomHeaderCbt callback is used to allow customization of + the HTTP header before sending. It is more powerful than the append + header functionality, allowing to make changes to any part of the header + before it is sent. The callback should return a negative code if an error + occurred, zero can be returned if the application wants ProtoHttp to + calculate the header size, or the size of the header can be returned if + the application has already calculated it. The header should *not* be + terminated with the double \\r\\n that indicates the end of the entire + header, as protohttp appends itself. + + The ProtoHttpReceiveHeaderCbT callback is used to view the header + immediately on reception. It can be used when the built-in header + cache (retrieved with ProtoHttpStatus('htxt') is too small to hold + the entire header received. It is also possible with this method + to view redirection response headers that cannot be retrieved + normally. This can be important if, for example, the application + wishes to attach new cookies to a redirection response. The + custom response header and custom header callback can be used in + conjunction to implement this type of functionality. + + \Version 1.0 02/16/2007 (jbrookes) First Version +*/ +/********************************************************************************F*/ +void ProtoHttpCallback(ProtoHttpRefT *pState, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pCallbackRef) +{ + pState->pCustomHeaderCb = pCustomHeaderCb; + pState->pReceiveHeaderCb = pReceiveHeaderCb; + pState->pCallbackRef = pCallbackRef; +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpDestroy + + \Description + Destroy the module and release its state + + \Input *pState - reference pointer + + \Output + None. + + \Version 04/12/2000 (gschaefer) First Version +*/ +/********************************************************************************F*/ +void ProtoHttpDestroy(ProtoHttpRefT *pState) +{ + if (pState->pSsl != NULL) + { + ProtoSSLDestroy(pState->pSsl); + } + _ProtoHttpFreeInputBuf(pState); + if (pState->pAppendHdr != NULL) + { + DirtyMemFree(pState->pAppendHdr, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + NetCritKill(&pState->HttpCrit); + DirtyMemFree(pState, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpGet + + \Description + Initiate an HTTP transfer. Pass in a URL and the module starts a transfer + from the appropriate server. + + \Input *pState - reference pointer + \Input *pUrl - the url to retrieve + \Input bHeadOnly - if TRUE only get header + + \Output + int32_t - negative=failure, else success + + \Version 04/12/2000 (gschaefer) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpGet(ProtoHttpRefT *pState, const char *pUrl, uint32_t bHeadOnly) +{ + int32_t iResult; + + // reset redirection count + pState->iNumRedirect = 0; + + // format the request + if (pUrl != NULL) + { + if ((iResult = _ProtoHttpFormatRequest(pState, pUrl, NULL, 0, bHeadOnly ? PROTOHTTP_REQUESTTYPE_HEAD : PROTOHTTP_REQUESTTYPE_GET)) < 0) + { + return(iResult); + } + } + // issue the request + if (!pState->bPipelining || (pUrl == NULL)) + { + _ProtoHttpSendRequest(pState); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpRecv + + \Description + Return the actual url data. + + \Input *pState - reference pointer + \Input *pBuffer - buffer to store data in + \Input iBufMin - minimum number of bytes to return (returns zero if this number is not available) + \Input iBufMax - maximum number of bytes to return (buffer size) + + \Output + int32_t - negative=error, zero=no data available or bufmax <= 0, positive=number of bytes read + + \Version 04/12/2000 (gschaefer) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpRecv(ProtoHttpRefT *pState, char *pBuffer, int32_t iBufMin, int32_t iBufMax) +{ + int32_t iResult; + + NetCritEnter(&pState->HttpCrit); + iResult = _ProtoHttpRecv(pState, pBuffer, iBufMin, iBufMax); + NetCritLeave(&pState->HttpCrit); + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpRecvAll + + \Description + Return all of the url data. + + \Input *pState - reference pointer + \Input *pBuffer - buffer to store data in + \Input iBufSize - size of buffer + + \Output + int32_t - PROTOHTTP_RECV*, or positive=bytes in response + + \Version 12/14/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpRecvAll(ProtoHttpRefT *pState, char *pBuffer, int32_t iBufSize) +{ + int32_t iRecvMax, iRecvResult; + + // try to read as much data as possible (leave one byte for null termination) + for (iRecvMax = iBufSize-1; (iRecvResult = ProtoHttpRecv(pState, pBuffer + pState->iRecvSize, 1, iRecvMax - pState->iRecvSize)) > 0; ) + { + // add to amount received + pState->iRecvSize += iRecvResult; + } + + // check response code + if (iRecvResult == PROTOHTTP_RECVDONE) + { + // null-terminate response & return completion success + if ((pBuffer != NULL) && (iBufSize > 0)) + { + pBuffer[pState->iRecvSize] = '\0'; + } + iRecvResult = pState->iRecvSize; + } + else if ((iRecvResult < 0) && (iRecvResult != PROTOHTTP_RECVWAIT)) + { + // an error occurred + NetPrintf(("protohttp: [%p] error %d receiving response\n", pState, iRecvResult)); + } + else if (iRecvResult == 0) + { + iRecvResult = (pState->iRecvSize < iRecvMax) ? PROTOHTTP_RECVWAIT : PROTOHTTP_RECVBUFF; + } + + // return result to caller + return(iRecvResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpPost + + \Description + Initiate transfer of data to to the server via a HTTP POST command. + + \Input *pState - reference pointer + \Input *pUrl - the URL that identifies the POST action. + \Input *pData - pointer to URL data (optional, may be NULL) + \Input iDataSize - size of data being uploaded (see Notes below) + \Input bDoPut - if TRUE, do a PUT instead of a POST + + \Output + int32_t - negative=failure, else number of data bytes sent + + \Notes + Any data that is not sent as part of the Post transaction should be sent + with ProtoHttpSend(). ProtoHttpSend() should be called at poll rate (i.e. + similar to how often ProtoHttpUpdate() is called) until all of the data has + been sent. The amount of data bytes actually sent is returned by the + function. + + If pData is not NULL and iDataSize is less than or equal to zero, iDataSize + will be recalculated as the string length of pData, to allow for easy string + sending. + + If pData is NULL and iDataSize is negative, the transaction is assumed to + be a streaming transaction and a chunked transfer will be performed. A + subsequent call to ProtoHttpSend() with iDataSize equal to zero will end + the transaction. + + \Version 03/03/2004 (sbevan) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpPost(ProtoHttpRefT *pState, const char *pUrl, const char *pData, int64_t iDataSize, uint32_t bDoPut) +{ + int32_t iDataSent; + // allow for easy string sending + if ((pData != NULL) && (iDataSize <= 0)) + { + iDataSize = (int32_t)strlen(pData); + } + // save post size (or -1 to indicate that this is a variable-length stream) + pState->iPostSize = iDataSize; + // make the request + if ((iDataSent = _ProtoHttpFormatRequest(pState, pUrl, pData, iDataSize, bDoPut ? PROTOHTTP_REQUESTTYPE_PUT : PROTOHTTP_REQUESTTYPE_POST)) >= 0) + { + // send the request + _ProtoHttpSendRequest(pState); + } + return(iDataSent); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpSend + + \Description + Send data during an ongoing Post transaction. + + \Input *pState - reference pointer + \Input *pData - pointer to data to send + \Input iDataSize - size of data being sent + + \Output + int32_t - negative=failure, else number of data bytes sent + + \Version 11/18/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpSend(ProtoHttpRefT *pState, const char *pData, int32_t iDataSize) +{ + int32_t iResult; + + // make sure we are in a sending state + if (pState->eState < ST_RESP) + { + // not ready to send data yet + return(0); + } + else if (pState->eState != ST_RESP) + { + // if we're past ST_RESP, an error occurred. + return(-1); + } + + /* clamp to max ProtoHttp buffer size - even though + we don't queue the data in the ProtoHttp buffer, this + gives us a reasonable max size to send in one chunk */ + if (iDataSize > pState->iInpMax) + { + iDataSize = pState->iInpMax; + } + + // get sole access to httpcrit + NetCritEnter(&pState->HttpCrit); + // send the data + iResult = (pState->iPostSize < 0) ? _ProtoHttpSendChunk(pState, pData, iDataSize) : _ProtoHttpSend(pState, pData, iDataSize); + // release access to httpcrit + NetCritLeave(&pState->HttpCrit); + + // return result + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpDelete + + \Description + Request deletion of a server-based resource. + + \Input *pState - reference pointer + \Input *pUrl - Url describing reference to delete + + \Output + int32_t - negative=failure, zero=success + + \Version 06/01/2009 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpDelete(ProtoHttpRefT *pState, const char *pUrl) +{ + int32_t iResult; + + // reset redirection count + pState->iNumRedirect = 0; + + // format the request + if ((iResult = _ProtoHttpFormatRequest(pState, pUrl, NULL, 0, PROTOHTTP_REQUESTTYPE_DELETE)) >= 0) + { + // issue the request + _ProtoHttpSendRequest(pState); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpOptions + + \Description + Request options from a server. + + \Input *pState - reference pointer + \Input *pUrl - Url describing reference to get options on + + \Output + int32_t - negative=failure, zero=success + + \Version 07/14/2010 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpOptions(ProtoHttpRefT *pState, const char *pUrl) +{ + int32_t iResult; + + // reset redirection count + pState->iNumRedirect = 0; + + // format the request + if ((iResult = _ProtoHttpFormatRequest(pState, pUrl, NULL, 0, PROTOHTTP_REQUESTTYPE_OPTIONS)) >= 0) + { + // issue the request + _ProtoHttpSendRequest(pState); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpPatch + + \Description + Initiate transfer of data to to the server via a HTTP PATCH command. + + \Input *pState - reference pointer + \Input *pUrl - the URL that identifies the POST action. + \Input *pData - pointer to URL data (optional, may be NULL) + \Input iDataSize - size of data being uploaded (see Notes below) + + \Output + int32_t - negative=failure, else number of data bytes sent + + \Notes + Any data that is not sent as part of the Patch transaction should be sent + with ProtoHttpSend(). ProtoHttpSend() should be called at poll rate (i.e. + similar to how often ProtoHttpUpdate() is called) until all of the data has + been sent. The amount of data bytes actually sent is returned by the + function. + + If pData is not NULL and iDataSize is less than or equal to zero, iDataSize + will be recalculated as the string length of pData, to allow for easy string + sending. + + If pData is NULL and iDataSize is negative, the transaction is assumed to + be a streaming transaction and a chunked transfer will be performed. A + subsequent call to ProtoHttpSend() with iDataSize equal to zero will end + the transaction. + + \Version 01/01/2017 (amakoukji) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpPatch(ProtoHttpRefT *pState, const char *pUrl, const char *pData, int64_t iDataSize) +{ + int32_t iDataSent; + // allow for easy string sending + if ((pData != NULL) && (iDataSize <= 0)) + { + iDataSize = (int32_t)strlen(pData); + } + // save post size (or -1 to indicate that this is a variable-length stream) + pState->iPostSize = iDataSize; + // make the request + if ((iDataSent = _ProtoHttpFormatRequest(pState, pUrl, pData, iDataSize, PROTOHTTP_REQUESTTYPE_PATCH)) >= 0) + { + // send the request + _ProtoHttpSendRequest(pState); + } + return(iDataSent); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpRequestCb2 + + \Description + Initiate an HTTP transfer. Pass in a URL and the module starts a transfer + from the appropriate server. Use ProtoHttpRequest() macro wrapper if + callbacks are not required. + + \Input *pState - reference pointer + \Input *pUrl - the url to retrieve + \Input *pData - user data to send to server (PUT and POST only) + \Input iDataSize - size of user data to send to server (PUT and POST only) + \Input eRequestType - request type to make + \Input *pWriteCb - write callback (optional) + \Input *pCustomHeaderCb - custom header callback (optional) + \Input *pReceiveHeaderCb - receive header callback (optional) + \Input *pUserData - callback user data (optional) + + \Output + int32_t - negative=failure, zero=success, >0=number of data bytes sent (PUT and POST only) + + \Version 09/11/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpRequestCb2(ProtoHttpRefT *pState, const char *pUrl, const char *pData, int64_t iDataSize, ProtoHttpRequestTypeE eRequestType, ProtoHttpWriteCbT *pWriteCb, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData) +{ + int32_t iResult; + + // save callbacks + pState->pWriteCb = pWriteCb; + pState->pReqCustomHeaderCb = pCustomHeaderCb; + pState->pReqReceiveHeaderCb = pReceiveHeaderCb; + pState->pUserData = pUserData; + + // make the request + if ((eRequestType == PROTOHTTP_REQUESTTYPE_GET) || (eRequestType == PROTOHTTP_REQUESTTYPE_HEAD)) + { + iResult = ProtoHttpGet(pState, pUrl, eRequestType == PROTOHTTP_REQUESTTYPE_HEAD); + } + else if ((eRequestType == PROTOHTTP_REQUESTTYPE_PUT) || (eRequestType == PROTOHTTP_REQUESTTYPE_POST)) + { + iResult = ProtoHttpPost(pState, pUrl, pData, iDataSize, eRequestType == PROTOHTTP_REQUESTTYPE_PUT); + } + else if (eRequestType == PROTOHTTP_REQUESTTYPE_DELETE) + { + iResult = ProtoHttpDelete(pState, pUrl); + } + else if (eRequestType == PROTOHTTP_REQUESTTYPE_OPTIONS) + { + iResult = ProtoHttpOptions(pState, pUrl); + } + else if (eRequestType == PROTOHTTP_REQUESTTYPE_PATCH) + { + iResult = ProtoHttpPatch(pState, pUrl, pData, iDataSize); + } + else + { + NetPrintf(("protohttp: [%p] unrecognized request type %d\n", pState, eRequestType)); + iResult = -1; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpAbort + + \Description + Abort current operation, if any. + + \Input *pState - reference pointer + + \Output + None. + + \Version 12/01/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +void ProtoHttpAbort(ProtoHttpRefT *pState) +{ + // acquire sole access to http crit + NetCritEnter(&pState->HttpCrit); + + // terminate current connection, if any + _ProtoHttpClose(pState, "abort"); + + // reset state + _ProtoHttpReset(pState); + + // release sole access to http crit + NetCritLeave(&pState->HttpCrit); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpSetBaseUrl + + \Description + Set base url that will be used for any relative url references. + + \Input *pState - module state + \Input *pUrl - base url + + \Output + None + + \Version 02/03/2010 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoHttpSetBaseUrl(ProtoHttpRefT *pState, const char *pUrl) +{ + char strHost[sizeof(pState->strHost)], strKind[8]; + int32_t iPort, iSecure; + uint8_t bPortSpecified; + + // parse the url for kind, host, and port + ProtoHttpUrlParse2(pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure, &bPortSpecified); + + // set base info + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] setting base url to %s://%s:%d\n", pState, iSecure ? "https" : "http", strHost, iPort)); + ds_strnzcpy(pState->strBaseHost, strHost, sizeof(pState->strBaseHost)); + pState->iBasePort = iPort; + pState->iBaseSecure = iSecure; +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpGetLocationHeader + + \Description + Get location header from the input buffer. The Location header requires + some special processing. + + \Input *pState - reference pointer + \Input *pInpBuf - buffer holding header text + \Input *pBuffer - [out] output buffer for parsed location header, null for size request + \Input iBufSize - size of output buffer, zero for size request + \Input **pHdrEnd- [out] pointer past end of parsed header (optional) + + \Output + int32_t - negative=not found or not enough space, zero=success, positive=size query result + + \Version 03/24/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpGetLocationHeader(ProtoHttpRefT *pState, const char *pInpBuf, char *pBuffer, int32_t iBufSize, const char **pHdrEnd) +{ + const char *pLocHdr; + int32_t iLocLen, iLocPreLen=0; + + // get a pointer to header + if ((pLocHdr = ProtoHttpFindHeaderValue(pInpBuf, "location")) == NULL) + { + return(-1); + } + + /* according to RFC location headers should be absolute, but some webservers respond with relative + URL's. we assume any url that does not include "://" is a relative url, and if we find one, we + assume the request keeps the same security, port, and host as the previous request */ + if ((pState != NULL) && (!strstr(pLocHdr, "://"))) + { + char strTemp[288]; // space for max DNS name (253 chars) plus max http url prefix + int32_t iPort, iSecure; + char *pStrHost; + + // get host info; if we're using a proxy we need to look at the proxied* fields + if (pState->strProxiedHost[0] != '\0') + { + pStrHost = pState->strProxiedHost; + iPort = pState->iProxiedPort; + iSecure = pState->iProxiedSecure; + } + else + { + pStrHost = pState->strHost; + iPort = pState->iPort; + iSecure = pState->iSecure; + } + + // format http url prefix + if ((pState->iSecure && (iPort == 443)) || (iPort == 80)) + { + ds_snzprintf(strTemp, sizeof(strTemp), "%s://%s", iSecure ? "https" : "http", pStrHost); + } + else + { + ds_snzprintf(strTemp, sizeof(strTemp), "%s://%s:%d", iSecure ? "https" : "http", pStrHost, iPort); + } + + // make sure relative URL includes '/' as the first character, required when we format the redirection url + if (*pLocHdr != '/') + { + ds_strnzcat(strTemp, "/", sizeof(strTemp)); + } + + // calculate url prefix length + iLocPreLen = (int32_t)strlen(strTemp); + + // copy url prefix text if a buffer is specified + if (pBuffer != NULL) + { + ds_strnzcpy(pBuffer, strTemp, iBufSize); + pBuffer = (char *)((uint8_t *)pBuffer + iLocPreLen); + iBufSize -= iLocPreLen; + } + } + + // extract location header and return size + iLocLen = ProtoHttpExtractHeaderValue(pLocHdr, pBuffer, iBufSize, pHdrEnd); + // if it's a size request add in (possible) url header length + if ((pBuffer == NULL) && (iBufSize == 0)) + { + iLocLen += iLocPreLen; + } + + // return to caller + return(iLocLen); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpControl + + \Description + ProtoHttp control function. Different selectors control different behaviors. + + \Input *pState - reference pointer + \Input iSelect - control selector ('time') + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + Selectors are: + + \verbatim + SELECTOR DESCRIPTION + 'apnd' The given buffer will be appended to future headers sent + by ProtoHttp. Note that the User-Agent and Accept lines + in the default header will be replaced, so if these lines + are desired, they should be supplied in the append header. + 'disc' Close current connection, if any. + 'hver' Sets header type verification: zero=disabled, else enabled + 'ires' Resize input buffer + 'keep' Sets keep-alive; zero=disabled, else enabled + 'gpxy' Set global proxy server to use for all protohttp refs + 'pipe' Sets pipelining; zero=disabled, else enabled + 'pnxt' Call to proceed to next piped result + 'prxy' Set proxy server to use for this ref + 'rmax' Sets maximum number of redirections (default=3; 0=disable) + 'rput' Sets connection-reuse on put/post (TRUE=enabled, default FALSE) + 'spam' Sets debug output verbosity (0...n) + 'time' Sets ProtoHttp client timeout in milliseconds (default=30s) + \endverbatim + + Unhandled selectors are passed on to ProtoSSL. + + \Version 11/12/2004 (jbrookes) First Version +*/ +/*******************************************************************************F*/ +int32_t ProtoHttpControl(ProtoHttpRefT *pState, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iSelect == 'apnd') + { + return(_ProtoHttpSetAppendHeader(pState, (const char *)pValue)); + } + if (iSelect == 'disc') + { + _ProtoHttpClose(pState, "user request"); + return(0); + } + if (iSelect == 'hver') + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] header type verification %s\n", pState, iValue ? "enabled" : "disabled")); + pState->bVerifyHdr = iValue; + } + if (iSelect == 'ires') + { + return(_ProtoHttpResizeBuffer(pState, iValue)); + } + if (iSelect == 'keep') + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] setting keep-alive to %d\n", pState, iValue)); + pState->iKeepAlive = pState->iKeepAliveDflt = iValue; + return(0); + } + if (iSelect == 'gpxy') + { + NetPrintf(("protohttp: setting %s as global proxy server\n", pValue)); + ds_strnzcpy(_ProtoHttp_strGlobalProxy, pValue, sizeof(_ProtoHttp_strGlobalProxy)); + return(0); + } + if (iSelect == 'pipe') + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] pipelining %s\n", pState, iValue ? "enabled" : "disabled")); + pState->bPipelining = iValue ? TRUE : FALSE; + return(0); + } + if (iSelect == 'pnxt') + { + NetPrintfVerbose((pState->iVerbose, 2, "protohttp: [%p] proceeding to next pipeline result\n", pState)); + pState->bPipeGetNext = TRUE; + return(0); + } + if (iSelect == 'prxy') + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] setting %s as proxy server\n", pState, pValue)); + ds_strnzcpy(pState->strProxy, pValue, sizeof(pState->strProxy)); + return(0); + } + if (iSelect == 'rmax') + { + pState->iMaxRedirect = iValue; + return(0); + } + if (iSelect == 'rput') + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] connection reuse on put/post %s\n", pState, iValue ? "enabled" : "disabled")); + pState->bReuseOnPost = iValue ? TRUE : FALSE; + return(0); + } + if (iSelect == 'spam') + { + pState->iVerbose = iValue; + // fall through to protossl + } + if (iSelect == 'time') + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] setting timeout to %d ms\n", pState, iValue)); + pState->uTimeout = (unsigned)iValue; + return(0); + } + // unhandled -- pass on to ProtoSSL + return(ProtoSSLControl(pState->pSsl, iSelect, iValue, iValue2, pValue)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpStatus + + \Description + Return status of current HTTP transfer. Status type depends on selector. + + \Input *pState - reference pointer + \Input iSelect - info selector (see Notes) + \Input *pBuffer - [out] storage for selector-specific output + \Input iBufSize - size of buffer + + \Output + int32_t - selector specific + + \Notes + Selectors are: + + \verbatim + SELECTOR RETURN RESULT + 'body' negative=failed or pending, else size of body (for 64bit size, get via pBuffer) + 'code' negative=no response, else server response code (ProtoHttpResponseE) + 'data' negative=failed, zero=pending, positive=amnt of data ready + 'date' last-modified date, if available + 'done' negative=failed, zero=pending, positive=done + 'essl' returns protossl error state + 'head' negative=failed or pending, else size of header + 'host' current host copied to output buffer + 'hres' return hResult containing either the socket error or ssl error or the http status code + 'htxt' current received http header text copied to output buffer + 'info' copies most recent info header received, if any, to output buffer (one time only) + 'imax' size of input buffer + 'iovr' returns input buffer overflow size (valid on PROTOHTTP_MINBUFF result code) + 'plst' return whether any piped requests were lost due to a premature close + 'port' current port + 'rtxt' most recent http request header text copied to output buffer + 'rmax' returns currently configured max redirection count + 'time' TRUE if the client timed out the connection, else FALSE + \endverbatim + + Unhandled selectors are passed on to ProtoSSL. + + \Version 04/12/2000 (gschaefer) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpStatus(ProtoHttpRefT *pState, int32_t iSelect, void *pBuffer, int32_t iBufSize) +{ + // return protossl error state (we cache this since we reset the state when we disconnect on error) + if (iSelect == 'essl') + { + return(pState->iSslFail); + } + + // return current host + if (iSelect == 'host') + { + ds_strnzcpy(pBuffer, pState->strHost, iBufSize); + return(0); + } + + // return hResult containing either the socket error or ssl error or the http status code + if (iSelect == 'hres') + { + if (pState->iHdrCode > 0) + { + return(DirtyErrGetHResult(DIRTYAPI_PROTO_HTTP, pState->iHdrCode, (pState->iHdrCode >= PROTOHTTP_RESPONSE_CLIENTERROR) ? TRUE : FALSE)); + } + else + { + return(pState->iHresult); + } + } + + // return size of input buffer + if (iSelect == 'imax') + { + return(pState->iInpMax); + } + + // return input overflow amount (valid after PROTOHTTP_MINBUFF result code) + if (iSelect == 'iovr') + { + return(pState->iInpOvr); + } + + // return piped requests lost status + if (iSelect == 'plst') + { + return(pState->bPipedRequestsLost); + } + + // return current port + if (iSelect == 'port') + { + return(pState->iPort); + } + + // return current redirection max + if (iSelect == 'rmax') + { + return(pState->iMaxRedirect); + } + + // return most recent http request header text + if (iSelect == 'rtxt') + { + ds_strnzcpy(pBuffer, pState->strRequestHdr, iBufSize); + return(0); + } + + // done check: negative=failed, zero=pending, positive=done + if (iSelect == 'done') + { + if (pState->eState == ST_FAIL) + return(-1); + if (pState->eState == ST_DONE) + return(1); + return(0); + } + + // data check: negative=failed, zero=pending, positive=data ready + if (iSelect == 'data') + { + if (pState->eState == ST_FAIL) + return(-1); + if ((pState->eState == ST_BODY) || (pState->eState == ST_DONE)) + return(pState->iInpLen); + return(0); + } + + // return response code + if (iSelect == 'code') + return(pState->iHdrCode); + + // return timeout indicator + if (iSelect == 'time') + return(pState->bTimeout); + + // copies info header (if available) to output buffer + if (iSelect == 'info') + { + if (pState->bInfoHdr) + { + if (pBuffer != NULL) + { + ds_strnzcpy(pBuffer, pState->strHdr, iBufSize); + } + pState->bInfoHdr = FALSE; + return(pState->iHdrCode); + } + return(0); + } + + // the following selectors early-out with errors in failure states + if ((iSelect == 'body') || (iSelect == 'date') || (iSelect == 'head') || (iSelect == 'htxt')) + { + // check the state + if (pState->eState == ST_FAIL) + return(-1); + if ((pState->eState != ST_BODY) && (pState->eState != ST_DONE)) + return(-2); + + // parse the tokens + if (iSelect == 'head') + { + return(pState->iHeadSize); + } + if (iSelect == 'body') + { + if ((pBuffer != NULL) && (iBufSize == sizeof(pState->iBodySize))) + { + ds_memcpy(pBuffer, &pState->iBodySize, iBufSize); + } + return((int32_t)pState->iBodySize); + } + if (iSelect == 'date') + { + return(pState->iHdrDate); + } + if (iSelect == 'htxt') + { + ds_strnzcpy(pBuffer, pState->strHdr, iBufSize); + return(0); + } + } + + // pass down to unhandled selectors to ProtoSSL + return(ProtoSSLStat(pState->pSsl, iSelect, pBuffer, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpCheckKeepAlive + + \Description + Checks whether a request for the given Url would be a keep-alive transaction. + + \Input *pState - reference pointer + \Input *pUrl - Url to check + + \Output + int32_t - TRUE if a request to this Url would use keep-alive, else FALSE + + \Version 05/12/2009 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpCheckKeepAlive(ProtoHttpRefT *pState, const char *pUrl) +{ + char strHost[sizeof(pState->strHost)], strKind[8]; + int32_t iPort, iSecure; + uint8_t bPortSpecified; + + // parse the url + ProtoHttpUrlParse2(pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure, &bPortSpecified); + + // refresh open status + if (pState->bConnOpen && (ProtoSSLStat(pState->pSsl, 'stat', NULL, 0) <= 0)) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp: [%p] check for keep-alive detected connection close\n", pState)); + pState->bConnOpen = FALSE; + } + + // see if a request for this url would use keep-alive + if (pState->bConnOpen && (pState->iKeepAlive > 0) && (pState->iPort == iPort) && (pState->iSecure == iSecure) && !ds_stricmp(pState->strHost, strHost)) + { + return(1); + } + else + { + return(0); + } +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpUpdate + + \Description + Give time to module to do its thing (should be called periodically to + allow module to perform work) + + \Input *pState - reference pointer + + \Output + None. + + \Version 04/12/2000 (gschaefer) First Version +*/ +/********************************************************************************F*/ +void ProtoHttpUpdate(ProtoHttpRefT *pState) +{ + int32_t iResult; + + // give time to comm module + ProtoSSLUpdate(pState->pSsl); + + // acquire sole access to http crit + NetCritEnter(&pState->HttpCrit); + + // check for timeout + if ((pState->eState != ST_IDLE) && (pState->eState != ST_DONE) && (pState->eState != ST_FAIL)) + { + if (NetTickDiff(NetTick(), pState->uTimer) >= 0) + { + NetPrintf(("protohttp: [%p] server timeout (state=%d)\n", pState, pState->eState)); + pState->eState = ST_FAIL; + pState->bTimeout = TRUE; + } + } + + // see if connection is complete + if (pState->eState == ST_CONN) + { + iResult = ProtoSSLStat(pState->pSsl, 'stat', NULL, 0); + if (iResult > 0) + { + pState->uTimer = NetTick() + pState->uTimeout; + pState->eState = ST_SEND; + pState->bConnOpen = TRUE; + } + if (iResult < 0) + { + NetPrintf(("protohttp: [%p] ST_CONN got ST_FAIL (err=%d)\n", pState, iResult)); + pState->eState = ST_FAIL; + pState->iSslFail = ProtoSSLStat(pState->pSsl, 'fail', NULL, 0); + pState->iHresult = ProtoSSLStat(pState->pSsl, 'hres', NULL, 0); + } + } + + // send buffered header+data to webserver + if (pState->eState == ST_SEND) + { + if (((iResult = _ProtoHttpSendBuff(pState)) > 0) && (pState->iInpLen == 0)) + { + #if DIRTYCODE_LOGGING + _ProtoHttpDisplayHeader(pState); + #endif + pState->iInpOff = 0; + pState->iHdrOff = 0; + pState->eState = ST_RESP; + } + } + + // wait for initial response + if (pState->eState == ST_RESP) + { + // try to send any remaining buffered data + _ProtoHttpSendBuff(pState); + + // try for the first line of http response + if ((iResult = _ProtoHttpHeaderRecvFirstLine(pState)) > 0) + { + // copy first line of header to input buffer and proceed + ds_strnzcpy(pState->pInpBuf, pState->strHdr, pState->iHdrOff+1); + pState->iInpLen = pState->iHdrOff; + pState->eState = ST_HEAD; + } + else if ((iResult < 0) && !_ProtoHttpRetrySendRequest(pState)) + { + NetPrintf(("protohttp: [%p] ST_HEAD byte0 got ST_FAIL (err=%d)\n", pState, iResult)); + pState->iInpLen = 0; + pState->eState = ST_FAIL; + } + } + + // try to receive header data + if (pState->eState == ST_HEAD) + { + // append data to buffer + if ((iResult = _ProtoHttpRecvData(pState, pState->pInpBuf+pState->iInpLen, pState->iInpMax - pState->iInpLen)) > 0) + { + pState->iInpLen += iResult; + } + // deal with network error (defer handling closed state to next block, to allow pipelined receives of already buffered transactions) + if ((iResult < 0) && ((iResult != SOCKERR_CLOSED) || (pState->iInpLen <= 4))) + { + NetPrintf(("protohttp: [%p] ST_HEAD append got ST_FAIL (err=%d, len=%d)\n", pState, iResult, pState->iInpLen)); + pState->eState = ST_FAIL; + pState->iSslFail = ProtoSSLStat(pState->pSsl, 'fail', NULL, 0); + pState->iHresult = ProtoSSLStat(pState->pSsl, 'hres', NULL, 0); + } + } + + // check for header completion, process it + if ((pState->eState == ST_HEAD) && (pState->iInpLen > 4)) + { + _ProtoHttpHeaderProcess(pState); + } + + // parse incoming body data + while ((pState->eState == ST_BODY) && (_ProtoHttpRecvBody(pState) != 0)) + ; + + // write callback processing + if (pState->pWriteCb != NULL) + { + _ProtoHttpWriteCbProcess(pState); + } + + // force disconnect in failure state + if (pState->eState == ST_FAIL) + { + _ProtoHttpClose(pState, "error"); + } + + // handle completion + if (pState->eState == ST_DONE) + { + if (pState->bPipelining && (pState->iPipedRequests > 0)) + { + // are we ready to proceed? + if ((pState->iBodyRcvd == pState->iBodySize) && pState->bPipeGetNext) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp: [%p] handling piped request\n", pState)); + _ProtoHttpCompactBuffer(pState); + pState->eState = ST_HEAD; + pState->iHeadSize = pState->iBodySize = pState->iBodyRcvd = 0; + pState->iPipedRequests -= 1; + pState->bPipeGetNext = FALSE; + } + } + else if (pState->bCloseHdr) + { + _ProtoHttpClose(pState, "server request"); + } + + // check for keep-alive connection close by server + if (pState->bConnOpen && (ProtoSSLStat(pState->pSsl, 'stat', NULL, 0) <= 0)) + { + _ProtoHttpClose(pState, "server close"); + } + } + + // release access to httpcrit + NetCritLeave(&pState->HttpCrit); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpSetCACert + + \Description + Add one or more X.509 CA certificates to the trusted list. A + certificate added will be available to all HTTP instances for + the lifetime of the application. This function can add one or more + PEM certificates or a single DER certificate. + + \Input *pCACert - pointer to certificate data + \Input iCertSize- certificate size in bytes + + \Output + int32_t - negative=failure, zero=success + + \Version 01/13/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpSetCACert(const uint8_t *pCACert, int32_t iCertSize) +{ + return(ProtoSSLSetCACert(pCACert, iCertSize)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpSetCACert2 + + \Description + Add one or more X.509 CA certificates to the trusted list. A + certificate added will be available to all HTTP instances for + the lifetime of the application. This function can add one or more + PEM certificates or a single DER certificate. + + This version of the function does not validate the CA at load time. + The X509 certificate data will be copied and retained until the CA + is validated, either by use of ProtoHttpValidateAllCA() or by the CA + being used to validate a certificate. + + \Input *pCACert - pointer to certificate data + \Input iCertSize- certificate size in bytes + + \Output + int32_t - negative=failure, zero=success + + \Version 04/21/2011 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpSetCACert2(const uint8_t *pCACert, int32_t iCertSize) +{ + return(ProtoSSLSetCACert2(pCACert, iCertSize)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpValidateAllCA + + \Description + Validate all CA that have been added but not yet been validated. Validation + is a one-time process and disposes of the X509 certificate that is retained + until validation. + + \Output + int32_t - negative=failure, zero=success + + \Version 04/21/2011 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpValidateAllCA(void) +{ + return(ProtoSSLValidateAllCA()); +} + +/*F*************************************************************************/ +/*! + \Function ProtoHttpClrCACerts + + \Description + Clears all dynamic CA certs from the list. + + \Version 01/14/2009 (jbrookes) +*/ +/**************************************************************************F*/ +void ProtoHttpClrCACerts(void) +{ + ProtoSSLClrCACerts(); +} diff --git a/src/thirdparty/dirtysdk/source/proto/protohttp2.c b/src/thirdparty/dirtysdk/source/proto/protohttp2.c new file mode 100644 index 00000000..915416b9 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protohttp2.c @@ -0,0 +1,3758 @@ +/*H********************************************************************************/ +/*! + \File protohttp2.c + + \Description + This module implements an HTTP/2 client that can perform basic transactions + with an HTTP/2 server. It conforms to but does not fully implement + the 2.0 HTTP spec (https://tools.ietf.org/html/rfc7540), and + allows for secure HTTP transactions as well as insecure transactions. + + \Copyright + Copyright (c) Electronic Arts 2016-2018. ALL RIGHTS RESERVED. + + \Todo + Implement HTTP/2 proxy support (via CONNECT) when we find a proxy that supports + it. + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/dirtydefs.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/dirtyvers.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/util/hpack.h" +#include "DirtySDK/proto/protohttp2.h" + +/*** Defines **********************************************************************/ + +//! size of a http2 header (24-bit length, 8-bit flag, 8-bit type and 31-bit stream id) +#define PROTOHTTP2_HEADER_SIZE (9) + +//! size of http2 setting (16-bit key and 32-bit value) +#define PROTOHTTP2_SETTING_SIZE (6) + +//! size of http2 ping (8 octets of opaque data) +#define PROTOHTTP2_PING_SIZE (8) + +//! size of http2 goaway (31-bit stream id and 32-bit error code) +#define PROTOHTTP2_GOAWAY_SIZE (8) + +//! size of http2 rst_stream (32-bit error code) +#define PROTOHTTP2_RST_STREAM_SIZE (4) + +//! size of http2 window update (31-bit window size increment) +#define PROTOHTTP2_WINDOW_UPDATE_SIZE (4) + +//! size of the http2 priority frame (1 bit exclusive, 31-bit stream dependency, 8-bit weight) +#define PROTOHTTP2_PRIORITY_SIZE (5) + +//! reserved size in the buffer for a header + important frames (ping/settings/rst_stream) +#define PROTOHTTP2_RESERVED_SIZE \ + ((PROTOHTTP2_HEADER_SIZE*4)+(PROTOHTTP2_SETTING_SIZE*SETTINGS_NUM)+PROTOHTTP2_PING_SIZE+PROTOHTTP2_RST_STREAM_SIZE) + +// flags for http frames +#define PROTOHTTP2_FLAG_ACK (1 << 0) //!< acknowledge ping / settings +#define PROTOHTTP2_FLAG_END_STREAM (1 << 0) //!< indicates end data chunk +#define PROTOHTTP2_FLAG_END_HEADERS (1 << 2) //!< indicates end headers, ie: no continuation frames +#define PROTOHTTP2_FLAG_PADDED (1 << 3) //!< indicates data / header is padded so look for pad length +#define PROTOHTTP2_FLAG_PRIORITY (1 << 5) //!< indicates priority information + +//! size of sent/received header cache +#define PROTOHTTP2_HDRCACHESIZE (1024*2) + +//! size of the receive window +#define PROTOHTTP2_WINDOWSIZE (1024*16) + +//! module version (maj.min) +#define PROTOHTTP2_VERSION (0x0200) + +//! maximum number of concurrent streams supported +#define PROTOHTTP2_MAX_STREAMS (100) + +//! default protocol timeout +#define PROTOHTTP2_TIMEOUT_DEFAULT (30*1000) + +//! minimum frame size +#define PROTOHTTP2_FRAMESIZE_MIN (1 << 14) + +//! maximum frame size +#define PROTOHTTP2_FRAMESIZE_MAX ((1 << 24) - 1) + +//! maximum flow-control window size +#define PROTOHTTP2_WINDOWSIZE_MAX ((1u << 31) - 1) + +/*** Macros ***********************************************************************/ + +//! calculates how much free space we have for encoding data/header frames (ie: user frames) +#define PROTOHTTP2_CalculateFreeSpace(pState) ((pState)->iOutMax-(pState)->iOutLen-PROTOHTTP2_RESERVED_SIZE) + +//! clamps the maximum frame size (16k-16m) +#define PROTOHTTP2_ClampFrameSize(iValue) (DS_CLAMP((iValue), PROTOHTTP2_FRAMESIZE_MIN, PROTOHTTP2_FRAMESIZE_MAX)) + +#if DIRTYCODE_LOGGING + // helper macro to print the frame type + #define PROTOHTTP2_GetFrameTypeStr(uType) ((uType) <= FRAMETYPE_LAST ? _ProtoHttp2_strFrameType[(uType)] : "UNKNOWN") +#endif + +/*** Type Definitions *************************************************************/ + +//! settings supported on the http2 connection +enum +{ + SETTINGS_HEADER_TABLE_SIZE = 1, //!< the maximum size of the header compression table used to decode header blocks + SETTINGS_ENABLE_PUSH, //!< used to disable server push + SETTINGS_MAX_CONCURRENT_STREAM, //!< the maximum number of concurrent streams that the sender will allow + SETTINGS_INITIAL_WINDOW_SIZE, //!< the sender's initial window size (in octets) for stream-level flow control + SETTINGS_MAX_FRAME_SIZE, //!< the size of the largest frame payload that the sender is willing to receive + SETTINGS_MAX_HEADER_LIST_SIZE, //!< the maximum size of header list that the sender is prepared to accept + + SETTINGS_NUM = SETTINGS_MAX_HEADER_LIST_SIZE +}; + +//! the different types of http2 frames we expect +typedef enum FrameTypeE +{ + FRAMETYPE_DATA, //!< convey arbitrary, variable-length sequences of octets associated with a stream + FRAMETYPE_HEADERS, //!< is used to open a stream, and additionally carries a header block fragment + FRAMETYPE_PRIORITY, //!< specifies the sender-advised priority of a stream + FRAMETYPE_RST_STREAM, //!< allows for immediate termination of a stream + FRAMETYPE_SETTINGS, //!< conveys configuration parameters that affect how endpoints communicate + FRAMETYPE_PUSH_PROMISE, //!< used to notify the peer endpoint in advance of streams the sender intends to initiate + FRAMETYPE_PING, //!< for measuring a minimal RTT from the sender, as well as determining whether an idle connection is still functional + FRAMETYPE_GOAWAY, //!< is used to initiate shutdown of a connection or to signal serious error conditions + FRAMETYPE_WINDOW_UPDATE,//!< used to implement flow control + FRAMETYPE_CONTINUATION, //!< used to continue a sequence of header block fragments + FRAMETYPE_LAST = FRAMETYPE_CONTINUATION +} FrameTypeE; + +//! the different types of errors we send in RST_STREAM/GOAWAY frames +typedef enum ErrorTypeE +{ + ERRORTYPE_NO_ERROR, //!< graceful shutdown + ERRORTYPE_PROTOCOL_ERROR, //!< protocol error detected + ERRORTYPE_INTERNAL_ERROR, //!< implementation fault + ERRORTYPE_FLOW_CONTROL_ERROR, //!< flow-control limits exceeded + ERRORTYPE_SETTINGS_TIMEOUT, //!< settings not acknowledged + ERRORTYPE_STREAM_CLOSED, //!< frame received for closed stream + ERRORTYPE_FRAME_SIZE_ERROR, //!< frame size incorrect + ERRORTYPE_REFUSED_STREAM, //!< stream not processed + ERRORTYPE_CANCEL, //!< stream cancelled + ERRORTYPE_COMPRESSION_ERROR, //!< compression state not updated + ERRORTYPE_CONNECT_ERROR, //!< TCP connection error for CONNECT method + ERRORTYPE_ENHANCE_YOUR_CALM, //!< processing capacity exceeded + ERRORTYPE_INADEQUATE_SECURITY, //!< negotiated TLS parameters not acceptable + ERRORTYPE_HTTP_1_1_REQUIRED //!< use HTTP/1.1 for the request +} ErrorTypeE; + +//! header embedded into every http2 frame +typedef struct FrameHeaderT +{ + uint32_t uLength; //!< length of the payload (24 bits) + uint8_t uType; //!< the frame type corresponds to the FrameTypeE enum (8 bits) + uint8_t uFlags; //!< the frame flags, this value depends on the frame type (8 bits) + uint8_t uPadding; //!< amount of padding (8 bits) + uint8_t bSkipFrame;//!< used for receive processing, not sent on the wire + int32_t iStreamId; //!< identifier of the stream (31 bits) +} FrameHeaderT; + +//! settings information +typedef struct SettingsT +{ + uint32_t uHeaderTableSize; //!< SETTINGS_HEADER_TABLE_SIZE + uint32_t uEnablePush; //!< SETTINGS_ENABLE_PUSH + uint32_t uMaxConcurrentStream; //!< SETTINGS_MAX_CONCURRENT_STREAM + uint32_t uInitialWindowSize; //!< SETTINGS_INITIAL_WINDOW_SIZE + uint32_t uMaxFrameSize; //!< SETTINGS_MAX_FRAME_SIZE + uint32_t uMaxHeaderListSize; //!< SETTINGS_MAX_HEADER_LIST_SIZE +} SettingsT; + +//! stream information +typedef struct StreamInfoT +{ + int32_t iStreamId; //!< stream identifier + ProtoHttp2StreamStateE eState; //!< current state of the stream + + ProtoHttpRequestTypeE eRequestType; //!< request type of current request + ProtoHttpResponseE eResponseCode; //!< HTTP error code from the response + ErrorTypeE eErrorType; //!< cached error code from a rst_stream + + char *pHeader; //!< storage for response header (uncompressed) + int32_t iHeaderLen; //!< length of the response header + + char strRequestHeader[PROTOHTTP2_HDRCACHESIZE]; //!< storage for cached request header (uncompressed) + + uint8_t *pData; //!< storage for data frames used for polling + int32_t iDataLen; //!< length of the data frame storage + int32_t iDataMax; //!< maximum size of the data frame storage + + int32_t iLocalWindow; //!< size of the stream wide receive window (local) + int32_t iPeerWindow; //!< size of the stream wide receive window (remote) + + ProtoHttp2WriteCbT *pWriteCb; //!< user write callback pointer + ProtoHttp2CustomHeaderCbT *pCustomHeaderCb; //!< custom header callback pointer + ProtoHttp2ReceiveHeaderCbT *pReceiveHeaderCb; //!< receive header callback pointer + void *pUserData; //!< user data to pass along with callback + + int64_t iBodySize; //!< size of body data + int64_t iBodyReceived; //!< size of body data received by caller + int32_t iRecvSize; //!< amount of data received by ProtoHttp2RecvAll + int32_t iHdrDate; //!< last modified date +} StreamInfoT; + +//! http2 module state +struct ProtoHttp2RefT +{ + ProtoSSLRefT *pSsl; //!< ssl module used for communication + + //! memgroup data + int32_t iMemGroup; + void *pMemGroupUserData; + + uint8_t *pOutBuf; //!< send buffer + int32_t iOutMax; //!< size of send buffer + int32_t iOutLen; //!< length in send buffer + int32_t iOutOff; //!< offset into the send buffer + uint8_t *pInpBuf; //!< recv buffer + int32_t iInpMax; //!< size of recv buffer + int32_t iInpLen; //!< length in recv buffer + + int32_t iLocalWindow; //!< size of the connection wide receive window (local) + int32_t iPeerWindow; //!< size of the connection wide receive window (remote) + + //! callback data + ProtoHttp2CustomHeaderCbT *pCustomHeaderCb; + ProtoHttp2ReceiveHeaderCbT *pReceiveHeaderCb; + void *pCallbackRef; + + int32_t iStreamId; //!< identifier of the current stream id for new requests + int32_t iVerbose; //!< logging verbosity + int32_t iSslFail; //!< ssl failure code, if any + int32_t iHresult; //!< ssl hresult code, if any + uint32_t uSettingsTimer; //!< timeout for the peer to acknowledge our settings + uint32_t uPingTimer; //!< timeout for the peer to acknowledge our ping + uint32_t uTimer; //!< timeout timer + uint32_t uTimeout; //!< protocol timeout + NetCritT HttpCrit; //!< critical section for guarding update from send/recv + + //! settings based on ProtoHttp2SettingsE + SettingsT PeerSettings; //!< settings advertised by our peer + SettingsT TempSettings; //!< settings we will advertise to our peer + SettingsT LocalSettings; //!< settings we have advertised and were ack'd + + char strHost[256]; //!< server name + char strBaseHost[256]; //!< base server name (used for partial urls) + int32_t iPort; //!< server port + int32_t bSecure; //!< secure connection? + int32_t iBasePort; //!< base port (used for partial urls) + int32_t bBaseSecure; //!< base security settings (used for partial urls) + + enum + { + ST_IDLE, //!< default state + ST_CONN, //!< connecting to the server + ST_ACTIVE, //!< active connection + ST_FAIL //!< connection failure + } eState; //!< current state of the module + ErrorTypeE eErrorType; //!< cached error when handling peer frames (goaway related) + + HpackRefT *pEncoder; //!< encoder context + HpackRefT *pDecoder; //!< decoder context + uint8_t bHuffman; //!< use huffman encoding? (default=FALSE) + uint8_t bSettingsRecv; //!< have we received any settings from our peer? + uint8_t bTimeout; //!< timeout indicator + uint8_t _pad; + + char *pAppendHdr; //!< append header ('apnd' control) buffer + int32_t iAppendLen; //!< size of append header + + int32_t iNumStreams; //!< current number of active streams + StreamInfoT Streams[PROTOHTTP2_MAX_STREAMS]; //!< list of stream info +}; + +/*** Variables ********************************************************************/ + +//! used when establishing a connection to try to cause failure with non http2 endpoints +static const uint8_t _ProtoHttp2_ConnectionPreface[24] = +{ + 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, + 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, + 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a +}; + +#if DIRTYCODE_LOGGING +//! frame types for logging purposes +static const char *_ProtoHttp2_strFrameType[] = +{ + "DATA", + "HEADERS", + "PRIORITY", + "RST_STREAM", + "SETTINGS", + "PUSH_PROMISE", + "PING", + "GOAWAY", + "WINDOW_UPDATE", + "CONTINUATION" +}; + +//! settings names for logging purposes +static const char *_ProtoHttp2_strSettingName[SETTINGS_NUM] = +{ + "SETTINGS_HEADER_TABLE_SIZE", + "SETTINGS_ENABLE_PUSH", + "SETTINGS_MAX_CONCURRENT_STREAM", + "SETTINGS_INITIAL_WINDOW_SIZE", + "SETTINGS_MAX_FRAME_SIZE", + "SETTINGS_MAX_HEADER_LIST_SIZE" +}; + +//! protohttp2 state for logging +static const char *_ProtoHttp2_strState[] = +{ + "ST_IDLE", + "ST_CONN", + "ST_ACTIVE", + "ST_FAIL" +}; +#endif + +//! error types to string mapping +static const char *_ProtoHttp2_strErrorType[] = +{ + "NO_ERROR", + "PROTOCOL_ERROR", + "INTERNAL_ERROR", + "FLOW_CONTROL_ERROR", + "SETTINGS_TIMEOUT", + "STREAM_CLOSED", + "FRAME_SIZE_ERROR", + "REFUSED_STREAM", + "CANCEL", + "COMPRESSION_ERROR", + "CONNECT_ERROR", + "ENHANCE_YOUR_CALM", + "INADEQUATE_SECURITY", + "HTTP_1_1_REQUIRED" +}; + +//! default settings based on rfc +static const SettingsT _ProtoHttp2_DefaultSettings = +{ + 0x1000, //!< SETTINGS_HEADER_TABLE_SIZE + 0x1, //!< SETTINGS_ENABLE_PUSH + 0x7fffffff, //!< SETTINGS_MAX_CONCURRENT_STREAM + 0xffff, //!< SETTINGS_INITIAL_WINDOW_SIZE + PROTOHTTP2_FRAMESIZE_MIN, //!< SETTINGS_MAX_FRAME_SIZE + 0x7fffffff //!< SETTINGS_MAX_HEADER_LIST_SIZE +}; + +//! http request names +static const char *_ProtoHttp2_strRequestNames[PROTOHTTP_NUMREQUESTTYPES] = +{ + "HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "CONNECT" +}; + +/*** Private Functions ************************************************************/ + +static void _ProtoHttp2StreamInfoCleanup(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo); +static void _ProtoHttp2PrepareRstStream(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo, ErrorTypeE eErrorType, const char *pMessage); + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2Close + + \Description + Closes the connection to the peer + + \Input *pState - module state + \Input *pReason - string representation of why we are closing the connection + + \Version 09/28/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2Close(ProtoHttp2RefT *pState, const char *pReason) +{ + if (ProtoSSLStat(pState->pSsl, 'stat', NULL, 0) >= 0) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp2: [%p] closing connection: %s\n", pState, pReason)); + ProtoSSLDisconnect(pState->pSsl); + pState->eState = ST_FAIL; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2StreamCloseOnError + + \Description + Sets the stream state to closed and fires a failure write callback + + \Input *pState - module state + \Input *pStreamInfo - stream information we are updating + + \Version 01/05/2017 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2StreamCloseOnError(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo) +{ + // if the stream already closed nothing left to do + if (pStreamInfo->eState == STREAMSTATE_CLOSED) + { + return; + } + pStreamInfo->eState = STREAMSTATE_CLOSED; + + // if we have a write callback, let the user know that we had an error occur + if (pStreamInfo->pWriteCb != NULL) + { + ProtoHttp2WriteCbInfoT CbInfo; + CbInfo.iStreamId = pStreamInfo->iStreamId; + CbInfo.eRequestType = pStreamInfo->eRequestType; + CbInfo.eRequestResponse = pStreamInfo->eResponseCode; + + pStreamInfo->pWriteCb(pState, &CbInfo, NULL, pState->bTimeout ? PROTOHTTP2_TIMEOUT : PROTOHTTP2_RECVFAIL, pStreamInfo->pUserData); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2Send + + \Description + Try and send some data + + \Input *pState - reference pointer + \Input *pBuf - pointer to buffer to send from + \Input iBufSize - amount of data to try and send + + \Output + int32_t - negative=error, else number of bytes sent + + \Version 11/02/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2Send(ProtoHttp2RefT *pState, const uint8_t *pBuf, int32_t iBufSize) +{ + int32_t iResult, iStream; + + // try and send some data + iResult = ProtoSSLSend(pState->pSsl, (const char *)pBuf, iBufSize); + if (iResult > 0) + { + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pState->iVerbose, 2, "protohttp2: [%p] sent %d bytes\n", pState, iResult)); + if (pState->iVerbose > 3) + { + NetPrintMem(pBuf, iResult, "http2-send"); + } + #endif + pState->uTimer = NetTick() + pState->uTimeout; + } + else if (iResult < 0) + { + NetPrintf(("protohttp2: [%p] error %d sending %d bytes\n", pState, iResult, iBufSize)); + pState->eState = ST_FAIL; + pState->iSslFail = ProtoSSLStat(pState->pSsl, 'fail', NULL, 0); + pState->iHresult = ProtoSSLStat(pState->pSsl, 'hres', NULL, 0); + + // close all the streams that are currently active + for (iStream = 0; iStream < pState->iNumStreams; iStream += 1) + { + _ProtoHttp2StreamCloseOnError(pState, &pState->Streams[iStream]); + } + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2Recv + + \Description + Try and recv some data + + \Input *pState - reference pointer + \Input *pBuf - pointer to buffer to recv to + \Input iBufSize - amount of data we can accept + + \Output + int32_t - negative=error, else number of bytes recv + + \Version 11/02/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2Recv(ProtoHttp2RefT *pState, uint8_t *pBuf, int32_t iBufSize) +{ + int32_t iResult, iStream; + + // try and send some data + iResult = ProtoSSLRecv(pState->pSsl, (char *)pBuf, iBufSize); + if (iResult > 0) + { + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pState->iVerbose, 2, "protohttp2: [%p] recv %d bytes\n", pState, iResult)); + if (pState->iVerbose > 3) + { + NetPrintMem(pBuf, iResult, "http2-recv"); + } + #endif + pState->uTimer = NetTick() + pState->uTimeout; + } + else if (iResult < 0) + { + NetPrintf(("protohttp2: [%p] %s got ST_FAIL (err=%d)\n", pState, _ProtoHttp2_strState[pState->eState], iResult)); + pState->eState = ST_FAIL; + pState->iSslFail = ProtoSSLStat(pState->pSsl, 'fail', NULL, 0); + pState->iHresult = ProtoSSLStat(pState->pSsl, 'hres', NULL, 0); + + // close all the streams that are currently active + for (iStream = 0; iStream < pState->iNumStreams; iStream += 1) + { + _ProtoHttp2StreamCloseOnError(pState, &pState->Streams[iStream]); + } + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2ApplyBaseUrl + + \Description + Apply base url elements (if set) to any url elements not specified (relative + url support). + + \Input *pState - module state + \Input *pKind - parsed http kind ("http" or "https") + \Input *pHost - [in/out] parsed URL host + \Input iHostSize - size of pHost buffer + \Input *pPort - [in/out] parsed port + \Input *pSecure - [in/out] parsed security (0 or 1) + \Input bPortSpecified - TRUE if a port is explicitly specified in the url, else FALSE + + \Output + uint8_t - non-zero if changed, else zero + + \Version 02/03/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t _ProtoHttp2ApplyBaseUrl(ProtoHttp2RefT *pState, const char *pKind, char *pHost, int32_t iHostSize, int32_t *pPort, int32_t *pSecure, uint8_t bPortSpecified) +{ + uint8_t bChanged = FALSE; + if ((*pHost == '\0') && (pState->strBaseHost[0] != '\0')) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] host not present; setting to %s\n", pState, pState->strBaseHost)); + ds_strnzcpy(pHost, pState->strBaseHost, iHostSize); + bChanged = TRUE; + } + if ((bPortSpecified == FALSE) && (pState->iBasePort != 0)) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] port not present; setting to %d\n", pState, pState->iBasePort)); + *pPort = pState->iBasePort; + bChanged = TRUE; + } + if (*pKind == '\0') + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] kind (protocol) not present; setting to %d\n", pState, pState->bBaseSecure)); + *pSecure = pState->bBaseSecure; + // if our port setting is default and incompatible with our security setting, override it + if (((*pPort == 80) && (*pSecure == TRUE)) || ((*pPort == 443) && (*pSecure == FALSE))) + { + *pPort = *pSecure ? 443 : 80; + } + bChanged = TRUE; + } + return(bChanged); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2Reset + + \Description + Reset the internal state to before we connected to a peer + + \Input *pState - module state + + \Version 10/31/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2Reset(ProtoHttp2RefT *pState) +{ + int32_t iStream; + + // set peer's default setting values (these are overwritten on connection establishment) + ds_memcpy(&pState->PeerSettings, &_ProtoHttp2_DefaultSettings, sizeof(pState->PeerSettings)); + // set my local default setting values + ds_memcpy(&pState->LocalSettings, &_ProtoHttp2_DefaultSettings, sizeof(pState->LocalSettings)); + + // set my updated local settings that we will advertise during connection establishment + ds_memcpy(&pState->TempSettings, &_ProtoHttp2_DefaultSettings, sizeof(pState->TempSettings)); + pState->TempSettings.uEnablePush = 0; // tell the peer that it cannot push + + /* tell peer the size of header we are willing to support (uncompressed) + note: if the size is larger than the size of max header list you will + need to look into supporting continuation frames */ + pState->TempSettings.uMaxHeaderListSize = PROTOHTTP2_HDRCACHESIZE; + + // update the size of the recv window + pState->iLocalWindow = pState->TempSettings.uInitialWindowSize = PROTOHTTP2_WINDOWSIZE; + pState->iPeerWindow = pState->PeerSettings.uInitialWindowSize; + + // update the maximum frame size based on our input buffer + if (pState->TempSettings.uMaxFrameSize < (uint32_t)(pState->iInpMax-PROTOHTTP2_RESERVED_SIZE)) + { + pState->TempSettings.uMaxFrameSize = (uint32_t)(pState->iInpMax-PROTOHTTP2_RESERVED_SIZE); + } + + // clear the dynamic tables + HpackClear(pState->pDecoder); + HpackClear(pState->pEncoder); + + // set the stream id to 1 (initial state) + pState->iStreamId = 1; + + // clean up the number of tracked streams + for (iStream = 0; iStream < pState->iNumStreams; iStream += 1) + { + _ProtoHttp2StreamInfoCleanup(pState, &pState->Streams[iStream]); + } + pState->iNumStreams = 0; + + // set the default state + pState->eState = ST_IDLE; + pState->eErrorType = ERRORTYPE_NO_ERROR; + pState->bTimeout = FALSE; + + // reset the sizes of the buffers + pState->iOutLen = pState->iInpLen = 0; + // reset the offset + pState->iOutOff = 0; + + // reset variables dealing with settings + pState->bSettingsRecv = FALSE; + pState->uSettingsTimer = 0; + + // reset ping timer + pState->uPingTimer = 0; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2StreamInfoGet + + \Description + Retrieves the stream information by identifier + + \Input *pState - module state + \Input iStreamId - identifier used for the lookup + + \Output + StreamInfoT * - pointer to stream information or NULL if not found + + \Version 10/26/2016 (eesponda) +*/ +/********************************************************************************F*/ +static StreamInfoT *_ProtoHttp2StreamInfoGet(ProtoHttp2RefT *pState, int32_t iStreamId) +{ + StreamInfoT *pStreamInfo = NULL; + int32_t iStream; + + // iterate through stream info and look for a match + for (iStream = 0; iStream < pState->iNumStreams; iStream += 1) + { + if (pState->Streams[iStream].iStreamId == iStreamId) + { + pStreamInfo = &pState->Streams[iStream]; + break; + } + } + + // return info to caller, or NULL if no match + return(pStreamInfo); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2StreamInfoCleanup + + \Description + Cleanup any dynamic memory consumed by the stream info struct + + \Input *pState - module state + \Input *pStreamInfo - pointer to the structure needing cleanup + + \Version 10/26/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2StreamInfoCleanup(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo) +{ + if (pStreamInfo->pHeader != NULL) + { + DirtyMemFree(pStreamInfo->pHeader, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pStreamInfo->pHeader = NULL; + } + if (pStreamInfo->pData != NULL) + { + DirtyMemFree(pStreamInfo->pData, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pStreamInfo->pData = NULL; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2DecodeFrameHeader + + \Description + Decodes the header of a http2 frame + + \Input *pState - module state + \Input *pBuf - the buffer we are pasing the data out of + \Input iBufLen - the size of the buffer + \Input *pHeader - [out] location we are parsing into + \Input **pStreamInfo- [out] stream info for frames that include non-zero streams + + \Output + const uint8_t * - the new location of the buffer after parsing or NULL + + \Version 09/28/2016 (eesponda) +*/ +/********************************************************************************F*/ +static const uint8_t *_ProtoHttp2DecodeFrameHeader(ProtoHttp2RefT *pState, const uint8_t *pBuf, int32_t iBufLen, FrameHeaderT *pHeader, StreamInfoT **pStreamInfo) +{ + // make sure we can parse the whole header + if (iBufLen < PROTOHTTP2_HEADER_SIZE) + { + return(NULL); + } + // make sure that we in a valid state, in the case that a previous frame in the current receive caused an error + if (pState->eErrorType != ERRORTYPE_NO_ERROR) + { + return(NULL); + } + + // decode 24-bit length + pHeader->uLength = *pBuf++ << 16; + pHeader->uLength |= *pBuf++ << 8; + pHeader->uLength |= *pBuf++; + // decode 8-bit type + pHeader->uType = *pBuf++; + // decode 8-bit flags + pHeader->uFlags = *pBuf++; + // decode 31-bit stream id (ignoring the high bit) + pHeader->iStreamId = (*pBuf++ & 0x7f) << 24; + pHeader->iStreamId |= *pBuf++ << 16; + pHeader->iStreamId |= *pBuf++ << 8; + pHeader->iStreamId |= *pBuf++; + // set the skip frame to false by default + pHeader->bSkipFrame = FALSE; + + /* ref: https://tools.ietf.org/html/rfc7540#section-5.1.1 + An endpoint that receives an unexpected stream identifier MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. */ + if ((pHeader->iStreamId > 0) && ((*pStreamInfo = _ProtoHttp2StreamInfoGet(pState, pHeader->iStreamId)) == NULL)) + { + /* ignore frames that are associated to streams that are no longer tracked. the rfc has more detailed rules, which can be tricky to implement. + just ignoring should cover our bases since this is an unexpected case. */ + if (pHeader->iStreamId < pState->iStreamId) + { + pHeader->bSkipFrame = TRUE; + } + else + { + NetPrintf(("protohttp2: [%p] received unrecogized stream identifier (0x%08x), closing connection\n", pState, pHeader->iStreamId)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + return(NULL); + } + } + + /* ref: https://tools.ietf.org/html/rfc7540#section-4.2 + An endpoint MUST send an error code of FRAME_SIZE_ERROR if a frame exceeds the size defined in SETTINGS_MAX_FRAME_SIZE. + A frame size error in a frame that could alter the state of the entire connection MUST be treated as a connection error (Section 5.4.1); this includes any frame + carrying a header block (Section 4.3) (that is, HEADERS, PUSH_PROMISE, and CONTINUATION), SETTINGS, and any frame with a stream identifier of 0. */ + if (pHeader->uLength > pState->LocalSettings.uMaxFrameSize) + { + if ((pHeader->iStreamId == 0) || (pHeader->uType == FRAMETYPE_HEADERS) || (pHeader->uType == FRAMETYPE_PUSH_PROMISE) || (pHeader->uType == FRAMETYPE_CONTINUATION)) + { + NetPrintf(("protohttp2: [%p] received frame with size exceeding maximum on connection impacting frame, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_FRAME_SIZE_ERROR; + return(NULL); + } + else + { + _ProtoHttp2PrepareRstStream(pState, *pStreamInfo, ERRORTYPE_FRAME_SIZE_ERROR, "received frame header with invalid size"); + pHeader->bSkipFrame = TRUE; + return(NULL); + } + } + + // make sure the whole frame has arrived, since anything past this we assume that we have received more than the PROTOHTTP2_HEADER_SIZE + if ((iBufLen-PROTOHTTP2_HEADER_SIZE) < (int32_t)pHeader->uLength) + { + return(NULL); + } + + /* ref: https://tools.ietf.org/html/rfc7540#section-4.1 + Flags that have no defined semantics for a particular frame type MUST be ignored and MUST be left unset (0x0) when sending. */ + if (((pHeader->uType == FRAMETYPE_HEADERS) || (pHeader->uType == FRAMETYPE_DATA) || (pHeader->uType == FRAMETYPE_PUSH_PROMISE)) && ((pHeader->uFlags & PROTOHTTP2_FLAG_PADDED) != 0)) + { + /* decode 8-bit pad length if present, add 1 for the length field itself; this is technically part of the frame payload but we do it here at a + central location */ + pHeader->uPadding = (*pBuf++) + 1; + + /* ref: https://tools.ietf.org/html/rfc7540#section-6.1 + The total number of padding octets is determined by the value of the Pad Length field. If the length of the padding is the length of the + frame payload or greater, the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. */ + if (pHeader->uPadding < pHeader->uLength) + { + pHeader->uLength -= pHeader->uPadding; + } + else + { + NetPrintf(("protohttp2: [%p] receive padding that is greater than the size of the frame, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + return(NULL); + } + } + else + { + pHeader->uPadding = 0; + } + + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pState->iVerbose, 2, "protohttp2: [%p] received frame (len=%u, type=%s(%u), flags=%u, stream=0x%08x, padding=%u, skip=%s)\n", + pState, pHeader->uLength, PROTOHTTP2_GetFrameTypeStr(pHeader->uType), pHeader->uType, pHeader->uFlags, pHeader->iStreamId, pHeader->uPadding, + pHeader->bSkipFrame ? "TRUE" : "FALSE")); + #endif + return(pBuf); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2EncodeFrameHeader + + \Description + Encodes the header of a http2 frame + + \Input *pState - module state + \Input *pBuf - the buffer we are writing the data to + \Input *pHeader - the header data we are writing + + \Version 09/29/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2EncodeFrameHeader(ProtoHttp2RefT *pState, uint8_t *pBuf, const FrameHeaderT *pHeader) +{ + // encode 24-bit length + *pBuf++ = (uint8_t)(pHeader->uLength >> 16); + *pBuf++ = (uint8_t)(pHeader->uLength >> 8); + *pBuf++ = (uint8_t)(pHeader->uLength); + // encode 8-bit type + *pBuf++ = pHeader->uType; + // encode 8-bit flags + *pBuf++ = pHeader->uFlags; + // encode 31-bit stream id + *pBuf++ = (uint8_t)(pHeader->iStreamId >> 24); + *pBuf++ = (uint8_t)(pHeader->iStreamId >> 16); + *pBuf++ = (uint8_t)(pHeader->iStreamId >> 8); + *pBuf++ = (uint8_t)(pHeader->iStreamId); + + NetPrintfVerbose((pState->iVerbose, 2, "protohttp2: [%p] encoding frame (len=%u, type=%s(%u), flags=%u, stream=0x%08x)\n", + pState, pHeader->uLength, _ProtoHttp2_strFrameType[pHeader->uType], pHeader->uType, pHeader->uFlags, pHeader->iStreamId)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2DecodeSettings + + \Description + Decodes the settings frame and saves the settings + + \Input *pState - module state + \Input *pBuf - the buffer we are parsing the data from + \Input *pHeader - the header data for the frame + + \Output + uint8_t - result of validation of the settings we received (TRUE=success, FALSE=failure) + + \Version 09/29/2016 (eesponda) +*/ +/********************************************************************************F*/ +static uint8_t _ProtoHttp2DecodeSettings(ProtoHttp2RefT *pState, const uint8_t *pBuf, const FrameHeaderT *pHeader) +{ + // if we received an ACK, commit our temp settings + if ((pHeader->uFlags & PROTOHTTP2_FLAG_ACK) != 0) + { + ds_memcpy(&pState->LocalSettings, &pState->TempSettings, sizeof(pState->LocalSettings)); + } + else + { + int32_t iOffset; + for (iOffset = 0; iOffset < (int32_t)pHeader->uLength; iOffset += PROTOHTTP2_SETTING_SIZE) + { + uint16_t uKey; + uint32_t uValue; + + // decode settings key + uKey = *pBuf++ << 8; + uKey |= *pBuf++; + // decode settings value + uValue = *pBuf++ << 24; + uValue |= *pBuf++ << 16; + uValue |= *pBuf++ << 8; + uValue |= *pBuf++; + + switch (uKey) + { + case SETTINGS_ENABLE_PUSH: + { + /* ref: https://tools.ietf.org/html/rfc7540#section-6.5.2 + Any value other than 0 or 1 MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. */ + if ((uValue != 0) && (uValue != 1)) + { + NetPrintf(("protohttp2: [%p] received SETTINGS_ENABLE_PUSH with a value other than 0 or 1, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + return(FALSE); + } + pState->PeerSettings.uEnablePush = uValue; + break; + } + case SETTINGS_HEADER_TABLE_SIZE: pState->PeerSettings.uHeaderTableSize = uValue; break; + case SETTINGS_INITIAL_WINDOW_SIZE: + { + /* ref: https://tools.ietf.org/html/rfc7540#section-6.5.2 + Values above the maximum flow-control window size of 2^31-1 MUST be treated as a connection error (Section 5.4.1) of type + FLOW_CONTROL_ERROR. */ + if (uValue > PROTOHTTP2_WINDOWSIZE_MAX) + { + NetPrintf(("protohttp2: [%p] received SETTINGS_INITIAL_WINDOW_SIZE with a value above our maximum window, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_FLOW_CONTROL_ERROR; + return(FALSE); + } + pState->PeerSettings.uInitialWindowSize = uValue; + break; + } + case SETTINGS_MAX_CONCURRENT_STREAM: pState->PeerSettings.uMaxConcurrentStream = uValue; break; + case SETTINGS_MAX_FRAME_SIZE: + { + /* ref: https://tools.ietf.org/html/rfc7540#section-6.5.2 + The value advertised by an endpoint MUST be between this initial value and the maximum allowed frame size (2^24-1 or 16,777,215 octets), inclusive. + Values outside this range MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. */ + if ((uValue < PROTOHTTP2_FRAMESIZE_MIN) || (uValue > PROTOHTTP2_FRAMESIZE_MAX)) + { + NetPrintf(("protohttp2: [%p] received SETTINGS_MAX_FRAME_SIZE with a value outside our valid frame size range, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + return(FALSE); + } + pState->PeerSettings.uMaxFrameSize = uValue; + break; + } + case SETTINGS_MAX_HEADER_LIST_SIZE: pState->PeerSettings.uMaxHeaderListSize = uValue; break; + default: NetPrintf(("protohttp2: [%p] unhandled settings key %u\n", pState, uKey)); break; + } + #if DIRTYCODE_LOGGING + if ((uKey >= SETTINGS_HEADER_TABLE_SIZE) && (uKey <= SETTINGS_MAX_HEADER_LIST_SIZE)) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] received setting (%s=%u)\n", + pState, _ProtoHttp2_strSettingName[uKey-1], uValue)); + } + #endif + } + } + + // success + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2WriteSetting + + \Description + Writes a setting into the payload + + \Input *pState - module state + \Input *pBuf - [out] the buffer we are writing the data to + \Input *pHeader - the frame header we are updating while writing + \Input uSettingKey - the key for the setting + \Input uSettingValue - the value for the setting + + \Output + int32_t - amount of data written + + \Version 10/24/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2WriteSetting(ProtoHttp2RefT *pState, uint8_t *pBuf, FrameHeaderT *pHeader, uint16_t uSettingKey, uint32_t uSettingValue) +{ + pHeader->uLength += PROTOHTTP2_SETTING_SIZE; + + *pBuf++ = (uint8_t)(uSettingKey >> 8); + *pBuf++ = (uint8_t)(uSettingKey); + *pBuf++ = (uint8_t)(uSettingValue >> 24); + *pBuf++ = (uint8_t)(uSettingValue >> 16); + *pBuf++ = (uint8_t)(uSettingValue >> 8); + *pBuf++ = (uint8_t)(uSettingValue); + + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] encode setting (%s=%u)\n", + pState, _ProtoHttp2_strSettingName[uSettingKey-1], uSettingValue)); + + return(PROTOHTTP2_SETTING_SIZE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2EncodeSettings + + \Description + Encodes the a settings frame + + \Input *pState - module state + \Input bAck - are we acknowledging or updating settings? + \Input *pBuf - [out] the buffer we are writing the data to + \Input iBufLen - the length of the buffer + + \Output + int32_t - amount of data written + + \Version 09/29/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2EncodeSettings(ProtoHttp2RefT *pState, uint8_t bAck, uint8_t *pBuf, int32_t iBufLen) +{ + FrameHeaderT Header; + uint8_t *pFrameHeader; + + // make sure we have enough room for the maximum amount of settings + if (iBufLen < (PROTOHTTP2_HEADER_SIZE + (PROTOHTTP2_SETTING_SIZE * SETTINGS_NUM))) + { + return(0); + } + + // setup defaults + ds_memclr(&Header, sizeof(Header)); + Header.uType = FRAMETYPE_SETTINGS; + + // save the header location and advance the buffer + pFrameHeader = pBuf; + pBuf += PROTOHTTP2_HEADER_SIZE; + + // if we need to acknowledge the peer's setting and our settings have not changed set the appropriate flags + if (bAck == TRUE) + { + Header.uFlags |= PROTOHTTP2_FLAG_ACK; + } + // otherwise, send our settings to the peer + else + { + if (pState->TempSettings.uHeaderTableSize != pState->LocalSettings.uHeaderTableSize) + { + pBuf += _ProtoHttp2WriteSetting(pState, pBuf, &Header, SETTINGS_HEADER_TABLE_SIZE, pState->TempSettings.uHeaderTableSize); + } + if (pState->TempSettings.uEnablePush != pState->LocalSettings.uEnablePush) + { + pBuf += _ProtoHttp2WriteSetting(pState, pBuf, &Header, SETTINGS_ENABLE_PUSH, pState->TempSettings.uEnablePush); + } + if (pState->TempSettings.uMaxConcurrentStream != pState->LocalSettings.uMaxConcurrentStream) + { + pBuf += _ProtoHttp2WriteSetting(pState, pBuf, &Header, SETTINGS_MAX_CONCURRENT_STREAM, pState->TempSettings.uMaxConcurrentStream); + } + if (pState->TempSettings.uInitialWindowSize != pState->LocalSettings.uInitialWindowSize) + { + pBuf += _ProtoHttp2WriteSetting(pState, pBuf, &Header, SETTINGS_INITIAL_WINDOW_SIZE, pState->TempSettings.uInitialWindowSize); + } + if (pState->TempSettings.uMaxFrameSize != pState->LocalSettings.uMaxFrameSize) + { + pBuf += _ProtoHttp2WriteSetting(pState, pBuf, &Header, SETTINGS_MAX_FRAME_SIZE, pState->TempSettings.uMaxFrameSize); + } + if (pState->TempSettings.uMaxHeaderListSize != pState->LocalSettings.uMaxHeaderListSize) + { + _ProtoHttp2WriteSetting(pState, pBuf, &Header, SETTINGS_MAX_HEADER_LIST_SIZE, pState->TempSettings.uMaxHeaderListSize); + } + + // ensure we actually wrote any settings + if (Header.uLength == 0) + { + return(0); + } + } + + // encode the frame header, by this point we know we have enough space + _ProtoHttp2EncodeFrameHeader(pState, pFrameHeader, &Header); + + return(Header.uLength+PROTOHTTP2_HEADER_SIZE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2EncodeHeaders + + \Description + Encodes the a headers frame + + \Input *pState - module state + \Input *pStreamInfo - information about the stream (id, etc) + \Input bEndStream - does this request contain data? + \Input *pHeader - the header we are using to encode + + \Output + int32_t - zero=success, negative=failure + + \Version 10/26/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2EncodeHeaders(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo, uint8_t bEndStream, const char *pHeader) +{ + FrameHeaderT Header; + uint8_t *pFrameHeader, *pBuf; + int32_t iBufMax, iBufLen, iResult; + + pBuf = pState->pOutBuf+pState->iOutLen; + iBufLen = PROTOHTTP2_CalculateFreeSpace(pState); + iBufMax = iBufLen-PROTOHTTP2_HEADER_SIZE; + + // make sure we at least have enough space for frame header + if (iBufMax <= 0) + { + return(PROTOHTTP2_MINBUFF); + } + + // setup the frame header + ds_memclr(&Header, sizeof(Header)); + Header.uType = FRAMETYPE_HEADERS; + Header.iStreamId = pStreamInfo->iStreamId; + Header.uFlags |= PROTOHTTP2_FLAG_END_HEADERS; + if (bEndStream == TRUE) + { + Header.uFlags |= PROTOHTTP2_FLAG_END_STREAM; + } + + // save the header location and advance the buffer + pFrameHeader = pBuf; + pBuf += PROTOHTTP2_HEADER_SIZE; + + // encode the header info the buffer + iResult = HpackEncode(pState->pEncoder, pHeader, pBuf, iBufMax, pState->bHuffman); + if ((iResult > 0) && (iResult < iBufMax)) + { + Header.uLength = (uint32_t)iResult; + + // encode the frame header, by this point we know we have enough space + _ProtoHttp2EncodeFrameHeader(pState, pFrameHeader, &Header); + + // update the stream state + if (pStreamInfo->eState == STREAMSTATE_IDLE) + { + pStreamInfo->eState = (bEndStream == TRUE) ? STREAMSTATE_HALF_CLOSED_LOCAL : STREAMSTATE_OPEN; + } + + // advance the buffer + pState->iOutLen += Header.uLength+PROTOHTTP2_HEADER_SIZE; + return(0); + } + + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2EncodeData + + \Description + Encodes the a data frame + + \Input *pState - module state + \Input *pStreamInfo - information about the stream (id, etc) + \Input *pInput - the payload data we are sending + \Input iInpLen - the length of the payload data + \Input bEndStream - will we be sending any more data frames? + + \Output + int32_t - amount of data written + + \Version 10/26/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2EncodeData(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo, const uint8_t *pInput, int32_t iInpLen, uint8_t bEndStream) +{ + FrameHeaderT Header; + uint8_t *pOutput = pState->pOutBuf+pState->iOutLen; + // if we are ending the stream we pull we include our reserved space + int32_t iOutLen = !bEndStream ? PROTOHTTP2_CalculateFreeSpace(pState) : pState->iOutMax-pState->iOutLen; + + // make sure we have enough space to encode the frame header plus some more + if (iOutLen <= PROTOHTTP2_HEADER_SIZE) + { + NetPrintfVerbose((pState->iVerbose, 2, "protohttp2: [%p] not enough room in the output buffer to encode data (free-space=%d)\n", + pState, iOutLen)); + return(PROTOHTTP2_MINBUFF); + } + iOutLen -= PROTOHTTP2_HEADER_SIZE; + + /* if we cannot fit the whole user payload, then fill the remainder of the buffer + don't set the end stream flag as more data needs to be sent */ + if (iInpLen > iOutLen) + { + iInpLen = iOutLen; + bEndStream = FALSE; + } + // make sure we have enough space in the window + if ((pState->iPeerWindow-iInpLen < 0) || (pStreamInfo->iPeerWindow-iInpLen < 0)) + { + NetPrintfVerbose((pState->iVerbose, 2, "protohttp2: [%p] not encoding data as the server receive window is out of space (stream=0x%08x, conn window=%d, stream window=%d)\n", + pState, pStreamInfo->iStreamId, pState->iPeerWindow, pStreamInfo->iPeerWindow)); + return(0); + } + + // setup the frame header + ds_memclr(&Header, sizeof(Header)); + Header.uType = FRAMETYPE_DATA; + Header.iStreamId = pStreamInfo->iStreamId; + Header.uLength = iInpLen; + + // end the stream if necessary + if (bEndStream == TRUE) + { + Header.uFlags |= PROTOHTTP2_FLAG_END_STREAM; + } + + // encode the frame header + _ProtoHttp2EncodeFrameHeader(pState, pOutput, &Header); + pOutput += PROTOHTTP2_HEADER_SIZE; + + // encode the data + ds_memcpy_s(pOutput, iOutLen, pInput, iInpLen); + + // update the stream state + if (bEndStream == TRUE) + { + if (pStreamInfo->eState == STREAMSTATE_OPEN) + { + pStreamInfo->eState = STREAMSTATE_HALF_CLOSED_LOCAL; + } + else if (pStreamInfo->eState == STREAMSTATE_HALF_CLOSED_REMOTE) + { + pStreamInfo->eState = STREAMSTATE_CLOSED; + } + } + + // advance the windows + pState->iPeerWindow -= iInpLen; + pStreamInfo->iPeerWindow -= iInpLen; + + // advance buffer + pState->iOutLen += iInpLen+PROTOHTTP2_HEADER_SIZE; + + // return amount of user payload data written to the output buffer + return(iInpLen); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2EncodeWindowUpdate + + \Description + Encodes the a window update frame + + \Input *pState - module state + \Input iStreamId - stream we are sending the window update for + \Input iIncrement - window increment we are sending for the stream + \Input *pWindow - [out] window we are incrementing + + \Output + int32_t - zero=success, negative=error + + \Notes + StreamId of 0 is reserved for updating the window of the entire + connection + + \Version 10/26/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2EncodeWindowUpdate(ProtoHttp2RefT *pState, int32_t iStreamId, int32_t iIncrement, int32_t *pWindow) +{ + FrameHeaderT Header; + const int32_t iPayloadSize = PROTOHTTP2_WINDOW_UPDATE_SIZE+PROTOHTTP2_HEADER_SIZE; + uint8_t *pBuf = pState->pOutBuf+pState->iOutLen; + int32_t iBufLen = pState->iOutMax-pState->iOutLen; + + // make sure we have enough space to encode + if (iBufLen < iPayloadSize) + { + return(PROTOHTTP2_MINBUFF); + } + + // setup the frame header + ds_memclr(&Header, sizeof(Header)); + Header.uType = FRAMETYPE_WINDOW_UPDATE; + Header.iStreamId = iStreamId; + Header.uLength = PROTOHTTP2_WINDOW_UPDATE_SIZE; + + // encode the frame header + _ProtoHttp2EncodeFrameHeader(pState, pBuf, &Header); + pBuf += PROTOHTTP2_HEADER_SIZE; + + // encode the data + *pBuf++ = (uint8_t)(iIncrement >> 24); + *pBuf++ = (uint8_t)(iIncrement >> 16); + *pBuf++ = (uint8_t)(iIncrement >> 8); + *pBuf++ = (uint8_t)(iIncrement); + + NetPrintfVerbose((pState->iVerbose, 2, "protohttp2: [%p] incrementing the window (stream=0x%08x, size=%d, increment=%d)\n", pState, iStreamId, *pWindow, iIncrement)); + *pWindow += iIncrement; + + // advance the buffer + pState->iOutLen += iPayloadSize; + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2EncodePing + + \Description + Encodes a ping frame + + \Input *pState - module state + \Input bAck - are we acknowledging a ping? + \Input *pInput - the opaque data we send + + \Output + int32_t - zero=success, negative=error + + \Version 10/26/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2EncodePing(ProtoHttp2RefT *pState, uint8_t bAck, const uint8_t *pInput) +{ + FrameHeaderT Header; + const int32_t iPayloadSize = PROTOHTTP2_PING_SIZE+PROTOHTTP2_HEADER_SIZE; + uint8_t *pOutput = pState->pOutBuf+pState->iOutLen; + int32_t iOutLen = pState->iOutMax-pState->iOutLen; + + // make sure we have enough space to encode + if (iOutLen < iPayloadSize) + { + return(PROTOHTTP2_MINBUFF); + } + + // setup the frame header + ds_memclr(&Header, sizeof(Header)); + Header.uType = FRAMETYPE_PING; + Header.uLength = PROTOHTTP2_PING_SIZE; + if (bAck == TRUE) + { + Header.uFlags |= PROTOHTTP2_FLAG_ACK; + } + + // encode the frame header + _ProtoHttp2EncodeFrameHeader(pState, pOutput, &Header); + pOutput += PROTOHTTP2_HEADER_SIZE; + + // encode the opaque data + ds_memcpy(pOutput, pInput, PROTOHTTP2_PING_SIZE); + // advance the buffer + pState->iOutLen += iPayloadSize; + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2EncodeGoAway + + \Description + Encodes a goaway frame + + \Input *pState - module state + \Input uErrorCode - error code being sent with frame + + \Notes + This function cannot fail since we know that a goaway frame will always + fit within our output buffer. Since we are closing the connection right + after we can disregard any data already encoded there within. + + \Version 10/31/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2EncodeGoAway(ProtoHttp2RefT *pState, uint32_t uErrorCode) +{ + FrameHeaderT Header; + uint8_t *pBuf = pState->pOutBuf; + const int32_t iPayloadSize = PROTOHTTP2_GOAWAY_SIZE+PROTOHTTP2_HEADER_SIZE; + // the variable points to the next stream id so deduct 2 to get last assigned stream id + const int32_t iLastStreamId = pState->iStreamId - 2; + + // setup the frame header + ds_memclr(&Header, sizeof(Header)); + Header.uType = FRAMETYPE_GOAWAY; + Header.uLength = PROTOHTTP2_GOAWAY_SIZE; + + // encode the frame header + _ProtoHttp2EncodeFrameHeader(pState, pBuf, &Header); + pBuf += PROTOHTTP2_HEADER_SIZE; + + // encode last-stream-id + *pBuf++ = (uint8_t)(iLastStreamId >> 24); + *pBuf++ = (uint8_t)(iLastStreamId >> 16); + *pBuf++ = (uint8_t)(iLastStreamId >> 8); + *pBuf++ = (uint8_t)(iLastStreamId); + // encode error code + *pBuf++ = (uint8_t)(uErrorCode >> 24); + *pBuf++ = (uint8_t)(uErrorCode >> 16); + *pBuf++ = (uint8_t)(uErrorCode >> 8); + *pBuf++ = (uint8_t)(uErrorCode); + + pState->iOutLen = iPayloadSize; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2EncodeRstStream + + \Description + Encodes a rst_stream frame + + \Input *pState - module state + \Input iStreamId - the stream we are resetting + \Input uErrorCode - the error that caused the reset + + \Output + int32_t - zero=success, negative=failure + + \Version 11/03/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2EncodeRstStream(ProtoHttp2RefT *pState, int32_t iStreamId, uint32_t uErrorCode) +{ + FrameHeaderT Header; + const int32_t iPayloadSize = PROTOHTTP2_RST_STREAM_SIZE+PROTOHTTP2_HEADER_SIZE; + uint8_t *pBuf = pState->pOutBuf+pState->iOutLen; + int32_t iBufSize = pState->iOutMax-pState->iOutLen; + + // make sure we have enough space to encode + if (iBufSize < iPayloadSize) + { + return(PROTOHTTP2_MINBUFF); + } + + // setup the frame header + ds_memclr(&Header, sizeof(Header)); + Header.uType = FRAMETYPE_RST_STREAM; + Header.uLength = PROTOHTTP2_RST_STREAM_SIZE; + Header.iStreamId = iStreamId; + + // encode the frame header + _ProtoHttp2EncodeFrameHeader(pState, pBuf, &Header); + pBuf += PROTOHTTP2_HEADER_SIZE; + + // encode the error code + *pBuf++ = (uint8_t)(uErrorCode >> 24); + *pBuf++ = (uint8_t)(uErrorCode >> 16); + *pBuf++ = (uint8_t)(uErrorCode >> 8); + *pBuf++ = (uint8_t)(uErrorCode); + + // advance the buffer + pState->iOutLen += iPayloadSize; + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2SendGoAway + + \Description + Sends goaway frame to peer to signal closing the connection + + \Input *pState - module state + \Input eErrorType - the error we are sending in the goaway + \Input *pReason - logging reason we are closing the connection + + \Version 10/31/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2SendGoAway(ProtoHttp2RefT *pState, ErrorTypeE eErrorType, const char *pReason) +{ + // only send this error if still connected + if (ProtoSSLStat(pState->pSsl, 'stat', NULL, 0) >= 0) + { + int32_t iStream; + + // encode and send go away + _ProtoHttp2EncodeGoAway(pState, eErrorType); + _ProtoHttp2Send(pState, pState->pOutBuf, pState->iOutLen); + + // close all the streams that are currently active + for (iStream = 0; iStream < pState->iNumStreams; iStream += 1) + { + _ProtoHttp2StreamCloseOnError(pState, &pState->Streams[iStream]); + } + + // close the connection + _ProtoHttp2Close(pState, pReason); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2PrepareRstStream + + \Description + Prepares the rst_stream to be sent on the next update + + \Input *pState - module state + \Input *pStreamInfo - information about stream we are resetting + \Input eErrorType - reason for closing the connection + \Input *pMessage - logging message about why we are resetting + + \Version 11/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2PrepareRstStream(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo, ErrorTypeE eErrorType, const char *pMessage) +{ + NetPrintf(("protohttp2: [%p] %s, sending %s to peer\n", pState, pMessage, _ProtoHttp2_strErrorType[eErrorType])); + if (_ProtoHttp2EncodeRstStream(pState, pStreamInfo->iStreamId, eErrorType) != 0) + { + NetPrintf(("protohttp2: [%p] not enough space in output buffer to encode rst_stream\n", pState)); + } + _ProtoHttp2StreamCloseOnError(pState, pStreamInfo); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2ParseHeader + + \Description + Decodes and parses the header frame information + + \Input *pState - module state + \Input *pStreamInfo - stream information + \Input *pBuf - the buffer we are parsing the data from + \Input iBufLen - length of the buffer + + \Output + int32_t - success=zero, failure=negative + + \Version 10/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2ParseHeader(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo, const uint8_t *pBuf, int32_t iBufLen) +{ + char strValue[128], *pHeader; + int32_t iResult, iHeaderMax; + ProtoHttp2ReceiveHeaderCbT *pReceiveHeaderCb; + void *pUserData; + const uint8_t bParse = pStreamInfo->iHeaderLen == 0; /* if this is the first header, we need to parse */ + + // allocate memory for uncompressed headers + if ((pStreamInfo->pHeader == NULL) && ((pStreamInfo->pHeader = (char *)DirtyMemAlloc(pState->LocalSettings.uMaxHeaderListSize, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL)) + { + NetPrintf(("protohttp2: [%p] could not allocate space for header list\n", pState)); + return(-1); + } + // point to the memory we are updating + pHeader = pStreamInfo->pHeader+pStreamInfo->iHeaderLen; + iHeaderMax = pState->LocalSettings.uMaxHeaderListSize-pStreamInfo->iHeaderLen; + + // decode the header and write the information to our cache + if ((iResult = HpackDecode(pState->pDecoder, pBuf, iBufLen, pHeader, iHeaderMax)) >= 0) + { + pStreamInfo->iHeaderLen += iResult; + } + else + { + NetPrintf(("protohttp2: [%p] could not decode the incoming headers\n", pState)); + return(-1); + } + + /* if we already received an initial header, we don't need to reparse this data as + we are just handling trailing header fields after the last data chunk. + these fields _should_ not contain any of the headers we are parsing for */ + if (bParse == TRUE) + { + // parse header code + pStreamInfo->eResponseCode = ProtoHttpParseHeaderCode(pStreamInfo->pHeader); + + // parse content-length field + if (ProtoHttpGetHeaderValue(pState, pStreamInfo->pHeader, "content-length", strValue, sizeof(strValue), NULL) != -1) + { + pStreamInfo->iBodySize = ds_strtoll(strValue, NULL, 10); + } + else + { + pStreamInfo->iBodySize = -1; + } + + // parse last-modified header + if (ProtoHttpGetHeaderValue(pState, pStreamInfo->pHeader, "last-modified", strValue, sizeof(strValue), NULL) != -1) + { + pStreamInfo->iHdrDate = (int32_t)ds_strtotime(strValue); + } + + #if DIRTYCODE_LOGGING + // if this is a redirect, we don't support it log something + if (PROTOHTTP_GetResponseClass(pStreamInfo->eResponseCode) == PROTOHTTP_RESPONSE_REDIRECTION) + { + NetPrintf(("protohttp2: [%p] auto-redirection not supported, if you want to follow the redirect please issue a new request\n", pState)); + } + #endif + + NetPrintfVerbose((pState->iVerbose, 0, "protohttp2: [%p] received %d response (%d bytes)\n", pState, pStreamInfo->eResponseCode, pStreamInfo->iHeaderLen)); + } + + #if DIRTYCODE_LOGGING + if (pState->iVerbose > 1) + { + NetPrintf(("protohttp2: [%p] received response header:\n", pState)); + NetPrintWrap(pHeader, 80); + } + #endif + + // request level callback takes priority to global + if ((pReceiveHeaderCb = pStreamInfo->pReceiveHeaderCb) != NULL) + { + pUserData = pStreamInfo->pUserData; + } + else + { + pReceiveHeaderCb = pState->pReceiveHeaderCb; + pUserData = pState->pCallbackRef; + } + + // if we have a receive header callback, invoke it + if (pReceiveHeaderCb != NULL) + { + pReceiveHeaderCb(pState, pStreamInfo->iStreamId, pHeader, iResult, pUserData); + } + + // header successfully parsed + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2HandleData + + \Description + Handles the incoming data frame + + \Input *pState - module state + \Input *pBuf - the buffer we are parsing the data from + \Input *pHeader - the header data for the frame + \Input *pStreamInfo - information about associated stream + + \Version 11/04/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2HandleData(ProtoHttp2RefT *pState, const uint8_t *pBuf, const FrameHeaderT *pHeader, StreamInfoT *pStreamInfo) +{ + uint8_t bEndStream = FALSE; + + // check the state of the stream to make sure we are in correct state + if ((pStreamInfo->eState != STREAMSTATE_OPEN) && (pStreamInfo->eState != STREAMSTATE_HALF_CLOSED_LOCAL)) + { + _ProtoHttp2PrepareRstStream(pState, pStreamInfo, ERRORTYPE_STREAM_CLOSED, "received data frame when in unexpected state"); + return; + } + + // update the stream data based on the flags + if ((pHeader->uFlags & PROTOHTTP2_FLAG_END_STREAM) != 0) + { + if (pStreamInfo->eState == STREAMSTATE_OPEN) + { + pStreamInfo->eState = STREAMSTATE_HALF_CLOSED_REMOTE; + } + else if (pStreamInfo->eState == STREAMSTATE_HALF_CLOSED_LOCAL) + { + pStreamInfo->eState = STREAMSTATE_CLOSED; + } + bEndStream = TRUE; + } + + // if we have a write callback, invoke it + if (pStreamInfo->pWriteCb != NULL) + { + ProtoHttp2WriteCbInfoT CbInfo; + CbInfo.iStreamId = pStreamInfo->iStreamId; + CbInfo.eRequestType = pStreamInfo->eRequestType; + CbInfo.eRequestResponse = pStreamInfo->eResponseCode; + + pStreamInfo->pWriteCb(pState, &CbInfo, pBuf, pHeader->uLength, pStreamInfo->pUserData); + pState->iLocalWindow -= pHeader->uLength; + pStreamInfo->iLocalWindow -= pHeader->uLength; + + // update amount of data received + pStreamInfo->iBodyReceived += pHeader->uLength; + + if (bEndStream == TRUE) + { + // update body size now that stream is complete + pStreamInfo->iBodySize = pStreamInfo->iBodyReceived; + pStreamInfo->pWriteCb(pState, &CbInfo, NULL, PROTOHTTP2_RECVDONE, pStreamInfo->pUserData); + } + } + else + { + // allocate space if necessary (size of the window) + if ((pStreamInfo->pData == NULL) && ((pStreamInfo->pData = (uint8_t *)DirtyMemAlloc(pStreamInfo->iDataMax, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL)) + { + _ProtoHttp2PrepareRstStream(pState, pStreamInfo, ERRORTYPE_INTERNAL_ERROR, "failed to allocate space for storing data"); + return; + } + // make sure the peer did not exceed the window + if (pStreamInfo->iDataLen+(int32_t)pHeader->uLength <= pStreamInfo->iDataMax) + { + ds_memcpy(pStreamInfo->pData+pStreamInfo->iDataLen, pBuf, pHeader->uLength); + pStreamInfo->iDataLen += pHeader->uLength; + pState->iLocalWindow -= pHeader->uLength; + pStreamInfo->iLocalWindow -= pHeader->uLength; + } + else + { + _ProtoHttp2PrepareRstStream(pState, pStreamInfo, ERRORTYPE_FLOW_CONTROL_ERROR, "peer exceeded the receive window"); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2HandleHeaders + + \Description + Handles the incoming headers frame + + \Input *pState - module state + \Input *pBuf - the buffer we are parsing the data from + \Input *pHeader - the header data for the frame + \Input *pStreamInfo - information about associated stream + + \Version 11/04/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2HandleHeaders(ProtoHttp2RefT *pState, const uint8_t *pBuf, const FrameHeaderT *pHeader, StreamInfoT *pStreamInfo) +{ + uint8_t bEndStream = FALSE; + int32_t iBufRead = 0; + + if ((pStreamInfo->eState != STREAMSTATE_OPEN) && (pStreamInfo->eState != STREAMSTATE_HALF_CLOSED_LOCAL)) + { + _ProtoHttp2PrepareRstStream(pState, pStreamInfo, ERRORTYPE_STREAM_CLOSED, "received header frames when stream is in unexpected state"); + return; + } + + // update the stream data based on the flags + if ((pHeader->uFlags & PROTOHTTP2_FLAG_END_STREAM) != 0) + { + if (pStreamInfo->eState == STREAMSTATE_OPEN) + { + pStreamInfo->eState = STREAMSTATE_HALF_CLOSED_REMOTE; + } + else if (pStreamInfo->eState == STREAMSTATE_HALF_CLOSED_LOCAL) + { + pStreamInfo->eState = STREAMSTATE_CLOSED; + } + bEndStream = TRUE; + } + + // skip the priority frame data if present + if ((pHeader->uFlags & PROTOHTTP2_FLAG_PRIORITY) != 0) + { + iBufRead += PROTOHTTP2_PRIORITY_SIZE; + } + + // if this is the end of the stream we can parse our headers + if ((pHeader->uFlags & PROTOHTTP2_FLAG_END_HEADERS) != 0) + { + if (_ProtoHttp2ParseHeader(pState, pStreamInfo, pBuf+iBufRead, pHeader->uLength-iBufRead) != 0) + { + _ProtoHttp2PrepareRstStream(pState, pStreamInfo, ERRORTYPE_COMPRESSION_ERROR, "failed to decompress header"); + } + } + else + { + NetPrintf(("protohttp2: [%p] expected end of the header as our maximum header list fits within a single frame, closing connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + } + + // if we have a write callback, invoke it + if ((pStreamInfo->pWriteCb != NULL) && (bEndStream == TRUE)) + { + ProtoHttp2WriteCbInfoT CbInfo; + CbInfo.iStreamId = pStreamInfo->iStreamId; + CbInfo.eRequestType = pStreamInfo->eRequestType; + CbInfo.eRequestResponse = pStreamInfo->eResponseCode; + + // update body size now that stream is complete + pStreamInfo->iBodySize = pStreamInfo->iBodyReceived; + pStreamInfo->pWriteCb(pState, &CbInfo, NULL, PROTOHTTP2_RECVDONE, pStreamInfo->pUserData); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2HandleFrame + + \Description + Handles the incoming http2 frame + + \Input *pState - module state + \Input *pBuf - the buffer we are parsing the data from + \Input *pHeader - the header data for the frame + \Input *pStreamInfo - stream information + + \Version 09/29/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2HandleFrame(ProtoHttp2RefT *pState, const uint8_t *pBuf, const FrameHeaderT *pHeader, StreamInfoT *pStreamInfo) +{ + int32_t iOffset; + + // don't do any processing if we were told to skip + if (pHeader->bSkipFrame == TRUE) + { + return; + } + + if (pHeader->uType == FRAMETYPE_SETTINGS) + { + const int32_t iPrevWindow = pState->PeerSettings.uInitialWindowSize; /* save previous window setting for calculation */ + + /* ref: https://tools.ietf.org/html/rfc7540#section-6.5 + SETTINGS frames always apply to a connection, never a single stream. The stream identifier for a SETTINGS frame MUST be zero (0x0). + If an endpoint receives a SETTINGS frame whose stream identifier field is anything other than 0x0, the endpoint MUST respond with + a connection error (Section 5.4.1) of type PROTOCOL_ERROR. */ + if (pHeader->iStreamId != 0) + { + NetPrintf(("protohttp2: [%p] received settings frame with stream id not 0, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + return; + } + /* ref: https://tools.ietf.org/html/rfc7540#section-6.5 + A SETTINGS frame with a length other than a multiple of 6 octets MUST be treated as a connection error (Section 5.4.1) of type + FRAME_SIZE_ERROR. */ + else if ((pHeader->uLength % PROTOHTTP2_SETTING_SIZE) != 0) + { + NetPrintf(("protohttp2: [%p] received settings frame with invalid size %d, closing the connection\n", pState, pHeader->uLength)); + pState->eErrorType = ERRORTYPE_FRAME_SIZE_ERROR; + return; + } + + // decode the settings and update what we have saved + if (_ProtoHttp2DecodeSettings(pState, pBuf, pHeader) == FALSE) + { + return; + } + + // if not an acknowledgement, apply settings and send acknowledgement + if ((pHeader->uFlags & PROTOHTTP2_FLAG_ACK) == 0) + { + int32_t iStream; + + // encode the acknowledge + if ((iOffset = _ProtoHttp2EncodeSettings(pState, TRUE, pState->pOutBuf+pState->iOutLen, pState->iOutMax-pState->iOutLen)) != 0) + { + pState->iOutLen += iOffset; + } + + // resize the encoder dynamic table to match the peer, send dynamic table update if needed + HpackResize(pState->pEncoder, pState->PeerSettings.uHeaderTableSize, pState->bSettingsRecv); + pState->bSettingsRecv = TRUE; + + // recalculate the stream windows based on new settings and how much we consumed + for (iStream = 0; iStream < pState->iNumStreams; iStream += 1) + { + StreamInfoT *pStream = &pState->Streams[iStream]; + if ((pStream->eState == STREAMSTATE_OPEN) || (pStream->eState == STREAMSTATE_HALF_CLOSED_LOCAL)) + { + pStream->iPeerWindow = pState->PeerSettings.uInitialWindowSize - (iPrevWindow - pStream->iPeerWindow); + } + } + } + else + { + // received an acknowledgement, disable timer + pState->uSettingsTimer = 0; + } + } + else if (pHeader->uType == FRAMETYPE_RST_STREAM) + { + uint32_t uErrorCode; + + /* ref: https://tools.ietf.org/html/rfc7540#section-6.4 + RST_STREAM frames MUST be associated with a stream. If a RST_STREAM frame is received with a stream identifier of 0x0, the recipient MUST + treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. */ + if (pHeader->iStreamId == 0) + { + NetPrintf(("protohttp2: [%p] received rst_stream frame with stream id 0, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + return; + } + /* ref: https://tools.ietf.org/html/rfc7540#section-6.4 + A RST_STREAM frame with a length other than 4 octets MUST be treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR. */ + else if (pHeader->uLength != PROTOHTTP2_RST_STREAM_SIZE) + { + NetPrintf(("protohttp2: [%p] received rst_stream frame with invalid size %d, closing the connection\n", pState, pHeader->uLength)); + pState->eErrorType = ERRORTYPE_FRAME_SIZE_ERROR; + return; + } + + // decode error code + uErrorCode = *pBuf++ << 24; + uErrorCode |= *pBuf++ << 16; + uErrorCode |= *pBuf++ << 8; + uErrorCode |= *pBuf++; + + NetPrintf(("protohttp2: [%p] received rst stream (error=%s)\n", pState, _ProtoHttp2_strErrorType[uErrorCode])); + + // cache the error code + pStreamInfo->eErrorType = (ErrorTypeE)uErrorCode; + + // close the stream and fire any callbacks needed + _ProtoHttp2StreamCloseOnError(pState, pStreamInfo); + } + else if (pHeader->uType == FRAMETYPE_GOAWAY) + { + int32_t iStreamId = 0, iStream; + uint32_t uErrorCode; + char strDebugData[256] = ""; + + /* ref: https://tools.ietf.org/html/rfc7540#section-6.8 + The GOAWAY frame applies to the connection, not a specific stream. An endpoint MUST treat a GOAWAY frame with a stream identifier other + than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. */ + if (pHeader->iStreamId != 0) + { + NetPrintf(("protohttp2: [%p] received goaway frame with stream id not 0, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + } + /* ref: https://tools.ietf.org/html/rfc7540#section-6.8 + the GOAWAY frame doesn't have any requirements about frame size but I need to ensure that we at least received the required amount of data */ + else if (pHeader->uLength < PROTOHTTP2_GOAWAY_SIZE) + { + NetPrintf(("protohttp2: [%p] received goway frame with invalid size %d, closing the connection\n", pState, pHeader->uLength)); + pState->eErrorType = ERRORTYPE_FRAME_SIZE_ERROR; + } + + // if we can decode it since no error occured, do that and save the error type + if (pState->eErrorType == ERRORTYPE_NO_ERROR) + { + // decode last-stream-id + iStreamId = *pBuf++ << 24; + iStreamId |= *pBuf++ << 16; + iStreamId |= *pBuf++ << 8; + iStreamId |= *pBuf++; + // decode error code + uErrorCode = *pBuf++ << 24; + uErrorCode |= *pBuf++ << 16; + uErrorCode |= *pBuf++ << 8; + uErrorCode |= *pBuf++; + // decode debug data + ds_strsubzcpy(strDebugData, sizeof(strDebugData), (const char *)pBuf, pHeader->uLength-PROTOHTTP2_GOAWAY_SIZE); + + // cache the error the peer sent + pState->eErrorType = (ErrorTypeE)uErrorCode; + } + NetPrintf(("protohttp2: [%p] received goaway frame (stream=0x%08x, error=%s, debug=%s)\n", pState, iStreamId, _ProtoHttp2_strErrorType[pState->eErrorType], strDebugData)); + + // close all the streams + for (iStream = 0; iStream < pState->iNumStreams; iStream += 1) + { + _ProtoHttp2StreamCloseOnError(pState, &pState->Streams[iStream]); + } + + // peer told us to go away, set the state and (attempt to) close the connection + pState->eState = ST_FAIL; + _ProtoHttp2Close(pState, "goaway"); + } + else if (pHeader->uType == FRAMETYPE_PING) + { + /* ref: https://tools.ietf.org/html/rfc7540#section-6.7 + PING frames are not associated with any individual stream. If a PING frame is received with a stream identifier field value other than + 0x0, the recipient MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. */ + if (pHeader->iStreamId != 0) + { + NetPrintf(("protohttp2: [%p] received ping frame with stream id not 0, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + } + /* ref: https://tools.ietf.org/html/rfc7540#section-6.7 + Receipt of a PING frame with a length field value other than 8 MUST be treated as a connection error (Section 5.4.1) of type + FRAME_SIZE_ERROR. */ + else if (pHeader->uLength != PROTOHTTP2_PING_SIZE) + { + NetPrintf(("protohttp2: [%p] received ping frame with incorrect length %d, closing the connection\n", pState, pHeader->uLength)); + pState->eErrorType = ERRORTYPE_FRAME_SIZE_ERROR; + } + else if ((pHeader->uFlags & PROTOHTTP2_FLAG_ACK) == 0) + { + // encode ping acknowledgement + if ((iOffset = _ProtoHttp2EncodePing(pState, TRUE, pBuf)) != 0) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] not enough space to encode ping frame into output buffer\n", pState)); + } + } + else + { + // we received an acknowlegement, reset timer + pState->uPingTimer = 0; + } + } + else if (pHeader->uType == FRAMETYPE_HEADERS) + { + /* ref: https://tools.ietf.org/html/rfc7540#section-6.2 + HEADERS frames MUST be associated with a stream. If a HEADERS frame is received whose stream identifier field is 0x0, the recipient MUST + respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. */ + if (pHeader->iStreamId != 0) + { + _ProtoHttp2HandleHeaders(pState, pBuf, pHeader, pStreamInfo); + } + else + { + NetPrintf(("protohttp2: [%p] received headers frame with stream id 0, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + } + } + else if (pHeader->uType == FRAMETYPE_DATA) + { + /* ref: https://tools.ietf.org/html/rfc7540#section-6.1 + DATA frames MUST be associated with a stream. If a DATA frame is received whose stream identifier field is 0x0, the recipient MUST + respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. */ + if (pHeader->iStreamId != 0) + { + _ProtoHttp2HandleData(pState, pBuf, pHeader, pStreamInfo); + } + else + { + NetPrintf(("protohttp2: [%p] received data frame with stream id 0, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + } + } + else if (pHeader->uType == FRAMETYPE_WINDOW_UPDATE) + { + int32_t iIncrement; + + /* ref: https://tools.ietf.org/html/rfc7540#section-6.9 + A WINDOW_UPDATE frame with a length other than 4 octets MUST be treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR. */ + if (pHeader->uLength != PROTOHTTP2_WINDOW_UPDATE_SIZE) + { + NetPrintf(("protohttp2: [%p] received window update frame with incorrect length %d, closing the connection\n", pState, pHeader->uLength)); + pState->eErrorType = ERRORTYPE_FRAME_SIZE_ERROR; + return; + } + + // decode 31-bit window size increment (ignoring the high bit) + iIncrement = (*pBuf++ & 0x7f) << 24; + iIncrement |= *pBuf++ << 16; + iIncrement |= *pBuf++ << 8; + iIncrement |= *pBuf++; + + // make sure the window size increment is valid + if (iIncrement != 0) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] received window update (stream=0x%08x, increment=%d)\n", pState, pHeader->iStreamId, iIncrement)); + if (pHeader->iStreamId == 0) + { + pState->iPeerWindow += iIncrement; + } + else + { + pStreamInfo->iPeerWindow += iIncrement; + } + } + else + { + /* ref: https://tools.ietf.org/html/rfc7540#section-6.9 + A receiver MUST treat the receipt of a WINDOW_UPDATE frame with an flow-control window increment of 0 as a stream error (Section 5.4.2) + of type PROTOCOL_ERROR; errors on the connection flow-control window MUST be treated as a connection error (Section 5.4.1). */ + if (pHeader->iStreamId == 0) + { + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + NetPrintf(("protohttp2: [%p] received invalid connection wide window size increment, closing connection\n", pState)); + } + else + { + _ProtoHttp2PrepareRstStream(pState, pStreamInfo, ERRORTYPE_PROTOCOL_ERROR, "received invalid stream window size increment"); + } + } + } + else if (pHeader->uType == FRAMETYPE_CONTINUATION) + { + /* we should never receive continuation as our header list maximum + is well below what we can send in a frame */ + NetPrintf(("protohttp2: [%p] received continuation frame when not expected, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + } + else if (pHeader->uType == FRAMETYPE_PUSH_PROMISE) + { + /* we disabled push in our settings so if we receive this type of frame + it is a protocol level error */ + NetPrintf(("protohttp2: [%p] received push promise frame when push was disabled, closing the connection\n", pState)); + pState->eErrorType = ERRORTYPE_PROTOCOL_ERROR; + } + + /* ref: https://tools.ietf.org/html/rfc7540#section-4.1 + Implementations MUST ignore and discard any frame that has a type that is unknown. */ +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2FormatRequestHeader + + \Description + Format a request header based on given input data. + + \Input *pState - module state + \Input *pStreamInfo - stream information for the request + \Input *pUrl - pointer to user-supplied url + \Input iDataLen - size of included data; zero if none, negative if streaming put/post + \Input **pOutHdr - [out] output of the formatting + + \Output + int32_t - zero=success, else error + + \Version 10/24/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2FormatRequestHeader(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo, const char *pUrl, int32_t iDataLen, char **pOutHdr) +{ + const char *pUrlSlash; + char *pHeader; + int32_t iHeaderLen = 0, iHeaderMax; + ProtoHttp2CustomHeaderCbT *pCustomHeaderCb; + void *pUserData; + + // if url is empty or isn't preceded by a slash, put one in + pUrlSlash = (*pUrl != '/') ? "/" : ""; + + // set up for header formatting + pHeader = (char *)pState->pOutBuf + pState->iOutLen; + iHeaderMax = pState->iOutMax - pState->iOutLen; + + // format request header + iHeaderLen += ds_snzprintf(pHeader+iHeaderLen, iHeaderMax-iHeaderLen, ":method: %s\r\n", _ProtoHttp2_strRequestNames[pStreamInfo->eRequestType]); + iHeaderLen += ds_snzprintf(pHeader+iHeaderLen, iHeaderMax-iHeaderLen, ":path: %s%s\r\n", pUrlSlash, pUrl); + iHeaderLen += ds_snzprintf(pHeader+iHeaderLen, iHeaderMax-iHeaderLen, ":scheme: %s\r\n", pState->bSecure ? "https" : "http"); + if ((pState->bSecure && (pState->iPort == 443)) || (pState->iPort == 80)) + { + iHeaderLen += ds_snzprintf(pHeader+iHeaderLen, iHeaderMax-iHeaderLen, ":authority: %s\r\n", pState->strHost); + } + else + { + iHeaderLen += ds_snzprintf(pHeader+iHeaderLen, iHeaderMax-iHeaderLen, ":authority: %s:%d\r\n", pState->strHost, pState->iPort); + } + if (iDataLen > 0) + { + iHeaderLen += ds_snzprintf(pHeader+iHeaderLen, iHeaderMax-iHeaderLen, "content-length: %qd\r\n", iDataLen); + } + if ((pState->pAppendHdr == NULL) || !ds_stristr(pState->pAppendHdr, "user-agent:")) + { + iHeaderLen += ds_snzprintf(pHeader+iHeaderLen, iHeaderMax-iHeaderLen, "user-agent: ProtoHttp %d.%d/DS %d.%d.%d.%d.%d (" DIRTYCODE_PLATNAME ")\r\n", + (PROTOHTTP2_VERSION >> 8) & 0xFF, PROTOHTTP2_VERSION & 0xFF, DIRTYSDK_VERSION_YEAR, DIRTYSDK_VERSION_SEASON, DIRTYSDK_VERSION_MAJOR, DIRTYSDK_VERSION_MINOR, DIRTYSDK_VERSION_PATCH); + } + if ((pState->pAppendHdr == NULL) || (pState->pAppendHdr[0] == '\0')) + { + iHeaderLen += ds_snzprintf(pHeader+iHeaderLen, iHeaderMax-iHeaderLen, "accept: */*\r\n"); + } + else + { + iHeaderLen += ds_snzprintf(pHeader+iHeaderLen, iHeaderMax-iHeaderLen, "%s", pState->pAppendHdr); + } + + // null-terminate the buffer + pHeader[iHeaderLen++] = '\0'; + + // request level callback takes priority to global + if ((pCustomHeaderCb = pStreamInfo->pCustomHeaderCb) != NULL) + { + pUserData = pStreamInfo->pUserData; + } + else + { + pCustomHeaderCb = pState->pCustomHeaderCb; + pUserData = pState->pCallbackRef; + } + + // call custom header format callback, if specified + if (pCustomHeaderCb != NULL) + { + if ((iHeaderLen = pCustomHeaderCb(pState, pHeader, iHeaderMax, NULL, 0, pUserData)) < 0) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp2: [%p] custom header callback error %d\n", pState, iHeaderLen)); + return(iHeaderLen); + } + if (iHeaderLen == 0) + { + iHeaderLen = (int32_t)strlen(pHeader); + } + } + + // make sure we were able to complete the header + if (iHeaderLen > iHeaderMax) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp2: [%p] not enough buffer to format request header (have %d, need %d)\n", pState, iHeaderMax, iHeaderLen)); + return(PROTOHTTP2_MINBUFF); + } + // make sure that the size doesn't exceed what the server supports + else if (iHeaderLen > (signed)pState->PeerSettings.uMaxHeaderListSize) + { + NetPrintf(("protohttp2: [%p] the formatted (uncompressed) header exceeds the size that the server supports (have %u, need %d)\n", + pState, pState->PeerSettings.uMaxHeaderListSize, iHeaderLen)); + return(-1); + } + + // save a copy of the header + ds_strnzcpy(pStreamInfo->strRequestHeader, pHeader, sizeof(pStreamInfo->strRequestHeader)); + + #if DIRTYCODE_LOGGING + if (pState->iVerbose > 1) + { + NetPrintf(("protohttp2: [%p] sending request:\n", pState)); + NetPrintWrap(pHeader, 80); + } + #endif + + /* allocate a temporary buffer to store the uncompressed buffer. we cannot use the current spot we have written the + data to because the compress header needs to be written to the same place before being sent on the wire. + this will not exceed our frame size */ + if ((*pOutHdr = (char *)DirtyMemAlloc(iHeaderLen+1, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL) + { + NetPrintf(("protohttp2: [%p] could not allocate space for request header compression\n", pState)); + return(-1); + } + ds_strnzcpy(*pOutHdr, pHeader, iHeaderLen+1); + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2SetAppendHeader + + \Description + Set given string as append header, allocating memory as required + + \Input *pState - module state + \Input *pAppendHdr - append header string + + \Output + int32_t - zero=success, else error + + \Version 10/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2SetAppendHeader(ProtoHttp2RefT *pState, const char *pAppendHdr) +{ + int32_t iAppendBufLen, iAppendStrLen; + + // check for empty append string, in which case we free the buffer + if ((pAppendHdr == NULL) || (*pAppendHdr == '\0')) + { + if (pState->pAppendHdr != NULL) + { + DirtyMemFree(pState->pAppendHdr, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->pAppendHdr = NULL; + } + pState->iAppendLen = 0; + return(0); + } + + // check to see if append header is already set + if ((pState->pAppendHdr != NULL) && (strcmp(pAppendHdr, pState->pAppendHdr) == 0)) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] ignoring set of append header '%s' that is already set\n", pState, pAppendHdr)); + return(0); + } + + // get append header length + iAppendStrLen = (int32_t)strlen(pAppendHdr); + // append buffer size includes null and space for \r\n if not included by submitter + iAppendBufLen = iAppendStrLen + 3; + + // see if we need to allocate a new buffer + if (iAppendBufLen > pState->iAppendLen) + { + if (pState->pAppendHdr != NULL) + { + DirtyMemFree(pState->pAppendHdr, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + if ((pState->pAppendHdr = DirtyMemAlloc(iAppendBufLen, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) != NULL) + { + pState->iAppendLen = iAppendBufLen; + } + else + { + NetPrintf(("protohttp2: [%p] could not allocate %d byte buffer for append header\n", pState, iAppendBufLen)); + pState->iAppendLen = 0; + return(-1); + } + } + + // copy append header + ds_strnzcpy(pState->pAppendHdr, pAppendHdr, iAppendStrLen+1); + + // if append header is not \r\n terminated, do it here + if ((pAppendHdr[iAppendStrLen-2] != '\r') || (pAppendHdr[iAppendStrLen-1] != '\n')) + { + ds_strnzcat(pState->pAppendHdr, "\r\n", pState->iAppendLen); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2ResizeInputBuffer + + \Description + Resize the input buffer + + \Input *pState - module state + \Input iBufMax - new buffer size + + \Output + int32_t - zero=success, else error + + \Version 11/14/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2ResizeInputBuffer(ProtoHttp2RefT *pState, int32_t iBufMax) +{ + int32_t iCopySize; + uint8_t *pInpBuf; + + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] resizing input buffer from %d to %d bytes\n", pState, pState->iInpMax, iBufMax)); + if ((pInpBuf = (uint8_t *)DirtyMemAlloc(iBufMax, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL) + { + NetPrintf(("protohttp2: [%p] could not resize input buffer\n", pState)); + return(-1); + } + + // calculate size of data to copy from old buffer to new + if ((iCopySize = pState->iInpLen) > iBufMax) + { + NetPrintf(("protohttp2: [%p] warning; resize of input buffer is losing %d bytes of data\n", pState, iCopySize - iBufMax)); + iCopySize = iBufMax; + } + // copy valid contents of input buffer, if any, to new buffer + ds_memcpy(pInpBuf, pState->pInpBuf, iCopySize); + + // dispose of old buffer + DirtyMemFree(pState->pInpBuf, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + + // update buffer variables + pState->pInpBuf = pInpBuf; + pState->iInpLen = iCopySize; + pState->iInpMax = iBufMax; + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2CheckSettings + + \Description + Handle all setting related synchronization + + \Input *pState - module state + + \Notes + This function will check to make sure that we get our settings + acknowledgement from our peer in time. It also checks to see + if we need to send a new settings frame to our peer, in this + case it will handle the encoding into our output buffer. + + \Version 11/15/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2CheckSettings(ProtoHttp2RefT *pState) +{ + // check for settings timeout + if (pState->uSettingsTimer != 0) + { + if (NetTickDiff(NetTick(), pState->uSettingsTimer) > 0) + { + NetPrintf(("protohttp2: [%p] peer did not send settings in time, closing connection\n", pState)); + pState->eErrorType = ERRORTYPE_SETTINGS_TIMEOUT; + } + } + // otherwise check if we need to synchronize our settings + else if (memcmp(&pState->LocalSettings, &pState->TempSettings, sizeof(pState->TempSettings)) != 0) + { + int32_t iOffset; + if ((iOffset = _ProtoHttp2EncodeSettings(pState, FALSE, pState->pOutBuf+pState->iOutLen, pState->iOutMax-pState->iOutLen)) != 0) + { + pState->iOutLen += iOffset; + pState->uSettingsTimer = NetTick() + PROTOHTTP2_TIMEOUT; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2CheckWindows + + \Description + Check the connection and stream wide windows to send increments when + necessary + + \Input *pState - module state + + \Version 11/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2CheckWindows(ProtoHttp2RefT *pState) +{ + int32_t iStream; + + // check the connection wide window, if so increment a large amount to handle our multiple streams + if (pState->iLocalWindow <= (PROTOHTTP2_MAX_STREAMS*PROTOHTTP2_WINDOWSIZE)) + { + if (_ProtoHttp2EncodeWindowUpdate(pState, 0, PROTOHTTP2_MAX_STREAMS*PROTOHTTP2_WINDOWSIZE, &pState->iLocalWindow) != 0) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] not enough space in the output buffer to encode window update for connection\n", pState)); + } + } + + // check the stream wide windows + for (iStream = 0; iStream < pState->iNumStreams; iStream += 1) + { + StreamInfoT *pStreamInfo = &pState->Streams[iStream]; + + if (pStreamInfo->iLocalWindow <= PROTOHTTP2_WINDOWSIZE) + { + if (_ProtoHttp2EncodeWindowUpdate(pState, pStreamInfo->iStreamId, PROTOHTTP2_WINDOWSIZE, &pStreamInfo->iLocalWindow) != 0) + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] not enough space in the output buffer to encode window update for stream\n", pState)); + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2CheckActivityTimeout + + \Description + If we lack i/o activity ping the server to make sure the connection is still + active. If the don't receive an ack in time we then close the connection. + + \Input *pState - module state + + \Version 08/13/2018 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttp2CheckActivityTimeout(ProtoHttp2RefT *pState) +{ + /* if the ping timer isn't active check if our connection has been idle, if so then lets ping the server to see if + we have a valid connection. */ + if (pState->uPingTimer == 0) + { + if (NetTickDiff(NetTick(), pState->uTimer) > 0) + { + uint8_t aInput[PROTOHTTP2_PING_SIZE]; + + NetPrintfVerbose((pState->iVerbose, 2, "protohttp2: [%p] idle after %us, sending ping to check if connection is still active\n", + pState, pState->uTimeout/1000)); + + // encode ping to check if connection is active + if (_ProtoHttp2EncodePing(pState, FALSE, aInput) == 0) + { + pState->uPingTimer = NetTick() + PROTOHTTP2_TIMEOUT_DEFAULT; + } + else + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] not enough space to encode ping frame into output buffer\n", pState)); + } + } + } + // otherwise if the ping timer is active, close the connection if the server doesn't acknowledge in time + else if (NetTickDiff(NetTick(), pState->uPingTimer) > 0) + { + NetPrintf(("protohttp2: [%p] peer did not acknowledge ping in time, closing connection\n", pState)); + _ProtoHttp2Close(pState, "ping timeout"); + pState->bTimeout = TRUE; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2Read + + \Description + Read data out of the stream's buffer + + \Input *pState - module state + \Input *pStreamInfo - stream we are reading from + \Input *pBuffer - buffer to store data in + \Input iBufMin - minimum number of bytes to return + \Input iBufMax - maximum number of bytes to return (buffer size) + + \Output + int32_t - negative=error, zero=no data available or bufmax <= 0, positive=number of bytes read + + \Version 11/23/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2Read(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo, uint8_t *pBuffer, int32_t iBufMin, int32_t iBufMax) +{ + int32_t iLen; + + // early out for failure result + if ((pState->eState == ST_FAIL) || ((pStreamInfo->eState == STREAMSTATE_CLOSED) && (pStreamInfo->eErrorType != ERRORTYPE_NO_ERROR))) + { + return(pState->bTimeout ? PROTOHTTP2_TIMEOUT : PROTOHTTP2_RECVFAIL); + } + // if they only wanted head that is what we will return + if ((pStreamInfo->iHeaderLen > 0) && (pStreamInfo->eRequestType == PROTOHTTP_REQUESTTYPE_HEAD)) + { + return(PROTOHTTP2_RECVHEAD); + } + // if we haven't sent the headers yet we cannot expect to receive any data + if (pStreamInfo->eState == STREAMSTATE_IDLE) + { + return(PROTOHTTP2_RECVWAIT); + } + + // if they are querying only for done state when no more data is available to be read + if ((iBufMax == 0) && ((pStreamInfo->eState == STREAMSTATE_HALF_CLOSED_REMOTE) || (pStreamInfo->eState == STREAMSTATE_CLOSED)) && (pStreamInfo->iBodyReceived == pStreamInfo->iBodySize)) + { + return(PROTOHTTP2_RECVDONE); + } + + // make sure the range is valid + if (iBufMax < 1) + { + return(0); + } + // clamp the range + iBufMin = DS_MAX(1, iBufMin); + iBufMax = DS_MAX(iBufMin, iBufMax); + iBufMin = DS_MIN(iBufMin, pStreamInfo->iDataMax); + iBufMax = DS_MIN(iBufMax, pStreamInfo->iDataMax); + + // figure out how much data is available + iLen = DS_MIN(pStreamInfo->iDataLen, iBufMax); + + // check for end of data + if ((iLen == 0) && ((pStreamInfo->eState == STREAMSTATE_HALF_CLOSED_REMOTE) || (pStreamInfo->eState == STREAMSTATE_CLOSED))) + { + // update body size now that stream is complete + pStreamInfo->iBodySize = pStreamInfo->iBodyReceived; + return(PROTOHTTP2_RECVDONE); + } + + // see if there is enough to return + if (iLen >= iBufMin) + { + // return data to caller + if (pBuffer != NULL) + { + ds_memcpy(pBuffer, pStreamInfo->pData, iLen); + + #if DIRTYCODE_LOGGING + NetPrintfVerbose((pState->iVerbose, 2, "protohttp2: [%p] read %d bytes\n", pState, iLen)); + if (pState->iVerbose > 3) + { + NetPrintMem(pBuffer, iLen, "http2-read"); + } + #endif + } + pStreamInfo->iBodyReceived += iLen; + + memmove(pStreamInfo->pData, pStreamInfo->pData+iLen, pStreamInfo->iDataLen-iLen); + pStreamInfo->iDataLen -= iLen; + + // return bytes read + return(iLen); + } + + // nothing available + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttp2EncodeRequest + + \Description + Encode the request into the output buffer (headers / data) + + \Input *pState - module state + \Input *pStreamInfo - stream we are reading from + \Input *pUrl - address we are sending the request to + \Input *pData - data we are encoding + \Input iDataSize - size of the data we are encoding + + \Output + int32_t - negative=error, otherwise amount of user data encoded + + \Version 11/23/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttp2EncodeRequest(ProtoHttp2RefT *pState, StreamInfoT *pStreamInfo, const char *pUrl, const uint8_t *pData, int32_t iDataSize) +{ + int32_t iResult, iOutLen; + char *pOutHdr = NULL; + + // save the outlen in case of error + iOutLen = pState->iOutLen; + + // format the request headers + if ((iResult = _ProtoHttp2FormatRequestHeader(pState, pStreamInfo, pUrl, iDataSize, &pOutHdr)) < 0) + { + return(iResult); + } + + // encode the request headers + if ((iResult = _ProtoHttp2EncodeHeaders(pState, pStreamInfo, iDataSize == 0, pOutHdr)) < 0) + { + NetPrintf(("protohttp2: [%p] not enough room in the output buffer to encode headers\n", pState)); + } + // encode the data if any exist + else if ((pData != NULL) && (iDataSize > 0)) + { + if ((iResult = _ProtoHttp2EncodeData(pState, pStreamInfo, pData, iDataSize, TRUE)) < 0) + { + pState->iOutLen = iOutLen; + } + } + + // cleanup temporary buffer + if (pOutHdr != NULL) + { + DirtyMemFree(pOutHdr, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + + // return result back to user + return(iResult); +} + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Create + + \Description + Allocate module state and prepare for use + + \Input iBufSize - length of recv buffer + + \Output + ProtoHttp2RefT *- pointer to module state, or NULL + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +ProtoHttp2RefT *ProtoHttp2Create(int32_t iBufSize) +{ + ProtoHttp2RefT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + + // query memgroup data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // clamp the buffer size based on the default frame size and maximum frame size + iBufSize = PROTOHTTP2_ClampFrameSize(iBufSize); + + // allocate state + if ((pState = (ProtoHttp2RefT *)DirtyMemAlloc(sizeof(*pState), PROTOHTTP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protohttp2: unable to allocate module state\n")); + return(NULL); + } + ds_memclr(pState, sizeof(*pState)); + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + pState->iVerbose = 1; + pState->uTimeout = PROTOHTTP2_TIMEOUT_DEFAULT; + pState->bHuffman = TRUE; + + // set the buffer sizes to the defaults, adding the additional space for a frame header + pState->iInpMax = iBufSize+PROTOHTTP2_RESERVED_SIZE; + pState->iOutMax = _ProtoHttp2_DefaultSettings.uMaxFrameSize+PROTOHTTP2_RESERVED_SIZE; + + // allocate ssl state + if ((pState->pSsl = ProtoSSLCreate()) == NULL) + { + NetPrintf(("protohttp2: [%p] unable to allocate ssl module state\n", pState)); + ProtoHttp2Destroy(pState); + return(NULL); + } + ProtoSSLControl(pState->pSsl, 'alpn', 0, 0, (void *)"h2"); // tell protossl to advertise the http2 protocol + ProtoSSLControl(pState->pSsl, 'snod', TRUE, 0, NULL); // set TCP_NODELAY on the SSL socket + + // allocate input buffer + if ((pState->pInpBuf = (uint8_t *)DirtyMemAlloc(pState->iInpMax, PROTOHTTP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protohttp2: [%p] unable to allocate protohttp2 input buffer\n", pState)); + ProtoHttp2Destroy(pState); + return(NULL); + } + ds_memclr(pState->pInpBuf, pState->iInpMax); + + // allocate output buffer + if ((pState->pOutBuf = (uint8_t *)DirtyMemAlloc(pState->iOutMax, PROTOHTTP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protohttp2: [%p] unable to allocate protohttp2 output buffer\n", pState)); + ProtoHttp2Destroy(pState); + return(NULL); + } + ds_memclr(pState->pOutBuf, pState->iOutMax); + + // create the encoder context + if ((pState->pEncoder = HpackCreate(_ProtoHttp2_DefaultSettings.uHeaderTableSize, FALSE)) == NULL) + { + NetPrintf(("protohttp2: [%p] unable to create encoder context\n", pState)); + ProtoHttp2Destroy(pState); + return(NULL); + } + // create the decoder context + if ((pState->pDecoder = HpackCreate(_ProtoHttp2_DefaultSettings.uHeaderTableSize, TRUE)) == NULL) + { + NetPrintf(("protohttp2: [%p] unable to create decoder context\n", pState)); + ProtoHttp2Destroy(pState); + return(NULL); + } + + // init crit + NetCritInit(&pState->HttpCrit, "ProtoHttp2"); + + // reset the state to default + _ProtoHttp2Reset(pState); + + // return the state + return(pState); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Update + + \Description + Give time to module to do its thing (should be called periodically to + allow module to perform work) + + \Input *pState - module state + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +void ProtoHttp2Update(ProtoHttp2RefT *pState) +{ + int32_t iResult; + + // give time to ssl module + ProtoSSLUpdate(pState->pSsl); + + // acquire sole access to http crit + NetCritEnter(&pState->HttpCrit); + + // see if the connection is complete + if (pState->eState == ST_CONN) + { + int32_t iStream; + + iResult = ProtoSSLStat(pState->pSsl, 'stat', NULL, 0); + if (iResult > 0) + { + /* just use some stack space to send this connection preface instead + of having to juggle with our output buffer (that might have data) */ + uint8_t aConnectionPreface[128]; + int32_t iOffset = 0; + + // add the connection preface to the buffer + ds_memcpy(aConnectionPreface, _ProtoHttp2_ConnectionPreface, sizeof(_ProtoHttp2_ConnectionPreface)); + iOffset += sizeof(_ProtoHttp2_ConnectionPreface); + + // encode our settings + iOffset += _ProtoHttp2EncodeSettings(pState, FALSE, aConnectionPreface+iOffset, sizeof(aConnectionPreface)-iOffset); + + // send the connection preface+settings and wait for settings from server + _ProtoHttp2Send(pState, aConnectionPreface, iOffset); + pState->eState = ST_ACTIVE; + pState->uSettingsTimer = NetTick() + PROTOHTTP2_TIMEOUT_DEFAULT; + } + else if (iResult < 0) + { + NetPrintf(("protohttp2: [%p] ST_CONN got ST_FAIL (err=%d)\n", pState, iResult)); + pState->eState = ST_FAIL; + pState->iSslFail = ProtoSSLStat(pState->pSsl, 'fail', NULL, 0); + pState->iHresult = ProtoSSLStat(pState->pSsl, 'hres', NULL, 0); + } + else if (NetTickDiff(NetTick(), pState->uTimer) >= 0) + { + NetPrintf(("protohttp2: [%p] timed out while establishing connection\n", pState)); + _ProtoHttp2Close(pState, "timeout"); + pState->bTimeout = TRUE; + } + + // if a failure occured when establishing the connection handle closing our streams + for (iStream = 0; (pState->eState == ST_FAIL) && (iStream < pState->iNumStreams); iStream += 1) + { + _ProtoHttp2StreamCloseOnError(pState, &pState->Streams[iStream]); + } + } + /* otherwise we are connected so let's try to send / receive data from our peer + we will act on the data depending our state */ + else if ((pState->eState > ST_CONN) && (pState->eState < ST_FAIL)) + { + // send any data if we have any + if (pState->iOutLen > 0) + { + // send as much data as we can and update our offset accordingly + if ((iResult = _ProtoHttp2Send(pState, pState->pOutBuf+pState->iOutOff, pState->iOutLen-pState->iOutOff)) > 0) + { + pState->iOutOff += iResult; + } + // if we sent all our data, clear our length/offset + if (pState->iOutOff == pState->iOutLen) + { + pState->iOutLen = pState->iOutOff = 0; + } + } + + // receive new data from our peer + if ((iResult = _ProtoHttp2Recv(pState, pState->pInpBuf+pState->iInpLen, pState->iInpMax-pState->iInpLen)) > 0) + { + FrameHeaderT Header; + const uint8_t *pBuf; + StreamInfoT *pStreamInfo = NULL; + + // increment the offset into the input buffer + pState->iInpLen += iResult; + + // decode as many frames as possible + while ((pBuf = _ProtoHttp2DecodeFrameHeader(pState, pState->pInpBuf, pState->iInpLen, &Header, &pStreamInfo)) != NULL) + { + const uint32_t uFrameLength = Header.uLength+Header.uPadding+PROTOHTTP2_HEADER_SIZE; + + // handle the frame + _ProtoHttp2HandleFrame(pState, pBuf, &Header, pStreamInfo); + + /* advance the buffer past the frame and update new length + note: we need to take advantage of as much space as possible in our buffer. + for that reason we cannot just use an offset into the buffer like we + do on the send side. */ + memmove(pState->pInpBuf, pState->pInpBuf+uFrameLength, pState->iInpLen-uFrameLength); + pState->iInpLen -= uFrameLength; + } + } + + // handle settings synchronization + _ProtoHttp2CheckSettings(pState); + + // check our receive windows and send updates if needed + _ProtoHttp2CheckWindows(pState); + + // check and handle if we lack i/o activity + _ProtoHttp2CheckActivityTimeout(pState); + + // attempt to handle any connection wide error + if (pState->eErrorType != ERRORTYPE_NO_ERROR) + { + _ProtoHttp2SendGoAway(pState, pState->eErrorType, _ProtoHttp2_strErrorType[pState->eErrorType]); + } + } + + // release access to http crit + NetCritLeave(&pState->HttpCrit); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Destroy + + \Description + Destroy the module and release its state + + \Input *pState - module state + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +void ProtoHttp2Destroy(ProtoHttp2RefT *pState) +{ + int32_t iStream; + + // try to gracefully close the connection + ProtoHttp2Close(pState); + + // clean up append header memory + _ProtoHttp2SetAppendHeader(pState, NULL); + + // cleanup stream info memory + for (iStream = 0; iStream < pState->iNumStreams; iStream += 1) + { + _ProtoHttp2StreamInfoCleanup(pState, &pState->Streams[iStream]); + } + + NetCritKill(&pState->HttpCrit); + + if (pState->pDecoder != NULL) + { + HpackDestroy(pState->pDecoder); + pState->pDecoder = NULL; + } + if (pState->pEncoder != NULL) + { + HpackDestroy(pState->pEncoder); + pState->pEncoder = NULL; + } + if (pState->pInpBuf != NULL) + { + DirtyMemFree(pState->pInpBuf, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->pInpBuf = NULL; + } + if (pState->pOutBuf != NULL) + { + DirtyMemFree(pState->pOutBuf, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->pOutBuf = NULL; + } + if (pState->pSsl != NULL) + { + ProtoSSLDestroy(pState->pSsl); + pState->pSsl = NULL; + } + DirtyMemFree(pState, PROTOHTTP_MEMID, pState->iMemGroup, pState->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Callback + + \Description + Set header callbacks. + + \Input *pState - module state + \Input *pCustomHeaderCb - pointer to custom send header callback (may be NULL) + \Input *pReceiveHeaderCb- pointer to recv header callback (may be NULL) + \Input *pUserData - user-supplied callback ref (may be NULL) + + \Notes + The ProtoHttpCustomHeaderCbT callback is used to allow customization of + the HTTP header before sending. It is more powerful than the append + header functionality, allowing to make changes to any part of the header + before it is sent. The callback should return a negative code if an error + occurred, zero can be returned if the application wants ProtoHttp to + calculate the header size, or the size of the header can be returned if + the application has already calculated it. The header should *not* be + terminated with the double \\r\\n that indicates the end of the entire + header, as protohttp appends itself. + + The ProtoHttpReceiveHeaderCbT callback is used to view the header + immediately on reception. It can be used when the built-in header + cache (retrieved with ProtoHttpStatus('htxt') is too small to hold + the entire header received. It is also possible with this method + to view redirection response headers that cannot be retrieved + normally. This can be important if, for example, the application + wishes to attach new cookies to a redirection response. The + custom response header and custom header callback can be used in + conjunction to implement this type of functionality. + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +void ProtoHttp2Callback(ProtoHttp2RefT *pState, ProtoHttp2CustomHeaderCbT *pCustomHeaderCb, ProtoHttp2ReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData) +{ + pState->pCustomHeaderCb = pCustomHeaderCb; + pState->pReceiveHeaderCb = pReceiveHeaderCb; + pState->pCallbackRef = pUserData; +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Request + + \Description + Initiate an HTTP transfer without callback. Pass in a URL and the module + starts a transfer from the appropriate server. + + \Input *pState - module state + \Input *pUrl - the url to retrieve + \Input *pData - user data to send to server (PUT and POST only) + \Input iDataSize - size of user data to send to server (PUT and POST only) + \Input eRequestType - request type to make + \Input *pStreamId - [out] identifier tied to the request + + \Output + int32_t - negative=failure, zero=success, >0=number of data bytes sent (PUT and POST only) + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttp2Request(ProtoHttp2RefT *pState, const char *pUrl, const uint8_t *pData, int32_t iDataSize, ProtoHttpRequestTypeE eRequestType, int32_t *pStreamId) +{ + return(ProtoHttp2RequestCb2(pState, pUrl, pData, iDataSize, eRequestType, pStreamId, NULL, NULL, NULL, NULL)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2RequestCb + + \Description + Initiate an HTTP transfer with write callback. Pass in a URL and the module starts + a transfer from the appropriate server. + + \Input *pState - module state + \Input *pUrl - the url to retrieve + \Input *pData - user data to send to server (PUT and POST only) + \Input iDataSize - size of user data to send to server (PUT and POST only) + \Input eRequestType - request type to make + \Input *pStreamId - [out] identifier tied to the request + \Input *pWriteCb - write callback (optional) + \Input *pUserData - write callback user data (optional) + + \Output + int32_t - negative=failure, zero=success, >0=number of data bytes sent (PUT and POST only) + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttp2RequestCb(ProtoHttp2RefT *pState, const char *pUrl, const uint8_t *pData, int32_t iDataSize, ProtoHttpRequestTypeE eRequestType, int32_t *pStreamId, ProtoHttp2WriteCbT *pWriteCb, void *pUserData) +{ + return(ProtoHttp2RequestCb2(pState, pUrl, pData, iDataSize, eRequestType, pStreamId, pWriteCb, NULL, NULL, pUserData)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2RequestCb2 + + \Description + Initiate an HTTP transfer with callbacks. Pass in a URL and the module starts + a transfer from the appropriate server. + + \Input *pState - module state + \Input *pUrl - the url to retrieve + \Input *pData - user data to send to server (PUT and POST only) + \Input iDataSize - size of user data to send to server (PUT and POST only) + \Input eRequestType - request type to make + \Input *pStreamId - [out] identifier tied to the request + \Input *pWriteCb - write callback (optional) + \Input *pCustomHeaderCb - custom header callback (optional) + \Input *pReceiveHeaderCb- receive header callback (optional) + \Input *pUserData - write callback user data (optional) + + \Output + int32_t - negative=failure, zero=success, >0=number of data bytes sent (PUT and POST only) + + \Version 09/11/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttp2RequestCb2(ProtoHttp2RefT *pState, const char *pUrl, const uint8_t *pData, int32_t iDataSize, ProtoHttpRequestTypeE eRequestType, int32_t *pStreamId, ProtoHttp2WriteCbT *pWriteCb, ProtoHttp2CustomHeaderCbT *pCustomHeaderCb, ProtoHttp2ReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData) +{ + char strKind[8], strHost[sizeof(pState->strHost)]; + int32_t iPort, bSecure, iResult = 0; + uint8_t bPortSpecified; + StreamInfoT StreamInfo; + + // make sure we can create a new stream + if (pState->iNumStreams >= PROTOHTTP2_MAX_STREAMS) + { + NetPrintf(("protohttp2: [%p] exceeded maximum number of concurrent stream\n", pState)); + return(-1); + } + if (pStreamId == NULL) + { + NetPrintf(("protohttp2: [%p] stream identifier output is NULL (required to save identifier)\n", pState)); + return(-2); + } + // make sure we can even attempt to fit the frame + if ((iDataSize > 0) && ((uint32_t)iDataSize > pState->LocalSettings.uMaxFrameSize)) + { + NetPrintf(("protohttp2: [%p] data size exceeds size of the frame, use streaming instead\n", pState)); + return(-3); + } + + NetPrintfVerbose((pState->iVerbose, 0, "protohttp2: [%p] %s %s\n", pState, _ProtoHttp2_strRequestNames[eRequestType], pUrl)); + + // parse the url for kind, host and port + pUrl = ProtoHttpUrlParse2(pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &bSecure, &bPortSpecified); + + // fill in any missing info (relative url) if available + if (_ProtoHttp2ApplyBaseUrl(pState, strKind, strHost, sizeof(strHost), &iPort, &bSecure, bPortSpecified) != 0) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp2: [%p] %s %s://%s:%d%s\n", pState, _ProtoHttp2_strRequestNames[eRequestType], + bSecure ? "https" : "http", strHost, iPort, pUrl)); + } + + // are we still connected, connected to same endpoint and have valid stream id? + if ((pState->eState > ST_IDLE) && (pState->eState < ST_FAIL) && (bSecure == pState->bSecure) && (ds_stricmp(strHost, pState->strHost) == 0) && (pState->iStreamId > 0)) + { + NetPrintfVerbose((pState->iVerbose, 0, "protohttp2: [%p] reusing previous connection\n", pState)); + } + else + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] request new connection -- url change to %s\n", pState, strHost)); + + // save new server/port/security state + ds_strnzcpy(pState->strHost, strHost, sizeof(pState->strHost)); + pState->iPort = iPort; + pState->bSecure = bSecure; + + // send goaway if necessary & close + _ProtoHttp2SendGoAway(pState, ERRORTYPE_NO_ERROR, "new connection"); + + // reset the state to default + _ProtoHttp2Reset(pState); + + // start connect + NetPrintfVerbose((pState->iVerbose, 0, "protohttp2: [%p] connect start (tick=%u)\n", pState, NetTick())); + ProtoSSLConnect(pState->pSsl, pState->bSecure, pState->strHost, 0, pState->iPort); + pState->eState = ST_CONN; + pState->uTimer = NetTick() + pState->uTimeout; + } + + // setup the stream info + ds_memclr(&StreamInfo, sizeof(StreamInfo)); + StreamInfo.iStreamId = *pStreamId = pState->iStreamId; + StreamInfo.eResponseCode = PROTOHTTP_RESPONSE_PENDING; + StreamInfo.eRequestType = eRequestType; + StreamInfo.pWriteCb = pWriteCb; + StreamInfo.pCustomHeaderCb = pCustomHeaderCb; + StreamInfo.pReceiveHeaderCb = pReceiveHeaderCb; + StreamInfo.pUserData = pUserData; + StreamInfo.iDataMax = StreamInfo.iLocalWindow = PROTOHTTP2_WINDOWSIZE; /* we never update our window size at runtime so assume this value */ + StreamInfo.iPeerWindow = pState->PeerSettings.uInitialWindowSize; + + NetCritEnter(&pState->HttpCrit); + // attempt to encode request + if ((iResult = _ProtoHttp2EncodeRequest(pState, &StreamInfo, pUrl, pData, iDataSize)) < 0) + { + _ProtoHttp2StreamInfoCleanup(pState, &StreamInfo); + *pStreamId = PROTOHTTP2_INVALID_STREAMID; + } + else + { + // add the stream information for tracking + ds_memcpy(&pState->Streams[pState->iNumStreams], &StreamInfo, sizeof(StreamInfo)); + pState->iNumStreams += 1; + + // increment to next stream id + pState->iStreamId += 2; + } + NetCritLeave(&pState->HttpCrit); + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Send + + \Description + Send data during an ongoing request + + \Input *pState - module state + \Input iStreamId - identifier of the stream to send the data on + \Input *pData - pointer to data to send + \Input iDataSize - size of data being sent + + \Output + int32_t - negative=PROTOHTTP2_MINBUFF or failure otherwise number of data + bytes sent + + \Notes + In the case of PROTOHTTP2_MINBUFF we do not have enough space to encode + the frame header. We need to return a special code due to the fact when sending + a zero sized end stream we need to have a way to tell if it was actually encoded + into the buffer. In this case you should retry to send on the next frame. + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttp2Send(ProtoHttp2RefT *pState, int32_t iStreamId, const uint8_t *pData, int32_t iDataSize) +{ + int32_t iResult = -1; + StreamInfoT *pStreamInfo; + + if ((pStreamInfo = _ProtoHttp2StreamInfoGet(pState, iStreamId)) != NULL) + { + // make sure the stream is open to allow us to send data + if (pStreamInfo->eState < STREAMSTATE_OPEN) + { + // not ready to send data yet + return(0); + } + else if ((pStreamInfo->eState != STREAMSTATE_OPEN) && (pStreamInfo->eState != STREAMSTATE_HALF_CLOSED_REMOTE)) + { + // we have already finished sending, an error occured + return(-1); + } + + // encode the data into the output buffer + NetCritEnter(&pState->HttpCrit); + iResult = _ProtoHttp2EncodeData(pState, pStreamInfo, pData, iDataSize, (iDataSize == PROTOHTTP2_STREAM_END)); + NetCritLeave(&pState->HttpCrit); + + /* if we don't have enough space to encode the buffer but we are not ending the stream, we can just send zero + bytes written. the only reason we send MINBUFF back is to make sure on zero sized payloads we have a way to + indiciate to try again */ + if ((iResult < 0) && (iDataSize != PROTOHTTP2_STREAM_END)) + { + iResult = 0; + } + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Recv + + \Description + Return the actual url data. + + \Input *pState - module state + \Input iStreamId - identifier of the stream to recv data from + \Input *pBuffer - buffer to store data in + \Input iBufMin - minimum number of bytes to return + \Input iBufMax - maximum number of bytes to return (buffer size) + + \Output + int32_t - negative=error, zero=no data available or bufmax <= 0, positive=number of bytes read + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttp2Recv(ProtoHttp2RefT *pState, int32_t iStreamId, uint8_t *pBuffer, int32_t iBufMin, int32_t iBufMax) +{ + StreamInfoT *pStreamInfo; + int32_t iResult = PROTOHTTP2_RECVFAIL; + + if ((pStreamInfo = _ProtoHttp2StreamInfoGet(pState, iStreamId)) != NULL) + { + NetCritEnter(&pState->HttpCrit); + iResult = _ProtoHttp2Read(pState, pStreamInfo, pBuffer, iBufMin, iBufMax); + NetCritLeave(&pState->HttpCrit); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2RecvAll + + \Description + Return all of the url data. + + \Input *pState - module state + \Input iStreamId - identifier of the stream to recv data from + \Input *pBuffer - buffer to store data in + \Input iBufSize - size of buffer + + \Output + int32_t - PROTOHTTP_RECV*, or positive=bytes in response + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttp2RecvAll(ProtoHttp2RefT *pState, int32_t iStreamId, uint8_t *pBuffer, int32_t iBufSize) +{ + StreamInfoT *pStreamInfo; + int32_t iRecvMax = iBufSize-1, iRecvResult = PROTOHTTP2_RECVFAIL; + + if ((pStreamInfo = _ProtoHttp2StreamInfoGet(pState, iStreamId)) != NULL) + { + NetCritEnter(&pState->HttpCrit); + // try to receive as much as possible, adding to amount received + while ((iRecvResult = _ProtoHttp2Read(pState, pStreamInfo, pBuffer+pStreamInfo->iRecvSize, 1, iRecvMax-pStreamInfo->iRecvSize)) > 0) + { + pStreamInfo->iRecvSize += iRecvResult; + } + NetCritLeave(&pState->HttpCrit); + } + + // check the response code + if (iRecvResult == PROTOHTTP2_RECVDONE) + { + pBuffer[pStreamInfo->iRecvSize] = 0; + iRecvResult = pStreamInfo->iRecvSize; + } + else if ((iRecvResult < 0) && (iRecvResult != PROTOHTTP2_RECVWAIT)) + { + // an error occured + NetPrintf(("protohttp2: [%p] error %d receiving response\n", pState, iRecvResult)); + } + else if (iRecvResult == 0) + { + iRecvResult = (pStreamInfo->iRecvSize < iRecvMax) ? PROTOHTTP2_RECVWAIT : PROTOHTTP2_RECVBUFF; + } + + // return result to caller + return(iRecvResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Abort + + \Description + Abort current operation, if any. + + \Input *pState - module state + \Input iStreamId - identifier of the stream to cancel + + \Notes + This will send a RST_STREAM frame to the peer endpoint with the + CANCEL (0x8) error code + + \Version 11/03/2016 (eesponda) +*/ +/********************************************************************************F*/ +void ProtoHttp2Abort(ProtoHttp2RefT *pState, int32_t iStreamId) +{ + StreamInfoT *pStreamInfo; + + // find stream id and make sure the stream is open to allow us to send data + if (((pStreamInfo = _ProtoHttp2StreamInfoGet(pState, iStreamId)) != NULL) && (pStreamInfo->eState > STREAMSTATE_IDLE) && (pStreamInfo->eState < STREAMSTATE_CLOSED)) + { + NetCritEnter(&pState->HttpCrit); + _ProtoHttp2PrepareRstStream(pState, pStreamInfo, ERRORTYPE_CANCEL, "cancelling the stream"); + NetCritLeave(&pState->HttpCrit); + } +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Close + + \Description + Close the connection to the server + + \Input *pState - module state + + \Notes + This will send a GOAWAY frame to the peer endpoint with the + NO_ERROR (0x0) error code + + \Version 11/22/2016 (eesponda) +*/ +/********************************************************************************F*/ +void ProtoHttp2Close(ProtoHttp2RefT *pState) +{ + // if not connected then nothing left to do + if ((pState->eState == ST_IDLE) || (pState->eState == ST_FAIL)) + { + return; + } + + _ProtoHttp2SendGoAway(pState, ERRORTYPE_NO_ERROR, "user request"); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Status + + \Description + Get status information + + \Input *pState - module state + \Input iStreamId - stream identifier used by certain selectors + \Input iSelect - info selector (see Notes) + \Input *pBuffer - [out] storage for selector-specific output + \Input iBufSize - size of buffer + + \Output + int32_t - selector specific + + \Notes + Selectors are: + + \verbatim + SELECTOR RETURN RESULT + 'body' negative=failed or pending, else size of body (for 64bit size, get via pBuffer) + 'code' negative=no response, else server response code (ProtoHttpResponseE) + 'date' returns last-modified data, if available + 'done' returns status of request negative=failed, zero=pending or positive=done + 'essl' returns protossl error state + 'head' returns header size negative=failed or pending, otherwise header size + 'host' current host copied to output buffer + 'hres' returns hResult containing either the socket error, ssl error, or http status code + 'htxt' response header for stream identified by iStreamId via output buffer + 'imax' returns size of input buffer + 'nstm' returns the number of active streams + 'port' returns the current port + 'rtxt' most http request header text copied to output buffer for stream + 'rtyp' returns the request type for the stream + 'strm' returns the state of the stream identified by iStreamId as ProtoHttp2StreamStateE + 'time' TRUE if the client timed out the connection, else FALSE + \endverbatim + + Unhandled selectors are passed on to ProtoSSL. + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttp2Status(ProtoHttp2RefT *pState, int32_t iStreamId, int32_t iSelect, void *pBuffer, int32_t iBufSize) +{ + const StreamInfoT *pStreamInfo; + + // return protossl error state (we cache this since we reset the state when we disconnect an error) + if (iSelect == 'essl') + { + return(pState->iSslFail); + } + // return current host + if (iSelect == 'host') + { + ds_strnzcpy(pBuffer, pState->strHost, iBufSize); + return(0); + } + // return hresult containing either the socket error, ssl error or http status code + if (iSelect == 'hres') + { + // validate stream id, stream information and status code + if ((iStreamId > 0) && ((pStreamInfo = _ProtoHttp2StreamInfoGet(pState, iStreamId)) != NULL) && (pStreamInfo->eResponseCode > 0)) + { + return(DirtyErrGetHResult(DIRTYAPI_PROTO_HTTP, pStreamInfo->eResponseCode, (pStreamInfo->eResponseCode >= PROTOHTTP_RESPONSE_CLIENTERROR) ? TRUE : FALSE)); + } + else + { + return(pState->iHresult); + } + } + // return size of input buffer + if (iSelect == 'imax') + { + return(pState->iInpMax); + } + // return number of active streams + if (iSelect == 'nstm') + { + return(pState->iNumStreams); + } + // return current port + if (iSelect == 'port') + { + return(pState->iPort); + } + // return timeout indicator + if (iSelect == 'time') + { + return(pState->bTimeout); + } + + // attempt to get the stream information for the below selectors where valid stream is required + if ((iStreamId <= 0) || ((pStreamInfo = _ProtoHttp2StreamInfoGet(pState, iStreamId)) == NULL)) + { + // pass down to unhandled selectors with no stream specified to ProtoSSL + return(ProtoSSLStat(pState->pSsl, iSelect, pBuffer, iBufSize)); + } + + // return response code + if (iSelect == 'code') + { + return(pStreamInfo->eResponseCode); + } + // return the header data + if (iSelect == 'date') + { + return(pStreamInfo->iHdrDate); + } + // done check: negative=failed, zero=pending, positive=done + if (iSelect == 'done') + { + if (pState->eState == ST_FAIL) + { + return(-1); + } + if (pStreamInfo->eState == STREAMSTATE_CLOSED) + { + return(1); + } + return(0); + } + // return the request header text + if (iSelect == 'rtxt') + { + ds_strnzcpy(pBuffer, pStreamInfo->strRequestHeader, iBufSize); + return(0); + } + // return the request type + if (iSelect == 'rtyp') + { + return(pStreamInfo->eRequestType); + } + // return the current state of the stream + if (iSelect == 'strm') + { + return(pStreamInfo->eState); + } + + /* check the state: + if failure then nothing is happening, thus nothing left to do + otherwise if we have yet to receive our headers then the remaining data has + not been filled out yet */ + if (pState->eState == ST_FAIL) + { + return(-1); + } + if (pStreamInfo->eResponseCode == PROTOHTTP_RESPONSE_PENDING) + { + return(-2); + } + + // negative=failed or pending, else size of body (for 64bit size, get via pBuffer) + if (iSelect == 'body') + { + if ((pBuffer != NULL) && (iBufSize == sizeof(pStreamInfo->iBodySize))) + { + ds_memcpy(pBuffer, &pStreamInfo->iBodySize, iBufSize); + } + return((int32_t)pStreamInfo->iBodySize); + } + // return size of the header + if (iSelect == 'head') + { + return(pStreamInfo->iHeaderLen); + } + // return the response header text + if (iSelect == 'htxt') + { + ds_strnzcpy(pBuffer, pStreamInfo->pHeader, iBufSize); + return(0); + } + + // unhandled selector + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2Control + + \Description + ProtoHttp2 control function. Different selectors control different behaviors. + + \Input *pState - module state + \Input iStreamId - stream identifier used by certain controls + \Input iControl - control selector (see Notes) + \Input iValue - control specific + \Input iValue2 - control specific + \Input *pValue - control specific + + \Output + int32_t - control specific + + \Notes + Selectors are: + + \verbatim + SELECTOR DESCRIPTION + 'apnd' The given buffer will be appended to future headers sent + by ProtoHttp2. Note that the User-Agent and Accept lines + in the default header will be replaced, so if these lines + are desired, they should be supplied to the append header. + 'huff' Sets the use of huffman encoding for strings (default=TRUE) + 'ires' Resize input buffer + 'spam' Sets debug output verbosity (0..n) + 'time' Sets ProtoHttp client timeout in milliseconds (default=30s) + \endverbatim + + Unhandled selectors are passed on to ProtoSSL. + + \Version 09/27/2016 (eesponda) +*/ +/*******************************************************************************F*/ +int32_t ProtoHttp2Control(ProtoHttp2RefT *pState, int32_t iStreamId, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iControl == 'apnd') + { + return(_ProtoHttp2SetAppendHeader(pState, (const char *)pValue)); + } + if (iControl == 'huff') + { + pState->bHuffman = (uint8_t)iValue; + return(0); + } + if (iControl == 'ires') + { + // clamp the input buffer size + iValue = PROTOHTTP2_ClampFrameSize(iValue); + + // attempt to resize the buffer if necessary + if (((uint32_t)iValue != pState->TempSettings.uMaxFrameSize) && (_ProtoHttp2ResizeInputBuffer(pState, iValue+PROTOHTTP2_RESERVED_SIZE) == 0)) + { + // set the temp settings to be sent to our peer + pState->TempSettings.uMaxFrameSize = (uint32_t)iValue; + return(0); + } + return(-1); + } + if (iControl == 'spam') + { + pState->iVerbose = iValue; + // fall through to protossl + } + if (iControl == 'time') + { + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] setting timeout to %d ms\n", pState, iValue)); + pState->uTimeout = (unsigned)iValue; + return(0); + } + + // unhandled control, fallthrough to protossl + return(ProtoSSLControl(pState->pSsl, iControl, iValue, iValue2, pValue)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2SetBaseUrl + + \Description + Set base url that will be used for any relative url references. + + \Input *pState - module state + \Input *pUrl - base url + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +void ProtoHttp2SetBaseUrl(ProtoHttp2RefT *pState, const char *pUrl) +{ + char strKind[8]; + uint8_t bPortSpecified; + + // parse the url for kind, host and port + ProtoHttpUrlParse2(pUrl, strKind, sizeof(strKind), pState->strBaseHost, sizeof(pState->strBaseHost), + &pState->iBasePort, &pState->bBaseSecure, &bPortSpecified); + + NetPrintfVerbose((pState->iVerbose, 1, "protohttp2: [%p] setting base url to %s://%s:%d\n", + pState, pState->bBaseSecure ? "https" : "http", pState->strBaseHost, pState->iBasePort)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2GetLocationHeader + + \Description + Get location header from the input buffer. The Location header requires + some special processing. + + \Input *pState - reference pointer + \Input *pInpBuf - buffer holding header text + \Input *pBuffer - [out] output buffer for parsed location header, null for size request + \Input iBufSize - size of output buffer, zero for size request + \Input **pHdrEnd- [out] pointer past end of parsed header (optional) + + \Output + int32_t - negative=not found or not enough space, zero=success, positive=size query result + + \Version 09/27/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttp2GetLocationHeader(ProtoHttp2RefT *pState, const char *pInpBuf, char *pBuffer, int32_t iBufSize, const char **pHdrEnd) +{ + const char *pLocHdr; + int32_t iLocLen, iLocPreLen=0; + + // get a pointer to header + if ((pLocHdr = ProtoHttpFindHeaderValue(pInpBuf, "location")) == NULL) + { + return(-1); + } + + /* according to RFC location headers should be absolute, but some webservers respond with relative + URL's. we assume any url that does not include "://" is a relative url, and if we find one, we + assume the request keeps the same security, port, and host as the previous request */ + if ((pState != NULL) && (!strstr(pLocHdr, "://"))) + { + char strTemp[288]; // space for max DNS name (253 chars) plus max http url prefix + + // format http url prefix + if ((pState->bSecure && (pState->iPort == 443)) || (pState->iPort == 80)) + { + ds_snzprintf(strTemp, sizeof(strTemp), "%s://%s", pState->bSecure ? "https" : "http", pState->strHost); + } + else + { + ds_snzprintf(strTemp, sizeof(strTemp), "%s://%s:%d", pState->bSecure ? "https" : "http", pState->strHost, pState->iPort); + } + + // make sure relative URL includes '/' as the first character, required when we format the redirection url + if (*pLocHdr != '/') + { + ds_strnzcat(strTemp, "/", sizeof(strTemp)); + } + + // calculate url prefix length + iLocPreLen = (int32_t)strlen(strTemp); + + // copy url prefix text if a buffer is specified + if (pBuffer != NULL) + { + ds_strnzcpy(pBuffer, strTemp, iBufSize); + pBuffer = (char *)((uint8_t *)pBuffer + iLocPreLen); + iBufSize -= iLocPreLen; + } + } + + // extract location header and return size + iLocLen = ProtoHttpExtractHeaderValue(pLocHdr, pBuffer, iBufSize, pHdrEnd); + // if it's a size request add in (possible) url header length + if ((pBuffer == NULL) && (iBufSize == 0)) + { + iLocLen += iLocPreLen; + } + + // return to caller + return(iLocLen); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttp2StreamFree + + \Description + Removes the stream information for the list by identifier + + \Input *pState - module state + \Input iStreamId - identifier of the stream info to remove + + \Version 11/01/2016 (eesponda) +*/ +/********************************************************************************F*/ +void ProtoHttp2StreamFree(ProtoHttp2RefT *pState, int32_t iStreamId) +{ + StreamInfoT *pStreamInfo; + + if ((pStreamInfo = _ProtoHttp2StreamInfoGet(pState, iStreamId)) != NULL) + { + // get index based on pointer + int32_t iStream = (int32_t)(pStreamInfo - pState->Streams); + + #if DIRTYCODE_LOGGING + if (pStreamInfo->eState != STREAMSTATE_CLOSED) + { + NetPrintf(("protohttp2: [%p] warning: freeing stream state for a stream that is not yet closed\n", pState)); + } + #endif + + // cleanup any dynamic memory + _ProtoHttp2StreamInfoCleanup(pState, pStreamInfo); + + // remove entry from stream list + if (iStream != (pState->iNumStreams-1)) + { + int32_t iNumMove = (pState->iNumStreams-1) - iStream; + + // move the stream info to remove the gap + memmove(pStreamInfo, pStreamInfo+1, iNumMove * sizeof(*pStreamInfo)); + } + // decrement count + pState->iNumStreams -= 1; + } +} diff --git a/src/thirdparty/dirtysdk/source/proto/protohttpmanager.c b/src/thirdparty/dirtysdk/source/proto/protohttpmanager.c new file mode 100644 index 00000000..23ec48c5 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protohttpmanager.c @@ -0,0 +1,2773 @@ +/*H********************************************************************************/ +/*! + \File protohttpmanager.c + + \Description + High-level module designed to create and manage a pool of ProtoHttp refs. A + client application can submit rapid-fire http requests and ProtoHttpManager + will distribute them efficiently across the ref pool internally, queuing + them for efficient use of keep-alive and pipelining requests where possible. + + \Notes + Pipelining resources: + + http://www.mozilla.org/projects/netlib/http/pipelining-faq.html + http://www.w3.org/Protocols/HTTP/Performance/Pipeline.html + + \Todo + Validate POST support + Pipelining: + - handle fewer responses than expected (?) + + \Copyright + Copyright (c) Electronic Arts 2009-2011. + + \Version 1.0 05/20/2009 (jbrookes) First Version +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protohttpmanager.h" + +/*** Defines **********************************************************************/ + +//! maximum number of protohttp refs that can be allocated +#ifdef DIRTYCODE_NX +#define HTTPMANAGER_MAXREFS (48) +#else +#define HTTPMANAGER_MAXREFS (64) +#endif + +//! maximum number of protohttp commands that can be queued for execution +#define HTTPMANAGER_MAXCMDS (256) + +//! maximum number of protohttp commands that can be queued in a given httpref +#define HTTPMANAGER_MAXREFQUEUE (16) + +//! define for final-mode diagnostic printing +#define HTTPMANAGER_FINALDEBUG (!DIRTYCODE_DEBUG && FALSE) + +#if HTTPMANAGER_FINALDEBUG +#if defined(DIRTYCODE_PC) +#include // OutputDebugStringA +#else +#include +#endif +#endif + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef enum HttpManagerHttpCmdStateE +{ + HTTPMANAGER_CMDSTATE_IDLE = 0, //!< unallocated + HTTPMANAGER_CMDSTATE_WAIT, //!< queued and waiting to start + HTTPMANAGER_CMDSTATE_PIPE, //!< pipelined + HTTPMANAGER_CMDSTATE_ACTV, //!< active + HTTPMANAGER_CMDSTATE_DONE, //!< complete + HTTPMANAGER_CMDSTATE_FAIL //!< queued execution of command failed +} HttpManagerHttpCmdStateE; + +typedef enum HttpManagerHttpRefStateE +{ + HTTPMANAGER_REFSTATE_NONE = 0, //!< no ref available + HTTPMANAGER_REFSTATE_IDLE, //!< ref is available + HTTPMANAGER_REFSTATE_BUSY, //!< ref is in use +} HttpManagerHttpRefStateE; + +//! http ref info +typedef struct HttpManagerHttpRefT +{ + ProtoHttpRefT *pProtoHttp; //!< http ref + struct HttpManagerHttpCmdT *HttpCmdQueue[HTTPMANAGER_MAXREFQUEUE]; //!< http command queue + uint32_t uLastTick; //!< last timestamp ref was accessed + uint8_t uHttpState; //!< http ref state + int8_t iTransactions; //!< number of transactions queued + int8_t iCurTransaction; //!< current transaction counter (used for callbacks) + int8_t _pad; +} HttpManagerHttpRefT; + +//! http cmd info +typedef struct HttpManagerHttpCmdT +{ + HttpManagerRefT *pHttpManager; //!< HttpManager ref, required by ProtoHttp callback + HttpManagerHttpRefT *pHttpRef; //!< pointer to http ref used for this transaction + int32_t iHttpHandle; //!< handle associated with this transaction (zero means unallocated) + int32_t iTimeout; //!< timeout to set for http ref (zero=do not set) + void *pCallbackRef; //!< user callback ref + ProtoHttpWriteCbT *pWriteCb; //!< write callback + ProtoHttpCustomHeaderCbT *pCustomHeaderCb; //!< custom header callback + ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb; //!< receive header callback + void *pUserData; //!< callback user data + const char *pUrl; //!< request url + char *pAppendHdr; //!< append header (optional) + int32_t iResult; //!< transaction result + uint32_t uQueueTick; //!< tick request was queued at + uint32_t uIssueTick; //!< tick request was issued at + uint32_t uComplTick; //!< tick request was completed at + uint64_t uBytesXfer; //!< bytes transferred + uint8_t uRequestType; //!< ProtoHttpRequestTypeE + uint8_t uState; //!< transaction state + uint8_t bKeepAlive; //!< TRUE if this command was queued with keep-alive + uint8_t bCopiedUrl; //!< TRUE if the url was copied +} HttpManagerHttpCmdT; + +//! http module state +struct HttpManagerRefT +{ + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData;//!< user data associated with mem group + + // user callback info + ProtoHttpCustomHeaderCbT *pCustomHeaderCb; //!< callback for modifying request header + ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb; //!< callback for viewing recv header on recepit + + //! current handle + int32_t iHttpHandle; + + //! module debuglevel + int32_t iVerbose; + + //! transaction stats + HttpManagerStatT HttpManagerStats; + + // module configuration variables + uint8_t bPipelining; //!< pipelining enable/disable + uint8_t bKeepalive; //!< keep-alive enable/disable + uint8_t bPipeWithoutKeepAlive; //!< if TRUE, will pipeline without a prior keep-alive connection + uint8_t bCopyUrl; //!< if TRUE, alloc memory for and copy url; else rely on caller to persist + int8_t iMaxPipedUrls; //!< maximum number of Urls that may be piped in a single request + uint8_t bAutoUpdate; //!< TRUE if auto-update enabled (default), else FALSE + uint8_t bXhttpEnabled; //!< XHTTP enabled (Xbox 360 only) + uint8_t _pad; + + //! number of available http refs + int32_t iHttpNumRefs; + //! protohttp buffer size + int32_t iHttpBufSize; + + //! global append header (optional) + char *pAppendHdr; + + //! protohttp module pool + HttpManagerHttpRefT HttpRefs[HTTPMANAGER_MAXREFS]; + + //! protohttp command pool + HttpManagerHttpCmdT HttpCmds[HTTPMANAGER_MAXCMDS]; +}; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Private variables + +#if HTTPMANAGER_FINALDEBUG +int32_t _HttpManager_iStartTick = 0; +#endif + +// Public variables + + +/*** Private Functions ************************************************************/ + +#if HTTPMANAGER_FINALDEBUG +/*F********************************************************************************/ +/*! + \Function _HttpManagerPrintfCode + + \Description + Special final-mode PC debugging hook to display netprintf debug output. + + \Input *pFormat - format string + \Input ... - variable-length arg list + + \Output + int32_t - zero + + \Version 05/22/2009 (jbrookes) +*/ +/********************************************************************************F*/ +#undef NetPrintf +#define NetPrintf(_x) _HttpManagerPrintfCode _x +static int32_t _HttpManagerPrintfCode(const char *pFormat, ...) +{ + va_list pFmtArgs; + char strText[4096]; + char strTick[16]; + const char *pText = strText; + + va_start(pFmtArgs, pFormat); + // check for simple formatting + if ((pFormat[0] == '%') && (pFormat[1] == 's') && (pFormat[2] == 0)) + { + pText = va_arg(pFmtArgs, const char *); + } + else + { + vsprintf(strText, pFormat, pFmtArgs); + } + va_end(pFmtArgs); + + ds_snzprintf(strTick, sizeof(strTick), "[%d] ", NetTick()-_HttpManager_iStartTick); + #if defined(DIRTYCODE_PC) + OutputDebugStringA(strTick); + OutputDebugStringA(pText); + #else + printf("%s%s", strTick, pText); + #endif + return(0); +} +#endif + +#if HTTPMANAGER_FINALDEBUG +/*F********************************************************************************/ +/*! + \Function _HttpManagerPrintfVerboseCode + + \Description + Special final-mode PC debugging hook to display netprintfverbose debug output. + + \Input iVerbosityLevel - module verbosity level + \Input iCheckLevel - verbosity to check against for this statement + \Input *pFormat - format string + \Input ... - variable-length arg list + + \Output + int32_t - zero + + \Version 05/26/2009 (jbrookes) +*/ +/********************************************************************************F*/ +#undef NetPrintfVerbose +#define NetPrintfVerbose(_x) _HttpManagerPrintfVerboseCode _x +static int32_t _HttpManagerPrintfVerboseCode(int32_t iVerbosityLevel, int32_t iCheckLevel, const char *pFormat, ...) +{ + va_list pFmtArgs; + char strText[4096]; + char strTick[16]; + const char *pText = strText; + + va_start(pFmtArgs, pFormat); + // check for simple formatting + if ((pFormat[0] == '%') && (pFormat[1] == 's') && (pFormat[2] == 0)) + { + pText = va_arg(pFmtArgs, const char *); + } + else + { + vsprintf(strText, pFormat, pFmtArgs); + } + va_end(pFmtArgs); + + if (iCheckLevel < iVerbosityLevel) + { + ds_snzprintf(strTick, sizeof(strTick), "[%d] ", NetTick()-_HttpManager_iStartTick); + #if defined(DIRTYCODE_PC) + OutputDebugStringA(strTick); + OutputDebugStringA(pText); + #else + printf("%s%s", strTick, pText); + #endif + } + return(0); +} +#endif + +#if DIRTYCODE_LOGGING || HTTPMANAGER_FINALDEBUG +/*F********************************************************************************/ +/*! + \Function _HttpManagerDisplayStats + + \Description + Display transfer stats for module. + + \Input *pHttpManager - httpmanager state + + \Version 02/21/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerDisplayStats(HttpManagerRefT *pHttpManager) +{ + // display stats + NetPrintf(("httpmanager: transactions: %d\n", pHttpManager->HttpManagerStats.uNumTransactions)); + if (pHttpManager->HttpManagerStats.uNumTransactions > 0) + { + NetPrintf(("httpmanager: keepalive transactions: %d\n", pHttpManager->HttpManagerStats.uNumKeepAliveTransactions)); + NetPrintf(("httpmanager: pipelined transactions: %d\n", pHttpManager->HttpManagerStats.uNumPipelinedTransactions)); + NetPrintf(("httpmanager: max active transactions: %d\n", pHttpManager->HttpManagerStats.uMaxActiveTransactions)); + NetPrintf(("httpmanager: max queued transactions: %d\n", pHttpManager->HttpManagerStats.uMaxQueuedTransactions)); + NetPrintf(("httpmanager: sum queue wait time: %dms\n", pHttpManager->HttpManagerStats.uSumQueueWaitLatency)); + NetPrintf(("httpmanager: avg queue wait time: %dms\n", pHttpManager->HttpManagerStats.uSumQueueWaitLatency/pHttpManager->HttpManagerStats.uNumTransactions)); + NetPrintf(("httpmanager: max queue wait time: %dms\n", pHttpManager->HttpManagerStats.uMaxQueueWaitLatency)); + NetPrintf(("httpmanager: sum queue free time: %dms\n", pHttpManager->HttpManagerStats.uSumQueueFreeLatency)); + NetPrintf(("httpmanager: avg queue free time: %dms\n", pHttpManager->HttpManagerStats.uSumQueueFreeLatency/pHttpManager->HttpManagerStats.uNumTransactions)); + NetPrintf(("httpmanager: max queue free time: %dms\n", pHttpManager->HttpManagerStats.uMaxQueueFreeLatency)); + NetPrintf(("httpmanager: total bytes transferred: %qd\n", pHttpManager->HttpManagerStats.uTransactionBytes)); + NetPrintf(("httpmanager: total transaction time: %d\n", pHttpManager->HttpManagerStats.uTransactionTime)); + NetPrintf(("httpmanager: avg bytes per second %.2f\n", (float)pHttpManager->HttpManagerStats.uTransactionBytes*1000.0f/(float)pHttpManager->HttpManagerStats.uTransactionTime)); + NetPrintf(("httpmanager: avg transaction size %.2f\n", (float)pHttpManager->HttpManagerStats.uTransactionBytes/(float)pHttpManager->HttpManagerStats.uNumTransactions)); + } +} +#endif + +/*F********************************************************************************/ +/*! + \Function _HttpManagerResetStats + + \Description + Reset transfer stats for module. + + \Input *pHttpManager - httpmanager state + + \Version 07/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerResetStats(HttpManagerRefT *pHttpManager) +{ + ds_memclr(&pHttpManager->HttpManagerStats, sizeof(pHttpManager->HttpManagerStats)); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerUrlCompare + + \Description + Compares base elements of given Urls (kind, host, port, security) and + returns whether they are identical or not. + + \Input *pUrlA - Url to compare + \Input *pUrlB - Url to compare + + \Output + uint32_t - zero=same, else different + + \Version 07/01/2009 (jbrookes) First Version +*/ +/********************************************************************************F*/ +static uint32_t _HttpManagerUrlCompare(const char *pUrlA, const char *pUrlB) +{ + char strKindA[16], strKindB[16], strHostA[128], strHostB[128]; + int32_t iPortA, iPortB, iSecureA, iSecureB; + + ProtoHttpUrlParse(pUrlA, strKindA, sizeof(strKindA), strHostA, sizeof(strHostA), &iPortA, &iSecureA); + ProtoHttpUrlParse(pUrlB, strKindB, sizeof(strKindB), strHostB, sizeof(strHostB), &iPortB, &iSecureB); + + return(!((ds_stricmp(strKindA, strKindB) == 0) && (ds_stricmp(strHostA, strHostB) == 0) && (iPortA == iPortB) && (iSecureA == iSecureB))); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerPipelineCheck + + \Description + See if specified requests will pipeline. + + \Input *pHttpCmd - previously issued command + \Input *pHttpCmd2 - command to check for piping with previous command + + \Output + int32_t - zero=will not pipe; else will pipe + + \Version 07/15/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerPipelineCheck(HttpManagerHttpCmdT *pHttpCmd, HttpManagerHttpCmdT *pHttpCmd2) +{ + // make sure request can be pipelined + if ((pHttpCmd2->uRequestType != PROTOHTTP_REQUESTTYPE_GET) && (pHttpCmd2->uRequestType != PROTOHTTP_REQUESTTYPE_HEAD)) + { + return(0); + } + + // make sure urls will pipe + return(_HttpManagerUrlCompare(pHttpCmd->pUrl, pHttpCmd2->pUrl) == 0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerPipelineReset + + \Description + Reset pipe following a request failure of some kind or another with subsequent + piped results that need to be reissued. + + \Input *pHttpManager - reference pointer + \Input *pHttpRef - http ref piped transactions we need to consider for reset are queued on + \Input iHttpCmdFirst - first command to reset + + \Version 07/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerPipelineReset(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, int32_t iHttpCmdFirst) +{ + HttpManagerHttpCmdT *pHttpCmd = NULL; + int32_t iHttpCmd; + + // reset commands for reissue + for (iHttpCmd = iHttpCmdFirst ; iHttpCmd < pHttpRef->iTransactions; iHttpCmd += 1) + { + pHttpCmd = pHttpRef->HttpCmdQueue[iHttpCmd]; + if ((pHttpCmd->uState == HTTPMANAGER_CMDSTATE_ACTV) || (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_PIPE)) + { + NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: reset handle %d for re-issue\n", pHttpCmd->iHttpHandle)); + pHttpCmd->uState = HTTPMANAGER_CMDSTATE_WAIT; + pHttpCmd->bKeepAlive = FALSE; + } + else + { + break; + } + } + if ((iHttpCmd - iHttpCmdFirst) > 0) + { + NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: reset %d requests on ref %2d for re-issue\n", iHttpCmd - iHttpCmdFirst, pHttpRef - pHttpCmd->pHttpManager->HttpRefs)); + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerCustomHeaderCb + + \Description + Handle ProtoHttp custom header send callback, and pass it on to caller-registered + callback handler with handle-specific callback user data pointer. + + \Input *pState - protohttp state + \Input *pHeader - request header + \Input uHeaderSize - amount of space available for header (including current header text) + \Input *pData - data to be appended to header + \Input iDataLen - total size of data + \Input *pUserRef - HttpManagerHttpRefT for this request + + \Output + int32_t - user callback result + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerCustomHeaderCb(ProtoHttpRefT *pState, char *pHeader, uint32_t uHeaderSize, const char *pData, int64_t iDataLen, void *pUserRef) +{ + HttpManagerHttpRefT *pHttpRef = (HttpManagerHttpRefT *)pUserRef; + HttpManagerHttpCmdT *pHttpCmd = pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]; + NetPrintfVerbose((pHttpCmd->pHttpManager->iVerbose, 1, "httpmanager: calling custom header callback with userref 0x%08x for handle %d\n", (uintptr_t)pHttpCmd->pCallbackRef, pHttpCmd->iHttpHandle)); + return(pHttpCmd->pHttpManager->pCustomHeaderCb(pState, pHeader, uHeaderSize, pData, iDataLen, pHttpCmd->pCallbackRef)); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerReceiveHeaderCb + + \Description + Handle ProtoHttp header receive callback, and pass it on to caller-registered + callback handler with handle-specific callback user data pointer. + + \Input *pState - protohttp state + \Input *pHeader - response header + \Input uHeaderSize - size of response header + \Input *pUserRef - HttpManagerHttpRefT for this request + + \Output + int32_t - user callback result + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerReceiveHeaderCb(ProtoHttpRefT *pState, const char *pHeader, uint32_t uHeaderSize, void *pUserRef) +{ + HttpManagerHttpRefT *pHttpRef = (HttpManagerHttpRefT *)pUserRef; + HttpManagerHttpCmdT *pHttpCmd = pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]; + HttpManagerRefT *pHttpManager = pHttpCmd->pHttpManager; + int32_t iResult, iHttpCmd = -1; + ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb; + void *pUserData; + + /* early out if pHttpManager is null; fifa encountred an issue + In theory, if HttpManagerAlloc/HttpManagerFree are being called from different thread + than ProtoHttpUpdate. When HttpManagerFree is called it clears out a structure HttpManagerHttpCmdT that contains the + pointer to pHttpManager, it is then a race condition on when the header callback is called causing a crash. */ + if (pHttpManager == NULL) + { + NetPrintf(("httpmanager: skipping _HttpManagerReceiveHeaderCb because pHttpManager is null\n")); + return(0); + } + + // check for an error response, and handle if it will affect piped commands + iResult = ProtoHttpStatus(pState, 'code', NULL, 0); + if (PROTOHTTP_GetResponseClass(iResult) != PROTOHTTP_RESPONSE_SUCCESSFUL) + { + if (PROTOHTTP_GetResponseClass(iResult) == PROTOHTTP_RESPONSE_REDIRECTION) + { + char strLocation[1024]; + ProtoHttpGetLocationHeader(pState, pHeader, strLocation, sizeof(strLocation), NULL); + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] redirect ref %d url=%s (%d)\n", pHttpCmd->iHttpHandle, + pHttpCmd->pHttpRef - pHttpManager->HttpRefs, strLocation, iResult)); + } + else + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] redirect ref %d (%d)\n", pHttpCmd->iHttpHandle, + pHttpCmd->pHttpRef - pHttpManager->HttpRefs, iResult)); + } + + /* if this transaction timed out (from a keep-alive connection that was closed) mark it for reissue, + otherwise, we just look for subsequent piped results that need to be reissued */ + if (iResult == PROTOHTTP_RESPONSE_REQUESTTIMEOUT) + { + iHttpCmd = 0; + } + if (PROTOHTTP_GetResponseClass(iResult) == PROTOHTTP_RESPONSE_REDIRECTION) + { + iHttpCmd = 1; + } + } + + // check for loss of piped commands due to premature close + if (ProtoHttpStatus(pState, 'plst', NULL, 0) == TRUE) + { + NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: piped commands lost due to premature close on handle %d\n", pHttpCmd->iHttpHandle)); + iHttpCmd = 1; + + // downgrade our pipelining support $$todo$$ - this should be tracked per site + if ((PROTOHTTP_GetResponseClass(iResult) != PROTOHTTP_RESPONSE_REDIRECTION) && (pHttpManager->bPipeWithoutKeepAlive)) + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager; disabling pipelining without keep-alive\n")); + pHttpManager->bPipeWithoutKeepAlive = FALSE; + } + } + + // reset pipe if necessary + if (iHttpCmd != -1) + { + _HttpManagerPipelineReset(pHttpManager, pHttpRef, iHttpCmd); + } + + // if we have a 408 timeout response, reset ref to idle state, so we can reissue the request + if (iResult == PROTOHTTP_RESPONSE_REQUESTTIMEOUT) + { + pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_IDLE; + // since we are going to re-issue this request, we do not forward the 408 response on to the application + return(0); + } + + // request level callback takes priority to global + if ((pReceiveHeaderCb = pHttpCmd->pReceiveHeaderCb) != NULL) + { + pUserData = pHttpCmd->pUserData; + } + else + { + pReceiveHeaderCb = pHttpManager->pReceiveHeaderCb; + pUserData = pHttpCmd->pCallbackRef; + } + + // forward to caller's receive callback, if installed + if (pReceiveHeaderCb != NULL) + { + NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: calling receive header callback with userref 0x%08x for handle %d\n", (uintptr_t)pUserData, pHttpCmd->iHttpHandle)); + return(pReceiveHeaderCb(pState, pHeader, uHeaderSize, pUserData)); + } + else + { + return(0); + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerWriteCbProcess + + \Description + User write callback processing, if write callback is set + + \Input *pHttpManager - reference pointer + \Input *pHttpCmd - http cmd + + \Version 07/30/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerWriteCbProcess(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) +{ + ProtoHttpWriteCbInfoT WriteCbInfo; + char strTempRecv[1024]; + int32_t iResult; + + ds_memclr(&WriteCbInfo, sizeof(WriteCbInfo)); + WriteCbInfo.eRequestType = (ProtoHttpRequestTypeE)pHttpCmd->uRequestType; + WriteCbInfo.eRequestResponse = PROTOHTTP_RESPONSE_PENDING; + + while ((iResult = HttpManagerRecv(pHttpManager, pHttpCmd->iHttpHandle, strTempRecv, 1, sizeof (strTempRecv))) > 0) + { + pHttpCmd->pWriteCb(pHttpCmd->pHttpRef->pProtoHttp, &WriteCbInfo, strTempRecv, iResult, pHttpCmd->pUserData); + } + + if ((iResult == PROTOHTTP_RECVDONE) || (iResult == PROTOHTTP_RECVHEAD) || (iResult == PROTOHTTP_RECVFAIL)) + { + if (iResult != PROTOHTTP_RECVFAIL) + { + WriteCbInfo.eRequestResponse = (ProtoHttpResponseE)ProtoHttpStatus(pHttpCmd->pHttpRef->pProtoHttp, 'code', NULL, 0); + pHttpCmd->pWriteCb(pHttpCmd->pHttpRef->pProtoHttp, &WriteCbInfo, "", iResult, pHttpCmd->pUserData); + } + else + { + pHttpCmd->pWriteCb(pHttpCmd->pHttpRef->pProtoHttp, &WriteCbInfo, "", PROTOHTTP_RECVFAIL, pHttpCmd->pUserData); + } + pHttpCmd->pWriteCb = NULL; + pHttpCmd->pCustomHeaderCb = NULL; + pHttpCmd->pReceiveHeaderCb = NULL; + pHttpCmd->pUserData = NULL; + } + + +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerSetAppendHeader + + \Description + Set append header, either on a global basis (pHttpCmd=NULL) or for a + specific HTTP command. + + \Input *pHttpManager - reference pointer + \Input *pHttpCmd - cmd to set append header for, or NULL to set global append header + \Input *pAppendHdr - append header to set + + \Output + int32_t - negative=failure, else success + + \Version 07/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerSetAppendHeader(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd, const char *pAppendHdr) +{ + char **ppAppendHdr; + + // point to append header we are operating on + ppAppendHdr = (pHttpCmd != NULL) ? &pHttpCmd->pAppendHdr : &pHttpManager->pAppendHdr; + + // if there is a previous append header, free it + if (*ppAppendHdr != NULL) + { + DirtyMemFree(*ppAppendHdr, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); + *ppAppendHdr = NULL; + } + // set new append header, if specified + if ((pAppendHdr != NULL) && (*pAppendHdr != '\0')) + { + int32_t iHdrLen = (int32_t)strlen(pAppendHdr); + // allocate memory to buffer append header + if ((*ppAppendHdr = DirtyMemAlloc(iHdrLen+1, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData)) == NULL) + { + NetPrintf(("httpmanager: could not allocate %d bytes for append header for handle %d\n", iHdrLen+1, (pHttpCmd != NULL) ? pHttpCmd->iHttpHandle: -1)); + return(-1); + } + // copy to append header + ds_strnzcpy(*ppAppendHdr, pAppendHdr, iHdrLen+1); + } + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerApplyAppendHeader + + \Description + Applies append header to the specified command. The append header set will + be the command-specific header if set, else it will be the global append + header if set. If neither is set, the append header is cleared. + + \Input *pHttpManager - reference pointer + \Input *pHttpCmd - cmd to get append header for + + \Version 07/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerApplyAppendHeader(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) +{ + char *pAppendHdr = pHttpCmd->pAppendHdr; + if (pAppendHdr == NULL) + { + pAppendHdr = pHttpManager->pAppendHdr; + } + ProtoHttpControl(pHttpCmd->pHttpRef->pProtoHttp, 'apnd', 0, 0, pAppendHdr); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerAllocHandle + + \Description + Allocate a new handle. Valid handles range from [1...7ffffff]. + + \Input *pHttpManager - reference pointer + + \Output + int32_t - new handle + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerAllocHandle(HttpManagerRefT *pHttpManager) +{ + int32_t iHttpHandle = pHttpManager->iHttpHandle; + pHttpManager->iHttpHandle = (pHttpManager->iHttpHandle + 1) & 0x7fffffff; + return(iHttpHandle); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerAllocCmd + + \Description + Allocate a new command. A command represents a single HTTP transaction request. + + \Input *pHttpManager - reference pointer + + \Output + HttpManagerHttpCmdT * - new command + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static HttpManagerHttpCmdT *_HttpManagerAllocCmd(HttpManagerRefT *pHttpManager) +{ + HttpManagerHttpCmdT *pHttpCmd; + int32_t iHttpCmd; + + for (iHttpCmd = 0, pHttpCmd = NULL; iHttpCmd < HTTPMANAGER_MAXCMDS; iHttpCmd += 1) + { + if (pHttpManager->HttpCmds[iHttpCmd].iHttpHandle == 0) + { + pHttpCmd = &pHttpManager->HttpCmds[iHttpCmd]; + ds_memclr(pHttpCmd, sizeof(*pHttpCmd)); + pHttpCmd->pHttpManager = pHttpManager; + pHttpCmd->iHttpHandle = _HttpManagerAllocHandle(pHttpManager); + break; + } + } + return(pHttpCmd); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerAllocRef + + \Description + Allocate an http ref. An http ref represents a single ProtoHttp module. In + HttpManager, all ProtoHttp refs are created at startup; this function simply + finds an appropriate ref to use for the specified transaction request. + + \Input *pHttpManager - reference pointer + \Input *pHttpCmd - command + + \Output + HttpManagerHttpRefT * - new command + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static HttpManagerHttpRefT *_HttpManagerAllocRef(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) +{ + HttpManagerHttpRefT *pHttpRef, *pAllocRef = NULL; + HttpManagerHttpCmdT *pHttpPipeCmd; + int32_t iHttpCmd, iHttpRef; + char strKind[16], strHost[128]; + int32_t iPort, iSecure; + + // parse Url into component parts + ProtoHttpUrlParse(pHttpCmd->pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure); + + // first pass, we try to find a ref we can pipeline this request on + if (pHttpManager->bPipelining) + { + for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) + { + // get ref + pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; + + // iterate through queued transactions, if any + for (iHttpCmd = 0; iHttpCmd < pHttpRef->iTransactions; iHttpCmd += 1) + { + // ref http command we might be able to pipe against + pHttpPipeCmd = pHttpRef->HttpCmdQueue[iHttpCmd]; + if ((pHttpPipeCmd->uState == HTTPMANAGER_CMDSTATE_WAIT) && ((pHttpPipeCmd->bKeepAlive == TRUE) || (pHttpManager->bPipeWithoutKeepAlive == TRUE))) + { + if (_HttpManagerPipelineCheck(pHttpPipeCmd, pHttpCmd) && (pHttpRef->iTransactions < HTTPMANAGER_MAXREFQUEUE)) + { + if (iHttpCmd == (pHttpRef->iTransactions - 1)) + { + pAllocRef = pHttpRef; + pHttpCmd->bKeepAlive = TRUE; + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] queued ref %d count=%d pipe=1 url=%s\n", pHttpCmd->iHttpHandle, iHttpRef, pAllocRef->iTransactions, pHttpCmd->pUrl)); + break; + } + } + } + } + } + } + + // if not pipelined + if (pAllocRef == NULL) + { + uint8_t aKeepAliveState[HTTPMANAGER_MAXREFS]; + int32_t iBestMatchIdx, iBestMatchTick, iHttpRefTick, iCurTick; + int32_t iQueueDepth; + + // build list of keep-alive matches (whether a ref will try keep-alive with the specified Url) for all http refs + ds_memclr(aKeepAliveState, sizeof(aKeepAliveState)); + for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) + { + pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; + if ((pHttpRef->uHttpState != HTTPMANAGER_REFSTATE_NONE) && ProtoHttpCheckKeepAlive(pHttpRef->pProtoHttp, pHttpCmd->pUrl)) + { + aKeepAliveState[iHttpRef] = TRUE; + } + } + + // iterate through ref list in order from least heavily loaded (queue size) to most heavily loaded + for (iQueueDepth = 0; (pAllocRef == NULL) && (iQueueDepth < HTTPMANAGER_MAXREFQUEUE); iQueueDepth += 1) + { + // first, try to find a ref at this depth with keep-alive potential + for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) + { + pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; + if ((pHttpRef->iTransactions == iQueueDepth) && (aKeepAliveState[iHttpRef] == TRUE)) + { + // we have a match + pAllocRef = pHttpRef; + pHttpCmd->bKeepAlive = TRUE; + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] queued ref %d count=%d keep=1 url=%s\n", pHttpCmd->iHttpHandle, iHttpRef, pAllocRef->iTransactions, pHttpCmd->pUrl)); + break; + } + } + // did we find one? + if (pAllocRef != NULL) + { + break; + } + + // second pass, just look for the least recently used ref at this depth + for (iHttpRef = 0, iBestMatchIdx = -1, iBestMatchTick = -1, iCurTick = NetTick(); iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) + { + pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; + iHttpRefTick = NetTickDiff(iCurTick, pHttpRef->uLastTick); + if ((pHttpRef->iTransactions == iQueueDepth) && (iHttpRefTick > iBestMatchTick)) + { + // consider this for a match + iBestMatchTick = iHttpRefTick; + iBestMatchIdx = iHttpRef; + } + } + // if we found a ref, use it + if (iBestMatchIdx >= 0) + { + pAllocRef = &pHttpManager->HttpRefs[iBestMatchIdx]; + pHttpCmd->bKeepAlive = FALSE; + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] queued ref %d count=%d keep=0 url=%s\n", pHttpCmd->iHttpHandle, iBestMatchIdx, pAllocRef->iTransactions, pHttpCmd->pUrl)); + break; + } + } + } + + // if we allocated a ref + if (pAllocRef != NULL) + { + // update transaction stats + if (pAllocRef->iTransactions > 0) + { + pHttpManager->HttpManagerStats.uNumQueuedTransactions += 1; + if (pHttpManager->HttpManagerStats.uMaxQueuedTransactions < pHttpManager->HttpManagerStats.uNumQueuedTransactions) + { + pHttpManager->HttpManagerStats.uMaxQueuedTransactions = pHttpManager->HttpManagerStats.uNumQueuedTransactions; + } + } + if (pHttpCmd->bKeepAlive) + { + pHttpManager->HttpManagerStats.uNumKeepAliveTransactions += 1; + } + + // bind ref to cmd + pHttpCmd->pHttpRef = pAllocRef; + // add command to ref queue + pHttpCmd->pHttpRef->HttpCmdQueue[pHttpCmd->pHttpRef->iTransactions++] = pHttpCmd; + // update last access time + pHttpCmd->pHttpRef->uLastTick = pHttpCmd->uQueueTick; + } + return(pAllocRef); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerGetCmd + + \Description + Find http command referenced by specified handle. + + \Input *pHttpManager - reference pointer + \Input iHandle - transaction handle + + \Output + HttpManagerHttpCmdT * - requested command + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static HttpManagerHttpCmdT *_HttpManagerGetCmd(HttpManagerRefT *pHttpManager, int32_t iHandle) +{ + HttpManagerHttpCmdT *pHttpCmd; + int32_t iHttpCmd; + + // walk cmd list and find transaction with given handle + for (iHttpCmd = 0, pHttpCmd = NULL; iHttpCmd < HTTPMANAGER_MAXCMDS; iHttpCmd += 1) + { + if (pHttpManager->HttpCmds[iHttpCmd].iHttpHandle == iHandle) + { + pHttpCmd = &pHttpManager->HttpCmds[iHttpCmd]; + break; + } + } + return(pHttpCmd); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerUpdateTransactionStats + + \Description + Update stats based on a single transaction request. + + \Input *pHttpManager - reference pointer + \Input *pHttpCmd - transaction command + + \Version 05/25/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerUpdateTransactionStats(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) +{ + uint32_t uLatency; + + pHttpManager->HttpManagerStats.uNumActiveTransactions += 1; + if (pHttpManager->HttpManagerStats.uMaxActiveTransactions < pHttpManager->HttpManagerStats.uNumActiveTransactions) + { + pHttpManager->HttpManagerStats.uMaxActiveTransactions = pHttpManager->HttpManagerStats.uNumActiveTransactions; + } + pHttpCmd->uIssueTick = NetTick(); + uLatency = NetTickDiff(pHttpCmd->uIssueTick, pHttpCmd->uQueueTick); + if (pHttpManager->HttpManagerStats.uMaxQueueWaitLatency < uLatency) + { + pHttpManager->HttpManagerStats.uMaxQueueWaitLatency = uLatency; + } + pHttpManager->HttpManagerStats.uSumQueueWaitLatency += uLatency; + pHttpManager->HttpManagerStats.uNumTransactions += 1; +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerResizeInputBuffer + + \Description + After PROTOHTTP_MINBUFF response, increase the input buffer size for the given + ProtoHttp ref to allow the request buffer space to be reissued successfully. + + \Input *pHttpManager - reference pointer + \Input *pHttpRef - http ref to resize + + \Output + int32_t - zero=success, else failure + + \Version 02/15/20011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerResizeInputBuffer(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef) +{ + int32_t iBuffSize, iCurrSize, iReqdSize; + + // get current and required sizes + if ((iCurrSize = ProtoHttpStatus(pHttpRef->pProtoHttp, 'imax', NULL, 0)) <= 0) + { + // something wrong with HTTP ref... can happen if HTTP ref is tromped, so we bail out here to prevent infinite loop below + NetPrintf(("httpmanager: ProtoHttpStats(0x%08x, 'imax') returned %d\n", (uintptr_t)pHttpRef->pProtoHttp, iCurrSize)); + return(-1); + } + iReqdSize = ProtoHttpStatus(pHttpRef->pProtoHttp, 'iovr', NULL, 0); + + // calc new buffer size in increments of original buffer size + for (iBuffSize = iCurrSize; iBuffSize < iReqdSize; iBuffSize += iCurrSize) + ; + + // resize the input buffer + return(ProtoHttpControl(pHttpRef->pProtoHttp, 'ires', iBuffSize, 0, NULL)); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerRequestIssue + + \Description + Issue an HTTP request + + \Input *pHttpManager - reference pointer + \Input *pHttpCmd - transaction command + \Input *pData - data associated with command, or NULL if none + \Input iDataSize - size of data associated with command, or zero if none + + \Output + int32_t - ProtoHttpRequest() result + + \Version 05/25/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerRequestIssue(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd, const char *pData, int64_t iDataSize) +{ + int32_t iResult; + + // initiate the request, we only specify the custom header callback as the rest are implemented within this module + if ((iResult = ProtoHttpRequestCb2(pHttpCmd->pHttpRef->pProtoHttp, pHttpCmd->pUrl, pData, iDataSize, (ProtoHttpRequestTypeE)pHttpCmd->uRequestType, NULL, pHttpCmd->pCustomHeaderCb, NULL, pHttpCmd->pUserData)) < 0) + { + return(iResult); + } + + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] started ref %d url=%s\n", pHttpCmd->iHttpHandle, pHttpCmd->pHttpRef - pHttpManager->HttpRefs, pHttpCmd->pUrl)); + + // if pipelining is enabled, a ProtoHttpGet request with NULL Url is required to flush the previous request + if (pHttpManager->bPipelining && ((pHttpCmd->uRequestType == PROTOHTTP_REQUESTTYPE_HEAD) || (pHttpCmd->uRequestType == PROTOHTTP_REQUESTTYPE_GET))) + { + ProtoHttpGet(pHttpCmd->pHttpRef->pProtoHttp, NULL, 0); + } + + // update stats + _HttpManagerUpdateTransactionStats(pHttpManager, pHttpCmd); + + // update status + pHttpCmd->uState = HTTPMANAGER_CMDSTATE_ACTV; + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerRequestStart + + \Description + Set up any initial transaction-specific settings and issue a GET or HEAD + request. + + \Input *pHttpManager - reference pointer + \Input *pHttpCmd - transaction command + \Input *pData - pointer to URL data (optional, may be NULL) + \Input iDataSize - size of data being uploaded (see Notes) + + \Output + int32_t - ProtoHttpRequest() result + + \Version 05/21/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerRequestStart(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd, const char *pData, int64_t iDataSize) +{ + int32_t iResult; + + // make sure ref is idle + #if DIRTYCODE_LOGGING + if (pHttpCmd->pHttpRef->uHttpState != HTTPMANAGER_REFSTATE_IDLE) + { + NetPrintf(("httpmanager: error; get request issued on non-idle ref\n")); + } + #endif + + // explicitly disable keep-alive if global setting has it unavailable + if (pHttpManager->bKeepalive == FALSE) + { + ProtoHttpControl(pHttpCmd->pHttpRef->pProtoHttp, 'keep', 0, 0, NULL); + } + // set timeout if requested + if (pHttpCmd->iTimeout != 0) + { + ProtoHttpControl(pHttpCmd->pHttpRef->pProtoHttp, 'time', pHttpCmd->iTimeout, 0, NULL); + } + // apply append header + _HttpManagerApplyAppendHeader(pHttpManager, pHttpCmd); + + // update httpref status + pHttpCmd->pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_BUSY; + pHttpCmd->pHttpRef->iCurTransaction = 0; + + // initiate the request + if ((iResult = _HttpManagerRequestIssue(pHttpManager, pHttpCmd, pData, iDataSize)) < 0) + { + // if there wasn't enough input buffer space to store the request, realloc the buffer and try again + if ((iResult == PROTOHTTP_MINBUFF) && (_HttpManagerResizeInputBuffer(pHttpManager, pHttpCmd->pHttpRef) == 0)) + { + // resized input buffer; try to issue the request again + iResult = _HttpManagerRequestIssue(pHttpManager, pHttpCmd, pData, iDataSize); + } + + // fail on error + if (iResult < 0) + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: ProtoHttpRequest() returned %d on handle %d url=%s\n", iResult, pHttpCmd->iHttpHandle, pHttpCmd->pUrl)); + pHttpCmd->uState = HTTPMANAGER_CMDSTATE_FAIL; + } + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerRequestCheckCompletion + + \Description + Check for transaction completion, and update state and stats accordingly. + + \Input *pHttpManager - reference pointer + \Input *pHttpCmd - transaction command + + \Version 07/03/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerRequestCheckCompletion(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) +{ + HttpManagerHttpRefT *pHttpRef = pHttpCmd->pHttpRef; + int32_t iHeadSize, iStatus; + #if DIRTYCODE_LOGGING + int32_t iRsltCode = 0; + #endif + + // are we done yet? + if ((iStatus = ProtoHttpStatus(pHttpRef->pProtoHttp, 'done', NULL, 0)) == 0) + { + // not done yet + return; + } + else if (iStatus == 1) + { + int64_t iBodySize; + ProtoHttpStatus(pHttpRef->pProtoHttp, 'body', &iBodySize, sizeof(iBodySize)); + if (iBodySize != (signed)pHttpCmd->uBytesXfer) + { + // transaction is complete, but data has not all been received by caller + return; + } + + #if DIRTYCODE_LOGGING + iRsltCode = ProtoHttpStatus(pHttpRef->pProtoHttp, 'code', NULL, 0); + #endif + } + + // remember completion time and mark transaction as complete + pHttpCmd->uComplTick = NetTick(); + pHttpCmd->uState = HTTPMANAGER_CMDSTATE_DONE; + + // get size of header + if ((iHeadSize = ProtoHttpStatus(pHttpRef->pProtoHttp, 'head', NULL, 0)) < 0) + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: could not get header size for stat tracking (err=%d)\n", iHeadSize)); + + // downgrade our pipelining support $$todo$$ - this should be tracked per site + if (pHttpManager->bPipeWithoutKeepAlive) + { + /*$$todo: this can happen for other reasons (e.g. cert untrusted on a secure connection) + but we should only be downgrading pipe without keep-alive if we get a generic socket + failure or a premature Connection: Close */ + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager; disabling pipelining without keep-alive\n")); + pHttpManager->bPipeWithoutKeepAlive = FALSE; + } + + // reset the pipeline + _HttpManagerPipelineReset(pHttpManager, pHttpRef, 1); + + iHeadSize = 0; + } + + // update transaction stats + pHttpCmd->uBytesXfer += (unsigned)iHeadSize; + pHttpManager->HttpManagerStats.uTransactionBytes += pHttpCmd->uBytesXfer; + pHttpManager->HttpManagerStats.uTransactionTime += NetTickDiff(pHttpCmd->uComplTick, pHttpCmd->uQueueTick); + + // log transaction results + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] %s ref %d url=%s (%d): %qd bytes in %dms, %.2fk/sec)\n", pHttpCmd->iHttpHandle, + (iStatus == 1) ? "complete" : "failed ", pHttpRef - pHttpManager->HttpRefs, pHttpCmd->pUrl, iRsltCode, pHttpCmd->uBytesXfer, NetTickDiff(pHttpCmd->uComplTick, pHttpCmd->uIssueTick), + ((float)pHttpCmd->uBytesXfer * 1000.0f) / (float)(NetTickDiff(pHttpCmd->uComplTick, pHttpCmd->uIssueTick)*1024))); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerRequestGetCmdQueueIdx + + \Description + Get index of given command in httpref command queue. + + \Input *pHttpManager - reference pointer + \Input *pHttpRef - http ref + \Input *pHttpCmd - transaction command + + \Output + int32_t - index of command in command queue; -1 if not in queue + + \Version 07/03/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerRequestGetCmdQueueIdx(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, HttpManagerHttpCmdT *pHttpCmd) +{ + int32_t iHttpCmd; + for (iHttpCmd = 0; (iHttpCmd < HTTPMANAGER_MAXREFQUEUE) && (pHttpRef->HttpCmdQueue[iHttpCmd] != pHttpCmd); iHttpCmd += 1) + ; + if (iHttpCmd == HTTPMANAGER_MAXREFQUEUE) + { + NetPrintf(("httpmanager: could not find handle %d in ref %d command queue!\n", pHttpCmd->iHttpHandle, pHttpRef-pHttpManager->HttpRefs)); + return(-1); + } + return(iHttpCmd); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerRequestDelCmdFromQueue + + \Description + Remove command from command queue and update ref state accordingly + + \Input *pHttpManager - reference pointer + \Input *pHttpRef - httpref pointer + \Input *pHttpCmd - transaction command + + \Todo + Possible issues that need to be considered: + 1) out-of-order command deletion may not be handled correctly + + \Version 07/03/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerRequestDelCmdFromQueue(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, HttpManagerHttpCmdT *pHttpCmd) +{ + int32_t iHttpCmd; + + // update transaction count + pHttpRef->iTransactions -= 1; + + // update ref state + if ((pHttpRef->iTransactions == 0) || (pHttpRef->HttpCmdQueue[1]->uState == HTTPMANAGER_CMDSTATE_WAIT)) + { + pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_IDLE; + } + + // update stat tracking + if (pHttpRef->iTransactions > 0) + { + pHttpManager->HttpManagerStats.uNumQueuedTransactions -= 1; + } + if (pHttpManager->HttpManagerStats.uNumActiveTransactions > 0) + { + pHttpManager->HttpManagerStats.uNumActiveTransactions -= 1; + } + + // sanity check -- should never have an iCurTransaction != 0 here + if (pHttpRef->iCurTransaction != 0) + { + NetPrintf(("httpmanager: warning -- current transaction index is not zero!\n", pHttpRef->iCurTransaction)); + } + + // find ourselves in the HttpRef queue + if ((iHttpCmd = _HttpManagerRequestGetCmdQueueIdx(pHttpManager, pHttpRef, pHttpCmd)) < 0) + { + NetPrintf(("httpmanager: error -- could not find handle %d in ref %d queue\n", pHttpCmd->iHttpHandle, pHttpRef - pHttpManager->HttpRefs)); + return; + } + + // remove command from ref command queue + if (iHttpCmd < pHttpRef->iTransactions) + { + NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: contracting transaction queue for ref %d\n", pHttpCmd->pHttpRef - pHttpManager->HttpRefs)); + memmove(&pHttpRef->HttpCmdQueue[iHttpCmd], &pHttpRef->HttpCmdQueue[iHttpCmd+1], sizeof(pHttpRef->HttpCmdQueue[0]) * (pHttpRef->iTransactions - iHttpCmd)); + } + pHttpRef->HttpCmdQueue[pHttpRef->iTransactions] = NULL; +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerRequestPipe + + \Description + Pipeline the given request. + + \Input *pHttpManager - reference pointer + \Input *pHttpRef - http ref to use for transaction + \Input *pHttpCmd - transaction command + + \Output + int32_t - ProtoHttpRequest() result + + \Version 07/01/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerRequestPipe(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, HttpManagerHttpCmdT *pHttpCmd) +{ + const char *pUrl; + uint32_t uRequestType; + int32_t iResult; + + // set up Url and request type + if (pHttpCmd != NULL) + { + pUrl = pHttpCmd->pUrl; + uRequestType = pHttpCmd->uRequestType; + } + else + { + pUrl = NULL; + uRequestType = 0; + } + + // explicitly disable keep-alive if global setting has it unavailable + if (pHttpManager->bKeepalive == FALSE) + { + ProtoHttpControl(pHttpRef->pProtoHttp, 'keep', 0, 0, NULL); + } + + /* set current transaction index. we have to do this before ProtoHttpRequest() as that + will call into the custom header callback, which relies on the index being accurate. */ + if ((pHttpRef->uHttpState == HTTPMANAGER_REFSTATE_IDLE) || (pUrl == NULL)) + { + pHttpRef->iCurTransaction = 0; + } + else + { + pHttpRef->iCurTransaction += 1; + } + + // set timeout if requested (only for the first request, since this is a global setting) + if ((pHttpCmd != NULL) && (pHttpCmd->iTimeout != 0) && (pHttpRef->iCurTransaction == 0)) + { + ProtoHttpControl(pHttpCmd->pHttpRef->pProtoHttp, 'time', pHttpCmd->iTimeout, 0, NULL); + } + + // apply append header as long as this isn't a pipeline flush + if (pHttpCmd != NULL) + { + _HttpManagerApplyAppendHeader(pHttpManager, pHttpCmd); + } + + // initiate the request + if ((iResult = ProtoHttpRequest(pHttpRef->pProtoHttp, pUrl, NULL, 0, (ProtoHttpRequestTypeE)uRequestType)) >= 0) + { + if (pHttpCmd != NULL) + { + // update stats + _HttpManagerUpdateTransactionStats(pHttpManager, pHttpCmd); + + // if this is the first request on this ref, mark it as busy and set up the first transaction handler + if (pHttpRef->uHttpState == HTTPMANAGER_REFSTATE_IDLE) + { + pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_BUSY; + pHttpCmd->uState = HTTPMANAGER_CMDSTATE_ACTV; + } + else + { + pHttpCmd->uState = HTTPMANAGER_CMDSTATE_PIPE; + pHttpManager->HttpManagerStats.uNumPipelinedTransactions += 1; + } + } + /* if this was a flush command, reset current command, now that all of the headers + have been formatted, so received header callbacks will get correct ref */ + else + { + pHttpRef->iCurTransaction = 0; + } + } + else if (pHttpRef->iCurTransaction > 0) + { + pHttpRef->iCurTransaction -= 1; + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerRequestPipeUpdateQueue + + \Description + Update queue upon completion of a piped request. + + \Input *pHttpManager - reference pointer + \Input *pHttpRef - http ref to use for transaction + \Input *pHttpCmd - transaction command + + \Version 07/03/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerRequestPipeUpdateQueue(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, HttpManagerHttpCmdT *pHttpCmd) +{ + if ((pHttpRef->iTransactions > 0) && (pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]->uState == HTTPMANAGER_CMDSTATE_PIPE)) + { + // if the previous command completed successfully, set state of next piped transaction to ACTV + if (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_DONE) + { + // set next handle to active state + NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: handle %d set to active state\n", pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]->iHttpHandle)); + pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]->uState = HTTPMANAGER_CMDSTATE_ACTV; + // allow protohttp to get the next piped transaction + ProtoHttpControl(pHttpRef->pProtoHttp, 'pnxt', 0, 0,NULL); + } + else // previous command did not complete; so we have to reset the pipe + { + int32_t iHttpCmd; + /* if this transaction was in a sequence of pipelined transactions, we need + to reset the state of the following transactions so they will be reissued */ + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: resetting state of piped requests following deletion of handle %d in state %d\n", + pHttpCmd->iHttpHandle, pHttpCmd->uState)); + for (iHttpCmd = pHttpRef->iCurTransaction; iHttpCmd < pHttpRef->iTransactions; iHttpCmd += 1) + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: handle %d reset from state %d to state %d\n", pHttpRef->HttpCmdQueue[iHttpCmd]->iHttpHandle, + pHttpRef->HttpCmdQueue[iHttpCmd]->uState, HTTPMANAGER_CMDSTATE_WAIT)); + pHttpRef->HttpCmdQueue[iHttpCmd]->uState = HTTPMANAGER_CMDSTATE_WAIT; + } + // reset httpref to idle status + pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_IDLE; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerCmdReleaseRef + + \Description + Releases an HttpRef from the HttpCmd it is associated with. This allows + any queued or piped transactions waiting on this ref to proceed. + + \Input *pHttpManager - reference pointer + \Input *pHttpCmd - http command + + \Version 07/10/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerCmdReleaseRef(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) +{ + uint32_t uFreeLatency; + + // no ref allocated? + if (pHttpCmd->pHttpRef == NULL) + { + return; + } + + // remove command from command queue + _HttpManagerRequestDelCmdFromQueue(pHttpManager, pHttpCmd->pHttpRef, pHttpCmd); + + // update queue for piped operations + _HttpManagerRequestPipeUpdateQueue(pHttpManager, pHttpCmd->pHttpRef, pHttpCmd); + + // if our request is currently active, abort it + if (ProtoHttpStatus(pHttpCmd->pHttpRef->pProtoHttp, 'done', NULL, 0) == 0) + { + ProtoHttpAbort(pHttpCmd->pHttpRef->pProtoHttp); + } + + // update time spent waiting to be freed + if ((pHttpCmd->uComplTick != 0) && (pHttpCmd->uState != HTTPMANAGER_CMDSTATE_FAIL)) + { + uFreeLatency = NetTickDiff(NetTick(), pHttpCmd->uComplTick); + } + else + { + NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: warning -- no completion timestamp for handle %d being deleted (%dms since queued)\n", + pHttpCmd->iHttpHandle, NetTickDiff(NetTick(), pHttpCmd->uQueueTick))); + NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: url=%s\n", pHttpCmd->pUrl)); + NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: state=%d\n", pHttpCmd->uState)); + NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: data=%qd\n", pHttpCmd->uBytesXfer)); + uFreeLatency = 0; + } + pHttpManager->HttpManagerStats.uSumQueueFreeLatency += uFreeLatency; + if (pHttpManager->HttpManagerStats.uMaxQueueFreeLatency < uFreeLatency) + { + pHttpManager->HttpManagerStats.uMaxQueueFreeLatency = uFreeLatency; + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerPipeline + + \Description + Try to pipeline a set of requests. + + \Input *pHttpManager - reference pointer + \Input *pHttpRef - ref to execute commands on + \Input *pHttpCmd - first command in ref command queue + + \Output + int32_t - zero=no requests issued + + \Version 06/08/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerPipeline(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, HttpManagerHttpCmdT *pHttpCmd) +{ + HttpManagerHttpCmdT *pHttpCmd2; + int32_t iHttpCmd, iResult; + + // not pipelining? + if (!pHttpManager->bPipelining) + { + return(0); + } + + // make sure request type can be pipelined + if ((pHttpCmd->uRequestType != PROTOHTTP_REQUESTTYPE_GET) && (pHttpCmd->uRequestType != PROTOHTTP_REQUESTTYPE_HEAD)) + { + return(0); + } + + // if we require keep-alive to pipeline, check keep-alive status + if (!pHttpManager->bPipeWithoutKeepAlive && (ProtoHttpCheckKeepAlive(pHttpRef->pProtoHttp, pHttpCmd->pUrl) == 0)) + { + return(0); + } + + // execute the first transaction + if ((iResult = _HttpManagerRequestPipe(pHttpManager, pHttpRef, pHttpCmd)) >= 0) + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] started ref %d url=%s\n", pHttpCmd->iHttpHandle, pHttpRef - pHttpManager->HttpRefs, pHttpCmd->pUrl)); + + // iterate through transaction queue and issue any pending requests that are queued for pipelining + for (iHttpCmd = 1; (iHttpCmd < pHttpRef->iTransactions) && (iHttpCmd < pHttpManager->iMaxPipedUrls); iHttpCmd += 1) + { + // ref next transaction in queue + pHttpCmd2 = pHttpRef->HttpCmdQueue[iHttpCmd]; + + // check to see requests pipeline, and if so try and pipeline it + if (_HttpManagerPipelineCheck(pHttpCmd, pHttpCmd2) == 0) + { + break; + } + if ((iResult = _HttpManagerRequestPipe(pHttpManager, pHttpRef, pHttpCmd2)) < 0) + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: could not pipe handle %d; request will be retried on a new ref\n", pHttpCmd2->iHttpHandle)); + break; + } + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] piped ref %d url=%s\n", pHttpCmd2->iHttpHandle, pHttpRef - pHttpManager->HttpRefs, pHttpCmd2->pUrl)); + + // update trailing ref + pHttpCmd = pHttpCmd2; + } + + // issue queued request(s) + _HttpManagerRequestPipe(pHttpManager, pHttpRef, NULL); + } + else if ((iResult == PROTOHTTP_MINBUFF) && (_HttpManagerResizeInputBuffer(pHttpManager, pHttpRef) == 0)) + { + // resized input buffer, let _HttpManagerRequestCb() re-issue it for us without pipelining + return(0); + } + else + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: ProtoHttpRequest() returned %d on handle %d url=%s\n", iResult, pHttpCmd->iHttpHandle, pHttpCmd->pUrl)); + pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_BUSY; + pHttpRef->iCurTransaction = 0; + pHttpCmd->uState = HTTPMANAGER_CMDSTATE_FAIL; + } + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerRequestCb + + \Description + Handle an HTTP request. + + \Input *pHttpManager - reference pointer + \Input iHandle - transaction handle + \Input *pUrl - the URL that identifies the POST action. + \Input *pData - pointer to URL data (optional, may be NULL) + \Input iDataSize - size of data being uploaded + \Input eRequestType - http request type + \Input *pWriteCb - write callback (optional) + \Input *pCustomHeaderCb - custom header callback (optional) + \Input *pReceiveHeaderCb- receive header callback (optional) + \Input *pUserData - user data for callbacks (optional) + + \Output + int32_t - negative=error, else number of bytes written + + \Version 06/08/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerRequestCb(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, const char *pData, int64_t iDataSize, ProtoHttpRequestTypeE eRequestType, ProtoHttpWriteCbT *pWriteCb, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData) +{ + HttpManagerHttpCmdT *pHttpCmd; + + // get referenced transaction + if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) + { + NetPrintf(("httpmanager: unrecognized transaction %d in request\n", iHandle)); + return(-1); + } + // remember request timestamp + pHttpCmd->uQueueTick = NetTick(); + // store request info + pHttpCmd->uRequestType = (uint8_t)eRequestType; + pHttpCmd->pWriteCb = pWriteCb; + pHttpCmd->pCustomHeaderCb = pCustomHeaderCb; + pHttpCmd->pReceiveHeaderCb = pReceiveHeaderCb; + pHttpCmd->pUserData = pUserData; + + // make a copy of url? + if (pHttpManager->bCopyUrl) + { + int32_t iStrLen = (int32_t)strlen(pUrl); + pHttpCmd->pUrl = DirtyMemAlloc(iStrLen+1, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); + ds_strnzcpy((char *)pHttpCmd->pUrl, pUrl, iStrLen+1); + pHttpCmd->bCopiedUrl = TRUE; + } + else + { + pHttpCmd->pUrl = pUrl; + pHttpCmd->bCopiedUrl = FALSE; + } + + // allocate a ref for the transaction + if (_HttpManagerAllocRef(pHttpManager, pHttpCmd) == NULL) + { + // unable to allocate ref, but HttpManager will try again + return(0); + } + + /* if this is the only transaction queued and we are not pipelining, or the request type + is not a GET or a HEAD (the only request types we pipeline) */ + if (((pHttpCmd->pHttpRef->iTransactions == 1) && !pHttpManager->bPipelining) || + ((eRequestType != PROTOHTTP_REQUESTTYPE_GET) && (eRequestType != PROTOHTTP_REQUESTTYPE_HEAD))) + { + // issue the request + return(_HttpManagerRequestStart(pHttpManager, pHttpCmd, pData, iDataSize)); + } + else + { + pHttpCmd->uState = HTTPMANAGER_CMDSTATE_WAIT; + return(0); + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerIdle + + \Description + NetConn idle function to update the HttpManager module. + + \Input *pData - pointer to module state + \Input uTick - current tick count + + \Notes + This function is installed as a NetConn Idle function. NetConnIdle() + must be regularly polled for this function to be called. + + \Version 1.0 07/23/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerIdle(void *pData, uint32_t uTick) +{ + HttpManagerRefT *pHttpManager = (HttpManagerRefT *)pData; + if (pHttpManager->bAutoUpdate) + { + HttpManagerUpdate(pHttpManager); + } +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerCreateRef + + \Description + Create a new http ref + + \Input *pHttpManager - module state + \Input iHttpRef - index of ref to create + + \Output + int32_t - zero=success, negative=failure + + \Version 01/31/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerCreateRef(HttpManagerRefT *pHttpManager, int32_t iHttpRef) +{ + HttpManagerHttpRefT *pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; + + // create protohttp ref + if ((pHttpRef->pProtoHttp = ProtoHttpCreate(pHttpManager->iHttpBufSize)) == NULL) + { + NetPrintf(("httpmanager: could not allocate http ref %d\n", iHttpRef)); + return(-1); + } + // temporarily snuff debug output + ProtoHttpControl(pHttpRef->pProtoHttp, 'spam', 0, 0, NULL); + + // default keep-alive to enabled + ProtoHttpControl(pHttpRef->pProtoHttp, 'keep', 1, 0, NULL); + // set pipelining enable/disable + ProtoHttpControl(pHttpRef->pProtoHttp, 'pipe', pHttpManager->bPipelining, 0, NULL); + // set up callbacks + ProtoHttpCallback(pHttpRef->pProtoHttp, pHttpManager->pCustomHeaderCb ? _HttpManagerCustomHeaderCb : NULL, _HttpManagerReceiveHeaderCb, pHttpRef); + // set default debug level + ProtoHttpControl(pHttpRef->pProtoHttp, 'spam', pHttpManager->iVerbose, 0, NULL); + + // init to idle state + pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_IDLE; + // init last access time + pHttpRef->uLastTick = NetTick(); + // return success to caller + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerDestroyRef + + \Description + Destroy an allocated http ref + + \Input *pHttpManager - module state + \Input iHttpRef - index of ref to destroy + + \Version 01/31/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static void _HttpManagerDestroyRef(HttpManagerRefT *pHttpManager, int32_t iHttpRef) +{ + HttpManagerHttpRefT *pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; + HttpManagerHttpCmdT *pHttpCmd; + int32_t iTransaction; + + // early out if ref is null + if (pHttpRef->pProtoHttp == NULL) + { + return; + } + + // invalidate any commands that referenced this ref + for (iTransaction = 0; iTransaction < pHttpRef->iTransactions; iTransaction += 1) + { + pHttpCmd = pHttpRef->HttpCmdQueue[iTransaction]; + if (pHttpCmd->pHttpRef == pHttpRef) + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: destroying active ref %2d; handle %d is being terminated\n", iHttpRef, pHttpCmd->iHttpHandle)); + pHttpCmd->pHttpRef = NULL; + pHttpCmd->uState = HTTPMANAGER_CMDSTATE_FAIL; + } + } + + // destroy ref + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: destroying http ref %d\n", iHttpRef)); + ProtoHttpDestroy(pHttpRef->pProtoHttp); + // clear state + ds_memclr(pHttpRef, sizeof(*pHttpRef)); +} + +/*F********************************************************************************/ +/*! + \Function _HttpManagerSizeRefPool + + \Description + Create or destroy ProtoHttp refs based on current pool size and new pool size + + \Input *pHttpManager - module state + \Input iHttpNumRefs - number of desired protohttp refs + + \Output + int32_t - zero=success, negative=failure + + \Version 01/31/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _HttpManagerSizeRefPool(HttpManagerRefT *pHttpManager, int32_t iHttpNumRefs) +{ + int32_t iHttpRef; + + // validate request size + if (iHttpNumRefs > HTTPMANAGER_MAXREFS) + { + NetPrintf(("httpmanager: clamping 'pool' request to max %d refs\n", HTTPMANAGER_MAXREFS)); + iHttpNumRefs = HTTPMANAGER_MAXREFS; + } + else if (iHttpNumRefs < 1) + { + NetPrintf(("httpmanager: clamping 'pool' request to min of one ref\n")); + iHttpNumRefs = 1; + } + + // increase size of ref pool? + if (pHttpManager->iHttpNumRefs < iHttpNumRefs) + { + int32_t iResult; + DirtyMemGroupEnter(pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); + for (iHttpRef = pHttpManager->iHttpNumRefs, iResult = 0; iHttpRef < iHttpNumRefs; iHttpRef += 1) + { + if ((iResult = _HttpManagerCreateRef(pHttpManager, iHttpRef)) < 0) + { + break; + } + } + DirtyMemGroupLeave(); + if (iResult < 0) + { + // could not allocate http ref + return(-1); + } + } + else if (pHttpManager->iHttpNumRefs > iHttpNumRefs) // decrease size of ref pool + { + for (iHttpRef = pHttpManager->iHttpNumRefs - 1; iHttpRef >= iHttpNumRefs; iHttpRef -= 1) + { + _HttpManagerDestroyRef(pHttpManager, iHttpRef); + } + } + + // save new ref count, return success + pHttpManager->iHttpNumRefs = iHttpNumRefs; + return(0); +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function HttpManagerCreate + + \Description + Allocate module state and prepare for use + + \Input iHttpBufSize - length of receive buffer for each protohttp ref + \Input iHttpNumRefs - number of protohttp modules to allocate + + \Output + HttpManagerRefT * - pointer to module state, or NULL + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +HttpManagerRefT *HttpManagerCreate(int32_t iHttpBufSize, int32_t iHttpNumRefs) +{ + HttpManagerRefT *pHttpManager; + void *pMemGroupUserData; + int32_t iMemGroup; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // clamp refcount + if (iHttpNumRefs > HTTPMANAGER_MAXREFS) + { + NetPrintf(("httpmanager: %d requested refs exceeds max of %d; clamping\n", iHttpNumRefs, HTTPMANAGER_MAXREFS)); + iHttpNumRefs = HTTPMANAGER_MAXREFS; + } + + // allocate the module state + if ((pHttpManager = DirtyMemAlloc(sizeof(*pHttpManager), HTTPMGR_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("httpmanager: unable to allocate module state\n")); + return(NULL); + } + ds_memclr(pHttpManager, sizeof(*pHttpManager)); + + // save parms & set defaults + pHttpManager->iMemGroup = iMemGroup; + pHttpManager->pMemGroupUserData = pMemGroupUserData; + pHttpManager->iHttpBufSize = iHttpBufSize; + pHttpManager->iHttpHandle = 1; // zero is reserved as invalid +#if !defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_PS4) && !defined(DIRTYCODE_GDK) + pHttpManager->bPipelining = TRUE; +#endif + pHttpManager->bKeepalive = TRUE; + pHttpManager->bPipeWithoutKeepAlive = TRUE; + pHttpManager->iMaxPipedUrls = 4; + pHttpManager->bCopyUrl = TRUE; + pHttpManager->bAutoUpdate = TRUE; + pHttpManager->iVerbose = 1; + + // allocate protohttp refs + if (_HttpManagerSizeRefPool(pHttpManager, iHttpNumRefs) < 0) + { + NetPrintf(("httpmanager: could not allocate ref pool\n")); + HttpManagerDestroy(pHttpManager); + return(NULL); + } + + // add httpmanager task handle + NetConnIdleAdd(_HttpManagerIdle, pHttpManager); + + #if HTTPMANAGER_FINALDEBUG + _HttpManager_iStartTick = NetTick(); + #endif + + // return new module state + return(pHttpManager); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerCallback + + \Description + Set header callbacks. + + \Input *pHttpManager - module state + \Input *pCustomHeaderCb - pointer to custom send header callback + \Input *pReceiveHeaderCb- pointer to recv header callback + + \Notes + See ProtoHttpCallback documentation for a description of the callbacks + specified here. + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void HttpManagerCallback(HttpManagerRefT *pHttpManager, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb) +{ + HttpManagerHttpRefT *pHttpRef; + int32_t iHttpRef; + + // save callback info + pHttpManager->pCustomHeaderCb = pCustomHeaderCb; + pHttpManager->pReceiveHeaderCb = pReceiveHeaderCb; + + // update protohttp refs with callback info + for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) + { + pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; + ProtoHttpCallback(pHttpRef->pProtoHttp, pHttpManager->pCustomHeaderCb ? _HttpManagerCustomHeaderCb : NULL, _HttpManagerReceiveHeaderCb, pHttpRef); + } +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerDestroy + + \Description + Destroy the module and release its state + + \Input *pHttpManager - reference pointer + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void HttpManagerDestroy(HttpManagerRefT *pHttpManager) +{ + HttpManagerHttpCmdT *pHttpCmd; + int32_t iHttpCmd, iHttpRef; + + // del httpmanager task handle + NetConnIdleDel(_HttpManagerIdle, pHttpManager); + + // if append header is set, free it + if (pHttpManager->pAppendHdr != NULL) + { + DirtyMemFree(pHttpManager->pAppendHdr, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); + } + + // destroy protohttp modules + for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) + { + _HttpManagerDestroyRef(pHttpManager, iHttpRef); + } + + // clean up command list + for (iHttpCmd = 0; iHttpCmd < HTTPMANAGER_MAXCMDS; iHttpCmd += 1) + { + pHttpCmd = &pHttpManager->HttpCmds[iHttpCmd]; + if (pHttpCmd->iHttpHandle != 0) + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: warning; handle %d not cleaned up\n", pHttpCmd->iHttpHandle)); + HttpManagerFree(pHttpManager, pHttpCmd->iHttpHandle); + } + } + + // display stats + #if DIRTYCODE_LOGGING || HTTPMANAGER_FINALDEBUG + if (pHttpManager->iVerbose > 0) + { + _HttpManagerDisplayStats(pHttpManager); + } + #endif + + // destroy module state + DirtyMemFree(pHttpManager, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerAlloc + + \Description + Allocate a new transaction handle + + \Input *pHttpManager - reference pointer + + \Output + int32_t - negative=failure, else transaction handle + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t HttpManagerAlloc(HttpManagerRefT *pHttpManager) +{ + HttpManagerHttpCmdT *pHttpCmd; + + // get an unallocated ProtoHttp ref + if ((pHttpCmd = _HttpManagerAllocCmd(pHttpManager)) == NULL) + { + NetPrintf(("httpmanager: could not allocate new http transaction for HttpManagerGet() request\n")); + return(-1); + } + // return transaction handle to caller + NetPrintfVerbose((pHttpManager->iVerbose, 2, "httpmanager: allocated handle %d\n", pHttpCmd->iHttpHandle)); + return(pHttpCmd->iHttpHandle); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerFree + + \Description + Release a new transaction handle + + \Input *pHttpManager - reference pointer + \Input iHandle - transaction handle + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void HttpManagerFree(HttpManagerRefT *pHttpManager, int32_t iHandle) +{ + HttpManagerHttpCmdT *pHttpCmd; + + // get referenced transaction + if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) + { + NetPrintf(("httpmanager: unrecognized transaction %d in HttpManagerFree()\n", iHandle)); + return; + } + NetPrintfVerbose((pHttpManager->iVerbose, 2, "httpmanager: releasing handle %d\n", iHandle)); + + // release the ref + _HttpManagerCmdReleaseRef(pHttpManager, pHttpCmd); + // free url, if it was copied + if (pHttpCmd->bCopiedUrl == TRUE) + { + if (pHttpCmd->pUrl != NULL) + { + DirtyMemFree((void *)pHttpCmd->pUrl, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); + } + else + { + NetPrintf(("httpmanager: warning; copied url null before free\n")); + } + } + // free append buffer, if present + if (pHttpCmd->pAppendHdr != NULL) + { + DirtyMemFree(pHttpCmd->pAppendHdr, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); + } + + // clear ref + ds_memclr(pHttpCmd, sizeof(*pHttpCmd)); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerGet + + \Description + Initiate an HTTP transaction. Pass in a URL and the module starts a transfer + from the appropriate server. + + \Input *pHttpManager - module state + \Input iHandle - transaction handle + \Input *pUrl - the url to retrieve + \Input bHeadOnly - if TRUE only get header + + \Output + int32_t - negative=failure, else success + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t HttpManagerGet(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, uint32_t bHeadOnly) +{ + return(_HttpManagerRequestCb(pHttpManager, iHandle, pUrl, NULL, 0, bHeadOnly ? PROTOHTTP_REQUESTTYPE_HEAD : PROTOHTTP_REQUESTTYPE_GET, NULL, NULL, NULL, NULL)); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerRecv + + \Description + Return the actual url data. + + \Input *pHttpManager - reference pointer + \Input iHandle - transaction handle (returned by HttpManagerGet()) + \Input *pBuffer - buffer to store data in + \Input iBufMin - minimum number of bytes to return (returns zero if this number is not available) + \Input iBufMax - maximum number of bytes to return (buffer size) + + \Output + int32_t - negative=error, zero=no data available or bufmax <= 0, positive=number of bytes read + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t HttpManagerRecv(HttpManagerRefT *pHttpManager, int32_t iHandle, char *pBuffer, int32_t iBufMin, int32_t iBufMax) +{ + HttpManagerHttpCmdT *pHttpCmd; + + // get referenced transaction + if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) + { + NetPrintf(("httpmanager: unrecognized transaction %d in HttpManagerRecv()\n", iHandle)); + return(-1); + } + + // wait until active + if (pHttpCmd->uState < HTTPMANAGER_CMDSTATE_ACTV) + { + return(0); + } + + // early-out if queued execution of request failed + if (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_FAIL) + { + return(PROTOHTTP_RECVFAIL); + } + + // update the ref + ProtoHttpUpdate(pHttpCmd->pHttpRef->pProtoHttp); + + // execute the receive + if ((pHttpCmd->iResult = ProtoHttpRecv(pHttpCmd->pHttpRef->pProtoHttp, pBuffer, iBufMin, iBufMax)) > 0) + { + // track bytes received + pHttpCmd->uBytesXfer += (unsigned)pHttpCmd->iResult; + } + else if ((pHttpCmd->iResult == PROTOHTTP_MINBUFF) && (_HttpManagerResizeInputBuffer(pHttpManager, pHttpCmd->pHttpRef) == 0)) + { + // resized input buffer; swallow the error result and try again next go-around + pHttpCmd->iResult = 0; + } + + // check for transaction completion + if (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_ACTV) + { + _HttpManagerRequestCheckCompletion(pHttpManager, pHttpCmd); + } + + // update last access time + { + uint32_t uCurTick = NetTick(); + if (NetTickDiff(uCurTick, pHttpCmd->pHttpRef->uLastTick) > 1000) + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: warning -- ref %d last updated %dms previous (receive)\n", pHttpCmd->pHttpRef - pHttpManager->HttpRefs, NetTickDiff(uCurTick, pHttpCmd->pHttpRef->uLastTick))); + } + pHttpCmd->pHttpRef->uLastTick = uCurTick; + } + + // return result to caller + return(pHttpCmd->iResult); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerRecvAll + + \Description + Return all of the url data. + + \Input *pHttpManager - reference pointer + \Input iHandle - transaction handle (returned by HttpManagerGet()) + \Input *pBuffer - buffer to store data in + \Input iBufSize - size of buffer + + \Output + int32_t - PROTOHTTP_RECV*, or positive=bytes in response + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t HttpManagerRecvAll(HttpManagerRefT *pHttpManager, int32_t iHandle, char *pBuffer, int32_t iBufSize) +{ + HttpManagerHttpCmdT *pHttpCmd; + + // get referenced transaction + if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) + { + NetPrintf(("httpmanager: unrecognized transaction %d in HttpManagerRecv()\n", iHandle)); + return(-1); + } + + // if transaction is not active, return no data available + if (pHttpCmd->uState != HTTPMANAGER_CMDSTATE_ACTV) + { + return(0); + } + + // pass through to http ref + if ((pHttpCmd->iResult = ProtoHttpRecvAll(pHttpCmd->pHttpRef->pProtoHttp, pBuffer, iBufSize)) == PROTOHTTP_MINBUFF) + { + // increase the input buffer size, for next go-around + if (_HttpManagerResizeInputBuffer(pHttpManager, pHttpCmd->pHttpRef) == 0) + { + // swallow the error result and try again next go-around + pHttpCmd->iResult = 0; + } + } + // return result to caller + return(pHttpCmd->iResult); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerPost + + \Description + Initiate transfer of data to to the server via a HTTP POST command. + + \Input *pHttpManager - reference pointer + \Input iHandle - transaction handle + \Input *pUrl - the URL that identifies the POST action. + \Input *pData - pointer to URL data (optional, may be NULL) + \Input iDataSize - size of data being uploaded (see Notes) + \Input bDoPut - if TRUE, do a PUT instead of a POST + + \Output + int32_t - negative=failure, else number of data bytes sent + + \Notes + See ProtoHttpPost() documentation. + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t HttpManagerPost(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, const char *pData, int64_t iDataSize, uint32_t bDoPut) +{ + return(_HttpManagerRequestCb(pHttpManager, iHandle, pUrl, pData, iDataSize, bDoPut ? PROTOHTTP_REQUESTTYPE_PUT : PROTOHTTP_REQUESTTYPE_POST, NULL, NULL, NULL, NULL)); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerSend + + \Description + Send data during an ongoing Post transaction. + + \Input *pHttpManager - reference pointer + \Input iHandle - transaction handle + \Input *pData - pointer to data to send + \Input iDataSize - size of data being sent + + \Output + int32_t - negative=failure, else number of data bytes sent + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t HttpManagerSend(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pData, int32_t iDataSize) +{ + HttpManagerHttpCmdT *pHttpCmd; + + // get referenced transaction + if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) + { + NetPrintf(("httpmanager: unrecognized transaction %d in HttpManagerRecv()\n", iHandle)); + return(-1); + } + + // if transaction is not active, return no data available + if (pHttpCmd->uState != HTTPMANAGER_CMDSTATE_ACTV) + { + return(0); + } + + // pass through to http ref + return(ProtoHttpSend(pHttpCmd->pHttpRef->pProtoHttp, pData, iDataSize)); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerRequestCb2 + + \Description + Make an HTTP request. + + \Input *pHttpManager - reference pointer + \Input iHandle - transaction handle + \Input *pUrl - the URL that identifies the POST action. + \Input *pData - pointer to URL data (optional, may be NULL) + \Input iDataSize - size of data being uploaded + \Input eRequestType - http request type + \Input *pWriteCb - write callback (optional) + \Input *pCustomHeaderCb - custom header callback (optional) + \Input *pReceiveHeaderCb- receive header callback (optional) + \Input *pUserData - user data for write callback + + \Output + int32_t - negative=error, else number of bytes written + + \Version 09/11/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t HttpManagerRequestCb2(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, const char *pData, int64_t iDataSize, ProtoHttpRequestTypeE eRequestType, ProtoHttpWriteCbT *pWriteCb, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData) +{ + return(_HttpManagerRequestCb(pHttpManager, iHandle, pUrl, pData, iDataSize, eRequestType, pWriteCb, pCustomHeaderCb, pReceiveHeaderCb, pUserData)); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerSetBaseUrl + + \Description + Set base url that will be used for any relative url references. + + \Input *pHttpManager - reference pointer + \Input iHandle - handle to set base url for + \Input *pUrl - base url + + \Version 02/03/2010 (jbrookes) +*/ +/********************************************************************************F*/ +void HttpManagerSetBaseUrl(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl) +{ + HttpManagerHttpCmdT *pHttpCmd; + // get referenced transaction + if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) + { + NetPrintf(("httpmanager: unrecognized transaction %d in HttpManagerSetBaseUrl()\n", iHandle)); + return; + } + // pass through to http ref + if ((pHttpCmd->pHttpRef != NULL) && (pHttpCmd->pHttpRef->pProtoHttp != NULL)) + { + ProtoHttpSetBaseUrl(pHttpCmd->pHttpRef->pProtoHttp, pUrl); + } +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerControl + + \Description + HttpManager control function. Different selectors control different + behaviors. + + \Input *pHttpManager - reference pointer + \Input iHandle - transaction handle + \Input iSelect - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + If a handle is specified (iHandle > 0), HttpManagerControl() will forward + the call to ProtoHttpControl() for the appropriate ref. HttpManager control + selectors are as follows: + + \verbatim + SELECTOR DESCRIPTION + 'apnd' Header append feature (see ProtoHttp doc for details). If a + handle is specified the scope is command-specific, otherwise + it is global. A command-specific append header takes + precedence over a global append header. + 'auto' enable/disable auto-update (polling of HttpManagerUpdate by + NetConnIdle(); default enabled) + 'cbup' sets callback user data pointer (pValue=callback) + 'copy' sets if urls are copied internally or not (default=true) + 'maxp' sets max pipeline depth (default 4) + 'pipe' pipelining enable/disable + 'pwka' pipelining without keep-alive enable/disable + 'spam' manager-level debug verbosity + 'stcl' clear httpmanager stats. + \endverbatim + + \Version 05/20/2009 (jbrookes) +*/ +/*******************************************************************************F*/ +int32_t HttpManagerControl(HttpManagerRefT *pHttpManager, int32_t iHandle, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue) +{ + HttpManagerHttpCmdT *pHttpCmd = NULL; + int32_t iHttpRef; + + // if we are given a handle, resolve http cmd + if ((iHandle > 0) && ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL)) + { + NetPrintf(("httpmanager: HttpManagerControl(%d, '%C') failed; unrecognized handle\n", iHandle, iSelect)); + return(-1); + } + + // commands that work with or without a cmd + + // set append header + if (iSelect == 'apnd') + { + return(_HttpManagerSetAppendHeader(pHttpManager, pHttpCmd, pValue)); + } + + // command-specific selector + if (pHttpCmd != NULL) + { + // put httpmanager control selectors here + if (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_IDLE) + { + // set callback user data pointer + if (iSelect == 'cbup') + { + NetPrintfVerbose((pHttpManager->iVerbose, 2, "httpmanager: setting callback for handle %d to 0x%08x\n", iHandle, pValue)); + pHttpCmd->pCallbackRef = pValue; + return(0); + } + // set timeout + if (iSelect == 'time') + { + pHttpCmd->iTimeout = iValue; + return(0); + } + } + + // if it's not an httpmanager control selector, and we have a ProtoHttp ref, pass it through to ProtoHttp + if ((pHttpCmd->pHttpRef != NULL) && (pHttpCmd->pHttpRef->pProtoHttp != NULL)) + { + return(ProtoHttpControl(pHttpCmd->pHttpRef->pProtoHttp, iSelect, iValue, iValue2, pValue)); + } + } + else // global (not command-specific) selectors + { + // enable/disable auto-update (polling of HttpManagerUpdate by NetConnIdle()) + if (iSelect == 'auto') + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: auto-update %s\n", iValue ? "enabled" : "disabled")); + pHttpManager->bAutoUpdate = iValue ? TRUE : FALSE; + return(0); + } + // copy url setting enable/disable + if (iSelect == 'copy') + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: urlcopy %s\n", iValue ? "enabled" : "disabled")); + pHttpManager->bCopyUrl = iValue ? TRUE : FALSE; + return(0); + } + // set max pipeline depth + if (iSelect == 'maxp') + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: setting max pipeline depth to %d\n", iValue)); + pHttpManager->iMaxPipedUrls = (int8_t)iValue; + return(0); + } + // set pipelining setting + #if !defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_PS4) && !defined(DIRTYCODE_GDK) + if (iSelect == 'pipe') + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: pipelining %s\n", iValue ? "enabled" : "disabled")); + pHttpManager->bPipelining = iValue ? TRUE : FALSE; + // intentional fall-through to pass down to protohttp refs + } + #endif + if (iSelect == 'pool') + { + return(_HttpManagerSizeRefPool(pHttpManager, iValue)); + } + // set pipelining without keep-alive + if (iSelect == 'pwka') + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: pipelining without keep-alive %s\n", iValue ? "enabled" : "disabled")); + pHttpManager->bPipeWithoutKeepAlive = iValue ? TRUE : FALSE; + return(0); + } + // manager-level debug verbosity + if (iSelect == 'spam') + { + pHttpManager->iVerbose = iValue; + if (iValue > 0) + { + // set protohttp spam level one lower than httpmanager's + iValue -= 1; + } + // intentional fall-through to pass down to protohttp refs + } + // clear stats + if (iSelect == 'stcl') + { + _HttpManagerResetStats(pHttpManager); + return(0); + } + + // if we haven't gotten it by now, it's an option to be applied to all http refs + for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) + { + if (pHttpManager->HttpRefs[iHttpRef].pProtoHttp != NULL) + { + ProtoHttpControl(pHttpManager->HttpRefs[iHttpRef].pProtoHttp, iSelect, iValue, iValue2, pValue); + } + } + return(0); + } + // unhandled + NetPrintf(("httpmanager: HttpManagerControl(%d, '%C') unhandled\n", iHandle, iSelect)); + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerStatus + + \Description + Return status of current HTTP transfer. Status type depends on selector. + + \Input *pHttpManager - reference pointer + \Input iHandle - transaction handle + \Input iSelect - info selector (see Notes) + \Input *pBuffer - [in/out] input and/or storage for selector-specific output + \Input iBufSize - size of buffer + + \Output + int32_t - selector specific + + \Notes + If a handle is specified, HttpManagerStatus() will call ProtoHttpStatus() for + the appropriate ref. HttpManager status selectors are as follows: + + \verbatim + SELECTOR DESCRIPTION + 'busy' Returns number of busy refs + 'hndl' Get HttpManager handle from ProtoHttp ref (passed in pBuffer) + 'href' Get ProtoHttp ref from HttpManager handle (returned in pBuffer, if specified) + 'stat' If pBuffer is NULL, display httpmanager stats (debug only); else copy stats to output buffer + 'urls' Copies url from specified command into output buffer + \endverbatim + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t HttpManagerStatus(HttpManagerRefT *pHttpManager, int32_t iHandle, int32_t iSelect, void *pBuffer, int32_t iBufSize) +{ + // if the user wants status of a specific transaction? + if (iHandle > 0) + { + HttpManagerHttpCmdT *pHttpCmd; + + // get referenced transaction + if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) + { + NetPrintf(("httpmanager: HttpManagerStatus(%d, '%C') failed; unrecognized handle\n", iHandle, iSelect)); + return(-1); + } + + // get href from handle + if (iSelect == 'href') + { + if ((pHttpCmd->pHttpRef != NULL) && (pHttpCmd->pHttpRef->pProtoHttp != NULL)) + { + if ((pBuffer != NULL) && (iBufSize == sizeof(void *))) + { + ds_memcpy(pBuffer, &pHttpCmd->pHttpRef->pProtoHttp, sizeof(void *)); + } + return(0); + } + return(-1); + } + + // copy url to status buffer + if (iSelect == 'urls') + { + ds_strnzcpy(pBuffer, pHttpCmd->pUrl, iBufSize); + return(0); + } + + // only allow status of active transactions (otherwise this transaction does not own the ref) + if (pHttpCmd->uState < HTTPMANAGER_CMDSTATE_ACTV) + { + if ((iSelect == 'done') || (iSelect == 'data') || (iSelect == 'time')) + { + return(0); + } + if ((iSelect == 'body') || (iSelect == 'code') || (iSelect == 'htxt')) + { + return(-1); + } + NetPrintf(("httpmanager: HttpManagerStatus(%d, '%C') failed; handle not active or done (state=%d)\n", iHandle, iSelect, pHttpCmd->uState)); + return(-1); + } + + // if it's not an httpmanager status selector, and we have a ProtoHttp ref, pass it through to ProtoHttp + if ((pHttpCmd->pHttpRef != NULL) && (pHttpCmd->pHttpRef->pProtoHttp != NULL)) + { + return(ProtoHttpStatus(pHttpCmd->pHttpRef->pProtoHttp, iSelect, pBuffer, iBufSize)); + } + } + else + { + // return count of the number of busy refs + if (iSelect == 'busy') + { + HttpManagerHttpRefT *pHttpRef; + int32_t iHttpRef, iBusyRefs; + + for (iHttpRef = 0, iBusyRefs = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) + { + pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; + if (pHttpRef->uHttpState == HTTPMANAGER_REFSTATE_BUSY) + { + iBusyRefs += 1; + } + } + return(iBusyRefs); + } + // get handle from href + if (iSelect == 'hndl') + { + if ((pBuffer != NULL) && (iBufSize == sizeof(void *))) + { + HttpManagerHttpRefT *pHttpRef; + ProtoHttpRefT *pProtoHttpFind; + int32_t iHttpRef; + + // get httpref from buffer + ds_memcpy(&pProtoHttpFind, pBuffer, sizeof(void *)); + + // find it + for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) + { + pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; + if ((pHttpRef->pProtoHttp == pProtoHttpFind) && (pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction] != NULL)) + { + // return active handle + return(pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]->iHttpHandle); + } + } + } + // not found + return(-1); + } + // display/fetch stats + if (iSelect == 'stat') + { + if (pBuffer != NULL) + { + if (iBufSize == sizeof(pHttpManager->HttpManagerStats)) + { + ds_memcpy(pBuffer, &pHttpManager->HttpManagerStats, sizeof(pHttpManager->HttpManagerStats)); + return(0); + } + else + { + return(-1); + } + } + #if DIRTYCODE_LOGGING || HTTPMANAGER_FINALDEBUG + else + { + _HttpManagerDisplayStats(pHttpManager); + } + #endif + return(0); + } + } + // unhandled + NetPrintf(("httpmanager: HttpManagerStatus(%d, '%C') unhandled\n", iHandle, iSelect)); + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function HttpManagerUpdate + + \Description + Give time to module to do its thing (should be called periodically to + allow module to perform work) + + \Input *pHttpManager - reference pointer + + \Version 05/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void HttpManagerUpdate(HttpManagerRefT *pHttpManager) +{ + HttpManagerHttpRefT *pHttpRef; + HttpManagerHttpCmdT *pHttpCmd; + int32_t iHttpCmd, iHttpRef; + + // look for unassigned commands (these happen when we make a request, if we have filled up our available HttpRef command slots) + for (iHttpCmd = 0; iHttpCmd < HTTPMANAGER_MAXCMDS; iHttpCmd += 1) + { + pHttpCmd = &pHttpManager->HttpCmds[iHttpCmd]; + if ((pHttpCmd->pUrl != NULL) && (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_IDLE) && (pHttpCmd->pHttpRef == NULL)) + { + // allocate a ref for the transaction + if (_HttpManagerAllocRef(pHttpManager, pHttpCmd) == NULL) + { + break; + } + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: deferred alloc for handle %d\n", pHttpCmd->iHttpHandle)); + // promote to wait state + pHttpCmd->uState = HTTPMANAGER_CMDSTATE_WAIT; + } + } + + // update protohttp modules + for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) + { + uint32_t uCurTick; + pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; + + // check for idle ref that has one or more transactions queued + if ((pHttpRef->uHttpState == HTTPMANAGER_REFSTATE_IDLE) && (pHttpRef->iTransactions > 0)) + { + // reference first queued transaction + pHttpCmd = pHttpRef->HttpCmdQueue[0]; + + // try to pipeline the command; if we can't, start the request + if (_HttpManagerPipeline(pHttpManager, pHttpRef, pHttpCmd) == 0) + { + // execute first queued transaction + _HttpManagerRequestStart(pHttpManager, pHttpCmd, NULL, 0); + } + } + + // update refs + uCurTick = NetTick(); + ProtoHttpUpdate(pHttpRef->pProtoHttp); + + if (NetTickDiff(uCurTick, pHttpRef->uLastTick) > 1000) + { + NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: warning -- ref %d last updated %dms previous (update)\n", iHttpRef, NetTickDiff(uCurTick, pHttpRef->uLastTick))); + } + pHttpRef->uLastTick = uCurTick; + } + + // process any refs with write callbacks to update + for (iHttpCmd = 0; iHttpCmd < HTTPMANAGER_MAXCMDS; iHttpCmd += 1) + { + pHttpCmd = &pHttpManager->HttpCmds[iHttpCmd]; + if ((pHttpCmd->pWriteCb != NULL) && (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_ACTV)) + { + _HttpManagerWriteCbProcess(pHttpManager, pHttpCmd); + } + } + +} + diff --git a/src/thirdparty/dirtysdk/source/proto/protohttpserv.c b/src/thirdparty/dirtysdk/source/proto/protohttpserv.c new file mode 100644 index 00000000..cb8ee4ca --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protohttpserv.c @@ -0,0 +1,2071 @@ +/*H********************************************************************************/ +/*! + \File protohttpserv.c + + \Description + This module implements an HTTP server that can perform basic transactions + (get/put) with an HTTP client. It conforms to but does not fully implement + the 1.1 HTTP spec (http://www.w3.org/Protocols/rfc2616/rfc2616.html), and + allows for secure HTTP transactions as well as insecure transactions. + + \Copyright + Copyright (c) Electronic Arts 2013 + + \Version 1.0 12/11/2013 (jbrookes) Initial version, based on HttpServ tester2 module +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtyvers.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" // NetConnSleep +#include "DirtySDK/proto/protohttputil.h" +#include "DirtySDK/proto/protossl.h" + +#include "DirtySDK/proto/protohttpserv.h" + +/*** Defines **********************************************************************/ + +#define HTTPSERV_VERSION (0x0100) //!< httpserv revision number (maj.min); update this for major bug fixes or protocol additions/changes +#define HTTPSERV_DISCTIME (5*1000) //!< give five seconds after we disconnect before destroying network resources +#define HTTPSERV_CHUNKWAITTEST (0) //!< this tests a corner case in protohttp chunk receive where only one byte of the chunk trailer is available +#define HTTPSERV_IDLETIMEOUT_DEFAULT (5*60*1000) //!< default keep-alive timeout +#define HTTPSERV_REQUESTTIMEOUT_DEFAULT (30*1000) //!< default request timeout +#define HTTPSERV_BUFSIZE_DEFAULT (16000) //!< the most user payload we can send in a single SSL packet + +/*** Function Prototypes **********************************************************/ + +/*** Type Definitions *************************************************************/ + +//! httpserv response table +typedef struct ProtoHttpServRespT +{ + ProtoHttpResponseE eResponse; + const char *pResponseText; +}ProtoHttpServRespT; + +//! httpserv transaction state for a single transaction +typedef struct ProtoHttpServThreadT +{ + struct ProtoHttpServThreadT *pNext; + ProtoSSLRefT *pProtoSSL; + + struct sockaddr RequestAddr; + + ProtoHttpServRequestT RequestInfo; //!< user data associated with this thread via request callback (for handing request) + ProtoHttpServResponseT ResponseInfo; //!< user data associated with this thread via request callback (for replying) + + char *pBuffer; + int32_t iBufMax; + int32_t iBufLen; + int32_t iBufOff; + int32_t iChkLen; + int32_t iChkRcv; + int32_t iChkOvr; //!< chunk overhead + int32_t iIdleTimer; + int32_t iDisconnectTimer; + + int64_t iContentSent; + + char strUserAgent[256]; + + uint8_t bConnected; + uint8_t bReceivedHeader; + uint8_t bParsedHeader; + uint8_t bProcessedRequest; + uint8_t bReceivedBody; + uint8_t bFormattedHeader; + uint8_t bSentHeader; + uint8_t bSentBody; + uint8_t bConnectionClose; + uint8_t bConnectionKeepAlive; + uint8_t bDisconnecting; + uint8_t uHttpThreadId; + uint8_t bHttp1_0; + uint8_t bChunked; + uint8_t _pad[2]; +} ProtoHttpServThreadT; + +//! httpserv module state +struct ProtoHttpServRefT +{ + ProtoSSLRefT *pListenSSL; + + int32_t iMemGroup; + void *pMemGroupUserData; + + ProtoHttpServRequestCbT *pRequestCb; + ProtoHttpServReceiveCbT *pReceiveCb; + ProtoHttpServSendCbT *pSendCb; + ProtoHttpServHeaderCbT *pHeaderCb; + ProtoHttpServLogCbT *pLogCb; + void *pUserData; + + char *pServerCert; + int32_t iServerCertLen; + char *pServerKey; + int32_t iServerKeyLen; + int32_t iSecure; + uint16_t uSslVersion; + uint16_t uSslVersionMin; + uint32_t uSslCiphers; + int32_t iClientCertLvl; + int32_t iIdleTimeout; + int32_t iRequestTimeout; + uint16_t uDebugLevel; + + char strServerName[128]; + char strAlpn[128]; + + ProtoHttpServThreadT *pThreadListHead; + ProtoHttpServThreadT *pThreadListTail; + uint8_t uCurrThreadId; +}; + +/*** Variables ********************************************************************/ + +//! http request names +static const char *_ProtoHttpServ_strRequestNames[PROTOHTTP_NUMREQUESTTYPES] = +{ + "HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", NULL +}; + +//! http response name table +static const ProtoHttpServRespT _ProtoHttpServ_Responses[] = +{ + // 1xx - informational reponse + { PROTOHTTP_RESPONSE_CONTINUE, "Continue" }, //!< continue with request, generally ignored + { PROTOHTTP_RESPONSE_SWITCHPROTO, "Switching Protocols" }, //!< 101 - OK response to client switch protocol request + // 2xx - success response + { PROTOHTTP_RESPONSE_OK, "OK" }, //!< client's request was successfully received, understood, and accepted + { PROTOHTTP_RESPONSE_CREATED, "Created" } , //!< new resource has been created + { PROTOHTTP_RESPONSE_ACCEPTED, "Accepted" } , //!< request accepted but not complete + { PROTOHTTP_RESPONSE_NONAUTH, "Non-Authoritative Information" }, //!< non-authoritative info (ok) + { PROTOHTTP_RESPONSE_NOCONTENT, "No Content" } , //!< request fulfilled, no message body + { PROTOHTTP_RESPONSE_RESETCONTENT, "Reset Content" } , //!< request success, reset document view + { PROTOHTTP_RESPONSE_PARTIALCONTENT, "Partial Content" } , //!< server has fulfilled partial GET request + // 3xx - redirection response + { PROTOHTTP_RESPONSE_MULTIPLECHOICES, "Multiple Choices" }, //!< requested resource corresponds to a set of representations + { PROTOHTTP_RESPONSE_MOVEDPERMANENTLY, "Moved Permanently" }, //!< requested resource has been moved permanently to new URI + { PROTOHTTP_RESPONSE_FOUND, "Found" }, //!< requested resources has been moved temporarily to a new URI + { PROTOHTTP_RESPONSE_SEEOTHER, "See Other" }, //!< response can be found under a different URI + { PROTOHTTP_RESPONSE_NOTMODIFIED, "Not Modified" }, //!< response to conditional get when document has not changed + { PROTOHTTP_RESPONSE_USEPROXY, "Use Proxy" }, //!< requested resource must be accessed through proxy + { PROTOHTTP_RESPONSE_TEMPREDIRECT, "Temporary Redirect" }, //!< requested resource resides temporarily under a different URI + // 4xx - client error response + { PROTOHTTP_RESPONSE_BADREQUEST, "Bad Request" }, //!< request could not be understood by server due to malformed syntax + { PROTOHTTP_RESPONSE_UNAUTHORIZED, "Unauthorized" }, //!< request requires user authorization + { PROTOHTTP_RESPONSE_PAYMENTREQUIRED, "Payment Required" }, //!< reserved for future user + { PROTOHTTP_RESPONSE_FORBIDDEN, "Forbidden" }, //!< request understood, but server will not fulfill it + { PROTOHTTP_RESPONSE_NOTFOUND, "Not Found" }, //!< Request-URI not found + { PROTOHTTP_RESPONSE_METHODNOTALLOWED, "Method Not Allowed" }, //!< method specified in the Request-Line is not allowed + { PROTOHTTP_RESPONSE_NOTACCEPTABLE, "Not Acceptable" }, //!< resource incapable of generating content acceptable according to accept headers in request + { PROTOHTTP_RESPONSE_PROXYAUTHREQ, "Proxy Authentication Required" }, //!< client must first authenticate with proxy + { PROTOHTTP_RESPONSE_REQUESTTIMEOUT, "Request Timeout" }, //!< client did not produce response within server timeout + { PROTOHTTP_RESPONSE_CONFLICT, "Conflict" }, //!< request could not be completed due to a conflict with current state of the resource + { PROTOHTTP_RESPONSE_GONE, "Gone" }, //!< requested resource is no longer available and no forwarding address is known + { PROTOHTTP_RESPONSE_LENGTHREQUIRED, "Length Required" }, //!< a Content-Length header was not specified and is required + { PROTOHTTP_RESPONSE_PRECONFAILED, "Precondition Failed" }, //!< precondition given in request-header field(s) failed + { PROTOHTTP_RESPONSE_REQENTITYTOOLARGE, "Request Entity Too Large" }, //!< request entity is larger than the server is able or willing to process + { PROTOHTTP_RESPONSE_REQURITOOLONG, "Request-URI Too Long" }, //!< Request-URI is longer than the server is willing to interpret + { PROTOHTTP_RESPONSE_UNSUPPORTEDMEDIA, "Unsupported Media Type" }, //!< request entity is in unsupported format + { PROTOHTTP_RESPONSE_REQUESTRANGE, "Requested Range Not Satisfiable" },//!< invalid range in Range request header + { PROTOHTTP_RESPONSE_EXPECTATIONFAILED, "Expectation Failed" }, //!< expectation in Expect request-header field could not be met by server + // 5xx - server error response + { PROTOHTTP_RESPONSE_INTERNALSERVERERROR, "Internal Server Error" }, //!< an unexpected condition prevented the server from fulfilling the request + { PROTOHTTP_RESPONSE_NOTIMPLEMENTED, "Not Implemented" }, //!< the server does not support the functionality required to fulfill the request + { PROTOHTTP_RESPONSE_BADGATEWAY, "Bad Gateway" }, //!< invalid response from gateway server + { PROTOHTTP_RESPONSE_SERVICEUNAVAILABLE, "Service Unavailable" }, //!< unable to handle request due to a temporary overloading or maintainance + { PROTOHTTP_RESPONSE_GATEWAYTIMEOUT, "Gateway Timeout" }, //!< gateway or DNS server timeout + { PROTOHTTP_RESPONSE_HTTPVERSUNSUPPORTED, "HTTP Version Not Supported" }, //!< the server does not support the HTTP protocol version that was used in the request + { PROTOHTTP_RESPONSE_PENDING, "Unknown" }, //!< unknown response code +}; + +/*** Private Functions ************************************************************/ + +static int32_t _ProtoHttpServFormatHeader(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread); +static int32_t _ProtoHttpServUpdateSendHeader(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread); + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServLogPrintf + + \Description + Log printing for HttpServ server (compiles in all builds, unlike debug output). + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread log entry is for (may be NULL) + \Input *pFormat - printf format string + \Input ... - variable argument list + + \Version 08/03/2007 (jbrookes) Borrowed from DirtyCast +*/ +/********************************************************************************F*/ +static void _ProtoHttpServLogPrintf(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread, const char *pFormat, ...) +{ + char strText[2048]; + int32_t iOffset; + va_list Args; + + // format prefix to output buffer + if (pHttpThread != NULL) + { + iOffset = ds_snzprintf(strText, sizeof(strText), "protohttpserv: [%p][%02x] ", pHttpServ, pHttpThread->uHttpThreadId); + } + else + { + iOffset = ds_snzprintf(strText, sizeof(strText), "protohttpserv: [%p] ", pHttpServ); + } + + // format output + va_start(Args, pFormat); + ds_vsnprintf(strText+iOffset, sizeof(strText)-iOffset, pFormat, Args); + va_end(Args); + + // forward to callback, or print if no callback installed + if ((pHttpServ != NULL) && (pHttpServ->pLogCb != NULL)) + { + pHttpServ->pLogCb(strText, pHttpServ->pUserData); + } + else + { + NetPrintf(("%s", strText)); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServLogPrintfVerbose + + \Description + Log printing for HttpServ server (compiles in all builds, unlike debug output) + with varying verbosity levels + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread log entry is for (may be NULL) + \Input uCheckLevel - the level we are checking our internal uDebugLevel against + \Input *pFormat - printf format string + \Input ... - variable argument list + + \Version 05/02/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _ProtoHttpServLogPrintfVerbose(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread, uint16_t uCheckLevel, const char *pFormat, ...) +{ + char strText[2048]; + va_list Args; + + if (pHttpServ->uDebugLevel > uCheckLevel) + { + // format output + va_start(Args, pFormat); + ds_vsnprintf(strText, sizeof(strText), pFormat, Args); + va_end(Args); + + // log this information + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "%s", strText); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServGetThreadFromId + + \Description + Get HttpServ thread based on specified thread id + + \Input *pHttpServ - module state + \Input uHttpThreadId - thread id + + \Output + ProtoHttpServThread * - requested thread, or null if no match + + \Version 03/28/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static ProtoHttpServThreadT *_ProtoHttpServGetThreadFromId(ProtoHttpServRefT *pHttpServ, uint32_t uHttpThreadId) +{ + ProtoHttpServThreadT *pHttpThread; + // find thread with matching id + for (pHttpThread = pHttpServ->pThreadListHead; (pHttpThread != NULL) && (pHttpThread->uHttpThreadId != uHttpThreadId); pHttpThread = pHttpThread->pNext) + ; + // return thead to caller if found, else null + return(pHttpThread); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServGetResponseText + + \Description + Return response text for specified response code + + \Input eResponseCode - code to get text for + + \Output + const char * - response text + + \Version 09/13/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_ProtoHttpServGetResponseText(ProtoHttpResponseE eResponseCode) +{ + int32_t iResponse; + for (iResponse = 0; _ProtoHttpServ_Responses[iResponse].eResponse != PROTOHTTP_RESPONSE_PENDING; iResponse += 1) + { + if (_ProtoHttpServ_Responses[iResponse].eResponse == eResponseCode) + { + break; + } + } + return(_ProtoHttpServ_Responses[iResponse].pResponseText); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServSSLCreate + + \Description + Create ProtoSSL listen object + + \Input *pHttpServ - module state + \Input uPort - port to bind + \Input bReuseAddr - TRUE to set SO_REUSEADDR, else FALSE + \Input uFlags - flag field, use PROTOHTTPSERV_FLAG_* + + \Output + ProtoSSLRefT * - socket ref, or NULL + + \Version 10/11/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static ProtoSSLRefT *_ProtoHttpServSSLCreate(ProtoHttpServRefT *pHttpServ, uint16_t uPort, uint8_t bReuseAddr, uint32_t uFlags) +{ + struct sockaddr BindAddr; + ProtoSSLRefT *pProtoSSL; + int32_t iResult; + + // create the protossl ref + if ((pProtoSSL = ProtoSSLCreate()) == NULL) + { + return(NULL); + } + + // enable reuseaddr + if (bReuseAddr) + { + ProtoSSLControl(pProtoSSL, 'radr', 1, 0, NULL); + } + + // bind ssl to specified port + SockaddrInit(&BindAddr, AF_INET); + if (uFlags & PROTOHTTPSERV_FLAG_LOOPBACK) + { + SockaddrInSetAddr(&BindAddr, INADDR_LOOPBACK); + } + SockaddrInSetPort(&BindAddr, uPort); + if ((iResult = ProtoSSLBind(pProtoSSL, &BindAddr, sizeof(BindAddr))) != SOCKERR_NONE) + { + _ProtoHttpServLogPrintf(pHttpServ, NULL, "error %d binding to port\n", iResult); + ProtoSSLDestroy(pProtoSSL); + return(NULL); + } + + // return ref to caller + return(pProtoSSL); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServThreadAlloc + + \Description + Allocate and initialize an HttpThread object + + \Input *pHttpServ - module state + \Input *pProtoSSL - ProtoSSL ref for this thread + \Input *pRequestAddr - address connection request was made from + + \Output + ProtoHttpServThreadT * - newly allocated HttpThread object, or NULL on failure + + \Version 03/21/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static ProtoHttpServThreadT *_ProtoHttpServThreadAlloc(ProtoHttpServRefT *pHttpServ, ProtoSSLRefT *pProtoSSL, struct sockaddr *pRequestAddr) +{ + ProtoHttpServThreadT *pHttpThread; + // allocate and init thread memory + if ((pHttpThread = DirtyMemAlloc(sizeof(*pHttpThread), HTTPSERV_MEMID, pHttpServ->iMemGroup, pHttpServ->pMemGroupUserData)) == NULL) + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "unable to alloc http thread\n"); + return(NULL); + } + ds_memclr(pHttpThread, sizeof(*pHttpThread)); + // allocate http thread streaming buffer + if ((pHttpThread->pBuffer = DirtyMemAlloc(HTTPSERV_BUFSIZE_DEFAULT, HTTPSERV_MEMID, pHttpServ->iMemGroup, pHttpServ->pMemGroupUserData)) == NULL) + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "unable to alloc http thread buffer\n"); + return(NULL); + } + // init thread members + pHttpThread->pProtoSSL = pProtoSSL; + pHttpThread->uHttpThreadId = pHttpServ->uCurrThreadId++; + ds_memcpy_s(&pHttpThread->RequestAddr, sizeof(pHttpThread->RequestAddr), pRequestAddr, sizeof(*pRequestAddr)); + pHttpThread->iBufMax = HTTPSERV_BUFSIZE_DEFAULT; + // return ref to caller + return(pHttpThread); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServThreadFree + + \Description + Free an HttpThread object + + \Input *pHttpServ - module state + \Input *pHttpThread - HttpThread object to free + + \Version 03/21/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoHttpServThreadFree(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 2, "destroying http thread\n"); + if (pHttpThread->pProtoSSL != NULL) + { + ProtoSSLDestroy(pHttpThread->pProtoSSL); + } + if (pHttpThread->pBuffer != NULL) + { + DirtyMemFree(pHttpThread->pBuffer, HTTPSERV_MEMID, pHttpServ->iMemGroup, pHttpServ->pMemGroupUserData); + } + DirtyMemFree(pHttpThread, HTTPSERV_MEMID, pHttpServ->iMemGroup, pHttpServ->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServThreadReallocBuf + + \Description + Reallocate buffer for HttpThread object + + \Input *pHttpServ - module state + \Input *pHttpThread - HttpThread object to realloc buffer for + \Input iBufSize - size to reallocate to + + \Version 03/21/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServThreadReallocBuf(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread, int32_t iBufSize) +{ + char *pBuffer; + if ((pBuffer = DirtyMemAlloc(iBufSize, HTTPMGR_MEMID, pHttpServ->iMemGroup, pHttpServ->pMemGroupUserData)) == NULL) + { + return(-1); + } + ds_memcpy(pBuffer, pHttpThread->pBuffer+pHttpThread->iBufOff, pHttpThread->iBufLen-pHttpThread->iBufOff); + DirtyMemFree(pHttpThread->pBuffer, HTTPMGR_MEMID, pHttpServ->iMemGroup, pHttpServ->pMemGroupUserData); + pHttpThread->pBuffer = pBuffer; + pHttpThread->iBufLen -= pHttpThread->iBufOff; + pHttpThread->iBufOff = 0; + pHttpThread->iBufMax = iBufSize; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServDisconnect + + \Description + Disconnect from a client + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + \Input bGraceful - TRUE=graceful disconnect, FALSE=hard close + + \Version 10/11/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoHttpServDisconnect(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread, uint8_t bGraceful) +{ + if (bGraceful) + { + ProtoSSLDisconnect(pHttpThread->pProtoSSL); + pHttpThread->iDisconnectTimer = NetTick(); + } + else + { + pHttpThread->bConnected = FALSE; + pHttpThread->iDisconnectTimer = NetTick() - (HTTPSERV_DISCTIME+1); + } + pHttpThread->bDisconnecting = TRUE; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServResetState + + \Description + Reset state after a transaction is completed + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Version 10/26/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoHttpServResetState(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + ds_memclr(&pHttpThread->RequestInfo, sizeof(pHttpThread->RequestInfo)); + ds_memclr(&pHttpThread->ResponseInfo, sizeof(pHttpThread->ResponseInfo)); + + pHttpThread->bReceivedHeader = FALSE; + pHttpThread->bReceivedBody = FALSE; + pHttpThread->bSentBody = FALSE; + pHttpThread->iContentSent = 0; + pHttpThread->iBufLen = 0; + pHttpThread->iBufOff = 0; + pHttpThread->iChkLen = 0; + pHttpThread->iChkOvr = 0; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServParseHeader + + \Description + Parse a received header + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Output + int32_t - positive=success, negative=failure + + \Version 09/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServParseHeader(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + const char *pHdr, *pEnd; + char strName[128], strValue[4*1024], strMethod[8]; + int32_t iResult; + + // parse http first line for method, url, query, and version + pHdr = ProtoHttpUtilParseRequest(pHttpThread->RequestInfo.strHeader, strMethod, sizeof(strMethod), + pHttpThread->RequestInfo.strUrl, sizeof(pHttpThread->RequestInfo.strUrl), + pHttpThread->RequestInfo.strQuery, sizeof(pHttpThread->RequestInfo.strQuery), + &pHttpThread->RequestInfo.eRequestType, &pHttpThread->bHttp1_0); + // make sure it's a valid request + if (pHdr == NULL) + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "invalid request headers\n"); + pHttpThread->ResponseInfo.eResponseCode = PROTOHTTP_RESPONSE_BADREQUEST; + return(-1); + } + + // update address + pHttpThread->RequestInfo.uAddr = SockaddrInGetAddr(&pHttpThread->RequestAddr); + + // log request info + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "url=%s\n", pHttpThread->RequestInfo.strUrl); + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "query=%s\n", pHttpThread->RequestInfo.strQuery); + + // detect if it is a 1.0 response; if so we default to closing the connection + if (pHttpThread->bHttp1_0) + { + pHttpThread->bConnectionClose = TRUE; + } + + // update the header so we dont have the unnecessary information + memmove(pHttpThread->RequestInfo.strHeader, pHdr, pHdr - pHttpThread->RequestInfo.strHeader); + + // parse header + for (iResult = 0; iResult >= 0; ) + { + // get next header name and value from header buffer + if ((iResult = ProtoHttpGetNextHeader(NULL, pHdr, strName, sizeof(strName), strValue, sizeof(strValue), &pEnd)) == 0) + { + // process header + if (!ds_stricmp(strName, "connection")) + { + if (!ds_stricmp(strValue, "close")) + { + pHttpThread->bConnectionClose = TRUE; + } + else if (!ds_stricmp(strValue, "keep-alive")) + { + pHttpThread->bConnectionKeepAlive = TRUE; + } + } + else if (!ds_stricmp(strName, "content-length") && !pHttpThread->bChunked) + { + pHttpThread->RequestInfo.iContentLength = ds_strtoll(strValue, NULL, 10); + } + else if (!ds_stricmp(strName, "content-type")) + { + ds_strnzcpy(pHttpThread->RequestInfo.strContentType, strValue, sizeof(pHttpThread->RequestInfo.strContentType)); + } + else if (!ds_stricmp(strName, "transfer-encoding")) + { + if ((pHttpThread->bChunked = !ds_stricmp(strValue, "chunked")) == TRUE) + { + pHttpThread->RequestInfo.iContentLength = -1; + } + } + else if (!ds_stricmp(strName, "user-agent")) + { + ds_strnzcpy(pHttpThread->strUserAgent, strValue, sizeof(pHttpThread->strUserAgent)); + } + else if (!ds_stricmp(strName, "expect")) + { + /* + RFC-2616 8.2.3 Use of 100 (Continue) Status + Upon receiving a request which includes an Expect request-header + field with the 100-continue expectation, an origin server MUST + either response with 100 (Continue) status and continue to + read from the input stream, or respond with a final status code. + The origin server MUST NOT wait for the request body buffer before + the 100 (Continue) response. If it responds with a final status + code, it MAY close the transport connection or it MAY continue + to read and discard and discard the rest of the request. It + MUST NOT perform the requested method of it returns a final + status code + + Since we support large payloads we will just respond with 100 + and continue to process the body + */ + if (!ds_stricmp(strValue, "100-continue")) + { + /* Currently we don't have a way to send a response back in the + flow and continue to process. To do this I'm just going to + set the response code, format and send the header then reset + the state */ + pHttpThread->ResponseInfo.eResponseCode = PROTOHTTP_RESPONSE_CONTINUE; + _ProtoHttpServFormatHeader(pHttpServ, pHttpThread); + _ProtoHttpServUpdateSendHeader(pHttpServ, pHttpThread); + pHttpThread->bFormattedHeader = FALSE; + pHttpThread->bSentHeader = FALSE; + } + } + else + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 2, "unprocessed header '%s'='%s'\n", strName, strValue); + } + } + + // move to next header + pHdr = pEnd; + } + + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 0, "processing %s %s from %a:%d user-agent=%s\n", + _ProtoHttpServ_strRequestNames[pHttpThread->RequestInfo.eRequestType], pHttpThread->RequestInfo.strUrl, + SockaddrInGetAddr(&pHttpThread->RequestAddr), SockaddrInGetPort(&pHttpThread->RequestAddr), + pHttpThread->strUserAgent); + + if ((iResult = pHttpServ->pHeaderCb(&pHttpThread->RequestInfo, &pHttpThread->ResponseInfo, pHttpServ->pUserData)) < 0) + { + return(-1); + } + + pHttpThread->bParsedHeader = TRUE; + pHttpThread->bProcessedRequest = FALSE; + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServFormatHeader + + \Description + Format response header + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Output + int32_t - positive=success, negative=failure + + \Version 09/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServFormatHeader(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + struct tm TmTime; + char strTime[64]; + int32_t iBufLen, iBufMax = pHttpThread->iBufMax; + + // format a basic response header + iBufLen = ds_snzprintf(pHttpThread->pBuffer, iBufMax, "HTTP/1.1 %d %s\r\n", pHttpThread->ResponseInfo.eResponseCode, + _ProtoHttpServGetResponseText(pHttpThread->ResponseInfo.eResponseCode)); + + // date header; specify current GMT time + ds_secstotime(&TmTime, ds_timeinsecs()); + iBufLen += ds_snzprintf(pHttpThread->pBuffer+iBufLen, iBufMax-iBufLen, "Date: %s\r\n", + ds_timetostr(&TmTime, TIMETOSTRING_CONVERSION_RFC_0822, FALSE, strTime, sizeof(strTime))); + + // redirection? + if (pHttpThread->ResponseInfo.strLocation[0] != '\0') + { + iBufLen += ds_snzprintf(pHttpThread->pBuffer+iBufLen, iBufMax-iBufLen, "Location: %s\r\n", pHttpThread->ResponseInfo.strLocation); + } + if (pHttpThread->ResponseInfo.iContentLength > 0) + { + // set content type + if (*pHttpThread->ResponseInfo.strContentType != '\0') + { + iBufLen += ds_snzprintf(pHttpThread->pBuffer+iBufLen, iBufMax-iBufLen, "Content-type: %s\r\n", pHttpThread->ResponseInfo.strContentType); + } + + // set content length or transfer-encoding + if (pHttpThread->ResponseInfo.iChunkLength == 0) + { + iBufLen += ds_snzprintf(pHttpThread->pBuffer+iBufLen, iBufMax-iBufLen, "Content-length: %qd\r\n", pHttpThread->ResponseInfo.iContentLength); + } + else + { + iBufLen += ds_snzprintf(pHttpThread->pBuffer+iBufLen, iBufMax-iBufLen, "Transfer-Encoding: Chunked\r\n"); + } + } + else + { + iBufLen += ds_snzprintf(pHttpThread->pBuffer+iBufLen, iBufMax-iBufLen, "Content-length: 0\r\n"); + } + iBufLen += ds_snzprintf(pHttpThread->pBuffer+iBufLen, iBufMax-iBufLen, "Server: %s/ProtoHttpServ %d.%d/DS %d.%d.%d.%d.%d (" DIRTYCODE_PLATNAME ")\r\n", + pHttpServ->strServerName, (HTTPSERV_VERSION>>8)&0xff, HTTPSERV_VERSION&0xff, DIRTYSDK_VERSION_YEAR, DIRTYSDK_VERSION_SEASON, + DIRTYSDK_VERSION_MAJOR, DIRTYSDK_VERSION_MINOR, DIRTYSDK_VERSION_PATCH); + if (pHttpThread->bConnectionClose) + { + iBufLen += ds_snzprintf(pHttpThread->pBuffer+iBufLen, iBufMax-iBufLen, "Connection: Close\r\n"); + } + + iBufLen += ds_strsubzcat(pHttpThread->pBuffer+iBufLen, iBufMax-iBufLen, pHttpThread->ResponseInfo.strHeader, pHttpThread->ResponseInfo.iHeaderLen); + + // format footer, update thread data + pHttpThread->iBufLen = ds_snzprintf(pHttpThread->pBuffer+iBufLen, iBufMax-iBufLen, "\r\n") + iBufLen; + pHttpThread->iBufOff = 0; + pHttpThread->bFormattedHeader = TRUE; + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServProcessRequest + + \Description + Process request + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Output + uint8_t - true=request complete, false=request still pending +*/ +/********************************************************************************F*/ +static uint8_t _ProtoHttpServProcessRequest(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + int32_t iResult; + + // make user callback + if ((iResult = pHttpServ->pRequestCb(&pHttpThread->RequestInfo, &pHttpThread->ResponseInfo, pHttpServ->pUserData)) < 0) + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "error %d processing request\n", iResult); + pHttpThread->bConnectionClose = TRUE; + return(TRUE); + } + // if the request is not complete return + else if (iResult != 0) + { + return(FALSE); + } + + // if we are not transferring anymore data and the user doesn't want to keep the connection open, we can close the connection + if (pHttpThread->ResponseInfo.iContentLength == 0 && !pHttpThread->bConnectionKeepAlive) + { + pHttpThread->bConnectionClose = TRUE; + } + + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServUpdateListen + + \Description + Update HttpServ while listening for a new connection + + \Input *pHttpServ - module state + + \Output + int32_t - positive=success, zero=in progress, negative=failure + + \Version 09/11/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServUpdateListen(ProtoHttpServRefT *pHttpServ) +{ + ProtoSSLRefT *pProtoSSL; + ProtoHttpServThreadT *pHttpThread; + struct sockaddr RequestAddr; + int32_t iAddrLen = sizeof(RequestAddr); + + // don't try Accept() if poll hint says nothing to read +#if 0 //$$ TODO - fix this to work on linux + if (ProtoSSLStat(pHttpServ->pListenSSL, 'read', NULL, 0) == 0) + { + return(0); + } +#endif + // accept incoming connection + SockaddrInit(&RequestAddr, AF_INET); + if ((pProtoSSL = ProtoSSLAccept(pHttpServ->pListenSSL, pHttpServ->iSecure, &RequestAddr, &iAddrLen)) == NULL) + { + return(0); + } + // allocate an http thread + if ((pHttpThread = _ProtoHttpServThreadAlloc(pHttpServ, pProtoSSL, &RequestAddr)) == NULL) + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "unable to alloc http thread\n"); + ProtoSSLDestroy(pProtoSSL); + return(0); + } + + // is the thread list empty? + if (pHttpServ->pThreadListHead == NULL) + { + pHttpServ->pThreadListHead = pHttpThread; // insert newly allocated httpthread as the first entry in the list + } + else + { + pHttpServ->pThreadListTail->pNext = pHttpThread; // enqueue newly allocated httpthread after the current tail entry in the list + } + pHttpServ->pThreadListTail = pHttpThread; // mark newly allocated httpthread as then new tail entry in the list + + // log connecting request + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "connecting to %a:%d (%s)\n", SockaddrInGetAddr(&pHttpThread->RequestAddr), + SockaddrInGetPort(&pHttpThread->RequestAddr), pHttpServ->iSecure ? "secure" : "insecure"); + + // set server certificate and private key, if specified + if (pHttpServ->iSecure) + { + ProtoSSLControl(pHttpThread->pProtoSSL, 'scrt', pHttpServ->iServerCertLen, 0, pHttpServ->pServerCert); + ProtoSSLControl(pHttpThread->pProtoSSL, 'skey', pHttpServ->iServerKeyLen, 0, pHttpServ->pServerKey); + ProtoSSLControl(pHttpThread->pProtoSSL, 'ccrt', pHttpServ->iClientCertLvl, 0, NULL); + if (pHttpServ->uSslVersionMin != 0) + { + ProtoSSLControl(pHttpThread->pProtoSSL, 'vmin', pHttpServ->uSslVersionMin, 0, NULL); + } + if (pHttpServ->uSslVersion != 0) + { + ProtoSSLControl(pHttpThread->pProtoSSL, 'vers', pHttpServ->uSslVersion, 0, NULL); + } + ProtoSSLControl(pHttpThread->pProtoSSL, 'ciph', pHttpServ->uSslCiphers, 0, NULL); + if (pHttpServ->strAlpn[0] != '\0') + { + ProtoSSLControl(pHttpThread->pProtoSSL, 'alpn', 0, 0, pHttpServ->strAlpn); + } + } + pHttpThread->bConnected = FALSE; + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServSocketClose + + \Description + Handle socket close on connect/send/recv + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + \Input iResult - socket operation result + \Input *pOperation - operation type (conn, recv, send) + + \Version 10/31/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoHttpServSocketClose(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread, int32_t iResult, const char *pOperation) +{ + ProtoSSLAlertDescT AlertDesc; + int32_t iAlert; + + if ((iAlert = ProtoSSLStat(pHttpThread->pProtoSSL, 'alrt', &AlertDesc, sizeof(AlertDesc))) != 0) + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 0, "%s ssl alert %s (sslerr=%d, hResult=0x%08x); closing connection\n", (iAlert == 1) ? "recv" : "sent", + AlertDesc.pAlertDesc, ProtoSSLStat(pHttpThread->pProtoSSL, 'fail', NULL, 0), ProtoSSLStat(pHttpThread->pProtoSSL, 'hres', NULL, 0)); + } + else + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 0, "%s returned %d (sockerr=%d, sslerr=%d, hResult=0x%08x); closing connection\n", pOperation, iResult, + ProtoSSLStat(pHttpThread->pProtoSSL, 'serr', NULL, 0), ProtoSSLStat(pHttpThread->pProtoSSL, 'fail', NULL, 0), + ProtoSSLStat(pHttpThread->pProtoSSL, 'hres', NULL, 0)); + } + + _ProtoHttpServDisconnect(pHttpServ, pHttpThread, 0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServUpdateConnect + + \Description + Update HttpServ while establishing a new connection + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Output + int32_t - positive=success, zero=in progress, negative=failure + + \Version 09/11/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServUpdateConnect(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + int32_t iStat; + + if ((iStat = ProtoSSLStat(pHttpThread->pProtoSSL, 'stat', NULL, 0)) > 0) + { + if (pHttpServ->iSecure) + { + char strTlsVersion[16], strCipherSuite[32], strResumed[] = " (resumed)"; + ProtoSSLStat(pHttpThread->pProtoSSL, 'vers', strTlsVersion, sizeof(strTlsVersion)); + ProtoSSLStat(pHttpThread->pProtoSSL, 'ciph', strCipherSuite, sizeof(strCipherSuite)); + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "connected to %a:%d using %s and %s%s\n", + SockaddrInGetAddr(&pHttpThread->RequestAddr), SockaddrInGetPort(&pHttpThread->RequestAddr), + strTlsVersion, strCipherSuite, ProtoSSLStat(pHttpThread->pProtoSSL, 'resu', NULL, 0) ? strResumed : ""); + } + else + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "connected to %a:%d (insecure)\n", + SockaddrInGetAddr(&pHttpThread->RequestAddr), SockaddrInGetPort(&pHttpThread->RequestAddr)); + } + pHttpThread->bConnected = TRUE; + pHttpThread->iIdleTimer = NetTick(); + return(1); + } + else if (iStat < 0) + { + _ProtoHttpServSocketClose(pHttpServ, pHttpThread, iStat, "conn"); + return(-1); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServUpdateRecvHeader + + \Description + Update HttpServ while receiving header + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Output + int32_t - positive=success, zero=in progress, negative=failure + + \Version 09/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServUpdateRecvHeader(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + int32_t iResult; + + if ((iResult = ProtoSSLRecv(pHttpThread->pProtoSSL, pHttpThread->pBuffer+pHttpThread->iBufLen, pHttpThread->iBufMax-pHttpThread->iBufLen)) > 0) + { + char *pHdrEnd; + int32_t iHdrLen; + + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "recv %d bytes\n", iResult); + + // update received data size + pHttpThread->iBufLen += iResult; + + // reset idle timeout + pHttpThread->iIdleTimer = NetTick(); + + // check for header termination + if ((pHdrEnd = strstr(pHttpThread->pBuffer, "\r\n\r\n")) != NULL) + { + // we want to keep the final header EOL + pHdrEnd += 2; + + // copy header to buffer + iHdrLen = (int32_t)(pHdrEnd - pHttpThread->pBuffer); + ds_strsubzcpy(pHttpThread->RequestInfo.strHeader, sizeof(pHttpThread->RequestInfo.strHeader), pHttpThread->pBuffer, iHdrLen); + if (pHttpServ->uDebugLevel > 1) + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "received %d byte header\n", iHdrLen); + NetPrintWrap(pHttpThread->RequestInfo.strHeader, 132); + } + + // skip header/body seperator + pHdrEnd += 2; + iHdrLen += 2; + + // remove header from buffer + memmove(pHttpThread->pBuffer, pHdrEnd, pHttpThread->iBufLen - iHdrLen); + pHttpThread->iBufLen -= iHdrLen; + pHttpThread->pBuffer[pHttpThread->iBufLen] = '\0'; + + // we've received the header + pHttpThread->bReceivedHeader = TRUE; + + // reset for next state + pHttpThread->bConnectionClose = FALSE; + pHttpThread->bParsedHeader = FALSE; + pHttpThread->bFormattedHeader = FALSE; + pHttpThread->bSentHeader = FALSE; + } + + // return whether we have parsed the header or not + iResult = pHttpThread->bReceivedHeader ? 1 : 0; + } + else if (iResult < 0) + { + _ProtoHttpServSocketClose(pHttpServ, pHttpThread, iResult, "recv"); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServProcessData + + \Description + Process received data + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Output + int32_t - positive=success, zero=in progress, negative=failure + + \Version 03/20/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServProcessData(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + int32_t iDataSize; + if ((iDataSize = pHttpServ->pReceiveCb(&pHttpThread->RequestInfo, pHttpThread->pBuffer+pHttpThread->iBufOff, pHttpThread->iBufLen-pHttpThread->iBufOff, pHttpServ->pUserData)) > 0) + { + pHttpThread->RequestInfo.iContentRecv += iDataSize; + pHttpThread->iBufOff += iDataSize; + + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "wrote %d bytes to file (%qd total)\n", iDataSize, pHttpThread->RequestInfo.iContentRecv); + + // if we've written all of the data reset buffer pointers + if (pHttpThread->iBufOff == pHttpThread->iBufLen) + { + pHttpThread->iBufOff = 0; + pHttpThread->iBufLen = 0; + } + } + return(iDataSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServCompactBuffer + + \Description + Compact the buffer + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Version 12/06/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoHttpServCompactBuffer(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + // compact the buffer + if (pHttpThread->iBufOff < pHttpThread->iBufLen) + { + memmove(pHttpThread->pBuffer, pHttpThread->pBuffer+pHttpThread->iBufOff, pHttpThread->iBufLen-pHttpThread->iBufOff); + } + pHttpThread->iBufLen -= pHttpThread->iBufOff; + pHttpThread->iBufOff = 0; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServProcessChunkData + + \Description + Process received chunk data + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Output + int32_t - positive=success, zero=in progress, negative=failure + + \Version 03/20/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServProcessChunkData(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + int32_t iChkLeft, iDataSize; + + // do we need a new chunk header? + if (pHttpThread->iChkLen == 0) + { + char *s = pHttpThread->pBuffer+pHttpThread->iBufOff, *s2; + char *t = pHttpThread->pBuffer+pHttpThread->iBufLen-1; + + // make sure we have a complete chunk header + for (s2=s; (s2 < t) && ((s2[0] != '\r') || (s2[1] != '\n')); s2++) + ; + if (s2 == t) + { + // if we're out of room, compact buffer + if (pHttpThread->iBufLen == pHttpThread->iBufMax) + { + _ProtoHttpServCompactBuffer(pHttpServ, pHttpThread); + } + return(0); + } + + // read chunk header; handle end of input + if ((pHttpThread->iChkLen = (int32_t)strtol(s, NULL, 16)) == 0) + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "processing end chunk\n"); + pHttpThread->iBufOff = 0; + pHttpThread->iBufLen = 0; + pHttpThread->RequestInfo.iContentLength = pHttpThread->RequestInfo.iContentRecv; + return(0); + } + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "processing %d byte chunk\n", pHttpThread->iChkLen); + + // reset chunk read counter + pHttpThread->iChkRcv = 0; + + // consume chunk header and \r\n trailer + pHttpThread->iBufOff += s2-s+2; + } + + // if chunk size plus trailer is bigger than our buffer, realloc our buffer larger to accomodate, so we can buffer an entire chunk in our buffer + if ((pHttpThread->iChkLen+2) > pHttpThread->iBufMax) + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 0, "increasing buffer to handle %d byte chunk\n", pHttpThread->iChkLen); + if (_ProtoHttpServThreadReallocBuf(pHttpServ, pHttpThread, pHttpThread->iChkLen+2) < 0) // chunk plus trailer + { + return(-1); + } + } + + // get how much of the chunk we have left to read + iChkLeft = pHttpThread->iChkLen - pHttpThread->iChkRcv; + + // get data size avaialble; make sure we have all of the remaining data and chunk trailer available + if ((iDataSize = pHttpThread->iBufLen-pHttpThread->iBufOff) < (iChkLeft+2)) + { + // compact buffer to make room for more data + _ProtoHttpServCompactBuffer(pHttpServ, pHttpThread); + return(0); + } + // clamp data size to whatever we have left to read + iDataSize = iChkLeft; + + // write the data + if ((iDataSize = pHttpServ->pReceiveCb(&pHttpThread->RequestInfo, pHttpThread->pBuffer+pHttpThread->iBufOff, iDataSize, pHttpServ->pUserData)) > 0) + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "wrote %d bytes to file (%qd total)\n", iDataSize, pHttpThread->RequestInfo.iContentRecv+iDataSize); + + pHttpThread->RequestInfo.iContentRecv += iDataSize; + pHttpThread->iBufOff += iDataSize; + pHttpThread->iChkRcv += iDataSize; + + // did we consume all of the chunk data? + if (pHttpThread->iChkRcv == pHttpThread->iChkLen) + { + // consume chunk trailer, reset chunk length + pHttpThread->iBufOff += 2; + pHttpThread->iChkLen = 0; + } + + // if we've written all of the data reset buffer pointers + if (pHttpThread->iBufOff == pHttpThread->iBufLen) + { + pHttpThread->iBufOff = 0; + pHttpThread->iBufLen = 0; + } + } + return(iDataSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServUpdateRecvBody + + \Description + Update HttpServ while receiving body + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Output + int32_t - positive=success, zero=in progress, negative=failure + + \Version 09/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServUpdateRecvBody(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + int32_t iDataSize, iResult; + + while ((pHttpThread->RequestInfo.iContentRecv < pHttpThread->RequestInfo.iContentLength) || (pHttpThread->RequestInfo.iContentLength == -1)) + { + // need new data? + while (pHttpThread->iBufLen < (signed)pHttpThread->iBufMax) + { + iResult = ProtoSSLRecv(pHttpThread->pProtoSSL, pHttpThread->pBuffer + pHttpThread->iBufLen, pHttpThread->iBufMax - pHttpThread->iBufLen); + if (iResult > 0) + { + pHttpThread->iBufLen += iResult; + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "recv %d bytes\n", iResult); + // reset idle timeout + pHttpThread->iIdleTimer = NetTick(); + } + else if ((iResult == SOCKERR_CLOSED) && (pHttpThread->RequestInfo.iContentLength == -1)) + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "keep-alive connection closed by remote host\n"); + pHttpThread->RequestInfo.iContentLength = pHttpThread->RequestInfo.iContentRecv + pHttpThread->iBufLen; + break; + } + else if (iResult < 0) + { + _ProtoHttpServSocketClose(pHttpServ, pHttpThread, iResult, "recv"); + return(-2); + } + else + { + break; + } + } + + // if we have data, write it out + if (pHttpThread->iBufLen > 0) + { + if (!pHttpThread->bChunked) + { + // write out as much data as we can + iDataSize = _ProtoHttpServProcessData(pHttpServ, pHttpThread); + } + else + { + // write out as many chunks as we have + while((iDataSize = _ProtoHttpServProcessChunkData(pHttpServ, pHttpThread)) > 0) + ; + } + + if (iDataSize < 0) + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "error %d writing to file\n", iDataSize); + pHttpThread->ResponseInfo.eResponseCode = PROTOHTTP_RESPONSE_INTERNALSERVERERROR; + return(-1); + } + } + else + { + break; + } + } + + // check for upload completion + if (pHttpThread->RequestInfo.iContentRecv == pHttpThread->RequestInfo.iContentLength) + { + // done receiving response + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "received upload of %qd bytes\n", pHttpThread->RequestInfo.iContentRecv); + + // trigger callback to indicate transaction is complete + pHttpServ->pReceiveCb(&pHttpThread->RequestInfo, NULL, 0, pHttpServ->pUserData); + + // done with response + pHttpThread->bReceivedBody = TRUE; + pHttpThread->ResponseInfo.eResponseCode = PROTOHTTP_RESPONSE_CREATED; + return(1); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServUpdateSendHeader + + \Description + Update HttpServ while sending header + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Output + int32_t - positive=success, zero=in progress, negative=failure + + \Version 09/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServUpdateSendHeader(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + int32_t iResult; + + // send data to requester + if ((iResult = ProtoSSLSend(pHttpThread->pProtoSSL, pHttpThread->pBuffer + pHttpThread->iBufOff, pHttpThread->iBufLen - pHttpThread->iBufOff)) > 0) + { + pHttpThread->iBufOff += iResult; + + // reset idle timeout + pHttpThread->iIdleTimer = NetTick(); + + // are we done? + if (pHttpThread->iBufOff == pHttpThread->iBufLen) + { + if (pHttpServ->uDebugLevel > 1) + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "sent %d byte header\n", pHttpThread->iBufOff); + NetPrintWrap(pHttpThread->pBuffer, 132); + } + if (pHttpThread->ResponseInfo.iContentLength == 0) + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 0, "sent %d response (no body)\n", pHttpThread->ResponseInfo.eResponseCode); + } + pHttpThread->bSentHeader = TRUE; + pHttpThread->iBufOff = 0; + pHttpThread->iBufLen = 0; + iResult = 1; + } + else + { + iResult = 0; + } + } + else if (iResult < 0) + { + _ProtoHttpServSocketClose(pHttpServ, pHttpThread, iResult, "send"); + pHttpThread->iBufOff = pHttpThread->iBufLen; + return(-1); + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServUpdateSendBody + + \Description + Update HttpServ while sending response + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Output + int32_t - positive=success, zero=in progress, negative=failure + + \Version 09/12/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpServUpdateSendBody(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + int32_t iResult, iReadOff, iReadMax; + uint8_t bError; + + // reserve space for chunk header + if (pHttpThread->ResponseInfo.iChunkLength != 0) + { + iReadOff = 10; + iReadMax = pHttpThread->ResponseInfo.iChunkLength; + } + else + { + iReadOff = 0; + iReadMax = pHttpThread->iBufMax; + } + + // while there is content to be sent + for (bError = FALSE; pHttpThread->iContentSent < pHttpThread->ResponseInfo.iContentLength; ) + { + // need new data? + if (pHttpThread->iBufOff == pHttpThread->iBufLen) + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 2, "sent=%qd len=%qd\n", pHttpThread->iContentSent, pHttpThread->ResponseInfo.iContentLength); + // reset chunk overhead tracker + pHttpThread->iChkOvr = 0; + + // read the data + if ((pHttpThread->iBufLen = pHttpServ->pSendCb(&pHttpThread->ResponseInfo, pHttpThread->pBuffer+iReadOff, iReadMax-iReadOff, pHttpServ->pUserData)) > 0) + { + pHttpThread->ResponseInfo.iContentRead += pHttpThread->iBufLen; + pHttpThread->iBufOff = 0; + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 2, "read %d bytes from file (%qd total)\n", pHttpThread->iBufLen, pHttpThread->ResponseInfo.iContentRead); + + // if chunked, fill in chunk header based on amount of data read + if (pHttpThread->ResponseInfo.iChunkLength != 0) + { + char strHeader[11]; + + // format chunk header before data + ds_snzprintf(strHeader, sizeof(strHeader), "%08x\r\n", pHttpThread->iBufLen); + ds_memcpy(pHttpThread->pBuffer, strHeader, iReadOff); + pHttpThread->iBufLen += iReadOff; + pHttpThread->iChkOvr += iReadOff; + + // add chunk trailer after data + pHttpThread->pBuffer[pHttpThread->iBufLen+0] = '\r'; + pHttpThread->pBuffer[pHttpThread->iBufLen+1] = '\n'; + pHttpThread->iBufLen += 2; + pHttpThread->iChkOvr += 2; + + // if this is the last of the data, add terminating chunk + if (pHttpThread->ResponseInfo.iContentRead == pHttpThread->ResponseInfo.iContentLength) + { + ds_snzprintf(strHeader, sizeof(strHeader), "%08x\r\n", 0); + ds_memcpy(pHttpThread->pBuffer+pHttpThread->iBufLen, strHeader, iReadOff); + pHttpThread->iBufLen += iReadOff; + pHttpThread->iChkOvr += iReadOff; + + pHttpThread->pBuffer[pHttpThread->iBufLen+0] = '\r'; + pHttpThread->pBuffer[pHttpThread->iBufLen+1] = '\n'; + pHttpThread->iBufLen += 2; + pHttpThread->iChkOvr += 2; + } + } + } + else + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "error %d reading from file\n", pHttpThread->iBufLen); + _ProtoHttpServDisconnect(pHttpServ, pHttpThread, FALSE); + bError = TRUE; + break; + } + } + + // do we have buffered data to send? + if (pHttpThread->iBufOff < pHttpThread->iBufLen) + { + int32_t iSendLen = pHttpThread->iBufLen - pHttpThread->iBufOff; + #if HTTPSERV_CHUNKWAITTEST + if (iSendLen > 1) iSendLen -= 1; + #endif + iResult = ProtoSSLSend(pHttpThread->pProtoSSL, pHttpThread->pBuffer + pHttpThread->iBufOff, iSendLen); + if (iResult > 0) + { + pHttpThread->iBufOff += iResult; + pHttpThread->iContentSent += iResult; + if (pHttpThread->iChkOvr > 0) + { + pHttpThread->iContentSent -= pHttpThread->iChkOvr; + pHttpThread->iChkOvr = 0; + } + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 2, "sent %d bytes (%qd total)\n", iResult, pHttpThread->iContentSent); + // reset idle timeout + pHttpThread->iIdleTimer = NetTick(); + #if HTTPSERV_CHUNKWAITTEST + NetConnSleep(1000); + #endif + } + else if (iResult < 0) + { + _ProtoHttpServSocketClose(pHttpServ, pHttpThread, iResult, "send"); + bError = TRUE; + break; + } + else + { + break; + } + } + } + + // check for send completion + if ((pHttpThread->iContentSent == pHttpThread->ResponseInfo.iContentLength) || (bError == TRUE)) + { + // done sending response + if (pHttpThread->ResponseInfo.iContentLength != 0) + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 0, "sent %d response (%qd bytes)\n", pHttpThread->ResponseInfo.eResponseCode, pHttpThread->iContentSent); + } + + // trigger callback to indicate transaction is complete + pHttpServ->pSendCb(&pHttpThread->ResponseInfo, NULL, 0, pHttpServ->pUserData); + + // done with response + pHttpThread->bSentBody = TRUE; + return(1); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpServUpdateThread + + \Description + Update an HttpServ thread + + \Input *pHttpServ - module state + \Input *pHttpThread - http thread (connection-specific) + + \Version 09/11/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoHttpServUpdateThread(ProtoHttpServRefT *pHttpServ, ProtoHttpServThreadT *pHttpThread) +{ + uint32_t uCurTick = NetTick(); + int32_t iTimeout; + + // if no thread, or we are disconnected, nothing to update + if (pHttpThread->pProtoSSL == NULL) + { + return; + } + + // update ProtoSSL object + ProtoSSLUpdate(pHttpThread->pProtoSSL); + + // poll for connection completion + if ((pHttpThread->bConnected == FALSE) && (_ProtoHttpServUpdateConnect(pHttpServ, pHttpThread) <= 0)) + { + return; + } + + // get timeout based on whether we are processing a request or not + iTimeout = pHttpThread->bReceivedHeader ? pHttpServ->iRequestTimeout : pHttpServ->iIdleTimeout; + + // see if we should timeout the connection + if (NetTickDiff(uCurTick, pHttpThread->iIdleTimer) > iTimeout) + { + if (pHttpThread->bReceivedHeader == TRUE) + { + _ProtoHttpServLogPrintf(pHttpServ, pHttpThread, "closing connection (request timeout)\n"); + } + else + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 1, "closing connection (idle timeout)\n"); + } + _ProtoHttpServDisconnect(pHttpServ, pHttpThread, FALSE); + return; + } + + // receive the header + if ((pHttpThread->bReceivedHeader == FALSE) && (_ProtoHttpServUpdateRecvHeader(pHttpServ, pHttpThread) <= 0)) + { + return; + } + + // if disconnecting, early out (this is intentionally after recv) + if (pHttpThread->bDisconnecting) + { + return; + } + + // parse headers + if ((pHttpThread->bParsedHeader == FALSE) && (_ProtoHttpServParseHeader(pHttpServ, pHttpThread) < 0)) + { + // If parsing header failed do not continue to do any processing, + // send a response back and close the connection + pHttpThread->bParsedHeader = TRUE; + pHttpThread->bReceivedBody = TRUE; + pHttpThread->bProcessedRequest = TRUE; + pHttpThread->bConnectionClose = TRUE; + return; + } + + // if data needed to download + if (pHttpThread->RequestInfo.iContentLength > 0 || pHttpThread->RequestInfo.iContentLength == -1) + { + if (pHttpThread->bReceivedBody == FALSE) + { + int32_t iResult; + if ((iResult = _ProtoHttpServUpdateRecvBody(pHttpServ, pHttpThread)) < 0) + { + // If receiving/processing the body failed do not continue to receive, + // send a response back and close the connection + pHttpThread->bReceivedBody = TRUE; + pHttpThread->bProcessedRequest = TRUE; + pHttpThread->bConnectionClose = TRUE; + return; + } + else if (iResult == 0) + { + return; + } + } + } + + // process the request + if (pHttpThread->bProcessedRequest == FALSE) + { + if (_ProtoHttpServProcessRequest(pHttpServ, pHttpThread) == FALSE) + { + return; + } + // mark that we've processed the request + pHttpThread->bProcessedRequest = TRUE; + } + + // format response header + if ((pHttpThread->bFormattedHeader == FALSE) && (_ProtoHttpServFormatHeader(pHttpServ, pHttpThread) < 0)) + { + return; + } + + // send response header + if ((pHttpThread->bSentHeader == FALSE) && (_ProtoHttpServUpdateSendHeader(pHttpServ, pHttpThread) <= 0)) + { + return; + } + + // process body data, if any + if (pHttpThread->ResponseInfo.iContentLength > 0 || pHttpThread->ResponseInfo.iChunkLength > 0) + { + if ((pHttpThread->bSentBody == FALSE) && (_ProtoHttpServUpdateSendBody(pHttpServ, pHttpThread) <= 0)) + { + return; + } + } + + // make sure we've sent all of the data + if ((pHttpThread->pProtoSSL != NULL) && (ProtoSSLStat(pHttpThread->pProtoSSL, 'send', NULL, 0) > 0)) + { + return; + } + + // if no keep-alive initiate disconnection + if (pHttpThread->bConnectionClose == TRUE) + { + _ProtoHttpServLogPrintfVerbose(pHttpServ, pHttpThread, 0, "closing connection\n"); + _ProtoHttpServDisconnect(pHttpServ, pHttpThread, TRUE); + } + + // reset transaction state + _ProtoHttpServResetState(pHttpServ, pHttpThread); +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ProtoHttpServCreate + + \Description + Create an HttpServ instance + + \Input iPort - port to listen on + \Input iSecure - TRUE if secure, else FALSE + \Input *pName - name of server, used in Server: header + + \Output + ProtoHttpServRefT * - pointer to state, or null on failure + + \Version 12/11/2013 (jbrookes) +*/ +/********************************************************************************F*/ +ProtoHttpServRefT *ProtoHttpServCreate(int32_t iPort, int32_t iSecure, const char *pName) +{ + return(ProtoHttpServCreate2(iPort, iSecure, pName, 0)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpServCreate2 + + \Description + Create an HttpServ instance + + \Input iPort - port to listen on + \Input iSecure - TRUE if secure, else FALSE + \Input *pName - name of server, used in Server: header + \Input uFlags - flag field, use PROTOHTTPSERV_FLAG_* + + \Output + ProtoHttpServRefT * - pointer to state, or null on failure + + \Version 02/22/2016 (amakoukji) +*/ +/********************************************************************************F*/ +ProtoHttpServRefT *ProtoHttpServCreate2(int32_t iPort, int32_t iSecure, const char *pName, uint32_t uFlags) +{ + ProtoHttpServRefT *pHttpServ; + int32_t iMemGroup; + void *pMemGroupUserData; + + // query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate module state + if ((pHttpServ = DirtyMemAlloc(sizeof(*pHttpServ), HTTPSERV_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + _ProtoHttpServLogPrintf(NULL, NULL, "unable to allocate module state\n"); + return(NULL); + } + ds_memclr(pHttpServ, sizeof(*pHttpServ)); + // save memgroup (will be used in ProtoHttpDestroy) + pHttpServ->iMemGroup = iMemGroup; + pHttpServ->pMemGroupUserData = pMemGroupUserData; + pHttpServ->uDebugLevel = 1; + + // create a protossl listen ref + if ((pHttpServ->pListenSSL = _ProtoHttpServSSLCreate(pHttpServ, iPort, TRUE, uFlags)) == NULL) + { + _ProtoHttpServLogPrintf(pHttpServ, NULL, "could not create ssl ref on port %d\n", iPort); + ProtoHttpServDestroy(pHttpServ); + return(NULL); + } + // start listening + if (ProtoHttpServListen(pHttpServ, 2) != SOCKERR_NONE) + { + ProtoHttpServDestroy(pHttpServ); + return(NULL); + } + + // save settings + pHttpServ->iSecure = iSecure; + ds_strnzcpy(pHttpServ->strServerName, pName, sizeof(pHttpServ->strServerName)); + + // initialize default values + pHttpServ->uCurrThreadId = 1; + pHttpServ->uSslCiphers = PROTOSSL_CIPHER_ALL; + pHttpServ->iIdleTimeout = HTTPSERV_IDLETIMEOUT_DEFAULT; + pHttpServ->iRequestTimeout = HTTPSERV_REQUESTTIMEOUT_DEFAULT; + + // log success + _ProtoHttpServLogPrintfVerbose(pHttpServ, NULL, 0, "listening on port %d (%s)\n", iPort, iSecure ? "secure" : "insecure"); + + // return new ref to caller + return(pHttpServ); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpServListen + + \Description + Listens on the listen socket with the specified settings + + \Input *pHttpServ - module state + \Input iBacklog - listen backlog setting + + \Output + int32_t - result of the listen call + + \Notes + This function is not required to be called for the server to work. + The create functions call it on your behalf with our default backlog, + this is only required for users that want to update settings. + + \Version 05/02/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpServListen(ProtoHttpServRefT *pHttpServ, int32_t iBacklog) +{ + int32_t iResult; + + // start listening + if ((iResult = ProtoSSLListen(pHttpServ->pListenSSL, iBacklog)) != SOCKERR_NONE) + { + _ProtoHttpServLogPrintf(pHttpServ, NULL, "error listening on socket (err=%d)\n", iResult); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpServDestroy + + \Description + Destroy an HttpServ instance + + \Input *pHttpServ - module state to destroy + + \Version 12/11/2013 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoHttpServDestroy(ProtoHttpServRefT *pHttpServ) +{ + ProtoHttpServThreadT *pHttpThread, *pHttpThreadNext; + + if (pHttpServ->pListenSSL != NULL) + { + ProtoSSLDestroy(pHttpServ->pListenSSL); + } + + for (pHttpThread = pHttpServ->pThreadListHead; pHttpThread != NULL; pHttpThread = pHttpThreadNext) + { + pHttpThreadNext = pHttpThread->pNext; + _ProtoHttpServThreadFree(pHttpServ, pHttpThread); + } + + DirtyMemFree(pHttpServ, HTTPSERV_MEMID, pHttpServ->iMemGroup, pHttpServ->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpServCallback + + \Description + Set required callback functions for HttpServ to use. + + \Input *pHttpServ - module state + \Input *pRequestCb - request handler callback + \Input *pReceiveCb - inbound data handler callback + \Input *pSendCb - outbound data handler callback + \Input *pHeaderCb - inbound header callback + \Input *pLogCb - logging function to use (optional) + \Input *pUserData - user data for callbacks + + \Version 12/11/2013 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoHttpServCallback(ProtoHttpServRefT *pHttpServ, ProtoHttpServRequestCbT *pRequestCb, ProtoHttpServReceiveCbT *pReceiveCb, ProtoHttpServSendCbT *pSendCb, ProtoHttpServHeaderCbT *pHeaderCb, ProtoHttpServLogCbT *pLogCb, void *pUserData) +{ + pHttpServ->pRequestCb = pRequestCb; + pHttpServ->pReceiveCb = pReceiveCb; + pHttpServ->pSendCb = pSendCb; + pHttpServ->pHeaderCb = pHeaderCb; + pHttpServ->pLogCb = pLogCb; + pHttpServ->pUserData = pUserData; +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpServControl + + \Description + Set behavior of module, based on selector input + + \Input *pHttpServ - reference pointer + \Input iSelect - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + Selectors are: + + \verbatim + SELECTOR DESCRIPTION + 'alpn' Set alpn string (pValue=string) + 'ccrt' Set client certificate level (0=disabled, 1=requested, 2=required) + 'ciph' Set supported cipher suites (iValue=PROTOSSL_CIPHER_*; default=PROTOSSL_CIPHER_ALL) + 'idle' Set idle timeout (iValue=timeout; default=5 minutes) + 'scrt' Set certificate (pValue=cert, iValue=len) + 'skey' Set private key (pValue=key, iValue=len) + 'spam' Set debug level (iValue=level) + 'time' Set request timeout in milliseconds (iValue=timeout; default=30 seconds) + 'vers' Set server-supported maximum SSL version (iValue=version; default=ProtoSSL default) + 'vmin' Set server-required minimum SSL version (iValue=version; default=ProtoSSL default) + \endverbatim + + \Version 12/12/2013 (jbrookes) +*/ +/*******************************************************************************F*/ +int32_t ProtoHttpServControl(ProtoHttpServRefT *pHttpServ, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iSelect == 'alpn') + { + ds_strnzcpy(pHttpServ->strAlpn, (const char *)pValue, sizeof(pHttpServ->strAlpn)); + return(0); + } + if (iSelect == 'ccrt') + { + pHttpServ->iClientCertLvl = iValue; + return(0); + } + if (iSelect == 'ciph') + { + pHttpServ->uSslCiphers = iValue; + return(0); + } + if (iSelect == 'idle') + { + pHttpServ->iIdleTimeout = iValue; + return(0); + } + if (iSelect == 'scrt') + { + pHttpServ->pServerCert = (char *)pValue; + pHttpServ->iServerCertLen = iValue; + return(0); + } + if (iSelect == 'skey') + { + pHttpServ->pServerKey = (char *)pValue; + pHttpServ->iServerKeyLen = iValue; + return(0); + } + if (iSelect == 'spam') + { + pHttpServ->uDebugLevel = (uint16_t)iValue; + return(0); + } + if (iSelect == 'time') + { + pHttpServ->iRequestTimeout = iValue; + return(0); + } + if (iSelect == 'vers') + { + pHttpServ->uSslVersion = iValue; + return(0); + } + if (iSelect == 'vmin') + { + pHttpServ->uSslVersionMin = iValue; + return(0); + } + // unsupported + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpServControl2 + + \Description + Set behavior of module, based on selector input + + \Input *pHttpServ - reference pointer + \Input iThread - thread to apply control to, or -1 for a global setting + \Input iSelect - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + See ProtoHttpServControl for global settings. Thread-relative control + selectors are passed through to ProtoSSL. + + \Version 03/28/2018 (jbrookes) +*/ +/*******************************************************************************F*/ +int32_t ProtoHttpServControl2(ProtoHttpServRefT *pHttpServ, int32_t iThread, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue) +{ + ProtoHttpServThreadT *pHttpThread; + + if (iThread == -1) + { + return(ProtoHttpServControl(pHttpServ, iSelect, iValue, iValue2, pValue)); + } + + if ((pHttpThread = _ProtoHttpServGetThreadFromId(pHttpServ, iThread)) == NULL) + { + _ProtoHttpServLogPrintf(pHttpServ, NULL, "invalid thread id %d\n", iThread); + return(-1); + } + + return(ProtoSSLControl(pHttpThread->pProtoSSL, iSelect, iValue, iValue2, pValue)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpServStatus + + \Description + Return status of module, based on selector input + + \Input *pHttpServ - module state + \Input iSelect - info selector (see Notes) + \Input *pBuffer - [out] storage for selector-specific output + \Input iBufSize - size of buffer + + \Output + int32_t - selector specific + + \Notes + Selectors are: + + \verbatim + SELECTOR RETURN RESULT + \endverbatim + + \Version 12/12/2013 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpServStatus(ProtoHttpServRefT *pHttpServ, int32_t iSelect, void *pBuffer, int32_t iBufSize) +{ + // unsupported selector + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpServUpdate + + \Description + Update HttpServ module + + \Input *pHttpServ - module state + + \Version 10/30/2013 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoHttpServUpdate(ProtoHttpServRefT *pHttpServ) +{ + ProtoHttpServThreadT *pHttpThread, *pHttpThreadNext, *pHttpThreadPrevious = NULL, **ppHttpThread; + + // if we don't have a listen object don't do anything at all + if (pHttpServ->pListenSSL == NULL) + { + return; + } + + // listen for a response + _ProtoHttpServUpdateListen(pHttpServ); + + // update http threads + for (pHttpThread = pHttpServ->pThreadListHead; pHttpThread != NULL; pHttpThread = pHttpThread->pNext) + { + _ProtoHttpServUpdateThread(pHttpServ, pHttpThread); + } + + // clean up expired threads + for (ppHttpThread = &pHttpServ->pThreadListHead; *ppHttpThread != NULL; ) + { + pHttpThread = *ppHttpThread; + if (pHttpThread->bDisconnecting && (NetTickDiff(NetTick(), pHttpThread->iDisconnectTimer) > HTTPSERV_DISCTIME)) + { + pHttpThreadNext = pHttpThread->pNext; + + // if dealing with tail of list, move tail pointer to previous entry (or NULL it if tail is last entry in list) + if (pHttpThread == pHttpServ->pThreadListTail) + { + pHttpServ->pThreadListTail = pHttpThreadPrevious; + if (pHttpServ->pThreadListTail != NULL) + { + pHttpServ->pThreadListTail->pNext = NULL; + } + } + + _ProtoHttpServThreadFree(pHttpServ, pHttpThread); + *ppHttpThread = pHttpThreadNext; + } + else + { + pHttpThreadPrevious = pHttpThread; + ppHttpThread = &(*ppHttpThread)->pNext; + } + } +} diff --git a/src/thirdparty/dirtysdk/source/proto/protohttputil.c b/src/thirdparty/dirtysdk/source/proto/protohttputil.c new file mode 100644 index 00000000..93ce26ce --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protohttputil.c @@ -0,0 +1,872 @@ +/*H********************************************************************************/ +/*! + \File protohttputil.c + + \Description + This module implements HTTP support routines such as URL processing, header + parsing, etc. + + \Copyright + Copyright (c) Electronic Arts 2000-2012. + + \Version 1.0 12/18/2012 (jbrookes) Refactored into separate module from protohttp. +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/proto/protohttputil.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Private variables + +//! http request names +static const char *_ProtoHttpUtil_strRequestNames[PROTOHTTP_NUMREQUESTTYPES] = +{ + "HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "CONNECT" +}; + + +// Public variables + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _WriteChar + + \Description + Write a character to output buffer, if there is room + + \Input *pBuffer - [out] output buffer + \Input cWrite - character to write to output + \Input iOffset - offset in output buffer to write to + \Input iLength - size of output buffer + + \Output + int32_t - iOffset+1 + + \Version 02/20/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _WriteChar(char *pBuffer, char cWrite, int32_t iOffset, int32_t iLength) +{ + if (iOffset < iLength) + { + pBuffer[iOffset] = cWrite; + } + return(iOffset+1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoHttpGetHeaderFieldName + + \Description + Extract header value from the designated header field. Passing pOutBuf=null + and iOutLen=0 results in returning the size required to store the field, + null-inclusive. + + \Input *pInpBuf - pointer to header text + \Input *pOutBuf - [out] output for header field name + \Input iOutLen - size of output buffer + + \Output + int32_t - negative=error, positive=success + + \Version 03/17/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoHttpGetHeaderFieldName(const char *pInpBuf, char *pOutBuf, int32_t iOutLen) +{ + int32_t iOutSize = 0; + + /* http/2 has some header names that start with a ':' character + so just write that out and continue to parsing */ + if (pInpBuf[iOutSize] == ':') + { + pOutBuf[iOutSize] = pInpBuf[iOutSize]; + iOutSize += 1; + } + + for (; (iOutSize < iOutLen) && (pInpBuf[iOutSize] != ':') && (pInpBuf[iOutSize] != '\0'); iOutSize += 1) + { + pOutBuf[iOutSize] = pInpBuf[iOutSize]; + } + if (iOutSize == iOutLen) + { + return(-1); + } + pOutBuf[iOutSize] = '\0'; + return(iOutSize); +} + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ProtoHttpUtilParseRequest + + \Description + Parse HTTP request line from the format as sent on the wire. + + \Input *pStrHeader - header buffer to parse for info + \Input *pStrMethod - [out] buffer to store request method string + \Input iMethodLen - size of method buffer + \Input *pStrUri - [out] buffer to store request uri + \Input iUriLen - size of uri buffer + \Input *pStrQuery - [out] buffer to store request query + \Input iQueryLen - size of query buffer + \Input *pRequestType - [out] storage for request type (optional) + \Input *pHttp1_0 - [out] storage for http1.0 boolean + + \Output + const char * - pointer to first header, or NULL on error + + \Version 01/03/2019 (jbrookes) From _ProtoHttpServParseHeader +*/ +/********************************************************************************F*/ +const char *ProtoHttpUtilParseRequest(const char *pStrHeader, char *pStrMethod, int32_t iMethodLen, char *pStrUri, int32_t iUriLen, char *pStrQuery, int32_t iQueryLen, ProtoHttpRequestTypeE *pRequestType, uint8_t *pHttp1_0) +{ + const char *pHdr, *pEnd; + int32_t iType; + + // process request type + for (iType = 0; iType < PROTOHTTP_NUMREQUESTTYPES; iType += 1) + { + if (!ds_strnicmp(_ProtoHttpUtil_strRequestNames[iType], pStrHeader, (int32_t)strlen(_ProtoHttpUtil_strRequestNames[iType]))) + { + break; + } + } + // make sure request type is valid + if (iType == PROTOHTTP_NUMREQUESTTYPES) + { + return(NULL); + } + // save request type + ds_strnzcpy(pStrMethod, _ProtoHttpUtil_strRequestNames[iType], iMethodLen); + if (pRequestType != NULL) + { + *pRequestType = (ProtoHttpRequestTypeE)iType; + } + + // find url + if ((pHdr = strchr(pStrHeader, '/')) == NULL) + { + // not a valid header + return(NULL); + } + // find end of url + if ((pEnd = strchr(pHdr, '?')) == NULL) + { + if ((pEnd = strchr(pHdr, ' ')) == NULL) + { + // not a valid header + return(NULL); + } + } + // copy the uri + ds_strsubzcpy(pStrUri, iUriLen, pHdr, (int32_t)(pEnd-pHdr)); + + // if the beginning of the query string was found, set that to the beginning so we can parse out the query string + if (*pEnd == '?') + { + pHdr = pEnd; + if ((pEnd = strchr(pHdr, ' ')) == NULL) + { + // not a valid header + return(NULL); + } + // copy the query string + ds_strsubzcpy(pStrQuery, iQueryLen, pHdr, (int32_t)(pEnd-pHdr)); + } + else + { + *pStrQuery = '\0'; + } + + // detect if it is a 1.0 response; if so we default to closing the connection + if (pHttp1_0 != NULL) + { + *pHttp1_0 = !ds_strnicmp(pEnd+1, "HTTP/1.0", 8) ? TRUE : FALSE; + } + + // skip to first header + if ((pHdr = strstr(pEnd, "\r\n")) == NULL) + { + // not a valid header + return(NULL); + } + // return pointer to first header + return(pHdr); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpExtractHeaderValue + + \Description + Extract header value from the designated header field. Passing pOutBuf=null + and iOutLen=0 results in returning the size required to store the field, + null-inclusive. + + \Input *pInpBuf - pointer to header text + \Input *pOutBuf - [out] output for header field value, null for size query + \Input iOutLen - size of output buffer or zero for size query + \Input **ppHdrEnd- [out] pointer past end of parsed header (optional) + + \Output + int32_t - negative=not found or not enough space, zero=success, positive=size query result + + \Version 03/17/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpExtractHeaderValue(const char *pInpBuf, char *pOutBuf, int32_t iOutLen, const char **ppHdrEnd) +{ + int32_t iValLen; + + // no input, nothing to look for + if (pInpBuf == NULL) + { + return(-1); + } + + // calc size and copy to buffer, if specified + for (iValLen = 0; *pInpBuf != '\0' ; pInpBuf += 1, iValLen += 1) + { + // check for EOL (CRLF) + if ((pInpBuf[0] == '\r') && (pInpBuf[1] == '\n')) + { + // check next character -- if whitespace we have a line continuation + if ((pInpBuf[2] == ' ') || (pInpBuf[2] == '\t')) + { + // skip CRLF and LWS + for (pInpBuf += 3; (*pInpBuf == ' ') || (*pInpBuf == '\t'); pInpBuf += 1) + ; + } + else // done + { + break; + } + } + + // copy to output buffer + if (pOutBuf != NULL) + { + pOutBuf[iValLen] = *pInpBuf; + if ((iValLen+1) >= iOutLen) + { + *pOutBuf = '\0'; + return(-1); + } + } + } + // set header end pointer, if requested + if (ppHdrEnd != NULL) + { + *ppHdrEnd = pInpBuf; + } + // if a copy request, null-terminate and return success + if (pOutBuf != NULL) + { + pOutBuf[iValLen] = '\0'; + return(0); + } + // if a size inquiry, return value length (null-inclusive) + return(iValLen+1); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpParseHeaderCode + + \Description + Parse header response code from HTTP header. + + \Input *pHdrBuf - pointer to first line of HTTP header + + \Output + int32_t - parsed header code + + \Version 01/12/2010 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpParseHeaderCode(const char *pHdrBuf) +{ + int32_t iHdrCode; + + // skip to result code + while ((*pHdrBuf != '\r') && (*pHdrBuf > ' ')) + { + pHdrBuf += 1; + } + while ((*pHdrBuf != '\r') && (*pHdrBuf <= ' ')) + { + pHdrBuf += 1; + } + // grab the result code + for (iHdrCode = 0; (*pHdrBuf >= '0') && (*pHdrBuf <= '9'); pHdrBuf += 1) + { + iHdrCode = (iHdrCode * 10) + (*pHdrBuf & 15); + } + return(iHdrCode); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpFindHeaderValue + + \Description + Finds the header field value for the designated header field. The input + header text to find should not include formatting (leading \n or trailing + colon). + + \Input *pHdrBuf - input buffer + \Input *pHeaderText - header field to find + + \Output + char * - pointer to header value text or null if not found + + \Version 03/17/2009 (jbrookes) +*/ +/********************************************************************************F*/ +const char *ProtoHttpFindHeaderValue(const char *pHdrBuf, const char *pHeaderText) +{ + char strSearchText[64]; + const char *pFoundText; + + ds_snzprintf(strSearchText, sizeof(strSearchText), "\n%s:", pHeaderText); + if ((pFoundText = ds_stristr(pHdrBuf, strSearchText)) != NULL) + { + // skip header field name + pFoundText += strlen(strSearchText); + + // skip any leading white-space + while ((*pFoundText != '\0') && (*pFoundText != '\r') && (*pFoundText <= ' ')) + { + pFoundText += 1; + } + } + + return(pFoundText); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpGetHeaderValue + + \Description + Extract header value for the header field specified by pName from pHdrBuf. + Passing pOutBuf=null and iOutLen=0 results in returning the size required + to store the field, null-inclusive. + + \Input *pState - module state + \Input *pHdrBuf - header text + \Input *pName - header field name (case-insensitive) + \Input *pBuffer - [out] pointer to buffer to store extracted header value (null for size query) + \Input iBufSize - size of output buffer (zero for size query) + \Input **pHdrEnd- [out] pointer past end of parsed header (optional) + + \Output + int32_t - negative=not found or not enough space, zero=success, positive=size query result + + \Version 03/24/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpGetHeaderValue(void *pState, const char *pHdrBuf, const char *pName, char *pBuffer, int32_t iBufSize, const char **pHdrEnd) +{ + int32_t iResult = -1; + + // check for location header request which is specially handled + if ((pHdrBuf = ProtoHttpFindHeaderValue(pHdrBuf, pName)) != NULL) + { + // extract the header + iResult = ProtoHttpExtractHeaderValue(pHdrBuf, pBuffer, iBufSize, pHdrEnd); + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpGetNextHeader + + \Description + Get the next header name/value pair from the header buffer. pHdrBuf should + initially be pointing to the start of the header text buffer ("HTTP...") and + this function can then be iteratively called to extract the individual header + fields from the header one by one. pHdrBuf could also start as the result + of a call to ProtoHttpGetHeaderValue() if desired. If the start of the + buffer is not the end of line marker, it is assumed to be the header to + retrieve. + + \Input *pState - module state + \Input *pHdrBuf - header text + \Input *pName - [out] header field name + \Input iNameSize- size of header field name buffer + \Input *pValue - [out] pointer to buffer to store extracted header value (null for size query) + \Input iValSize - size of output buffer (zero for size query) + \Input **pHdrEnd- [out] pointer past end of parsed header + + \Output + int32_t - negative=not found or not enough space, zero=success, positive=size query result + + \Version 03/24/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpGetNextHeader(void *pState, const char *pHdrBuf, char *pName, int32_t iNameSize, char *pValue, int32_t iValSize, const char **pHdrEnd) +{ + int32_t iNameLen, iResult; + + // is this a first call? + if (!strncmp(pHdrBuf, "HTTP", 4)) + { + // skip http header + for ( ; *pHdrBuf != '\r' && *pHdrBuf != '\0'; pHdrBuf += 1) + ; + if (*pHdrBuf == '\0') + { + return(-1); + } + } + + // skip crlf from previous line? + if ((pHdrBuf[0] == '\r') && (pHdrBuf[1] == '\n')) + { + pHdrBuf += 2; + } + + // get header name + if ((iNameLen = _ProtoHttpGetHeaderFieldName(pHdrBuf, pName, iNameSize)) <= 0) + { + return(-1); + } + // skip header name and leading whitespace + for (pHdrBuf += iNameLen + 1; (*pHdrBuf != '\0') && (*pHdrBuf != '\r') && (*pHdrBuf <= ' '); pHdrBuf += 1) + ; + + // get header value + iResult = ProtoHttpExtractHeaderValue(pHdrBuf, pValue, iValSize, pHdrEnd); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpUrlEncodeIntParm + + \Description + Url-encode an integer parameter. + + \Input *pBuffer - [out] pointer to buffer to append parameter to + \Input iLength - length of buffer + \Input *pParm - pointer to parameter (not encoded) + \Input iValue - integer to encode + + \Output + int32_t - number of characters (excluding null) written + + \Notes + See ProtoHttpUrlEncodeStrParm2 for notes on output + + \Version 11/30/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpUrlEncodeIntParm(char *pBuffer, int32_t iLength, const char *pParm, int32_t iValue) +{ + char strValue[32]; + // format the value + ds_snzprintf(strValue, sizeof(strValue), "%d", iValue); + // encode it + return(ProtoHttpUrlEncodeStrParm(pBuffer, iLength, pParm, strValue)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpUrlEncodeStrParm + + \Description + Url-encode a string parameter. + + \Input *pBuffer - [out] pointer to buffer to append parameter to + \Input iLength - length of buffer + \Input *pParm - pointer to url parameter (not encoded) + \Input *pData - string to encode + + \Output + int32_t - negative=failure, zero=success + + \Notes + See ProtoHttpUrlEncodeStrParm2 for notes on output + + \Version 11/30/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t ProtoHttpUrlEncodeStrParm(char *pBuffer, int32_t iLength, const char *pParm, const char *pData) +{ + // table of "safe" characters + // 0=hex encode, non-zero=direct encode, @-O=valid hex digit (&15 to get value) + static char _strSafe[256] = + "0000000000000000" "0000000000000000" // 0x00 - 0x1f + "0000000000000000" "@ABCDEFGHI000000" // 0x20 - 0x3f (allow digits) + "0JKLMNO111111111" "1111111111100000" // 0x40 - 0x5f (allow uppercase) + "0JKLMNO111111111" "1111111111100000" // 0x60 - 0x7f (allow lowercase) + "0000000000000000" "0000000000000000" // 0x80 - 0x9f + "0000000000000000" "0000000000000000" // 0xa0 - 0xbf + "0000000000000000" "0000000000000000" // 0xc0 - 0xdf + "0000000000000000" "0000000000000000"; // 0xe0 - 0xff} + + return(ProtoHttpUrlEncodeStrParm2(pBuffer, iLength, pParm, pData, _strSafe)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpUrlEncodeStrParm2 + + \Description + Url-encode a string parameter. + + \Input *pBuffer - [out] pointer to buffer to append parameter to + \Input iLength - length of buffer + \Input *pParm - pointer to url parameter (not encoded) + \Input *pData - string to encode + \Input *pStrSafe- safe string table + + \Output + int32_t - number of characters (excluding null) formatted (see note) + + \Notes + Always null-terminates if the buffer is not zero-sized. The return value + is the number of characters (excluding the terminating null byte) which + would be written to the final string regardless of whether enough space + is available or not. Therefore, a return value greater than the size + of the buffer indicates the output is truncated. + + \Version 11/30/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpUrlEncodeStrParm2(char *pBuffer, int32_t iLength, const char *pParm, const char *pData, const char *pStrSafe) +{ + // hex translation table + static const char _strHex[16] = "0123456789ABCDEF"; + int32_t iOffset, iTerm; + + // locate the append point + for (iOffset = 0; (pBuffer[iOffset] != '\0') && (iOffset < iLength); iOffset += 1) + ; + + // add in the parameter (non encoded) + for (; *pParm != '\0'; pParm += 1) + { + iOffset = _WriteChar(pBuffer, *pParm, iOffset, iLength); + } + + // add in the encoded data + for (; *pData != '\0'; pData += 1) + { + uint8_t ch = *pData; + // see how we need to encode this + if (pStrSafe[ch] == '0') + { + iOffset = _WriteChar(pBuffer, '%', iOffset, iLength); + iOffset = _WriteChar(pBuffer, _strHex[ch>>4], iOffset, iLength); + iOffset = _WriteChar(pBuffer, _strHex[ch&15], iOffset, iLength); + } + else + { + iOffset = _WriteChar(pBuffer, ch, iOffset, iLength); + } + } + + // locate terminating null + iTerm = (iOffset < iLength) ? iOffset : iLength-1; + + // terminate if possible + if (iLength > 0) + { + pBuffer[iTerm] = '\0'; + } + return(iOffset); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpUrlDecodeStrParm + + \Description + Url-decode a string parameter. + + \Input *pBuffer - pointer to the buffer with the url-encoded string + \Input *pData - [out] location to write the url-decoded string + \Input iDataSize- length of pData + + \Output + int32_t - negative=failure, zero=success + + \Version 08/25/2015 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoHttpUrlDecodeStrParm(const char *pBuffer, char *pData, int32_t iDataSize) +{ + int32_t iInpLen = (int32_t)strlen(pBuffer); + int32_t iInpOff, iOutOff; + + // save room for null termination of output + iDataSize -= 1; + + for (iInpOff = 0, iOutOff = 0; (pBuffer[iInpOff] != '\0') && (iOutOff < iDataSize); iInpOff += 1) + { + const char ch = pBuffer[iInpOff]; + if ((ch == '%') && (iInpOff < (iInpLen-2))) + { + // save the base 16 number in a string to be converted to an character + char aTemp[3] = { '\0' }; //!< 2 character + null term + aTemp[0] = pBuffer[iInpOff+1]; + aTemp[1] = pBuffer[iInpOff+2]; + iInpOff += 2; + + // convert the base 16 number to a character and write it to the output string + pData[iOutOff++] = (char)strtol(aTemp, NULL, 16 /* Base */); + } + else + { + pData[iOutOff++] = ch; + } + } + + // null-terminate + pData[iOutOff] = '\0'; + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpUrlParse + + \Description + Parses a Url into component parts. + + \Input *pUrl - Url to parse + \Input *pKind - [out] http request kind + \Input iKindSize - size of kind output buffer + \Input *pHost - [out] host name + \Input iHostSize - size of host output buffer + \Input *pPort - [out] request port + \Input *pSecure - [out] request security + + \Output + const char * - pointer past end of parsed Url + + \Version 07/01/2009 (jbrookes) First Version +*/ +/********************************************************************************F*/ +const char *ProtoHttpUrlParse(const char *pUrl, char *pKind, int32_t iKindSize, char *pHost, int32_t iHostSize, int32_t *pPort, int32_t *pSecure) +{ + uint8_t bPortSpecified; + // parse the url into component parts + return(ProtoHttpUrlParse2(pUrl, pKind, iKindSize, pHost, iHostSize, pPort, pSecure, &bPortSpecified)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpUrlParse2 + + \Description + Parse information from given URL. + + \Input *pUrl - pointer to input URL to parse + \Input *pKind - [out] storage for parsed URL kind (eg "http", "https") + \Input iKindSize - size of pKind buffer + \Input *pHost - [out] storage for parsed URL host + \Input iHostSize - size of pHost buffer + \Input *pPort - [out] storage for parsed port + \Input *pSecure - [out] storage for parsed security (0 or 1) + \Input *pPortSpecified - [out] storage for whether port was specified + + \Output + const char * - pointer to URL arguments + + \Notes + URI RFC http://www.ietf.org/rfc/rfc2396.txt + + \Version 11/10/2004 (jbrookes) Split/combined from ProtoHttpGet() and ProtoHttpPost() +*/ +/********************************************************************************F*/ +const char *ProtoHttpUrlParse2(const char *pUrl, char *pKind, int32_t iKindSize, char *pHost, int32_t iHostSize, int32_t *pPort, int32_t *pSecure, uint8_t *pPortSpecified) +{ + char strKind[PROTOHTTPUTIL_SCHEME_MAX] = ""; + const char *s; + int32_t i, iPort; + + // skip any leading white-space + for ( ; (*pUrl != 0) && (*pUrl <= ' '); pUrl += 1) + ; + + // see if there is a protocol reference (cf rfc2396 sec 3.1) + for (s = pUrl; isalnum(*s) || (*s == '+') || (*s == '-') || (*s == '.'); s++) + ; + if (*s == ':') + { + // copy over the protocol kind + ds_strsubzcpy(strKind, sizeof(strKind), pUrl, (int32_t)(s-pUrl)); + pUrl += (s-pUrl)+1; + } + ds_strnzcpy(pKind, strKind, iKindSize); + + // determine if secure connection + *pSecure = (ds_stricmp(pKind, "https") == 0); + + // skip past any white space + for ( ; (*pUrl != 0) && (*pUrl <= ' '); pUrl += 1) + ; + + // skip optional // + if ((pUrl[0] == '/') && (pUrl[1] == '/')) + { + pUrl += 2; + } + + // extract the server name + for (i = 0; i < (iHostSize-1); i++) + { + // make sure the data is valid + if ((*pUrl <= ' ') || (*pUrl == '/') || (*pUrl == '?') || (*pUrl == ':')) + { + break; + } + pHost[i] = *pUrl++; + } + pHost[i] = '\0'; + + // extract the port if included + iPort = 0; + if (*pUrl == ':') + { + for (pUrl++, iPort = 0; (*pUrl >= '0') && (*pUrl <= '9'); pUrl++) + { + iPort = (iPort * 10) + (*pUrl & 15); + } + } + // if port is unspecified, set it here to a standard port + if (iPort == 0) + { + iPort = *pSecure ? 443 : 80; + *pPortSpecified = FALSE; + } + else + { + *pPortSpecified = TRUE; + } + *pPort = iPort; + + // skip past white space to arguments + for ( ; (*pUrl != 0) && (*pUrl <= ' '); pUrl += 1) + ; + + // return pointer to arguments + return(pUrl); +} + +/*F********************************************************************************/ +/*! + \Function ProtoHttpGetNextParam + + \Description + Get the next query param name/value pair from the uri buffer. This function + can be iteratively called to extra the individual params one by one. + + \Input *pState - module state + \Input *pUriBuf - path text + \Input *pName - [out] query key name + \Input iNameSize- size of query key name buffer + \Input *pValue - [out] pointer to buffer to store extracted query value + \Input iValSize - size of output buffer + \Input **pUriEnd- [out] pointer past end of parsed query + + \Output + int32_t - negative=not found or not enough space, zero=success +*/ +/********************************************************************************F*/ +int32_t ProtoHttpGetNextParam(void *pState, const char *pUriBuf, char *pName, int32_t iNameSize, char *pValue, int32_t iValSize, const char **pUriEnd) +{ + const char* pEnd = NULL; + + // check if there is anything left to parse + if (pUriBuf[0] == '\0') + { + return(-1); + } + + // try to find the '?' character denoting the beginning + // of the query string + // if found set the new beginning one character after + // otherwise we parse as it has already been removed + if ((pEnd = strchr(pUriBuf, '?')) != NULL) + { + pUriBuf = pEnd + 1; + } + + // try to find the end of the key + if ((pEnd = strchr(pUriBuf, '=')) == NULL) + { + return(-1); + } + + // copy the value and set the new position + // we are skipping an extra character for '=' + ds_strsubzcpy(pName, iNameSize, pUriBuf, (int32_t)(pEnd-pUriBuf)); + pUriBuf = pEnd + 1; + + // try to find the end of the value + if ((pEnd = strchr(pUriBuf, '&')) == NULL) + { + // go to the end of the string + if ((pEnd = strchr(pUriBuf, '\0')) == NULL) + { + return(-1); + } + + // copy the value and set the new position + // we are not skipping an extra character as we have reached + // the end of the string + ds_strsubzcpy(pValue, iValSize, pUriBuf, (int32_t)(pEnd-pUriBuf)); + pUriBuf = pEnd; + } + else + { + // copy the value and set the new position + // we are skipping an extra character for '&' + ds_strsubzcpy(pValue, iValSize, pUriBuf, (int32_t)(pEnd-pUriBuf)); + pUriBuf = pEnd + 1; + } + + // save the location + if (pUriEnd != NULL) + { + *pUriEnd = pUriBuf; + } + + return(0); +} diff --git a/src/thirdparty/dirtysdk/source/proto/protomangle.c b/src/thirdparty/dirtysdk/source/proto/protomangle.c new file mode 100644 index 00000000..327f753d --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protomangle.c @@ -0,0 +1,1129 @@ +/*H*************************************************************************************************/ +/*! + \File protomangle.c + + \Description + This module encapsulates client services for use of the EA.Com Demangler service. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2003. ALL RIGHTS RESERVED. + + \Version 1.0 04/03/03 (JLB) First Version +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtycert.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/proto/protohttp.h" +#include "DirtySDK/proto/protomangle.h" + +/*** Defines ***************************************************************************/ + +#define PROTOMANGLE_VERBOSE (DIRTYCODE_DEBUG && TRUE) + +#define PROTOMANGLE_RAND_PORTBASE (2000) + +//! range used for random port generation (used to be 8000 but that proved to conflict with Sony port range restriction on PS4) +#define PROTOMANGLE_RAND_PORTRANGE (6000) + +#define PROTOMANGLE_HTTP_BUFSIZE (1024) + +#define PROTOMANGLE_HTTP_TIMEOUT (60 * 1000) + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +typedef enum ProtoMangleResponseE +{ + PROTOMANGLE_RESP_INVALID = -1, + PROTOMANGLE_RESP_SUCCESS, + PROTOMANGLE_RESP_PROBE, + PROTOMANGLE_RESP_FAILURE, + + PROTOMANGLE_NUMRESP +} ProtoMangleResponseE; + +//! parsed probe request data +typedef struct ProtoMangleProbeT +{ + int32_t iCurCnt; //!< current probe iteration + int32_t iSndCnt; //!< number of probes to send + int32_t iAddr; //!< peer address + int32_t iPort; //!< peer port + int32_t iSrcPort; //!< local port used to bind (if -1, bind a random port) + int32_t iId; //!< transaction ID + char strTag[64]; //!< response tag +} ProtoMangleProbeT; + +//! internal module state +struct ProtoMangleRefT //!< module state +{ + ProtoHttpRefT *pHttp; //!< http module + + SocketT *pProbeSock; //!< socket for sending UDP probes + SocketT *pSharedProbeSock; //!< shared socket, if any + + //! module memory group + int32_t iMemGroup; + void *pMemGroupUserData; + + int32_t iHostAddr; //!< local address + int32_t iGamePort; //!< game port + + int32_t iPeerAddr; //!< peer address (when eMangleState==ST_MNGL_DONE) + int32_t iPeerPort; //!< peer port to connect to (when eMangleState==ST_MNGL_DONE) + + char strCookie[PROTOMANGLE_STRCOOKIE_MAX]; //!< session cookie + char strGameID[PROTOMANGLE_STRGAMEID_MAX]; //!< game feature ID + char strLKey[PROTOMANGLE_STRLKEY_MAX]; //!< user LKey + char strServer[PROTOMANGLE_STRSERVER_MAX]; //!< server name + int32_t iServerPort; //!< server port + + ProtoMangleResponseE eResponse; //!< most recent server response + + int32_t iRandPort; //!< 'random' port to bind if no local port requested + + enum + { + ST_MNGL_IDLE, //!< created + ST_MNGL_WAIT, //!< waiting for response from HTTP server + ST_MNGL_DONE, //!< successful completion + ST_MNGL_FAIL, //!< failure condition + ST_MNGL_REPT //!< reporting + } eMangleState; + + int32_t iRecvSize; //!< current receive size + char strBuf[PROTOMANGLE_HTTP_BUFSIZE]; +}; + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +//! response codes +static const char *_ProtoMangle_pResponseTable[PROTOMANGLE_NUMRESP] = +{ + "success", // PROTOMANGLE_RESP_SUCCESS + "probe", // PROTOMANGLE_RESP_PROBE + "failure" // PROTOMANGLE_RESP_FAILURE +}; + +//! report codes +static const char *_ProtoMangle_pReportTable[PROTOMANGLE_NUMSTATUS] = +{ + "connected", // PROTOMANGLE_STATUS_CONNECTED + "failed" // PROTOMANGLE_STATUS_FAILED +}; + +// Public variables + + +/*** Private Functions *****************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleSockClose + + \Description + Safe socket close of a probe socket + + \Input *pRef - module state + \Input bRelease - whether shared probe socket should be release or not + + \Version 01/14/05 (JLB) +*/ +/*************************************************************************************************F*/ +static void _ProtoMangleSockClose(ProtoMangleRefT *pRef, uint32_t bRelease) +{ + if (pRef->pProbeSock != NULL) + { + if (pRef->pProbeSock != pRef->pSharedProbeSock) + { + NetPrintf(("protomangle: [%p] closing probe socket %p\n", pRef, pRef->pProbeSock)); + SocketShutdown(pRef->pProbeSock, SOCK_NOSEND); + SocketClose(pRef->pProbeSock); + pRef->pProbeSock = NULL; + } + else if (bRelease) + { + NetPrintf(("protomangle: [%p] releasing shared probe socket %p\n", pRef, pRef->pProbeSock)); + SocketRelease(pRef->pProbeSock); + pRef->pProbeSock = pRef->pSharedProbeSock = NULL; + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleHttpPost + + \Description + Send a POST request to HTTP server. + + \Input *pRef - module state + \Input *pUrl - url to post to + \Input *pCookie - session cookie + \Input *pData - POST data + + \Output + int32_t - zero=success, negative=failure + + \Version 07/18/03 (JLB) +*/ +/*************************************************************************************************F*/ +static int32_t _ProtoMangleHttpPost(ProtoMangleRefT *pRef, const char *pUrl, const char *pCookie, const char *pData) +{ + char strBuffer[256]; + + // setup the extra header fields + ds_snzprintf(strBuffer, sizeof(strBuffer), + "Cookie: sessionID=%s\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n", + pCookie); + + // set extra header fields + ProtoHttpControl(pRef->pHttp, 'apnd', 0, 0, strBuffer); + + // setup the url + ds_snzprintf(strBuffer, sizeof(strBuffer), "http://%s:%d/%s", pRef->strServer, pRef->iServerPort, pUrl); + + // execute the post + return(ProtoHttpPost(pRef->pHttp, strBuffer, pData, (int32_t)strlen(pData), FALSE)); +} + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleHttpRequest + + \Description + Issue HTTP request to the demangler server. + + \Input *pRef - module state + + \Version 1.0 04/08/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +static void _ProtoMangleHttpRequest(ProtoMangleRefT *pRef) +{ + char strAppend[256], strUrl[256 + (3 * DIRTYCERT_SERVICENAME_SIZE)], strAddrText[20], strServiceName[DIRTYCERT_SERVICENAME_SIZE]; + int32_t iFormatted = 0; + + if (DirtyCertStatus('snam', strServiceName, DIRTYCERT_SERVICENAME_SIZE) < 0) + { + ds_snzprintf(strServiceName, sizeof(strServiceName), "invalid"); + } + + // setup the url + iFormatted = ds_snzprintf(strUrl, sizeof(strUrl), "http://%s:%d/getPeerAddress?myIP=%s&myPort=%d&version=1.0", + pRef->strServer, pRef->iServerPort, SocketInAddrGetText(pRef->iHostAddr, strAddrText, sizeof(strAddrText)), pRef->iGamePort); + + // add service name + ProtoHttpUrlEncodeStrParm(strUrl + iFormatted, sizeof(strUrl) - iFormatted, "&gameID=", strServiceName); + + // setup the extra header fields + ds_snzprintf(strAppend, sizeof(strAppend), "Cookie: sessionID=%s\r\n", pRef->strCookie); + + // set extra header fields + ProtoHttpControl(pRef->pHttp, 'apnd', 0, 0, strAppend); + + // send the request + ProtoHttpGet(pRef->pHttp, strUrl, FALSE); + + // set state + pRef->iRecvSize = 0; + pRef->eMangleState = ST_MNGL_WAIT; +} + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleGetParm + + \Description + Parse a parameter from a demangler server response. + + \Input *pDst - pointer to buffer for parsed parm + \Input iBufLen - length of destination buffer + \Input *pSrc - pointer to server reseponse + \Input *pParmName - name of parameter to parse + \Input bParseCount - if TRUE, parse response count + + \Output + int32_t - response count (one if bParseCount==FALSE), or negative if parmname not found + + \Version 1.0 04/08/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +static int32_t _ProtoMangleGetParm(char *pDst, int32_t iBufLen, const char *pSrc, const char *pParmName, int32_t bParseCount) +{ + const char *pParm; + int32_t iStr, iId = -1; + + // clear the destination buffer + pDst[0] = '\0'; + + // make sure we have room for terminating NULL character + iBufLen--; + + // look for parm + if ((pParm = strstr(pSrc, pParmName)) != NULL) + { + // parse count? + if (bParseCount == TRUE) + { + char *pId; + + if ((pId = strchr(pParm, '-')) != NULL) + { + iId = pId[1] - '0'; + if ((iId < 0) || (iId > 9)) + { + iId = -1; + } + } + } + else + { + iId = TRUE; + } + + // look for value + if ((pParm = strchr(pParm, '=')) != NULL) + { + // found string; make a copy of it + for (iStr = 0, pParm++; (iStr < iBufLen) && (pParm[iStr] != '\r') && (pParm[iStr] != '\n'); iStr++) + { + pDst[iStr] = pParm[iStr]; + } + + // NULL terminate + pDst[iStr++] = '\0'; + } + } + + return(iId); +} + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleParseAdvance + + \Description + Advance past current parm block + + \Input *pSrc - pointer to current block + \Input *pTerminator - termination character + + \Output + const char* - pointer to next block, or NULL if there is none + + \Notes + Parm blocks are terminated by double LFs. + + \Version 1.0 04/09/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +static const char *_ProtoMangleParseAdvance(const char *pSrc, const char *pTerminator) +{ + if ((pSrc = strstr(pSrc, pTerminator)) != NULL) + { + while(*pSrc == '\n') + { + pSrc++; + } + } + + return(pSrc); +} + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleParseResponseType + + \Description + Determine type of demangler server response. + + \Input *pSrc - module state + \Input *pResponse - storage for parsed response type + + \Output + const char * - pointer to next entry + + \Version 1.0 04/08/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +static const char *_ProtoMangleParseResponseType(const char *pSrc, ProtoMangleResponseE *pResponse) +{ + char strCmd[32]; + int32_t iResp; + + *pResponse = PROTOMANGLE_RESP_INVALID; + if (_ProtoMangleGetParm(strCmd, sizeof(strCmd), pSrc, "status", FALSE)) + { + for (iResp = 0; iResp < PROTOMANGLE_NUMRESP; iResp++) + { + if (!strncmp(strCmd, _ProtoMangle_pResponseTable[iResp], strlen(_ProtoMangle_pResponseTable[iResp]))) + { + *pResponse = (ProtoMangleResponseE)iResp; + break; + } + } + } + + return(_ProtoMangleParseAdvance(pSrc, "\n")); +} + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleParseSuccess + + \Description + Parse demangler server 'success' response. + + \Input *pRef - module state + \Input *pStr - server response string + + \Output + int32_t - TRUE if successfully parsed, else FALSE + + \Version 1.0 04/09/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +static int32_t _ProtoMangleParseSuccess(ProtoMangleRefT *pRef, const char *pStr) +{ + char strParm[32]; + int32_t bLinesParsed = 0; + + if ((_ProtoMangleGetParm(strParm, sizeof(strParm), pStr, "peerIP", FALSE)) >= 0) + { + if ((pRef->iPeerAddr = SocketInTextGetAddr(strParm)) != 0) + { + bLinesParsed++; + } + } + + if ((_ProtoMangleGetParm(strParm, sizeof(strParm), pStr, "peerPort", FALSE)) >= 0) + { + pRef->iPeerPort = atoi(strParm); + bLinesParsed++; + } + + return(bLinesParsed == 2); +} + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleParseProbeRequest + + \Description + Parse a 'probe' demangler server request. + + \Input *pProbe - pointer to probe structure to fill in + \Input *pRef - module ref + \Input *pSrc - server response to parse + + \Output + int32_t - TRUE if response parsed, else FALSE + + \Version 1.0 04/08/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +static int32_t _ProtoMangleParseProbeRequest(ProtoMangleProbeT *pProbe, ProtoMangleRefT *pRef, const char *pSrc) +{ + int32_t bLinesParsed = 0, iParm; + char strParm[64]; + + if (pSrc == NULL) + { + return(FALSE); + } + + // init + pProbe->iCurCnt = 0; + pProbe->iSrcPort = -1; + pProbe->iId = -1; + + // parse target address + if ((pProbe->iId = _ProtoMangleGetParm(strParm, sizeof(strParm), pSrc, "targetIP", TRUE)) >= 0) + { + if ((pProbe->iAddr = SocketInTextGetAddr(strParm)) != 0) + { + bLinesParsed++; + } + } + + if (pProbe->iId < 0) + { + return(FALSE); + } + + // parse target port + if (_ProtoMangleGetParm(strParm, sizeof(strParm), pSrc, "targetPort", TRUE) == pProbe->iId) + { + if ((pProbe->iPort = atoi(strParm)) > 0) + { + bLinesParsed++; + } + } + + // parse request tag + if (_ProtoMangleGetParm(strParm, sizeof(strParm), pSrc, "tag", TRUE) == pProbe->iId) + { + ds_strnzcpy(pProbe->strTag, strParm, sizeof(pProbe->strTag)); + bLinesParsed++; + } + + // parse send count + if (_ProtoMangleGetParm(strParm, sizeof(strParm), pSrc, "sendCount", TRUE) == pProbe->iId) + { + if (((iParm = atoi(strParm)) > 1) && (iParm < 32)) + { + pProbe->iSndCnt = iParm; + bLinesParsed++; + } + } + + // parse source port (optional) + if (_ProtoMangleGetParm(strParm, sizeof(strParm), pSrc, "sourcePort", TRUE) == pProbe->iId) + { + if ((iParm = atoi(strParm)) > 0) + { + #ifdef DIRTYCODE_PS4 + if (iParm > 32767) + { + int32_t iParmOriginal = iParm; + iParm &= 36863; + if (iParmOriginal != iParm) + { + NetPrintf(("protomangle: using port %d instead of %d.\n", iParm, iParmOriginal)); + } + } + #endif + pProbe->iSrcPort = iParm; + } + } + + return(bLinesParsed == 4); +} + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleSetupProbeSocket + + \Description + Bind probe socket. + + \Input *pRef - module state + \Input *pProbe - probe parameters + + \Output + int32_t - local port bound to socket, or negative on error + + \Version 1.0 04/09/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +static int32_t _ProtoMangleSetupProbeSocket(ProtoMangleRefT *pRef, ProtoMangleProbeT *pProbe) +{ + struct sockaddr BindAddr; + + // tear down previous socket if we're not using shared socket + _ProtoMangleSockClose(pRef, FALSE); + + // if we have a shared socket and this is a request for the shared socket port, set it and return + if ((pRef->pSharedProbeSock != NULL) && (pProbe->iSrcPort == pRef->iGamePort)) + { + NetPrintf(("protomangle: [%p] using shared socket ref %p to probe port %d\n", pRef, pRef->pSharedProbeSock, pRef->iGamePort)); + pRef->pProbeSock = pRef->pSharedProbeSock; + } + else + { + // recreate probe socket + if ((pRef->pProbeSock = SocketOpen(AF_INET, SOCK_DGRAM, 0)) == NULL) + { + NetPrintf(("protomangle: [%p] error creating probe socket\n", pRef)); + return(-1); + } + + // set up local binding + SockaddrInit(&BindAddr, AF_INET); + if (pProbe->iSrcPort != -1) + { + SockaddrInSetPort(&BindAddr, pProbe->iSrcPort); + } + else + { + SockaddrInSetPort(&BindAddr, pRef->iRandPort++); + } + + // bind locally + if (SocketBind(pRef->pProbeSock, &BindAddr, sizeof(BindAddr)) == SOCKERR_NONE) + { + NetPrintf(("protomangle: [%p] created probe socket %p bound to port %d\n", pRef, pRef->pProbeSock, SockaddrInGetPort(&BindAddr))); + } + else + { + NetPrintf(("protomangle: [%p] error binding probe socket %p to port %d\n", pRef, pRef->pProbeSock, SockaddrInGetPort(&BindAddr))); + // tear down socket since binding failed + _ProtoMangleSockClose(pRef, FALSE); + return(-1); + } + } + + // return local port to caller + SocketInfo(pRef->pProbeSock, 'bind', 0, &BindAddr, sizeof(BindAddr)); + return(SockaddrInGetPort(&BindAddr)); +} + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleSendProbe + + \Description + Send UDP probe packet(s) to demangler server. + + \Input *pRef - module state + \Input *pProbe - probe parameters + \Input iSrcPort - source port + \Input iProbeReq - probe request count from current request list + + \Version 04/08/03 (JLB) +*/ +/*************************************************************************************************F*/ +static void _ProtoMangleSendProbe(ProtoMangleRefT *pRef, ProtoMangleProbeT *pProbe, int32_t iSrcPort, int32_t iProbeReq) +{ + struct sockaddr PeerAddr; + char strMesg[128], strAddrText[20]; + int32_t iErr, iMsgSize; + + // set up target + SockaddrInit(&PeerAddr, AF_INET); + SockaddrInSetAddr(&PeerAddr, pProbe->iAddr); + SockaddrInSetPort(&PeerAddr, pProbe->iPort); + + // format probe package + ds_snzprintf(strMesg, sizeof(strMesg), "sourceIP=%s\r\nsourcePort=%d\r\ntag=%s\r\nsendCount=%d\r\n", + SocketInAddrGetText(pRef->iHostAddr, strAddrText, sizeof(strAddrText)), iSrcPort, pProbe->strTag, pProbe->iCurCnt); + iMsgSize = (int32_t)strlen(strMesg)+1; + + // send probe + iErr = SocketSendto(pRef->pProbeSock, strMesg, iMsgSize, 0, &PeerAddr, sizeof(PeerAddr)); + + #if PROTOMANGLE_VERBOSE + if (iErr == iMsgSize) + { + NetPrintf(("protomangle: [%p] success ", pRef)); + } + else + { + NetPrintf(("protomangle: [%p] error %d ", pRef, iErr)); + } + NetPrintf(("sending probe %d/%d (%d) from port %d\n", pProbe->iCurCnt, pProbe->iSndCnt, iProbeReq, iSrcPort)); + #else + (void)iErr; + #endif +} + +/*F*************************************************************************************************/ +/*! + \Function _ProtoMangleHandleProbeRequest + + \Description + Parse a probe request block, and issue any probes requested. + + \Input *pRef - module state + \Input *pSrc - probe request string + + \Output + int32_t - zero on success, negative on failure + + \Version 1.0 04/08/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +static int32_t _ProtoMangleHandleProbeRequest(ProtoMangleRefT *pRef, const char *pSrc) +{ + ProtoMangleProbeT Probe; + int32_t iProbeReq, iSrcPort; + + // parse probe request list, + for (iProbeReq = 1; _ProtoMangleParseProbeRequest(&Probe, pRef, pSrc) != FALSE; iProbeReq++) + { + // make sure sequence IDs match + if (iProbeReq != Probe.iId) + { + NetPrintf(("protomangle: [%p] warning, probe sequence mismatch\n", pRef)); + } + + // init probe socket + if ((iSrcPort = _ProtoMangleSetupProbeSocket(pRef, &Probe)) < 0) + { + return(-1); + } + + // send iSndCnt probes + for (Probe.iCurCnt = 0; Probe.iCurCnt < Probe.iSndCnt; Probe.iCurCnt++) + { + _ProtoMangleSendProbe(pRef, &Probe, iSrcPort, iProbeReq); + } + + // advance to next response block + pSrc = _ProtoMangleParseAdvance(pSrc,"\n\n"); + } + + return(0); +} + + +/*** Public Functions ******************************************************************/ + + + +/*F*************************************************************************************************/ +/*! + \Function ProtoMangleCreate + + \Description + Allocate module state and prepare for use + + \Input *pServer - pointer to server name + \Input iPort - server port + \Input *pGameID - pointer to game feature ID string + \Input *pLKey - pointer to user LKey + + \Output + ProtoMangleRefT * - reference pointer (must be passed to all other functions) + + \Version 1.0 04/03/03 (JLB) First Version + \Version 1.1 06/16/03 (JLB) Added server parms as arguments +*/ +/*************************************************************************************************F*/ +ProtoMangleRefT *ProtoMangleCreate(const char *pServer, int32_t iPort, const char *pGameID, const char *pLKey) +{ + ProtoMangleRefT *pRef; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + NetPrintf(("protomangle: server:%s port:%d gameid:%s lkey:%s\n", pServer, iPort, pGameID, pLKey)); + + // allocate and init module state + if ((pRef = DirtyMemAlloc(sizeof(*pRef), PROTOMANGLE_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protomangle: could not allocate module state\n")); + return(NULL); + } + ds_memclr(pRef, sizeof(*pRef)); + pRef->iMemGroup = iMemGroup; + pRef->pMemGroupUserData = pMemGroupUserData; + + // create http module + if ((pRef->pHttp = ProtoHttpCreate(PROTOMANGLE_HTTP_BUFSIZE)) == NULL) + { + DirtyMemFree(pRef, PROTOMANGLE_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + return(NULL); + } + + // since we know we are going to have an ongoing transaction, enable keep-alive from the beginning + ProtoHttpControl(pRef->pHttp, 'keep', 1, 0, NULL); + + // set default http timeout + ProtoHttpControl(pRef->pHttp, 'time', PROTOMANGLE_HTTP_TIMEOUT, 0, NULL); + + // generate 'random' port in range + pRef->iRandPort = (NetTick() % PROTOMANGLE_RAND_PORTRANGE) + PROTOMANGLE_RAND_PORTBASE; + + // save server parms, gameID, LKey + ds_strnzcpy(pRef->strServer, pServer, sizeof(pRef->strServer)); + pRef->iServerPort = iPort; + ds_strnzcpy(pRef->strGameID, pGameID, sizeof(pRef->strGameID)); + ds_strnzcpy(pRef->strLKey, pLKey, sizeof(pRef->strLKey)); + + // return ref to caller + return(pRef); +} + +/*F*************************************************************************************************/ +/*! + \Function ProtoMangleDestroy + + \Description + Destroy the module and release its state + + \Input *pRef - reference pointer + + \Version 1.0 04/03/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +void ProtoMangleDestroy(ProtoMangleRefT *pRef) +{ + NetPrintf(("protomangle: [%p] shutting down\n", pRef)); + + // close probe socket, if we own it, else release it + _ProtoMangleSockClose(pRef, TRUE); + + // shut down http module + ProtoHttpDestroy(pRef->pHttp); + + // release ref + DirtyMemFree(pRef, PROTOMANGLE_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); +} + +/*F*************************************************************************************************/ +/*! + \Function ProtoMangleConnect2 + + \Description + Initiate transaction with demangler service + + \Input *pRef - module state + \Input iMyPort - game port + \Input *pSessID - pointer to unique session ID (must be identical between clients) + \Input iSessIDLen - lenght of SessID string + + \Version 01/16/13 mclouatre +*/ +/*************************************************************************************************F*/ +void ProtoMangleConnect2(ProtoMangleRefT *pRef, int32_t iMyPort, const char *pSessID, int32_t iSessIDLen) +{ + // save port/addr + pRef->iHostAddr = SocketGetLocalAddr(); + pRef->iGamePort = iMyPort; + + // save cookie + ds_strnzcpy(pRef->strCookie, pSessID, sizeof(pRef->strCookie)); + + /* close previous http socket to prevent http connections spanning + sessions, which doesn't work with the demangler load balancer */ + ProtoHttpControl(pRef->pHttp, 'disc', 0, 0, NULL); + + // fire off initial request + _ProtoMangleHttpRequest(pRef); +} + +/*F*************************************************************************************************/ +/*! + \Function ProtoMangleConnect + + \Description + Initiate transaction with demangler service + + \Input *pRef - module state + \Input iMyPort - game port + \Input *pSessID - pointer to unique session ID (must be identical between clients) + + \Version 04/07/03 jbrookes +*/ +/*************************************************************************************************F*/ +void ProtoMangleConnect(ProtoMangleRefT *pRef, int32_t iMyPort, const char *pSessID) +{ + ProtoMangleConnect2(pRef, iMyPort, pSessID, (int32_t)strlen(pSessID)); +} + +/*F*************************************************************************************************/ +/*! + \Function ProtoMangleConnectSocket + + \Description + Initiate transaction with demangler service, using the given shared socket ref for + UDP probes that are to be sent out over the game port. + + \Input *pRef - module state + \Input uSockRef - socket reference to use for UDP probes sent over game port + \Input *pSessID - pointer to unique session ID (must be identical between clients) + + \Version 1.0 08/24/04 (JLB) First Version +*/ +/*************************************************************************************************F*/ +void ProtoMangleConnectSocket(ProtoMangleRefT *pRef, intptr_t uSockRef, const char *pSessID) +{ + struct sockaddr SockAddr; + + // import socket + pRef->pSharedProbeSock = SocketImport(uSockRef); + if (pRef->pSharedProbeSock == NULL) + { + NetPrintf(("protomangle: [%p] unable to import sockref 0x%08x\n", pRef, uSockRef)); + return; + } + + // save addr and socket ref + pRef->iHostAddr = SocketGetLocalAddr(); + + // look up and save bind port + SocketInfo(pRef->pSharedProbeSock, 'bind', 0, &SockAddr, sizeof(SockAddr)); + pRef->iGamePort = SockaddrInGetPort(&SockAddr); + + NetPrintf(("protomangle: [%p] created shared socket using platform socket ref %p bound to port %d\n", pRef, pRef->pSharedProbeSock, pRef->iGamePort)); + + // save cookie + ds_strnzcpy(pRef->strCookie, pSessID, sizeof(pRef->strCookie)); + + /* close previous http socket to prevent http connections spanning + sessions, which doesn't work with the demangler load balancer */ + ProtoHttpControl(pRef->pHttp, 'disc', 0, 0, NULL); + + // fire off initial reques + _ProtoMangleHttpRequest(pRef); +} + +/*F*************************************************************************************************/ +/*! + \Function ProtoMangleUpdate + + \Description + Update module state + + \Input *pRef - module reference + + \Version 04/07/03 (JLB) +*/ +/*************************************************************************************************F*/ +void ProtoMangleUpdate(ProtoMangleRefT *pRef) +{ + // handle updates to connect process + if (pRef->eMangleState == ST_MNGL_WAIT) + { + int32_t iRecvLen; + + // update HTTP request + ProtoHttpUpdate(pRef->pHttp); + + // poll for completion + if ((iRecvLen = ProtoHttpRecvAll(pRef->pHttp, pRef->strBuf, sizeof(pRef->strBuf))) >= 0) + { + const char *pStr; + + // get data + NetPrintf(("protomangle: [%p] server response\n", pRef)); + NetPrintWrap(pRef->strBuf, 80); + + // parse response type + pStr = _ProtoMangleParseResponseType(pRef->strBuf, &pRef->eResponse); + switch(pRef->eResponse) + { + case PROTOMANGLE_RESP_SUCCESS: + // parse success result + pRef->eMangleState = _ProtoMangleParseSuccess(pRef, pStr) ? ST_MNGL_DONE : ST_MNGL_FAIL; + break; + case PROTOMANGLE_RESP_PROBE: + // parse and issue probes + if (_ProtoMangleHandleProbeRequest(pRef, pStr) >= 0) + { + // request server update + _ProtoMangleHttpRequest(pRef); + break; + } + // intentional fall-through in failure condition + case PROTOMANGLE_RESP_FAILURE: + case PROTOMANGLE_RESP_INVALID: + default: + pRef->eMangleState = ST_MNGL_FAIL; + break; + } + } + else if ((iRecvLen < 0) && (iRecvLen != PROTOHTTP_RECVWAIT)) + { + pRef->eMangleState = ST_MNGL_FAIL; + } + } + + // handle response to report + if (pRef->eMangleState == ST_MNGL_REPT) + { + // update HTTP request + ProtoHttpUpdate(pRef->pHttp); + + // done? + if (ProtoHttpStatus(pRef->pHttp, 'done', NULL, 0) != 0) + { + pRef->eMangleState = (ProtoHttpStatus(pRef->pHttp, 'code', NULL, 0) == 200) ? ST_MNGL_DONE : ST_MNGL_FAIL; + } + } +} + +/*F*************************************************************************************************/ +/*! + \Function ProtoMangleComplete + + \Description + Returns TRUE and fills in address and port if Demangler has finished operation successfully. + + \Input *pRef - module state + \Input *pAddr - storage for address (result==TRUE) + \Input *pPort - storage for port (result==TRUE) + + \Output + int32_t - TRUE if complete, else FALSE, or <0 to indicate an error + + \Version 1.0 04/09/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +int32_t ProtoMangleComplete(ProtoMangleRefT *pRef, int32_t *pAddr, int32_t *pPort) +{ + if (pRef->eMangleState == ST_MNGL_DONE) + { + // close probe socket + _ProtoMangleSockClose(pRef, TRUE); + + // return address and port to caller + if ((pAddr != NULL) && (pPort != NULL)) + { + *pAddr = pRef->iPeerAddr; + *pPort = pRef->iPeerPort; + } + + // reset to IDLE state + pRef->eMangleState = ST_MNGL_IDLE; + return(TRUE); + } + else if ((pRef->eMangleState == ST_MNGL_WAIT) || (pRef->eMangleState == ST_MNGL_REPT)) + { + return(FALSE); + } + else + { + pRef->eMangleState = ST_MNGL_IDLE; + return(-1); + } +} + +/*F*************************************************************************************************/ +/*! + \Function ProtoMangleReport + + \Description + Post result report to server. + + \Input *pRef - mangle ref + \Input eStatus - connection status + \Input iLatency - peer latency (optional, -1 to not send) + + \Output + int32_t - zero=success, negative=failure + + \Notes + Report is of the form: + + POST http://demangler.ea.com:3658/connectionStatus + Cookie: sessionID=6ba7b810-9dad-11d1-80b4-00c04fd430c8 + myIP=10.14.221.1&myPort=3250&version=1.0&status=connected&gameFeatureID=madden-ps2-2004&latency=80 + + \Version 1.0 07/18/03 (JLB) First Version +*/ +/*************************************************************************************************F*/ +int32_t ProtoMangleReport(ProtoMangleRefT *pRef, ProtoMangleStatusE eStatus, int32_t iLatency) +{ + char strData[128 + (3 * DIRTYCERT_SERVICENAME_SIZE)], strAddrText[20], strServiceName[DIRTYCERT_SERVICENAME_SIZE]; + int32_t iFormatted = 0; + + // make sure status is valid + if (eStatus > PROTOMANGLE_STATUS_FAILED) + { + return(-1); + } + + if (DirtyCertStatus('snam', strServiceName, DIRTYCERT_SERVICENAME_SIZE) < 0) + { + ds_snzprintf(strServiceName, sizeof(strServiceName), "invalid"); + } + + // format response to demangler server + iFormatted = ds_snzprintf(strData, sizeof(strData), "myIP=%s&myPort=%d&version=1.0&status=%s&gameFeatureID=%s\n", + SocketInAddrGetText(pRef->iHostAddr, strAddrText, sizeof(strAddrText)), pRef->iPeerPort, + _ProtoMangle_pReportTable[eStatus], pRef->strGameID); + + // add service name + ProtoHttpUrlEncodeStrParm(strData + iFormatted, sizeof(strData) - iFormatted, "&gameID=", strServiceName); + + // add latency info, if included + if (iLatency >= 0) + { + char strData2[32]; + ds_snzprintf(strData2, sizeof(strData2), "&latency=%d", iLatency); + ds_strnzcat(strData, strData2, sizeof(strData)); + } + + // post result to demangler server + _ProtoMangleHttpPost(pRef, "connectionStatus", pRef->strCookie, strData); + + // set state & return success + pRef->eMangleState = ST_MNGL_REPT; + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function ProtoMangleControl + + \Description + ProtoMangle control function. + + \Input *pRef - mangle ref + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Version 05/12/05 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t ProtoMangleControl(ProtoMangleRefT *pRef, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue) +{ + if (iControl == 'abrt') + { + // close probe socket + _ProtoMangleSockClose(pRef, TRUE); + + // reset to IDLE state + pRef->eMangleState = ST_MNGL_IDLE; + return(0); + } + if (iControl == 'time') + { + ProtoHttpControl(pRef->pHttp, 'time', iValue, 0, NULL); + return(0); + } + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function ProtoMangleStatus + + \Description + Return module status based on input selector. + + \Input *pRef - mangle ref + \Input iSelect - status selector + \Input *pBuf - [out] storage for output data + \Input iBufSize - size of output buffer + + \Version 1.0 01/13/05 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t ProtoMangleStatus(ProtoMangleRefT *pRef, int32_t iSelect, void *pBuf, int32_t iBufSize) +{ + if (iSelect == 'idle') + { + return(pRef->eMangleState == ST_MNGL_IDLE); + } + if (iSelect == 'time') + { + return(ProtoHttpStatus(pRef->pHttp, 'time', NULL, 0)); + } + // unhandled + return(-1); +} + diff --git a/src/thirdparty/dirtysdk/source/proto/protoname.c b/src/thirdparty/dirtysdk/source/proto/protoname.c new file mode 100644 index 00000000..aff3249c --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protoname.c @@ -0,0 +1,136 @@ +/*H*************************************************************************************************/ +/*! + + \File protoname.c + + \Description + This module provides name lookup services via DNS. It is platform indepedent + and can be used to resolve names for use with other protocol modules. At this + point, name support is being removed from other protocol modules so it can be + centralized here. + + \Notes + None. + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 1.0 03/19/02 (GWS) First Version + +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protoname.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +// Public variables + + +/*** Private Functions *****************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function _ProtoNameSync + + \Description + Synchronous (blocking) lookup. + + \Input *pName - pointer to name + \Input iTimeout - timeout in millseconds + + \Output + uint32_t - 0=failed, else the IP address + + \Version 1.0 03/19/02 (GWS) First Version +*/ +/*************************************************************************************************F*/ +static uint32_t _ProtoNameSync(const char *pName, int32_t iTimeout) +{ + HostentT *pLookup; + uint32_t uAddr = 0; + + // start the query + pLookup = SocketLookup(pName, iTimeout); + if (pLookup != NULL) + { + // wait for completion + while (!pLookup->Done(pLookup)) + { + // give away some time + NetConnSleep(10); + } + + // get the address + uAddr = pLookup->addr; + pLookup->Free(pLookup); + } + + // return the address + return(uAddr); +} + + +/*** Public functions *****************************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function ProtoNameAsync + + \Description + Async lookup of a domain name. Same as SocketLookup + but deals with platform specific issues (i.e., this + works on PS2/EE while calling SocketLookup directly + would not). + + \Input *pName - pointer to name + \Input iTimeout - timeout in millseconds + + \Output + HostentT * - HostentT structure pointer + + \Version 1.0 03/19/02 (GWS) First Version +*/ +/*************************************************************************************************F*/ +HostentT *ProtoNameAsync(const char *pName, int32_t iTimeout) +{ + return(SocketLookup(pName, iTimeout)); +} + +/*F*************************************************************************************************/ +/*! + \Function ProtoNameSync + + \Description + Synchronous (blocking) lookup of a domain name. + + \Input *pName - pointer to name + \Input iTimeout - timeout in millseconds + + \Output + uint32_t - 0=failed, else the IP address + + \Version 1.0 03/19/02 (GWS) First Version +*/ +/*************************************************************************************************F*/ +uint32_t ProtoNameSync(const char *pName, int32_t iTimeout) +{ + return(_ProtoNameSync(pName, iTimeout)); +} diff --git a/src/thirdparty/dirtysdk/source/proto/protossl.c b/src/thirdparty/dirtysdk/source/proto/protossl.c new file mode 100644 index 00000000..78d47d84 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protossl.c @@ -0,0 +1,14611 @@ +/*H********************************************************************************/ +/*! + \File protossl.c + + \Description + This module is a from-scratch TLS implementation. It does not use any + third-party code of any kind and was developed entirely by EA. + + \Notes + References: + TLS1.0 RFC: https://tools.ietf.org/html/rfc2246 + TLS1.1 RFC: https://tools.ietf.org/html/rfc4346 + TLS1.2 RFC: https://tools.ietf.org/html/rfc5246 + TLS1.3 RFC: https://tools.ietf.org/html/rfc8446 + ASN.1 encoding rules: https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf + + \Copyright + Copyright (c) Electronic Arts 2002-2018 + + \Version 03/08/2002 (gschaefer) Initial SSL 2.0 implementation + \Version 03/03/2004 (sbevan) Added certificate validation + \Version 11/05/2005 (gschaefer) Rewritten to follow SSL 3.0 specification + \Version 10/12/2012 (jbrookes) Added support for TLS1.0 & TLS1.1 + \Version 10/20/2013 (jbrookes) Added server handshake, client cert support + \Version 11/06/2013 (jbrookes) Added support for TLS1.2 + \Version 03/31/2017 (eesponda) Added support for EC ciphers + \Version 03/28/2018 (jbrookes) Added support for TLS1.3 + \Version 08/15/2018 (jbrookes) Removed SSLv3 & RC4 ciphers +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtycert.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/crypt/cryptdef.h" +#include "DirtySDK/crypt/cryptaes.h" +#include "DirtySDK/crypt/cryptchacha.h" +#include "DirtySDK/crypt/cryptcurve.h" +#include "DirtySDK/crypt/cryptgcm.h" +#include "DirtySDK/crypt/crypthash.h" +#include "DirtySDK/crypt/crypthmac.h" +#include "DirtySDK/crypt/cryptmd5.h" +#include "DirtySDK/crypt/cryptrand.h" +#include "DirtySDK/crypt/cryptrsa.h" +#include "DirtySDK/crypt/cryptsha1.h" +#include "DirtySDK/util/base64.h" + +#include "DirtySDK/proto/protossl.h" + +#include "cryptrandpriv.h" + +/*** Defines **********************************************************************/ + +#define DEBUG_RAW_DATA (DIRTYCODE_LOGGING && 0) // display raw debug data +#define DEBUG_ENC_PERF (DIRTYCODE_LOGGING && 0) // show verbose crypto performance +#define DEBUG_MSG_LIST (DIRTYCODE_LOGGING && 0) // list the message states +#define DEBUG_ALL_OBJS (DIRTYCODE_LOGGING && 0) // display all headers/objects while parsing +#define DEBUG_VAL_CERT (DIRTYCODE_LOGGING && 0) // display verbose certificate validation info +#define DEBUG_RES_SESS (DIRTYCODE_LOGGING && 0) // display verbose session resume info +#define DEBUG_MOD_PRNT (DIRTYCODE_LOGGING && 0) // display public key modulus when parsing certificate (useful when adding new CA) + +#define SSL_VERBOSE_DEFAULT (1) + +#define SSL_CACERTFLAG_NONE (0) +#define SSL_CACERTFLAG_GOSCA (1) //!< identifies a GOS CA, for use on internal (*.ea.com) servers only +#define SSL_CACERTFLAG_CAPROVIDER (2) //!< identifies a CA that can function as a CA provider (used for cert loading) + +#define SSL_ERR_GOSCA_INVALIDUSE (-100) //!< error indicating invalid use of a GOS CA to access a non ea.com domain +#define SSL_ERR_CERT_INVALIDDATE (-101) //!< error indicating a CA certificate is expired +#define SSL_ERR_CAPROVIDER_INVALID (-102) //!< attempt to use an invalid CA for CA provider use + +#define SSL_MIN_PACKET 5 // smallest packet (5 bytes framing) +#define SSL_CRYPTO_PAD 2048 // maximum crypto overhead ref: http://tools.ietf.org/html/rfc5246#section-6.2.3 +#define SSL_RAW_PACKET 16384 // max raw data size ref: http://tools.ietf.org/html/rfc5246#section-6.2.1 +#define SSL_RCVMAX_PACKET ((SSL_RAW_PACKET+SSL_CRYPTO_PAD)*2) // max recv packet buffer; sized to allow a handshake packet up to twice the ssl frame size +#define SSL_SNDMAX_PACKET (SSL_RAW_PACKET) // max send packet buffer +#define SSL_SNDOVH_PACKET (384) // reserve space for header/mac in send packet +#define SSL_SNDLIM_PACKET (SSL_SNDMAX_PACKET-SSL_SNDOVH_PACKET) // max send user payload size + +#define SSL_KEYBLOCK_LEN (512) //!< length of keyblock that holds generated key material/tls1.3 secrets/keys/ivs +#define SSL_KEYMATERIAL_LEN (256) //!< length of key material we generate for TLS1.2 and prior + +#define SSL_SIG_MAX (512) //!< max signature size (4096 bit) + +#define SSL3_SESSION_TICKET_MAX (1536) //!< max session ticket size +#define SSL3_SESSION_NONCE_MAX (256) //!< max session ticket nonce size + +#define SSL_SESSHIST_MAX (32) // max number of previous sessions that will be tracked for possible later resumption +#define SSL_SESSVALIDTIME_MAX (2*60*60*1000) // session validity cached for a max of two hours (tls1.2 and prior) +#define SSL_SESSID_SIZE (32) + +#define SSL_CERTVALID_MAX (32) +#define SSL_CERTVALIDTIME_MAX (2*60*60*1000) // certificate validity cached for a max of two hours + +#define SSL_FINGERPRINT_SIZE (CRYPTSHA1_HASHSIZE) + +// min/default/max protocol versions supported +#define SSL3_VERSION_MIN (PROTOSSL_VERSION_TLS1_0) +#define SSL3_VERSION (PROTOSSL_VERSION_TLS1_2) +#define SSL3_VERSION_MAX (PROTOSSL_VERSION_TLS1_3) + +// internal names for TLS protocol versions +#define SSL3_SSLv3 (0x0300) // defined internally strictly to identify SSLv3 cipher suites that are still supported +#define SSL3_TLS1_0 (PROTOSSL_VERSION_TLS1_0) +#define SSL3_TLS1_1 (PROTOSSL_VERSION_TLS1_1) +#define SSL3_TLS1_2 (PROTOSSL_VERSION_TLS1_2) +#define SSL3_TLS1_3 (PROTOSSL_VERSION_TLS1_3) + +// TLS record types +#define SSL3_REC_CIPHER 20 // cipher change record +#define SSL3_REC_ALERT 21 // alert record +#define SSL3_REC_HANDSHAKE 22 // handshake record +#define SSL3_REC_APPLICATION 23 // application data record + +// TLS handshake header length +#define SSL3_MSG_HEADER_SIZE 4 + +// TLS handshake message types; see https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-7 +#define SSL3_MSG_HELLO_REQUEST 0 // hello request +#define SSL3_MSG_CLIENT_HELLO 1 // client hello +#define SSL3_MSG_SERVER_HELLO 2 // server hello +#define SSL3_MSG_NEW_SESSION_TICKET 4 // session ticket +#define SSL3_MSG_END_OF_EARLY_DATA 5 // end of early data (tls1.3) +#define SSL3_MSG_ENCRYPTED_EXTENSIONS 8 // encrypted extensions (tls1.3) +#define SSL3_MSG_CERTIFICATE 11 // certificate +#define SSL3_MSG_SERVER_KEY 12 // server key data +#define SSL3_MSG_CERT_REQ 13 // certificate request +#define SSL3_MSG_SERVER_DONE 14 // server handshake done +#define SSL3_MSG_CERT_VERIFY 15 // certificate verify +#define SSL3_MSG_CLIENT_KEY 16 // client key data +#define SSL3_MSG_FINISHED 20 // handshake finished +#define SSL3_MSG_KEY_UPDATE 24 // key update (tls1.3) +#define SSL3_MSG_MESSAGE_HASH 254 // synthetic message for HRR + +// MAC types (ident equals size) +#define SSL3_MAC_NULL 0 // none +#define SSL3_MAC_MD5 16 // md5 +#define SSL3_MAC_SHA 20 // sha1 +#define SSL3_MAC_SHA256 32 // sha2-256 +#define SSL3_MAC_SHA384 48 // sha2-384 +#define SSL3_MAC_SHA512 64 // sha2-512 +#define SSL3_MAC_MAXSIZE (CRYPTHASH_MAXDIGEST) // maximum MAC size + +// PRF types +#define SSL3_PRF_NULL 0 // no cipher-suite defined prf +#define SSL3_PRF_SHA256 CRYPTHASH_SHA256 +#define SSL3_PRF_SHA384 CRYPTHASH_SHA384 + +// key types +#define SSL3_KEY_NULL 0 // no record key exchange +#define SSL3_KEY_RSA 1 // use rsa +#define SSL3_KEY_ECDHE 2 // use ecdhe + +// signing types +#define SSL3_SIGN_RSA 1 +#define SSL3_SIGN_ECDSA 64 + +// key lengths +#define SSL3_KEYLEN_NULL 0 +#define SSL3_KEYLEN_128 16 // 128-bit key length in bytes +#define SSL3_KEYLEN_256 32 // 256-bit key length in bytes + +// cipher types +#define SSL3_ENC_NULL 0 // no record encryption +#define SSL3_ENC_AES 1 // use aes +#define SSL3_ENC_GCM 2 // use gcm +#define SSL3_ENC_CHACHA 3 // use chacha + +// hash ids +#define SSL3_HASHID_NONE (0) +#define SSL3_HASHID_MD5 (1) +#define SSL3_HASHID_SHA1 (2) +#define SSL3_HASHID_SHA224 (3) +#define SSL3_HASHID_SHA256 (4) +#define SSL3_HASHID_SHA384 (5) +#define SSL3_HASHID_SHA512 (6) +#define SSL3_HASHID_MAX (SSL3_HASHID_SHA512) + +// signature algorithm ids +#define SSL3_SIGALG_NONE (0) +#define SSL3_SIGALG_RSA (1) +#define SSL3_SIGALG_DSA (2) +#define SSL3_SIGALG_ECDSA (3) + +// signature verification schemes +#define SSL3_SIGVERIFY_NONE (0) +#define SSL3_SIGVERIFY_RSA_PKCS1 (1) +#define SSL3_SIGVERIFY_RSA_PSS (2) + +// curve types +enum +{ + SSL3_CURVE_NONE = 0xff, + SSL3_CURVE_SECP256R1 = 0, + SSL3_CURVE_X25519, + SSL3_CURVE_SECP384R1, + SSL3_CURVE_X448, + SSL3_NUM_CURVES +}; +// index of default ec +#define SSL3_CURVE_DEFAULT (SSL3_CURVE_SECP256R1) +// index of max ec +#define SSL3_CURVE_MAX (SSL3_CURVE_X448) + +/* signature schemes (tls1.3, but overlaps with previous signature algorithms); see + https://tools.ietf.org/html/rfc8446#section-4.2.3 */ + +// RSASSA-PKCS1-v1_5 algorithms +#define SSL3_SIGSCHEME_RSA_PKCS1_MD5 (0x0101) // deprecated +#define SSL3_SIGSCHEME_RSA_PKCS1_SHA1 (0x0201) // legacy +#define SSL3_SIGSCHEME_RSA_PKCS1_SHA256 (0x0401) +#define SSL3_SIGSCHEME_RSA_PKCS1_SHA384 (0x0501) +#define SSL3_SIGSCHEME_RSA_PKCS1_SHA512 (0x0601) +// ECDSA algorithms +#define SSL3_SIGSCHEME_ECDSA_SHA1 (0x0203) // legacy, required for TLS1.0/1.1 ECDSA cipher suites +#define SSL3_SIGSCHEME_ECDSA_SHA256 (0x0403) +#define SSL3_SIGSCHEME_ECDSA_SHA384 (0x0503) +#define SSL3_SIGSCHEME_ECDSA_SHA512 (0x0603) +// RSASSA-PSS algorithms with public key OID rsaEncryption +#define SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA256 (0x0804) +#define SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA384 (0x0805) +#define SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA512 (0x0806) +// EdDSA algorithms +#define SSL3_SIGSCHEME_EDDSA_ED25519 (0x0807) // unsupported +#define SSL3_SIGSCHEME_EDDSA_ED448 (0x0808) // unsupported +// RSASSA-PSS algorithms with public key OID RSASSA-PSS +#define SSL3_SIGSCHEME_RSA_PSS_PSS_SHA256 (0x0809) +#define SSL3_SIGSCHEME_RSA_PSS_PSS_SHA384 (0x080a) +#define SSL3_SIGSCHEME_RSA_PSS_PSS_SHA512 (0x080b) + +// curve types +#define SSL3_CURVETYPE_EXPLICIT_PRIME (1) +#define SSL3_CURVETYPE_EXPLICIT_CHAR (2) +#define SSL3_CURVETYPE_NAMED_CURVE (3) + +// TLS alert defines + +// alert level +#define SSL3_ALERT_LEVEL_WARNING 1 +#define SSL3_ALERT_LEVEL_FATAL 2 + +// alert identifiers +#define SSL3_ALERT_DESC_CLOSE_NOTIFY 0 +#define SSL3_ALERT_DESC_UNEXPECTED_MESSAGE 10 +#define SSL3_ALERT_DESC_BAD_RECORD_MAC 20 +#define SSL3_ALERT_DESC_DECRYPTION_FAILED 21 // reserved +#define SSL3_ALERT_DESC_RECORD_OVERFLOW 22 +#define SSL3_ALERT_DESC_DECOMPRESSION_FAILURE 30 // reserved +#define SSL3_ALERT_DESC_HANDSHAKE_FAILURE 40 +#define SSL3_ALERT_DESC_NO_CERTIFICATE 41 // reserved +#define SSL3_ALERT_DESC_BAD_CERTFICIATE 42 +#define SSL3_ALERT_DESC_UNSUPPORTED_CERTIFICATE 43 +#define SSL3_ALERT_DESC_CERTIFICATE_REVOKED 44 +#define SSL3_ALERT_DESC_CERTIFICATE_EXPIRED 45 +#define SSL3_ALERT_DESC_CERTIFICATE_UNKNOWN 46 +#define SSL3_ALERT_DESC_ILLEGAL_PARAMETER 47 +// the following alert types are all TLS only +#define SSL3_ALERT_DESC_UNKNOWN_CA 48 +#define SSL3_ALERT_DESC_ACCESS_DENIED 49 +#define SSL3_ALERT_DESC_DECODE_ERROR 50 +#define SSL3_ALERT_DESC_DECRYPT_ERROR 51 +#define SSL3_ALERT_DESC_EXPORT_RESTRICTION 60 // reserved +#define SSL3_ALERT_DESC_PROTOCOL_VERSION 70 +#define SSL3_ALERT_DESC_INSUFFICIENT_SECURITY 71 +#define SSL3_ALERT_DESC_INTERNAL_ERROR 80 +#define SSL3_ALERT_DESC_INAPPROPRIATE_FALLBACK 86 +#define SSL3_ALERT_DESC_USER_CANCELLED 90 +#define SSL3_ALERT_DESC_NO_RENEGOTIATION 100 // reserved +#define SSL3_ALERT_DESC_MISSING_EXTENSION 109 +#define SSL3_ALERT_DESC_UNSUPPORTED_EXTENSION 110 +#define SSL3_ALERT_DESC_CERTIFICATE_UNOBTAINABLE 111 +#define SSL3_ALERT_DESC_UNRECOGNIZED_NAME 112 +#define SSL3_ALERT_DESC_BAD_CERTIFICATE_STATUS 113 +#define SSL3_ALERT_DESC_BAD_CERTIFICATE_HASH 114 +#define SSL3_ALERT_DESC_UNKNOWN_PSK_IDENTITY 115 +#define SSL3_ALERT_DESC_CERTIFICATE_REQUIRED 116 +#define SSL3_ALERT_DESC_NO_APPLICATION_PROTOCOL 120 + +// certificate setup +#define SSL_CERT_X509 1 + +// tls extension types +#define SSL_EXTN_SERVER_NAME (0x0000) //!< identifier for server name extension +#define SSL_EXTN_ELLIPTIC_CURVES (0x000a) //!< identifier for elliptic curves extension and supported curves +#define SSL_EXTN_SIGNATURE_ALGORITHMS (0x000d) //!< identifier for signature algorithms extension +#define SSL_EXTN_ALPN (0x0010) //!< identifier for ALPN (Application Level Protocol Negotiation) extension +#define SSL_EXTN_PRE_SHARED_KEY (0x0029) //!< identifier for pre_shared_key extension +#define SSL_EXTN_EARLY_DATA (0x002a) //!< identifier for early_data extension (not supported) +#define SSL_EXTN_SUPPORTED_VERSIONS (0x002b) //!< identifier for supported_versions extension +#define SSL_EXTN_COOKIE (0x002c) //!< identifier for cookie extension +#define SSL_EXTN_PSK_MODES (0x002d) //!< identifier for psk_key_exchange_modes extension +#define SSL_EXTN_CERT_AUTH (0x002f) //!< identifier for certificate_authorities extension (not supported) +#define SSL_EXTN_OID_FILTER (0x0030) //!< idetntifer for oid_filter extension (not supported) +#define SSL_EXTN_POST_HANDSHAKE_AUTH (0x0031) //!< identifier for post_handshake_auth extension (not supported) +#define SSL_EXTN_SIGALGS_CERT (0x0032) //!< identifier for signature_algorithms_cert extension (not supported) +#define SSL_EXTN_KEY_SHARE (0x0033) //!< identifier for key_share extension +#define SSL_EXTN_MAX (SSL_EXTN_KEY_SHARE) //!< max defined extension; renegotiation_info is special case + +#define SSL_EXTN_RENEGOTIATION_INFO (0xff01) //!< renegotiation_info extension (not supported) + +#define SSL_EXTN_ALL (0xffff) //!< special value used to indicate all extensions, not a bitfield + +// ALPN defines +#define SSL_ALPN_MAX_PROTOCOLS (4) //!< max supported ALPN protocols + +// tls states +enum +{ + // network states + ST_IDLE = 0, + ST_ADDR, + ST_CONN, + ST_WAIT_CONN, + ST_WAIT_CA, // waiting for the CA to be fetched + + // ssl states + ST3_SEND_HELLO = 20, + ST3_RECV_HELLO, + ST3_SEND_HELLO_RETRY, + ST3_SEND_EXTN, + ST3_SEND_CERT, + ST3_SEND_CERT_REQ, + ST3_SEND_KEY, + ST3_SEND_DONE, + ST3_SEND_VERIFY, + ST3_SEND_CHANGE, + ST3_SEND_FINISH, + ST3_RECV_CHANGE, + ST3_RECV_FINISH, + // post-finish states + ST3_SEND_HELLO_REQUEST, + ST3_PROC_ASYNC, // synthetic state for iterative async operations + ST3_SECURE, + ST_UNSECURE, + + // failure states + ST_FAIL = 0x1000, + ST_FAIL_DNS, + ST_FAIL_CONN, + ST_FAIL_CONN_SSL2, + ST_FAIL_CONN_NOTSSL, + ST_FAIL_CONN_MINVERS, + ST_FAIL_CONN_MAXVERS, + ST_FAIL_CONN_NOCIPHER, + ST_FAIL_CONN_NOCURVE, + ST_FAIL_CERT_NONE, + ST_FAIL_CERT_INVALID, + ST_FAIL_CERT_HOST, + ST_FAIL_CERT_NOTRUST, + ST_FAIL_CERT_BADDATE, + ST_FAIL_SETUP, + ST_FAIL_SECURE, + ST_FAIL_CERT_REQUEST +}; + +// ASN.1 definitions +#define ASN_CLASS_UNIV 0x00 +#define ASN_CLASS_APPL 0x40 +#define ASN_CLASS_CONT 0x80 +#define ASN_CLASS_PRIV 0xc0 + +#define ASN_PRIMITIVE 0x00 +#define ASN_CONSTRUCT 0x20 + +#define ASN_TYPE_BOOLEAN 0x01 +#define ASN_TYPE_INTEGER 0x02 +#define ASN_TYPE_BITSTRING 0x03 +#define ASN_TYPE_OCTSTRING 0x04 +#define ASN_TYPE_NULL 0x05 +#define ASN_TYPE_OBJECT 0x06 +#define ASN_TYPE_UTF8STR 0x0c +#define ASN_TYPE_SEQN 0x10 +#define ASN_TYPE_SET 0x11 +#define ASN_TYPE_PRINTSTR 0x13 +#define ASN_TYPE_T61 0x14 +#define ASN_TYPE_IA5 0x16 +#define ASN_TYPE_UTCTIME 0x17 +#define ASN_TYPE_GENERALIZEDTIME 0x18 +#define ASN_TYPE_UNICODESTR 0x1e + +#define ASN_IMPLICIT_TAG 0x80 +#define ASN_EXPLICIT_TAG 0xa0 + +enum { + ASN_OBJ_NONE = 0, + ASN_OBJ_COUNTRY, + ASN_OBJ_STATE, + ASN_OBJ_CITY, + ASN_OBJ_ORGANIZATION, + ASN_OBJ_UNIT, + ASN_OBJ_COMMON, + ASN_OBJ_SUBJECT_ALT, + ASN_OBJ_BASIC_CONSTRAINTS, + ASN_OBJ_MD5, + ASN_OBJ_SHA1, + ASN_OBJ_SHA256, + ASN_OBJ_SHA384, + ASN_OBJ_SHA512, + ASN_OBJ_RSA_PKCS_KEY, + ASN_OBJ_ECDSA_KEY, + ASN_OBJ_RSA_PKCS_MD5, + ASN_OBJ_RSA_PKCS_SHA1, + ASN_OBJ_RSA_PKCS_SHA256, + ASN_OBJ_RSA_PKCS_SHA384, + ASN_OBJ_RSA_PKCS_SHA512, + ASN_OBJ_RSASSA_PSS, + ASN_OBJ_ECDSA_SHA256, + ASN_OBJ_ECDSA_SHA384, + ASN_OBJ_ECDSA_SHA512, + ASN_OBJ_ECDSA_SECP256R1, + ASN_OBJ_ECDSA_SECP384R1, + ASN_OBJ_ECDSA_SECP521R1, + ASN_OBJ_PKCS1_MGF1, + ASN_OBJ_COUNT, +}; + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! ASN object table +typedef struct ASNObjectT +{ + int32_t iType; //!< symbolic type + int32_t iSize; //!< size of object + uint8_t strData[16]; //!< object identifier +} ASNObjectT; + +//! generic binary certificate container plus some parsed certificate info +typedef struct CertificateDataT +{ + int32_t iCrvType; + int32_t iKeyType; + int32_t iSigType; + int32_t iCertSize; + uint8_t aCertData[1]; +} CertificateDataT; + +//! structure holding .pem certificate signature begin/end strings +typedef struct CertificateSignatureT +{ + const char *pCertBeg; + const char *pCertEnd; + uint32_t uCertType; +} CertificateSignatureT; + +//! note: this layout differs somewhat from x.509, but I think it +//! makes more sense this way +typedef struct X509CertificateT +{ + ProtoSSLCertIdentT Issuer; //!< certificate was issued by (matches subject in another cert) + ProtoSSLCertIdentT Subject; //!< certificate was issued for this site/authority + + char strGoodFrom[32]; //!< good from this date + char strGoodTill[32]; //!< until this date + uint64_t uGoodFrom; + uint64_t uGoodTill; + + const uint8_t *pSubjectAlt; //!< subject alternative name, if present + int32_t iSubjectAltLen; //!< subject alternative length + + int32_t iSerialSize; //!< certificate serial number + uint8_t SerialData[32]; //!< certificate serial data + + int32_t iSigType; //!< digital signature type + int32_t iSigHash; //!< signature hash algorithm + int32_t iSigSize; //!< size of signature data + int32_t iSigSalt; //!< salt length when signature algorithm is rsassa-pss + int32_t iMgfHash; //!< message-generation function hash when signature algorithm is rsassa-pss + uint8_t SigData[SSL_SIG_MAX]; //!< digital signature data + + int32_t iKeyType; //!< public key algorithm type + int32_t iCrvType; //!< type of curve, if keytype==ecdsa + int32_t iKeyModSize; //!< size of public key modulus (rsa) / curve data (ecdsa) + uint8_t KeyModData[SSL_SIG_MAX];//!< public key modulus + int32_t iKeyExpSize; //!< size of public key exponent + uint8_t KeyExpData[16]; //!< public key exponent + + // iMaxHeight is valid only if iCertIsCA is set + int32_t iCertIsCA; //!< whether this cert can be used as a CA cert + int32_t iMaxHeight; //!< the pathLenConstraints of a CA, 0 means no limit/field not present + + int32_t iHashSize; //!< size of certificate signature hash + CryptHashTypeE eHashType; //!< type of certificate signature hash + uint8_t HashData[CRYPTHASH_MAXDIGEST]; +} X509CertificateT; + +//! private key definition +typedef struct X509PrivateKeyT +{ + CryptBinaryObjT Modulus; //!< key modulus + CryptBinaryObjT PublicExponent; //!< public key exponent + CryptBinaryObjT PrivateExponent; //!< private key exponent + CryptBinaryObjT PrimeP; //!< prime factor (p) of modulus + CryptBinaryObjT PrimeQ; //!< prime factor (q) of modulus + CryptBinaryObjT ExponentP; //!< exponent d mod p-1 + CryptBinaryObjT ExponentQ; //!< exponent d mod q-1 + CryptBinaryObjT Coefficient; //!< inverse of q mod p + char strPrivKeyData[4096]; //!< buffer to hold private key data +} X509PrivateKeyT; + +//! defines data for formatting/processing Finished packet +struct SSLFinished +{ + char strLabel[16]; + uint8_t uNextState[2]; // [not resuming, resuming] +}; + +//! define data for handshake transitions +typedef struct SSLHandshakeMapT +{ + int8_t iCurMsg; + int8_t iNxtMsg; +} SSLHandshakeMapT; + +//! minimal certificate authority data (just enough to validate another certificate) +typedef struct ProtoSSLCACertT +{ + ProtoSSLCertIdentT Subject; //!< identity of this certificate authority + uint32_t uFlags; //!< SSL_CACERTFLAG_* + + int32_t iKeyType; //!< public key algorithm type + int32_t iCrvType; //!< type of curve, if keytype==ecdsa + + int32_t iKeyModSize; //!< size of public key modulus + const uint8_t *pKeyModData; //!< public key modulus for signature verification + + int32_t iKeyExpSize; //!< size of public key exponent + uint8_t KeyExpData[16]; //!< public key exponent + + int32_t iMemGroup; //!< memgroup cert was allocated with (zero == static) + void *pMemGroupUserData; //!< memgroup user data + + X509CertificateT *pX509Cert; //!< X509 certificate, if this cert has not yet been validated + struct ProtoSSLCACertT *pNext; //!< link to next cert in list +} ProtoSSLCACertT; + +//! async op function pointer type +typedef int32_t (AsyncOpT)(ProtoSSLRefT *pState); + +//! info for async op +typedef struct AsyncOpInfoT +{ + AsyncOpT *pAsyncOp; //!< async operation to execute, when in ASYNC state + int32_t iNextState; //!< next state, when in ASYNC + int32_t iFailState; //!< fail state, if async op fails + int32_t iFailAlert; //!< fail alert, if async op fails +} AsyncOpInfoT; + +//! cipher parameter lookup structure +typedef struct CipherSuiteT +{ + uint16_t uIdent; //!< two-byte identifier + uint16_t uMinVers; //!< minimum required SSL version + uint8_t uKey; //!< key exchange algorithm (SSL3_KEY_*) + uint8_t uLen; //!< key length (SSL3_KEYLEN_*) + uint8_t uSig; //!< signature algorithm (SSL3_SIGALG_*) + uint8_t uEnc; //!< encryption algorithm (SSL3_ENC_*) + uint8_t uMac; //!< MAC digest size (SSL3_MAC_*) + uint8_t uMacType; //!< MAC digest type (CRYPTHASH_*) + uint8_t uVecSize; //!< explicit IV size + uint8_t uPrfType; //!< cipher-suite PRF type (SSL3_PRF_*) + // no explicit pad here to make static initialization cleaner + uint32_t uId; //!< PROTOSSL_CIPHER_* + char strName[66]; //!< cipher name +} CipherSuiteT; + +//! elliptic curve used for key exchange +typedef struct EllipticCurveT +{ + uint16_t uIdent; //!< two-byte identifier + uint16_t uId; //!< PROTOSSL_CURVE_* + char strName[32]; //!< curve name +} EllipticCurveT; + +//! session info, used for tls1.2 and prior resume +typedef struct SessionInfoT +{ + uint16_t uSslVersion; //!< ssl version used for connection + uint16_t uCipherId; //!< cipher used for connection + uint8_t SessionId[SSL_SESSID_SIZE]; //!< session id used to identify session + uint8_t MasterSecret[48]; //!< master secret generated for session +} SessionInfoT; + +//! session ticket data (tls1.3 session tickets only, not supported in previous versions) +typedef struct SessionTicketT +{ + CryptHashTypeE eHashType; + uint32_t uRecvTime; + uint32_t uLifetime; + uint32_t uAgeAdd; + uint32_t uMaxEarlyDataSize; + uint16_t uTickLen; + uint16_t uExtnLen; + uint8_t uNonceLen; + uint8_t aResumeKey[CRYPTHASH_MAXDIGEST]; + uint8_t aTicketNonce[SSL3_SESSION_NONCE_MAX]; + uint8_t aTicketData[SSL3_SESSION_TICKET_MAX]; +} SessionTicketT; + +//! secure session history, used for ssl resume +typedef struct SessionHistoryT +{ + char strHost[256]; //!< hostname for the session + uint16_t uPort; //!< port for the session + uint8_t bSessionTicket; //!< TRUE if session ticket (tls1.3) else tls1.2-style resume info + uint8_t _pad; + uint32_t uSessionUseTick; //!< tick when session was last used + uint32_t uSessionExpTick; //!< tick when session will expire + union + { + SessionInfoT SessionInfo; + SessionTicketT SessionTicket; + }; +} SessionHistoryT; + +//! certificate validation history, used to skip validation of certificates we've already valdiated +typedef struct CertValidHistoryT +{ + uint32_t uCertValidTick; //!< tick when certificate was validated + uint32_t uCertSize; //!< size of certificate data + uint8_t FingerprintId[SSL_FINGERPRINT_SIZE]; //!< certificate fingerprint +} CertValidHistoryT; + +//! alpn extension protocol information +typedef struct AlpnProtocolT +{ + uint8_t uLength; + uint8_t _pad[3]; + char strName[256]; +} AlpnProtocolT; + +//! signature algorithm information +typedef struct SignatureAlgorithmT +{ + uint8_t uHashId; + uint8_t uSigAlg; +} SignatureAlgorithmT; + +//! signature scheme +typedef struct SignatureSchemeT +{ + uint16_t uIdent; //!< two-byte signature scheme identifier + SignatureAlgorithmT SigAlg; //!< hash and signature algorithm used by the signature scheme + uint8_t uVerifyScheme; //!< verification algorithm (RSA signature schemes only) + uint8_t uOidType; //!< certificate oid used for signature scheme selection +} SignatureSchemeT; + +//! signature generate/verify data for async op +typedef struct SignatureVerifyT +{ + const SignatureSchemeT *pSigScheme; + const X509PrivateKeyT *pPrivateKey; + uint8_t DsaContext[CRYPTCURVE_DSA_MAXSTATE]; + uint8_t aSigData[SSL_SIG_MAX]; + int32_t iSigSize; + uint8_t aHashDigest[CRYPTHASH_MAXDIGEST]; + int32_t iHashSize; + CryptHashTypeE eHashType; + uint8_t aKeyData[128]; + int32_t iKeySize; + int32_t iNextState; + uint8_t bEccContextInitialized; + uint8_t _pad[3]; +} SignatureVerifyT; + +//! extra state information required for secure connections (not allocated for unsecure connections) +typedef struct SecureStateT +{ + uint64_t uSendSeqn; //!< send sequence number + uint64_t uRecvSeqn; //!< recv sequence number + + uint32_t uTimer; //!< base of setup timing + + int32_t iSendProg; //!< progress within send + int32_t iSendSize; //!< total bytes to send + + int32_t iRecvHead; //!< progress receiving packet header + int32_t iRecvProg; //!< progress receiving packet data + int32_t iRecvSize; //!< total bytes to receive (tls record size) + int32_t iRecvBase; //!< progress decrypting packet data + int32_t iRecvHshkProg; //!< progress processing handshake messages + int32_t iRecvHshkSize; //!< size of current handshake message being processed (including header) + + const CipherSuiteT *pCipher; //!< selected cipher suite + const EllipticCurveT *pEllipticCurve; //!< selected elliptic curve + const SignatureSchemeT *pSigScheme; //!< selected signature scheme + uint32_t uSentCiphers; //!< bitmask of ciphers that were sent in ClientHello + uint16_t uRetryCipher; //!< cipher ident sent by HelloRetryRequest (tls1.3) + uint16_t _pad16; + + SignatureVerifyT SigVerify; + + uint8_t ClientRandom[32]; //!< clients random seed + uint8_t ServerRandom[32]; //!< servers random seed + uint8_t SessionId[SSL_SESSID_SIZE]; //!< session id + uint16_t uSslVersion; //!< ssl version of connection + uint16_t uSslClientVersion; //!< client-requested ssl version (validated in premaster secret) + + uint8_t bSessionResume; //!< true if session is resumed during handshaking + uint8_t bSendSecure; //!< true if sending secure + uint8_t bRecvSecure; //!< true if receiving secure + uint8_t bDateVerifyFailed; //!< true if date verification of a cert in chain failed + uint8_t bRSAContextInitialized; //!< true if we have initialized the RSAContext + uint8_t bEccContextInitialized; //!< true if we have initialized the EccContext + uint8_t bEccKeyGenerated; //!< true if an Ecc Public Key has been generated + uint8_t bSigGenerated; //!< true if signature source has been generated + uint8_t bRecvProc; //!< true if packet has been processed by _RecvPacket() + uint8_t bHelloRetry; //!< true if we're in a HelloRetryRequest flow (tls1.3) + uint8_t bSentCert; //!< true if a certificate was sent in handshaking + uint8_t bRecvCert; //!< true if a certificate was received in handshaking + uint8_t bSentSessionId; //!< true if we sent a session identifier in clienthello + uint8_t bRenegotiationInfo; //!< true if renegotation info was sent by peer + uint8_t uPubKeyLength; //!< length of pubkey used for ECDHE key exchange + int8_t iCurMsg; //!< current handshake message id, used for flow verification + + uint8_t PubKey[256]; //!< peer's public key, used in ECDHE key exchange (note that in the server flow, the server's public key lives here temporarily) + uint8_t PreMasterKey[48]; //!< pre-master-key + uint8_t MasterKey[48]; //!< master key + uint8_t PreSharedKey[CRYPTHASH_MAXDIGEST]; //!< PSK for tls1.3, used in resume flow + + CryptRSAT RSAContext; + + uint8_t KeyBlock[SSL_KEYBLOCK_LEN]; //!< key block + uint8_t *pServerMAC; //!< server mac secret + uint8_t *pClientMAC; //!< client mac secret + uint8_t *pServerKey; //!< server key secret + uint8_t *pClientKey; //!< client key secret + uint8_t *pServerInitVec; //!< init vector (CBC ciphers) + uint8_t *pClientInitVec; //!< init vector (CBC ciphers) + uint8_t *pServerSecret; //!< tls1.3 server early/handshake/application/... secret + uint8_t *pClientSecret; //!< tls1.3 client early/handshake/application/... secret + uint8_t *pResumeSecret; //!< tls1.3 resumption secret + + const uint8_t *pCookie; //!< pointer to cookie in receive buffer (tls1.3 extension) + + CryptMD5T HandshakeMD5; //!< MD5 of all handshake data + CryptSha1T HandshakeSHA; //!< SHA of all handshake data + CryptSha2T HandshakeSHA256; //!< SHA256 of all handshake data + CryptSha2T HandshakeSHA384; //!< SHA384 of all handshake data + CryptSha2T HandshakeSHA512; //!< SHA512 of all handshake data + uint8_t aFinishHash[CRYPTHASH_MAXDIGEST]; //!< handshake data storage for tls1.3 flow + + uint8_t aClientHelloHash[CRYPTSHA256_HASHSIZE]; //!< hash of sent clienthello; used in HelloRetryRequest flow + + CryptAesT ReadAes; //!< aes read cipher state + CryptAesT WriteAes; //!< aes write cipher state + + CryptGcmT ReadGcm; //!< gcm read cipher state + CryptGcmT WriteGcm; //!< gcm write cipher state + + CryptChaChaT ReadChaCha; //!< chacha read cipher state + CryptChaChaT WriteChaCha; //!< chacha write cipher state + + X509CertificateT Cert; //!< the x509 certificate + + uint8_t EccContext[CRYPTCURVE_DH_MAXSTATE]; //!< elliptic curve state + + char strAlpnProtocol[256]; //!< protocol negotiated using the alpn extension + + uint8_t RecvHead[SSL_MIN_PACKET]; //!< buffer to receive the SSL packet header + uint8_t SendData[SSL_SNDMAX_PACKET]; //!< put at end to make references efficient, include space for debug fence + uint8_t RecvData[SSL_RCVMAX_PACKET]; //!< put at end to make references efficient, include space for debug fence +} SecureStateT; + +//! module state +struct ProtoSSLRefT +{ + SocketT *pSock; //!< comm socket + HostentT *pHost; //!< host entry + + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + NetCritT SecureCrit; //!< for guarding multithreaded access to secure state + + char strHost[256]; //!< host that we connect to. + struct sockaddr PeerAddr; //!< peer info + struct sockaddr LocalAddr; //!< cached value of our local address being used, some connections are very short lived making it otherwise difficult to read the local addr info reliably + + AsyncOpInfoT AsyncInfo; //!< info for async op execution + + int32_t iState; //!< protocol state + int32_t iClosed; //!< socket closed flag + SecureStateT *pSecure; //!< secure state reference + X509CertificateT *pCertToVal; //!< server cert to be validated (used in ST_WAIT_CA state) + ProtoSSLCertInfoT CertInfo; //!< certificate info (used on failure) + CertificateDataT *pCertificate; //!< server/client certificate + char *pPrivateKey; //!< server/client private key + int32_t iPrivateKeyLen; //!< private key length + + uint32_t uEnabledCiphers; //!< enabled ciphers + uint32_t uEnabledCurves; //!< enabled curves + int32_t iRecvBufSize; //!< TCP recv buffer size; 0=default + int32_t iSendBufSize; //!< TCP send buffer size; 0=default + int32_t iLastSocketError; //!< Last socket error before closing the socket + int32_t iCARequestId; //!< CA request id (valid if positive) + + int32_t iMaxSendRate; //!< max send rate (0=uncapped) + int32_t iMaxRecvRate; //!< max recv rate (0=uncapped) + + uint16_t uSslVersion; //!< ssl version application wants us to use + uint16_t uSslVersionMin; //!< minimum ssl version application will accept + + uint8_t bAllowAnyCert; //!< bypass certificate validation + uint8_t bSessionResumeEnabled; //!< trueif session resume is enabled (default) + uint8_t bServer; //!< true if server, else client + uint8_t bReuseAddr; //!< if true set SO_REUSEADDR + uint8_t bNoDelay; //!< if true set TCP_NODELAY on the socket + uint8_t bKeepAlive; //!< if true override tcp keep-alive (disabled by default) + uint8_t bAsyncRecv; //!< if true enable async receive on the ssl socket + int8_t iClientCertLevel; //!< 0=no client cert required; 1=client cert requested, 2=client cert required + uint8_t uHelloExtn; //!< enabled ClientHello extensions + int8_t iCurveDflt; //!< default elliptic curve to use in ClientHello (tls1.3) + int8_t iVerbose; //!< spam level + + uint8_t bCertInfoSet; //!< true if cert info has been set + uint8_t uAlertLevel; //!< level of most recent alert + uint8_t uAlertValue; //!< value of most recent alert + uint8_t bAlertSent; //!< true if most recent alert was sent, else false if it was received + uint8_t _pad; + + uint32_t uKeepAliveTime; //!< tcp keep-alive time; 0=default + + /* for alpn extension; + on the client: these are the protocol preferences + on the server: these are the protocols it supports */ + AlpnProtocolT aAlpnProtocols[SSL_ALPN_MAX_PROTOCOLS]; + uint16_t uNumAlpnProtocols; //!< the number of alpn protocols in the list + uint16_t uAlpnExtensionLength; //!< the size of the list we encode in ClientHello (we calculate when we build the list) +}; + +//! global state +typedef struct ProtoSSLStateT +{ + //! critical section for locking access to state memory + NetCritT StateCrit; + + //! previous session info, used for secure session share/resume + SessionHistoryT SessionHistory[SSL_SESSHIST_MAX]; + + //! validated certificate info + CertValidHistoryT CertValidHistory[SSL_CERTVALID_MAX]; + + // allocation identifiers + int32_t iMemGroup; + void *pMemGroupUserData; + + /* global default settings */ + + int32_t iDfltVers; //!< global version setting + int32_t iDfltMinVers; //!< global min version setting + int32_t iDfltCiph; //!< global cipher setting + int32_t iDfltCurves; //!< global curve setting +}ProtoSSLStateT; + +/*** Function Prototypes **********************************************************/ + +// issue a CA request +static int32_t _ProtoSSLInitiateCARequest(ProtoSSLRefT *pState); + +// update recv handshake hash +static void _ProtoSSLRecvHandshakeFinish(ProtoSSLRefT *pState); + +// set async execution +static int32_t _ProtoSSLUpdateSetAsyncState(ProtoSSLRefT *pState, AsyncOpT *pAsyncOp, int32_t iNextState, int32_t iFailState, int32_t iFailAlert); + +/*** Variables ********************************************************************/ + +static ProtoSSLStateT *_ProtoSSL_pState = NULL; + +//! ServerRandom value that identifies a ServerHello as a HelloRetryRequest +static const uint8_t _SSL3_HelloRetryRequestRandom[] = +{ + 0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11, 0xbe, 0x1d, 0x8c, 0x02, 0x1e, 0x65, 0xb8, 0x91, + 0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb, 0x8c, 0x5e, 0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c +}; + +//! ServerRandom trailing eight bytes that identify an illegitimate downgrade from tls1.3 to tls1.2 +static const uint8_t _SSL3_ServerRandomDowngrade12[] = +{ + 0x44, 0x4f, 0x57, 0x4e, 0x47, 0x52, 0x44, 0x01 +}; +//! ServerRandom trailing eight bytes that identify an illegitimate downgrade from tls1.3 to tls1.1 or previous +static const uint8_t _SSL3_ServerRandomDowngrade11[] = +{ + 0x44, 0x4f, 0x57, 0x4e, 0x47, 0x52, 0x44, 0x00 +}; + +//! supported ssl cipher suites in order of preference; see http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4 +static const CipherSuiteT _SSL3_CipherSuite[] = +{ + // TLS1.3 cipher suites + { 0x1301, SSL3_TLS1_3, SSL3_KEY_NULL, SSL3_KEYLEN_128, SSL3_SIGALG_NONE, SSL3_ENC_GCM, SSL3_MAC_NULL, CRYPTHASH_NULL, 12, SSL3_PRF_SHA256, PROTOSSL_CIPHER_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256" }, // TLS1.3 suite 2: gcm128+sha256 + { 0x1302, SSL3_TLS1_3, SSL3_KEY_NULL, SSL3_KEYLEN_256, SSL3_SIGALG_NONE, SSL3_ENC_GCM, SSL3_MAC_NULL, CRYPTHASH_NULL, 12, SSL3_PRF_SHA384, PROTOSSL_CIPHER_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384" }, // TLS1.3 suite 1: gcm256+sha384 + { 0x1303, SSL3_TLS1_3, SSL3_KEY_NULL, SSL3_KEYLEN_256, SSL3_SIGALG_NONE, SSL3_ENC_CHACHA, SSL3_MAC_NULL, CRYPTHASH_NULL, 12, SSL3_PRF_SHA256, PROTOSSL_CIPHER_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256" }, // TLS1.3 suite 3: chacha20_poly1305+sha256 + // TLS1.2 AEAD cipher suites + { 0xc02b, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_128, SSL3_SIGALG_ECDSA, SSL3_ENC_GCM, SSL3_MAC_NULL, CRYPTHASH_NULL, 4, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" }, // ecdhe+ecdsa+gcm+sha256 + { 0xc02c, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256, SSL3_SIGALG_ECDSA, SSL3_ENC_GCM, SSL3_MAC_NULL, CRYPTHASH_NULL, 4, SSL3_PRF_SHA384, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" }, // ecdhe+ecdsa+gcm+sha384 + { 0xcca9, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256, SSL3_SIGALG_ECDSA, SSL3_ENC_CHACHA, SSL3_MAC_NULL, CRYPTHASH_NULL, 12, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" }, // ecdhe+ecdsa+chacha+sha256 + { 0xc02f, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_128, SSL3_SIGALG_RSA, SSL3_ENC_GCM, SSL3_MAC_NULL, CRYPTHASH_NULL, 4, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" }, // ecdhe+rsa+gcm+sha256 + { 0xc030, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256, SSL3_SIGALG_RSA, SSL3_ENC_GCM, SSL3_MAC_NULL, CRYPTHASH_NULL, 4, SSL3_PRF_SHA384, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" }, // ecdhe+rsa+gcm+sha384 + { 0xcca8, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256, SSL3_SIGALG_RSA, SSL3_ENC_CHACHA, SSL3_MAC_NULL, CRYPTHASH_NULL, 12, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" }, // ecdhe+rsa+chacha+sha256 + { 0x009c, SSL3_TLS1_2, SSL3_KEY_RSA, SSL3_KEYLEN_128, SSL3_SIGALG_RSA, SSL3_ENC_GCM, SSL3_MAC_NULL, CRYPTHASH_NULL, 4, SSL3_PRF_SHA256, PROTOSSL_CIPHER_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256" }, // suite 156: rsa+gcm+sha256 + { 0x009d, SSL3_TLS1_2, SSL3_KEY_RSA, SSL3_KEYLEN_256, SSL3_SIGALG_RSA, SSL3_ENC_GCM, SSL3_MAC_NULL, CRYPTHASH_NULL, 4, SSL3_PRF_SHA384, PROTOSSL_CIPHER_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384" }, // suite 157: rsa+gcm+sha384 + // TLS1.2 cipher suites + { 0xc023, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_128, SSL3_SIGALG_ECDSA, SSL3_ENC_AES, SSL3_MAC_SHA256, CRYPTHASH_SHA256, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" }, // ecdhe+ecdsa+aes+sha256 + { 0xc024, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256, SSL3_SIGALG_ECDSA, SSL3_ENC_AES, SSL3_MAC_SHA384, CRYPTHASH_SHA384, 16, SSL3_PRF_SHA384, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384" }, // ecdhe+ecdsa+aes+sha384 + { 0xc027, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_128, SSL3_SIGALG_RSA, SSL3_ENC_AES, SSL3_MAC_SHA256, CRYPTHASH_SHA256, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" }, // ecdhe+rsa+aes+sha256 + { 0xc028, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256, SSL3_SIGALG_RSA, SSL3_ENC_AES, SSL3_MAC_SHA384, CRYPTHASH_SHA384, 16, SSL3_PRF_SHA384, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" }, // ecdhe+rsa+aes+sha384 + { 0x003c, SSL3_TLS1_2, SSL3_KEY_RSA, SSL3_KEYLEN_128, SSL3_SIGALG_RSA, SSL3_ENC_AES, SSL3_MAC_SHA256, CRYPTHASH_SHA256, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256" }, // suite 60: rsa+aes+sha256 + { 0x003d, SSL3_TLS1_2, SSL3_KEY_RSA, SSL3_KEYLEN_256, SSL3_SIGALG_RSA, SSL3_ENC_AES, SSL3_MAC_SHA256, CRYPTHASH_SHA256, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_RSA_WITH_AES_256_CBC_SHA256, "TLS_RSA_WITH_AES_256_CBC_SHA256" }, // suite 61: rsa+aes+sha256 + // TLS1.0 cipher suites + { 0xc009, SSL3_TLS1_0, SSL3_KEY_ECDHE, SSL3_KEYLEN_128, SSL3_SIGALG_ECDSA, SSL3_ENC_AES, SSL3_MAC_SHA, CRYPTHASH_SHA1, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" }, // ecdhe+ecdsa+aes+sha + { 0xc00a, SSL3_TLS1_0, SSL3_KEY_ECDHE, SSL3_KEYLEN_256, SSL3_SIGALG_ECDSA, SSL3_ENC_AES, SSL3_MAC_SHA, CRYPTHASH_SHA1, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" }, // ecdhe+ecdsa+aes+sha + { 0xc013, SSL3_TLS1_0, SSL3_KEY_ECDHE, SSL3_KEYLEN_128, SSL3_SIGALG_RSA, SSL3_ENC_AES, SSL3_MAC_SHA, CRYPTHASH_SHA1, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" }, // ecdhe+rsa+aes+sha + { 0xc014, SSL3_TLS1_0, SSL3_KEY_ECDHE, SSL3_KEYLEN_256, SSL3_SIGALG_RSA, SSL3_ENC_AES, SSL3_MAC_SHA, CRYPTHASH_SHA1, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" }, // ecdhe+rsa+aes+sha + // SSLv3 cipher suites + { 0x002f, SSL3_SSLv3, SSL3_KEY_RSA, SSL3_KEYLEN_128, SSL3_SIGALG_RSA, SSL3_ENC_AES, SSL3_MAC_SHA, CRYPTHASH_SHA1, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA" }, // suite 47: rsa+aes+sha + { 0x0035, SSL3_SSLv3, SSL3_KEY_RSA, SSL3_KEYLEN_256, SSL3_SIGALG_RSA, SSL3_ENC_AES, SSL3_MAC_SHA, CRYPTHASH_SHA1, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA" }, // suite 53: rsa+aes+sha +}; + +//! supported elliptic curves; see http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 +static const EllipticCurveT _SSL3_EllipticCurves[SSL3_NUM_CURVES] = +{ + // curve 23: P-256 + { CRYPTCURVE_SECP256R1, PROTOSSL_CURVE_SECP256R1, "SECP256R1" }, + // curve 29: X25519 + { CRYPTCURVE_X25519, PROTOSSL_CURVE_X25519, "X25519" }, + // curve 24: P-384 + { CRYPTCURVE_SECP384R1, PROTOSSL_CURVE_SECP384R1, "SECP384R1" }, + // curve 30: X448 + { CRYPTCURVE_X448, PROTOSSL_CURVE_X448, "X448" } +}; + +//! signature schemes; see https://tools.ietf.org/html/rfc8446#section-4.2.3 +static const SignatureSchemeT _SSL3_SignatureSchemes[] = +{ + { SSL3_SIGSCHEME_ECDSA_SHA256, { SSL3_HASHID_SHA256, SSL3_SIGALG_ECDSA }, SSL3_SIGVERIFY_NONE, ASN_OBJ_ECDSA_SECP256R1 }, + { SSL3_SIGSCHEME_RSA_PSS_PSS_SHA256, { SSL3_HASHID_SHA256, SSL3_SIGALG_RSA }, SSL3_SIGVERIFY_RSA_PSS, ASN_OBJ_RSASSA_PSS }, + { SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA256, { SSL3_HASHID_SHA256, SSL3_SIGALG_RSA }, SSL3_SIGVERIFY_RSA_PSS, ASN_OBJ_RSA_PKCS_KEY }, + { SSL3_SIGSCHEME_RSA_PKCS1_SHA256, { SSL3_HASHID_SHA256, SSL3_SIGALG_RSA }, SSL3_SIGVERIFY_RSA_PKCS1, ASN_OBJ_RSA_PKCS_KEY }, + { SSL3_SIGSCHEME_ECDSA_SHA384, { SSL3_HASHID_SHA384, SSL3_SIGALG_ECDSA }, SSL3_SIGVERIFY_NONE, ASN_OBJ_ECDSA_SECP384R1 }, + { SSL3_SIGSCHEME_RSA_PSS_PSS_SHA384, { SSL3_HASHID_SHA384, SSL3_SIGALG_RSA }, SSL3_SIGVERIFY_RSA_PSS, ASN_OBJ_RSASSA_PSS }, + { SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA384, { SSL3_HASHID_SHA384, SSL3_SIGALG_RSA }, SSL3_SIGVERIFY_RSA_PSS, ASN_OBJ_RSA_PKCS_KEY }, + { SSL3_SIGSCHEME_RSA_PKCS1_SHA384, { SSL3_HASHID_SHA384, SSL3_SIGALG_RSA }, SSL3_SIGVERIFY_RSA_PKCS1, ASN_OBJ_RSA_PKCS_KEY }, + { SSL3_SIGSCHEME_RSA_PSS_PSS_SHA512, { SSL3_HASHID_SHA512, SSL3_SIGALG_RSA }, SSL3_SIGVERIFY_RSA_PSS, ASN_OBJ_RSASSA_PSS }, + { SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA512, { SSL3_HASHID_SHA512, SSL3_SIGALG_RSA }, SSL3_SIGVERIFY_RSA_PSS, ASN_OBJ_RSA_PKCS_KEY }, + { SSL3_SIGSCHEME_RSA_PKCS1_SHA512, { SSL3_HASHID_SHA512, SSL3_SIGALG_RSA }, SSL3_SIGVERIFY_RSA_PKCS1, ASN_OBJ_RSA_PKCS_KEY }, + { SSL3_SIGSCHEME_ECDSA_SHA1, { SSL3_HASHID_SHA1, SSL3_SIGALG_ECDSA }, SSL3_SIGVERIFY_NONE, ASN_OBJ_ECDSA_KEY }, + { SSL3_SIGSCHEME_RSA_PKCS1_SHA1, { SSL3_HASHID_SHA1, SSL3_SIGALG_RSA }, SSL3_SIGVERIFY_RSA_PKCS1, ASN_OBJ_RSA_PKCS_KEY } +}; + +//! handshake transition validation map for TLS1.2 and prior clients; see https://tools.ietf.org/html/rfc5246#section-7.3 +static const SSLHandshakeMapT _SSL3_ClientRecvMsgMap[] = +{ + { SSL3_MSG_CLIENT_HELLO, SSL3_MSG_SERVER_HELLO }, + { SSL3_MSG_SERVER_HELLO, SSL3_MSG_CERTIFICATE }, + { SSL3_MSG_SERVER_HELLO, SSL3_MSG_FINISHED }, + { SSL3_MSG_CERTIFICATE, SSL3_MSG_SERVER_KEY }, + { SSL3_MSG_CERTIFICATE, SSL3_MSG_CERT_REQ }, + { SSL3_MSG_CERTIFICATE, SSL3_MSG_SERVER_DONE }, + { SSL3_MSG_SERVER_KEY, SSL3_MSG_CERT_REQ }, + { SSL3_MSG_SERVER_KEY, SSL3_MSG_SERVER_DONE }, + { SSL3_MSG_CERT_REQ, SSL3_MSG_SERVER_DONE }, + { SSL3_MSG_SERVER_DONE, SSL3_MSG_FINISHED }, + { -1, -1 } +}; +//! handshake transition validation map for TLS1.2 and prior servers +static const SSLHandshakeMapT _SSL3_ServerRecvMsgMap[] = +{ + { 0, SSL3_MSG_CLIENT_HELLO }, + { SSL3_MSG_CLIENT_HELLO, SSL3_MSG_CERTIFICATE }, + { SSL3_MSG_CLIENT_HELLO, SSL3_MSG_CLIENT_KEY }, + { SSL3_MSG_CLIENT_HELLO, SSL3_MSG_FINISHED }, + { SSL3_MSG_CERTIFICATE, SSL3_MSG_CLIENT_KEY }, + { SSL3_MSG_CLIENT_KEY, SSL3_MSG_CERT_VERIFY }, + { SSL3_MSG_CLIENT_KEY, SSL3_MSG_FINISHED }, + { SSL3_MSG_CERT_VERIFY, SSL3_MSG_FINISHED }, + { -1, -1 } +}; + +//! handshake transition validation map for TLS1.3 clients; see https://tools.ietf.org/html/rfc8446#section-2 +static const SSLHandshakeMapT _SSL3_ClientRecvMsgMap_13[] = +{ + { SSL3_MSG_CLIENT_HELLO, SSL3_MSG_SERVER_HELLO }, + { SSL3_MSG_SERVER_HELLO, SSL3_MSG_ENCRYPTED_EXTENSIONS }, + { SSL3_MSG_SERVER_HELLO, SSL3_MSG_FINISHED }, + { SSL3_MSG_ENCRYPTED_EXTENSIONS, SSL3_MSG_CERT_REQ }, + { SSL3_MSG_ENCRYPTED_EXTENSIONS, SSL3_MSG_CERTIFICATE }, + { SSL3_MSG_ENCRYPTED_EXTENSIONS, SSL3_MSG_FINISHED }, + { SSL3_MSG_CERT_REQ, SSL3_MSG_CERTIFICATE }, + { SSL3_MSG_CERTIFICATE, SSL3_MSG_CERT_VERIFY }, + { SSL3_MSG_CERT_VERIFY, SSL3_MSG_FINISHED }, + { SSL3_MSG_FINISHED, SSL3_MSG_NEW_SESSION_TICKET }, + { SSL3_MSG_NEW_SESSION_TICKET, SSL3_MSG_NEW_SESSION_TICKET }, + { -1, -1 } +}; +//! handshake transition validation map for TLS1.3 servers +static const SSLHandshakeMapT _SSL3_ServerRecvMsgMap_13[] = +{ + { 0, SSL3_MSG_CLIENT_HELLO }, + { SSL3_MSG_CLIENT_HELLO, SSL3_MSG_CLIENT_HELLO }, + { SSL3_MSG_CLIENT_HELLO, SSL3_MSG_CERTIFICATE }, + { SSL3_MSG_CLIENT_HELLO, SSL3_MSG_FINISHED }, + { SSL3_MSG_CERTIFICATE, SSL3_MSG_CERT_VERIFY }, + { SSL3_MSG_CERTIFICATE, SSL3_MSG_FINISHED }, + { SSL3_MSG_CERT_VERIFY, SSL3_MSG_FINISHED }, + { -1, -1 } +}; + +//! .pem certificate signature identifiers; see https://tools.ietf.org/html/rfc7468 +static const CertificateSignatureT _SSL3_CertSignatures[] = +{ + { "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----", ASN_OBJ_RSA_PKCS_KEY }, // pkcs#1 certificate + { "-----BEGIN X509 CERTIFICATE-----", "-----END X509 CERTIFICATE-----", ASN_OBJ_RSA_PKCS_KEY }, // pkcs#1 certificate (alternate/obsolete form) + { "-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----", ASN_OBJ_RSA_PKCS_KEY }, // pkcs#1 private key + { "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----", ASN_OBJ_RSASSA_PSS }, // pkcs#8 private key (rsa-pss) + { "-----BEGIN EC PRIVATE KEY-----", "-----END EC PRIVATE KEY-----", ASN_OBJ_ECDSA_KEY } // pkcs#8 private key (ecdsa) +}; + +//! translation table for SSL3_HASHID_* to CryptHashE type +static const CryptHashTypeE _SSL3_HashIdToCrypt[SSL3_HASHID_MAX+1] = +{ + CRYPTHASH_NULL, // SSL3_HASHID_NONE + CRYPTHASH_MD5, // SSL3_HASHID_MD5 + CRYPTHASH_SHA1, // SSL3_HASHID_SHA1 + CRYPTHASH_SHA224, // SSL3_HASHID_SHA224 + CRYPTHASH_SHA256, // SSL3_HASHID_SHA256 + CRYPTHASH_SHA384, // SSL3_HASHID_SHA384 + CRYPTHASH_SHA512 // SSL3_hashid_sha512 +}; + +//! translation table for SSL3_HASHID_* to ASN hash object identifier; ASN_OBJ_NONE means unsupported +static const uint32_t _SSL3_CrypttoASN[CRYPTHASH_NUMHASHES] = +{ + ASN_OBJ_NONE, // CRYPTHASH_NULL + ASN_OBJ_NONE, // CRYPTHASH_MURMUR3 + ASN_OBJ_MD5, // CRYPTHASH_MD5 + ASN_OBJ_SHA1, // CRYPTHASH_SHA1 + ASN_OBJ_NONE, // CRYPTHASH_SHA224 + ASN_OBJ_SHA256, // CRYPTHASH_SHA256 + ASN_OBJ_SHA384, // CRYPTHASH_SHA384 + ASN_OBJ_SHA512 // CRYPTHASH_SHA512 +}; + +//! ssl version name table +static const char *_SSL3_strVersionNames[] = +{ + "SSLv3", // defined here to retain easy version number to name translation, do not remove + "TLSv1", + "TLSv1.1", + "TLSv1.2", + "TLSv1.3" +}; + +// signature types - subtract type from ASN_OBJ_RSA_PKCS_MD5 to get offset for this table +static const char *_SSL3_strSignatureTypes[] = +{ + "md5WithRSAEncryption", + "sha1WithRSAEncryption", + "sha256WithRSAEncryption", + "sha384WithRSAEncryption", + "sha512WithRSAEncryption", + "ecdsa-with-SHA256", + "ecdsa-with-SHA384", + "ecdsa-with-SHA512" +}; + +#if DIRTYCODE_LOGGING +#if DEBUG_ALL_OBJS +static const char *_SSL3_strAsnTypes[] = +{ + "ASN_00", + "ASN_TYPE_BOOLEAN", // 0x01 + "ASN_TYPE_INTEGER", // 0x02 + "ASN_TYPE_BITSTRING", // 0x03 + "ASN_TYPE_OCTSTRING", // 0x04 + "ASN_TYPE_NULL", // 0x05 + "ASN_TYPE_OBJECT", // 0x06 + "ASN_07", "ASN_08", "ASN_09", + "ASN_0A", "ASN_0B", + "ASN_TYPE_UTF8STR", // 0x0c + "ASN_0D", "ASN_0E", "ASN_0F", + "ASN_TYPE_SEQN", // 0x10 + "ASN_TYPE_SET", // 0x11 + "ASN_12", // 0x12 + "ASN_TYPE_PRINTSTR", // 0x13 + "ASN_TYPE_T61", // 0x14 (Teletex string) + "ASN_15", + "ASN_TYPE_IA5", // 0x16 (IA5 string) + "ASN_TYPE_UTCTIME", // 0x17 + "ASN_TYPE_GENERALIZEDTIME", // 0x18 + "ASN_19", "ASN_1A", "ASN_1B", + "ASN_1C", "ASN_1D", + "ASN_TYPE_UNICODESTR", // 0x1e + "ASN_1F", +}; +static const char *_SSL3_strAsnObjs[ASN_OBJ_COUNT] = +{ + "ASN_OBJ_NONE", + "ASN_OBJ_COUNTRY", + "ASN_OBJ_STATE", + "ASN_OBJ_CITY", + "ASN_OBJ_ORGANIZATION", + "ASN_OBJ_UNIT", + "ASN_OBJ_COMMON", + "ASN_OBJ_SUBJECT_ALT", + "ASN_OBJ_BASIC_CONSTRAINTS", + "ASN_OBJ_MD5", + "ASN_OBJ_SHA1", + "ASN_OBJ_SHA256", + "ASN_OBJ_SHA384", + "ASN_OBJ_SHA512", + "ASN_OBJ_RSA_PKCS_KEY", + "ASN_OBJ_ECDSA_KEY", + "ASN_OBJ_RSA_PKCS_MD5", + "ASN_OBJ_RSA_PKCS_SHA1", + "ASN_OBJ_RSA_PKCS_SHA256", + "ASN_OBJ_RSA_PKCS_SHA384", + "ASN_OBJ_RSA_PKCS_SHA512", + "ASN_OBJ_RSASSA_PSS", + "ASN_OBJ_ECDSA_SHA256", + "ASN_OBJ_ECDSA_SHA384", + "ASN_OBJ_ECDSA_SHA512", + "ASN_OBJ_ECDSA_SECP256R1", + "ASN_OBJ_ECDSA_SECP384R1", + "ASN_OBJ_ECDSA_SECP512R1", + "ASN_OBJ_PKCS1_MGF1" +}; +#endif // DEBUG_ALL_OBJS + +// tls extension names +static const char *_SSL3_strExtensionNames[] = +{ + "server_name", + "max_fragment_length", + "client_certificate_url", + "trusted_ca_keys", + "truncated_hmac", + "status_request", + "user_mapping", + "client_authz", + "server_authz", + "cert_type", + "supported_groups", // (renamed from "elliptic_curves") + "ec_point_formats", + "srp", + "signature_algorithms", + "use_srtp", + "heartbeat", + "application_layer_protocol_negotiation", + "status_request_v2", + "signed_certificate_timestamp", + "client_certificate_type", + "server_certificate_type", + "padding", + "encrypt_then_mac", + "extended_master_secret", + "token_binding", + "cached_info", + "unassigned", "unassigned", "unassigned", "unassigned", "unassigned", "unassigned", "unassigned", "unassigned", "unassigned", + "SessionTicket TLS", + "unassigned", "unassigned", "unassigned", "unassigned", "unassigned", + "pre_shared_key", + "early_data", + "supported_versions", + "cookie", + "psk_key_exchange_modes", + "unassigned", + "certificate_authorities", + "oid_filter", + "post_handshake_auth", + "signature_algorithms_cert", + "key_share", +}; + +#endif // DIRTYCODE_LOGGING + +//! alert description table; see https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml +static const ProtoSSLAlertDescT _ProtoSSL_AlertList[] = +{ + { SSL3_ALERT_DESC_CLOSE_NOTIFY, "close notify" }, // 0 + { SSL3_ALERT_DESC_UNEXPECTED_MESSAGE, "unexpected message" }, // 10 + { SSL3_ALERT_DESC_BAD_RECORD_MAC, "bad record mac" }, // 20 + { SSL3_ALERT_DESC_DECRYPTION_FAILED, "decryption failed" }, // 21 - reserved + { SSL3_ALERT_DESC_RECORD_OVERFLOW, "record overflow" }, // 22 + { SSL3_ALERT_DESC_DECOMPRESSION_FAILURE, "decompression failure" }, // 30 - reserved + { SSL3_ALERT_DESC_HANDSHAKE_FAILURE, "handshake failure" }, // 40 + { SSL3_ALERT_DESC_NO_CERTIFICATE, "no certificate" }, // 41 - reserved + { SSL3_ALERT_DESC_BAD_CERTFICIATE, "bad certificate" }, // 42 + { SSL3_ALERT_DESC_UNSUPPORTED_CERTIFICATE, "unsupported certificate" }, // 43 + { SSL3_ALERT_DESC_CERTIFICATE_REVOKED, "certificate revoked" }, // 44 + { SSL3_ALERT_DESC_CERTIFICATE_EXPIRED, "certificate expired" }, // 45 + { SSL3_ALERT_DESC_CERTIFICATE_UNKNOWN, "certificate unknown" }, // 46 + { SSL3_ALERT_DESC_ILLEGAL_PARAMETER, "illegal parameter" }, // 47 + // the following alert types are all TLS only + { SSL3_ALERT_DESC_UNKNOWN_CA, "unknown ca" }, // 48 + { SSL3_ALERT_DESC_ACCESS_DENIED, "access denied" }, // 49 + { SSL3_ALERT_DESC_DECODE_ERROR, "decode error" }, // 50 + { SSL3_ALERT_DESC_DECRYPT_ERROR, "decrypt error" }, // 51 + { SSL3_ALERT_DESC_EXPORT_RESTRICTION, "export restriction" }, // 60 - reserved + { SSL3_ALERT_DESC_PROTOCOL_VERSION, "protocol version" }, // 70 + { SSL3_ALERT_DESC_INSUFFICIENT_SECURITY, "insufficient security" }, // 71 + { SSL3_ALERT_DESC_INTERNAL_ERROR, "internal error" }, // 80 + { SSL3_ALERT_DESC_INAPPROPRIATE_FALLBACK, "inappropriate fallback" }, // 86 + { SSL3_ALERT_DESC_USER_CANCELLED, "user cancelled" }, // 90 + { SSL3_ALERT_DESC_NO_RENEGOTIATION, "no renegotiation" }, // 100 - reserved + { SSL3_ALERT_DESC_MISSING_EXTENSION, "missing extension" }, // 109 + { SSL3_ALERT_DESC_UNSUPPORTED_EXTENSION, "unsupported extension" }, // 110 + // alert extensions; see http://tools.ietf.org/html/rfc6066#section-9 + { SSL3_ALERT_DESC_CERTIFICATE_UNOBTAINABLE, "certificate unobtainable" }, // 111 + { SSL3_ALERT_DESC_UNRECOGNIZED_NAME, "unrecognized name" }, // 112 + { SSL3_ALERT_DESC_BAD_CERTIFICATE_STATUS, "bad certificate status" }, // 113 + { SSL3_ALERT_DESC_BAD_CERTIFICATE_HASH, "bad certificate hash" }, // 114 + // alert extension; see http://tools.ietf.org/html/rfc4279#section-6 + { SSL3_ALERT_DESC_UNKNOWN_PSK_IDENTITY, "unknown psk identify" }, // 115 + // alert extension; see https://tools.ietf.org/html/rfc8446#section-4.4.2.4 + { SSL3_ALERT_DESC_CERTIFICATE_REQUIRED, "certificate required" }, // 116 + // alert extension; see http://tools.ietf.org/html/rfc7301#section-3.2 + { SSL3_ALERT_DESC_NO_APPLICATION_PROTOCOL, "no application protocol" }, // 120 + { -1, NULL }, // list terminator +}; + +// ASN object identification table +static const struct ASNObjectT _SSL_ObjectList[] = +{ + { ASN_OBJ_COUNTRY, 3, { 0x55, 0x04, 0x06 } }, + { ASN_OBJ_CITY, 3, { 0x55, 0x04, 0x07 } }, + { ASN_OBJ_STATE, 3, { 0x55, 0x04, 0x08 } }, + { ASN_OBJ_ORGANIZATION, 3, { 0x55, 0x04, 0x0a } }, + { ASN_OBJ_UNIT, 3, { 0x55, 0x04, 0x0b } }, + { ASN_OBJ_COMMON, 3, { 0x55, 0x04, 0x03 } }, + { ASN_OBJ_SUBJECT_ALT, 3, { 0x55, 0x1d, 0x11 } }, + { ASN_OBJ_BASIC_CONSTRAINTS, 3, { 0x55, 0x1d, 0x13 } }, + + // OBJ_md5 - OID 1.2.840.113549.2.5 + { ASN_OBJ_MD5, 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05 } }, + // OBJ_sha1 - OID 1.3.14.3.2.26 + { ASN_OBJ_SHA1, 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1a } }, + // OBJ_sha256 - OID 2.16.840.1.101.3.4.2.1 + { ASN_OBJ_SHA256, 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 } }, + // OBJ_sha384 - OID 2.16.840.1.101.3.4.2.2 + { ASN_OBJ_SHA384, 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 } }, + // OBJ_sha512 - OID 2.16.840.1.101.3.4.2.3 + { ASN_OBJ_SHA512, 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 } }, + + // RSA (PKCS #1 v1.5) key transport algorithm, OID 1.2.840.113349.1.1.1 + { ASN_OBJ_RSA_PKCS_KEY, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 } }, + // RSA (PKCS #1 v1.5) with MD5 signature, OID 1.2.840.113549.1.1.4 + { ASN_OBJ_RSA_PKCS_MD5, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04 } }, + // RSA (PKCS #1 v1.5) with SHA-1 signature; sha1withRSAEncryption OID 1.2.840.113549.1.1.5 + { ASN_OBJ_RSA_PKCS_SHA1, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05 } }, + /* the following are obsolete alternate definitions of sha1withRSAEncryption; we define them + here for compatibility, because some certificates are still generated with these ids + (by makecert.exe, included with WindowsSDK, for example) */ + // RSA (PKCS #1 v1.5) with SHA-1 signature; sha-1WithRSAEncryption (obsolete) OID 1.3.14.3.2.29 + { ASN_OBJ_RSA_PKCS_SHA1, 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1d } }, + // RSA (PKCS #1 v1.5) with SHA-1 signature; rsaSignatureWithsha1 (obsolete) OID 1.3.36.3.3.1.2 + { ASN_OBJ_RSA_PKCS_SHA1, 5, { 0x2b, 0x24, 0x03, 0x03, 0x01, 0x02 } }, + + // PKCS-MGF1 1.2.840.113549.1.1.8 + { ASN_OBJ_PKCS1_MGF1, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08 } }, + // RSASSA-PSS PKCS#1 1.2.840.113549.1.1.10 + { ASN_OBJ_RSASSA_PSS, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a } }, + + /* sha2+rsa combinations */ + // RSA (PKCS #1 v1.5) with SHA-256 signature; sha256withRSAEncryption OID 1.2.840.113549.1.1.11 + { ASN_OBJ_RSA_PKCS_SHA256, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b } }, + // RSA (PKCS #1 v1.5) with SHA-384 signature; sha384withRSAEncryption OID 1.2.840.113549.1.1.12 + { ASN_OBJ_RSA_PKCS_SHA384, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c } }, + // RSA (PKCS #1 v1.5) with SHA-512 signature; sha512withRSAEncryption OID 1.2.840.113549.1.1.13 + { ASN_OBJ_RSA_PKCS_SHA512, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d } }, + + // ecdsa key PKCS#8 1.2.840.10045.2.1 + { ASN_OBJ_ECDSA_KEY, 7, { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 } }, + // prime256v1/secp256r1 1.2.840.10045.3.1.7 + { ASN_OBJ_ECDSA_SECP256R1, 8, {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 } }, + // ecdsa-with-SHA256 1.2.840.10045.4.3.2 + { ASN_OBJ_ECDSA_SHA256, 8, {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 } }, + // ecdsa-with-SHA384 1.2.840.10045.4.3.3 + { ASN_OBJ_ECDSA_SHA384, 8, {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03 } }, + // ecdsa-with-SHA512 1.2.840.10045.4.3.4 + { ASN_OBJ_ECDSA_SHA512, 8, {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 } }, + + // prime384v1/secp384r1 1.3.132.0.34 + { ASN_OBJ_ECDSA_SECP384R1, 5, {0x2b, 0x81, 0x04, 0x00, 0x22 } }, + // prime521v1/secp521r1 1.3.132.0.35 + { ASN_OBJ_ECDSA_SECP521R1, 5, {0x2b, 0x81, 0x04, 0x00, 0x23 } }, + + // array terminator + { ASN_OBJ_NONE, 0, { 0 } } +}; + +// The 2048-bit public key modulus for 2015 GOS CA Cert signed with sha256 and an exponent of 65537 +static const uint8_t _ProtoSSL_GOS2015ServerModulus2048[] = +{ + 0xcc, 0x6d, 0x54, 0xb6, 0xf4, 0xe4, 0x84, 0xe7, 0x20, 0x76, 0x02, 0xd7, 0x97, 0x48, 0x75, 0x7e, + 0x7e, 0xfb, 0x43, 0x9c, 0x3d, 0xa1, 0x96, 0x47, 0xc1, 0x5d, 0x07, 0x8b, 0x30, 0x73, 0xbf, 0x9d, + 0xfe, 0x75, 0x94, 0x55, 0x21, 0xd0, 0x88, 0x74, 0x66, 0x4c, 0xa2, 0xb7, 0xfe, 0x9f, 0xc0, 0x3b, + 0xf0, 0x60, 0xa0, 0xdb, 0x08, 0x33, 0x2b, 0x6e, 0xf8, 0x02, 0x05, 0xb9, 0x87, 0x9d, 0xac, 0x65, + 0xd5, 0x06, 0x9d, 0x05, 0xe8, 0xd1, 0xb6, 0xf5, 0xde, 0x7d, 0xa5, 0xa4, 0x7d, 0x8a, 0xcb, 0x99, + 0x31, 0xb6, 0x85, 0x9b, 0xa2, 0xce, 0x39, 0xe2, 0x8c, 0x65, 0xaa, 0x07, 0xfc, 0x15, 0x33, 0x07, + 0x00, 0xd1, 0x72, 0x15, 0x13, 0x0d, 0x87, 0x0f, 0x5c, 0xa2, 0x5e, 0xd0, 0xd5, 0xbf, 0xd9, 0x03, + 0x32, 0x62, 0xaf, 0xf5, 0xef, 0x53, 0x36, 0xa8, 0x34, 0xda, 0xb6, 0xa3, 0xec, 0x5c, 0x6a, 0xc0, + 0x67, 0xf8, 0xbe, 0x37, 0x9f, 0xb3, 0xc8, 0x2d, 0xf0, 0x36, 0x4a, 0x6f, 0x6b, 0x06, 0xee, 0xb7, + 0x85, 0xf2, 0x7f, 0x73, 0x6c, 0x01, 0x84, 0x83, 0xe4, 0xda, 0x46, 0xd0, 0x23, 0x9a, 0x6d, 0xf1, + 0x77, 0x7c, 0x05, 0x81, 0x90, 0x4f, 0x6a, 0x44, 0x83, 0x78, 0x3b, 0x71, 0xad, 0x12, 0xc0, 0x48, + 0xc8, 0x73, 0x89, 0xf1, 0x98, 0x78, 0x7b, 0xb4, 0x08, 0x4a, 0xba, 0xe8, 0x59, 0x57, 0xe2, 0xfc, + 0x29, 0xac, 0xbf, 0xf5, 0xa2, 0x9d, 0x4f, 0x2c, 0x64, 0xdc, 0xd7, 0x92, 0x19, 0x1c, 0xc5, 0xfa, + 0xdb, 0x92, 0xc0, 0x90, 0x4b, 0xa8, 0xe9, 0xf2, 0x0d, 0x94, 0x1a, 0xb2, 0x5f, 0xdd, 0x33, 0xae, + 0xff, 0x66, 0x90, 0x97, 0xb2, 0xa8, 0xa5, 0x1b, 0xfa, 0x6f, 0x41, 0xb2, 0x84, 0xba, 0x52, 0x34, + 0x97, 0x4a, 0xd3, 0xc7, 0xb2, 0x3f, 0xdd, 0xdb, 0xc9, 0xb1, 0x13, 0x82, 0x77, 0xe8, 0x6a, 0xcd +}; + +// the 256 bit public key curve data for 2019 GS CA cert signed with sha384 +static const uint8_t _ProtoSSL_GS2019ServerKey[] = +{ + 0x04, 0x13, 0x3f, 0x21, 0x93, 0x86, 0xf7, 0x65, 0xc4, 0x7f, 0x8c, 0x1c, 0xef, 0x49, 0xa8, 0x2a, + 0x32, 0xe3, 0x6c, 0xd4, 0xd0, 0x12, 0x9a, 0x1e, 0x18, 0x10, 0xce, 0xd3, 0xb0, 0xd8, 0x1e, 0xff, + 0xcc, 0x5b, 0x73, 0x5b, 0xc7, 0x5b, 0xeb, 0x0b, 0xf4, 0x06, 0x04, 0x0e, 0x1c, 0x27, 0xd6, 0x87, + 0x31, 0xde, 0x68, 0x7d, 0xdb, 0xfa, 0x03, 0x32, 0x89, 0x2a, 0x30, 0x5d, 0xb3, 0xf8, 0xc2, 0xeb, + 0xa4 +}; + +// The 2048-bit modulus for the VeriSign 2006 CA Cert signed with sha1 and an exponent of 65537 +static const uint8_t _ProtoSSL_VeriSign2006ServerModulus[] = +{ + 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35, 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, + 0x7c, 0xbc, 0x3c, 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3, 0x64, + 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22, 0xe8, 0x2a, 0xaa, 0xa6, 0x42, + 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1, 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, + 0xef, 0x43, 0xdb, 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0, 0xc3, + 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85, 0x26, 0xe5, 0x2b, 0x8f, 0x1b, + 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33, 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, + 0xe8, 0x70, 0x51, 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74, 0xdb, + 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0, 0xf4, 0xa2, 0x25, 0xf2, 0xaf, + 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06, 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, + 0xb5, 0x19, 0xff, 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4, 0xd7, + 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19, 0x1d, 0x1c, 0x40, 0xcb, 0x74, + 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe, 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, + 0x8d, 0x63, 0x47, 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5, 0x95, + 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14, 0x08, 0x7e, 0xe5, 0x3f, 0x9f, + 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f, 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15 +}; + +// The 2048-bit public key modulus for DigiCert Global Root CA +static const uint8_t _ProtoSSL_DigiCertServerModulus[] = +{ + 0xE2, 0x3B, 0xE1, 0x11, 0x72, 0xDE, 0xA8, 0xA4, 0xD3, 0xA3, 0x57, 0xAA, 0x50, 0xA2, 0x8F, 0x0B, + 0x77, 0x90, 0xC9, 0xA2, 0xA5, 0xEE, 0x12, 0xCE, 0x96, 0x5B, 0x01, 0x09, 0x20, 0xCC, 0x01, 0x93, + 0xA7, 0x4E, 0x30, 0xB7, 0x53, 0xF7, 0x43, 0xC4, 0x69, 0x00, 0x57, 0x9D, 0xE2, 0x8D, 0x22, 0xDD, + 0x87, 0x06, 0x40, 0x00, 0x81, 0x09, 0xCE, 0xCE, 0x1B, 0x83, 0xBF, 0xDF, 0xCD, 0x3B, 0x71, 0x46, + 0xE2, 0xD6, 0x66, 0xC7, 0x05, 0xB3, 0x76, 0x27, 0x16, 0x8F, 0x7B, 0x9E, 0x1E, 0x95, 0x7D, 0xEE, + 0xB7, 0x48, 0xA3, 0x08, 0xDA, 0xD6, 0xAF, 0x7A, 0x0C, 0x39, 0x06, 0x65, 0x7F, 0x4A, 0x5D, 0x1F, + 0xBC, 0x17, 0xF8, 0xAB, 0xBE, 0xEE, 0x28, 0xD7, 0x74, 0x7F, 0x7A, 0x78, 0x99, 0x59, 0x85, 0x68, + 0x6E, 0x5C, 0x23, 0x32, 0x4B, 0xBF, 0x4E, 0xC0, 0xE8, 0x5A, 0x6D, 0xE3, 0x70, 0xBF, 0x77, 0x10, + 0xBF, 0xFC, 0x01, 0xF6, 0x85, 0xD9, 0xA8, 0x44, 0x10, 0x58, 0x32, 0xA9, 0x75, 0x18, 0xD5, 0xD1, + 0xA2, 0xBE, 0x47, 0xE2, 0x27, 0x6A, 0xF4, 0x9A, 0x33, 0xF8, 0x49, 0x08, 0x60, 0x8B, 0xD4, 0x5F, + 0xB4, 0x3A, 0x84, 0xBF, 0xA1, 0xAA, 0x4A, 0x4C, 0x7D, 0x3E, 0xCF, 0x4F, 0x5F, 0x6C, 0x76, 0x5E, + 0xA0, 0x4B, 0x37, 0x91, 0x9E, 0xDC, 0x22, 0xE6, 0x6D, 0xCE, 0x14, 0x1A, 0x8E, 0x6A, 0xCB, 0xFE, + 0xCD, 0xB3, 0x14, 0x64, 0x17, 0xC7, 0x5B, 0x29, 0x9E, 0x32, 0xBF, 0xF2, 0xEE, 0xFA, 0xD3, 0x0B, + 0x42, 0xD4, 0xAB, 0xB7, 0x41, 0x32, 0xDA, 0x0C, 0xD4, 0xEF, 0xF8, 0x81, 0xD5, 0xBB, 0x8D, 0x58, + 0x3F, 0xB5, 0x1B, 0xE8, 0x49, 0x28, 0xA2, 0x70, 0xDA, 0x31, 0x04, 0xDD, 0xF7, 0xB2, 0x16, 0xF2, + 0x4C, 0x0A, 0x4E, 0x07, 0xA8, 0xED, 0x4A, 0x3D, 0x5E, 0xB5, 0x7F, 0xA3, 0x90, 0xC3, 0xAF, 0x27 +}; + +// only certificates from these authorities are supported +static ProtoSSLCACertT _ProtoSSL_CACerts[] = +{ + // gos2015 CA + { { "US", "California", "Redwood City", "Electronic Arts, Inc.", + "Global Online Studio/emailAddress=GOSDirtysockSupport@ea.com", + "GOS 2015 Certificate Authority" }, + SSL_CACERTFLAG_GOSCA|SSL_CACERTFLAG_CAPROVIDER, ASN_OBJ_RSA_PKCS_SHA256, 0, + sizeof(_ProtoSSL_GOS2015ServerModulus2048), _ProtoSSL_GOS2015ServerModulus2048, + 3, { 0x01, 0x00, 0x01 }, + 0, NULL, NULL, &_ProtoSSL_CACerts[1] }, + // gos2019 CA + { { "US", "California", "Redwood City", "Electronic Arts, Inc.", + "EADP Gameplay Services", + "Gameplay Services 2019 Certificate Authority" }, + SSL_CACERTFLAG_GOSCA, ASN_OBJ_ECDSA_KEY, ASN_OBJ_ECDSA_SECP256R1, + sizeof(_ProtoSSL_GS2019ServerKey), _ProtoSSL_GS2019ServerKey, + 0, { 0x00 }, + 0, NULL, NULL, &_ProtoSSL_CACerts[2] }, + // verisign 2006 CA + { { "US", "", "", "VeriSign, Inc.", + "VeriSign Trust Network, (c) 2006 VeriSign, Inc. - For authorized use only", + "VeriSign Class 3 Public Primary Certification Authority - G5" }, + SSL_CACERTFLAG_NONE, ASN_OBJ_RSA_PKCS_SHA1, 0, + 256, _ProtoSSL_VeriSign2006ServerModulus, + 3, { 0x01, 0x00, 0x01 }, + 0, NULL, NULL, &_ProtoSSL_CACerts[3]}, + // digicert CA + { { "US", "", "", "DigiCert Inc", "www.digicert.com", "DigiCert Global Root CA" }, + SSL_CACERTFLAG_NONE, ASN_OBJ_RSA_PKCS_SHA256, 0, + 256, _ProtoSSL_DigiCertServerModulus, + 3, { 0x01, 0x00, 0x01 }, + 0, NULL, NULL, NULL }, +}; + + +/*** Private functions ************************************************************/ + +/* + safe (bounded) reading +*/ + +/*F********************************************************************************/ +/*! + \Function _SafeRead8 + + \Description + Read a uint8_t from buffer + + \Input *pData - pointer to buffer to read from + \Input *pDataEnd - pointer to end of buffer + + \Output + uint8_t - value from buffer, or zero if buffer overrun + + \Version 01/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t _SafeRead8(const uint8_t *pData, const uint8_t *pDataEnd) +{ + uint8_t u8 = ((pData+1) <= pDataEnd) ? pData[0] : 0; + return(u8); +} + +/*F********************************************************************************/ +/*! + \Function _SafeRead16 + + \Description + Read a uint16_t from buffer in network order + + \Input *pData - pointer to buffer to read from + \Input *pDataEnd - pointer to end of buffer + + \Output + uint16_t - value from buffer, or zero if buffer overrun + + \Version 01/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static uint16_t _SafeRead16(const uint8_t *pData, const uint8_t *pDataEnd) +{ + uint16_t u16 = ((pData+2) <= pDataEnd) ? ((uint16_t)pData[0]<<8)|((uint16_t)pData[1]) : 0; + return(u16); +} + +/*F********************************************************************************/ +/*! + \Function _SafeRead24 + + \Description + Read a 24bit value from buffer in network order + + \Input *pData - pointer to buffer to read from + \Input *pDataEnd - pointer to end of buffer + + \Output + uint32_t - value from buffer, or zero if buffer overrun + + \Version 01/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _SafeRead24(const uint8_t *pData, const uint8_t *pDataEnd) +{ + uint32_t u32 = ((pData+3) <= pDataEnd) ? ((uint32_t)pData[0]<<16)|((uint32_t)pData[1]<<8)|((uint32_t)pData[2]) : 0; + return(u32); +} + +/*F********************************************************************************/ +/*! + \Function _SafeRead32 + + \Description + Read a uint32_t from buffer in network order + + \Input *pData - pointer to buffer to read from + \Input *pDataEnd - pointer to end of buffer + + \Output + uint32_t - value from buffer, or zero if buffer overrun + + \Version 01/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _SafeRead32(const uint8_t *pData, const uint8_t *pDataEnd) +{ + uint32_t u32 = ((pData+4) <= pDataEnd) ? ((uint32_t)pData[0]<<24)|((uint32_t)pData[1]<<16)|((uint32_t)pData[2]<<8)|((uint32_t)pData[3]) : 0; + return(u32); +} + +/*F********************************************************************************/ +/*! + \Function _SafeReadBytes + + \Description + Read specified bytes from buffer + + \Input *pBuffer - [out] buffer to write to + \Input uBufSize - size of output buffe + \Input *pData - pointer to buffer to read from + \Input uNumBytes - number of bytes to read + \Input *pDataEnd - pointer to end of buffer + + \Version 01/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SafeReadBytes(uint8_t *pBuffer, uint32_t uBufSize, const uint8_t *pData, uint32_t uNumBytes, const uint8_t *pDataEnd) +{ + if ((pData+uNumBytes) <= pDataEnd) + { + ds_memcpy_s(pBuffer, uBufSize, pData, uNumBytes); + } + else + { + ds_memclr(pBuffer, uBufSize); + } +} + +/*F********************************************************************************/ +/*! + \Function _SafeReadString + + \Description + Read length-delimited string from buffer + + \Input *pBuffer - [out] string buffer to write to + \Input uBufSize - size of output buffe + \Input *pString - pointer to source to read from + \Input uStrLen - length of source string + \Input *pDataEnd - pointer to end of buffer + + \Version 01/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SafeReadString(char *pBuffer, uint32_t uBufSize, const char *pString, uint32_t uStrLen, const uint8_t *pDataEnd) +{ + if ((pString+uStrLen) <= (const char *)pDataEnd) + { + ds_strsubzcpy(pBuffer, uBufSize, pString, uStrLen); + } + else + { + ds_memclr(pBuffer, uBufSize); + } +} + +/* + asn.1 parsing routines +*/ + +/*F********************************************************************************/ +/*! + \Function _AsnGetHashType + + \Description + Get CryptHashE from ASN.1 hash object type + + \Input iHashType - asn.1 hash type + + \Output + CryptHashTypeE - hash type + + \Version 11/28/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static CryptHashTypeE _AsnGetHashType(int32_t iHashType) +{ + CryptHashTypeE eHashType; + // convert from asn object type to crypt hash type + switch (iHashType) + { + case ASN_OBJ_MD5: + eHashType = CRYPTHASH_MD5; + break; + case ASN_OBJ_SHA1: + eHashType = CRYPTHASH_SHA1; + break; + case ASN_OBJ_SHA256: + eHashType = CRYPTHASH_SHA256; + break; + case ASN_OBJ_SHA384: + eHashType = CRYPTHASH_SHA384; + break; + case ASN_OBJ_SHA512: + eHashType = CRYPTHASH_SHA512; + break; + default: + eHashType = CRYPTHASH_NULL; + break; + } + // return hash type to caller + return(eHashType); +} + +/*F********************************************************************************/ +/*! + \Function _AsnGetObject + + \Description + Return OID based on type + + \Input iType - Type of OID (ASN_OBJ_*) + + \Output + ASNObjectT *- pointer to OID, or NULL if not found + + \Version 03/18/2015 (jbrookes) +*/ +/********************************************************************************F*/ +static const ASNObjectT *_AsnGetObject(int32_t iType) +{ + const ASNObjectT *pObject; + int32_t iIndex; + + // locate the matching type + for (iIndex = 0, pObject = NULL; _SSL_ObjectList[iIndex].iType != ASN_OBJ_NONE; iIndex += 1) + { + if (_SSL_ObjectList[iIndex].iType == iType) + { + pObject = &_SSL_ObjectList[iIndex]; + break; + } + } + + return(pObject); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseHeader + + \Description + Parse an asn.1 header + + \Input *pData - pointer to header data to parse + \Input *pLast - pointer to end of header + \Input *pType - pointer to storage for header type + \Input *pSize - pointer to storage for data size + + \Output uint8_t * - pointer to next block, or NULL if end of stream + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +static const uint8_t *_AsnParseHeader(const uint8_t *pData, const uint8_t *pLast, int32_t *pType, int32_t *pSize) +{ + int32_t iCnt; + uint32_t uLen; + int32_t iType, iSize; + + // reset the output + if (pSize != NULL) + { + *pSize = 0; + } + if (pType != NULL) + { + *pType = 0; + } + + /* handle end of data (including early termination due to data truncation or + invalid data) and make sure we have at least enough data for the type and + size */ + if ((pData == NULL) || (pData+2 > pLast)) + { + return(NULL); + } + + // get the type + iType = *pData++; + if (pType != NULL) + { + *pType = iType; + } + + // figure the length + if ((uLen = *pData++) > 127) + { + // get number of bytes + iCnt = (uLen & 127); + // validate length (do not allow overflow) and make sure there is enough data + if ((iCnt > (int32_t)sizeof(uLen)) || ((pData + iCnt) > pLast)) + { + return(NULL); + } + // calc the length + for (uLen = 0; iCnt > 0; --iCnt) + { + uLen = (uLen << 8) | *pData++; + } + } + iSize = (signed)uLen; + // validate record size + if ((iSize < 0) || ((pData+iSize) > pLast)) + { + return(NULL); + } + // save the size + if (pSize != NULL) + { + *pSize = iSize; + } + + #if DEBUG_ALL_OBJS + NetPrintf(("protossl: _AsnParseHeader type=%s (0x%02x) size=%d\n", + _SSL3_strAsnTypes[iType&~(ASN_CONSTRUCT|ASN_IMPLICIT_TAG|ASN_EXPLICIT_TAG)], + iType, iSize)); + #if DEBUG_RAW_DATA + NetPrintMem(pData, iSize, _SSL3_strAsnTypes[iType&~(ASN_CONSTRUCT|ASN_IMPLICIT_TAG|ASN_EXPLICIT_TAG)]); + #endif + #endif + + // return pointer to next + return(pData); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseHeaderType + + \Description + Parse an asn.1 header of specified type + + \Input *pData - pointer to header data to parse + \Input *pLast - pointer to end of header + \Input iType - type of header to extract + \Input *pSize - pointer to storage for data size + + \Output uint8_t * - pointer to next block, or NULL if end of stream or unexpected type + + \Version 10/10/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_AsnParseHeaderType(const uint8_t *pData, const uint8_t *pLast, int32_t iType, int32_t *pSize) +{ + int32_t iTypeParsed; + pData = _AsnParseHeader(pData, pLast, &iTypeParsed, pSize); + if (iTypeParsed != iType) + { + return(NULL); + } + return(pData); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseObject + + \Description + Parse an object type + + \Input *pData - pointer to object + \Input iSize - size of object + + \Output + int32_t - type of object; zero if object is unrecognized + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _AsnParseObject(const uint8_t *pData, int32_t iSize) +{ + int32_t iType = 0; + int32_t iIndex; + + // locate the matching type + for (iIndex = 0; _SSL_ObjectList[iIndex].iType != ASN_OBJ_NONE; ++iIndex) + { + if ((iSize >= _SSL_ObjectList[iIndex].iSize) && (memcmp(pData, _SSL_ObjectList[iIndex].strData, _SSL_ObjectList[iIndex].iSize) == 0)) + { + // save the type and return it + iType = _SSL_ObjectList[iIndex].iType; + + #if DEBUG_ALL_OBJS + NetPrintf(("protossl: _AsnParseObject obj=%s (%d)\n", _SSL3_strAsnObjs[iType], iType)); + #if DEBUG_RAW_DATA + NetPrintMem(pData, _SSL_ObjectList[iIndex].iSize, _SSL3_strAsnObjs[iType]); + #endif + #endif + + break; + } + } + + #if DEBUG_ALL_OBJS + if (iType == 0) + { + NetPrintMem(pData, iSize, "unrecognized asn.1 object"); + } + #endif + + return(iType); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseString + + \Description + Extract a string + + \Input *pData - pointer to data to extract string from + \Input iSize - size of source data + \Input *pString - pointer to buffer to copy string into + \Input iLength - size of buffer + \Input iType - ASN string type + + \Notes + The number of characters copied will be whichever is smaller between + iSize and iLength-1. If iLength-1 is greater than iSize (ie, the buffer + is larger than the source string) the string output in pString will be + NULL-terminated. + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +static void _AsnParseString(const uint8_t *pData, int32_t iSize, char *pString, int32_t iLength, int32_t iType) +{ + if (iType != ASN_TYPE_UNICODESTR) + { + for (; (iSize > 0) && (iLength > 1); --iSize, --iLength) + { + *pString++ = *pData++; + } + if (iLength > 0) + { + *pString = 0; + } + } + else // we do a straight conversion to ASCII here + { + for (pData += 1; (iSize > 0) && (iLength > 1); iSize -= 2, iLength -= 1) + { + *pString++ = *pData; + pData += 2; + } + if (iLength > 0) + { + *pString = 0; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseStringMulti + + \Description + Extract a string, appending to output instead of overwriting. + + \Input *pData - pointer to data to extract string from + \Input iSize - size of source data + \Input *pString - pointer to buffer to copy string into + \Input iLength - size of buffer + + \Notes + The number of characters copied will be whichever is smaller between + iSize and iLength-1. If iLength-1 is greater than iSize (ie, the buffer + is larger than the source string) the string output in pString will be + NULL-terminated. + + \Version 03/25/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static void _AsnParseStringMulti(const uint8_t *pData, int32_t iSize, char *pString, int32_t iLength) +{ + // find append point + for (; (*pString != '\0') && (iLength > 1); --iLength) + { + pString += 1; + } + // extract + for (; (iSize > 0) && (iLength > 1); --iSize, --iLength) + { + *pString++ = *pData++; + } + // terminate + if (iLength > 0) + { + *pString = '\0'; + } +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseIdent + + \Description + Extract an Ident (subject/issuer) from certificate + + \Input *pData - pointer to data to extract string from + \Input iSize - size of source data + \Input *pIdent - [out] storage for parsed ident fields + + \Output + const uint8_t * - pointer past end of ident + + \Version 10/09/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_AsnParseIdent(const uint8_t *pData, int32_t iSize, ProtoSSLCertIdentT *pIdent) +{ + int32_t iObjType, iType; + const uint8_t *pDataEnd; + + for (iObjType = 0, pDataEnd = pData+iSize; (pData = _AsnParseHeader(pData, pDataEnd, &iType, &iSize)) != NULL; ) + { + // skip past seqn/set references + if ((iType == ASN_TYPE_SEQN+ASN_CONSTRUCT) || (iType == ASN_TYPE_SET+ASN_CONSTRUCT)) + { + continue; + } + if (iType == ASN_TYPE_OBJECT+ASN_PRIMITIVE) + { + iObjType = _AsnParseObject(pData, iSize); + } + if ((iType == ASN_TYPE_PRINTSTR+ASN_PRIMITIVE) || (iType == ASN_TYPE_UTF8STR+ASN_PRIMITIVE) || (iType == ASN_TYPE_T61+ASN_PRIMITIVE) || (iType == ASN_TYPE_UNICODESTR+ASN_PRIMITIVE)) + { + if (iObjType == ASN_OBJ_COUNTRY) + { + _AsnParseString(pData, iSize, pIdent->strCountry, sizeof(pIdent->strCountry), iType); + } + if (iObjType == ASN_OBJ_STATE) + { + _AsnParseString(pData, iSize, pIdent->strState, sizeof(pIdent->strState), iType); + } + if (iObjType == ASN_OBJ_CITY) + { + _AsnParseString(pData, iSize, pIdent->strCity, sizeof(pIdent->strCity), iType); + } + if (iObjType == ASN_OBJ_ORGANIZATION) + { + _AsnParseString(pData, iSize, pIdent->strOrg, sizeof(pIdent->strOrg), iType); + } + if (iObjType == ASN_OBJ_UNIT) + { + if (pIdent->strUnit[0] != '\0') + { + ds_strnzcat(pIdent->strUnit, ", ", sizeof(pIdent->strUnit)); + } + _AsnParseStringMulti(pData, iSize, pIdent->strUnit, sizeof(pIdent->strUnit)); + } + if (iObjType == ASN_OBJ_COMMON) + { + _AsnParseString(pData, iSize, pIdent->strCommon, sizeof(pIdent->strCommon), iType); + } + iObjType = 0; + } + pData += iSize; + } + + return(pDataEnd); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseDate + + \Description + Parse and extract a date object from ASN.1 certificate + + \Input *pData - pointer to header data + \Input iSize - size of object + \Input *pBuffer - [out] output for date string object + \Input iBufSize - size of output buffer + \Input *pDateTime - [out] storage for converted time, optional + + \Output + const uint8_t * - pointer past object, or NULL if an error occurred + + \Version 10/10/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_AsnParseDate(const uint8_t *pData, int32_t iSize, char *pBuffer, int32_t iBufSize, uint64_t *pDateTime) +{ + int32_t iType; + if (((pData = _AsnParseHeader(pData, pData+iSize, &iType, &iSize)) == NULL) || ((iType != ASN_TYPE_UTCTIME) && (iType != ASN_TYPE_GENERALIZEDTIME))) + { + return(NULL); + } + _AsnParseString(pData, iSize, pBuffer, iBufSize, iType); + if (pDateTime != NULL) + { + *pDateTime = ds_strtotime2(pBuffer, iType == ASN_TYPE_UTCTIME ? TIMETOSTRING_CONVERSION_ASN1_UTCTIME : TIMETOSTRING_CONVERSION_ASN1_GENTIME); + } + pData += iSize; + return(pData); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseBinaryPtr + + \Description + Parse and extract a binary object from ASN.1 certificate + + \Input *pData - pointer to header data + \Input *pLast - pointer to end of data + \Input iType - type of data we are expecting + \Input **ppObj - [out] storage for pointer to binary object + \Input *pObjSize - [out] storage for size of binary object + \Input *pName - name of object (used for debug output) + + \Output + const uint8_t * - pointer past object, or NULL if an error occurred + + \Version 11/18/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_AsnParseBinaryPtr(const uint8_t *pData, const uint8_t *pLast, int32_t iType, uint8_t **ppObj, int32_t *pObjSize, const char *pName) +{ + int32_t iSize; + if ((pData = _AsnParseHeaderType(pData, pLast, iType, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseBinary: could not get %s\n", pName)); + return(NULL); + } + // skip leading zero if present + if ((iSize > 0) && (*pData == '\0')) + { + pData += 1; + iSize -= 1; + } + // save object pointer + *ppObj = (uint8_t *)pData; + // save object size + *pObjSize = iSize; + // skip object + return(pData+iSize); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseBinary + + \Description + Parse and extract a binary object from ASN.1 certificate + + \Input *pData - pointer to header data + \Input *pLast - pointer to end of data + \Input iType - type of data we are expecting + \Input *pBuffer - [out] output for binary object (may be null to skip) + \Input iBufSize - size of output buffer + \Input *pOutSize - [out] output for binary object size + \Input *pName - name of object (used for debug output) + + \Output + const uint8_t * - pointer past object, or NULL if an error occurred + + \Version 10/10/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_AsnParseBinary(const uint8_t *pData, const uint8_t *pLast, int32_t iType, uint8_t *pBuffer, int32_t iBufSize, int32_t *pOutSize, const char *pName) +{ + const uint8_t *pObjData = NULL, *pNext; + // parse object info + pNext = _AsnParseBinaryPtr(pData, pLast, iType, (uint8_t **)&pObjData, pOutSize, pName); + // save data + if ((pObjData != NULL) && (pBuffer != NULL)) + { + // validate size + if (*pOutSize > iBufSize) + { + NetPrintf(("protossl: _AsnParseBinary: %s is too large (size=%d, max=%d)\n", pName, *pOutSize, iBufSize)); + return(NULL); + } + ds_memcpy(pBuffer, pObjData, *pOutSize); + } + // skip object + return(pNext); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseOptional + + \Description + Parse optional object of ASN.1 certificate + + \Input *pData - pointer to header data + \Input iSize - size of header + \Input *pCert - pointer to certificate to fill in from header data + + \Output + int32_t - negative=error, zero=no error + + \Version 10/10/2012 (jbrookes) Extracted from _AsnParseCertificate() +*/ +/********************************************************************************F*/ +static int32_t _AsnParseOptional(const uint8_t *pData, int32_t iSize, X509CertificateT *pCert) +{ + const uint8_t *pLast; + int32_t iCritical, iObjType, iLen, iType; + + for (iCritical = iObjType = 0, pLast = pData+iSize; (pData = _AsnParseHeader(pData, pLast, &iType, &iSize)) != NULL; ) + { + // ignore seqn/set references + if ((iType == ASN_TYPE_SEQN+ASN_CONSTRUCT) || (iType == ASN_TYPE_SET+ASN_CONSTRUCT)) + { + continue; + } + + // parse the object type + if (iType == ASN_TYPE_OBJECT+ASN_PRIMITIVE) + { + iObjType = _AsnParseObject(pData, iSize); + } + + // parse a subject alternative name (SAN) object + if (iObjType == ASN_OBJ_SUBJECT_ALT) + { + if (iType == ASN_TYPE_OCTSTRING+ASN_PRIMITIVE) + { + // save reference to subject alternative blob + pCert->iSubjectAltLen = iSize; + pCert->pSubjectAlt = pData; + } + } + + // parse a basic constraints object + if (iObjType == ASN_OBJ_BASIC_CONSTRAINTS) + { + // obj_basic_constraints: [boolean: critical][integer: pathLenConstraints] + if (iType == ASN_TYPE_OCTSTRING+ASN_PRIMITIVE) + { + // if no critical flag is present, mark it as critical. ex: https://www.wellsfargo.com/ + if (iCritical == 0) + { + iCritical = 1; + } + // do not add pData for oct + continue; + } + + if ((iType == ASN_TYPE_BOOLEAN+ASN_PRIMITIVE) && (iSize == 1)) + { + if (!iCritical) + { + // check if the critical flag is set (the basic constraints MUST be critical) + if ((iCritical = *pData) == 0) + { + return(-1); + } + } + else + { + // this is the flag to indicate whether it's a CA + pCert->iCertIsCA = (*pData != 0) ? TRUE : FALSE; + } + } + + if ((iType == ASN_TYPE_INTEGER+ASN_PRIMITIVE) && (pCert->iCertIsCA != FALSE) && (iSize <= (signed)sizeof(pCert->iMaxHeight))) + { + for (iLen = 0; iLen < iSize; iLen++) + { + pCert->iMaxHeight = (pCert->iMaxHeight << 8) | pData[iLen]; + } + /* As per http://tools.ietf.org/html/rfc2459#section-4.2.1.10: A value of zero indicates that only an end-entity certificate + may follow in the path. Where it appears, the pathLenConstraint field MUST be greater than or equal to zero. Where pathLenConstraint + does not appear, there is no limit to the allowed length of the certification path. In our case (iMaxHeight), a value of zero means + no pathLenConstraint is present, 1 means the pathLenConstraint is 0, 2 means the pathLenConstraint is 1, ... */ + //$$ todo - https://tools.ietf.org/html/rfc5280#section-4.2.1.9 updates this to add the keyCertSign bit in the key usage extension as a requirement + if (pCert->iMaxHeight++ < 0) + { + return(-2); + } + } + } + pData += iSize; + } + if (pCert->pSubjectAlt != NULL) + { + NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: parsed SAN; length=%d\n", pCert->iSubjectAltLen)); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseSequencedObject + + \Description + Parse a sequenced object + + \Input *pData - pointer to object to parse + \Input *pLast - pointer to end of data + \Input *pObjType - [out] storage for parsed object type + + \Output + uint8_t * - pointer past end of object, or NULL + + \Version 11/21/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_AsnParseSequencedObject(const uint8_t *pData, const uint8_t *pLast, int32_t *pObjType) +{ + int32_t iSize; + if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + return(NULL); + } + if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize)) == NULL) + { + return(NULL); + } + if ((*pObjType = _AsnParseObject(pData, iSize)) < 0) + { + return(NULL); + } + return(pData+iSize); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseSignatureAlgorithms + + \Description + Parse Signature Algorithms object of ASN.1 certificate + + \Input *pData - pointer to header data + \Input *pLast - pointer to end of data + \Input *pCert - pointer to certificate to fill in from sigalg object + + \Output + uint8_t * - pointer past end of object + + \Version 11/21/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_AsnParseSignatureAlgorithms(const uint8_t *pData, const uint8_t *pLast, X509CertificateT *pCert) +{ + int32_t iMgfFunc, iSize; + const uint8_t *pData2; + + // parse signature algorithm identifier + if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get signature algorithm identifier\n")); + return(NULL); + } + pCert->iSigType = _AsnParseObject(pData, iSize); + pData += iSize; + + // RSA-PKCS? + if ((pCert->iSigType >= ASN_OBJ_RSA_PKCS_MD5) && (pCert->iSigType <= ASN_OBJ_RSA_PKCS_SHA512)) + { + // get sig hash type from sig type + pCert->iSigHash = pCert->iSigType-ASN_OBJ_RSA_PKCS_MD5+ASN_OBJ_MD5; + return(pData); + } + else if (pCert->iSigType == ASN_OBJ_ECDSA_SHA256) + { + pCert->iSigHash = ASN_OBJ_SHA256; + return(pData); + } + else if (pCert->iSigType == ASN_OBJ_ECDSA_SHA384) + { + pCert->iSigHash = ASN_OBJ_SHA384; + return(pData); + } + else if (pCert->iSigType != ASN_OBJ_RSASSA_PSS) + { + NetPrintf(("protossl: unsupported signature algorithm %d\n", pCert->iSigType)); + return(NULL); + } + + // set default parameter types, which are used when only partial settings are specified + pCert->iSigHash = ASN_OBJ_SHA1; + pCert->iSigSalt = 20; + iMgfFunc = ASN_OBJ_PKCS1_MGF1; + pCert->iMgfHash = ASN_OBJ_SHA1; + + // parse rsassa-pss parameter sequence; see https://tools.ietf.org/html/rfc3447#appendix-A.2.3 + if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not parse rsassa_pss parameter sequence\n")); + return(NULL); + } + + // parse optional signature hash object + if ((pData2 = _AsnParseHeaderType(pData, pLast, ASN_EXPLICIT_TAG+ASN_PRIMITIVE+0, &iSize)) != NULL) + { + if ((pData = _AsnParseSequencedObject(pData2, pLast, &pCert->iSigHash)) == NULL) + { + NetPrintf(("protossl: _AsnParseSignatureAlgorithm: error parsing rsassa_pss hash parameter\n")); + return(NULL); + } + } + + // parse optional mgf+hash object + if ((pData2 = _AsnParseHeaderType(pData, pLast, ASN_EXPLICIT_TAG+ASN_PRIMITIVE+1, &iSize)) != NULL) + { + // parse mgf + if (((pData2 = _AsnParseSequencedObject(pData2, pLast, &iMgfFunc)) == NULL) || (iMgfFunc != ASN_OBJ_PKCS1_MGF1)) + { + NetPrintf(("protossl: _AsnParseSignatureAlgorithm: error parsing rsassa_pss mgf parameter")); + return(NULL); + } + // parse mgf hash + if ((pData = _AsnParseSequencedObject(pData2, pLast, &pCert->iMgfHash)) == NULL) + { + NetPrintf(("protossl: _AsnParseSignatureAlgorithm: error parsing rsassa_pss hash parameter\n")); + return(NULL); + } + } + + // look for explicit signature parameters (optional) + if ((pData2 = _AsnParseHeaderType(pData, pLast, ASN_EXPLICIT_TAG+ASN_PRIMITIVE+2, &iSize)) != NULL) + { + int32_t iSigSaltLen = 0; + uint8_t uSigSalt = 0; + + // parse salt length + if (((pData = _AsnParseBinary(pData2, pLast, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &uSigSalt, sizeof(uSigSalt), &iSigSaltLen, "salt length")) == NULL) || (iSigSaltLen != 1)) + { + return(NULL); + } + pCert->iSigSalt = uSigSalt; + } + + #if DEBUG_VAL_CERT + NetPrintf(("protossl: rsassa-pss parameters\n")); + NetPrintf(("protossl: iSigType=%d\n", pCert->iSigType)); + NetPrintf(("protossl: iSigHash=%d\n", pCert->iSigHash)); + NetPrintf(("protossl: iMgfFunc=%d\n", iMgfFunc)); + NetPrintf(("protossl: iMgfHash=%d\n", pCert->iMgfHash)); + NetPrintf(("protossl: iSigSalt=%d\n", pCert->iSigSalt)); + #endif + + // return pointer past end of data + return(pData); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParseCertificate + + \Description + Parse an x.509 certificate in ASN.1 format + + \Input *pCert - pointer to certificate to fill in from header data + \Input *pData - pointer to header data + \Input iSize - size of header + + \Output + int32_t - negative=error, zero=no error + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _AsnParseCertificate(X509CertificateT *pCert, const uint8_t *pData, int32_t iSize) +{ + const uint8_t *pInfData; + const uint8_t *pInfSkip; + const uint8_t *pSigSkip; + const uint8_t *pKeySkip; + const uint8_t *pKeyData; + const uint8_t *pLast = pData+iSize; + const CryptHashT *pHash; + int32_t iKeySize, iType; + + // clear the certificate + ds_memclr(pCert, sizeof(*pCert)); + + // process the base sequence + if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not parse base sequence\n")); + return(-1); + } + + // process the info sequence + if ((pData = _AsnParseHeaderType(pInfData = pData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not parse info sequence\n")); + return(-2); + } + pInfSkip = pData+iSize; + + // skip any non-integer tag (optional version) + if (*pData != ASN_TYPE_INTEGER+ASN_PRIMITIVE) + { + if ((pData = _AsnParseHeader(pData, pLast, NULL, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not skip non-integer tag\n")); + return(-3); + } + pData += iSize; + } + + // grab the certificate serial number + if (((pData = _AsnParseHeader(pData, pInfSkip, &iType, &iSize)) == NULL) || ((unsigned)iSize > sizeof(pCert->SerialData))) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get certificate serial number (iSize=%d)\n", iSize)); + return(-4); + } + pCert->iSerialSize = iSize; + ds_memcpy(pCert->SerialData, pData, iSize); + pData += iSize; + + // find signature algorithm + if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get signature algorithm\n")); + return(-5); + } + pSigSkip = pData+iSize; + + // get the signature algorithm type + if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get signature algorithm type\n")); + return(-6); + } + if ((pCert->iSigType = _AsnParseObject(pData, iSize)) == ASN_OBJ_NONE) + { + NetPrintMem(pData, iSize, "protossl: unsupported signature algorithm"); + return(-7); + } + + // parse issuer + if ((pData = _AsnParseHeaderType(pSigSkip, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get issuer\n")); + return(-8); + } + pData = _AsnParseIdent(pData, iSize, &pCert->Issuer); + + // parse the validity info + if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get validity info\n")); + return(-9); + } + + // get validity dates + if ((pData = _AsnParseDate(pData, iSize, pCert->strGoodFrom, sizeof(pCert->strGoodFrom), &pCert->uGoodFrom)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get from date\n")); + return(-10); + } + if ((pData = _AsnParseDate(pData, iSize, pCert->strGoodTill, sizeof(pCert->strGoodTill), &pCert->uGoodTill)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get to date\n")); + return(-11); + } + #if DEBUG_VAL_CERT + { + char strTimeFrom[32], strTimeTill[32]; + NetPrintf(("protossl: certificate valid from %s to %s\n", + ds_secstostr(pCert->uGoodFrom, TIMETOSTRING_CONVERSION_RFC_0822, FALSE, strTimeFrom, sizeof(strTimeFrom)), + ds_secstostr(pCert->uGoodTill, TIMETOSTRING_CONVERSION_RFC_0822, FALSE, strTimeTill, sizeof(strTimeTill)))); + } + #endif + + // get subject + if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get subject\n")); + return(-12); + } + pData = _AsnParseIdent(pData, iSize, &pCert->Subject); + + // parse the public key + if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get public key\n")); + return(-13); + } + + // find the key algorithm sequence + if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get key algorithm sequence\n")); + return(-14); + } + pKeySkip = pData+iSize; + + // grab the public key algorithm + if ((pData = _AsnParseHeaderType(pData, pKeySkip, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get public key algorithm\n")); + return(-15); + } + pCert->iKeyType = _AsnParseObject(pData, iSize); + + // if ecdsa, grab the curve type before we move past the public key object + if (pCert->iKeyType == ASN_OBJ_ECDSA_KEY) + { + if ((pData = _AsnParseHeaderType(pData+iSize, pKeySkip, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get ecdsa curve type\n")); + return(-15); + } + pCert->iCrvType = _AsnParseObject(pData, iSize); + } + + // find the actual public key + if (((pData = _AsnParseHeaderType(pKeySkip, pLast, ASN_TYPE_BITSTRING+ASN_PRIMITIVE, &iSize)) == NULL) || (iSize < 1)) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get actual public key\n")); + return(-16); + } + + // skip the "extra bits" indicator and save keydata ref + pKeyData = pData+1; + iKeySize = iSize-1; + pData += iSize; + + // parse optional info object, if present; see https://tools.ietf.org/html/rfc5280#section-4.1 + if (((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_EXPLICIT_TAG+3, &iSize)) != NULL) && (_AsnParseOptional(pData, iSize, pCert) < 0)) + { + return(-17); + } + + // parse signature algorithm sequence + if ((pData = _AsnParseHeaderType(pInfSkip, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get signature algorithm sequence\n")); + return(-18); + } + pSigSkip = pData+iSize; + + // parse signature algorithms + if ((pData = _AsnParseSignatureAlgorithms(pData, pLast, pCert)) == NULL) + { + return(-19); + } + + // parse the signature data + if ((pCert->iSigType == ASN_OBJ_ECDSA_SHA256) || (pCert->iSigType == ASN_OBJ_ECDSA_SHA384) || (pCert->iSigType == ASN_OBJ_ECDSA_SHA512)) + { + // find the bitstring object + if ((pData = _AsnParseHeaderType(pSigSkip, pLast, ASN_TYPE_BITSTRING+ASN_PRIMITIVE, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get signature algorithm identifier\n")); + return(-20); + } + // skip leading pad byte; note we assume it is zero (no padding) + pData += 1; + iSize -= 1; + // save signature object data; we will parse it later + ds_memcpy_s(pCert->SigData, sizeof(pCert->SigData), pData, iSize); + pCert->iSigSize = iSize; + } + else if ((pData = _AsnParseBinary(pSigSkip, pLast, ASN_TYPE_BITSTRING+ASN_PRIMITIVE, pCert->SigData, sizeof(pCert->SigData), &pCert->iSigSize, "signature data")) == NULL) + { + return(-21); + } + + // parse the public key data (extract modulus & exponent) + if ((pCert->iKeyType == ASN_OBJ_RSA_PKCS_KEY) || (pCert->iKeyType == ASN_OBJ_RSASSA_PSS)) + { + pLast = pKeyData+iKeySize; + + // parse the sequence + if ((pData = _AsnParseHeaderType(pKeyData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParseCertificate: could not get public key sequence\n")); + return(-22); + } + // parse the key modulus + if ((pData = _AsnParseBinary(pData, pLast, ASN_TYPE_INTEGER+ASN_PRIMITIVE, pCert->KeyModData, sizeof(pCert->KeyModData), &pCert->iKeyModSize, "key modulus")) == NULL) + { + return(-23); + } + #if DEBUG_MOD_PRNT + NetPrintMem(pCert->KeyModData, pCert->iKeyModSize, "public key modulus"); + #endif + // parse the key exponent + if ((pData = _AsnParseBinary(pData, pLast, ASN_TYPE_INTEGER+ASN_PRIMITIVE, pCert->KeyExpData, sizeof(pCert->KeyExpData), &pCert->iKeyExpSize, "key exponent")) == NULL) + { + return(-24); + } + } + else if (pCert->iKeyType == ASN_OBJ_ECDSA_KEY) + { + // copy the curve data; unlike rsa, keydata points to the actual data + ds_memcpy_s(pCert->KeyModData, sizeof(pCert->KeyModData), pKeyData, iKeySize); + pCert->iKeyModSize = iKeySize; + #if DEBUG_MOD_PRNT + NetPrintMem(pCert->KeyModData, pCert->iKeyModSize, "public key curve data"); + #endif + } + else + { + NetPrintf(("protossl: _AsnParseCertificate: unrecognized key type %d\n", pCert->iKeyType)); + return(-25); + } + + // get cert hash type + pCert->eHashType = _AsnGetHashType(pCert->iSigHash); + + // generate the certificate hash + if ((pHash = CryptHashGet(pCert->eHashType)) != NULL) + { + // do the hash + uint8_t aContext[CRYPTHASH_MAXSTATE]; + pHash->Init(aContext, pHash->iHashSize); + pHash->Update(aContext, pInfData, (int32_t)(pInfSkip-pInfData)); + pHash->Final(aContext, pCert->HashData, pHash->iHashSize); + // save hash size + pCert->iHashSize = pHash->iHashSize; + } + else + { + NetPrintf(("protossl: could not get hash type %d\n", pCert->iSigHash)); + return(-26); + } + + // certificate parsed successfully + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParsePrivateKeyRSA + + \Description + Parse public key modulus and public or private key exponent from private + key certificate. + + \Input *pPrivateKeyData - private key certificate data in base64 form + \Input iPrivateKeyLen - length of private key certificate data + \Input *pPrivateKey - [out] parsed private key data + + \Output + int32_t - private key certificate binary (decoded) size, or negative on error + + \Notes + \verbatim + RSAPrivateKey ::= SEQUENCE { + version Version, + modulus INTEGER, + publicExponent INTEGER, + privateExponent INTEGER, + primeP INTEGER, + primeQ INTEGER, + exponentP INTEGER, + exponentQ INTEGER, + coefficient INTEGER + } + \endverbatim + + \Version 03/15/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _AsnParsePrivateKeyRSA(const char *pPrivateKeyData, int32_t iPrivateKeyLen, X509PrivateKeyT *pPrivateKey) +{ + int32_t iPrivKeySize, iSize, iSize2, iTemp = 0; + const uint8_t *pData, *pData2; + + // clear output structure + ds_memclr(pPrivateKey, sizeof(*pPrivateKey)); + + // base64 decode into buffer + if ((iPrivKeySize = Base64Decode3(pPrivateKeyData, iPrivateKeyLen, pPrivateKey->strPrivKeyData, sizeof(pPrivateKey->strPrivKeyData))) == 0) + { + NetPrintf(("protossl: _AsnParsePrivateKey: could not decode private key\n")); + return(-2); + } + // parse the sequence + if ((pData = _AsnParseHeaderType((uint8_t *)pPrivateKey->strPrivKeyData, (uint8_t *)pPrivateKey->strPrivKeyData+iPrivKeySize, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParsePrivateKey: could not get private key sequence\n")); + return(-3); + } + // skip the version + if ((pData = _AsnParseBinary(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, NULL, 0, &iTemp, "version")) == NULL) + { + return(-4); + } + // check for sequence... if there is one, this is an RSA-PSS PKCS#8 certificate with some additional stuff we need to skip + if ((pData2 = _AsnParseHeaderType(pData, pData+iSize, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize2)) != NULL) + { + if ((pData = _AsnParseHeaderType(pData2, pData2+iSize2, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize2)) == NULL) + { + return(-5); + } + if (_AsnParseObject(pData, iSize2) != ASN_OBJ_RSASSA_PSS) + { + return(-6); + } + pData += iSize2; + if ((pData = _AsnParseHeaderType(pData, pData+iSize, ASN_TYPE_OCTSTRING+ASN_PRIMITIVE, &iSize)) == NULL) + { + return(-7); + } + if ((pData = _AsnParseHeaderType(pData, pData+iSize, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + return(-8); + } + if ((pData = _AsnParseBinary(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, NULL, 0, &iTemp, "zero")) == NULL) + { + return(-9); + } + } + // parse public key modulus + if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->Modulus.pObjData, &pPrivateKey->Modulus.iObjSize, "key modulus")) == NULL) + { + return(-10); + } + // parse public key exponent + if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->PublicExponent.pObjData, &pPrivateKey->PublicExponent.iObjSize, "public key exponent")) == NULL) + { + return(-11); + } + // parse private key exponent + if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->PrivateExponent.pObjData, &pPrivateKey->PrivateExponent.iObjSize, "private key exponent")) == NULL) + { + return(-12); + } + // parse primeP + if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->PrimeP.pObjData, &pPrivateKey->PrimeP.iObjSize, "primeP")) == NULL) + { + return(-13); + } + // parse primeQ + if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->PrimeQ.pObjData, &pPrivateKey->PrimeQ.iObjSize, "primeQ")) == NULL) + { + return(-14); + } + // parse exponentP + if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->ExponentP.pObjData, &pPrivateKey->ExponentP.iObjSize, "exponentP")) == NULL) + { + return(-15); + } + // parse exponentQ + if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->ExponentQ.pObjData, &pPrivateKey->ExponentQ.iObjSize, "exponentQ")) == NULL) + { + return(-16); + } + // parse coefficient + if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->Coefficient.pObjData, &pPrivateKey->Coefficient.iObjSize, "coefficient")) == NULL) + { + return(-17); + } + return(iPrivKeySize); +} + +/*F********************************************************************************/ +/*! + \Function _AsnParsePrivateKeyEcdsa + + \Description + Parse public key modulus and public or private key exponent from private + key certificate. + + \Input *pPrivateKeyData - private key certificate data in base64 form + \Input iPrivateKeyLen - length of private key certificate data + \Input *pPrivateKey - [out] parsed private key data + + \Output + int32_t - private key certificate binary (decoded) size, or negative on error + + \Notes + + ref: https://tools.ietf.org/html/rfc5915#section-3 + + ECPrivateKey ::= SEQUENCE { + version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + privateKey OCTET STRING, + parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + publicKey [1] BIT STRING OPTIONAL + } + + \Version 03/09/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _AsnParsePrivateKeyEcdsa(const char *pPrivateKeyData, int32_t iPrivateKeyLen, X509PrivateKeyT *pPrivateKey) +{ + int32_t iPrivKeySize, iSize, iTemp = 0; + const uint8_t *pData; + + // clear output structure + ds_memclr(pPrivateKey, sizeof(*pPrivateKey)); + + // decode private key into private key buffer + if ((iPrivKeySize = Base64Decode3(pPrivateKeyData, iPrivateKeyLen, pPrivateKey->strPrivKeyData, sizeof(pPrivateKey->strPrivKeyData))) == 0) + { + NetPrintf(("protossl: _AsnParsePrivateKey: could not decode private key\n")); + return(-2); + } + // parse the sequence + if ((pData = _AsnParseHeaderType((uint8_t *)pPrivateKey->strPrivKeyData, (uint8_t *)pPrivateKey->strPrivKeyData+iPrivKeySize, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + NetPrintf(("protossl: _AsnParsePrivateKey: could not get private key sequence\n")); + return(-3); + } + // skip the version + if ((pData = _AsnParseBinary(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, NULL, 0, &iTemp, "version")) == NULL) + { + return(-4); + } + // parse the private key; we store it in the Modulus field + if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_OCTSTRING+ASN_PRIMITIVE, &pPrivateKey->Modulus.pObjData, &pPrivateKey->Modulus.iObjSize, "key modulus")) == NULL) + { + return(-5); + } + return(iPrivKeySize); +} + +/* + asn.1 writing +*/ + +/*F********************************************************************************/ +/*! + \Function _AsnWriteBn + + \Description + Writes a BigNumber as an ASN.1 object + + \Input *pBuffer - [out] storage for generated object + \Input iBufSize - size of output buffer + \Input *pBn - BigNumber to write + \Input uAsnType - ASN.1 object type to write + + \Output + uint8_t * - pointer past end of written object + + \Version 03/13/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t *_AsnWriteBn(uint8_t *pBuffer, int32_t iBufSize, const CryptBnT *pBn, uint8_t uAsnType) +{ + int32_t iByteLen = CryptBnByteLen(pBn); + uint8_t *pSize; + *pBuffer++ = uAsnType; + pSize = pBuffer++; + if ((uAsnType & (ASN_TYPE_BITSTRING|ASN_TYPE_INTEGER)) && (CryptBnBitTest(pBn, (iByteLen*8)-1))) + { + *pBuffer++ = 0; + } + CryptBnFinal(pBn, pBuffer, iByteLen); + pBuffer += iByteLen; + *pSize = pBuffer-pSize-1; + return(pBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _AsnWriteSignatureEcdsa + + \Description + Writes an ASN.1 Elliptic Curve signature + + \Input *pSigData - [out] storage for generated ASN.1 encoded signature + \Input iSigSize - size of output buffer + \Input *pSignature - signature to write + + \Output + int32_t - size of written signature + + \Version 03/13/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _AsnWriteSignatureEcdsa(uint8_t *pSigData, int32_t iSigSize, CryptEccPointT *pSignature) +{ + uint8_t *pSigOut = pSigData, *pSigLen; + // write the signature to output buffer + *pSigOut++ = ASN_CONSTRUCT|ASN_TYPE_SEQN; + // save pointer to length + pSigLen = pSigOut++; + // add signature.x + pSigOut = _AsnWriteBn(pSigOut, iSigSize, &pSignature->X, ASN_TYPE_INTEGER|ASN_PRIMITIVE); + // add signature.y + pSigOut = _AsnWriteBn(pSigOut, iSigSize, &pSignature->Y, ASN_TYPE_INTEGER|ASN_PRIMITIVE); + // write construct length (does not include type or length) + *pSigLen = pSigOut-pSigData-2; + // return total size + return(*pSigLen+2); +} + +/*F********************************************************************************/ +/*! + \Function _AsnWriteDigitalHashObject + + \Description + Generate a DigitalHash object + + \Input *pBuffer - [out] storage for generated digitalhash object + \Input iBufSize - size of output buffer + \Input *pHashData - pointer to hash data to embed in the object (may be NULL) + \Input iHashSize - size of hash data + \Input eHashType - type of hash + + \Output + int32_t - size of generated object + + \Version 04/30/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _AsnWriteDigitalHashObject(uint8_t *pBuffer, int32_t iBufSize, const uint8_t *pHashData, int32_t iHashSize, CryptHashTypeE eHashType) +{ + const ASNObjectT *pObject; + uint8_t *pBufStart = pBuffer; + + // make sure there's space + if (iBufSize < (CRYPTHASH_MAXDIGEST+64)) + { + NetPrintf(("protossl: insufficient space for digital hash object\n")); + return(0); + } + // get ASN object to encode + if ((pObject = _AsnGetObject(_SSL3_CrypttoASN[eHashType])) == NULL) + { + NetPrintf(("protossl: failed to get digital hash ASN object\n")); + return(0); + } + + /* generate DER-encoded DigitalHash object as per http://tools.ietf.org/html/rfc5246#section-4.7; note that + this code assumes the total object size is 127 bytes or fewer as it uses single-byte length encoding. + format defined by https://tools.ietf.org/html/rfc8017#section-9.2 */ + *pBuffer++ = ASN_CONSTRUCT | ASN_TYPE_SEQN; + *pBuffer++ = (2) + (2 + pObject->iSize) + (2) + (2 + iHashSize); + *pBuffer++ = ASN_CONSTRUCT | ASN_TYPE_SEQN; + *pBuffer++ = (2 + pObject->iSize) + (2); + // add hash object + *pBuffer++ = ASN_TYPE_OBJECT; + *pBuffer++ = pObject->iSize; + // add asn.1 object identifier for hash + ds_memcpy(pBuffer, pObject->strData, pObject->iSize); + pBuffer += pObject->iSize; + // add null object + *pBuffer++ = ASN_TYPE_NULL; + *pBuffer++ = 0x00; + // add hash object (octet string) + *pBuffer++ = ASN_TYPE_OCTSTRING; + *pBuffer++ = iHashSize; + // add hash data + if (pHashData != NULL) + { + ds_memcpy(pBuffer, pHashData, iHashSize); + pBuffer += iHashSize; + } + + // return final object size + return(pBuffer - pBufStart); +} + +/* + certificate functions +*/ + +/*F********************************************************************************/ +/*! + \Function _CertificateFindSignature + + \Description + Find PEM signature in the input data, if it exists, and return a + pointer to the encapsulated data. + + \Input *pCertData - pointer to data to scan + \Input iCertSize - size of input data + \Input *pSigText - signature text to find + + \Output + const uint8_t * - pointer to data, or null if not found + + \Version 01/14/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_CertificateFindSignature(const uint8_t *pCertData, int32_t iCertSize, const char *pSigText) +{ + int32_t iSigLen = (int32_t)strlen(pSigText); + int32_t iCertIdx; + + for (iCertIdx = 0; iCertIdx < iCertSize; iCertIdx += 1) + { + if ((pCertData[iCertIdx] == *pSigText) && ((iCertSize - iCertIdx) >= iSigLen)) + { + if (!strncmp((const char *)pCertData+iCertIdx, pSigText, iSigLen)) + { + return(pCertData+iCertIdx); + } + } + } + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function _CertificateFindData + + \Description + Finds PEM signature in the input data, if it exists, and stores the + offsets to the beginning and end of the embedded signature data. + + \Input *pCertData - pointer to data to scan + \Input iCertSize - size of input data + \Input **pCertBeg - [out] storage for index to beginning of certificate data + \Input **pCertEnd - [out] storage for index to end of certificate data + \Input *pCertType - [out] storage for certificate type (ASN_OBJ_\*) + + \Output + int32_t - 0=did not find certificate, else did find certificate + + \Version 01/14/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CertificateFindData(const uint8_t *pCertData, int32_t iCertSize, const uint8_t **pCertBeg, const uint8_t **pCertEnd, uint32_t *pCertType) +{ + const CertificateSignatureT *pCertSig; + int32_t iCertSig; + + for (iCertSig = 0; iCertSig < (signed)(sizeof(_SSL3_CertSignatures)/sizeof(_SSL3_CertSignatures[0])); iCertSig += 1) + { + pCertSig = &_SSL3_CertSignatures[iCertSig]; + + // make sure "end-cert" occurs after start since we support bundles + if (((*pCertBeg = _CertificateFindSignature(pCertData, iCertSize, pCertSig->pCertBeg)) != NULL) && + ((*pCertEnd = _CertificateFindSignature(*pCertBeg, (int32_t)(pCertData+iCertSize-*pCertBeg), pCertSig->pCertEnd)) != NULL)) + { + // skip begin signature + *pCertBeg += strlen(pCertSig->pCertBeg); + // write cert type + *pCertType = pCertSig->uCertType; + // return size to caller + return((int32_t)(*pCertEnd-*pCertBeg)); + } + } + + // if no signature, assume it's just straight base64 pkcs1 + *pCertBeg = pCertData; + *pCertEnd = pCertData + iCertSize; + *pCertType = ASN_OBJ_RSA_PKCS_KEY; + return(iCertSize); +} + +/*F********************************************************************************/ +/*! + \Function _CertificateDecodePrivate + + \Description + Parse public key modulus and public or private key exponent from private + key certificate. + + \Input *pPrivateKeyData - private key certificate data in base64 form + \Input iPrivateKeyLen - length of private key certificate data + \Input *pPrivateKey - [out] parsed private key data + + \Output + int32_t - private key certificate binary (decoded) size, or negative on error + + \Version 03/09/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CertificateDecodePrivate(const char *pPrivateKeyData, int32_t iPrivateKeyLen, X509PrivateKeyT *pPrivateKey) +{ + const uint8_t *pCertBeg, *pCertEnd; + int32_t iCertSize, iResult; + uint32_t uCertType; + + // make sure we have key data first + if (pPrivateKeyData == NULL) + { + NetPrintf(("protossl: _AsnParsePrivateKey: no private key set\n")); + return(-1); + } + + // find the base64-encoded certificate data + iCertSize = _CertificateFindData((const uint8_t *)pPrivateKeyData, iPrivateKeyLen, &pCertBeg, &pCertEnd, &uCertType); + + // parse ecdsa or rsa? + if (uCertType == ASN_OBJ_ECDSA_KEY) + { + iResult = _AsnParsePrivateKeyEcdsa((const char *)pCertBeg, iCertSize, pPrivateKey); + } + else + { + iResult = _AsnParsePrivateKeyRSA((const char *)pCertBeg, iCertSize, pPrivateKey); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _CertificateDecodePublic + + \Description + Decode the specified PEM certificate into binary format, parse and extract some information + + \Input *pState - module state reference + \Input *pPemData - certificate to decode + \Input iPemSize - certificate length + + \Output + CertificateDataT * - pointer to new certificate data, or null + + \Version 03/09/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static CertificateDataT *_CertificateDecodePublic(ProtoSSLRefT *pState, const uint8_t *pPemData, int32_t iPemSize) +{ + const uint8_t *pCertBeg, *pCertEnd; + CertificateDataT *pCertificate = NULL; + X509CertificateT Cert; + int32_t iCertSize; + uint32_t uCertType; + + // find the base64-encoded certificate data + iPemSize = _CertificateFindData(pPemData, iPemSize, &pCertBeg, &pCertEnd, &uCertType); + // get decoded size + iCertSize = Base64Decode3((const char *)pCertBeg, iPemSize, NULL, 0x7fffffff); + // allocate certificate buffer + if ((iCertSize > 0) && ((pCertificate = DirtyMemAlloc(sizeof(*pCertificate)+iCertSize, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) != NULL)) + { + // base64 decode to binary + Base64Decode3((const char *)pCertBeg, iPemSize, (char *)pCertificate->aCertData, iCertSize); + // parse the certificate + _AsnParseCertificate(&Cert, pCertificate->aCertData, iCertSize); + // save some cert info + pCertificate->iCertSize = iCertSize; + pCertificate->iCrvType = Cert.iCrvType; + pCertificate->iKeyType = Cert.iKeyType; + pCertificate->iSigType = Cert.iSigType; + } + return(pCertificate); +} + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _CertificateDebugFormatIdent + + \Description + Print cert ident info to debug output. + + \Input *pIdent - cert ident info + \Input *pStrBuf - [out] buffer to format cert ident into + \Input iBufLen - length of buffer + + \Output + char * - pointer to result string + + \Version 01/13/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_CertificateDebugFormatIdent(const ProtoSSLCertIdentT *pIdent, char *pStrBuf, int32_t iBufLen) +{ + ds_snzprintf(pStrBuf, iBufLen, "C=%s, ST=%s, L=%s, O=%s, OU=%s, CN=%s", + pIdent->strCountry, pIdent->strState, pIdent->strCity, + pIdent->strOrg, pIdent->strUnit, pIdent->strCommon); + return(pStrBuf); +} +#else +#define _CertificateDebugFormatIdent(__pIdent, __pStrBuf, __iBufLen) +#endif + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _CertificateDebugPrint + + \Description + Print cert ident info to debug output. + + \Input *pCert - cert to print debug info for + \Input *pMessage - string identifier (may be null) + + \Version 01/13/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CertificateDebugPrint(const X509CertificateT *pCert, const char *pMessage) +{ + char strCertIdent[1024]; + #if DEBUG_VAL_CERT + char strTimeFrom[32], strTimeTill[32]; + char strMaxHeight[32]; + #endif + if (pMessage != NULL) + { + NetPrintf(("protossl: %s\n", pMessage)); + } + NetPrintf(("protossl: issuer: %s\n", _CertificateDebugFormatIdent(&pCert->Issuer, strCertIdent, sizeof(strCertIdent)))); + NetPrintf(("protossl: subject: %s\n", _CertificateDebugFormatIdent(&pCert->Subject, strCertIdent, sizeof(strCertIdent)))); + #if DEBUG_VAL_CERT + NetPrintf(("protossl: valid: from %s to %s\n", + ds_secstostr(pCert->uGoodFrom, TIMETOSTRING_CONVERSION_RFC_0822, FALSE, strTimeFrom, sizeof(strTimeFrom)), + ds_secstostr(pCert->uGoodTill, TIMETOSTRING_CONVERSION_RFC_0822, FALSE, strTimeTill, sizeof(strTimeTill)))); + NetPrintf(("protossl: keytype: %d\n", pCert->iKeyType)); + NetPrintf(("protossl: keyexp size: %d\n", pCert->iKeyExpSize)); + NetPrintf(("protossl: keymod size: %d\n", pCert->iKeyModSize)); + NetPrintf(("protossl: sigtype: %d\n", pCert->iSigType)); + NetPrintf(("protossl: sigsize: %d\n", pCert->iSigSize)); + ds_snzprintf(strMaxHeight, sizeof(strMaxHeight), "%d", pCert->iMaxHeight - 1); + NetPrintf(("protossl: CertIsCA(%d): pathLenConstraints(%s)\n", pCert->iCertIsCA, (pCert->iMaxHeight == 0) ? "unlimited" : strMaxHeight)); + #endif +} +#else +#define _CertificateDebugPrint(__pCert, __pMessage) +#endif + +/*F********************************************************************************/ +/*! + \Function _CertificateSetFailureInfo + + \Description + Set the certificate info (used on failure) + + \Input *pState - module state (may be NULL) + \Input *pCert - pointer to certificate to fill in from header data (may be NULL) + \Input bIssuer - if TRUE copy issuer, else subject + + \Version 01/24/2012 (szhu) +*/ +/********************************************************************************F*/ +static void _CertificateSetFailureInfo(ProtoSSLRefT *pState, X509CertificateT *pCert, uint8_t bIssuer) +{ + if ((pState != NULL) && (pCert != NULL) && (pState->bCertInfoSet == FALSE)) + { + const ProtoSSLCertIdentT *pCertIdent = bIssuer ? &pCert->Issuer : &pCert->Subject; + ds_memcpy_s(&pState->CertInfo.Ident, sizeof(pState->CertInfo.Ident), pCertIdent, sizeof(*pCertIdent)); + pState->CertInfo.iKeyModSize = pCert->iSigSize; // CACert::KeyModSize == Cert::SigSize + pState->bCertInfoSet = TRUE; + } +} + +/*F********************************************************************************/ +/*! + \Function _CertificateCompareIdent + + \Description + Compare two identities + + \Input *pIdent1 - pointer to ProtoSSLCertIdentT structure + \Input *pIdent2 - pointer to ProtoSSLCertIdentT structure + \Input bMatchUnit - if TRUE compare the Unit string + + \Output + int32_t - zero=match, non-zero=no-match + + \Notes + Unit is considered an optional match criteria by openssl. + + \Version 03/11/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CertificateCompareIdent(const ProtoSSLCertIdentT *pIdent1, const ProtoSSLCertIdentT *pIdent2, uint8_t bMatchUnit) +{ + int32_t iResult; + + #if DEBUG_VAL_CERT + char strIdent1[256], strIdent2[256]; + _CertificateDebugFormatIdent(pIdent1, strIdent1, sizeof(strIdent1)); + _CertificateDebugFormatIdent(pIdent2, strIdent2, sizeof(strIdent2)); + NetPrintf(("protossl: comparing '%s' to '%s'\n", strIdent2, strIdent1)); + #endif + + iResult = strcmp(pIdent1->strCountry, pIdent2->strCountry) != 0; + iResult += strcmp(pIdent1->strState, pIdent2->strState) != 0; + iResult += strcmp(pIdent1->strCity, pIdent2->strCity) != 0; + iResult += strcmp(pIdent1->strOrg, pIdent2->strOrg) != 0; + iResult += strcmp(pIdent1->strCommon, pIdent2->strCommon) != 0; + if (bMatchUnit) + { + iResult += strcmp(pIdent1->strUnit, pIdent2->strUnit) != 0; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _CertificateMatchHostname + + \Description + Perform wildcard case-insensitive string comparison of given input + strings. This implementation assumes the first string does not + include a wildcard character and the second string includes exactly + zero or one wildcard characters, which must be an asterisk. The + wildcard is intentionally limited as per the notes below to only + match a single domain fragment. + + \Input *pString1 - first string to compare + \Input *pString2 - second string to compare + + \Output + int32_t - like strcmp() + + \Notes + As specified per RFC 2818: + + \verbatim + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + \endverbatim + + \Version 03/19/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CertificateMatchHostname(const char *pString1, const char *pString2) +{ + int32_t r; + char c1, c2; + + do { + c1 = *pString1++; + if ((c1 >= 'A') && (c1 <= 'Z')) + c1 ^= 32; + c2 = *pString2; + if ((c2 >= 'A') && (c2 <= 'Z')) + c2 ^= 32; + if ((c2 == '*') && (c1 != '.') && (c1 != '\0')) + { + r = _CertificateMatchHostname(pString1, pString2+1); + if (r == 0) + { + break; + } + r = 0; + } + else + { + pString2 += 1; + r = c1-c2; + } + } while ((c1 != 0) && (c2 != 0) && (r == 0)); + + return(r); +} + +/*F********************************************************************************/ +/*! + \Function _CertificateMatchSubjectAlternativeName + + \Description + Takes as input a hostname and Subject Alternative Name object, + returns zero if the hostname matches a SAN object entry and non-zero + otherwise. + + \Input *pStrHost - pointer to hostname to compare against + \Input *pSubject - pointer to subject alternative object + \Input iSubjectAltLen - length of subject alternative object + + \Output + int32_t - like strcmp() + + \Version 11/01/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CertificateMatchSubjectAlternativeName(const char *pStrHost, const uint8_t *pSubject, int32_t iSubjectAltLen) +{ + const uint8_t *pSubjectEnd = pSubject + iSubjectAltLen; + char strCompare[256], *pStrCompare; + int32_t iType, iSize; + + // parse the wrapper object + pSubject = _AsnParseHeader(pSubject, pSubjectEnd, &iType, &iSize); + + // check against subject alternative strings + for (pSubjectEnd = pSubject + iSubjectAltLen; pSubject != NULL; pSubject += iSize) + { + // parse the object + pSubject = _AsnParseHeader(pSubject, pSubjectEnd, &iType, &iSize); + + // if it's not a dnsname object, skip it + if (iType != ASN_IMPLICIT_TAG+2) + { + continue; + } + + // make a copy of the comparison string + ds_strsubzcpy(strCompare, sizeof(strCompare), (const char *)pSubject, iSize); + + // skip leading whitespace, if any + for (pStrCompare = strCompare; ((*pStrCompare > 0) && (*pStrCompare <= ' ')); pStrCompare += 1) + ; + + // do the name compare + if (_CertificateMatchHostname(pStrHost, pStrCompare) == 0) + { + return(0); + } + } + + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _CertificateGetSigAlg + + \Description + Get signature algorithm to use based on certificate key type + + \Input *pCertificate - certificate to get signature algorithm for + + \Output + uint32_t uCertSigAlg - signature algorithm + + \Version 05/12/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _CertificateGetSigAlg(const CertificateDataT *pCertificate) +{ + uint32_t uCertSigAlg = SSL3_SIGALG_NONE; + if (pCertificate != NULL) + { + uCertSigAlg = (pCertificate->iKeyType == ASN_OBJ_ECDSA_KEY) ? SSL3_SIGALG_ECDSA : SSL3_SIGALG_RSA; + } + return(uCertSigAlg); +} + +/* + pkcs1 verification and generation +*/ + +/*F********************************************************************************/ +/*! + \Function _Pkcs1VerifyRSAES + + \Description + Validate RSAES-PKCS1-v1_5 padding as defined by + https://tools.ietf.org/html/rfc8017#section-7.2. + + \Input *pData - data to validate + \Input iKeySize - key modulus size, in bytes + \Input iSecretSize - premaster secret size, in bytes + + \Output + int32_t - zero=invalid, else valid + + \Version 02/18/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _Pkcs1VerifyRSAES(const uint8_t *pData, int32_t iKeySize, int32_t iSecretSize) +{ + int32_t iIndex; + uint8_t bValid = TRUE; + + // validate signature + if ((pData[0] != 0) || (pData[1] != 2)) + { + NetPrintf(("protossl: pkcs1 signature invalid\n")); + bValid = FALSE; + } + // validate random padding; must be non-zero + for (iIndex = 2; iIndex < iKeySize-iSecretSize-1; iIndex += 1) + { + if (pData[iIndex] == 0) + { + NetPrintf(("protossl: pkcs1 padding validation found zero byte\n")); + bValid = FALSE; + break; + } + } + // validate zero byte before premaster key data + if (pData[iKeySize-iSecretSize-1] != 0) + { + NetPrintf(("protossl: pkcs1 premaster key padding not zero\n")); + bValid = FALSE; + } + // return result + return(bValid); +} + +/*F********************************************************************************/ +/*! + \Function _Pkcs1VerifyEMSA + + \Description + As per https://tools.ietf.org/html/rfc5246#section-4.7 validate + EMSA-PKCS1-v1_5 padding, and return pointer to signature hash if valid. + Format defined by https://tools.ietf.org/html/rfc8017#section-9.2. + + \Input *pData - data to validate + \Input iSigSize - signature size, in bytes + \Input iHashSize - signature hash size, in bytes + \Input eHashType - signature hash type (if CRYPTHASH_NULL, don't validate hash object) + + \Output + uint8_t * - pointer to signature data, or NULL if invalid + + \Version 04/27/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static const uint8_t *_Pkcs1VerifyEMSA(const uint8_t *pData, int32_t iSigSize, int32_t iHashSize, CryptHashTypeE eHashType) +{ + uint8_t aHashObj[CRYPTHASH_MAXDIGEST+64]; + int32_t iIndex, iSize; + + // validate EMSA signature + if ((pData[0] != 0) || (pData[1] != 1)) + { + return(NULL); + } + // validate padding; must be 0xff + for (iIndex = 2; (iIndex < (iSigSize-iHashSize)) && (pData[iIndex] == 0xff); iIndex += 1) + ; + // validate zero byte immediately preceding signature hash object + if (pData[iIndex++] != 0) + { + return(NULL); + } + + // validate the digital hash object that wraps the signature hash + if (eHashType != CRYPTHASH_NULL) + { + // generate digitahash object we expect, and validate we got it + if (((iSize = _AsnWriteDigitalHashObject(aHashObj, sizeof(aHashObj), NULL, iHashSize, eHashType)) == 0) || (memcmp(aHashObj, pData + iIndex, iSize))) + { + return(NULL); + } + iIndex += iSize; + } + + // make sure we validated the entire signature, minus the hash itself + if ((iIndex+iHashSize) != iSigSize) + { + return(NULL); + } + + // return pointer to signature hash + return(pData+iIndex); +} + +/*F********************************************************************************/ +/*! + \Function _Pkcs1DoMGF1 + + \Description + Implements MFG1 as per https://tools.ietf.org/html/rfc8017#appendix-B.2.1 + + \Input *pOutput - [out] output of mgf + \Input iOutputLen - length of output buf + \Input *pInput - mgf input + \Input iInputLen - length of mgf input + \Input eHashType - mgf hash type + + \Version 05/11/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _Pkcs1DoMGF1(uint8_t *pOutput, int32_t iOutputLen, const uint8_t *pInput, int32_t iInputLen, CryptHashTypeE eHashType) +{ + uint8_t aHashState[CRYPTHASH_MAXSTATE], aCounter[4]; + const CryptHashT *pHash; + int32_t iHashLen; + uint32_t uRound; + + // get hash object and length + if ((pHash = CryptHashGet(eHashType)) == NULL) + { + return; + } + iHashLen = CryptHashGetSize(eHashType); + + // do the hashing + for (uRound = 0; iOutputLen > 0; uRound += 1, pOutput += iHashLen, iOutputLen -= iHashLen) + { + aCounter[0] = (uint8_t)(uRound >> 24); + aCounter[1] = (uint8_t)(uRound >> 16); + aCounter[2] = (uint8_t)(uRound >> 8); + aCounter[3] = (uint8_t)(uRound); + + pHash->Init(aHashState, iHashLen); + pHash->Update(aHashState, pInput, iHashLen); + pHash->Update(aHashState, aCounter, sizeof(aCounter)); + pHash->Final(aHashState, pOutput, iHashLen); + } +} + +/*F********************************************************************************/ +/*! + \Function _Pkcs1VerifyEMSA_PSS + + \Description + As per https://tools.ietf.org/html/rfc8017#section-9.1.2 validate + EMSA-PSS envelope, and compare against the specified hash value. + + \Input *pSigData - signature data + \Input iSigSize - signature size, in bytes + \Input eSigHash - signature hash + \Input iSaltSize - salt size, in bytes + \Input *pMsgHash - message hash data + \Input iHashSize - signature hash size, in bytes + \Input eMgfHash - mgf hash type + + \Output + uint32_t - zero=verify failure, else success + + \Notes + This function deliberately uses coding standards and comments matching + the specification to make it easier to compare while reading the specs. + + The maximum salt length supported is 128 bytes (2*CRYPTHASH_MAXDIGEST) + + \Version 05/11/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _Pkcs1VerifyEMSA_PSS(const uint8_t *pSigData, int32_t iSigSize, CryptHashTypeE eSigHash, int32_t iSaltSize, const uint8_t *pMsgHash, int32_t iHashSize, CryptHashTypeE eMgfHash) +{ + uint8_t maskedDB[SSL_SIG_MAX], dbMask[SSL_SIG_MAX], DB[SSL_SIG_MAX], H[CRYPTHASH_MAXDIGEST], salt[CRYPTHASH_MAXDIGEST*2]; + const CryptHashT *pHash = CryptHashGet(eSigHash); + uint8_t aHashState[CRYPTHASH_MAXSTATE]; + uint8_t Mprime[CRYPTHASH_MAXDIGEST*3 + 8], Hprime[CRYPTHASH_MAXDIGEST]; + int32_t iDB, iDBLen; + uint8_t uCheck; + + // if the rightmost octet of EM does not have hexadecimal value 0xbc, output "inconsistent" and stop. + if (pSigData[iSigSize-1] != 0xBC) + { + NetPrintf(("protossl: signature failed identity check\n")); + return(0); + } + + // let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and let H be the next hLen octets. + iDBLen = iSigSize - iHashSize - 1; + ds_memcpy_s(maskedDB, sizeof(maskedDB), pSigData, iDBLen); + ds_memcpy_s(H, sizeof(H), pSigData + iDBLen, iHashSize); + + /* if the leftmost 8emLen - emBits bits of the leftmost octet in maskedDB are not all equal to zero, output "inconsistent" and stop. + note that we assume our modulus size here is a multiple of eight bits */ + if (maskedDB[0] & 0x80) + { + NetPrintf(("protossl: signature failed masked validity check\n")); + return(0); + } + + // let dbMask = MGF(H, emLen - hLen - 1). + _Pkcs1DoMGF1(dbMask, iDBLen, H, iDBLen, eSigHash); + + // let DB = maskedDB \xor dbMask. + for (iDB = 0; iDB < iDBLen; iDB += 1) + { + DB[iDB] = maskedDB[iDB] ^ dbMask[iDB]; + } + + /* set the leftmost 8emLen - emBits bits of the leftmost octet in DB to zero. + note that we assume our modulus size here is a multiple of eight bits */ + DB[0] &= 0x7f; + + #if DEBUG_RAW_DATA + NetPrintMem(DB, iDBLen, "certificate verify unmasked data"); + #endif + + /* if the emLen - hLen - sLen - 2 leftmost octets of DB are not zero or if the octet at position emLen - hLen - sLen - 1 + (the leftmost position is "position 1") does not have hexadecimal value 0x01, output "inconsistent" and stop. */ + for (iDB = 0, uCheck = 0; iDB < (iDBLen - iSaltSize - 1); iDB += 1) + uCheck |= DB[iDB]; + if ((uCheck != 0) || (DB[iDB] != 1)) + { + NetPrintf(("protossl: signature failed padding validity check\n")); + return(0); + } + + // validate salt length + if (iSaltSize > (signed)sizeof(salt)) + { + NetPrintf(("protossl: salt length %d too big in signature verify\n", iSaltSize)); + return(0); + } + // let salt be the last sLen octets of DB. + ds_memcpy_s(salt, sizeof(salt), DB + iDBLen - iSaltSize, iSaltSize); + + // let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt + ds_memclr(Mprime, 8); + ds_memcpy(Mprime + 8, pMsgHash, iHashSize); + ds_memcpy(Mprime + 8 + iHashSize, salt, iSaltSize); + + // let H' = Hash(M'), an octet string of length hLen. + pHash->Init(aHashState, iHashSize); + pHash->Update(aHashState, Mprime, 8 + iHashSize + iSaltSize); + pHash->Final(aHashState, Hprime, iHashSize); + + #if DEBUG_RAW_DATA + NetPrintMem(H, iHashSize, "H"); + NetPrintMem(Hprime, iHashSize, "Hprime"); + NetPrintMem(pMsgHash, iHashSize, "MsgHash"); + NetPrintMem(salt, iSaltSize, "salt"); + #endif + + // if H = H', output "consistent". Otherwise, output "inconsistent". + if (memcmp(H, Hprime, iHashSize)) + { + NetPrintf(("protossl: signature failed hash validity check\n")); + return(0); + } + + // success + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _Pkcs1EncodeEMSA_PSS + + \Description + As per https://tools.ietf.org/html/rfc8017#section-9.1.1 encode + specified hash data in an EMSA-PSS envelope. + + \Input *pBuffer - [out] storage for encoded message + \Input iBufSize - size of output buffer + \Input *pHashData - message hash + \Input iHashSize - size of hash + \Input eHashType - hash type + + \Output + int32_t - size of encoded message, or negative on error + + \Notes + This function deliberately uses coding standards and comments matching + the specification to make it easier to compare while reading the specs. + + \Version 05/16/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _Pkcs1EncodeEMSA_PSS(uint8_t *pBuffer, int32_t iBufSize, const uint8_t *pHashData, int32_t iHashSize, CryptHashTypeE eHashType) +{ + uint8_t maskedDB[SSL_SIG_MAX], dbMask[SSL_SIG_MAX], DB[SSL_SIG_MAX]; + uint8_t aSaltBuf[CRYPTHASH_MAXDIGEST], Mprime[(CRYPTHASH_MAXDIGEST*2)+8], H[(CRYPTHASH_MAXDIGEST*2)+8], *pTemp; + const CryptHashT *pHash = CryptHashGet(eHashType); + uint8_t aHashState[CRYPTHASH_MAXSTATE]; + int32_t iDB, iDBLen = iBufSize - iHashSize - 1; + + // if emLen < hLen + sLen + 2, output "encoding error" and stop. + if (iBufSize < ((iHashSize*2)+2)) + { + return(-1); + } + // generate a random octet string salt of length sLen; if sLen = 0, then salt is the empty string. + CryptRandGet(aSaltBuf, sizeof(aSaltBuf)); + // let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt + pTemp = Mprime; + ds_memclr(pTemp, 8); + pTemp += 8; + ds_memcpy(pTemp, pHashData, iHashSize); + pTemp += iHashSize; + ds_memcpy(pTemp, aSaltBuf, iHashSize); + + // let H = Hash(M'), an octet string of length hLen. + pHash->Init(aHashState, iHashSize); + pHash->Update(aHashState, Mprime, 8 + iHashSize + iHashSize); + pHash->Final(aHashState, H, iHashSize); + + #if DEBUG_RAW_DATA + NetPrintMem(H, iHashSize, "H"); + NetPrintMem(pHashData, iHashSize, "MsgHash"); + NetPrintMem(aSaltBuf, iHashSize, "salt"); + #endif + + // generate an octet string PS consisting of emLen - sLen - hLen - 2 zero octets. The length of PS may be 0. + ds_memclr(DB, iBufSize - (iHashSize*2) - 2); + pTemp = DB + iBufSize - (iHashSize*2) - 2; + + // let DB = PS || 0x01 || salt; DB is an octet string of length emLen - hLen - 1. + *pTemp++ = 0x01; + ds_memcpy(pTemp, aSaltBuf, iHashSize); + + // let dbMask = MGF(H, emLen - hLen - 1). + _Pkcs1DoMGF1(dbMask, iDBLen, H, iDBLen, eHashType); + + // let maskedDB = DB \xor dbMask. + for (iDB = 0; iDB < iDBLen; iDB += 1) + { + maskedDB[iDB] = DB[iDB] ^ dbMask[iDB]; + } + + /* set the leftmost 8emLen - emBits bits of the leftmost octet in maskedDB to zero. + note that we assume our modulus size here is a multiple of eight bits */ + maskedDB[0] &= 0x7f; + + // let EM = maskedDB || H || 0xbc. + pTemp = pBuffer; + ds_memcpy(pTemp, maskedDB, iDBLen); + pTemp += iDBLen; + ds_memcpy(pTemp, H, iHashSize); + pTemp += iHashSize; + *pTemp++ = 0xbc; + + // output EM + return(pTemp-pBuffer); +} + +/* + session history +*/ + +/*F********************************************************************************/ +/*! + \Function _SessionHistorySet + + \Description + Set entry in session history buffer + + \Input *pSecure - secure state + \Input *pSessHist - session history entry to set + \Input *pSessTick - session ticket for session (if tls1.3; else NULL) + \Input *pHostName - hostname session is associated with + \Input uPort - port session is associated with + \Input *pSessionId - session id of session + \Input *pMasterSecret- master secret associated with session + \Input uCurTick - current tick count + + \Version 03/15/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SessionHistorySet(SecureStateT *pSecure, SessionHistoryT *pSessHist, SessionTicketT *pSessTick, const char *pHostName, uint16_t uPort, const uint8_t *pSessionId, const uint8_t *pMasterSecret, uint32_t uCurTick) +{ + // save hostname & port + ds_strnzcpy(pSessHist->strHost, pHostName, sizeof(pSessHist->strHost)); + pSessHist->uPort = uPort; + // set access tick + pSessHist->uSessionUseTick = uCurTick; + // set expire tick; add a 250ms fudge factor to make sure we don't send an expiring ticket + pSessHist->uSessionExpTick = (pSessTick != NULL) ? pSessTick->uLifetime * 1000 : SSL_SESSVALIDTIME_MAX; + pSessHist->uSessionExpTick += uCurTick-250; + + if (pSessTick != NULL) + { + pSessHist->bSessionTicket = TRUE; + ds_memcpy_s(&pSessHist->SessionTicket, sizeof(pSessHist->SessionTicket), pSessTick, sizeof(*pSessTick)); + } + else + { + SessionInfoT *pSessInfo = &pSessHist->SessionInfo; + pSessHist->bSessionTicket = FALSE; + pSessInfo->uSslVersion = pSecure->uSslVersion; + pSessInfo->uCipherId = pSecure->pCipher->uIdent; + ds_memcpy(pSessInfo->SessionId, pSessionId, sizeof(pSessInfo->SessionId)); + ds_memcpy(pSessInfo->MasterSecret, pMasterSecret, sizeof(pSessInfo->MasterSecret)); + } +} + +/*F********************************************************************************/ +/*! + \Function _SessionHistoryGet + + \Description + Returns pointer to specified session history entry, or NULL if not found. + + \Input *pHostName - hostname associated with session (may be null) + \Input uPort - port associated with session (if using hostname) + \Input *pSessionId - session id to look for (may be null) + + \Output + SessionHistoryT * - session history entry, or NULL if not found + + \Version 03/15/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static SessionHistoryT *_SessionHistoryGet(const char *pHostName, uint16_t uPort, const uint8_t *pSessionId) +{ + SessionHistoryT *pSessHist, *pSessInfo; + ProtoSSLStateT *pState = _ProtoSSL_pState; + const uint8_t aZeroSessionId[32] = { 0 }; + int32_t iSess; + + #if DIRTYCODE_DEBUG + if (pState == NULL) + { + NetPrintf(("protossl: warning, protossl not initialized, session history feature is disabled\n")); + return(NULL); + } + #endif + + // exclude NULL sessionId + if ((pSessionId != NULL) && !memcmp(pSessionId, aZeroSessionId, sizeof(aZeroSessionId))) + { + return(NULL); + } + + // search session history for a match + for (iSess = 0, pSessInfo = NULL; (iSess < SSL_SESSHIST_MAX) && (pSessInfo == NULL); iSess += 1) + { + pSessHist = &pState->SessionHistory[iSess]; + + // make sure the ticket isn't expired + if (NetTickDiff(NetTick(), pSessHist->uSessionExpTick) > 0) + { + NetPrintfVerbose((DEBUG_RES_SESS, 0, "protossl: expiring session for %s:%d\n", pSessHist->strHost, pSessHist->uPort)); + continue; + } + + // check for resume by hostname/port + if ((pHostName != NULL) && (!strcmp(pSessHist->strHost, pHostName)) && (pSessHist->uPort == uPort)) + { + pSessInfo = pSessHist; + } + // check for resume by sessionId (tls1.2 and prior) + if ((pSessionId != NULL) && (!memcmp(pSessHist->SessionInfo.SessionId, pSessionId, sizeof(pSessHist->SessionInfo.SessionId)))) + { + pSessInfo = pSessHist; + } + } + + // return session (or null) to caller + return(pSessInfo); +} + +/*F********************************************************************************/ +/*! + \Function _SessionHistoryAdd + + \Description + Save a new session in the session history buffer + + \Input *pSecure - secure state + \Input *pHostName - hostname session is for + \Input uPort - port session is for + \Input *pSessTick - session ticket for session, if tls1.3 + \Input *pSessionId - session id for session + \Input iSessionLen - length of session + \Input *pMasterSecret - master secret associated with session + + \Version 03/15/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SessionHistoryAdd(SecureStateT *pSecure, const char *pHostName, uint16_t uPort, SessionTicketT *pSessTick, const uint8_t *pSessionId, uint32_t iSessionLen, const uint8_t *pMasterSecret) +{ + ProtoSSLStateT *pState = _ProtoSSL_pState; + SessionHistoryT *pSessHist; + int32_t iSess, iSessOldest; + int32_t iTickDiff, iTickDiffMax; + uint32_t uCurTick = NetTick(); + + #if DIRTYCODE_DEBUG + if (pState == NULL) + { + NetPrintf(("protossl: warning, protossl not initialized, session history feature is disabled\n")); + return; + } + #endif + + NetCritEnter(&pState->StateCrit); + + // see if we already have a session for this peer + for (iSess = 0; iSess < SSL_SESSHIST_MAX; iSess += 1) + { + pSessHist = &pState->SessionHistory[iSess]; + // already have this peer in our history? + if (!strcmp(pSessHist->strHost, pHostName) && (pSessHist->uPort == uPort)) + { + NetPrintfVerbose((DEBUG_RES_SESS, 0, "protossl: updating session history entry %d (host=%s:%d)\n", iSess, pHostName, uPort)); + _SessionHistorySet(pSecure, pSessHist, pSessTick, pHostName, uPort, pSessionId, pMasterSecret, uCurTick); + NetCritLeave(&pState->StateCrit); + return; + } + } + + // try to find an unallocated session + for (iSess = 0; iSess < SSL_SESSHIST_MAX; iSess += 1) + { + pSessHist = &pState->SessionHistory[iSess]; + // empty slot? + if (pSessHist->strHost[0] == '\0') + { + NetPrintfVerbose((DEBUG_RES_SESS, 0, "protossl: adding session history entry %d (host=%s:%d)\n", iSess, pHostName, uPort)); + _SessionHistorySet(pSecure, pSessHist, pSessTick, pHostName, uPort, pSessionId, pMasterSecret, uCurTick); + NetCritLeave(&pState->StateCrit); + return; + } + } + + // find the oldest session + for (iSess = 0, iTickDiffMax = 0, iSessOldest = 0; iSess < SSL_SESSHIST_MAX; iSess += 1) + { + pSessHist = &pState->SessionHistory[iSess]; + // find least recently updated session + if ((iTickDiff = NetTickDiff(uCurTick, pSessHist->uSessionUseTick)) > iTickDiffMax) + { + iTickDiffMax = iTickDiff; + iSessOldest = iSess; + } + } + + NetPrintfVerbose((DEBUG_RES_SESS, 0, "protossl: replacing session history entry %d (host=%s:%d)\n", iSessOldest, pHostName, uPort)); + _SessionHistorySet(pSecure, &pState->SessionHistory[iSessOldest], pSessTick, pHostName, uPort, pSessionId, pMasterSecret, uCurTick); + + NetCritLeave(&pState->StateCrit); +} + +/*F********************************************************************************/ +/*! + \Function _SessionHistoryInvalidate + + \Description + Invalidate specified session from session history cache + + \Input *pHostName - hostname of session to invalidate + \Input uPort - port of session to invalidate + + \Version 02/04/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _SessionHistoryInvalidate(const char *pHostName, uint16_t uPort) +{ + SessionHistoryT *pSessInfo = NULL; + ProtoSSLStateT *pState = _ProtoSSL_pState; + + #if DIRTYCODE_DEBUG + if (pState == NULL) + { + NetPrintf(("protossl: warning, protossl not initialized, session history feature is disabled\n")); + return; + } + #endif + + // acquire access to session history + NetCritEnter(&pState->StateCrit); + + // find session history + if ((pSessInfo = _SessionHistoryGet(pHostName, uPort, NULL)) != NULL) + { + // reset entry + NetPrintf(("protossl: invalidating session entry\n")); + ds_memclr(pSessInfo, sizeof(*pSessInfo)); + } + + // release access to session history, return to caller + NetCritLeave(&pState->StateCrit); +} + +/*F********************************************************************************/ +/*! + \Function _SessionHistoryGetInfo + + \Description + Check to see if we can find a session for the specified address or session + id in our history buffer, and return sesession info if found. + + \Input *pSessBuff - [out] storage for session history entry + \Input *pHostName - hostname of session (may be null) + \Input uPort - port of session (if hostname is not null) + \Input *pSessionId - session id to look for (may be null) + + \Output + SessionHistoryT * - session history entry, or NULL if not found + + \Version 03/15/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static SessionHistoryT *_SessionHistoryGetInfo(SessionHistoryT *pSessBuff, const char *pHostName, uint16_t uPort, const uint8_t *pSessionId) +{ + SessionHistoryT *pSessInfo = NULL; + ProtoSSLStateT *pState = _ProtoSSL_pState; + + #if DIRTYCODE_DEBUG + if (pState == NULL) + { + NetPrintf(("protossl: warning, protossl not initialized, session history feature is disabled\n")); + return(NULL); + } + #endif + + // acquire access to session history + NetCritEnter(&pState->StateCrit); + + // find session history + if ((pSessInfo = _SessionHistoryGet(pHostName, uPort, pSessionId)) != NULL) + { + // update timestamp + pSessInfo->uSessionUseTick = NetTick(); + // copy to caller + ds_memcpy(pSessBuff, pSessInfo, sizeof(*pSessBuff)); + pSessInfo = pSessBuff; + } + + // release access to session history, return to caller + NetCritLeave(&pState->StateCrit); + return(pSessInfo); +} + +/* + certificate validation vistory +*/ + +/*F********************************************************************************/ +/*! + \Function _CertValidHistorySet + + \Description + Set entry in certvalid history buffer + + \Input *pCertHist - session history entry to set + \Input *pFingerprintId - fingerprint for certificates validated + \Input uCertSize - certificate size + \Input uCurTick - current tick count + + \Version 04/29/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CertValidHistorySet(CertValidHistoryT *pCertHist, const uint8_t *pFingerprintId, uint32_t uCertSize, uint32_t uCurTick) +{ + // update fingerprint + ds_memcpy(pCertHist->FingerprintId, pFingerprintId, sizeof(pCertHist->FingerprintId)); + // save cert size + pCertHist->uCertSize = uCertSize; + // set timestamp + pCertHist->uCertValidTick = uCurTick; +} + +/*F********************************************************************************/ +/*! + \Function _CertValidHistoryGet + + \Description + Returns pointer to specified session history entry, or NULL if not found. + + \Input *pFingerprintId - certificate fingerprint + \Input uCertSize - size of certificate to check in validity history + + \Output + CertValidHistoryT * - certificate validation history entry, or NULL if not found + + \Version 04/29/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static CertValidHistoryT *_CertValidHistoryGet(const uint8_t *pFingerprintId, uint32_t uCertSize) +{ + CertValidHistoryT *pCertHist, *pCertInfo; + ProtoSSLStateT *pState = _ProtoSSL_pState; + const uint8_t aZeroFingerprintId[SSL_FINGERPRINT_SIZE] = { 0 }; + uint32_t uCurTick = NetTick(); + int32_t iCert; + + #if DIRTYCODE_DEBUG + if (pState == NULL) + { + NetPrintf(("protossl: warning, protossl not initialized, certificate validation history feature is disabled\n")); + return(NULL); + } + #endif + + // exclude NULL fingerprintId request + if ((pFingerprintId == NULL) || !memcmp(pFingerprintId, aZeroFingerprintId, sizeof(aZeroFingerprintId))) + { + return(NULL); + } + + // find certificate history + for (iCert = 0, pCertInfo = NULL; iCert < SSL_CERTVALID_MAX; iCert += 1) + { + // ref history entry + pCertHist = &pState->CertValidHistory[iCert]; + // skip null entries + if (pCertHist->uCertSize == 0) + { + continue; + } + // check for expiration + if (NetTickDiff(uCurTick, pCertHist->uCertValidTick) > SSL_CERTVALIDTIME_MAX) + { + NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: expiring certificate validity for entry %d\n", iCert)); + ds_memclr(pCertHist, sizeof(*pCertHist)); + continue; + } + // check for match + if ((pCertHist->uCertSize == uCertSize) && (!memcmp(pCertHist->FingerprintId, pFingerprintId, sizeof(pCertHist->FingerprintId)))) + { + pCertInfo = pCertHist; + break; + } + } + + // return session (or null) to caller + return(pCertInfo); +} + +/*F********************************************************************************/ +/*! + \Function _CertValidHistoryAdd + + \Description + Added fingerprint of validated certificate to certificate validity history buffer + + \Input *pFingerprintId - certificate fingerprint + \Input uCertSize - size of certificate + + \Version 03/15/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _CertValidHistoryAdd(const uint8_t *pFingerprintId, uint32_t uCertSize) +{ + ProtoSSLStateT *pState = _ProtoSSL_pState; + CertValidHistoryT *pCertHist; + int32_t iCert, iCertOldest; + int32_t iTickDiff, iTickDiffMax; + uint32_t uCurTick = NetTick(); + + #if DIRTYCODE_DEBUG + if (pState == NULL) + { + NetPrintf(("protossl: warning, protossl not initialized, certificate validation history feature is disabled\n")); + return; + } + #endif + + NetCritEnter(&pState->StateCrit); + + // try to find an unallocated session + for (iCert = 0; iCert < SSL_CERTVALID_MAX; iCert += 1) + { + pCertHist = &pState->CertValidHistory[iCert]; + // empty slot? + if (pCertHist->uCertSize == 0) + { + #if DEBUG_VAL_CERT + NetPrintf(("protossl: adding certificate validation history entry %d\n", iCert)); + #endif + _CertValidHistorySet(pCertHist, pFingerprintId, uCertSize, uCurTick); + NetCritLeave(&pState->StateCrit); + return; + } + } + + // find the oldest session + for (iCert = 0, iTickDiffMax = 0, iCertOldest = 0; iCert < SSL_CERTVALID_MAX; iCert += 1) + { + pCertHist = &pState->CertValidHistory[iCert]; + // find least recently updated session + if ((iTickDiff = NetTickDiff(uCurTick, pCertHist->uCertValidTick)) > iTickDiffMax) + { + iTickDiffMax = iTickDiff; + iCertOldest = iCert; + } + } + + NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: replacing certificate validation entry %d\n", iCertOldest)); + _CertValidHistorySet(&pState->CertValidHistory[iCertOldest], pFingerprintId, uCertSize, uCurTick); + + NetCritLeave(&pState->StateCrit); +} + +/*F********************************************************************************/ +/*! + \Function _CertValidHistoryCheck + + \Description + Check to see if certificate fingerprint is in certificiate validity history + + \Input *pFingerprintId - certificate fingerprint + \Input uCertSize - size of certificate + + \Output + int32_t - zero=not validated, else validated + + \Version 04/29/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _CertValidHistoryCheck(const uint8_t *pFingerprintId, uint32_t uCertSize) +{ + ProtoSSLStateT *pState = _ProtoSSL_pState; + CertValidHistoryT *pCertInfo; + int32_t iResult = 0; + + #if DIRTYCODE_DEBUG + if (pState == NULL) + { + NetPrintf(("protossl: warning, protossl not initialized, certificate validation history feature is disabled\n")); + return(0); + } + #endif + + // acquire access to session history + NetCritEnter(&pState->StateCrit); + + // find certificate validity history + if ((pCertInfo = _CertValidHistoryGet(pFingerprintId, uCertSize)) != NULL) + { + iResult = 1; + NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: certificate validation deferred to cache\n")); + } + + // release access to session history, return to caller + NetCritLeave(&pState->StateCrit); + return(iResult); +} + +/* + digital signature generation and verification +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLEccInitContext + + \Description + Initialize ECC context for key exchange and retrieve the dh interface + + \Input *pSecure - secure state + \Input *pEllipticCurve - curve to init state with + + \Output + const CryptCurveDhT * - the found dh interface or NULL if not found for curve + + \Version 03/14/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const CryptCurveDhT *_ProtoSSLEccInitContext(SecureStateT *pSecure, const EllipticCurveT *pEllipticCurve) +{ + const CryptCurveDhT *pEcc; + + // find the dh interface, this _should not_ fail if everything is correctly configured + if ((pEcc = CryptCurveGetDh(pEllipticCurve->uIdent)) == NULL) + { + NetPrintf(("protossl: could not find curve %s (ident=0x%04x), mismatch between protossl and cryptcurve?\n", + pEllipticCurve->strName, pEllipticCurve->uIdent)); + return(NULL); + } + if (pSecure->bEccContextInitialized) + { + return(pEcc); + } + // init the state + if (pEcc->Init(pSecure->EccContext, pEllipticCurve->uIdent) == 0) + { + pSecure->bEccContextInitialized = TRUE; + } + else + { + NetPrintf(("protossl: initialize failed for curve %s (ident=0x%04x)\n", pEllipticCurve->strName, pEllipticCurve->uIdent)); + } + return(pEcc); +} + +/*F********************************************************************************/ +/* + \Function _ProtoSSLDsaInitContext + + \Description + Gets the interface for the ecdsa curve that we use for signing + and verifying + + \Input *pSigVerify - the signature verify state + \Input iCrvType - the asn identifier for the ecdsa curve + + \Output + const CryptCurveDsaT * - the interface or NULL if not found + + \Version 05/10/2018 (eesponda) +*/ +/********************************************************************************F*/ +static const CryptCurveDsaT *_ProtoSSLDsaInitContext(SignatureVerifyT *pSigVerify, int32_t iCrvType) +{ + const CryptCurveDsaT *pDsa; + int32_t iCurveIdent = 0; + + // map the ASN to the curve we want to use + if (iCrvType == ASN_OBJ_ECDSA_SECP256R1) + { + iCurveIdent = CRYPTCURVE_SECP256R1; + } + else if (iCrvType == ASN_OBJ_ECDSA_SECP384R1) + { + iCurveIdent = CRYPTCURVE_SECP384R1; + } + + // find the dsa interface, this _should not_ fail if everything is correctly configured + if ((pDsa = CryptCurveGetDsa(iCurveIdent)) == NULL) + { + NetPrintf(("protossl: could not find curve ident 0x%04x for dsa, mismatch between protossl and cryptcurve?\n", + iCurveIdent)); + return(NULL); + } + if (pSigVerify->bEccContextInitialized) + { + return(pDsa); + } + + // attempt to initialize, catch failure in case we get an unsupported curve + if (pDsa->Init(pSigVerify->DsaContext, iCurveIdent) == 0) + { + pSigVerify->bEccContextInitialized = TRUE; + } + else + { + NetPrintf(("protossl: initialize failed for dsa curve (ident=0x%04x)\n", iCurveIdent)); + } + return(pDsa); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateSignatureHash + + \Description + Generate a signature for key exchange + + \Input *pSecure - secure state + \Input *pHash - signature hash algorithm + \Input *pHashDigest - [out] buffer for signature hash + \Input *pData - data to sign + \Input iDataLen - length of data + + \Output + int32_t - size of signature hash + + \Version 02/17/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLGenerateSignatureHash(const SecureStateT *pSecure, const CryptHashT *pHash, uint8_t *pHashDigest, const uint8_t *pData, int32_t iDataLen) +{ + uint8_t HashState[CRYPTHASH_MAXSTATE]; + pHash->Init(HashState, pHash->iHashSize); + pHash->Update(HashState, pSecure->ClientRandom, sizeof(pSecure->ClientRandom)); + pHash->Update(HashState, pSecure->ServerRandom, sizeof(pSecure->ServerRandom)); + pHash->Update(HashState, pData, iDataLen); + pHash->Final(HashState, pHashDigest, pHash->iHashSize); + return(pHash->iHashSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateSignatureEcdsa + + \Description + Generate an ECDSA signature; expected to be called iteratively + + \Input *pSecure - secure state + \Input *pSigData - [out] signature data buffer + \Input iSigSize - signature data size + \Input *pPrivateKey - ECC private key data + \Input iKeySize - size of key + \Input iCrvType - curve type + + \Output + int32_t - one=call again, zero=success, negative=failure + + \Version 03/09/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLGenerateSignatureEcdsa(SecureStateT *pSecure, const uint8_t *pSigData, int32_t iSigSize, const uint8_t *pPrivateKey, int32_t iKeySize, int32_t iCrvType) +{ + CryptEccPointT Signature; + int32_t iResult; + CryptBnT PrivKey; + uint32_t uCryptUsecs; + SignatureVerifyT *pSigVerify = &pSecure->SigVerify; + const CryptCurveDsaT *pDsa; + + // get the dsa functions + if ((pDsa = _ProtoSSLDsaInitContext(pSigVerify, iCrvType)) == NULL) + { + return(-1); + } + // if the state is not initialized then assume a failure + if (!pSigVerify->bEccContextInitialized) + { + return(-1); + } + + // init private key + CryptBnInitFrom(&PrivKey, -1, pPrivateKey, iKeySize); + + // sign digest with the private key + if ((iResult = pDsa->Sign(pSigVerify->DsaContext, &PrivKey, pSigData, iSigSize, &Signature, &uCryptUsecs)) != 1) + { + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (ecdsa sig encrypt) %dms\n", uCryptUsecs/1000)); + pSecure->uTimer += uCryptUsecs/1000; + pSigVerify->iSigSize = _AsnWriteSignatureEcdsa(pSigVerify->aSigData, sizeof(pSigVerify->aSigData), &Signature); + pSigVerify->bEccContextInitialized = FALSE; + } + return(iResult); +} +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateSignatureRSA + + \Description + Generate an RSA signature; expected to be called iteratively + + \Input *pSecure - secure state + \Input *pSigData - signature data + \Input iSigSize - signature data size + \Input *pPrivateKey - private key used to sign + + \Output + int32_t - one=call again, zero=success, negative=failure + + \Version 03/09/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLGenerateSignatureRSA(SecureStateT *pSecure, const uint8_t *pSigData, int32_t iSigSize, const X509PrivateKeyT *pPrivateKey) +{ + // set up for rsa encryption + if (!pSecure->bRSAContextInitialized) + { + // setup to encrypt signature object using private key + if (CryptRSAInit2(&pSecure->RSAContext, pPrivateKey->Modulus.iObjSize, &pPrivateKey->PrimeP, &pPrivateKey->PrimeQ, &pPrivateKey->ExponentP, &pPrivateKey->ExponentQ, &pPrivateKey->Coefficient) != 0) + { + NetPrintf(("protossl: RSA init failed on private key encrypt\n")); + return(-1); + } + // encrypt signature object + if ((pSecure->uSslVersion < SSL3_TLS1_2) || (pSecure->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PKCS1)) + { + CryptRSAInitPrivate(&pSecure->RSAContext, pSigData, iSigSize); + } + else + { + CryptRSAInitSignature(&pSecure->RSAContext, pSigData, iSigSize); + } + pSecure->bRSAContextInitialized = TRUE; + } + + /* encrypt signature hash; break it up into chunks of 64 to prevent blocking thread entire time. + we don't do them one at a time because for a typical private key that would be 2048 calls and + would incur unacceptable overhead, so this is a compromise between doing it all at once and + doing it one step at a time */ + if (CryptRSAEncrypt(&pSecure->RSAContext, 64) > 0) + { + return(1); + } + + // copy data to signature output + pSecure->SigVerify.iSigSize = pSecure->RSAContext.iKeyModSize; + ds_memcpy_s(pSecure->SigVerify.aSigData, sizeof(pSecure->SigVerify.aSigData), pSecure->RSAContext.EncryptBlock, pSecure->SigVerify.iSigSize); + + // done + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (rsa sig encrypt) %dms (%d calls)\n", + pSecure->RSAContext.uCryptMsecs, pSecure->RSAContext.uNumExpCalls)); + pSecure->uTimer += pSecure->RSAContext.uCryptMsecs; + pSecure->bRSAContextInitialized = FALSE; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateSignatureAsync + + \Description + Generate a digital signature; expected to be called iteratively + by the async update function. + + \Input *pState - module state + + \Output + int32_t - one=call again, zero=success, negative=failure + + \Version 03/09/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLGenerateSignatureAsync(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + SignatureVerifyT *pSigVerify = &pSecure->SigVerify; + int32_t iResult; + + if ((pSigVerify->pSigScheme != NULL) && (pSigVerify->pSigScheme->SigAlg.uSigAlg == SSL3_SIGALG_ECDSA)) + { + iResult = _ProtoSSLGenerateSignatureEcdsa(pSecure, pSigVerify->aSigData, pSigVerify->iSigSize, pSigVerify->aKeyData, pSigVerify->iKeySize, (pSigVerify->iKeySize == 32) ? ASN_OBJ_ECDSA_SECP256R1 : ASN_OBJ_ECDSA_SECP384R1); + } + else + { + iResult = _ProtoSSLGenerateSignatureRSA(pSecure, pSigVerify->aSigData, pSigVerify->iSigSize, pSigVerify->pPrivateKey); + } + if (iResult <= 0) + { + if (iResult < 0) + { + NetPrintf(("protossl: %s signature generate failed\n", pSigVerify->pSigScheme->SigAlg.uSigAlg == SSL3_SIGALG_ECDSA ? "ecdsa" : "rsa")); + } + pSigVerify->pSigScheme = NULL; + pSecure->bSigGenerated = TRUE; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateSignature + + \Description + Starts async operation to generate a digital signature + + \Input *pState - module state + \Input *pSecure - secure state + \Input *pSigScheme - signature scheme to use + \Input *pSigData - signature to sign + \Input iSigSize - signature size + \Input *pPrivateKey - private key + \Input iNextState - next state to transition to after async op + + \Output + int32_t - one=call again, zero=success, negative=failure + + \Version 03/09/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLGenerateSignature(ProtoSSLRefT *pState, SecureStateT *pSecure, const SignatureSchemeT *pSigScheme, const uint8_t *pSigData, int32_t iSigSize, X509PrivateKeyT *pPrivateKey, int32_t iNextState) +{ + SignatureVerifyT *pSigVerify = &pSecure->SigVerify; + + pSigVerify->pSigScheme = pSigScheme; + ds_memcpy_s(pSigVerify->aSigData, sizeof(pSigVerify->aSigData), pSigData, iSigSize); + pSigVerify->iSigSize = iSigSize; + pSigVerify->pPrivateKey = pPrivateKey; + pSigVerify->iNextState = iNextState; + pSigVerify->bEccContextInitialized = FALSE; + if (pSigScheme->SigAlg.uSigAlg == SSL3_SIGALG_ECDSA) + { + pSigVerify->iKeySize = pPrivateKey->Modulus.iObjSize; + ds_memcpy_s(pSigVerify->aKeyData, sizeof(pSigVerify->aKeyData), pPrivateKey->Modulus.pObjData, pSigVerify->iKeySize); + } + + return(_ProtoSSLUpdateSetAsyncState(pState, _ProtoSSLGenerateSignatureAsync, iNextState, ST_FAIL_SETUP, SSL3_ALERT_DESC_INTERNAL_ERROR)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLVerifySignatureEcdsa + + \Description + Verify an ECDSA signature; expected to be called iteratively + + \Input *pSecure - secure state + \Input *pSigData - signature data + \Input iSigSize - signature data size + \Input *pHashData - hash to validate + \Input iHashSize - hash size + \Input *pPubKey - ECC public key data + \Input iPubKeySize - size of key + \Input iCrvType - curve type + + \Output + int32_t - one=call again, zero=success, negative=failure + + \Version 02/09/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLVerifySignatureEcdsa(SecureStateT *pSecure, const uint8_t *pSigData, int32_t iSigSize, const uint8_t *pHashData, int32_t iHashSize, const uint8_t *pPubKey, int32_t iPubKeySize, int32_t iCrvType) +{ + CryptEccPointT PubKey, Signature; + const uint8_t *pData = pSigData, *pLast = pSigData+iSigSize; + SignatureVerifyT *pSigVerify = &pSecure->SigVerify; + const CryptCurveDsaT *pDsa; + uint8_t aSigData[128]; + int32_t iSize, iResult; + uint32_t uCryptUsecs; + + // get the dsa functions + if ((pDsa = _ProtoSSLDsaInitContext(pSigVerify, iCrvType)) == NULL) + { + return(-1); + } + // if the state is not initialized then assume a failure + if (!pSigVerify->bEccContextInitialized) + { + return(-1); + } + + // init public key + pDsa->PointInit(&PubKey, pPubKey, iPubKeySize); + + // parse the sequence + if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL) + { + return(-1); + } + // parse and set the first component + if ((pData = _AsnParseBinary(pData, pLast, ASN_TYPE_INTEGER+ASN_PRIMITIVE, aSigData, sizeof(aSigData), &iSize, "signature data x")) == NULL) + { + return(-1); + } + CryptBnInitFrom(&Signature.X, -1, aSigData, iSize); + // parse and set the second component + if ((pData = _AsnParseBinary(pData, pLast, ASN_TYPE_INTEGER+ASN_PRIMITIVE, aSigData, sizeof(aSigData), &iSize, "signature data y")) == NULL) + { + return(-1); + } + CryptBnInitFrom(&Signature.Y, -1, aSigData, iSize); + + // do the verify + if ((iResult = pDsa->Verify(pSigVerify->DsaContext, &PubKey, pHashData, iHashSize, &Signature, &uCryptUsecs)) != 1) + { + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (ecdsa sig verify) %dms\n", uCryptUsecs/1000)); + pSecure->uTimer += uCryptUsecs/1000; + pSigVerify->bEccContextInitialized = FALSE; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLVerifySignatureRSA + + \Description + Verify an RSA signature; expected to be called iteratively + + \Input *pSecure - secure state + \Input *pSigData - signature data + \Input iSigSize - signature data size + \Input iSigType - signature type + \Input *pHashData - hash to validate + \Input iHashSize - hash size + \Input eHashType - hash type + \Input iSigSalt - signature salt length + \Input eMgfHash - signature message generation function (mgf) + \Input *pKeyModData - rsa public key modulus + \Input iKeyModSize - key modulus length + \Input *pKeyExpData - rsa public key exponent + \Input iKeyExpSize - key exponent length + + \Output + int32_t - one=call again, zero=success, negative=failure + + \Version 07/22/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLVerifySignatureRSA(SecureStateT *pSecure, const uint8_t *pSigData, int32_t iSigSize, int32_t iSigType, const uint8_t *pHashData, int32_t iHashSize, CryptHashTypeE eHashType, int32_t iSigSalt, CryptHashTypeE eMgfHash, const uint8_t *pKeyModData, int32_t iKeyModSize, const uint8_t *pKeyExpData, int32_t iKeyExpSize) +{ + int32_t iResult; + + // set up for rsa encryption + if (!pSecure->bRSAContextInitialized) + { + if (CryptRSAInit(&pSecure->RSAContext, pKeyModData, iKeyModSize, pKeyExpData, iKeyExpSize)) + { + return(-1); + } + CryptRSAInitSignature(&pSecure->RSAContext, pSigData, iSigSize); + pSecure->bRSAContextInitialized = TRUE; + } + // do a round of encryption + if ((iResult = CryptRSAEncrypt(&pSecure->RSAContext, 16)) > 0) + { + return(iResult); + } + pSecure->bRSAContextInitialized = FALSE; + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (rsa sig verify) %dms\n", pSecure->RSAContext.uCryptMsecs)); + pSecure->uTimer += pSecure->RSAContext.uCryptMsecs; + + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->RSAContext.EncryptBlock, pSecure->RSAContext.iKeyModSize, "decrypted signature"); + #endif + + // validate decrypted signature + if (iSigType != ASN_OBJ_RSASSA_PSS) + { + iResult = -1; + // extract hash data from signature block + if ((pSigData = _Pkcs1VerifyEMSA(pSecure->RSAContext.EncryptBlock, iSigSize, iHashSize, eHashType)) != NULL) + { + // compare hash data with precalculated certificate body hash + iResult = !memcmp(pHashData, pSigData, iHashSize) ? 0 : -1; + } + } + else + { + // extract and validate signature hash + iResult = _Pkcs1VerifyEMSA_PSS(pSecure->RSAContext.EncryptBlock, iKeyModSize, eHashType, iSigSalt, pHashData, iHashSize, eMgfHash) ? 0 : -1; + } + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLVerifySignatureAsync + + \Description + Verify a digital signature; expected to be called iteratively + by the async update function. + + \Input *pState - module state + + \Output + int32_t - one=call again, zero=success, negative=failure + + \Version 02/16/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLVerifySignatureAsync(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + SignatureVerifyT *pSigVerify = &pSecure->SigVerify; + int32_t iResult; + + if ((pSigVerify->pSigScheme != NULL) && (pSigVerify->pSigScheme->SigAlg.uSigAlg == SSL3_SIGALG_ECDSA)) + { + iResult = _ProtoSSLVerifySignatureEcdsa(pSecure, pSigVerify->aSigData, pSigVerify->iSigSize, pSigVerify->aHashDigest, pSigVerify->iHashSize, pSecure->Cert.KeyModData, pSecure->Cert.iKeyModSize, pSecure->Cert.iCrvType); + } + else + { + iResult = _ProtoSSLVerifySignatureRSA(pSecure, pSigVerify->aSigData, pSigVerify->iSigSize, (pSigVerify->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PSS) ? ASN_OBJ_RSASSA_PSS : ASN_OBJ_RSA_PKCS_SHA256, + pSigVerify->aHashDigest, pSigVerify->iHashSize, pSigVerify->eHashType, pSigVerify->iHashSize, pSigVerify->eHashType, pSecure->Cert.KeyModData, pSecure->Cert.iKeyModSize, pSecure->Cert.KeyExpData, + pSecure->Cert.iKeyExpSize); + } + if (iResult <= 0) + { + if (iResult < 0) + { + NetPrintf(("protossl: %s signature verify failed\n", pSigVerify->pSigScheme->SigAlg.uSigAlg == SSL3_SIGALG_ECDSA ? "ecdsa" : "rsa")); + } + pSigVerify->pSigScheme = NULL; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLVerifySignature + + \Description + Starts async operation to verify a digital signature + + \Input *pState - module state + \Input *pSecure - secure state + \Input *pSigScheme - signature scheme to use + \Input *pSigData - signature data + \Input iSigSize - signature data size + \Input *pHashData - hash to validate + \Input iHashSize - hash size + \Input eHashType - hash type + \Input iNextState - next state to transition to after async op + + \Output + int32_t - one=call again, zero=success, negative=failure + + \Version 02/16/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLVerifySignature(ProtoSSLRefT *pState, SecureStateT *pSecure, const SignatureSchemeT *pSigScheme, const uint8_t *pSigData, int32_t iSigSize, const uint8_t *pHashData, int32_t iHashSize, CryptHashTypeE eHashType, int32_t iNextState) +{ + SignatureVerifyT *pSigVerify = &pSecure->SigVerify; + + pSigVerify->pSigScheme = pSigScheme; + ds_memcpy_s(pSigVerify->aSigData, sizeof(pSigVerify->aSigData), pSigData, iSigSize); + pSigVerify->iSigSize = iSigSize; + ds_memcpy_s(pSigVerify->aHashDigest, sizeof(pSigVerify->aHashDigest), pHashData, iHashSize); + pSigVerify->iHashSize = iHashSize; + pSigVerify->eHashType = eHashType; + pSigVerify->iNextState = iNextState; + pSigVerify->bEccContextInitialized = FALSE; + + return(_ProtoSSLUpdateSetAsyncState(pState, _ProtoSSLVerifySignatureAsync, iNextState, ST_FAIL_SETUP, SSL3_ALERT_DESC_DECRYPT_ERROR)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLVerifyCertificateSignature + + \Description + Check an X.509 signature for validity; BLOCKING + + \Input *pSecure - secure state + \Input *pCert - pointer to certificate to validate + \Input *pKeyModData - pointer to key modulus data + \Input iKeyModSize - size of key modulus data + \Input *pKeyExpData - pointer to key exponent data + \Input iKeyExpSize - size of key exponent data + \Input iKeyType - key type + \Input iCrvType - key curve type, if ec + + \Output + int32_t - zero=success, else failure + + \Version 07/22/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLVerifyCertificateSignature(SecureStateT *pSecure, X509CertificateT *pCert, const uint8_t *pKeyModData, int32_t iKeyModSize, const uint8_t *pKeyExpData, int32_t iKeyExpSize, int32_t iKeyType, int32_t iCrvType) +{ + int32_t iResult; + + NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: verifying signature\n")); + + // if ecdsa + if (iKeyType == ASN_OBJ_ECDSA_KEY) + { + // verify the signature + while((iResult = _ProtoSSLVerifySignatureEcdsa(pSecure, pCert->SigData, pCert->iSigSize, pCert->HashData, pCert->iHashSize, pKeyModData, iKeyModSize, iCrvType)) > 0) + ; + } + else + { + while((iResult = _ProtoSSLVerifySignatureRSA(pSecure, pCert->SigData, pCert->iSigSize, pCert->iSigType, pCert->HashData, pCert->iHashSize, pCert->eHashType, pCert->iSigSalt, + _AsnGetHashType(pCert->iMgfHash), pKeyModData, iKeyModSize, pKeyExpData, iKeyExpSize)) > 0) + ; + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLVerifyCertificate + + \Description + Verify a previously parsed x.509 certificate + + \Input *pState - module state (may be NULL) + \Input *pSecure - secure state + \Input *pCert - pointer to certificate to fill in from header data + \Input bCertIsCA - is the certificate a CA? + + \Output int32_t - zero=success, positive=duplicate, else error + + \Version 03/11/2009 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLVerifyCertificate(ProtoSSLRefT *pState, SecureStateT *pSecure, X509CertificateT *pCert, uint8_t bCertIsCA) +{ + int32_t iResult = 0; + + // if self-signed permitted and certificate is self-signed, then point to its key info (mod+exp) + if ((bCertIsCA == TRUE) && (_CertificateCompareIdent(&pCert->Subject, &pCert->Issuer, TRUE) == 0)) + { + if (_ProtoSSLVerifyCertificateSignature(pSecure, pCert, pCert->KeyModData, pCert->iKeyModSize, pCert->KeyExpData, pCert->iKeyExpSize, pCert->iKeyType, pCert->iCrvType) != 0) + { + NetPrintf(("protossl: _VerifyCertificate: signature hash mismatch on self-signed cert\n")); + _CertificateDebugPrint(pCert, "failed cert"); + return(-50); + } + } + else + { + ProtoSSLCACertT *pCACert; + + #if DIRTYCODE_LOGGING + if ((bCertIsCA == TRUE) && (pCert->iCertIsCA == FALSE)) // when ProtoSSLSetCACert* is called + { + NetPrintf(("protossl: _VerifyCertificate: warning --- trying to add a non-CA cert as a CA.\n")); + _CertificateDebugPrint(pCert, "warning cert"); + } + #endif + + // locate a matching CA + for (pCACert = _ProtoSSL_CACerts; pCACert != NULL; pCACert = pCACert->pNext) + { + // first, compare to see if our subject matches their issuer + if ((_CertificateCompareIdent(&pCACert->Subject, &pCert->Issuer, (bCertIsCA||pCert->iCertIsCA)) != 0) || + ((pCACert->iKeyModSize != pCert->iSigSize) && (pCert->iKeyType != ASN_OBJ_ECDSA_KEY))) + { + continue; + } + + // verify against this CA + if (_ProtoSSLVerifyCertificateSignature(pSecure, pCert, pCACert->pKeyModData, pCACert->iKeyModSize, pCACert->KeyExpData, pCACert->iKeyExpSize, pCACert->iKeyType, pCACert->iCrvType) != 0) + { + continue; + } + + NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: verifying succeeded\n")); + + // special processing when validating against a GOS CA + if (pCACert->uFlags & SSL_CACERTFLAG_GOSCA) + { + // make sure the domain of the certificate is an EA domain + if (ds_stricmpwc(pCert->Subject.strCommon, "*.ea.com") && ds_stricmpwc(pCert->Subject.strCommon, "*.easports.com")) + { + return(SSL_ERR_GOSCA_INVALIDUSE); + } + // ignore date range validation failure + if (pSecure->bDateVerifyFailed) + { + NetPrintf(("protossl: ignoring date range validation failure for GOS CA\n")); + pSecure->bDateVerifyFailed = FALSE; + } + } + // only CAPROVIDER CAs can be used against gosca + if (!ds_stricmpwc(pCert->Subject.strCommon, "gosca.ea.com") && !(pCACert->uFlags & SSL_CACERTFLAG_CAPROVIDER)) + { + return(SSL_ERR_CAPROVIDER_INVALID); + } + + // if the CA hasn't been verified already, do that now + if (pCACert->pX509Cert != NULL) + { + if ((iResult = _ProtoSSLVerifyCertificate(pState, pSecure, pCACert->pX509Cert, TRUE)) == 0) + { + #if DIRTYCODE_LOGGING + char strIdentSubject[512], strIdentIssuer[512]; + int32_t iVerbose = (pState != NULL) ? pState->iVerbose : 0; + NetPrintfVerbose((iVerbose, 0, "protossl: ca (%s) validated by ca (%s)\n", _CertificateDebugFormatIdent(&pCACert->pX509Cert->Subject, strIdentSubject, sizeof(strIdentSubject)), + _CertificateDebugFormatIdent(&pCACert->pX509Cert->Issuer, strIdentIssuer, sizeof(strIdentIssuer)))); + #endif + + // cert successfully verified + DirtyMemFree(pCACert->pX509Cert, PROTOSSL_MEMID, pCACert->iMemGroup, pCACert->pMemGroupUserData); + pCACert->pX509Cert = NULL; + } + else + { + _CertificateSetFailureInfo(pState, pCACert->pX509Cert, TRUE); + continue; + } + } + + // verified + break; + } + if (pCACert == NULL) + { + #if DIRTYCODE_LOGGING + // output debug logging only if we're manually loading a CA cert + if (bCertIsCA) + { + _CertificateDebugPrint(pCert, "_VerifyCertificate() -- no CA available for this certificate"); + } + #endif + _CertificateSetFailureInfo(pState, pCert, TRUE); + return(-51); + } + } + // success + return(iResult); +} + +/* + hmac, building tls1 keys and key material, generating and verifying mac +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLDoHmac + + \Description + Calculates HMAC using CryptHmac + + \Input *pBuffer - [out] storage for generated MAC + \Input iBufLen - size of output + \Input *pMessage - input data to hash + \Input iMessageLen - size of input data + \Input *pMessage2 - more input data to hash (optional) + \Input iMessageLen2 - size of more input data (if pMessage2 != NULL) + \Input pKey - seed + \Input iKeyLen - seed length + \Input eHashType - hash type + + \Version 10/11/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLDoHmac(uint8_t *pBuffer, int32_t iBufLen, const uint8_t *pMessage, int32_t iMessageLen, const uint8_t *pMessage2, int32_t iMessageLen2, const uint8_t *pKey, int32_t iKeyLen, CryptHashTypeE eHashType) +{ + if (pMessage2 != NULL) + { + CryptHmacMsgT Message[2]; + Message[0].pMessage = pMessage; + Message[0].iMessageLen = iMessageLen; + Message[1].pMessage = pMessage2; + Message[1].iMessageLen = iMessageLen2; + CryptHmacCalcMulti(pBuffer, iBufLen, Message, 2, pKey, iKeyLen, eHashType); + } + else + { + CryptHmacCalc(pBuffer, iBufLen, pMessage, iMessageLen, pKey, iKeyLen, eHashType); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLDoPHash + + \Description + Implements P_hash as defined in https://tools.ietf.org/html/rfc5246#section-5 + + \Input *pBuffer - [out] storage for P_hash result + \Input iOutLen - size of output expected + \Input *pSecret - secret + \Input iSecretLen - length of secret + \Input *pSeed - seed + \Input iSeedLen - length of seed + \Input eHashType - hash type + + \Version 10/11/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLDoPHash(uint8_t *pBuffer, int32_t iOutLen, const uint8_t *pSecret, int32_t iSecretLen, const uint8_t *pSeed, int32_t iSeedLen, CryptHashTypeE eHashType) +{ + uint8_t aWork[128]; + int32_t iHashSize = CryptHashGetSize(eHashType); + + // A(1) + _ProtoSSLDoHmac(aWork, sizeof(aWork), pSeed, iSeedLen, NULL, 0, pSecret, iSecretLen, eHashType); + ds_memcpy(aWork+iHashSize, pSeed, iSeedLen); + _ProtoSSLDoHmac(pBuffer, iOutLen, aWork, iSeedLen+iHashSize, NULL, 0, pSecret, iSecretLen, eHashType); + + // A(n) + while (iOutLen > iHashSize) + { + uint8_t aWork2[128]; + + pBuffer += iHashSize; + iOutLen -= iHashSize; + + _ProtoSSLDoHmac(aWork2, sizeof(aWork2), aWork, iHashSize, NULL, 0, pSecret, iSecretLen, eHashType); + ds_memcpy(aWork, aWork2, iHashSize); + _ProtoSSLDoHmac(pBuffer, iOutLen, aWork, iSeedLen+iHashSize, NULL, 0, pSecret, iSecretLen, eHashType); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLDoPRF + + \Description + Implements PRF as defined in https://tools.ietf.org/html/rfc5246#section-5 + + \Input *pBuffer - [out] storage for P_hash result + \Input iOutLen - size of output expected + \Input *pSecret - secret + \Input iSecretLen - length of secret + \Input *pSeed - seed + \Input iSeedLen - length of seed + + \Version 10/11/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLDoPRF(uint8_t *pBuffer, int32_t iOutLen, const uint8_t *pSecret, int32_t iSecretLen, const uint8_t *pSeed, int32_t iSeedLen) +{ + uint8_t aMD5Buf[SSL_KEYMATERIAL_LEN], aSHABuf[SSL_KEYMATERIAL_LEN]; + int32_t iLS = iSecretLen/2; // split secret in two + int32_t iBufCnt; + iLS += iSecretLen & 1; // handle odd secret lengths + + // execute md5 p_hash + _ProtoSSLDoPHash(aMD5Buf, iOutLen, pSecret, iLS, pSeed, iSeedLen, CRYPTHASH_MD5); + + // execute sha1 p_hash + _ProtoSSLDoPHash(aSHABuf, iOutLen, pSecret+iLS, iLS, pSeed, iSeedLen, CRYPTHASH_SHA1); + + // execute XOR of MD5 and SHA hashes + for (iBufCnt = 0; iBufCnt < iOutLen; iBufCnt += 1) + { + pBuffer[iBufCnt] = aMD5Buf[iBufCnt] ^ aSHABuf[iBufCnt]; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLBuildKey + + \Description + Builds key material/master secret for TLS versions prior to 1.3 + + \Input *pState - module state reference + \Input *pBuffer - [out] output for generated key data + \Input iBufSize - size of output buffer + \Input *pSource - source data + \Input iSourceLen - length of source data + \Input *pRandomA - random data + \Input *pRandomB - random data + \Input iRandomLen - length of random data + \Input *pLabel - label (TLS1 PRF only) + \Input uSslVersion - ssl version being used + + \Version 10/11/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLBuildKey(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufSize, uint8_t *pSource, int32_t iSourceLen, uint8_t *pRandomA, uint8_t *pRandomB, int32_t iRandomLen, const char *pLabel, uint16_t uSslVersion) +{ + SecureStateT *pSecure = pState->pSecure; + uint8_t aWork[128]; // must hold at least 13+32+32 bytes + + ds_strnzcpy((char *)aWork, pLabel, sizeof(aWork)); + ds_memcpy(aWork+13, pRandomA, iRandomLen); + ds_memcpy(aWork+45, pRandomB, iRandomLen); + if (uSslVersion < SSL3_TLS1_2) + { + // ref http://tools.ietf.org/html/rfc2246#section-6.3 + _ProtoSSLDoPRF(pBuffer, iBufSize, pSource, iSourceLen, aWork, 77); + } + else + { + // ref https://tools.ietf.org/html/rfc5246#section-6.3; tls1.2 drops md5+sha1 prf and uses the cipher prf hash directly instead + _ProtoSSLDoPHash(pBuffer, iBufSize, pSource, iSourceLen, aWork, 77, (CryptHashTypeE)pSecure->pCipher->uPrfType); + } + + if (pBuffer == pSecure->MasterKey) + { + #if DIRTYCODE_LOGGING + char strBuf1[65], strBuf2[97]; + #endif + + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey), "PreMaster"); + NetPrintMem(pSecure->MasterKey, sizeof(pSecure->MasterKey), "Master"); + #endif + + // log params for wireshark decrypt + NetPrintfVerbose((pState->iVerbose, 0, "protossl: CLIENT_RANDOM %s %s\n", + ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)), + ds_fmtoctstring(strBuf2, sizeof(strBuf2), pBuffer, iBufSize))); + + // get rid of premaster secret from memory + ds_memclr(pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey)); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLBuildKeyMaterial + + \Description + Build and distribute key material from master secret, server + random, and client random. Used for TLS1.2 and prior. + + \Input *pState - module state reference + + \Version 03/19/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLBuildKeyMaterial(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + uint8_t *pData; + + #if DEBUG_RAW_DATA + NetPrintf(("protossl: building key material\n")); + NetPrintMem(pSecure->MasterKey, sizeof(pSecure->MasterKey), "MasterKey"); + NetPrintMem(pSecure->ServerRandom, sizeof(pSecure->ServerRandom), "ServerRandom"); + NetPrintMem(pSecure->ClientRandom, sizeof(pSecure->ClientRandom), "ClientRandom"); + #endif + + // build key material; limit to SSL_KEYMATERIAL_LEN for upstream code even though the keyblock is bigger + _ProtoSSLBuildKey(pState, pSecure->KeyBlock, SSL_KEYMATERIAL_LEN, pSecure->MasterKey, sizeof(pSecure->MasterKey), + pSecure->ServerRandom, pSecure->ClientRandom, sizeof(pSecure->ServerRandom), "key expansion", + pSecure->uSslVersion); + + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->KeyBlock, sizeof(pSecure->KeyBlock), "KeyExpansion"); + #endif + + // distribute the key material + pData = pSecure->KeyBlock; + pSecure->pClientMAC = pData; + pData += pSecure->pCipher->uMac; + pSecure->pServerMAC = pData; + pData += pSecure->pCipher->uMac; + pSecure->pClientKey = pData; + pData += pSecure->pCipher->uLen; + pSecure->pServerKey = pData; + pData += pSecure->pCipher->uLen; + pSecure->pClientInitVec = pData; + pData += pSecure->pCipher->uVecSize; + pSecure->pServerInitVec = pData; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateMacSource + + \Description + Build the MAC (Message Authentication Code) source data + + \Input *pBuffer - [out] storage for generated MAC + \Input uSeqn - sequence number + \Input uType - message type + \Input uSslVers - ssl version + \Input uSize - data size + + \Output + uint8_t * - pointer past end of generated MAC + + \Version 10/11/2012 (jbrookes) Refactored and added TLS support +*/ +/********************************************************************************F*/ +static uint8_t *_ProtoSSLGenerateMacSource(uint8_t *pBuffer, uint64_t uSeqn, uint32_t uType, uint32_t uSslVers, uint32_t uSize) +{ + *pBuffer++ = (uint8_t)((uSeqn>>56)&255); + *pBuffer++ = (uint8_t)((uSeqn>>48)&255); + *pBuffer++ = (uint8_t)((uSeqn>>40)&255); + *pBuffer++ = (uint8_t)((uSeqn>>32)&255); + *pBuffer++ = (uint8_t)((uSeqn>>24)&255); + *pBuffer++ = (uint8_t)((uSeqn>>16)&255); + *pBuffer++ = (uint8_t)((uSeqn>>8)&255); + *pBuffer++ = (uint8_t)((uSeqn>>0)&255); + *pBuffer++ = (uint8_t)uType; + *pBuffer++ = (uint8_t)(uSslVers>>8); + *pBuffer++ = (uint8_t)(uSslVers>>0); + *pBuffer++ = (uint8_t)(uSize>>8); + *pBuffer++ = (uint8_t)(uSize>>0); + return(pBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateMac + + \Description + Generate MAC for secure send when using non-AEAD ciphers + + \Input *pSecure - secure state + \Input *pSend - data to authenticate + \Input iSize - size of data to authenticate + \Input bServer - TRUE if operating as server, else FALSE + + \Output + int32_t - size of authenticated data plus MAC + + \Version 03/02/2018 (jbrookes) Refactored from _ProtoSSLSendPacket() +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLGenerateMac(SecureStateT *pSecure, uint8_t *pSend, int32_t iSize, uint8_t bServer) +{ + uint8_t MacTemp[CRYPTHASH_MAXDIGEST], *pMacTemp; + + // generate the mac source + pMacTemp = _ProtoSSLGenerateMacSource(MacTemp, pSecure->uSendSeqn, pSecure->SendData[0], pSecure->uSslVersion, (uint32_t)iSize); + + // hash the mac and append to send data + _ProtoSSLDoHmac(pSend+iSize, pSecure->pCipher->uMac, MacTemp, (int32_t)(pMacTemp-MacTemp), pSend, iSize, + bServer ? pSecure->pServerMAC : pSecure->pClientMAC, + pSecure->pCipher->uMac, (CryptHashTypeE)pSecure->pCipher->uMacType); + + // return size+mac to caller + return(iSize+pSecure->pCipher->uMac); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLVerifyMac + + \Description + Verify MAC for secure recv when using non-AEAD ciphers + + \Input *pSecure - secure state + \Input iSize - size of data to authenticate + \Input bServer - TRUE if operating as server, else FALSE + \Input *pBadMac - [in/out] bad MAC flag + + \Output + int32_t - zero=success, negative=failure + + \Version 03/02/2018 (jbrookes) Refactored from _ProtoSSLSendPacket() +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLVerifyMac(SecureStateT *pSecure, int32_t iSize, uint8_t bServer, uint8_t *pBadMac) +{ + uint8_t MacTemp[CRYPTHASH_MAXDIGEST], *pMacTemp; + uint8_t bBadMac; + + // make sure there is room for mac + if (iSize >= (int32_t)pSecure->pCipher->uMac) + { + iSize -= pSecure->pCipher->uMac; + // remove the mac from size + pSecure->iRecvProg = pSecure->iRecvSize = pSecure->iRecvBase+iSize; + } + else + { + NetPrintf(("protossl: _ProtoSSLVerifyMac: no room for mac (%d < %d)\n", iSize, pSecure->pCipher->uMac)); + *pBadMac = TRUE; + return(-1); + } + + // generate MAC source + pMacTemp = _ProtoSSLGenerateMacSource(MacTemp, pSecure->uRecvSeqn, pSecure->RecvHead[0], pSecure->uSslVersion, (uint32_t)iSize); + + // do the hash + _ProtoSSLDoHmac(MacTemp, pSecure->pCipher->uMac, MacTemp, (int32_t)(pMacTemp-MacTemp), + pSecure->RecvData+pSecure->iRecvBase, pSecure->iRecvSize-pSecure->iRecvBase, + bServer ? pSecure->pClientMAC : pSecure->pServerMAC, + pSecure->pCipher->uMac, (CryptHashTypeE)pSecure->pCipher->uMacType); + + // validate MAC + bBadMac = memcmp(MacTemp, pSecure->RecvData+pSecure->iRecvSize, pSecure->pCipher->uMac) ? TRUE : FALSE; + // accumulate MAC flag + *pBadMac |= bBadMac; + // return success or failure to caller + return(!(*pBadMac) ? 0 : -1); +} + +/* + handshake hash management +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHandshakeHashInit + + \Description + Init handshake hash states + + \Input *pSecure - secure state ref + + \Version 03/21/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLHandshakeHashInit(SecureStateT *pSecure) +{ + CryptMD5Init(&pSecure->HandshakeMD5); + CryptSha1Init(&pSecure->HandshakeSHA); + CryptSha2Init(&pSecure->HandshakeSHA256, SSL3_MAC_SHA256); + CryptSha2Init(&pSecure->HandshakeSHA384, SSL3_MAC_SHA384); + CryptSha2Init(&pSecure->HandshakeSHA512, SSL3_MAC_SHA512); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHandshakeHashUpdate + + \Description + Update handshake hash with specified data + + \Input *pSecure - secure state ref + \Input *pData - data to add to hash + \Input iSize - size of data to add + \Input *pLabel - debug label + + \Version 03/21/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLHandshakeHashUpdate(SecureStateT *pSecure, const uint8_t *pData, int32_t iSize, const char *pLabel) +{ + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: %s handshake update (size=%d)\n", pLabel, iSize)); + CryptMD5Update(&pSecure->HandshakeMD5, pData, iSize); + CryptSha1Update(&pSecure->HandshakeSHA, pData, iSize); + CryptSha2Update(&pSecure->HandshakeSHA256, pData, iSize); + CryptSha2Update(&pSecure->HandshakeSHA384, pData, iSize); + CryptSha2Update(&pSecure->HandshakeSHA512, pData, iSize); + + #if DEBUG_RAW_DATA + NetPrintMem(pData, iSize, "handshake data"); + #endif +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHandshakeHashGet + + \Description + Get the current handshake hash, for the specified hash type + + \Input *pSecure - secure state ref + \Input eHashType - hash type to get + \Input *pBuffer - [out] buffer to store hash + \Input iBufSize - size of output buffer (should be bigger than hash size) + + \Output + int32_t - size of hash data, or zero on failure + + \Version 03/21/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHandshakeHashGet(SecureStateT *pSecure, CryptHashTypeE eHashType, uint8_t *pBuffer, int32_t iBufSize) +{ + CryptMD5T MD5Context; + CryptSha1T SHA1Context; + CryptSha2T SHA2Context; + int32_t iHashSize; + + if ((iHashSize = CryptHashGetSize(eHashType)) < 0) + { + NetPrintf(("protossl: handshake hash type %d unknown\n", eHashType)); + ds_memclr(pBuffer, iBufSize); + return(0); + } + else if (iHashSize > iBufSize) + { + NetPrintf(("protossl: handshake hash too large for buffer; truncating\n")); + iHashSize = iBufSize; + } + + switch (eHashType) + { + case CRYPTHASH_MD5: + ds_memcpy_s(&MD5Context, sizeof(MD5Context), &pSecure->HandshakeMD5, sizeof(pSecure->HandshakeMD5)); + CryptMD5Final(&MD5Context, pBuffer, iHashSize); + break; + case CRYPTHASH_SHA1: + ds_memcpy_s(&SHA1Context, sizeof(SHA1Context), &pSecure->HandshakeSHA, sizeof(pSecure->HandshakeSHA)); + CryptSha1Final(&SHA1Context, pBuffer, iHashSize); + break; + case CRYPTHASH_SHA256: + ds_memcpy(&SHA2Context, &pSecure->HandshakeSHA256, sizeof(pSecure->HandshakeSHA256)); + CryptSha2Final(&SHA2Context, pBuffer, iHashSize); + break; + case CRYPTHASH_SHA384: + ds_memcpy(&SHA2Context, &pSecure->HandshakeSHA384, sizeof(pSecure->HandshakeSHA384)); + CryptSha2Final(&SHA2Context, pBuffer, iHashSize); + break; + case CRYPTHASH_SHA512: + ds_memcpy(&SHA2Context, &pSecure->HandshakeSHA512, sizeof(pSecure->HandshakeSHA512)); + CryptSha2Final(&SHA2Context, pBuffer, iHashSize); + break; + default: + break; + } + + return(iHashSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHandshakeHashInject + + \Description + Inject synthetic message hash in Hello Retry Request as per + https://tools.ietf.org/html/rfc8446#section-4.4.1 + + \Input *pSecure - secure state ref + \Input eHashType - hash type to update + + \Version 08/10/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLHandshakeHashInject(SecureStateT *pSecure, CryptHashTypeE eHashType) +{ + uint8_t aHash[CRYPTHASH_MAXDIGEST], aHead[4]; + CryptSha2T *pHandshakeCtx; + int32_t iHashSize = CryptHashGetSize(eHashType); + + // get handshake context + pHandshakeCtx = (eHashType == CRYPTHASH_SHA256) ? &pSecure->HandshakeSHA256 : &pSecure->HandshakeSHA384; + + // get and reinit current hash + CryptSha2Final(pHandshakeCtx, aHash, iHashSize); + CryptSha2Init(pHandshakeCtx, iHashSize); + + // create synthetic message header + aHead[0] = SSL3_MSG_MESSAGE_HASH; + aHead[1] = 0; + aHead[2] = 0; + aHead[3] = (uint8_t)iHashSize; + + #if DEBUG_RAW_DATA + NetPrintMem(aHead, sizeof(aHead), "msghead"); + NetPrintMem(aHash, iHashSize, "msghash"); + #endif + + // hash the message header + CryptSha2Update(pHandshakeCtx, aHead, sizeof(aHead)); + // and the message hash + CryptSha2Update(pHandshakeCtx, aHash, iHashSize); +} + +/* + tls 1.3 key schedule +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHkdfExtract + + \Description + Implements HKDF-Extract as defined in rfc 5869; see + https://tools.ietf.org/html/rfc5869#section-2.2 + + \Input *pOutput - [out] output of extract operation + \Input iOutputLen - desired length of output + \Input *pSalt - salt input to extract + \Input iSaltLen - salt length + \Input *pKey - key input to extract + \Input iKeyLen - length of key + \Input eHashType - hash to use for extract + + \Version 03/08/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLHkdfExtract(uint8_t *pOutput, int32_t iOutputLen, const uint8_t *pSalt, int32_t iSaltLen, const uint8_t *pKey, int32_t iKeyLen, CryptHashTypeE eHashType) +{ + // the RFC defines this as HMAC-Hash(salt, key) but our CryptHmac params are reversed + CryptHmacCalc(pOutput, iOutputLen, pKey, iKeyLen, pSalt, iSaltLen, eHashType); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHkdfExpand + + \Description + Implements HKDF-Expand as defined in rfc 5869; see + https://tools.ietf.org/html/rfc5869#section-2.3 + + \Input *pOutput - [out] output of expand operation + \Input iOutputLen - desired length of output + \Input *pKey - key input to expand (prk) + \Input iKeyLen - length of key + \Input *pInfo - info input to expand + \Input iInfoLen - length of info + \Input eHashType - hash to use for expand + + \Version 03/08/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLHkdfExpand(uint8_t *pOutput, int32_t iOutputLen, const uint8_t *pKey, int32_t iKeyLen, const uint8_t *pInfo, int32_t iInfoLen, CryptHashTypeE eHashType) +{ + uint32_t uRound, uNumRounds; + int32_t iHashLen = CryptHashGetSize(eHashType); + uint8_t uPrev, *pPrev; + uint32_t uPrevLen; + CryptHmacMsgT Message[3]; + uint8_t uCount; + + // N = ceil(L/HashLen) + uNumRounds = (iOutputLen/ iHashLen); + uNumRounds += iOutputLen % iHashLen != 0 ? 1 : 0; + // T(0) = empty string (zero length) + uPrev = 0; + pPrev = &uPrev; + uPrevLen = 0; + // T(1) ... T(N) + for (uRound = 1; uRound <= uNumRounds; uRound += 1, pOutput += iHashLen, iOutputLen -= iHashLen) + { + uCount = (uint8_t)uRound; + + Message[0].pMessage = pPrev; + Message[0].iMessageLen = uPrevLen; + Message[1].pMessage = pInfo; + Message[1].iMessageLen = iInfoLen; + Message[2].pMessage = &uCount; + Message[2].iMessageLen = sizeof(uCount); + CryptHmacCalcMulti(pOutput, iOutputLen, Message, sizeof(Message)/sizeof(Message[0]), pKey, iKeyLen, eHashType); + + pPrev = pOutput; + uPrevLen = iHashLen; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHkdfExpandLabel + + \Description + Implements HKDF-Expand-Label as per + https://tools.ietf.org/html/rfc8446#section-7.1 + + \Input *pOutput - [out] output of expand operation + \Input iOutputLen - desired length of output + \Input *pKey - key input to expand (ikm) + \Input iKeyLen - length of key + \Input *pLabel - string context label + \Input *pHashValue - hash data + \Input iHashLen - hash data length + \Input eHashType - hash to use for label expand + + \Output + int32_t - length of output + + \Version 03/08/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHkdfExpandLabel(uint8_t *pOutput, int32_t iOutputLen, const uint8_t *pKey, int32_t iKeyLen, const char *pLabel, const uint8_t *pHashValue, int32_t iHashLen, CryptHashTypeE eHashType) +{ + const char *_strLabelPrefix = "tls13 "; + const int32_t iPrefixLen = (int32_t)strlen(_strLabelPrefix); + int32_t iLabelLen = (int32_t)strlen(pLabel); + uint8_t aInfoBuf[1024]; + int32_t iInfoLen = 0, iBufLen = sizeof(aInfoBuf); + + // HkdfLabel.length -- secret key length u16 + aInfoBuf[iInfoLen++] = (uint8_t)(iOutputLen >> 8); + aInfoBuf[iInfoLen++] = (uint8_t)(iOutputLen); + // HkdfLabelT.label -- length-prefixed string + aInfoBuf[iInfoLen++] = (uint8_t)(iLabelLen + iPrefixLen); + iInfoLen += ds_snzprintf((char *)aInfoBuf + iInfoLen, iBufLen - iInfoLen, "%s%s", _strLabelPrefix, pLabel); + // HkdfLabelT.hash_value -- hash data + aInfoBuf[iInfoLen++] = (uint8_t)iHashLen; + ds_memcpy_s(aInfoBuf + iInfoLen, iBufLen - iInfoLen, pHashValue, iHashLen); + iInfoLen += iHashLen; + + // do the expand + if (iInfoLen <= iBufLen) + { + _ProtoSSLHkdfExpand(pOutput, iOutputLen, pKey, iKeyLen, aInfoBuf, iInfoLen, eHashType); + #if DEBUG_RAW_DATA + NetPrintMem(pKey, iKeyLen, "key"); + NetPrintMem(aInfoBuf, iInfoLen, "info"); + NetPrintMem(pOutput, iOutputLen, "output"); + #endif + } + else + { + NetPrintf(("protossl: buffer too small in Hkdf expansion\n")); + } + // return size of output + return(iOutputLen); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLDeriveSecret + + \Description + Implements Derive-Secret as per + https://tools.ietf.org/html/rfc8446#section-7.1 + + \Input *pSecure - secure state + \Input *pOutput - [out] output of expand operation + \Input iOutputLen - desired length of output + \Input *pKey - key input to expand (ikm) + \Input iKeyLen - length of key + \Input *pLabel - string context label + \Input eHashType - hash to use for label expand + \Input iHashLen - hash data length (zero for empty-message hash) + \Input *pHashData - hash data to use (NULL to use current handshake hash) + + \Output + int32_t - output length of secret + + \Version 03/08/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLDeriveSecret(SecureStateT *pSecure, uint8_t *pOutput, int32_t iOutputLen, const uint8_t *pKey, int32_t iKeyLen, const char *pLabel, CryptHashTypeE eHashType, int32_t iHashLen, const uint8_t *pHashData) +{ + uint8_t aHashBuf[CRYPTHASH_MAXDIGEST]; + + // zero hash length indicates a request for an empty-message hash + if (iHashLen == 0) + { + const CryptHashT *pHash = CryptHashGet(eHashType); + uint8_t aHashState[CRYPTHASH_MAXSTATE]; + iHashLen = CryptHashGetSize(eHashType); + pHash->Init(aHashState, iHashLen); + pHash->Final(aHashState, aHashBuf, iHashLen); + pHashData = aHashBuf; + } + else if (pHashData == NULL) + { + // get handshake hash data + _ProtoSSLHandshakeHashGet(pSecure, eHashType, aHashBuf, sizeof(aHashBuf)); + pHashData = aHashBuf; + } + #if DEBUG_RAW_DATA + NetPrintMem(pHashData, iHashLen, "handshake hash"); + #endif + + // do the expand & return length of output + _ProtoSSLHkdfExpandLabel(pOutput, iOutputLen, pKey, iKeyLen, pLabel, pHashData, iHashLen, eHashType); + return(iOutputLen); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLBuildSecrets + + \Description + Builds early, handshake, and master secrets as per + https://tools.ietf.org/html/rfc8446#section-7.1 + + \Input *pSecure - secure state + \Input *pHandshakeSecret - [out] buffer to hold handshake secret + \Input *pMasterSecret - [out] buffer to hold master secret + \Input *pKey - ECDHE key used to build handshake/master secret + \Input iKeyLen - length of key + \Input eHashType - hash to use for label expand + \Input iHashLen - hash data length + + \Version 03/21/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLBuildSecrets(SecureStateT *pSecure, uint8_t *pHandshakeSecret, uint8_t *pMasterSecret, uint8_t *pKey, int32_t iKeyLen, CryptHashTypeE eHashType, int32_t iHashLen) +{ + uint8_t aZeroBuf[CRYPTHASH_MAXDIGEST], aWorkBuf[CRYPTHASH_MAXDIGEST], aWorkBuf2[CRYPTHASH_MAXDIGEST]; + + // create zero buf + ds_memclr(aZeroBuf, iHashLen); + + // HKDF-Extract(PSK|0, 0) (Early Secret) + _ProtoSSLHkdfExtract(aWorkBuf, iHashLen, aZeroBuf, iHashLen, pSecure->bSessionResume ? pSecure->PreSharedKey : aZeroBuf, iHashLen, eHashType); + #if DEBUG_RAW_DATA + NetPrintMem(aWorkBuf, iHashLen, "early secret"); + #endif + + // Derive-Secret(., "derived", "") + _ProtoSSLDeriveSecret(pSecure, aWorkBuf2, iHashLen, aWorkBuf, iHashLen, "derived", eHashType, 0, NULL); + // HKDF-Extract(salt, ECDHE) (Handshake Secret) + _ProtoSSLHkdfExtract(pHandshakeSecret, iHashLen, aWorkBuf2, iHashLen, pKey, iKeyLen, eHashType); + #if DEBUG_RAW_DATA + NetPrintMem(pHandshakeSecret, iHashLen, "handshake secret"); + #endif + + // Derive-Secret(., "derived", "") + _ProtoSSLDeriveSecret(pSecure, aWorkBuf2, iHashLen, pHandshakeSecret, iHashLen, "derived", eHashType, 0, NULL); + // HKDF-Extract(salt, 0) (Master Secret) + _ProtoSSLHkdfExtract(pMasterSecret, iHashLen, aWorkBuf2, iHashLen, aZeroBuf, iHashLen, eHashType); + #if DEBUG_RAW_DATA + NetPrintMem(pMasterSecret, iHashLen, "master secret"); + #endif +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLBuildHandshakeKey + + \Description + Builds TLS1.3 secrets and keys for handshake as per + https://tools.ietf.org/html/rfc8446#section-7.1 + + \Input *pState - protossl ref + + \Output + int32_t - result of curve secret function + + \Notes + Secrets, keys, and IVs are stored in the KeyBlock of the SecureStateT + as follows. When more than one version of an item exists, later + versions overwrite earlier versions in place: + + master_secret + + server_secret (early, handshake, application_traffic) + client_secret (early, handshake, application_traffic) + + server_write_key (handshake, application) + server_write_iv (handshake, application) + client_write_key (handshake, application) + client_write_iv (handshake, application) + + resumption_master_secret + + Currently, only EC(DHE) key exchange is supported. + + \Version 03/21/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLBuildHandshakeKey(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + const CryptCurveDhT *pEcc; + CryptHashTypeE eHashType = pSecure->pCipher->uPrfType; + int32_t iHashLen = CryptHashGetSize(eHashType); + uint8_t *pKeyBlock = pSecure->KeyBlock; + uint8_t aHandshakeSecret[CRYPTHASH_MAXDIGEST]; + uint8_t uZero = 0; + CryptEccPointT PublicKey; + int32_t iResult, iSecretSize = 0; + + // init context with key exchange private key, if we have not already done so + if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) != NULL) + { + uint32_t uCryptUsecs; + + // derive EC(DHE) key for handshake traffic encryption + pEcc->PointInit(&PublicKey, pSecure->PubKey, pSecure->uPubKeyLength); + if ((iResult = pEcc->Secret(&pSecure->EccContext, &PublicKey, NULL, &uCryptUsecs)) > 0) + { + return(iResult); + } + iSecretSize = pEcc->PointFinal(pSecure->EccContext, NULL, TRUE, pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey)); + // done with ECC state + pSecure->bEccContextInitialized = FALSE; + + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate key for traffic secret) %dms\n", uCryptUsecs/1000)); + pSecure->uTimer += uCryptUsecs/1000; + } + + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->PreMasterKey, iSecretSize, "ec(dhe) key"); + #endif + + // build main secrets used to derive keying secrets; we save the master secret for later generation of application key info + _ProtoSSLBuildSecrets(pSecure, aHandshakeSecret, pKeyBlock, pSecure->PreMasterKey, iSecretSize, eHashType, iHashLen); + pKeyBlock += iHashLen; + + // build server and client handshake secrets: https://tools.ietf.org/html/rfc8446#section-7.1 + + // build server handshake secret + pKeyBlock += _ProtoSSLDeriveSecret(pSecure, pSecure->pServerSecret = pKeyBlock, iHashLen, aHandshakeSecret, iHashLen, "s hs traffic", eHashType, iHashLen, NULL); + + // build client handshake secret + pKeyBlock += _ProtoSSLDeriveSecret(pSecure, pSecure->pClientSecret = pKeyBlock, iHashLen, aHandshakeSecret, iHashLen, "c hs traffic", eHashType, iHashLen, NULL); + + // build server and client handshake key material: https://tools.ietf.org/html/rfc8446#section-7.3 + + // [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length) + pKeyBlock += _ProtoSSLHkdfExpandLabel(pSecure->pServerKey = pKeyBlock, pSecure->pCipher->uLen, pSecure->pServerSecret, iHashLen, "key", &uZero, 0, eHashType); + // [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length) -- NOTE that iv_length of 12 is the full iv size for the cipher, as the iv is fully derived in TLS1.3 + pKeyBlock += _ProtoSSLHkdfExpandLabel(pSecure->pServerInitVec = pKeyBlock, 12, pSecure->pServerSecret, iHashLen, "iv", &uZero, 0, eHashType); + + // [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length) + pKeyBlock += _ProtoSSLHkdfExpandLabel(pSecure->pClientKey = pKeyBlock, pSecure->pCipher->uLen, pSecure->pClientSecret, iHashLen, "key", &uZero, 0, eHashType); + // [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length) -- see note above + pKeyBlock += _ProtoSSLHkdfExpandLabel(pSecure->pClientInitVec = pKeyBlock, 12, pSecure->pClientSecret, iHashLen, "iv", &uZero, 0, eHashType); + + // save pointer to buffer for resumption secret, derived later + pSecure->pResumeSecret = pKeyBlock; + + // log params for wireshark decrypt + #if DIRTYCODE_LOGGING + if (pState->iVerbose > 0) + { + char strBuf1[128], strBuf2[128]; + NetPrintf(("protossl: [%p] CLIENT_RANDOM %s %s\n", pState, ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)), + ds_fmtoctstring(strBuf2, sizeof(strBuf2), pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey)))); + NetPrintf(("protossl: [%p] CLIENT_HANDSHAKE_TRAFFIC_SECRET %s %s\n", pState, ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)), + ds_fmtoctstring(strBuf2, sizeof(strBuf2), pSecure->pClientSecret, iHashLen))); + NetPrintf(("protossl: [%p] SERVER_HANDSHAKE_TRAFFIC_SECRET %s %s\n", pState, ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)), + ds_fmtoctstring(strBuf2, sizeof(strBuf2), pSecure->pServerSecret, iHashLen))); + } + #endif + + // init secure state with handshake traffic keys + if (pSecure->pCipher->uEnc == SSL3_ENC_GCM) + { + CryptGcmInit(&pSecure->ReadGcm, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen); + CryptGcmInit(&pSecure->WriteGcm, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen); + } + else + { + CryptChaChaInit(&pSecure->ReadChaCha, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen); + CryptChaChaInit(&pSecure->WriteChaCha, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen); + } + // sending and receiving secure from this point + pSecure->bSendSecure = pSecure->bRecvSecure = TRUE; + pSecure->uSendSeqn = pSecure->uRecvSeqn = 0; + + // done + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLBuildApplicationKey + + \Description + Builds TLS1.3 secrets and keys for application data as per + https://tools.ietf.org/html/rfc8446#section-7.1 + + \Input *pState - protossl ref + \Input *pSecure - secure state + + \Notes + See _ProtoSSLBuildHandshakeKey notes for keyblock layout + + \Version 03/22/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLBuildApplicationKey(ProtoSSLRefT *pState, SecureStateT *pSecure) +{ + CryptHashTypeE eHashType = pSecure->pCipher->uPrfType; + int32_t iHashLen = CryptHashGetSize(eHashType); + uint8_t uZero = 0; + + // build server application traffic secret (note this overwrites the server handshake traffic secret) + _ProtoSSLDeriveSecret(pSecure, pSecure->pServerSecret, iHashLen, pSecure->KeyBlock, iHashLen, "s ap traffic", eHashType, iHashLen, pSecure->aFinishHash); + + // build client application traffic secret (note this overwrites the client handshake traffic secret) + _ProtoSSLDeriveSecret(pSecure, pSecure->pClientSecret, iHashLen, pSecure->KeyBlock, iHashLen, "c ap traffic", eHashType, iHashLen, pSecure->aFinishHash); + + // log params for wireshark decrypt + #if DIRTYCODE_LOGGING + if (pState->iVerbose > 0) + { + char strBuf1[128], strBuf2[128]; + NetPrintf(("protossl: [%p] CLIENT_TRAFFIC_SECRET_0 %s %s\n", pState, ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)), + ds_fmtoctstring(strBuf2, sizeof(strBuf2), pSecure->pClientSecret, iHashLen))); + NetPrintf(("protossl: [%p] SERVER_TRAFFIC_SECRET_0 %s %s\n", pState, ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)), + ds_fmtoctstring(strBuf2, sizeof(strBuf2), pSecure->pServerSecret, iHashLen))); + } + #endif + + // [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length) + _ProtoSSLHkdfExpandLabel(pSecure->pServerKey, pSecure->pCipher->uLen, pSecure->pServerSecret, iHashLen, "key", &uZero, 0, eHashType); + // [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length) -- NOTE that iv_length of 12 is the full iv size for the cipher, as the iv is fully derived in TLS1.3 + _ProtoSSLHkdfExpandLabel(pSecure->pServerInitVec, 12, pSecure->pServerSecret, iHashLen, "iv", &uZero, 0, eHashType); + + // [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length) + _ProtoSSLHkdfExpandLabel(pSecure->pClientKey, pSecure->pCipher->uLen, pSecure->pClientSecret, iHashLen, "key", &uZero, 0, eHashType); + // [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length) -- see note above + _ProtoSSLHkdfExpandLabel(pSecure->pClientInitVec, 12, pSecure->pClientSecret, iHashLen, "iv", &uZero, 0, eHashType); + + // build resumption master secret + _ProtoSSLDeriveSecret(pSecure, pSecure->pResumeSecret, iHashLen, pSecure->KeyBlock, iHashLen, "res master", eHashType, iHashLen, NULL); + + #if DEBUG_RES_SESS + NetPrintMem(pSecure->KeyBlock, iHashLen, "master secret"); + NetPrintMem(pSecure->pResumeSecret, iHashLen, "resumption_master_secret"); + #endif + + // reinit secure state with application traffic keys + if (pSecure->pCipher->uEnc == SSL3_ENC_GCM) + { + CryptGcmInit(&pSecure->ReadGcm, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen); + CryptGcmInit(&pSecure->WriteGcm, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen); + } + else + { + CryptChaChaInit(&pSecure->ReadChaCha, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen); + CryptChaChaInit(&pSecure->WriteChaCha, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen); + } + // sending and receiving secure from this point + pSecure->bSendSecure = pSecure->bRecvSecure = TRUE; + pSecure->uSendSeqn = pSecure->uRecvSeqn = 0; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLCalcResumeBinder + + \Description + Calculate the psk binder as per + https://tools.ietf.org/html/rfc8446#section-4.2.11.2 + + \Input *pState - protossl ref + \Input *pBuffer - [out] buffer to store calculated binder + \Input iBufLen - size of output buffer + \Input *pSessTick - session ticket + \Input *pHshkMsgBuf - handshake message buffer + \Input iHshkMsgLen - length of handshake message + \Input iHashSize - binder hash size + + \Output + uint8_t * - pointer past end of binder + + \Version 12/19/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t *_ProtoSSLCalcResumeBinder(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen, const SessionTicketT *pSessTick, const uint8_t *pHshkMsgBuf, int32_t iHshkMsgLen, int32_t iHashSize) +{ + uint8_t aZeroBuf[CRYPTHASH_MAXDIGEST], aWorkBuf[CRYPTHASH_MAXDIGEST], aWorkBuf2[CRYPTHASH_MAXDIGEST], aBinderKey[CRYPTHASH_MAXDIGEST]; + uint8_t aHash[CRYPTHASH_MAXDIGEST], aHashCtx[CRYPTHASH_MAXSTATE]; + uint8_t aMsgHead[] = { SSL3_MSG_CLIENT_HELLO, 0, 0, 0 }; + CryptSha2T *pBinderCtx = (CryptSha2T *)aHashCtx; + SecureStateT *pSecure = pState->pSecure; + + // calculate buffer length - this includes the binders we haven't written out yet + iBufLen = iHshkMsgLen + iHashSize + 3; + // set in clienthello header + aMsgHead[2] = (uint8_t)(iBufLen>>8); + aMsgHead[3] = (uint8_t)(iBufLen>>0); + + // create zero buf + ds_memclr(aZeroBuf, iHashSize); + + // if we're in a HelloRetryRequest flow, we need to include the original [ClientHello...HelloRetryRequest] in the binder hash + if (pSecure->bHelloRetry) + { + // get handshake context + CryptSha2T *pHandshakeCtx = (pSessTick->eHashType == CRYPTHASH_SHA256) ? &pSecure->HandshakeSHA256 : &pSecure->HandshakeSHA384; + // copy to binder hash + ds_memcpy_s(pBinderCtx, sizeof(*pBinderCtx), pHandshakeCtx, sizeof(*pHandshakeCtx)); + } + else + { + // init binder hash + CryptSha2Init(pBinderCtx, iHashSize); + } + // hash the ClientHello message header + CryptSha2Update(pBinderCtx, aMsgHead, sizeof(aMsgHead)); + // hash the ClientHello message body up to binders list + CryptSha2Update(pBinderCtx, pHshkMsgBuf, iHshkMsgLen); + // get binder hash + CryptSha2Final(pBinderCtx, aHash, iHashSize); + + #if DEBUG_RES_SESS + #if DEBUG_RAW_DATA + NetPrintMem(aMsgHead, sizeof(aMsgHead), "message head"); + NetPrintMem(pHshkMsgBuf, iHshkMsgLen, "message body"); + #endif + NetPrintMem(aHash, iHashSize, "binder hash"); + #endif + + // calculate the binder + + // resumption_key = HKDF-Expand-Label(resumption_key, "resumption", ticket_nonce, ticket_nonce.len) + _ProtoSSLHkdfExpandLabel(pSecure->PreSharedKey, iHashSize, pSessTick->aResumeKey, iHashSize, "resumption", pSessTick->aTicketNonce, pSessTick->uNonceLen, pSessTick->eHashType); + // HKDF-Extract(0, PSK) (Early Secret) + _ProtoSSLHkdfExtract(aWorkBuf, iHashSize, aZeroBuf, iHashSize, pSecure->PreSharedKey, iHashSize, pSessTick->eHashType); + // Derive-Secret(., "res binder", "") + _ProtoSSLDeriveSecret(pSecure, aWorkBuf2, iHashSize, aWorkBuf, iHashSize, "res binder", pSessTick->eHashType, 0, NULL); + // finished_key = HKDF-Expand-Label(binder_key, "finished", "", Hash.length) + _ProtoSSLHkdfExpandLabel(aBinderKey, iHashSize, aWorkBuf2, iHashSize, "finished", NULL, 0, pSessTick->eHashType); + // verify_data = HMAC(finished_key, Hash(ClientHello*) + _ProtoSSLHkdfExtract(pBuffer, iHashSize, aBinderKey, iHashSize, aHash, iHashSize, pSessTick->eHashType); + + #if DEBUG_RES_SESS + NetPrintMem(pSecure->PreSharedKey, iHashSize, "PSK"); + NetPrintMem(aWorkBuf, iHashSize, "early_secret"); + NetPrintMem(aWorkBuf2, iHashSize, "binder key"); + NetPrintMem(aBinderKey, iHashSize, "finished key"); + NetPrintMem(pBuffer, iHashSize, "verify_data"); + #endif + + // return pointer past end of binder + return(pBuffer+iHashSize); +} + +/* + encryption and decryption +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLAesEncrypt + + \Description + Encrypt data with AES + + \Input *pSecure - secure state + \Input *pSend - data to encrypt + \Input iSize - size of data to encrypt + + \Output + int32_t - size of encrypted data + + \Version 01/14/2018 (jbrookes) Refactored from _ProtoSSLSendPacket() +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLAesEncrypt(SecureStateT *pSecure, uint8_t *pSend, int32_t iSize) +{ + int32_t iPadBytes; + + // calculate padding + if ((iPadBytes = 16 - (iSize % 16)) == 0) + { + iPadBytes = 16; + } + + // set the padding data + ds_memset(pSend+iSize, iPadBytes-1, iPadBytes); + iSize += iPadBytes; + + // fill in the explict IV for TLS1.1 + if (pSecure->uSslVersion >= SSL3_TLS1_1) + { + pSend -= 16; + CryptRandGet(pSend, 16); + #if DEBUG_RAW_DATA + NetPrintMem(pSend, 16, "explicit IV"); + #endif + iSize += 16; + } + + // do the encryption + CryptAesEncrypt(&pSecure->WriteAes, pSend, iSize); + + // return encrypted size to caller + return(iSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLAesDecrypt + + \Description + Decrypt data with AES + + \Input *pSecure - secure state + \Input *pRecv - data to decrypt + \Input iSize - size of data to decrypt + \Input *pBadMac - [out] bad mac flag + + \Output + int32_t - size of decrypted data, or -1 on error + + \Version 01/14/2018 (jbrookes) Refactored from _RecvPacket() +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLAesDecrypt(SecureStateT *pSecure, uint8_t *pRecv, int32_t iSize, uint8_t *pBadMac) +{ + int32_t iPad, iPadBytes, iPadStart; + + // decrypt the data + CryptAesDecrypt(&pSecure->ReadAes, pRecv, iSize); + + // if TLS1.1 or greater, skip explicit IV + if ((pSecure->uSslVersion >= SSL3_TLS1_1) && (pSecure->iRecvSize >= 16)) + { + pSecure->iRecvBase += 16; + pRecv += 16; + iSize -= 16; + } + + // read number of pad bytes + iPadBytes = pRecv[iSize-1]; + + /* As per http://tools.ietf.org/html/rfc5246#section-6.2.3.2, padding may be up to 255 bytes + in length. Each uint8 in the padding data vector MUST be filled with the padding length + value, padding MUST be checked, and a padding error MUST result in a bad_record_mac + alert. To eliminate a possible timing attack, we note the error here but wait until + after the MAC is generated to report it. */ + for (iPad = 0, iPadStart = iSize-iPadBytes-1; iPad < iPadBytes; iPad += 1) + { + if (pRecv[iPadStart+iPad] != iPadBytes) + { + #if DIRTYCODE_LOGGING + if (!(*pBadMac)) + { + NetPrintf(("protossl: _ProtoSSLAesDecrypt bad padding (len=%d)\n", iPadBytes)); + NetPrintMem(pRecv+iSize-iPadBytes-1, iPadBytes, "_ProtoSSLAesDecrypt padding"); + } + #endif + *pBadMac = TRUE; + iSize = -1; + break; + } + } + + // remove pad if validation was successful + if (!(*pBadMac)) + { + iSize -= iPadBytes+1; + } + // return size to caller + return(iSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLAeadGenerateNonce + + \Description + Generate AEAD Nonce (IV) + + \Input *pSecure - secure state + \Input *pBuffer - [out] storage for generated nonce + \Input uSeqn - sequence number + \Input *pSeqn - sequence pointer (NULL to use sequence number) + \Input *pInitVec - implicit initialization vector (IV) + + \Version 07/08/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLAeadGenerateNonce(SecureStateT *pSecure, uint8_t *pBuffer, uint64_t uSeqn, const uint8_t *pSeqn, const uint8_t *pInitVec) +{ + if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pSecure->pCipher->uEnc != SSL3_ENC_CHACHA)) + { + *pBuffer++ = *pInitVec++; + *pBuffer++ = *pInitVec++; + *pBuffer++ = *pInitVec++; + *pBuffer++ = *pInitVec++; + if (pSeqn == NULL) + { + *pBuffer++ = (uint8_t)((uSeqn>>56)&255); + *pBuffer++ = (uint8_t)((uSeqn>>48)&255); + *pBuffer++ = (uint8_t)((uSeqn>>40)&255); + *pBuffer++ = (uint8_t)((uSeqn>>32)&255); + *pBuffer++ = (uint8_t)((uSeqn>>24)&255); + *pBuffer++ = (uint8_t)((uSeqn>>16)&255); + *pBuffer++ = (uint8_t)((uSeqn>>8)&255); + *pBuffer++ = (uint8_t)((uSeqn>>0)&255); + } + else + { + ds_memcpy(pBuffer, pSeqn, 8); + } + } + else + { + /* tls1.3 (and chacha) nonce generated as per: https://tools.ietf.org/html/rfc8446#section-5.3; + the 64-bit record number is extended to iv_length, and xor'd with iv */ + uint32_t uCount; + pBuffer[ 0] = 0; + pBuffer[ 1] = 0; + pBuffer[ 2] = 0; + pBuffer[ 3] = 0; + pBuffer[ 4] = (uint8_t)((uSeqn>>56)&255); + pBuffer[ 5] = (uint8_t)((uSeqn>>48)&255); + pBuffer[ 6] = (uint8_t)((uSeqn>>40)&255); + pBuffer[ 7] = (uint8_t)((uSeqn>>32)&255); + pBuffer[ 8] = (uint8_t)((uSeqn>>24)&255); + pBuffer[ 9] = (uint8_t)((uSeqn>>16)&255); + pBuffer[10] = (uint8_t)((uSeqn>>8)&255); + pBuffer[11] = (uint8_t)((uSeqn>>0)&255); + for (uCount = 0; uCount < 12; uCount += 1) + { + pBuffer[uCount] ^= pInitVec[uCount]; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLAeadEncrypt + + \Description + Encrypt data with an AEAD cipher + + \Input *pSecure - secure state + \Input *pSend - data to encrypt + \Input iSize - size of data to encrypt + \Input bServer - TRUE if server, else FALSE + + \Output + int32_t - size of encrypted data + + \Version 01/14/2018 (jbrookes) Refactored from _ProtoSSLSendPacket() +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLAeadEncrypt(SecureStateT *pSecure, uint8_t *pSend, int32_t iSize, uint8_t bServer) +{ + uint8_t AeadData[13], AeadNonce[12], AeadTag[16]; + int32_t iAeadDataSize; + + // change content type to application_data, append content type to output + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + uint8_t uContentType = pSecure->SendData[0]; + pSecure->SendData[0] = SSL3_REC_APPLICATION; + pSend[iSize++] = uContentType; + } + + // generate AEAD additional data + if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) + { + // generate aead data (13 bytes, matches TLS MAC) + _ProtoSSLGenerateMacSource(AeadData, pSecure->uSendSeqn, pSecure->SendData[0], pSecure->uSslVersion, (uint32_t)iSize); + iAeadDataSize = sizeof(AeadData); + } + else + { + /* as per https://tools.ietf.org/html/rfc8446#section-5.2, tls1.3 AEAD data is the record header. + we already have the first three bytes of the record header formatted in SendData but send length isn't written + yet, so we calculate it here as the input size plus the tag size */ + int32_t iSendSize = iSize + sizeof(AeadTag); + AeadData[0] = pSecure->SendData[0]; + AeadData[1] = pSecure->SendData[1]; + AeadData[2] = pSecure->SendData[2]; + AeadData[3] = (uint8_t)(iSendSize>>8); + AeadData[4] = (uint8_t)(iSendSize>>0); + iAeadDataSize = 5; + #if DEBUG_RAW_DATA + NetPrintMem(AeadData, iAeadDataSize, "AeadData-send"); + #endif + } + + // generate nonce + if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pSecure->pCipher->uEnc != SSL3_ENC_CHACHA)) + { + // generate nonce (4 bytes implicit salt from the IV; 8 bytes explicit, we use sequence) + _ProtoSSLAeadGenerateNonce(pSecure, AeadNonce, pSecure->uSendSeqn, NULL, bServer ? pSecure->pServerInitVec : pSecure->pClientInitVec); + // write explicit IV to output; this is not encrypted + ds_memcpy(pSend-8, AeadNonce+4, 8); + #if DEBUG_RAW_DATA + NetPrintMem(pSend-8, 8, "explicit IV"); + #endif + } + else + { + // generate tls1.3/chacha nonce + _ProtoSSLAeadGenerateNonce(pSecure, AeadNonce, pSecure->uSendSeqn, NULL, bServer ? pSecure->pServerInitVec : pSecure->pClientInitVec); + } + + // do the encryption + if (pSecure->pCipher->uEnc == SSL3_ENC_GCM) + { + iSize = CryptGcmEncrypt(&pSecure->WriteGcm, pSend, iSize, AeadNonce, sizeof(AeadNonce), AeadData, iAeadDataSize, AeadTag, sizeof(AeadTag)); + } + else + { + iSize = CryptChaChaEncrypt(&pSecure->WriteChaCha, pSend, iSize, AeadNonce, sizeof(AeadNonce), AeadData, iAeadDataSize, AeadTag, sizeof(AeadTag)); + } + + // append tag to output + ds_memcpy(pSend+iSize, AeadTag, sizeof(AeadTag)); + iSize += sizeof(AeadTag); + + // add explicit IV to size *after* encrypt+tag + if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pSecure->pCipher->uEnc != SSL3_ENC_CHACHA)) + { + iSize += 8; + } + + // return encrypted size to caller + return(iSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLAeadDecrypt + + \Description + Decrypt data with an AEAD cipher + + \Input *pSecure - secure state + \Input *pRecv - data to decrypt + \Input iSize - size of data to decrypt + \Input bServer - TRUE if server, else FALSE + \Input *pBadMac - [out] bad mac flag + \Input *pAlert - [out] alert, on error + + \Output + int32_t - size of decrypted data, or -1 on error + + \Version 01/14/2018 (jbrookes) Refactored from _RecvPacket() +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLAeadDecrypt(SecureStateT *pSecure, uint8_t *pRecv, int32_t iSize, uint8_t bServer, uint8_t *pBadMac, int32_t *pAlert) +{ + uint8_t AeadData[13], AeadNonce[12]; + int32_t iAeadDataSize; + + // generate nonce + if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pSecure->pCipher->uEnc != SSL3_ENC_CHACHA)) + { + // generate nonce (4 bytes implicit salt from TLS IV; 8 bytes explicit from packet) + _ProtoSSLAeadGenerateNonce(pSecure, AeadNonce, 0, pRecv, bServer ? pSecure->pClientInitVec : pSecure->pServerInitVec); + + // skip explicit IV in packet data + pSecure->iRecvBase += 8; + pRecv += 8; + } + else + { + // generate tls1.3/chacha nonce + _ProtoSSLAeadGenerateNonce(pSecure, AeadNonce, pSecure->uRecvSeqn, NULL, bServer ? pSecure->pClientInitVec : pSecure->pServerInitVec); + } + + // remove authentication tag + pSecure->iRecvSize -= 16; + pSecure->iRecvProg = pSecure->iRecvSize; + + // recalculate size + iSize = pSecure->iRecvSize - pSecure->iRecvBase; + + // generate AEAD additional data + if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) + { + // generate aead data (13 bytes, matches TLS MAC) + _ProtoSSLGenerateMacSource(AeadData, pSecure->uRecvSeqn, pSecure->RecvHead[0], pSecure->uSslVersion, (uint32_t)iSize); + iAeadDataSize = sizeof(AeadData); + } + else + { + // as per https://tools.ietf.org/html/rfc8446#section-5.2, tls1.3 AEAD data is the record header + iAeadDataSize = sizeof(pSecure->RecvHead); + ds_memcpy_s(AeadData, sizeof(AeadData), pSecure->RecvHead, iAeadDataSize); + #if DEBUG_RAW_DATA + NetPrintMem(AeadData, iAeadDataSize, "AeadData-recv"); + #endif + } + + // decrypt data + if (pSecure->pCipher->uEnc == SSL3_ENC_GCM) + { + iSize = CryptGcmDecrypt(&pSecure->ReadGcm, pRecv, iSize, AeadNonce, sizeof(AeadNonce), AeadData, iAeadDataSize, pRecv+iSize, 16); + } + else + { + iSize = CryptChaChaDecrypt(&pSecure->ReadChaCha, pRecv, iSize, AeadNonce, sizeof(AeadNonce), AeadData, iAeadDataSize, pRecv+iSize, 16); + } + + /* as per http://tools.ietf.org/html/rfc5288#section-3: Implementations MUST send TLS Alert bad_record_mac for all types of + failures encountered in processing the AES-GCM algorithm; this gets handled later in the receive flow */ + if (iSize < 0) + { + NetPrintf(("protossl: aead decrypt of received data failed\n")); + *pBadMac = TRUE; + } + + // for tls1.3, handle padding and content-type extraction + if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && !(*pBadMac)) + { + // handle record padding as per https://tools.ietf.org/html/rfc8446#section-5.4 + for ( ; (iSize > 0) && (pRecv[iSize-1] == 0); iSize -= 1) + ; + // read and overwrite content type + if (iSize > 0) + { + pSecure->iRecvSize = pSecure->iRecvProg = pSecure->iRecvBase+iSize-1; + pSecure->RecvHead[0] = pSecure->RecvData[pSecure->iRecvSize]; + } + else + { + /* Implementations MUST limit their scanning to the cleartext returned from the AEAD decryption. If a receiving implementation + does not find a non-zero octet in the cleartext, it MUST terminate the connection with an "unexpected_message" alert. */ + NetPrintf(("protossl: _ProtoSSLAeadDecrypt: no content-type included in message\n")); + *pAlert = SSL3_ALERT_DESC_UNEXPECTED_MESSAGE; + iSize = -1; + } + } + // return size to caller + return(iSize); +} + +/* + secure packet send and receive +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendSecure + + \Description + Send secure queued data + + \Input *pState - module state + \Input *pSecure - secure state + + \Output + int32_t - zero if nothing was sent, else one + + \Version 11/07/2013 (jbrookes) Refactored from _ProtoSSLUpdateSend() +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendSecure(ProtoSSLRefT *pState, SecureStateT *pSecure) +{ + int32_t iResult, iXfer = 0; + + // see if there is data to send + if (pSecure->iSendProg < pSecure->iSendSize) + { + // try to send + iResult = SocketSend(pState->pSock, (char *)pSecure->SendData+pSecure->iSendProg, pSecure->iSendSize-pSecure->iSendProg, 0); + if (iResult > 0) + { + pSecure->iSendProg += iResult; + iXfer = 1; + } + if (iResult < 0) + { + pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE; + pState->iClosed = 1; + } + // see if the data can be cleared + if (pSecure->iSendProg == pSecure->iSendSize) + { + pSecure->iSendProg = pSecure->iSendSize = 0; + } + } + + // return if something was sent + return(iXfer); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendPacket + + \Description + Build an outgoing SSL packet. Accepts head/body buffers to allow easy + contruction of header / data in individual buffers (or set one to null + if data already combind). + + \Input *pState - ssl state ptr + \Input uType - record type + \Input *pHeadPtr - pointer to head buffer + \Input iHeadLen - length of head buffer + \Input *pBodyPtr - pointer to data to send + \Input iBodyLen - length of data to send + + \Output int32_t - -1=invalid length, zero=no error + + \Version 11/10/2005 gschaefer +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendPacket(ProtoSSLRefT *pState, uint8_t uType, const void *pHeadPtr, int32_t iHeadLen, const void *pBodyPtr, int32_t iBodyLen) +{ + SecureStateT *pSecure = pState->pSecure; + uint32_t uRecordVersion; + uint8_t *pSend; + int32_t iSize; + + // verify if the input buffer length is good + if ((iHeadLen + iBodyLen + SSL_SNDOVH_PACKET) > (signed)sizeof(pSecure->SendData)) + { + NetPrintf(("protossl: _ProtoSSLSendPacket: buffer overflow (iHeadLen=%d, iBodyLen=%d)\n", iHeadLen, iBodyLen)); + return(-1); + } + + // setup record frame + pSecure->SendData[0] = uType; + // get ssl version to embed in header; as per https://tools.ietf.org/html/rfc8446#section-5.1 the record layer version is frozen at 1.2 for TLS1.3+ + uRecordVersion = (pSecure->uSslVersion < SSL3_TLS1_3) ? pSecure->uSslVersion : SSL3_TLS1_2; + pSecure->SendData[1] = (uint8_t)(uRecordVersion>>8); + pSecure->SendData[2] = (uint8_t)(uRecordVersion>>0); + + // point to data area + pSend = pSecure->SendData+5; + iSize = 0; + + /* reserve space for explicit IV if we're TLS1.1 or greater and are using AES (block cipher). reserving + space here means we don't have to shuffle packet data around later. note that the IV is *not* included + in calculating the handshake data for the finish packet */ + if (pSecure->bSendSecure && (pSecure->pCipher != NULL) && (pSecure->pCipher->uEnc == SSL3_ENC_AES) && (pSecure->uSslVersion >= SSL3_TLS1_1)) + { + pSend += 16; + } + // reserve space for explicit IV for GCM (TLS1.2 and lower) + if (pSecure->bSendSecure && (pSecure->pCipher != NULL) && (pSecure->pCipher->uEnc == SSL3_ENC_GCM) && (pSecure->uSslVersion < SSL3_TLS1_3)) + { + pSend += 8; + } + + // copy over the head + ds_memcpy(pSend+iSize, pHeadPtr, iHeadLen); + iSize += iHeadLen; + + // copy over the body + ds_memcpy(pSend+iSize, pBodyPtr, iBodyLen); + iSize += iBodyLen; + + // hash handshake data for "finish" packet + if (uType == SSL3_REC_HANDSHAKE) + { + _ProtoSSLHandshakeHashUpdate(pSecure, pSend, iSize, "_ProtoSSLSendPacket"); + } + + // handle encryption + if (pSecure->bSendSecure && (pSecure->pCipher != NULL)) + { + // add MAC for non-AEAD ciphers + if (pSecure->pCipher->uMacType != CRYPTHASH_NULL) + { + iSize = _ProtoSSLGenerateMac(pSecure, pSend, iSize, pState->bServer); + } + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: _ProtoSSLSendPacket (secure enc=%d mac=%d): type=%d, size=%d, seq=%qd\n", + pSecure->pCipher->uEnc, pSecure->pCipher->uMac, pSecure->SendData[0], iSize, pSecure->uSendSeqn)); + #if (DEBUG_RAW_DATA > 1) + NetPrintMem(pSecure->SendData, iSize+5, "_ProtoSSLSendPacket"); + #endif + + // encrypt the data + if (pSecure->pCipher->uEnc == SSL3_ENC_AES) + { + iSize = _ProtoSSLAesEncrypt(pSecure, pSend, iSize); + } + if ((pSecure->pCipher->uEnc == SSL3_ENC_GCM) || (pSecure->pCipher->uEnc == SSL3_ENC_CHACHA)) + { + iSize = _ProtoSSLAeadEncrypt(pSecure, pSend, iSize, pState->bServer); + } + } + else + { + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: _ProtoSSLSendPacket (unsecure): type=%d, size=%d, seq=%qd\n", pSecure->SendData[0], iSize, pSecure->uSendSeqn)); + #if (DEBUG_RAW_DATA > 1) + NetPrintMem(pSecure->SendData, iSize+5, "_ProtoSSLSendPacket"); + #endif + } + + // setup total record size + pSecure->SendData[3] = (uint8_t)(iSize>>8); + pSecure->SendData[4] = (uint8_t)(iSize>>0); + + // setup buffer pointers + pSecure->iSendProg = 0; + pSecure->iSendSize = iSize+5; + + // increment the sequence + pSecure->uSendSeqn += 1; + return(0); +} + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvGetRecordTypeName + + \Description + Get debug ssl record type name + + \Input uRecordType - tls record type + + \Output + const char * - pointer to record type name, or "unknown" if not recognized + + \Version 11/15/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_ProtoSSLRecvGetRecordTypeName(uint32_t uRecordType) +{ + static const char *_ContentTypeNames[5] = { "ChangeCipherSpec", "Alert", "Handshake", "Application", "Heartbeat" }; + const char *pContentType = ((uRecordType >= 20) && (uRecordType <= 24)) ? _ContentTypeNames[uRecordType-20] : "unknown"; + return(pContentType); +} +#endif + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvGetHandshakeTypeName + + \Description + Get debug ssl handshake type name + + \Input uHandshakeType - tls record type + + \Output + const char * - pointer to handshake type name, or "unknown" if not recognized + + \Version 05/03/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_ProtoSSLRecvGetHandshakeTypeName(uint32_t uHandshakeType) +{ + static const char *_HandshakeTypeNames[] = + { + "hello_request", "client_hello", "server_hello", "hello_verify_request", + "new_session_ticket", "end_of_early_data", "hello_retry_request", "unassigned_7", + "encrypted_extensions", "unassigned_9", "unassigned_10", "certificate", + "server_key_exchange", "certificate_request", "server_hello_done", "certificate_verify", + "client_key_exchange", "unassigned_17", "unassigned_18", "unassigned_19", + "finished", "certificate_url", "certificate_status", "supplemental_data", + "key_update", + "message_hash" + }; + const char *pHandshakeType; + if (uHandshakeType <= SSL3_MSG_KEY_UPDATE) + { + pHandshakeType = _HandshakeTypeNames[uHandshakeType]; + } + else if (uHandshakeType == SSL3_MSG_MESSAGE_HASH) + { + pHandshakeType = _HandshakeTypeNames[SSL3_MSG_KEY_UPDATE+1]; + } + else + { + pHandshakeType = "unknown"; + } + return(pHandshakeType); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvReset + + \Description + Reset receive tracking; called when we're done processing a packet or + in an error condition + + \Input *pSecure - secure state + + \Version 11/14/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static inline void _ProtoSSLRecvReset(SecureStateT *pSecure) +{ + pSecure->iRecvHead = pSecure->iRecvProg = pSecure->iRecvSize = pSecure->iRecvBase = pSecure->iRecvHshkProg = 0; + pSecure->bRecvProc = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvPacket + + \Description + Decode ssl3 record header, decrypt data, verify mac. + + \Input *pState - ssl state ptr + \Input *pAlert - [out] alert, on error + + \Output + int32_t - 0 for success, negative for error + + \Version 11/10/2005 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvPacket(ProtoSSLRefT *pState, int32_t *pAlert) +{ + SecureStateT *pSecure = pState->pSecure; + int32_t iSize = pSecure->iRecvSize-pSecure->iRecvBase; + uint8_t bBadMac = FALSE; + + /* check the record type - as per http://tools.ietf.org/html/rfc5246#section-6, if a TLS implementation + receives an unexpected record type, it MUST send an unexpected_message alert */ + if ((pSecure->RecvHead[0] < SSL3_REC_CIPHER) || (pSecure->RecvHead[0] > SSL3_REC_APPLICATION)) + { + NetPrintf(("protossl: _RecvPacket: unknown record type (%s) %d\n", _ProtoSSLRecvGetRecordTypeName(pSecure->RecvHead[0]), pSecure->RecvHead[0])); + *pAlert = SSL3_ALERT_DESC_UNEXPECTED_MESSAGE; + return(-1); + } + + /* as per https://tools.ietf.org/html/rfc8446#section-5 check for change_cipher_spec record; if + we find one, we ignore it */ + if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && (pSecure->RecvHead[0] == SSL3_REC_CIPHER)) + { + // validate ccs message + if ((iSize != 1) || (pSecure->RecvData[0] != 1)) + { + NetPrintf(("protossl: invalid change_cipher_spec message in tls1.3 flow\n")); + *pAlert = SSL3_ALERT_DESC_DECODE_ERROR; + return(-1); + } + else + { + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: skipping dummy change_cipher_spec message in tls1.3 flow\n")); + pSecure->iRecvHead = pSecure->iRecvProg = pSecure->iRecvSize = pSecure->iRecvBase = pSecure->iRecvHshkProg = 0; + return(0); + } + } + + // if we are reciving the finished packet, we need to transition to secure receiving + if (pState->iState == ST3_RECV_FINISH) + { + pSecure->bRecvSecure = TRUE; + } + + // handle decryption + if (pSecure->bRecvSecure && (pSecure->pCipher != NULL)) + { + // decrypt the data + if (pSecure->pCipher->uEnc == SSL3_ENC_AES) + { + iSize = _ProtoSSLAesDecrypt(pSecure, pSecure->RecvData+pSecure->iRecvBase, iSize, &bBadMac); + } + if ((pSecure->pCipher->uEnc == SSL3_ENC_GCM) || (pSecure->pCipher->uEnc == SSL3_ENC_CHACHA)) + { + iSize = _ProtoSSLAeadDecrypt(pSecure, pSecure->RecvData+pSecure->iRecvBase, iSize, pState->bServer, &bBadMac, pAlert); + } + + // validate MAC for non-AEAD ciphers + if (pSecure->pCipher->uMacType != CRYPTHASH_NULL) + { + iSize = _ProtoSSLVerifyMac(pSecure, iSize, pState->bServer, &bBadMac); + } + + // handle mac/decrypt errors + if (iSize < 0) + { + if (bBadMac) + { + NetPrintf(("protossl: _RecvPacket: bad MAC!\n")); + *pAlert = SSL3_ALERT_DESC_BAD_RECORD_MAC; + } + return(-1); + } + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: _RecvPacket (secure enc=%d mac=%d): type=%s, size=%d, seq=%qd\n", + pSecure->pCipher->uEnc, pSecure->pCipher->uMac, _ProtoSSLRecvGetRecordTypeName(pSecure->RecvHead[0]), iSize, pSecure->uRecvSeqn)); + #if (DEBUG_RAW_DATA > 1) + NetPrintMem(pSecure->RecvData, pSecure->iRecvSize, "_RecvPacket"); + #endif + } + else + { + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: _RecvPacket (unsecure): type=%s, size=%d, seq=%qd\n", _ProtoSSLRecvGetRecordTypeName(pSecure->RecvHead[0]), iSize, pSecure->uRecvSeqn)); + #if (DEBUG_RAW_DATA > 1) + NetPrintMem(pSecure->RecvData+pSecure->iRecvBase, iSize, "_RecvPacket"); + #endif + } + + // increment the sequence number + pSecure->uRecvSeqn += 1; + + /* check for empty packet; some implementations of SSL emit a zero-length frame followed immediately + by an n-length frame when TLS1.0 is employed with a CBC cipher like AES. this is done as a defense + against the BEAST attack. we need to detect and clear the empty packet here so the following code + doesn't use the old packet header with new data */ + if (pSecure->iRecvSize == 0) + { + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: detected empty packet\n")); + pSecure->iRecvHead = 0; + } + + // return success + return(0); +} + +/* + alerts +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGetAlert + + \Description + Get alert from table based on alert type + + \Input *pState - module state + \Input *pAlertDesc - [out] storage for alert description + \Input uAlertLevel - alert level + \Input uAlertType - alert type + + \Output + int32_t - 0=no alert, 1=alert + + \Version 10/31/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLGetAlert(ProtoSSLRefT *pState, ProtoSSLAlertDescT *pAlertDesc, uint8_t uAlertLevel, uint8_t uAlertType) +{ + int32_t iErr, iResult = 0; + + // set up default alertdesc + pAlertDesc->iAlertType = uAlertType; + pAlertDesc->pAlertDesc = "unknown"; + + // find alert description + if (uAlertLevel != 0) + { + for (iErr = 0; _ProtoSSL_AlertList[iErr].iAlertType != -1; iErr += 1) + { + if (_ProtoSSL_AlertList[iErr].iAlertType == uAlertType) + { + pAlertDesc->pAlertDesc = _ProtoSSL_AlertList[iErr].pAlertDesc; + iResult = 1; + break; + } + } + } + return(iResult); +} + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _ProtoSSLDebugAlert + + \Description + Debug-only display of info following server alert + + \Input *pState - module state + \Input uAlertLevel - alert level + \Input uAlertType - alert type + + \Version 04/04/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLDebugAlert(ProtoSSLRefT *pState, uint8_t uAlertLevel, uint8_t uAlertType) +{ + ProtoSSLAlertDescT Alert; + _ProtoSSLGetAlert(pState, &Alert, uAlertLevel, uAlertType); + NetPrintf(("protossl: ALERT: level=%d, type=%d, name=%s\n", uAlertLevel, uAlertType, Alert.pAlertDesc)); +} +#else +#define _ProtoSSLDebugAlert(_pState, _uAlertLevel, _uAlertType) +#endif + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendAlert + + \Description + Send an alert + + \Input *pState - module state reference + \Input iLevel - alert level (1=warning, 2=error) + \Input iValue - alert value + + \Output + int32_t - current state + + \Version 11/06/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendAlert(ProtoSSLRefT *pState, int32_t iLevel, int32_t iValue) +{ + /* $$TODO: we can currently only send if the send state is empty; this should + be addressed so we can queue an alert for sending at any time */ + if ((pState->pSecure != NULL) && (pState->pSecure->iSendProg == 0) && (pState->pSecure->iSendSize == 0)) + { + uint8_t strHead[2]; + #if DIRTYCODE_LOGGING + ProtoSSLAlertDescT Alert; + _ProtoSSLGetAlert(pState, &Alert, iLevel, iValue); + #endif + pState->uAlertLevel = strHead[0] = (uint8_t)iLevel; + pState->uAlertValue = strHead[1] = (uint8_t)iValue; + pState->bAlertSent = TRUE; + NetPrintf(("protossl: sending alert: level=%d type=%s (%d)\n", iLevel, Alert.pAlertDesc, iValue)); + // stage the alert for sending + _ProtoSSLSendPacket(pState, SSL3_REC_ALERT, strHead, sizeof(strHead), NULL, 0); + // flush alert + _ProtoSSLSendSecure(pState, pState->pSecure); + /* As per http://tools.ietf.org/html/rfc5246#section-7.2.2, any error alert sent + or received must result in current session information being reset (no resume) */ + if (iLevel == SSL3_ALERT_LEVEL_FATAL) + { + _SessionHistoryInvalidate(pState->strHost, SockaddrInGetPort(&pState->PeerAddr)); + } + } + else + { + NetPrintf(("protossl: unable to send alert (pSecure=%p, iSendProg=%d, iSendSize=%d)\n", pState->pSecure, + pState->pSecure->iSendProg, pState->pSecure->iSendSize)); + } + + // return current state + return(pState->iState); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvAlert + + \Description + Process an alert message + + \Input *pState - module state reference + \Input *pSecure - secure state reference + + \Version 11/14/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLRecvAlert(ProtoSSLRefT *pState, SecureStateT *pSecure) +{ + pState->uAlertLevel = pSecure->RecvData[pSecure->iRecvBase]; + pState->uAlertValue = pSecure->RecvData[pSecure->iRecvBase+1]; + pState->bAlertSent = FALSE; + + // process warnings + if (pState->uAlertLevel == SSL3_ALERT_LEVEL_WARNING) + { + if (pState->uAlertValue == SSL3_ALERT_DESC_CLOSE_NOTIFY) + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: received close notification\n")); + // only an error if we are still in setup + if (pState->iState < ST3_SECURE) + { + pState->iState = ST_FAIL_SETUP; + } + } + if (pState->uAlertValue == SSL3_ALERT_DESC_NO_RENEGOTIATION) + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: received no_renegotiation alert\n")); + pState->iState = (pState->iState == ST3_RECV_HELLO) ? ST3_SECURE : ST_FAIL_SECURE; + } + } + else // if not a warning, it is an error + { + _ProtoSSLDebugAlert(pState, pSecure->RecvData[pSecure->iRecvBase], pSecure->RecvData[pSecure->iRecvBase+1]); + pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE; + } + + // consume the alert message + _ProtoSSLRecvReset(pSecure); + + // process if the alert resulted in a failure state + if (pState->iState >= ST_FAIL) + { + /* As per http://tools.ietf.org/html/rfc5246#section-7.2.2 any error alert sent or + received must result in current session information being reset (no resume) */ + _SessionHistoryInvalidate(pState->strHost, SockaddrInGetPort(&pState->PeerAddr)); + pState->iClosed = 1; + } +} + +/* + handshake utility functions +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateFingerprint + + \Description + Calculate fingerprint for given data + + \Input *pOutBuf - output buffer to store fingerprint + \Input iOutSize - size of output buffer + \Input *pInpData - input data to calculate fingerprint for + \Input iInpSize - size of input data + \Input eHashType - hash operation to use + + \Version 04/29/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLGenerateFingerprint(uint8_t *pOutBuf, int32_t iOutSize, const uint8_t *pInpData, int32_t iInpSize, CryptHashTypeE eHashType) +{ + const CryptHashT *pHash; + + // calculate the fingerprint using designated hash type + if ((pHash = CryptHashGet(eHashType)) != NULL) + { + uint8_t aHashState[CRYPTHASH_MAXSTATE]; + int32_t iHashSize = CryptHashGetSize(eHashType); + if (iHashSize > iOutSize) + { + iHashSize = iOutSize; + } + pHash->Init(aHashState, iHashSize); + pHash->Update(aHashState, pInpData, iInpSize); + pHash->Final(aHashState, pOutBuf, iHashSize); + #if DEBUG_VAL_CERT + NetPrintMem(pOutBuf, iHashSize, "sha1-fingerprint"); + #endif + } + else + { + NetPrintf(("protossl: _AsnParseCertificate: could not calculate fingerprint\n")); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateEccSharedSecret + + \Description + Generate the ECDHE shared secret if necessary + + \Input *pState - module state reference + + \Output + uint8_t - TRUE=operation complete, FALSE=operation ongoing + + \Version 03/07/2017 (eesponda) +*/ +/********************************************************************************F*/ +static uint8_t _ProtoSSLGenerateEccSharedSecret(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + const CryptCurveDhT *pEcc; + CryptEccPointT PublicKey; + int32_t iSecretSize = 0; + uint32_t uCryptUsecs = 0; + + // only generate the secret if we are using an ECDHE cipher + if (pSecure->pCipher->uKey != SSL3_KEY_ECDHE) + { + return(TRUE); + } + + /* init context with key exchange private key, if we have not already done so. + if we fail to get the dh functions then our master secret will be bad which will + lead to handshake failure */ + if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) != NULL) + { + /* generate shared secret: elliptic curve generation takes multiple frames + so stay in the same state until the operation is complete */ + pEcc->PointInit(&PublicKey, pSecure->PubKey, pSecure->uPubKeyLength); + if (pEcc->Secret(&pSecure->EccContext, &PublicKey, NULL, &uCryptUsecs) > 0) + { + return(FALSE); + } + iSecretSize = pEcc->PointFinal(pSecure->EccContext, NULL, TRUE, pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey)); + } + + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate shared secret) %dms\n", + uCryptUsecs/1000)); + pSecure->uTimer += uCryptUsecs/1000; + + // build master secret + _ProtoSSLBuildKey(pState, pSecure->MasterKey, sizeof(pSecure->MasterKey), pSecure->PreMasterKey, iSecretSize, + pSecure->ClientRandom, pSecure->ServerRandom, sizeof(pSecure->ClientRandom), "master secret", + pSecure->uSslVersion); + + // finished with ecc context + pSecure->bEccContextInitialized = FALSE; + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateCertificateVerifyHash + + \Description + Generate a tls1.3 DigitalSignatureHash; see + https://tools.ietf.org/html/rfc8446#section-4.4.3 + + \Input *pBuffer - [out] storage for generated digitalsignaturehash object + \Input eHashType - signature algorithm hash type + \Input *pHashData - pointer to hash data to embed in the object + \Input iHashSize - size of hash data + \Input bServer - TRUE if server, else FALSE + \Input bSending - TRUE if sending, else receiving + + \Output + int32_t - size of generated hash + + \Version 05/18/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLGenerateCertificateVerifyHash(uint8_t *pBuffer, CryptHashTypeE eHashType, const uint8_t *pHashData, int32_t iHashSize, uint8_t bServer, uint8_t bSending) +{ + uint8_t aTempBuf[64 + 34 + CRYPTHASH_MAXDIGEST], *pTempBuf = aTempBuf; + uint8_t aHashState[CRYPTHASH_MAXSTATE]; + const CryptHashT *pHash = CryptHashGet(eHashType); + + // 64 bytes of spaces + ds_memset(pTempBuf, 32, 64); + pTempBuf += 64; + // context string + pTempBuf += ds_snzprintf((char *)pTempBuf, sizeof(aTempBuf) - 64, "TLS 1.3, %s CertificateVerify", ((bServer && bSending) || (!bServer && !bSending)) ? "server" : "client"); + // zero separator + *pTempBuf++ = '\0'; + // handshake hash + ds_memcpy(pTempBuf, pHashData, iHashSize); + pTempBuf += iHashSize; + + // hash the envelope with the signature algorithm hash (may not match handshake hash) + iHashSize = CryptHashGetSize(eHashType); + pHash->Init(aHashState, iHashSize); + pHash->Update(aHashState, aTempBuf, pTempBuf - aTempBuf); + pHash->Final(aHashState, pBuffer, iHashSize); + + // return hash size to caller + return(iHashSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGenerateFinishHash + + \Description + Generates finish hash data + + \Input *pBuffer - [out] storage for generated finish hash + \Input *pSecure - secure state + \Input *pLabelTLS - label to use for TLS1 finish hash calculation + \Input bServer - TRUE if we're the server, else client + \Input bSending - TRUE if we're sending the finish hash, else receiving + + \Output + int32_t - finish hash size + + \Version 10/11/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLGenerateFinishHash(uint8_t *pBuffer, SecureStateT *pSecure, const char *pLabelTLS, uint8_t bServer, uint8_t bSending) +{ + uint8_t aMacTemp[128]; + int32_t iHashSize; + + if (pSecure->uSslVersion < SSL3_TLS1_3) + { + iHashSize = 12; + ds_strnzcpy((char *)aMacTemp, pLabelTLS, sizeof(aMacTemp)); + + if (pSecure->uSslVersion < SSL3_TLS1_2) + { + // setup the finish verification hashes as per https://tools.ietf.org/html/rfc4346#section-7.4.9 + _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_MD5, aMacTemp+15, sizeof(aMacTemp)-15); + _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_SHA1, aMacTemp+31, sizeof(aMacTemp)-31); + _ProtoSSLDoPRF(pBuffer, iHashSize, pSecure->MasterKey, sizeof(pSecure->MasterKey), aMacTemp, 51); + } + else + { + // setup the finish verification hashes as per https://tools.ietf.org/html/rfc5246#section-7.4.9 + int32_t iHashSize2 = _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, aMacTemp+15, sizeof(aMacTemp)-15); + _ProtoSSLDoPHash(pBuffer, iHashSize, pSecure->MasterKey, sizeof(pSecure->MasterKey), aMacTemp, 15+iHashSize2, pSecure->pCipher->uPrfType); + } + } + else + { + // setup the finish verification hashes as per https://tools.ietf.org/html/rfc8446#section-4.4.4 + uint8_t aFinKey[CRYPTHASH_MAXDIGEST], *pHandshakeSecret; + // get handshake hash + iHashSize = _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, aMacTemp, sizeof(aMacTemp)); + // get handshake secret + pHandshakeSecret = ((bServer && bSending) || (!bServer && !bSending)) ? pSecure->pServerSecret : pSecure->pClientSecret; + // finished_key = HKDF-Expand-Label(BaseKey, "finished", "", Hash.length) + _ProtoSSLHkdfExpandLabel(aFinKey, iHashSize, pHandshakeSecret, iHashSize, "finished", aMacTemp, 0, pSecure->pCipher->uPrfType); + // verify_data = HMAC(finished_key, Hash(Handshake Context + Certificate* + CertificateVerify*) + _ProtoSSLHkdfExtract(pBuffer, iHashSize, aFinKey, iHashSize, aMacTemp, iHashSize, pSecure->pCipher->uPrfType); + } + + return(iHashSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGetSignatureScheme + + \Description + Get SignatureScheme from two-byte ident + + \Input uIdent - ident + + \Output + const SignatureSchemeT * - pointer to signature scheme, or NULL + + \Version 05/12/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static const SignatureSchemeT *_ProtoSSLGetSignatureScheme(const uint16_t uIdent) +{ + const SignatureSchemeT *pSigScheme; + int32_t iTable; + + for (iTable = 0, pSigScheme = NULL; iTable < (signed)(sizeof(_SSL3_SignatureSchemes) / sizeof(_SSL3_SignatureSchemes[0])); iTable += 1) + { + if (_SSL3_SignatureSchemes[iTable].uIdent != uIdent) + { + continue; + } + pSigScheme = &_SSL3_SignatureSchemes[iTable]; + } + return(pSigScheme); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLChooseSignatureScheme + + \Description + Choose a SignatureScheme from a list + + \Input *pSecure - secure state + \Input *pCertificate - server/client certificate + \Input *pData - signature scheme list + \Input uSigSchemeLen - length of list in bytes + \Input *pDataEnd - safe parse limit + + \Output + const SignatureSchemeT * - pointer to signature scheme, or NULL + + \Version 05/12/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static const SignatureSchemeT *_ProtoSSLChooseSignatureScheme(SecureStateT *pSecure, CertificateDataT *pCertificate, const uint8_t *pData, uint32_t uSigSchemeLen, const uint8_t *pDataEnd) +{ + const SignatureSchemeT *pSigScheme; + int32_t iSigScheme, iNumSigSchemes; + uint32_t uCertSigAlg; + + // get signature scheme we need for certificate, if available + uCertSigAlg = _CertificateGetSigAlg(pCertificate); + + // pick first supported signature scheme + for (iSigScheme = 0, iNumSigSchemes = (signed)(uSigSchemeLen/sizeof(SignatureAlgorithmT)); iSigScheme < iNumSigSchemes; iSigScheme += 1, pData += 2) + { + // skip unsupported signature schemes + if ((pSigScheme = _ProtoSSLGetSignatureScheme(_SafeRead16(pData, pDataEnd))) == NULL) + { + continue; + } + // don't pick a signature scheme we don't have a certificate for + if ((uCertSigAlg != SSL3_SIGALG_NONE) && (pSigScheme->SigAlg.uSigAlg != uCertSigAlg)) + { + continue; + } + // don't allow pss signature schemes with a pkcs1 certificate + if ((uCertSigAlg == SSL3_SIGALG_RSA) && (pCertificate->iKeyType != pSigScheme->uOidType)) + { + continue; + } + // tls1.3 specific checks + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + // don't allow pkcs1 signature schemes + if (pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PKCS1) + { + continue; + } + // enforce tls1.3 more restrictive signature schemes based on certificate key type + if ((uCertSigAlg == SSL3_SIGALG_ECDSA) && (pCertificate->iCrvType != pSigScheme->uOidType)) + { + continue; + } + } + return(pSigScheme); + } + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLGetCipher + + \Description + Get cipher from ident, validate that we previously sent it + + \Input *pSecure - secure state + \Input uCipherIdent - cipher ident to get cipher for + + \Output + CipherSuiteT * - pointer to cipher, or null + + \Version 01/18/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const CipherSuiteT *_ProtoSSLGetCipher(SecureStateT *pSecure, uint16_t uCipherIdent) +{ + const CipherSuiteT *pCipher; + int32_t iIndex; + + // match the cipher from our list + for (iIndex = 0, pCipher = NULL; (iIndex < (signed)(sizeof(_SSL3_CipherSuite)/sizeof(_SSL3_CipherSuite[0])) && (pCipher == NULL)); iIndex += 1) + { + if (uCipherIdent != _SSL3_CipherSuite[iIndex].uIdent) + { + continue; + } + // validate that we sent it + if ((_SSL3_CipherSuite[iIndex].uId & pSecure->uSentCiphers) == 0) + { + NetPrintf(("protossl: received cipher from server that we did not send\n")); + break; + } + pCipher = &_SSL3_CipherSuite[iIndex]; + } + + // return to caller + return(pCipher); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLChooseCipher + + \Description + Choose cipher from list of idents idents + + \Input *pSecure - secure state + \Input *pCertificate - server/client cert, if we have one + \Input *pCipherList - list of cipher idents + \Input iNumCiphers - number of ciphers + \Input *pDataEnd - end of data for safe parsing + \Input uCipherMask - mask of enabled ciphers + \Input uCipherPref - ident of preferred cipher, or zero for no preference + + \Output + CipherSuiteT * - pointer to cipher, or null + + \Version 02/19/2018 (jbrookes) Refactored from _ProtoSSLUpdateRecvClientHello +*/ +/********************************************************************************F*/ +static const CipherSuiteT *_ProtoSSLChooseCipher(SecureStateT *pSecure, CertificateDataT *pCertificate, const uint8_t *pCipherList, int32_t iNumCiphers, const uint8_t *pDataEnd, uint32_t uCipherMask, uint32_t uCipherPref) +{ + const uint8_t *pCipherStart = pCipherList; + const CipherSuiteT *pCipher; + int32_t iCipher, iIndex; + uint16_t uCipherIdent; + uint32_t uCertSigAlg; + + // get signature algorithm we need for certificate, if available + uCertSigAlg = _CertificateGetSigAlg(pCertificate); + + // pick first supported cipher + for (iCipher = 0, iIndex = 0, pSecure->pCipher = NULL; (iCipher < iNumCiphers) && (pSecure->pCipher == NULL); iCipher += 1, pCipherList += 2) + { + // read cipher + uCipherIdent = _SafeRead16(pCipherList, pDataEnd); + + // check against our list + for (iIndex = 0; iIndex < (signed)(sizeof(_SSL3_CipherSuite)/sizeof(_SSL3_CipherSuite[0])); iIndex += 1) + { + // ref supported cipher list entry + pCipher = &_SSL3_CipherSuite[iIndex]; + + // skip non-matching ciphers + if (uCipherIdent != pCipher->uIdent) + { + continue; + } + // skip non-preferred cipher, if set + if ((uCipherPref != 0) && (uCipherIdent != uCipherPref)) + { + continue; + } + // skip disabled ciphers + if ((pCipher->uId & uCipherMask) == 0) + { + continue; + } + /* skip elliptic curve ciphers if we found no supported elliptic curves in the extensions; for tls1.3 + we let this throogh, and will send a HelloRetryRequest asking for an elliptic curve we support */ + if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pCipher->uKey != SSL3_KEY_RSA) && (pSecure->pEllipticCurve == NULL)) + { + continue; + } + // tls1.2 or prior; skip ecdsa/rsa ciphers if we don't have an ecdsa/rsa cert + if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pCipher->uSig != uCertSigAlg)) + { + continue; + } + // skip ciphers that require a newer version of SSL than has been negotiated + if (pCipher->uMinVers > pSecure->uSslVersion) + { + continue; + } + // tls1.3 requires tls1.3 ciphers + if ((pSecure->uSslVersion >= SSL3_TLS1_3) && (pCipher->uMinVers < SSL3_TLS1_3)) + { + continue; + } + + // found a cipher + pSecure->pCipher = pCipher; + break; + } + } + // if we were looking for a preferred cipher and didn't find it, try again with no preference + if ((pSecure->pCipher == NULL) && (uCipherPref != 0)) + { + pSecure->pCipher = _ProtoSSLChooseCipher(pSecure, pCertificate, pCipherStart, iNumCiphers, pDataEnd, uCipherMask, 0); + } + return(pSecure->pCipher); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLChooseCurve + + \Description + Choose curve based on enabled curves and default curve preferences + + \Input uEnabledCurves - enabled curves + \Input iCurveDflt - preferred default curve + + \Output + EllipticCurveT * - pointer to curve, or null + + \Version 06/07/2019 (eesponda) +*/ +/********************************************************************************F*/ +static const EllipticCurveT *_ProtoSSLChooseCurve(uint32_t uEnabledCurves, int32_t iCurveDflt) +{ + int32_t iEllipticCurve; + const EllipticCurveT *pEllipticCurve = NULL; + + // if the default curve is enabled and supported use that + if ((iCurveDflt != -1) && ((_SSL3_EllipticCurves[iCurveDflt].uId & uEnabledCurves) != 0)) + { + pEllipticCurve = &_SSL3_EllipticCurves[iCurveDflt]; + } + // otherwise choose the first supported curve in our list + for (iEllipticCurve = 0; (iEllipticCurve < SSL3_NUM_CURVES) && (pEllipticCurve == NULL); iEllipticCurve += 1) + { + // skip disabled curves + if ((_SSL3_EllipticCurves[iEllipticCurve].uId & uEnabledCurves) == 0) + { + continue; + } + // found a curve + pEllipticCurve = &_SSL3_EllipticCurves[iEllipticCurve]; + } + return(pEllipticCurve); +} + +/* + hello extension writing +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnWriteAlpn + + \Description + Write Application Level Protocol Negotiation (ALPN) extension to ClientHello + and ServerHello handshake packets; ref http://tools.ietf.org/html/rfc7301 + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + + \Output + int32_t - number of bytes added to buffer + + \Version 08/03/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnWriteAlpn(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen) +{ + uint16_t uAlpnExtensionLength, uExtnLength; + uint8_t *pBufStart = pBuffer; + const SecureStateT *pSecure = pState->pSecure; + + // if no protocols, don't write anything into ClientHello + if ((!pState->bServer) && (pState->uNumAlpnProtocols == 0)) + { + return(0); + } + // if no protocol selected, don't write anything into ServerHello + else if ((pState->bServer) && ((pSecure == NULL) || (*pSecure->strAlpnProtocol == '\0'))) + { + return(0); + } + + // calculate the size of the extension based on if we are client or server + uAlpnExtensionLength = (!pState->bServer) ? pState->uAlpnExtensionLength : ((uint8_t)strlen(pSecure->strAlpnProtocol) + sizeof(uint8_t)); + uExtnLength = 2+uAlpnExtensionLength; // ident+length+protocol list length + + // make sure we have room + if ((2+2+uExtnLength) > iBufLen) + { + NetPrintf(("protossl: could not add extension alpn; insufficient buffer\n")); + return(0); + } + + // extension ident + *pBuffer++ = (uint8_t)(SSL_EXTN_ALPN>>8); + *pBuffer++ = (uint8_t)(SSL_EXTN_ALPN>>0); + // extension length + *pBuffer++ = (uint8_t)(uExtnLength>>8); + *pBuffer++ = (uint8_t)(uExtnLength>>0); + // alpn length + *pBuffer++ = (uint8_t)(pState->uAlpnExtensionLength>>8); + *pBuffer++ = (uint8_t)(pState->uAlpnExtensionLength>>0); + + // write the protocols + if (!pState->bServer) + { + int16_t iProtocol; + + // alpn protocols (length + byte string) + for (iProtocol = 0; iProtocol < pState->uNumAlpnProtocols; iProtocol += 1) + { + const AlpnProtocolT *pProtocol = &pState->aAlpnProtocols[iProtocol]; + + *pBuffer++ = pProtocol->uLength; + ds_memcpy(pBuffer, pProtocol->strName, pProtocol->uLength); + pBuffer += pProtocol->uLength; + } + } + else + { + const uint8_t uProtocolLen = uAlpnExtensionLength-(uint8_t)sizeof(uint8_t); + + // alpn protocol + *pBuffer++ = uProtocolLen; + ds_memcpy(pBuffer, pSecure->strAlpnProtocol, uProtocolLen); + pBuffer += uProtocolLen; + } + + return((int32_t)(pBuffer-pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnWriteCookie + + \Description + Write Cookie extension, if we have one + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + + \Output + int32_t - number of bytes added to buffer + + \Version 11/29/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnWriteCookie(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen) +{ + uint8_t *pBufStart = pBuffer; + SecureStateT *pSecure = pState->pSecure; + int32_t iCookieLength; + + // only if we have a cookie + if (pSecure->pCookie == NULL) + { + return(0); + } + + // read cookie length + iCookieLength = (pSecure->pCookie[0] << 8) | pSecure->pCookie[1]; + + // make sure we have enough room + if ((2+2+2+iCookieLength) > iBufLen) + { + NetPrintf(("protossl: could not add extension cookie; insufficient buffer\n")); + return(0); + } + + // extension ident + *pBuffer++ = (uint8_t)(SSL_EXTN_COOKIE>>8); + *pBuffer++ = (uint8_t)(SSL_EXTN_COOKIE>>0); + // extension length + *pBuffer++ = (uint8_t)((iCookieLength+2)>>8); + *pBuffer++ = (uint8_t)((iCookieLength+2)>>0); + // cookie length + *pBuffer++ = (uint8_t)(iCookieLength>>8); + *pBuffer++ = (uint8_t)(iCookieLength>>0); + // add the cookie + ds_memcpy_s(pBuffer, iBufLen-6, pSecure->pCookie+2, iCookieLength); + pBuffer += iCookieLength; + + // as per https://tools.ietf.org/html/rfc8446#section-4.2.2 cookies can only be used once + pSecure->pCookie = NULL; + + return((int32_t)(pBuffer-pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnWriteEllipticCurves + + \Description + Write the Elliptic Curves / Supported Groups extension to the ClientHello; + ref http://tools.ietf.org/html/rfc4492 + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + + \Output + int32_t - number of bytes added to buffer + + \Notes + TLS1.3 calls this "supported_groups", but the actual extension format is + identical. + + \Version 01/19/2017 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnWriteEllipticCurves(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen) +{ + const uint16_t uMaxEllipticCurvesLength = sizeof(_SSL3_EllipticCurves[0].uIdent)*(sizeof(_SSL3_EllipticCurves)/sizeof(*_SSL3_EllipticCurves)); + uint16_t uEllipticCurvesLength, uExtnLength; + uint8_t *pBufStart = pBuffer, *pExtnLength, uNumCurves; + int32_t iEllipticCurve; + + // make sure we have enough room + if ((2+2+2+uMaxEllipticCurvesLength) > iBufLen) + { + NetPrintf(("protossl: could not add extension supported_groups; insufficient buffer\n")); + return(0); + } + // if no curves are enabled don't write anything + if (pState->uEnabledCurves == 0) + { + return(0); + } + + // extension ident + *pBuffer++ = (uint8_t)(SSL_EXTN_ELLIPTIC_CURVES>>8); + *pBuffer++ = (uint8_t)(SSL_EXTN_ELLIPTIC_CURVES>>0); + // save the location and skip past + pExtnLength = pBuffer; + pBuffer += 4; + // supported elliptic curves + for (iEllipticCurve = 0, uNumCurves = 0; iEllipticCurve < (signed)(sizeof(_SSL3_EllipticCurves)/sizeof(*_SSL3_EllipticCurves)); iEllipticCurve += 1) + { + // skip disabled curves + if ((_SSL3_EllipticCurves[iEllipticCurve].uId & pState->uEnabledCurves) == 0) + { + continue; + } + *pBuffer++ = (uint8_t)(_SSL3_EllipticCurves[iEllipticCurve].uIdent>>8); + *pBuffer++ = (uint8_t)(_SSL3_EllipticCurves[iEllipticCurve].uIdent>>0); + uNumCurves += 1; + } + uEllipticCurvesLength = sizeof(_SSL3_EllipticCurves[0].uIdent)*uNumCurves; + uExtnLength = uEllipticCurvesLength+2; + + // extension length + *pExtnLength++ = (uint8_t)(uExtnLength>>8); + *pExtnLength++ = (uint8_t)(uExtnLength>>0); + // elliptic curves length + *pExtnLength++ = (uint8_t)(uEllipticCurvesLength>>8); + *pExtnLength++ = (uint8_t)(uEllipticCurvesLength>>0); + + return((int32_t)(pBuffer-pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnWriteKeyShare + + \Description + Write the KeyShare extension to the ClientHello; + ref https://tools.ietf.org/html/rfc8446#section-4.2.8 + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + + \Output + int32_t - number of bytes added to buffer + + \Version 01/25/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnWriteKeyShare(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen) +{ + SecureStateT *pSecure = pState->pSecure; + int32_t iDataLength = 0; + uint8_t *pBufStart = pBuffer; + uint8_t aPublicKey[128]; + const EllipticCurveT *pEllipticCurve; + + // ref elliptic curve + if (((pEllipticCurve = pSecure->pEllipticCurve) == NULL) && (pState->iCurveDflt != -1) && ((_SSL3_EllipticCurves[pState->iCurveDflt].uId & pState->uEnabledCurves) != 0)) + { + pEllipticCurve = _ProtoSSLChooseCurve(pState->uEnabledCurves, pState->iCurveDflt); + } + + // set up curve data + if (pEllipticCurve != NULL) + { + const CryptCurveDhT *pEcc; + int32_t iPublicKeySize = 0; + + // set public key ident + aPublicKey[0] = (uint8_t)(pEllipticCurve->uIdent>>8); + aPublicKey[1] = (uint8_t)(pEllipticCurve->uIdent>>0); + + // if server in helloretryrequest flow, use KeyShareHelloRetryRequest value which does not include a key or key size + if (pState->bServer && (pState->iState == ST3_SEND_HELLO_RETRY)) + { + iDataLength = 2; + } + else + { + iDataLength = 4; + + // encode public key generated in Hello into buffer + if (pSecure->bEccKeyGenerated && ((pEcc = _ProtoSSLEccInitContext(pSecure, pEllipticCurve)) != NULL)) + { + iPublicKeySize = pEcc->PointFinal(pSecure->EccContext, NULL, FALSE, aPublicKey+4, sizeof(aPublicKey)-4); + iDataLength += iPublicKeySize; + } + + // encode public key size + aPublicKey[2] = (uint8_t)(iPublicKeySize>>8); + aPublicKey[3] = (uint8_t)(iPublicKeySize>>0); + } + } + + // make sure we have enough room + if ((2+2+2+iDataLength) > iBufLen) + { + NetPrintf(("protossl: could not add extension key_share; insufficient buffer\n")); + return(0); + } + + // extension ident + *pBuffer++ = (uint8_t)(SSL_EXTN_KEY_SHARE>>8); + *pBuffer++ = (uint8_t)(SSL_EXTN_KEY_SHARE>>0); + // extension length (client only) + if (!pState->bServer) + { + *pBuffer++ = (uint8_t)((iDataLength+2)>>8); + *pBuffer++ = (uint8_t)((iDataLength+2)>>0); + } + // key_share length + *pBuffer++ = (uint8_t)(iDataLength>>8); + *pBuffer++ = (uint8_t)(iDataLength>>0); + // key share data + if (iDataLength > 0) + { + ds_memcpy(pBuffer, aPublicKey, iDataLength); + pBuffer += iDataLength; + } + // return extension size to caller + return((int32_t)(pBuffer-pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnWritePreSharedKey + + \Description + Write Pre-Shared Key (PSK) extension to the ClientHello handshake packet; + ref https://tools.ietf.org/html/rfc8446#section-4.2.11 + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + \Input *pHshkMsgBuf - handshake message buffer pointer + + \Output + int32_t - number of bytes added to buffer + + \Version 12/07/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnWritePreSharedKey(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen, const uint8_t *pHshkMsgBuf) +{ + uint8_t *pBufStart = pBuffer, *pHshkMsgEnd; + const SessionHistoryT *pSessHist; + const SessionTicketT *pSessTick; + int32_t iExtnLength, iHashSize; + uint32_t uAgeAdd; + + // only add if we have a ticket to match + if (((pSessHist = _SessionHistoryGet(pState->strHost, SockaddrInGetPort(&pState->PeerAddr), NULL)) == NULL) || !pSessHist->bSessionTicket) + { + return(0); + } + pSessTick = &pSessHist->SessionTicket; + + // get hash length + iHashSize = CryptHashGetSize(pSessTick->eHashType); + + // make sure we have enough room + iExtnLength = 4 + 2 + 2 + pSessTick->uTickLen + 4 + 2 + 1 + iHashSize; + if (iExtnLength > iBufLen) + { + return((iBufLen == 0) ? iExtnLength : 0); + } + + // pre_shared_Key extension ident + *pBuffer++ = (uint8_t)(SSL_EXTN_PRE_SHARED_KEY>>8); + *pBuffer++ = (uint8_t)(SSL_EXTN_PRE_SHARED_KEY>>0); + + // client adds offered psks (we only support one) + if (!pState->bServer) + { + // set pre_shared_key extension length + *pBuffer++ = (uint8_t)((iExtnLength-4)>>8); + *pBuffer++ = (uint8_t)((iExtnLength-4)>>0); + + // add psks identity length + *pBuffer++ = (uint8_t)((2+pSessTick->uTickLen+sizeof(uint32_t))>>8); + *pBuffer++ = (uint8_t)((2+pSessTick->uTickLen+sizeof(uint32_t))>>0); + // add identity length + *pBuffer++ = (uint8_t)(pSessTick->uTickLen>>8); + *pBuffer++ = (uint8_t)(pSessTick->uTickLen>>0); + // add ticket + ds_memcpy(pBuffer, pSessTick->aTicketData, pSessTick->uTickLen); + pBuffer += pSessTick->uTickLen; + // add obfuscated ticket age + uAgeAdd = (time(NULL) - pSessTick->uRecvTime) * 1000; + uAgeAdd += pSessTick->uAgeAdd; + *pBuffer++ = (uint8_t)(uAgeAdd>>24); + *pBuffer++ = (uint8_t)(uAgeAdd>>16); + *pBuffer++ = (uint8_t)(uAgeAdd>>8); + *pBuffer++ = (uint8_t)(uAgeAdd>>0); + // save handshake message end + pHshkMsgEnd = pBuffer; + // add binders length + *pBuffer++ = (uint8_t)((iHashSize+1)>>8); + *pBuffer++ = (uint8_t)((iHashSize+1)>>0); + // add binder length + *pBuffer++ = (uint8_t)(iHashSize>>0); + // calculate the binder + pBuffer = _ProtoSSLCalcResumeBinder(pState, pBuffer, iBufLen, pSessTick, pHshkMsgBuf, pHshkMsgEnd-pHshkMsgBuf, iHashSize); + } + else // indicated selected psk - we only support one + { + *pBuffer++ = (uint8_t)(2>>8); + *pBuffer++ = (uint8_t)(2>>0); + *pBuffer++ = (uint8_t)(0>>8); + *pBuffer++ = (uint8_t)(0>>0); + } + return((int32_t)(pBuffer-pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnWritePreSharedKeyModes + + \Description + Write Pre-Shared Key Exchange Modes extension to the ClientHello handshake + packet; ref https://tools.ietf.org/html/rfc8446#section-4.2.9 + + This message is client-only + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + + \Output + int32_t - number of bytes added to buffer + + \Version 12/12/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnWritePreSharedKeyModes(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen) +{ + const uint16_t uExtnLength = 2; + uint8_t *pBufStart = pBuffer; + + // make sure we have enough room + if ((2+2+uExtnLength) > iBufLen) + { + NetPrintf(("protossl: could not add extension psk_modes; insufficient buffer\n")); + return(0); + } + + // extension ident + *pBuffer++ = (uint8_t)(SSL_EXTN_PSK_MODES>>8); + *pBuffer++ = (uint8_t)(SSL_EXTN_PSK_MODES>>0); + // extension length + *pBuffer++ = (uint8_t)(uExtnLength>>8); + *pBuffer++ = (uint8_t)(uExtnLength>>0); + // psk_mode array length + *pBuffer++ = 1; + // psk_modes (only offer psk_dhe_ke; most servers explicitly disallow psk_ke) + *pBuffer++ = 1; + + return((int32_t)(pBuffer-pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnWriteRenegotiationInfo + + \Description + Write Renegotiation Info extension to the ClientHello handshake packet; + ref https://tools.ietf.org/html/rfc5746 + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + + \Output + int32_t - number of bytes added to buffer + + \Notes + This extension is added strictly for compatibility with sites that require + it and will fail the connection if it is not present. ProtoSSL does not + support renegotiation of any kind and an attempt by a server to renegotiate + will result in a no_renegotiation alert. + + \Version 03/27/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnWriteRenegotiationInfo(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen) +{ + uint32_t uHostLen = (uint32_t)strlen(pState->strHost); + uint8_t *pBufStart = pBuffer; + + // make sure we have enough room + if ((uHostLen+9) > (unsigned)iBufLen) + { + NetPrintf(("protossl: could not add extension server_name; insufficient buffer\n")); + return(0); + } + + // renegotiation_info extension ident + *pBuffer++ = (uint8_t)((SSL_EXTN_RENEGOTIATION_INFO>>8)&0xff); + *pBuffer++ = (uint8_t)((SSL_EXTN_RENEGOTIATION_INFO>>0)&0xff); + // empty renegotiation_info length + *pBuffer++ = (uint8_t)0; + *pBuffer++ = (uint8_t)1; + // empty renegotiation_info + *pBuffer++ = (uint8_t)0; + + return((int32_t)(pBuffer-pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnWriteServerName + + \Description + Write Server Name Indication (SNI) extension to the ClientHello handshake + packet; ref http://tools.ietf.org/html/rfc6066#page-6 + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + + \Output + int32_t - number of bytes added to buffer + + \Version 10/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnWriteServerName(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen) +{ + uint32_t uHostLen = (uint32_t)strlen(pState->strHost); + uint8_t *pBufStart = pBuffer; + + // make sure we have enough room + if ((uHostLen+9) > (unsigned)iBufLen) + { + NetPrintf(("protossl: could not add extension server_name; insufficient buffer\n")); + return(0); + } + + // server name extension ident + *pBuffer++ = (uint8_t)(SSL_EXTN_SERVER_NAME>>8); + *pBuffer++ = (uint8_t)(SSL_EXTN_SERVER_NAME>>0); + // server name extension length + *pBuffer++ = (uint8_t)((uHostLen+5) >> 8); + *pBuffer++ = (uint8_t)(uHostLen+5); + + // server name list length + *pBuffer++ = (uint8_t)((uHostLen+3) >> 8); + *pBuffer++ = (uint8_t)(uHostLen+3); + // type: hostname + *pBuffer++ = 0; + // hostname length + *pBuffer++ = (uint8_t)(uHostLen >> 8); + *pBuffer++ = (uint8_t)(uHostLen); + + // hostname + ds_memcpy(pBuffer, pState->strHost, uHostLen); + pBuffer += (int32_t)uHostLen; + + return((int32_t)(pBuffer-pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnWriteSignatureAlgorithms + + \Description + Write Signature Algorithms extension to the ClientHello handshake packet; + ref https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + + \Output + int32_t - number of bytes added to buffer + + \Version 10/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnWriteSignatureAlgorithms(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen) +{ + const int32_t _iListLength = sizeof(_SSL3_SignatureSchemes)/sizeof(_SSL3_SignatureSchemes[0])*2; // signature hash algorithms list length + const int32_t _iExtnLength = 2+_iListLength; // ident+length+algorithms list length + SecureStateT *pSecure = pState->pSecure; + uint8_t *pBufStart = pBuffer; + int32_t iIndex; + + // as per RFC, this extension is not meaningful for TLS versions prior to 1.2; clients MUST NOT offer it if they are offering prior versions + if (pSecure->uSslVersion < SSL3_TLS1_2) + { + return(0); + } + // make sure we have enough room + if ((2+2+_iExtnLength) > iBufLen) + { + NetPrintf(("protossl: could not add extension signature_algorithms; insufficient buffer\n")); + return(0); + } + + // signature hash algorithms extension ident + *pBuffer++ = (uint8_t)(SSL_EXTN_SIGNATURE_ALGORITHMS>>8); + *pBuffer++ = (uint8_t)(SSL_EXTN_SIGNATURE_ALGORITHMS>>0); + // signature hash algorithms extension length + *pBuffer++ = (uint8_t)(_iExtnLength>>8); + *pBuffer++ = (uint8_t)(_iExtnLength>>0); + // signature hash algorithms extension list length + *pBuffer++ = (uint8_t)(_iListLength>>8); + *pBuffer++ = (uint8_t)(_iListLength>>0); + // supported signature algorithms + for (iIndex = 0; iIndex < (signed)(sizeof(_SSL3_SignatureSchemes)/sizeof(*_SSL3_SignatureSchemes)); iIndex += 1) + { + *pBuffer++ = (uint8_t)(_SSL3_SignatureSchemes[iIndex].uIdent>>8); + *pBuffer++ = (uint8_t)(_SSL3_SignatureSchemes[iIndex].uIdent>>0); + } + + return((int32_t)(pBuffer-pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnWriteSupportedVersions + + \Description + Write the Supported Versions extension to the ClientHello; + ref https://tools.ietf.org/html/rfc8446#section-4.2.1 + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + + \Output + int32_t - number of bytes added to buffer + + \Notes + Unlike other extensions, supported_versions is an extension required to + support TLS1.3. A supported_versions extension overrides the legacy TLS + client version in the ClientHello, which remains fixed at 1.2. + + \Version 01/24/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnWriteSupportedVersions(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen) +{ + int32_t iVersion, iNumVersions; + int32_t iDataLength, iExtnLength; + uint8_t *pBufStart = pBuffer; + SecureStateT *pSecure = pState->pSecure; + + // list all versions if not server + if (!pState->bServer) + { + iNumVersions = pState->uSslVersion - pState->uSslVersionMin + 1; + iDataLength = (iNumVersions * 2) + 1; + } + else + { + iNumVersions = 1; + iDataLength = 2; + } + // calculate extension length + iExtnLength = 2 + 2 + iDataLength; + + // make sure we have enough room + if (iExtnLength > iBufLen) + { + NetPrintf(("protossl: could not add extension supported_versions; insufficient buffer\n")); + return(0); + } + + // extension ident + *pBuffer++ = (uint8_t)(SSL_EXTN_SUPPORTED_VERSIONS>>8); + *pBuffer++ = (uint8_t)(SSL_EXTN_SUPPORTED_VERSIONS>>0); + // extension length + *pBuffer++ = (uint8_t)((iDataLength)>>8); + *pBuffer++ = (uint8_t)((iDataLength)>>0); + + // add extension data + if (!pState->bServer) + { + // size of versions list + *pBuffer++ = (uint8_t)(iNumVersions*2); + // supported_versions data + for (iVersion = pState->uSslVersion; iVersion >= pState->uSslVersionMin; iVersion -= 1) + { + *pBuffer++ = (uint8_t)(iVersion>>8); + *pBuffer++ = (uint8_t)(iVersion>>0); + } + } + else + { + *pBuffer++ = (uint8_t)(pSecure->uSslVersion>>8); + *pBuffer++ = (uint8_t)(pSecure->uSslVersion>>0); + } + + return((int32_t)(pBuffer-pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLAddHelloExtensions + + \Description + Add any ClientHello extensions + + \Input *pState - module state reference + \Input *pBuffer - buffer to write extension into + \Input iBufLen - length of buffer + \Input *pHshkMsgBuf - handshake message buffer + \Input uHelloExtn - flag of which hello extensions to include + + \Output + uint8_t * - pointer past end of extension chunk + + \Notes + ClientHello extension registry: + http://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml + + \Version 10/01/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t *_ProtoSSLAddHelloExtensions(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen, const uint8_t *pHshkMsgBuf, uint32_t uHelloExtn) +{ + SecureStateT *pSecure = pState->pSecure; + uint32_t uExtnLen; + int32_t iBufOff, iPskOff = 0; + + // skip extensions length field + iBufOff = 2; + + // add enabled extensions (client only) + if (!pState->bServer) + { + if (uHelloExtn & PROTOSSL_HELLOEXTN_SERVERNAME) + { + iBufOff += _ProtoSSLHelloExtnWriteServerName(pState, pBuffer+iBufOff, iBufLen-iBufOff); + } + if (uHelloExtn & PROTOSSL_HELLOEXTN_ELLIPTIC_CURVES) + { + iBufOff += _ProtoSSLHelloExtnWriteEllipticCurves(pState, pBuffer+iBufOff, iBufLen-iBufOff); + } + } + + // add enabled extensions (client & server) + if (uHelloExtn & PROTOSSL_HELLOEXTN_ALPN) + { + iBufOff += _ProtoSSLHelloExtnWriteAlpn(pState, pBuffer+iBufOff, iBufLen-iBufOff); + } + if ((!pState->bServer && (pState->uSslVersionMin < SSL3_TLS1_3)) || (pState->bServer && pSecure->bRenegotiationInfo && (pSecure->uSslVersion < SSL3_TLS1_3))) + { + iBufOff += _ProtoSSLHelloExtnWriteRenegotiationInfo(pState, pBuffer+iBufOff, iBufLen-iBufOff); + } + if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && (pState->iState != ST3_SEND_EXTN) && (pState->iState != ST3_SEND_CERT_REQ)) + { + iBufOff += _ProtoSSLHelloExtnWriteKeyShare(pState, pBuffer+iBufOff, iBufLen-iBufOff); + iBufOff += _ProtoSSLHelloExtnWriteSupportedVersions(pState, pBuffer+iBufOff, iBufLen-iBufOff); + iBufOff += _ProtoSSLHelloExtnWriteCookie(pState, pBuffer+iBufOff, iBufLen-iBufOff); + } + if (((uHelloExtn & PROTOSSL_HELLOEXTN_SIGALGS) && !pState->bServer) || ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && (!pState->bServer || pState->iState == ST3_SEND_CERT_REQ))) + { + iBufOff += _ProtoSSLHelloExtnWriteSignatureAlgorithms(pState, pBuffer+iBufOff, iBufLen-iBufOff); + } + + // add psk-modes (client only) + if (!pState->bServer) + { + iBufOff += _ProtoSSLHelloExtnWritePreSharedKeyModes(pState, pBuffer+iBufOff, iBufLen-iBufOff); + } + + /* get pre-shared key length so we can write overall extension length. we need that value + to be correct when we actually write the pre_shared_key extension, so that we can calculate + the binder hash correctly */ + if (!pState->bServer && (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)) + { + int32_t iPskLen = _ProtoSSLHelloExtnWritePreSharedKey(pState, NULL, 0, NULL); + iPskOff = iBufOff; + if ((iBufOff+iPskLen) > iBufLen) + { + NetPrintf(("protossl: could not add extension psk_modes; insufficient buffer\n")); + iPskLen = 0; + } + iBufOff += iPskLen; + } + + // update extension length + if (iBufOff > 2) + { + uExtnLen = (uint32_t)(iBufOff-2); + pBuffer[0] = (uint8_t)(uExtnLen>>8); + pBuffer[1] = (uint8_t)(uExtnLen>>0); + } + else + { + iBufOff = 0; + } + + // add pre_shared_key extension + if ((iBufOff > 2) && (iPskOff > 0)) + { + _ProtoSSLHelloExtnWritePreSharedKey(pState, pBuffer+iPskOff, iBufLen-iPskOff, pHshkMsgBuf); + } + + // return updated buffer pointer + return(pBuffer+iBufOff); +} + +/* + hello extension parsing +*/ + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnGetName + + \Description + Get debug extension name for logging + + \Input uExtnId + + \Output + const char * - pointer to name, or "unknown" if not recognized + + \Version 04/11/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_ProtoSSLHelloExtnGetName(uint32_t uExtnId) +{ + const char *pName = "unknown"; + if (uExtnId <= SSL_EXTN_MAX) + { + pName = _SSL3_strExtensionNames[uExtnId]; + } + else if (uExtnId == SSL_EXTN_RENEGOTIATION_INFO) + { + pName = "renegotiation_info"; + } + return(pName); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnParseAlpn + + \Description + Parse the ALPN extension from the client & server hello + + \Input *pState - module state reference + \Input *pData - the extension data we are parsing + \Input *pDataEnd - end of extension data + + \Output + int32_t - result of opertion (zero or ST_FAIL_* on error) + + \Version 08/05/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnParseAlpn(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd) +{ + SecureStateT *pSecure = pState->pSecure; + uint16_t uAlpnExtensionLength = _SafeRead16(pData, pDataEnd); + pData += 2; + + if (uAlpnExtensionLength == 0) + { + /* if the extension has data but the list length is zero + treat this as handshake failure */ + NetPrintf(("protossl: received invalid alpn extension length in client hello\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE); + return(ST_FAIL_SETUP); + } + + if (!pState->bServer) + { + // save the negotiated protocol in the secure state so it can be queried (add 1 for nul terminator) + _SafeReadString(pSecure->strAlpnProtocol, sizeof(pSecure->strAlpnProtocol), (const char *)pData+1, _SafeRead8(pData, pDataEnd), pDataEnd); + NetPrintfVerbose((pState->iVerbose, 0, "protossl: server negotiated protocol %s using alpn extension\n", pSecure->strAlpnProtocol)); + } + else if (pState->uNumAlpnProtocols > 0) // skip parsing if the user has not chosed any protocols + { + uint32_t uProtocol; + + // loop through the protocols and pick the first supported + for (uProtocol = 0; (uProtocol < pState->uNumAlpnProtocols) && (pSecure->strAlpnProtocol[0] == '\0'); uProtocol += 1) + { + const AlpnProtocolT *pProtocol = &pState->aAlpnProtocols[uProtocol]; + uint32_t uOffset, uDataLen; + + for (uOffset = 0; uOffset < uAlpnExtensionLength; uOffset += uDataLen+1) + { + const uint8_t *pExtensionData = pData+uOffset; + uDataLen = _SafeRead8(pExtensionData, pDataEnd); + _SafeReadString(pSecure->strAlpnProtocol, sizeof(pSecure->strAlpnProtocol), (const char *)pExtensionData+1, uDataLen, pDataEnd); + + if (!strcmp(pSecure->strAlpnProtocol, pProtocol->strName)) + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: negotiated protocol %s using alpn extension\n", pSecure->strAlpnProtocol)); + break; + } + else + { + pSecure->strAlpnProtocol[0] = '\0'; + } + } + } + + if (pSecure->strAlpnProtocol[0] == '\0') + { + /* if we did not match a protocol then we do not support any + of the protocols that the client prefers; treat this as handshake failure */ + NetPrintf(("protossl: client requested protocols that are not supported by the server via alpn extension\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_NO_APPLICATION_PROTOCOL); + return(ST_FAIL_SETUP); + } + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnParseCookie + + \Description + Parse the Cookie extension from the client & server hello + + \Input *pState - module state reference + \Input *pData - the extension data we are parsing + \Input *pDataEnd - end of extension data + + \Output + int32_t - result of opertion (zero or ST_FAIL_* on error) + + \Version 11/29/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnParseCookie(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd) +{ + SecureStateT *pSecure = pState->pSecure; + + // point to cookie in receive buffer so we don't have to waste memory saving it off + pSecure->pCookie = pData; + NetPrintfVerbose((pState->iVerbose, 0, "protossl: parsed cookie of length %d\n", _SafeRead16(pData, pDataEnd))); + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnParseEllipticCurves + + \Description + Parse the elliptic curves extension from the client hello + + \Input *pState - module state reference + \Input *pData - the extension data we are parsing + \Input *pDataEnd - end of extension data + + \Output + int32_t - result of opertion (zero or ST_FAIL_* on error) + + \Version 01/19/2017 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnParseEllipticCurves(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd) +{ + int32_t iEllipticCurve, iIndex; + SecureStateT *pSecure = pState->pSecure; + uint16_t uEllipticCurveExtensionLength = _SafeRead16(pData, pDataEnd); + pData += 2; + + if (uEllipticCurveExtensionLength == 0) + { + /* if the extension has data but the list length is zero + treat this as handshake failure */ + NetPrintf(("protossl: received invalid elliptic curves extension length in client hello\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE); + return(ST_FAIL_SETUP); + } + + // pick first supported elliptic curve + for (iEllipticCurve = 0, pSecure->pEllipticCurve = NULL; (iEllipticCurve < (signed)(uEllipticCurveExtensionLength/2)) && (pSecure->pEllipticCurve == NULL); iEllipticCurve += 1, pData += 2) + { + for (iIndex = 0; iIndex < (signed)(sizeof(_SSL3_EllipticCurves)/sizeof(*_SSL3_EllipticCurves)); iIndex += 1) + { + if ((pData+2) > pDataEnd) + { + continue; + } + // skip non-matching elliptic curve + if ((pData[0] != (uint8_t)(_SSL3_EllipticCurves[iIndex].uIdent >> 8)) || (pData[1] != (uint8_t)(_SSL3_EllipticCurves[iIndex].uIdent))) + { + continue; + } + // skip disabled curves + if ((_SSL3_EllipticCurves[iIndex].uId & pState->uEnabledCurves) == 0) + { + continue; + } + // found an elliptic curve + pSecure->pEllipticCurve = &_SSL3_EllipticCurves[iIndex]; + NetPrintfVerbose((pState->iVerbose, 0, "protossl: using elliptic curve %s (ident=0x%04x)\n", pSecure->pEllipticCurve->strName, pSecure->pEllipticCurve->uIdent)); + break; + } + } + + // ecdhe key exchange is required in TLS1.3+. if we cannot find a curve that we share in common, we need to fail the handshake + if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && (pSecure->pEllipticCurve == NULL)) + { + NetPrintf(("protossl: no elliptic curve found\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE); + return(ST_FAIL_SETUP); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnParseKeyShare + + \Description + Parse the key share extension from the hello (tls1.3) + + \Input *pState - module state reference + \Input *pData - the extension data we are parsing + \Input *pDataEnd - end of extension data + + \Output + int32_t - result of opertion (zero or ST_FAIL_* on error) + + \Version 01/25/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnParseKeyShare(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd) +{ + SecureStateT *pSecure = pState->pSecure; + int32_t iKeyShare, iIndex; + uint16_t uKeyShareCurveId; + uint32_t uKeyShareLen; + uint32_t uExtnLen; + + NetPrintfVerbose((pState->iVerbose, 0, "protossl: parsing keyshare extension\n")); + + // process extension length; server only + if (pState->bServer) + { + if ((uExtnLen = _SafeRead16(pData, pDataEnd)) == 0) + { + NetPrintf(("protossl: received empty key_share\n")); + } + pDataEnd = DS_MIN(&pData[2]+uExtnLen, pDataEnd); + pData += 2; + } + + // iterate through array of key share objects + for (iKeyShare = 0, uKeyShareLen = 0, pSecure->pEllipticCurve = NULL; (pData < pDataEnd) && (pSecure->pEllipticCurve == NULL); iKeyShare += 1) + { + // get keyshare object curve id + uKeyShareCurveId = _SafeRead16(pData, pDataEnd); + pData += 2; + // get keyshare size (if key is present) + if (pData < pDataEnd) + { + uKeyShareLen = _SafeRead16(pData, pDataEnd); + // skip to keyshare data + pData += 2; + } + + // see if we have a match + for (iIndex = 0; iIndex < (signed)(sizeof(_SSL3_EllipticCurves)/sizeof(*_SSL3_EllipticCurves)); iIndex += 1) + { + // skip non-matching elliptic curve + if (uKeyShareCurveId != _SSL3_EllipticCurves[iIndex].uIdent) + { + continue; + } + // skip disabled curves + if ((_SSL3_EllipticCurves[iIndex].uId & pState->uEnabledCurves) == 0) + { + continue; + } + // found an elliptic curve + pSecure->pEllipticCurve = &_SSL3_EllipticCurves[iIndex]; + + // save key + if (uKeyShareLen > 0) + { + _SafeReadBytes(pSecure->PubKey, sizeof(pSecure->PubKey), pData, uKeyShareLen, pDataEnd); + pSecure->uPubKeyLength = uKeyShareLen; + } + + NetPrintfVerbose((pState->iVerbose, 0, "protossl: using elliptic curve %s (ident=0x%04x)\n", pSecure->pEllipticCurve->strName, pSecure->pEllipticCurve->uIdent)); + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->PubKey, pSecure->uPubKeyLength, "key_share"); + #endif + break; + } + // move to next keyshare object + pData += uKeyShareLen; + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnParsePreSharedKey + + \Description + Parse the pre_shared_key extension from the hello (tls1.3) + + \Input *pState - module state reference + \Input *pData - the extension data we are parsing + \Input *pDataEnd - end of extension data + + \Output + int32_t - result of opertion (zero or ST_FAIL_* on error) + + \Version 12/13/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnParsePreSharedKey(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd) +{ + SecureStateT *pSecure = pState->pSecure; + + NetPrintfVerbose((pState->iVerbose, 0, "protossl: parsing pre_shared_key extension\n")); + + if (pState->bServer) + { + //$$TODO add server parsing of psk + } + else + { + uint16_t uIdentity = _SafeRead16(pData, pDataEnd); + pSecure->bSessionResume = (uIdentity == 0) ? TRUE : FALSE; + NetPrintf(("protossl: psk selected identity=%d; resume %s\n", uIdentity, pSecure->bSessionResume ? "true" : "false")); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnParseRenegotiationInfo + + \Description + Parse the Renegotiation Info extension from the client & server hello + ref https://tools.ietf.org/html/rfc5746 + + \Input *pState - module state reference + \Input *pData - the extension data we are parsing + \Input *pDataEnd - end of extension data + + \Output + int32_t - result of opertion (zero or ST_FAIL_* on error) + + \Version 03/27/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnParseRenegotiationInfo(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd) +{ + SecureStateT *pSecure = pState->pSecure; + pSecure->bRenegotiationInfo = TRUE; + NetPrintfVerbose((pState->iVerbose, 0, "protossl: parsed renegotation_info\n")); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnParseSignatureAlgorithms + + \Description + Parse the signature algorithms (tls1.3 calls them signature + schemes) extension. + + \Input *pState - module state reference + \Input *pData - the extension data we are parsing + \Input *pDataEnd - end of extension data + + \Output + int32_t - result of opertion (zero or ST_FAIL_* on error) + + \Notes + This is needed when sending ServerKeyExchange messages to the client + + \Version 03/03/2017 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnParseSignatureAlgorithms(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd) +{ + SecureStateT *pSecure = pState->pSecure; + uint16_t uSigSchemeLen; + + // get sig scheme length + uSigSchemeLen = _SafeRead16(pData, pDataEnd); + pData += 2; + + // if the extension has data but the list length is zero treat this as handshake failure + if (uSigSchemeLen == 0) + { + NetPrintf(("protossl: received invalid signature algorithms length in client hello\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE); + return(ST_FAIL_SETUP); + } + + // pick first supported signature algorithm + pSecure->pSigScheme = _ProtoSSLChooseSignatureScheme(pSecure, pState->pCertificate, pData, uSigSchemeLen, pDataEnd); + + /* tls1.2 https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1) and tls1.3 (https://tools.ietf.org/html/rfc8446#section-9.2) + define actions around the presence/absence of this extension, but do not define actions in the case no suitable signature algorithms + are included. we treat this as a handshake failure based around observed behavior of other implementations e.g. openssl */ + if (pSecure->pSigScheme == NULL) + { + NetPrintf(("protossl: no acceptable signature algorithms included in signature_algorithms extension\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE); + return(ST_FAIL_SETUP); + } + + // log choice and return success + NetPrintfVerbose((pState->iVerbose, 0, "protossl: using signature scheme 0x%04x\n", pSecure->pSigScheme->uIdent)); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLHelloExtnParseSupportedVersions + + \Description + Parse the Supported Versions extension from the ClientHello + ref https://tools.ietf.org/html/rfc8446#section-4.2.1 + + \Input *pState - module state reference + \Input *pData - the extension data we are parsing + \Input *pDataEnd - end of extension data + + \Output + int32_t - result of opertion (zero or ST_FAIL_* on error) + + \Version 01/26/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLHelloExtnParseSupportedVersions(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd) +{ + SecureStateT *pSecure = pState->pSecure; + uint32_t uExtnLength = pState->bServer ? _SafeRead8(pData++, pDataEnd) : 2; + uint32_t uSslVersion; + + // pick first valid supported version + for (pDataEnd = pData + uExtnLength; pData < pDataEnd; pData += 2) + { + uSslVersion = _SafeRead16(pData, pDataEnd); + + // range check + if ((uSslVersion < pState->uSslVersionMin) || (uSslVersion > pState->uSslVersion)) + { + continue; + } + + // found a version, roll with it + pSecure->uSslClientVersion = pSecure->uSslVersion = uSslVersion; + NetPrintfVerbose((pState->iVerbose, 0, "protossl: ssl protocol version overriden by supported_versions extension to %s\n", _SSL3_strVersionNames[pSecure->uSslVersion & 0xff])); + return(0); + } + + /* if the "supported_versions" extension in the ServerHello contains a version not offered by the client or contains + a version prior to TLS 1.3, the client MUST abort the handshake with an "illegal_parameter" alert */ + if (!pState->bServer) + { + NetPrintf(("protossl: supported_versions extension did not contain a valid version\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_SETUP); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLParseHelloExtensions + + \Description + Parse the hello extensions in the client & server hello handshake packets + + \Input *pState - module state reference + \Input *pData - start of extension data (length word) + \Input *pDataEnd - end of the payload data + \Input uExtensionParse - id of extension to parse, or -1 to parse all + + \Output + int32_t - result of opertion (zero or ST_FAIL_* on error) + + \Version 08/05/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLParseHelloExtensions(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd, uint16_t uExtensionParse) +{ + SecureStateT *pSecure = pState->pSecure; + uint16_t uExtensionType, uExtensionLength; + int32_t iResult = 0; + + // read overall length of extensions, and constrain reading to extension end + uExtensionLength = _SafeRead16(pData, pDataEnd); + pDataEnd = DS_MIN(&pData[2]+uExtensionLength, pDataEnd); + pData += 2; + + // parse the complete extension section while we have not encountered an error + for (iResult = 0; (pData < pDataEnd) && (iResult == 0); pData += uExtensionLength) + { + // get extension type and length + uExtensionType = _SafeRead16(pData, pDataEnd); + uExtensionLength = _SafeRead16(pData+2, pDataEnd); + // skip header + pData += 4; + + // validate extension fits in available space + if ((pData+uExtensionLength) > pDataEnd) + { + NetPrintf(("protossl: extension length %d too big\n", uExtensionLength)); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR); + return(ST_FAIL_SETUP); + } + + // check for specific extension parsing + if ((uExtensionParse != SSL_EXTN_ALL) && (uExtensionParse != uExtensionType)) + { + continue; + } + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: parsing extension %s (%d) with length %d\n", _ProtoSSLHelloExtnGetName(uExtensionType), uExtensionType, uExtensionLength)); + + // skip extensions without data + if (uExtensionLength == 0) + { + continue; + } + + #if DEBUG_RAW_DATA + NetPrintMem(pData, uExtensionLength, "extension data"); + #endif + + // parse extensions only clients send + if (pState->bServer) + { + if (uExtensionType == SSL_EXTN_ELLIPTIC_CURVES) + { + iResult = _ProtoSSLHelloExtnParseEllipticCurves(pState, pData, pDataEnd); + } + } + + // parse ALPN extension + if (uExtensionType == SSL_EXTN_ALPN) + { + iResult = _ProtoSSLHelloExtnParseAlpn(pState, pData, pDataEnd); + } + + // the following extensions are only parsed if this is a tls1.2 or greater connection request + if (pSecure->uSslVersion < SSL3_TLS1_2) + { + continue; + } + + // parse supported_versions extension only if tls1.3 is enabled, hello version is 1.2, and we are explicitly parsing for it + if ((pState->uSslVersion >= SSL3_TLS1_3) && (pSecure->uSslVersion == SSL3_TLS1_2) && (uExtensionType == SSL_EXTN_SUPPORTED_VERSIONS) && (uExtensionParse == uExtensionType)) + { + iResult = _ProtoSSLHelloExtnParseSupportedVersions(pState, pData, pDataEnd); + } + + // parse SignatureAlgorithms + if (uExtensionType == SSL_EXTN_SIGNATURE_ALGORITHMS) + { + iResult = _ProtoSSLHelloExtnParseSignatureAlgorithms(pState, pData, pDataEnd); + } + if (uExtensionType == SSL_EXTN_RENEGOTIATION_INFO) + { + iResult = _ProtoSSLHelloExtnParseRenegotiationInfo(pState, pData, pDataEnd); + } + + // the following extensions are only parsed if this is a tls1.3 connection request + if (pSecure->uSslVersion < SSL3_TLS1_3) + { + continue; + } + + // parse cookie extension + if (uExtensionType == SSL_EXTN_COOKIE) + { + iResult = _ProtoSSLHelloExtnParseCookie(pState, pData, pDataEnd); + } + // parse KeyShare extnesion + if (uExtensionType == SSL_EXTN_KEY_SHARE) + { + iResult = _ProtoSSLHelloExtnParseKeyShare(pState, pData, pDataEnd); + } + // parse PreSharedKey extnesion + if (uExtensionType == SSL_EXTN_PRE_SHARED_KEY) + { + iResult = _ProtoSSLHelloExtnParsePreSharedKey(pState, pData, pDataEnd); + } + } + + return(iResult); +} + +/* + handshaking +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendClientHello + + \Description + Send ClientHello handshake packet; ref http://tools.ietf.org/html/rfc5246#section-7.4.1.2 + for TLS1.2 and https://tools.ietf.org/html/rfc8446#section-4.1.2 for TLS1.3 + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_SEND_HELLO, ST3_RECV_HELLO, ST_FAIL_SETUP) + + \Version 03/15/2013 (jbrookes) Added session resume support +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendClientHello(ProtoSSLRefT *pState) +{ + int32_t iCipher, iNumCiphers, iBodyLen; + uint8_t strHead[4], strBody[2048]; + uint8_t *pData = strBody, *pCiphSize; + SecureStateT *pSecure = pState->pSecure; + SessionHistoryT SessHist; + uint32_t uHelloExtn = pState->uHelloExtn, uEnabledCiphers = pState->uEnabledCiphers; + + // if we haven't picked an elliptic curve, pick our default + if ((pSecure->pEllipticCurve == NULL) && (pState->iCurveDflt != -1)) + { + pSecure->pEllipticCurve = _ProtoSSLChooseCurve(pState->uEnabledCurves, pState->iCurveDflt); + } + + // for tls1.3 start ecdhe key generation here so we can spread it out + if ((pState->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && (pSecure->pEllipticCurve != NULL)) + { + const CryptCurveDhT *pEcc; + uint32_t uCryptUsecs; + + // initialize elliptic curve context if not already initialized + if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) == NULL) + { + /* if we cannot find the dh functions, there is some configuration mishap or the server is faulty. + let's fail early here so we can debug the issue instead of a null pointer exception */ + return(ST_FAIL_SETUP); + } + // generate the public key + if (pEcc->Public(pSecure->EccContext, NULL, &uCryptUsecs) > 0) + { + return(ST3_SEND_HELLO); + } + pSecure->uPubKeyLength = pEcc->PointFinal(pSecure->EccContext, NULL, FALSE, pSecure->PubKey, sizeof(pSecure->PubKey)); + pSecure->bEccKeyGenerated = TRUE; + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate public key for server key message) %dms\n", + uCryptUsecs/1000)); + pSecure->uTimer += uCryptUsecs/1000; + } + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send ClientHello\n")); + + // initialize the ssl performance timer + pSecure->uTimer = 0; + + // reset client cert level (server will let us know if we need to send one) + pState->iClientCertLevel = 0; + + // set desired ssl version + pSecure->uSslVersion = pState->uSslVersion; + // remember requested version; this is later used to detect a version rollback attack + pSecure->uSslClientVersion = pState->uSslVersion; + + // set TLS1.3 specific options + if (pSecure->uSslClientVersion >= PROTOSSL_VERSION_TLS1_3) + { + // freeze hello version at 1.2 as per https://tools.ietf.org/html/rfc8446#section-4.1.2 + pSecure->uSslClientVersion = PROTOSSL_VERSION_TLS1_2; + // add required elliptic_curves (which TLS1.3 calls supported_groups) + uHelloExtn |= PROTOSSL_HELLOEXTN_ELLIPTIC_CURVES; + // if no tls1.3 ciphers are enabled, do that now + if ((pState->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && !(uEnabledCiphers & PROTOSSL_CIPHER_ALL_13)) + { + uEnabledCiphers |= PROTOSSL_CIPHER_ALL_13; + } + } + *pData++ = (uint8_t)(pSecure->uSslClientVersion>>8); + *pData++ = (uint8_t)(pSecure->uSslClientVersion>>0); + + // set random data + if (pState->uSslVersion < PROTOSSL_VERSION_TLS1_3) + { + // TLS1.2 and prior specify first four bytes of client random are utc time + uint32_t uUtcTime = (uint32_t)ds_timeinsecs(); + pSecure->ClientRandom[0] = (uint8_t)(uUtcTime >> 24); + pSecure->ClientRandom[1] = (uint8_t)(uUtcTime >> 16); + pSecure->ClientRandom[2] = (uint8_t)(uUtcTime >> 8); + pSecure->ClientRandom[3] = (uint8_t)(uUtcTime >> 0); + CryptRandGet(&pSecure->ClientRandom[4], sizeof(pSecure->ClientRandom) - 4); + } + else + { + // TLS1.3 has 32 bytes of random data + CryptRandGet(pSecure->ClientRandom, sizeof(pSecure->ClientRandom)); + } + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->ClientRandom, sizeof(pSecure->ClientRandom), "ClientRandom"); + #endif + // set client random in packet + ds_memcpy(pData, pSecure->ClientRandom, sizeof(pSecure->ClientRandom)); + pData += 32; + + // if we have a previous session for this peer and resume is enabled, set it + if ((_SessionHistoryGetInfo(&SessHist, pState->strHost, SockaddrInGetPort(&pState->PeerAddr), NULL) != NULL) && pState->bSessionResumeEnabled) + { + SessionInfoT *pSessInfo = &SessHist.SessionInfo; + NetPrintfVerbose((DEBUG_RES_SESS, 0, "protossl: setting session id for resume\n")); + #if DEBUG_RES_SESS && DEBUG_RAW_DATA + NetPrintMem(pSessInfo->SessionId, sizeof(pSessInfo->SessionId), "ClientHello session id"); + #endif + /* save session id and master secret; the sessionid will be compared when we + receive the serverhello to determine if we are in the resume flow or not */ + ds_memcpy(pSecure->SessionId, pSessInfo->SessionId, sizeof(pSecure->SessionId)); + ds_memcpy(pSecure->MasterKey, pSessInfo->MasterSecret, sizeof(pSecure->MasterKey)); + pSecure->bSentSessionId = TRUE; + } + else if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + /* as per https://tools.ietf.org/html/rfc8446#section-4.1.2 a tls 1.3 client MUST always send + a non-empty sessionID in support of middlebox compatibility mode */ + if (!pSecure->bSentSessionId) + { + /* generate a new session id if we haven't sent one already; otherwise, in an HRR flow we + need to resend the same sessionid as we did the first time */ + CryptRandGet(pSecure->SessionId, sizeof(pSecure->SessionId)); + pSecure->bSentSessionId = TRUE; + } + } + else + { + pSecure->bSentSessionId = FALSE; + } + + // add sessionid to ClientHello + if (pSecure->bSentSessionId) + { + *pData++ = (uint8_t)sizeof(pSecure->SessionId); + ds_memcpy(pData, pSecure->SessionId, sizeof(pSecure->SessionId)); + pData += sizeof(pSecure->SessionId); + } + else // add in empty session identifier + { + *pData++ = 0; + } + + // add the cipher suite list + *pData++ = 0; + pCiphSize = pData++; // save and skip cipher size location + for (iCipher = 0, iNumCiphers = 0, pSecure->uSentCiphers = 0; iCipher < (signed)(sizeof(_SSL3_CipherSuite)/sizeof(_SSL3_CipherSuite[0])); iCipher += 1) + { + // skip ciphers that require a newer version of SSL than we are offering + if (_SSL3_CipherSuite[iCipher].uMinVers > pState->uSslVersion) + { + continue; + } + // skip disabled ciphers + if ((_SSL3_CipherSuite[iCipher].uId & uEnabledCiphers) == 0) + { + continue; + } + // add cipher to list + *pData++ = (uint8_t)(_SSL3_CipherSuite[iCipher].uIdent>>8); + *pData++ = (uint8_t)(_SSL3_CipherSuite[iCipher].uIdent>>0); + iNumCiphers += 1; + // remember we sent it + pSecure->uSentCiphers |= _SSL3_CipherSuite[iCipher].uId; + // add the elliptic curve extension if we are using any ecdhe ciphers + if (_SSL3_CipherSuite[iCipher].uKey == SSL3_KEY_ECDHE) + { + uHelloExtn |= PROTOSSL_HELLOEXTN_ELLIPTIC_CURVES; + } + } + // make sure we selected at least one cipher + if (iNumCiphers == 0) + { + NetPrintf(("protossl: no ciphers selected in ClientHello\n")); + return(ST_FAIL_SETUP); + } + // write cipher suite list size + *pCiphSize = iNumCiphers*2; + + // add compression_methods list with only null compression (we don't support compression) + *pData++ = 1; + *pData++ = 0; + + // add extensions + if (uHelloExtn != 0) + { + pData = _ProtoSSLAddHelloExtensions(pState, pData, (uint32_t)sizeof(strBody)-(uint32_t)(pData-strBody), strBody, uHelloExtn); + } + + // setup the header + iBodyLen = pData-strBody; + strHead[0] = SSL3_MSG_CLIENT_HELLO; + strHead[1] = 0; + strHead[2] = (uint8_t)(iBodyLen>>8); + strHead[3] = (uint8_t)(iBodyLen>>0); + + // if in hrr flow, make sure we're not sending the same clienthello we sent last time + if (pSecure->uSslVersion >= SSL3_TLS1_3) + { + uint8_t aClientHelloHash[sizeof(pSecure->aClientHelloHash)]; + // hash clienthello for possible hrr compare + _ProtoSSLGenerateFingerprint(aClientHelloHash, sizeof(aClientHelloHash), strBody, iBodyLen, CRYPTHASH_SHA256); + // if in hrr flow, compare + if ((pSecure->bHelloRetry) && (!memcmp(aClientHelloHash, pSecure->aClientHelloHash, sizeof(aClientHelloHash)))) + { + /* as per https://tools.ietf.org/html/rfc8446#section-4.1.4 sending the same clienthello twice + must generate an illegal_parameter alert */ + NetPrintf(("protossl: sending the same ClientHello twice\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_SETUP); + + } + // copy hello hash to state for possible future compare + ds_memcpy_s(pSecure->aClientHelloHash, sizeof(pSecure->aClientHelloHash), aClientHelloHash, sizeof(aClientHelloHash)); + } + + // set starting handshake message + pSecure->iCurMsg = SSL3_MSG_CLIENT_HELLO; + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, strBody, iBodyLen); + return(ST3_RECV_HELLO); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvClientHello + + \Description + Process ClientHello handshake packet + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_SEND_HELLO, ST3_SEND_HELLO_RETRY, or ST_FAIL_* on error) + + \Version 10/15/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvClientHello(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + SecureStateT *pSecure = pState->pSecure; + int32_t iNumCiphers, iParseResult; + const uint8_t *pDataEnd = pData + iDataSize; + const uint8_t *pCipherList; + SessionHistoryT SessHist; + uint32_t uEnabledCiphers = pState->uEnabledCiphers, uPreferredCipher=0, uSessionSize; + uint8_t aSessionId[SSL_SESSID_SIZE], uCompression, uCompressionMethods; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process ClientHello\n")); + + // get protocol version client wants from us; make sure it is at most TLS1.2, as that is the highest allowed version for this field + if ((pSecure->uSslClientVersion = pSecure->uSslVersion = _SafeRead16(pData, pDataEnd)) > PROTOSSL_VERSION_TLS1_2) + { + NetPrintf(("protossl: invalid ssl version %d in client hello\n", pSecure->uSslClientVersion)); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_CONN); + } + pData += 2; + + // save client random data + _SafeReadBytes(pSecure->ClientRandom, sizeof(pSecure->ClientRandom), pData, sizeof(pSecure->ClientRandom), pDataEnd); + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->ClientRandom, sizeof(pSecure->ClientRandom), "ClientRandom"); + #endif + pData += 32; + + // check for possible session resume + uSessionSize = _SafeRead8(pData++, pDataEnd); + _SafeReadBytes(aSessionId, sizeof(aSessionId), pData, SSL_SESSID_SIZE, pDataEnd); + pData += uSessionSize; + + // read cipher suite list size and save cipher list pointer (we will parse it after extensions, if present) + iNumCiphers = _SafeRead16(pData, pDataEnd) / 2; + pData += 2; + pCipherList = pData; + // skip cipher list + pData += iNumCiphers*2; + + // save and skip compression_methods + uCompressionMethods = _SafeRead8(pData++, pDataEnd); + // read first method; allows us to later check for one null method if we have a tls1.3 connection + uCompression = _SafeRead8(pData, pDataEnd); + pData += uCompressionMethods; + + // parse hello extensions for supported_versions first; this allows extension parsers to know if the connection will be tls1.3 or not + if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_SUPPORTED_VERSIONS)) != 0) + { + return(iParseResult); + } + + // pick protocol version; this must be done after possible supported_versions extension parsing + if (pSecure->uSslVersion > pState->uSslVersion) + { + NetPrintfVerbose((pState->iVerbose, 1, "protossl: client requested SSL version %d.%d, downgrading to our max supported version\n", pData[0], pData[1])); + pSecure->uSslVersion = pState->uSslVersion; + } + else if (pSecure->uSslVersion < pState->uSslVersionMin) + { + NetPrintf(("protossl: client requested SSL version %d.%d is not supported\n", pData[0], pData[1])); + /* As per http://tools.ietf.org/html/rfc5246#appendix-E.1, if server supports (or is willing to use) + only versions greater than client_version, it MUST send a "protocol_version" alert message and + close the connection. */ + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_PROTOCOL_VERSION); + return(ST_FAIL_CONN_MINVERS); + } + NetPrintfVerbose((pState->iVerbose, 0, "protossl: using %s\n", _SSL3_strVersionNames[pSecure->uSslVersion&0xff])); + + // parse all other extensions + if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_ALL)) != 0) + { + return(iParseResult); + } + + // check for session resume info + if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) + { + if (pState->bSessionResumeEnabled) + { + // get info for possible resume + if ((uSessionSize == sizeof(pSecure->SessionId)) && _SessionHistoryGetInfo(&SessHist, NULL, 0, aSessionId)) + { + // flag that we got the info + pSecure->bSessionResume = TRUE; + // save cipher preference + uPreferredCipher = SessHist.SessionInfo.uCipherId; + } + } + } + else + { + // save session info for later echo as per https://tools.ietf.org/html/rfc8446#section-4.1.3 + if (uSessionSize == sizeof(pSecure->SessionId)) + { + ds_memcpy(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId)); + pSecure->bSessionResume = TRUE; + } + else + { + pSecure->bSessionResume = FALSE; + } + // if we are in the HelloRetryRequest flow, set the preferred cipher to the cipher we previously selected + if (pSecure->bHelloRetry) + { + uPreferredCipher = pSecure->uRetryCipher; + } + } + + // choose a cipher from cipher list, with optional cipher preference + if ((pSecure->pCipher = _ProtoSSLChooseCipher(pSecure, pState->pCertificate, pCipherList, iNumCiphers, pDataEnd, uEnabledCiphers, uPreferredCipher)) == NULL) + { + NetPrintf(("protossl: no matching cipher\n")); + /* As per http://tools.ietf.org/html/rfc5246#section-7.4.1.3, a client providing no + ciphers we support results in a handshake failure alert */ + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE); + return(ST_FAIL_CONN_NOCIPHER); + } + else + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: using cipher suite %s (ident=%d,%d)\n", pSecure->pCipher->strName, pCipherList[0], pCipherList[1])); + } + + // make sure legacy_compression field includes only null compression if tls 1.3 + if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && ((uCompressionMethods != 1) || (uCompression != 0))) + { + /* as per https://tools.ietf.org/html/rfc8446#section-4.1.2 compression must be disabled; "if a TLS 1.3 ClientHello is + received with any other value in this field, the server MUST abort the handshake with an "illegal_parameter" alert */ + NetPrintf(("protossl: legacy_compression_methods not null for tls 1.3 connection\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_CONN); + } + + // now that we've selected the cipher, check for possible resume + if ((pSecure->uSslVersion < SSL3_TLS1_3) && (pSecure->bSessionResume)) + { + SessionInfoT *pSessInfo = &SessHist.SessionInfo; + if ((pSessInfo->uSslVersion == pSecure->uSslVersion) && (pSessInfo->uCipherId == pSecure->pCipher->uIdent)) + { + ds_memcpy(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId)); + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->SessionId, sizeof(pSecure->SessionId), "ClientHello session id"); + #endif + NetPrintfVerbose((pState->iVerbose, 0, "protossl: resuming previous session\n")); + // copy session id and master secret + ds_memcpy(pSecure->MasterKey, pSessInfo->MasterSecret, sizeof(pSecure->MasterKey)); + ds_memcpy(pSecure->SessionId, pSessInfo->SessionId, sizeof(pSecure->SessionId)); + } + else + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: no session resume due to cipher or version mismatch\n")); + pSecure->bSessionResume = FALSE; + } + } + + // if no session resume, generate a new sessionid for possible future reuse + if ((pSecure->uSslVersion < SSL3_TLS1_3) && !pSecure->bSessionResume) + { + CryptRandGet(pSecure->SessionId, sizeof(pSecure->SessionId)); + } + + // make sure we found a key share we support + if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && ((pSecure->pEllipticCurve == NULL) || (pSecure->uPubKeyLength == 0))) + { + // pick default key share + pSecure->pEllipticCurve = _ProtoSSLChooseCurve(pState->uEnabledCurves, pState->iCurveDflt); + return(ST3_SEND_HELLO_RETRY); + } + + // if not tls1.3 and we got a key_share from a 1.3 client, zero it here so we don't think it's a valid key later in the handshake flow + if ((pSecure->uSslVersion < SSL3_TLS1_3) && (pSecure->uPubKeyLength != 0)) + { + pSecure->uPubKeyLength = 0; + } + + return(ST3_SEND_HELLO); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendHelloRetryRequest + + \Description + Send Hello Retry Request (TLS 1.3+) as defined by + https://tools.ietf.org/html/rfc8446#section-4.1.4 + + \Input *pState - module state reference + + \Output + int32_t - ST3_RECV_HELLO + + \Version 08/08/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendHelloRetryRequest(ProtoSSLRefT *pState) +{ + uint8_t strBody[512], *pData = strBody; + uint8_t strHead[4]; + uint16_t uSslVersion; + SecureStateT *pSecure = pState->pSecure; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send HelloRetryRequest\n")); + + // remember we're in HRR flow + pSecure->bHelloRetry = TRUE; + + // inject synthetic message hash (we only need to do tls1.3 hashes here) + _ProtoSSLHandshakeHashInject(pSecure, CRYPTHASH_SHA256); + _ProtoSSLHandshakeHashInject(pSecure, CRYPTHASH_SHA384); + + // add fixed tls1.2 version to message body + uSslVersion = SSL3_TLS1_2; + *pData++ = (uint8_t)(uSslVersion>>8); + *pData++ = (uint8_t)(uSslVersion>>0); + + // add special random value to indicate this is HelloRetryRequest, not a ServerHello + ds_memcpy(pData, _SSL3_HelloRetryRequestRandom, sizeof(_SSL3_HelloRetryRequestRandom)); + pData += 32; + // echo client's sessionid + if (pSecure->bSessionResume) + { + *pData++ = sizeof(pSecure->SessionId); + ds_memcpy(pData, pSecure->SessionId, sizeof(pSecure->SessionId)); + pData += sizeof(pSecure->SessionId); + } + else + { + *pData++ = 0; + } + + // the cipher we send here MUST be chosen in the subsequent ServerHello + pSecure->uRetryCipher = pSecure->pCipher->uIdent; + *pData++ = (uint8_t)(pSecure->uRetryCipher>>8); + *pData++ = (uint8_t)(pSecure->uRetryCipher>>0); + + // set legacy_compression_method + *pData++ = 0; + + // add keyshare extension + pData = _ProtoSSLAddHelloExtensions(pState, pData, (uint32_t)(sizeof(strBody)-(pData-strBody)), strBody, 0); + + // setup the header + strHead[0] = SSL3_MSG_SERVER_HELLO; + strHead[1] = 0; + strHead[2] = (uint8_t)((pData-strBody)>>8); + strHead[3] = (uint8_t)((pData-strBody)>>0); + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, strBody, pData-strBody); + + // return next state + return(ST3_RECV_HELLO); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvHelloRetryRequest + + \Description + Process Hello Retry Request from server (TLS1.3+) as per + https://tools.ietf.org/html/rfc8446#section-4.1.4 + + \Input *pState - module state reference + \Input *pData - pointer to message data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST_SEND_HELLO, or ST_FAIL_* on error) + + \Notes + The HelloRetryRequest masquerades as a ServerHello, but with a specific + ServerRandom value. This function expects to be given data immediately + following the ServerRandom. + + \Version 08/08/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvHelloRetryRequest(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + SecureStateT *pSecure = pState->pSecure; + int32_t iParseResult; + const uint8_t *pDataEnd = pData + iDataSize; + const CipherSuiteT *pCipher; + uint8_t aSessionId[SSL_SESSID_SIZE]; + uint32_t uSessionLen; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process HelloRetryRequest\n")); + + // validate legacy_version, which must be TLS1.2 + if (pSecure->uSslVersion != PROTOSSL_VERSION_TLS1_2) + { + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_SETUP); + } + + // as per https://tools.ietf.org/html/rfc8446#section-4.1.4, a second HRR results in a fatal error + if (pSecure->bHelloRetry) + { + NetPrintf(("protossl: received HRR after already receiving an HRR\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNEXPECTED_MESSAGE); + return(ST_FAIL_SETUP); + } + + // inject synthetic message hash (we only need to do tls1.3 hashes here) + _ProtoSSLHandshakeHashInject(pState->pSecure, CRYPTHASH_SHA256); + _ProtoSSLHandshakeHashInject(pState->pSecure, CRYPTHASH_SHA384); + + // read legacy session id + if ((uSessionLen = _SafeRead8(pData, pDataEnd)) == SSL_SESSID_SIZE) + { + _SafeReadBytes(aSessionId, sizeof(aSessionId), pData+1, SSL_SESSID_SIZE, pDataEnd); + } + pData += uSessionLen+1; + + /* as per https://tools.ietf.org/html/rfc8446#section-4.1.3, a client receiving a legacy_session_id field that + does not match what it sent in the ClientHello MUST abort the handshake with an "illegal_parameter" alert */ + if (pSecure->bSentSessionId ? memcmp(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId)) : uSessionLen != 0) + { + NetPrintf(("protossl: received mismatched sessionid\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_SETUP); + } + + // as per https://tools.ietf.org/html/rfc8446#section-4.1.4, a client receiving a cipher suite that was not offered MUST abort the handshake + if ((pCipher = _ProtoSSLGetCipher(pSecure, _SafeRead16(pData, pDataEnd))) == NULL) + { + NetPrintf(("protossl: received cipher in HelloRetryRequest that we didn't send (ident=%d,%d)\n", pData[0], pData[1])); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_SETUP); + } + // save cipher for later verification when we receive the ServerHello + pSecure->uRetryCipher = pCipher->uIdent; + pData += 2; + + // skip legacy_compression_method, make sure it's zero + if (_SafeRead8(pData++, pDataEnd)) + { + /* as per https://tools.ietf.org/html/rfc8446#section-4.1.2 compression must be disabled; "if a TLS 1.3 ClientHello is + received with any other value in this field, the server MUST abort the handshake with an "illegal_parameter" alert */ + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_CONN); + } + + // parse hello extensions for supported_versions first; this allows extension parsers to know if the connection will be tls1.3 or not + if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_SUPPORTED_VERSIONS)) != 0) + { + return(iParseResult); + } + // parse the rest of the extensions + if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_ALL)) != 0) + { + return(iParseResult); + } + // remember we're in the HelloRetryRequest flow + pSecure->bHelloRetry = TRUE; + /* if we hit HRR, we assume that we need to make a change to our curve. let's throw + away the context and generated key */ + pSecure->bEccContextInitialized = FALSE; + pSecure->bEccKeyGenerated = FALSE; + + // retry ClientHello + return(ST3_SEND_HELLO); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendServerHello + + \Description + Send ServerHello handshake packet + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_SEND_HELLO, ST3_SEND_EXTN, ST3_SEND_CHANGE, + ST3_SEND_CERT, or ST_FAIL_* on error) + + \Version 10/15/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendServerHello(ProtoSSLRefT *pState) +{ + uint8_t strHead[4]; + uint8_t strBody[512]; + uint8_t *pData = strBody; + SecureStateT *pSecure = pState->pSecure; + uint32_t uSslVersion; + int32_t iNextState; + + // if tls1.3, generate public key (tls1.2 and prior do this in SendServerKeyExchange) + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + const CryptCurveDhT *pEcc; + uint32_t uCryptUsecs; + + // initialize elliptic curve context if not already initialized + if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) == NULL) + { + /* if we cannot find the dh functions, there is some configuration mishap or the server is faulty. + let's fail early here so we can debug the issue instead of a null pointer exception */ + return(ST_FAIL_SETUP); + } + // generate the public key; public key is extracted and sent to peer in the key_share extension + if (pEcc->Public(pSecure->EccContext, NULL, &uCryptUsecs) > 0) + { + return(ST3_SEND_HELLO); + } + pSecure->bEccKeyGenerated = TRUE; + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate public key for server key message) %dms\n", + uCryptUsecs/1000)); + pSecure->uTimer += uCryptUsecs/1000; + } + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send ServerHello\n")); + + // tls1.3 fixes hello version number at 1.2 + uSslVersion = (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) ? pSecure->uSslVersion : SSL3_TLS1_2; + + // set negotiated protocol version + *pData++ = (uint8_t)(uSslVersion>>8); + *pData++ = (uint8_t)(uSslVersion>>0); + + // generate and set server random data + CryptRandGet(pSecure->ServerRandom, sizeof(pSecure->ServerRandom)); + // set random downgrade bytes as appropriate, as per https://tools.ietf.org/html/rfc8446#section-4.1.3 + if ((pSecure->uSslVersion == SSL3_TLS1_2) && (pState->uSslVersion >= SSL3_TLS1_3)) + { + ds_memcpy(&pSecure->ServerRandom[24], _SSL3_ServerRandomDowngrade12, sizeof(_SSL3_ServerRandomDowngrade12)); + } + else if ((pSecure->uSslVersion < SSL3_TLS1_2) && (pState->uSslVersion >= SSL3_TLS1_2)) + { + ds_memcpy(&pSecure->ServerRandom[24], _SSL3_ServerRandomDowngrade11, sizeof(_SSL3_ServerRandomDowngrade11)); + } + ds_memcpy(pData, pSecure->ServerRandom, sizeof(pSecure->ServerRandom)); + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->ServerRandom, sizeof(pSecure->ServerRandom), "ServerRandom"); + #endif + pData += 32; + + // add sessionid to ServerHello (TLS1.2 and prior) / echo client's legacy sessionid (TLS1.3) + if (((pSecure->uSslVersion < SSL3_TLS1_3) && pState->bSessionResumeEnabled) || (pSecure->bSessionResume)) + { + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->SessionId, sizeof(pSecure->SessionId), "SessionId"); + #endif + *pData++ = sizeof(pSecure->SessionId); + ds_memcpy(pData, pSecure->SessionId, sizeof(pSecure->SessionId)); + pData += sizeof(pSecure->SessionId); + } + else + { + *pData++ = 0; + } + + // add the selected cipher suite + *pData++ = (uint8_t)(pSecure->pCipher->uIdent>>8); + *pData++ = (uint8_t)(pSecure->pCipher->uIdent>>0); + + // add null compression_method + *pData++ = 0; + + // add extensions; note that for TLS1.3 we only add extensions required to establish secure comms; the rest are sent in EncryptedExtensions + pData = _ProtoSSLAddHelloExtensions(pState, pData, (uint32_t)sizeof(strBody)-(uint32_t)(pData-strBody), strBody, (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) ? pState->uHelloExtn : 0); + + // setup the header + strHead[0] = SSL3_MSG_SERVER_HELLO; + strHead[1] = 0; + strHead[2] = (uint8_t)((pData-strBody)>>8); + strHead[3] = (uint8_t)((pData-strBody)>>0); + + // send the packet + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, strBody, (int32_t)(pData-strBody)); + + // determine next state + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + // send encrypted extensions + iNextState = ST3_SEND_EXTN; + } + else + { + iNextState = pSecure->bSessionResume ? ST3_SEND_CHANGE : ST3_SEND_CERT; + } + return(iNextState); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvServerHello + + \Description + Process ServerHello handshake packet + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_RECV_HELLO, ST3_RECV_CHANGE, ST3_PROC_ASYNC, + or ST_FAIL_* on error) + + \Notes + Also handles initial parsing of tls1.3 HelloRetryRequest; if an HRR is + identified, processing is handed off to the HRR-specific handler. + + \Version 03/15/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvServerHello(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + int32_t iParseResult, iState = ST3_RECV_HELLO; + const uint8_t *pDataEnd, *pSessionId=NULL, *pCipher; + SecureStateT *pSecure = pState->pSecure; + const uint8_t *pDataStart = pData; + uint8_t aSessionId[SSL_SESSID_SIZE], uSessionLen; + + /* get the location of server hello end; some servers will not send any extension length + so we need to make sure we don't parse past the end of the packet server hello */ + pDataEnd = pData + iDataSize; + + // get server-specified version of the protocol + pSecure->uSslVersion = _SafeRead16(pData, pDataEnd); + pData += 2; + + // save server random data + _SafeReadBytes(pSecure->ServerRandom, sizeof(pSecure->ServerRandom), pData, sizeof(pSecure->ServerRandom), pDataEnd); + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->ServerRandom, sizeof(pSecure->ServerRandom), "ServerRandom"); + #endif + pData += 32; + + /* as per https://tools.ietf.org/html/rfc8446#section-4.1.3, check for special random value, + indicating this is a HelloRetryRequest and not a ServerHello */ + if (!memcmp(pSecure->ServerRandom, _SSL3_HelloRetryRequestRandom, sizeof(_SSL3_HelloRetryRequestRandom))) + { + return(_ProtoSSLRecvHelloRetryRequest(pState, pData, iDataSize - (pData-pDataStart))); + } + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process Server Hello\n")); + + // read sessionid length + uSessionLen = _SafeRead8(pData++, pDataEnd); + // save pointer to and skip sessionid + pSessionId = pData; + pData += uSessionLen; + + // save cipher offset + pCipher = pData; + pData += 2; + + // validate and skip compression + if (_SafeRead8(pData++, pDataEnd) != 0) + { + /* as per https://tools.ietf.org/html/rfc8446#section-4.1.2 compression must be disabled; "if a TLS 1.3 ClientHello is received + with any other value in this field, the server MUST abort the handshake with an "illegal_parameter" alert. unlike the server + flow, the client flow enforces this restriction unconditionally, as our client does not support compression */ + NetPrintf(("protossl: compression_methods not zero\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_CONN); + } + + // parse hello extensions for supported_versions first; this allows extension parsers to know if the connection will be tls1.3 or not + if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_SUPPORTED_VERSIONS)) != 0) + { + return(iParseResult); + } + + // make sure we support version; this must be done after possible supported_versions extension parsing + if (pSecure->uSslVersion != pState->uSslVersion) + { + if ((pSecure->uSslVersion < pState->uSslVersionMin) || (pSecure->uSslVersion > pState->uSslVersion)) + { + NetPrintf(("protossl: server specified SSL version 0x%04x is not supported\n", pSecure->uSslVersion)); + /* As per http://tools.ietf.org/html/rfc5246#appendix-E.1 - If the version chosen by + the server is not supported by the client (or not acceptable), the client MUST send a + "protocol_version" alert message and close the connection. */ + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_PROTOCOL_VERSION); + return((pSecure->uSslVersion < pState->uSslVersionMin) ? ST_FAIL_CONN_MINVERS : ST_FAIL_CONN_MAXVERS); + } + else + { + NetPrintfVerbose((pState->iVerbose, 1, "protossl: downgrading SSL version\n")); + } + } + NetPrintfVerbose((pState->iVerbose, 0, "protossl: using %s to connect to %s\n", _SSL3_strVersionNames[pSecure->uSslVersion&0xff], pState->strHost)); + + // parse all other extensions + if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_ALL)) != 0) + { + return(iParseResult); + } + + // protect against possible downgrade attack as per https://tools.ietf.org/html/rfc8446#section-4.1.3 + if ((pSecure->uSslVersion == SSL3_TLS1_2) && (pState->uSslVersion >= SSL3_TLS1_3) && !memcmp(&pSecure->ServerRandom[24], _SSL3_ServerRandomDowngrade12, sizeof(_SSL3_ServerRandomDowngrade12))) + { + NetPrintf(("protossl: invalid tls1.3 downgrade detected\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_SETUP); + } + else if ((pSecure->uSslVersion < SSL3_TLS1_2) && (pState->uSslVersion >= SSL3_TLS1_2) && !memcmp(&pSecure->ServerRandom[24], _SSL3_ServerRandomDowngrade11, sizeof(_SSL3_ServerRandomDowngrade11))) + { + NetPrintf(("protossl: invalid tls1.2 downgrade detected\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_SETUP); + } + + // get and validate cipher + if ((pSecure->pCipher = _ProtoSSLGetCipher(pSecure, _SafeRead16(pCipher, pDataEnd))) == NULL) + { + NetPrintf(("protossl: no matching cipher (ident=%d,%d)\n", pCipher[0], pCipher[1])); + /* as per https://tools.ietf.org/html/rfc8446#section-4.1.3: a client receiving a cipher suite that was not offered + MUST abort the handshake with an "illegal_parameter" alert */ + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_CONN_NOCIPHER); + } + else + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: using cipher suite %s (ident=%d,%d)\n", pSecure->pCipher->strName, pCipher[0], pCipher[1])); + } + + // if in retry flow, make sure we got the expected cipher as per https://tools.ietf.org/html/rfc8446#section-4.1.4 + if (pSecure->bHelloRetry && (pSecure->pCipher->uIdent != pSecure->uRetryCipher)) + { + NetPrintf(("protossl: cipher specified in ServerHello does not match the cipher from HelloRetryRequest\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_SETUP); + } + + // read session info + if (uSessionLen == SSL_SESSID_SIZE) + { + _SafeReadBytes(aSessionId, sizeof(aSessionId), pSessionId, SSL_SESSID_SIZE, pDataEnd); + #if DEBUG_RAW_DATA + NetPrintMem(aSessionId, sizeof(aSessionId), "ServerHello session id"); + #endif + } + + // process session id (tls1.2 and previous only) + if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) + { + // save server session id + if (uSessionLen == sizeof(pSecure->SessionId)) + { + // check for ClientHello/ServerHello session match; a match indicates we are resuming the session and bypassing key exchange + if (!memcmp(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId))) + { + // set resume and update state + NetPrintfVerbose((pState->iVerbose, 0, "protossl: resuming previous session\n")); + pSecure->bSessionResume = TRUE; + iState = ST3_RECV_CHANGE; + } + else + { + // save session id for possible future resumption + ds_memcpy(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId)); + } + } + + // clear premaster secret if not resuming + if (!pSecure->bSessionResume) + { + ds_memclr(pSecure->MasterKey, sizeof(pSecure->MasterKey)); + } + } + else if (pSecure->bSentSessionId ? memcmp(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId)) : uSessionLen != 0) + { + /* as per https://tools.ietf.org/html/rfc8446#section-4.1.3, a client receiving a legacy_session_id field that + does not match what it sent in the ClientHello MUST abort the handshake with an "illegal_parameter" alert */ + NetPrintf(("protossl: received mismatched sessionid\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_SETUP); + } + + // tls1.3+ data is immediately encrypted following ServerHello and keyshare/psk + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + // make sure the server gave us a keyshare we support + if (pSecure->pEllipticCurve == NULL) + { + NetPrintf(("protossl: no supported elliptic curve given in tls1.3 server hello\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER); + return(ST_FAIL_SETUP); + } + // update the handshake hash with ServerHello, which hasn't been processed yet + //$$todo - is this actually required? + _ProtoSSLRecvHandshakeFinish(pState); + // set up async execution to build secrets and key material + iState = _ProtoSSLUpdateSetAsyncState(pState, _ProtoSSLBuildHandshakeKey, iState, ST_FAIL_SETUP, SSL3_ALERT_DESC_INTERNAL_ERROR); + } + + // return new state + return(iState); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendEncryptedExtensions + + \Description + Send EncryptedExtensions handshake message (TLS1.3+ only) + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_SEND_EXTN, ST3_SEND_CERT_REQ or ST3_SEND_CERT) + + \Version 01/26/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendEncryptedExtensions(ProtoSSLRefT *pState) +{ + uint8_t strBody[512], strHead[4], *pData = strBody+2; + uint32_t uExtnLen; + + // data after ServerHello is encrypted + if (_ProtoSSLBuildHandshakeKey(pState) > 0) + { + NetPrintfVerbose((pState->iVerbose, 1, "protossl: generating handshake key\n")); + return(ST3_SEND_EXTN); + } + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send EncryptedExtensions\n")); + + // add extensions to message body + pData = _ProtoSSLAddHelloExtensions(pState, pData, (uint32_t)sizeof(strBody)-2, strBody, pState->uHelloExtn); + uExtnLen = pData - strBody - 2; + // set extn length + strBody[0] = (uint8_t)(uExtnLen>>8); + strBody[1] = (uint8_t)(uExtnLen>>0); + + // setup the header + strHead[0] = SSL3_MSG_ENCRYPTED_EXTENSIONS; + strHead[1] = 0; + strHead[2] = (uint8_t)((uExtnLen+2)>>8); + strHead[3] = (uint8_t)((uExtnLen+2)>>0); + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, strBody, uExtnLen+2); + + // return next state + return((pState->iClientCertLevel > 0) ? ST3_SEND_CERT_REQ : ST3_SEND_CERT); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvEncryptedExtensions + + \Description + Process EncryptedExtensions handshake packet (TLS1.3+ only) + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_RECV_FINISH, ST3_RECV_HELLO, or ST_FAIL_* on error) + + \Version 01/26/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvEncryptedExtensions(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + SecureStateT *pSecure = pState->pSecure; + int32_t iParseResult, iNextState = (pSecure->bSessionResume) ? ST3_RECV_FINISH : ST3_RECV_HELLO; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process EncryptedExtensions\n")); + + // parse the extensions + iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pData+iDataSize, SSL_EXTN_ALL); + + // move to next state + return((iParseResult == 0) ? iNextState : iParseResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLUpdateSendCertificate + + \Description + Send Certificate handshake packet + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_SEND_KEY, ST3_SEND_VERIFY, ST3_SEND_CERT_REQ, + or ST3_SEND_DONE) + + \Version 10/15/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendCertificate(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + uint8_t strHead[4]; + uint8_t strBody[4096]; + int32_t iCertSize, iNumCerts, iBodySize=0, iExtnSize=0; + int32_t iState = (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) ? ST3_SEND_KEY : ST3_SEND_VERIFY; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send Certificate\n")); + + // set certificate_request_context + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + strBody[iBodySize++] = 0; + iExtnSize = 2; + } + + /* if we have a certificate, add it to body $$todo if client we need to validate certificate matches sigalg/cipher, and send an + empty message if it does not. if server we should have already validated that or aborted the connection if it does not */ + if (pState->pCertificate != NULL) + { + iCertSize = pState->pCertificate->iCertSize; + ds_memcpy_s(strBody+iBodySize+6, sizeof(strBody)-iBodySize-6, pState->pCertificate->aCertData, pState->pCertificate->iCertSize); + } + else if (pState->bServer) + { + /* as per https://tools.ietf.org/html/rfc8446#section-4.4.2 a server's certificate list MUST not be empty; this is + a general practice in earlier versions but not specifically enforced prior */ + NetPrintf(("protossl: no valid certificate\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE); + return(ST_FAIL_CERT_INVALID); + } + else + { + /* as per https://tools.ietf.org/html/rfc5246#section-7.4.6, a client MUST send an empty certificate_list if it does not have an + appropriate certificate to send in response to the server's authentication request. */ + iCertSize = 0; + // if we're a tls1.3 client skip sending CertificateVerify and go directly to Finished + if (pSecure->uSslVersion >= SSL3_TLS1_3) + { + iState = ST3_SEND_FINISH; + } + } + iNumCerts = (iCertSize) ? 1 : 0; + + // copy the certificate list into message body + strBody[iBodySize++] = 0; + strBody[iBodySize++] = (uint8_t)(((iCertSize+iExtnSize)+(iNumCerts*3))>>8); + strBody[iBodySize++] = (uint8_t)(((iCertSize+iExtnSize)+(iNumCerts*3))>>0); + if (iNumCerts) + { + strBody[iBodySize++] = 0; + strBody[iBodySize++] = (uint8_t)((iCertSize)>>8); + strBody[iBodySize++] = (uint8_t)((iCertSize)>>0); + } + iBodySize += iCertSize; + + /* set Certificate extensions - note that this field is expected after every certificate, so this + code only works when a single certificate is being sent */ + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + strBody[iBodySize++] = 0; + strBody[iBodySize++] = 0; + } + + // setup the header + strHead[0] = SSL3_MSG_CERTIFICATE; + strHead[1] = 0; + strHead[2] = (uint8_t)((iBodySize)>>8); + strHead[3] = (uint8_t)((iBodySize)>>0); + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, strBody, iBodySize); + + /* remember we sent a certificate (since we will send an empty message if no certificate + is available, only remember if we *actually* sent a certificate). this is used later + to determine if we should send a CertificateVerify handshake message */ + pSecure->bSentCert = iNumCerts ? TRUE : FALSE; + + // if we're a server executing a TLS1.2 or prior handshake, override state transition set above + if ((pSecure->uSslVersion <= SSL3_TLS1_2) && (pState->bServer) && (pSecure->pCipher->uKey == SSL3_KEY_RSA)) + { + iState = (pState->iClientCertLevel > 0) ? ST3_SEND_CERT_REQ : ST3_SEND_DONE; + } + return(iState); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvCertificate + + \Description + Process Certificate handshake packet; ref http://tools.ietf.org/html/rfc5246#section-7.4.2 + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_RECV_HELLO, ST_WAIT_CA, or ST_FAIL_* on error) + + \Version 08/26/2011 (szhu) Added multi-certificate support +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvCertificate(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + SecureStateT *pSecure = pState->pSecure; + int32_t iCertSize, iCertHeight, iSize1, iSize2, iResult, iVerify; + uint8_t aFingerprintId[SSL_FINGERPRINT_SIZE], bCertCached = FALSE; + X509CertificateT leafCert, prevCert; + #if DIRTYCODE_LOGGING + char strIdentSubject[512], strIdentIssuer[512]; + #endif + const uint8_t *pDataEnd = pData+iDataSize; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process Certificate\n")); + + ds_memclr(&leafCert, sizeof(leafCert)); + ds_memclr(&prevCert, sizeof(prevCert)); + + // skip certificate_request_context + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + uint32_t uContextLen = _SafeRead8(pData, pDataEnd); + NetPrintfVerbose((DEBUG_RAW_DATA, 0, "protossl: certificate_request_context length=%d\n", uContextLen)); + pData += uContextLen + 1; + iDataSize -= uContextLen + 1; + } + + // get outer size + if ((iCertSize = iSize1 = _SafeRead24(pData, pDataEnd)) <= 3) + { + NetPrintf(("protossl: no certificate included in certificate message\n")); + if (!pState->bServer) + { + /* as per https://tools.ietf.org/html/rfc8446#section-4.4.2.4, If the server supplies an empty Certificate message, the client MUST + abort the handshake with a "decode_error" alert. */ + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR); + iResult = ST_FAIL_CERT_NONE; + } + else if (pState->bServer && (pState->iClientCertLevel == 2)) + { + /* as per http://tools.ietf.org/html/rfc5246#section-7.4.6 (TLS 1.2), a client providing an empty certificate response to a certificate + request can either be ignored and treated as unauthenticated, or responded to with a fatal handshake failure. a server + MUST always provide a certificate if the agreed-upon key exchange method uses certificates for authentication + as per https://tools.ietf.org/html/rfc8446#section-4.4.2.4 (TLS 1.3), If the client does not send any certificates (i.e., it sends an empty Certificate message), + the server MAY at its discretion either continue the handshake without client authentication or abort the handshake with a "certificate_required" alert. + Also, if some aspect of the certificate chain was unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its discretion either + continue the handshake (considering the client unauthenticated) or abort the handshake. */ + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, pSecure->uSslVersion < SSL3_TLS1_3 ? SSL3_ALERT_DESC_HANDSHAKE_FAILURE : SSL3_ALERT_DESC_CERTIFICATE_REQUIRED); + iResult = ST_FAIL_CERT_NONE; + } + else + { + iResult = (pSecure->uSslVersion < SSL3_TLS1_3) ? ST3_RECV_HELLO : ST3_RECV_FINISH; + } + return(iResult); + } + else if (iCertSize != (iDataSize-3)) + { + NetPrintf(("protossl: invalid certificate length\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_BAD_CERTFICIATE); + return(ST_FAIL_CERT_INVALID); + } + else + { + // remember we received a certificate in the certificate message + pSecure->bRecvCert = TRUE; + + // generate a fingerprint of entire certificate envelope + _ProtoSSLGenerateFingerprint(aFingerprintId, sizeof(aFingerprintId), pData, iCertSize, CRYPTHASH_SHA1); + + // see if we've already validated this chain + if (_CertValidHistoryCheck(aFingerprintId, iCertSize)) + { + NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: matched certificate fingerprint in certificate validity history\n")); + bCertCached = TRUE; + } + } + + // skip outer size + pData += 3; + + // process certificates + for (iVerify = -1, iCertHeight = 0; (iSize1 > 0) && (iVerify != 0); pData += iSize2, iSize1 -= iSize2) + { + uint64_t uTime; + int32_t iParse; + + // get certificate size + iSize2 = _SafeRead24(pData, pDataEnd); + + // make sure certificate size isn't too large + if (iSize2 > iSize1) + { + NetPrintf(("protossl: _ServerCert: certificate is larger than envelope (%d/%d)\n", iSize1, iSize2)); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_BAD_CERTFICIATE); + return(ST_FAIL_CERT_INVALID); + } + + // skip certificate object length + pData += 3; + iSize1 -= 3; + + // parse the certificate + if ((iParse = _AsnParseCertificate(&pSecure->Cert, pData, iSize2)) < 0) + { + NetPrintf(("protossl: _ServerCert: x509 cert invalid (error=%d)\n", iParse)); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_BAD_CERTFICIATE); + return(ST_FAIL_CERT_INVALID); + } + #if DEBUG_VAL_CERT + _CertificateDebugPrint(&pSecure->Cert, "parsed certificate"); + #endif + + // skip TLS1.3 certificate extension field + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + const uint8_t *pExtn = pData + iSize2; + uint16_t uExtnLen = _SafeRead16(pExtn, pDataEnd); + iSize2 += uExtnLen + 2; + } + + // allow verification bypass; note this must come after first certificate is parsed since we need it later for key exchange + if (pState->bAllowAnyCert) + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: bypassing certificate verify (disabled)\n")); + return(ST3_RECV_HELLO); + } + + // validate date range + if ((uTime = ds_timeinsecs()) != 0) + { + if ((uTime < pSecure->Cert.uGoodFrom) || (uTime > pSecure->Cert.uGoodTill)) + { + #if DIRTYCODE_LOGGING + struct tm TmTime; + char strTime[32]; + _CertificateDebugPrint(&pSecure->Cert, "certificate failed date range validation:"); + NetPrintf(("protossl: from: %s\n", ds_timetostr(ds_secstotime(&TmTime, pSecure->Cert.uGoodFrom), TIMETOSTRING_CONVERSION_ISO_8601, FALSE, strTime, sizeof(strTime)))); + NetPrintf(("protossl: till: %s\n", ds_timetostr(ds_secstotime(&TmTime, pSecure->Cert.uGoodTill), TIMETOSTRING_CONVERSION_ISO_8601, FALSE, strTime, sizeof(strTime)))); + #endif + iVerify = SSL_ERR_CERT_INVALIDDATE; + pSecure->bDateVerifyFailed = TRUE; + } + } + + // processing specific to first (leaf) certificate + if (iCertHeight == 0) + { + // ensure certificate was issued to this host (server certificates only) + if (!pState->bServer && (_CertificateMatchHostname(pState->strHost, pSecure->Cert.Subject.strCommon) != 0)) + { + // check against alternative name(s), if present + if (_CertificateMatchSubjectAlternativeName(pState->strHost, pSecure->Cert.pSubjectAlt, pSecure->Cert.iSubjectAltLen) != 0) + { + NetPrintf(("protossl: _ServerCert: certificate not issued to this host (%s != %s)\n", pState->strHost, pSecure->Cert.Subject.strCommon)); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_CERTIFICATE_UNKNOWN); + _CertificateDebugPrint(&pSecure->Cert, "cert"); + _CertificateSetFailureInfo(pState, &pSecure->Cert, FALSE); + return(ST_FAIL_CERT_HOST); + } + } + // save leaf cert as we will need it later for handshake + ds_memcpy(&leafCert, &pSecure->Cert, sizeof(leafCert)); + // debug display of leaf certificate signature algorithm and signature length + NetPrintfVerbose((pState->iVerbose, 0, "protossl: sigalg=%s, siglen=%d\n", _SSL3_strSignatureTypes[pSecure->Cert.iSigType-ASN_OBJ_RSA_PKCS_MD5], pSecure->Cert.iSigSize*8)); + + // if we cached this cert chain allow bypass; this must come after we've verified the hostname matches + if (bCertCached) + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: bypassing certificate verify (cached)\n")); + iVerify = 0; + break; + } + } + else + { + // check to see if the cert is the next cert in the trust chain anchored by the leaf certificate + if ((_CertificateCompareIdent(&pSecure->Cert.Subject, &prevCert.Issuer, prevCert.iCertIsCA) != 0) || + ((pSecure->Cert.iKeyModSize != prevCert.iSigSize) && (pSecure->Cert.iKeyType != ASN_OBJ_ECDSA_KEY))) + { + // not part of our chain, so skip it + _CertificateDebugPrint(&pSecure->Cert, "_ServerCert: certificate not part of chain, skipping:"); + continue; + } + + // check basic constraints (CA) + if (pSecure->Cert.iCertIsCA == FALSE) + { + continue; + } + + // check basic constraints (pathLenConstraints) + if ((pSecure->Cert.iMaxHeight != 0) && (iCertHeight > pSecure->Cert.iMaxHeight)) + { + continue; + } + + // verify previous cert's signature with current cert's public key + if ((_ProtoSSLVerifyCertificateSignature(pSecure, &prevCert, pSecure->Cert.KeyModData, pSecure->Cert.iKeyModSize, + pSecure->Cert.KeyExpData, pSecure->Cert.iKeyExpSize, pSecure->Cert.iKeyType, pSecure->Cert.iCrvType)) != 0) + { + continue; + } + + NetPrintfVerbose((pState->iVerbose, 0, "protossl: cert (%s) verified by ca (%s)\n", _CertificateDebugFormatIdent(&prevCert.Subject, strIdentSubject, sizeof(strIdentSubject)), + _CertificateDebugFormatIdent(&prevCert.Issuer, strIdentIssuer, sizeof(strIdentIssuer)))); + } + + // track cert height + iCertHeight += 1; + // force _VerifyCertificate to update pState->CertInfo on cert failure + pState->bCertInfoSet = FALSE; + // see if we can verify against a trust anchor; a success here means we are done parsing the certificate chain, whether there are more certificates or not + iVerify = _ProtoSSLVerifyCertificate(pState, pSecure, &pSecure->Cert, FALSE); + + // save current cert for validation of next cert + ds_memcpy(&prevCert, &pSecure->Cert, sizeof(prevCert)); + } + + // process verification result + if (iVerify != 0) + { + #if DIRTYCODE_LOGGING + if (iVerify == SSL_ERR_GOSCA_INVALIDUSE) + { + NetPrintf(("protossl: gos ca (%s) may not be used for non-ea domain %s\n", _CertificateDebugFormatIdent(&pSecure->Cert.Issuer, strIdentSubject, sizeof(strIdentSubject)), + pSecure->Cert.Subject.strCommon)); + } + else if (iVerify == SSL_ERR_CAPROVIDER_INVALID) + { + NetPrintf(("protossl: ca (%s) may not be used as a ca provider\n", _CertificateDebugFormatIdent(&pSecure->Cert.Issuer, strIdentSubject, sizeof(strIdentSubject)))); + } + else + { + _CertificateDebugPrint(&pSecure->Cert, "_VerifyCertificate() failed -- no CA available for this certificate"); + NetPrintf(("protossl: _ServerCert: x509 cert untrusted (error=%d)\n", iVerify)); + } + #endif + + // check if we need to fetch CA + if ((iVerify != SSL_ERR_GOSCA_INVALIDUSE) && (_ProtoSSLInitiateCARequest(pState) == 0)) + { + iResult = ST_WAIT_CA; + } + else + { + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNKNOWN_CA); + iResult = ST_FAIL_CERT_NOTRUST; + } + } + else if (pSecure->bDateVerifyFailed) + { + // an invalid date is not flagged as a verification failure, so we check the flag directly here + NetPrintf(("protossl: _ServerCert: failed date validation\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_CERTIFICATE_EXPIRED); + iResult = ST_FAIL_CERT_BADDATE; + } + else + { + // cert chain is valid + NetPrintfVerbose((pState->iVerbose, 0, "protossl: cert (%s) verified by ca (%s)\n", _CertificateDebugFormatIdent(&pSecure->Cert.Subject, strIdentSubject, sizeof(strIdentSubject)), + _CertificateDebugFormatIdent(&pSecure->Cert.Issuer, strIdentIssuer, sizeof(strIdentIssuer)))); + + // process next handshake packet + iResult = ST3_RECV_HELLO; + // add cert fingerprint to cert validation history if it's not already there + if (!bCertCached) + { + _CertValidHistoryAdd(aFingerprintId, iCertSize); + } + } + // restore the leaf cert for use during handshake + if ((iCertHeight != 0) && (iResult != ST_FAIL_CERT_NOTRUST)) + { + ds_memcpy(&pSecure->Cert, &leafCert, sizeof(pSecure->Cert)); + } + + // still in hello state (if validated) + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendServerKeyExchange + + \Description + Send the ServerKeyExchange message (only for ECDHE key exchange) + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_SEND_KEY, ST3_SEND_CERT_REQ, ST3_SEND_DONE + or ST_FAIL_SETUP on error) + + \Version 03/03/2017 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendServerKeyExchange(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + uint8_t aBody[1024], aHead[4]; + uint8_t *pData = aBody; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send ServerKeyExchange\n")); + + // if using RSA for key exchange we do not send this message, treat as error + if (pSecure->pCipher->uKey == SSL3_KEY_RSA) + { + NetPrintf(("protossl: _SendServerKey: wrong key exchange algorithm RSA")); + return(ST_FAIL_SETUP); + } + if (pSecure->pSigScheme == NULL) + { + if (pSecure->uSslVersion < SSL3_TLS1_2) + { + pSecure->pSigScheme = _ProtoSSLGetSignatureScheme((pSecure->pCipher->uSig == SSL3_SIGALG_RSA) ? SSL3_SIGSCHEME_RSA_PKCS1_SHA1 : SSL3_SIGSCHEME_ECDSA_SHA1); + } + else + { + /* if we failed to find a matching signature algorithm and the key exchange uses ecdhe we fail + as we won't be able to match a signature algorithm to sign with */ + NetPrintf(("protossl: _SendServerKey: no valid signature algorithm needed for key exchange\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE); + return(ST_FAIL_SETUP); + } + } + // make sure we found a supported elliptic curve if we are using ECC cipher suite + if (pSecure->pEllipticCurve == NULL) + { + NetPrintf(("protossl: _SendServerKey: no matching elliptic curve when using ecc cipher suite\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE); + /* when using ECC cipher suite make sure that we have a suitable elliptic curve. + we are allowed to try a different cipher suite but I do not believe it is worth + the hassle of reparsing. + note: ECDHE-ECDSA is allowed to use a different curve for the ephemeral ECDH key + in the ServerKeyExchange message */ + return(ST_FAIL_SETUP); + } + + /* generate public key + elliptic curve generation takes multiple frames + so stay in the same state until the operation is complete */ + if (!pSecure->bEccKeyGenerated) + { + const CryptCurveDhT *pEcc; + uint32_t uCryptUsecs; + + // initialize elliptic curve context if not already initialized + if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) == NULL) + { + /* if we cannot find the dh functions, there is some configuration mishap or the server is faulty. + let's fail early here so we can debug the issue instead of a null pointer exception */ + return(ST_FAIL_SETUP); + } + // generate the public key + if (pEcc->Public(pSecure->EccContext, NULL, &uCryptUsecs) > 0) + { + return(ST3_SEND_KEY); + } + pSecure->uPubKeyLength = pEcc->PointFinal(pSecure->EccContext, NULL, FALSE, pSecure->PubKey, sizeof(pSecure->PubKey)); + pSecure->bEccKeyGenerated = TRUE; + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate public key for server key message) %dms\n", + uCryptUsecs/1000)); + pSecure->uTimer += uCryptUsecs/1000; + } + + // setup the data that needed to be hashed + *pData++ = SSL3_CURVETYPE_NAMED_CURVE; + *pData++ = (uint8_t)(pSecure->pEllipticCurve->uIdent>>8); + *pData++ = (uint8_t)(pSecure->pEllipticCurve->uIdent>>0); + *pData++ = pSecure->uPubKeyLength; + ds_memcpy_s(pData, pSecure->uPubKeyLength, pSecure->PubKey, sizeof(pSecure->PubKey)); + pData += pSecure->uPubKeyLength; + + // setup the encryption for the server key exchange signature + if (!pSecure->bSigGenerated) + { + CryptHashTypeE eHashType = _SSL3_HashIdToCrypt[pSecure->pSigScheme->SigAlg.uHashId]; + const CryptHashT *pHash; + + // calculate signature + if ((pHash = CryptHashGet(eHashType)) != NULL) + { + uint8_t aSigObj[SSL_SIG_MAX], aHashDigest[CRYPTHASH_MAXDIGEST]; + int32_t iPrivateKeySize, iHashSize, iSigSize; + X509PrivateKeyT PrivateKey; + + // decode private key, extract key modulus and private exponent + if ((iPrivateKeySize = _CertificateDecodePrivate(pState->pPrivateKey, pState->iPrivateKeyLen, &PrivateKey)) < 0) + { + NetPrintf(("protossl: unable to decode private key\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_INTERNAL_ERROR); + return(ST_FAIL_SETUP); + } + + // generate signature hash and encode (where appropriate) to create signature hash object + if ((pSecure->uSslVersion < SSL3_TLS1_2) && (pSecure->pCipher->uSig != SSL3_SIGALG_ECDSA)) + { + iSigSize = _ProtoSSLGenerateSignatureHash(pSecure, CryptHashGet(CRYPTHASH_MD5), aSigObj, aBody, (uint32_t)(pData-aBody)); + iSigSize += _ProtoSSLGenerateSignatureHash(pSecure, CryptHashGet(CRYPTHASH_SHA1), aSigObj+iSigSize, aBody, (uint32_t)(pData-aBody)); + } + else + { + iHashSize = _ProtoSSLGenerateSignatureHash(pSecure, pHash, aHashDigest, aBody, (uint32_t)(pData-aBody)); + if (pSecure->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PKCS1) + { + iSigSize = _AsnWriteDigitalHashObject(aSigObj, sizeof(aSigObj), aHashDigest, iHashSize, eHashType); + } + else if (pSecure->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PSS) + { + iSigSize = _Pkcs1EncodeEMSA_PSS(aSigObj, PrivateKey.Modulus.iObjSize, aHashDigest, iHashSize, eHashType); + } + else // ecdsa signatures have no additional encoding + { + ds_memcpy_s(aSigObj, sizeof(aSigObj), aHashDigest, iHashSize); + iSigSize = iHashSize; + } + } + + // start async signature generation + return(_ProtoSSLGenerateSignature(pState, pSecure, pSecure->pSigScheme, aSigObj, iSigSize, &PrivateKey, ST3_SEND_KEY)); + } + } + + // signature is ready, format the message + if (pSecure->uSslVersion >= SSL3_TLS1_2) + { + *pData++ = (uint8_t)(pSecure->pSigScheme->uIdent>>8); + *pData++ = (uint8_t)(pSecure->pSigScheme->uIdent>>0); + } + *pData++ = (uint8_t)(pSecure->SigVerify.iSigSize>>8); + *pData++ = (uint8_t)(pSecure->SigVerify.iSigSize>>0); + ds_memcpy(pData, pSecure->SigVerify.aSigData, pSecure->SigVerify.iSigSize); + pData += pSecure->SigVerify.iSigSize; + + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->SigVerify.aSigData, pSecure->SigVerify.iSigSize, "encrypted server key exchange signature"); + #endif + + // format the packet header + aHead[0] = SSL3_MSG_SERVER_KEY; + aHead[1] = 0; + aHead[2] = (uint8_t)((pData-aBody)>>8); + aHead[3] = (uint8_t)((pData-aBody)>>0); + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, aHead, sizeof(aHead), aBody, (int32_t)(pData-aBody)); + return((pState->iClientCertLevel > 0) ? ST3_SEND_CERT_REQ : ST3_SEND_DONE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvServerKeyExchange + + \Description + Process ServerKeyExchange handshake packet; not used with RSA cipher suites + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_RECV_HELLO, ST_PROC_ASYNC, or ST_FAIL_SETUP on error) + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvServerKeyExchange(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + int32_t iIndex; + SecureStateT *pSecure = pState->pSecure; + CryptHashTypeE eHashType; + const CryptHashT *pHash; + const uint8_t *pBegin = pData, *pDataEnd = pData+iDataSize, *pSigData; + const SignatureSchemeT *pSigScheme; + int32_t iNextState = ST3_RECV_HELLO; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process ServerKeyExchange\n")); + + // if using RSA for key exchange we do not receive this message, treat as error + if (pSecure->pCipher->uKey == SSL3_KEY_RSA) + { + NetPrintf(("protossl: _ServerKey: wrong key exchange algorithm RSA\n")); + return(ST_FAIL_SETUP); + } + // make sure it is using the curve type we support + if ((_SafeRead8(pData, pDataEnd) == SSL3_CURVETYPE_NAMED_CURVE) && (pData+3 <= pDataEnd)) + { + // validate the elliptic curve sent by the server + for (iIndex = 0; iIndex < (signed)(sizeof(_SSL3_EllipticCurves)/sizeof(*_SSL3_EllipticCurves)); iIndex += 1) + { + // skip non-matching elliptic curve + if ((pData[1] != (uint8_t)(_SSL3_EllipticCurves[iIndex].uIdent >> 8)) || (pData[2] != (uint8_t)(_SSL3_EllipticCurves[iIndex].uIdent))) + { + continue; + } + // found an elliptic curve + pSecure->pEllipticCurve = &_SSL3_EllipticCurves[iIndex]; + NetPrintfVerbose((pState->iVerbose, 0, "protossl: _ServerKey: using named curve %s (ident=0x%04x)\n", pSecure->pEllipticCurve->strName, pSecure->pEllipticCurve->uIdent)); + break; + } + pData += 3; + } + // make sure the server sent us one of our supported elliptic curves + if (pSecure->pEllipticCurve == NULL) + { + NetPrintf(("protossl: _ServerKey: no matching named curve when using ecdhe key exchange\n")); + return(ST_FAIL_SETUP); + } + + // copy the public key + pSecure->uPubKeyLength = _SafeRead8(pData++, pDataEnd); + _SafeReadBytes(pSecure->PubKey, sizeof(pSecure->PubKey), pData, pSecure->uPubKeyLength, pDataEnd); + pData += pSecure->uPubKeyLength; + + // if we attempted a TLS 1.3 connection but were downgraded, reset the ecc parameters + if (pState->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + pSecure->bEccContextInitialized = FALSE; + pSecure->bEccKeyGenerated = FALSE; + } + + // get signature scheme + if (pSecure->uSslVersion < SSL3_TLS1_2) + { + // EC ciphers in TLS versions prior to 1.2 do not include their signature algorithm in the packet data and are assumed to be SHA1 + pSigScheme = _ProtoSSLGetSignatureScheme(pSecure->Cert.iKeyType == ASN_OBJ_ECDSA_KEY ? SSL3_SIGSCHEME_ECDSA_SHA1 : SSL3_SIGSCHEME_RSA_PKCS1_SHA1); + pSigData = pData; + } + else + { + if ((pSigScheme = _ProtoSSLGetSignatureScheme(_SafeRead16(pData, pDataEnd))) == NULL) + { + NetPrintf(("protossl: _ServerKey: unsupported signature scheme or signature algorithm\n")); + return(ST_FAIL_SETUP); + } + pSigData = pData+2; + } + // get signature hash + eHashType = _SSL3_HashIdToCrypt[pSigScheme->SigAlg.uHashId]; + + // calculate and verify the signature + if ((pHash = CryptHashGet(eHashType)) != NULL) + { + uint8_t aHashDigest[CRYPTHASH_MAXDIGEST]; + int32_t iHashSize, iSigSize = _SafeRead16(pSigData, pDataEnd); + pSigData += 2; + + // create signature hash + if ((pSecure->uSslVersion < SSL3_TLS1_2) && (pSigScheme->uIdent != SSL3_SIGSCHEME_ECDSA_SHA1)) + { + // ecdhe-rsa-sha cipher suites + iHashSize = _ProtoSSLGenerateSignatureHash(pSecure, CryptHashGet(CRYPTHASH_MD5), aHashDigest, pBegin, (uint32_t)(pData-pBegin)); + iHashSize += _ProtoSSLGenerateSignatureHash(pSecure, CryptHashGet(CRYPTHASH_SHA1), aHashDigest+iHashSize, pBegin, (uint32_t)(pData-pBegin)); + } + else + { + iHashSize = _ProtoSSLGenerateSignatureHash(pSecure, pHash, aHashDigest, pBegin, (uint32_t)(pData-pBegin)); + } + + // verify the signature; note this is executed iteratively + iNextState = _ProtoSSLVerifySignature(pState, pSecure, pSigScheme, pSigData, iSigSize, aHashDigest, iHashSize, pSecure->uSslVersion >= SSL3_TLS1_2 ? eHashType : CRYPTHASH_NULL, ST3_RECV_HELLO); + } + else + { + NetPrintf(("protossl: _ServerKey: unsupported signature hash\n")); + return(ST_FAIL_SETUP); + } + + return(iNextState); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendCertificateRequest + + \Description + Send CertificateRequest handshake packet + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_SEND_DONE) + + \Version 10/15/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendCertificateRequest(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + uint8_t strHead[4], strBody[128]; + int32_t iBodySize; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send Certificate Request\n")); + + if (pSecure->uSslVersion < SSL3_TLS1_3) + { + // setup the body + iBodySize = 0; + strBody[iBodySize++] = 2; // number of certificate types + strBody[iBodySize++] = SSL3_SIGN_RSA; // rsa signing support + strBody[iBodySize++] = SSL3_SIGN_ECDSA; // ecdsa signing support + + // add TLS1.2 required SignatureAndHashAlgorithm as per http://tools.ietf.org/html/rfc5246#section-7.4.4 + if (pSecure->uSslVersion >= SSL3_TLS1_2) + { + int32_t iScheme, iNumSchemes = sizeof(_SSL3_SignatureSchemes)/sizeof(_SSL3_SignatureSchemes[0]); + strBody[iBodySize++] = 0; + strBody[iBodySize++] = iNumSchemes * 2; + for (iScheme = 0; iScheme < iNumSchemes; iScheme += 1) + { + strBody[iBodySize++] = (uint8_t)(_SSL3_SignatureSchemes[iScheme].uIdent>>8); + strBody[iBodySize++] = (uint8_t)(_SSL3_SignatureSchemes[iScheme].uIdent>>0); + } + } + + // number of certificate authorities (not supported) + strBody[iBodySize++] = 0; + strBody[iBodySize++] = 0; + } + else + { + const uint8_t *pDataEnd; + // write empty certificate_request_context, which we do not require as we don't support post-handshake authentication + strBody[0] = 0; + // add signature_algorithms extension + pDataEnd = _ProtoSSLAddHelloExtensions(pState, strBody+1, sizeof(strBody)-1, strBody, PROTOSSL_HELLOEXTN_SIGALGS); + iBodySize = pDataEnd - strBody; + } + + // setup the header + strHead[0] = SSL3_MSG_CERT_REQ; + strHead[1] = 0; + strHead[2] = 0; + strHead[3] = iBodySize; + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, sizeof(strHead), strBody, iBodySize); + return((pSecure->uSslVersion < SSL3_TLS1_3) ? ST3_SEND_DONE : ST3_SEND_CERT); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvCertificateRequest + + \Description + Process CertificateRequest handshake packet + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_RECV_HELLO, or ST_FAIL_* on error) + + \Version 10/18/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvCertificateRequest(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + SecureStateT *pSecure = pState->pSecure; + const uint8_t *pDataEnd = pData + iDataSize; + int32_t iType, iNumTypes; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process CertificateRequest\n")); + + // process tls1.2 and prior certificate request + if (pSecure->uSslVersion <= SSL3_TLS1_2) + { + uint8_t uSign; + // make sure an rsa/ecdsa certificate is permissable + for (iType = 0, iNumTypes = _SafeRead8(pData++, pDataEnd); iType < iNumTypes; iType += 1) + { + uSign = _SafeRead8(pData+iType, pDataEnd); + if ((uSign == SSL3_SIGN_RSA) || (uSign == SSL3_SIGN_ECDSA)) + { + break; + } + } + // makes sure we got a valid signing algorithm + if (iType == iNumTypes) + { + NetPrintf(("protossl: no valid cert signing option found in RecvCertificateRequest\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR); + return(ST_FAIL_SETUP); + } + // skip past cert types + pData += iNumTypes; + + // TLS1.2 includes SignatureAndHashAlgorithm specification: http://tools.ietf.org/html/rfc5246#section-7.4.4 + if (pSecure->uSslVersion == SSL3_TLS1_2) + { + // get and skip list length + uint32_t uSigSchemeLen = _SafeRead16(pData, pDataEnd); + pData += 2; + // pick first supported signature algorithm + if ((pSecure->pSigScheme = _ProtoSSLChooseSignatureScheme(pSecure, pState->pCertificate, pData, uSigSchemeLen, pDataEnd)) == NULL) + { + NetPrintf(("protossl: no valid signature algorithm found RecvCertificateRequest\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE); + return(ST_FAIL_SETUP); + } + } + } + else + { + // parse tls1.3 certificate request as per https://tools.ietf.org/html/rfc8446#section-4.3.2 + int32_t iParseResult; + // skip certificate_request_context, which we do not require as we don't support post-handshake authentication + pData += _SafeRead8(pData, pDataEnd)+1; + // parse signature_algorithms + if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_SIGNATURE_ALGORITHMS)) != 0) + { + return(iParseResult); + } + } + + /* As per http://tools.ietf.org/html/rfc5246#section-7.4.6, if no suitable certificate is available, + the client MUST send a certificate message containing no certificates, so we don't bother to check + whether we have a certificate here */ + pState->iClientCertLevel = 1; + return(ST3_RECV_HELLO); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendServerHelloDone + + \Description + Send ServerHelloDone handshake packet; marks end of server hello sequence + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_RECV_HELLO) + + \Notes + Applies to TLS1.2 and prior only + + \Version 10/15/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendServerHelloDone(ProtoSSLRefT *pState) +{ + uint8_t strHead[4]; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send ServerHelloDone\n")); + + // setup the header + strHead[0] = SSL3_MSG_SERVER_DONE; + strHead[1] = 0; + strHead[2] = 0; + strHead[3] = 0; + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, NULL, 0); + return(ST3_RECV_HELLO); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvServerHelloDone + + \Description + Process ServerHelloDone handshake packet; marks end of server hello sequence + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_SEND_CERT or ST3_SEND_KEY) + + \Notes + Applies to TLS1.2 and prior only + + \Version 10/15/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvServerHelloDone(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process ServerHelloDone\n")); + return((pState->iClientCertLevel != 0) ? ST3_SEND_CERT : ST3_SEND_KEY); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLUpdateSendClientKeyExchange + + \Description + Send ClientKeyExchange handshake packet to the server + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_SEND_VERIFY or ST3_SEND_CHANGE on completion, + ST3_SEND_KEY if computation is ongoing, ST_FAIL_SETUP on error) + + \Version 01/23/2017 (eesponda) Updated to support ECDHE +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendClientKeyExchange(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + uint8_t aHead[6]; + + // handle rsa key exchange + if (pState->pSecure->pCipher->uKey == SSL3_KEY_RSA) + { + if (!pSecure->bRSAContextInitialized) + { + // build pre-master secret + CryptRandGet(pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey)); + /* From http://tools.ietf.org/html/rfc5246#section-7.4.7.1, client_version: The version requested + by the client in the ClientHello. This is used to detect version roll-back attacks */ + pSecure->PreMasterKey[0] = (uint8_t)(pSecure->uSslClientVersion>>8); + pSecure->PreMasterKey[1] = (uint8_t)(pSecure->uSslClientVersion>>0); + + // init rsa state + if (CryptRSAInit(&pSecure->RSAContext, pSecure->Cert.KeyModData, pSecure->Cert.iKeyModSize, pSecure->Cert.KeyExpData, pSecure->Cert.iKeyExpSize)) + { + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_INTERNAL_ERROR); + return(ST_FAIL_SETUP); + } + CryptRSAInitMaster(&pSecure->RSAContext, pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey)); + pSecure->bRSAContextInitialized = TRUE; + } + + // encrypt the premaster key (iterative) + if (CryptRSAEncrypt(&pSecure->RSAContext, 1)) + { + return(ST3_SEND_KEY); + } + pSecure->bRSAContextInitialized = FALSE; + + // update ssl perf timer + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (encrypt rsa client premaster secret) %dms (%d calls)\n", + pSecure->RSAContext.uCryptMsecs, pSecure->RSAContext.uNumExpCalls)); + pSecure->uTimer += pSecure->RSAContext.uCryptMsecs; + + // build master secret + _ProtoSSLBuildKey(pState, pSecure->MasterKey, sizeof(pSecure->MasterKey), pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey), + pSecure->ClientRandom, pSecure->ServerRandom, sizeof(pSecure->ClientRandom), "master secret", + pSecure->uSslVersion); + + // setup the header + aHead[0] = SSL3_MSG_CLIENT_KEY; + aHead[1] = 0; + aHead[2] = (uint8_t)((pSecure->Cert.iKeyModSize+2)>>8); + aHead[3] = (uint8_t)((pSecure->Cert.iKeyModSize+2)>>0); + aHead[4] = (uint8_t)(pSecure->Cert.iKeyModSize>>8); + aHead[5] = (uint8_t)(pSecure->Cert.iKeyModSize>>0); + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, aHead, 6, pSecure->RSAContext.EncryptBlock, pSecure->RSAContext.iKeyModSize); + } + else + { + uint8_t aPublicKey[128]; + int32_t iPublicKeySize; + uint32_t uCryptUsecs; + const CryptCurveDhT *pEcc; + + /* generate public key; elliptic curve generation takes multiple frames + so stay in the same state until the operation is complete */ + + // initialize elliptic curve context if not already initialized + if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) == NULL) + { + /* if we cannot find the dh functions, there is some configuration mishap or the server is faulty. + let's fail early here so we can debug the issue instead of a null pointer exception */ + return(ST_FAIL_SETUP); + } + // generate the public key + if (pEcc->Public(&pSecure->EccContext, NULL, &uCryptUsecs) > 0) + { + return(ST3_SEND_KEY); + } + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate public key for client key message) %dms\n", + uCryptUsecs/1000)); + pSecure->uTimer += uCryptUsecs/1000; + + // encode public key into buffer + iPublicKeySize = pEcc->PointFinal(pSecure->EccContext, NULL, FALSE, aPublicKey+1, sizeof(aPublicKey)-1); + aPublicKey[0] = iPublicKeySize; + + // format header + aHead[0] = SSL3_MSG_CLIENT_KEY; + aHead[1] = 0; + aHead[2] = 0; + aHead[3] = iPublicKeySize+1; + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, aHead, 4, aPublicKey, iPublicKeySize+1); + } + + // move to next state + return(pSecure->bSentCert ? ST3_SEND_VERIFY : ST3_SEND_CHANGE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvClientKeyExchange + + \Description + Process ClientKeyExchange handshake packet + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_RECV_HELLO, ST3_RECV_CHANGE, or ST_FAIL_SETUP on error) + + \Version 10/15/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvClientKeyExchange(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + SecureStateT *pSecure = pState->pSecure; + int32_t iCryptKeySize, iPrivKeySize; + uint8_t strRandomSecret[48]; + X509PrivateKeyT PrivateKey; + const uint8_t *pCryptKeyData; + uint16_t uSslVersion; + const uint8_t *pDataEnd = pData+iDataSize; + + // handle RSA key exchange + if (pSecure->pCipher->uKey == SSL3_KEY_RSA) + { + // read encrypted key data + iCryptKeySize = _SafeRead16(pData, pDataEnd); + pCryptKeyData = pData + 2; + + // decode private key and extract key modulus and private exponent + if ((iPrivKeySize = _CertificateDecodePrivate(pState->pPrivateKey, pState->iPrivateKeyLen, &PrivateKey)) < 0) + { + NetPrintf(("protossl: unable to decode private key\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_INTERNAL_ERROR); + return(ST_FAIL_SETUP); + } + + // decrypt client premaster secret using private key + NetPrintfVerbose((pState->iVerbose, 1, "protossl: decrypt client key (iKeySize=%d, iKeyModSize=%d, iKeyExpSize=%d)\n", iPrivKeySize, PrivateKey.Modulus.iObjSize, PrivateKey.PrivateExponent.iObjSize)); + if (CryptRSAInit2(&pSecure->RSAContext, PrivateKey.Modulus.iObjSize, &PrivateKey.PrimeP, &PrivateKey.PrimeQ, &PrivateKey.ExponentP, &PrivateKey.ExponentQ, &PrivateKey.Coefficient)) + { + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_INTERNAL_ERROR); + return(ST_FAIL_SETUP); + } + CryptRSAInitSignature(&pSecure->RSAContext, pCryptKeyData, iCryptKeySize); + CryptRSAEncrypt(&pSecure->RSAContext, 0); + + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (rsa decrypt client premaster secret) %dms\n", pSecure->RSAContext.uCryptMsecs)); + pSecure->uTimer += pSecure->RSAContext.uCryptMsecs; + + // copy out and display decrypted client premaster key data + ds_memcpy(pSecure->PreMasterKey, pSecure->RSAContext.EncryptBlock+iCryptKeySize-sizeof(pSecure->PreMasterKey), sizeof(pSecure->PreMasterKey)); + #if DEBUG_RAW_DATA + NetPrintMem(pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey), "client premaster key"); + #endif + + // generate random for use in possible roll-back attack handling + CryptRandGet(strRandomSecret, sizeof(strRandomSecret)); + // validate client TLS version (first two bytes of premaster secret) + if ((uSslVersion = (pSecure->PreMasterKey[0] << 8) | pSecure->PreMasterKey[1]) != pSecure->uSslClientVersion) + { + NetPrintf(("protossl: detected possible roll-back attack; premaster secret tls version %d.%d does not match client-specified version\n", + pSecure->PreMasterKey[0], pSecure->PreMasterKey[1])); + } + + /* As per http://tools.ietf.org/html/rfc5246#section-7.4.7.1, a mismatch in the premaster secret tls version + and the client-specified version should be handled by generating a random premaster secret and continuing + on. The random data should be generated unconditionally to prevent possible timing attacks. This same + response is also utilized for any detected PKCS#1 padding errors. */ + if ((uSslVersion != pSecure->uSslClientVersion) || !_Pkcs1VerifyRSAES(pSecure->RSAContext.EncryptBlock, iCryptKeySize, sizeof(pSecure->PreMasterKey))) + { + ds_memcpy(pSecure->PreMasterKey, strRandomSecret, sizeof(strRandomSecret)); + } + + // build master secret + _ProtoSSLBuildKey(pState, pSecure->MasterKey, sizeof(pSecure->MasterKey), pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey), + pSecure->ClientRandom, pSecure->ServerRandom, sizeof(pSecure->ClientRandom), "master secret", + pSecure->uSslVersion); + } + else + { + // for ecdhe key exchange we just copy the public key + pSecure->uPubKeyLength = _SafeRead8(pData++, pDataEnd); + _SafeReadBytes(pSecure->PubKey, sizeof(pSecure->PubKey), pData, pSecure->uPubKeyLength, pDataEnd); + } + + // move on to next state + return(((pState->iClientCertLevel > 0) && pSecure->bRecvCert) ? ST3_RECV_HELLO : ST3_RECV_CHANGE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendCertificateVerify + + \Description + Send CertificateVerify handshake packet + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_SEND_CHANGE or ST3_SEND_FINISH on completion, + ST3_SEND_VERIFY if RSA computation is ongoing, or ST_FAIL_SETUP + on error) + + \Version 10/30/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendCertificateVerify(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + uint32_t uHeadSize; + uint8_t strHead[16]; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send CertificateVerify\n")); + + // set up for encryption of certificate verify message + if (!pSecure->bSigGenerated) + { + uint8_t aSigObj[SSL_SIG_MAX+128]; // must be big enough to hold max hash size plus ASN.1 object encoding (TLS1.2), or a full signature object (TLS1.3+) + int32_t iPrivKeySize, iHashSize=0; + X509PrivateKeyT PrivateKey; + + // decode private key and extract key modulus and private exponent + if ((iPrivKeySize = _CertificateDecodePrivate(pState->pPrivateKey, pState->iPrivateKeyLen, &PrivateKey)) < 0) + { + NetPrintf(("protossl: unable to decode private key\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_INTERNAL_ERROR); + return(ST_FAIL_SETUP); + } + + // set up buffer with current handshake hash + if (pSecure->uSslVersion < SSL3_TLS1_2) + { + // select signature scheme based on server/client certificate + pSecure->pSigScheme = _ProtoSSLGetSignatureScheme(pState->pCertificate->iKeyType == ASN_OBJ_ECDSA_KEY ? SSL3_SIGSCHEME_ECDSA_SHA1 : SSL3_SIGSCHEME_RSA_PKCS1_SHA1); + // generate digest hash + if (pSecure->pSigScheme->SigAlg.uSigAlg != SSL3_SIGALG_ECDSA) + { + iHashSize = _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_MD5, aSigObj, SSL3_MAC_MD5); + } + iHashSize += _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_SHA1, aSigObj+iHashSize, SSL3_MAC_SHA); + } + else + { + CryptHashTypeE eHashType = _SSL3_HashIdToCrypt[pSecure->pSigScheme->SigAlg.uHashId]; + uint8_t aMessageHash[CRYPTHASH_MAXDIGEST], aTempHash[CRYPTHASH_MAXDIGEST]; + + // get message digest hash + if (pSecure->uSslVersion < SSL3_TLS1_3) + { + // get handshake hash using signature hash algorithm + iHashSize = _ProtoSSLHandshakeHashGet(pSecure, eHashType, aMessageHash, sizeof(aMessageHash)); + } + else + { + // get handshake hash using cipher prf hash algorithm + iHashSize = _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, aTempHash, sizeof(aTempHash)); + // construct tls1.3 digital signature envelope using signature hash algorithm + iHashSize = _ProtoSSLGenerateCertificateVerifyHash(aMessageHash, eHashType, aTempHash, iHashSize, pState->bServer, TRUE); + } + + // encode message digest hash + if ((pSecure->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PKCS1) && (pSecure->uSslVersion < SSL3_TLS1_3)) + { + iHashSize = _AsnWriteDigitalHashObject(aSigObj, sizeof(aSigObj), aMessageHash, iHashSize, eHashType); + } + else if (pSecure->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PSS) + { + iHashSize = _Pkcs1EncodeEMSA_PSS(aSigObj, PrivateKey.Modulus.iObjSize, aMessageHash, iHashSize, eHashType); + } + else // ecdsa signatures have no additional encoding + { + ds_memcpy_s(aSigObj, sizeof(aSigObj), aMessageHash, iHashSize); + } + } + + #if DEBUG_RAW_DATA + NetPrintMem(aSigObj, iHashSize, "certificate verify hash"); + #endif + + // start async signature generation + return(_ProtoSSLGenerateSignature(pState, pSecure, pSecure->pSigScheme, aSigObj, iHashSize, &PrivateKey, ST3_SEND_VERIFY)); + } + + // setup the header + strHead[0] = SSL3_MSG_CERT_VERIFY; + strHead[1] = 0; + + if (pSecure->uSslVersion < SSL3_TLS1_2) + { + strHead[2] = (uint8_t)((pSecure->SigVerify.iSigSize+2)>>8); + strHead[3] = (uint8_t)((pSecure->SigVerify.iSigSize+2)>>0); + strHead[4] = (uint8_t)(pSecure->SigVerify.iSigSize>>8); + strHead[5] = (uint8_t)(pSecure->SigVerify.iSigSize>>0); + uHeadSize = 6; + } + else + { + strHead[2] = (uint8_t)((pSecure->SigVerify.iSigSize+4)>>8); + strHead[3] = (uint8_t)((pSecure->SigVerify.iSigSize+4)>>0); + strHead[4] = (uint8_t)(pSecure->pSigScheme->uIdent>>8); + strHead[5] = (uint8_t)(pSecure->pSigScheme->uIdent>>0); + strHead[6] = (uint8_t)(pSecure->SigVerify.iSigSize>>8); + strHead[7] = (uint8_t)(pSecure->SigVerify.iSigSize>>0); + uHeadSize = 8; + } + + // send the packet + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, uHeadSize, pSecure->SigVerify.aSigData, pSecure->SigVerify.iSigSize); + + // transition to next state + return((pSecure->uSslVersion < SSL3_TLS1_3) ? ST3_SEND_CHANGE : ST3_SEND_FINISH); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvCertificateVerify + + \Description + Process CertificateVerify handshake packet + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_RECV_CHANGE, ST3_RECV_FINISH, ST_PROC_ASYNC, + or ST_FAIL_SETUP on error) + + \Version 10/30/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvCertificateVerify(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + SecureStateT *pSecure = pState->pSecure; + int32_t iCryptKeySize, iHashSize; + const uint8_t *pCryptKeyData; + CryptHashTypeE eHashType = CRYPTHASH_NULL; + const uint8_t *pDataEnd = pData+iDataSize; + uint8_t aHandshakeHash[CRYPTHASH_MAXDIGEST]; + const SignatureSchemeT *pSigScheme = NULL; + int32_t iNextState = (pSecure->uSslVersion < SSL3_TLS1_3) ? ST3_RECV_CHANGE : ST3_RECV_FINISH; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process CertificateVerify\n")); + + // get encrypted key data size and offset + if (pSecure->uSslVersion < SSL3_TLS1_2) + { + iCryptKeySize = _SafeRead16(pData, pDataEnd); + pCryptKeyData = pData + 2; + pSigScheme = _ProtoSSLGetSignatureScheme((pSecure->Cert.iKeyType == ASN_OBJ_ECDSA_KEY) ? SSL3_SIGSCHEME_ECDSA_SHA1 : SSL3_SIGSCHEME_RSA_PKCS1_SHA1); + } + else + { + // get signature info + pSigScheme = _ProtoSSLGetSignatureScheme(_SafeRead16(pData, pDataEnd)); + // validate scheme and key type; as per https://tools.ietf.org/html/rfc8446#section-4.4.3 PKCS1 is not a supported signature scheme in tls1.3+ + if ((pSigScheme == NULL) || ((pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PKCS1) && (pSecure->uSslVersion >= SSL3_TLS1_3))) + { + NetPrintf(("protossl: unsupported hashid or signature algorithm in CertificateVerify\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR); + return(ST_FAIL_SETUP); + } + // get crypt key size and data + iCryptKeySize = _SafeRead16(pData+2, pDataEnd); + pCryptKeyData = pData + 4; + } + + // get digest hash from signature scheme + eHashType = _SSL3_HashIdToCrypt[pSigScheme->SigAlg.uHashId]; + + // get handshake hash + if ((pSecure->uSslVersion < SSL3_TLS1_2) && (pSigScheme->SigAlg.uSigAlg != SSL3_SIGALG_ECDSA)) + { + iHashSize = _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_MD5, aHandshakeHash, SSL3_MAC_MD5); + iHashSize += _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_SHA1, aHandshakeHash + iHashSize, SSL3_MAC_SHA); + } + else if (pSecure->uSslVersion < SSL3_TLS1_3) + { + iHashSize = _ProtoSSLHandshakeHashGet(pSecure, eHashType, aHandshakeHash, sizeof(aHandshakeHash)); + } + else + { + // tls1.3 wraps handshake hash in a digital signature envelope + uint8_t aHandshakeHash2[CRYPTHASH_MAXDIGEST]; + // get handshake hash + iHashSize = _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, aHandshakeHash2, sizeof(aHandshakeHash2)); + // construct tls1.3 digital signature envelope + iHashSize = _ProtoSSLGenerateCertificateVerifyHash(aHandshakeHash, eHashType, aHandshakeHash2, iHashSize, pState->bServer, FALSE); + } + + // verify the signature; note this is executed iteratively + return(_ProtoSSLVerifySignature(pState, pSecure, pSigScheme, pCryptKeyData, iCryptKeySize, aHandshakeHash, iHashSize, pSecure->uSslVersion >= SSL3_TLS1_2 ? eHashType : CRYPTHASH_NULL, iNextState)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendChangeCipherSpec + + \Description + Send ChangeCipherSpec handshake packet + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_CHANGE, ST3_SEND_FINISH) + + \Notes + Applies to TLS1.2 and prior only + + \Version 10/15/2013 (jbrookes) Updated to handle client and server +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendChangeCipherSpec(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + uint8_t strHead[4]; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send ChangeCipherSpec\n")); + + // generate the shared secret if necessary + if (!pState->bServer && !pSecure->bSessionResume && !_ProtoSSLGenerateEccSharedSecret(pState)) + { + return(ST3_SEND_CHANGE); + } + + // build key material if we haven't already + if (pSecure->pServerKey == NULL) + { + _ProtoSSLBuildKeyMaterial(pState); + } + + // initialize write cipher + if (pSecure->pCipher->uEnc == SSL3_ENC_AES) + { + CryptAesInit(&pSecure->WriteAes, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen, CRYPTAES_KEYTYPE_ENCRYPT, + pState->bServer ? pSecure->pServerInitVec : pSecure->pClientInitVec); + } + if (pSecure->pCipher->uEnc == SSL3_ENC_GCM) + { + CryptGcmInit(&pSecure->WriteGcm, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen); + } + if (pSecure->pCipher->uEnc == SSL3_ENC_CHACHA) + { + CryptChaChaInit(&pSecure->WriteChaCha, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen); + } + + // mark as cipher change + strHead[0] = 1; + _ProtoSSLSendPacket(pState, SSL3_REC_CIPHER, strHead, 1, NULL, 0); + + // reset the sequence number + pSecure->uSendSeqn = 0; + return(ST3_SEND_FINISH); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvChangeCipherSpec + + \Description + Process ChangeCipherSpec handshake packet + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_RECV_CHANGE, ST3_RECV_FINISH) + + \Notes + Applies to TLS1.2 and prior only + + \Version 10/15/2013 (jbrookes) Updated to handle client and server +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvChangeCipherSpec(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + SecureStateT *pSecure = pState->pSecure; + const uint8_t *pDataEnd = pData + iDataSize; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process ChangeCipherSpec\n")); + + // validate ccs message + if ((iDataSize != 1) || (_SafeRead8(pData, pDataEnd) != 1)) + { + NetPrintf(("protossl: invalid ChangeCipherSpec message\n")); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR); + pState->iClosed = 1; + _ProtoSSLRecvReset(pSecure); + pState->iState = ST_FAIL_SETUP; + } + + // generate the shared secret if necessary + if (pState->bServer && !pSecure->bSessionResume && !_ProtoSSLGenerateEccSharedSecret(pState)) + { + return(ST3_RECV_CHANGE); + } + + // build key material if we haven't already + if (pSecure->pServerKey == NULL) + { + _ProtoSSLBuildKeyMaterial(pState); + } + + // initialize read cipher + if (pSecure->pCipher->uEnc == SSL3_ENC_AES) + { + CryptAesInit(&pSecure->ReadAes, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen, CRYPTAES_KEYTYPE_DECRYPT, + pState->bServer ? pSecure->pClientInitVec : pSecure->pServerInitVec); + } + if (pSecure->pCipher->uEnc == SSL3_ENC_GCM) + { + CryptGcmInit(&pSecure->ReadGcm, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen); + } + if (pSecure->pCipher->uEnc == SSL3_ENC_CHACHA) + { + CryptChaChaInit(&pSecure->ReadChaCha, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen); + } + + // reset sequence number + pSecure->uRecvSeqn = 0; + + // just gobble packet -- we assume next state is crypted regardless + return(ST3_RECV_FINISH); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendFinished + + \Description + Send Finished handshake packet + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_RECV_CHANGE, ST3_RECV_HELLO, + ST3_RECV_FINISH, ST3_SECURE) + + \Version 10/23/2013 (jbrookes) Rewritten to handle client and server +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendFinished(ProtoSSLRefT *pState) +{ + uint8_t strHead[4]; + uint8_t strBody[256]; + SecureStateT *pSecure = pState->pSecure; + static const struct SSLFinished _SendFinished[2] = { + { "client finished", {ST3_RECV_CHANGE, ST3_SECURE } }, + { "server finished", {ST3_SECURE, ST3_RECV_CHANGE } }, + }; + const struct SSLFinished *pFinished = &_SendFinished[pState->bServer]; + int32_t iNextState = ST3_SECURE; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: SendFinished\n")); + + // do finish hash, save size in packet header + strHead[3] = (uint8_t)_ProtoSSLGenerateFinishHash(strBody, pSecure, pFinished->strLabel, pState->bServer, TRUE); + + // setup the header + strHead[0] = SSL3_MSG_FINISHED; + strHead[1] = 0; + strHead[2] = 0; + + // all sends from here on out are secure + pSecure->bSendSecure = TRUE; + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, sizeof(strHead), strBody, strHead[3]); + + // tls1.3 handshake processing + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + if (pState->bServer) + { + // save handshake hash for building application secrets + _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, pSecure->aFinishHash, sizeof(pSecure->aFinishHash)); + } + else + { + // switch to application keys; this must come after _ProtoSSLSendPacket as the server expects the Finished packet to be sent using the handshake key + _ProtoSSLBuildApplicationKey(pState, pSecure); + } + } + + // determine next state + if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) + { + // next state will be secure mode (handshaking complete) or recv cipher change depending on client/server and if resuming or not + iNextState = pFinished->uNextState[pSecure->bSessionResume]; + } + else if (pState->bServer) + { + // server will expect either Finished or Certificate if requesting a client cert + iNextState = (pState->iClientCertLevel != 0) ? ST3_RECV_HELLO : ST3_RECV_FINISH; + } + return(iNextState); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvFinished + + \Description + Process Finished handshake packet + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_SEND_CHANGE, ST3_SEND_CERT, ST3_SEND_FINISH, + ST3_SECURE, or ST_FAIL_SETUP on error) + + \Version 10/23/2013 (jbrookes) Rewritten to handle client and server +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLUpdateRecvFinished(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + SecureStateT *pSecure = pState->pSecure; + uint8_t aMacFinal[CRYPTHASH_MAXDIGEST]; + int32_t iMacSize, iNextState = ST3_SECURE; + static const struct SSLFinished _RecvFinished[2] = { + { "server finished", {ST3_SECURE, ST3_SEND_CHANGE } }, + { "client finished", {ST3_SEND_CHANGE, ST3_SECURE } }, + }; + const struct SSLFinished *pFinished = &_RecvFinished[pState->bServer]; + const uint8_t *pDataEnd = pData + iDataSize; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process RecvFinished\n")); + + // calculate the finish verification hash + iMacSize = _ProtoSSLGenerateFinishHash(aMacFinal, pSecure, pFinished->strLabel, pState->bServer, FALSE); + + // verify the hash + if (memcmp(aMacFinal, pData, DS_MIN(iMacSize, pDataEnd-pData))) + { + NetPrintf(("protossl: finish hash mismatch; failed setup\n")); + #if DEBUG_RAW_DATA + NetPrintMem(aMacFinal, iMacSize, "aMacFinal"); + NetPrintMem(pData, iMacSize, "pData"); + #endif + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECRYPT_ERROR); + return(ST_FAIL_SETUP); + } + + // save session (tls1.2 and previous only) + if (pSecure->uSslVersion <= PROTOSSL_VERSION_TLS1_2) + { + _SessionHistoryAdd(pSecure, pState->strHost, SockaddrInGetPort(&pState->PeerAddr), NULL, pSecure->SessionId, sizeof(pSecure->SessionId), pSecure->MasterKey); + } + + // tls1.3 handshake processing + if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) + { + if (pState->bServer) + { + // switch to application keys + _ProtoSSLBuildApplicationKey(pState, pSecure); + } + else + { + // update hash state with received Finished packet from server (which hasn't been processed yet) + _ProtoSSLRecvHandshakeFinish(pState); + // save current handshake hash + _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, pSecure->aFinishHash, sizeof(pSecure->aFinishHash)); + } + } + + // determine next state + if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) + { + // next state will be secure mode (handshaking complete) or send cipher change depending on client/server and if resuming or not + iNextState = pFinished->uNextState[pSecure->bSessionResume]; + } + else if (!pState->bServer) + { + // client will send either Finished or Certificate if a client cert was requested by the server + iNextState = (pState->iClientCertLevel != 0) ? ST3_SEND_CERT : ST3_SEND_FINISH; + } + return(iNextState); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSendHelloRequest + + \Description + Send HelloRequest handshake packet + + \Input *pState - module state reference + + \Output + int32_t - next state (ST3_RECV_HELLO) + + \Version 03/28/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSendHelloRequest(ProtoSSLRefT *pState) +{ + uint8_t strHead[4]; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send HelloRequest\n")); + + // setup the header + strHead[0] = SSL3_MSG_HELLO_REQUEST; + strHead[1] = 0; + strHead[2] = 0; + strHead[3] = 0; + + // setup packet for send + _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, sizeof(strHead), strHead, 0); + + // set up to receive ClientHello + return(ST3_RECV_HELLO); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvHelloRequest + + \Description + Receive HelloRequest handshake packet; we respond with a no_renegotation + warning alert, or unexpected_message if the connection is TLS 1.3. + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (current state) + + \Version 03/28/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvHelloRequest(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + SecureStateT *pSecure = pState->pSecure; + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process HelloRequest\n")); + + if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) + { + // respond with no_renegotation alert; if we were to implement renegotation, we'd move to ST3_SEND_HELLO + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_WARNING, SSL3_ALERT_DESC_NO_RENEGOTIATION); + } + else + { + // hello request is not a valid tls 1.3 message + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNEXPECTED_MESSAGE); + } + + return(pState->iState); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvNewSessionTicket + + \Description + Process NewSessionTicket handshake packet; see + https://tools.ietf.org/html/rfc8446#section-4.6.1 + + \Input *pState - module state reference + \Input *pData - packet data + \Input iDataSize - size of packet data + + \Output + int32_t - next state (ST3_SECURE) + + \Version 03/17/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvNewSessionTicket(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize) +{ + const uint8_t *pDataEnd = pData + iDataSize; + SecureStateT *pSecure = pState->pSecure; + SessionTicketT SessTick; + CryptHashTypeE eHashType = pSecure->pCipher->uPrfType; + int32_t iHashLen = CryptHashGetSize(eHashType); + + NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process NewSessionTicket\n")); + + // initialize session ticket memory + ds_memset(&SessTick, 0, sizeof(SessTick)); + // save receipt time + SessTick.uRecvTime = time(NULL); + // save resumption key + ds_memcpy_s(SessTick.aResumeKey, sizeof(SessTick.aResumeKey), pSecure->pResumeSecret, iHashLen); + // save hash type + SessTick.eHashType = eHashType; + + // parse required session ticket fields + SessTick.uLifetime = _SafeRead32(pData, pDataEnd); + pData += 4; + SessTick.uAgeAdd = _SafeRead32(pData, pDataEnd); + pData += 4; + + // copy ticket nonce + SessTick.uNonceLen = _SafeRead8(pData, pDataEnd); + pData += 1; + _SafeReadBytes(SessTick.aTicketNonce, sizeof(SessTick.aTicketNonce), pData, SessTick.uNonceLen, pDataEnd); + pData += SessTick.uNonceLen; + + // copy ticket length + SessTick.uTickLen = _SafeRead16(pData, pDataEnd); + pData += 2; + + // make sure we have enough space + if (SessTick.uTickLen > sizeof(SessTick.aTicketData)) + { + NetPrintf(("protossl: session ticket too large for session history buffer\n")); + return(ST3_SECURE); + } + + // copy ticket data + _SafeReadBytes(SessTick.aTicketData, sizeof(SessTick.aTicketData), pData, SessTick.uTickLen, pDataEnd); + pData += SessTick.uTickLen; + + // parse extensions length, if present + if (pData <= (pDataEnd-2)) + { + SessTick.uExtnLen = _SafeRead16(pData, pDataEnd); + } + + NetPrintfVerbose((pState->iVerbose, 0, "protossl: ticket_sni: %s:%d, ticket_lifetime=%d, ticket_age_add=0x%08x, nonce_len=%d, ticket_len=%d, extn_len=%d\n", + pState->strHost, SockaddrInGetPort(&pState->PeerAddr), SessTick.uLifetime, SessTick.uAgeAdd, SessTick.uNonceLen, SessTick.uTickLen, SessTick.uExtnLen)); + + // add ticket to session history + _SessionHistoryAdd(pSecure, pState->strHost, SockaddrInGetPort(&pState->PeerAddr), &SessTick, NULL, 0, NULL); + + return(ST3_SECURE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvHandshakeValidate + + \Description + Validate handshake message transition is permissible; as per + https://tools.ietf.org/html/rfc8446#section-4: "a peer which receives a + handshake message in an unexpected order MUST abort the handshake + with an "unexpected_message" alert." + + \Input *pState - protossl state + \Input *pSecure - secure state + \Input iNxtMsg - next message + + \Output + int32_t - true if validated, else false + + \Notes + This function validates that a given handshake state transition is possible + within the spec, but it does not validate that the transition is correct + given the specific set of parameters (ciphers/signature algorithms/etc) + selected. Such validation is left up to the specific handshake handler. + + \Version 05/02/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLRecvHandshakeValidate(ProtoSSLRefT *pState, SecureStateT *pSecure, int32_t iNxtMsg) +{ + static const SSLHandshakeMapT *_aHandshakeMaps[2][2] = + { + { _SSL3_ClientRecvMsgMap, _SSL3_ClientRecvMsgMap_13 }, + { _SSL3_ServerRecvMsgMap, _SSL3_ServerRecvMsgMap_13 } + }; + uint32_t uMapVersIdx = (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) ? 1 : 0; + const SSLHandshakeMapT *pMap; + int32_t iCount; + + for (iCount = 0, pMap = _aHandshakeMaps[pState->bServer][uMapVersIdx]; pMap[iCount].iCurMsg != -1; iCount += 1) + { + if (pSecure->iCurMsg != pMap[iCount].iCurMsg) + { + continue; + } + if (iNxtMsg != pMap[iCount].iNxtMsg) + { + continue; + } + break; + } + + NetPrintfVerbose((DEBUG_MSG_LIST || (pMap[iCount].iCurMsg == -1), 0, "protossl: %s handshake message state transition %s(%d)->%s(%d)\n", (pMap[iCount].iCurMsg != -1) ? "validated" : "invalid", + _ProtoSSLRecvGetHandshakeTypeName(pSecure->iCurMsg), pSecure->iCurMsg, + _ProtoSSLRecvGetHandshakeTypeName(iNxtMsg), iNxtMsg)); + + return(pMap[iCount].iCurMsg != -1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvHandshake + + \Description + Decode ssl handshake packet, validate message flow + + \Input *pState - protossl state + \Input *pSecure - secure state + \Input *pMsgType - [out] handshake message type + + \Output + uint8_t * - pointer to handshake packet start, or NULL if error + + \Version 11/10/2005 (gschaefer) +*/ +/********************************************************************************F*/ +static const uint8_t *_ProtoSSLRecvHandshake(ProtoSSLRefT *pState, SecureStateT *pSecure, uint8_t *pMsgType) +{ + uint8_t *pRecv = pSecure->RecvData+pSecure->iRecvHshkProg; + + // get message type + *pMsgType = pRecv[0]; + + // make sure next message is possible in handshake flow + if (!_ProtoSSLRecvHandshakeValidate(pState, pSecure, *pMsgType)) + { + return(NULL); + } + + // update current message + pSecure->iCurMsg = *pMsgType; + + // point to data + return(pRecv+4); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLRecvHandshakeFinish + + \Description + Complete processing of handshake packet, including computation of + handshake hash. + + \Input *pState - module state reference + + \Version 03/16/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLRecvHandshakeFinish(ProtoSSLRefT *pState) +{ + SecureStateT *pSecure = pState->pSecure; + + // if this is a handshake packet and we haven't already processed it + if ((pSecure->RecvHead[0] == SSL3_REC_HANDSHAKE) && (pSecure->iRecvSize > 0) && (pSecure->iRecvSize >= pSecure->iRecvHshkSize)) + { + // keep a running hash of received data for verify/finish hashes + _ProtoSSLHandshakeHashUpdate(pSecure, pSecure->RecvData+pSecure->iRecvHshkProg, pSecure->iRecvHshkSize, "recv"); + // consume the packet + pSecure->iRecvHshkProg += pSecure->iRecvHshkSize; + pSecure->iRecvHshkSize = 0; + } + + // see if all the data was consumed + if (pSecure->iRecvHshkProg == pSecure->iRecvSize) + { + _ProtoSSLRecvReset(pSecure); + } +} + +/* + main update loop +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLUpdateAsync + + \Description + Perform iterative operations + + \Input *pState - module state reference + \Input *pSecure - secure state reference + + \Output + int32_t - one if async operation occurred, else zero + + \Version 02/15/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLUpdateAsync(ProtoSSLRefT *pState, SecureStateT *pSecure) +{ + #if DEBUG_ENC_PERF + uint64_t uTickUsec = NetTickUsec(); + #endif + int32_t iResult; + // not in async state, return + if (pState->iState != ST3_PROC_ASYNC) + { + return(0); + } + // execute async op and check for completion + iResult = pState->AsyncInfo.pAsyncOp(pState); + #if DEBUG_ENC_PERF + NetPrintf(("protossl: async op took %qd us\n", NetTickDiff(NetTickUsec(), uTickUsec))); + #endif + if (iResult == 0) + { + pState->iState = pState->AsyncInfo.iNextState; + pState->AsyncInfo.iNextState = 0; + return(0); + } + else if (iResult < 0) + { + pState->iState = pState->AsyncInfo.iFailState; + pState->AsyncInfo.iNextState = 0; + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, pState->AsyncInfo.iFailAlert); + return(0); + } + // continuing to execute async op + return(ST3_PROC_ASYNC); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLUpdateSetAsyncState + + \Description + Set up for 'async' execution of pAsyncOp. Asynchronous in this + case means iterative execution from the main update loop. + + \Input *pState - module state + \Input *pAsyncOp - function to execute asynchronously + \Input iNextState - state to transition to on success + \Input iFailState - state to transition to on failure + \Input iFailAlert - alert to send on failure + + \Output + int32_t - updated state (ST3_PROC_ASYNC) + + \Version 02/16/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLUpdateSetAsyncState(ProtoSSLRefT *pState, AsyncOpT *pAsyncOp, int32_t iNextState, int32_t iFailState, int32_t iFailAlert) +{ + pState->iState = ST3_PROC_ASYNC; + pState->AsyncInfo.iNextState = iNextState; + pState->AsyncInfo.iFailState = iFailState; + pState->AsyncInfo.iFailAlert = iFailAlert; + pState->AsyncInfo.pAsyncOp = pAsyncOp; + return(_ProtoSSLUpdateAsync(pState, pState->pSecure)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLUpdateSend + + \Description + Send data on a secure connection + + \Input *pState - module state reference + \Input *pSecure - secure state reference + + \Output + int32_t - one if data was sent, else zero + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLUpdateSend(ProtoSSLRefT *pState, SecureStateT *pSecure) +{ + // handle send states if output buffer is empty + if (pSecure->iSendProg == pSecure->iSendSize) + { + // deal with send states + if (pState->iState == ST3_SEND_HELLO) + { + pState->iState = pState->bServer ? _ProtoSSLSendServerHello(pState) : _ProtoSSLSendClientHello(pState); + } + else if (pState->iState == ST3_SEND_HELLO_RETRY) + { + pState->iState = _ProtoSSLSendHelloRetryRequest(pState); + } + else if (pState->iState == ST3_SEND_EXTN) + { + pState->iState = _ProtoSSLSendEncryptedExtensions(pState); + } + else if (pState->iState == ST3_SEND_CERT) + { + pState->iState = _ProtoSSLSendCertificate(pState); + } + else if (pState->iState == ST3_SEND_CERT_REQ) + { + pState->iState = _ProtoSSLSendCertificateRequest(pState); + } + else if (pState->iState == ST3_SEND_DONE) + { + pState->iState = _ProtoSSLSendServerHelloDone(pState); + } + else if (pState->iState == ST3_SEND_KEY) + { + pState->iState = pState->bServer ? _ProtoSSLSendServerKeyExchange(pState) : _ProtoSSLSendClientKeyExchange(pState); + } + else if (pState->iState == ST3_SEND_VERIFY) + { + pState->iState = _ProtoSSLSendCertificateVerify(pState); + } + else if (pState->iState == ST3_SEND_CHANGE) + { + pState->iState = _ProtoSSLSendChangeCipherSpec(pState); + } + else if (pState->iState == ST3_SEND_FINISH) + { + pState->iState = _ProtoSSLSendFinished(pState); + } + else if (pState->iState == ST3_SEND_HELLO_REQUEST) + { + pState->iState = _ProtoSSLSendHelloRequest(pState); + } + } + + // send any queued data + return(_ProtoSSLSendSecure(pState, pState->pSecure)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLUpdateRecvHandshake + + \Description + Process a handshake message + + \Input *pState - module state reference + \Input *pSecure - secure state reference + + \Output + uint32_t - FALSE if there was an error, else TRUE + + \Version 11/14/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _ProtoSSLUpdateRecvHandshake(ProtoSSLRefT *pState, SecureStateT *pSecure) +{ + uint8_t bUnhandledMsg = FALSE, uMsgType; + const uint8_t *pData; + int32_t iDataSize; + + // make sure it's a handshake message + if (pSecure->RecvHead[0] != SSL3_REC_HANDSHAKE) + { + /* as per https://tools.ietf.org/html/rfc8446#section-5, if a TLS implementation receives an unexpected record type, + it MUST terminate the connection with an "unexpected_message" alert */ + NetPrintf(("protossl: received unexpected record %s (%d) when expecting a handshake record\n", _ProtoSSLRecvGetRecordTypeName(pSecure->RecvHead[0]), pSecure->RecvHead[0])); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNEXPECTED_MESSAGE); + return(FALSE); + } + + // calculate handshake packet size + if (pSecure->iRecvHshkSize == 0) + { + pData = pSecure->RecvData + pSecure->iRecvHshkProg; + pSecure->iRecvHshkSize = ((pData[1]<<16)|(pData[2]<<8)|(pData[3]<<0)) + SSL3_MSG_HEADER_SIZE; + // make sure packet fits in our buffer + if ((pSecure->iRecvHshkProg+pSecure->iRecvHshkSize) > (int32_t)sizeof(pSecure->RecvData)) + { + NetPrintf(("protossl: _ProtoSSLUpdateRecvHandshake: packet at base %d is too long (%d vs %d)\n", pSecure->iRecvHshkProg, + pSecure->iRecvHshkSize, sizeof(pSecure->RecvData)-pSecure->iRecvHshkProg)); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR); + return(FALSE); + } + } + + // make sure we have the entire packet before proceeding + if (pSecure->iRecvSize < pSecure->iRecvHshkSize) + { + // receive next packet header + pSecure->iRecvHead = 0; + pSecure->bRecvProc = FALSE; + return(TRUE); + } + + // size of handshake data is handshake size minus header size + iDataSize = pSecure->iRecvHshkSize - SSL3_MSG_HEADER_SIZE; + + // get handshake message and ensure it is valid in the handshake state machine + if ((pData = _ProtoSSLRecvHandshake(pState, pSecure, &uMsgType)) == NULL) + { + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNEXPECTED_MESSAGE); + return(FALSE); + } + + // process handshake message based on state + if (pState->iState == ST3_RECV_HELLO) + { + if (uMsgType == SSL3_MSG_CLIENT_HELLO) + { + pState->iState = _ProtoSSLRecvClientHello(pState, pData, iDataSize); + } + else if (uMsgType == SSL3_MSG_SERVER_HELLO) + { + pState->iState = _ProtoSSLRecvServerHello(pState, pData, iDataSize); + } + else if (uMsgType == SSL3_MSG_ENCRYPTED_EXTENSIONS) + { + pState->iState = _ProtoSSLRecvEncryptedExtensions(pState, pData, iDataSize); + } + else if (uMsgType == SSL3_MSG_CERTIFICATE) + { + pState->iState = _ProtoSSLRecvCertificate(pState, pData, iDataSize); + } + else if (uMsgType == SSL3_MSG_CERT_REQ) + { + pState->iState = _ProtoSSLRecvCertificateRequest(pState, pData, iDataSize); + } + else if (uMsgType == SSL3_MSG_CERT_VERIFY) + { + pState->iState = _ProtoSSLRecvCertificateVerify(pState, pData, iDataSize); + } + else if (uMsgType == SSL3_MSG_CLIENT_KEY) + { + pState->iState = _ProtoSSLRecvClientKeyExchange(pState, pData, iDataSize); + } + else if (uMsgType == SSL3_MSG_SERVER_KEY) + { + pState->iState = _ProtoSSLRecvServerKeyExchange(pState, pData, iDataSize); + } + else if (uMsgType == SSL3_MSG_SERVER_DONE) + { + pState->iState = _ProtoSSLRecvServerHelloDone(pState, pData, iDataSize); + } + else + { + bUnhandledMsg = TRUE; + } + } + else if (pState->iState == ST3_RECV_FINISH) + { + if (uMsgType == SSL3_MSG_FINISHED) + { + pState->iState = _ProtoSSLUpdateRecvFinished(pState, pData, iDataSize); + } + else + { + bUnhandledMsg = TRUE; + } + } + else if (pState->iState == ST3_SECURE) + { + // handle post-finish handshake messages + if (uMsgType == SSL3_MSG_HELLO_REQUEST) + { + pState->iState = _ProtoSSLRecvHelloRequest(pState, pData, iDataSize); + } + else if (uMsgType == SSL3_MSG_NEW_SESSION_TICKET) + { + pState->iState = _ProtoSSLRecvNewSessionTicket(pState, pData, iDataSize); + } + // finish here since we won't in _ProtoSSLUpdateRecvPacket() + _ProtoSSLRecvHandshakeFinish(pState); + } + else + { + bUnhandledMsg = TRUE; + } + + // fatal alert if message was unhandled + if (bUnhandledMsg) + { + NetPrintf(("protossl: unhandled handshake message %s(%d) in state %d\n", _ProtoSSLRecvGetHandshakeTypeName(uMsgType), uMsgType, pState->iState)); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNEXPECTED_MESSAGE); + } + + return(!bUnhandledMsg); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLUpdateRecvPacket + + \Description + Process a received packet + + \Input *pState - module state reference + \Input *pSecure - secure state reference + + \Version 11/14/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLUpdateRecvPacket(ProtoSSLRefT *pState, SecureStateT *pSecure) +{ + int32_t iState = pState->iState; + + if (pSecure->RecvHead[0] == SSL3_REC_ALERT) + { + _ProtoSSLRecvAlert(pState, pSecure); + } + else if ((pSecure->RecvHead[0] == SSL3_REC_CIPHER) && (pState->iState == ST3_RECV_CHANGE)) + { + if ((pState->iState = _ProtoSSLRecvChangeCipherSpec(pState, pSecure->RecvData+pSecure->iRecvHshkProg, pSecure->iRecvSize)) != ST3_RECV_CHANGE) + { + pSecure->iRecvHshkProg = pSecure->iRecvSize; + } + } + else if (!_ProtoSSLUpdateRecvHandshake(pState, pSecure)) + { + pState->iClosed = 1; + _ProtoSSLRecvReset(pSecure); + pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE; + } + + // output final crypto timing, if we have just reached secure state + if ((iState != pState->iState) && (pState->iState == ST3_SECURE) && (pSecure->uTimer > 0)) + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: SSL Perf (setup) %dms\n", pSecure->uTimer)); + pSecure->uTimer = 0; + } + + /* finish recv handshake processing: due to the fact that the recv hello state handles which state we are in based on the + packet data we want to move forward regardless. when we are not in the recv hello state we want to make sure to to only + move to the next state when the state changes to allow for any additional processing that happens over multiple frames. */ + if ((pState->iState == ST3_RECV_HELLO) || (iState != pState->iState)) + { + _ProtoSSLRecvHandshakeFinish(pState); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLUpdateRecvValidate + + \Description + Validate received packet + + \Input *pState - module state reference + \Input *pSecure - secure state reference + + \Version 11/14/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoSSLUpdateRecvValidate(ProtoSSLRefT *pState, SecureStateT *pSecure) +{ + /* validate the format version, and make sure it is a 3.x protocol version. we make an assumption here that + the 3.x packet format will not change in future revisions to the TLS protocol (this was confirmed with the + 1.3 version of the protocol). validation of the specific protocol version is handled during handshaking. */ + if (pSecure->RecvHead[1] == (SSL3_VERSION>>8)) + { + uint32_t uRecvSize = (pSecure->RecvHead[3]<<8)|(pSecure->RecvHead[4]<<0); + // check data length to make sure it is valid; as per http://tools.ietf.org/html/rfc5246#section-6.2.1: The length MUST NOT exceed 2^14+2^11 + if (uRecvSize > SSL_RCVMAX_PACKET) + { + NetPrintf(("protossl: received oversized packet (%d/%d); terminating connection\n", uRecvSize, SSL_RCVMAX_PACKET)); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_RECORD_OVERFLOW); + pState->iClosed = 1; + _ProtoSSLRecvReset(pSecure); + pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE; + } + // save data offset + pSecure->iRecvBase = pSecure->iRecvSize; + // add in the data length + pSecure->iRecvSize += uRecvSize; + // make sure it doesn't exceed our buffer + if (pSecure->iRecvSize > (int32_t)sizeof(pSecure->RecvData)) + { + NetPrintf(("protossl: packet length of %d is too large\n", pSecure->iRecvSize)); + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_RECORD_OVERFLOW); + pState->iClosed = 1; + _ProtoSSLRecvReset(pSecure); + pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE; + } + } + else + { + if ((pSecure->RecvHead[0] == 0x80) && (pSecure->RecvHead[2] == 1)) + { + NetPrintf(("protossl: received %d byte SSLv2 ClientHello offering SSLv%d.%d; disconnecting\n", + pSecure->RecvHead[1], pSecure->RecvHead[3], pSecure->RecvHead[4])); + pState->iState = ST_FAIL_CONN_SSL2; + } + else + { + NetPrintf(("protossl: received what appears to be a non-SSL connection attempt; disconnecting\n")); + pState->iState = ST_FAIL_CONN_NOTSSL; + } + pState->iClosed = 1; + _ProtoSSLRecvReset(pSecure); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLUpdateRecv + + \Description + Receive data on a secure connection + + \Input *pState - module state reference + \Input *pSecure - secure state reference + + \Output + int32_t - 1=data was received, else 0 + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLUpdateRecv(ProtoSSLRefT *pState, SecureStateT *pSecure) +{ + int32_t iResult, iXfer = 0; + + // receive ssl record header + if (pSecure->iRecvHead < SSL_MIN_PACKET) + { + iResult = SocketRecv(pState->pSock, (char *)pSecure->RecvHead+pSecure->iRecvHead, SSL_MIN_PACKET-pSecure->iRecvHead, 0); + if (iResult > 0) + { + pSecure->iRecvHead += iResult; + iXfer = 1; + } + if (iResult < 0) + { + pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE; + pState->iClosed = 1; + } + } + // wait for a complete ssl record header before passing this point + if (pSecure->iRecvHead < SSL_MIN_PACKET) + { + return(iXfer); + } + + /* see if we can determine the full packet size + this needs to handle the following situations: + - initial receive of a packet (just the header has been received) + - receive of the second or later packet in a fragmented handshake packet + it needs to NOT be executed in the following scenario: + - when processing a packet that has been received but has not had all of its handshake packets processed */ + if ((pSecure->iRecvProg == pSecure->iRecvSize) && !pSecure->bRecvProc) + { + _ProtoSSLUpdateRecvValidate(pState, pSecure); + } + + // finish receiving ssl record data + if (pSecure->iRecvProg < pSecure->iRecvSize) + { + iResult = SocketRecv(pState->pSock, (char *)pSecure->RecvData+pSecure->iRecvProg, pSecure->iRecvSize-pSecure->iRecvProg, 0); + if (iResult > 0) + { + pSecure->iRecvProg += iResult; + iXfer = 1; + } + if (iResult < 0) + { + pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE; + pState->iClosed = 1; + } + } + + // handle decryption and data validation + if ((pSecure->iRecvProg == pSecure->iRecvSize) && !pSecure->bRecvProc) + { + uint8_t bFirstRecord = (pSecure->iRecvBase == 0) ? TRUE : FALSE; + int32_t iAlert = 0; + // at end of receive, process the packet + if ((iResult = _ProtoSSLRecvPacket(pState, &iAlert)) < 0) + { + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, iAlert); + pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE; + _ProtoSSLRecvReset(pSecure); + pState->iClosed = 1; + } + // remember we've received it + pSecure->bRecvProc = (pSecure->iRecvSize > 0) ? TRUE : FALSE; + // initial handshake offset needs to be updated to skip stuff decryption might have skipped (e.g. AEAD explicit IV in tls1.2) + if (bFirstRecord) + { + pSecure->iRecvHshkProg = pSecure->iRecvBase; + } + } + + // process received packet - note that this might get called multiple times per ssl record, or one or more times per multiple ssl records + if ((pSecure->iRecvSize > 0) && (pSecure->iRecvProg == pSecure->iRecvSize) && ((pSecure->RecvHead[0] != SSL3_REC_APPLICATION) || (pState->iState == ST3_RECV_HELLO)) && (pState->iClosed == 0)) + { + _ProtoSSLUpdateRecvPacket(pState, pSecure); + } + + return(iXfer); +} + +/* + on-demand ca install +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLInitiateCARequest + + \Description + Initiate CA fetch request + + \Input *pState - module state reference + + \Output + int32_t - 0=success, negative=failure + + \Version 02/28/2012 (szhu) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLInitiateCARequest(ProtoSSLRefT *pState) +{ + // we allow only one fetch request for each ssl negotiation attempt + if (pState->bCertInfoSet && (pState->iCARequestId <= 0) && !pState->bServer) + { + if ((pState->iCARequestId = DirtyCertCARequestCert(&pState->CertInfo, pState->strHost, SockaddrInGetPort(&pState->PeerAddr))) > 0) + { + // save the failure cert, it will be validated again after fetching CA cert + if (pState->pCertToVal == NULL) + { + pState->pCertToVal = DirtyMemAlloc(sizeof(*pState->pCertToVal), PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + // continue to fetch CA cert even if we fail to allocate cert memory + if (pState->pCertToVal != NULL) + { + ds_memcpy(pState->pCertToVal, &pState->pSecure->Cert, sizeof(*pState->pCertToVal)); + } + return(0); + } + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLUpdateCARequest + + \Description + Update CA fetch request status + + \Input *pState - module state reference + + \Output + int32_t - updated module state + + \Version 02/28/2012 (szhu) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLUpdateCARequest(ProtoSSLRefT *pState) +{ + int32_t iComplete; + // see if request completed + if ((iComplete = DirtyCertCARequestDone(pState->iCARequestId)) != 0) + { + DirtyCertCARequestFree(pState->iCARequestId); + pState->iCARequestId = 0; + // if CA fetch request failed + if (iComplete < 0) + { + _CertificateSetFailureInfo(pState, pState->pCertToVal, TRUE); + pState->iState = ST_FAIL_CERT_REQUEST; + } + // if cert not validated + else if ((pState->pCertToVal == NULL) || (_ProtoSSLVerifyCertificate(pState, pState->pSecure, pState->pCertToVal, FALSE) != 0)) + { + _CertificateSetFailureInfo(pState, pState->pCertToVal, TRUE); + pState->iState = ST_FAIL_CERT_NOTRUST; + } + else + { + // cert validated + #if DIRTYCODE_LOGGING + char strIdentSubject[512], strIdentIssuer[512]; + NetPrintfVerbose((pState->iVerbose, 0, "protossl: cert (%s) validated by ca (%s)\n", _CertificateDebugFormatIdent(&pState->pCertToVal->Subject, strIdentSubject, sizeof(strIdentSubject)), + _CertificateDebugFormatIdent(&pState->pCertToVal->Issuer, strIdentIssuer, sizeof(strIdentIssuer)))); + #endif + pState->iState = ST3_RECV_HELLO; + } + + if (pState->pCertToVal != NULL) + { + DirtyMemFree(pState->pCertToVal, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->pCertToVal = NULL; + } + } + return(pState->iState); +} + +/* + module state management +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLAllocSecureState + + \Description + Allocate secure state + + \Input iMemGroup - memgroup info for alloc + \Input *pMemGroupUserData - memgroup info for alloc + + \Output + SecureStateT * - secure state, or null on alloc failure + + \Version 03/17/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static SecureStateT *_ProtoSSLAllocSecureState(int32_t iMemGroup, void *pMemGroupUserData) +{ + SecureStateT *pSecure = DirtyMemAlloc(sizeof(*pSecure), PROTOSSL_MEMID, iMemGroup, pMemGroupUserData); + if (pSecure != NULL) + { + ds_memclr(pSecure, sizeof(*pSecure)); + } + return(pSecure); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLResetSecureState + + \Description + Reset secure state. Does not affect the TCP connection, if any. + + \Input *pState - Reference pointer + \Input iSecure - secure status (0=disabled, 1=enabled) + + \Output + int32_t - SOCKERR_NONE on success, SOCKERR_NOMEM on failure + + \Version 03/17/2010 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLResetSecureState(ProtoSSLRefT *pState, int32_t iSecure) +{ + SecureStateT *pSecure; + + // acquire access to secure state + NetCritEnter(&pState->SecureCrit); + + // see if we need to get rid of secure state + if (!iSecure && (pState->pSecure != NULL)) + { + DirtyMemFree(pState->pSecure, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->pSecure = NULL; + } + + // see if we need to alloc secure state + if (iSecure && (pState->pSecure == NULL)) + { + pState->pSecure = _ProtoSSLAllocSecureState(pState->iMemGroup, pState->pMemGroupUserData); + } + + // reset secure state if present + if ((pSecure = pState->pSecure) != NULL) + { + // clear secure state + ds_memclr(pSecure, sizeof(*pSecure)); + + // reset handshake hashes + _ProtoSSLHandshakeHashInit(pSecure); + } + + // release access to secure state + NetCritLeave(&pState->SecureCrit); + + // return allocate error if secure wanted and failed + return((iSecure && !pSecure) ? SOCKERR_NOMEM : SOCKERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLResetState + + \Description + Reset connection back to unconnected state (will disconnect from server). + + \Input *pState - Reference pointer + \Input iSecure - to be completed + + \Output + int32_t - SOCKERR_NONE on success, SOCKERR_NOMEM on failure + + \Version 03/25/2004 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLResetState(ProtoSSLRefT *pState, int32_t iSecure) +{ + // close socket if needed + if (pState->pSock != NULL) + { + pState->iLastSocketError = SocketInfo(pState->pSock, 'serr', 0, NULL, 0); + SocketClose(pState->pSock); + pState->pSock = NULL; + } + + // done with resolver record + if (pState->pHost != NULL) + { + pState->pHost->Free(pState->pHost); + pState->pHost = NULL; + } + + // free dirtycert certificate + if (pState->pCertToVal != NULL) + { + DirtyMemFree(pState->pCertToVal, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->pCertToVal = NULL; + } + + // done with dirtycert + if (pState->iCARequestId > 0) + { + DirtyCertCARequestFree(pState->iCARequestId); + } + pState->iCARequestId = 0; + + // reset the state + pState->iState = ST_IDLE; + pState->iClosed = 1; + pState->uAlertLevel = 0; + pState->uAlertValue = 0; + pState->bAlertSent = FALSE; + + // reset secure state + return(_ProtoSSLResetSecureState(pState, iSecure)); +} + +/* + trusted store management +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLChkCACert + + \Description + Check to see if the given CA cert already exists in our list of + CA certs. + + \Input *pNewCACert - pointer to new CA cert to check for duplicates of + + \Output + int32_t - zero=not duplicate, non-zero=duplicate + + \Version 05/11/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLChkCACert(const X509CertificateT *pNewCACert) +{ + const ProtoSSLCACertT *pCACert; + + for (pCACert = _ProtoSSL_CACerts; pCACert != NULL; pCACert = pCACert->pNext) + { + if (!_CertificateCompareIdent(&pCACert->Subject, &pNewCACert->Subject, TRUE) && (pCACert->iKeyModSize == pNewCACert->iKeyModSize) && + !memcmp(pCACert->pKeyModData, pNewCACert->KeyModData, pCACert->iKeyModSize)) + { + break; + } + } + + return(pCACert != NULL); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLAddCACert + + \Description + Add a new CA certificate to certificate list + + \Input *pCert - pointer to new cert to add + \Input bVerified - TRUE if verified, else false + \Input iMemGroup - memgroup to use for alloc + \Input *pMemGroupUserData - memgroup userdata for alloc + + \Output + int32_t - zero=error/duplicate, one=added + + \Version 01/13/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLAddCACert(X509CertificateT *pCert, uint8_t bVerified, int32_t iMemGroup, void *pMemGroupUserData) +{ + ProtoSSLCACertT *pCACert; + int32_t iCertSize = sizeof(*pCACert) + pCert->iKeyModSize; + + // see if this certificate already exists + if (_ProtoSSLChkCACert(pCert)) + { + _CertificateDebugPrint(pCert, "ignoring redundant add of CA cert"); + return(0); + } + + // find append point for new CA + for (pCACert = _ProtoSSL_CACerts; pCACert->pNext != NULL; pCACert = pCACert->pNext) + ; + + // allocate new record + if ((pCACert->pNext = (ProtoSSLCACertT *)DirtyMemAlloc(iCertSize, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + _CertificateDebugPrint(pCert, "failed to allocate memory for cert"); + return(0); + } + + // clear allocated memory + pCACert = pCACert->pNext; + ds_memclr(pCACert, iCertSize); + + // if this cert has not already been verified, allocate memory for X509 cert data and copy the X509 cert data for later validation + if (!bVerified) + { + if ((pCACert->pX509Cert = (X509CertificateT *)DirtyMemAlloc(sizeof(*pCert), PROTOSSL_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + _CertificateDebugPrint(pCert, "failed to allocate memory for X509 cert"); + DirtyMemFree(pCACert->pNext, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData); + pCACert->pNext = NULL; + return(0); + } + // copy cert data + ds_memcpy(pCACert->pX509Cert, pCert, sizeof(*pCert)); + } + + // copy textual identity of this certificate + // (don't need to save issuer since we already trust this certificate) + ds_memcpy(&pCACert->Subject, &pCert->Subject, sizeof(pCACert->Subject)); + + // copy key info + pCACert->iKeyType = pCert->iKeyType; + pCACert->iCrvType = pCert->iCrvType; + + // copy exponent data + pCACert->iKeyExpSize = pCert->iKeyExpSize; + ds_memcpy(pCACert->KeyExpData, pCert->KeyExpData, pCACert->iKeyExpSize); + + // copy modulus data, immediately following header + pCACert->iKeyModSize = pCert->iKeyModSize; + pCACert->pKeyModData = (uint8_t *)pCACert + sizeof(*pCACert); + ds_memcpy((uint8_t *)pCACert->pKeyModData, pCert->KeyModData, pCACert->iKeyModSize); + + // save memgroup and user info used to allocate + pCACert->iMemGroup = iMemGroup; + + _CertificateDebugPrint(pCert, "added new certificate authority"); + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSetCACert + + \Description + Add one or more X.509 CA certificates to the trusted list. A + certificate added will be available to all ProtoSSL modules for + the lifetime of the application. This functional can add one or more + PEM certificates or a single DER certificate. + + \Input *pCertData - pointer to certificate data (PEM or DER) + \Input iCertSize - size of certificate data + \Input bVerify - if TRUE verify cert chain on add + + \Output + int32_t - negative=error, positive=count of CAs added + + \Notes + The certificate must be in .DER (binary) or .PEM (base64-encoded) + format. + + \Version 01/13/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoSSLSetCACert(const uint8_t *pCertData, int32_t iCertSize, uint8_t bVerify) +{ + int32_t iResult, iCount = -1; + X509CertificateT Cert; + uint8_t *pCertBuffer = NULL; + const int32_t _iMaxCertSize = 4096; + const uint8_t *pCertBeg, *pCertEnd; + int32_t iMemGroup; + uint32_t uCertType; + void *pMemGroupUserData; + SecureStateT *pSecure; + + #if DIRTYCODE_LOGGING + uint32_t uTick = NetTick(); + #endif + + // process PEM signature if present + if (_CertificateFindData(pCertData, iCertSize, &pCertBeg, &pCertEnd, &uCertType) == 0) + { + // no markers -- consume all the data + pCertBeg = pCertData; + pCertEnd = pCertData+iCertSize; + } + + // remember remaining data for possible further parsing + iCertSize -= pCertEnd-pCertData; + pCertData = pCertEnd; + + // get memgroup settings for certificate blob + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // if the cert is base64 encoded we decode it; otherwise we assume it is binary and parse it directly + if ((iResult = Base64Decode2((int32_t)(pCertEnd-pCertBeg), (const char *)pCertBeg, NULL)) > 0) + { + if (iResult > _iMaxCertSize) + { + return(-111); + } + // allocate cert buffer + if ((pCertBuffer = (uint8_t *)DirtyMemAlloc(_iMaxCertSize, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + return(-112); + } + // decode the cert + Base64Decode2((int32_t)(pCertEnd-pCertBeg), (const char *)pCertBeg, (char *)pCertBuffer); + pCertBeg = pCertBuffer; + pCertEnd = pCertBeg+iResult; + } + + // allocate temporary secure state to verify certificate with + if ((pSecure = _ProtoSSLAllocSecureState(iMemGroup, pMemGroupUserData)) == NULL) + { + DirtyMemFree(pCertBuffer, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData); + return(-113); + } + + // parse the x.509 certificate onto stack + if ((iResult = _AsnParseCertificate(&Cert, pCertBeg, (int32_t)(pCertEnd-pCertBeg))) == 0) + { + // verify signature of this certificate (self-signed allowed) + if (!bVerify || ((iResult = _ProtoSSLVerifyCertificate(NULL, pSecure, &Cert, TRUE)) == 0)) + { + // add certificate to CA list + iCount = _ProtoSSLAddCACert(&Cert, bVerify, iMemGroup, pMemGroupUserData); + } + } + + // if CA was PEM encoded and there is extra data, check for more CAs + while ((iResult == 0) && (iCertSize > 0) && (_CertificateFindData(pCertData, iCertSize, &pCertBeg, &pCertEnd, &uCertType) != 0)) + { + // remember remaining data for possible further parsing + iCertSize -= pCertEnd-pCertData; + pCertData = pCertEnd; + + // cert must be base64 encoded + if (((iResult = Base64Decode2((int32_t)(pCertEnd-pCertBeg), (const char *)pCertBeg, NULL)) <= 0) || (iResult > _iMaxCertSize)) + { + break; + } + Base64Decode2((int32_t)(pCertEnd-pCertBeg), (const char *)pCertBeg, (char *)pCertBuffer); + + // parse the x.509 certificate onto stack + if ((iResult = _AsnParseCertificate(&Cert, pCertBuffer, iResult)) < 0) + { + continue; + } + + // verify signature of this certificate (self-signed allowed) + if (bVerify && ((iResult = _ProtoSSLVerifyCertificate(NULL, pSecure, &Cert, TRUE)) < 0)) + { + continue; + } + + // add certificate to CA list + iCount += _ProtoSSLAddCACert(&Cert, bVerify, iMemGroup, pMemGroupUserData); + } + + // cleanup temp secure state + DirtyMemFree(pSecure, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData); + + // cleanup temp allocation + if (pCertBuffer != NULL) + { + DirtyMemFree(pCertBuffer, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData); + } + + NetPrintf(("protossl: SSL Perf (CA load) %dms\n", NetTickDiff(NetTick(), uTick))); + return(iCount); +} + +/* + misc helpers +*/ + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLSetSockOpt + + \Description + Set socket options + + \Input *pState - module state + + \Version 09/12/2012 (jbrookes) Refactored from ProtoSSLBind() and ProtoSSLConnect() +*/ +/********************************************************************************F*/ +static void _ProtoSSLSetSockOpt(ProtoSSLRefT *pState) +{ + // set debug level + SocketControl(pState->pSock, 'spam', pState->iVerbose, NULL, NULL); + + // set recv/send buffer size? + if (pState->iRecvBufSize != 0) + { + SocketControl(pState->pSock, 'rbuf', pState->iRecvBufSize, NULL, NULL); + } + if (pState->iSendBufSize != 0) + { + SocketControl(pState->pSock, 'sbuf', pState->iSendBufSize, NULL, NULL); + } + + // set max send/recv rate? + if (pState->iMaxRecvRate != 0) + { + SocketControl(pState->pSock, 'maxr', pState->iMaxRecvRate, NULL, NULL); + } + if (pState->iMaxSendRate != 0) + { + SocketControl(pState->pSock, 'maxs', pState->iMaxSendRate, NULL, NULL); + } + + // set keep-alive options? + if (pState->bKeepAlive != 0) + { + SocketControl(pState->pSock, 'keep', pState->bKeepAlive, &pState->uKeepAliveTime, &pState->uKeepAliveTime); + } + + // set nodelay? + if (pState->bNoDelay) + { + SocketControl(pState->pSock, 'ndly', TRUE, NULL, NULL); + } + + // set reuseaddr + if (pState->bReuseAddr) + { + SocketControl(pState->pSock, 'radr', TRUE, NULL, NULL); + } + + // if async receive is enabled set it on the socket and adjust the packet queue to fit a large packet (SSL_RCVMAX_PACKET) + if (pState->bAsyncRecv) + { + SocketControl(pState->pSock, 'arcv', TRUE, NULL, NULL); + SocketControl(pState->pSock, 'pque', (SSL_RCVMAX_PACKET / SOCKET_MAXUDPRECV) + 1, NULL, NULL); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoSSLCacheLocalAddress + + \Description + Cache value of our local address being used, some connections are very short + lived making it otherwise difficult to read the local addr info reliably + + \Input *pState - reference pointer + + \Version 09/13/2017 (cvienneau) used in Qos2.0 +*/ +/********************************************************************************F*/ +static void _ProtoSSLCacheLocalAddress(ProtoSSLRefT *pState) +{ + if (pState->pSock != NULL) + { + if (SocketInfo(pState->pSock, 'bind', 0, &pState->LocalAddr, sizeof(pState->LocalAddr)) != 0) + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: _ProtoSSLCacheLocalAddress failed to read local address info from 'bind'.\n")); + } + } + else + { + NetPrintfVerbose((pState->iVerbose, 0, "protossl: _ProtoSSLCacheLocalAddress socket is NULL.\n")); + } +} + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ProtoSSLStartup + + \Description + Start up ProtoSSL. Used to create global state shared across SSL refs. + + \Output + int32_t - negative=failure, else success + + \Version 09/14/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLStartup(void) +{ + ProtoSSLStateT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + + // make sure we haven't already been called + if (_ProtoSSL_pState != NULL) + { + NetPrintf(("protossl: global state already allocated\n")); + return(-1); + } + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pState = DirtyMemAlloc(sizeof(*pState), PROTOSSL_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protossl: could not allocate global state\n")); + return(-1); + } + ds_memclr(pState, sizeof(*pState)); + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + NetCritInit(&pState->StateCrit, "ProtoSSL Global State"); + + // initalize cyptrand module + if (CryptRandInit() != 0) + { + NetCritKill(&pState->StateCrit); + DirtyMemFree(pState, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData); + return(-1); + } + + // set global defaults + pState->iDfltCiph = PROTOSSL_CIPHER_ALL; + pState->iDfltVers = SSL3_VERSION; + pState->iDfltMinVers = SSL3_TLS1_0; + pState->iDfltCurves = PROTOSSL_CURVE_ALL; + + // save state ref + _ProtoSSL_pState = pState; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLShutdown + + \Description + Shut down ProtoSSL. Cleans up global state. + + \Version 09/14/2012 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoSSLShutdown(void) +{ + ProtoSSLStateT *pState = _ProtoSSL_pState; + + // make sure we haven't already been called + if (pState == NULL) + { + NetPrintf(("protossl: global state not allocated\n")); + return; + } + + // deallocate stored CAs + ProtoSSLClrCACerts(); + + // shutdown cyptrand module + CryptRandShutdown(); + + // shut down, deallocate resources + NetCritKill(&pState->StateCrit); + DirtyMemFree(pState, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + + // clear state pointer + _ProtoSSL_pState = NULL; +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLCreate + + \Description + Allocate an SSL connection and prepare for use + + \Output + ProtoSSLRefT * - module state; NULL=failure + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +ProtoSSLRefT *ProtoSSLCreate(void) +{ + ProtoSSLRefT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pState = DirtyMemAlloc(sizeof(*pState), PROTOSSL_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protossl: could not allocate module state\n")); + return(NULL); + } + ds_memclr(pState, sizeof(*pState)); + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserData = pMemGroupUserData; + + // set defaults + pState->iLastSocketError = SOCKERR_NONE; + pState->iVerbose = SSL_VERBOSE_DEFAULT; + pState->bSessionResumeEnabled = TRUE; + pState->iCurveDflt = SSL3_CURVE_DEFAULT; + pState->uHelloExtn = PROTOSSL_HELLOEXTN_DEFAULT; + + // set defaults with global overrides + if (_ProtoSSL_pState != NULL) + { + pState->uSslVersion = _ProtoSSL_pState->iDfltVers; + pState->uSslVersionMin = _ProtoSSL_pState->iDfltMinVers; + pState->uEnabledCiphers = _ProtoSSL_pState->iDfltCiph; + pState->uEnabledCurves = _ProtoSSL_pState->iDfltCurves; + } + else + { + pState->uSslVersion = SSL3_VERSION; + pState->uSslVersionMin = SSL3_TLS1_0; + pState->uEnabledCiphers = PROTOSSL_CIPHER_ALL; + pState->uEnabledCurves = PROTOSSL_CURVE_ALL; + } + + // init secure state critical section + NetCritInit(&pState->SecureCrit, "ProtoSSL Secure State"); + + // return module state + return(pState); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLReset + + \Description + Reset connection back to unconnected state (will disconnect from server). + + \Input *pState - module state reference + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +void ProtoSSLReset(ProtoSSLRefT *pState) +{ + // reset to unsecure mode + _ProtoSSLResetState(pState, 0); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLDestroy + + \Description + Destroy the module and release its state. Will disconnect from the + server if required. + + \Input *pState - module state reference + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +void ProtoSSLDestroy(ProtoSSLRefT *pState) +{ + // reset to unsecure mode (free all secure resources) + _ProtoSSLResetState(pState, 0); + // free certificate, if allocated + if (pState->pCertificate != NULL) + { + DirtyMemFree(pState->pCertificate, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + // free private key, if allocated + if (pState->pPrivateKey != NULL) + { + DirtyMemFree(pState->pPrivateKey, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + // kill critical section + NetCritKill(&pState->SecureCrit); + // free remaining state + DirtyMemFree(pState, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLAccept + + \Description + Accept an incoming connection. + + \Input *pState - module state reference + \Input iSecure - flag indicating use of secure connection: 1=secure, 0=unsecure + \Input *pAddr - where the client's address should be written + \Input *pAddrlen - the length of the client's address space + + \Output + ProtoSSLRefT * - accepted connection or NULL if not available + + \Version 10/27/2013 (jbrookes) +*/ +/********************************************************************************F*/ +ProtoSSLRefT *ProtoSSLAccept(ProtoSSLRefT *pState, int32_t iSecure, struct sockaddr *pAddr, int32_t *pAddrlen) +{ + ProtoSSLRefT *pClient; + SocketT *pSocket; + + // check for connect + pSocket = SocketAccept(pState->pSock, pAddr, pAddrlen); + if (pSocket == NULL) + { + return(NULL); + } + + // we have an incoming connection attempt, so create an ssl object for it + DirtyMemGroupEnter(pState->iMemGroup, pState->pMemGroupUserData); + pClient = ProtoSSLCreate(); + DirtyMemGroupLeave(); + if (pClient == NULL) + { + SocketClose(pSocket); + return(NULL); + } + + // set up new ssl object with the socket we just accepted + if (_ProtoSSLResetState(pClient, iSecure) != SOCKERR_NONE) + { + ProtoSSLDestroy(pClient); + return(NULL); + } + pClient->pSock = pSocket; + ds_memcpy(&pClient->PeerAddr, pAddr, *pAddrlen); + + // update socket status + SocketInfo(pClient->pSock, 'stat', 0, NULL, 0); + + // set client state + pClient->iState = (pClient->pSecure ? ST3_RECV_HELLO : ST_UNSECURE); + pClient->iClosed = 0; + pClient->bServer = TRUE; + return(pClient); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLBind + + \Description + Create a socket bound to the given address. + + \Input *pState - module state reference + \Input *pAddr - the IPv4 address + \Input iAddrlen - the size of the IPv4 address. + + \Output + int32_t - SOCKERR_xxx (zero=success, negative=failure) + + \Version 03/03/2004 (sbevan) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLBind(ProtoSSLRefT *pState, const struct sockaddr *pAddr, int32_t iAddrlen) +{ + // if we had a socket, get last error from it and close it + if (pState->pSock != NULL) + { + pState->iLastSocketError = SocketInfo(pState->pSock, 'serr', 0, NULL, 0); + SocketClose(pState->pSock); + } + + // create the socket + if ((pState->pSock = SocketOpen(AF_INET, SOCK_STREAM, 0)) == NULL) + { + return(SOCKERR_OTHER); + } + + // set socket options + _ProtoSSLSetSockOpt(pState); + + // do the bind, return result + return(SocketBind(pState->pSock, pAddr, iAddrlen)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLConnect + + \Description + Make a secure connection to an SSL server. + + \Input *pState - module state reference + \Input iSecure - flag indicating use of secure connection (1=secure, 0=unsecure) + \Input *pAddr - textual form of address (1.2.3.4 or www.ea.com) + \Input uAddr - the IP address of the server (if not in textual form) + \Input iPort - the TCP port of the server (if not in textual form) + + \Output + int32_t - SOCKERR_xxx (zero=success, negative=failure) + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLConnect(ProtoSSLRefT *pState, int32_t iSecure, const char *pAddr, uint32_t uAddr, int32_t iPort) +{ + int32_t iIndex; + int32_t iError; + + // reset connection state + iError = _ProtoSSLResetState(pState, iSecure); + if (iError != SOCKERR_NONE) + { + return(iError); + } + + // only allow secure connection if dirtycert service name has been set + if ((iSecure != 0) && (DirtyCertStatus('snam', NULL, 0) < 0)) + { + NetPrintf(("protossl: ************************************************************************************\n")); + NetPrintf(("protossl: ProtoSSLConnect() requires a valid DirtyCert service name in the format\n")); + NetPrintf(("protossl: \"game-year-platform\", set when calling NetConnStartup() by using the -servicename\n")); + NetPrintf(("protossl: argument, to be set before SSL use is allowed. If a service name doesn't include\n")); + NetPrintf(("protossl: dashes it is assumed to simply be the 'game' identifier, in which case DirtySDK will\n")); + NetPrintf(("protossl: fill in the year and platform. For titles using BlazeSDK, the service name specified\n")); + NetPrintf(("protossl: must match the BlazeSDK service name, therefore the full name should be specified.\n")); + NetPrintf(("protossl: *** PLEASE SET A UNIQUE AND MEANINGFUL SERVICE NAME, EVEN FOR TOOL OR SAMPLE USE ***\n")); + return(SOCKERR_INVALID); + } + + // allocate the socket + if ((pState->pSock = SocketOpen(AF_INET, SOCK_STREAM, 0)) == NULL) + { + return(SOCKERR_NORSRC); + } + + // set socket options + _ProtoSSLSetSockOpt(pState); + + // init peer structure + SockaddrInit(&pState->PeerAddr, AF_INET); + + // clear previous cert info, if any + pState->bCertInfoSet = FALSE; + ds_memclr(&pState->CertInfo, sizeof(pState->CertInfo)); + + // handle default address case + if (pAddr == NULL) + { + pAddr = ""; + } + + // parse the address string + for (iIndex = 0; (pAddr[iIndex] != 0) && (pAddr[iIndex] != ':') && ((unsigned)iIndex < sizeof(pState->strHost)-1); ++iIndex) + { + // copy over to host + pState->strHost[iIndex] = pAddr[iIndex]; + } + pState->strHost[iIndex] = 0; + + // attempt to set host address + SockaddrInSetAddrText(&pState->PeerAddr, pState->strHost); + if (SockaddrInGetAddr(&pState->PeerAddr) == 0) + { + SockaddrInSetAddr(&pState->PeerAddr, uAddr); + } + + // attempt to set peer address + if (pAddr[iIndex] == ':') + { + SockaddrInSetPort(&pState->PeerAddr, atoi(pAddr+iIndex+1)); + } + else + { + SockaddrInSetPort(&pState->PeerAddr, iPort); + } + + // see if we need to start DNS request + if (SockaddrInGetAddr(&pState->PeerAddr) == 0) + { + // do dns lookup prior to connect + pState->pHost = SocketLookup(pState->strHost, 30*1000); + pState->iState = ST_ADDR; + } + else + { + // set to connect state + pState->iState = ST_CONN; + } + + // return error code + return(SOCKERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLListen + + \Description + Start listening for an incoming connection. + + \Input *pState - module state reference + \Input iBacklog - number of pending connections allowed + + \Output + int32_t - SOCKERR_xxx (zero=success, negative=failure) + + \Version 03/03/2004 (sbevan) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLListen(ProtoSSLRefT *pState, int32_t iBacklog) +{ + return(SocketListen(pState->pSock, iBacklog)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLDisconnect + + \Description + Disconnect from the server + + \Input *pState - module state reference + + \Output + int32_t - SOCKERR_xxx (zero=success, negative=failure) + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLDisconnect(ProtoSSLRefT *pState) +{ + if (pState->pSock) + { + // send a close_notify alert as per http://tools.ietf.org/html/rfc5246#section-7.2.1 + if ((pState->pSecure != NULL) && (pState->iState == ST3_SECURE)) + { + // send the alert + _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_WARNING, SSL3_ALERT_DESC_CLOSE_NOTIFY); + } + + /* if in server mode, just close the write side. on the client we do an immediate hard + close of the socket as the calling application that is polling to drive our operation + may stop updating us once they have received all of the data, which would prevent us + from closing the socket */ + if (pState->bServer) + { + SocketShutdown(pState->pSock, SOCK_NOSEND); + } + else + { + SocketClose(pState->pSock); + pState->pSock = NULL; + } + } + + pState->iState = ST_IDLE; + pState->iClosed = 1; + + // done with dirtycert + if (pState->iCARequestId > 0) + { + DirtyCertCARequestFree(pState->iCARequestId); + } + pState->iCARequestId = 0; + return(SOCKERR_NONE); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLUpdate + + \Description + Give time to module to do its thing (should be called + periodically to allow module to perform work). Calling + once per 100ms or so should be enough. + + This is actually a collection of state functions plus + the overall update function. They are kept together for + ease of reading. + + \Input *pState - module state reference + + \Version 11/10/2005 gschaefer +*/ +/********************************************************************************F*/ +void ProtoSSLUpdate(ProtoSSLRefT *pState) +{ + int32_t iXfer; + int32_t iResult; + SecureStateT *pSecure = pState->pSecure; + + // resolve the address + if (pState->iState == ST_ADDR) + { + // check for completion + if (pState->pHost->Done(pState->pHost)) + { + pState->iState = (pState->pHost->addr != 0) ? ST_CONN : ST_FAIL_DNS; + SockaddrInSetAddr(&pState->PeerAddr, pState->pHost->addr); + // free the record + pState->pHost->Free(pState->pHost); + pState->pHost = NULL; + } + } + + // see if we should start a connection + if (pState->iState == ST_CONN) + { + // start the connection attempt + if ((iResult = SocketConnect(pState->pSock, &pState->PeerAddr, sizeof pState->PeerAddr)) == SOCKERR_NONE) + { + pState->iState = ST_WAIT_CONN; + } + else + { + pState->iState = ST_FAIL_CONN; + pState->iClosed = 1; + } + } + + // wait for connection + if (pState->iState == ST_WAIT_CONN) + { + iResult = SocketInfo(pState->pSock, 'stat', 0, NULL, 0); + if (iResult > 0) + { + pState->iState = pSecure ? ST3_SEND_HELLO : ST_UNSECURE; + pState->iClosed = 0; + _ProtoSSLCacheLocalAddress(pState); + } + if (iResult < 0) + { + pState->iState = ST_FAIL_CONN; + pState->iClosed = 1; + } + } + + // handle secure i/o (non-secure is done immediately in ProtoSSLSend/ProtoSSLRecv) + while ((pState->pSock != NULL) && (pState->iState >= ST3_SEND_HELLO) && (pState->iState <= ST3_SECURE)) + { + // get access to secure state + NetCritEnter(&pState->SecureCrit); + + // update async processing, if any + if (_ProtoSSLUpdateAsync(pState, pSecure)) + { + NetCritLeave(&pState->SecureCrit); + break; + } + + // update send processing + iXfer = _ProtoSSLUpdateSend(pState, pSecure); + + // update recv processing + iXfer += _ProtoSSLUpdateRecv(pState, pSecure); + + // release access to secure state + NetCritLeave(&pState->SecureCrit); + + // break out of loop if no i/o activity + if (iXfer == 0) + { + break; + } + } + + // wait for CA cert (this comes last intentionally) + if (pState->iState == ST_WAIT_CA) + { + // acquire secure state crit section to guard dirtycert access + NetCritEnter(&pState->SecureCrit); + + // update CA request processing + _ProtoSSLUpdateCARequest(pState); + + // release secure state crit section + NetCritLeave(&pState->SecureCrit); + } +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLSend + + \Description + Send data to the server over secure TCP connection (actually, whether the + connection is secure or not is determined by the secure flag passed during + the SSLConnect call). + + \Input *pState - module state reference + \Input *pBuffer - data to send + \Input iLength - length of data (if negative, input data is assumed to be null-terminated string) + + \Output + int32_t - negative=error, otherwise number of bytes sent + + \Version 06/03/2002 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLSend(ProtoSSLRefT *pState, const char *pBuffer, int32_t iLength) +{ + int32_t iResult = SOCKERR_CLOSED; + SecureStateT *pSecure = pState->pSecure; + + // allow easy string sends + if (iLength < 0) + { + iLength = (int32_t)strlen(pBuffer); + } + // guard against zero-length sends, which can result in an invalid send condition with some stream (e.g. RC4) ciphers + if (iLength == 0) + { + return(0); + } + + // make sure connection established + if (pState->iState == ST3_SECURE) + { + iResult = 0; + + // get access to secure state + NetCritEnter(&pState->SecureCrit); + + // make sure buffer is empty + if (pSecure->iSendSize == 0) + { + // limit send length + if (iLength > SSL_SNDLIM_PACKET) + { + iLength = SSL_SNDLIM_PACKET; + } + + // setup packet for send + if (_ProtoSSLSendPacket(pState, SSL3_REC_APPLICATION, NULL, 0, pBuffer, iLength) == 0) + { + iResult = iLength; + // try and send now + ProtoSSLUpdate(pState); + } + } + + // release access to secure state + NetCritLeave(&pState->SecureCrit); + } + + // handle unsecure sends + if (pState->iState == ST_UNSECURE) + { + iResult = SocketSend(pState->pSock, pBuffer, iLength, 0); + } + + // return the result + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLRecv + + \Description + Receive data from the server + + \Input *pState - module state reference + \Input *pBuffer - receiver data + \Input iLength - maximum buffer length + + \Output + int32_t - negative=error, zero=nothing available, positive=bytes received + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLRecv(ProtoSSLRefT *pState, char *pBuffer, int32_t iLength) +{ + SecureStateT *pSecure = pState->pSecure; + int32_t iResult = 0; + + // make sure in right state + if (pState->iState == ST3_SECURE) + { + // get access to secure state + NetCritEnter(&pState->SecureCrit); + + // check for more data if no packet pending + if ((pSecure->iRecvProg == 0) || (pSecure->iRecvProg != pSecure->iRecvSize)) + { + ProtoSSLUpdate(pState); + } + + // check for end of data + if (((pSecure->iRecvSize < SSL_MIN_PACKET) || (pSecure->iRecvProg < pSecure->iRecvSize)) && (pState->iClosed)) + { + iResult = SOCKERR_CLOSED; + } + // see if data pending + else if ((pSecure->iRecvProg == pSecure->iRecvSize) && (pSecure->iRecvBase < pSecure->iRecvSize) && + (pSecure->RecvHead[0] == SSL3_REC_APPLICATION)) + { + iResult = pSecure->iRecvSize-pSecure->iRecvBase; + // only return what user can store + if (iResult > iLength) + { + iResult = iLength; + } + // return the data + ds_memcpy(pBuffer, pSecure->RecvData+pSecure->iRecvBase, iResult); + pSecure->iRecvBase += iResult; + + // see if we can grab a new packet + if (pSecure->iRecvBase >= pSecure->iRecvSize) + { + _ProtoSSLRecvReset(pSecure); + } + } + + // release access to secure state + NetCritLeave(&pState->SecureCrit); + } + + // handle unsecure receive + if (pState->iState == ST_UNSECURE) + { + iResult = SocketRecv(pState->pSock, pBuffer, iLength, 0); + } + + // return error if in failure state + if (pState->iState >= ST_FAIL) + { + iResult = -1; + } + + // terminate buffer if there is room + if ((iResult > 0) && (iResult < iLength)) + { + pBuffer[iResult] = 0; + } + + // return the data size + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLStat + + \Description + Return the current module status (according to selector) + + \Input *pState - module state reference + \Input iSelect - status selector ('conn'=return "am i connected" flag) + \Input pBuffer - buffer pointer + \Input iLength - buffer size + + \Output + int32_t - negative=error, zero=false, positive=true + + \Notes + Selectors are: + + \verbatim + SELECTOR DESCRIPTION + 'addr' Address of peer we are connecting/connected to + 'alpn' Get the negotiated protocol using the ALPN extension from the secure state + 'alrt' Return alert status 0=no alert, 1=recv alert, 2=sent alert; alert desc copied to pBuffer + 'cert' Return SSL cert info (valid after 'fail') + 'cfip' TRUE/FALSE indication if a CA fetch is in progress + 'ciph' Cipher suite negotiated (string name in output buffer) + 'crpt' Returns whether we are performing any crypto operations at this time + 'fail' Return PROTOSSL_ERROR_* or zero if no error + 'hres' Return hResult containing either the socket error or ssl error + 'htim' Return timing of most recent SSL handshake in milliseconds + 'ladd' cached local client address used for the last connection; copy into pBuffer, buffer must be at least sizeof(struct sockaddr) + 'maxr' Return max recv rate (0=uncapped) + 'maxs' Return max send rate (0=uncapped) + 'recv' Return number of bytes in recv buffer (secure only) + 'resu' Returns whether session was resumed or not + 'rsao' [DEPRECATED] - same as 'crpt' + 'send' Return number of bytes in send buffer (secure only) + 'serr' Return socket error + 'salg' Signature algorithm negotiated (string name in output buffer); not available on resume + 'sock' Copy SocketT ref to output buffer + 'stat' Return like SocketInfo('stat') + 'vers' Return current SSL version + \endverbatim + + \Version 03/08/2002 (gschaefer) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLStat(ProtoSSLRefT *pState, int32_t iSelect, void *pBuffer, int32_t iLength) +{ + int32_t iResult = -1; + + // pass-through to SocketInfo(NULL,...) + if (pState == NULL) + { + return(SocketInfo(NULL, iSelect, 0, pBuffer, iLength)); + } + + // return address of peer we are trying to connect to + if (iSelect == 'addr') + { + if ((pBuffer != NULL) && (iLength == sizeof(pState->PeerAddr))) + { + ds_memcpy(pBuffer, &pState->PeerAddr, iLength); + } + return(SockaddrInGetAddr(&pState->PeerAddr)); + } + if ((iSelect == 'alpn') && (pState->pSecure != NULL)) + { + if (pBuffer != NULL) + { + ds_strnzcpy(pBuffer, pState->pSecure->strAlpnProtocol, iLength); + } + + return(0); + } + // return most recent alert if any + if (iSelect == 'alrt') + { + if ((pBuffer != NULL) && (iLength == sizeof(ProtoSSLAlertDescT))) + { + ProtoSSLAlertDescT Alert; + if ((iResult = _ProtoSSLGetAlert(pState, &Alert, pState->uAlertLevel, pState->uAlertValue)) != 0) + { + ds_memcpy(pBuffer, &Alert, sizeof(Alert)); + iResult = pState->bAlertSent ? 2 : 1; + } + } + return(iResult); + } + // return certificate info (valid after 'fail' response) + if ((iSelect == 'cert') && (pBuffer != NULL) && (iLength == sizeof(pState->CertInfo))) + { + ds_memcpy(pBuffer, &pState->CertInfo, sizeof(pState->CertInfo)); + return(0); + } + // return if a CA fetch request is in progress + if (iSelect == 'cfip') + { + return((pState->iState == ST_WAIT_CA) ? 1 : 0); + } + // return current cipher suite + if ((iSelect == 'ciph') && (pState->pSecure != NULL) && (pState->pSecure->pCipher != NULL)) + { + if (pBuffer != NULL) + { + ds_strnzcpy(pBuffer, pState->pSecure->pCipher->strName, iLength); + } + return(pState->pSecure->pCipher->uId); + } + // return whether we are performing any crypto operations at this time + if ((iSelect == 'crpt') || (iSelect == 'rsao')) + { + switch (pState->iState) + { + case ST3_SEND_HELLO: + case ST3_SEND_EXTN: + case ST3_SEND_KEY: + case ST3_SEND_VERIFY: + case ST3_SEND_CHANGE: + case ST3_RECV_CHANGE: + case ST3_PROC_ASYNC: + iResult = TRUE; + break; + default: + iResult = FALSE; + break; + } + return(iResult); + } + // return timing of most recent SSL handshake in milliseconds + if ((iSelect == 'htim') && (pState->pSecure != NULL)) + { + return(pState->pSecure->uTimer); + } + // cached local client address used for the last connection; copy into pBuffer, buffer must be at least sizeof(struct sockaddr) + if (iSelect == 'ladd') + { + if ((pBuffer != NULL) && (iLength >= (int32_t)sizeof(pState->LocalAddr))) + { + ds_memcpy(pBuffer, &pState->LocalAddr, sizeof(pState->LocalAddr)); + return(0); + } + return(-1); + } + // return configured max receive rate + if (iSelect == 'maxr') + { + return(pState->iMaxRecvRate); + } + // return configured max send rate + if (iSelect == 'maxs') + { + return(pState->iMaxSendRate); + } + // return number of bytes in recv buffer (useful only when connection type is secure) + if (iSelect == 'recv') + { + return((pState->pSecure != NULL) ? pState->pSecure->iRecvSize-pState->pSecure->iRecvProg : 0); + } + // return whether session was resumed or not + if ((iSelect == 'resu') && (pState->pSecure != NULL)) + { + return(pState->pSecure->bSessionResume); + } + // return current signature algorithm if available + if ((iSelect == 'salg') && (pState->pSecure != NULL)) + { + if ((pBuffer != NULL) && (pState->pSecure->Cert.iSigType >= ASN_OBJ_RSA_PKCS_MD5)) + { + ds_strnzcpy(pBuffer, _SSL3_strSignatureTypes[pState->pSecure->Cert.iSigType-ASN_OBJ_RSA_PKCS_MD5], iLength); + return(1); + } + } + // return number of bytes in send buffer (useful only when connection type is secure) + if (iSelect == 'send') + { + return((pState->pSecure != NULL) ? pState->pSecure->iSendSize-pState->pSecure->iSendProg : 0); + } + // return last socket error + if (iSelect == 'serr') + { + // pass through to socket module if we have a socket, else return cached last error + return((pState->pSock != NULL) ? SocketInfo(pState->pSock, iSelect, 0, pBuffer, iLength) : pState->iLastSocketError); + } + // return socket ref + if (iSelect == 'sock') + { + if ((pBuffer == NULL) || (iLength != sizeof(pState->pSock))) + { + return(-1); + } + ds_memcpy(pBuffer, &pState->pSock, sizeof(pState->pSock)); + return(0); + } + // return current ssl version for the connection + if ((iSelect == 'vers') && (pState->pSecure != NULL)) + { + if (pBuffer != NULL) + { + ds_strnzcpy(pBuffer, _SSL3_strVersionNames[pState->pSecure->uSslVersion&0xff], iLength); + } + return(pState->pSecure->uSslVersion); + } + // return failure code + if (iSelect == 'fail') + { + if (pState->iState & ST_FAIL) + { + switch (pState->iState) + { + case ST_FAIL_DNS: + iResult = PROTOSSL_ERROR_DNS; + break; + case ST_FAIL_CONN: + iResult = PROTOSSL_ERROR_CONN; + break; + case ST_FAIL_CONN_SSL2: + iResult = PROTOSSL_ERROR_CONN_SSL2; + break; + case ST_FAIL_CONN_NOTSSL: + iResult = PROTOSSL_ERROR_CONN_NOTSSL; + break; + case ST_FAIL_CONN_MINVERS: + iResult = PROTOSSL_ERROR_CONN_MINVERS; + break; + case ST_FAIL_CONN_MAXVERS: + iResult = PROTOSSL_ERROR_CONN_MAXVERS; + break; + case ST_FAIL_CONN_NOCIPHER: + iResult = PROTOSSL_ERROR_CONN_NOCIPHER; + break; + case ST_FAIL_CONN_NOCURVE: + iResult = PROTOSSL_ERROR_CONN_NOCURVE; + break; + case ST_FAIL_CERT_NONE: + iResult = PROTOSSL_ERROR_CERT_MISSING; + break; + case ST_FAIL_CERT_INVALID: + iResult = PROTOSSL_ERROR_CERT_INVALID; + break; + case ST_FAIL_CERT_HOST: + iResult = PROTOSSL_ERROR_CERT_HOST; + break; + case ST_FAIL_CERT_NOTRUST: + iResult = PROTOSSL_ERROR_CERT_NOTRUST; + break; + case ST_FAIL_CERT_BADDATE: + iResult = PROTOSSL_ERROR_CERT_BADDATE; + break; + case ST_FAIL_CERT_REQUEST: + iResult = PROTOSSL_ERROR_CERT_REQUEST; + break; + case ST_FAIL_SETUP: + iResult = PROTOSSL_ERROR_SETUP; + break; + case ST_FAIL_SECURE: + iResult = PROTOSSL_ERROR_SECURE; + break; + default: + iResult = PROTOSSL_ERROR_UNKNOWN; + break; + } + } + else + { + iResult = 0; + } + return(iResult); + } + + if (iSelect == 'hres') + { + uint32_t hResult; + int32_t iSerr = ProtoSSLStat(pState, 'serr', NULL, 0); + int32_t iFail = ProtoSSLStat(pState, 'fail', NULL, 0); + + if (iSerr < SOCKERR_CLOSED) + { + hResult = DirtyErrGetHResult(DIRTYAPI_SOCKET, iSerr, TRUE); + } + else if (iFail != 0) + { + hResult = DirtyErrGetHResult(DIRTYAPI_PROTO_SSL, iFail, TRUE); + } + else + { + hResult = DirtyErrGetHResult(DIRTYAPI_PROTO_SSL, 0, FALSE); //success hResult + } + return(hResult); + } + + // only pass through if socket is valid + if (pState->pSock != NULL) + { + // special processing for 'stat' selector + if (iSelect == 'stat') + { + // if we're in a failure state, return error + if (pState->iState >= ST_FAIL) + { + return(-1); + } + // don't check connected status until we are connected and done with secure negotiation (if secure) + if (pState->iState < ST3_SECURE) + { + return(0); + } + // if we're connected (in ST_UNSECURE or ST3_SECURE state) fall through + } + + // pass through request to the socket module + iResult = SocketInfo(pState->pSock, iSelect, 0, pBuffer, iLength); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLControl + + \Description + ProtoSSL control function. Different selectors control different behaviors. + + \Input *pState - module state reference + \Input iSelect - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + Selectors are: + + \verbatim + SELECTOR DESCRIPTION + 'alpn' Set the ALPN protocols (using IANA registerd named) as a comma delimited list + 'arcv' Set async receive on the ssl socket + 'ccrt' Set client certificate level (0=disabled, 1=requested, 2=required) + 'ciph' Set enabled/disabled ciphers (PROTOSSL_CIPHER_*) + 'crvd' Set default curve (-1=no default; else [0,SSL3_EC_MAX] + 'curv' Set enabled/disabled curves (PROTOSS_CURVE_*) + 'extn' Set enabled ClientHello extensions (PROTOSSL_HELLOEXTN_*), (default=0=disabled) + 'gcph' Set global cipher default + 'gcrv' Set global curve default + 'gvrs' Set global version default + 'gvmn' Set global version min default + 'host' Set remote host + 'hreq' Send HelloRequest (server only) + 'maxr' Set max recv rate (0=uncapped, default) + 'maxs' Set max send rate (0=uncapped, default) + 'ncrt' Disable client certificate validation + 'rbuf' Set socket recv buffer size (must be called before Connect()) + 'resu' Set whether session resume is enabled or disabled (default=1=enabled) + 'sbuf' Set socket send buffer size (must be called before Connect()) + 'scrt' Set certificate (pValue=cert, iValue=len) + 'snod' Set whether TCP_NODELAY option is enabled or disabled on the socket (must be called before Connect()) + 'secu' Start secure negotiation on an established unsecure connection + 'skey' Set private key (pValue=key, iValue=len) + 'skep' Set socket keep-alive settings (iValue=enable/disable, iValue2=keep-alive time/interval) + 'spam' Set debug logging level (default=1) + 'vers' Set client-requested SSL version (default=0x302, TLS1.1) + 'vmin' Set minimum SSL version application will accept + \endverbatim + + \Version 01/27/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLControl(ProtoSSLRefT *pState, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue) +{ + int32_t iResult = -1; + + if (iSelect == 'alpn') + { + uint16_t uProtocol, uAlpnExtensionLength; + const char *pSrc = (const char *)pValue; + if (pSrc == NULL) + { + NetPrintf(("protossl: invalid ALPN extension protocol list provided\n")); + return(-1); + } + NetPrintfVerbose((pState->iVerbose, 0, "protossl: setting the ALPN extension protocols using %s\n", (const char *)pValue)); + ds_memclr(pState->aAlpnProtocols, sizeof(pState->aAlpnProtocols)); + + for (uProtocol = 0, uAlpnExtensionLength = 0; uProtocol < SSL_ALPN_MAX_PROTOCOLS; uProtocol += 1) + { + AlpnProtocolT *pProtocol = &pState->aAlpnProtocols[uProtocol]; + if ((pProtocol->uLength = (uint8_t)ds_strsplit(pSrc, ',', pProtocol->strName, sizeof(pProtocol->strName), &pSrc)) == 0) + { + break; + } + + uAlpnExtensionLength += pProtocol->uLength; + uAlpnExtensionLength += sizeof(pProtocol->uLength); + } + pState->uNumAlpnProtocols = uProtocol; + pState->uAlpnExtensionLength = uAlpnExtensionLength; + + return(0); + } + if (iSelect == 'arcv') + { + pState->bAsyncRecv = (uint8_t)iValue; + NetPrintfVerbose((pState->iVerbose, 0, "protossl: async recv set to %s\n", pState->bAsyncRecv ? "TRUE" : "FALSE")); + return(0); + } + if (iSelect == 'ccrt') + { + NetPrintf(("protossl: setting client cert level to %d\n", iValue)); + pState->iClientCertLevel = iValue; + return(0); + } + if (iSelect == 'ciph') + { + pState->uEnabledCiphers = (uint32_t)iValue; + NetPrintfVerbose((pState->iVerbose, 0, "protossl: enabled ciphers=%d\n", iValue)); + return(0); + } + if (iSelect == 'crvd') + { + // attempt to validate the curve is enabled + int8_t iCurveDflt = (int8_t)DS_CLAMP(iValue, -1, SSL3_CURVE_MAX); + if ((iCurveDflt == -1) || ((_SSL3_EllipticCurves[iCurveDflt].uId & pState->uEnabledCurves) != 0)) + { + pState->iCurveDflt = iCurveDflt; + NetPrintf(("protossl: choosing curve %d\n", pState->iCurveDflt)); + return(0); + } + else + { + NetPrintf(("protossl: selected curve %d is disabled and cannot be used as default\n", iCurveDflt)); + return(-1); + } + } + if (iSelect == 'curv') + { + pState->uEnabledCurves = (uint32_t)iValue; + NetPrintfVerbose((pState->iVerbose, 0, "protossl: enabled curves=%u\n", pState->uEnabledCurves)); + return(0); + } + if (iSelect == 'extn') + { + pState->uHelloExtn = (uint8_t)iValue; + NetPrintfVerbose((pState->iVerbose, 0, "protossl: clienthello extensions set to 0x%02x\n", pState->uHelloExtn)); + return(0); + } + // handle global settings + if (((iSelect == 'gcph') || (iSelect == 'gcrv') || (iSelect == 'gver') || (iSelect == 'gvmn')) && (_ProtoSSL_pState != NULL)) + { + if (iSelect == 'gcph') + { + NetPrintf(("protossl: setting global default cipher mask to 0x%x\n", iValue)); + _ProtoSSL_pState->iDfltCiph = iValue; + return(0); + } + if (iSelect == 'gcrv') + { + NetPrintf(("protossl: setting global default curve mask to 0x%08x\n", iValue)); + _ProtoSSL_pState->iDfltCurves = iValue; + return(0); + } + if (iSelect == 'gver') + { + NetPrintf(("protossl: setting global default version to 0x%x\n", iValue)); + _ProtoSSL_pState->iDfltVers = iValue; + return(0); + } + if (iSelect == 'gvmn') + { + NetPrintf(("protossl: setting global default min version to 0x%x\n", iValue)); + _ProtoSSL_pState->iDfltMinVers = iValue; + return(0); + } + } + if (iSelect == 'host') + { + NetPrintf(("protossl: setting host to '%s'\n", (const char *)pValue)); + ds_strnzcpy(pState->strHost, (const char *)pValue, sizeof(pState->strHost)); + return(0); + } + if ((iSelect == 'hreq') && (pState->bServer) && (pState->iState == ST3_SECURE)) + { + NetPrintf(("protossl: sending Hello Request\n")); + pState->iState = ST3_SEND_HELLO_REQUEST; + return(0); + } + if (iSelect == 'maxr') + { + pState->iMaxRecvRate = iValue; + if (pState->pSock != NULL) + { + SocketControl(pState->pSock, iSelect, iValue, NULL, NULL); + } + return(0); + } + if (iSelect == 'maxs') + { + pState->iMaxSendRate = iValue; + if (pState->pSock != NULL) + { + SocketControl(pState->pSock, iSelect, iValue, NULL, NULL); + } + return(0); + } + if (iSelect == 'ncrt') + { + pState->bAllowAnyCert = (uint8_t)iValue; + return(0); + } + if (iSelect == 'radr') + { + pState->bReuseAddr = TRUE; + return(0); + } + if (iSelect == 'rbuf') + { + pState->iRecvBufSize = iValue; + return(0); + } + if (iSelect == 'resu') + { + pState->bSessionResumeEnabled = iValue ? TRUE : FALSE; + return(0); + } + if (iSelect == 'sbuf') + { + pState->iSendBufSize = iValue; + return(0); + } + if (iSelect == 'scrt') + { + if (pState->pCertificate != NULL) + { + DirtyMemFree(pState->pCertificate, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + } + pState->pCertificate = _CertificateDecodePublic(pState, (uint8_t *)pValue, iValue); + return(0); + } + if (iSelect == 'snod') + { + pState->bNoDelay = (uint8_t)iValue; + return(0); + } + if (iSelect == 'skey') + { + if (pState->pPrivateKey != NULL) + { + DirtyMemFree(pState->pPrivateKey, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData); + pState->iPrivateKeyLen = 0; + } + if ((pState->pPrivateKey = DirtyMemAlloc(iValue, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) != NULL) + { + ds_memcpy(pState->pPrivateKey, pValue, iValue); + pState->iPrivateKeyLen = iValue; + } + else + { + NetPrintf(("protossl: could not allocate memory for private key\n")); + } + return(0); + } + if (iSelect == 'skep') + { + pState->bKeepAlive = (uint8_t)iValue; + pState->uKeepAliveTime = (uint32_t)iValue2; + return(0); + } + if (iSelect == 'secu') + { + if (pState->iState != ST_UNSECURE) + { + NetPrintf(("protossl: cannot promote to a secure connection unless connected in unsecure state\n")); + return(-1); + } + _ProtoSSLResetSecureState(pState, 1); + pState->iState = ST3_SEND_HELLO; + return(0); + } + if (iSelect == 'spam') + { + pState->iVerbose = (int8_t)iValue; + return(0); + } + if (iSelect == 'vers') + { + uint32_t uSslVersion = DS_CLAMP(iValue, pState->uSslVersionMin, SSL3_VERSION_MAX); + if (pState->uSslVersion != uSslVersion) + { + NetPrintf(("protossl: setting sslvers to %s (0x%04x)\n", _SSL3_strVersionNames[uSslVersion&0xff], uSslVersion)); + pState->uSslVersion = uSslVersion; + } + return(0); + } + if (iSelect == 'vmin') + { + uint32_t uSslVersionMin = DS_CLAMP(iValue, SSL3_VERSION_MIN, SSL3_VERSION_MAX); + if (pState->uSslVersionMin != uSslVersionMin) + { + NetPrintf(("protossl: setting min sslvers to %s (0x%04x)\n", _SSL3_strVersionNames[uSslVersionMin&0xff], uSslVersionMin)); + pState->uSslVersionMin = uSslVersionMin; + // make sure requested version is at least minimum version + ProtoSSLControl(pState, 'vers', pState->uSslVersion, 0, NULL); + } + return(0); + } + // if we have a socket ref, pass unhandled selector through + if (pState->pSock != NULL) + { + iResult = SocketControl(pState->pSock, iSelect, iValue, pValue, NULL); + } + else + { + NetPrintf(("protossl: ProtoSSLControl('%C') unhandled\n", iSelect)); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLSetCACert + + \Description + Add one or more X.509 CA certificates to the trusted list. A + certificate added will be available to all ProtoSSL instances for + the lifetime of the application. This function can add one or more + PEM certificates or a single DER certificate. + + \Input *pCertData - pointer to certificate data (PEM or DER) + \Input iCertSize - size of certificate data + + \Output + int32_t - negative=error, positive=count of CAs added + + \Notes + The certificate must be in .DER (binary) or .PEM (base64-encoded) + format. + + \Version 01/13/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLSetCACert(const uint8_t *pCertData, int32_t iCertSize) +{ + return(_ProtoSSLSetCACert(pCertData, iCertSize, TRUE)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLSetCACert2 + + \Description + Add one or more X.509 CA certificates to the trusted list. A + certificate added will be available to all ProtoSSL instances for + the lifetime of the application. This function can add one or more + PEM certificates or a single DER certificate. + + This version of the function does not validate the CA at load time. + The X509 certificate data will be copied and retained until the CA + is validated, either by use of ProtoSSLValidateAllCA() or by the CA + being used to validate a certificate. + + \Input *pCertData - pointer to certificate data (PEM or DER) + \Input iCertSize - size of certificate data + + \Output + int32_t - negative=error, positive=count of CAs added + + \Notes + The certificate must be in .DER (binary) or .PEM (base64-encoded) + format. + + \Version 04/21/2011 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLSetCACert2(const uint8_t *pCertData, int32_t iCertSize) +{ + return(_ProtoSSLSetCACert(pCertData, iCertSize, FALSE)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLValidateAllCA + + \Description + Validate all CA that have been added but not yet been validated. Validation + is a one-time process and disposes of the X509 certificate that is retained + until validation. + + \Output + int32_t - zero on success; else the number of certs that could not be validated + + \Version 04/21/2011 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLValidateAllCA(void) +{ + ProtoSSLCACertT *pCACert; + SecureStateT *pSecure; + void *pMemGroupUserData; + int32_t iInvalid, iMemGroup; + + // get memgroup settings for certificate blob + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate secure state for certificate validation + if ((pSecure = _ProtoSSLAllocSecureState(iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protossl: could not allocate secure state for ca validation\n")); + return(-1); + } + + // validate all installed CA Certs that have not yet been validated + for (pCACert = _ProtoSSL_CACerts, iInvalid = 0; pCACert != NULL; pCACert = pCACert->pNext) + { + // if the CA hasn't been verified already, do that now + if (pCACert->pX509Cert != NULL) + { + if (_ProtoSSLVerifyCertificate(NULL, pSecure, pCACert->pX509Cert, TRUE) == 0) + { + #if DIRTYCODE_LOGGING + char strIdentSubject[512], strIdentIssuer[512]; + NetPrintf(("protossl: ca (%s) validated by ca (%s)\n", _CertificateDebugFormatIdent(&pCACert->pX509Cert->Subject, strIdentSubject, sizeof(strIdentSubject)), + _CertificateDebugFormatIdent(&pCACert->pX509Cert->Issuer, strIdentIssuer, sizeof(strIdentIssuer)))); + #endif + + // cert successfully verified + DirtyMemFree(pCACert->pX509Cert, PROTOSSL_MEMID, pCACert->iMemGroup, pCACert->pMemGroupUserData); + pCACert->pX509Cert = NULL; + } + else + { + _CertificateDebugPrint(pCACert->pX509Cert, "ca could not be validated"); + iInvalid += 1; + } + } + } + + // free secure state used for validation + DirtyMemFree(pSecure, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData); + + // return number of certs we could not validate + return(iInvalid); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLClrCACerts + + \Description + Clears all dynamic CA certs from the list. + + \Version 01/14/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoSSLClrCACerts(void) +{ + ProtoSSLCACertT *pCACert, *pCACert0=NULL; + + /* + * This code makes the following assumptions: + * 1) There is at least one static cert. + * 2) All static certs come first, followed by all dynamic certs. + */ + + // scan for first dynamic certificate + for (pCACert = _ProtoSSL_CACerts; (pCACert != NULL) && (pCACert->iMemGroup == 0); ) + { + pCACert0 = pCACert; + pCACert = pCACert->pNext; + } + // any dynamic certs? + if ((pCACert != NULL) && (pCACert0 != NULL)) + { + // null-terminate static list + pCACert0->pNext = NULL; + + // delete dynamic certs + for ( ; pCACert != NULL; ) + { + pCACert0 = pCACert->pNext; + if (pCACert->pX509Cert != NULL) + { + DirtyMemFree(pCACert->pX509Cert, PROTOSSL_MEMID, pCACert->iMemGroup, pCACert->pMemGroupUserData); + } + DirtyMemFree(pCACert, PROTOSSL_MEMID, pCACert->iMemGroup, pCACert->pMemGroupUserData); + pCACert = pCACert0; + } + } +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLPkcs1GenerateInit + + \Description + Init for generating a PKCSv1.5 RSA signature + + \Input *pPkcs1 - pkcs1 state + \Input *pHashData - hash of the data which was signed + \Input iHashLen - length of the hash + \Input iHashType - the hash which was used + \Input iModSize - size of the modulus + \Input *pPrimeP - prime p from the private key + \Input *pPrimeQ - prime q from the private key + \Input *pExponentP - exponent p from the private key + \Input *pExponentQ - exponent q from the private key + \Input *pCoefficient- coefficient from the private key + + \Notes + This function will block on the RSA operation. + + \Output + int32_t - zero=validation successful, negative=validation failed + + \Version 03/15/2020 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLPkcs1GenerateInit(ProtoSSLPkcs1T *pPkcs1, const uint8_t *pHashData, int32_t iHashLen, int32_t iHashType, int32_t iModSize, const CryptBinaryObjT *pPrimeP, const CryptBinaryObjT *pPrimeQ, const CryptBinaryObjT *pExponentP, const CryptBinaryObjT *pExponentQ, const CryptBinaryObjT *pCoefficient) +{ + static uint8_t aSigData[SSL_SIG_MAX]; + int32_t iSigSize; + + // generate the signature data + if ((iSigSize = _AsnWriteDigitalHashObject(aSigData, sizeof(aSigData), pHashData, iHashLen, (CryptHashTypeE)iHashType)) == 0) + { + return(-1); + } + #if DEBUG_RAW_DATA + NetPrintMem(pHashData, iHashLen, "message digest"); + #endif + + // init the rsa module with the private key and signature information + if (CryptRSAInit2(&pPkcs1->RSAContext, iModSize, pPrimeP, pPrimeQ, pExponentP, pExponentQ, pCoefficient)) + { + return(-1); + } + CryptRSAInitPrivate(&pPkcs1->RSAContext, aSigData, iSigSize); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLPkcs1GenerateUpdate + + \Description + Generate the PKCSv1.5 RSA signature + + \Input *pPkcs1 - pkcs1 state + \Input iNumIterations - number of iterations to perform or zero to block until complete + \Input *pSigData - [out] signature data + \Input iSigSize - size of the signature + + \Output + int32_t - 1=operation pending, 0=operation complete + + \Version 03/15/2020 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLPkcs1GenerateUpdate(ProtoSSLPkcs1T *pPkcs1, int32_t iNumIterations, uint8_t *pSigData, int32_t iSigSize) +{ + // perform the encryption rounds + if (CryptRSAEncrypt(&pPkcs1->RSAContext, iNumIterations) > 0) + { + return(1); + } + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (pkcs1 sig encrypt) %dms\n", pPkcs1->RSAContext.uCryptMsecs)); + + #if DEBUG_RAW_DATA + NetPrintMem(pPkcs1->RSAContext.EncryptBlock, pPkcs1->RSAContext.iKeyModSize, "encrypted signature"); + #endif + + // copy the signature + ds_memcpy_s(pSigData, iSigSize, pPkcs1->RSAContext.EncryptBlock, pPkcs1->RSAContext.iKeyModSize); + return(0); +} + +/*F********************************************************************************/ +/*! + \Function ProtoSSLPkcs1Verify + + \Description + Verify the PKCSv1.5 RSA signature + + \Input *pSigData - signature data + \Input iSigLen - length of the signature + \Input *pHashData - hash of the data which was signed + \Input iHashLen - length of the hash + \Input iHashType - the hash which was used + \Input *pMod - public key modulus + \Input iModSize - size of the modulus + \Input *pExp - public key exponent + \Input iExpSize - size of the exponent + + \Notes + This function will block on the RSA operation. + + \Output + int32_t - zero=validation successful, negative=validation failed + + \Version 02/14/2020 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoSSLPkcs1Verify(const uint8_t *pSigData, int32_t iSigLen, const uint8_t *pHashData, int32_t iHashLen, int32_t iHashType, const uint8_t *pMod, int32_t iModSize, const uint8_t *pExp, int32_t iExpSize) +{ + CryptRSAT RSA; + int32_t iResult = -1; + + // init the rsa module with the public key and signature information + if (CryptRSAInit(&RSA, pMod, iModSize, pExp, iExpSize)) + { + return(-1); + } + CryptRSAInitSignature(&RSA, pSigData, iSigLen); + + // perform the encryption rounds + CryptRSAEncrypt(&RSA, 0); + NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (pkcs1 sig verify) %dms\n", RSA.uCryptMsecs)); + + #if DEBUG_RAW_DATA + NetPrintMem(RSA.EncryptBlock, RSA.iKeyModSize, "decrypted signature"); + NetPrintMem(pHashData, iHashLen, "message digest"); + #endif + + // extract hash data from signature block + if ((pSigData = _Pkcs1VerifyEMSA(RSA.EncryptBlock, iSigLen, iHashLen, (CryptHashTypeE)iHashType)) != NULL) + { + // compare hash data with precalculated certificate body hash + iResult = !memcmp(pHashData, pSigData, iHashLen) ? 0 : -1; + } + + return(iResult); +} diff --git a/src/thirdparty/dirtysdk/source/proto/protostream.c b/src/thirdparty/dirtysdk/source/proto/protostream.c new file mode 100644 index 00000000..8212a9b1 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protostream.c @@ -0,0 +1,1170 @@ +/*H********************************************************************************/ +/*! + \File protostream.c + + \Description + Manage streaming of an Internet media resource. + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 11/16/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/proto/protohttp.h" + +#include "DirtySDK/proto/protostream.h" + +/*** Defines **********************************************************************/ + +#define PROTOSTREAM_MINBUFFER (32*1024) //!< minimum buffer configuration +#define PROTOSTREAM_MAXURL (256) //!< maximum url length +#define PROTOSTREAM_RESTARTFREQ_MAX (60) //!< maximum restart increase is one minute +#define PROTOSTREAM_SAMPLE_PERIOD (2000) //!< stats sampling period + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +// module state memory +struct ProtoStreamRefT +{ + ProtoHttpRefT *pProtoHttp; //!< protohttp ref + + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + ProtoStreamCallbackT *pCallback; //!< user callback + void *pUserData; //!< user callback data + int32_t iCallbackRate; //!< callback rate in ms + uint32_t uLastCallback; //!< last time callback was triggered + + enum + { + ST_IDLE, //!< idle state + ST_OPEN //!< stream is active + } eState; + + int32_t iRestartFreq; //!< restart frequency (PROTOSTREAM_FREQ_* or restart time in seconds) + int32_t iRestartTime; //!< restart timer + int32_t iRestartIncr; //!< amount to increase restart frequency by in case of an error + int32_t iRestartThreshold; //!< minimum amount of data that must be buffered before playback starts + int32_t iTimeout; //!< current http timeout + + int32_t iLastSize; //!< previous stream size + int32_t iLastModified; //!< previous last mod time + int32_t iLastHttpCode; //!< most recent http result code, saved on stream completion + int32_t iLastRecvTime; //!< time data was last received + + int32_t iBufSize; //!< streaming buffer size + int32_t iBufLen; //!< amount of data in buffer + int32_t iBufInp; //!< buffer input position + int32_t iBufOut; //!< buffer output position + int32_t iBufMin; //!< min amount of data to provide to caller; -1 if no min + + int32_t iBufAvg; //!< measured buffer size average + int32_t iBufDev; //!< measured buffer size deviation + + int32_t iStreamRead; //!< total amount of data read from stream + int32_t iStreamTime; //!< time stream has been open for, in milliseconds + + #if DIRTYCODE_DEBUG + uint32_t uStarveTime; //!< time to starve input until, for testing (debug only) + #endif + + uint8_t *pBufMin; //!< buffer to handle min-sized reads + + uint8_t bPrebuffering; //!< pre-buffering data before giving it to app + uint8_t bReceivedHeader; //!< TRUE=received HTTP header, else FALSE + uint8_t bPaused; //!< TRUE if stream is paused, else FALSE + int8_t iVerbose; //!< verbose debug level (debug only) + int8_t iStreamStatus; //!< stream status: -1=error, 0=in progress, 1=complete + + char strUrl[PROTOSTREAM_MAXURL]; //!< current url + + char strError[256]; //!< buffer to store HTTP result text, if any + uint8_t aBuffer[1]; //!< variable-sized streaming buffer - must come last +}; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _ProtoStreamDefaultCallback + + \Description + Default (empty) callback. + + \Input *pProtoStream - pointer to module state + \Input eStatus - callback status (PROTOSTREAM_STATUS_*) + \Input *pBuffer - data pointer, or null + \Input iLength - data length, or zero + \Input *pUserData - user data pointer + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoStreamDefaultCallback(ProtoStreamRefT *pProtoStream, ProtoStreamStatusE eStatus, const uint8_t *pBuffer, int32_t iLength, void *pUserData) +{ + // no data consumed + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoStreamDone + + \Description + Called when stream is complete. + + \Input *pProtoStream - pointer to module state + + \Output + None. + + \Version 11/21/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoStreamDone(ProtoStreamRefT *pProtoStream) +{ + NetPrintf(("protostream: stream done\n")); + + // notify user of stream completion + pProtoStream->pCallback(pProtoStream, PROTOSTREAM_STATUS_DONE, NULL, 0, pProtoStream->pUserData); + + // set up for restart if appropriate + if (pProtoStream->iRestartFreq != PROTOSTREAM_FREQ_ONCE) + { + int32_t iRestartFreq = pProtoStream->iRestartFreq; + + // if the most recent http response code was an error, increase restart time + if (pProtoStream->iLastHttpCode != PROTOHTTP_RESPONSE_SUCCESSFUL) + { + iRestartFreq += pProtoStream->iRestartIncr; + if (iRestartFreq > PROTOSTREAM_RESTARTFREQ_MAX) + { + iRestartFreq = PROTOSTREAM_RESTARTFREQ_MAX; + } + else + { + pProtoStream->iRestartIncr *= 2; + } + NetPrintf(("protostream: setting restart frequency to %d due to http error %d\n", + iRestartFreq, pProtoStream->iLastHttpCode)); + } + else + { + pProtoStream->iRestartIncr = 1; + } + + // set restart time + pProtoStream->iRestartTime = NetTick() + iRestartFreq * 1000; + } + + // go to idle state + pProtoStream->eState = ST_IDLE; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoStreamReset + + \Description + Reset the module state. + + \Input *pProtoStream - pointer to module state + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoStreamReset(ProtoStreamRefT *pProtoStream) +{ + // abort any ongoing stream + ProtoHttpAbort(pProtoStream->pProtoHttp); + + // reset buffering state + pProtoStream->iBufLen = 0; + pProtoStream->iBufInp = 0; + pProtoStream->iBufOut = 0; + + // reset other misc stuff + pProtoStream->uLastCallback = 0; + pProtoStream->eState = ST_IDLE; + pProtoStream->bReceivedHeader = FALSE; + pProtoStream->bPrebuffering = TRUE; + pProtoStream->iStreamStatus = 0; + pProtoStream->strUrl[0] = '\0'; + pProtoStream->iLastHttpCode = 0; + pProtoStream->iStreamTime = 0; + pProtoStream->iStreamRead = 0; + pProtoStream->iRestartThreshold = pProtoStream->iBufSize/2; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoStreamUpdateStats + + \Description + Update stream statistics + + \Input *pProtoStream - pointer to module state + \Input iDataRead - amount of data read + + \Version 11/22/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoStreamUpdateStats(ProtoStreamRefT *pProtoStream, int32_t iDataRead) +{ + int32_t iRecvTime, iRecvElapsed; + + // get current time + iRecvTime = NetTick(); + + // prime lastrecvtime + if (pProtoStream->iLastRecvTime == 0) + { + pProtoStream->iLastRecvTime = iRecvTime; + } + + // calculate elapsed time since last receive and update last received time + iRecvElapsed = iRecvTime - pProtoStream->iLastRecvTime; + pProtoStream->iLastRecvTime = iRecvTime; + + // update overall time and size info + pProtoStream->iStreamTime += iRecvElapsed; + pProtoStream->iStreamRead += iDataRead; + + // perform latency calc using weighted time average + if ((iRecvElapsed >= 0) && (iRecvElapsed < PROTOSTREAM_SAMPLE_PERIOD)) + { + // figure out weight of existing data + int32_t iWeight = PROTOSTREAM_SAMPLE_PERIOD - iRecvElapsed; + // figure deviation first since it uses average + int32_t iChange = pProtoStream->iBufLen - pProtoStream->iBufAvg; + if (iChange < 0) + { + iChange = -iChange; + } + // calc weighted deviation + pProtoStream->iBufDev = ((iWeight*pProtoStream->iBufDev) + (iRecvElapsed*iChange))/PROTOSTREAM_SAMPLE_PERIOD; + // calc weighted average + pProtoStream->iBufAvg = ((iWeight*pProtoStream->iBufAvg) + (iRecvElapsed*pProtoStream->iBufLen))/PROTOSTREAM_SAMPLE_PERIOD; + // calc restart threshold, if we're not prebuffering + if (pProtoStream->bPrebuffering == FALSE) + { + pProtoStream->iRestartThreshold = pProtoStream->iBufSize - pProtoStream->iBufAvg + pProtoStream->iBufDev; + // clamp restart threshold to [0.5 ... 1.0] + if (pProtoStream->iRestartThreshold < pProtoStream->iBufSize/2) + { + pProtoStream->iRestartThreshold = pProtoStream->iBufSize/2; + } + else if (pProtoStream->iRestartThreshold > pProtoStream->iBufSize) + { + pProtoStream->iRestartThreshold = pProtoStream->iBufSize; + } + } + NetPrintfVerbose((pProtoStream->iVerbose, 1, "protostream: dev=%5d avg=%5d len=%5d thr=%d\n", + pProtoStream->iBufDev, pProtoStream->iBufAvg, pProtoStream->iBufLen, pProtoStream->iRestartThreshold)); + } + else + { + // if more than our scale has elapsed, use this data + pProtoStream->iBufDev = 0; + pProtoStream->iBufAvg = 0; + pProtoStream->iRestartThreshold = pProtoStream->iBufSize/2; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoStreamChanged + + \Description + Returns whether stream has changed since previous stream or not + + \Input *pProtoStream - pointer to module state + + \Output + uint32_t - TRUE if stream changed, else FALSE + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _ProtoStreamChanged(ProtoStreamRefT *pProtoStream) +{ + int32_t iLast, iSize; + uint32_t bChanged = FALSE; + + // get size + iSize = ProtoHttpStatus(pProtoStream->pProtoHttp, 'body', NULL, 0); + + // if size has changed or is unspecified, stream has changed + if ((iSize <= 0) || (iSize != pProtoStream->iLastSize)) + { + pProtoStream->iLastSize = iSize; + bChanged = TRUE; + } + + // get last modified time + iLast = ProtoHttpStatus(pProtoStream->pProtoHttp, 'date', NULL, 0); + + // if last modified time has changed or is unspecified, stream has changed + if ((iLast == 0) || (iLast != pProtoStream->iLastModified)) + { + pProtoStream->iLastModified = iLast; + bChanged = TRUE; + } + + // return changed status + return(bChanged); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoStreamRecv + + \Description + Try and receive data if there is room + + \Input *pProtoStream - pointer to module state + + \Output + int32_t - bytes received, or negative if error + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoStreamRecv(ProtoStreamRefT *pProtoStream) +{ + int32_t iRecvMax, iRecvResult; + + // if the stream is done, don't try and receive any more + if (pProtoStream->iStreamStatus != 0) + { + return(0); + } + + #if DIRTYCODE_DEBUG + if (pProtoStream->uStarveTime != 0) + { + if (NetTickDiff(NetTick(), pProtoStream->uStarveTime) > 0) + { + NetPrintf(("protostream: stream starvation complete\n")); + pProtoStream->uStarveTime = 0; + } + else + { + return(0); + } + } + #endif + + // no room in buffer? + if (pProtoStream->iBufLen == pProtoStream->iBufSize) + { + return(0); + } + + // get max size we can receive + iRecvMax = pProtoStream->iBufSize - pProtoStream->iBufLen; + if (iRecvMax > (pProtoStream->iBufSize - pProtoStream->iBufInp)) + { + iRecvMax = pProtoStream->iBufSize - pProtoStream->iBufInp; + } + + // have we received the header yet? + if (pProtoStream->bReceivedHeader == FALSE) + { + // wait until we've received the header + if (ProtoHttpStatus(pProtoStream->pProtoHttp, 'head', NULL, 0) > 0) + { + // if the stream hasn't changed since last time, skip it + if (!_ProtoStreamChanged(pProtoStream)) + { + // mark stream as complete + NetPrintf(("protostream: stream unchanged from last play -- skipping\n")); + ProtoHttpAbort(pProtoStream->pProtoHttp); + pProtoStream->iStreamStatus = 1; + return(0); + } + // if the response isn't ok, disable prebuffering so client code isn't stuck waiting + if (ProtoHttpStatus(pProtoStream->pProtoHttp, 'code', NULL, 0) != PROTOHTTP_RESPONSE_OK) + { + pProtoStream->bPrebuffering = FALSE; + } + // mark header as received + pProtoStream->bReceivedHeader = TRUE; + } + } + + // try and get some data + if ((iRecvResult = ProtoHttpRecv(pProtoStream->pProtoHttp, (char *)pProtoStream->aBuffer + pProtoStream->iBufInp, 1, iRecvMax)) > 0) + { + #if DIRTYCODE_LOGGING + if (pProtoStream->iVerbose > 1) + { + int32_t iStreamBps=0; + if (pProtoStream->iStreamTime != 0) + { + iStreamBps = (int32_t)(((int64_t)pProtoStream->iStreamRead*8*1000)/(int64_t)pProtoStream->iStreamTime); + } + NetPrintf(("protostream: recv [0x%04x,0x%04x] len=%d bps=%d clk=%.2f\n", pProtoStream->iBufInp, + pProtoStream->iBufInp+iRecvResult, pProtoStream->iBufLen+iRecvResult, iStreamBps, + (float)pProtoStream->iStreamTime/1000.0f)); + } + #endif + + // update buffer pointers + pProtoStream->iBufLen += iRecvResult; + pProtoStream->iBufInp += iRecvResult; + if (pProtoStream->iBufInp == pProtoStream->iBufSize) + { + pProtoStream->iBufInp = 0; + } + } + else if (iRecvResult == PROTOHTTP_RECVDONE) + { + // mark stream as complete + NetPrintf(("protostream: stream data transfer complete\n")); + pProtoStream->iLastHttpCode = ProtoHttpStatus(pProtoStream->pProtoHttp, 'code', NULL, 0); + pProtoStream->iStreamStatus = (pProtoStream->iLastHttpCode == PROTOHTTP_RESPONSE_OK) ? 1 : -1; + if (pProtoStream->iStreamStatus < 0) + { + ds_strsubzcpy(pProtoStream->strError, sizeof(pProtoStream->strError), (char *)pProtoStream->aBuffer+pProtoStream->iBufOut, pProtoStream->iBufLen-pProtoStream->iBufOut); + } + } + else if ((iRecvResult < 0) && (iRecvResult != PROTOHTTP_RECVWAIT)) + { + NetPrintf(("protostream: error %d receiving http response\n", iRecvResult)); + pProtoStream->iStreamStatus = -1; + pProtoStream->iLastSize = -1; + } + + // return result to caller + return(iRecvResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoStreamDataCallback + + \Description + Determine amount of data that can be read from buffer, and call + user callback with a pointer to the beginning of the data in the queue + and the amount of data that can be read. + + \Input *pProtoStream - pointer to module state + \Input eStatus - current status + + \Output + int32_t - amount of data read + + \Version 01/25/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoStreamDataCallback(ProtoStreamRefT *pProtoStream, ProtoStreamStatusE eStatus) +{ + int32_t iRead, iReadMax; + + // determine max amount of data that can be read + iReadMax = pProtoStream->iBufSize - pProtoStream->iBufOut; + if (iReadMax > pProtoStream->iBufLen) + { + iReadMax = pProtoStream->iBufLen; + } + + // if buffer minimum conditions do not apply, allow the user to grab some data + if ((iReadMax > pProtoStream->iBufMin) || ((pProtoStream->iBufMin >= pProtoStream->iBufLen) && (pProtoStream->iStreamStatus == 1))) + { + if ((iRead = pProtoStream->pCallback(pProtoStream, eStatus, pProtoStream->aBuffer + pProtoStream->iBufOut, iReadMax, pProtoStream->pUserData)) > 0) + { + NetPrintfVerbose((pProtoStream->iVerbose, 1, "protostream: read [0x%04x,0x%04x]\n", pProtoStream->iBufOut, pProtoStream->iBufOut+iRead)); + + // if they consumed too much data, print a diagnostic warning and clamp + if (iRead > iReadMax) + { + NetPrintf(("protostream: warning; tried to read %d bytes when max was %d\n", iRead, iReadMax)); + iRead = iReadMax; + } + + // update buffer status + pProtoStream->iBufOut += iRead; + if (pProtoStream->iBufOut == pProtoStream->iBufSize) + { + pProtoStream->iBufOut = 0; + } + pProtoStream->iBufLen -= iRead; + } + else if (iRead < 0) + { + NetPrintf(("protostream: stream error - aborting\n")); + pProtoStream->eState = ST_IDLE; + } + } + else // enforce minimum data amount, if specified + { + // make sure we have enough data + if (pProtoStream->iBufLen < pProtoStream->iBufMin) + { + return(0); + } + // read minimum amount of data + if ((iReadMax = ProtoStreamRead(pProtoStream, (char *)pProtoStream->pBufMin, pProtoStream->iBufMin, pProtoStream->iBufMin)) != pProtoStream->iBufMin) + { + NetPrintf(("protostream: stream error; minbuf read failed err=%d\n", iReadMax)); + return(0); + } + // pass data on to caller + iRead = pProtoStream->pCallback(pProtoStream, eStatus, pProtoStream->pBufMin, iReadMax, pProtoStream->pUserData); + } + + return(iRead); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoStreamDataCallbackProcess + + \Description + Do data callback processing. + + \Input *pProtoStream - pointer to module state + + \Output + None. + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoStreamDataCallbackProcess(ProtoStreamRefT *pProtoStream) +{ + ProtoStreamStatusE eStatus; + uint32_t uTick = NetTick(); + int32_t iRead; + + // if we're prebuffering, wait until there is enough data or the stream is complete + if (pProtoStream->bPrebuffering == TRUE) + { + if ((pProtoStream->iBufLen < pProtoStream->iRestartThreshold) && (pProtoStream->iStreamStatus != 1)) + { + return; + } + NetPrintf(("protostream: prebuffered %d bytes\n", pProtoStream->iBufLen)); + pProtoStream->bPrebuffering = FALSE; + } + + // determine callback status type + if (pProtoStream->uLastCallback != 0) + { + // recurring data callback + eStatus = PROTOSTREAM_STATUS_DATA; + + // only update if update rate exceeded and not paused + if ((NetTickDiff(uTick, pProtoStream->uLastCallback) < pProtoStream->iCallbackRate) || (pProtoStream->bPaused == TRUE)) + { + return; + } + } + else + { + // start of stream + eStatus = PROTOSTREAM_STATUS_BEGIN; + } + + // update callback timer + pProtoStream->uLastCallback = uTick; + + // do the callback + iRead = _ProtoStreamDataCallback(pProtoStream, eStatus); + if ((iRead > 0) && (pProtoStream->iBufOut == 0) && (pProtoStream->iBufLen > 0)) + { + /* if they consumed all of the data, and we wrapped, + and there is still data to be consumed, give them + another shot at the data */ + _ProtoStreamDataCallback(pProtoStream, eStatus); + } + + // if we have emptied the buffer, and the stream is not done, rebuffer + if ((pProtoStream->iBufLen == 0) && (pProtoStream->iStreamStatus == 0)) + { + NetPrintf(("protostream: exhausted buffer; prebuffering %d bytes\n", pProtoStream->iRestartThreshold)); + pProtoStream->bPrebuffering = TRUE; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoStreamOpen + + \Description + Begin streaming an Internet media source + + \Input *pProtoStream - pointer to module state + \Input *pUrl - resource to stream + \Input *pReq - request body, if POST + \Input iFreq - restart frequency in seconds, or PROTOSTREAM_FREQ_* + + \Output + int32_t - negative=failure, else success + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoStreamOpen(ProtoStreamRefT *pProtoStream, const char *pUrl, const char *pReq, int32_t iFreq) +{ + int32_t iResult; + + // reset state + _ProtoStreamReset(pProtoStream); + + // cache info + pProtoStream->iRestartFreq = iFreq; + ds_strnzcpy(pProtoStream->strUrl, pUrl, sizeof(pProtoStream->strUrl)); + + // open stream + if ((iResult = ProtoHttpRequest(pProtoStream->pProtoHttp, pUrl, pReq, -1, pReq != NULL ? PROTOHTTP_REQUESTTYPE_POST : PROTOHTTP_REQUESTTYPE_GET)) >= 0) + { + pProtoStream->eState = ST_OPEN; + } + else + { + NetPrintf(("protostream: error opening stream '%s'\n", pUrl)); + } + + // return result code to caller + return(iResult); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ProtoStreamCreate + + \Description + Create the stream module + + \Input iBufSize - size of streaming buffer (at least PROTOSTREAM_MINBUFFER) + + \Output + ProtoStreamRefT * - new module state, or NULL + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +ProtoStreamRefT *ProtoStreamCreate(int32_t iBufSize) +{ + ProtoStreamRefT *pProtoStream; + int32_t iRefSize; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // enforce minimum buffer size + if (iBufSize < PROTOSTREAM_MINBUFFER) + { + iBufSize = PROTOSTREAM_MINBUFFER; + } + + // calc ref size + iRefSize = sizeof(*pProtoStream) - sizeof(pProtoStream->aBuffer) + iBufSize; + + // allocate and init module state + if ((pProtoStream = DirtyMemAlloc(iRefSize, PROTOSTREAM_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protostream: could not allocate module state\n")); + return(NULL); + } + ds_memclr(pProtoStream, iRefSize); + pProtoStream->iMemGroup = iMemGroup; + pProtoStream->pMemGroupUserData = pMemGroupUserData; + pProtoStream->pCallback = _ProtoStreamDefaultCallback; + + // create http ref + if ((pProtoStream->pProtoHttp = ProtoHttpCreate(4096)) == NULL) + { + NetPrintf(("protostream: could not allocate http module\n")); + DirtyMemFree(pProtoStream, PROTOSTREAM_MEMID, pProtoStream->iMemGroup, pProtoStream->pMemGroupUserData); + return(NULL); + } + + // init other state variables + pProtoStream->iBufSize = iBufSize; + pProtoStream->iBufMin = -1; + pProtoStream->iVerbose = 1; + pProtoStream->iTimeout = 60*1000; + ProtoHttpControl(pProtoStream->pProtoHttp, 'time', pProtoStream->iTimeout, 0, NULL); + + // return ref to caller + return(pProtoStream); +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamDestroy + + \Description + Destroy the ProtoStream module + + \Input *pProtoStream - pointer to module state + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoStreamDestroy(ProtoStreamRefT *pProtoStream) +{ + // free minbuf buffer, if allocated + if (pProtoStream->pBufMin != NULL) + { + DirtyMemFree(pProtoStream->pBufMin, PROTOSTREAM_MEMID, pProtoStream->iMemGroup, pProtoStream->pMemGroupUserData); + } + + // dispose of http module + ProtoHttpDestroy(pProtoStream->pProtoHttp); + + // dispose of module memory + DirtyMemFree(pProtoStream, PROTOSTREAM_MEMID, pProtoStream->iMemGroup, pProtoStream->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamSetCallback + + \Description + Set recurring ProtoStream callback. + + \Input *pProtoStream - pointer to module state + \Input iRate - callback rate in ms + \Input *pCallback - data callback + \Input *pUserData - user data for callback + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoStreamSetCallback(ProtoStreamRefT *pProtoStream, int32_t iRate, ProtoStreamCallbackT *pCallback, void *pUserData) +{ + pProtoStream->pCallback = (pCallback != NULL) ? pCallback : _ProtoStreamDefaultCallback; + pProtoStream->pUserData = pUserData; + pProtoStream->iCallbackRate = iRate; +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamSetHttpCallback + + \Description + Set custom callbacks for ProtoHttp ref managed by ProtoStream. + + \Input *pProtoStream - pointer to module state + \Input *pCustomHeaderCb - pointer to custom send header callback (may be NULL) + \Input *pReceiveHeaderCb- pointer to recv header callback (may be NULL) + \Input *pUserData - user-supplied callback ref (may be NULL) + + \Version 10/26/2011 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoStreamSetHttpCallback(ProtoStreamRefT *pProtoStream, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData) +{ + ProtoHttpCallback(pProtoStream->pProtoHttp, pCustomHeaderCb, pReceiveHeaderCb, pUserData); +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamOpen + + \Description + Begin streaming an Internet media source + + \Input *pProtoStream - pointer to module state + \Input *pUrl - resource to stream + \Input iFreq - restart frequency in seconds, or PROTOSTREAM_FREQ_* + + \Output + int32_t - negative=failure, else success + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoStreamOpen(ProtoStreamRefT *pProtoStream, const char *pUrl, int32_t iFreq) +{ + // reset restart values + pProtoStream->iRestartIncr = 1; + pProtoStream->iRestartThreshold = pProtoStream->iBufSize/2; + + // reset previous stream info + pProtoStream->iLastSize = 0; + pProtoStream->iLastModified = 0; + + // open stream + return(_ProtoStreamOpen(pProtoStream, pUrl, NULL, iFreq)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamOpen2 + + \Description + Begin streaming an Internet media source using POST request + + \Input *pProtoStream - pointer to module state + \Input *pUrl - resource to stream + \Input *pReq - request body + \Input iFreq - restart frequency in seconds, or PROTOSTREAM_FREQ_* + + \Output + int32_t - negative=failure, else success + + \Version 10/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoStreamOpen2(ProtoStreamRefT *pProtoStream, const char *pUrl, const char *pReq, int32_t iFreq) +{ + // reset restart values + pProtoStream->iRestartIncr = 1; + pProtoStream->iRestartThreshold = pProtoStream->iBufSize/2; + + // reset previous stream info + pProtoStream->iLastSize = 0; + pProtoStream->iLastModified = 0; + + // open stream + return(_ProtoStreamOpen(pProtoStream, pUrl, pReq, iFreq)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamRead + + \Description + Read at least iMinLen bytes from stream into user buffer. + + \Input *pProtoStream - pointer to module state + \Input *pBuffer - [out] output buffer + \Input iBufLen - size of output buffer + \Input iMinLen - minimum amount of data to copy + + \Output + int32_t - number of bytes copied; negative=stream not open + + \Version 11/21/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoStreamRead(ProtoStreamRefT *pProtoStream, char *pBuffer, int32_t iBufLen, int32_t iMinLen) +{ + char *pBufStart = pBuffer; + int32_t iBufCopy; + + // make sure we're open + if (pProtoStream->eState != ST_OPEN) + { + NetPrintf(("protostream: no stream open\n")); + return(-1); + } + + // make sure we're not prebuffering, have enough data, and are not paused + if ((pProtoStream->bPrebuffering == TRUE) || (pProtoStream->iBufLen < iMinLen) || (pProtoStream->bPaused == TRUE)) + { + return(0); + } + + // determine amount of data to copy + iBufCopy = (pProtoStream->iBufLen < iBufLen) ? pProtoStream->iBufLen : iBufLen; + + // does copy span tail end of buffer? + if ((pProtoStream->iBufOut + iBufCopy) > pProtoStream->iBufSize) + { + // copy tail end of data to buffer + int32_t iBufCopy2 = pProtoStream->iBufSize - pProtoStream->iBufOut; + ds_memcpy(pBuffer, &pProtoStream->aBuffer[pProtoStream->iBufOut], iBufCopy2); + + NetPrintfVerbose((pProtoStream->iVerbose, 1, "protostream: read [0x%04x,0x%04x]\n", pProtoStream->iBufOut, pProtoStream->iBufOut+iBufCopy2)); + + // adjust buffer parameters + pProtoStream->iBufOut = 0; + pProtoStream->iBufLen -= iBufCopy2; + + // adjust current copy parameters + pBuffer += iBufCopy2; + iBufCopy -= iBufCopy2; + } + + // copy data to output buffer + NetPrintfVerbose((pProtoStream->iVerbose, 1, "protostream: read [0x%04x,0x%04x]\n", pProtoStream->iBufOut, pProtoStream->iBufOut+iBufCopy)); + ds_memcpy(pBuffer, &pProtoStream->aBuffer[pProtoStream->iBufOut], iBufCopy); + pBuffer += iBufCopy; + + // adjust buffer parameters + pProtoStream->iBufOut += iBufCopy; + if (pProtoStream->iBufOut == pProtoStream->iBufSize) + { + pProtoStream->iBufOut = 0; + } + pProtoStream->iBufLen -= iBufCopy; + + // return amount copied to caller + return((int32_t)(pBuffer - pBufStart)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamPause + + \Description + Pause/unpause stream. + + \Input *pProtoStream - pointer to module state + \Input bPause - TRUE to pause, else FALSE + + \Version 01/25/2012 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoStreamPause(ProtoStreamRefT *pProtoStream, uint8_t bPause) +{ + NetPrintf(("protostream: pause %s (was %s)\n", bPause ? "enabled" : "disabled", pProtoStream->bPaused ? "enabled" : "disabled")); + if ((pProtoStream->bPaused = bPause) == TRUE) + { + ProtoHttpControl(pProtoStream->pProtoHttp, 'time', 0x7fffffff, 0, NULL); + } + else + { + ProtoHttpControl(pProtoStream->pProtoHttp, 'time', pProtoStream->iTimeout, 0, NULL); + } +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamClose + + \Description + Stop streaming + + \Input *pProtoStream - pointer to module state + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoStreamClose(ProtoStreamRefT *pProtoStream) +{ + _ProtoStreamReset(pProtoStream); +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamStatus + + \Description + Get module status. + + \Input *pProtoStream - pointer to module state + \Input iStatus - status selector + \Input *pBuffer - selector specific + \Input iBufSize - selector specific + + \Output + int32_t - selector specific + + \Notes + iStatus can be one of the following: + + \verbatim + 'bufl' - amount of data currently buffered + 'bufs' - size of current stream buffer + 'code' - most recent http result code + 'done' - stream status: -1=error, 0=in progress, 1=complete + 'serr' - copies http error result string to pBuffer + \endverbatim + + Unrecognized codes are passed through to ProtoHttpStatus(). + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoStreamStatus(ProtoStreamRefT *pProtoStream, int32_t iStatus, void *pBuffer, int32_t iBufSize) +{ + // return current amount of data in buffer + if (iStatus == 'bufl') + { + return(pProtoStream->iBufLen); + } + // return current size of stream buffer + if (iStatus == 'bufs') + { + return(pProtoStream->iBufSize); + } + // return most recent http result code + if (iStatus == 'code') + { + return(pProtoStream->iLastHttpCode); + } + // return completion status + if (iStatus == 'done') + { + return(pProtoStream->iStreamStatus); + } + // return error string, if set + if (iStatus == 'serr') + { + ds_strnzcpy(pBuffer, pProtoStream->strError, iBufSize); + return(0); + } + // if not handled, let ProtoHttp take a stab at it + return(ProtoHttpStatus(pProtoStream->pProtoHttp, iStatus, pBuffer, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamControl + + \Description + Set control options + + \Input *pProtoStream - pointer to module state + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + iStatus can be one of the following: + + \verbatim + 'minb' - set minimum data amount of data for data callback (note; not enforced at end of stream) + 'play' - set whether playing or not (client timeout is disabled if not playing) + 'spam' - set verbose debug level (debug only) + 'strv' - set number of seconds to starve input (debug only) + \endverbatim + + Unhandled codes are passed through to ProtoHttpControl(). + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoStreamControl(ProtoStreamRefT *pProtoStream, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + // set minimum amount of data caller needs in a callback + if (iControl == 'minb') + { + if ((pProtoStream->iBufMin != iValue) && (pProtoStream->pBufMin != NULL)) + { + DirtyMemFree(pProtoStream->pBufMin, PROTOSTREAM_MEMID, pProtoStream->iMemGroup, pProtoStream->pMemGroupUserData); + } + if ((iValue > 0) && ((pProtoStream->pBufMin = DirtyMemAlloc(iValue, PROTOSTREAM_MEMID, pProtoStream->iMemGroup, pProtoStream->pMemGroupUserData)) != NULL)) + { + pProtoStream->iBufMin = iValue; + } + else + { + NetPrintf(("protostream: could not allocate %d bytes for minbuffer\n", iValue)); + pProtoStream->iBufMin = -1; + } + return(0); + } + // set http timeout based on if application is playing back or not + if (iControl == 'play') + { + // set parameters to pass through to ProtoHttp + iControl = 'time'; + iValue = (iValue != 0) ? pProtoStream->iTimeout : 0x40000000; // 'disabled' == very large timeout value + } + #if DIRTYCODE_LOGGING + // set verbosity for us and pass through to protohttp as well + if (iControl == 'spam') + { + pProtoStream->iVerbose = iValue; + } + #endif + #if DIRTYCODE_DEBUG + // set to starve for a period of time (debug only) + if (iControl == 'strv') + { + NetPrintf(("protostream: starving input for %d seconds\n", iValue)); + pProtoStream->uStarveTime = NetTick() + (iValue * 1000); + return(0); + } + #endif + if (iControl == 'time') + { + // remember most recent timeout value, and pass through to protohttp + pProtoStream->iTimeout = iValue; + } + + // if not handled, let ProtoHttp take a stab at it + return(ProtoHttpControl(pProtoStream->pProtoHttp, iControl, iValue, iValue2, pValue)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoStreamUpdate + + \Description + Update the ProtoStream module + + \Input *pProtoStream - pointer to module state + + \Version 11/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoStreamUpdate(ProtoStreamRefT *pProtoStream) +{ + // update module in idle state + if (pProtoStream->eState == ST_IDLE) + { + // if we have a url and are in restart mode + if ((pProtoStream->strUrl[0] != '\0') && (pProtoStream->iRestartFreq != PROTOSTREAM_FREQ_ONCE)) + { + // time to restart? + if (NetTickDiff(NetTick(), pProtoStream->iRestartTime) > 0) + { + char strUrl[PROTOSTREAM_MAXURL]; + ds_strnzcpy(strUrl, pProtoStream->strUrl, sizeof(strUrl)); + _ProtoStreamOpen(pProtoStream, strUrl, NULL, pProtoStream->iRestartFreq); + } + } + } + + // update stream in open state + if (pProtoStream->eState == ST_OPEN) + { + int32_t iRecvResult; + + // give time to http module + ProtoHttpUpdate(pProtoStream->pProtoHttp); + + // try and receive data + for ( iRecvResult = 1; iRecvResult > 0; ) + { + if ((iRecvResult = _ProtoStreamRecv(pProtoStream)) >= 0) + { + _ProtoStreamUpdateStats(pProtoStream, iRecvResult); + } + } + + // user data callback processing + _ProtoStreamDataCallbackProcess(pProtoStream); + + // check for stream completion + if ((pProtoStream->iStreamStatus != 0) && (pProtoStream->iBufLen == 0)) + { + _ProtoStreamDone(pProtoStream); + } + } +} diff --git a/src/thirdparty/dirtysdk/source/proto/prototunnel.c b/src/thirdparty/dirtysdk/source/proto/prototunnel.c new file mode 100644 index 00000000..3a3a9a64 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/prototunnel.c @@ -0,0 +1,3314 @@ +/*H********************************************************************************/ +/*! + \File prototunnel.c + + \Description + + ProtoTunnel creates and manages a Virtual DirtySock Tunnel connection. The + tunnel transparently bundles data sent from multiple ports to a specific + remote host into a single send, optionally encrypting packets based on the + tunnel mappings set up by the caller. Only data sent over a UDP socket may + be tunneled. ProtoTunnel does not itself send packets unsolicited, it merely + encapsulates packets being sent by other API's like CommUDP or VoIP. + Therefore, ProtoTunnel packets always include user data. + + \Notes + \verbatim + Encryption Key + + ProtoTunnel assumes that the encryption key used is provided by the + caller creating the tunnel. It does not provide any mechanism for + secure key exchange; this is left to the caller. A good random + encryption key of at least sixty-four characters is strongly recommended + to provide adequate security. + + Packet format + + ProtoTunnel bundles multiple packets bound for different ports on the + same destination address into a single packet. There are two types of + packets, handshake packets and regular packets. A handshake packet is + identical in layout to a regular packet, with the addition of handshake + data. Packets contain the following data elements: + + - Two-byte Ident field + - Two-byte tunnel encryption header + - Handshake info (handshake packets only) + - Encrypted list of one or more two-byte sub-packet headers + - Encrypted HMAC message digest + - Encrypted packet data (if any) + - Unencrypted packet data (if any) + + 0 8 15 + ------------------------------- + | Ident | Ident field + ------------------------------- + | Encryption | Encryption field + ------------------------------- + | | + | Handshake | Handshake field (handshake packets only) + | | + ------------------------------- + | Packet size | PIdx | Packet #1 header + ------------------------------- + | Packet size | PIdx | Packet #2 header + ------------------------------- + . + . + . + ------------------------------- + | Packet size | PIdx | Packet #n header + ------------------------------- + | | + | HMAC | HMAC message digest (length dependent on HMAC type and size, may be zero) + | | + ------------------------------- + | | + | Encrypted Packet Data | Encrypted Packet Data + | | + ------------------------------- + | | + | Unencrypted Packet Data | Unencrypted Packet Data + | | + ------------------------------- + + Ident field + + The Ident field contains one of two values; handshake packets are identified + by the high bit being set (0x8000) and otherwise contain a two byte protocol + version identifier. For example, an Ident field of 0x8101 would indicate a + handshake packet using protocol version 1.1. Non-handshake packets do not + include the high bit, with the lower 15 bits used to indicate the remote + tunnel index on an active connection. The ident field is authenticated but + not encrypted, so it may be processed before the packet is decrypted. During + handshaking, an endpoint receiving packets with a lesser version of the + protocol specified will downgrade to that version. (Note that downgrading + to versions older than 1.1 is not supported). The ident field is the only + field that is required to exist in any given ProtoTunnel protocol version. + + Encryption field + + The Encryption field contains a 16bit stream index used to synchronize + the stream cipher in the event of lost packets. The high bit (0x8000) + is reserved to indicate that the packet is encrypted. The remaining + 15bit value is shifted up by three bits to give a total window size of 256k. + Because of the shift, the stream cipher encrypts in blocks of eight bytes. + This requires the stream cipher to be iterated over the amount + of data that is encrypted, and then advanced by the number of bytes + required to round up to the next multiple of eight bytes. In the event + one or more tunnel packets are lost, the receiving side will advance the + stream cipher by the amount of data lost to remain synchronized with the + sender. A packet received that has a negative stream offset relative to + the current offset is assumed to be a previous packet. ProtoTunnel will + make an attempt to decrypt such a packet with a copy of the previous + state. If that fails, the packet is discarded. All packets contain some + encrypted data, and all packet data is authenticated (see HMAC below). + + Handshake field + + Handshake data is sent until a connection is determined to be active. The + format of the field is as follows: + + 0 8 15 + ------------------------------- + | Client Ident | Client Ident field + | | + ------------------------------- + | Connection Ident | Connection Ident field + ------------------------------- + | Active |XXXXXXXXXXXXXX| Active field + ------------------------------- + + The Client Ident is used on the receiving side to match incoming handshake + packets to the specific tunnel endpoint configured to receive them. This + is necessary when the packets arrive from an unexpected address and/or port, + usually due to NAT. Because the Handshake data is unencrypted, this can + be trivially done, without having to potentially try decryption against + many endpoints. This is an important consideration for servers that many + have hundreds or even thousands of endpoints. + + The Connection Ident field is used to specify the Ident that non-handshake + packets from the remote endpoint will use to identify the connection they + belong to. This ident is simply the index of the tunnel endpoint in the + tunnel list. Because the non-handshake Ident field is 15 bits, this limits + the maximum number of tunnel endpoints to 32,767. The Ident present in + non-handshake packets is useful when a port and/or address change takes + place. There are multiple scenarios where this type of behavior can occur; + for example a connection where data is not sent for a period of time can + have its route expired by a NAT device. When data resumes being sent, + it likely will originate from a different port. Another example is a + corporate firewall, which may aggressively reallocate ports or even + addresses dynamically. Including the Ident in unencrypted form non- + handshake packets allows trivial rematching of incoming packet data without + needing to decrypt the packet data. + + The Active field is how ProtoTunnel determines if a connection is active. + Initially, it is set to zero. When an endpoint successfully receives + data from a remote endpoint, the Active field in the handshake packets + it sends is set to one. When an endpoint receives a handshake packet + from a remote endpoint with the Active field set to one, the connection + is considered to be complete, and handshake information is no longer + sent. + + Packet Headers + + Packet headers consist of a twelve-bit size field and a four-bit port + index. This means the maximum bundled packet size is 4k and the maximum + number of port mappings in a tunnel is 16. ProtoTunnel restricts the + actual maximum number of port mappings to eight. ProtoTunnel also + reserves the last port to use for embedded control data (see ProtoTunnel + Control Info below), so this leaves seven user-allocatable ports. It + also means that tunnels are required to be created with the same port + mappings on each side of the tunnel, otherwise the bundled tunnel + packets will not be forwarded to the correct ports. The tunnel + demultiplexer iterates through the packet headers until the aggregate + size of packet data plus packet headers plus encryption header equals + the size of the received packet. When the packet format is encrypted, + packet headers are decrypted one at a time until all of the headers are + successfully decrypted. If there are any discrepencies (e.g. invalid + port mappings or aggregate sizes don't match) the packet is considered + to be invalid and is discarded. + + Encrypted HMAC message digest + + The HMAC message digest provides message authentication; in the event + that packet data is modified on the wire, the HMAC calculation on the + receiving side will not match the expected result, which will indicate + that the data has been modified, and the packet will be discarded. Any + supported HMAC algorithm maybe be chosen, and any amount of truncation + is supported up to 1/2 the hash size. The maximum size of the HMAC is + 24 bytes. A NULL HMAC may be chosen, in which case no space is consumed + in the packet body. It is assumed that both sides are configured with + the same HMAC type and size, otherwise a connection is not be possible. + + Encrypted Packet Data + + Encrypted packets, if any, immediately follow the final header. All + encrypted packet data is decrypted together in one pass. + + Unencrypted Packet Data + + Unencrypted packets, if any, immediately follow encrypted packets, or + packet headers if there are no encrypted packets. + + Matching packets to an endpoint + + When ProtoTunnel is running in a configuration where multiple endpoints + are using the same socket, an incoming packet must be matched to a specific + tunnel endpoint for it to be properly received. Two examples of this type + of scenario are a dedicated server, or a peer-mesh game with more than + two clients. Due to NAT behavior we may not always know where a packet + will be coming from at any given time. + + ProtoTunnel matches differently depending on whether the tunnel is active + or not. If the tunnel is not yet active, the ClientId field in the handshake + data is used to match the packet to the appropriate tunnel. During this + phase of handshaking, ProtoTunnel will update the address and port of the + remote endpoint based on where the data originates from, assuming the + packet can be decrypted and authenticated. When a tunnel is active, + ProtoTunnel uses the Ident field to directly route the packet to the proper + endpoint. If the source addr and/or port change while a tunnel is active, + the updated port will be detected and used to match future packets. + + \endverbatim + + \Copyright + Copyright (c) 2005-2015 Electronic Arts Inc. + + \Version 12/02/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/crypt/cryptarc4.h" +#include "DirtySDK/crypt/crypthash.h" +#include "DirtySDK/crypt/crypthmac.h" + +#include "DirtySDK/proto/prototunnel.h" + +/*** Defines **********************************************************************/ + +#define PROTOTUNNEL_HMAC_DEBUG (FALSE) +#define PROTOTUNNEL_HMAC_MAXSIZE (16) //!< maximum supported HMAC size +#define PROTOTUNNEL_HMAC_DEFSIZE (12) //!< default supported HMAC size + +#define PROTOTUNNEL_PKTHDRSIZE (2) +#define PROTOTUNNEL_MAXHDROFFS (sizeof(ProtoTunnelHandshakeT)) +#define PROTOTUNNEL_MAXPACKETS (8) +#define PROTOTUNNEL_MAXHDRSIZE (PROTOTUNNEL_PKTHDRSIZE * PROTOTUNNEL_MAXPACKETS) +#define PROTOTUNNEL_PACKETBUFFER (SOCKET_MAXUDPRECV) +#define PROTOTUNNEL_MAXPACKET (PROTOTUNNEL_PACKETBUFFER - PROTOTUNNEL_MAXHDROFFS - PROTOTUNNEL_PKTHDRSIZE - PROTOTUNNEL_HMAC_MAXSIZE) + +#define PROTOTUNNEL_CRYPTBITS (3) // number of bits added to extend window +#define PROTOTUNNEL_CRYPTALGN (1 << PROTOTUNNEL_CRYPTBITS) +#define PROTOTUNNEL_CRYPTMASK (PROTOTUNNEL_CRYPTALGN-1) + +#define PROTOTUNNEL_CRYPTARC4_ITER (12) // improved security by skipping slightly less secure first output data (3k bytes), this data is not transmitted so does not produce overhead on the connection + +#define PROTOTUNNEL_CONTROL_PORTIDX (PROTOTUNNEL_MAXPORTS-1) + +#define PROTOTUNNEL_IDENT_HANDSHAKE (0x8000) + +#define PROTOTUNNEL_IPPROTO (IPPROTO_IP) + +#define PROTOTUNNEL_MAXKEYS (PROTOTUNNEL_MAXPORTS) + +// comma delimited set of support versions +// IMPORTANT: make sure to add/delete when min/current version is changed +#define PROTOTUNNEL_VERSIONS ("1.1") + +typedef enum ProtoTunnelControlE +{ + PROTOTUNNEL_CTRL_INIT +} ProtoTunnelControlE; + +typedef enum ProtoTunnelPortListFuncE +{ + PROTOTUNNEL_PORTLIST_ADD, + PROTOTUNNEL_PORTLIST_DEL, + PROTOTUNNEL_PORTLIST_CHK +} ProtoTunnelPortListFuncE; + +// packet processing return codes +#define PROTOTUNNEL_PACKETRECVFAIL_VALIDATE (-1) //!< decrypt or validate failed +#define PROTOTUNNEL_PACKETRECVFAIL_NOMATCH (-2) //!< could not match packet to a tunnel +#define PROTOTUNNEL_PACKETRECVFAIL_VERSION (-3) //!< fatal prototunnel protocol version mismatch +#define PROTOTUNNEL_PACKETRECVFAIL_OUTOFORDER (-4) //!< out-of-order packet discard; packet is previous to our current window offset +#define PROTOTUNNEL_PACKETRECVFAIL_TRYAGAIN (-5) //!< packet decrypt failed but rematch found new key/offset; try again + +/*** Macros ***********************************************************************/ + +#define PROTOTUNNEL_GetIdentFromPacket(__pPacketData) (((__pPacketData[0])<<8) | (__pPacketData)[1]) +#define PROTOTUNNEL_GetEncryptionFromPacket(__uTunnelVers, __pPacketData) (((__pPacketData[2])<<8) | (__pPacketData)[3]) +#define PROTOTUNNEL_GetStreamOffsetFromPacket(__uTunnelVers, __pPacketData) (PROTOTUNNEL_GetEncryptionFromPacket(__uTunnelVers, __pPacketData) & 0x7fff) + +/*** Type Definitions *************************************************************/ + +//! handshake packet format for version 1.1 of the protocol +typedef struct ProtoTunnelHandshake_1_1_T +{ + uint8_t aCrypt[2]; //!< stream cipher offset + uint8_t aClientIdent[4]; //!< client identifier, used to tag handshake packets for initial matching of packet to tunnel + uint8_t aConnIdent[2]; //!< connection identifier, used to tag regular packets for re-matching + uint8_t uActive; //!< is connection active? +} ProtoTunnelHandshake_1_1_T; + +//! prototunnel handshake packet format +typedef struct ProtoTunnelHandshakeT +{ + uint8_t aIdent[2]; + union + { + ProtoTunnelHandshake_1_1_T V1_1; + } Version; +} ProtoTunnelHandshakeT; + +//! handshake control info +typedef struct ProtoTunnelControlT +{ + uint8_t uPacketType; //!< PROTOTUNNEL_CTRL_* + uint8_t aClientId[4]; //!< source clientId + uint8_t aProtocolVers[2]; //!< protocol version + uint8_t uActive; //!< is connection active? +} ProtoTunnelControlT; + +typedef struct ProtoTunnelT +{ + ProtoTunnelInfoT Info; //!< mapping info + uint32_t uVirtualAddr; //!< virtual address, used for NAT support + uint32_t uLocalClientId; //!< tunnel-specific local clientId (can override ProtoTunnelT if non-zero) + int16_t iBuffSize; //!< current next open position in packet data buffer + int16_t iDataSize; //!< current amount of data queued + int8_t iNumPackets; //!< number of packets queued + uint8_t uHandshakeSize; //!< size of handshake header, determined by protocol version + uint8_t uConnIdent[2]; //!< connection identifier, determined in handshaking (version 1.1+ only) + + NetCritT PacketCrit; //!< packet buffer critical section + + uint16_t uSendOffset; //!< stream send offset + uint16_t uRecvOffset; //!< stream recv offset + uint16_t uRecvOOPOffset; //!< stream recv offset for OOPState + uint16_t _pad2; + + CryptArc4T CryptSendState; //!< crypto state for encrypting data + CryptArc4T CryptRecvState; //!< crypto state for decrypting data + CryptArc4T CryptRecvOOPState; //!< state for receiving out-of-order packets + + #if DIRTYCODE_LOGGING + uint32_t uLastStatUpdate; //!< last time stat update was printed + ProtoTunnelStatT LastSendStat; //!< previous send stat + #endif + + uint32_t uLastTunnelSend; //!< last time data was sent on this tunnel + + ProtoTunnelStatT SendStat; //!< cumulative send statistics + ProtoTunnelStatT RecvStat; //!< cumulative recv statistics + + uint32_t uLastSendNumBytes; //!< tracking variable for send bytes per second calculation + uint32_t uLastSendNumSubpacketBytes; //!< tracking variable for send number of subpacket bytes per second calculation + uint32_t uLastRecvNumBytes; //!< tracking variable for receive bytes per second calculation + uint32_t uLastRecvNumSubpacketBytes; //!< tracking variable for receive number of subpacket bytes per second calculation + + char aKeyList[PROTOTUNNEL_MAXKEYS][128]; //!< crypto key(s) + char aHmacKey[64]; //!< storage for HMAC key (HMAC IV encrypted by tunnel key) + uint8_t uActive; //!< "active" if we've received data from remote peer + uint8_t uRefCount; //!< tunnel ref count + uint8_t uSendKey; //!< index of key we are using to send with + uint8_t uRecvKey; //!< index of key we are using to recv with + uint8_t bSendCtrlInfo; //!< whether to send control info or not + uint8_t aForceCrypt[PROTOTUNNEL_MAXPACKETS]; //!< force crypt override (per sub-packet) + uint8_t aPacketData[PROTOTUNNEL_PACKETBUFFER+PROTOTUNNEL_MAXHDRSIZE+2]; //!< packet aggregator +} ProtoTunnelT; + +struct ProtoTunnelRefT +{ + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + SocketT *pSocket; //!< physical socket tunnel uses + SocketT *pServerSocket; //!< server socket (xboxone only) + + uint16_t aServerRemotePortList[16]; //!< remote ports for server socket + uint16_t uTunnelPort; //!< port socket tunnel is bound to + uint32_t uLocalClientId; //!< local client identifier + + uint16_t uVersion; //!< protocol version + uint8_t uHmacType; //!< HMAC hash type + uint8_t uHmacSize; //!< HMAC size + + int32_t iMaxTunnels; //!< maximum number of tunnels + int32_t iMaxRecv; //!< maximum number of receive calls that may be made in one execution of _ProtoTunnelRecvCallback + int32_t iIdleCbRate; //!< rate that socket callback is called by idle thread in milliseconds + int32_t iVerbosity; //!< verbosity level + + uint32_t uVirtualAddr; //!< next free virtual address + uint32_t uFlushRate; //!< rate at which packets are flushed by update + + #if DIRTYCODE_DEBUG + int32_t iPacketDrop; //!< drop the next n packets + #endif + + uint32_t uNumRecvCalls; //!< number of receive calls + uint32_t uNumPktsRecvd; //!< number of packets received + uint32_t uNumSubPktsRecvd; //!< number of sub-packets received + uint32_t uNumPktsDiscard; //!< number of out-of-order packet discards + + ProtoTunnelCallbackT *pCallback; //!< prototunnel event callback + void *pUserData; //!< callback user data + + RawRecvCallbackT *pRawRecvCallback; //!< raw inbound data filtering function + void *pRawRecvUserData; //!< callback user data + + NetCritT TunnelsCritS; //!< "send thread" critical section + NetCritT TunnelsCritR; //!< "recv thread" critical section + ProtoTunnelT Tunnels[1]; //!< variable-length tunnel array - must be last +}; + +/*** Variables ********************************************************************/ + +// hmac init vector; borrowed from SHA512 initial hash +static const uint8_t _ProtoTunnel_aHmacInitVec[64] = +{ + 0x6a, 0x09, 0xe6, 0x67, 0xf3, 0xbc, 0xc9, 0x08, + 0xbb, 0x67, 0xae, 0x85, 0x84, 0xca, 0xa7, 0x3b, + 0x3c, 0x6e, 0xf3, 0x72, 0xfe, 0x94, 0xf8, 0x2b, + 0xa5, 0x4f, 0xf5, 0x3a, 0x5f, 0x1d, 0x36, 0xf1, + 0x51, 0x0e, 0x52, 0x7f, 0xad, 0xe6, 0x82, 0xd1, + 0x9b, 0x05, 0x68, 0x8c, 0x2b, 0x3e, 0x6c, 0x1f, + 0x1f, 0x83, 0xd9, 0xab, 0xfb, 0x41, 0xbd, 0x6b, + 0x5b, 0xe0, 0xcd, 0x19, 0x13, 0x7e, 0x21, 0x79 +}; + +static uint8_t _ProtoTunnel_bBaseAddrUsed[256]; + +//! used to control behavior for _ProtoTunnelSocketOpen +static uint8_t _ProtoTunnel_bRetryRandomOnFailure = TRUE; + +/*** Private Functions ************************************************************/ + +int32_t ProtoTunnelValidatePacket(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint8_t *pOutputData, const uint8_t *pPacketData, int32_t iPacketSize, const char *pKey); + + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelSetVersion + + \Description + Set protocol version for the specified tunnel + + \Input *pProtoTunnel - module state + \Input *pTunnel - tunnel to set version for + \Input uTunnelVers - version to set (PROTOTUNNEL_VERSION_*) + + \Version 03/26/2015 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoTunnelSetVersion(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint32_t uTunnelVers) +{ + uint32_t uHandshakeSize = 2; // all handshake sizes are a minimum of two bytes + + // calculate size of header + switch (uTunnelVers) + { + case PROTOTUNNEL_VERSION_1_1: + uHandshakeSize += sizeof(ProtoTunnelHandshake_1_1_T); + break; + default: + NetPrintf(("prototunnel: [%p] trying to set unknown version %d.%d; assuming 1.1 format\n", pProtoTunnel, uTunnelVers>>8, uTunnelVers&0xff)); + uHandshakeSize += sizeof(ProtoTunnelHandshake_1_1_T); + break; + } + + pTunnel->Info.uTunnelVers = uTunnelVers; + pTunnel->uHandshakeSize = uHandshakeSize; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelGetBaseAddress + + \Description + Assign a free base address to the specified prototunnel instance + + \Input *pProtoTunnel - module state + + \Output + int32_t - 0 for success, -1 if no address available + + \Version 08/22/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelGetBaseAddress(ProtoTunnelRefT *pProtoTunnel) +{ + int32_t iRetCode = -1; // default to error + int32_t iIndex; + + // skip index=0 because we don't want to create base addresses with format 0.x.x.x + for (iIndex = 1; iIndex < 256; iIndex++) + { + if (_ProtoTunnel_bBaseAddrUsed[iIndex] == FALSE) + { + _ProtoTunnel_bBaseAddrUsed[iIndex] = TRUE; + pProtoTunnel->uVirtualAddr = iIndex << 24; + + NetPrintf(("prototunnel: [%p] base virtual address set to %a\n", pProtoTunnel, pProtoTunnel->uVirtualAddr)); + + iRetCode = 0; // signal success + break; + } + } + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelReleaseBaseAddress + + \Description + Return a free base address to be assigned to a new protoutunnel instance + + \Input *pProtoTunnel - module state + + \Version 08/22/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _ProtoTunnelReleaseBaseAddress(ProtoTunnelRefT *pProtoTunnel) +{ + NetPrintf(("prototunnel: [%p] releasing base virtual address %a\n", pProtoTunnel, (pProtoTunnel->uVirtualAddr&0xFF000000))); + + _ProtoTunnel_bBaseAddrUsed[pProtoTunnel->uVirtualAddr >> 24] = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelIndexFromId + + \Description + Return tunnel index of tunnel with specified Id, or -1 if there is no + tunnel that matches. + + \Input *pProtoTunnel - module state + \Input uTunnelId - tunnel ident + + \Output + int32_t - tunnel index, or -1 if not found + + \Version 02/20/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelIndexFromId(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId) +{ + int32_t iTunnel; + + // find tunnel id + for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel += 1) + { + // found it? + if (pProtoTunnel->Tunnels[iTunnel].uVirtualAddr == uTunnelId) + { + return(iTunnel); + } + } + // not found + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelStreamAdvance + + \Description + Advance stream offset, and advance crypt state if we need to for padding + purposes. + + \Input *pCryptState - pointer to crypt state to advance + \Input *pOffset - pointer to current offset (16bit in crypt data units) + \Input uOffset - amount to advance by (in bytes) + + \Version 12/08/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoTunnelStreamAdvance(CryptArc4T *pCryptState, uint16_t *pOffset, uint32_t uOffset) +{ + uint32_t uCryptAlign; + + // convert from number of bytes to number of crypt data units + *pOffset += (uint16_t)(uOffset>>PROTOTUNNEL_CRYPTBITS); + + // if we have 'left-over' bytes, advance the crypt state and add a data unit + if ((uCryptAlign = (uOffset & PROTOTUNNEL_CRYPTMASK)) != 0) + { + uCryptAlign = PROTOTUNNEL_CRYPTALGN - uCryptAlign; + CryptArc4Advance(pCryptState, uCryptAlign); + *pOffset += 1; + } + // handle 15 bit wrapping + *pOffset &= 0x7fff; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelVirtualToPhysical + + \Description + Convert a virtual address into its corresponding physical address. + + \Input *pProtoTunnel - pointer to module state + \Input uVirtualAddr - virtual address + \Input *pBuf - [out] storage for sockaddr (optional) + \Input iBufSize - size of buffer + + \Output + int32_t - physical address, or zero if no match + + \Version 03/31/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelVirtualToPhysical(ProtoTunnelRefT *pProtoTunnel, uint32_t uVirtualAddr, char *pBuf, int32_t iBufSize) +{ + ProtoTunnelT *pTunnel; + uint32_t uRemoteAddr; + int32_t iTunnel; + + // acquire exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // find tunnel virtual address is bound to + for (iTunnel = 0, uRemoteAddr = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) + { + pTunnel = &pProtoTunnel->Tunnels[iTunnel]; + if (pTunnel->uVirtualAddr == uVirtualAddr) + { + struct sockaddr SockAddr; + if ((pBuf != NULL) && (iBufSize >= (signed)sizeof(SockAddr))) + { + SockaddrInit(&SockAddr, AF_INET); + SockaddrInSetAddr(&SockAddr, pTunnel->Info.uRemoteAddr); + SockaddrInSetPort(&SockAddr, pTunnel->Info.uRemotePort); + ds_memcpy(pBuf, &SockAddr, sizeof(SockAddr)); + } + uRemoteAddr = pTunnel->Info.uRemoteAddr; + break; + } + } + + // release exclusive access to tunnel list + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + + // return addr to caller + return(uRemoteAddr); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelServerPortListFunc + + \Description + Perform a function (add, del, check) on server socket port list + + \Input *pProtoTunnel - pointer to module state + \Input uPort - port value + \Input ePortOp - function type (PORTLIST_*) + + \Output + uint32_t - true=success, else false + + \Version 10/04/2017 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _ProtoTunnelServerPortListFunc(ProtoTunnelRefT *pProtoTunnel, uint16_t uPort, ProtoTunnelPortListFuncE ePortOp) +{ + const int32_t iNumPorts = sizeof(pProtoTunnel->aServerRemotePortList) / sizeof(pProtoTunnel->aServerRemotePortList[0]); + uint16_t uCheckVal = (ePortOp != PROTOTUNNEL_PORTLIST_ADD) ? uPort : 0; + uint32_t bSuccess = FALSE; + int32_t iPortIdx; + + // find entry + for (iPortIdx = 0; iPortIdx < iNumPorts; iPortIdx += 1) + { + if (pProtoTunnel->aServerRemotePortList[iPortIdx] == uCheckVal) + { + break; + } + } + // operate on entry + if (iPortIdx < iNumPorts) + { + switch (ePortOp) + { + case PROTOTUNNEL_PORTLIST_DEL: + uPort = 0; + case PROTOTUNNEL_PORTLIST_ADD: + pProtoTunnel->aServerRemotePortList[iPortIdx] = uPort; + case PROTOTUNNEL_PORTLIST_CHK: + bSuccess = TRUE; + default: + break; + } + } + // return result to caller + return(bSuccess); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelSocketSendto + + \Description + Convert a virtual address into its corresponding physical address. + + \Input *pProtoTunnel - pointer to module state + \Input *pBuf - data to send + \Input iLen - length of data to send + \Input iFlags - flags + \Input *pTo - address to send to + \Input iToLen - length of address + + \Output + int32_t - SocketSendto() result, or SOCKERR_INVALID if no socket + + \Version 10/01/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelSocketSendto(ProtoTunnelRefT *pProtoTunnel, const char *pBuf, int32_t iLen, int32_t iFlags, const struct sockaddr *pTo, int32_t iToLen) +{ + SocketT *pSocket = _ProtoTunnelServerPortListFunc(pProtoTunnel, SockaddrInGetPort(pTo), PROTOTUNNEL_PORTLIST_CHK) ? pProtoTunnel->pServerSocket : pProtoTunnel->pSocket; + if (pSocket != NULL) + { + return(SocketSendto(pSocket, pBuf, iLen, iFlags, pTo, iToLen)); + } + else + { + return(SOCKERR_INVALID); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelBufferCollect + + \Description + Collect buffered data in final packet form. + + \Input *pProtoTunnel - pointer to module state + \Input *pTunnel - tunnel to send data on + \Input iHeadSize - size of packet headers + \Input *pPacketData - [out] buffer for finalized packet + \Input **ppHeaderDst - pointer to current header output in finalized packet + \Input **ppPacketDst - pointer to current packet data output in finalized packet + \Input uPortFlag - flag indicating whether we are collecting encrypted or + unencrypted packet data + \Input *pLimit - end of buffer, used for debug overflow check + + \Output + int32_t - size of packet data added to output buffer + + \Version 12/08/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelBufferCollect(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, int32_t iHeadSize, uint8_t *pPacketData, uint8_t **ppHeaderDst, uint8_t **ppPacketDst, uint32_t uPortFlag, uint8_t *pLimit) +{ + int32_t iDataSize, iPacket, iPacketSize; + uint8_t *pHeaderSrc, *pPacketSrc; + uint32_t bEnabled; + + // point to packet headers and data + pHeaderSrc = pTunnel->aPacketData + PROTOTUNNEL_MAXHDROFFS; + pPacketSrc = pTunnel->aPacketData + PROTOTUNNEL_MAXHDRSIZE + PROTOTUNNEL_MAXHDROFFS; + iDataSize = 0; + + // collect packets + for (iPacket = 0; iPacket < pTunnel->iNumPackets; iPacket++) + { + // get packet size + iPacketSize = (pHeaderSrc[0] << 4) | (pHeaderSrc[1] >> 4); + + // if packet encryption matches that specified by the caller, copy it and its header + bEnabled = (((pTunnel->Info.aPortFlags[pHeaderSrc[1] & 0xf] & (unsigned)PROTOTUNNEL_PORTFLAG_ENCRYPTED) == uPortFlag)); + if (pTunnel->aForceCrypt[iPacket] != 0) + { + bEnabled = !bEnabled; + } + if (bEnabled) + { + // make sure we can fit the packet; this shouldn't happen, because we only buffer packets we can fit in ProtoTunnelBufferData() + if ((((*ppPacketDst) + iPacketSize) - pLimit) > 0) + { + NetPrintf(("prototunnel: [%p][%04d] collect buffer overflow by %d bytes; packet of size %d will be discarded\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), (((*ppPacketDst) + iPacketSize) - pLimit), iPacketSize)); + break; + } + + // copy packet data and add it to packet buffer + ds_memcpy(*ppPacketDst, pPacketSrc, iPacketSize); + (*ppHeaderDst)[0] = pHeaderSrc[0]; + (*ppHeaderDst)[1] = pHeaderSrc[1]; + iDataSize += iPacketSize; + *ppPacketDst += iPacketSize; + *ppHeaderDst += PROTOTUNNEL_PKTHDRSIZE; + } + + // increment to next packet header and packet data + pHeaderSrc += PROTOTUNNEL_PKTHDRSIZE; + pPacketSrc += iPacketSize; + } + + // return size to caller + return(iDataSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelBufferSend + + \Description + Send buffered data + + \Input *pProtoTunnel - pointer to module state + \Input *pTunnel - tunnel to send data on + \Input uCurTick - current tick count + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoTunnelBufferSend(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint32_t uCurTick) +{ + uint8_t aPacketData[PROTOTUNNEL_PACKETBUFFER]; + int32_t iCryptSize, iDataSize, iHeadSize, iHshkSize, iResult; + uint8_t *pHeaderDst, *pPacketDst, *pCrypt; + uint8_t *pHmac; + uint16_t uSendHeader = pTunnel->uSendOffset | 0x8000; // add GENERIC bit so we know the protocol type + struct sockaddr SendAddr; + #if DIRTYCODE_LOGGING + int32_t iNumPackets; + #endif + + // get sole access to packet buffer + NetCritEnter(&pTunnel->PacketCrit); + + // no data to send or socket to send it on? + if ((pProtoTunnel->pSocket == NULL) || (pTunnel->iDataSize <= 0)) + { + NetCritLeave(&pTunnel->PacketCrit); + return; + } + + // create send addr + SockaddrInit(&SendAddr, AF_INET); + SockaddrInSetAddr(&SendAddr, pTunnel->Info.uRemoteAddr); + SockaddrInSetPort(&SendAddr, pTunnel->Info.uRemotePort); + + // format packet in local buffer + ds_memclr(aPacketData, sizeof(aPacketData)); + + // get handshake header size and total header size (including sub-packet headers) + iHshkSize = (pTunnel->bSendCtrlInfo) ? pTunnel->uHandshakeSize : 4; + iHeadSize = (pTunnel->iNumPackets * PROTOTUNNEL_PKTHDRSIZE) + iHshkSize; + + // set up for copy + pHeaderDst = aPacketData + iHshkSize; + pPacketDst = aPacketData + iHeadSize; + + // reserve space for the HMAC; we put it here so it will be encrypted and easy to locate + pHmac = aPacketData + iHeadSize; + iCryptSize = pProtoTunnel->uHmacSize; + ds_memclr(pHmac, iCryptSize); + pPacketDst += iCryptSize; + + // collect encrypted packets + iCryptSize += _ProtoTunnelBufferCollect(pProtoTunnel, pTunnel, iHeadSize, aPacketData, &pHeaderDst, &pPacketDst, PROTOTUNNEL_PORTFLAG_ENCRYPTED, aPacketData + PROTOTUNNEL_PACKETBUFFER); + + // collect unencrypted packets + iDataSize = _ProtoTunnelBufferCollect(pProtoTunnel, pTunnel, iHeadSize, aPacketData, &pHeaderDst, &pPacketDst, 0, aPacketData + PROTOTUNNEL_PACKETBUFFER) + iCryptSize; + + // calculate total encrypted size (encrypted headers + encrypted data, but skip the first two bytes) + iCryptSize += iHeadSize - iHshkSize; + // total size of packet + iDataSize += iHeadSize; + + // write protocol header + if (pTunnel->bSendCtrlInfo) + { + ProtoTunnelHandshakeT Handshake; + uint32_t uLocalClientId = (pTunnel->uLocalClientId != 0) ? pTunnel->uLocalClientId : pProtoTunnel->uLocalClientId; + uint16_t uTunnelIndex = pTunnel - pProtoTunnel->Tunnels; + uint16_t uIdent = pTunnel->Info.uTunnelVers | PROTOTUNNEL_IDENT_HANDSHAKE; + + Handshake.aIdent[0] = (uint8_t)(uIdent >> 8); + Handshake.aIdent[1] = (uint8_t)(uIdent); + Handshake.Version.V1_1.aCrypt[0] = 0; + Handshake.Version.V1_1.aCrypt[1] = 0; + Handshake.Version.V1_1.aClientIdent[0] = (uint8_t)(uLocalClientId >> 24); + Handshake.Version.V1_1.aClientIdent[1] = (uint8_t)(uLocalClientId >> 16); + Handshake.Version.V1_1.aClientIdent[2] = (uint8_t)(uLocalClientId >> 8); + Handshake.Version.V1_1.aClientIdent[3] = (uint8_t)(uLocalClientId); + Handshake.Version.V1_1.aConnIdent[0] = (uint8_t)(uTunnelIndex >> 8); + Handshake.Version.V1_1.aConnIdent[1] = (uint8_t)(uTunnelIndex); + Handshake.Version.V1_1.uActive = pTunnel->uActive; + ds_memcpy(aPacketData, &Handshake, sizeof(Handshake)); + + pCrypt = ((ProtoTunnelHandshakeT *)aPacketData)->Version.V1_1.aCrypt; + } + else + { + aPacketData[0] = pTunnel->uConnIdent[0]; + aPacketData[1] = pTunnel->uConnIdent[1]; + pCrypt = aPacketData+2; + } + + // set tunnel encryption header + pCrypt[0] = (uint8_t)(uSendHeader >> 8); + pCrypt[1] = (uint8_t)(uSendHeader >> 0); + + // calculate the hmac and write it into the space reserved for it + if (pProtoTunnel->uHmacType != CRYPTHASH_NULL) + { + CryptHmacCalc(pHmac, pProtoTunnel->uHmacSize, aPacketData, iDataSize, (uint8_t *)pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), (CryptHashTypeE)pProtoTunnel->uHmacType); + #if PROTOTUNNEL_HMAC_DEBUG + NetPrintMem(aPacketData, iDataSize, "send-hmac-data"); + NetPrintMem(pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), "send-hmac-key"); + NetPrintMem(pHmac, pProtoTunnel->uHmacSize, "send-hmac"); + #endif + } + + // verbose-only writing of unencrypted payload + if (pProtoTunnel->iVerbosity > 3) + { + NetPrintMem(aPacketData, (iDataSize < 64) ? iDataSize : 64, "prototunnel-send-nocrypt"); + } + // encrypt payload and advance stream if necessary + CryptArc4Apply(&pTunnel->CryptSendState, aPacketData + iHshkSize, iCryptSize); + _ProtoTunnelStreamAdvance(&pTunnel->CryptSendState, &pTunnel->uSendOffset, iCryptSize); + + // accumulate stats + pTunnel->SendStat.uLastPacketTime = uCurTick; + pTunnel->SendStat.uNumBytes += iDataSize; + pTunnel->SendStat.uNumSubpacketBytes += iDataSize - iHeadSize - pProtoTunnel->uHmacSize; + pTunnel->SendStat.uNumPackets += 1; + pTunnel->SendStat.uNumSubpackets += pTunnel->iNumPackets + pTunnel->bSendCtrlInfo; + + #if DIRTYCODE_LOGGING + iNumPackets = pTunnel->iNumPackets + pTunnel->bSendCtrlInfo; + #endif + + // mark data as sent + pTunnel->iBuffSize = 0; + pTunnel->iDataSize = 0; + pTunnel->iNumPackets = 0; + ds_memclr(pTunnel->aForceCrypt, sizeof(pTunnel->aForceCrypt)); + + // release packet buffer critical section + NetCritLeave(&pTunnel->PacketCrit); + + #if DIRTYCODE_LOGGING + if (NetTickDiff(uCurTick, pTunnel->uLastStatUpdate) > 5000) + { + ProtoTunnelStatT DiffStat; + DiffStat.uNumBytes = pTunnel->SendStat.uNumBytes - pTunnel->LastSendStat.uNumBytes; + DiffStat.uNumSubpacketBytes = pTunnel->SendStat.uNumSubpacketBytes - pTunnel->LastSendStat.uNumSubpacketBytes; + DiffStat.uNumPackets = pTunnel->SendStat.uNumPackets - pTunnel->LastSendStat.uNumPackets; + DiffStat.uNumSubpackets = pTunnel->SendStat.uNumSubpackets - pTunnel->LastSendStat.uNumSubpackets; + ds_memcpy_s(&pTunnel->LastSendStat, sizeof(pTunnel->LastSendStat), &pTunnel->SendStat, sizeof(pTunnel->SendStat)); + NetPrintfVerbose((pProtoTunnel->iVerbosity, 2, "prototunnel: [%p][%04d] pkts: %d eff: %.2f sent: %d eff: %.2f\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), + DiffStat.uNumPackets, (float)DiffStat.uNumSubpackets / (float)DiffStat.uNumPackets, + DiffStat.uNumBytes, (float)DiffStat.uNumSubpacketBytes / (float)DiffStat.uNumBytes)); + pTunnel->uLastStatUpdate = uCurTick; + } + if (pProtoTunnel->iVerbosity > 3) + { + NetPrintMem(aPacketData, (iDataSize < 64) ? iDataSize : 64, "prototunnel-send"); + } + #endif + + // send the data + if ((iResult = _ProtoTunnelSocketSendto(pProtoTunnel, (char *)aPacketData, iDataSize, 0, &SendAddr, sizeof(SendAddr))) < 0) + { + NetPrintf(("prototunnel: [%p][%04d] send - error %d sending buffered packet to %a:%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), + iResult, SockaddrInGetAddr(&SendAddr), SockaddrInGetPort(&SendAddr))); + } + else + { + NetPrintfVerbose((pProtoTunnel->iVerbosity, 2, "prototunnel: [%p][%04d] send - sent %d bytes [%d packets] to %a:%d\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iDataSize, iNumPackets, SockaddrInGetAddr(&SendAddr), SockaddrInGetPort(&SendAddr))); + } + + pTunnel->uLastTunnelSend = NetTick(); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelBufferData + + \Description + Buffer a send + + \Input *pProtoTunnel - pointer to module state + \Input *pTunnel - tunnel to buffer data on + \Input *pData - data to be sent + \Input uSize - size of data to send + \Input uPortIdx - index of port in tunnel map + \Input bForceCrypt - TRUE to force encryption of packet, else FALSE + + \Output + int32_t - amount of data buffered, or socket error code (SOCKERR_XXX) + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelBufferData(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, const uint8_t *pData, uint32_t uSize, uint32_t uPortIdx, uint8_t bForceCrypt) +{ + uint32_t uCurTick = NetTick(); + uint8_t *pBuffer; + + // make sure data is within size limits + if ((uSize == 0) || (uSize > PROTOTUNNEL_MAXPACKET)) + { + NetPrintf(("prototunnel: [%p][%04d] packet of size %d will not be tunneled (max size = %d)\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uSize, PROTOTUNNEL_MAXPACKET)); + NetPrintMem(pData, 64, "prototunnel-nobuf"); + return(SOCKERR_NORSRC); + } + + // acquire packet buffer critical section + NetCritEnter(&pTunnel->PacketCrit); + + // if buffer is full, or we've already buffered max packets, flush buffer + if ((((unsigned)pTunnel->iDataSize + uSize + PROTOTUNNEL_PKTHDRSIZE) > PROTOTUNNEL_PACKETBUFFER) || + (pTunnel->iNumPackets == PROTOTUNNEL_MAXPACKETS)) + { + // flush current packet + _ProtoTunnelBufferSend(pProtoTunnel, pTunnel, uCurTick); + } + + // if buffer is empty, reserve space for max header data (1.1 handshake + max sub-packet headers) + if (pTunnel->iBuffSize == 0) + { + pTunnel->iBuffSize = PROTOTUNNEL_MAXHDROFFS + PROTOTUNNEL_MAXHDRSIZE; + pTunnel->iDataSize = PROTOTUNNEL_MAXHDROFFS + pProtoTunnel->uHmacSize; + ds_memclr(pTunnel->aPacketData, pTunnel->iBuffSize); + } + + // store packet header + pBuffer = pTunnel->aPacketData + (pTunnel->iNumPackets * PROTOTUNNEL_PKTHDRSIZE) + PROTOTUNNEL_MAXHDROFFS; + pBuffer[0] = (uint8_t)(uSize >> 4); + pBuffer[1] = (uint8_t)((uSize << 4) | uPortIdx); + + // store packet data + pBuffer = pTunnel->aPacketData + pTunnel->iBuffSize; + ds_memcpy(pBuffer, pData, uSize); + pTunnel->iBuffSize += uSize; + pTunnel->iDataSize += uSize + PROTOTUNNEL_PKTHDRSIZE; + pTunnel->aForceCrypt[(uint32_t)pTunnel->iNumPackets] = bForceCrypt; + + // update overall count + pTunnel->iNumPackets += 1; + + if (pTunnel->Info.aPortFlags[uPortIdx] & PROTOTUNNEL_PORTFLAG_AUTOFLUSH) + { + _ProtoTunnelBufferSend(pProtoTunnel, pTunnel, uCurTick); + } + + // release packet buffer critical section + NetCritLeave(&pTunnel->PacketCrit); + + // return buffered size to caller + return(uSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelSendCallback + + \Description + Global send callback handler. + + \Input *pSocket - socket to send on + \Input iType - socket type + \Input *pData - data to be sent + \Input iDataSize - size of data to send + \Input *pTo - destination address + \Input *pCallref - prototunnel ref + + \Output + int32_t - 0 = send not handled; >0 = send successfully handled (bytes sent); <0 = send handled but failed (SOCKERR_XXX) + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelSendCallback(SocketT *pSocket, int32_t iType, const uint8_t *pData, int32_t iDataSize, const struct sockaddr *pTo, void *pCallref) +{ + ProtoTunnelRefT *pProtoTunnel = (ProtoTunnelRefT *)pCallref; + ProtoTunnelT *pTunnel = NULL; + uint32_t uAddr, uPort; + int32_t iPort = 0, iTunnel; + int32_t iResult = 0; // default to "not handled" + uint32_t bFound, bForceCrypt=0; + #if DIRTYCODE_LOGGING + uint32_t bFoundAddr = FALSE; + #endif + struct sockaddr SockAddr; + + // only handle dgram sockets, and don't handle our own socket + if ((iType != SOCK_DGRAM) || (pProtoTunnel->pSocket == pSocket) || (pProtoTunnel->pServerSocket == pSocket)) + { + return(0); // not handled + } + + // if a destination address is not specified, get it from the socket + if (pTo == NULL) + { + SocketInfo(pSocket, 'peer', 0, &SockAddr, sizeof(SockAddr)); + pTo = &SockAddr; + } + + // get destination address and port + uAddr = SockaddrInGetAddr(pTo); + uPort = SockaddrInGetPort(pTo); + + // ignore sends to an invalid destination + if ((uAddr == 0) || (uPort == 0)) + { + return(0); // not handled + } + + // get exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + + // see if we have a match + for (iTunnel = 0, bFound = FALSE; (iTunnel < pProtoTunnel->iMaxTunnels) && (bFound == FALSE); iTunnel++) + { + pTunnel = &pProtoTunnel->Tunnels[iTunnel]; + + // does virtual address match? + if (pTunnel->uVirtualAddr != uAddr) + { + continue; + } + #if DIRTYCODE_LOGGING + // remember if we have a tunnel to this address + bFoundAddr = TRUE; + #endif + // see if we have a port match or are at the end of our port list + for (iPort = 0; iPort < PROTOTUNNEL_MAXPORTS; iPort++) + { + uint32_t uPort2 = pTunnel->Info.aRemotePortList[iPort]; + if (uPort2 == uPort) + { + bFound = TRUE; + break; + } + } + } + + // found a match? + if (bFound == TRUE) + { + // if we have a match, queue data + iResult = _ProtoTunnelBufferData(pProtoTunnel, pTunnel, pData, iDataSize, iPort, bForceCrypt); + } + else + { + NetPrintfVerbose((pProtoTunnel->iVerbosity, 1, "prototunnel: [%p] send - no match for data sent to %a:%d (%s mismatch)\n", pProtoTunnel, uAddr, uPort, bFoundAddr ? "port" : "addr")); + } + + // release exclusive access to tunnel list and return result + NetCritLeave(&pProtoTunnel->TunnelsCritS); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelDecryptAndValidatePacket2 + + \Description + Decrypt the tunnel packet + + \Input *pProtoTunnel - pointer to module state + \Input *pTunnel - tunnel + \Input *pHeadOffset - [out] offset of sub-packet header data + \Input *pPacketData - pointer to tunnel packet + \Input iRecvLen - size of tunnel packet + \Input bUpdateState - TRUE to update crypto state, else FALSE + \Input bCurrentState - TRUE when decrypting against current state, FALSE against oop state + + \Output + int32_t - PROTOTUNNEL_PACKETRECVFAIL_* on error, else number of subpackets decoded + + \Version 12/07/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelDecryptAndValidatePacket2(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint32_t *pHeadOffset, uint8_t *pPacketData, int32_t iRecvLen, uint8_t bUpdateState, uint8_t bCurrentState) +{ + int32_t iEncryptedSize, iNumPackets, iPacketOffset, iPacketSize, iHdrCopySize; + uint8_t aPacketHeader[PROTOTUNNEL_MAXHDRSIZE+PROTOTUNNEL_MAXHDROFFS]; + uint8_t *pPacketStart = pPacketData; + uint32_t uRecvOffset, uRecvOffsetState; + CryptArc4T CryptRecvState, *pCryptRecvState; + uint16_t *pRecvOffsetState; + uint16_t uIdent, uVersion; + uint8_t bSynced = FALSE; + uint32_t uHeadOffset; + + // make a copy of packet header before doing anything else, in case we need to restore it after a decrypt failure + iHdrCopySize = DS_MIN(sizeof(aPacketHeader), (unsigned)iRecvLen); + ds_memcpy(aPacketHeader, pPacketData, iHdrCopySize); + + // get reference to crypt state based on whether we are decoding the current state or oop state + pCryptRecvState = (bCurrentState) ? &pTunnel->CryptRecvState : &pTunnel->CryptRecvOOPState; + pRecvOffsetState = (bCurrentState) ? &pTunnel->uRecvOffset : &pTunnel->uRecvOOPOffset; + + // get stream offset from packet header - this tells us where we need to be to be able to decrypt the packet + uRecvOffset = PROTOTUNNEL_GetStreamOffsetFromPacket(pTunnel->Info.uTunnelVers, pPacketData); + + // work on a local copy of the crypt state and stream offset, in case we need to throw this packet out + ds_memcpy_s(&CryptRecvState, sizeof(CryptRecvState), pCryptRecvState, sizeof(*pCryptRecvState)); + uRecvOffsetState = *pRecvOffsetState; + + // lost data? sync the cipher + if (uRecvOffset != uRecvOffsetState) + { + // calc how many data units we've missed + int16_t iRecvOffset = (signed)uRecvOffset; + int16_t iTunlOffset = (signed)uRecvOffsetState; + // calc 15bit offset + int16_t iRecvDiff = (iRecvOffset - iTunlOffset) & 0x7fff; + /* sign extend our 15bit offset - we do it this way because the simpler way of shifting left and + back again right relies on the right shift sign-extending the value, however in the C standard + it is implementation-dependent whether the shift is treated as signed or unsigned */ + iRecvDiff |= (iRecvDiff & 16384) << 1; + + /* check to see if packet is previous to our current window offset; we call this an out-of-order packet and handle it + differently from a skipped packet, which is ahead of our current window offset. this distinction is made because + it is trivial to advance the state, but it cannot be rolled back */ + if (iRecvDiff < 0) + { + NetPrintf(("prototunnel: [%p][%04d] received out of order packet (off=%d)\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iRecvDiff)); + return(PROTOTUNNEL_PACKETRECVFAIL_OUTOFORDER); + } + + // advance the cipher to resync + NetPrintf(("prototunnel: [%p][%04d] stream skip %d bytes\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iRecvDiff<Tunnels), uVersion>>8, uVersion&0xff)); + if (uVersion != pTunnel->Info.uTunnelVers) + { + NetPrintf(("prototunnel: [%p][%04d] version mismatch; received %d.%d but expected %d.%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uVersion>>8, uVersion&0xff, + pTunnel->Info.uTunnelVers>>8, pTunnel->Info.uTunnelVers&0xff)); + if ((uVersion > pTunnel->Info.uTunnelVers) || (uVersion < PROTOTUNNEL_VERSION_1_1)) + { + NetPrintf(("prototunnel: [%p][%04d] can't connect with protocol version %d.%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uVersion>>8, uVersion&0xff)); + return(PROTOTUNNEL_PACKETRECVFAIL_VERSION); + } + } + + ds_memcpy(&Handshake, pPacketData, sizeof(Handshake)); + pTunnel->uConnIdent[0] = Handshake.Version.V1_1.aConnIdent[0]; + pTunnel->uConnIdent[1] = Handshake.Version.V1_1.aConnIdent[1]; + NetPrintf(("prototunnel: [%p][%04d] setting connection ident to %d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), (pTunnel->uConnIdent[0] << 8) | pTunnel->uConnIdent[1])); + + uHeadOffset = pTunnel->uHandshakeSize; + } + else + { + uVersion = pTunnel->Info.uTunnelVers; + uHeadOffset = 4; + } + + // skip header + pPacketData += uHeadOffset; + iPacketOffset = uHeadOffset; + // save sub-packet header offset for later use + *pHeadOffset = uHeadOffset; + + /* subtract out hmac size while we decrypt packet headers and check overall + packet size, as hmac is not included in that calculation */ + iRecvLen -= pProtoTunnel->uHmacSize; + + // iterate through packet headers + for (iNumPackets = 0, iEncryptedSize = 0; iPacketOffset < iRecvLen; iNumPackets++) + { + // decrypt the packet header + CryptArc4Apply(&CryptRecvState, pPacketData, PROTOTUNNEL_PKTHDRSIZE); + uRecvOffset += PROTOTUNNEL_PKTHDRSIZE; + + // extract size and port info + iPacketSize = (pPacketData[0] << 4) | (pPacketData[1] >> 4); + iPacketOffset += iPacketSize + PROTOTUNNEL_PKTHDRSIZE; + if (pTunnel->Info.aPortFlags[pPacketData[1] & 0xf] & PROTOTUNNEL_PORTFLAG_ENCRYPTED) + { + iEncryptedSize += iPacketSize; + } + + // increment to next packet header + pPacketData += PROTOTUNNEL_PKTHDRSIZE; + } + + // make sure size matches + if (iPacketOffset != iRecvLen) + { + NetPrintf(("prototunnel: [%p][%04d] recv - mismatched size in received packet (%d received, %d decoded)\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iRecvLen+pProtoTunnel->uHmacSize, iPacketOffset)); + // restore original packet header + ds_memcpy(pPacketStart, aPacketHeader, iHdrCopySize); + return(PROTOTUNNEL_PACKETRECVFAIL_VALIDATE); + } + + // restore hmac to packet size, and add hmac to size of data to be decrypted + iRecvLen += pProtoTunnel->uHmacSize; + iEncryptedSize += pProtoTunnel->uHmacSize; + + // decrypt encrypted packet data + if (iEncryptedSize > 0) + { + CryptArc4Apply(&CryptRecvState, pPacketData, iEncryptedSize); + uRecvOffset += iEncryptedSize; + } + + // calculate HMAC and compare with what we received + if (pProtoTunnel->uHmacType != CRYPTHASH_NULL) + { + uint8_t aPackHmac[PROTOTUNNEL_HMAC_MAXSIZE], aCalcHmac[PROTOTUNNEL_HMAC_MAXSIZE]; + // make a copy of HMAC + ds_memcpy_s(aPackHmac, sizeof(aPackHmac), pPacketData, pProtoTunnel->uHmacSize); + // clear HMAC payload in packet to zero so we can calculate the HMAC ourselves + ds_memclr(pPacketData, pProtoTunnel->uHmacSize); + // calculate HMAC + CryptHmacCalc(aCalcHmac, pProtoTunnel->uHmacSize, pPacketStart, iRecvLen, (uint8_t *)pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), (CryptHashTypeE)pProtoTunnel->uHmacType); + // validate + if (memcmp(aPackHmac, aCalcHmac, pProtoTunnel->uHmacSize)) + { + NetPrintf(("prototunnel: [%p][%04d] bad HMAC!\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + #if PROTOTUNNEL_HMAC_DEBUG + NetPrintMem(pPacketData, pProtoTunnel->uHmacSize, "recv-hmac"); + NetPrintMem(pPacketStart, iRecvLen, "recv-hmac-data"); + NetPrintMem(pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), "recv-hmac-key"); + #endif + return(PROTOTUNNEL_PACKETRECVFAIL_VALIDATE); + } + else + { + #if PROTOTUNNEL_HMAC_DEBUG + NetPrintf(("prototunnel: [%p][%04d] recv - hmac validated\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + #endif + } + } + + // update crypt state and stream offset with working temp copies + if (bUpdateState) + { + // if we had to sync the cipher, save current state to oop state before advancing for possible future out-of-order packet decoding + if (bCurrentState && bSynced) + { + NetPrintf(("prototunnel: [%p][%04d] saving out-of-order state\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + ds_memcpy_s(&pTunnel->CryptRecvOOPState, sizeof(pTunnel->CryptRecvOOPState), pCryptRecvState, sizeof(*pCryptRecvState)); + pTunnel->uRecvOOPOffset = *pRecvOffsetState; + } + // now save advanced state + ds_memcpy_s(pCryptRecvState, sizeof(*pCryptRecvState), &CryptRecvState, sizeof(CryptRecvState)); + *pRecvOffsetState = (uint16_t)uRecvOffsetState; + // advance stream by received encrypted packet data size, plus padding if necessary + _ProtoTunnelStreamAdvance(pCryptRecvState, pRecvOffsetState, uRecvOffset); + } + + // if we downgraded protocol version, update here + if (pTunnel->Info.uTunnelVers != uVersion) + { + NetPrintf(("prototunnel: [%p][%04d] downgrading to version %d.%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uVersion>>8, uVersion&0xff)); + _ProtoTunnelSetVersion(pProtoTunnel, pTunnel, uVersion); + } + + // return number of packets decoded + return(iNumPackets); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelDecryptAndValidatePacket + + \Description + Decrypt the tunnel packet, with limited out-of-order packet recovery + + \Input *pProtoTunnel - pointer to module state + \Input *pTunnel - tunnel + \Input *pHeadOffset - [out] offset of sub-packet header data + \Input *pPacketData - pointer to tunnel packet + \Input iRecvLen - size of tunnel packet + \Input bUpdateState - TRUE to update crypt state, else FALSE + + \Output + int32_t - PROTOTUNNEL_PACKETRECVFAIL_* on error, else number of subpackets decoded + + \Notes + If an out-of-order packet is detected against the current tunnel decrypt + state, a single attempt is made to recover the packet using the out-of-order + decrypt state. + + \Version 02/21/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelDecryptAndValidatePacket(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint32_t *pHeadOffset, uint8_t *pPacketData, int32_t iRecvLen, uint8_t bUpdateState) +{ + int32_t iResult; + // try decrypting packet with current state + if ((iResult = _ProtoTunnelDecryptAndValidatePacket2(pProtoTunnel, pTunnel, pHeadOffset, pPacketData, iRecvLen, bUpdateState, TRUE)) == PROTOTUNNEL_PACKETRECVFAIL_OUTOFORDER) + { + // received out-of-order packet prior to our current state; try again with out-of-order packet state + if ((iResult = _ProtoTunnelDecryptAndValidatePacket2(pProtoTunnel, pTunnel, pHeadOffset, pPacketData, iRecvLen, bUpdateState, FALSE)) > 0) + { + NetPrintf(("prototunnel: [%p][%04d] out of order packet recovered by oop state\n", pProtoTunnel, pTunnel - pProtoTunnel->Tunnels)); + } + } + // return result + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelGetControlInfo + + \Description + Get control info from handshake + + \Input *pTunnel - tunnel packet was received on + \Input *pControlInfo - [out] control info from packet data + \Input *pPacketData - source data + + \Output + uint32_t - non-zero if control info was found, else zero + + \Version 03/26/2015 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _ProtoTunnelGetControlInfo(ProtoTunnelT *pTunnel, ProtoTunnelControlT *pControlInfo, const uint8_t *pPacketData) +{ + uint32_t bFound = FALSE; + + // get ident + uint16_t uIdent = PROTOTUNNEL_GetIdentFromPacket(pPacketData); + + // get control info, if present + if (uIdent & PROTOTUNNEL_IDENT_HANDSHAKE) + { + ProtoTunnelHandshakeT *pHandshake = (ProtoTunnelHandshakeT *)pPacketData; + pControlInfo->uPacketType = PROTOTUNNEL_CTRL_INIT; + pControlInfo->aProtocolVers[0] = pHandshake->aIdent[0] & ~0x80; + pControlInfo->aProtocolVers[1] = pHandshake->aIdent[1]; + pControlInfo->aClientId[0] = pHandshake->Version.V1_1.aClientIdent[0]; + pControlInfo->aClientId[1] = pHandshake->Version.V1_1.aClientIdent[1]; + pControlInfo->aClientId[2] = pHandshake->Version.V1_1.aClientIdent[2]; + pControlInfo->aClientId[3] = pHandshake->Version.V1_1.aClientIdent[3]; + pControlInfo->uActive = pHandshake->Version.V1_1.uActive; + bFound = TRUE; + } + else + { + ds_memclr(pControlInfo, sizeof(*pControlInfo)); + } + + return(bFound); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelCryptSetup + + \Description + Setup crypto state for key index iKey + + \Input *pTunnel - tunnel to setup crypto state for + \Input iKey - index of key to use to setup state + \Input bForceSetup - force setup + + \Version 02/24/2016 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoTunnelCryptSetup(ProtoTunnelT *pTunnel, int32_t iKey, uint8_t bForceSetup) +{ + uint8_t *pKey = (uint8_t *)pTunnel->aKeyList[iKey]; + int32_t iKeyLen = (int32_t)strlen((char *)pKey); + + // only setup if we haven't already, or if forced + if ((pTunnel->uSendKey == (uint8_t)iKey) && (bForceSetup != TRUE)) + { + return; + } + + // initialize HMAC key (shared for both send and recv) by running IV through RC4 initialized with tunnel key + CryptArc4Init(&pTunnel->CryptSendState, pKey, iKeyLen, PROTOTUNNEL_CRYPTARC4_ITER); + ds_memcpy_s(pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), _ProtoTunnel_aHmacInitVec, sizeof(_ProtoTunnel_aHmacInitVec)); + CryptArc4Apply(&pTunnel->CryptSendState, (uint8_t *)pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey)); + + // initialize send/recv crypto state + CryptArc4Init(&pTunnel->CryptSendState, pKey, iKeyLen, PROTOTUNNEL_CRYPTARC4_ITER); + CryptArc4Init(&pTunnel->CryptRecvState, pKey, iKeyLen, PROTOTUNNEL_CRYPTARC4_ITER); + pTunnel->uSendKey = pTunnel->uRecvKey = (uint8_t)iKey; + pTunnel->uSendOffset = pTunnel->uRecvOffset = 0; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelMatchTunnel + + \Description + Attempt to decrypt incoming data against a tunnel, and check for clientId match + + \Input *pProtoTunnel - module state + \Input *pTunnel - tunnel to check data against + \Input *pPacketData - source data + \Input iPacketSize - size of source data + + \Output + int32_t - FALSE=no match, TRUE=match + + \Version 06/26/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelMatchTunnel(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, const uint8_t *pPacketData, int32_t iPacketSize) +{ + uint8_t aPacketData[PROTOTUNNEL_PACKETBUFFER]; + int32_t iNumPackets, iRecvKey; + ProtoTunnelControlT ControlInfo; + uint32_t uClientId; + + // try any non-empty keys in this tunnel + for (iNumPackets = -1, iRecvKey = 0; iRecvKey < PROTOTUNNEL_MAXKEYS; iRecvKey += 1) + { + // skip empty keys + if (pTunnel->aKeyList[iRecvKey][0] == '\0') + { + continue; + } + + // try and validate packet against this key with a zero stream offset + if ((iNumPackets = ProtoTunnelValidatePacket(pProtoTunnel, pTunnel, aPacketData, pPacketData, iPacketSize, pTunnel->aKeyList[iRecvKey])) > 0) + { + break; + } + } + + // if we couldn't successfully decode the packet, we return no match + if (iNumPackets < 0) + { + return(FALSE); + } + + // if we're rematching an active tunnel, we're done + if (pTunnel->uActive != 0) + { + // reset crypt state, matching what ProtoTunnelValidatePacket did to successfully decrypt the packet + _ProtoTunnelCryptSetup(pTunnel, iRecvKey, TRUE); + return(TRUE); + } + + // get packet control info + if (_ProtoTunnelGetControlInfo(pTunnel, &ControlInfo, aPacketData) != 0) + { + uClientId = ControlInfo.aClientId[0] << 24; + uClientId |= ControlInfo.aClientId[1] << 16; + uClientId |= ControlInfo.aClientId[2] << 8; + uClientId |= ControlInfo.aClientId[3]; + } + else + { + NetPrintf(("prototunnel: [%p][%04d] no control data included in packet on inactive tunnel\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + return(FALSE); + } + + // if we found the matching tunnel, we're done + if (uClientId == pTunnel->Info.uRemoteClientId) + { + #if DIRTYCODE_LOGGING + if (iRecvKey != 0) + { + NetPrintf(("prototunnel: [%p][%04d] matched key with index=%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iRecvKey)); + } + #endif + _ProtoTunnelCryptSetup(pTunnel, iRecvKey, FALSE); + return(TRUE); + } + else + { + NetPrintf(("prototunnel: [%p][%04d] packet clientId 0x%08x does not match tunnel clientId 0x%08x\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uClientId, pTunnel->Info.uRemoteClientId)); + if (pProtoTunnel->iVerbosity > 3) + { + NetPrintMem(aPacketData, (iPacketSize < 64) ? iPacketSize : 64, "prototunnel-recv-decrypt"); + } + } + + // no match + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelFindTunnel + + \Description + Find the tunnel endpoint for incoming packet + + \Input *pProtoTunnel - module state + \Input *pPacketData - packet data + \Input iRecvLen - packet size + \Input uRecvAddr - source addr of packet + \Input uRecvPort - source port of packet + + \Output + int32_t - matching tunnel index; iMaxTunnels means no match + + \Version 03/26/2015 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelFindTunnel(ProtoTunnelRefT *pProtoTunnel, uint8_t *pPacketData, int32_t iRecvLen, uint32_t uRecvAddr, uint16_t uRecvPort) +{ + int32_t iTunnel = pProtoTunnel->iMaxTunnels; + ProtoTunnelT *pTunnel = NULL; + uint32_t uClientId; + uint16_t uIdent = PROTOTUNNEL_GetIdentFromPacket(pPacketData); + + if (uIdent & PROTOTUNNEL_IDENT_HANDSHAKE) + { + ProtoTunnelHandshakeT *pHandshake = (ProtoTunnelHandshakeT *)pPacketData; + uClientId = pHandshake->Version.V1_1.aClientIdent[0] << 24; + uClientId |= pHandshake->Version.V1_1.aClientIdent[1] << 16; + uClientId |= pHandshake->Version.V1_1.aClientIdent[2] << 8; + uClientId |= pHandshake->Version.V1_1.aClientIdent[3]; + + // find tunnel with matching clientId + for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel += 1) + { + pTunnel = &pProtoTunnel->Tunnels[iTunnel]; + if (pTunnel->Info.uRemoteClientId == uClientId) + { + if (pTunnel->uActive == 0) + { + // decrypt and validate packet + if (!_ProtoTunnelMatchTunnel(pProtoTunnel, pTunnel, pPacketData, iRecvLen)) + { + NetPrintf(("prototunnel: [%p][%04d] error; validate failed on match of uClientId=0x%08x to inactive tunnel\n", pProtoTunnel, iTunnel, uClientId)); + iTunnel = pProtoTunnel->iMaxTunnels; + } + } + else + { + NetPrintf(("prototunnel: [%p][%04d] warning; matching uClientId=0x%08x to active tunnel\n", pProtoTunnel, iTunnel, uClientId)); + } + break; + } + } + } + else if (uIdent < (unsigned)pProtoTunnel->iMaxTunnels) + { + // a non-handshake ident field contains the local tunnel index exchanged in handshaking + pTunnel = &pProtoTunnel->Tunnels[uIdent]; + if (pTunnel->uActive == 1) + { + // decrypt and validate packet + if (_ProtoTunnelMatchTunnel(pProtoTunnel, pTunnel, pPacketData, iRecvLen) != 0) + { + // found tunnel to match incoming data with + iTunnel = (signed)uIdent; + } + } + } + + // handle port change + if ((iTunnel < pProtoTunnel->iMaxTunnels) && (pTunnel != NULL) && (pTunnel->Info.uRemotePort != uRecvPort)) + { + NetPrintf(("prototunnel: [%p][%04d] detected port change; updating remote port from %d to %d\n", pProtoTunnel, iTunnel, pTunnel->Info.uRemotePort, uRecvPort)); + pTunnel->Info.uRemotePort = uRecvPort; + } + + // couldn't match? + if (iTunnel == pProtoTunnel->iMaxTunnels) + { + NetPrintf(("prototunnel: [%p] could not match packet from %a:%d (ident=0x%04x)\n", pProtoTunnel, uRecvAddr, uRecvPort, uIdent)); + } + return(iTunnel); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelRecvData + + \Description + Match incoming data with a tunnel, and if source address is mapped, + decode the number of subpackets and decrypt packet data. + + \Input *pProtoTunnel - module state + \Input *pRemotePortList - [out] storage for remote port list of matching tunnel + \Input iPortListSize - size of port list + \Input *pHeadOffset - [out] storage for sub-packet header data offset + \Input *pPacketData - source data + \Input iRecvLen - size of source data + \Input *pRecvAddr - source address + + \Output + int32_t - PROTOTUNNEL_PACKETRECVFAIL_*, else number of decoded packets + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelRecvData(ProtoTunnelRefT *pProtoTunnel, uint16_t *pRemotePortList, int32_t iPortListSize, uint32_t *pHeadOffset, uint8_t *pPacketData, int32_t iRecvLen, struct sockaddr *pRecvAddr) +{ + uint32_t uRecvAddr, uRecvPort; + int32_t iNumPackets, iTunnel; + ProtoTunnelT *pTunnel; + uint32_t uCurTick = NetTick(); + + // get exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // find tunnel with matching remote address, port, and mode + for (iTunnel = 0, uRecvAddr = SockaddrInGetAddr(pRecvAddr), uRecvPort = SockaddrInGetPort(pRecvAddr); iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) + { + pTunnel = &pProtoTunnel->Tunnels[iTunnel]; + if ((pTunnel->Info.uRemoteAddr == uRecvAddr) && (pTunnel->Info.uRemotePort == uRecvPort)) + { + if (pTunnel->uActive == 0) + { + // decrypt packet and verify clientId matches + if (_ProtoTunnelMatchTunnel(pProtoTunnel, pTunnel, pPacketData, iRecvLen)) + { + // found tunnel to match incoming data with + break; + } + } + else + { + break; + } + } + } + + // if no matching tunnel found, use packet data to match to a tunnel + if (iTunnel == pProtoTunnel->iMaxTunnels) + { + iTunnel = _ProtoTunnelFindTunnel(pProtoTunnel, pPacketData, iRecvLen, uRecvAddr, uRecvPort); + } + + // did we find a tunnel? + if (iTunnel < pProtoTunnel->iMaxTunnels) + { + pTunnel = &pProtoTunnel->Tunnels[iTunnel]; + + // if the tunnel isn't active yet, bind to it and mark it as active + if (pTunnel->uActive == 0) + { + if ((pTunnel->Info.uRemoteAddr != uRecvAddr) || (pTunnel->Info.uRemotePort != uRecvPort)) + { + NetPrintf(("prototunnel: [%p][%04d] updating remote address from %a:%d to %a:%d\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->Info.uRemoteAddr, pTunnel->Info.uRemotePort, uRecvAddr, uRecvPort)); + } + + // update tunnel to addr/port combo + pTunnel->Info.uRemoteAddr = uRecvAddr; + pTunnel->Info.uRemotePort = uRecvPort; + + // mark that we're now active + pTunnel->uActive = 1; + + NetPrintf(("prototunnel: [%p][%04d] matched incoming data from %a:%d to tunnelId 0x%08x / clientId 0x%08x\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uRecvAddr, uRecvPort, pTunnel->uVirtualAddr, pTunnel->Info.uRemoteClientId)); + } + + // make a copy of port list for later use + ds_memcpy(pRemotePortList, pTunnel->Info.aRemotePortList, iPortListSize); + + // display recv info + NetPrintfVerbose((pProtoTunnel->iVerbosity, 2, "prototunnel: [%p][%04d] recv - received %d bytes on tunnel 0x%08x from %a:%d\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iRecvLen, pTunnel->uVirtualAddr, uRecvAddr, uRecvPort)); + + iNumPackets = _ProtoTunnelDecryptAndValidatePacket(pProtoTunnel, pTunnel, pHeadOffset, pPacketData, iRecvLen, TRUE); + + if (iNumPackets > 0) + { + ProtoTunnelControlT ControlInfo; + + // accumulate receive stats + pTunnel->RecvStat.uLastPacketTime = uCurTick; + pTunnel->RecvStat.uNumBytes += iRecvLen; + pTunnel->RecvStat.uNumSubpacketBytes += iRecvLen - (iNumPackets * PROTOTUNNEL_PKTHDRSIZE) - 2 - pProtoTunnel->uHmacSize; + pTunnel->RecvStat.uNumPackets += 1; + pTunnel->RecvStat.uNumSubpackets += iNumPackets; + pProtoTunnel->uNumSubPktsRecvd += (unsigned)iNumPackets; + + // process any control info + if (_ProtoTunnelGetControlInfo(pTunnel, &ControlInfo, pPacketData) != 0) + { + if (ControlInfo.uActive == 1) + { + NetPrintf(("prototunnel: [%p][%04d] connection is active; disabling control packets\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + pTunnel->bSendCtrlInfo = FALSE; + } + else if (!pTunnel->bSendCtrlInfo) + { + /* this path is typically exercised when one side of the connection ends up refcounting a tunnel and the other side + ends up destroying/recreating the tunnel because both consoles get Blaze messages with different timing. Under such + conditions, the prototunnel handshaking (sending of ctrl messages) needs to occur again. */ + NetPrintf(("prototunnel: [%p][%04d] peer no longer sees connection as active; resume sending control packets\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + pTunnel->bSendCtrlInfo = TRUE; + } + } + else if ((pTunnel->bSendCtrlInfo) && (pTunnel->uActive == 1)) + { + NetPrintf(("prototunnel: [%p][%04d] connection is active; disabling control packets\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + pTunnel->bSendCtrlInfo = FALSE; + } + + // rewrite address to virtual address + SockaddrInSetAddr(pRecvAddr, pTunnel->uVirtualAddr); + } + else if (iNumPackets == PROTOTUNNEL_PACKETRECVFAIL_VALIDATE) + { + /* attempt rematch with a different key and/or stream offset of zero; if rematch is successful we return TRYAGAIN, + as we need the caller to call us again following a successful rematch. if rematch fails here, we do not want to + try again, so we directly return the error code */ + NetPrintf(("prototunnel: [%p][%04d] attempting tunnel rematch after VALIDATE error\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + if (_ProtoTunnelMatchTunnel(pProtoTunnel, pTunnel, pPacketData, iRecvLen)) + { + NetPrintf(("prototunnel: [%p][%04d] tunnel rematch succeeded\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + iNumPackets = PROTOTUNNEL_PACKETRECVFAIL_TRYAGAIN; + } + else + { + NetPrintf(("prototunnel: [%p][%04d] tunnel rematch failed\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + } + } + else if (iNumPackets == PROTOTUNNEL_PACKETRECVFAIL_OUTOFORDER) + { + NetPrintf(("prototunnel: [%p][%04d] discarding out of order packet\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + pTunnel->SendStat.uNumDiscards += 1; + pProtoTunnel->uNumPktsDiscard += 1; + } + } + else + { + // tell calling code there is no matching tunnel + iNumPackets = PROTOTUNNEL_PACKETRECVFAIL_NOMATCH; + } + + // release exclusive access + NetCritLeave(&pProtoTunnel->TunnelsCritR); + + // return number of packets decoded + return(iNumPackets); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelRecv + + \Description + Callback to handle idle and recv callbacks on a tunnel socket. + + \Input *pProtoTunnel - module ref + \Input pPacketData - packet data + \Input iRecvLen - receive length + \Input *pRecvAddr - sender's address + + \Output + int32_t - zero + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelRecv(ProtoTunnelRefT *pProtoTunnel, uint8_t *pPacketData, int32_t iRecvLen, struct sockaddr *pRecvAddr) +{ + int32_t iPacket, iNumPackets; + uint16_t aRemotePortList[PROTOTUNNEL_MAXPORTS]; + uint8_t *pPacketHead; + uint32_t uHeadOffset; + + // find matching tunnel and decode packets + if ((iNumPackets = _ProtoTunnelRecvData(pProtoTunnel, aRemotePortList, sizeof(aRemotePortList), &uHeadOffset, pPacketData, iRecvLen, pRecvAddr)) == PROTOTUNNEL_PACKETRECVFAIL_TRYAGAIN) + { + // we were able to match a new key/stream offset; run it through again + iNumPackets = _ProtoTunnelRecvData(pProtoTunnel, aRemotePortList, sizeof(aRemotePortList), &uHeadOffset, pPacketData, iRecvLen, pRecvAddr); + } + + // if no packets + if (iNumPackets <= 0) + { + NetPrintf(("prototunnel: [%p] recv - received unhandled %d bytes from %a:%d (%d)\n", pProtoTunnel, iRecvLen, + SockaddrInGetAddr(pRecvAddr), SockaddrInGetPort(pRecvAddr), iNumPackets)); + + // if event callback is set, call it + if ((pProtoTunnel->pCallback != NULL) && (iNumPackets == PROTOTUNNEL_PACKETRECVFAIL_NOMATCH)) + { + pProtoTunnel->pCallback(pProtoTunnel, PROTOTUNNEL_EVENT_RECVNOMATCH, (char *)pPacketData, iRecvLen, pRecvAddr, pProtoTunnel->pUserData); + } + + // display recv info + if (pProtoTunnel->iVerbosity > 3) + { + NetPrintMem(pPacketData, (iRecvLen < 64) ? iRecvLen : 64, "prototunnel-recv-nomatch"); + } + return(0); + } + + // display decrypted packet data + #if DIRTYCODE_LOGGING + if (pProtoTunnel->iVerbosity > 3) + { + NetPrintMem(pPacketData, (iRecvLen < 64) ? iRecvLen : 64, "prototunnel-recv"); + } + #endif + + // demultiplex aggregate packet data and push into the appropriate sockets + for (iPacket = 0, pPacketHead = pPacketData+uHeadOffset, pPacketData = pPacketData+uHeadOffset+(iNumPackets*PROTOTUNNEL_PKTHDRSIZE)+pProtoTunnel->uHmacSize; iPacket < iNumPackets; iPacket++) + { + // extract size and port info + uint32_t uPktHead = (pPacketHead[0] << 8) | pPacketHead[1]; + uint32_t uPktSize = uPktHead >> 4; + uint32_t uPortIdx = uPktHead & 0xf; + uint32_t uPort = aRemotePortList[uPortIdx]; + + // if it is a control packet, skip it + if (uPortIdx != PROTOTUNNEL_CONTROL_PORTIDX) + { + SocketT *pSocket; + + // find SOCK_DGRAM socket bound to this port + if (SocketInfo(NULL, 'bndu', uPort, &pSocket, sizeof(pSocket)) == 0) + { + // rewrite port to match what socket will be expecting + SockaddrInSetPort(pRecvAddr, uPort); + + // display tunneled receive + NetPrintfVerbose((pProtoTunnel->iVerbosity, 2, "prototunnel: [%p] recv - %db->%d\n", pProtoTunnel, uPktSize, uPort)); + + // forward data on to caller + SocketControl(pSocket, 'push', uPktSize, pPacketData, pRecvAddr); + } + else + { + NetPrintf(("prototunnel: [%p] recv - warning, got data for port %d with no socket bound to that port\n", pProtoTunnel, uPort)); + } + } + else + { + NetPrintf(("prototunnel: [%p] recv - received control packet\n", pProtoTunnel)); + } + + // skip to next packet + pPacketHead += PROTOTUNNEL_PKTHDRSIZE; + pPacketData += uPktSize; + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelRecvCallback + + \Description + Callback to handle idle and recv callbacks on a tunnel socket. + + \Input *pSocket - socket ref + \Input iFlags - unused + \Input *_pRef - tunnel map ref + + \Output + int32_t - zero + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoTunnelRecvCallback(SocketT *pSocket, int32_t iFlags, void *_pRef) +{ + ProtoTunnelRefT *pProtoTunnel = (ProtoTunnelRefT *)_pRef; + struct sockaddr RecvAddr; + int32_t iRecvAddrLen = sizeof(RecvAddr), iRecv, iRecvLen; + uint8_t aPacketData[PROTOTUNNEL_PACKETBUFFER]; + + SockaddrInit(&RecvAddr, AF_INET); + + // got any input? + for (iRecv = 0; iRecv < pProtoTunnel->iMaxRecv; iRecv += 1) + { + if ((iRecvLen = SocketRecvfrom(pSocket, (char *)aPacketData, sizeof(aPacketData), 0, &RecvAddr, &iRecvAddrLen)) <= 0) + { + break; + } + + // drop packet for easy testing of lost packet recovery + #if DIRTYCODE_DEBUG + if (pProtoTunnel->iPacketDrop > 0) + { + pProtoTunnel->iPacketDrop -= 1; + NetPrintf(("prototunnel: [%p] dropping received packet\n", pProtoTunnel)); + continue; + } + #endif + + // forward to any registered raw inbound data filter + if (pProtoTunnel->pRawRecvCallback) + { + if (pProtoTunnel->pRawRecvCallback(pSocket, aPacketData, iRecvLen, &RecvAddr, sizeof(RecvAddr), pProtoTunnel->pRawRecvUserData) > 0) + { + // inbound raw data was swallowed by the filter, this packet is to be ignored by ProtoTunnel + continue; + } + } + + // process the packet + _ProtoTunnelRecv(pProtoTunnel, aPacketData, iRecvLen, &RecvAddr); + } + + // update stats + pProtoTunnel->uNumRecvCalls += iRecv > 0; + pProtoTunnel->uNumPktsRecvd += (unsigned)iRecv; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelSocketConfig + + \Description + Configure tunnel socket + + \Input *pProtoTunnel - pointer to module state + \Input *pSocket - socket to configure + + \Output + uint32_t - local port socket is bound to + + \Version 09/18/2013 (jbrookes) split from _ProtoTunnelSocketOpen +*/ +/********************************************************************************F*/ +static uint32_t _ProtoTunnelSocketConfig(ProtoTunnelRefT *pProtoTunnel, SocketT *pSocket) +{ + struct sockaddr BindAddr; + uint32_t uPort; + + // retrieve bound port + SocketInfo(pSocket, 'bind', 0, &BindAddr, sizeof(BindAddr)); + + // reference local port + uPort = SockaddrInGetPort(&BindAddr); + NetPrintf(("prototunnel: [%p] bound socket to port %d\n", pProtoTunnel, uPort)); + + // set up for socket callback events + SocketCallback(pSocket, CALLB_RECV, pProtoTunnel->iIdleCbRate, pProtoTunnel, &_ProtoTunnelRecvCallback); + + return(uPort); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelSocketOpen + + \Description + Open a tunnel socket + + \Input *pProtoTunnel - pointer to module state + \Input iPort - port tunnel will go over + + \Output + SocketT * - new socket or NULL on failure + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static SocketT *_ProtoTunnelSocketOpen(ProtoTunnelRefT *pProtoTunnel, int32_t iPort) +{ + struct sockaddr BindAddr; + SocketT *pSocket; + int32_t iResult; + + // open the socket + if ((pSocket = SocketOpen(AF_INET, SOCK_DGRAM, PROTOTUNNEL_IPPROTO)) == NULL) + { + NetPrintf(("prototunnel: [%p] unable to open socket\n", pProtoTunnel)); + return(NULL); + } + + // bind socket to specified port + SockaddrInit(&BindAddr, AF_INET); + SockaddrInSetPort(&BindAddr, iPort); + if ((iResult = SocketBind(pSocket, &BindAddr, sizeof(BindAddr))) != SOCKERR_NONE) + { + if (_ProtoTunnel_bRetryRandomOnFailure) + { + NetPrintf(("prototunnel: [%p] error %d binding to port %d, trying random\n", pProtoTunnel, iResult, iPort)); + SockaddrInSetPort(&BindAddr, 0); + if ((iResult = SocketBind(pSocket, &BindAddr, sizeof(BindAddr))) != SOCKERR_NONE) + { + NetPrintf(("prototunnel: [%p] error %d binding to port\n", pProtoTunnel, iResult)); + SocketClose(pSocket); + return(NULL); + } + } + else + { + NetPrintf(("prototunnel: [%p] error %d binding to port %d\n", pProtoTunnel, iResult, iPort)); + SocketClose(pSocket); + return(NULL); + } + } + + // return socket to caller + return(pSocket); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoTunnelStatCalc + + \Description + Calculates ProtoTunnel Stats + + \Input *pTunnel - Tunnel that calculations are to be performed on. + \Input iSelect - 'rcvs' for receive stats and 'snds' for send stats + + \Version 09/17/2014 (tcho) +*/ +/********************************************************************************F*/ +static void _ProtoTunnelStatCalc(ProtoTunnelT *pTunnel, int32_t iSelect) +{ + uint32_t uCurTick = NetTick(); + uint32_t uRange = 0; + + if (iSelect == 'rcvs') + { + if (pTunnel->RecvStat.uUpdateTime == 0) + { + pTunnel->RecvStat.uBytePerSecond = 0; + pTunnel->RecvStat.uRawBytesPerSecond = 0; + pTunnel->RecvStat.uEfficiency = 0; + } + else + { + uRange = NetTickDiff(uCurTick, pTunnel->RecvStat.uUpdateTime); + pTunnel->RecvStat.uRawBytesPerSecond = (pTunnel->RecvStat.uNumBytes - pTunnel->uLastRecvNumBytes) * 1000 / uRange; + pTunnel->RecvStat.uBytePerSecond = (pTunnel->RecvStat.uNumSubpacketBytes - pTunnel->uLastRecvNumSubpacketBytes) * 1000 / uRange; + pTunnel->RecvStat.uEfficiency = (uint32_t)(((float)pTunnel->RecvStat.uBytePerSecond/(float)pTunnel->RecvStat.uRawBytesPerSecond) * 100.0f); + } + + // update tracking variable + pTunnel->RecvStat.uPrevUpdateTime = pTunnel->RecvStat.uUpdateTime; + pTunnel->RecvStat.uUpdateTime = uCurTick; + pTunnel->uLastRecvNumBytes = pTunnel->RecvStat.uNumBytes; + pTunnel->uLastRecvNumSubpacketBytes = pTunnel->RecvStat.uNumSubpacketBytes; + } + else if (iSelect == 'snds') + { + if (pTunnel->SendStat.uUpdateTime == 0) + { + pTunnel->SendStat.uBytePerSecond = 0; + pTunnel->SendStat.uRawBytesPerSecond = 0; + pTunnel->SendStat.uEfficiency = 0; + } + else + { + uRange = NetTickDiff(uCurTick, pTunnel->SendStat.uUpdateTime); + pTunnel->SendStat.uRawBytesPerSecond = (pTunnel->SendStat.uNumBytes - pTunnel->uLastSendNumBytes) * 1000 / uRange; + pTunnel->SendStat.uBytePerSecond = (pTunnel->SendStat.uNumSubpacketBytes - pTunnel->uLastSendNumSubpacketBytes) * 1000 / uRange; + pTunnel->SendStat.uEfficiency = (uint32_t)(((float)pTunnel->SendStat.uBytePerSecond/(float)pTunnel->SendStat.uRawBytesPerSecond) * 100.0f); + } + + // update tracking variable + pTunnel->SendStat.uPrevUpdateTime = pTunnel->SendStat.uUpdateTime; + pTunnel->SendStat.uUpdateTime = uCurTick; + pTunnel->uLastSendNumBytes = pTunnel->SendStat.uNumBytes; + pTunnel->uLastSendNumSubpacketBytes = pTunnel->SendStat.uNumSubpacketBytes; + } + +} +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ProtoTunnelCreate + + \Description + Create the ProtoTunnel module. + + \Input iMaxTunnels - maximum number of tunnels module can allocate + \Input iTunnelPort - local port for socket all tunnels will use + + \Output + ProtoTunnelRefT * - pointer to new module, or NULL + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +ProtoTunnelRefT *ProtoTunnelCreate(int32_t iMaxTunnels, int32_t iTunnelPort) +{ + ProtoTunnelRefT *pProtoTunnel; + int32_t iRefSize = sizeof(*pProtoTunnel) + ((iMaxTunnels-1) * sizeof(ProtoTunnelT)); + int32_t iMemGroup; + void *pMemGroupUserData; + + // maximum of 32k tunnels due to size of ident field + if (iMaxTunnels > 32767) + { + NetPrintf(("prototunnel: clamping requested %d tunnels to max of 32767\n", iMaxTunnels)); + iMaxTunnels = 32767; + } + + // query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pProtoTunnel = DirtyMemAlloc(iRefSize, PROTOTUNNEL_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("prototunnel: could not allocate module state\n")); + return(NULL); + } + ds_memclr(pProtoTunnel, iRefSize); + pProtoTunnel->iMemGroup = iMemGroup; + pProtoTunnel->pMemGroupUserData = pMemGroupUserData; + pProtoTunnel->iMaxTunnels = iMaxTunnels; + pProtoTunnel->iMaxRecv = 64; + pProtoTunnel->iIdleCbRate = 100; + pProtoTunnel->uTunnelPort = iTunnelPort; + pProtoTunnel->uHmacType = CRYPTHASH_MURMUR3; + pProtoTunnel->uHmacSize = PROTOTUNNEL_HMAC_DEFSIZE; + pProtoTunnel->uVersion = PROTOTUNNEL_VERSION; + + if (_ProtoTunnelGetBaseAddress(pProtoTunnel) != 0) + { + DirtyMemFree(pProtoTunnel, PROTOTUNNEL_MEMID, pProtoTunnel->iMemGroup, pProtoTunnel->pMemGroupUserData); + return(NULL); + } + + // create the tunnel socket + if ((pProtoTunnel->pSocket = _ProtoTunnelSocketOpen(pProtoTunnel, iTunnelPort)) == NULL) + { + _ProtoTunnelReleaseBaseAddress(pProtoTunnel); + DirtyMemFree(pProtoTunnel, PROTOTUNNEL_MEMID, pProtoTunnel->iMemGroup, pProtoTunnel->pMemGroupUserData); + return(NULL); + } + pProtoTunnel->uTunnelPort = _ProtoTunnelSocketConfig(pProtoTunnel, pProtoTunnel->pSocket); + + // initialize critical sections + NetCritInit(&pProtoTunnel->TunnelsCritS, "prototunnel-global-send"); + NetCritInit(&pProtoTunnel->TunnelsCritR, "prototunnel-global-recv"); + + // restrict max udp packet size + SocketControl(NULL, 'maxp', PROTOTUNNEL_MAXPACKET, NULL, NULL); + + // hook into global socket send hook + SocketControl(NULL, 'sdcb', TRUE, (void *)_ProtoTunnelSendCallback, pProtoTunnel); + + // configure prototunnel socket to NOT call socket send callbacks + SocketControl(pProtoTunnel->pSocket, 'scbk', FALSE, NULL, NULL); + + // return ref to caller + return(pProtoTunnel); +} + +/*F********************************************************************************/ +/*! + \Function ProtoTunnelDestroy + + \Description + Destroy the ProtoTunnel module. + + \Input *pProtoTunnel - pointer to module state + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoTunnelDestroy(ProtoTunnelRefT *pProtoTunnel) +{ + // clear global socket send hook + SocketControl(NULL, 'sdcb', FALSE, (void *)_ProtoTunnelSendCallback, pProtoTunnel); + + // close tunnel socket + if (pProtoTunnel->pSocket != NULL) + { + SocketClose(pProtoTunnel->pSocket); + } + + // close server socket, if allocated + if (pProtoTunnel->pServerSocket != NULL) + { + SocketClose(pProtoTunnel->pServerSocket); + } + + // dispose of critical sections + NetCritKill(&pProtoTunnel->TunnelsCritR); + NetCritKill(&pProtoTunnel->TunnelsCritS); + + _ProtoTunnelReleaseBaseAddress(pProtoTunnel); + + // dispose of module memory + DirtyMemFree(pProtoTunnel, PROTOTUNNEL_MEMID, pProtoTunnel->iMemGroup, pProtoTunnel->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function ProtoTunnelCallback + + \Description + Set event callback + + \Input *pProtoTunnel - pointer to module state + \Input *pCallback - callback pointer + \Input *pUserData - callback data + + \Version 03/24/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoTunnelCallback(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelCallbackT *pCallback, void *pUserData) +{ + pProtoTunnel->pCallback = pCallback; + pProtoTunnel->pUserData = pUserData; +} + +/*F********************************************************************************/ +/*! + \Function ProtoTunnelAlloc + + \Description + Allocate a tunnel. + + \Input *pProtoTunnel - pointer to module state + \Input *pInfo - tunnel info + \Input *pKey - encryption key for tunnel + + \Output + int32_t - negative=error, else allocated tunnel id + + \Version 12/07/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoTunnelAlloc(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelInfoT *pInfo, const char *pKey) +{ + ProtoTunnelT *pTunnel; + int32_t iTunnel; + + // get exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // see if we already have a tunnel with this clientId + for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) + { + pTunnel = &pProtoTunnel->Tunnels[iTunnel]; + if (pTunnel->Info.uRemoteClientId == pInfo->uRemoteClientId) + { + int32_t iKey, iKeySlot, iResult = (signed)pTunnel->uVirtualAddr; + + // refcount the tunnel + NetPrintf(("prototunnel: [%p][%04d] refcounting tunnel with id=0x%08x key=%s clientId=0x%08x remote address=%a\n", + pProtoTunnel, iTunnel, pTunnel->uVirtualAddr, pKey, pInfo->uRemoteClientId, pInfo->uRemoteAddr)); + pTunnel->uRefCount += 1; + + // append key to key list + for (iKey = 0, iKeySlot = -1; iKey < PROTOTUNNEL_MAXKEYS; iKey++) + { + #if DIRTYCODE_LOGGING + if (!strcmp(pKey, pTunnel->aKeyList[iKey])) + { + NetPrintf(("prototunnel: [%p][%04d] warning - duplicate key %s on alloc\n", pProtoTunnel, iTunnel, pKey)); + } + #endif + if ((iKeySlot == -1) && (pTunnel->aKeyList[iKey][0] == '\0')) + { + iKeySlot = iKey; + } + } + if (iKeySlot != -1) + { + ds_strnzcpy(pTunnel->aKeyList[iKeySlot], pKey, sizeof(pTunnel->aKeyList[iKeySlot])); + } + else + { + NetPrintf(("prototunnel: [%p][%04d] error - key overflow on alloc\n", pProtoTunnel, iTunnel)); + iResult = -1; + } + + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + + return(iResult); + } + } + + // find an unallocated tunnel + for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) + { + if (pProtoTunnel->Tunnels[iTunnel].uVirtualAddr == 0) + { + break; + } + } + // make sure we found room + if (iTunnel == pProtoTunnel->iMaxTunnels) + { + NetPrintf(("prototunnel: [%p] could not allocate a new tunnel\n", pProtoTunnel)); + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + return(-1); + } + + // ref and init tunnel + pTunnel = &pProtoTunnel->Tunnels[iTunnel]; + ds_memclr(pTunnel, sizeof(*pTunnel)); + ds_memcpy_s(&pTunnel->Info, sizeof(pTunnel->Info), pInfo, sizeof(*pInfo)); + NetCritInit(&pTunnel->PacketCrit, "prototunnel-tunnel"); + ds_strnzcpy(pTunnel->aKeyList[0], pKey, sizeof(pTunnel->aKeyList[0])); + pTunnel->uRefCount = 1; + + pTunnel->uLastTunnelSend = NetTick(); + + // init tunnel crypto state + _ProtoTunnelCryptSetup(pTunnel, 0, TRUE); + + // port index seven is reserved by ProtoTunnel and always encrypted + pTunnel->Info.aPortFlags[PROTOTUNNEL_CONTROL_PORTIDX] = PROTOTUNNEL_PORTFLAG_ENCRYPTED; + + // initialize to send connect info + pTunnel->bSendCtrlInfo = TRUE; + + // assign a virtual address/id + pTunnel->uVirtualAddr = pProtoTunnel->uVirtualAddr++; + + // if unspecified, set remote port + if (pTunnel->Info.uRemotePort == 0) + { + pTunnel->Info.uRemotePort = pProtoTunnel->uTunnelPort; + } + + // set default tunnel protocol version + _ProtoTunnelSetVersion(pProtoTunnel, pTunnel, pProtoTunnel->uVersion); + + #if DIRTYCODE_LOGGING + pTunnel->uLastStatUpdate = NetTick(); + #endif + + // release exclusive access to tunnel list + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + + // debug spam + #if DIRTYCODE_LOGGING + { + int32_t iPort; + NetPrintf(("prototunnel: [%p][%04d] creating map to remote client %a:%d (id=0x%08x)\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->Info.uRemoteAddr, pTunnel->Info.uRemotePort, pTunnel->Info.uRemoteClientId)); + for (iPort = 0; (iPort < PROTOTUNNEL_MAXPORTS) && (pTunnel->Info.aRemotePortList[iPort] != 0); iPort++) + { + NetPrintf(("prototunnel: [%p][%04d] [%d] %d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iPort, pTunnel->Info.aRemotePortList[iPort])); + } + } + #endif + + // return addr/id to caller + NetPrintf(("prototunnel: [%p][%04d] allocated tunnel with id 0x%08x key=%s\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->uVirtualAddr, pTunnel->aKeyList[0])); + return(pTunnel->uVirtualAddr); +} + +/*F********************************************************************************/ +/*! + \Function ProtoTunnelFree + + \Description + Free a tunnel. + + \Input *pProtoTunnel - pointer to module state + \Input uTunnelId - id of tunnel to free + \Input *pKey - key tunnel was allocated with + + \Notes + pKey is only required when tunnel refcounting is used. Otherwise, pKey + may be specified as NULL. + + \Version 12/07/2005 (jbrookes) +*/ +/********************************************************************************F*/ +uint32_t ProtoTunnelFree(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId, const char *pKey) +{ + return(ProtoTunnelFree2(pProtoTunnel, uTunnelId, pKey, 0)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoTunnelFree2 + + \Description + Free a tunnel. Same as ProtoTunnelFree but takes an IP address that is + no longer used. Consider for deprecation in the future. + + \Input *pProtoTunnel - pointer to module state + \Input uTunnelId - id of tunnel to free + \Input *pKey - key tunnel was allocated with + \Input uAddr - address of peer that is being freed + + \Notes + pKey is only required when tunnel refcounting is used. Otherwise, pKey + may be specified as NULL. + + \Version 03/15/2010 (jrainy) +*/ +/********************************************************************************F*/ +uint32_t ProtoTunnelFree2(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId, const char *pKey, uint32_t uAddr) +{ + ProtoTunnelT *pTunnel; + int32_t iTunnel; + uint32_t uRet = (uint32_t)-1; + + #if DIRTYCODE_LOGGING + uint32_t bFound = FALSE; + #endif + + // get exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // find tunnel id + for (iTunnel = 0, pTunnel = &pProtoTunnel->Tunnels[0]; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++, pTunnel++) + { + // found it? + if (pTunnel->uVirtualAddr == uTunnelId) + { + #if DIRTYCODE_LOGGING + bFound = TRUE; + #endif + + // deallocate the tunnel + if (pTunnel->uRefCount == 1) + { + NetPrintf(("prototunnel: [%p][%04d] freeing tunnel 0x%08x\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uTunnelId)); + + // flush buffer before destroy + _ProtoTunnelBufferSend(pProtoTunnel, pTunnel, NetTick()); + + // dispose of critical section + NetCritKill(&pTunnel->PacketCrit); + + ds_memclr(pTunnel, sizeof(*pTunnel)); + + uRet = 0; + } + else + { + int32_t iKey; + + NetPrintf(("prototunnel: [%p][%04d] decrementing refcount of tunnel 0x%08x (clientId=0x%08x)\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uTunnelId, pTunnel->Info.uRemoteClientId)); + + // remove key from key list + for (iKey = 0; iKey < PROTOTUNNEL_MAXKEYS; iKey++) + { + if (!strcmp(pKey, pTunnel->aKeyList[iKey])) + { + ds_memclr(pTunnel->aKeyList[iKey], sizeof(pTunnel->aKeyList[iKey])); + break; + } + } + + // did we blow away our send key? + if (pTunnel->uSendKey == (uint8_t)iKey) + { + NetPrintf(("prototunnel: [%p][%04d] free of current send key; picking another\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); + for (iKey = 0; iKey < PROTOTUNNEL_MAXKEYS; iKey++) + { + if (pTunnel->aKeyList[iKey][0] != '\0') + { + NetPrintf(("prototunnel: [%p][%04d] picking key %d (%s) offset=%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iKey, pTunnel->aKeyList[iKey], pTunnel->uSendOffset)); + // init tunnel crypto state for new key + _ProtoTunnelCryptSetup(pTunnel, iKey, FALSE); + break; + } + } + } + else if (iKey == PROTOTUNNEL_MAXKEYS) + { + NetPrintf(("prototunnel: [%p][%04d] could not find key %s in key list on free\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pKey)); + } + + pTunnel->uRefCount -= 1; + uRet = pTunnel->uRefCount; + + NetPrintf(("prototunnel: [%p][%04d] refcounting down tunnel with id=0x%08x key=%s clientId=0x%08x remote address=%a\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->uVirtualAddr, pKey, pTunnel->Info.uRemoteClientId, pTunnel->Info.uRemoteAddr)); + } + + // done + break; + } + } + + #if DIRTYCODE_LOGGING + if (bFound == FALSE) + { + NetPrintf(("prototunnel: [%p][%04d] could not find tunnel with id 0x%08x to free\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uTunnelId)); + } + #endif + + // release exclusive access to tunnel list + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + return(uRet); +} + +/*F********************************************************************************/ +/*! + \Function ProtoTunnelUpdatePortList + + \Description + Updates the port list for the given tunnel. Port and port flag info is + copied over from the specified info structure if the port value is non- + zero. + + \Input *pProtoTunnel - pointer to module state + \Input uTunnelId - id of tunnel to update port mapping for + \Input *pInfo - structure containing updated port info + + \Output + int32_t - zero=success, else could not find tunnel with given id + + \Version 06/12/2008 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoTunnelUpdatePortList(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId, ProtoTunnelInfoT *pInfo) +{ + ProtoTunnelT *pTunnel; + int32_t iTunnel, iResult = -1; + int32_t iPort; + + // get exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // find tunnel id + for (iTunnel = 0, pTunnel = &pProtoTunnel->Tunnels[0]; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++, pTunnel++) + { + // found it? + if (pTunnel->uVirtualAddr == uTunnelId) + { + // copy portlist items that should be updated + for (iPort = 0; iPort < PROTOTUNNEL_MAXPORTS; iPort += 1) + { + if (pInfo->aRemotePortList[iPort] != 0) + { + NetPrintf(("prototunnel: [%p][%04d] updating port mapping %d for tunnel=0x%08x from (%d,%d) to (%d,%d)\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iPort, uTunnelId, + pTunnel->Info.aRemotePortList[iPort], pTunnel->Info.aPortFlags[iPort], + pInfo->aRemotePortList[iPort], pInfo->aPortFlags[iPort])); + pTunnel->Info.aRemotePortList[iPort] = pInfo->aRemotePortList[iPort]; + pTunnel->Info.aPortFlags[iPort] = pInfo->aPortFlags[iPort]; + } + } + iResult = 0; + break; + } + } + + // release exclusive access to tunnel list + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + + // return result code to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoTunnelValidatePacket + + \Description + Validate key against packet, a tunnel has not been allocated yet + + \Input *pProtoTunnel- pointer to module state + \Input *pTunnel - pointer to tunnel + \Input *pOutputData - [out] pointer to buffer to store decrypted data (may be NULL) + \Input *pPacketData - pointer to tunnel packet + \Input iPacketSize - size of tunnel packet + \Input *pKey - encryption key for tunnel + + \Output + int32_t - PROTOTUNNEL_PACKETRECVFAIL_* on error, else number of subpackets decoded + + \Version 06/06/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoTunnelValidatePacket(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint8_t *pOutputData, const uint8_t *pPacketData, int32_t iPacketSize, const char *pKey) +{ + uint8_t aPacketData[PROTOTUNNEL_PACKETBUFFER]; + int32_t iNumPackets; + uint32_t uHeadOffset; + + CryptArc4T CryptTemp; + uint32_t uRecvOffsetTemp; + uint8_t aHmacKeyTemp[64]; + + // copy packet data to temp buffer + ds_memcpy_s(aPacketData, sizeof(aPacketData), pPacketData, iPacketSize); + + // save current hmac key + ds_memcpy_s(aHmacKeyTemp, sizeof(aHmacKeyTemp), pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey)); + + // init HMAC key by running IV through RC4 initialized with tunnel key + CryptArc4Init(&CryptTemp, (const unsigned char *)pKey, (int32_t)strlen(pKey), PROTOTUNNEL_CRYPTARC4_ITER); + ds_memcpy_s(pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), _ProtoTunnel_aHmacInitVec, sizeof(_ProtoTunnel_aHmacInitVec)); + CryptArc4Apply(&CryptTemp, (uint8_t *)pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey)); + + // save current recv state + ds_memcpy_s(&CryptTemp, sizeof(CryptTemp), &pTunnel->CryptRecvState, sizeof(pTunnel->CryptRecvState)); + uRecvOffsetTemp = pTunnel->uRecvOffset; + + // init recv state with specified key & reset offset + CryptArc4Init(&pTunnel->CryptRecvState, (unsigned char *)pKey, (int32_t)strlen(pKey), PROTOTUNNEL_CRYPTARC4_ITER); + pTunnel->uRecvOffset = 0; + + // decrypt and validate packet data + iNumPackets = _ProtoTunnelDecryptAndValidatePacket(pProtoTunnel, pTunnel, &uHeadOffset, aPacketData, iPacketSize, FALSE); + + // restore previous recv state + ds_memcpy(&pTunnel->CryptRecvState, &CryptTemp, sizeof(CryptTemp)); + pTunnel->uRecvOffset = uRecvOffsetTemp; + + // restore previous HMAC key + ds_memcpy_s(pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), aHmacKeyTemp, sizeof(aHmacKeyTemp)); + + // copy decrypted data to output buffer, if available + if ((iNumPackets > 0) && (pOutputData != NULL)) + { + ds_memcpy_s(pOutputData, iPacketSize, aPacketData, sizeof(aPacketData)); + } + + // return number of packets decoded + return(iNumPackets); +} + +/*F********************************************************************************/ +/*! + \Function ProtoTunnelStatus + + \Description + Get module status + + \Input *pProtoTunnel - pointer to module state + \Input iSelect - status selector + \Input iValue - selector specific + \Input *pBuf - [out] - selector specific + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + 'actv' - return number of active tunnels based on version (via iValue) + 'bnds' - return if the specified port (iValue) is part of the server port list + 'dpkt' - return number of out-of-order packet discards + 'hmac' - return HMAC type/size + 'idle' - return idle callback rate + 'lprt' - return local socket port + 'maxp' - return maximum size of packet that can be tunneled (NOTE: pProtoTunnel can be NULL) + 'rcvs' - copy receive statistics to pBuf + 'rcal' - return number of recv calls + 'rmax' - return maximum number of recv calls allowed in _ProtoTunnelRecvCallback + 'rprt' - remote game port for the specificed tunnel id + 'rsub' - return number of sub-packets received + 'rtot' - return number of packets received + 'snds' - copy send statistics to pBuf + 'sock' - copy socket ref to pBuf + 'vers' - return protocol version used with this tunnel id (specified with iValue) + 'vset' - return set of supported versions as a comma delimited string (via pBuf) + 'vtop' - convert virtual address to physical address (pBuf == sockaddr, optional) + \endverbatim + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoTunnelStatus(ProtoTunnelRefT *pProtoTunnel, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + if (iSelect == 'actv') + { + int32_t iTunnel; + int32_t iNumActive = 0; + + // acquire exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // get active tunnels by version + for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel += 1) + { + const ProtoTunnelT *pTunnel = &pProtoTunnel->Tunnels[iTunnel]; + if (pTunnel->uActive == 0) + { + continue; + } + if (pTunnel->Info.uTunnelVers == (uint16_t)iValue) + { + iNumActive += 1; + } + } + + // release exclusive access to tunnel list + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + + return(iNumActive); + } + if (iSelect == 'bnds') + { + return(_ProtoTunnelServerPortListFunc(pProtoTunnel, (uint16_t)iValue, PROTOTUNNEL_PORTLIST_CHK)); + } + if (iSelect == 'dpkt') + { + return(pProtoTunnel->uNumPktsDiscard); + } + if (iSelect == 'hmac') + { + return((pProtoTunnel->uHmacType << 4) | pProtoTunnel->uHmacSize); + } + if (iSelect == 'idle') + { + return(pProtoTunnel->iIdleCbRate); + } + if (iSelect == 'lprt') + { + return(pProtoTunnel->uTunnelPort); + } + if (iSelect == 'maxp') + { + return(PROTOTUNNEL_MAXPACKET); + } + if (iSelect == 'rcal') + { + return(pProtoTunnel->uNumRecvCalls); + } + if (iSelect == 'rmax') + { + return(pProtoTunnel->iMaxRecv); + } + if (iSelect == 'rprt') + { + if ((pBuf != NULL) && (iBufSize == sizeof(uint16_t))) + { + int32_t iTunnel = _ProtoTunnelIndexFromId(pProtoTunnel, (uint32_t)iValue); + if (iTunnel != -1) + { + *(uint16_t *)pBuf = pProtoTunnel->Tunnels[iTunnel].Info.uRemotePort; + return(0); + } + else + { + NetPrintf(("prototunnel: [%p] 'rprt' status selector called with unknown tunnelid=0x%08x\n", pProtoTunnel, iValue)); + return(-1); + } + } + } + if (iSelect == 'rsub') + { + return(pProtoTunnel->uNumSubPktsRecvd); + } + if (iSelect == 'rtot') + { + return(pProtoTunnel->uNumPktsRecvd); + } + if (iSelect == 'rcvs') + { + if ((pBuf != NULL) && (iBufSize == sizeof(ProtoTunnelStatT)) && (iValue < pProtoTunnel->iMaxTunnels)) + { + _ProtoTunnelStatCalc(&pProtoTunnel->Tunnels[iValue], iSelect); + ds_memcpy(pBuf, &pProtoTunnel->Tunnels[iValue].RecvStat, sizeof(ProtoTunnelStatT)); + return(0); + } + } + if (iSelect == 'snds') + { + if ((pBuf != NULL) && (iBufSize == sizeof(ProtoTunnelStatT)) && (iValue < pProtoTunnel->iMaxTunnels)) + { + _ProtoTunnelStatCalc(&pProtoTunnel->Tunnels[iValue], iSelect); + ds_memcpy(pBuf, &pProtoTunnel->Tunnels[iValue].SendStat, sizeof(ProtoTunnelStatT)); + return(0); + } + } + if (iSelect == 'sock') + { + ds_memcpy(pBuf, &pProtoTunnel->pSocket, iBufSize); + return(0); + } + if (iSelect == 'vers') + { + int32_t iResult = -1; + int32_t iTunnel = _ProtoTunnelIndexFromId(pProtoTunnel, (uint32_t)iValue); + if (iTunnel != -1) + { + iResult = (int32_t)pProtoTunnel->Tunnels[iTunnel].Info.uTunnelVers; + } + else + { + NetPrintf(("prototunnel: [%p] 'vers' status selector called with unknown tunnelid=0x%08x\n", pProtoTunnel, iValue)); + } + return(iResult); + } + if (iSelect == 'vset') + { + if (pBuf != NULL && iBufSize > 0) + { + ds_strnzcpy(pBuf, PROTOTUNNEL_VERSIONS, iBufSize); + return(0); + } + } + if (iSelect == 'vtop') + { + return(_ProtoTunnelVirtualToPhysical(pProtoTunnel, iValue, pBuf, iBufSize)); + } + // selector not supported + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function ProtoTunnelControl + + \Description + Control the module + + \Input *pProtoTunnel - pointer to module state + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector result + + \Notes + iControl can be one of the following: + + \verbatim + 'bind' - recreate tunnel socket bound to specified port (iValue is port to bind to) + 'bndr' - remove a server tunnel port mapping from list (iValue is port to remove) + 'bnds' - recreate server tunnel socket bound to ephemeral port (iValue is ignored, iValue2 is remote server port) + 'clid' - set local clientId for all tunnels + 'drop' - drop next iValue packets (debug only; used to test crypt recovery) + 'flsh' - flush the specified tunnelId + 'hmac' - set hmac type (iValue) and size (iValue2) + 'idle' - set idle callback rate in milliseconds (iValue; default is 100) + 'rand' - set behavior to retry random port on bind failure via iValue, call before ProtoTunnelCreate + 'rate' - set flush rate in milliseconds; defaults to 16ms + 'rmax' - set maximum receive calls per call to _ProtoTunnelRecvCallback (iValue; default is 64) + 'rprt' - set specified tunnel's remote port + 'rrcb' - set raw receive callback + 'rrud' - set raw receive user data + 'sock' - set socket ref + 'spam' - set verbosity level (debug only) + 'tcid' - set per-tunnel local clientId override for specific tunnel + \endverbatim + + Unrecognized selectors are passed through to SocketControl() + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoTunnelControl(ProtoTunnelRefT *pProtoTunnel, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue) +{ + if (iControl == 'bind') + { + SocketT *pOldSocket = NULL, *pNewSocket = NULL; + NetPrintf(("prototunnel: [%p] recreating tunnel socket bound to port %d\n", pProtoTunnel, iValue)); + + // early out if we already have socket bound to this port + if (pProtoTunnel->uTunnelPort == (unsigned)iValue) + { + NetPrintf(("prototunnel: [%p] already have socket bound to port %d\n", pProtoTunnel, pProtoTunnel->uTunnelPort)); + return(0); + } + + // recreate tunnel socket bound to new port + if ((pNewSocket = _ProtoTunnelSocketOpen(pProtoTunnel, iValue)) == NULL) + { + NetPrintf(("prototunnel: [%p] could not recreate tunnel socket\n", pProtoTunnel)); + return(-1); + } + + // acquire exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // save old tunnel socket and replace with new socket + if (pProtoTunnel->pSocket != NULL) + { + pOldSocket = pProtoTunnel->pSocket; + } + pProtoTunnel->pSocket = pNewSocket; + + // release exclusive access to tunnel list + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + + // reconfigure new tunnel socket + pProtoTunnel->uTunnelPort = _ProtoTunnelSocketConfig(pProtoTunnel, pProtoTunnel->pSocket); + + // close old socket + if (pOldSocket != NULL) + { + SocketClose(pOldSocket); + } + return(0); + } + if (iControl == 'bndr') + { + // del remote port from list + if (!_ProtoTunnelServerPortListFunc(pProtoTunnel, (uint16_t)iValue, PROTOTUNNEL_PORTLIST_DEL)) + { + NetPrintf(("prototunnel: [%p] could not del port %d from server port list\n", pProtoTunnel, iValue)); + } + return(0); + } + if (iControl == 'bnds') + { + if (pProtoTunnel->pServerSocket == NULL) + { + SocketT *pSocket = NULL; + + // create tunnel socket (binding to an ephemeral port) + NetPrintf(("prototunnel: [%p] creating server tunnel socket (binding to system selected ephemeral port) - uServerRemotePort = %d\n", pProtoTunnel, iValue)); + if ((pSocket = _ProtoTunnelSocketOpen(pProtoTunnel, 0)) == NULL) + { + NetPrintf(("prototunnel: [%p] could not create server tunnel socket\n", pProtoTunnel)); + return(-1); + } + + // acquire exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // set new socket + pProtoTunnel->pServerSocket = pSocket; + + // release exclusive access to tunnel list + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + + // reconfigure new tunnel socket + _ProtoTunnelSocketConfig(pProtoTunnel, pProtoTunnel->pServerSocket); + } + else + { + NetPrintf(("prototunnel: --- reusing server socket ---\n")); + } + + // add remote port to list + if (!_ProtoTunnelServerPortListFunc(pProtoTunnel, (uint16_t)iValue, PROTOTUNNEL_PORTLIST_ADD)) + { + NetPrintf(("prototunnel: [%p] could not add port to server port list\n", pProtoTunnel)); + } + return(0); + } + if (iControl == 'clid') + { + NetPrintf(("prototunnel: [%p] setting local clientId=0x%08x\n", pProtoTunnel, iValue)); + pProtoTunnel->uLocalClientId = iValue; + return(0); + } + #if DIRTYCODE_DEBUG + if (iControl == 'drop') + { + pProtoTunnel->iPacketDrop = iValue; + return(0); + } + #endif + if ((iControl == 'flsh') || (iControl == 'rprt')) + { + ProtoTunnelT *pTunnel; + int32_t iTunnel; + uint32_t uCurTick = NetTick(); + + // acquire exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // flush specified tunnel + for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) + { + pTunnel = &pProtoTunnel->Tunnels[iTunnel]; + if (pTunnel->uVirtualAddr == (unsigned)iValue) + { + if (iControl == 'flsh') + { + NetPrintf(("prototunnel: [%p][%04d] explicitly flushing tunnel 0x%08x\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->uVirtualAddr)); + _ProtoTunnelBufferSend(pProtoTunnel, pTunnel, uCurTick); + } + if (iControl == 'rprt') + { + NetPrintf(("prototunnel: [%p][%04d] updating remote port for tunnel 0x%08x to %d\n", + pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->uVirtualAddr, iValue2)); + pTunnel->Info.uRemotePort = (unsigned)iValue2; + } + break; + } + } + + // release exclusive access to tunnel list + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + + // if we didn't find the tunnel + if (iTunnel == pProtoTunnel->iMaxTunnels) + { + NetPrintf(("prototunnel: [%p] unable to find tunnel 0x%08x for '%c%c%c%c' operation\n", + pProtoTunnel, iValue, (uint8_t)(iControl>>24), (uint8_t)(iControl>>16), (uint8_t)(iControl>>8), (uint8_t)iControl)); + return(-1); + } + return(0); + } + if (iControl == 'hmac') + { + int32_t iHashSize; + // validate hmac type + if ((iValue <= CRYPTHASH_NULL) || (iValue >= CRYPTHASH_NUMHASHES)) + { + NetPrintf(("prototunnel: [%p] ignoring attempt to set invalid hmactype %d\n", pProtoTunnel, iValue)); + return(-1); + } + // validate hmac size + iHashSize = CryptHashGetSize((CryptHashTypeE)iValue); + if (iValue2 > iHashSize) + { + NetPrintf(("prototunnel: [%p] hmacsize %d is larger than max hmactype size %d; truncating\n", pProtoTunnel, iValue2, iHashSize)); + iValue2 = iHashSize; + } + if (iValue2 > PROTOTUNNEL_HMAC_MAXSIZE) + { + NetPrintf(("prototunnel: [%p] hmacsize %d is too large; truncating\n", pProtoTunnel, iValue2)); + iValue2 = PROTOTUNNEL_HMAC_MAXSIZE; + } + NetPrintf(("prototunnel: [%p] setting hmactype=%d and hmacsize=%d\n", pProtoTunnel, iValue, iValue2)); + pProtoTunnel->uHmacType = (uint8_t)iValue; + pProtoTunnel->uHmacSize = (uint8_t)iValue2; + return(0); + } + if (iControl == 'idle') + { + pProtoTunnel->iIdleCbRate = iValue; + SocketCallback(pProtoTunnel->pSocket, CALLB_RECV, pProtoTunnel->iIdleCbRate, pProtoTunnel, &_ProtoTunnelRecvCallback); + return(0); + } + if (iControl == 'rand') + { + NetPrintf(("prototunnel: setting retry random port on bind failure to %s\n", (uint8_t)iValue ? "TRUE" : "FALSE")); + _ProtoTunnel_bRetryRandomOnFailure = (uint8_t)iValue; + return(0); + } + if (iControl == 'rate') + { + pProtoTunnel->uFlushRate = iValue; + return(0); + } + if (iControl == 'rmax') + { + pProtoTunnel->iMaxRecv = iValue; + return(0); + } + if (iControl == 'rrcb') + { + NetPrintf(("prototunnel: [%p] 'rrcb' selector used to change raw recv callback from %p to %p\n", pProtoTunnel, pProtoTunnel->pRawRecvCallback, pValue)); + pProtoTunnel->pRawRecvCallback = (RawRecvCallbackT *)pValue; + return(0); + } + if (iControl == 'rrud') + { + NetPrintf(("prototunnel: [%p] 'rrud' selector used to change raw recv callback user data from %p to %p\n", pProtoTunnel, pProtoTunnel->pRawRecvUserData, pValue)); + pProtoTunnel->pRawRecvUserData = (void *)pValue; + return(0); + } + if (iControl == 'sock') + { + NetPrintf(("prototunnel: [%p] replacing tunnel socket\n", pProtoTunnel)); + + // acquire exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // close current tunnel socket + if (pProtoTunnel->pSocket != NULL) + { + SocketClose(pProtoTunnel->pSocket); + pProtoTunnel->pSocket = NULL; + } + // write in the new socket and configure it + pProtoTunnel->pSocket = (SocketT *)pValue; + _ProtoTunnelSocketConfig(pProtoTunnel, pProtoTunnel->pSocket); + + // release exclusive access to tunnel list + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); + return(0); + } + if (iControl == 'spam') + { + pProtoTunnel->iVerbosity = iValue; + return(0); + } + if (iControl == 'tcid') + { + int32_t iTunnel = _ProtoTunnelIndexFromId(pProtoTunnel, (uint32_t)iValue); + if (iTunnel != -1) + { + NetPrintf(("prototunnel: [%p][%04d] changing local client id from 0x%08x to 0x%08x\n", pProtoTunnel, iTunnel, pProtoTunnel->Tunnels[iTunnel].uLocalClientId, iValue2)); + pProtoTunnel->Tunnels[iTunnel].uLocalClientId = (uint32_t)iValue2; + return(0); + } + else + { + NetPrintf(("prototunnel: [%p] 'tcid' control selector called with unknown tunnelid=0x%08x\n", pProtoTunnel, iValue)); + return(-1); + } + } + if (iControl == 'vers') + { + if (iValue < PROTOTUNNEL_VERSION_MIN) + { + NetPrintf(("prototunnel [%p] ignoring attempt to set invalid version %d.%d\n", pProtoTunnel, iValue>>8, iValue&0xff)); + return(-1); + } + NetPrintf(("prototunnel: [%p] setting version to %d.%d\n", pProtoTunnel, iValue>>8, iValue&0xff)); + pProtoTunnel->uVersion = (uint16_t)iValue; + return(0); + } + // pass-through to SocketControl() + return(SocketControl(pProtoTunnel->pSocket, iControl, iValue, NULL, NULL)); +} + +/*F********************************************************************************/ +/*! + + \Function ProtoTunnelUpdate + + \Description + Update the module + + \Input *pProtoTunnel - pointer to module state + + \Version 12/02/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoTunnelUpdate(ProtoTunnelRefT *pProtoTunnel) +{ + uint32_t uCurTick = NetTick(); + int32_t iTunnel; + + // time to flush? + + // acquire exclusive access to tunnel list + NetCritEnter(&pProtoTunnel->TunnelsCritS); + NetCritEnter(&pProtoTunnel->TunnelsCritR); + + // flush all tunnels + // check individual flush timers, which are reset each send + for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) + { + ProtoTunnelT *pTunnel = &(pProtoTunnel->Tunnels[iTunnel]); + if (pTunnel->uVirtualAddr + && (NetTickDiff(uCurTick, pTunnel->uLastTunnelSend) > (signed)pProtoTunnel->uFlushRate)) + { + _ProtoTunnelBufferSend(pProtoTunnel, pTunnel, uCurTick); + } + } + + // release exclusive access to tunnel list + NetCritLeave(&pProtoTunnel->TunnelsCritR); + NetCritLeave(&pProtoTunnel->TunnelsCritS); +} + +/*F*************************************************************************************/ +/*! + \Function ProtoTunnelRawSendto + + \Description + Send data to a remote host over the prototunnel socket. The destination address + is supplied along with the data. Important: remote host shall not be expecting + tunneled data because that function bypasses all the tunneling logic on the sending + side. + + \Input *pProtoTunnel - pointer to module state + \Input *pBuf - the data to be sent + \Input iLen - size of data + \Input *pTo - the address to send to + \Input iToLen - length of address + + \Output + int32_t - number of bytes sent or standard network error code (SOCKERR_xxx) + + \Version 07/12/2011 (mclouatre) +*/ +/************************************************************************************F*/ +int32_t ProtoTunnelRawSendto(ProtoTunnelRefT *pProtoTunnel, const char *pBuf, int32_t iLen, const struct sockaddr *pTo, int32_t iToLen) +{ + return(_ProtoTunnelSocketSendto(pProtoTunnel, pBuf, iLen, 0, pTo, iToLen)); +} diff --git a/src/thirdparty/dirtysdk/source/proto/protoupnp.c b/src/thirdparty/dirtysdk/source/proto/protoupnp.c new file mode 100644 index 00000000..34ed5bae --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protoupnp.c @@ -0,0 +1,2253 @@ +/*H********************************************************************************/ +/*! + \File protoupnp.c + + \Description + Implements a simple UPnP client, designed specifically to talk to a UPnP + router and open up a firewall port for peer-peer communication with a + remote client. + + \Notes + ProtoUpnp implementation was based on the following documents: + + References: + [1] UPnP Device Architecture: http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0-20080424.pdf + [2] UPnP Resources: http://upnp.org/sdcps-and-certification/resources/ + [3] Intel Software for UPnP Technology: http://software.intel.com/en-us/articles/intel-software-for-upnp-technology-download-tools/ + [4] Internet Gateway Device v1.0: http://upnp.org/specs/gw/igd1/ + [5] SOAP 1.2: http://www.w3.org/TR/2003/REC-soap12-part0-20030624/ + [6] UPnP compatibility list (old): http://ccgi.mgillespie.plus.com/upnp/test.php + + In addition, many thanks to the Burnout3 team, whose UPnP implementation + was used as reference. + + UPnP Optional Features: + [a] - Finite Lease Duration + [b] - Wildcard in Source IP (Remote Host) + [c] - Wildcard in External Port + [d] - Non-matching Internal and External Ports + [e] - Specific Remote Host + [f] - Specific External Port + + Tested against the following routers: + <1> Linksys WRT54G 3.03.06 [b,c,d,e,f] + <2> Linksys BEFSR41 [b,c?,d,e,f] [c? because GetSpecificPortMapping with wc port fails) + <3> Netgear WGR614v6 1.0.8 [b,d,f] + + Common UPnP SOAP errors we have seen in usage: + + 402: Invalid Args + 403: Undefined + 501: Action Failed + 713: SpecifiedArrayIndexInvalid (GetGenericPortMapping return code, but we don't use that method?) + 714: NoSuchEntryInArray (GetSpecificPortMapping/DeletePortMapping return code, should not be fatal) + 716: WildCardNotPermittedInExtPort (AddPortMapping return code, but we don't ask for a wildcard external port?) + 718: ConflictInMappingEntry (AddPortMapping return code) + + \Copyright + Copyright (c) 2005 Electronic Arts Inc. + + \Version 03/23/2005 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protohttp.h" +#include "DirtySDK/xml/xmlparse.h" + +#include "DirtySDK/proto/protoupnp.h" + +/*** Defines **********************************************************************/ + +//! define this to allow reuse of a preexisting mapping that matches what we want +#define PROTOUPNP_REUSEMAPPING (FALSE) + +//! define this to use WAN IP address as the source address in the port map if a remote host is unspecified +#define PROTOMANGLE_USEWANIPSRCADDR (FALSE) + +//! define this to use verbose variable descriptions in requests +#define PROTOUPNP_FULLVARDESC (FALSE) + +//! addr to send discovery requests to (239.255.255.250) +#define PROTOUPNP_DISCOVERYADDR (0xEFFFFFFA) + +//! port to send discovery requests to +#define PROTOUPNP_DISCOVERYPORT (1900) + +//! interval at which we send out discovery request broadcasts +#define PROTOUPNP_DISCOVERYINTERVAL (15000) + +//! maximum number of services parsed +#define PROTOUPNP_MAXSERVICES (5) + +//! default port to map +#define PROTOUPNP_DEFAULTPORTMAP (3658) + +/*** Macros ***********************************************************************/ + +//! add a soap item of type string +#define _ProtoUpnpSoapRequestAddStr(_pUpnp, _pName, _pData) _ProtoUpnpSoapRequestAdd(_pUpnp, _pName, "string", _pData) + +//! add a soap item of type ui2 +#define _ProtoUpnpSoapRequestAddUI2(_pUpnp, _pName, _uData) _ProtoUpnpSoapRequestAdd(_pUpnp, _pName, "ui2", _ProtoUpnpIntToStr(_uData)) + +//! add a soap item of type ui4 +#define _ProtoUpnpSoapRequestAddUI4(_pUpnp, _pName, _uData) _ProtoUpnpSoapRequestAdd(_pUpnp, _pName, "ui4", _ProtoUpnpIntToStr(_uData)) + +//! add a soap item of type boolean +#define _ProtoUpnpSoapRequestAddBln(_pUpnp, _pName, _uData) _ProtoUpnpSoapRequestAdd(_pUpnp, _pName, "boolean", _ProtoUpnpIntToStr(_uData)) + +/*** Type Definitions *************************************************************/ + +//! port mapping description +typedef struct ProtoUpnpPortMapT +{ + int32_t iAddr; + int32_t iPort; + int32_t iLeaseDuration; + char strDesc[31]; + uint8_t bEnabled; +} ProtoUpnpPortMapT; + +//! upnp service info +typedef struct ProtoUpnpServiceT +{ + char strServiceType[64]; //!< service type description + char strDescriptionUrl[128]; //!< service description URL + char strControlUrl[256]; //!< service control url +} ProtoUpnpServiceT; + +//! upnp device info +typedef struct ProtoUpnpDeviceT +{ + char strUrl[128]; //!< string holding device description url + char strUrlBase[64]; //!< base url, with addr/port info + char strUdn[64]; //!< device universal device name + char strModel[127]; //!< device manufacturer and model + uint8_t bDiscovered; //!< TRUE if we've discovered a UPnP device + uint32_t uExternalAddress; //!< external address of device + ProtoUpnpPortMapT CurPortMap; //!< current port mapping + int32_t iNumServices; //!< number of services device supports + ProtoUpnpServiceT Services[PROTOUPNP_MAXSERVICES]; //!< list of services device supports +} ProtoUpnpDeviceT; + +//! module state +struct ProtoUpnpRefT +{ + int32_t iRefCount; //!< module ref count + + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + SocketT *pUdp; //!< udp socket, used for discovery process + ProtoHttpRefT *pProtoHttp; //!< http module, used for device communication + + int32_t iClientAddr; //!< local address + + int32_t iDiscoveryTimer; //!< timer used to time discovery sends + struct sockaddr DiscoveryAddr; //!< address to send discovery to + + int32_t iStatus; //!< current status (PROTOUPNP_STATUS_*) + + int32_t iService; //!< current service index + + ProtoUpnpDeviceT Device; + + enum + { + ST_IDLE, //!< idle state + ST_DISCOVERY, //!< UPnP discovery + ST_DESCRIPTION, //!< UPnP description + ST_SERVICEDESC, //!< service description + ST_GETSTATEVAR, //!< QueryStateVariable request + ST_GETEXTADDR, //!< GetExternalAddress request + ST_GETPORTMAPPING, //!< GetSpecificPortMapping request + ST_DELPORTMAPPING, //!< DeletePortMapping request + ST_ADDPORTMAPPING, //!< AddPortMapping request + + ST_LAST, //!< last state + } eState; + + int32_t iSendSize; //!< size of current send buffer + int32_t iSentBytes; //!< amount of data that we have sent + int32_t iHttpError; //!< most recent ProtoHttpRecv() error, if any + int32_t iSoapError; //!< SOAP error + + int32_t iRemoteHost; //!< address of remote host + int32_t iPortToMapExt; //!< external port we are trying to map + int32_t iPortToMapInt; //!< internal port we are trying to map + int32_t iLeaseDuration; //!< lease duration to try and set + + const ProtoUpnpMacroT *pCommandList; //!< pointer to current command list, if any + + uint8_t bRequestInProgress; //!< TRUE if a device request is in progress + uint8_t bEnableMapping; //!< TRUE to enable mapping, else FALSE + uint8_t bPortIsMapped; //!< TRUE if port is already mapped + uint8_t bVerbose; //!< TRUE to set verbose mode, else FALSE + + #if DIRTYCODE_DEBUG + uint8_t bFakeResponse; //!< TRUE if faking a response, else FALSE + #endif + + char strRequestName[64]; //!< name of current request + char strSendBuf[2048]; //!< buffer to construct posts in + char strRecvBuf[16*1024]; //!< buffer to receive into + + NetCritT Crit; //!< critical section +}; + +/*** Variables ********************************************************************/ + +/*! protoupnp description, sent in soap requests. note that this string is deliberately + short because some implementations (e.g. BEFSR41) only store a small number of + characters (e.g. 11) for the mapping description */ +static const char _ProtoUpnp_strDescription[] = "EA Tunnel"; + +//! protoupnp macro to check for and get the upnp information +static const ProtoUpnpMacroT _ProtoUpnp_cmd_dscg[] = +{ + { 'disc', 0, 0, NULL }, // discovery + { 'desc', 0, 0, NULL }, // get router description + { 'gadr', 0, 0, NULL }, // GetExternalIPAddress + { 0, 0, 0, NULL } +}; + +//! protoupnp macro to add a port mapping +static const ProtoUpnpMacroT _ProtoUpnp_cmd_addp[] = +{ + { 'gprt', 0, 0, NULL }, // GetSpecificPortMapping + { 'aprt', 0, 0, NULL }, // AddPortMapping + { 0, 0, 0, NULL } +}; + +//! protoupnp macro to discover, describe, and check port mapping +static const ProtoUpnpMacroT _ProtoUpnp_cmd_dscp[] = +{ + { 'disc', 0, 0, NULL }, // discovery + { 'desc', 0, 0, NULL }, // get router description + { 'gadr', 0, 0, NULL }, // GetExternalIPAddress + { 'gprt', 0, 0, NULL }, // GetSpecificPortMapping + { 0, 0, 0, NULL } +}; + +//! protoupnp macro to discover, describe, and add port map +static const ProtoUpnpMacroT _ProtoUpnp_cmd_upnp[] = +{ + { 'disc', 0, 0, NULL }, // discovery + { 'desc', 0, 0, NULL }, // get router description + { 'gadr', 0, 0, NULL }, // GetExternalIPAddress + { 'gprt', 0, 0, NULL }, // GetSpecificPortMapping + { 'aprt', 0, 0, NULL }, // AddPortMapping + { 0, 0, 0, NULL } +}; + +//! protoupnp macro to fully test router +static const ProtoUpnpMacroT _ProtoUpnp_cmd_test[] = +{ + // initial test is basically the 'addp' macro plus service description + { 'disc', 0, 0, NULL }, + { 'desc', 0, 0, NULL }, + { 'sdsc', 0, 0, NULL }, + { 'gadr', 0, 0, NULL }, + { 'gprt', 0, 0, NULL }, + { 'aprt', 0, 0, NULL }, + { 'dprt', 0, 0, NULL }, + + // test Wildcard in Source IP optional feature + { 'host', 0, 0, NULL }, // set host to use wildcard + { 'aprt', 0, 0, NULL }, + { 'gprt', 0, 0, NULL }, + { 'dprt', 0, 0, NULL }, + { 'host', -1, 0, NULL }, // restore host + + // test Wildcard in External Port optional feature + { 'extp', 0, 0, NULL }, + { 'aprt', 0, 0, NULL }, + { 'gprt', 0, 0, NULL }, + { 'dprt', 0, 0, NULL }, + + // test Non-matching Internal and External Ports optional feature + { 'extp', 3000, 0, NULL }, + { 'aprt', 0, 0, NULL }, + { 'gprt', 0, 0, NULL }, + { 'dprt', 0, 0, NULL }, + + // done + { 0, 0, 0, NULL } +}; + +//! map states to control request idents +static uint32_t _ProtoUpnp_aStateMap[] = +{ + 'idle', // ST_IDLE + 'disc', // ST_DISCOVERY + 'desc', // ST_DESCRIPTION + 'sdsc', // ST_SERVICEDESC + 'gvar', // ST_GETSTATEVAR + 'gadr', // ST_GETEXTADDR + 'gprt', // ST_GETPORTMAPPING + 'dprt', // ST_DELPORTMAPPING + 'aprt', // ST_ADDPORTMAPPING +}; + +//! upnp module ref +static ProtoUpnpRefT *_ProtoUpnp_pRef = NULL; + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpIntToStr + + \Description + Convert an integer to a string, and return a pointer to the string. + + \Input iVal - value to convert to string + + \Output + const char *- pointer to string + + \Version 03/30/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_ProtoUpnpIntToStr(int32_t iVal) +{ + static char _strVal[16]; + ds_snzprintf(_strVal, sizeof(_strVal), "%d", iVal); + return(_strVal); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpFindUrl + + \Description + Find the url following the http://host:port/ portion of a url. + + \Input *pUrl - pointer to source url to parse + + \Output + const char * - pointer to url or NULL + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_ProtoUpnpFindUrl(const char *pUrl) +{ + // make sure it's http (or https) + if ((pUrl = ds_stristr(pUrl, "http")) == NULL) + { + return(NULL); + } + + // skip past double slash + if ((pUrl = ds_stristr(pUrl, "//")) == NULL) + { + return(NULL); + } + pUrl += 2; + + // next slash is the start of the url + return(strchr(pUrl, '/')); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpMakeFullUrl + + \Description + Create a full url for the given device. + + \Input *pDevice - pointer to device + \Input *pBuffer - [out] storage for full url + \Input iBufSize - size of output buffer + \Input *pUrl - pointer to input url (which may be relative or absolute) + + \Version 11/07/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpMakeFullUrl(ProtoUpnpDeviceT *pDevice, char *pBuffer, int iBufSize, const char *pUrl) +{ + // clear output url buffer + ds_memclr(pBuffer, iBufSize); + + // prepend url base if we weren't given an absolute url + if (ds_strnicmp(pUrl, "http", 4)) + { + ds_strnzcpy(pBuffer, pDevice->strUrlBase, iBufSize); + // if relative url does not start with a slash, add one here + if (*pUrl != '/') + { + NetPrintf(("protoupnp: relative url does not start with a forward slash; adding one\n")); + ds_strnzcat(pBuffer, "/", iBufSize); + } + } + ds_strnzcat(pBuffer, pUrl, iBufSize); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpReset + + \Description + Reset module state + + \Input *pProtoUpnp - pointer to module state + + \Version 07/14/2009 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpReset(ProtoUpnpRefT *pProtoUpnp) +{ + // init device structure + ds_memclr(&pProtoUpnp->Device, sizeof(pProtoUpnp->Device)); + + // force immediate discovery broadcast + pProtoUpnp->iDiscoveryTimer = NetTick() - PROTOUPNP_DISCOVERYINTERVAL; + + // clear status + pProtoUpnp->iStatus = 0; + + // reset service index + pProtoUpnp->iService = 0; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpError + + \Description + Handle error condition + + \Input *pProtoUpnp - pointer to module state + \Input *pReason - reason for error + + \Version 10/07/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpError(ProtoUpnpRefT *pProtoUpnp, const char *pReason) +{ + // abort current command list? + //pProtoUpnp->pCommandList = NULL; + + // reset to idle state + pProtoUpnp->eState = ST_IDLE; + + // output reason to debug output + NetPrintf(("protoupnp: error -- %s\n", pReason)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpSendDiscoveryRequest + + \Description + Send a discovery request + + \Input *pProtoUpnp - pointer to module state + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpSendDiscoveryRequest(ProtoUpnpRefT *pProtoUpnp) +{ + /*! HTTP-formatted packet to send in UDP discovery broadcast + [1] 1.2.2 Discovery: Search: Request with M-SEARCH */ + static const char _strDiscoveryPacket[] = + "M-SEARCH * HTTP/1.1\r\n" \ + "Host:239.255.255.250:1900\r\n" \ + "ST:urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" \ + "Man:\"ssdp:discover\"\r\n" \ + "MX:3\r\n" \ + "\r\n"; + + // send discovery multicast + #if DIRTYCODE_LOGGING + if (pProtoUpnp->bVerbose) + { + NetPrintf(("protoupnp: multicasting discovery request\n")); + } + #endif + SocketSendto(pProtoUpnp->pUdp, _strDiscoveryPacket, sizeof(_strDiscoveryPacket), 0, + &pProtoUpnp->DiscoveryAddr, sizeof(pProtoUpnp->DiscoveryAddr)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpParseDiscoveryResponse + + \Description + Parse a discovery response + + \Input *pProtoUpnp - pointer to module state + \Input *pResponse - pointer to response + + \Output + int32_t - zero=failure, else success + + \Notes + The discovery response is an HTTP response in a UDP packet. Since + ProtoHttp does not support UDP, we do the parsing inline here. + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoUpnpParseDiscoveryResponse(ProtoUpnpRefT *pProtoUpnp, const char *pResponse) +{ + const char strLocation[] = "Location:"; + const char *pLocation, *pUrl; + int32_t iStrLen; + + // echo response to debug output + #if DIRTYCODE_LOGGING + if (pProtoUpnp->bVerbose) + { + NetPrintf(("protoupnp: received discovery response:\n")); + NetPrintWrap(pProtoUpnp->strRecvBuf, 80); + } + #endif + + // make sure we have a valid header + if (strncmp(pResponse, "HTTP", 4)) + { + NetPrintf(("protoupnp: ignoring non-http response\n")); + return(0); + } + + /*! check for substring that must be included in a valid discovery response + [1] 1.2.3 Discovery: Search: Response */ + if (!ds_stristr(pResponse, "urn:schemas-upnp-org:device:wanconnectiondevice")) + { + NetPrintf(("protoupnp: ignoring non-discovery response\n")); + return(0); + } + + // find and skip location header + if ((pLocation = ds_stristr(pResponse, strLocation)) == NULL) + { + NetPrintf(("protoupnp: response did not include a location header\n")); + return(0); + } + pLocation += sizeof(strLocation) - 1; + + // skip to location body + while((*pLocation != '\0') && (*pLocation <= ' ')) + { + pLocation += 1; + } + + // copy url, leaving room for null termination + for (iStrLen = 0; iStrLen < (signed)(sizeof(pProtoUpnp->Device.strUrl) - 1); iStrLen++) + { + if ((*pLocation == '\0') || (*pLocation == '\r') || (*pLocation == '\n')) + { + break; + } + pProtoUpnp->Device.strUrl[iStrLen] = *pLocation++; + } + + // null terminate url + pProtoUpnp->Device.strUrl[iStrLen] = '\0'; + NetPrintf(("protoupnp: found upnp device '%s'\n", pProtoUpnp->Device.strUrl)); + + // extract address/port + if ((pUrl = _ProtoUpnpFindUrl(pProtoUpnp->Device.strUrl)) != NULL) + { + ds_strsubzcpy(pProtoUpnp->Device.strUrlBase, sizeof(pProtoUpnp->Device.strUrlBase), pProtoUpnp->Device.strUrl, (int32_t)(pUrl-pProtoUpnp->Device.strUrl)); + NetPrintf(("protoupnp: parsed base url '%s'\n", pProtoUpnp->Device.strUrlBase)); + } + + // mark device as valid and set status + pProtoUpnp->Device.bDiscovered = TRUE; + + // reset to idle state + pProtoUpnp->eState = ST_IDLE; + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpGetRemoteHost + + \Description + Get string-formatted address for remote host used in UPnP service requests. + + \Input *pProtoUpnp - pointer to module state + \Input *pBuffer - buffer + \Input iBufSize - buffer size + + \Version 11/07/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpGetRemoteHost(ProtoUpnpRefT *pProtoUpnp, char *pBuffer, int32_t iBufSize) +{ + uint32_t uRemoteHost; + + // get remote host address + #if PROTOMANGLE_USEWANIPSRCADDR + uRemoteHost = (pProtoUpnp->iRemoteHost == -1) ? pProtoUpnp->Device.uExternalAddress : (unsigned)pProtoUpnp->iRemoteHost; + #else + uRemoteHost = (pProtoUpnp->iRemoteHost == -1) ? 0 : (unsigned)pProtoUpnp->iRemoteHost; + #endif + + // return formatted host string + if (uRemoteHost != 0) + { + SocketInAddrGetText(uRemoteHost, pBuffer, iBufSize); + } + else + { + *pBuffer = '\0'; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpHttpReset + + \Description + Reset HTTP-tracking variables prior to initiating an HTTP transaction. + + \Input *pProtoUpnp - pointer to module state + + \Version 10/10/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpHttpReset(ProtoUpnpRefT *pProtoUpnp) +{ + pProtoUpnp->iHttpError = 0; + pProtoUpnp->iSoapError = 0; + pProtoUpnp->bRequestInProgress = TRUE; + ds_memclr(pProtoUpnp->strRecvBuf, sizeof(pProtoUpnp->strRecvBuf)); + if (pProtoUpnp->pProtoHttp != NULL) + { + ProtoHttpControl(pProtoUpnp->pProtoHttp, 'keep', 0, 0, NULL); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpHttpWaitResponse + + \Description + Wait for an HTTP response. + + \Input *pProtoUpnp - pointer to module state + + \Output + int32_t - negative=failure, zero=in progress, positve=success + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoUpnpHttpWaitResponse(ProtoUpnpRefT *pProtoUpnp) +{ + int32_t iRecvResult; + + // if faking a response, data is already in buffer waiting for us + #if DIRTYCODE_DEBUG + if (pProtoUpnp->bFakeResponse) + { + pProtoUpnp->bFakeResponse = FALSE; + pProtoUpnp->bRequestInProgress = FALSE; + pProtoUpnp->eState = ST_IDLE; + return(2); + } + #endif + + // give some time to ProtoHttp + ProtoHttpUpdate(pProtoUpnp->pProtoHttp); + + // send any data that is still needed to be sent + if (pProtoUpnp->iSentBytes < pProtoUpnp->iSendSize) + { + int32_t iResult; + if ((iResult = ProtoHttpSend(pProtoUpnp->pProtoHttp, pProtoUpnp->strSendBuf+pProtoUpnp->iSentBytes, pProtoUpnp->iSendSize-pProtoUpnp->iSentBytes)) >= 0) + { + pProtoUpnp->iSentBytes += iResult; + } + else + { + NetPrintf(("protoupnp: %s request send failed for service %s with result %d\n", pProtoUpnp->strRequestName, + pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType, iResult)); + pProtoUpnp->iHttpError = iResult; + pProtoUpnp->bRequestInProgress = FALSE; + pProtoUpnp->eState = ST_IDLE; + pProtoUpnp->iSentBytes = 0; + return(-1); + } + } + + // read data + if ((iRecvResult = ProtoHttpRecvAll(pProtoUpnp->pProtoHttp, pProtoUpnp->strRecvBuf, sizeof(pProtoUpnp->strRecvBuf))) >= 0) + { + // echo response to debug output + #if DIRTYCODE_LOGGING + if (pProtoUpnp->bVerbose) + { + NetPrintf(("\n")); + NetPrintWrap(pProtoUpnp->strRecvBuf, 80); + } + #endif + + // reset transaction state + pProtoUpnp->bRequestInProgress = FALSE; + pProtoUpnp->eState = ST_IDLE; + pProtoUpnp->iSentBytes = 0; + + // check for zero-length response (has been shown to happen in some Windows ICS error responses) + if (iRecvResult == 0) + { + return(-1); + } + // success + return(1); + } + else if ((iRecvResult < 0) && (iRecvResult != PROTOHTTP_RECVWAIT)) + { + NetPrintf(("protoupnp: %s request receive failed for service %s with result %d\n", pProtoUpnp->strRequestName, + pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType, iRecvResult)); + pProtoUpnp->iHttpError = iRecvResult; + pProtoUpnp->bRequestInProgress = FALSE; + pProtoUpnp->eState = ST_IDLE; + pProtoUpnp->iSentBytes = 0; + return(-1); + } + + // return pending result + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpSoapFormatRequestHeader + + \Description + Format SOAP header for request given by pRequestName, using the currently + active service. + + \Input *pProtoUpnp - pointer to module state + \Input *pRequestName - pointer to request name + + \Version 03/27/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpSoapFormatRequestHeader(ProtoUpnpRefT *pProtoUpnp, const char *pRequestName) +{ + char strHdrExtra[256]; + + // set up extended header fields + ds_snzprintf(strHdrExtra, sizeof(strHdrExtra), + "Content-Type: text/xml; charset=\"utf-8\"\r\n" + "SOAPAction: \"%s#%s\"\r\n" + "Cache-Control: no-cache\r\n", + pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType, + pRequestName); + + // tell ProtoHttp to use this for the extended header info + ProtoHttpControl(pProtoUpnp->pProtoHttp, 'apnd', 0, 0, strHdrExtra); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpSoapRequestOpen + + \Description + Format a SOAP request based on pRequestName and the given variable-argument + list that constructs the SOAP request body, for the currently active + service. + + \Input *pProtoUpnp - pointer to module state + \Input *pRequestName - pointer to request name + + \Version 03/30/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpSoapRequestOpen(ProtoUpnpRefT *pProtoUpnp, const char *pRequestName) +{ + char *pBuf = pProtoUpnp->strSendBuf; + int32_t iBufSize = sizeof(pProtoUpnp->strSendBuf); + + // set up the header + _ProtoUpnpSoapFormatRequestHeader(pProtoUpnp, pRequestName); + + // set up soap envelope start + pProtoUpnp->iSendSize = ds_snzprintf(pBuf, iBufSize, + "\r\n" + " \r\n" + " \r\n", + pRequestName, + pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType); + + // save request name + ds_strnzcpy(pProtoUpnp->strRequestName, pRequestName, sizeof(pProtoUpnp->strRequestName)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpSoapRequestAdd + + \Description + Add a value to a soap request. This function should not be used directly, + the _ProtoUpnpSoapRequestAddStr, AddUI2, AddUI4, and AddBln macros should be + used instead. + + \Input *pProtoUpnp - pointer to module state + \Input *pName - pointer to item name + \Input *pType - pointer to item type + \Input *pData - pointer to item data + + + \Version 03/30/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpSoapRequestAdd(ProtoUpnpRefT *pProtoUpnp, const char *pName, const char *pType, const char *pData) +{ + char *pBuf = pProtoUpnp->strSendBuf + pProtoUpnp->iSendSize; + int32_t iBufSize = sizeof(pProtoUpnp->strSendBuf) - pProtoUpnp->iSendSize; + + #if PROTOUPNP_FULLVARDESC + pProtoUpnp->iSendSize += ds_snzprintf(pBuf, iBufSize, + " <%s xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"%s\">%s\r\n", + pName, pType, pData, pName); + #else + pProtoUpnp->iSendSize += ds_snzprintf(pBuf, iBufSize, " <%s>%s\r\n", pName, pData, pName); + #endif +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpSoapRequestClose + + \Description + Close the soap request. + + \Input *pProtoUpnp - pointer to module state + + \Version 03/30/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpSoapRequestClose(ProtoUpnpRefT *pProtoUpnp) +{ + char *pBuf = pProtoUpnp->strSendBuf + pProtoUpnp->iSendSize; + int32_t iBufSize = sizeof(pProtoUpnp->strSendBuf) - pProtoUpnp->iSendSize; + + // append soap envelope end + pProtoUpnp->iSendSize += ds_snzprintf(pBuf, iBufSize, + " \r\n" + " \r\n" + "\r\n\r\n", + pProtoUpnp->strRequestName); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpSoapRequestPost + + \Description + Post a formatted SOAP request to gateway device. + + \Input *pProtoUpnp - pointer to module state + + \Version 03/27/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpSoapRequestPost(ProtoUpnpRefT *pProtoUpnp) +{ + int32_t iResult; + + if (pProtoUpnp->bRequestInProgress == TRUE) + { + _ProtoUpnpError(pProtoUpnp, "a soap request is already in progress"); + return; + } + + // print request to debug output + NetPrintf(("protoupnp: initiating %s request for service %s\n", pProtoUpnp->strRequestName, + pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType)); + + // initiate external address fetch operation + _ProtoUpnpHttpReset(pProtoUpnp); + if ((iResult = ProtoHttpPost(pProtoUpnp->pProtoHttp, + pProtoUpnp->Device.Services[pProtoUpnp->iService].strControlUrl, + pProtoUpnp->strSendBuf, pProtoUpnp->iSendSize, + FALSE)) >= 0) + { + pProtoUpnp->iSentBytes = iResult; + } + else + { + NetPrintf(("protoupnp: %s request failed for service %s with result %d\n", pProtoUpnp->strRequestName, + pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType, iResult)); + pProtoUpnp->iHttpError = iResult; + pProtoUpnp->bRequestInProgress = FALSE; + pProtoUpnp->eState = ST_IDLE; + return; + } + + // verbose output? + #if DIRTYCODE_LOGGING + if (pProtoUpnp->bVerbose) + { + NetPrintWrap(pProtoUpnp->strSendBuf, 80); + } + #endif +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpSoapWaitResponse + + \Description + Poll for a soap response. If there is an error response, this function + parses it and stores the value internally. + + \Input *pProtoUpnp - pointer to module state + + \Version 03/30/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoUpnpSoapWaitResponse(ProtoUpnpRefT *pProtoUpnp) +{ + ProtoHttpResponseE eResponse; + int32_t iResult; + + // wait until http transaction completes + if ((iResult = _ProtoUpnpHttpWaitResponse(pProtoUpnp)) == 0) + { + return(iResult); + } + #if DIRTYCODE_DEBUG + else if (iResult == 2) + { + return(iResult); + } + #endif + + // check for error response + eResponse = ProtoHttpStatus(pProtoUpnp->pProtoHttp, 'code', NULL, 0); + + // if an error + if (eResponse != PROTOHTTP_RESPONSE_SUCCESSFUL) + { + // if it is a soap error, parse the response + if (eResponse == PROTOHTTP_RESPONSE_SERVERERROR) + { + const char *pXml, *pXml2; + + // find UPnPError + if ((pXml = XmlFind(pProtoUpnp->strRecvBuf, "%*:Envelope.%*:Body.%*:Fault.detail.UPnPError")) != NULL) + { + // get error code + if ((pXml2 = XmlFind(pXml, ".errorCode")) != NULL) + { + pProtoUpnp->iSoapError = XmlContentGetInteger(pXml2, 0); + } + + // get error description + #if DIRTYCODE_LOGGING + { + char strErrorText[96] = "unknown"; + if ((pXml2 = XmlFind(pXml, ".errorDescription")) != NULL) + { + XmlContentGetString(pXml2, strErrorText, sizeof(strErrorText), ""); + } + NetPrintf(("protoupnp: soap error %d (%s) in response to %s request\n", + pProtoUpnp->iSoapError, strErrorText, pProtoUpnp->strRequestName)); + } + #endif + } + } + + iResult = -1; + } + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpXmlGetAddress + + \Description + Get an address from XML field pName. + + \Input *pXml - pointer to current location in xml file + \Input *pName - name of field to get + \Input *pAddress - [out] output buffer to store address + + \Output + int32_t - negative=not found, else found + + \Version 10/10/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoUpnpXmlGetAddress(const char *pXml, const char *pName, int32_t *pAddress) +{ + if ((pXml = XmlFind(pXml, pName)) != NULL) + { + *pAddress = XmlContentGetAddress(pXml, 0); + return(0); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpXmlGetBoolean + + \Description + Get a boolean from XML field pName. + + \Input *pXml - pointer to current location in xml file + \Input *pName - name of field to get + \Input *pBoolean - [out] output buffer to store boolean + + \Output + int32_t - negative=not found, else found + + \Version 10/10/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoUpnpXmlGetBoolean(const char *pXml, const char *pName, uint8_t *pBoolean) +{ + if ((pXml = XmlFind(pXml, pName)) != NULL) + { + *pBoolean = (uint8_t)XmlContentGetInteger(pXml, 0); + return(0); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpXmlGetInteger + + \Description + Get an integer from XML field pName. + + \Input *pXml - pointer to current location in xml file + \Input *pName - name of field to get + \Input *pInteger - [out] output buffer to store integer + + \Output + int32_t - negative=not found, else found + + \Version 10/10/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoUpnpXmlGetInteger(const char *pXml, const char *pName, int32_t *pInteger) +{ + if ((pXml = XmlFind(pXml, pName)) != NULL) + { + *pInteger = XmlContentGetInteger(pXml, 0); + return(0); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpXmlGetString + + \Description + Get a string from XML field pName. + + \Input *pXml - pointer to current location in xml file + \Input *pName - name of field to get + \Input *pBuffer - [out] output buffer to store string + \Input iBufSize - size of output buffer + + \Output + int32_t - negative=not found, else found + + \Version 10/10/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoUpnpXmlGetString(const char *pXml, const char *pName, char *pBuffer, int32_t iBufSize) +{ + ds_memclr(pBuffer, iBufSize); + if ((pXml = XmlFind(pXml, pName)) != NULL) + { + return(XmlContentGetString(pXml, pBuffer, iBufSize, "")); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpXmlParseDescription + + \Description + Parse a device description response + + \Input *pProtoUpnp - pointer to module state + + \Output + int32_t - negative=failure, else success + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoUpnpXmlParseDescription(ProtoUpnpRefT *pProtoUpnp) +{ + char strContentA[256], strContentB[256], strContentC[256]; + ProtoUpnpDeviceT *pDevice = &pProtoUpnp->Device; + const char *pXml, *pXml2; + + // parse optional URLBase field if present + if ((pXml = XmlFind(pProtoUpnp->strRecvBuf, "root.URLBase")) != NULL) + { + // get urlbase string + int32_t iUrlBaseLen = XmlContentGetString(pXml, pProtoUpnp->Device.strUrlBase, sizeof(pProtoUpnp->Device.strUrlBase), ""); + if (iUrlBaseLen > 0) + { + // if urlbase ends in a trailing slash, trim it + if (pProtoUpnp->Device.strUrlBase[iUrlBaseLen-1] == '/') + { + NetPrintf(("protoupnp: trimming trailing slash from parsed URLBase string\n")); + pProtoUpnp->Device.strUrlBase[iUrlBaseLen-1] = '\0'; + } + NetPrintf(("protoupnp: parsed URLBase=%s\n", pProtoUpnp->Device.strUrlBase)); + } + else + { + NetPrintf(("protoupnp: unable to parse URLBase field\n")); + } + } + + // find a WANConnectionDevice + NetPrintf(("protoupnp: parsing xml response for a WANConnectionDevice:\n")); + for (pXml = XmlFind(pProtoUpnp->strRecvBuf, "root.device"); pXml != NULL; ) + { + // get deviceType + pXml2 = XmlFind(pXml, ".deviceType"); + + // get deviceType contents + XmlContentGetString(pXml2, strContentA, sizeof(strContentA), ""); + NetPrintf(("protoupnp: deviceType=%s\n", strContentA)); + + // is this the deviceType we are looking for? + if (ds_stristr(strContentA, "WANConnectionDevice")) + { + // get UDN + _ProtoUpnpXmlGetString(pXml2, "UDN", pDevice->strUdn, sizeof(pDevice->strUdn)); + // get manufacturer name + _ProtoUpnpXmlGetString(pXml2, "manufacturer", strContentA, sizeof(strContentA)); + // get model name + _ProtoUpnpXmlGetString(pXml2, "modelName", strContentB, sizeof(strContentB)); + // get model number (firmware rev) + _ProtoUpnpXmlGetString(pXml2, "modelNumber", strContentC, sizeof(strContentC)); + + // format model name + ds_snzprintf(pProtoUpnp->Device.strModel, sizeof(pProtoUpnp->Device.strModel), "%s %s %s", + strContentA, strContentB, strContentC); + NetPrintf(("protoupnp: device=%s UDN=%s\n", pProtoUpnp->Device.strModel, pDevice->strUdn)); + break; + } + + // first try to advance through recursion + if ((pXml2 = XmlFind(pXml, ".deviceList.device")) == NULL) + { + // if no child device lists, skip to next element + pXml2 = XmlSkip(pXml); + } + pXml = pXml2; + } + + // find any services associated with this device + NetPrintf(("protoupnp: parsing xml response for WANConnection Services:\n")); + for (pXml = XmlFind(pXml, ".serviceList.service"); pXml != NULL; pXml = XmlNext(pXml)) + { + // get serviceType header + if (_ProtoUpnpXmlGetString(pXml, ".serviceType", strContentB, sizeof(strContentB)) >= 0) + { + // get serviceType contents + NetPrintf(("protoupnp: serviceType=%s\n", strContentB)); + + // if it's not a serviceType we want, ignore it + if (!ds_stristr(strContentB, "connection")) + { + continue; + } + + // if we are out of space to store it, continue + if (pDevice->iNumServices >= PROTOUPNP_MAXSERVICES) + { + NetPrintf(("protoupnp: service list full, ignoring this entry\n")); + continue; + } + + // if this a serviceType we want, find the controlUrl + if (_ProtoUpnpXmlGetString(pXml, ".controlURL", strContentA, sizeof(strContentA)) >= 0) + { + ProtoUpnpServiceT *pService = &pDevice->Services[pDevice->iNumServices]; + + // create controlURL + _ProtoUpnpMakeFullUrl(&pProtoUpnp->Device, pService->strControlUrl, sizeof(pService->strControlUrl), strContentA); + NetPrintf(("protoupnp: controlURL=%s\n", pService->strControlUrl)); + + // create serviceURL + _ProtoUpnpXmlGetString(pXml, ".SCPDURL", strContentA, sizeof(strContentA)); + _ProtoUpnpMakeFullUrl(&pProtoUpnp->Device, pService->strDescriptionUrl, sizeof(pService->strDescriptionUrl), strContentA); + NetPrintf(("protoupnp: SCPDURL=%s\n", pService->strDescriptionUrl)); + + // save serviceType + ds_strnzcpy(pService->strServiceType, strContentB, sizeof(pService->strServiceType)); + NetPrintf(("protoupnp: serviceType=%s\n", pService->strServiceType)); + + pDevice->iNumServices += 1; + } + } + } + + return((pDevice->iNumServices > 0) ? 0 : -1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpXmlParseGetExtAddr + + \Description + Parse a service GetExternalIpAddress response + + \Input *pProtoUpnp - pointer to module state + + \Output + int32_t - negative=failure, else success + + \Version 03/25/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoUpnpXmlParseGetExtAddr(ProtoUpnpRefT *pProtoUpnp) +{ + const char *pXml; + + // parse response and look for external address + NetPrintf(("protoupnp: parsing xml response to GetExternalIPAddress request:\n")); + if ((pXml = XmlFind(pProtoUpnp->strRecvBuf, "%*:Envelope.%*:Body.%*:GetExternalIPAddressResponse")) != NULL) + { + // first, try to get contents ($$note - try this with a Netgear as it is different from the Linksys) + if ((pProtoUpnp->Device.uExternalAddress = XmlContentGetAddress(pXml, 0)) == 0) + { + // if that didn't work, try NewExternalIPAddress + _ProtoUpnpXmlGetAddress(pXml, ".NewExternalIPAddress", (int32_t *)&pProtoUpnp->Device.uExternalAddress); + } + // display parsed address + NetPrintf(("protoupnp: IPAddress=%a\n", pProtoUpnp->Device.uExternalAddress)); + } + + return((pProtoUpnp->Device.uExternalAddress != 0) ? 0 : -1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpXmlParseGetPortMapping + + \Description + Parse a service GetSpecificPortMappingEntry response + + \Input *pProtoUpnp - pointer to module state + + \Version 10/07/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoUpnpXmlParseGetPortMapping(ProtoUpnpRefT *pProtoUpnp) +{ + const char *pXml; + + // parse response and look for external address + NetPrintf(("protoupnp: parsing xml response to GetSpecificPortMappingEntry request:\n")); + if ((pXml = XmlFind(pProtoUpnp->strRecvBuf, "%*:Envelope.%*:Body.%*:GetSpecificPortMappingEntryResponse")) != NULL) + { + // parse addr + _ProtoUpnpXmlGetAddress(pXml, ".NewInternalClient", &pProtoUpnp->Device.CurPortMap.iAddr); + // parse port + _ProtoUpnpXmlGetInteger(pXml, ".NewInternalPort", &pProtoUpnp->Device.CurPortMap.iPort); + // parse enabled + _ProtoUpnpXmlGetBoolean(pXml, ".NewEnabled", &pProtoUpnp->Device.CurPortMap.bEnabled); + // parse description + _ProtoUpnpXmlGetString(pXml, ".NewPortMappingDescription", pProtoUpnp->Device.CurPortMap.strDesc, + sizeof(pProtoUpnp->Device.CurPortMap.strDesc)); + + // debug output + NetPrintf(("protoupnp: found port mapping ->%a:%d enabled=%s (%s)\n", + pProtoUpnp->Device.CurPortMap.iAddr, pProtoUpnp->Device.CurPortMap.iPort, + pProtoUpnp->Device.CurPortMap.bEnabled ? "true" : "false", + pProtoUpnp->Device.CurPortMap.strDesc)); + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoUpnpIdle + + \Description + NetConn idle function to update the ProtoUpnp module. + + \Input *pData - pointer to module state + \Input uTick - current tick count + + \Notes + This function is installed as a NetConn Idle function. NetConnIdle() + must be regularly polled for this function to be called. + + \Version 1.0 02/01/2008 (cadam) First Version +*/ +/********************************************************************************F*/ +static void _ProtoUpnpIdle(void *pData, uint32_t uTick) +{ + ProtoUpnpRefT *pRef = _ProtoUpnp_pRef; + + ProtoUpnpUpdate(pRef); +} + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ProtoUpnpCreate + + \Description + Create the ProtoUpnp module state. + + \Output + ProtoUpnpRefT * - pointer to module state, or NULL + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +ProtoUpnpRefT *ProtoUpnpCreate(void) +{ + ProtoUpnpRefT *pProtoUpnp; + int32_t iMemGroup, iResult; + void *pMemGroupUserData; + struct sockaddr BindAddr; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // if already created, just ref it + if (_ProtoUpnp_pRef != NULL) + { + _ProtoUpnp_pRef->iRefCount += 1; + return(_ProtoUpnp_pRef); + } + + // allocate and initialize module state + if ((pProtoUpnp = DirtyMemAlloc(sizeof(*pProtoUpnp), PROTOUPNP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + return(NULL); + } + ds_memclr(pProtoUpnp, sizeof(*pProtoUpnp)); + pProtoUpnp->iMemGroup = iMemGroup; + pProtoUpnp->pMemGroupUserData = pMemGroupUserData; + + // open udp socket + if ((pProtoUpnp->pUdp = SocketOpen(AF_INET, SOCK_DGRAM, 0)) == NULL) + { + NetPrintf(("protoupnp: unable to open udp socket\n")); + DirtyMemFree(pProtoUpnp, PROTOUPNP_MEMID, pProtoUpnp->iMemGroup, pProtoUpnp->pMemGroupUserData); + return(NULL); + } + // bind udp socket + SockaddrInit(&BindAddr, AF_INET); + if ((iResult = SocketBind(pProtoUpnp->pUdp, &BindAddr, sizeof(BindAddr))) == SOCKERR_NONE) + { + #if DIRTYCODE_LOGGING + SocketInfo(pProtoUpnp->pUdp, 'bind', 0, &BindAddr, sizeof(BindAddr)); + NetPrintf(("protoupnp: bound discovery socket to port %d\n", SockaddrInGetPort(&BindAddr))); + #endif + } + else + { + NetPrintf(("protoupnp: error %d binding discovery socket\n", iResult)); + SocketClose(pProtoUpnp->pUdp); + DirtyMemFree(pProtoUpnp, PROTOUPNP_MEMID, pProtoUpnp->iMemGroup, pProtoUpnp->pMemGroupUserData); + return(NULL); + } + + // allocate protohttp module + if ((pProtoUpnp->pProtoHttp = ProtoHttpCreate(1024)) == NULL) + { + NetPrintf(("protoupnp: unable to create protohttp module\n")); + SocketClose(pProtoUpnp->pUdp); + DirtyMemFree(pProtoUpnp, PROTOUPNP_MEMID, pProtoUpnp->iMemGroup, pProtoUpnp->pMemGroupUserData); + return(NULL); + } + + // setup critical section + NetCritInit(&pProtoUpnp->Crit, "protoupnp"); + + // set to quiet mode + ProtoHttpControl(pProtoUpnp->pProtoHttp, 'spam', FALSE, 0, NULL); + + // set up discovery sendto address + SockaddrInit(&pProtoUpnp->DiscoveryAddr, AF_INET); + SockaddrInSetAddr(&pProtoUpnp->DiscoveryAddr, PROTOUPNP_DISCOVERYADDR); + SockaddrInSetPort(&pProtoUpnp->DiscoveryAddr, PROTOUPNP_DISCOVERYPORT); + + // set initial state + pProtoUpnp->eState = ST_IDLE; + pProtoUpnp->iLeaseDuration = 4*60*60; // four hours + pProtoUpnp->iPortToMapExt = pProtoUpnp->iPortToMapInt = PROTOUPNP_DEFAULTPORTMAP; + pProtoUpnp->bEnableMapping = TRUE; + pProtoUpnp->iRemoteHost = -1; + + // add protoupnp task handle + NetConnIdleAdd(_ProtoUpnpIdle, pProtoUpnp); + + // save ref and return module pointer to caller + pProtoUpnp->iRefCount = 1; + _ProtoUpnp_pRef = pProtoUpnp; + + return(pProtoUpnp); +} + +/*F********************************************************************************/ +/*! + \Function ProtoUpnpGetRef + + \Description + Get ProtoUpnp reference + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +ProtoUpnpRefT *ProtoUpnpGetRef(void) +{ + return(_ProtoUpnp_pRef); +} + +/*F********************************************************************************/ +/*! + \Function ProtoUpnpDestroy + + \Description + Destroy the ProtoUpnp module. + + \Input *pProtoUpnp - pointer to module state + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoUpnpDestroy(ProtoUpnpRefT *pProtoUpnp) +{ + // if we aren't the last, just decrement the refcount and return + if (--pProtoUpnp->iRefCount > 0) + { + return; + } + + // destroy ProtoHttp module + ProtoHttpDestroy(pProtoUpnp->pProtoHttp); + + // close udp socket + SocketClose(pProtoUpnp->pUdp); + + // destroy critical section + NetCritKill(&pProtoUpnp->Crit); + + // del protoupnp task handle + NetConnIdleDel(_ProtoUpnpIdle, pProtoUpnp); + + // release module memory + DirtyMemFree(pProtoUpnp, PROTOUPNP_MEMID, pProtoUpnp->iMemGroup, pProtoUpnp->pMemGroupUserData); + + // clear ref + _ProtoUpnp_pRef = NULL; +} + +/*F********************************************************************************/ +/*! + \Function ProtoUpnpStatus + + \Description + Get information from the ProtoUpnp module. + + \Input *pProtoUpnp - pointer to module state + \Input iSelect - info selector + \Input *pBuf - [out] pointer to output buffer + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iSelect can be one of the following: + + \verbatim + 'body' - get most recent http response body + 'ctrl' - return control ident for current operation + 'disc' - TRUE if a UPnP device has been discovered, else FALSE + 'dnam' - device name ("manufacturer modelName modelNumber") + 'done' - returns TRUE if in IDLE state and no macro is currently being processed + 'durn' - device URN + 'extn' - device external (WAN) IP address + 'extp' - returns external port used in mapping operations + 'idle' - return if module is idle or not + 'intp' - returns internal port used in mapping operations + 'lerr' - most recent error result (http or soap) + 'macr' - return current macro being executed, or zero if none, + name copied to pBuf + 'rbdy' - get most recent htto request body + 'stat' - module status (PROTOUPNP_STATUS_*) + \endverbatim + + \Version 10/10/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoUpnpStatus(ProtoUpnpRefT *pProtoUpnp, int32_t iSelect, void *pBuf, int32_t iBufSize) +{ + // return most recent http body + if (iSelect == 'body') + { + ds_strnzcpy(pBuf, pProtoUpnp->strRecvBuf, iBufSize); + return(0); + } + // return control ident for current operation + if (iSelect == 'ctrl') + { + return(_ProtoUpnp_aStateMap[pProtoUpnp->eState]); + } + // have we discovered a device? + if (iSelect == 'disc') + { + return(pProtoUpnp->Device.bDiscovered); + } + // return device name + if (iSelect == 'dnam') + { + ds_strnzcpy(pBuf, pProtoUpnp->Device.strModel, iBufSize); + return(0); + } + if (iSelect == 'done') + { + return((pProtoUpnp->pCommandList == NULL) && (pProtoUpnp->eState == ST_IDLE)); + } + // return device urn + if (iSelect == 'durn') + { + ds_strnzcpy(pBuf, pProtoUpnp->Device.strUdn, iBufSize); + return(0); + } + // return device external (WAN) IP address + if (iSelect == 'extn') + { + return(pProtoUpnp->Device.uExternalAddress); + } + // return external port used in map operations + if (iSelect == 'extp') + { + return(pProtoUpnp->iPortToMapExt); + } + // return if idle or not + if (iSelect == 'idle') + { + return(pProtoUpnp->eState == ST_IDLE); + } + // set source port + if (iSelect == 'intp') + { + return(pProtoUpnp->iPortToMapInt); + } + // last error + if (iSelect == 'lerr') + { + if (pProtoUpnp->iSoapError != 0) + { + return(pProtoUpnp->iSoapError); + } + else if (pProtoUpnp->iHttpError != -1) + { + return(pProtoUpnp->iHttpError); + } + else + { + return(0); + } + } + // return current macro being executed + if (iSelect == 'macr') + { + int32_t iMacro = (pProtoUpnp->pCommandList != NULL) ? pProtoUpnp->pCommandList->iControl : 0; + if (pBuf != NULL) + { + ds_strnzcpy(pBuf, pProtoUpnp->strRequestName, iBufSize); + } + return(iMacro); + } + // most recent request body + if (iSelect == 'rbdy') + { + ds_strnzcpy(pBuf, pProtoUpnp->strSendBuf, iBufSize); + return(0); + } + // return module status + if (iSelect == 'stat') + { + return(pProtoUpnp->iStatus); + } + // try passing it to ProtoHttp + return(ProtoHttpStatus(pProtoUpnp->pProtoHttp, iSelect, pBuf, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoUpnpControl + + \Description + Control ProtoUpnp behavior + + \Input *pProtoUpnp - pointer to module state + \Input iControl - control selector + \Input iValue - selector specifc + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + 'abrt' - abort current operation + 'aprt' - start AddPortMapping request + 'desc' - initiate description + 'disc' - initiate discovery + 'dprt' - start DeletePortMapping request + 'extp' - set external port + 'gadr' - start GetExternalIPAddress request + 'gprt' - start GetSpecificPortMappingEntry request + 'host' - host address to allow traffic through firewall from + 'intp' - set internal port + 'ldur' - set lease duration + 'macr' - execute a macro. iValue=macro id, or pValue=pointer to user macro + 'addp' - macro to add a new port mapping + 'port' - set internal and external ports + 'spam' - enable/disable verbose output + \endverbatim + + \Version 05/24/2005 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoUpnpControl(ProtoUpnpRefT *pProtoUpnp, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue) +{ + char strAddrText[20]; + + // abort + if (iControl == 'abrt') + { + NetCritEnter(&pProtoUpnp->Crit); + pProtoUpnp->eState = ST_IDLE; + pProtoUpnp->bRequestInProgress = FALSE; + NetCritLeave(&pProtoUpnp->Crit); + return(0); + } + // set dest port + if (iControl == 'extp') + { + NetPrintf(("protoupnp: set external port to %d\n", iValue)); + pProtoUpnp->iPortToMapExt = iValue; + return(0); + } + // fake a command + #if DIRTYCODE_DEBUG + if (iControl == 'fake') + { + const int32_t _StateMap[] = { 'idle', 'disc', 'desc', 'sdsc', 'gvar', 'gext', 'gprt', 'dprt', 'aprt' }; + int32_t iState; + + NetCritEnter(&pProtoUpnp->Crit); + + // copy input into receive buffer + ds_strnzcpy(pProtoUpnp->strRecvBuf, pValue, sizeof(pProtoUpnp->strRecvBuf)); + + // fake the command + if (iValue == 'disc') + { + // parse the response & update status + NetPrintf(("protoupnp: faking 'disc' command\n")); + _ProtoUpnpParseDiscoveryResponse(pProtoUpnp, pProtoUpnp->strRecvBuf); + pProtoUpnp->iStatus |= PROTOUPNP_STATUS_DISCOVERED; + } + else + { + for (iState = 0; iState < ST_LAST; iState++) + { + if (_StateMap[iState] == iValue) + { + NetPrintf(("protoupnp: faking '%c%c%c%c' command\n", + (iValue>>24)&0xff, (iValue>>16)&0xff, (iValue>>8)&0xff,(iValue>>0)&0xff)); + pProtoUpnp->bFakeResponse = TRUE; + pProtoUpnp->eState = iState; + break; + } + } + } + + NetCritLeave(&pProtoUpnp->Crit); + return(0); + } + #endif + // set remote host + if (iControl == 'host') + { + NetPrintf(("protoupnp: set remote host to %a\n", iValue)); + pProtoUpnp->iRemoteHost = iValue; + return(0); + } + // set source port + if (iControl == 'intp') + { + NetPrintf(("protoupnp: set internal port to %d\n", iValue)); + pProtoUpnp->iPortToMapInt = iValue; + return(0); + } + // set lease duration + if (iControl == 'ldur') + { + NetPrintf(("protoupnp: set lease duration to %d\n", iValue)); + pProtoUpnp->iLeaseDuration = iValue; + return(0); + } + // set both internal and external ports + if (iControl == 'port') + { + NetPrintf(("protoupnp: setting internal and external port to %d\n", iValue)); + pProtoUpnp->iPortToMapInt = pProtoUpnp->iPortToMapExt = iValue; + return(0); + } + // set verbose + if (iControl == 'spam') + { + ProtoHttpControl(pProtoUpnp->pProtoHttp, iControl, iValue, 0, NULL); + pProtoUpnp->bVerbose = iValue; + return(0); + } + + /* + The following control functions can only be initiated in IDLE state + */ + + if ((pProtoUpnp->eState != ST_IDLE) || (pProtoUpnp->bRequestInProgress)) + { + NetPrintf(("protoupnp: '%c%c%c%c' only valid in IDLE state\n", + (iControl>>24)&0xff, (iControl>>16)&0xff, (iControl>>8)&0xff, iControl&0xff)); + return(-1); + } + + // execute a macro + if (iControl == 'macr') + { + const ProtoUpnpMacroT *pMacro; + + // is it a built-in, set up command list to execute + if (iValue == 'dscg') + { + pProtoUpnp->pCommandList = _ProtoUpnp_cmd_dscg; + } + else if (iValue == 'dscp') + { + pProtoUpnp->pCommandList = _ProtoUpnp_cmd_dscp; + } + else if (iValue == 'addp') + { + pProtoUpnp->pCommandList = _ProtoUpnp_cmd_addp; + } + else if (iValue == 'upnp') + { + pProtoUpnp->pCommandList = _ProtoUpnp_cmd_upnp; + } + else if (iValue == 'test') + { + pProtoUpnp->pCommandList = _ProtoUpnp_cmd_test; + } + else // user-specified macro + { + pProtoUpnp->pCommandList = pValue; + } + // start macro + pMacro = pProtoUpnp->pCommandList; + NetPrintf(("protoupnp: command list start -> %c%c%c%c\n", + (pMacro->iControl>>24)&0xff, (pMacro->iControl>>16)&0xff, + (pMacro->iControl>>8)&0xff, pMacro->iControl&0xff)); + ProtoUpnpControl(pProtoUpnp, pMacro->iControl, pMacro->iValue, pMacro->iValue2, pMacro->pValue); + return(0); + } + + // start discovery + if (iControl == 'disc') + { + // reset module + _ProtoUpnpReset(pProtoUpnp); + + NetPrintf(("protoupnp: searching for upnp devices\n")); + + // set to discovery state + pProtoUpnp->eState = ST_DISCOVERY; + return(0); + } + + // only allow subsequent operations if we've discovered a device + if (pProtoUpnp->Device.bDiscovered != TRUE) + { + NetPrintf(("protoupnp: '%c%c%c%c' only valid if a device has been discovered\n", + (iControl>>24)&0xff, (iControl>>16)&0xff, (iControl>>8)&0xff, iControl&0xff)); + return(-1); + } + + // start description + if (iControl == 'desc') + { + // initiate description fetch operation + NetPrintf(("protoupnp: sending description request\n")); + ds_strnzcpy(pProtoUpnp->strRequestName, "Description", sizeof(pProtoUpnp->strRequestName)); + _ProtoUpnpHttpReset(pProtoUpnp); + ProtoHttpGet(pProtoUpnp->pProtoHttp, pProtoUpnp->Device.strUrl, FALSE); + + // set to description state + pProtoUpnp->eState = ST_DESCRIPTION; + return(0); + } + + // only allow subsequent operations if we've described a device + if (pProtoUpnp->Device.iNumServices == 0) + { + NetPrintf(("protoupnp: '%c%c%c%c' only valid if a device has been described\n", + (iControl>>24)&0xff, (iControl>>16)&0xff, (iControl>>8)&0xff, iControl&0xff)); + return(-1); + } + + // start addportmapping request + if (iControl == 'aprt') + { + // if we haven't already, acquire client address + if (pProtoUpnp->iClientAddr == 0) + { + // get local address + pProtoUpnp->iClientAddr = NetConnStatus('addr', 0, NULL, 0); + } + + // if a mapping doesn't exist, go ahead and map it + if (!pProtoUpnp->bPortIsMapped) + { + // get remote host address + _ProtoUpnpGetRemoteHost(pProtoUpnp, strAddrText, sizeof(strAddrText)); + + // post AddPortMapping request + NetPrintf(("protoupnp: addportmap %s:%d->%a:%d duration=%d enabled=%s\n", strAddrText, pProtoUpnp->iPortToMapExt, pProtoUpnp->iClientAddr, + pProtoUpnp->iPortToMapInt, pProtoUpnp->iLeaseDuration, pProtoUpnp->bEnableMapping ? "true" : "false")); + + // format the request + _ProtoUpnpSoapRequestOpen(pProtoUpnp, "AddPortMapping"); + _ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewRemoteHost", strAddrText); + _ProtoUpnpSoapRequestAddUI2(pProtoUpnp, "NewExternalPort", pProtoUpnp->iPortToMapExt); + _ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewProtocol", "UDP"); + _ProtoUpnpSoapRequestAddUI2(pProtoUpnp, "NewInternalPort", pProtoUpnp->iPortToMapInt); + _ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewInternalClient", SocketInAddrGetText(pProtoUpnp->iClientAddr, strAddrText, sizeof(strAddrText))); + _ProtoUpnpSoapRequestAddBln(pProtoUpnp, "NewEnabled", pProtoUpnp->bEnableMapping); + _ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewPortMappingDescription", _ProtoUpnp_strDescription); + _ProtoUpnpSoapRequestAddUI4(pProtoUpnp, "NewLeaseDuration", pProtoUpnp->iLeaseDuration); + _ProtoUpnpSoapRequestClose(pProtoUpnp); + _ProtoUpnpSoapRequestPost(pProtoUpnp); + + // set to poll for response + pProtoUpnp->eState = ST_ADDPORTMAPPING; + } + else + { + #if PROTOUPNP_REUSEMAPPING + // do we already have the mapping we want? + if ((pProtoUpnp->Device.CurPortMap.iAddr == pProtoUpnp->iClientAddr) && + (pProtoUpnp->Device.CurPortMap.iPort == pProtoUpnp->iPortToMapInt) && + (pProtoUpnp->Device.CurPortMap.bEnabled == pProtoUpnp->bEnableMapping) && + (!strcmp(pProtoUpnp->Device.CurPortMap.strDesc, _ProtoUpnp_strDescription))) + { + NetPrintf(("protoupnp: reusing already existing port mapping\n")); + } + else + #endif + { + // we have to delete the current mapping before we can add a new one + ProtoUpnpControl(pProtoUpnp, 'dprt', 0, 0, NULL); + + // if this is a command-list command, don't consume it + if (pProtoUpnp->pCommandList != NULL) + { + pProtoUpnp->pCommandList -= 1; + } + } + } + return(0); + } + // start delportmapping request + if (iControl == 'dprt') + { + // get remote host address + _ProtoUpnpGetRemoteHost(pProtoUpnp, strAddrText, sizeof(strAddrText)); + + // post DeletePortMapping request + _ProtoUpnpSoapRequestOpen(pProtoUpnp, "DeletePortMapping"); + _ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewRemoteHost", strAddrText); + _ProtoUpnpSoapRequestAddUI2(pProtoUpnp, "NewExternalPort", pProtoUpnp->iPortToMapExt); + _ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewProtocol", "UDP"); + _ProtoUpnpSoapRequestClose(pProtoUpnp); + _ProtoUpnpSoapRequestPost(pProtoUpnp); + + // set to poll for response + pProtoUpnp->eState = ST_DELPORTMAPPING; + return(0); + } + // start getexternalipaddress request + if (iControl == 'gadr') + { + // post GetExternalIPAddress request + _ProtoUpnpSoapRequestOpen(pProtoUpnp, "GetExternalIPAddress"); + _ProtoUpnpSoapRequestClose(pProtoUpnp); + _ProtoUpnpSoapRequestPost(pProtoUpnp); + + // set to getextaddr state + pProtoUpnp->eState = ST_GETEXTADDR; + return(0); + } + // start getgenericportmapping request + if (iControl == 'ggpt') + { + // post GetSpecificPortMappingEntry request + _ProtoUpnpSoapRequestOpen(pProtoUpnp, "GetGenericPortMappingEntry"); + _ProtoUpnpSoapRequestAddUI2(pProtoUpnp, "NewPortMappingIndex", iValue); + _ProtoUpnpSoapRequestClose(pProtoUpnp); + _ProtoUpnpSoapRequestPost(pProtoUpnp); + + // set to poll for response + pProtoUpnp->eState = ST_GETPORTMAPPING; + return(0); + } + // start getspecificportmapping request + if (iControl == 'gprt') + { + // get remote host address + _ProtoUpnpGetRemoteHost(pProtoUpnp, strAddrText, sizeof(strAddrText)); + + // post GetSpecificPortMappingEntry request + _ProtoUpnpSoapRequestOpen(pProtoUpnp, "GetSpecificPortMappingEntry"); + _ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewRemoteHost", strAddrText); + _ProtoUpnpSoapRequestAddUI2(pProtoUpnp, "NewExternalPort", pProtoUpnp->iPortToMapExt); + _ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewProtocol", "UDP"); + _ProtoUpnpSoapRequestClose(pProtoUpnp); + _ProtoUpnpSoapRequestPost(pProtoUpnp); + + // set to poll for response + pProtoUpnp->eState = ST_GETPORTMAPPING; + return(0); + } + // start QueryStateVariable request + if (iControl == 'gvar') + { + // post QueryStateVariable request + _ProtoUpnpSoapRequestOpen(pProtoUpnp, "QueryStateVariable"); + _ProtoUpnpSoapRequestAddStr(pProtoUpnp, "varName", pValue); + _ProtoUpnpSoapRequestClose(pProtoUpnp); + _ProtoUpnpSoapRequestPost(pProtoUpnp); + + // set to poll for response + pProtoUpnp->eState = ST_GETSTATEVAR; + return(0); + } + // get service description + if (iControl == 'sdsc') + { + // initiate description fetch operation + NetPrintf(("protoupnp: sending service description request\n")); + ds_strnzcpy(pProtoUpnp->strRequestName, "ServiceDesc", sizeof(pProtoUpnp->strRequestName)); + _ProtoUpnpHttpReset(pProtoUpnp); + ProtoHttpGet(pProtoUpnp->pProtoHttp, pProtoUpnp->Device.Services[pProtoUpnp->iService].strDescriptionUrl, FALSE); + + // set to description state + pProtoUpnp->eState = ST_SERVICEDESC; + return(0); + } + // unhandled + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function ProtoUpnpUpdate + + \Description + Update the ProtoUpnp module. + + \Input *pProtoUpnp - pointer to module state + + \Version 03/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoUpnpUpdate(ProtoUpnpRefT *pProtoUpnp) +{ + int32_t iCurTick = NetTick(); + int32_t iResult; + + NetCritEnter(&pProtoUpnp->Crit); + + // update in idle state + if (pProtoUpnp->eState == ST_IDLE) + { + // update command list + if (pProtoUpnp->pCommandList != NULL) + { + // get next command + const ProtoUpnpMacroT *pMacro = ++pProtoUpnp->pCommandList; + + // process command + if (pMacro->iControl != 0) + { + NetPrintf(("protoupnp: command list -> %c%c%c%c\n", + (pMacro->iControl>>24)&0xff, (pMacro->iControl>>16)&0xff, + (pMacro->iControl>>8)&0xff, pMacro->iControl&0xff)); + ProtoUpnpControl(pProtoUpnp, pMacro->iControl, pMacro->iValue, pMacro->iValue2, pMacro->pValue); + } + else + { + NetPrintf(("protoupnp: command list complete\n")); + pProtoUpnp->pCommandList = NULL; + } + } + } + + // update during discovery process + if (pProtoUpnp->eState == ST_DISCOVERY) + { + struct sockaddr RemoteAddr; + int32_t iResponseSize, iAddrLen = sizeof(RemoteAddr); + + // send out a discovery broadcast? + if (NetTickDiff(iCurTick, pProtoUpnp->iDiscoveryTimer) >= PROTOUPNP_DISCOVERYINTERVAL) + { + // send discovery request + _ProtoUpnpSendDiscoveryRequest(pProtoUpnp); + + // set timer for next broadcast + pProtoUpnp->iDiscoveryTimer = iCurTick; + } + + // poll for discovery response + if ((iResponseSize = SocketRecvfrom(pProtoUpnp->pUdp, pProtoUpnp->strRecvBuf, SOCKET_MAXUDPRECV, 0, &RemoteAddr, &iAddrLen)) > 0) + { + // add null-termination + pProtoUpnp->strRecvBuf[iResponseSize] = '\0'; + + // parse the response + _ProtoUpnpParseDiscoveryResponse(pProtoUpnp, pProtoUpnp->strRecvBuf); + + // update status + pProtoUpnp->iStatus |= PROTOUPNP_STATUS_DISCOVERED; + } + } + + // update during description process + if (pProtoUpnp->eState == ST_DESCRIPTION) + { + // check for response + if ((iResult = _ProtoUpnpHttpWaitResponse(pProtoUpnp)) > 0) + { + // parse the response + if (_ProtoUpnpXmlParseDescription(pProtoUpnp) >= 0) + { + pProtoUpnp->iStatus |= PROTOUPNP_STATUS_DESCRIBED; + } + else + { + _ProtoUpnpError(pProtoUpnp, "parsing description"); + } + } + else if (iResult < 0) + { + _ProtoUpnpError(pProtoUpnp, "getting description"); + } + } + + // update during service description process + if (pProtoUpnp->eState == ST_SERVICEDESC) + { + // check for response + if ((iResult = _ProtoUpnpHttpWaitResponse(pProtoUpnp)) > 0) + { + // $$ todo - handle service description response + } + else if (iResult < 0) + { + _ProtoUpnpError(pProtoUpnp, "getting service description"); + } + } + + // update during QueryStateVariable request + if (pProtoUpnp->eState == ST_GETSTATEVAR) + { + // check for response + if ((iResult = _ProtoUpnpHttpWaitResponse(pProtoUpnp)) > 0) + { + // $$ todo - handle querystatevariable response + } + else if (iResult < 0) + { + _ProtoUpnpError(pProtoUpnp, "querying state variable"); + } + } + + // update during GetExternalIPAddress request + if (pProtoUpnp->eState == ST_GETEXTADDR) + { + // check for response + if ((iResult = _ProtoUpnpSoapWaitResponse(pProtoUpnp)) > 0) + { + // parse the response + if (_ProtoUpnpXmlParseGetExtAddr(pProtoUpnp) >= 0) + { + pProtoUpnp->iStatus |= PROTOUPNP_STATUS_GOTEXTADDR; + } + else + { + // didn't find it, so check the next service + if (pProtoUpnp->iService < (pProtoUpnp->Device.iNumServices-1)) + { + pProtoUpnp->iService += 1; + ProtoUpnpControl(pProtoUpnp, 'gadr', 0, 0, NULL); + } + } + } + else if (iResult < 0) + { + _ProtoUpnpError(pProtoUpnp, "getting external address"); + } + } + + // update during GetSpecificPortMappingEntry request + if (pProtoUpnp->eState == ST_GETPORTMAPPING) + { + // check for response + if ((iResult = _ProtoUpnpSoapWaitResponse(pProtoUpnp)) != 0) + { + // if we got a success response, that means a mapping already exists + if (iResult > 0) + { + NetPrintf(("protoupnp: mapping already exists\n")); + _ProtoUpnpXmlParseGetPortMapping(pProtoUpnp); + pProtoUpnp->iStatus |= PROTOUPNP_STATUS_FNDPORTMAP; + pProtoUpnp->bPortIsMapped = TRUE; + } + else if (pProtoUpnp->iSoapError == 714) + { + NetPrintf(("protoupnp: mapping does not exist\n")); + pProtoUpnp->bPortIsMapped = FALSE; + } + else if (pProtoUpnp->iSoapError == 501) + { + // not supported? try continuing on anyway (BEFSR41) + NetPrintf(("protoupnp: unable to query mapping\n")); + pProtoUpnp->bPortIsMapped = FALSE; + } + else // some other error, so bail + { + _ProtoUpnpError(pProtoUpnp, "getting portmapping"); + } + } + } + + // update during DeletePortMapping request + if (pProtoUpnp->eState == ST_DELPORTMAPPING) + { + // check for response + if ((iResult = _ProtoUpnpSoapWaitResponse(pProtoUpnp)) != 0) + { + // if we got a success response, that means we've successfully deleted the mapping + if (iResult > 0) + { + NetPrintf(("protoupnp: deleted port mapping\n")); + pProtoUpnp->iStatus |= PROTOUPNP_STATUS_DELPORTMAP; + pProtoUpnp->bPortIsMapped = FALSE; + } + else if (pProtoUpnp->iSoapError == 714) + { + NetPrintf(("protoupnp: mapping does not exist\n")); + pProtoUpnp->bPortIsMapped = FALSE; + } + else // some other error, so bail + { + _ProtoUpnpError(pProtoUpnp, "deleting port mapping"); + } + } + } + + // update during AddPortMapping request + if (pProtoUpnp->eState == ST_ADDPORTMAPPING) + { + // check for response + if ((iResult = _ProtoUpnpSoapWaitResponse(pProtoUpnp)) != 0) + { + // if we got a success response, that means the mapping succeeded + if (iResult > 0) + { + pProtoUpnp->iStatus |= PROTOUPNP_STATUS_ADDPORTMAP; + NetPrintf(("protoupnp: port mapping added\n")); + } + else + { + uint8_t bRetry = TRUE; + if (pProtoUpnp->iSoapError == 716) + { + NetPrintf(("protoupnp: wildcard not permitted in ext port\n")); + bRetry = FALSE; + } + else if (pProtoUpnp->iSoapError == 725) + { + NetPrintf(("protoupnp: finite lease duration not supported -- trying again with infinite\n")); + pProtoUpnp->iLeaseDuration = 0; + } + else if (pProtoUpnp->iSoapError == 726) + { + NetPrintf(("protoupnp: specific remote host not supported -- trying again with wildcard\n")); + pProtoUpnp->iRemoteHost = 0; + } + else if (pProtoUpnp->iLeaseDuration != 0) + { + NetPrintf(("protoupnp: unknown error adding port mapping -- trying again with infinite lease duration\n")); + pProtoUpnp->iLeaseDuration = 0; + } + else if (pProtoUpnp->iRemoteHost != 0) + { + NetPrintf(("protoupnp: unknown error adding port mapping -- trying again with wildcard source IP\n")); + pProtoUpnp->iRemoteHost = 0; + } + else + { + // nothing else to try, so just fail + bRetry = FALSE; + } + + // retry or fail? + if (bRetry) + { + ProtoUpnpControl(pProtoUpnp, 'aprt', 0, 0, NULL); + } + else + { + _ProtoUpnpError(pProtoUpnp, "adding port mapping"); + } + } + } + } + + NetCritLeave(&pProtoUpnp->Crit); +} diff --git a/src/thirdparty/dirtysdk/source/proto/protowebsocket.c b/src/thirdparty/dirtysdk/source/proto/protowebsocket.c new file mode 100644 index 00000000..b90cc6db --- /dev/null +++ b/src/thirdparty/dirtysdk/source/proto/protowebsocket.c @@ -0,0 +1,2051 @@ +/*H********************************************************************************/ +/*! + \File protowebsocket.c + + \Description + This module implements a WebSocket client interface as documented by + RFC 6455 (http://tools.ietf.org/html/rfc6455). The current version of the + WebSockets protocol implemented here is version 13. Up to date registry + information regarding new extensions, protocols, result codes, etc. can be + found at http://www.iana.org/assignments/websocket/websocket.xml. + + \Notes + While the standard supports up to a 64-bit frame size, this implementation + only supports up to 32-bit frame sizes. + + \Todo + - Sec-WebSocket-Protocols support + + \Copyright + Copyright (c) 2012 Electronic Arts Inc. + + \Version 11/26/2012 (jbrookes) First Version + \Version 03/31/2017 (jbrookes) Added frame send/recv capability +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtyvers.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/crypt/cryptrand.h" +#include "DirtySDK/crypt/cryptsha1.h" +#include "DirtySDK/proto/protohttp.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/util/base64.h" + +#include "DirtySDK/proto/protowebsocket.h" + +/*** Defines **********************************************************************/ + +//! ProtoWebSocket revision number (maj.min) +#define PROTOWEBSOCKET_VERSION (0x0100) // update this for major bug fixes or protocol additions/changes + +//! default ProtoWebSocket timeout +#define PROTOWEBSOCKET_TIMEOUT (5*30*1000) + +//! define WebSocket protocol version +#define PROTOWEBSOCKET_PROTOCOLVERS (13) + +//! default protowebsocket buffer size +#define PROTOWEBSOCKET_BUFSIZE_MIN (4*1024) + +// opcode definitions +#define PROTOWEBSOCKET_OPCODE_BASE (0x0) +#define PROTOWEBSOCKET_OPCODE_CONT (PROTOWEBSOCKET_OPCODE_BASE+0) +#define PROTOWEBSOCKET_OPCODE_TEXT (PROTOWEBSOCKET_OPCODE_BASE+1) +#define PROTOWEBSOCKET_OPCODE_BINA (PROTOWEBSOCKET_OPCODE_BASE+2) +// control frame type definitions +#define PROTOWEBSOCKET_CTRL_BASE (0x8) +#define PROTOWEBSOCKET_CTRL_CLSE (PROTOWEBSOCKET_CTRL_BASE+0) +#define PROTOWEBSOCKET_CTRL_PING (PROTOWEBSOCKET_CTRL_BASE+1) +#define PROTOWEBSOCKET_CTRL_PONG (PROTOWEBSOCKET_CTRL_BASE+2) +// fragment end +#define PROTOWEBSOCKET_OPCODE_FIN (0x80) + +// max WebSocket protocol send header size +#define PROTOWEBSOCKET_MAXSENDHDR (12) + +//! default size for our temporary input buffer +#define PROTOWEBSOCKET_DEFAULT_INPUTBUFSIZE (256) + +/*** Type Definitions *************************************************************/ + +// state for a websocket connection +struct ProtoWebSocketRefT +{ + int32_t iMemGroup; + void *pMemGroupUserData; + + ProtoSSLRefT *pProtoSSL; + + enum + { + ST_DISC, //!< disconnected + ST_CONN, //!< connecting + ST_SEND, //!< sending handshake + ST_RECV, //!< receiving handshake + ST_OPEN, //!< connected + ST_FAIL //!< connection failed + } eState; //!< current state + + int32_t iVersion; //!< protocol version + + char *pOutBuf; //!< output buffer + int32_t iOutMax; //!< max amount of data that can be stored in the buffer + int32_t iOutLen; //!< current amount of data in buffer + int32_t iOutOff; //!< current offset within buffer + int32_t iRecvRslt; + int32_t iRecvSize; //!< used by RecvFrame to track total received size + + char *pInpBuf; //!< temporary input buffer used for connection establishment + int32_t iInpMax; //!< max amount of data that can be stored in the buffer + int32_t iInpLen; //!< current amount of data in buffer + int32_t iInpOff; //!< current offset within buffer + + int32_t iFrameHdrOff; + int32_t iFrameHdrLen; + int32_t iFrameLen; + int32_t iFrameType; + + int32_t iCtrlDataLen; //!< length of control packet data + int32_t iCtrlDataOff; //!< offset while receiving control packet data + + int32_t iVerbose; + uint32_t uTimer; + int32_t iTimeout; + int32_t iKeepAlive; //!< if >0, number of seconds before issuing a keep-alive pong + uint32_t uKeepTimer; + int32_t iSSLFail; + int32_t iCloseReason; + + char *pAppendHdr; //!< append header buffer pointer + int32_t iAppendLen; //!< size of append header buffer + + int32_t iPort; //!< current connection port + + uint8_t bTimeout; + uint8_t bDoPong; //!< execute a 'pong' frame at earliest opportunity + uint8_t bDoClose; //!< execute a 'close' frame at earliest opportunity; disconnect + uint8_t bClosing; //!< have sent a close frame + uint8_t bRecvCtrlData; //!< currently receiving control data + uint8_t bMessageSend; //!< if true, we're sending a message (FIN bit not sent until message is complete) + uint8_t bFrameFrag; //!< if true, we're sending a fragmented frame + uint8_t bFrameFin; //!< if true this packet is a fin packet + + char strHost[256]; //!< server name + char strFrameHdr[PROTOWEBSOCKET_MAXSENDHDR]; //!< cache to receive frame header into + + char strRawKey[32]; //!< generated key sent to server; must hold 16 characters base64 encoded + char strEncKey[32]; //!< encoded key we expect to get back from server; must hold 16 characters base64 encoded + + char strCtrlData[256]; //!< cached control packet data +}; + +/*** Variables ********************************************************************/ + +#if DIRTYCODE_LOGGING +static const char *_ProtoWebSocket_strOpcodeNames[16] = +{ + "cont", + "text", + "bina", + "", "", "", "", "", + "clse", + "ping", + "pong", + "", "", "", "", "" +}; +static const char *_ProtoWebSocket_strCloseReasons[PROTOWEBSOCKET_CLOSEREASON_COUNT] = +{ + "normal", + "going away", + "protocol error", + "unsupported data", + "reserved", + "no status received", + "abnormal closure", + "invalid frame data", + "policy violation", + "message too big", + "mandatory extension", + "internal error", + "service restart", + "try again later", + "reserved", + "tls handshake", +}; +#endif + +/*** Function Prototypes **********************************************************/ + +static int32_t _ProtoWebSocketRecvData(ProtoWebSocketRefT *pWebSocket, char *pStrBuf, int32_t iSize); + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketSetAppendHeader + + \Description + Set given string as append header, allocating memory as required. + + \Input *pWebSocket - module state + \Input *pAppendHdr - append header string + + \Output + int32_t - zero=success, else error + + \Version 12/04/2012 (jbrookes) Copied from ProtoHttp +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketSetAppendHeader(ProtoWebSocketRefT *pWebSocket, const char *pAppendHdr) +{ + int32_t iAppendBufLen, iAppendStrLen; + + // check for empty append string, in which case we free the buffer + if ((pAppendHdr == NULL) || (*pAppendHdr == '\0')) + { + if (pWebSocket->pAppendHdr != NULL) + { + DirtyMemFree(pWebSocket->pAppendHdr, PROTOHTTP_MEMID, pWebSocket->iMemGroup, pWebSocket->pMemGroupUserData); + pWebSocket->pAppendHdr = NULL; + } + pWebSocket->iAppendLen = 0; + return(0); + } + + // check to see if append header is already set + if ((pWebSocket->pAppendHdr != NULL) && (!strcmp(pAppendHdr, pWebSocket->pAppendHdr))) + { + NetPrintfVerbose((pWebSocket->iVerbose, 1, "protowebsocket: [%p] ignoring set of append header '%s' that is already set\n", pWebSocket, pAppendHdr)); + return(0); + } + + // get append header length + iAppendStrLen = (int32_t)strlen(pAppendHdr); + // append buffer size includes null and space for \r\n if not included by submitter + iAppendBufLen = iAppendStrLen + 3; + + // see if we need to allocate a new buffer + if (iAppendBufLen > pWebSocket->iAppendLen) + { + if (pWebSocket->pAppendHdr != NULL) + { + DirtyMemFree(pWebSocket->pAppendHdr, PROTOHTTP_MEMID, pWebSocket->iMemGroup, pWebSocket->pMemGroupUserData); + } + if ((pWebSocket->pAppendHdr = DirtyMemAlloc(iAppendBufLen, PROTOHTTP_MEMID, pWebSocket->iMemGroup, pWebSocket->pMemGroupUserData)) != NULL) + { + pWebSocket->iAppendLen = iAppendBufLen; + } + else + { + NetPrintf(("protowebsocket: [%p] could not allocate %d byte buffer for append header\n", pWebSocket, iAppendBufLen)); + pWebSocket->iAppendLen = 0; + return(-1); + } + } + + // copy append header + ds_strnzcpy(pWebSocket->pAppendHdr, pAppendHdr, iAppendStrLen+1); + + // if append header is not \r\n terminated, do it here + if (pAppendHdr[iAppendStrLen-2] != '\r' || pAppendHdr[iAppendStrLen-1] != '\n') + { + ds_strnzcat(pWebSocket->pAppendHdr, "\r\n", pWebSocket->iAppendLen); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketGenerateKey + + \Description + Generate random base64-encoded key to be sent to server in initial + handshake, and base64-encoded SHA1 hash of the key that we expect + from the server in response. + + \Input *pWebSocket - module state + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoWebSocketGenerateKey(ProtoWebSocketRefT *pWebSocket) +{ + char strAccept[128]; + uint8_t aSha1Data[CRYPTSHA1_HASHSIZE]; + CryptSha1T Sha1State; + uint8_t aRandom[16]; + + // get 16-digit random number + CryptRandGet(aRandom, sizeof(aRandom)); + + // base64 encode string + Base64Encode2((char *)aRandom, sizeof(aRandom), pWebSocket->strRawKey, sizeof(pWebSocket->strRawKey)); + + // now, generate accept header we must receive to validate response + + // concatentate key with hardcoded GUID as per spec + ds_strnzcpy(strAccept, pWebSocket->strRawKey, sizeof(strAccept)); + ds_strnzcat(strAccept, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", sizeof(strAccept)); + + // hash the key with SHA1 + CryptSha1Init(&Sha1State); + CryptSha1Update(&Sha1State, (uint8_t *)strAccept, (uint32_t)strlen(strAccept)); + CryptSha1Final(&Sha1State, aSha1Data, CRYPTSHA1_HASHSIZE); + + // base64 encode for final accept key + Base64Encode2((char *)aSha1Data, CRYPTSHA1_HASHSIZE, pWebSocket->strEncKey, sizeof(pWebSocket->strEncKey)); + + NetPrintfVerbose((pWebSocket->iVerbose, 2, "protowebsocket: [%p] raw key=%s\n", pWebSocket, pWebSocket->strRawKey)); + NetPrintfVerbose((pWebSocket->iVerbose, 2, "protowebsocket: [%p] enc key=%s\n", pWebSocket, pWebSocket->strEncKey)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketFormatHeader + + \Description + Format websocket handshake header + + \Input *pWebSocket - module state + \Input *pHost - websocket host + \Input *pUrl - websocket url + \Input iPort - protocol port + \Input iSecure - true=secure, else false + + \Output + int32_t - positive=success, negative=failure + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketFormatHeader(ProtoWebSocketRefT *pWebSocket, const char *pHost, const char *pUrl, int32_t iPort, int32_t iSecure) +{ + int32_t iBufSize = pWebSocket->iOutMax, iBufLen = 0; + + // if no url specified use the minimum + if (*pUrl == '\0') + { + pUrl = "/"; + } + + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "GET %s HTTP/1.1\r\n", pUrl); + if ((iSecure && (iPort == 443)) || (iPort == 80)) + { + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "Host: %s\r\n", pHost); + } + else + { + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "Host: %s:%d\r\n", pHost, iPort); + } + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "Upgrade: websocket\r\n"); + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "Connection: upgrade\r\n"); + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "Sec-WebSocket-Key: %s\r\n", pWebSocket->strRawKey); + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "Sec-WebSocket-Version: %d\r\n", pWebSocket->iVersion); + if ((pWebSocket->pAppendHdr == NULL) || !ds_stristr(pWebSocket->pAppendHdr, "User-Agent:")) + { + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "User-Agent: ProtoWebSocket %d.%d/DS %d.%d.%d.%d.%d (" DIRTYCODE_PLATNAME ")\r\n", + (PROTOWEBSOCKET_VERSION>>8)&0xff, PROTOWEBSOCKET_VERSION&0xff, DIRTYSDK_VERSION_YEAR, DIRTYSDK_VERSION_SEASON, + DIRTYSDK_VERSION_MAJOR, DIRTYSDK_VERSION_MINOR, DIRTYSDK_VERSION_PATCH); + } + if ((pWebSocket->pAppendHdr == NULL) || (pWebSocket->pAppendHdr[0] == '\0')) + { + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "Accept: */*\r\n"); + } + else + { + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "%s", pWebSocket->pAppendHdr); + } + iBufLen += ds_snzprintf(pWebSocket->pOutBuf+iBufLen, iBufSize-iBufLen, "\r\n"); + pWebSocket->iOutLen = iBufLen; + pWebSocket->iOutOff = 0; + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketDisconnect + + \Description + Disconnect from server + + \Input *pWebSocket - module state + \Input iNewState - new state to set (fail or disc) + + \Version 11/30/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketDisconnect(ProtoWebSocketRefT *pWebSocket, int32_t iNewState) +{ + NetPrintfVerbose((pWebSocket->iVerbose, 0, "protowebsocket: [%p] disconnecting\n", pWebSocket)); + pWebSocket->eState = iNewState; + if (iNewState == ST_FAIL) + { + pWebSocket->iSSLFail = ProtoSSLStat(pWebSocket->pProtoSSL, 'fail', NULL, 0); + } + return(ProtoSSLDisconnect(pWebSocket->pProtoSSL)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketReset + + \Description + Reset module state + + \Input *pWebSocket - module state + + \Version 11/30/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoWebSocketReset(ProtoWebSocketRefT *pWebSocket) +{ + // if we're not already disconnected, disconnect now + if (pWebSocket->eState != ST_DISC && pWebSocket->eState != ST_FAIL) + { + _ProtoWebSocketDisconnect(pWebSocket, ST_DISC); + } + + // reset state + pWebSocket->iOutLen = 0; + pWebSocket->iOutOff = 0; + pWebSocket->iInpLen = 0; + pWebSocket->iInpOff = 0; + pWebSocket->iRecvRslt = 0; + pWebSocket->iRecvSize = 0; + pWebSocket->iFrameHdrOff = 0; + pWebSocket->iFrameHdrLen = 2; // minimal header size + pWebSocket->iFrameLen = 0; + pWebSocket->iFrameType = 0; + pWebSocket->iCtrlDataLen = 0; + pWebSocket->iSSLFail = 0; + pWebSocket->bTimeout = FALSE; + pWebSocket->bDoPong = FALSE; + pWebSocket->bDoClose = FALSE; + pWebSocket->bClosing = FALSE; + pWebSocket->bFrameFrag = FALSE; + pWebSocket->bFrameFin = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketFail + + \Description + Handle a socket failure + + \Input *pWebSocket - module state + \Input iResult - result from a socket operation + \Input *pStrDebug - debug text indicating failure context + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoWebSocketFail(ProtoWebSocketRefT *pWebSocket, int32_t iResult, const char *pStrDebug) +{ + if (pWebSocket->eState == ST_FAIL) + { + return; + } + NetPrintf(("protowebsocket: [%p] failure: %s (err=%d)\n", pWebSocket, pStrDebug, iResult)); + _ProtoWebSocketDisconnect(pWebSocket, ST_FAIL); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketValidateHeader + + \Description + Validate server response header + + \Input *pWebSocket - module state + \Input *pBuffer - buffer holding received header + \Input *pHeader - name of header to validate + \Input *pValue - header value to validate + + \Output + int32_t - zero=success, negative=failure + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketValidateHeader(ProtoWebSocketRefT *pWebSocket, const char *pBuffer, const char *pHeader, const char *pValue) +{ + char strDebug[256]; + char strHdrVal[128]; + + if (ProtoHttpGetHeaderValue(NULL, pBuffer, pHeader, strHdrVal, sizeof(strHdrVal), NULL) < 0) + { + ds_snzprintf(strDebug, sizeof(strDebug), "missing %s header", pHeader); + _ProtoWebSocketFail(pWebSocket, -1, strDebug); + return(-1); + } + else if (ds_stricmp(strHdrVal, pValue)) + { + ds_snzprintf(strDebug, sizeof(strDebug), "invalid %s header value '%s'", pHeader, pValue); + _ProtoWebSocketFail(pWebSocket, -1, strDebug); + return(-1); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketProcessHeader + + \Description + Process websocket handshake server response + + \Input *pWebSocket - module state + \Input *pBuffer - buffer holding received header + + \Output + int32_t - positive=success, negative=failure + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketProcessHeader(ProtoWebSocketRefT *pWebSocket, const char *pBuffer) +{ + int32_t iHdrCode; + + NetPrintfVerbose((pWebSocket->iVerbose, 1, "protowebsocket: [%p] handshake response received\n", pWebSocket)); + #if DIRTYCODE_LOGGING + if (pWebSocket->iVerbose > 2) + { + NetPrintWrap(pBuffer, 80); + } + #endif + + /* as per https://www.w3.org/TR/websockets/ 4.8: When the user agent validates the server's response during + the "establish a WebSocket connection" algorithm, if the status code received from the server is not 101 + (e.g. it is a redirect), the user agent must fail the WebSocket connection. */ + + // validate 101 response + iHdrCode = ProtoHttpParseHeaderCode(pBuffer); + if (iHdrCode != 101) + { + _ProtoWebSocketFail(pWebSocket, iHdrCode, "invalid http response code"); + return(-1); + } + // validate "upgrade: websocket" header + if (_ProtoWebSocketValidateHeader(pWebSocket, pBuffer, "upgrade", "websocket") < 0) + { + return(-2); + } + // validate "connection: upgrade" header + if (_ProtoWebSocketValidateHeader(pWebSocket, pBuffer, "connection", "upgrade") < 0) + { + return(-3); + } + // validate accept header + if (_ProtoWebSocketValidateHeader(pWebSocket, pBuffer, "sec-websocket-accept", pWebSocket->strEncKey) < 0) + { + return(-4); + } + + //$$TODO - support Sec-WebSocket-Extensions? + //$$TODO - support Sec-WebSocket-Protocol? + + pWebSocket->eState = ST_OPEN; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketSend + + \Description + Try and send some data. If data is sent, the timout value is updated. + + \Input *pWebSocket - module state + \Input *pStrBuf - pointer to buffer to send from + \Input iSize - amount of data to try and send + + \Output + int32_t - negative=error, else number of bytes sent + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketSend(ProtoWebSocketRefT *pWebSocket, const char *pStrBuf, int32_t iSize) +{ + int32_t iResult; + + // try and send some data + if ((iResult = ProtoSSLSend(pWebSocket->pProtoSSL, pStrBuf, iSize)) > 0) + { + NetPrintfVerbose((pWebSocket->iVerbose, 1, "protowebsocket: [%p] sent %d bytes\n", pWebSocket, iResult)); + #if DIRTYCODE_LOGGING + if (pWebSocket->iVerbose > 3) + { + NetPrintMem(pStrBuf, iResult, "ws-send"); + } + #endif + + // sent data, so update timer + pWebSocket->uTimer = NetTick(); + } + else if (iResult < 0) + { + NetPrintf(("protowebsocket: [%p] error %d sending %d bytes\n", pWebSocket, iResult, iSize)); + pWebSocket->eState = ST_FAIL; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketSendBuff + + \Description + Send buffered data + + \Input *pWebSocket - module state + + \Output + int32_t - zero=in progress, positive=done, negative=failure + + \Version 11/29/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketSendBuff(ProtoWebSocketRefT *pWebSocket) +{ + int32_t iResult; + if ((iResult = _ProtoWebSocketSend(pWebSocket, pWebSocket->pOutBuf+pWebSocket->iOutOff, pWebSocket->iOutLen-pWebSocket->iOutOff)) > 0) + { + pWebSocket->iOutOff += iResult; + if (pWebSocket->iOutOff == pWebSocket->iOutLen) + { + iResult = pWebSocket->iOutLen; + pWebSocket->iOutLen = pWebSocket->iOutOff = 0; + } + else + { + iResult = 0; + } + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketSetLength + + \Description + Endode message length in the format websocket protocol reqires + + \Input *pWebSocket - module state + \Input *pBuffer - buffer to encode to + \Input iMask - true if data is masked, else false + \Input iLength - length to set + + \Output + uint8_t * - pointer past end of encoded length + + \Notes + While the standard supports up to a 64-bit frame size, this implementation + only supports up to 32-bit frame sizes. + + \Version 11/29/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t *_ProtoWebSocketSetLength(ProtoWebSocketRefT *pWebSocket, uint8_t *pBuffer, int32_t iMask, int32_t iLength) +{ + iMask = iMask ? 0x80 : 0; + if (iLength < 126) + { + *pBuffer++ = (uint8_t)iLength | (uint8_t)iMask; + } + else if (iLength < 65536) + { + *pBuffer++ = (uint8_t)126 | (uint8_t)iMask; + *pBuffer++ = (uint8_t)(iLength >> 8); + *pBuffer++ = (uint8_t)(iLength >> 0); + } + else + { + *pBuffer++ = (uint8_t)127 | (uint8_t)iMask; + *pBuffer++ = 0; + *pBuffer++ = 0; + *pBuffer++ = 0; + *pBuffer++ = 0; + *pBuffer++ = (uint8_t)(iLength >> 24); + *pBuffer++ = (uint8_t)(iLength >> 16); + *pBuffer++ = (uint8_t)(iLength >> 8); + *pBuffer++ = (uint8_t)(iLength >> 0); + } + return(pBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketSetMask + + \Description + Generate and encode mask; required for sending data from client to server + + \Input *pWebSocket - module state + \Input *pBuffer - [out] buffer to write mask to + \Input **ppMask - [out] storage for pointer to mask + + \Output + uint8_t * - pointer past end of encoded mask + + \Version 11/29/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static uint8_t *_ProtoWebSocketSetMask(ProtoWebSocketRefT *pWebSocket, uint8_t *pBuffer, uint8_t **ppMask) +{ + // generate four-byte random mask + CryptRandGet(pBuffer, 4); + // save pointer to mask for caller + *ppMask = pBuffer; + // print mask for extended diagnostics + NetPrintfVerbose((pWebSocket->iVerbose, 2, "protowebsocket: [%p] mask=0x%02x%02x%02x%02x\n", pWebSocket, + pBuffer[0], pBuffer[1], pBuffer[2], pBuffer[3])); + return(pBuffer+4); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketSendData + + \Description + Send masked data from client to server. + + \Input *pWebSocket - module state + \Input *pBuffer - data to send + \Input iLength - length of data to send + \Input uOpcode - frame type + + \Output + int32_t - number of data bytes sent + + \Version 11/29/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketSendData(ProtoWebSocketRefT *pWebSocket, const char *pBuffer, int32_t iLength, uint8_t uOpcode) +{ + uint8_t *pPacket = (uint8_t *)pWebSocket->pOutBuf, *pMask; + int32_t iBufIdx, iOutMax, iResult; + uint8_t uFrameFin = PROTOWEBSOCKET_OPCODE_FIN; + + // only allow if there's not a send in progress + if (pWebSocket->iOutLen != 0) + { + return(0); + } + + // opcode is only set for the first packet of a fragmented message + if (pWebSocket->bMessageSend && pWebSocket->bFrameFrag) + { + uOpcode = 0; + } + + // calculate available space, while reserving max header size + iOutMax = pWebSocket->iOutMax - PROTOWEBSOCKET_MAXSENDHDR; + if (iLength > iOutMax) + { + // for message sending, clear FIN bit when it isn't the final frame + if (pWebSocket->bMessageSend) + { + uFrameFin = 0; + pWebSocket->bFrameFrag = TRUE; + } + // constrain send length to max available space + iLength = iOutMax; + } + else if (pWebSocket->bMessageSend) + { + // reset fragmentation flag on final frame of message + pWebSocket->bFrameFrag = FALSE; + } + + // set packet opcode + *pPacket++ = uOpcode|uFrameFin; + // set packet length + pPacket = _ProtoWebSocketSetLength(pWebSocket, pPacket, 1, iLength); + // set packet mask + pPacket = _ProtoWebSocketSetMask(pWebSocket, pPacket, &pMask); + + // mask the data + for (iBufIdx = 0; iBufIdx < iLength; iBufIdx += 1) + { + *pPacket++ = *pBuffer++ ^ pMask[iBufIdx&0x3]; + } + + // set buffer parms + pWebSocket->iOutOff = 0; + pWebSocket->iOutLen = (int32_t)(pPacket-(uint8_t *)pWebSocket->pOutBuf); + + // try to send + if ((iResult = _ProtoWebSocketSendBuff(pWebSocket)) < 0) + { + return(iResult); + } + + #if DIRTYCODE_LOGGING + if (iResult > 0) + { + NetPrintfVerbose((pWebSocket->iVerbose, 1, "protowebsocket: [%p] sent frame fin=%d typ=%s len=%d\n", pWebSocket, ((uint8_t *)pWebSocket->pOutBuf)[0]>>7, + _ProtoWebSocket_strOpcodeNames[((uint8_t *)pWebSocket->pOutBuf)[0]&0xf], iResult)); + } + #endif + + + // return amount of data buffered + return(iLength); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketSendUserData + + \Description + Send masked data from client to server making, wrapping the state error + checking + + \Input *pWebSocket - module state + \Input *pBuffer - data to send + \Input iLength - length of data to send + \Input uOpcode - frame type + + \Output + int32_t - number of data bytes sent + + \Version 08/30/2017 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketSendUserData(ProtoWebSocketRefT *pWebSocket, const char *pBuffer, int32_t iLength, uint8_t uOpcode) +{ + // handle not connected states + if ((pWebSocket->eState == ST_DISC) || (pWebSocket->eState == ST_FAIL)) + { + return(SOCKERR_NOTCONN); + } + if (pWebSocket->eState != ST_OPEN) + { + return(0); + } + return(_ProtoWebSocketSendData(pWebSocket, pBuffer, iLength, uOpcode)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketSendClose + + \Description + Send a close notification to server (or queue it if we are in the middle + of another send). + + \Input *pWebSocket - module state + \Input iReason - (optional) close reason (PROTOWEBSOCKET_CLOSEREASON_*) + \Input *pStrReason - (optional) string close reason + + \Output + int32_t - number of bytes data sent + + \Version 11/30/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketSendClose(ProtoWebSocketRefT *pWebSocket, int32_t iReason, const char *pStrReason) +{ + int32_t iDataLen, iResult; + + // if we've already sent a close frame, we don't want to send another + if (pWebSocket->bClosing) + { + return(0); + } + pWebSocket->bClosing = TRUE; + + // format reason data + if (iReason != 0) + { + iDataLen = 2; + pWebSocket->strCtrlData[0] = (iReason>>8)&0xff; + pWebSocket->strCtrlData[1] = iReason&0xff; + if (pStrReason != NULL) + { + ds_strnzcpy(pWebSocket->strCtrlData+2, pStrReason, sizeof(pWebSocket->strCtrlData)-2); + iDataLen += (int32_t)strlen(pWebSocket->strCtrlData+2); + } + } + else + { + pWebSocket->strCtrlData[0] = '\0'; + iDataLen = 0; + } + + // try immediate send + NetPrintfVerbose((pWebSocket->iVerbose, 0, "protowebsocket: [%p] closing connection (code=%d, text=%s)\n", pWebSocket, iReason, pStrReason)); + if ((iResult = _ProtoWebSocketSendData(pWebSocket, pWebSocket->strCtrlData, iDataLen, PROTOWEBSOCKET_CTRL_CLSE)) == 0) + { + // couldn't buffer for sending, so set up to try again + pWebSocket->bDoClose = TRUE; + pWebSocket->iCtrlDataLen = iDataLen; + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketSendPing + + \Description + Send a ping notification to server -- unlike close, we don't queue for + later sending if we can't send immediately. + + \Input *pWebSocket - module state + \Input *pStrData - (optional) string ping data + \Input iType - PING or PONG + + \Output + int32_t - number of bytes data sent + + \Version 11/30/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketSendPing(ProtoWebSocketRefT *pWebSocket, const char *pStrData, int32_t iType) +{ + int32_t iDataLen = 0; + if (pStrData != NULL) + { + iDataLen = (int32_t)strlen(pStrData); + } + return(_ProtoWebSocketSendData(pWebSocket, pStrData, iDataLen, iType)); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketRecv + + \Description + Try and receive some data. If data is received, the timout value is + updated. + + \Input *pWebSocket - module state + \Input *pStrBuf - [out] pointer to buffer to receive into + \Input iSize - amount of data to try and receive + + \Output + int32_t - negative=error, else success + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketRecv(ProtoWebSocketRefT *pWebSocket, char *pStrBuf, int32_t iSize) +{ + // if we have no buffer space, don't try to receive + if (iSize == 0) + { + return(0); + } + + /* if we are receiving when in the RECV state or if we have no data already waiting + pull from the TCP buffer. when we are in the RECV state we need to pull into the temp + input buffer, hence why we need the state check */ + if ((pWebSocket->eState == ST_RECV) || (pWebSocket->iInpLen == 0)) + { + pWebSocket->iRecvRslt = ProtoSSLRecv(pWebSocket->pProtoSSL, pStrBuf, iSize); + } + // otherwise pull from the temp input buffer + else + { + iSize = DS_MIN(iSize, pWebSocket->iInpLen-pWebSocket->iInpOff); + ds_memcpy(pStrBuf, pWebSocket->pInpBuf+pWebSocket->iInpOff, iSize); + pWebSocket->iInpOff += iSize; + + // if we read the entire buffer, reset the variables + if (pWebSocket->iInpLen == pWebSocket->iInpOff) + { + pWebSocket->iInpLen = pWebSocket->iInpOff = 0; + } + pWebSocket->iRecvRslt = iSize; + } + + // handle the result + if (pWebSocket->iRecvRslt > 0) + { + NetPrintfVerbose((pWebSocket->iVerbose, 1, "protowebsocket: [%p] recv %d bytes\n", pWebSocket, pWebSocket->iRecvRslt)); + #if DIRTYCODE_LOGGING + if (pWebSocket->iVerbose > 3) + { + NetPrintMem(pStrBuf, pWebSocket->iRecvRslt, "ws-recv"); + } + #endif + + // received data, so update timer + pWebSocket->uTimer = NetTick(); + } + return(pWebSocket->iRecvRslt); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketCheckCtrl + + \Description + Check a control frame received from the server + + \Input *pWebSocket - module state + \Input *pStrBuf - pointer to buffer containing received data from server + \Input iSize - amount of data to received from the server + + \Version 11/29/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoWebSocketCheckCtrl(ProtoWebSocketRefT *pWebSocket, char *pStrBuf, int32_t iSize) +{ + if (pWebSocket->iFrameType == PROTOWEBSOCKET_CTRL_PING) + { + NetPrintfVerbose((pWebSocket->iVerbose, 1, "protowebsocket: [%p] got a ping with %d bytes of data\n", pWebSocket, iSize)); + // set to respond with a pong + pWebSocket->bDoPong = TRUE; + } + else if (pWebSocket->iFrameType == PROTOWEBSOCKET_CTRL_PONG) + { + NetPrintfVerbose((pWebSocket->iVerbose, 1, "protowebsocket: [%p] got a pong with %d bytes of data\n", pWebSocket, iSize)); + } + else if (pWebSocket->iFrameType == PROTOWEBSOCKET_CTRL_CLSE) + { + int32_t iCloseReason = ((uint8_t)pStrBuf[0]) << 8 | (uint8_t)pStrBuf[1]; + if ((iCloseReason >= PROTOWEBSOCKET_CLOSEREASON_BASE) && (iCloseReason <= PROTOWEBSOCKET_CLOSEREASON_MAX)) + { + NetPrintfVerbose((pWebSocket->iVerbose, 0, "protowebsocket: [%p] got a close with reason '%s' (len=%d)\n", pWebSocket, + _ProtoWebSocket_strCloseReasons[iCloseReason-PROTOWEBSOCKET_CLOSEREASON_BASE], iSize)); + pWebSocket->iCloseReason = iCloseReason; + } + else + { + NetPrintfVerbose((pWebSocket->iVerbose, 0, "protowebsocket: [%p] got a close with unknown reason %d (len=%d)\n", pWebSocket, + iCloseReason, iSize)); + pWebSocket->iCloseReason = PROTOWEBSOCKET_CLOSEREASON_UNKNOWN; + } + // disconnect + _ProtoWebSocketDisconnect(pWebSocket, ST_DISC); + } + else + { + NetPrintf(("protowebsocket: [%p] ignoring unknown control type %d (len=%d)\n", pWebSocket, pWebSocket->iFrameType, iSize)); + // ignore unknown frame type data? + } + + // done with control data, reset frame header info; this is only necessary for control messages with no associated data + pWebSocket->iFrameHdrOff = 0; + pWebSocket->iFrameHdrLen = 2; // minimum header size + pWebSocket->bFrameFin = FALSE; +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketCheckHeader + + \Description + Check a server->client header for completion + + \Input *pWebSocket - module state + \Input *pStrBuf - pointer to buffer containing received data from server + \Input iSize - amount of data received from the server + + \Output + int32_t - zero=in progress, else complete + + \Notes + While the standard supports up to a 64-bit frame size, this implementation + only supports up to 32-bit frame sizes. + + \Version 01/09/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketCheckHeader(ProtoWebSocketRefT *pWebSocket, char *pStrBuf, int32_t iSize) +{ + // check for minimum frame size + if (iSize < 2) + { + return(0); + } + // decode frame info + pWebSocket->bFrameFin = ((uint8_t *)pStrBuf)[0] >> 7; + pWebSocket->iFrameType = pStrBuf[0]&0x0f; + pWebSocket->iFrameLen = pStrBuf[1]; + // handle extended frame lengths + if (pWebSocket->iFrameLen == 126) + { + pWebSocket->iFrameHdrLen = 4; + } + else if (pWebSocket->iFrameLen == 127) + { + pWebSocket->iFrameHdrLen = 10; + } + // check extended header size + if (iSize < pWebSocket->iFrameHdrLen) + { + return(0); + } + // decode extended frame lengths + if (pWebSocket->iFrameLen == 126) + { + pWebSocket->iFrameLen = (((uint8_t *)pStrBuf)[2] << 8) | ((uint8_t *)pStrBuf)[3]; + } + else if (pWebSocket->iFrameLen == 127) + { + pWebSocket->iFrameLen = (((uint8_t *)pStrBuf)[6] << 24) | (((uint8_t *)pStrBuf)[7] << 16) | (((uint8_t *)pStrBuf)[8] << 8) | ((uint8_t *)pStrBuf)[9]; + } + // if it's a control frame, set up to process it + if (pWebSocket->iFrameType & PROTOWEBSOCKET_CTRL_BASE) + { + pWebSocket->iCtrlDataLen = pWebSocket->iFrameLen; + pWebSocket->iCtrlDataOff = 0; + } + return(1); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketRecvHeader + + \Description + Receive a server->client frame header. As headers are variable length + and we receive data directly into the user-supplied buffer, we process + the header separately from the data. + + \Input *pWebSocket - module state + + \Output + int32_t - negative=error, zero=in progress, positive=done + + \Version 01/09/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketRecvHeader(ProtoWebSocketRefT *pWebSocket) +{ + int32_t iResult; + + // receive first two header bytes + while ((iResult = _ProtoWebSocketRecv(pWebSocket, pWebSocket->strFrameHdr+pWebSocket->iFrameHdrOff, pWebSocket->iFrameHdrLen-pWebSocket->iFrameHdrOff)) > 0) + { + // accumulate result + pWebSocket->iFrameHdrOff += iResult; + // see if we have a valid header + if ((iResult = _ProtoWebSocketCheckHeader(pWebSocket, pWebSocket->strFrameHdr, pWebSocket->iFrameHdrOff)) > 0) + { + break; + } + } + #if DIRTYCODE_LOGGING + if (iResult > 0) + { + NetPrintfVerbose((pWebSocket->iVerbose, 1, "protowebsocket: [%p] recv frame fin=%d typ=%s len=%d\n", pWebSocket, ((uint8_t *)pWebSocket->strFrameHdr)[0]>>7, + _ProtoWebSocket_strOpcodeNames[pWebSocket->iFrameType], pWebSocket->iFrameLen)); + } + #endif + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketRecvCtrl + + \Description + Receive a control packet into internal buffer. Any data that doesn't + fit in the internal buffer is discarded. + + \Input *pWebSocket - module state + + \Output + int32_t - zero + + \Version 02/01/2013 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketRecvCtrl(ProtoWebSocketRefT *pWebSocket) +{ + char strTempBuf[256], *pStrBuf; + int32_t iSize; + + // receive into control data cache buffer (if we have enough room) + if (pWebSocket->iCtrlDataOff < (signed)sizeof(pWebSocket->strCtrlData)) + { + pStrBuf = pWebSocket->strCtrlData + pWebSocket->iCtrlDataOff; + iSize = (signed)sizeof(pWebSocket->strCtrlData) - pWebSocket->iCtrlDataOff; + } + else // no room, so just receive into stack buffer and throw out + { + pStrBuf = strTempBuf; + iSize = (signed)sizeof(strTempBuf); + } + pWebSocket->bRecvCtrlData = TRUE; + pWebSocket->iCtrlDataOff += _ProtoWebSocketRecvData(pWebSocket, pStrBuf, iSize); + pWebSocket->bRecvCtrlData = FALSE; + + // process if we've received the whole control frame + if ((pWebSocket->iCtrlDataOff == pWebSocket->iCtrlDataLen) && (pStrBuf != strTempBuf)) + { + _ProtoWebSocketCheckCtrl(pWebSocket, pStrBuf, pWebSocket->iCtrlDataLen); + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketRecvData + + \Description + Try and receive some data. + + \Input *pWebSocket - module state + \Input *pStrBuf - [out] pointer to buffer to receive into + \Input iSize - amount of data to try and receive + + \Output + int32_t - negative=error, else success + + \Version 11/27/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _ProtoWebSocketRecvData(ProtoWebSocketRefT *pWebSocket, char *pStrBuf, int32_t iSize) +{ + int32_t iResult; + + // receive header + if ((pWebSocket->iFrameLen == 0) && ((iResult = _ProtoWebSocketRecvHeader(pWebSocket)) <= 0)) + { + return(iResult); + } + + // receive control frame data + if ((pWebSocket->iFrameType & PROTOWEBSOCKET_CTRL_BASE) && (!pWebSocket->bRecvCtrlData)) + { + return(_ProtoWebSocketRecvCtrl(pWebSocket)); + } + + // limit read maximum to frame len + if (iSize > pWebSocket->iFrameLen) + { + iSize = pWebSocket->iFrameLen; + } + + // receive some data + if ((iResult = _ProtoWebSocketRecv(pWebSocket, pStrBuf, iSize)) > 0) + { + // decrement frame size by amount of user data received + pWebSocket->iFrameLen -= iResult; + // if we've received all of the data, reset header info + if (pWebSocket->iFrameLen == 0) + { + pWebSocket->iFrameHdrOff = 0; + pWebSocket->iFrameHdrLen = 2; // minimum header size + } + } + else if (iResult < 0) + { + _ProtoWebSocketFail(pWebSocket, iResult, "receiving data"); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketProcessSendPong + + \Description + Process a pending pong request + + \Input *pWebSocket - module state + + \Version 11/30/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoWebSocketProcessSendPong(ProtoWebSocketRefT *pWebSocket) +{ + int32_t iResult; + if ((pWebSocket->bDoPong != TRUE) || (pWebSocket->iOutLen > 0)) + { + return; + } + if ((iResult = _ProtoWebSocketSendPing(pWebSocket, pWebSocket->strCtrlData, PROTOWEBSOCKET_CTRL_PONG)) >= 0) + { + pWebSocket->bDoPong = FALSE; + } + else if (iResult < 0) + { + _ProtoWebSocketFail(pWebSocket, iResult, "sending pong"); + pWebSocket->bDoPong = FALSE; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketProcessSendClose + + \Description + Process a pending close request. + + \Input *pWebSocket - module state + + \Version 11/30/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoWebSocketProcessSendClose(ProtoWebSocketRefT *pWebSocket) +{ + int32_t iResult; + if ((pWebSocket->bDoClose != TRUE) || (pWebSocket->iOutLen > 0)) + { + return; + } + if ((iResult = _ProtoWebSocketSendData(pWebSocket, pWebSocket->strCtrlData, pWebSocket->iCtrlDataLen, PROTOWEBSOCKET_CTRL_CLSE)) == 0) + { + pWebSocket->bDoClose = FALSE; + } + else if (iResult < 0) + { + _ProtoWebSocketFail(pWebSocket, iResult, "sending close"); + pWebSocket->bDoClose = FALSE; + } +} + +/*F********************************************************************************/ +/*! + \Function _ProtoWebSocketProcessSend + + \Description + Process an in-progress send from the output buffer. + + \Input *pWebSocket - module state + + \Version 11/30/2012 (jbrookes) +*/ +/********************************************************************************F*/ +static void _ProtoWebSocketProcessSend(ProtoWebSocketRefT *pWebSocket) +{ + int32_t iResult; + if (pWebSocket->iOutLen == 0) + { + return; + } + if ((iResult = _ProtoWebSocketSendBuff(pWebSocket)) < 0) + { + _ProtoWebSocketFail(pWebSocket, iResult, "sending data"); + } +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketCreate + + \Description + Allocate a WebSocket connection and prepare for use + + \Input iBufSize - websocket buffer size (determines max message size) + + \Output + ProtoWebSocketRefT * - module state, or NULL on failure + + \Version 11/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +ProtoWebSocketRefT *ProtoWebSocketCreate(int32_t iBufSize) +{ + ProtoWebSocketRefT *pWebSocket; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // clamp the buffer size + if (iBufSize < PROTOWEBSOCKET_BUFSIZE_MIN) + { + iBufSize = PROTOWEBSOCKET_BUFSIZE_MIN; + } + iBufSize += PROTOWEBSOCKET_MAXSENDHDR; + + // allocate and init module state + if ((pWebSocket = DirtyMemAlloc(sizeof(*pWebSocket), PROTOWEBSOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protowebsocket: could not allocate module state\n")); + return(NULL); + } + ds_memclr(pWebSocket, sizeof(*pWebSocket)); + pWebSocket->iMemGroup = iMemGroup; + pWebSocket->pMemGroupUserData = pMemGroupUserData; + pWebSocket->iTimeout = PROTOWEBSOCKET_TIMEOUT; + pWebSocket->iVersion = PROTOWEBSOCKET_PROTOCOLVERS; + pWebSocket->iVerbose = 1; + + // allocate ssl module + if ((pWebSocket->pProtoSSL = ProtoSSLCreate()) == NULL) + { + NetPrintf(("protowebsocket: [%p] unable to allocate ssl module\n", pWebSocket)); + ProtoWebSocketDestroy(pWebSocket); + return(NULL); + } + // allocate websocket buffer + if ((pWebSocket->pOutBuf = DirtyMemAlloc(iBufSize, PROTOWEBSOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protowebsocket: [%p] unable to allocate websocket buffer\n", pWebSocket)); + ProtoWebSocketDestroy(pWebSocket); + return(NULL); + } + pWebSocket->iOutMax = iBufSize; + + // allocate temp input buffer + pWebSocket->iInpMax = PROTOWEBSOCKET_DEFAULT_INPUTBUFSIZE; + if ((pWebSocket->pInpBuf = (char *)DirtyMemAlloc(pWebSocket->iInpMax, PROTOWEBSOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("protowebsocket: [%p] unable to allocate temporary input buffer\n", pWebSocket)); + ProtoWebSocketDestroy(pWebSocket); + return(NULL); + } + + // return module state to caller + return(pWebSocket); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketCreate2 + + \Description + Allocate a WebSocket connection and prepare for use: ProtoSSL function + signature compatible version. + + \Output + ProtoWebSocketRefT * - module state, or NULL on failure + + \Version 03/08/2013 (jbrookes) +*/ +/********************************************************************************F*/ +ProtoWebSocketRefT *ProtoWebSocketCreate2(void) +{ + return(ProtoWebSocketCreate(PROTOWEBSOCKET_BUFSIZE_MIN)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketDestroy + + \Description + Destroy a connection and release state + + \Input *pWebSocket - websocket module state + + \Version 11/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoWebSocketDestroy(ProtoWebSocketRefT *pWebSocket) +{ + if (pWebSocket->pAppendHdr != NULL) + { + DirtyMemFree(pWebSocket->pAppendHdr, PROTOWEBSOCKET_MEMID, pWebSocket->iMemGroup, pWebSocket->pMemGroupUserData); + } + if (pWebSocket->pInpBuf != NULL) + { + DirtyMemFree(pWebSocket->pInpBuf, PROTOWEBSOCKET_MEMID, pWebSocket->iMemGroup, pWebSocket->pMemGroupUserData); + } + if (pWebSocket->pOutBuf != NULL) + { + DirtyMemFree(pWebSocket->pOutBuf, PROTOWEBSOCKET_MEMID, pWebSocket->iMemGroup, pWebSocket->pMemGroupUserData); + } + if (pWebSocket->pProtoSSL != NULL) + { + ProtoSSLDestroy(pWebSocket->pProtoSSL); + } + DirtyMemFree(pWebSocket, PROTOWEBSOCKET_MEMID, pWebSocket->iMemGroup, pWebSocket->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketConnect + + \Description + Initiate a connection to a server + + \Input *pWebSocket - websocket module state + \Input *pUrl - url containing host information; ws:// or wss:// + + \Output + int32_t - zero=success, else failure + + \Version 11/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoWebSocketConnect(ProtoWebSocketRefT *pWebSocket, const char *pUrl) +{ + int32_t iSecure, iResult; + char strKind[16]; + + // reset module state + _ProtoWebSocketReset(pWebSocket); + + // parse out the url + pUrl = ProtoHttpUrlParse(pUrl, strKind, sizeof(strKind), pWebSocket->strHost, sizeof(pWebSocket->strHost), &pWebSocket->iPort, &iSecure); + + // do our own secure determination + iSecure = ds_stricmp(strKind, "wss") ? 0 : 1; + if (iSecure && (pWebSocket->iPort == 80)) + { + pWebSocket->iPort = 443; + } + + // create key we send and encded key we must validate in server response + _ProtoWebSocketGenerateKey(pWebSocket); + + // format the request header for sending once connected + _ProtoWebSocketFormatHeader(pWebSocket, pWebSocket->strHost, pUrl, pWebSocket->iPort, iSecure); + + // start the connect + NetPrintfVerbose((pWebSocket->iVerbose, 0, "protowebsocket: [%p] connecting to %s:%d\n", pWebSocket, pWebSocket->strHost, pWebSocket->iPort)); + iResult = ProtoSSLConnect(pWebSocket->pProtoSSL, iSecure, pWebSocket->strHost, 0, pWebSocket->iPort); + pWebSocket->eState = (iResult == SOCKERR_NONE) ? ST_CONN : ST_FAIL; + pWebSocket->uTimer = NetTick(); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketDisconnect + + \Description + Disconnect from the server. Note that this is an asynchronous operation. + Connection status can be polled with the 'conn' status selector + + \Input *pWebSocket - websocket module state + + \Output + int32_t - zero=success, else failure + + \Version 11/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoWebSocketDisconnect(ProtoWebSocketRefT *pWebSocket) +{ + // ignore request in states where we are already disconnected + if ((pWebSocket->eState == ST_DISC) || (pWebSocket->eState == ST_FAIL)) + { + return(0); + } + // if we're in handshaking just do an immediate close + if (pWebSocket->eState != ST_OPEN) + { + return(_ProtoWebSocketDisconnect(pWebSocket, ST_DISC)); + } + // send close notification + return(_ProtoWebSocketSendClose(pWebSocket, PROTOWEBSOCKET_CLOSEREASON_NORMAL, NULL)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketSend + + \Description + Send data to a server over a WebSocket connection. Stream-type send, no + framing is performed by ProtoWebSocket. + + \Input *pWebSocket - websocket module state + \Input *pBuffer - data to send + \Input iLength - amount of data to send + + \Output + int32_t - negative=failure, else number of bytes of data sent + + \Version 11/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoWebSocketSend(ProtoWebSocketRefT *pWebSocket, const char *pBuffer, int32_t iLength) +{ + return(_ProtoWebSocketSendUserData(pWebSocket, pBuffer, iLength, PROTOWEBSOCKET_OPCODE_BINA)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketSendText + + \Description + Send text data to a server over a WebSocket connection. Stream-type send, no + framing is performed by ProtoWebSocket. + + \Input *pWebSocket - websocket module state + \Input *pBuffer - data to send (NULL terminated C-style string) + + \Output + int32_t - negative=failure, else number of bytes of data sent + + \Version 08/30/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoWebSocketSendText(ProtoWebSocketRefT *pWebSocket, const char *pBuffer) +{ + return(_ProtoWebSocketSendUserData(pWebSocket, pBuffer, (int32_t)strlen(pBuffer), PROTOWEBSOCKET_OPCODE_TEXT)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketSendMessage + + \Description + Send a message to a server over a WebSocket connection. If the message + size is larger than the ProtoWebSocket buffer size, the message will be + sent fragmented. + + \Input *pWebSocket - websocket module state + \Input *pBuffer - data to send + \Input iLength - amount of data to send + + \Output + int32_t - negative=failure, else number of bytes of data sent + + \Version 03/31/2017 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoWebSocketSendMessage(ProtoWebSocketRefT *pWebSocket, const char *pBuffer, int32_t iLength) +{ + int32_t iResult; + pWebSocket->bMessageSend = TRUE; + iResult = _ProtoWebSocketSendUserData(pWebSocket, pBuffer, iLength, PROTOWEBSOCKET_OPCODE_BINA); + pWebSocket->bMessageSend = FALSE; + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketSendMessageText + + \Description + Send a text message to a server over a WebSocket connection. If the message + size is larger than the ProtoWebSocket buffer size, the message will be + sent fragmented. + + \Input *pWebSocket - websocket module state + \Input *pBuffer - data to send (NULL terminated C-style string) + + \Output + int32_t - negative=failure, else number of bytes of data sent + + \Version 08/30/2017 (eesponda) +*/ +/********************************************************************************F*/ +int32_t ProtoWebSocketSendMessageText(ProtoWebSocketRefT *pWebSocket, const char *pBuffer) +{ + int32_t iResult; + pWebSocket->bMessageSend = TRUE; + iResult = _ProtoWebSocketSendUserData(pWebSocket, pBuffer, (int32_t)strlen(pBuffer), PROTOWEBSOCKET_OPCODE_TEXT); + pWebSocket->bMessageSend = FALSE; + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketRecv + + \Description + Receive data from a server over a WebSocket connection. Stream-type recv, + framing bits are ignored. + + \Input *pWebSocket - websocket module state + \Input *pBuffer - [out] buffer to receive data + \Input iLength - size of buffer + + \Output + int32_t - negative=failure, else number of bytes of data received + + \Version 11/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoWebSocketRecv(ProtoWebSocketRefT *pWebSocket, char *pBuffer, int32_t iLength) +{ + // handle not connected states + if ((pWebSocket->eState == ST_DISC) || (pWebSocket->eState == ST_FAIL)) + { + return(SOCKERR_NOTCONN); + } + if (pWebSocket->eState != ST_OPEN) + { + return(0); + } + + // connected, do the receive + return(_ProtoWebSocketRecvData(pWebSocket, pBuffer, iLength)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketRecvMessage + + \Description + Receive a message from a server over a WebSocket connection. Note that + the user buffer is expected to persist and be capable of receiving the + entire message. + + \Input *pWebSocket - websocket module state + \Input *pBuffer - [out] buffer to receive data + \Input iLength - size of buffer + + \Output + int32_t - negative=failure, SOCKERR_NOMEM if the user buffer + is too small, zero=pending, else frame size received + + \Version 03/30/2017 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoWebSocketRecvMessage(ProtoWebSocketRefT *pWebSocket, char *pBuffer, int32_t iLength) +{ + int32_t iResult; + + // see if we need more buffer + if (pWebSocket->iRecvSize == iLength) + { + return(SOCKERR_NOMEM); + } + + // do the receive + if ((iResult = ProtoWebSocketRecv(pWebSocket, pBuffer+pWebSocket->iRecvSize, iLength-pWebSocket->iRecvSize)) > 0) + { + // accumulate receive size + pWebSocket->iRecvSize += iResult; + + // return accumulated size if final frame and completely read, else return zero + if ((pWebSocket->bFrameFin) && (pWebSocket->iFrameLen == 0)) + { + iResult = pWebSocket->iRecvSize; + pWebSocket->iRecvSize = 0; + pWebSocket->bFrameFin = FALSE; + } + else + { + iResult = 0; + } + } + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketStatus + + \Description + Get module status + + \Input *pWebSocket - websocket module state + \Input iSelect - status selector + \Input *pBuffer - [inp/out] buffer, selector specific + \Input iBufSize - size of buffer + + \Output + int32_t - selector specific + + \Notes + Selectors are: + + \verbatim + SELECTOR RETURN RESULT + 'crsn' returns close reason (PROTOWEBSOCKET_CLOSEREASON_*) + 'essl' returns protossl error state + 'host' current host copied to output buffer + 'port' current port + 'stat' connection status (-1=not connected, 0=connection in progress, 1=connected) + 'time' TRUE if the client timed out the connection, else FALSE + \endverbatim + + \Version 11/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoWebSocketStatus(ProtoWebSocketRefT *pWebSocket, int32_t iSelect, void *pBuffer, int32_t iBufSize) +{ + // return close reason + if (iSelect == 'crsn') + { + if ((pBuffer != NULL) && (pWebSocket->iCtrlDataLen > 0)) + { + int32_t iCopyLen = (iBufSize < pWebSocket->iCtrlDataLen) ? iBufSize : pWebSocket->iCtrlDataLen; + ds_memcpy(pBuffer, pWebSocket->strCtrlData, iCopyLen); + } + return(pWebSocket->iCloseReason); + } + + // return protossl error state (we cache this since the state is reset when we disconnect on error) + if (iSelect == 'essl') + { + return(pWebSocket->iSSLFail); + } + + // return current host + if (iSelect == 'host') + { + ds_strnzcpy(pBuffer, pWebSocket->strHost, iBufSize); + return(0); + } + + // return current port + if (iSelect == 'port') + { + return(pWebSocket->iPort); + } + + // check connection status + if (iSelect == 'stat') + { + if ((pWebSocket->eState == ST_DISC) || (pWebSocket->eState == ST_FAIL)) + { + return(-1); + } + else if (pWebSocket->eState != ST_OPEN) + { + return(0); + } + // if we're connected (ST_OPEN) fall through to protossl connectivity check + } + + // return timeout indicator + if (iSelect == 'time') + { + return(pWebSocket->bTimeout); + } + + // pass through unhandled selectors to protossl + return(ProtoSSLStat(pWebSocket->pProtoSSL, iSelect, pBuffer, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketControl + + \Description + Control module behavior + + \Input *pWebSocket - websocket module state + \Input iSelect - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + Selectors are: + + \verbatim + SELECTOR DESCRIPTION + 'apnd' The given buffer will be appended to future headers sent by + ProtoWebSocket. The Accept header in the default header will + be omitted. If there is a User-Agent header included in the + append header, it will replace the default User-Agent header. + 'clse' Send a close message with optional reason (iValue) and data + (string passed in pValue) + 'keep' Set keep-alive timer, in seconds; client sends a 'pong' at this + rate to keep connection alive (0 disables; disabled by default) + 'ires' Resize the input buffer + 'ping' Send a ping message with optional data (string passed in pValue) + 'pong' Send a pong message with optional data (string passed in pValue) + 'spam' Sets debug output verbosity (0...n) + 'time' Sets ProtoWebSocket client timeout in seconds (default=300s) + \endverbatim + + Unhandled selectors are passed on to ProtoSSL. + + \Version 11/30/2012 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t ProtoWebSocketControl(ProtoWebSocketRefT *pWebSocket, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iSelect == 'apnd') + { + return(_ProtoWebSocketSetAppendHeader(pWebSocket, (const char *)pValue)); + } + if (iSelect == 'clse') + { + return(_ProtoWebSocketSendClose(pWebSocket, iValue, (const char *)pValue)); + } + if (iSelect == 'keep') + { + // enforce a minimum keep-alive interval of 30s + if (iValue < 30) + { + NetPrintf(("protowebsocket: [%p] warning: enforcing a minimum keep-alive interval (%ds->30s)\n", pWebSocket, iValue)); + iValue = 30; + } + pWebSocket->iKeepAlive = iValue*1000; + return(0); + } + if (iSelect == 'ires') + { + char *pInpBuf; + + // make sure that we don't truncate any data already in the buffer, this assumes that you don't resize multiple times + iValue = DS_MAX(iValue, PROTOWEBSOCKET_DEFAULT_INPUTBUFSIZE); + + // allocate buffer and reassign + if ((pInpBuf = (char *)DirtyMemAlloc(iValue, PROTOWEBSOCKET_MEMID, pWebSocket->iMemGroup, pWebSocket->pMemGroupUserData)) == NULL) + { + NetPrintf(("protowebsocket: [%p] unable to allocate buffer for resizing input buffer\n", pWebSocket)); + return(-1); + } + ds_memcpy(pInpBuf, pWebSocket->pInpBuf, pWebSocket->iInpLen); + + DirtyMemFree(pWebSocket->pInpBuf, PROTOWEBSOCKET_MEMID, pWebSocket->iMemGroup, pWebSocket->pMemGroupUserData); + pWebSocket->pInpBuf = pInpBuf; + pWebSocket->iInpMax = iValue; + + return(0); + } + if ((iSelect == 'ping') || (iSelect == 'pong')) + { + return(_ProtoWebSocketSendPing(pWebSocket, (const char *)pValue, (iSelect == 'ping') ? PROTOWEBSOCKET_CTRL_PING : PROTOWEBSOCKET_CTRL_PONG)); + } + if (iSelect == 'spam') + { + pWebSocket->iVerbose = iValue; + return(0); + } + if (iSelect == 'time') + { + pWebSocket->iTimeout = iValue*1000; + return(0); + } + // pass unhandled selectors through to ProtoSSL + return(ProtoSSLControl(pWebSocket->pProtoSSL, iSelect, iValue, iValue2, pValue)); +} + +/*F********************************************************************************/ +/*! + \Function ProtoWebSocketUpdate + + \Description + Give life to module (should be called periodically to allow module to + perform work). + + \Input *pWebSocket - websocket module state + + \Version 11/26/2012 (jbrookes) +*/ +/********************************************************************************F*/ +void ProtoWebSocketUpdate(ProtoWebSocketRefT *pWebSocket) +{ + uint32_t uCurTick = NetTick(); + int32_t iResult; + char *pStart, *pEnd; + + // give time to comm module + ProtoSSLUpdate(pWebSocket->pProtoSSL); + + // check for timeout + if ((pWebSocket->eState != ST_DISC) && (pWebSocket->eState != ST_FAIL)) + { + if (NetTickDiff(uCurTick, pWebSocket->uTimer) > pWebSocket->iTimeout) + { + NetPrintf(("protowebsocket: [%p] inactivity timeout (state=%d)\n", pWebSocket, pWebSocket->eState)); + pWebSocket->eState = ST_FAIL; + pWebSocket->bTimeout = TRUE; + } + } + + // see if network connection is complete + if (pWebSocket->eState == ST_CONN) + { + if ((iResult = ProtoSSLStat(pWebSocket->pProtoSSL, 'stat', NULL, 0)) > 0) + { + pWebSocket->uTimer = uCurTick; + pWebSocket->eState = ST_SEND; + } + else if (iResult < 0) + { + _ProtoWebSocketFail(pWebSocket, iResult, "connecting"); + } + } + + // sending handshake + if (pWebSocket->eState == ST_SEND) + { + if ((iResult = _ProtoWebSocketSendBuff(pWebSocket)) > 0) + { + NetPrintfVerbose((pWebSocket->iVerbose, 1, "protowebsocket: [%p] sent handshake request\n", pWebSocket)); + #if DIRTYCODE_LOGGING + if (pWebSocket->iVerbose > 2) + { + NetPrintWrap(pWebSocket->pOutBuf, 80); + } + #endif + pWebSocket->eState = ST_RECV; + } + else if (iResult < 0) + { + _ProtoWebSocketFail(pWebSocket, iResult, "sending handshake request"); + } + } + + // receive server handshake response: note that due to serialized nature of handshaking + if (pWebSocket->eState == ST_RECV) + { + if ((iResult = _ProtoWebSocketRecv(pWebSocket, pWebSocket->pInpBuf+pWebSocket->iInpLen, pWebSocket->iInpMax-pWebSocket->iInpLen)) > 0) + { + pWebSocket->iInpLen += iResult; + // see if we have the complete header + pStart = pWebSocket->pInpBuf; + pEnd = pWebSocket->pInpBuf+pWebSocket->iInpLen-3; + + // scan for blank line marking body start + while ((pStart != pEnd) && ((pStart[0] != '\r') || (pStart[1] != '\n') || (pStart[2] != '\r') || (pStart[3] != '\n'))) + { + pStart += 1; + } + + if (pStart != pEnd) + { + // process the header + if ((iResult = _ProtoWebSocketProcessHeader(pWebSocket, pWebSocket->pInpBuf)) >= 0) + { + NetPrintf(("protowebsocket: [%p] connection open\n", pWebSocket)); + pWebSocket->eState = ST_OPEN; + } + else + { + _ProtoWebSocketFail(pWebSocket, iResult, "processing handshake response"); + } + // remove header from buffer + pWebSocket->iInpOff = (int32_t)(pStart+4-pWebSocket->pInpBuf); + } + } + else if (iResult < 0) + { + _ProtoWebSocketFail(pWebSocket, iResult, "receiving handshake response"); + } + } + + // process in open state + if (pWebSocket->eState == ST_OPEN) + { + // issue a keep-alive? + if ((pWebSocket->iKeepAlive > 0) && (NetTickDiff(uCurTick, pWebSocket->uTimer) > pWebSocket->iKeepAlive)) + { + NetPrintfVerbose((pWebSocket->iVerbose, 0, "protowebsocket: [%p] sending keep-alive at %u\n", pWebSocket, uCurTick)); + pWebSocket->bDoPong = TRUE; + pWebSocket->strCtrlData[0] = '\0'; + pWebSocket->iCtrlDataLen = 0; + } + + // process pong request + _ProtoWebSocketProcessSendPong(pWebSocket); + + // process close request + _ProtoWebSocketProcessSendClose(pWebSocket); + + // process send request + _ProtoWebSocketProcessSend(pWebSocket); + } +} diff --git a/src/thirdparty/dirtysdk/source/util/aws.c b/src/thirdparty/dirtysdk/source/util/aws.c new file mode 100644 index 00000000..029eb807 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/aws.c @@ -0,0 +1,851 @@ +/*H********************************************************************************/ +/*! + \File aws.c + + \Description + Implements AWS utility functions, including SigV4 signing and signed binary + event reading and writing. + + \Notes + References: + Signature V4: https://docs.aws.amazon.com/en_us/general/latest/gr/signature-version-4.html + SigV4 Signing: https://docs.aws.amazon.com/en_us/general/latest/gr/sigv4_signing.html + SigV4 Examples: https://docs.aws.amazon.com/en_us/general/latest/gr/sigv4-signed-request-examples.html + Event Stream Encoding: https://docs.aws.amazon.com/transcribe/latest/dg/streaming-format.html + + \Copyright + Copyright 2018 Electronic Arts + + \Version 12/26/2018 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/crypt/crypthash.h" +#include "DirtySDK/crypt/crypthmac.h" +#include "DirtySDK/proto/protohttputil.h" + +#include "DirtySDK/util/aws.h" + +/*** Defines **********************************************************************/ + +//! define this for debug output +#define AWS_DEBUG (DIRTYCODE_DEBUG && FALSE) + +//! length of aws binary event prelude +#define AWS_EVENT_PRELUDE_SIZE (12) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! AWS event header types +typedef enum +{ + AWS_BOOLEAN_TRUE = 0, + AWS_BOOLEAN_FALSE, + AWS_BYTE, + AWS_SHORT, + AWS_INTEGER, + AWS_LONG, + AWS_BYTE_ARRAY, + AWS_STRING, + AWS_TIMESTAMP, + AWS_UUID +} AWSHeaderValueE; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _AWSHashSha2 + + \Description + Calculate SHA2 hash of specified data + + \Input *pHashBuf - [out] storage for hash data + \Input iHashLen - length of hash and hash data buffer + \Input *pData - data to hash + \Input iDataLen - length of data to hash, or -1 if data is a string + + \Version 01/15/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static void _AWSHashSha2(uint8_t *pHashBuf, int32_t iHashLen, const uint8_t *pData, int32_t iDataLen) +{ + CryptSha2T Sha2; + // get data length if it is a string + if (iDataLen < 0) + { + iDataLen = (int32_t)strlen((const char *)pData); + } + // create hash of request payload + CryptSha2Init(&Sha2, iHashLen); + CryptSha2Update(&Sha2, pData, iDataLen); + CryptSha2Final(&Sha2, pHashBuf, iHashLen); +} + +/*F********************************************************************************/ +/*! + \Function _AWSGetDateTime + + \Description + Get current datetime in ISO8601 basic format. + + \Input *pStrDateTime - [out] storage for datetime string + \Input iDateTimeLen - length of datetime buffer + \Input *pStrDate - [out, optional] storage for date string (YYYYMMDD) + \Input iDateLen - length of date buffer + + \Version 12/26/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _AWSGetDateTime(char *pStrDateTime, int32_t iDateTimeLen, char *pStrDate, int32_t iDateLen) +{ + struct tm CurTime; + uint64_t uTime; + // get current time + uTime = ds_timeinsecs(); + // get full date in ISO8601 (basic format) + ds_timetostr(ds_secstotime(&CurTime, uTime), TIMETOSTRING_CONVERSION_ISO_8601_BASIC, FALSE, pStrDateTime, iDateTimeLen); + // get date string (optional) + if (pStrDate != NULL) + { + ds_strsubzcpy(pStrDate, iDateLen, pStrDateTime, 8); + } +} + +/*F********************************************************************************/ +/*! + \Function _AWSGetKeyInfo + + \Description + Extract keyid and key from keyid:key string + + \Input *pStrKeyInfo - pointer to source keyid:key info + \Input *pStrId - [out] storage for keyid + \Input iIdLen - size of keyid buffer + \Input *pStrKey - [out] storage for key + \Input iKeyLen - size of key buffer + + \Version 12/26/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _AWSGetKeyInfo(const char *pStrKeyInfo, char *pStrId, int32_t iIdLen, char *pStrKey, int32_t iKeyLen) +{ + const char *pSep; + *pStrKey = *pStrId = '\0'; + if ((pSep = strchr(pStrKeyInfo, ':')) != NULL) + { + ds_strsubzcpy(pStrId, iIdLen, pStrKeyInfo, (int32_t)(pSep-pStrKeyInfo)); + ds_strnzcpy(pStrKey, pSep+1, iKeyLen); + } +} + +/*F********************************************************************************/ +/*! + \Function _AWSGetHostInfo + + \Description + Get region from host + + \Input *pStrHost - hostname, used to derive service and region + \Input *pStrRegion - [out] AWS region buffer + \Input iRegionLen - length of region buffer + + \Version 12/31/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _AWSGetHostInfo(const char *pStrHost, char *pStrRegion, int32_t iRegionLen) +{ + const char *pDot; + // skip service name in host header + pDot = strchr(pStrHost, '.'); + pStrHost = pDot+1; + // get region from host header + pDot = strchr(pStrHost, '.'); + ds_strsubzcpy(pStrRegion, iRegionLen, pStrHost, (int32_t)(pDot-pStrHost)); +} + +/*F********************************************************************************/ +/*! + \Function _AWSSignGetHeaders + + \Description + Given an HTTP header buffer, creates the Canonical and Signed header lists + required for AWSV4 signing. + + \Input *pHeader - HTTP header to parse + \Input *pCanonHdrBuf - [out] buffer for canonical header list + \Input iCanonHdrLen - lengh of canonical header buffer + \Input *pSignedHdrBuf - [out] buffer for signed header list + \Input iSignedHdrLen - length of signed header buffer + \Input *pStrHost - [out] buffer for hostname from host header + \Input iHostLen - length of host buffer + \Input *pStrMethod - [out] buffer for method from request + \Input iMethodLen - length of method buffer + \Input *pStrUri - [out] buffer for URI from request + \Input iUriLen - length of URI buffer + \Input *pStrQuery - [out] buffer for query from request + \Input iQueryLen - length of query buffer + + \Version 12/28/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _AWSSignGetHeaders(const char *pHeader, char *pCanonHdrBuf, int32_t iCanonHdrLen, char *pSignedHdrBuf, int32_t iSignedHdrLen, char *pStrHost, int32_t iHostLen, char *pStrMethod, int32_t iMethodLen, char *pStrUri, int32_t iUriLen, char *pStrQuery, int32_t iQueryLen) +{ + char strName[128], strValue[256], strLine[384]; + const char *pHdrTmp = pHeader; + + // get HTTPRequestMethod, CanonicalURI, and CanonicalQuery from first line of request if this is *not* HTTP2 + if (ds_strnicmp(pHeader, ":method:", 8)) + { + pHdrTmp = ProtoHttpUtilParseRequest(pHeader, pStrMethod, iMethodLen, pStrUri, iUriLen, pStrQuery, iQueryLen, NULL, NULL); + } + + // get headers, process them, and add to canonical and signed lists in sorted order + while (ProtoHttpGetNextHeader(NULL, pHdrTmp, strName, sizeof(strName), strValue, sizeof(strValue), &pHdrTmp) == 0) + { + // save copy of host header value + if (!ds_stricmp(strName, "host") || !ds_stricmp(strName, ":authority")) + { + ds_strnzcpy(pStrHost, strValue, iHostLen); + } + // save copy of method header value (http2) + if (!ds_stricmp(strName, ":method")) + { + ds_strnzcpy(pStrMethod, strValue, iMethodLen); + } + // save copy of path header value (http2) + if (!ds_stricmp(strName, ":path")) + { + ds_strnzcpy(pStrUri, strValue, iUriLen); + } + // http2 pseudo-headers don't include values in their canonical representation + if (strName[0] == ':') + { + strValue[0] = '\0'; + } + // convert name to lower case + ds_strtolower(strName); + // trim spaces in value + ds_strtrim(strValue); + // format header line for canonical headers list + ds_snzprintf(strLine, sizeof(strLine), "%s:%s", strName, strValue); + // insert header line in canonical header list + ds_strlistinsert(pCanonHdrBuf, iCanonHdrLen, strLine, '\n'); + // insert header name in signed header list + ds_strlistinsert(pSignedHdrBuf, iSignedHdrLen, strName, ';'); + } + // eliminate trailing semicolon in signed header list + pSignedHdrBuf[strlen(pSignedHdrBuf)-1] = '\0'; +} + +/*F********************************************************************************/ +/*! + \Function _AWSSignCreateCanonicalRequest + + \Description + Create a Canonical Request for Signature Version 4 as per + https://docs.aws.amazon.com/en_us/general/latest/gr/sigv4-create-canonical-request.html + + \Input *pSigBuf - [out] storage for signature + \Input iSigLen - length of signature buffer + \Input *pHeader - request header + \Input *pRequest - request body + \Input *pStrDateTime - datetime string + \Input *pCanonHdrs - canonical header list + \Input *pSignedHdrs - signed header list + \Input *pMethod - request method + \Input *pUri - request uri + \Input *pQuery - request query + + \Version 12/21/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _AWSSignCreateCanonicalRequest(char *pSigBuf, int32_t iSigLen, const char *pHeader, const char *pRequest, const char *pStrDateTime, const char *pCanonHdrs, const char *pSignedHdrs, const char *pMethod, const char *pUri, const char *pQuery) +{ + uint8_t aHashBuf[CRYPTSHA256_HASHSIZE]; + char strSigTemp[1024], strSignTemp[(CRYPTSHA256_HASHSIZE*2)+1]; + int32_t iOffset; + + // create hash of request payload + _AWSHashSha2(aHashBuf, sizeof(aHashBuf), (const uint8_t *)pRequest, -1); + + // create CanonicalRequest + iOffset = 0; + iOffset += ds_snzprintf(strSigTemp+iOffset, sizeof(strSigTemp)-iOffset, "%s\n", pMethod); // HTTPRequestMethod + '\n' + + iOffset += ds_snzprintf(strSigTemp+iOffset, sizeof(strSigTemp)-iOffset, "%s\n", pUri); // CanonicalURI + '\n' + + iOffset += ds_snzprintf(strSigTemp+iOffset, sizeof(strSigTemp)-iOffset, "%s\n", pQuery); // CanonicalQueryString + '\n' + + iOffset += ds_snzprintf(strSigTemp+iOffset, sizeof(strSigTemp)-iOffset, "%s\n", pCanonHdrs); // CanonicalHeaders + '\n' + + iOffset += ds_snzprintf(strSigTemp+iOffset, sizeof(strSigTemp)-iOffset, "%s\n", pSignedHdrs); // SignedHeaders + '\n' + + iOffset += ds_snzprintf(strSigTemp+iOffset, sizeof(strSigTemp)-iOffset, "%s", ds_fmtoctstring_lc(strSignTemp, sizeof(strSignTemp), aHashBuf, sizeof(aHashBuf))); // HexEncode(Hash(RequestPayload)) + NetPrintfVerbose((AWS_DEBUG, 0, "aws: CanonicalRequest:\n%s\n", strSigTemp)); + + // hash the CanonicalRequest + _AWSHashSha2(aHashBuf, sizeof(aHashBuf), (const uint8_t *)strSigTemp, iOffset); + + // create the octet string version of the hash in output buffer + ds_fmtoctstring_lc(pSigBuf, iSigLen, aHashBuf, sizeof(aHashBuf)); // HexEncode(Hash(CanonicalRequest)) +} + +/*F********************************************************************************/ +/*! + \Function _AWSSignCreateStringToSign + + \Description + Create a String to Sign for Signature Version 4 as per + https://docs.aws.amazon.com/en_us/general/latest/gr/sigv4-create-string-to-sign.html + + \Input *pSignStr - [out] storage for string to sign + \Input iSignLen - length of sign buffer + \Input *pSignType - signing type string + \Input *pDateTimeStr - datetime in ISO8601 basic format + \Input *pKeyPath - keypath (date/region/service) string + \Input *pSigBuf - signature created in Task1 + + \Output + int32_t - length of string to sign + + \Version 12/26/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _AWSSignCreateStringToSign(char *pSignStr, int32_t iSignLen, const char *pSignType, const char *pDateTimeStr, const char *pKeyPath, const char *pSigBuf) +{ + int32_t iOffset = 0; + iOffset += ds_snzprintf(pSignStr+iOffset, iSignLen-iOffset, "%s\n", pSignType); // AWS4-HMAC-SHA256\n + iOffset += ds_snzprintf(pSignStr+iOffset, iSignLen-iOffset, "%s\n", pDateTimeStr); // YYYYMMDD'T'HHMMSS'Z' + iOffset += ds_snzprintf(pSignStr+iOffset, iSignLen-iOffset, "%s/aws4_request\n", pKeyPath); // /YYYYMMDD///aws4_request\n + iOffset += ds_snzprintf(pSignStr+iOffset, iSignLen-iOffset, "%s", pSigBuf); // base16-encoded hashed CanonicalRequest + return(iOffset); +} + +/*F********************************************************************************/ +/*! + \Function _AWSSignCreateSignature + + \Description + Calculate the Signature for AWS Signature Version 4 as per + https://docs.aws.amazon.com/en_us/general/latest/gr/sigv4-calculate-signature.html + + \Input *pSigBuf - [out] storage for signature + \Input iSigLen - length of signature buffer + \Input *pSign - string to sign + \Input *pSecret - secret key + \Input *pStrDate - date string + \Input *pRegion - AWS region + \Input *pService - AWS service + + \Version 12/26/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _AWSSignCreateSignature(uint8_t *pSigBuf, int32_t iSigLen, const char *pSign, const char *pSecret, const char *pStrDate, const char *pRegion, const char *pService) +{ + uint8_t aHmac[CRYPTSHA256_HASHSIZE]; + const char *pRequest = "aws4_request"; + char strKey[256]; + + // create signing key + ds_snzprintf(strKey, sizeof(strKey), "%s%s", "AWS4", pSecret); + + // kDate = HMAC("AWS4" + kSecret, Date); note our CryptHmac parameters are reversed from their documentation + CryptHmacCalc(aHmac, sizeof(aHmac), (const uint8_t *)pStrDate, (int32_t)strlen(pStrDate), (const uint8_t *)strKey, (int32_t)strlen(strKey), CRYPTHASH_SHA256); + // kRegion = HMAC(kDate, Region) + CryptHmacCalc(aHmac, sizeof(aHmac), (const uint8_t *)pRegion, (int32_t)strlen(pRegion), aHmac, sizeof(aHmac), CRYPTHASH_SHA256); + // kService = HMAC(kRegion, Service) + CryptHmacCalc(aHmac, sizeof(aHmac), (const uint8_t *)pService, (int32_t)strlen(pService), aHmac, sizeof(aHmac), CRYPTHASH_SHA256); + // kSigning = HMAC(kService, "aws4_request") + CryptHmacCalc(aHmac, sizeof(aHmac), (const uint8_t *)pRequest, (int32_t)strlen(pRequest), aHmac, sizeof(aHmac), CRYPTHASH_SHA256); + + // sign the string + CryptHmacCalc(aHmac, sizeof(aHmac), (const uint8_t *)pSign, (int32_t)strlen(pSign), aHmac, sizeof(aHmac), CRYPTHASH_SHA256); + // copy signature to output + ds_memcpy_s(pSigBuf, iSigLen, aHmac, sizeof(aHmac)); +} + +/*F********************************************************************************/ +/*! + \Function _AWSSignCreateAuthorization + + \Description + Create the Authorization header for adding to the HTTP request + https://docs.aws.amazon.com/en_us/general/latest/gr/sigv4-add-signature-to-request.html + + \Input *pAuthBuf - [out] storage for authorization header + \Input iAuthLen - length of auth buffer + \Input *pStrSig - signature + \Input *pKeyId - key id + \Input *pStrDateTime - datetime string + \Input *pKeyPath - keypath (date/region/service) string + \Input *pSignedHdrs - signed headers + + \Output + int32_t - length of authorization header + + \Version 12/26/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _AWSSignCreateAuthorization(char *pAuthBuf, int32_t iAuthLen, const char *pStrSig, const char *pKeyId, const char *pStrDateTime, const char *pKeyPath, const char *pSignedHdrs) +{ + int32_t iOffset; + // Authorization: algorithm Credential=access key ID/credential scope, SignedHeaders=SignedHeaders, Signature=signature + iOffset = 0; + iOffset += ds_snzprintf(pAuthBuf+iOffset, iAuthLen-iOffset, "Authorization: AWS4-HMAC-SHA256 "); + iOffset += ds_snzprintf(pAuthBuf+iOffset, iAuthLen-iOffset, "Credential=%s/%s/aws4_request, ", pKeyId, pKeyPath); + iOffset += ds_snzprintf(pAuthBuf+iOffset, iAuthLen-iOffset, "SignedHeaders=%s, ", pSignedHdrs); + iOffset += ds_snzprintf(pAuthBuf+iOffset, iAuthLen-iOffset, "Signature=%s\r\n", pStrSig); + return(iOffset); +} + +/*F********************************************************************************/ +/*! + \Function _AWSWriteEventPrelude + + \Description + Write binary event prelude to buffer + + \Input *pBuffer - [out] buffer to write prelude to + \Input iHdrLen - length of header + \Input iMsgLen - length of message + + \Version 01/16/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static void _AWSWriteEventPrelude(uint8_t *pBuffer, int32_t iHdrLen, int32_t iMsgLen) +{ + uint32_t uCrc32; + // write total length (including message_crc) + pBuffer[0] = (uint8_t)(iMsgLen>>24); + pBuffer[1] = (uint8_t)(iMsgLen>>16); + pBuffer[2] = (uint8_t)(iMsgLen>>8); + pBuffer[3] = (uint8_t)(iMsgLen>>0); + // write headers length + pBuffer[4] = (uint8_t)(iHdrLen>>24); + pBuffer[5] = (uint8_t)(iHdrLen>>16); + pBuffer[6] = (uint8_t)(iHdrLen>>8); + pBuffer[7] = (uint8_t)(iHdrLen>>0); + // calculate and write prelude_crc + uCrc32 = NetCrc32(pBuffer, 8, NULL); + pBuffer[8] = (uint8_t)(uCrc32>>24); + pBuffer[9] = (uint8_t)(uCrc32>>16); + pBuffer[10] = (uint8_t)(uCrc32>>8); + pBuffer[11] = (uint8_t)(uCrc32>>0); +} + +/*F********************************************************************************/ +/*! + \Function _AWSWriteEventHeader + + \Description + Write binary event header to buffer + + \Input *pBuffer - [out] buffer to write header to + \Input iBufSize - buffer size + \Input *pHeader - name of header to write + \Input *pValue - header value data + \Input iValueSize - size of header value + \Input eHeaderValue - header value type + + \Output + int32_t - size of header written + + \Version 01/16/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _AWSWriteEventHeader(uint8_t *pBuffer, int32_t iBufSize, const char *pHeader, const uint8_t *pValue, int32_t iValueSize, AWSHeaderValueE eHeaderValue) +{ + int32_t iOffset=0, iStrLen = (int32_t)strlen(pHeader); + // write header length + pBuffer[iOffset++] = (uint8_t)iStrLen; + // copy header name + ds_memcpy_s(pBuffer+iOffset, iBufSize-iOffset, pHeader, iStrLen); + iOffset += iStrLen; + // copy header value type + pBuffer[iOffset++] = (uint8_t)eHeaderValue; + // copy header value length + if ((eHeaderValue == AWS_BYTE_ARRAY) || (eHeaderValue == AWS_STRING)) + { + pBuffer[iOffset++] = (uint8_t)(iValueSize>>8); + pBuffer[iOffset++] = (uint8_t)(iValueSize>>0); + } + // copy header value + #if EA_SYSTEM_LITTLE_ENDIAN + if ((eHeaderValue == AWS_SHORT) || (eHeaderValue == AWS_INTEGER) || (eHeaderValue == AWS_LONG) || (eHeaderValue == AWS_TIMESTAMP)) + { + int32_t iByte, iData; + for (iByte = 0, iData = iValueSize-1; iByte < iValueSize; iByte += 1) + { + pBuffer[iOffset++] = pValue[iData--]; + } + } + else + #endif + { + ds_memcpy_s(pBuffer+iOffset, iBufSize-iOffset, pValue, iValueSize); + iOffset += iValueSize; + } + // return offset to caller + return(iOffset); +} + +/*F********************************************************************************/ +/*! + \Function _AWSWriteSignEvent + + \Description + Sign an AWS binary event + + \Input *pSignature - [out] buffer to write signature to + \Input iSigSize - size of signature to write + \Input *pHead - header of event to sign + \Input iHeadSize - size of header + \Input *pData - data to sign + \Input iDataSize - size of data (may be zero) + \Input *pSignInfo - [in/out] signing info for event + + \Notes + The signature field in the signing info is used to sign the event, and + then updated with the new signature, which AWS calls "signature chaining". + + String stringToSign = + "AWS4-HMAC-SHA256-PAYLOAD" + "\n" + DATE + "\n" + + KEYPATH + "\n" + + Hex(priorSignature) + "\n" + + HexHash(nonSigHeaders) + "\n" + + HexHash(payload) + + \Version 01/16/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static void _AWSWriteSignEvent(uint8_t *pSignature, int32_t iSigSize, const uint8_t *pHead, int32_t iHeadSize, const uint8_t *pData, int32_t iDataSize, AWSSignInfoT *pSignInfo) +{ + char strSign[1024], strDateTime[32], strDate[9], strSignTemp[(CRYPTSHA256_HASHSIZE*2)+1]; + uint8_t aHashBuf[CRYPTSHA256_HASHSIZE]; + int32_t iOffset; + // get datetime + _AWSGetDateTime(strDateTime, sizeof(strDateTime), strDate, sizeof(strDate)); + // create the string to signl includes header, datetime, keypath, and priorSignature + iOffset = _AWSSignCreateStringToSign(strSign, sizeof(strSign), "AWS4-HMAC-SHA256-PAYLOAD", strDateTime, pSignInfo->strKeyPath, pSignInfo->strSignature); + // append hexhash of non-signed headers + _AWSHashSha2(aHashBuf, sizeof(aHashBuf), pHead, iHeadSize); + iOffset += ds_snzprintf(strSign+iOffset, sizeof(strSign)-iOffset, "\n%s\n", ds_fmtoctstring_lc(strSignTemp, sizeof(strSignTemp), aHashBuf, sizeof(aHashBuf))); // HexEncode(Hash(nonSigHeaders)) + \n + // append hexhash of payload + _AWSHashSha2(aHashBuf, sizeof(aHashBuf), pData, iDataSize); + iOffset += ds_snzprintf(strSign+iOffset, sizeof(strSign)-iOffset, "%s", ds_fmtoctstring_lc(strSignTemp, sizeof(strSignTemp), aHashBuf, sizeof(aHashBuf))); // HexEncode(Hash(payload)) + // debug logging of string to sign + NetPrintfVerbose((AWS_DEBUG, 0, "aws: EventStringToSign:\n%s\n", strSign)); + // sign the string; String signature = HMAC(derived_signing_key, stringToSign); + _AWSSignCreateSignature(pSignature, iSigSize, strSign, pSignInfo->strKey, strDate, pSignInfo->strRegion, pSignInfo->strService); + // update signing key (signature chaining) + ds_strnzcpy(pSignInfo->strSignature, ds_fmtoctstring_lc(strSignTemp, sizeof(strSignTemp), pSignature, iSigSize), sizeof(pSignInfo->strSignature)); +} + +/*F********************************************************************************/ +/*! + \Function _AWSReadEventHeader + + \Description + Read an event header from an event + + \Input *pBuffer - buffer pointing to event to read + \Input iBufSize - size of buffer + \Input *pHeaderName - [out] buffer to store header name + \Input iNameSize - size of header name buffer + \Input *pHeaderValue - [out] buffer to store header value + \Input *pValueSize - [in] size of value buffer, [out] size of value + \Input *pValueType - [out] storage for value type + + \Output + int32_t - size of event + + \Version 01/17/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _AWSReadEventHeader(const uint8_t *pBuffer, int32_t iBufSize, char *pHeaderName, int32_t iNameSize, uint8_t *pHeaderValue, int32_t *pValueSize, AWSHeaderValueE *pValueType) +{ + int32_t iHdrLen, iValLen=0; + int32_t iOffset=0; + // get length + iHdrLen = pBuffer[iOffset++]; + // copy string + ds_strsubzcpy(pHeaderName, iNameSize, (const char *)pBuffer+iOffset, iHdrLen); + iOffset += iHdrLen; + // get value type + *pValueType = (AWSHeaderValueE)pBuffer[iOffset++]; + // get value length (assume string for now) $$todo - add support for other types + if ((*pValueType == AWS_BYTE_ARRAY) || (*pValueType == AWS_STRING)) + { + iValLen = pBuffer[iOffset++] << 8; + iValLen |= pBuffer[iOffset++]; + } + // copy value + ds_memcpy_s(pHeaderValue, *pValueSize, pBuffer+iOffset, iValLen); + iOffset += iValLen; + // null-terminate if string + if (*pValueType == AWS_STRING) + { + pHeaderValue[iValLen] = '\0'; + } + *pValueSize = iValLen; + return(iOffset); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function AWSSignSigV4 + + \Description + Sign request as per Amazon Signature Version 4 Signing Process: + https://docs.aws.amazon.com/en_us/general/latest/gr/signature-version-4.html + + \Input *pHeader - http header + \Input iHeaderSize - size of header buffer + \Input *pRequest - request body + \Input *pKeyInfo - signing key info ("keyid:key") + \Input *pService - AWS service + \Input *pSignInfo - [out, optional] storage for signing info for later use + + \Output + int32_t - updated header length + + \Version 12/26/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t AWSSignSigV4(char *pHeader, int32_t iHeaderSize, const char *pRequest, const char *pKeyInfo, const char *pService, AWSSignInfoT *pSignInfo) +{ + char strCanonHdrs[512]="", strSignedHdrs[256]="", strDateTime[32]="", strHost[256], strQuery[128]=""; + char strSig[(CRYPTSHA256_HASHSIZE*2)+1]; + char strRegion[32], strSign[256], strDate[9], strMethod[8], strUri[128]; + char strKey[64], strKeyId[32], strKeyPath[128]; + uint8_t aSigBuf[CRYPTSHA256_HASHSIZE]; + int32_t iOffset; + + // get/assemble info we need to sign request + + // get DateTime for X-Amz-Date header + _AWSGetDateTime(strDateTime, sizeof(strDateTime), strDate, sizeof(strDate)); + // append DateTime to header so it will be included in canonical/signed headers + iOffset = (int32_t)strlen(pHeader); + iOffset += ds_snzprintf(pHeader+iOffset, iHeaderSize-iOffset, "X-Amz-Date: %s\r\n", strDateTime); + // create canonical and signed header lists + _AWSSignGetHeaders(pHeader, strCanonHdrs, sizeof(strCanonHdrs), strSignedHdrs, sizeof(strSignedHdrs), strHost, sizeof(strHost), strMethod, sizeof(strMethod), strUri, sizeof(strUri), strQuery, sizeof(strQuery)); + // extract region and date info + _AWSGetHostInfo(strHost, strRegion, sizeof(strRegion)); + // extract keyid and key from secret + _AWSGetKeyInfo(pKeyInfo, strKeyId, sizeof(strKeyId), strKey, sizeof(strKey)); + // create keypath + ds_snzprintf(strKeyPath, sizeof(strKeyPath), "%s/%s/%s", strDate, strRegion, pService); + + // sign the request as per https://docs.aws.amazon.com/en_us/general/latest/gr/signature-version-4.html + + // Task 1: Create a Canonical Request for Signature Version 4 + _AWSSignCreateCanonicalRequest(strSig, sizeof(strSig), pHeader, pRequest, strDateTime, strCanonHdrs, strSignedHdrs, strMethod, strUri, strQuery); + // Task 2: Create a String to Sign for Signature Version 4 + _AWSSignCreateStringToSign(strSign, sizeof(strSign), "AWS4-HMAC-SHA256", strDateTime, strKeyPath, strSig); + NetPrintfVerbose((AWS_DEBUG, 0, "aws: StringToSign:\n%s\n", strSign)); + // Task 3: Calculate the Signature for AWS Signature Version 4 and convert it to a hexstring + _AWSSignCreateSignature(aSigBuf, sizeof(aSigBuf), strSign, strKey, strDate, strRegion, pService); + ds_fmtoctstring_lc(strSig, sizeof(strSig), aSigBuf, sizeof(aSigBuf)); + // Task 4: Create the Authorization header + iOffset += _AWSSignCreateAuthorization(pHeader+iOffset, iHeaderSize-iOffset, strSig, strKeyId, strDateTime, strKeyPath, strSignedHdrs); + + // save signing info + if (pSignInfo != NULL) + { + ds_strnzcpy(pSignInfo->strService, pService, sizeof(pSignInfo->strService)); + ds_strnzcpy(pSignInfo->strRegion, strRegion, sizeof(pSignInfo->strRegion)); + ds_strnzcpy(pSignInfo->strKeyPath, strKeyPath, sizeof(pSignInfo->strKeyPath)); + ds_strnzcpy(pSignInfo->strSignature, strSig, sizeof(pSignInfo->strSignature)); + ds_strnzcpy(pSignInfo->strKey, strKey, sizeof(pSignInfo->strKey)); + } + + // return updated header length + return(iOffset); +} + +/*F********************************************************************************/ +/*! + \Function AWSWriteEvent + + \Description + Write an AWS signed binary event + + \Input *pBuffer - [out] buffer to write event + \Input iBufSize - size of buffer + \Input *pData - event data + \Input *pDataSize - [in/out] size of input data available/written + \Input *pEventType - event type name + \Input *pSignInfo - [in/out] signing info for event + + \Output + int32_t - size of event data written + + \Notes + The signature field in the signing info is used to sign the event, and + then updated with the new signature; AWS calls this "signature chaining". + + \Version 01/16/2019 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t AWSWriteEvent(uint8_t *pBuffer, int32_t iBufSize, const uint8_t *pData, int32_t *pDataSize, const char *pEventType, AWSSignInfoT *pSignInfo) +{ + int32_t iOffset=0, iHdrOff, iHdrLen, iAudOff, iAudLen, iSigOff, uCrc32; + uint64_t uTimeMs = ds_timeinsecs() * 1000; + static const uint8_t aZeroBuf[CRYPTSHA256_HASHSIZE] = { 0 }; + static const char *pContentType = "application/octet-stream"; + static const char *pMessageType = "event"; + const uint8_t *pSignData=(const uint8_t *)""; + int32_t iSignDataSize=0; + + // reserve room for outer chunk prelude + iOffset += AWS_EVENT_PRELUDE_SIZE; + + // write outer chunk headers + iOffset += _AWSWriteEventHeader(pBuffer+iOffset, iBufSize-iOffset, ":date", (const uint8_t *)&uTimeMs, sizeof(uTimeMs), AWS_TIMESTAMP); + iHdrOff = iOffset; + iOffset += _AWSWriteEventHeader(pBuffer+iOffset, iBufSize-iOffset, ":chunk-signature", aZeroBuf, sizeof(aZeroBuf), AWS_BYTE_ARRAY); + iSigOff = iOffset-sizeof(aZeroBuf); + iHdrLen = iOffset-AWS_EVENT_PRELUDE_SIZE; + + if (pData != NULL) + { + // reserve room for event chunk prelude + iAudOff = iOffset; + iOffset += AWS_EVENT_PRELUDE_SIZE; + + // write event chunk headers + iOffset += _AWSWriteEventHeader(pBuffer+iOffset, iBufSize-iOffset, ":event-type", (const uint8_t *)pEventType, (int32_t)strlen(pEventType), AWS_STRING); + iOffset += _AWSWriteEventHeader(pBuffer+iOffset, iBufSize-iOffset, ":content-type", (const uint8_t *)pContentType, (int32_t)strlen(pContentType), AWS_STRING); + iOffset += _AWSWriteEventHeader(pBuffer+iOffset, iBufSize-iOffset, ":message-type", (const uint8_t *)pMessageType, (int32_t)strlen(pMessageType), AWS_STRING); + iAudLen = iOffset-iAudOff-AWS_EVENT_PRELUDE_SIZE; + + // calculate how much data we can write, including the event chunk message_crc and outer chunk message_crc + *pDataSize = DS_MIN(*pDataSize, iBufSize-iOffset-4-4); + // copy event data + ds_memcpy(pBuffer+iOffset, pData, *pDataSize); + iOffset += *pDataSize; + + // finish event chunk (add four bytes for crc32) + _AWSWriteEventPrelude(pBuffer+iAudOff, iAudLen, iOffset+4-iAudOff); + + // calculate and write event chunk message_crc + uCrc32 = NetCrc32(pBuffer+iAudOff, iOffset-iAudOff, NULL); + pBuffer[iOffset++] = (uint8_t)(uCrc32>>24); + pBuffer[iOffset++] = (uint8_t)(uCrc32>>16); + pBuffer[iOffset++] = (uint8_t)(uCrc32>>8); + pBuffer[iOffset++] = (uint8_t)(uCrc32>>0); + + // locate data to sign + pSignData = pBuffer+iAudOff; + iSignDataSize = iOffset-iAudOff; + } + + // sign the event + _AWSWriteSignEvent(pBuffer+iSigOff, CRYPTSHA256_HASHSIZE, pBuffer+AWS_EVENT_PRELUDE_SIZE, iHdrOff-AWS_EVENT_PRELUDE_SIZE, pSignData, iSignDataSize, pSignInfo); + + // finish outer chunk (add four bytes for crc32) + _AWSWriteEventPrelude(pBuffer, iHdrLen, iOffset+4); + + // calculate and write outer chunk message_crc + uCrc32 = NetCrc32(pBuffer, iOffset, NULL); + pBuffer[iOffset++] = (uint8_t)(uCrc32>>24); + pBuffer[iOffset++] = (uint8_t)(uCrc32>>16); + pBuffer[iOffset++] = (uint8_t)(uCrc32>>8); + pBuffer[iOffset++] = (uint8_t)(uCrc32>>0); + + // return offset to caller + return(iOffset); +} + +/*F********************************************************************************/ +/*! + \Function AWSReadEvent + + \Description + Read an AWS signed binary event + + \Input *pBuffer - buffer pointing to event to read + \Input iBufSize - size of buffer + \Input *pEventType - [out] buffer to store header event type + \Input iEventSize - size of event type buffer + \Input *pMessage - [out] buffer to store event message + \Input *pMessageSize - [in] size of message buffer, [out] size of message + + \Output + int32_t - size of event + + \Notes + This function does not attempt to verify the CRCs. + + \Version 01/17/2019 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t AWSReadEvent(const uint8_t *pBuffer, int32_t iBufSize, char *pEventType, int32_t iEventSize, char *pMessage, int32_t *pMessageSize) +{ + char strHeaderName[128], strHeaderValue[1024]; + int32_t iOffset=0, iValueLen; + int32_t iMsgLen, iHdrLen; + AWSHeaderValueE eValueType; + int32_t iResult = -1; + + // make sure we have enough data for prelude + if (iBufSize < AWS_EVENT_PRELUDE_SIZE) + { + return(iResult); + } + + // read overall message length + iMsgLen = pBuffer[iOffset++] << 24; + iMsgLen |= pBuffer[iOffset++] << 16; + iMsgLen |= pBuffer[iOffset++] << 8; + iMsgLen |= pBuffer[iOffset++] << 0; + // read header length + iHdrLen = pBuffer[iOffset++] << 24; + iHdrLen |= pBuffer[iOffset++] << 16; + iHdrLen |= pBuffer[iOffset++] << 8; + iHdrLen |= pBuffer[iOffset++] << 0; + // skip crc $$todo - verify this? + iOffset += 4; + + // calculate actual message length (total-headers-prelude-crc32 + iMsgLen = iMsgLen-iHdrLen-AWS_EVENT_PRELUDE_SIZE-4; + + // get message headers + while (iOffset < (iHdrLen+AWS_EVENT_PRELUDE_SIZE)) + { + iOffset += _AWSReadEventHeader(pBuffer+iOffset, iBufSize-iOffset, strHeaderName, sizeof(strHeaderName), (uint8_t *)strHeaderValue, (iValueLen=(int32_t)sizeof(strHeaderValue), &iValueLen), &eValueType); + if (eValueType != AWS_STRING) + { + NetPrintfVerbose((AWS_DEBUG, 0, "aws: %s: [%d bytes] type=%d\n", strHeaderName, iValueLen, eValueType)); + continue; + } + + NetPrintfVerbose((AWS_DEBUG, 0, "aws: %s: %s\n", strHeaderName, strHeaderValue)); + if (!ds_stricmp(strHeaderName, ":event-type") || !ds_stricmp(strHeaderName, ":exception-type")) + { + ds_strnzcpy(pEventType, strHeaderValue, iEventSize); + } + } + + // copy message body + ds_strsubzcpy(pMessage, *pMessageSize, (const char *)pBuffer+iOffset, iMsgLen); + *pMessageSize = iMsgLen; + // skip past message + iOffset += iMsgLen; + // skip past crc32 + iOffset += 4; + // return offset past event to caller + return(iOffset); +} diff --git a/src/thirdparty/dirtysdk/source/util/base64.c b/src/thirdparty/dirtysdk/source/util/base64.c new file mode 100644 index 00000000..b5fe0275 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/base64.c @@ -0,0 +1,586 @@ +/*H*******************************************************************/ +/*! + \File base64.c + + \Description + This module implements Base-64 encoding/decoding as defined in RFC + 4648: https://tools.ietf.org/html/rfc4648 + + \Notes + No strict compliance check has been made. Instead it has only + been confirmed that Decode(Encode(x)) = x for various inputs + which was all that was required for the original application. + + \Copyright + Copyright (c) Electronic Arts 2003-2017. ALL RIGHTS RESERVED. + + \Version 1.0 12/11/2003 (SJB) First Version +*/ +/*******************************************************************H*/ + +/*** Include files ***************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/util/base64.h" + +/*** Defines *********************************************************/ + +/*** Type Definitions ************************************************/ + +/*** Variables *******************************************************/ + +/* + The table used in converting an unsigned char -> Base64. + This is as per the RFC. +*/ +static const char _Base64_strEncode[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* The table used in converting an unsigned char -> Base64 (URL) + This is as per the RFC: https://tools.ietf.org/html/rfc4648#section-5 */ +static const char _Base64_strEncodeUrl[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; + +/* + The table used in coverting Base64 -> unsigned char. + This is not strictly needed since this mapping can be determined + by searching _Base64_Encode, but by having it as a separate table + it means the decode can run in O(n) time where n is the length of + the input rather than O(n*m) where m is 64. + + The table is designed such that after subtracting '+' from the + base 64 value, the corresponding char value can be found by + indexing into this table. Since the characters chosen by the + Base64 designers are not contiguous in ASCII there are some holes + in the table and these are filled with -1 to indicate an invalid + value. +*/ +static const signed char _Base64_strDecode[] = +{ + 62, // + + -1, -1, -1, // ,-. + 63, // / + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // 0-9 + -1, -1, -1, -1, -1, -1, -1, // :;<=>?@ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, // A- + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // -Z + -1, -1, -1, -1, -1, -1, // [\]^_` + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // a- + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // -z +}; + +static const signed char _Base64_strDecodeUrl[] = +{ + -1, -1, // +, + 62, // - + -1, -1, // ./ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // 0-9 + -1, -1, -1, -1, -1, -1, -1, // :;<=>?@ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, // A- + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // -Z + -1, -1, -1, -1, // [\]^ + 63, // _ + -1, // ` + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // a- + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // -z +}; + +/*** Private functions ***********************************************/ + +/*F*******************************************************************/ +/*! + \Function _Base64Encode + + \Description + Base-64 encode a string. + + \Input iInputLen - the length of the input + \Input *pInput - the input + \Input *pOutput - the output + \Input iOutputLen - the output buffer size + \Input *pEncode - the table to use for encoding + \Input bPadded - do you want padding to be encoded? + + \Output + int32_t - encoded length, or -1 on error + + \Notes + pOutput is NUL terminated. + + It is assumed that pOutput is large enough to hold + Base64EncodedSize(iInputLen)+1 bytes. + + The following chart should help explain the various bit shifts + that are in the code. On the top are the 8-bit bytes of the + input and on the bottom are the 6-bit bytes of the output :- + + \verbatim + | 0 | 1 | 2 | in + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 0 | 1 | 2 | 3 | out + \endverbatim + + As the above shows the out0 is the top 6 bits of in0, out1 is + the bottom two bits of in0 along with the top 4 bits of in1, + ... etc. + + To speed things up the encoder tries to work on chunks of 3 + input bytes at a time since that produces 4 output bytes + without having to maintain any inter-byte processing state. + + For any input that is not a multiple of 3 then 1 or 2 padding + bytes are are added as appropriate as described in the RFC. + + \Version 12/11/2002 (sbevan) First Version +*/ +/*******************************************************************F*/ +static int32_t _Base64Encode(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen, const char *pEncode, uint8_t bPadded) +{ + int32_t iInp, iOut; + + // validate output buffer size, including null termination + if ((Base64EncodedSize(iInputLen)+1) > iOutputLen) + { + return(-1); + } + + // encode the data + for (iInp=0, iOut=0; iInputLen >= 3; iOut += 4, iInp += 3, iInputLen -= 3) + { + pOutput[iOut+0] = pEncode[(pInput[iInp]>>2)&0x3f]; + pOutput[iOut+1] = pEncode[((pInput[iInp]&0x3)<<4)|((pInput[iInp+1]>>4)&0x0f)]; + pOutput[iOut+2] = pEncode[((pInput[iInp+1]&0xf)<<2)|((pInput[iInp+2]>>6)&0x03)]; + pOutput[iOut+3] = pEncode[(pInput[iInp+2]&0x3f)]; + } + switch (iInputLen) + { + case 1: + pOutput[iOut+0] = pEncode[(pInput[iInp]>>2)&0x3f]; + pOutput[iOut+1] = pEncode[((pInput[iInp]&0x3)<<4)]; + + if (bPadded) + { + pOutput[iOut+2] = '='; + pOutput[iOut+3] = '='; + iOut += 4; + } + else + { + iOut += 2; + } + break; + case 2: + pOutput[iOut+0] = pEncode[(pInput[iInp]>>2)&0x3f]; + pOutput[iOut+1] = pEncode[((pInput[iInp]&0x3)<<4)|((pInput[iInp+1]>>4)&0x0f)]; + pOutput[iOut+2] = pEncode[((pInput[iInp+1]&0xf)<<2)]; + + if (bPadded) + { + pOutput[iOut+3] = '='; + iOut += 4; + } + else + { + iOut += 3; + } + break; + } + pOutput[iOut] = '\0'; + return(iOut); +} + +/*F*******************************************************************/ +/*! + \Function _Base64Decode + + \Description + Decode a Base-64 encoded string. + + \Input *pInput - the Base-64 encoded string + \Input iInputLen - the length of the encoded input + \Input *pOutput - [out] the decoded string, or NULL to calculate output size + \Input iOutputLen - size of output buffer + \Input *pDecode - the table to use for the decoding + + \Output + int32_t - decoded size on success, zero on error. + + \Notes + The only reasons for failure are if the input base64 sequence does + not amount to a number of characters comprising an even multiple + of four characters, or if the input contains a character not in + the Base64 character set and not a whitespace character. This + behavior is mentioned in the RFC: + + "In base64 data, characters other than those in Table 1, + line breaks, and other white space probably indicate a + transmission error, about which a warning message or even + a message rejection might be appropriate under some + circumstances." + + If pOutput is NULL, the function will return the number of + characters required to buffer the output, or zero on error. + + The following chart should help explain the various bit shifts + that are in the code. On the top are the 6-bit bytes in the + input and and on the bottom are the 8-bit bytes of the output :- + + \verbatim + | 0 | 1 | 2 | 3 | in + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 0 | 1 | 2 | out + \endverbatim + + As the above shows the out0 consists of all of in0 and the top + 2 bits of in1, out1 is the bottom 4 bits of in1 and the top 4 + bits of in2, ... etc. + + \Version 12/11/2002 (sbevan) First Version + \Version 01/29/2009 (jbrookes) Enhanced to skip whitespace, added decoded length functionality +*/ +/*******************************************************************F*/ +static int32_t _Base64Decode(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen, const signed char *pDecode) +{ + char ci[4]; + int8_t co0, co1, co2, co3; + int32_t iInputCnt, iInputOff, iOutputOff; + + for (iInputOff = 0, iOutputOff = 0; iInputOff < iInputLen; ) + { + // scan input + for (iInputCnt = 0; (iInputCnt < 4) && (iInputOff < iInputLen) && (pInput[iInputOff] != '\0') && (iOutputOff < iOutputLen); iInputOff += 1) + { + // ignore whitespace + if ((pInput[iInputOff] == ' ') || (pInput[iInputOff] == '\t') || (pInput[iInputOff] == '\r') || (pInput[iInputOff] == '\n')) + { + continue; + } + // basic range validation + else if ((pInput[iInputOff] < '+') || (pInput[iInputOff] > 'z')) + { + return(0); + } + // fetch input character + else + { + ci[iInputCnt++] = pInput[iInputOff]; + } + } + // if we didn't get anything, we're done + if (iInputCnt == 0) + { + break; + } + if (iInputCnt < 4) + { + // error if we did not get at least 2 + if (iInputCnt < 2) + { + return(0); + } + // otherwise for everything under the quad, default to padding + if (iInputCnt < 3) + { + ci[2] = '='; + } + ci[3] = '='; + } + // decode the sequence + co0 = pDecode[(int32_t)ci[0]-'+']; + co1 = pDecode[(int32_t)ci[1]-'+']; + co2 = pDecode[(int32_t)ci[2]-'+']; + co3 = pDecode[(int32_t)ci[3]-'+']; + + if ((co0 >= 0) && (co1 >= 0)) + { + if ((co2 >= 0) && (co3 >= 0)) + { + if ((pOutput != NULL) && ((iOutputLen - iOutputOff) > 2)) + { + pOutput[iOutputOff+0] = (co0<<2)|((co1>>4)&0x3); + pOutput[iOutputOff+1] = (co1&0x3f)<<4|((co2>>2)&0x3F); + pOutput[iOutputOff+2] = ((co2&0x3)<<6)|co3; + } + iOutputOff += 3; + } + else if ((co2 >= 0) && (ci[3] == '=')) + { + if ((pOutput != NULL) && ((iOutputLen - iOutputOff) > 1)) + { + pOutput[iOutputOff+0] = (co0<<2)|((co1>>4)&0x3); + pOutput[iOutputOff+1] = (co1&0x3f)<<4|((co2>>2)&0x3F); + } + iOutputOff += 2; + // consider input complete + iInputOff = iInputLen; + } + else if ((ci[2] == '=') && (ci[3] == '=')) + { + if ((pOutput != NULL) && ((iOutputLen - iOutputOff) > 0)) + { + pOutput[iOutputOff+0] = (co0<<2)|((co1>>4)&0x3); + } + iOutputOff += 1; + // consider input complete + iInputOff = iInputLen; + } + else + { + // illegal input character + return(0); + } + } + else + { + // illegal input character + return(0); + } + } + // return length of decoded data or zero if decoding failed + return((iInputOff == iInputLen) ? iOutputOff : 0); +} + +/*** Public functions ************************************************/ + +/*F*******************************************************************/ +/*! + \Function Base64Encode + + \Deprecated + The calling convention for Base64Encode will be replaced with + that of Base64Encode2 in a future release. + + \Description + Base-64 encode a string. + + \Input iInputLen - the length of the input + \Input *pInput - the input + \Input *pOutput - the output + + \Notes + pOutput is NUL terminated. + + It is assumed that pOutput is large enough to hold + Base64EncodedSize(iInputLen)+1 bytes. + + \Version 1.0 12/11/2002 (SJB) First Version +*/ +/*******************************************************************F*/ +void Base64Encode(int32_t iInputLen, const char *pInput, char *pOutput) +{ + Base64Encode2(pInput, iInputLen, pOutput, 0x7fffffff); +} + +/*F*******************************************************************/ +/*! + \Function Base64Encode2 + + \Description + Base-64 encode a string. + + \Input iInputLen - the length of the input + \Input *pInput - the input + \Input *pOutput - the output + \Input iOutputLen - the output buffer size + + \Output + int32_t - encoded length, or -1 on error + + \Notes + pOutput is NUL terminated. + + \Version 12/11/2002 (sbevan) First Version +*/ +/*******************************************************************F*/ +int32_t Base64Encode2(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen) +{ + return(_Base64Encode(pInput, iInputLen, pOutput, iOutputLen, _Base64_strEncode, TRUE)); +} + +/*F*******************************************************************/ +/*! + \Function Base64Decode + + \Deprecated + The calling convention for Base64Decode will be replaced with + that of Base64Decode3 in a future release. + + \Description + Decode a Base-64 encoded string. + + \Input iInputLen - the length of the encoded input + \Input *pInput - the Base-64 encoded string + \Input *pOutput - [out] the decoded string, or NULL to calculate output size + + \Output + int32_t - true on success, false on error. + + \Notes + It is assumed that pOutput is large enough to hold + Base64DecodedSize(iInputLen) bytes. If pOutput is NULL, + the function will return the number of characters required to + buffer the output or zero on error. + + \Version 12/11/2002 (sbevan) First Version + \Version 01/29/2009 (jbrookes) Enhanced to skip whitespace, added decoded length functionality +*/ +/*******************************************************************F*/ +int32_t Base64Decode(int32_t iInputLen, const char *pInput, char *pOutput) +{ + int32_t iOutputLen = Base64Decode2(iInputLen, pInput, pOutput); + if (pOutput != NULL) + { + iOutputLen = iOutputLen ? TRUE : FALSE; + } + return(iOutputLen); +} + +/*F*******************************************************************/ +/*! + \Function Base64Decode2 + + \Deprecated + The calling convention for Base64Decode2 will be replaced with + that of Base64Decode3 in a future release. + + \Description + Decode a Base-64 encoded string. + + \Input iInputLen - the length of the encoded input + \Input *pInput - the Base-64 encoded string + \Input *pOutput - [out[ the decoded string, or NULL to calculate output size + + \Output + int32_t - decoded size on success, zero on error. + + \Notes + It is assumed that pOutput is large enough to hold + Base64DecodedSize(iInputLen) bytes. If pOutput is NULL, + the function will return the number of characters required to + buffer the output or zero on error. + + \Version 12/11/2002 (sbevan) First Version + \Version 01/29/2009 (jbrookes) Enhanced to skip whitespace, added decoded length functionality +*/ +/*******************************************************************F*/ +int32_t Base64Decode2(int32_t iInputLen, const char *pInput, char *pOutput) +{ + return(Base64Decode3(pInput, iInputLen, pOutput, 0x7fffffff)); +} + +/*F*******************************************************************/ +/*! + \Function Base64Decode3 + + \Description + Decode a Base-64 encoded string. + + \Input *pInput - the Base-64 encoded string + \Input iInputLen - the length of the encoded input + \Input *pOutput - [out] the decoded string, or NULL to calculate output size + \Input iOutputLen - size of output buffer + + \Output + int32_t - decoded size on success, zero on error. + + \Notes + If pOutput is NULL, the function will return the number of + characters required to buffer the output, or zero on error. + + \Version 12/11/2002 (sbevan) First Version + \Version 01/29/2009 (jbrookes) Enhanced to skip whitespace, added decoded length functionality +*/ +/*******************************************************************F*/ +int32_t Base64Decode3(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen) +{ + return(_Base64Decode(pInput, iInputLen, pOutput, iOutputLen, _Base64_strDecode)); +} + +/*F*******************************************************************/ +/*! + \Function Base64EncodeUrl + + \Description + Base-64 encode a string url. + + \Input *pInput - the input + \Input iInputLen - the length of the input + \Input *pOutput - the output + \Input iOutputLen - the output buffer size + + \Output + int32_t - encoded length, or -1 on error + + \Notes + pOutput is NUL terminated. + + \Version 11/15/2017 (eesponda) +*/ +/*******************************************************************F*/ +int32_t Base64EncodeUrl(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen) +{ + return(Base64EncodeUrl2(pInput, iInputLen, pOutput, iOutputLen, TRUE)); +} + +/*F*******************************************************************/ +/*! + \Function Base64EncodeUrl2 + + \Description + Base-64 encode a string url with extra options + + \Input *pInput - the input + \Input iInputLen - the length of the input + \Input *pOutput - the output + \Input iOutputLen - the output buffer size + \Input bPadded - is the output padded? + + \Output + int32_t - encoded length, or -1 on error + + \Notes + pOutput is NUL terminated. + + \Version 05/20/2020 (eesponda) +*/ +/*******************************************************************F*/ +int32_t Base64EncodeUrl2(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen, uint8_t bPadded) +{ + return(_Base64Encode(pInput, iInputLen, pOutput, iOutputLen, _Base64_strEncodeUrl, bPadded)); +} + +/*F*******************************************************************/ +/*! + \Function Base64DecodeUrl + + \Description + Decode a Base-64 encoded url string. + + \Input *pInput - the Base-64 encoded string + \Input iInputLen - the length of the encoded input + \Input *pOutput - [out] the decoded string, or NULL to calculate output size + \Input iOutputLen - size of output buffer + + \Output + int32_t - decoded size on success, zero on error. + + \Notes + If pOutput is NULL, the function will return the number of + characters required to buffer the output, or zero on error. + + \Version 11/15/2017 (eesponda) +*/ +/*******************************************************************F*/ +int32_t Base64DecodeUrl(const char *pInput, int32_t iInputLen, char *pOutput, int32_t iOutputLen) +{ + return(_Base64Decode(pInput, iInputLen, pOutput, iOutputLen, _Base64_strDecodeUrl)); +} + diff --git a/src/thirdparty/dirtysdk/source/util/binary7.c b/src/thirdparty/dirtysdk/source/util/binary7.c new file mode 100644 index 00000000..cbafec29 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/binary7.c @@ -0,0 +1,157 @@ +/*H*************************************************************************************/ +/*! + \File binary7.c + + \Description + This module provides routines to encode/decode binary7 data to/from a buffer. + + \Copyright + Copyright (c) Electronic Arts 2009. ALL RIGHTS RESERVED. + + \Version 1.0 11/02/2009 (cadam) First version +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include +#include +#include +#include + +// include platform types and functions +#include "DirtySDK/platform.h" + +// self-include +#include "DirtySDK/util/binary7.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +/*** Private Functions *****************************************************************/ + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function Binary7Encode + + \Description + Encode binary data to fit in 7-bit high-ASCII characters. All of the requested + data will be encoded in a reversible form that is slightly larger than the + original data. + + \Input *pDst - destination buffer (will hold encoded data) + \Input iDstLen - destination buffer max size (in bytes) + \Input *pSrc - source buffer + \Input iSrcLen - source buffer length (in bytes) + \Input bTerminate - TRUE to terminate the buffer, else FALSE. + + \Output + int32_t - < 0 indicates error; >= 0 indicates bytes in converted data + + \Notes + If there is insufficient space to store all the encoded data, an error is + returned immediately; no data is encoded or written. + Storage requirements are (slen*8+6)/7 bytes. + + \Version 01/20/2004 (djones) +*/ +/*************************************************************************************F*/ +char *Binary7Encode(unsigned char *pDst, int32_t iDstLen, unsigned const char *pSrc, int32_t iSrcLen, uint32_t bTerminate) +{ + int32_t iBytesLeft = iSrcLen; + uint32_t uBuffer = 0; // buffer for holding leftover bits + int32_t iBits = 0; // number of bits currently in buffer + + // verify destination buffer is big enough to hold encoded data. + if (iDstLen < ((((iSrcLen * 8) + 6) / 7) + (signed)bTerminate)) + { + return(NULL); + } + + // encode all bytes. + while (iBytesLeft-- > 0) + { + uBuffer |= (*pSrc++) << iBits; // merge new bits up above leftover bits + iBits += 8; + while (iBits >= 7) // have enough bits to write out an encoded byte + { + *pDst++ = 0x80 | (uBuffer & 0x7F); // write encoded byte + uBuffer >>= 7; // discard written bits + iBits -= 7; + } + } + + // a few bits left over? + if (iBits > 0) + { + *pDst++ = 0x80 | uBuffer; // write leftover bits + } + + // terminate? + if (bTerminate) + { + *pDst = '\0'; + } + + // return pointer past end of dest buffer + return((char *)pDst); +} + +/*F*************************************************************************************/ +/*! + \Function Binary7Decode + + \Description + Decode binary data from 7-bit high-ASCII characters to 8-bit data. + + \Input *pDst - destination buffer (will hold decoded data) + \Input iDstLen - destination buffer max size (in bytes) + \Input *pSrc` - source buffer + + \Output + int32_t - < 0 indicates error; >= 0 indicates bytes in converted data + + \Notes + Decoding will stop at the first low-ASCII character, or until dlen bytes have + been written, whichever comes first. Storage requirements are slen*7/8 bytes. + + \Version 01/20/2004 (djones) +*/ +/*************************************************************************************F*/ +unsigned const char *Binary7Decode(unsigned char *pDst, int32_t iDstLen, unsigned const char *pSrc) +{ + uint32_t uBuffer = 0; // buffer for holding leftover bits + int32_t iBits = 0; // number of bits currently in buffer + + // Begin decoding bytes. + while ((*pSrc & 0x80) && (iDstLen > 0)) + { + uBuffer |= (*pSrc++ & 0x7F) << iBits; // merge new bits up above leftover bits + iBits += 7; + if (iBits >= 8) // have enough bits to write out a decoded byte + { + *pDst++ = uBuffer & 0xFF; // write decoded byte + uBuffer >>= 8; // discard written bits + iBits -= 8; + iDstLen -= 1; + } + } + + // Unlike encoding, where all source bits must be converted, + // in decoding, extra bits are discarded, so no special + // handling is required for leftover bits here. + + // return pointer past end of dest buffer + return(pSrc); +} + diff --git a/src/thirdparty/dirtysdk/source/util/hpack.c b/src/thirdparty/dirtysdk/source/util/hpack.c new file mode 100644 index 00000000..7e15d380 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/hpack.c @@ -0,0 +1,1430 @@ +/*H********************************************************************************/ +/*! + \File hpack.c + + \Description + This module implements a decode/encoder based on the HPACK spec + (https://tools.ietf.org/html/rfc7541). Which is used for encoding/decoding + the HEADERS frame in the HTTP/2 protocol. + + \Copyright + Copyright (c) Electronic Arts 2016. ALL RIGHTS RESERVED. +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/proto/protohttputil.h" +#include "DirtySDK/util/hpack.h" + +/*** Defines **********************************************************************/ + +//! size of the static table +#define HPACK_STATIC_TABLE_SIZE (61) + +//! size of huffman code table +#define HPACK_HUFFMAN_TABLE_SIZE (256) + +//! overhead for a table entry +#define HPACK_TABLE_ENTRY_OVERHEAD (32) + +//! controls if we log the dynamic table info +#define HPACK_DYNAMICTABLE_LOGGING (DIRTYCODE_LOGGING && FALSE) + +//! flags for different header field types +#define HPACK_INDEXED_HEADER_FIELD (1 << 7) +#define HPACK_LITERAL_HEADER_FIELD (1 << 6) +#define HPACK_DYNAMIC_TABLE_UPDATE (1 << 5) +#define HPACK_LITERAL_HEADER_FIELD_NEVER (1 << 4) + +/*** Type Definitions *************************************************************/ + +//! defines an entry in the dynamic table +typedef struct TableEntryT +{ + char *pName; //!< header name + char *pValue; //!< header value +} TableEntryT; + +//! defines an entry in the static table +typedef struct StaticTableEntryT +{ + const char *pName; //!< header name + const char *pValue; //!< header value +} StaticTableEntryT; + +//! linked list node we use to define our dynamic table +typedef struct DynamicNodeT +{ + struct DynamicNodeT *pNext; + struct DynamicNodeT *pPrev; + + TableEntryT Entry; +} DynamicNodeT; + +//! entry in the huffman tree +typedef struct HuffmanCodeT +{ + uint32_t uCode; //!< the huffman code + uint32_t uLen; //!< the length of the code in bits +} HuffmanCodeT; + +//! tree used for huffman decoding +typedef struct HuffmanNodeT +{ + struct HuffmanNodeT *pLeft; //!< 1 bit + struct HuffmanNodeT *pRight; //!< 0 bit + + uint8_t uChar; //!< the character represented by the sequence + uint8_t _pad[3]; +} HuffmanNodeT; + +//! data used to keep track of the huffman encoding +typedef struct HuffmanEncodeT +{ + uint32_t aResult[256]; + uint32_t uCount; + uint32_t uNextBoundary; +} HuffmanEncodeT; + +//! module ref for our encoder/decoder +struct HpackRefT +{ + DynamicNodeT *pHead; //!< points to the head of the dynamic table (for insertion/lookup) + DynamicNodeT *pTail; //!< points to the tail of the dynamic table (for removal) + + uint32_t uTableSize; //!< current size of the dynamic table + uint32_t uTableMax; //!< maximum size of the dynamic table + + HuffmanNodeT *pHuffmanTree; //!< huffman tree used for decoding + + //! memgroup data + int32_t iMemGroup; + void *pMemGroupUserData; + + uint8_t bUpdatePending; //!< there is a pending update to the dynamic table + uint8_t _pad[3]; +}; + +/*** Variables ********************************************************************/ + +//! our fixed static table +static const StaticTableEntryT Hpack_aStaticTable[HPACK_STATIC_TABLE_SIZE] = +{ + { ":authority", "" }, + { ":method", "GET" }, + { ":method", "POST" }, + { ":path", "/" }, + { ":path", "/index.html" }, + { ":scheme", "http" }, + { ":scheme", "https" }, + { ":status", "200" }, + { ":status", "204" }, + { ":status", "206" }, + { ":status", "304" }, + { ":status", "400" }, + { ":status", "404" }, + { ":status", "500" }, + { "accept-charset", "" }, + { "accept-encoding", "gzip, deflate" }, + { "accept-language", "" }, + { "accept-ranges", "" }, + { "accept", "" }, + { "access-control-allow-origin", "" }, + { "age", "" }, + { "allow", "" }, + { "authorization", "" }, + { "cache-control", "" }, + { "content-disposition", "" }, + { "content-encoding", "" }, + { "content-language", "" }, + { "content-length", "" }, + { "content-location", "" }, + { "content-range", "" }, + { "content-type", "" }, + { "cookie", "" }, + { "date", "" }, + { "etag", "" }, + { "expect", "" }, + { "expires", "" }, + { "from", "" }, + { "host", "" }, + { "if-match", "" }, + { "if-modified-since", "" }, + { "if-none-match", "" }, + { "if-range", "" }, + { "if-unmodified-since", "" }, + { "last-modified", "" }, + { "link", "" }, + { "location", "" }, + { "max-forwards", "" }, + { "proxy-authenticate", "" }, + { "proxy-authorization", "" }, + { "range", "" }, + { "referer", "" }, + { "refresh", "" }, + { "retry-after", "" }, + { "server", "" }, + { "set-cookie", "" }, + { "strict-transport-security", "" }, + { "transfer-encoding", "" }, + { "user-agent", "" }, + { "vary", "" }, + { "via", "" }, + { "www-authenticate", "" } +}; + +//! table of huffman codes used for encoding / decoding based on +//! https://tools.ietf.org/html/rfc7541#appendix-B +static const HuffmanCodeT Hpack_aHuffmanCodes[256] = +{ + { 0x00001ff8, 13 }, { 0x007fffd8, 23 }, { 0x0fffffe2, 28 }, { 0x0fffffe3, 28 }, { 0x0fffffe4, 28 }, { 0x0fffffe5, 28 }, { 0x0fffffe6, 28 }, { 0x0fffffe7, 28 }, /* 0 - 7 */ + { 0x0fffffe8, 28 }, { 0x00ffffea, 24 }, { 0x3ffffffc, 30 }, { 0x0fffffe9, 28 }, { 0x0fffffea, 28 }, { 0x3ffffffd, 30 }, { 0x0fffffeb, 28 }, { 0x0fffffec, 28 }, /* 8 - 15 */ + { 0x0fffffed, 28 }, { 0x0fffffee, 28 }, { 0x0fffffef, 28 }, { 0x0ffffff0, 28 }, { 0x0ffffff1, 28 }, { 0x0ffffff2, 28 }, { 0x3ffffffe, 30 }, { 0x0ffffff3, 28 }, /* 16 - 23 */ + { 0x0ffffff4, 28 }, { 0x0ffffff5, 28 }, { 0x0ffffff6, 28 }, { 0x0ffffff7, 28 }, { 0x0ffffff8, 28 }, { 0x0ffffff9, 28 }, { 0x0ffffffa, 28 }, { 0x0ffffffb, 28 }, /* 24 - 31 */ + { 0x00000014, 6 }, { 0x000003f8, 10 }, { 0x000003f9, 10 }, { 0x00000ffa, 12 }, { 0x00001ff9, 13 }, { 0x00000015, 6 }, { 0x000000f8, 8 }, { 0x000007fa, 11 }, /* 32 - 39 */ + { 0x000003fa, 10 }, { 0x000003fb, 10 }, { 0x000000f9, 8 }, { 0x000007fb, 11 }, { 0x000000fa, 8 }, { 0x00000016, 6 }, { 0x00000017, 6 }, { 0x00000018, 6 }, /* 40 - 47 */ + { 0x00000000, 5 }, { 0x00000001, 5 }, { 0x00000002, 5 }, { 0x00000019, 6 }, { 0x0000001a, 6 }, { 0x0000001b, 6 }, { 0x0000001c, 6 }, { 0x0000001d, 6 }, /* 48 - 55 */ + { 0x0000001e, 6 }, { 0x0000001f, 6 }, { 0x0000005c, 7 }, { 0x000000fb, 8 }, { 0x00007ffc, 15 }, { 0x00000020, 6 }, { 0x00000ffb, 12 }, { 0x000003fc, 10 }, /* 56 - 63 */ + { 0x00001ffa, 13 }, { 0x00000021, 6 }, { 0x0000005d, 7 }, { 0x0000005e, 7 }, { 0x0000005f, 7 }, { 0x00000060, 7 }, { 0x00000061, 7 }, { 0x00000062, 7 }, /* 64 - 71 */ + { 0x00000063, 7 }, { 0x00000064, 7 }, { 0x00000065, 7 }, { 0x00000066, 7 }, { 0x00000067, 7 }, { 0x00000068, 7 }, { 0x00000069, 7 }, { 0x0000006a, 7 }, /* 72 - 79 */ + { 0x0000006b, 7 }, { 0x0000006c, 7 }, { 0x0000006d, 7 }, { 0x0000006e, 7 }, { 0x0000006f, 7 }, { 0x00000070, 7 }, { 0x00000071, 7 }, { 0x00000072, 7 }, /* 80 - 87 */ + { 0x000000fc, 8 }, { 0x00000073, 7 }, { 0x000000fd, 8 }, { 0x00001ffb, 13 }, { 0x0007fff0, 19 }, { 0x00001ffc, 13 }, { 0x00003ffc, 14 }, { 0x00000022, 6 }, /* 88 - 95 */ + { 0x00007ffd, 15 }, { 0x00000003, 5 }, { 0x00000023, 6 }, { 0x00000004, 5 }, { 0x00000024, 6 }, { 0x00000005, 5 }, { 0x00000025, 6 }, { 0x00000026, 6 }, /* 96 - 103 */ + { 0x00000027, 6 }, { 0x00000006, 5 }, { 0x00000074, 7 }, { 0x00000075, 7 }, { 0x00000028, 6 }, { 0x00000029, 6 }, { 0x0000002a, 6 }, { 0x00000007, 5 }, /* 104 - 111 */ + { 0x0000002b, 6 }, { 0x00000076, 7 }, { 0x0000002c, 6 }, { 0x00000008, 5 }, { 0x00000009, 5 }, { 0x0000002d, 6 }, { 0x00000077, 7 }, { 0x00000078, 7 }, /* 112 - 119 */ + { 0x00000079, 7 }, { 0x0000007a, 7 }, { 0x0000007b, 7 }, { 0x00007ffe, 15 }, { 0x000007fc, 11 }, { 0x00003ffd, 14 }, { 0x00001ffd, 13 }, { 0x0ffffffc, 28 }, /* 120 - 127 */ + { 0x000fffe6, 20 }, { 0x003fffd2, 22 }, { 0x000fffe7, 20 }, { 0x000fffe8, 20 }, { 0x003fffd3, 22 }, { 0x003fffd4, 22 }, { 0x003fffd5, 22 }, { 0x007fffd9, 23 }, /* 128 - 135 */ + { 0x003fffd6, 22 }, { 0x007fffda, 23 }, { 0x007fffdb, 23 }, { 0x007fffdc, 23 }, { 0x007fffdd, 23 }, { 0x007fffde, 23 }, { 0x00ffffeb, 24 }, { 0x007fffdf, 23 }, /* 136 - 143 */ + { 0x00ffffec, 24 }, { 0x00ffffed, 24 }, { 0x003fffd7, 22 }, { 0x007fffe0, 23 }, { 0x00ffffee, 24 }, { 0x007fffe1, 23 }, { 0x007fffe2, 23 }, { 0x007fffe3, 23 }, /* 144 - 151 */ + { 0x007fffe4, 23 }, { 0x001fffdc, 21 }, { 0x003fffd8, 22 }, { 0x007fffe5, 23 }, { 0x003fffd9, 22 }, { 0x007fffe6, 23 }, { 0x007fffe7, 23 }, { 0x00ffffef, 24 }, /* 152 - 159 */ + { 0x003fffda, 22 }, { 0x001fffdd, 21 }, { 0x000fffe9, 20 }, { 0x003fffdb, 22 }, { 0x003fffdc, 22 }, { 0x007fffe8, 23 }, { 0x007fffe9, 23 }, { 0x001fffde, 21 }, /* 160 - 167 */ + { 0x007fffea, 23 }, { 0x003fffdd, 22 }, { 0x003fffde, 22 }, { 0x00fffff0, 24 }, { 0x001fffdf, 21 }, { 0x003fffdf, 22 }, { 0x007fffeb, 23 }, { 0x007fffec, 23 }, /* 168 - 175 */ + { 0x001fffe0, 21 }, { 0x001fffe1, 21 }, { 0x003fffe0, 22 }, { 0x001fffe2, 21 }, { 0x007fffed, 23 }, { 0x003fffe1, 22 }, { 0x007fffee, 23 }, { 0x007fffef, 23 }, /* 176 - 183 */ + { 0x000fffea, 20 }, { 0x003fffe2, 22 }, { 0x003fffe3, 22 }, { 0x003fffe4, 22 }, { 0x007ffff0, 23 }, { 0x003fffe5, 22 }, { 0x003fffe6, 22 }, { 0x007ffff1, 23 }, /* 184 - 191 */ + { 0x03ffffe0, 26 }, { 0x03ffffe1, 26 }, { 0x000fffeb, 20 }, { 0x0007fff1, 19 }, { 0x003fffe7, 22 }, { 0x007ffff2, 23 }, { 0x003fffe8, 22 }, { 0x01ffffec, 25 }, /* 192 - 199 */ + { 0x03ffffe2, 26 }, { 0x03ffffe3, 26 }, { 0x03ffffe4, 26 }, { 0x07ffffde, 27 }, { 0x07ffffdf, 27 }, { 0x03ffffe5, 26 }, { 0x00fffff1, 24 }, { 0x01ffffed, 25 }, /* 200 - 207 */ + { 0x0007fff2, 19 }, { 0x001fffe3, 21 }, { 0x03ffffe6, 26 }, { 0x07ffffe0, 27 }, { 0x07ffffe1, 27 }, { 0x03ffffe7, 26 }, { 0x07ffffe2, 27 }, { 0x00fffff2, 24 }, /* 208 - 215 */ + { 0x001fffe4, 21 }, { 0x001fffe5, 21 }, { 0x03ffffe8, 26 }, { 0x03ffffe9, 26 }, { 0x0ffffffd, 28 }, { 0x07ffffe3, 27 }, { 0x07ffffe4, 27 }, { 0x07ffffe5, 27 }, /* 216 - 223 */ + { 0x000fffec, 20 }, { 0x00fffff3, 24 }, { 0x000fffed, 20 }, { 0x001fffe6, 21 }, { 0x003fffe9, 22 }, { 0x001fffe7, 21 }, { 0x001fffe8, 21 }, { 0x007ffff3, 23 }, /* 224 - 231 */ + { 0x003fffea, 22 }, { 0x003fffeb, 22 }, { 0x01ffffee, 25 }, { 0x01ffffef, 25 }, { 0x00fffff4, 24 }, { 0x00fffff5, 24 }, { 0x03ffffea, 26 }, { 0x007ffff4, 23 }, /* 232 - 239 */ + { 0x03ffffeb, 26 }, { 0x07ffffe6, 27 }, { 0x03ffffec, 26 }, { 0x03ffffed, 26 }, { 0x07ffffe7, 27 }, { 0x07ffffe8, 27 }, { 0x07ffffe9, 27 }, { 0x07ffffea, 27 }, /* 240 - 247 */ + { 0x07ffffeb, 27 }, { 0x0ffffffe, 28 }, { 0x07ffffec, 27 }, { 0x07ffffed, 27 }, { 0x07ffffee, 27 }, { 0x07ffffef, 27 }, { 0x07fffff0, 27 }, { 0x03ffffee, 26 } /* 248 - 255 */ +}; + +// EOS (End of String) specifier +static const HuffmanCodeT Hpack_EOS = { 0x3fffffff, 30 }; + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _HpackDynamicTableGet + + \Description + Attempts to find a table entry in the static and dynamic tables + + \Input *pRef - module state + \Input iIndex - the index of the table entry we are looking for + + \Output + const TableEntryT * - the found entry or NULL + + \Version 10/05/2016 (eesponda) +*/ +/********************************************************************************F*/ +static const TableEntryT *_HpackDynamicTableGet(HpackRefT *pRef, int32_t iIndex) +{ + int32_t iCount; + const DynamicNodeT *pNode; + + // loop through the list either finding our index or hitting the end, if index is found return the entry + for (pNode = pRef->pHead, iCount = 0; pNode != NULL; pNode = pNode->pNext, iCount += 1) + { + if (iCount == iIndex) + { + break; + } + } + return(pNode != NULL ? &pNode->Entry : NULL); +} + +/*F********************************************************************************/ +/*! + \Function _HpackHuffmanTreeInsert + + \Description + Inserts a character into the huffman tree recursively + + \Input *pRef - module state + \Input **pNode - current node we are visiting + \Input uCode - huffman code + \Input uLen - number of bits in the code + \Input uChar - character corresponding to huffman code + + \Output + uint8_t - TRUE if successfully, FALSE otherwise + + \Version 10/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static uint8_t _HpackHuffmanTreeInsert(HpackRefT *pRef, HuffmanNodeT **pNode, uint32_t uCode, uint32_t uLen, uint8_t uChar) +{ + HuffmanNodeT **pTargetNode = ((uCode >> (uLen-1)) & 1) ? &(*pNode)->pLeft : &(*pNode)->pRight; + if (*pTargetNode == NULL) + { + if ((*pTargetNode = (HuffmanNodeT *)DirtyMemAlloc(sizeof(**pTargetNode), HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData)) == NULL) + { + NetPrintf(("hpack: [%p] could not allocate node for huffman tree\n", pRef)); + return(FALSE); + } + ds_memclr(*pTargetNode, sizeof(**pTargetNode)); + } + + if ((uLen-1) > 0) + { + return(_HpackHuffmanTreeInsert(pRef, pTargetNode, uCode, uLen-1, uChar)); + } + else + { + (*pTargetNode)->uChar = uChar; + return(TRUE); + } +} + +/*F********************************************************************************/ +/*! + \Function _HpackHuffmanTreeBuild + + \Description + Builds the huffman tree based on the static huffman code table + + \Input *pRef - module state + + \Output + uint8_t - TRUE if successfully, FALSE otherwise + + \Version 10/19/2016 (eesponda) +*/ +/********************************************************************************F*/ +static uint8_t _HpackHuffmanTreeBuild(HpackRefT *pRef) +{ + uint8_t uChar, bResult = TRUE; + + // allocate the root of the tree + if ((pRef->pHuffmanTree = (HuffmanNodeT *)DirtyMemAlloc(sizeof(*pRef->pHuffmanTree), HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData)) == NULL) + { + NetPrintf(("hpack: [%p] could not allocate root of huffman tree\n", pRef)); + return(FALSE); + } + ds_memclr(pRef->pHuffmanTree, sizeof(*pRef->pHuffmanTree)); + + /* go through each character one at a time 0-255 + building a path in the huffman tree based on the code for the character + at the end save the character in the leaf + + if any allocation fails this will be all cleaned up in the destroy that + happens if this function returns FALSE + */ + for (uChar = 0; (uChar < HPACK_HUFFMAN_TABLE_SIZE-1) && (bResult == TRUE); uChar += 1) + { + const HuffmanCodeT *pHuffman = &Hpack_aHuffmanCodes[uChar]; + bResult = _HpackHuffmanTreeInsert(pRef, &pRef->pHuffmanTree, pHuffman->uCode, pHuffman->uLen, uChar); + } + + return(bResult); +} + +/*F********************************************************************************/ +/*! + \Function _HpackHuffmanTreeDestroy + + \Description + Recursively clean up the huffman tree + + \Input *pRef - module state + \Input *pNode - currently visited node to cleanup + + \Version 10/19/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _HpackHuffmanTreeDestroy(HpackRefT *pRef, HuffmanNodeT *pNode) +{ + if (pNode == NULL) + { + return; + } + + _HpackHuffmanTreeDestroy(pRef, pNode->pLeft); + pNode->pLeft = NULL; + + _HpackHuffmanTreeDestroy(pRef, pNode->pRight); + pNode->pRight = NULL; + + DirtyMemFree(pNode, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function _HpackHuffmanTreeDecode + + \Description + Use the huffman tree to decode a series of octets + + \Input *pRef - module state + \Input *pData - input buffer with octets to decode + \Input iLen - size of the input buffer to decode + \Input *pBuffer - [out] output string that we write data to + \Input *pSuccess- [out] if the decoding was success + + \Version 10/19/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _HpackHuffmanTreeDecode(HpackRefT *pRef, const uint8_t *pData, int32_t iLen, char **pBuffer, uint8_t *pSuccess) +{ + int32_t iIndex, iWrite; + char strTemp[256]; + const HuffmanNodeT *pNode = pRef->pHuffmanTree; + + /* loop through each octet traversing the tree bit by bit + when a leaf node is found write that to our output */ + for (iIndex = 0, iWrite = 0; iIndex < iLen; iIndex += 1) + { + int32_t iBit; + for (iBit = 7; iBit >= 0; iBit -= 1) + { + pNode = ((pData[iIndex] >> iBit) & 1) ? pNode->pLeft : pNode->pRight; + if (pNode == NULL) + { + NetPrintf(("hpack: [%p] huffman code does not exist in tree\n", pRef)); + *pSuccess = FALSE; + return; + } + if ((pNode->pLeft != NULL) || (pNode->pRight != NULL)) + { + continue; + } + + // copy string but leave enough room for null terminator + if ((uint32_t)iWrite < sizeof(strTemp)-1) + { + strTemp[iWrite++] = (char)pNode->uChar; + } + pNode = pRef->pHuffmanTree; + } + } + + // write null terminator and return number of bytes written + strTemp[iWrite++] = '\0'; + + // allocate space for the output buffer + if ((*pBuffer = (char *)DirtyMemAlloc(iWrite, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData)) != NULL) + { + ds_strnzcpy(*pBuffer, strTemp, iWrite); + } + else + { + NetPrintf(("hpack: [%p] could not allocate space for decoding the string into\n", pRef)); + *pSuccess = FALSE; + } +} + +/*F********************************************************************************/ +/*! + \Function _HpackDecodeInteger + + \Description + Decodes an integer from the sequence of octets + + \Input *pBuf - where we decode the integer from + \Input iBufLen - size of the input buffer + \Input uMask - enable bits we use for decoding the integer + \Input *pValue - [out] where we write the decoded integer + + \Output + int32_t - amount of bytes read from the buffer + + \Version 10/07/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _HpackDecodeInteger(const uint8_t *pBuf, int32_t iBufLen, uint8_t uMask, uint32_t *pValue) +{ + int32_t iRead = 1; //!< we always at least read one byte + + if ((*pValue = *pBuf & uMask) == uMask) + { + uint8_t uMSB = 0, uByte; + + // read until you decode the full integer or reach the end of the buffer + do + { + uByte = pBuf[iRead++]; + *pValue += (uByte & 0x7f) * (1 << uMSB); + uMSB += 7; + } while (((uByte & 0x80) == 0x80) && (iRead < iBufLen)); + + // log in the case of reaching the end + if (iRead == iBufLen) + { + NetPrintf(("hpack: reached the end of the buffer before decoding full integer\n")); + } + } + return(iRead); +} + +/*F********************************************************************************/ +/*! + \Function _HpackDecodeString + + \Description + Decodes an string from the sequence of octets + + \Input *pRef - module state + \Input *pBuf - where we decode the string from + \Input iBufLen - size of the input buffer + \Input *pOutput - [out] where we write the decode string + \Input *pSuccess- [out] if the decode was successful + + \Output + int32_t - amount of bytes read from the buffer + + \Version 10/07/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _HpackDecodeString(HpackRefT *pRef, const uint8_t *pBuf, int32_t iBufLen, char **pOutput, uint8_t *pSuccess) +{ + int32_t iRead = 0; + uint8_t bHuffman; + uint32_t uLen; + + // initialize the output to NULL in cases of error + *pOutput = NULL; + + // decode if huffman encoding was used and the length of the string + bHuffman = (*pBuf & 0x80) == 0x80; + iRead += _HpackDecodeInteger(pBuf, iBufLen, 0x7f, &uLen); + + // make sure we have enough space in input + if ((uint32_t)iBufLen-iRead < uLen) + { + NetPrintf(("hpack: [%p] not enough space in input buffer to decode string\n", pRef)); + *pSuccess = FALSE; + return(iBufLen-iRead); + } + + // decode using the huffman tree or just copy the literal string + if (bHuffman == TRUE) + { + _HpackHuffmanTreeDecode(pRef, pBuf+iRead, uLen, pOutput, pSuccess); + } + else + { + // allocate the buffer for decoding + if ((*pOutput = (char *)DirtyMemAlloc(uLen+1, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData)) != NULL) + { + ds_strnzcpy(*pOutput, (const char *)pBuf+iRead, uLen+1); + } + else + { + NetPrintf(("hpack: [%p] could not allocate space for decoding the string into\n", pRef)); + *pSuccess = FALSE; + } + } + + return(iRead+uLen); +} + +/*F********************************************************************************/ +/*! + \Function _HpackDecodeIndexedField + + \Description + Decodes an indexed header field + + \Input *pRef - module state + \Input uIndex - index of the header field + \Input *pEntry - [out] header field data retrieved from the table + + \Output + uint8_t - TRUE if successfully pulled the indexed field from the table + + \Version 10/17/2016 (eesponda) +*/ +/********************************************************************************F*/ +static uint8_t _HpackDecodeIndexedField(HpackRefT *pRef, uint32_t uIndex, TableEntryT *pEntry) +{ + const TableEntryT *pFound; + uint8_t bResult = TRUE; + + if (uIndex == 0) + { + NetPrintf(("hpack: [%p] received invalid index of 0 when decoding indexed header field\n", pRef)); + return(FALSE); + } + uIndex -= 1; // make it zero-indexed + + // try to find the entry in the static table index space + if (uIndex < HPACK_STATIC_TABLE_SIZE) + { + ds_memcpy(pEntry, &Hpack_aStaticTable[uIndex], sizeof(*pEntry)); + } + // try to find the entry in the dynamic table index space + else if ((pFound = _HpackDynamicTableGet(pRef, uIndex - HPACK_STATIC_TABLE_SIZE)) != NULL) + { + ds_memcpy(pEntry, pFound, sizeof(*pEntry)); + } + else + { + NetPrintf(("hpack: [%p] indexed header field could not be found at index %u\n", pRef, uIndex)); + bResult = FALSE; + } + return(bResult); +} +/*F********************************************************************************/ +/*! + \Function _HpackFindIndexedField + + \Description + Find an index given a header field + + \Input *pRef - module state + \Input *pEntry - header field we need the index for + \Input *pIndex - [out] index of the header field + + \Output + uint8_t - TRUE if successfully found the index for the header field + + \Version 10/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static uint8_t _HpackFindIndexedField(HpackRefT *pRef, const StaticTableEntryT *pEntry, uint32_t *pIndex) +{ + uint32_t uIndex; + const TableEntryT *pFound; + + // try to find a valid index in the static table space + for (uIndex = 0; uIndex < HPACK_STATIC_TABLE_SIZE; uIndex += 1) + { + if (strncmp(pEntry->pName, Hpack_aStaticTable[uIndex].pName, strlen(pEntry->pName)) == 0 && + strncmp(pEntry->pValue, Hpack_aStaticTable[uIndex].pValue, strlen(pEntry->pValue)) == 0) + { + *pIndex = uIndex+1; + return(TRUE); + } + } + + // if not found in static, try the dynamic table space + uIndex = 0; + while ((pFound = _HpackDynamicTableGet(pRef, uIndex)) != NULL) + { + if (strncmp(pEntry->pName, pFound->pName, strlen(pEntry->pName)) == 0 && + strncmp(pEntry->pValue, pFound->pValue, strlen(pEntry->pValue)) == 0) + { + *pIndex = uIndex+HPACK_STATIC_TABLE_SIZE+1; + return(TRUE); + } + uIndex += 1; + } + + // otherwise it was not found + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function _HpackFindIndexedFieldName + + \Description + Find an index given a header field name + + \Input *pRef - module state + \Input *pName - the header field name we are looking for + \Input *pIndex - [out] index of the header field + + \Output + uint8_t - TRUE if successfully found the index for the header field name + + \Version 10/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static uint8_t _HpackFindIndexedFieldName(HpackRefT *pRef, const char *pName, uint32_t *pIndex) +{ + uint32_t uIndex; + const TableEntryT *pFound; + + // try to find a valid index in the static table space + for (uIndex = 0; uIndex < HPACK_STATIC_TABLE_SIZE; uIndex += 1) + { + if (strncmp(pName, Hpack_aStaticTable[uIndex].pName, strlen(Hpack_aStaticTable[uIndex].pName)) == 0) + { + *pIndex = uIndex+1; + return(TRUE); + } + } + + // if not found in static, try the dynamic table space + uIndex = 0; + while ((pFound = _HpackDynamicTableGet(pRef, uIndex)) != NULL) + { + if (strncmp(pName, pFound->pName, strlen(pFound->pName)) == 0) + { + *pIndex = uIndex+HPACK_STATIC_TABLE_SIZE+1; + return(TRUE); + } + uIndex += 1; + } + + // otherwise it was not found + return(FALSE); +} + + +/*F********************************************************************************/ +/*! + \Function _HpackDecodeLiteralField + + \Description + Decodes a literal header field + + \Input *pRef - module state + \Input *pBuf - bytes we are decoding the header field from + \Input iBufSize - size of the input buffer + \Input uIndex - index of the header field + \Input *pEntry - [out] header field data retrieved from the table + \Input *pSuccess- [out] if the decode was successful + + \Output + int32_t - number of bytes read from the buffer + + \Version 10/17/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _HpackDecodeLiteralField(HpackRefT *pRef, const uint8_t *pBuf, int32_t iBufSize, uint32_t uIndex, TableEntryT *pEntry, uint8_t *pSuccess) +{ + int32_t iRead = 0; + + // if the header name is indexed then pull it from the table + if (uIndex > 0) + { + char *pName; + int32_t iLen; + + // retrieve the entry, the value in pEntry will point to the static table entry + if (_HpackDecodeIndexedField(pRef, uIndex, pEntry) == FALSE) + { + *pSuccess = FALSE; + return(0); + } + + // allocate the space for the string as this will go into the dynamic table + iLen = (int32_t)strlen(pEntry->pName)+1; + if ((pName = (char *)DirtyMemAlloc(iLen, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData)) != NULL) + { + ds_strnzcpy(pName, pEntry->pName, iLen); + } + else + { + NetPrintf(("hpack: [%p] could not allocate space for the entry name\n", pRef)); + *pSuccess = FALSE; + } + pEntry->pName = pName; + } + // otherwise decode it from the buffer + else + { + iRead += _HpackDecodeString(pRef, pBuf+iRead, iBufSize-iRead, &pEntry->pName, pSuccess); + } + + // decode the value from the buffer + iRead += _HpackDecodeString(pRef, pBuf+iRead, iBufSize-iRead, &pEntry->pValue, pSuccess); + + // return how much data was read + return(iRead); +} + +/*F********************************************************************************/ +/*! + \Function _HpackDynamicTableEject + + \Description + Ejects header fields from the dynamic table until size hits a specified + value + + \Input *pRef - module state + \Input uSize - size we need the table to be under + + \Version 10/17/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _HpackDynamicTableEject(HpackRefT *pRef, uint32_t uSize) +{ + // if we are over the size, eject entries + while (pRef->uTableSize > uSize) + { + DynamicNodeT *pNode = pRef->pTail; + + // recalculate new size + pRef->uTableSize -= (uint32_t)strlen(pNode->Entry.pName) + (uint32_t)strlen(pNode->Entry.pValue) + HPACK_TABLE_ENTRY_OVERHEAD; + + // fix up pointers and delete memory + if (pRef->pTail != pRef->pHead) + { + pRef->pTail = pNode->pPrev; + pRef->pTail->pNext = NULL; + } + else + { + pRef->pTail = pRef->pHead = NULL; + } + DirtyMemFree(pNode->Entry.pName, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + DirtyMemFree(pNode->Entry.pValue, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + DirtyMemFree(pNode, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + } +} + +/*F********************************************************************************/ +/*! + \Function _HpackDynamicTableResize + + \Description + Updates the maximum size of the dynamic table + + \Input *pRef - module state + \Input uSize - new maximum size of the table + + \Version 10/17/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _HpackDynamicTableResize(HpackRefT *pRef, uint32_t uSize) +{ + // eject entries + _HpackDynamicTableEject(pRef, uSize); + + // update size + pRef->uTableMax = uSize; + +#if HPACK_DYNAMICTABLE_LOGGING + NetPrintf(("hpack: [%p] new table size maximum %u\n", pRef, pRef->uTableMax)); +#endif +} + +/*F********************************************************************************/ +/*! + \Function _HpackDynamicTableInsert + + \Description + Inserts a new entry into the dynamic table, ejecting any necessary entries + + \Input *pRef - module state + \Input *pEntry - new entry we are adding to the table + + \Output + uint8_t - success=TRUE, failure=FALSE + + \Version 10/17/2016 (eesponda) +*/ +/********************************************************************************F*/ +static uint8_t _HpackDynamicTableInsert(HpackRefT *pRef, const TableEntryT *pEntry) +{ + DynamicNodeT *pNode; + uint32_t uEntrySize; + + // make sure entry is valid + if ((pEntry->pName == NULL) || (pEntry->pValue == NULL)) + { + return(FALSE); + } + // allocate memory for the node in the dynamic table + if ((pNode = (DynamicNodeT *)DirtyMemAlloc(sizeof(*pNode), HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData)) == NULL) + { + return(FALSE); + } + ds_memclr(pNode, sizeof(*pNode)); + ds_memcpy(&pNode->Entry, pEntry, sizeof(pNode->Entry)); + + uEntrySize = (uint32_t)strlen(pEntry->pName) + (uint32_t)strlen(pEntry->pValue) + HPACK_TABLE_ENTRY_OVERHEAD; + pRef->uTableSize += uEntrySize; + + // attach to list + if (pRef->pTail != NULL) + { + pNode->pNext = pRef->pHead; + pRef->pHead->pPrev = pNode; + } + else + { + pRef->pTail = pNode; + } + pRef->pHead = pNode; + + // if we are over the size, eject entries + _HpackDynamicTableEject(pRef, pRef->uTableMax); + + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function _HpackEncodeInteger + + \Description + Encodes an integer into a sequence of octets + + \Input uValue - integer we are encoding + \Input uMask - enable bits we use for encoding the integer + \Input *pBuf - [out] where we encode the integer into + \Input iBufLen - size of the output buffer + + \Output + int32_t - amount of bytes written to the buffer + + \Version 10/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _HpackEncodeInteger(uint32_t uValue, uint8_t uMask, uint8_t *pBuf, int32_t iBufLen) +{ + int32_t iWrite = 1; + + if (uValue < uMask) + { + *pBuf |= (uint8_t)uValue; + } + else + { + *pBuf |= uMask; //!< turn on all the bits in the prefix + uValue -= uMask; //!< decrease the value by that amount + + for (iWrite = 1; (iWrite < iBufLen) && (uValue >= 0x80); iWrite += 1) + { + // set the lsb of the value and set the msb as a continuation flag + pBuf[iWrite] = (uValue % 0x80) + 0x80; + uValue /= 0x80; + } + pBuf[iWrite++] = uValue; + } + return(iWrite); +} + +/*F********************************************************************************/ +/*! + \Function _HpackHuffmanEncode + + \Description + Huffman encodes a character + + \Input *pEncode - encoding state + \Input *pHuffman - huffman code information + + \Version 10/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _HpackHuffmanEncode(HuffmanEncodeT *pEncode, const HuffmanCodeT *pHuffman) +{ + int32_t iIndex; + const int32_t iBits = sizeof(*pEncode->aResult) * 8; + const int32_t iSize = sizeof(pEncode->aResult) / sizeof(*pEncode->aResult); + + // increase the count of bits and track the next octet boundary + pEncode->uCount += pHuffman->uLen; + if (pEncode->uCount > pEncode->uNextBoundary) + { + /* figure out how much we need to increment the boundary by + if the count is on an octet, just move the boundary there + if the count is over an octet, just move to the next octet past count (might be more than 1 octet) */ + const uint8_t uDifference = pEncode->uCount - pEncode->uNextBoundary; + if ((uDifference % 8) == 0) + { + pEncode->uNextBoundary = pEncode->uCount; + } + else + { + const uint8_t uNumOctets = (uDifference / 8) + 1; + pEncode->uNextBoundary += 8 * uNumOctets; + } + } + + // loop through the bytes backwards shifting by the left + for (iIndex = iSize-1; iIndex > 0; iIndex -= 1) + { + uint32_t uResult; + if ((uResult = (pEncode->aResult[iIndex] << pHuffman->uLen) | (pEncode->aResult[iIndex-1] >> (iBits-pHuffman->uLen))) != 0) + { + pEncode->aResult[iIndex] = uResult; + } + } + // finally encode using the huffman code + *pEncode->aResult = (*pEncode->aResult << pHuffman->uLen) | pHuffman->uCode; +} + +/*F********************************************************************************/ +/*! + \Function _HpackEncodeString + + \Description + Encodes a string into a sequence of octets + + \Input *pRef - module state + \Input *pBuf - where we encode the string from + \Input iBufSize - size of the input buffer + \Input *pOutput - [out] where we write the encoded string + \Input iOutSize - size of the output buffer + \Input bHuffman - use huffman encoding? + + \Output + int32_t - amount of bytes written into the buffer + + \Version 10/21/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _HpackEncodeString(HpackRefT *pRef, const char *pBuf, int32_t iBufSize, uint8_t *pOutput, int32_t iOutSize, uint8_t bHuffman) +{ + int32_t iWrite = 0; + + if (bHuffman == TRUE) + { + int32_t iIndex, iTotalBytes; + uint32_t uLeftover; + HuffmanCodeT Huffman; + HuffmanEncodeT Encode; + + ds_memclr(Encode.aResult, sizeof(Encode.aResult)); + Encode.uCount = 0; + Encode.uNextBoundary = 8; + + // encode each character one at a time + for (iIndex = 0; iIndex < iBufSize; iIndex += 1) + { + uint8_t uChar = pBuf[iIndex]; + _HpackHuffmanEncode(&Encode, &Hpack_aHuffmanCodes[uChar]); + } + + // finalize the huffman encoding using the EOS code + if ((uLeftover = Encode.uNextBoundary - Encode.uCount) > 0) + { + Huffman.uCode = Hpack_EOS.uCode >> (Hpack_EOS.uLen - uLeftover); + Huffman.uLen = uLeftover; + _HpackHuffmanEncode(&Encode, &Huffman); + } + + pOutput[iWrite] = 0x80; + iTotalBytes = Encode.uCount/8; + iWrite += _HpackEncodeInteger(iTotalBytes, 0x7f, pOutput, iOutSize); + + /* write the sequence to the output buffer + to ensure we write only as much as needed we calculate + how many bytes we need to write each iteration */ + while ((iTotalBytes > 0) && (iWrite < iOutSize)) + { + // figure out how many bytes we need to write + int32_t iRemainder = iTotalBytes % sizeof(*Encode.aResult); + // figure out what index into the result we need to read from + iIndex = (iTotalBytes-1) / sizeof(*Encode.aResult); + + // if on the boundary set the bytes to equal to the boundary + if (iRemainder == 0) + { + iRemainder = sizeof(*Encode.aResult); + } + // write the correct number of bytes to the output + for (; iRemainder > 0; iRemainder -= 1, iTotalBytes -= 1) + { + pOutput[iWrite++] = (uint8_t)(Encode.aResult[iIndex] >> ((iRemainder-1) * 8)); + } + } + } + else + { + iWrite += _HpackEncodeInteger(iBufSize, 0x7f, pOutput, iOutSize); + + ds_memcpy_s(pOutput+iWrite, iOutSize-iWrite, pBuf, iBufSize); + iWrite += iBufSize; + } + + return(iWrite); +} + + +#if HPACK_DYNAMICTABLE_LOGGING +/*F********************************************************************************/ +/*! + \Function _HpackPrintTable + + \Description + Prints the entries in the dynamic table + + \Input *pRef - module state + + \Version 11/04/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _HpackPrintTable(HpackRefT *pRef) +{ + const TableEntryT *pEntry; + uint32_t uIndex = 0; + + while ((pEntry = _HpackDynamicTableGet(pRef, uIndex)) != NULL) + { + uIndex += 1; + NetPrintf(("hpack: [%p] [%02u] (s = %u) %s: %s\n", pRef, uIndex, strlen(pEntry->pName) + strlen(pEntry->pValue) + HPACK_TABLE_ENTRY_OVERHEAD, pEntry->pName, pEntry->pValue)); + } + NetPrintf(("hpack: [%p] table size: %u\n", pRef, pRef->uTableSize)); +} +#endif + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function HpackCreate + + \Description + Allocate module state and prepare for use + + \Input uTableMax - maximum size of the dynamic table + \Input bDecoder - is the ref for a decoder? + + \Output + HpackRefT * - pointer to module state, or NULL + + \Notes + uTableMax is based upon SETTINGS_HEADER_TABLE_SIZE in the + HTTP/2 header frame + + \Version 10/05/2016 (eesponda) +*/ +/********************************************************************************F*/ +HpackRefT *HpackCreate(uint32_t uTableMax, uint8_t bDecoder) +{ + HpackRefT *pRef; + int32_t iMemGroup; + void *pMemGroupUserData; + + // query memgroup data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate state + if ((pRef = (HpackRefT *)DirtyMemAlloc(sizeof(*pRef), HPACK_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("hpack: unable to allocate module state\n")); + return(NULL); + } + ds_memclr(pRef, sizeof(*pRef)); + pRef->uTableMax = uTableMax; + pRef->iMemGroup = iMemGroup; + pRef->pMemGroupUserData = pMemGroupUserData; + + // build huffman tree + if ((bDecoder == TRUE) && (_HpackHuffmanTreeBuild(pRef) == FALSE)) + { + HpackDestroy(pRef); + return(NULL); + } + + return(pRef); +} + +/*F********************************************************************************/ +/*! + \Function HpackDestroy + + \Description + Destroy the module and release its state + + \Input *pRef - module state + + \Version 10/05/2016 (eesponda) +*/ +/********************************************************************************F*/ +void HpackDestroy(HpackRefT *pRef) +{ + // clean up the tree recursively + if (pRef->pHuffmanTree != NULL) + { + _HpackHuffmanTreeDestroy(pRef, pRef->pHuffmanTree); + pRef->pHuffmanTree = NULL; + } + + // clear the dynamic table + HpackClear(pRef); + + DirtyMemFree(pRef, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function HpackDecode + + \Description + Decode the byte array into formatted header string + + \Input *pRef - module state + \Input *pInput - the bytes we are decoding + \Input iInpSize - number of bytes in the input + \Input *pOutput - [out] formatted header string CRLF delimited + \Input iOutSize - size of the string we are writing to + + \Output + int32_t - success=amount of data written to output, failure=negative + + \Version 10/17/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t HpackDecode(HpackRefT *pRef, const uint8_t *pInput, int32_t iInpSize, char *pOutput, int32_t iOutSize) +{ + int32_t iRead = 0, iWrite = 0; + uint32_t uIndex; + uint8_t bSuccess = TRUE; + + // clear the output + ds_memclr(pOutput, iOutSize); + + while ((iRead < iInpSize) && (bSuccess == TRUE)) + { + TableEntryT Entry; + uint32_t uSize; + uint8_t uByte = pInput[iRead], bWrite = TRUE, bFree = FALSE; + ds_memclr(&Entry, sizeof(Entry)); + + /* mask out the higher bits to figure out what type of header field it is + then pass a mask of the lower bits to the decode */ + + // indexed header field '1' 7-bit prefix + if ((uByte & HPACK_INDEXED_HEADER_FIELD) != 0) + { + iRead += _HpackDecodeInteger(pInput+iRead, iInpSize-iRead, HPACK_INDEXED_HEADER_FIELD-1, &uIndex); + bSuccess = _HpackDecodeIndexedField(pRef, uIndex, &Entry); + } + // literal header field with incremental indexing '01' 6-bit prefix + else if ((uByte & HPACK_LITERAL_HEADER_FIELD) != 0) + { + iRead += _HpackDecodeInteger(pInput+iRead, iInpSize-iRead, HPACK_LITERAL_HEADER_FIELD-1, &uIndex); + iRead += _HpackDecodeLiteralField(pRef, pInput+iRead, iInpSize-iRead, uIndex, &Entry, &bSuccess); + + // add entry to dynamic table + if (_HpackDynamicTableInsert(pRef, &Entry) == FALSE) + { + bFree = TRUE; /* free the memory */ + bSuccess = FALSE; /* set the failure flag */ + } + } + // dynamic table size update '001' 5-bit prefix + else if ((uByte & HPACK_DYNAMIC_TABLE_UPDATE) != 0) + { + bWrite = FALSE; /* disable writing headers */ + + iRead += _HpackDecodeInteger(pInput+iRead, iInpSize-iRead, HPACK_DYNAMIC_TABLE_UPDATE-1, &uSize); + _HpackDynamicTableResize(pRef, uSize); + } + // literal header field never indexed '0001' 4-bit prefix + else if ((uByte & HPACK_LITERAL_HEADER_FIELD_NEVER) != 0) + { + iRead += _HpackDecodeInteger(pInput+iRead, iInpSize-iRead, HPACK_LITERAL_HEADER_FIELD_NEVER-1, &uIndex); + iRead += _HpackDecodeLiteralField(pRef, pInput+iRead, iInpSize-iRead, uIndex, &Entry, &bSuccess); + bFree = TRUE; /* cleanup after it is written */ + } + // literal header field without indexing '0000' 4-bit prefix + else + { + iRead += _HpackDecodeInteger(pInput+iRead, iInpSize-iRead, HPACK_LITERAL_HEADER_FIELD_NEVER-1, &uIndex); + iRead += _HpackDecodeLiteralField(pRef, pInput+iRead, iInpSize-iRead, uIndex, &Entry, &bSuccess); + bFree = TRUE; /* cleanup after it is written */ + } + + // write the header field out if not dynamic table size update + if (bWrite == TRUE) + { + iWrite += ds_snzprintf(pOutput+iWrite, iOutSize-iWrite, "%s: %s\r\n", Entry.pName, Entry.pValue); + } + // if we need to cleanup entry memory do so + if (bFree == TRUE) + { + DirtyMemFree(Entry.pName, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + DirtyMemFree(Entry.pValue, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + } + } + +#if HPACK_DYNAMICTABLE_LOGGING + _HpackPrintTable(pRef); +#endif + + return(bSuccess == TRUE ? iWrite : -1); +} + +/*F********************************************************************************/ +/*! + \Function HpackEncode + + \Description + Encode the formatted header string into a byte array + + \Input *pRef - module state + \Input *pInput - formatted header string CRLF delimited + \Input *pOutput - [out] byte array we are writing into + \Input iOutSize - size of the byte array we are writing to + \Input bHuffman - do we need to huffman encode the strings? + + \Output + int32_t - success=amount of data written, failure=negative + + \Version 10/17/2016 (eesponda) +*/ +/********************************************************************************F*/ +int32_t HpackEncode(HpackRefT *pRef, const char *pInput, uint8_t *pOutput, int32_t iOutSize, uint8_t bHuffman) +{ + char strName[256], strValue[4*1024]; + StaticTableEntryT TempEntry; + int32_t iWrite = 0; + + // clear the output + ds_memclr(pOutput, iOutSize); + + // if there is a pending dynamic table update, encode that first + if (pRef->bUpdatePending == TRUE) + { + pOutput[iWrite] = HPACK_DYNAMIC_TABLE_UPDATE; + iWrite += _HpackEncodeInteger(pRef->uTableMax, HPACK_DYNAMIC_TABLE_UPDATE-1, pOutput+iWrite, iOutSize-iWrite); + + pRef->bUpdatePending = FALSE; + } + + // point to the address of the stack variables + TempEntry.pName = strName; + TempEntry.pValue = strValue; + + // for each header entry try to encode + while (ProtoHttpGetNextHeader(NULL, pInput, strName, sizeof(strName), strValue, sizeof(strValue), &pInput) == 0) + { + uint32_t uIndex; + TableEntryT Entry; + + /* force the strName to lowercase */ + for (uIndex = 0; (strName[uIndex] != '\0') && (uIndex < sizeof(strName)); uIndex += 1) + { + if (isupper(strName[uIndex])) + { + strName[uIndex] = tolower(strName[uIndex]); + } + } + + /* attempt to find the header entry in the static/dynamic table + if found, then encode it as an indexed header field */ + if (_HpackFindIndexedField(pRef, &TempEntry, &uIndex) == TRUE) + { + // write indexed header field '1' followed by index using 7-bit prefix + pOutput[iWrite] = HPACK_INDEXED_HEADER_FIELD; + iWrite += _HpackEncodeInteger(uIndex, HPACK_INDEXED_HEADER_FIELD-1, pOutput+iWrite, iOutSize-iWrite); + } + // otherwise encode it as literal header field with indexing + else + { + int32_t iLen; + + /* write literal header field with incremental indexing '01' followed by index (if indexable name) or encoded string (if not) + and encoded string (for value) */ + pOutput[iWrite] = HPACK_LITERAL_HEADER_FIELD; + if (_HpackFindIndexedFieldName(pRef, TempEntry.pName, &uIndex) == TRUE) + { + iWrite += _HpackEncodeInteger(uIndex, HPACK_LITERAL_HEADER_FIELD-1, pOutput+iWrite, iOutSize-iWrite); + } + else + { + iWrite += 1; + iWrite += _HpackEncodeString(pRef, TempEntry.pName, (int32_t)strlen(TempEntry.pName), pOutput+iWrite, iOutSize-iWrite, bHuffman); + } + iWrite += _HpackEncodeString(pRef, TempEntry.pValue, (int32_t)strlen(TempEntry.pValue), pOutput+iWrite, iOutSize-iWrite, bHuffman); + + // allocate space for the entry name in the dynamic table + iLen = (int32_t)strlen(TempEntry.pName)+1; + if ((Entry.pName = (char *)DirtyMemAlloc(iLen, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData)) != NULL) + { + ds_strnzcpy(Entry.pName, TempEntry.pName, iLen); + } + else + { + NetPrintf(("hpack: [%p] could not allocate space for entry name for encoding dynamic table\n", pRef)); + } + // allocate space for the entry value in the dynamic table + iLen = (int32_t)strlen(TempEntry.pValue)+1; + if ((Entry.pValue = (char *)DirtyMemAlloc(iLen, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData)) != NULL) + { + ds_strnzcpy(Entry.pValue, TempEntry.pValue, iLen); + } + else + { + NetPrintf(("hpack: [%p] could not allocate space for entry value for encoding dynamic table\n", pRef)); + } + + // insert the new entry into the dynamic table + if (_HpackDynamicTableInsert(pRef, &Entry) == FALSE) + { + DirtyMemFree(Entry.pName, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + DirtyMemFree(Entry.pValue, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + return(-1); + } + } + } + +#if HPACK_DYNAMICTABLE_LOGGING + _HpackPrintTable(pRef); +#endif + + return(iWrite); +} + +/*F********************************************************************************/ +/*! + \Function HpackClear + + \Description + Clears the dynamic table + + \Input *pRef - module state + + \Version 10/31/2016 (eesponda) +*/ +/********************************************************************************F*/ +void HpackClear(HpackRefT *pRef) +{ + DynamicNodeT *pNode; + + //! walk the list and clean up the memory + while ((pNode = pRef->pHead) != NULL) + { + pRef->pHead = pNode->pNext; + + DirtyMemFree(pNode->Entry.pName, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + DirtyMemFree(pNode->Entry.pValue, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + DirtyMemFree(pNode, HPACK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData); + } + pRef->pTail = NULL; + pRef->uTableSize = 0; +} + +/*F********************************************************************************/ +/*! + \Function HpackResize + + \Description + Resizes the dynamic table + + \Input *pRef - module state + \Input uTableSize - new maximum table size + \Input bSendUpdate - do we need to notify peer of dynamic table size change + + \Version 11/09/2016 (eesponda) +*/ +/********************************************************************************F*/ +void HpackResize(HpackRefT *pRef, uint32_t uTableSize, uint8_t bSendUpdate) +{ + _HpackDynamicTableResize(pRef, uTableSize); + pRef->bUpdatePending = bSendUpdate; +} diff --git a/src/thirdparty/dirtysdk/source/util/jsonformat.c b/src/thirdparty/dirtysdk/source/util/jsonformat.c new file mode 100644 index 00000000..eb595379 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/jsonformat.c @@ -0,0 +1,1377 @@ +/*H*************************************************************************************/ +/*! + \File jsonformat.c + + \Description + This module formats simple JSON, in a linear fashion, using a character buffer + that the client provides. + + \Copyright + Copyright (c) Electronic Arts 2012. + + \Notes + \verbatim + When JsonInit() is called, a 24-bytes header is added at the beginning of the + client-provided buffer. The header is an ascii string that looks like this: + + where: + AAAAAAAA is the offset field + BBBBBBBB is the buffer size field + CC is the indent field + DD is the flag field + Those first 24 bytes are replaced by whitespaces when JsonFinish() is called. + + Required buffer capacity for json = ??? length(JSON) + 1. + That means if length(buffer) <= length(JSON), buffer-overrun occurs. + \endverbatim + + \Version 12/11/2012 (jbrookes) First Version, based on XmlFormat +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/util/jsonformat.h" + +/*** Defines ***************************************************************************/ + +#define JSON_HEADER_LEN (22) +#define JSON_MAX_TAG_LENGTH (128) +#define JSON_MAX_FLOAT_LENGTH (128) +#define JSON_MAX_DATE_LENGTH (32) + +/*** Type Definitions ******************************************************************/ + +/*** Variables *************************************************************************/ + +static const uint8_t _Json_Hex2AsciiTable[16] = "0123456789abcdef"; + +// ascii->hex conversion table for 7bit ASCII characters +static const uint8_t _Json_Ascii2HexTable[128] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +/*! 7bit ASCII characters that should be encoded ([0..31], ", ?, \) + characters that are represented with a 1 are escaped in long form + (u00xy) whereas characters represented with a 2 are escaped with + a single backslash */ +static const uint8_t _Json_EncodeStringTable[128] = +{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, // 00-0F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10-1F + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20-2F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 30-3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40-4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50-5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60-6F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, // 70-7F +}; + + +static int32_t _iLastIndent; //$$TODO - clean this up (put it in table?) + + + +/*** Private Functions *****************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function _JsonGet8 + + \Description + Get 8bit value from given offset in buffer + + \Input *pJson - pointer to start of buffer + \Input iOffset - offset within buffer to get value from + + \Output + int32_t - value + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonGet8(const char *pJson, int32_t iOffset) +{ + return((_Json_Ascii2HexTable[(int32_t)pJson[iOffset+0]] << 4) | + _Json_Ascii2HexTable[(int32_t)pJson[iOffset+1]]); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonSet8 + + \Description + Save 8bit value in buffer + + \Input *pJson - pointer to start of buffer + \Input iOffset - offset to save at + \Input iValue - value to save + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static void _JsonSet8(char *pJson, int32_t iOffset, int32_t iValue) +{ + pJson[iOffset+0] = _Json_Hex2AsciiTable[(iValue >> 4) & 15]; + pJson[iOffset+1] = _Json_Hex2AsciiTable[(iValue >> 0) & 15]; +} + +/*F*************************************************************************************/ +/*! + \Function _JsonGet32 + + \Description + Get 32bit value from given offset in buffer + + \Input *pJson - pointer to start of buffer + \Input iOffset - offset within buffer to get value from + + \Output + int32_t - value + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonGet32(char *pJson, int32_t iOffset) +{ + return((_Json_Ascii2HexTable[(int32_t)pJson[iOffset+0]] << 28) | + (_Json_Ascii2HexTable[(int32_t)pJson[iOffset+1]] << 24) | + (_Json_Ascii2HexTable[(int32_t)pJson[iOffset+2]] << 20) | + (_Json_Ascii2HexTable[(int32_t)pJson[iOffset+3]] << 16) | + (_Json_Ascii2HexTable[(int32_t)pJson[iOffset+4]] << 12) | + (_Json_Ascii2HexTable[(int32_t)pJson[iOffset+5]] << 8) | + (_Json_Ascii2HexTable[(int32_t)pJson[iOffset+6]] << 4) | + _Json_Ascii2HexTable[(int32_t)pJson[iOffset+7]]); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonSet32 + + \Description + Save 32-bit value at given offset in buffer + + \Input *pJson - pointer to start of buffer + \Input iOffset - offset into buffer to save + \Input iValue - value to save into buffer + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static void _JsonSet32(char *pJson, int32_t iOffset, int32_t iValue) +{ + pJson[iOffset+0] = _Json_Hex2AsciiTable[(iValue>>28)&15]; + pJson[iOffset+1] = _Json_Hex2AsciiTable[(iValue>>24)&15]; + pJson[iOffset+2] = _Json_Hex2AsciiTable[(iValue>>20)&15]; + pJson[iOffset+3] = _Json_Hex2AsciiTable[(iValue>>16)&15]; + pJson[iOffset+4] = _Json_Hex2AsciiTable[(iValue>>12)&15]; + pJson[iOffset+5] = _Json_Hex2AsciiTable[(iValue>>8)&15]; + pJson[iOffset+6] = _Json_Hex2AsciiTable[(iValue>>4)&15]; + pJson[iOffset+7] = _Json_Hex2AsciiTable[(iValue>>0)&15]; +} + +/*F*************************************************************************************/ +/*! + \Function _JsonGetOffset + + \Description + Get offset from buffer + + \Input *pJson - pointer to start of buffer + + \Output + int32_t - offset + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonGetOffset(char *pJson) +{ + return(_JsonGet32(pJson, 1)); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonGetLength + + \Description + Get length from buffer + + \Input *pJson - pointer to start of buffer + + \Output + int32_t - length + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonGetLength(char *pJson) +{ + return(_JsonGet32(pJson, 9)); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonGetIndent + + \Description + Get indent level from buffer + + \Input *pJson - pointer to start of buffer + + \Output + int32_t - indent + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonGetIndent(char *pJson) +{ + return(_JsonGet8(pJson, 17)); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonGetFlags + + \Description + Get flags from buffer + + \Input *pJson - pointer to start of buffer + + \Output + int32_t - flags + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonGetFlags(const char *pJson) +{ + return(_JsonGet8(pJson, 19)); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonSetOffset + + \Description + Save offset in buffer + + \Input *pJson - pointer to start of buffer + \Input iOffset - offset to save in buffer + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static void _JsonSetOffset(char *pJson, int32_t iOffset) +{ + _JsonSet32(pJson, 1, iOffset); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonSetLength + + \Description + Save length in buffer + + \Input *pJson - pointer to start of buffer + \Input iLength - length to save in buffer + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static void _JsonSetLength(char *pJson, int32_t iLength) +{ + _JsonSet32(pJson, 9, iLength); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonSetIndent + + \Description + Save indent level in buffer + + \Input *pJson - pointer to start of buffer + \Input iIndent - indent level to save + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static void _JsonSetIndent(char *pJson, int32_t iIndent) +{ + _JsonSet8(pJson, 17, iIndent); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonSetFlags + + \Description + Save flags in buffer + + \Input *pJson - pointer to start of buffer + \Input iFlags - flags + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static void _JsonSetFlags(char *pJson, int32_t iFlags) +{ + _JsonSet8(pJson, 19, iFlags); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonValidHeader + + \Description + Check whether buffer is initialized with a valid header. + + \Input *pBuffer - buffer to which JSON will be written + + \Output + int32_t - returns 1 if there is a valid header, 0 otherwise + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonValidHeader(const char *pBuffer) +{ + return((pBuffer[0] != '{') ? 0 : 1); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonOpenTag + + \Description + Check whether there is an open tag in the buffer. + + \Input *pBuffer - buffer to which JSON will be written + \Input iOffset - current offset within the buffer + + \Output + int32_t - returns 1 if an open tag exists in the buffer, 0 otherwise + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonOpenTag(const char *pBuffer, int32_t iOffset) +{ + int32_t iBackPos; + int32_t iBeginCount = 0; + int32_t iEndCount = 0; + + for (iBackPos = (iOffset-1); iBackPos >= JSON_HEADER_LEN; iBackPos--) + { + // Count all closing tags (ended tag with no children will be counted as starting and closing tag) + if (pBuffer[iBackPos] == '}') + { + iEndCount++; + iBackPos--; + } + // Count all starting tags + else if (pBuffer[iBackPos] == '{') + { + iBeginCount++; + // If there are more open tags than close tags, return true + if (iBeginCount > iEndCount) + { + return(1); + } + } + } + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonUpdateHeader + + \Description + Update the JSON header to reflect the number of characters written. If the buffer + was full, this function will update the header to indicate the full buffer and will + return the JSON_ERR_FULL error code. + + \Input *pBuffer - buffer to which json will be written + \Input iNumChars - number of characters written, -1 for buffer filled + \Input iLength - length of the buffer + \Input iOffset - current offset within the buffer + \Input iIndent - current indent level + + \Output + int32_t - returns JSON_ERR_FULL if buffer was filled, JSON_ERR_NONE otherwise + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonUpdateHeader(char *pBuffer, int32_t iNumChars, int32_t iLength, int32_t iOffset, int32_t iIndent) +{ + iOffset += iNumChars; + + // leave room for null character + iLength -= 1; + + if ((iNumChars < 0) || (iOffset > iLength)) + { + pBuffer[iLength] = '\0'; + iOffset = iLength; + _JsonSetOffset(pBuffer, iOffset); + return(JSON_ERR_FULL); + } + else + { + _iLastIndent = _JsonGetIndent(pBuffer); + _JsonSetOffset(pBuffer, iOffset); + _JsonSetIndent(pBuffer, iIndent); + return(JSON_ERR_NONE); + } +} + +/*F****************************************************************************/ +/*! + \Function _JsonIndent + + \Description + Add the appropriate level of whitespace to indent the current tag if + the whitespace flag is enabled for the buffer. + + \Input *pBuffer - buffer to which JSON will be written + \Input iLength - length of the buffer + \Input iOffset - current offset within the buffer + \Input iIndent - current indent level + \Input iFlags - flags + + \Output + int32_t - negative=buffer overrun, zero/positive=number of characters written + + \Version 12/11/2012 (jbrookes) +*/ +/****************************************************************************F*/ +static int32_t _JsonIndent(char *pBuffer, int32_t iLength, int32_t iOffset, int32_t iIndent, int32_t iFlags) +{ + int32_t iNumChars = -1; + + #if DIRTYCODE_LOGGING + if (iIndent < 0) + { + NetPrintf(("jsonformat: warning -- invalid indent level (%d) in _JsonIndent\n", iIndent)); + } + #endif + + if ((iFlags & JSON_FL_WHITESPACE) == 0) + { + iNumChars = 0; + } + else if (iIndent <= 0) + { + if ((iLength - iOffset) > 1) + { + iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "\n"); + } + } + else + { + if ((iLength - iOffset) > ((iIndent * 2) + 1)) + { + iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "\n%*c", iIndent * 2, ' '); + } + } + + return(iNumChars); +} + +#if 0 //UNUSED? +/*F*************************************************************************************/ +/*! + \Function _JsonFormatInsert + + \Description + Insert a preformatted item + + \Input *pBuffer - the JSON buffer + \Input *pValue - raw string to insert (must be correctly preformatted) + + \Output + int32_t - return code. + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonFormatInsert(char *pBuffer, const char *pValue) +{ + int32_t iWidth, iCount; + int32_t iOffset, iLength, iIndent, iFlags; + + // make sure there is a header and extract fields + if (!_JsonValidHeader(pBuffer)) + return(JSON_ERR_UNINIT); + + iOffset = _JsonGetOffset(pBuffer); + iLength = _JsonGetLength(pBuffer); + iIndent = _JsonGetIndent(pBuffer); + iFlags = _JsonGetFlags(pBuffer); + + // there must be an open tag in the buffer + if (!_JsonOpenTag(pBuffer, iOffset)) + { + return(JSON_ERR_NOT_OPEN); + } + + // check if there's enough room for the insertion to complete successfully + iWidth = (int32_t)strlen(pValue); + // figure out indent size + iWidth += ((pValue[0] == '{') && (iFlags & JSON_FL_WHITESPACE)) ? (1 + iIndent * 2) : 0; + // we must be able to insert completely + if ((iLength - iOffset) <= iWidth) + { + return(JSON_ERR_FULL); + } + + // buffer is good, now start to determine the insertion type + // 1. value, handle indent if necessary + if (pValue[0] == '{') + { + if (iFlags & JSON_FL_WHITESPACE) + { + pBuffer[iOffset++] = '\n'; + for (iCount = 0; iCount < iIndent; ++iCount) + { + pBuffer[iOffset++] = ' '; + pBuffer[iOffset++] = ' '; + } + } + } + // 2. attr="value"> + else if (strchr(pValue, '"') != NULL) + { + // the tag must be open for accepting attributes (no children and no element text set) + if (!_JsonOpenForAttr(pBuffer, iOffset)) + { + return(JSON_ERR_ATTR_POSITION); + } + // replace the close tag '>' to ' ' for input (pValue[] must look like: attr="value">) + pBuffer[iOffset - 1] = ' '; + } + + // for all insertion types, insert the string + for (iCount = 0; pValue[iCount] != 0; ++iCount) + { + pBuffer[iOffset++] = pValue[iCount]; + } + pBuffer[iOffset] = 0; + + // update offset and return + _JsonSetOffset(pBuffer, iOffset); + return(JSON_ERR_NONE); +} +#endif + +/*F*************************************************************************************/ +/*! + \Function _JsonEncodeString + + \Description + Encode a string as needed for reserved characters + + \Input *pBuffer - the JSON buffer + \Input iLength - number of eight-bit characters available in buffer + \Input *_pSrc - source string to add to buffer + + \Output + int32_t - negative=not enough buffer, zero/positive=encoded length. + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonEncodeString(char *pBuffer, int32_t iLength, const char *_pSrc) +{ + char * const pBackup = pBuffer; + const uint8_t *pSource; + int32_t iRemain, iEncodeType; + + if (_pSrc == NULL) + { + _pSrc = "null"; + } + pSource = (const uint8_t *)_pSrc; + + // encode the string + for (iRemain = iLength; (iRemain > 1) && (*pSource != '\0'); ++pSource) + { + if ((*pSource < 128) && ((iEncodeType = _Json_EncodeStringTable[*pSource]) != 0)) + { + if ((iEncodeType == 2) && (iRemain > 6)) + { + // hex encode all out of range values + *pBuffer++ = '\\'; + *pBuffer++ = 'u'; + *pBuffer++ = '0'; + *pBuffer++ = '0'; + *pBuffer++ = _Json_Hex2AsciiTable[(*pSource)>>4]; + *pBuffer++ = _Json_Hex2AsciiTable[(*pSource)&0xF]; + iRemain -= 6; + } + else if ((iEncodeType == 1) && (iRemain > 1)) + { + *pBuffer++ = '\\'; + *pBuffer++ = *pSource; + } + else // buffer overrun + { + break; + } + } + else + { + // normal encoding + *pBuffer++ = *pSource; + --iRemain; + } + } + + // make sure space for terminator + if (iRemain > 0) + { + *pBuffer = 0; + } + + // if character(s) remains, return negative to indicate buffer-overrun. + if (*pSource != '\0') + { + // if buffer has been changed, reset the change(s) + if (pBackup != pBuffer) + { + *pBackup = '\0'; + } + return(-1); + } + + // return encoded size + return(iLength-iRemain); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonAddStr + + \Description + Adds a string element + + \Input *pBuffer - JSON buffer + \Input *pElemName - name of the element (may be null) + \Input *pValue - value of the element + \Input bQuote - TRUE if value should be quoted + + \Output + int32_t - JSON_ERR_* + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonAddStr(char *pBuffer, const char *pElemName, const char *pValue, uint8_t bQuote) +{ + int32_t iOffset, iLength, iNumChars, iIndent, iFlags, iTempLength; + + if (!_JsonValidHeader(pBuffer)) + { + return(JSON_ERR_UNINIT); + } + + iOffset = _JsonGetOffset(pBuffer); + iLength = _JsonGetLength(pBuffer); + iIndent = _JsonGetIndent(pBuffer); + iFlags = _JsonGetFlags(pBuffer); + + // there must be an open tag in the buffer + if (_JsonOpenTag(pBuffer, iOffset)) + { + iNumChars = 0; + if (iIndent <= _iLastIndent) + { + if ((iTempLength = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, ",")) <= 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(JSON_ERR_FULL); + } + iNumChars = iTempLength; + } + + if ((iTempLength = _JsonIndent(pBuffer+iNumChars, iLength-iNumChars, iOffset, iIndent, iFlags)) < 0) + { + return(JSON_ERR_FULL); + } + iNumChars += iTempLength; + + // add element name + if (pElemName != NULL) + { + if ((iTempLength = ds_snzprintf(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, "\"%s\":", pElemName)) <= 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(JSON_ERR_FULL); + } + iNumChars += iTempLength; + } + + // add leading quote + if (bQuote) + { + if ((iTempLength = ds_snzprintf(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, "\"")) <= 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(JSON_ERR_FULL); + } + iNumChars += iTempLength; + } + + // add element value + if ((iTempLength = _JsonEncodeString(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, pValue)) < 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(JSON_ERR_FULL); + } + iNumChars += iTempLength; + + // add trailing quote + if (bQuote) + { + if ((iTempLength = ds_snzprintf(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, "\"")) <= 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(JSON_ERR_FULL); + } + iNumChars += iTempLength; + } + + // all done successfully + return(_JsonUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent)); + } + else + { + return(JSON_ERR_NOT_OPEN); + } +} + + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function JsonInit + + \Description + Initialize the Json Buffer. This MUST be called prior to any other calls. + Client can allocate the buffer on stack, but should not access it directly. + (see JsonData()). + + \Input *pBuffer - buffer to which JSON will be written. + \Input iBufLen size of buffer passed in. + \Input uFlags - encoding flags (JSONFORMAT_FL_xxx) + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +void JsonInit(char *pBuffer, int32_t iBufLen, unsigned char uFlags) +{ + int32_t iOffset; + + ds_memclr(pBuffer, iBufLen); + if (iBufLen > JSON_HEADER_LEN) + { + iOffset = ds_snzprintf(pBuffer, iBufLen - 1, "{00000000000000000000}{"); + _JsonSetOffset(pBuffer, iOffset); + _JsonSetLength(pBuffer, iBufLen); + _JsonSetFlags(pBuffer, uFlags); + _JsonSetIndent(pBuffer, 1); + _iLastIndent = 0; + } +} + +/*F*************************************************************************************/ +/*! + \Function JsonBufSizeIncrease + + \Description + Notify the jsonformat API that the buffer size was increased. + + \Input *pBuffer - buffer to which JSON is being written. + \Input iNewBufLen new size for that buffer + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +void JsonBufSizeIncrease(char *pBuffer, int32_t iNewBufLen) +{ + _JsonSetLength(pBuffer, iNewBufLen); +} + +/*F*************************************************************************************/ +/*! + \Function JsonFinish + + \Description + Signal completion of output to this buffer. Clear the JSON API hidden + data. + + \Input *pBuffer buffer to which JSON was written. + + \Output char* - pointer to real JSON data (skipping past header) + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +char *JsonFinish(char *pBuffer) +{ + if (_JsonValidHeader(pBuffer)) + { + int32_t iOffset = _JsonGetOffset(pBuffer); + int32_t iBufLen = _JsonGetLength(pBuffer); + int32_t iFlags = _JsonGetFlags(pBuffer); + + // add final close tag + if (iFlags == JSON_FL_WHITESPACE) + { + ds_snzprintf(pBuffer + iOffset, iBufLen - iOffset, "\n}"); + } + else + { + ds_snzprintf(pBuffer + iOffset, iBufLen - iOffset, "}"); + } + + // remove header + ds_memset(pBuffer, ' ', JSON_HEADER_LEN); + pBuffer += JSON_HEADER_LEN; + } + return(pBuffer); +} + +/*F*************************************************************************************/ +/*! + \Function JsonObjectStart + + \Description + Start a JSON object, to which you can add other objects or elements. Must be + ended with JsonObjectEnd. + + \Input *pBuffer - JSON buffer + \Input *pName - name of the object (may be null) + + \Output + int32_t - JSON_ERR_* + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonObjectStart(char *pBuffer, const char *pName) +{ + int32_t iOffset, iLength, iNumChars, iIndent, iFlags; + + if (!_JsonValidHeader(pBuffer)) + { + return(JSON_ERR_UNINIT); + } + + iOffset = _JsonGetOffset(pBuffer); + iLength = _JsonGetLength(pBuffer); + iIndent = _JsonGetIndent(pBuffer); + iFlags = _JsonGetFlags(pBuffer); + + if (iIndent <= _iLastIndent) + { + if ((iNumChars = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, ",")) <= 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(JSON_ERR_FULL); + } + iOffset += iNumChars; + } + + if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent, iFlags)) < 0) + { + return(JSON_ERR_FULL); + } + iOffset += iNumChars; + + if (pName != NULL) + { + if ((iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "\"%s\":", pName)) < 0) + { + return(JSON_ERR_FULL); + } + iOffset += iNumChars; + } + + if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent++, iFlags)) < 0) + { + return(JSON_ERR_FULL); + } + iOffset += iNumChars; + + if ((iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "{")) < 0) + { + return(JSON_ERR_FULL); + } + return(_JsonUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonObjectEnd + + \Description + End the current object -- must have an outstanding open tag. + + \Input *pBuffer - JSON buffer. + + \Output + int32_t - JSON_ERR_* + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonObjectEnd(char *pBuffer) +{ + int32_t iOffset, iLength, iNumChars; + int32_t iIndent, iFlags; + + if (!_JsonValidHeader(pBuffer)) + { + return(JSON_ERR_UNINIT); + } + + iOffset = _JsonGetOffset(pBuffer); + iLength = _JsonGetLength(pBuffer); + iFlags = _JsonGetFlags(pBuffer); + iIndent = _JsonGetIndent(pBuffer) - 1; + + if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent, iFlags)) < 0) + { + return(JSON_ERR_FULL); + } + iOffset += iNumChars; + + iNumChars = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, "}"); + return(_JsonUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonArrayStart + + \Description + Start a JSON array, to which you can add other objects or elements. Must be + ended with JsonArrayEnd. + + \Input *pBuffer - JSON buffer + \Input *pName - name of the array (may be null) + + \Output + int32_t - JSON_ERR_* + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonArrayStart(char *pBuffer, const char *pName) +{ + int32_t iOffset, iLength, iNumChars, iIndent, iFlags; + + if (!_JsonValidHeader(pBuffer)) + { + return(JSON_ERR_UNINIT); + } + + iOffset = _JsonGetOffset(pBuffer); + iLength = _JsonGetLength(pBuffer); + iIndent = _JsonGetIndent(pBuffer); + iFlags = _JsonGetFlags(pBuffer); + + iNumChars = 0; + if (iIndent <= _iLastIndent) + { + int32_t iTempLength; + if ((iTempLength = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, ",")) <= 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(JSON_ERR_FULL); + } + iNumChars = iTempLength; + } + iOffset += iNumChars; + if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent, iFlags)) < 0) + { + return(JSON_ERR_FULL); + } + iOffset += iNumChars; + + if (pName != NULL) + { + if ((iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "\"%s\":", pName)) < 0) + { + return(JSON_ERR_FULL); + } + iOffset += iNumChars; + } + + if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent++, iFlags)) < 0) + { + return(JSON_ERR_FULL); + } + iOffset += iNumChars; + + if ((iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "[")) < 0) + { + return(JSON_ERR_FULL); + } + return(_JsonUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonArrayEnd + + \Description + End the current array -- must have an outstanding open array + + \Input *pBuffer - JSON buffer + + \Output + int32_t - JSON_ERR_* + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonArrayEnd(char *pBuffer) +{ + int32_t iOffset, iLength, iNumChars; + int32_t iIndent, iFlags; + + if (!_JsonValidHeader(pBuffer)) + { + return(JSON_ERR_UNINIT); + } + + iOffset = _JsonGetOffset(pBuffer); + iLength = _JsonGetLength(pBuffer); + iFlags = _JsonGetFlags(pBuffer); + iIndent = _JsonGetIndent(pBuffer) - 1; + + if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent, iFlags)) < 0) + { + return(JSON_ERR_FULL); + } + iOffset += iNumChars; + + iNumChars = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, "]"); + return(_JsonUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonAddStr + + \Description + Adds a string element + + \Input *pBuffer - JSON buffer + \Input *pElemName - name of the element (may be null) + \Input *pValue - value of the element + + \Output + int32_t - JSON_ERR_* + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonAddStr(char *pBuffer, const char *pElemName, const char *pValue) +{ + return(_JsonAddStr(pBuffer, pElemName, pValue, TRUE)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonAddInt + + \Description + Add an integer element + + \Input *pBuffer - JSON buffer + \Input *pElemName - name of the element + \Input iValue - integer value of the element + + \Output + int32_t - JSON_ERR_* + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonAddInt(char *pBuffer, const char *pElemName, int64_t iValue) +{ + char strInt[64]; + ds_snzprintf(strInt, sizeof(strInt), "%lld", iValue); + return(_JsonAddStr(pBuffer, pElemName, strInt, FALSE)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonAddNum + + \Description + Add a complete, contained decimal element. Builds start and end tag. + Nothing can be appended to this tag. Format it using formatSpec. + + \Input *pBuffer - JSON buffer + \Input *pElemName - name of the element + \Input *pFormatSpec - format spec for formatting the float (see printf) + \Input fValue - float value of the element + + \Output + int32_t - JSON_ERR_* + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonAddNum(char *pBuffer, const char *pElemName, const char *pFormatSpec, float fValue) +{ + char strFloat[JSON_MAX_FLOAT_LENGTH]; + + if (ds_snzprintf(strFloat, sizeof(strFloat), pFormatSpec, fValue) <= 0) + { + return(JSON_ERR_INVALID_PARAM); + } + return(_JsonAddStr(pBuffer, pElemName, strFloat, FALSE)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonAddDate + + \Description + Add a date (will be encoded as a string; use JsonAddInteger for integer encoding) + + \Input *pBuffer - JSON buffer + \Input *pElemName - name of the element + \Input uEpochDate - date/time since epoch + + \Output + int32_t - JSON_ERR_* + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonAddDate(char *pBuffer, const char *pElemName, uint32_t uEpochDate) +{ + struct tm *pTime, Time; + char strDate[JSON_MAX_DATE_LENGTH]; + + if ((pTime = ds_secstotime(&Time, uEpochDate)) == NULL) + { + return(JSON_ERR_INVALID_PARAM); + } + + ds_timetostr(pTime, TIMETOSTRING_CONVERSION_ISO_8601, FALSE, strDate, sizeof(strDate)); + return(_JsonAddStr(pBuffer, pElemName, strDate, FALSE)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonFormatVPrintf + + \Description + Takes a format string and variable parameter list and formats into syntax + correct JSON. The "v" (variable args) version can be called by other + functions that use "..." in the their prototype. + + \Input *pJsonBuff - JSON output buffer + \Input iBufLen - length of Json output buffer (negative=append to existing buffer) + \Input *pFormat - printf style format string + \Input pFmtArgs - variable argument list (use va_start / va_end to get) + + \Output + char * - pointer to formatted buffer + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +char *JsonFormatVPrintf(char *pJsonBuff, int32_t iBufLen, const char *pFormat, va_list pFmtArgs) +{ +#if 0 + int32_t iToken; + int32_t iIndex; + char strName[65]; + const char *pName; + const char *pParse; + unsigned char uFlags = 0; + /* table of allowed name characters. see http://www.w3.org/TR/xml/#charsets + for information on name characters allowed by the XML 1.0 specification */ + static char _ConvName[128] = + " " " " + " -. " "0123456789: " + " ABCDEFGHIJKLMNO" "PQRSTUVWXYZ _" + " abcdefghijklmno" "pqrstuvwxyz "; + + // determine if whitespace indenting is desired + if (*pFormat == ' ') + { + uFlags |= JSON_FL_WHITESPACE; + ++pFormat; + } + + // start the formatting + if (iBufLen >= 0) + JsonInit(pJsonBuff, iBufLen, uFlags); + + // parse the format string + for (pParse = pFormat; *pParse != 0; ++pParse) + { + // look for tag open + if (pParse[0] == '{') + { + for (iIndex = 0; (iIndex < (signed)(sizeof(strName) - 1)) && (pParse[iIndex+1] > 0) && (pParse[iIndex+1] < 127); ++iIndex) + { + if (_ConvName[(int32_t)pParse[iIndex+1]] <= ' ') + break; + strName[iIndex] = pParse[iIndex+1]; + } + strName[iIndex] = 0; + if (iIndex > 0) + { + JsonObjStart(pJsonBuff, strName); + } + } + + // parse name for assignments + if (pParse[0] == '=') + { + // find start of name + for (pName = pParse; (pName != pFormat) && (pName[-1] > 0) && (pName[-1] < 127); --pName) + { + if (_ConvName[pName[-1]&127] <= ' ') + break; + } + // copy and format name in private buffer + for (iIndex = 0; (iIndex < (signed)(sizeof(strName) - 1)) && (pName+iIndex != pParse); ++iIndex) + { + strName[iIndex] = pName[iIndex]; + } + strName[iIndex] = 0; + } + + // grab next 3 characters as a token + iToken = ' '; + iToken = (iToken << 8) | pParse[0]; + iToken = (iToken << 8) | pParse[1]; + iToken = (iToken << 8) | pParse[2]; + + // handle format tokens + if (pParse[0] == '}') + { + JsonObjEnd(pJsonBuff); + } + else if (iToken == ' >%s') + { + const char *pString = va_arg(pFmtArgs, const char *); + JsonElemSetString(pJsonBuff, pString); + } + else if (iToken == ' >%d') + { + int32_t iInteger = va_arg(pFmtArgs, int32_t); + JsonElemSetInt(pJsonBuff, iInteger); + } + else if (iToken == ' >%e') + { + uint32_t uEpoch = va_arg(pFmtArgs, uint32_t); + if (uEpoch == 0) + uEpoch = (uint32_t)ds_timeinsecs(); + JsonElemSetDate(pJsonBuff, uEpoch); + } + else if (iToken == ' >%a') + { + uint32_t uAddr = va_arg(pFmtArgs, uint32_t); + JsonElemSetAddr(pJsonBuff, uAddr); + } + else if (strName[0] == 0) + { + } + else if (iToken == ' =%s') + { + const char *pString = va_arg(pFmtArgs, const char *); + JsonAttrSetString(pJsonBuff, strName, pString); + } + else if (iToken == ' =%d') + { + int32_t iInteger = va_arg(pFmtArgs, int32_t); + JsonAttrSetInt(pJsonBuff, strName, iInteger); + } + else if (iToken == ' =%e') + { + uint32_t uEpoch = va_arg(pFmtArgs, uint32_t); + if (uEpoch == 0) + uEpoch = (uint32_t)ds_timeinsecs(); + JsonAttrSetDate(pJsonBuff, strName, uEpoch); + } + else if (iToken == ' =%a') + { + uint32_t uAddr = va_arg(pFmtArgs, uint32_t); + JsonAttrSetAddr(pJsonBuff, strName, uAddr); + } + } + + // finish up and return data pointer + if (iBufLen >= 0) + pJsonBuff = JsonFinish(pJsonBuff); + return(pJsonBuff); + #else + return(NULL); + #endif +} + +/*F*************************************************************************************/ +/*! + \Function JsonFormatPrintf + + \Description + Takes a format string and variable parameter list and formats into syntax + correct JSON. + + \Input *pJsonBuff - JSON output buffer + \Input iBufLen - length of Json output buffer (negative=append to existing buffer) + \Input *pFormat - printf style format string + + \Output + char * - pointer to formatted buffer + + \Version 12/11/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +char *JsonFormatPrintf(char *pJsonBuff, int32_t iBufLen, const char *pFormat, ...) +{ + char *pResult; + va_list pFmtArgs; + + // setup varargs and call real function + va_start(pFmtArgs, pFormat); + pResult = JsonFormatVPrintf(pJsonBuff, iBufLen, pFormat, pFmtArgs); + va_end(pFmtArgs); + + // return pointer to start of valid JSON data (skips leading spaces) + return(pResult); +} diff --git a/src/thirdparty/dirtysdk/source/util/jsonparse.c b/src/thirdparty/dirtysdk/source/util/jsonparse.c new file mode 100644 index 00000000..baf03d94 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/jsonparse.c @@ -0,0 +1,1229 @@ +/*H*************************************************************************************/ +/*! + \File jsonparse.c + + \Description + Simple JSON parser. + + \Copyright + Copyright (c) Electronic Arts 2012. + + \Notes + Written by Greg Schaefer outside of EA for a personal project, but donated + back for a good cause. + + The parse buffer contains a list of objects, consisting of an offset and size. + The offset is the offset of the object in the JSON buffer. The size is the + size of the object in the JSON buffer, except for arrays and objects, where it + is the size of the object in the parse buffer (used when skipping past an array + or object). Values are written as uint16_t, with progressive encoding employed + when required. + + UNICODE support is limited to extracting ASCII characters that are encoded + as UNICODE. + + \Todo + Implement support for getting real numbers + + \Version 12/11/2012 (jbrookes) Added to DirtySDK, added some new functionality +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/util/jsonparse.h" + +/*** Defines ***************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Variables *************************************************************************/ + +//! decode table to classify characters as whitespace, number, letter, bracket, brace, or invalid +static const uint8_t _strDecode[256] = +{ + 0,' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', // 00..0f + ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', // 10..1f + ' ', 0,'"', 0, 0, 0, 0, 0, 0, 0, 0, 0,',','1', 0, 0, // 20..2f + '1','1','1','1','1','1','1','1','1','1',':', 0, 0, 0, 0, 0, // 30..3f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..4f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'[','\\',']', 0, 0, // 50..5f + ' ','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', // 60..6f + 'a','a','a','a','a','a','a','a','a','a','a','{',0 ,'}', 0, 0, // 70..7f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80..8f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 90..9f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // a0..af + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // b0..bf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // c0..cf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // d0..df + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // e0..ef + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // f0..ff +}; + +//! characters that need to be escaped +static const uint8_t _strEscape[256] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..0f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10..1f + 0, 0,'"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'/', // 20..2f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30..3f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..4f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50..5f + 0, 0, 8, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 10, 0, // 60..6f + 0, 0, 0, 0, 0,'u', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 70..7f + 0, 0, 13, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80..8f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 90..9f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // a0..af + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // b0..bf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // c0..cf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // d0..df + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // e0..ef + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // f0..ff +}; + +//! hex->int decode table +static const uint8_t _HexDecode[256] = +{ + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,128,128,128,128,128,128, + 128, 10, 11, 12, 13, 14, 15,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128, 10, 11, 12, 13, 14, 15,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128 +}; + + +/*** Private Functions *****************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function _JsonGetQuoted + + \Description + Get text from a quoted string + + \Input *pDst - [out] storage for text + \Input iLim - size of output buffer + \Input *pSrc - pointer to quoted string + \Input iSkip - output buffer increment when writing (zero for length check) + + \Output + int32_t - returns string length + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +static int32_t _JsonGetQuoted(uint8_t *pDst, int32_t iLim, const uint8_t *pSrc, int32_t iSkip) +{ + int32_t iLen = 0; + uint8_t c; + + // make room for terminator + iLim -= 1; + + // make sure its a quoted string + if (*pSrc == '"') + { + for (++pSrc; (iLen < iLim) && (c = *pSrc) != '"'; ++pSrc) + { + // handle escape sequence + if ((c == '\\') && (pSrc[1] > ' ')) + { + c = _strEscape[*++pSrc]; + if ((c == 'u') && (pSrc[1] != '"') && (pSrc[2] != '"') && (pSrc[3] != '"') && (pSrc[4] != '"')) + { + c = (_HexDecode[pSrc[3]]<<4)|(_HexDecode[pSrc[4]]<<0), pSrc += 4; + } + } + // conditional save (to use as length check) + *pDst = c; + pDst += iSkip; + ++iLen; + } + *pDst = '\0'; + } + + return(iLen); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonParseWrite + + \Description + Write a value to the parse buffer. Depending on size, the value will be encoded + in one or two 16 bit values using a progressive encoding scheme. + + \Input *pParse - [out] buffer for parse value + \Input *pMax - limit of parse buffer + \Input uValue - value to write to parse buffer + \Input bWide - if TRUE, write using wide encoding + + \Output + uint16_t * - pointer to next value + + \Notes + Values from zero to 0x7fff are written as-is using a uint16_t. Larger values + are written with the lower 15 bits or'd with 0x8000, followed by bits 15-31 in + the following uint16_t. The largest number that can be encoded this way is + 0x7fffffff, as one bit is sacrificed for the progressive bit. + + \Version 02/02/2016 (jbrookes) +*/ +/*************************************************************************************F*/ +static uint16_t *_JsonParseWrite(uint16_t *pParse, uint16_t *pMax, uint32_t uValue, uint8_t bWide) +{ + if ((uValue > 0x7fff) || (bWide == TRUE)) + { + if (pParse < pMax) + { + *pParse = (uint16_t)uValue | 0x8000; + } + uValue >>= 15; + pParse += 1; + } + if (pParse < pMax) + { + *pParse = (uint16_t)uValue; + } + pParse += 1; + return(pParse); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonParseRead + + \Description + Read a progressively-encoded value from the parse buffer. + + \Input *pParse - parse buffer + \Input *pValue - [out] storage for value read from parse buffer + \Input *pParseMax - end of parse buffer + + \Output + uint16_t * - pointer to next value + + \Notes + See _JsonParseWrite for a description of the progressive encoding scheme. + + \Version 02/02/2016 (jbrookes) +*/ +/*************************************************************************************F*/ +static const uint16_t *_JsonParseRead(const uint16_t *pParse, uint32_t *pValue, const uint16_t *pParseMax) +{ + *pValue = 0; + if (pParse < pParseMax) + { + *pValue = *pParse; + } + pParse += 1; + if (*pValue & 0x8000) + { + *pValue &= ~0x8000; + if (pParse < pParseMax) + { + *pValue |= (uint32_t)*pParse << 15; + } + pParse += 1; + } + return(pParse); +} + +/*F*************************************************************************************/ +/*! + \Function _JsonParseReadOffsetAndSize + + \Description + Read offset and size values. If offset exceeds parse buffer size, an offset and + size of zero are returned. + + \Input *pParse - parse buffer + \Input *pOffset - [out] storage for offset + \Input *pSize - [out] storage for size + \Input *pParseMax - end of parse buffer + + \Output + uint16_t * - pointer to next parse object + + \Notes + See _JsonParseWrite for a description of the progressive encoding scheme. + + \Version 02/02/2016 (jbrookes) +*/ +/*************************************************************************************F*/ +static const uint16_t *_JsonParseReadOffsetAndSize(const uint16_t *pParse, uint32_t *pOffset, uint32_t *pSize, const uint16_t *pParseMax) +{ + pParse = _JsonParseRead(pParse, pOffset, pParseMax); + pParse = _JsonParseRead(pParse, pSize, pParseMax); + if (pParse > pParseMax) + { + *pOffset = 0; + *pSize = 0; + } + return(pParse); +} + + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************/ +/*! + \Function JsonParse + + \Description + Pre-parse a JSON buffer for fast parsing. Must be called on a buffer before + any Find() or Get() functions are used. Pass pDst=NULL and iMax=0 to query + the required table size. + + \Input *pDst - [out] storage for lookup table, two elements per object + \Input iMax - number of uint16_t elements in output table + \Input *_pSrc - pointer to JSON buffer + \Input iLen - length of JSON buffer; if -1 a strlen is performed + + \Output + int32_t - zero=table too small, else size of table in bytes + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonParse(uint16_t *pDst, int32_t iMax, const char *_pSrc, int32_t iLen) +{ + uint8_t c; + int32_t iArray = 0; + int32_t iObject = 0; + uint16_t *pArray[16]; + uint16_t *pObject[16]; + const uint8_t *pSrc = (const uint8_t *)_pSrc; + const uint8_t *pOff = pSrc; + const uint8_t *pEnd; + uint16_t *pBeg = pDst; + uint16_t *pMax = pDst+iMax; + uint32_t uStart; + uint8_t bTruncated = FALSE; + int32_t iBufSize; + + // easy string length + if (iLen < 0) + { + iLen = (int32_t)strlen(_pSrc); + } + pEnd = pSrc+iLen; + + // check size and save json pointer in parse buffer + if (iMax != 0) + { + // make sure we have enough space to store json pointer plus at least one max-sized json parse object + if (iMax < (int32_t)(sizeof(pSrc)/sizeof(uint16_t)) + 4 + 4) + { + return(0); + } + // save json pointer at start of parse buffer + ds_memcpy(pDst, &pSrc, sizeof(pSrc)); + // save max size + ds_memcpy(pDst + (sizeof(pSrc)/sizeof(*pDst)), &iMax, sizeof(iMax)); + } + // add space for pointer + pDst += sizeof(pSrc)/sizeof(*pDst); + // add space for max size + pDst += sizeof(iMax)/sizeof(*pDst); + + // find the tokens + for (; pSrc != pEnd; ++pSrc) + { + c = _strDecode[*pSrc]; + + // handle end of decoding + if (c == '\0') + { + break; + } + // handle white space + if (c == ' ') + { + continue; + } + // save start of token + pDst = _JsonParseWrite(pDst, pMax, uStart = (uint32_t)(pSrc-pOff), FALSE); + + // handle number + if (c == '1') + { + // skip past number + for (++pSrc; (pSrc != pEnd) && (*pSrc >= '0') && (*pSrc <= '9'); ++pSrc) + ; + // handle decimal ending + if ((pSrc != pEnd) && (*pSrc == '.')) + { + for (++pSrc; (pSrc != pEnd) && (*pSrc >= '0') && (*pSrc <= '9'); ++pSrc) + ; + } + // handle exponent + if ((pSrc != pEnd) && ((*pSrc|32) == 'e')) + { + // gobble extension after exponent + ++pSrc; + if ((pSrc != pEnd) && ((*pSrc == '+') || (*pSrc == '-'))) + { + ++pSrc; + } + for (; (pSrc != pEnd) && (*pSrc >= '0') && (*pSrc <= '9'); ++pSrc) + ; + } + --pSrc; + pDst = _JsonParseWrite(pDst, pMax, (uint32_t)((pSrc-pOff)-uStart+1), FALSE); + } + // handle string + else if (c == '"') + { + // walk the string + for (++pSrc; (pSrc != pEnd) && (*pSrc != '"'); ++pSrc) + { + if ((*pSrc == '\\') && (pSrc[1] > ' ')) + { + ++pSrc; + } + } + pDst = _JsonParseWrite(pDst, pMax, (uint32_t)((pSrc-pOff)-uStart+1), FALSE); + } + // handle token + else if (c == 'a') + { + for (++pSrc; (pSrc != pEnd) && (_strDecode[*pSrc] == 'a'); ++pSrc) + ; + --pSrc; + pDst = _JsonParseWrite(pDst, pMax, (uint32_t)((pSrc-pOff)-uStart+1), FALSE); + } + // handle object open + else if ((c == '{') && (iObject < (int32_t)(sizeof(pObject)/sizeof(pObject[0])))) + { + pObject[iObject++] = pDst; + pDst = _JsonParseWrite(pDst, pMax, 0, TRUE); + } + // handle object close + else if ((c == '}') && (iObject > 0)) + { + --iObject; + _JsonParseWrite(pObject[iObject], pMax, (uint32_t)(pDst-pObject[iObject]), TRUE); + pDst = _JsonParseWrite(pDst, pMax, 1, FALSE); + } + // handle array open + else if ((c == '[') && (iArray < (int32_t)(sizeof(pArray)/sizeof(pArray[0])))) + { + pArray[iArray++] = pDst; + pDst = _JsonParseWrite(pDst, pMax, 0, TRUE); + } + // handle array close + else if ((c == ']') && (iArray > 0)) + { + --iArray; + _JsonParseWrite(pArray[iArray], pMax, (uint32_t)(pDst-pArray[iArray]), TRUE); + pDst = _JsonParseWrite(pDst, pMax, 1, FALSE); + } + else + { + pDst = _JsonParseWrite(pDst, pMax, 1, FALSE); + } + } + + // close any remaining objects -- invalid JSON? + while (iObject-- > 0) + { + _JsonParseWrite(pObject[iObject], pMax, (uint32_t)(pDst-pObject[iObject]), TRUE); + } + while (iArray-- > 0) + { + _JsonParseWrite(pArray[iArray], pMax, (uint32_t)(pDst-pArray[iArray]), TRUE); + } + + // make sure we always terminate output buffer + if ((iMax != 0) && (pDst > pMax-2)) + { + pDst = pMax-2; + bTruncated = TRUE; + } + // terminate output buffer + pDst = _JsonParseWrite(pDst, pMax, 0, FALSE); + pDst = _JsonParseWrite(pDst, pMax, 0, FALSE); + + // calculate size of buffer in bytes + iBufSize = (int32_t)(((uint8_t *)pDst) - ((uint8_t *)pBeg)); + + // return size in bytes, or zero if the table was truncated + return(bTruncated ? 0 : iBufSize); +} + +/*F*************************************************************************************/ +/*! +\Function JsonParse2 + + \Description + Pre-parse a JSON buffer for fast parsing. Must be called on a buffer before + any Find() or Get() functions are used. This version allocates the required + amount of memory internally rather than taking the buffer as input. + + \Input *pSrc - pointer to JSON buffer + \Input iLen - length of JSON buffer; if -1 a strlen is performed + \Input iMemModule - memory module passed to DirtyMemAlloc + \Input iMemGroup - memory group passed to DirtyMemAlloc + \Input *pMemGroupUserData - memory group user data passed to DirtyMemAlloc + + \Output + uint16_t * - allocated parse table, or NULL on error + + \Version 12/05/2016 (jbrookes) +*/ +/*************************************************************************************F*/ +uint16_t *JsonParse2(const char *pSrc, int32_t iLen, int32_t iMemModule, int32_t iMemGroup, void *pMemGroupUserData) +{ + uint16_t *pBuff; + int32_t iSize; + + // get buffer size + if ((iSize = JsonParse(NULL, 0, pSrc, iLen)) == 0) + { + return(NULL); + } + // allocate buffer space + if ((pBuff = DirtyMemAlloc(iSize, iMemModule, iMemGroup, pMemGroupUserData)) == NULL) + { + return(NULL); + } + // parse the buffer + if (JsonParse(pBuff, iSize, pSrc, iLen) != iSize) + { + DirtyMemFree(pBuff, iMemModule, iMemGroup, pMemGroupUserData); + pBuff = NULL; + } + return(pBuff); +} + +/*F*************************************************************************************/ +/*! + \Function JsonFind + + \Description + Locate a JSON element + + \Input *pParse - pre-parsed lookup from JsonParse + \Input *pName - name of element to locate + + \Output + const char * - pointer to element, or NULL if not found + + \Notes + A name can include a period character '.' which indicates an object reference, + or an open bracket '[' which indicates an array reference. A bracket must be + the last character in the name. + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +const char *JsonFind(const uint16_t *pParse, const char *pName) +{ + return(JsonFind2(pParse, NULL, pName, 0)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonFind2 + + \Description + Locate a JSON element, starting from an offset, with an optional array index + + \Input *pParse - pre-parsed lookup from JsonParse + \Input *_pJsonOff - offset into JSON buffer to begin search + \Input *pName - name of element to locate + \Input iIndex - [optional] index of array element to return + + \Output + const uint8_t * - pointer to element, or NULL if not found + + \Notes + This version of Find is useful for parsing arrays of scalars or objects, or + for starting a parse at an offset within the JSON document. + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +const char *JsonFind2(const uint16_t *pParse, const char *_pJsonOff, const char *pName, int32_t iIndex) +{ + const uint8_t *pJsonOff = (const uint8_t *)_pJsonOff; + const uint8_t *pJson; + const char *pLast, *pElem; + const uint16_t *pParseMax, *pTemp, *pTemp2; + int32_t iLen, iMax; + uint32_t uOffset, uOffsetNext, uSize, uSizeNext, uTemp; + uint8_t c; + + // save pointer for parse max + pParseMax = pParse; + + // get json data pointer; note cast to avoid invalid vc warning -- thinks &pJson is const whereas it points to something const + ds_memcpy((uint8_t *)&pJson, pParse, sizeof(pJson)); + pParse += sizeof(pJson)/sizeof(*pParse); + // get parse buffer max size + ds_memcpy(&iMax, pParse, sizeof(iMax)); + pParse += sizeof(iMax)/sizeof(*pParse); + + // point to end of parse buffer + pParseMax += iMax; + + // if json has outer object, then skip it + pTemp = _JsonParseRead(pParse, &uOffset, pParseMax); + if (pJson[uOffset] == '{') + { + pParse = _JsonParseRead(pTemp, &uTemp, pParseMax); + } + + // if they gave us an offset, move the parse forward to match + if (pJsonOff != NULL) + { + uint32_t uJsonOff = (uint32_t)(pJsonOff - pJson); + while (1) + { + pTemp = _JsonParseReadOffsetAndSize(pParse, &uOffset, &uSize, pParseMax); + if ((uOffset >= uJsonOff) || (uSize == 0)) + { + break; + } + pParse = pTemp; + } + } + + // parse the find string + for (uSize = 1; (*pName != 0) && (uSize != 0); ) + { + pTemp = _JsonParseReadOffsetAndSize(pParse, &uOffset, &uSize, pParseMax); + + // handle traversing into an object + if (*pName == '.') + { + // if not an object, then its an error + if (pJson[uOffset] != '{') + { + break; + } + // move into object and keep parsing + pParse = pTemp; + ++pName; + continue; + } + // handle traversing into an array + else if (*pName == '[') + { + // if not an array, then its an error + if (pJson[uOffset] != '[') + { + break; + } + // skip to nth element + for (pParse = pTemp, uSize = 1; (iIndex > 0) && (uSize != 0); iIndex -= 1) + { + pTemp = _JsonParseReadOffsetAndSize(pParse, &uOffset, &uSize, pParseMax); + c = pJson[uOffset]; + if (c == ']') + { + break; + } + else if (c == '{') + { + // skip to end of object + pParse += uSize; + pTemp = pParse; + } + // skip object + if (uSize != 0) + { + pTemp = _JsonParseReadOffsetAndSize(pParse, &uOffset, &uSize, pParseMax); + } + // skip trailing comma? + pTemp = _JsonParseReadOffsetAndSize(pTemp, &uOffset, &uSize, pParseMax); + if ((uSize != 0) && (pJson[uOffset] == ',')) + { + pParse = pTemp; + } + } + // if we didn't end on a close bracket we've found our element + if ((uSize != 0) && (pJson[uOffset] != ']')) // must re-check because we could end exactly on the close bracket + { + pName = "\0"; + } + break; + } + // check to see if object name matches + else if (((*pName|32) >= 'a') && ((*pName|32) <= 'z')) + { + // figure out length + for (pLast = pName; (*pLast != 0) && (*pLast != '.') && (*pLast != '['); ++pLast) + ; + iLen = (int32_t)(pLast-pName); + + // search for this name + for (uSize = 1; uSize != 0; pParse = pTemp) + { + pTemp = _JsonParseReadOffsetAndSize(pParse, &uOffset, &uSize, pParseMax); + c = pJson[uOffset]; + + // skip arrays & objects + if ((c == '[') || (c == '{')) + { + pTemp = _JsonParseReadOffsetAndSize(pParse+uSize, &uOffset, &uSize, pParseMax); + if (uSize == 0) + { + break; + } + continue; + } + + // end of array/object is terminator, or we hit the end of the parse buffer + if ((c == ']') || (c == '}') || (uSize == 0)) + { + return(NULL); + } + + // look for a label + pTemp2 = _JsonParseReadOffsetAndSize(pTemp, &uOffsetNext, &uSizeNext, pParseMax); + if ((c == '"') && (pJson[uOffsetNext] == ':') && (iLen+2 == (signed)uSize)) + { + if (memcmp(pJson+uOffset+1, pName, iLen) == 0) + { + // skip the label + pParse = pTemp2; + uOffset = uOffsetNext; + uSize = uSizeNext; + pName = pLast; + break; + } + } + } + } + else + { + // invalid pattern + return(NULL); + } + } + + // get current offset and size from parse + _JsonParseReadOffsetAndSize(pParse, &uOffset, &uSize, pParseMax); + + /* point to element; validate the name matches and that we are pointing at a valid element. both checks + are required, as we could have found the name successfully, but then failed to find the element */ + if ((*pName == '\0') && (uSize != 0)) + { + pElem = (const char *)(pJson+uOffset); + } + else + { + pElem = NULL; + } + return(pElem); +} + +/*F*************************************************************************************/ +/*! + \Function JsonGetString + + \Description + Extract a JSON string element. Pass pBuffer=NULL for length check. + + \Input *pJson - JSON buffer + \Input *pBuffer - [out] storage for string (may be NULL for length query) + \Input iLength - length of output buffer + \Input *pDefault - default value to use, if string could not be extracted + + \Output + int32_t - length of string, or negative on error + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonGetString(const char *pJson, char *pBuffer, int32_t iLength, const char *pDefault) +{ + return(JsonGetString2(pJson, pBuffer, iLength, pDefault, NULL)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonGetString2 + + \Description + Extract a JSON string element. Pass pBuffer=NULL for length check. Error + accumulating version. + + \Input *pJson - JSON buffer + \Input *pBuffer - [out] storage for string (may be NULL for length query) + \Input iLength - length of output buffer + \Input *pDefault - default value to use, if string could not be extracted + \Input *pError - [in/out] accumulation flag for parse errors; 1=error, 0=no error + + \Output + int32_t - length of string, or negative on error + + \Version 12/05/2016 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonGetString2(const char *pJson, char *pBuffer, int32_t iLength, const char *pDefault, uint8_t *pError) +{ + uint8_t c; + + // handle default value + if (pJson == NULL) + { + // error if no default string + if (pDefault == NULL) + { + if (pError != NULL) + { + *pError = 1; + } + return(-1); + } + // copy string if it isn't a length-only query + if (pBuffer != NULL) + { + ds_strnzcpy(pBuffer, pDefault, iLength); + pDefault = pBuffer; + } + // return the length + return((int32_t)strlen(pDefault)); + } + + // handle length query + if (pBuffer == NULL) + { + return(_JsonGetQuoted(&c, INT32_MAX, (const uint8_t *)pJson, 0)); + } + + // make sure return buffer has terminator length + if (iLength < 1) + { + if (pError != NULL) + { + *pError = 1; + } + return(-1); + } + + // copy the string + return(_JsonGetQuoted((uint8_t *)pBuffer, iLength, (const uint8_t *)pJson, 1)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonGetInteger + + \Description + Extract a JSON integer number element. + + \Input *pJson - JSON buffer + \Input iDefault - default value to use, if number could not be extracted + + \Output + int64_t - integer, or default value + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int64_t JsonGetInteger(const char *pJson, int64_t iDefault) +{ + return(JsonGetInteger2(pJson, iDefault, INT64_MIN, INT64_MAX, NULL)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonGetInteger2 + + \Description + Extract a JSON integer number element with range checking and error accumulation. + Pass zero for both iMin and iMax to disable range checking. + + \Input *pJson - JSON buffer + \Input iDefault - default value to use, if number could not be extracted + \Input iMin - min value for range checking + \Input iMax - max value for range checking + \Input *pError - [in/out] accumulation flag for parse errors; 1=error, 0=no error + + \Output + int64_t - integer, or default value + + \Version 12/05/2016 (jbrookes) +*/ +/*************************************************************************************F*/ +int64_t JsonGetInteger2(const char *pJson, int64_t iDefault, int64_t iMin, int64_t iMax, uint8_t *pError) +{ + int64_t iSign = 1; + int64_t iValue; + + // handle default + if (pJson == NULL) + { + if (pError != NULL) + { + *pError = 1; + } + return(iDefault); + } + + // handle negative + if (*pJson == '-') + { + ++pJson; + iSign = -1; + } + + // parse the value + for (iValue = 0; (*pJson >= '0') && (*pJson <= '9'); ++pJson) + { + iValue = (iValue * 10) + (*pJson & 15); + } + + // range check + if ((iMin != 0) || (iMax != 0)) + { + iValue = DS_CLAMP(iValue, iMin, iMax); + } + + // return with sign + return(iSign*iValue); +} + +/*F*************************************************************************************/ +/*! + \Function JsonGetDate + + \Description + Extract a JSON date element + + \Input *pJson - JSON buffer + \Input uDefault - default date value to use + + \Output + uint32_t - extracted date, or default + + \Notes + Date is assumed to be in ISO_8601 format + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +uint32_t JsonGetDate(const char *pJson, uint32_t uDefault) +{ + return(JsonGetDate2(pJson, uDefault, NULL)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonGetDate2 + + \Description + Extract a JSON date element + + \Input *pJson - JSON buffer + \Input uDefault - default date value to use + \Input *pError - [in/out] accumulation flag for parse errors; 1=error, 0=no error + + \Output + uint32_t - extracted date, or default + + \Notes + Date is assumed to be in ISO_8601 format + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +uint32_t JsonGetDate2(const char *pJson, uint32_t uDefault, uint8_t *pError) +{ + uint32_t uDate = uDefault; + char strValue[128]; + + JsonGetString(pJson, strValue, sizeof(strValue), ""); + if ((uDate = (uint32_t)ds_strtotime2(strValue, TIMETOSTRING_CONVERSION_ISO_8601)) == 0) + { + uDate = uDefault; + if (pError != NULL) + { + *pError = 1; + } + } + return(uDate); +} + +/*F*************************************************************************************/ +/*! + \Function JsonGetBoolean + + \Description + Extract a JSON boolean + + \Input *pJson - JSON buffer + \Input bDefault - default boolean value to use + + \Output + uint8_t - extracted bool, or default + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +uint8_t JsonGetBoolean(const char *pJson, uint8_t bDefault) +{ + return(JsonGetBoolean2(pJson, bDefault, NULL)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonGetBoolean2 + + \Description + Extract a JSON boolean, with accumulating error + + \Input *pJson - JSON buffer + \Input bDefault - default boolean value to use + \Input *pError - [in/out] accumulation flag for parse errors; 1=error, 0=no error + + \Output + uint8_t - extracted bool, or default + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +uint8_t JsonGetBoolean2(const char *pJson, uint8_t bDefault, uint8_t *pError) +{ + uint8_t bValue = bDefault; + + // handle default + if (pJson == NULL) + { + if (pError != NULL) + { + *pError = 1; + } + return(bDefault); + } + // check for true + if (strncmp((const char *)pJson, "true", 4) == 0) + { + bValue = TRUE; + } + else if (strncmp((const char *)pJson, "false", 4) == 0) + { + bValue = FALSE; + } + else if (pError != NULL) + { + *pError = 1; + } + // return result + return(bValue); +} + +/*F*************************************************************************************/ +/*! + \Function JsonGetEnum + + \Description + Extract a JSON enumerated value. The source type is assumed to be a string, + and pEnumArray contains a NULL-terminated list of strings that may be converted + to an enum value. + + \Input *pJson - JSON buffer + \Input *pEnumArray - NULL-terminated list of enum strings + \Input iDefault - default enum value to use if no match is found + + \Output + int32_t - enum value; or default + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonGetEnum(const char *pJson, const char *pEnumArray[], int32_t iDefault) +{ + return(JsonGetEnum2(pJson, pEnumArray, iDefault, NULL)); +} + +/*F*************************************************************************************/ +/*! + \Function JsonGetEnum2 + + \Description + Extract a JSON enumerated value, with accumulating error. The source type is + assumed to be a string, and pEnumArray contains a NULL-terminated list of strings + that may be converted to an enum value. + + \Input *pJson - JSON buffer + \Input *pEnumArray - NULL-terminated list of enum strings + \Input iDefault - default enum value to use if no match is found + \Input *pError - [in/out] accumulation flag for parse errors; 1=error, 0=no error + + \Output + int32_t - enum value; or default + + \Version 12/13/2012 (jbrookes) +*/ +/*************************************************************************************F*/ +int32_t JsonGetEnum2(const char *pJson, const char *pEnumArray[], int32_t iDefault, uint8_t *pError) +{ + int32_t iEnum = iDefault; + char strValue[128]; + + JsonGetString(pJson, strValue, sizeof(strValue), ""); + for (iEnum = 0; pEnumArray[iEnum] != NULL; iEnum += 1) + { + if (!strcmp(pEnumArray[iEnum], strValue)) + { + return(iEnum); + } + } + if (pError != NULL) + { + *pError = 1; + } + return(iDefault); +} + +/*F*************************************************************************************************/ +/*! + \Function JsonSeekObjectEnd + + \Description + Seek to the end of an object within the JSON text buffer + + \Input *pList - pointer to the beginning of object + + \Output + const char * - pointer to the end of the object, NULL for error + + \Version 04/17/2013 (amakoukji) +*/ +/*************************************************************************************************F*/ +const char *JsonSeekObjectEnd(const char *pList) +{ + const char *pCurrent = pList; + const char *pEnd = pList + (int32_t)strlen(pList); + uint32_t uDepth = 0; + char c; + + while (pCurrent < pEnd) + { + c = *pCurrent; + + switch (c) + { + case '"': + { + // walk the string + for (++pCurrent; (pCurrent != pEnd) && (*pCurrent != '"'); ++pCurrent) + { + if ((*pCurrent == '\\') && (pCurrent[1] > ' ')) + { + ++pCurrent; + } + } + } + break; + + case '{': + { + uDepth += 1; + } + break; + + case '}': + { + uDepth -= 1; + } + break; + + case ',': + { + if (uDepth == 0) + { + return(pCurrent); + } + } + break; + + case ']': + { + if (uDepth == 0) + { + return(pCurrent); + } + } + break; + } + + pCurrent += 1; + } + + // error + return(NULL); +} + +/*F*************************************************************************************************/ +/*! + \Function JsonSeekValue + + \Description + Given a pointer to the beginning of a simple JSON key value pair, seeks the + start of the value + + \Input *pKey - module ref + + \Output + int32_t - pointer to the start of the value, NULL for error + + \Version 11/28/2013 (amakoukji) +*/ +/*************************************************************************************************F*/ +const char *JsonSeekValue(const char *pKey) +{ + const char *pTemp = pKey; + + if (pKey == NULL) + { + return(NULL); + } + + // seek to the ':' + while (*pTemp != ':') + { + if ((*pTemp == '\0') || (*pTemp == '\n')) + { + return(NULL); + } + pTemp += 1; + } + pTemp += 1; + + // seek to non-whitespace + while ((*pTemp == ' ') || (*pTemp == '\t')) + { + if ((*pTemp == '\0') || (*pTemp == '\n')) + { + return(NULL); + } + pTemp += 1; + } + + return(pTemp); +} + diff --git a/src/thirdparty/dirtysdk/source/util/murmurhash3.c b/src/thirdparty/dirtysdk/source/util/murmurhash3.c new file mode 100644 index 00000000..792e4950 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/murmurhash3.c @@ -0,0 +1,448 @@ +/*H********************************************************************************/ +/*! + \File murmurhash3.c + + \Description + An implementation of MurmurHash3, based heavily on the x64 128bit output + implementation. MurmurHash3 is a public domain hashing algorithm written + by Austin Appleby, ref http://code.google.com/p/smhasher/wiki/MurmurHash3. + + MurmurHash3 is not considered cryptographically secure, however it has + excellent collision resistance and is orders of magnitude faster than the + fastest secure cryptographic hash. + + \Copyright + Copyright (c) 2014 Electronic Arts + + \Version 03/07/2014 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include + +#include "DirtySDK/util/murmurhash3.h" + +/*** Defines **********************************************************************/ + +#if defined(_MSC_VER) + #define MH3_FORCE_INLINE __forceinline +#else + #define MH3_FORCE_INLINE __inline__ __attribute__((always_inline)) +#endif + + +#define MURMURHASH3_C1 (0x87c37b91114253d5) +#define MURMURHASH3_C2 (0x4cf5ad432745937f) + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +#if !defined(_MSC_VER) +/*F********************************************************************************/ +/*! + \Function _rotl64 + + \Description + Implementation of _rotl64 for non-Windows. + + \Input x - 64bit data to rotate + \Input r - number of bits to rotate + + \Output + uint64_t - rotated data + + \Version 03/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static MH3_FORCE_INLINE uint64_t _MurmurHash3Rotl64(uint64_t x, int8_t r) +{ + return (x << r) | (x >> (64 - r)); +} +#else + #define _MurmurHash3Rotl64(x, y) _rotl64(x, y) +#endif + +/*F********************************************************************************/ +/*! + \Function _MurmurHash3Fmix + + \Description + 'fmix' operation from murmurhash3 + + \Input k - 64bit data to mix + + \Output + uint64_t - 64 bits of mixed data + + \Version 03/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static MH3_FORCE_INLINE uint64_t _MurmurHash3Fmix (uint64_t k) +{ + k ^= k >> 33; + k *= 0xff51afd7ed558ccd; + k ^= k >> 33; + k *= 0xc4ceb9fe1a85ec53; + k ^= k >> 33; + return(k); +} + +/*F********************************************************************************/ +/*! + \Function _MurmurHash3Read64 + + \Description + Read 64 bits of input data. Little-endian versions that can handle an + unaligned 64bit load from memory do a direct read for speed. Other + versions execute a portable 64bit load, which is slower. + + \Input *pData - input data to read + \Input iData - 64bit offset of data to read + + \Output + uint64_t - 64 bits of data ready to be hashed + + \Version 03/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +MH3_FORCE_INLINE static uint64_t _MurmurHash3Read64(const uint64_t *pData, int32_t iData) +{ +#if EA_SYSTEM_LITTLE_ENDIAN + return(pData[iData]); +#else + /* read data little endian; we do this because we want to optimize + for running on x64 hardware */ + const uint8_t *_pData = (const uint8_t *)(pData+iData); + uint64_t uData; + uData = ((uint64_t)_pData[0]); + uData |= ((uint64_t)_pData[1]) << 8; + uData |= ((uint64_t)_pData[2]) << 16; + uData |= ((uint64_t)_pData[3]) << 24; + uData |= ((uint64_t)_pData[4]) << 32; + uData |= ((uint64_t)_pData[5]) << 40; + uData |= ((uint64_t)_pData[6]) << 48; + uData |= ((uint64_t)_pData[7]) << 56; + return(uData); +#endif +} + +/*F********************************************************************************/ +/*! + \Function _MurmurHash3Transform + + \Description + Add 16 byte block of state data to the MurmurHash3 context (hash the data) + + \Input *pContext - hash context + + \Version 03/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _MurmurHash3Transform(MurmurHash3T *pContext) +{ + uint64_t k1 = _MurmurHash3Read64((uint64_t *)pContext->aData, 0); + uint64_t k2 = _MurmurHash3Read64((uint64_t *)pContext->aData, 1); + uint64_t h1 = pContext->aState[0]; + uint64_t h2 = pContext->aState[1]; + const uint64_t c1 = MURMURHASH3_C1; + const uint64_t c2 = MURMURHASH3_C2; + + k1 *= c1; k1 = _MurmurHash3Rotl64(k1,31); k1 *= c2; h1 ^= k1; + + h1 = _MurmurHash3Rotl64(h1,27); h1 += h2; h1 = h1*5+0x52dce729; + + k2 *= c2; k2 = _MurmurHash3Rotl64(k2,33); k2 *= c1; h2 ^= k2; + + h2 = _MurmurHash3Rotl64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5; + + pContext->aState[0] = h1; + pContext->aState[1] = h2; +} + +/*F********************************************************************************/ +/*! + \Function _MurmurHash3Finalize + + \Description + Hash remaining data, output hash result + + \Input *pOutput - [out] buffer to store result hash + \Input iOutLen - length of output + \Input *pInput - remaining input data to hash + \Input iInpLen - total size of input data (not remaining) + \Input h1 - 1st 64bit word of 128bit state + \Input h2 - 2nd 64bit word of 128bit state + + \Version 03/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static void _MurmurHash3Finalize(uint8_t *pOutput, int32_t iOutLen, const uint8_t *pInput, int32_t iInpLen, uint64_t h1, uint64_t h2) +{ + const uint64_t c1 = MURMURHASH3_C1; + const uint64_t c2 = MURMURHASH3_C2; + uint64_t k1 = 0; + uint64_t k2 = 0; + + // hash remaining unaligned data + switch(iInpLen&15) + { + case 15: k2 ^= ((uint64_t)pInput[14]) << 48; + case 14: k2 ^= ((uint64_t)pInput[13]) << 40; + case 13: k2 ^= ((uint64_t)pInput[12]) << 32; + case 12: k2 ^= ((uint64_t)pInput[11]) << 24; + case 11: k2 ^= ((uint64_t)pInput[10]) << 16; + case 10: k2 ^= ((uint64_t)pInput[ 9]) << 8; + case 9: k2 ^= ((uint64_t)pInput[ 8]) << 0; + k2 *= c2; k2 = _MurmurHash3Rotl64(k2,33); k2 *= c1; h2 ^= k2; + + case 8: k1 ^= ((uint64_t)pInput[ 7]) << 56; + case 7: k1 ^= ((uint64_t)pInput[ 6]) << 48; + case 6: k1 ^= ((uint64_t)pInput[ 5]) << 40; + case 5: k1 ^= ((uint64_t)pInput[ 4]) << 32; + case 4: k1 ^= ((uint64_t)pInput[ 3]) << 24; + case 3: k1 ^= ((uint64_t)pInput[ 2]) << 16; + case 2: k1 ^= ((uint64_t)pInput[ 1]) << 8; + case 1: k1 ^= ((uint64_t)pInput[ 0]) << 0; + k1 *= c1; k1 = _MurmurHash3Rotl64(k1,31); k1 *= c2; h1 ^= k1; + }; + + // finalization + h1 ^= iInpLen; + h2 ^= iInpLen; + + h1 += h2; + h2 += h1; + + h1 = _MurmurHash3Fmix(h1); + h2 = _MurmurHash3Fmix(h2); + + h1 += h2; + h2 += h1; + + // write hash to output + switch(iOutLen) + { + case 16: pOutput[15] = (uint8_t)(h2 >> 56); + case 15: pOutput[14] = (uint8_t)(h2 >> 48); + case 14: pOutput[13] = (uint8_t)(h2 >> 40); + case 13: pOutput[12] = (uint8_t)(h2 >> 32); + case 12: pOutput[11] = (uint8_t)(h2 >> 24); + case 11: pOutput[10] = (uint8_t)(h2 >> 16); + case 10: pOutput[9] = (uint8_t)(h2 >> 8); + case 9: pOutput[8] = (uint8_t)(h2); + case 8: pOutput[7] = (uint8_t)(h2 >> 56); + case 7: pOutput[6] = (uint8_t)(h1 >> 48); + case 6: pOutput[5] = (uint8_t)(h1 >> 40); + case 5: pOutput[4] = (uint8_t)(h1 >> 32); + case 4: pOutput[3] = (uint8_t)(h1 >> 24); + case 3: pOutput[2] = (uint8_t)(h1 >> 16); + case 2: pOutput[1] = (uint8_t)(h1 >> 8); + case 1: pOutput[0] = (uint8_t)(h1); + }; +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function MurmurHash3Init + + \Description + Init the MurmurHash3 context + + \Input *pContext - hash context + + \Version 03/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void MurmurHash3Init(MurmurHash3T *pContext) +{ + ds_memclr(pContext, sizeof(*pContext)); + // borrowed init vector from MD5 + pContext->aState[0] = 0x67452301efcdab89; + pContext->aState[1] = 0x98badcfe10325476; +} + +/*F********************************************************************************/ +/*! + \Function MurmurHash3Init2 + + \Description + Init the MurmurHash3 context, with hash size + + \Input *pContext - hash context + \Input iHashSize - hash size (may be 32 or 128) + + \Version 03/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void MurmurHash3Init2(MurmurHash3T *pContext, int32_t iHashSize) +{ + MurmurHash3Init(pContext); +} + +/*F********************************************************************************/ +/*! + \Function MurmurHash3Update + + \Description + Add data to the MurmurHash3 context (hash the data) + + \Input *pContext - hash context + \Input *pBuffer - input data to hash + \Input iLength - length of data to hash + + \Version 03/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void MurmurHash3Update(MurmurHash3T *pContext, const void *pBuffer, int32_t iLength) +{ + const uint8_t *pInput = (const uint8_t *)pBuffer; + const uint64_t c1 = MURMURHASH3_C1; + const uint64_t c2 = MURMURHASH3_C2; + int32_t iCount, iAdd; + uint64_t h1, h2, k1, k2; + + // get index into block buffer + iCount = pContext->iCount&15; + pContext->iCount += iLength; + + // see if we need to append to existing data + if (iCount > 0) + { + // figure out number to fill block + iAdd = 16-iCount; + + // if less than a full block + if (iLength < iAdd) + { + ds_memcpy(pContext->aData+iCount, pInput, iLength); + return; + } + + // finish off the block and transform + ds_memcpy(pContext->aData+iCount, pInput, iAdd); + pInput += iAdd; + iLength -= iAdd; + _MurmurHash3Transform(pContext); + } + + // get state & 64-bit data pointer + h1 = pContext->aState[0]; + h2 = pContext->aState[1]; + + // hash 128 bit blocks of data; inline for speed + for ( ; iLength >= 16; iLength -= 16, pInput += 16) + { + k1 = _MurmurHash3Read64((const uint64_t *)pInput, 0); + k2 = _MurmurHash3Read64((const uint64_t *)pInput, 1); + + k1 *= c1; k1 = _MurmurHash3Rotl64(k1,31); k1 *= c2; h1 ^= k1; + + h1 = _MurmurHash3Rotl64(h1,27); h1 += h2; h1 = h1*5+0x52dce729; + + k2 *= c2; k2 = _MurmurHash3Rotl64(k2,33); k2 *= c1; h2 ^= k2; + + h2 = _MurmurHash3Rotl64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5; + } + + // update state + pContext->aState[0] = h1; + pContext->aState[1] = h2; + + // copy partial block data, if any, into state + if (iLength > 0) + { + ds_memcpy_s(pContext->aData, sizeof(pContext->aData), pInput, iLength); + } +} + +/*F********************************************************************************/ +/*! + \Function MurmurHash3Final + + \Description + Convert MurmurHash3 state into final output form + + \Input *pContext - hash context + \Input *pBuffer - [out] buffer to hold output + \Input iLength - length of output buffer + + \Version 03/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void MurmurHash3Final(MurmurHash3T *pContext, void *pBuffer, int32_t iLength) +{ + _MurmurHash3Finalize((uint8_t *)pBuffer, iLength, pContext->aData, pContext->iCount, pContext->aState[0], pContext->aState[1]); +} + +/*F********************************************************************************/ +/*! + \Function MurmurHash3 + + \Description + Generate 128-bit MurmurHash3 of specified input data + + \Input *_pOutput - [out] buffer to hold output + \Input iOutLen - length of output buffer + \Input *_pInput - input data to be hashed + \Input iInpLen - length of input + \Input *pKey - key to init hash + \Input iKeyLen - length of key (must be 16) + + \Todo + Support key lengths other than 16 + + \Version 03/07/2014 (jbrookes) +*/ +/********************************************************************************F*/ +void MurmurHash3(void *_pOutput, int32_t iOutLen, const void *_pInput, int32_t iInpLen, const void *pKey, int32_t iKeyLen) +{ + const uint8_t *pInput = (const uint8_t *)_pInput; + uint8_t *pOutput = (uint8_t *)_pOutput; + int32_t iBlock, iNumBlocks = iInpLen/16; + const uint64_t c1 = MURMURHASH3_C1; + const uint64_t c2 = MURMURHASH3_C2; + uint64_t h1, h2, k1, k2; + const uint64_t *pInput64; + + // get state (seed) + h1 = _MurmurHash3Read64((const uint64_t *)pKey, 0); + h2 = _MurmurHash3Read64((const uint64_t *)pKey, 1); + + // get 64-bit pointer to input + pInput64 = (const uint64_t *)pInput; + + // hash the 128 bit blocks of data + for (iBlock = 0; iBlock < iNumBlocks; iBlock += 1) + { + k1 = _MurmurHash3Read64(pInput64, iBlock*2+0); + k2 = _MurmurHash3Read64(pInput64, iBlock*2+1); + + k1 *= c1; k1 = _MurmurHash3Rotl64(k1,31); k1 *= c2; h1 ^= k1; + + h1 = _MurmurHash3Rotl64(h1,27); h1 += h2; h1 = h1*5+0x52dce729; + + k2 *= c2; k2 = _MurmurHash3Rotl64(k2,33); k2 *= c1; h2 ^= k2; + + h2 = _MurmurHash3Rotl64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5; + } + + // hash remaining data and output hash + _MurmurHash3Finalize(pOutput, iOutLen, pInput + iNumBlocks*16, iInpLen, h1, h2); +} + + + + + + + diff --git a/src/thirdparty/dirtysdk/source/util/protobufcommon.c b/src/thirdparty/dirtysdk/source/util/protobufcommon.c new file mode 100644 index 00000000..b586c406 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/protobufcommon.c @@ -0,0 +1,93 @@ +/*H*************************************************************************************/ +/*! + \File protobufcommon.h + + \Description + Shared definitions and functionality for the protobuf encoder / decoder + + \Copyright + Copyright (c) Electronic Arts 2017. ALL RIGHTS RESERVED. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************H*/ + +/*** Include Files *********************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/util/protobufcommon.h" + +/*** Varibles **************************************************************************/ + +//! strings representation for debugging +static const char *_ProtobufCommon_aTypes[] = +{ + "PROTOBUF_TYPE_VARINT", + "PROTOBUF_TYPE_64BIT", + "PROTOBUF_TYPE_LENGTH_DELIMITED", + "PROTOBUF_TYPE_START_GROUP", + "PROTOBUF_TYPE_END_GROUP", + "PROTOBUF_TYPE_32BIT" +}; + +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function ProtobufCommonReadSize + + \Description + Reads the encoded size of the message from the buffer + + \Input *pBuffer - buffer we are using to read + \Input iBufLen - size of the buffer + \Input *pResult - [out] size of the message + + \Output + const uint8_t * - location past the encoded size or NULL if iBufLen is less than + 4 bytes or the size of the message + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufCommonReadSize(const uint8_t *pBuffer, int32_t iBufLen, int32_t *pResult) +{ + *pResult = 0; + + /* if the full size of the buffer is less than the size + we cannot read the message size */ + if (iBufLen < 4) + { + return(pBuffer); + } + iBufLen -= 4; + + *pResult |= (*pBuffer++ << 24); + *pResult |= (*pBuffer++ << 16); + *pResult |= (*pBuffer++ << 8); + *pResult |= (*pBuffer++ << 0); + + // make sure that the size of the message isn't larger than the buffer + return((*pResult <= iBufLen) ? pBuffer : NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufCommonGetType + + \Description + Converts the type to its string representation + + \Input eType - type we are converting + + \Output + const char * - string representation or empty string if no mapping found + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const char *ProtobufCommonGetType(ProtobufTypeE eType) +{ + return((eType >= PROTOBUF_TYPE_VARINT) && (eType <= PROTOBUF_TYPE_32BIT) ? _ProtobufCommon_aTypes[eType] : ""); +} + diff --git a/src/thirdparty/dirtysdk/source/util/protobufread.c b/src/thirdparty/dirtysdk/source/util/protobufread.c new file mode 100644 index 00000000..22e07111 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/protobufread.c @@ -0,0 +1,927 @@ +/*H*************************************************************************************/ +/*! + \File protobufread.c + + \Description + Implementation of decoder for the Google Protobuf wire format + See: https://developers.google.com/protocol-buffers/docs/encoding + + This only supports protobuf version 3, if any lesser versions are used + the result is undefined. + + \Copyright + Copyright (c) Electronic Arts 2017-2018. ALL RIGHTS RESERVED. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************H*/ + +/*** Includes **************************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/util/protobufcommon.h" +#include "DirtySDK/util/protobufread.h" + +/*** Macros ****************************************************************************/ + +// helper to read the header information and return the location of the first element +#define PROTOBUF_ReadPackedRepeated(pState, pCurrent, pRepeated) ((ProtobufReadMessage((pState), (pCurrent), (pRepeated)) != NULL) ? (pRepeated)->pBuffer : NULL) + +/*** Private Functions *****************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function _ProtobufReadVarint + + \Description + Reads varint data from the buffer + + \Input *pBuffer - buffer we are reading from + \Input iBufLen - length of the buffer + \Input *pValue - [out] data pulled from the buffer + + \Output + int32_t - amount of bytes read from the buffer + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +static int32_t _ProtobufReadVarint(const uint8_t *pBuffer, int32_t iBufLen, uint64_t *pValue) +{ + int32_t iRead = 0; + uint8_t uMSB = 0, uByte; + + // initialize the value to zero + if (pValue != NULL) + { + *pValue = 0; + } + + // sanity check + if ((pBuffer == NULL) || (iBufLen <= 0)) + { + return(0); + } + + // read until you decode the full integer or reach the end of the buffer + do + { + uByte = pBuffer[iRead++]; + if (pValue != NULL) + { + *pValue += (uByte & 0x7f) * ((uint64_t)1 << uMSB); + } + uMSB += 7; + } while (((uByte & 0x80) == 0x80) && (iRead < iBufLen)); + + return(iRead); +} + +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadInit + + \Description + Initializes the reading structure based on buffer + + \Input *pState - state we are initializing + \Input *pBuffer - buffer we are using to read + \Input iBufLen - size of the buffer + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +void ProtobufReadInit(ProtobufReadT *pState, const uint8_t *pBuffer, int32_t iBufLen) +{ + ds_memclr(pState, sizeof(*pState)); + pState->pBuffer = pBuffer; + pState->pBufEnd = pBuffer+iBufLen; +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadFind + + \Description + Finds a field in the buffer + + \Input *pState - reader state + \Input uField - field we are looking for + + \Output + const uint8_t * - pointer to the location of the field or NULL if not found + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadFind(const ProtobufReadT *pState, uint32_t uField) +{ + const uint8_t *pBuffer = pState->pBuffer; + + for (;;) + { + uint32_t uField2; + uint8_t uType; + + // read the tag information, if we didn't read anything then we are done + if ((pBuffer = ProtobufReadHeader(pState, pBuffer, &uField2, &uType)) == NULL) + { + break; + } + + // check to see if it is the field you are looking for + if (uField == uField2) + { + return(pBuffer); + } + // try to read past + else if ((pBuffer = ProtobufReadSkip(pState, pBuffer, uType)) == NULL) + { + break; + } + } + return(NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadFind2 + + \Description + Finds a field in the buffer allowing to continue from a point + + \Input *pState - reader state + \Input uField - field we are looking for + \Input *pCurrent - where to continue from or NULL if to start from the beginning + + \Output + const uint8_t * - pointer to the location of the field or NULL if not found / end + of buffer + + \Notes + This function is useful when reading repeated length delimited types (not packed). + Since the reading happens procedurally, we need to continue finding repeated elements + past the previous one that was read. + + For this reason, when passing data into pCurrent make sure this is past the element + that you have read otherwise parsing will fail. + + \code{.c} + char strText[256]; + const uint8_t *pCurrent = NULL; + + while ((pCurrent = ProtobufReadString(&Read, ProtobufReadFind2(&Read, 1, pCurrent), strText, sizeof(strText))) != NULL) + { + ... + } + \endcode + + \Version 12/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadFind2(const ProtobufReadT *pState, uint32_t uField, const uint8_t *pCurrent) +{ + // assign the buffer to the beginning if we are not continuing + if (pCurrent == NULL) + { + pCurrent = pState->pBuffer; + } + + for (;;) + { + uint32_t uField2; + uint8_t uType; + + // read the tag information, if we didn't read anything then we are done + if ((pCurrent = ProtobufReadHeader(pState, pCurrent, &uField2, &uType)) == NULL) + { + break; + } + + // check to see if it is the field you are looking for + if (uField == uField2) + { + return(pCurrent); + } + + // try read past + if ((pCurrent = ProtobufReadSkip(pState, pCurrent, uType)) == NULL) + { + break; + } + } + return(NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadHeader + + \Description + Reads the tag header information from the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pField - [out] field identifier + \Input *pType - [out] type identifier (equates to ProtobufTypeE) + + \Output + const uint8_t * - pointer to the location past the tag header or NULL if the end + + \Notes + This function is for more advanced use where speed in critical to prevent + scanning the entire buffer when using ProtobufReadFind. When in doubt do + not use this and ProtobufReadSkip directly. + + \code{.c} + uint32_t uField; + uint8_t uType; + const uint8_t *pCurrent = Read.pBuffer; + + while ((pCurrent = ProtobufReadHeader(&Read, pCurrent, &uField, &uType)) != NULL) + { + // check for correct field / type combo you expect, for instance in a simple case + // note: normally you would check all your known type/field combos + if ((uType == PROTOBUF_TYPE_VARINT) && (uField == 1)) + { + uint64_t uResult; + pCurrent = ProtobufReadVarint2(&Read, pCurrent, &uResult); + } + // otherwise skip + else + { + pCurrent = ProtobufReadSkip(&Read, pCurrent, uType); + } + } + \endcode + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadHeader(const ProtobufReadT *pState, const uint8_t *pCurrent, uint32_t *pField, uint8_t *pType) +{ + int32_t iRead; + uint64_t uTag; + + // read the tag information, if we didn't read anything then we have reached the end + if ((iRead = _ProtobufReadVarint(pCurrent, (int32_t)(pState->pBufEnd-pCurrent), &uTag)) <= 0) + { + return(NULL); + } + pCurrent += iRead; + + // parse the tag information and return current location + *pField = (uint32_t)(uTag >> 3); + *pType = uTag & 7; + return(pCurrent); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadSkip + + \Description + Read over an element in the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input uType - type identifier (equates to ProtobufTypeE) + + \Output + const uint8_t * - location past the element or NULL if unhandled type / end of buffer + + \Notes + See notes in ProtobufReadHeader + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadSkip(const ProtobufReadT *pState, const uint8_t *pCurrent, uint8_t uType) +{ + switch (uType) + { + case PROTOBUF_TYPE_VARINT: + { + pCurrent += _ProtobufReadVarint(pCurrent, (int32_t)(pState->pBufEnd-pCurrent), NULL); + break; + } + + case PROTOBUF_TYPE_64BIT: + { + pCurrent += DS_MIN((uint32_t)(pState->pBufEnd-pCurrent), 8); + break; + } + + case PROTOBUF_TYPE_LENGTH_DELIMITED: + { + uint64_t uValue; + pCurrent += _ProtobufReadVarint(pCurrent, (int32_t)(pState->pBufEnd-pCurrent), &uValue); + pCurrent += DS_MIN((uint32_t)(pState->pBufEnd-pCurrent), uValue); + break; + } + + case PROTOBUF_TYPE_32BIT: + { + pCurrent += DS_MIN((uint32_t)(pState->pBufEnd-pCurrent), 4); + break; + } + + case PROTOBUF_TYPE_START_GROUP: + case PROTOBUF_TYPE_END_GROUP: + default: + // not sure how to read these so return an error so we can stop trying to read + NetPrintf(("protobufreader: found '%s' type that we cannot handle\n", ProtobufCommonGetType(uType))); + return(NULL); + } + return(pCurrent); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadVarint + + \Description + Reads a varint from the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + + \Output + uint64_t - value read from the buffer, zero if pCurrent is NULL + + \Notes + This function is different than many of the other read functions where the output + is a pointer output parameter, due to the nature of the varint we read it out in + an uint64_t. Since this will cause some annoyances in using it for most cases we + opted to use a special function in this case. This version of the function will + not work when reading repeated fields so please use ProtobufReadVarint2 in that + case. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +uint64_t ProtobufReadVarint(const ProtobufReadT *pState, const uint8_t *pCurrent) +{ + uint64_t uResult; + _ProtobufReadVarint(pCurrent, (int32_t)(pState->pBufEnd-pCurrent), &uResult); + return(uResult); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadVarint2 + + \Description + Reads a varint from the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pResult - [out] result of reading + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + The result of this function is only important when reading repeated fields where + we want to stop reading when there are no more elements in the field to read. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadVarint2(const ProtobufReadT *pState, const uint8_t *pCurrent, uint64_t *pResult) +{ + int32_t iRead = _ProtobufReadVarint(pCurrent, (int32_t)(pState->pBufEnd-pCurrent), pResult); + return((iRead > 0) ? (pCurrent+iRead) : NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadRepeatedVarint + + \Description + Reads a repeated varint from the buffer into an array + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pResult - [out] output array + \Input iCount - number of elements that can be written + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + If you need to figure out the amount of elements for the repeated field, you + can call the ProtobufReadNumRepeatedElements function. Otherwise, you should + pass in an array large enough to hold the elements you expect. + + \Version 01/02/2018 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadRepeatedVarint(const ProtobufReadT *pState, const uint8_t *pCurrent, uint64_t *pResult, int32_t iCount) +{ + ProtobufReadT Field; + int32_t iIndex = 0; + ds_memclr(pResult, sizeof(*pResult) * iCount); + + // read the header for the packed repeated field + if ((pCurrent = PROTOBUF_ReadPackedRepeated(pState, pCurrent, &Field)) == NULL) + { + return(pCurrent); + } + // write as many elements as possible from the buffer + while ((pCurrent = ProtobufReadVarint2(&Field, pCurrent, (iIndex < iCount) ? &pResult[iIndex++] : NULL)) != NULL) + ; + return((Field.pBufEnd < pState->pBufEnd) ? (Field.pBufEnd+1) : NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadSint32 + + \Description + Reads a sint32 from the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pResult - [out] result of reading + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + The result of this function is only important when reading repeated fields where + we want to stop reading when there are no more elements in the field to read. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadSint32(const ProtobufReadT *pState, const uint8_t *pCurrent, int32_t *pResult) +{ + uint64_t uResult; + pCurrent = ProtobufReadVarint2(pState, pCurrent, &uResult); + *pResult = (int32_t)(((uResult >> 1) ^ -(int32_t)(uResult & 1))); + return(pCurrent); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadRepeatedSint32 + + \Description + Reads a repeated varint sint32 from the buffer into an array + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pResult - [out] output array + \Input iCount - number of elements that can be written + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + If you need to figure out the amount of elements for the repeated field, you + can call the ProtobufReadNumRepeatedElements function. Otherwise, you should + pass in an array large enough to hold the elements you expect. + + \Version 01/02/2018 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadRepeatedSint32(const ProtobufReadT *pState, const uint8_t *pCurrent, int32_t *pResult, int32_t iCount) +{ + ProtobufReadT Field; + int32_t iIndex = 0; + ds_memclr(pResult, sizeof(*pResult) * iCount); + + // read the header for the packed repeated field + if ((pCurrent = PROTOBUF_ReadPackedRepeated(pState, pCurrent, &Field)) == NULL) + { + return(pCurrent); + } + // write as many elements as possible from the buffer + while ((pCurrent = ProtobufReadSint32(&Field, pCurrent, (iIndex < iCount) ? &pResult[iIndex++] : NULL)) != NULL) + ; + return((Field.pBufEnd < pState->pBufEnd) ? (Field.pBufEnd+1) : NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadSint64 + + \Description + Reads a sint64 from the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pResult - [out] result of reading + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + The result of this function is only important when reading repeated fields where + we want to stop reading when there are no more elements in the field to read. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadSint64(const ProtobufReadT *pState, const uint8_t *pCurrent, int64_t *pResult) +{ + uint64_t uResult; + pCurrent = ProtobufReadVarint2(pState, pCurrent, &uResult); + *pResult = ((uResult >> 1) ^ -(int64_t)(uResult & 1)); + return(pCurrent); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadRepeatedSint64 + + \Description + Reads a repeated varint sint64 from the buffer into an array + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pResult - [out] output array + \Input iCount - number of elements that can be written + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + If you need to figure out the amount of elements for the repeated field, you + can call the ProtobufReadNumRepeatedElements function. Otherwise, you should + pass in an array large enough to hold the elements you expect. + + \Version 01/02/2018 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadRepeatedSint64(const ProtobufReadT *pState, const uint8_t *pCurrent, int64_t *pResult, int32_t iCount) +{ + ProtobufReadT Field; + int32_t iIndex = 0; + ds_memclr(pResult, sizeof(*pResult) * iCount); + + // read the header for the packed repeated field + if ((pCurrent = PROTOBUF_ReadPackedRepeated(pState, pCurrent, &Field)) == NULL) + { + return(pCurrent); + } + // write as many elements as possible from the buffer + while ((pCurrent = ProtobufReadSint64(&Field, pCurrent, (iIndex < iCount) ? &pResult[iIndex++] : NULL)) != NULL) + ; + return((Field.pBufEnd < pState->pBufEnd) ? (Field.pBufEnd+1) : NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadFixed32 + + \Description + Reads a fixed32, float or sfixed32 from the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pOutput - [out] output of reading + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + The result of this function is only important when reading repeated fields where + we want to stop reading when there are no more elements in the field to read. + We use void * for reading to allow you to pass in different 32 bit types, if the + wrong type is used a max of 4 bytes is always read. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadFixed32(const ProtobufReadT *pState, const uint8_t *pCurrent, void *pOutput) +{ + uint32_t uLength = (pCurrent != NULL) ? DS_MIN((uint32_t)(pState->pBufEnd-pCurrent), 4) : 0; + if (pOutput != NULL) + { + ds_memcpy_s(pOutput, 4, pCurrent, uLength); + } + return((uLength > 0) ? (pCurrent+uLength) : NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadRepeatedFixed32 + + \Description + Reads a repeated fixed32, float or sfixed32 from the buffer into an array + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pOutput - [out] output array + \Input iOutLen - size of the output + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + Since fixed types can be read as a block, we can memcpy the entire repeated field + straight into the array. To achieve this you need to make sure to pass in the size + in bytes of the array and not the number of elements. + + If you need to figure out the amount of elements for the repeated field, you + can call the ProtobufReadNumRepeatedElements function. Otherwise, you should + pass in an array large enough to hold the elements you expect. + + \Version 01/02/2018 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadRepeatedFixed32(const ProtobufReadT *pState, const uint8_t *pCurrent, void *pOutput, int32_t iOutLen) +{ + ProtobufReadT Field; + + // sanity check to try to weed out incorrect usage + if ((iOutLen % 4) != 0) + { + NetPrintf(("protobufwrite: [%p] fixed64 repeated output length is not a multiple of 8, please check to make sure you are sending size instead of count\n", pState)); + return(NULL); + } + ds_memclr(pOutput, iOutLen); + + // read the header for the packed repeated field + if ((pCurrent = PROTOBUF_ReadPackedRepeated(pState, pCurrent, &Field)) == NULL) + { + return(pCurrent); + } + + // write as many elements as possible from the buffer + ds_memcpy_s(pOutput, iOutLen, pCurrent, (uint32_t)(Field.pBufEnd-pCurrent)); + return((Field.pBufEnd < pState->pBufEnd) ? (Field.pBufEnd+1) : NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadFixed64 + + \Description + Reads a fixed64, double or sfixed64 from the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pOutput - [out] output of reading + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + The result of this function is only important when reading repeated fields where + we want to stop reading when there are no more elements in the field to read. + We use void * for reading to allow you to pass in different 64 bit types, if the + wrong type is used a max of 8 bytes is always read. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadFixed64(const ProtobufReadT *pState, const uint8_t *pCurrent, void *pOutput) +{ + uint32_t uLength = (pCurrent != NULL) ? DS_MIN((uint32_t)(pState->pBufEnd-pCurrent), 8) : 0; + if (pOutput != NULL) + { + ds_memcpy_s(pOutput, 8, pCurrent, uLength); + } + return((uLength > 0) ? (pCurrent+uLength) : NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadRepeatedFixed64 + + \Description + Reads a repeated fixed64, double or sfixed64 from the buffer into an array + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pOutput - [out] output array + \Input iOutLen - size of the output + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + Since fixed types can be read as a block, we can memcpy the entire repeated field + straight into the array. To achieve this you need to make sure to pass in the size + in bytes of the array and not the number of elements. + + If you need to figure out the amount of elements for the repeated field, you + can call the ProtobufReadNumRepeatedElements function. Otherwise, you should + pass in an array large enough to hold the elements you expect. + + \Version 01/02/2018 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadRepeatedFixed64(const ProtobufReadT *pState, const uint8_t *pCurrent, void *pOutput, int32_t iOutLen) +{ + ProtobufReadT Field; + + // sanity check to try to weed out incorrect usage + if ((iOutLen % 8) != 0) + { + NetPrintf(("protobufwrite: [%p] fixed64 repeated output length is not a multiple of 8, please check to make sure you are sending size instead of count\n", pState)); + return(NULL); + } + ds_memclr(pOutput, iOutLen); + + // read the header for the packed repeated field + if ((pCurrent = PROTOBUF_ReadPackedRepeated(pState, pCurrent, &Field)) == NULL) + { + return(pCurrent); + } + + // write as many elements as possible from the buffer + ds_memcpy_s(pOutput, iOutLen, pCurrent, (uint32_t)(Field.pBufEnd-pCurrent)); + return((Field.pBufEnd < pState->pBufEnd) ? (Field.pBufEnd+1) : NULL); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadBytes + + \Description + Reads the bytes type from the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pOutput - [out] output of reading + \Input iOutLen - size of the output + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + The result of this function is only important when reading repeated fields where + we want to stop reading when there are no more elements in the field to read. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadBytes(const ProtobufReadT *pState, const uint8_t *pCurrent, uint8_t *pOutput, int32_t iOutLen) +{ + uint64_t uLength; + int32_t iRead; + + // parse the length and update to not pass the end + if ((iRead = _ProtobufReadVarint(pCurrent, (int32_t)(pState->pBufEnd-pCurrent), &uLength)) <= 0) + { + if (pOutput != NULL) + { + ds_memclr(pOutput, iOutLen); + } + return(NULL); + } + pCurrent += iRead; + uLength = DS_MIN((uint32_t)(pState->pBufEnd-pCurrent), uLength); + + ds_memcpy_s(pOutput, iOutLen, pCurrent, (int32_t)(uLength)); + return(pCurrent+uLength); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadString + + \Description + Reads a string from the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pOutput - [out] output of reading + \Input iOutLen - size of the output + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + The result of this function is only important when reading repeated fields where + we want to stop reading when there are no more elements in the field to read. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadString(const ProtobufReadT *pState, const uint8_t *pCurrent, char *pOutput, int32_t iOutLen) +{ + uint64_t uLength; + int32_t iRead; + + // parse the length and update to not pass the end + if ((iRead = _ProtobufReadVarint(pCurrent, (int32_t)(pState->pBufEnd-pCurrent), &uLength)) <= 0) + { + if (pOutput != NULL) + { + *pOutput = '\0'; + } + return(NULL); + } + pCurrent += iRead; + uLength = DS_MIN((uint32_t)(pState->pBufEnd-pCurrent), uLength); + + ds_strsubzcpy(pOutput, iOutLen, (const char *)pCurrent, (int32_t)(uLength)); + return(pCurrent+uLength); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadMessage + + \Description + Reads an embedded message from the buffer + + \Input *pState - reader state + \Input *pCurrent - location in the buffer or NULL + \Input *pMsg - [out] new reader state used for reading embedded message + + \Output + const uint8_t * - location past the element in the buffer or NULL if end is reached + + \Notes + The result of this function is only important when reading repeated fields where + we want to stop reading when there are no more elements in the field to read. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +const uint8_t *ProtobufReadMessage(const ProtobufReadT *pState, const uint8_t *pCurrent, ProtobufReadT *pMsg) +{ + uint64_t uLength; + int32_t iRead; + + if ((iRead = _ProtobufReadVarint(pCurrent, (int32_t)(pState->pBufEnd-pCurrent), &uLength)) <= 0) + { + ProtobufReadInit(pMsg, NULL, 0); + return(NULL); + } + pCurrent += iRead; + + // update length to not read past the end + uLength = DS_MIN((uint32_t)(pState->pBufEnd-pCurrent), uLength); + + ProtobufReadInit(pMsg, pCurrent, (int32_t)(uLength)); + return(pCurrent+uLength); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufReadNumRepeatedElements + + \Description + Gets the number of elements in a repeated field + + \Input *pState - reader state + \Input uField - field identifier of the repeated field + \Input uType - types encoded in the repeated field (they are not sent over the wire) + + \Output + int32_t - number of elements in the repeated field + + \Notes + This function is available as an utility. If you don't need to get the number + of elements don't call it as it saves from relooping over the repeated field. + + \Version 08/14/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufReadNumRepeatedElements(const ProtobufReadT *pState, uint32_t uField, uint8_t uType) +{ + int32_t iNumElements = 0; + + if ((uType == PROTOBUF_TYPE_VARINT) || (uType == PROTOBUF_TYPE_64BIT) || (uType == PROTOBUF_TYPE_32BIT)) + { + ProtobufReadT Repeated; + const uint8_t *pCurrent = PROTOBUF_ReadPackedRepeated(pState, ProtobufReadFind(pState, uField), &Repeated); + + if (uType == PROTOBUF_TYPE_VARINT) + { + while ((pCurrent = ProtobufReadVarint2(&Repeated, pCurrent, NULL)) != NULL) + { + iNumElements += 1; + } + } + else if (uType == PROTOBUF_TYPE_64BIT) + { + iNumElements = (int32_t)(Repeated.pBufEnd-Repeated.pBuffer) / 8; + } + else if (uType == PROTOBUF_TYPE_32BIT) + { + iNumElements = (int32_t)(Repeated.pBufEnd-Repeated.pBuffer) / 4; + } + } + else if (uType == PROTOBUF_TYPE_LENGTH_DELIMITED) + { + const uint8_t *pCurrent = NULL; + while ((pCurrent = ProtobufReadFind2(pState, uField, pCurrent)) != NULL) + { + pCurrent = ProtobufReadSkip(pState, pCurrent, uType); + iNumElements += 1; + } + } + + return(iNumElements); +} diff --git a/src/thirdparty/dirtysdk/source/util/protobufwrite.c b/src/thirdparty/dirtysdk/source/util/protobufwrite.c new file mode 100644 index 00000000..be4d4835 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/protobufwrite.c @@ -0,0 +1,684 @@ +/*H*************************************************************************************/ +/*! + \File protobufwrite.c + + \Description + Implementation of encoder for the Google Protobuf wire format + See: https://developers.google.com/protocol-buffers/docs/encoding + + \Copyright + Copyright (c) Electronic Arts 2017-2018. ALL RIGHTS RESERVED. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************H*/ + +/*** Includes **************************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/util/protobufcommon.h" +#include "DirtySDK/util/protobufwrite.h" + +/*** Defines ***************************************************************************/ + +//! maximum octets used to encode a varint +#define PROTOBUF_VARINT_MAX (10) + +//! max number of levels that we can embed messages +#define PROTOBUF_MAX_EMBEDDED_MSG (100) + +/*** Macros ****************************************************************************/ + +//! encodes the field number and type into a tag +#define PROTOBUF_MakeTag(uField, uType) (((uField) << 3) | (uType)) + +/*** Type Definitions ******************************************************************/ + +//! module state +struct ProtobufWriteRefT +{ + uint8_t *pBuffer; //!< pointer to the start of the buffer + int32_t iBufLen; //!< length of the buffer + int32_t iBufOff; //!< offset into the buffer + + int32_t iMemGroup; //!< memory group identifier + void *pMemGroupUserdata; //!< memory group data + + uint8_t *aMessagesBegin[PROTOBUF_MAX_EMBEDDED_MSG]; //!< stack of pointers to where each encoded messages begin + int32_t iNumMessages; //!< current number of messages we are embedding + + uint8_t bEncodeSize; //!< should we encode the size of the message at the beginning of the buffer? (needed for grpc) + uint8_t _pad[3]; +}; + +/*** Variables *************************************************************************/ + +// variable to test if something is set +static const uint8_t _aEmptyBuffer[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +/*** Private Functions *****************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function _ProtobufWriteVarint + + \Description + Write a varint type to the buffer + + \Input uValue - varint value to write + \Input *pBuffer - [out] destination we are writing to + \Input iBufLen - size of the output destination + \Input *pError - [out] where we write the result of the operation + + \Output + int32_t - amount of bytes written to the buffer + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +static int32_t _ProtobufWriteVarint(uint64_t uValue, uint8_t *pBuffer, int32_t iBufLen, int32_t *pError) +{ + int32_t iWrite; + + /* make we can at least write 1 byte for the cases + where we can fit it in one octet */ + if (iBufLen <= 0) + { + *pError = PROTOBUFWRITE_ERROR_FULL; + return(0); + } + + for (iWrite = 0; uValue >= 0x80; iWrite += 1) + { + // make sure we have space left + if (iWrite >= (iBufLen - 1)) + { + *pError = PROTOBUFWRITE_ERROR_FULL; + return(0); + } + + // set the lsb of the value and set the msb as a continuation flag + pBuffer[iWrite] = (uValue % 0x80) + 0x80; + uValue /= 0x80; + } + pBuffer[iWrite++] = (uint8_t)uValue; + + return(iWrite); +} + +/*F*************************************************************************************/ +/*! + \Function _ProtobufMemcpy + + \Description + Copies data to the buffer + + \Input *pDst - [out] pointer to output + \Input iDstLen - size of output + \Input *pSrc - pointer to input + \Input iSrcLen - size of input + \Input *pError - [out] where we write the result of the operation + + \Output + int32_t - amount of bytes written to the buffer + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +static int32_t _ProtobufMemcpy(void *pDst, int32_t iDstLen, const void *pSrc, int32_t iSrcLen, int32_t *pError) +{ + if ((iDstLen = DS_MIN(iDstLen, iSrcLen)) > 0) + { + ds_memcpy(pDst, pSrc, iDstLen); + } + else + { + *pError = PROTOBUFWRITE_ERROR_FULL; + } + return(iDstLen); +} + +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteCreate + + \Description + Create and initialize the writer module ref + + \Input *pBuffer - where we are writing to + \Input iBufLen - the size of the buffer we are writing to + \Input bEncodeSize - encode the size of the message at the beginning of the buffer? + + \Output + ProtobufWriteRefT * - pointer to module ref or NULL if failure + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +ProtobufWriteRefT *ProtobufWriteCreate(uint8_t *pBuffer, int32_t iBufLen, uint8_t bEncodeSize) +{ + ProtobufWriteRefT *pState; + int32_t iMemGroup; + void *pMemGroupUserdata; + + // query memory group information + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserdata); + + // allocate module state + if ((pState = (ProtobufWriteRefT *)DirtyMemAlloc(sizeof(*pState), PROTOBUF_MEMID, iMemGroup, pMemGroupUserdata)) == NULL) + { + NetPrintf(("protobufwriter: cannot allocate writer module state\n")); + return(NULL); + } + ds_memclr(pState, sizeof(*pState)); + pState->pBuffer = pBuffer; + pState->iBufLen = iBufLen; + pState->iMemGroup = iMemGroup; + pState->pMemGroupUserdata = pMemGroupUserdata; + + // assign the encode size setting, if true save space at the beginning for encoding size + if ((pState->bEncodeSize = bEncodeSize) == TRUE) + { + pState->iBufOff += 4; + } + + return(pState); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteVarint + + \Description + Write the non-zigzag encoded (sint32, sint64) versions of the varint types + + \Input *pState - module state + \Input uValue - value we are writing + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Notes + This function supports the following types: int32, uint32, uint64, bool and + enum. + + If you pass in 0 for uField, nothing will be written to the buffer for the tag + which you only want to do when writing packed repeated fields. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteVarint(ProtobufWriteRefT *pState, uint64_t uValue, uint32_t uField) +{ + int32_t iError = PROTOBUFWRITE_ERROR_OK; + + // don't write the defaults to save space when not in a repeated field + if ((uValue != 0) || (uField == 0)) + { + if (uField > 0) + { + pState->iBufOff += _ProtobufWriteVarint(PROTOBUF_MakeTag(uField, PROTOBUF_TYPE_VARINT), pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + } + pState->iBufOff += _ProtobufWriteVarint(uValue, pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + } + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteRepeatedVarint + + \Description + Write the non-zigzag encoded (sint32, sint64) versions of the varint types + repeated + + \Input *pState - module state + \Input *pValues - array of values we are writing + \Input iCount - number of entries in the array + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Notes + This function supports the following types: int32, uint32, uint64, bool and + enum. + + \Version 12/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteRepeatedVarint(ProtobufWriteRefT *pState, uint64_t *pValues, int32_t iCount, uint32_t uField) +{ + int32_t iIndex, iError; + + ProtobufWriteMessageBegin(pState, uField); + for (iIndex = 0, iError = PROTOBUFWRITE_ERROR_OK; (iIndex < iCount) && (iError == PROTOBUFWRITE_ERROR_OK); iIndex += 1) + { + iError = ProtobufWriteVarint(pState, pValues[iIndex], 0); + } + ProtobufWriteMessageEnd(pState); + + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteSint32 + + \Description + Write the zigzag encoded sint32 type + + \Input *pState - module state + \Input iValue - value we are writing + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Notes + If you pass in 0 for uField, nothing will be written for the tag + which you only want to do when writing packed repeated fields. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteSint32(ProtobufWriteRefT *pState, int32_t iValue, uint32_t uField) +{ + int32_t iError = PROTOBUFWRITE_ERROR_OK; + // zigzag encode the value + const uint64_t uValue = (iValue << 1) ^ (iValue >> 31); + + // don't write the defaults to save space when not in a repeated field + if ((uValue != 0) || (uField == 0)) + { + if (uField > 0) + { + pState->iBufOff += _ProtobufWriteVarint(PROTOBUF_MakeTag(uField, PROTOBUF_TYPE_VARINT), pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + } + pState->iBufOff += _ProtobufWriteVarint(uValue, pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + } + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteRepeatedSint32 + + \Description + Write the zigzag encoded sint32 type repeated + + \Input *pState - module state + \Input *pValues - array of values we are writing + \Input iCount - number of entries in the array + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Version 12/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteRepeatedSint32(ProtobufWriteRefT *pState, int32_t *pValues, int32_t iCount, uint32_t uField) +{ + int32_t iIndex, iError; + + ProtobufWriteMessageBegin(pState, uField); + for (iIndex = 0, iError = PROTOBUFWRITE_ERROR_OK; (iIndex < iCount) && (iError == PROTOBUFWRITE_ERROR_OK); iIndex += 1) + { + iError = ProtobufWriteSint32(pState, pValues[iIndex], 0); + } + ProtobufWriteMessageEnd(pState); + + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteSint64 + + \Description + Write the zigzag encoded sint64 type + + \Input *pState - module state + \Input iValue - value we are writing + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Notes + If you pass in 0 for uField, nothing will be written for the tag + which you only want to do when writing packed repeated fields. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteSint64(ProtobufWriteRefT *pState, int64_t iValue, uint32_t uField) +{ + int32_t iError = PROTOBUFWRITE_ERROR_OK; + // zigzag encode the value + const uint64_t uValue = (iValue << 1) ^ (iValue >> 63); + + // don't write the defaults to save space when not in a repeated field + if ((uValue != 0) || (uField == 0)) + { + if (uField > 0) + { + pState->iBufOff += _ProtobufWriteVarint(PROTOBUF_MakeTag(uField, PROTOBUF_TYPE_VARINT), pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + } + pState->iBufOff += _ProtobufWriteVarint(uValue, pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + } + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteRepeatedSint64 + + \Description + Write the zigzag encoded sint64 type repeated + + \Input *pState - module state + \Input *pValues - array of values we are writing + \Input iCount - number of entries in the array + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Version 12/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteRepeatedSint64(ProtobufWriteRefT *pState, int64_t *pValues, int32_t iCount, uint32_t uField) +{ + int32_t iIndex, iError; + + ProtobufWriteMessageBegin(pState, uField); + for (iIndex = 0, iError = PROTOBUFWRITE_ERROR_OK; (iIndex < iCount) && (iError == PROTOBUFWRITE_ERROR_OK); iIndex += 1) + { + iError = ProtobufWriteSint64(pState, pValues[iIndex], 0); + } + ProtobufWriteMessageEnd(pState); + + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteFixed32 + + \Description + Write the fixed32, float or sfixed32 type to the buffer + + \Input *pState - module state + \Input *pValue - value we are writing + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Notes + The fixed types are written in little endian so we use memcpy. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteFixed32(ProtobufWriteRefT *pState, const void *pValue, uint32_t uField) +{ + int32_t iError = PROTOBUFWRITE_ERROR_OK; + + // don't write the defaults to save space + if (memcmp(pValue, _aEmptyBuffer, 4) != 0) + { + pState->iBufOff += _ProtobufWriteVarint(PROTOBUF_MakeTag(uField, PROTOBUF_TYPE_32BIT), pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + pState->iBufOff += _ProtobufMemcpy(pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, pValue, 4, &iError); + } + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteRepeatedFixed32 + + \Description + Write the repeated fixed32, float or sfixed32 type to the buffer + + \Input *pState - module state + \Input *pInput - array of values we are writing + \Input iInpLen - size (in bytes) of the array + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Version 12/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteRepeatedFixed32(ProtobufWriteRefT *pState, const void *pInput, int32_t iInpLen, uint32_t uField) +{ + int32_t iError = PROTOBUFWRITE_ERROR_OK; + + ProtobufWriteMessageBegin(pState, uField); + pState->iBufOff += _ProtobufMemcpy(pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, pInput, iInpLen, &iError); + ProtobufWriteMessageEnd(pState); + + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteFixed64 + + \Description + Write the fixed64, double or sfixed64 type to the buffer + + \Input *pState - module state + \Input *pValue - value we are writing + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Notes + The fixed types are written in little endian so we use memcpy. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteFixed64(ProtobufWriteRefT *pState, const void *pValue, uint32_t uField) +{ + int32_t iError = PROTOBUFWRITE_ERROR_OK; + + // don't write the defaults to save space + if (memcmp(pValue, _aEmptyBuffer, 8) != 0) + { + pState->iBufOff += _ProtobufWriteVarint(PROTOBUF_MakeTag(uField, PROTOBUF_TYPE_64BIT), pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + pState->iBufOff += _ProtobufMemcpy(pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, pValue, 8, &iError); + } + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteRepeatedFixed64 + + \Description + Write the repeated fixed64, double or sfixed64 type to the buffer + + \Input *pState - module state + \Input *pInput - array of values we are writing + \Input iInpLen - size (in bytes) of the array + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Version 12/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteRepeatedFixed64(ProtobufWriteRefT *pState, const void *pInput, int32_t iInpLen, uint32_t uField) +{ + int32_t iError = PROTOBUFWRITE_ERROR_OK; + + ProtobufWriteMessageBegin(pState, uField); + pState->iBufOff += _ProtobufMemcpy(pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, pInput, iInpLen, &iError); + ProtobufWriteMessageEnd(pState); + + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteLengthDelimited + + \Description + Write the length delimited types + + \Input *pState - module state + \Input *pValue - value we are writing + \Input iLength - size of the value + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteLengthDelimited(ProtobufWriteRefT *pState, const void *pValue, int32_t iLength, uint32_t uField) +{ + int32_t iError = PROTOBUFWRITE_ERROR_OK; + + // don't write empty data to save space when not in a repeated field + if ((pValue != NULL) && (iLength > 0)) + { + pState->iBufOff += _ProtobufWriteVarint(PROTOBUF_MakeTag(uField, PROTOBUF_TYPE_LENGTH_DELIMITED), pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + pState->iBufOff += _ProtobufWriteVarint(iLength, pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + pState->iBufOff += _ProtobufMemcpy(pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, pValue, iLength, &iError); + } + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteMessageBegin + + \Description + Begin encoding an embedded message in place + + \Input *pState - module state + \Input uField - field identifier + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Notes + Embedded messages work like a stack (FILO) when nesting more messages. + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteMessageBegin(ProtobufWriteRefT *pState, uint32_t uField) +{ + int32_t iError = PROTOBUFWRITE_ERROR_OK; + + // make sure that we have not reached our maximum + if (pState->iNumMessages == PROTOBUF_MAX_EMBEDDED_MSG) + { + return(PROTOBUFWRITE_ERROR_EMBED); + } + + pState->iBufOff += _ProtobufWriteVarint(PROTOBUF_MakeTag(uField, PROTOBUF_TYPE_LENGTH_DELIMITED), pState->pBuffer+pState->iBufOff, pState->iBufLen-pState->iBufOff, &iError); + pState->aMessagesBegin[pState->iNumMessages++] = pState->pBuffer+pState->iBufOff; + + if ((pState->iBufLen-pState->iBufOff) > PROTOBUF_VARINT_MAX) + { + pState->iBufOff += PROTOBUF_VARINT_MAX; + } + else + { + iError = PROTOBUFWRITE_ERROR_FULL; + } + + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteMessageEnd + + \Description + Finish encoding an embedded message in place + + \Input *pState - module state + + \Output + int32_t - 0 (PROTOBUFWRITE_ERROR_OK) on success, >0 on error + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteMessageEnd(ProtobufWriteRefT *pState) +{ + int32_t iError = PROTOBUFWRITE_ERROR_OK, iInpLen, iBufOff; + uint8_t *pMsgBegin; + + // make sure that we still have messages in the stack + if (pState->iNumMessages > 0) + { + pMsgBegin = pState->aMessagesBegin[--pState->iNumMessages]; + } + else + { + return(PROTOBUFWRITE_ERROR_EMBED); + } + + // calculate the size of the message and encode it to the buffer + iInpLen = (int32_t)((pState->pBuffer+pState->iBufOff)-pMsgBegin-PROTOBUF_VARINT_MAX); + iBufOff = _ProtobufWriteVarint(iInpLen, pMsgBegin, PROTOBUF_VARINT_MAX, &iError); + + // fill the gap (if any) + memmove(pMsgBegin+iBufOff, pMsgBegin+PROTOBUF_VARINT_MAX, iInpLen); + pState->iBufOff -= (PROTOBUF_VARINT_MAX-iBufOff); + + return(iError); +} + +/*F*************************************************************************************/ +/*! + \Function ProtobufWriteDestroy + + \Description + Finish writing to the buffer and return the amount of data written + + \Input *pState - module state + + \Output + int32_t - amount of bytes written to the buffer + + \Version 07/05/2017 (eesponda) +*/ +/*************************************************************************************F*/ +int32_t ProtobufWriteDestroy(ProtobufWriteRefT *pState) +{ + const int32_t iResult = pState->iBufOff; + + // encode the size if needed + if (pState->bEncodeSize == TRUE) + { + // calculate the size of the message without accounting for encoded size + const int32_t iMessageSize = SocketNtohl(iResult-4); + ds_memcpy(pState->pBuffer, (const void *)&iMessageSize, sizeof(iMessageSize)); + } + + DirtyMemFree(pState, PROTOBUF_MEMID, pState->iMemGroup, pState->pMemGroupUserdata); + return(iResult); +} diff --git a/src/thirdparty/dirtysdk/source/util/utf8.c b/src/thirdparty/dirtysdk/source/util/utf8.c new file mode 100644 index 00000000..f753da42 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/util/utf8.c @@ -0,0 +1,527 @@ +/*H*************************************************************************************************/ +/*! + + \File utf8.c + + \Description + This module implements routines for converting to and from UTF-8. + + \Notes + This code only decodes the first three octets of UTF-8, thus it only handles UCS-2 codes, + not UCS-4 codes. It also does not handle UTF-16 (and surrogate pairs), and is therefore + limited to encoding to/decoding from the basic reference plane. + + Helpful references: + + http://www.utf-8.com/ - links + http://www.cis.ohio-state.edu/cgi-bin/rfc/rfc2279.html - RFC 2279 + http://www.unicode.org/charts/ - UNICODE character charts + http://www-106.ibm.com/developerworks/library/utfencodingforms/ - UNICODE primer + http://www.columbia.edu/kermit/utf8.html - UTF-8 samples + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2003. ALL RIGHTS RESERVED. + + \Version 1.0 03/25/03 (JLB) First version. + +*/ +/*************************************************************************************************H*/ + + +/*** Include files *********************************************************************/ + +#include "DirtySDK/util/utf8.h" + +/*** Defines ***************************************************************************/ + +/*** Macros ****************************************************************************/ + +/*** Type Definitions ******************************************************************/ + +/*** Function Prototypes ***************************************************************/ + +/*** Variables *************************************************************************/ + +// Private variables + +// Public variables + + +/*** Private Functions *****************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function _Utf8GetNumBytes + + \Description + Decode the number of bytes in a UTF-8 encoded sequence. + + \Input cLead - lead character of UTF-8 sequence + + \Output + int32_t - number of bytes in the sequence + + \Version 03/25/03 (JLB) +*/ +/*************************************************************************************************F*/ +static int32_t _Utf8GetNumBytes(unsigned char cLead) +{ + int32_t iCodeSize; + + if ((cLead & 0x80) == 0x00) + { + iCodeSize = 1; + } + else if ((cLead & 0xE0) == 0xC0) + { + iCodeSize = 2; + } + else if ((cLead & 0xF0) == 0xE0) + { + iCodeSize = 3; + } + else + { + iCodeSize = 4; + } + + return(iCodeSize); +} + +/*F*************************************************************************************************/ +/*! + \Function _Utf8DecodeToUCS2 + + \Description + Decode a UTF-8 sequence into a UCS-2 code point. + + \Input *pOutPtr - pointer to output for decoded UCS-2 value + \Input *pStr - pointer to input UTF-8 string + + \Output + int32_t - number of input 8bit characters consumed + + \Notes + UCS-2 range (hex) UTF-8 octet sequence (binary) + 007F 0xxxxxxx + 07FF 110xxxxx 10xxxxxx + FFFF 1110xxxx 10xxxxxx 10xxxxxx + + \Version 03/26/03 (JLB) +*/ +/*************************************************************************************************F*/ +static int32_t _Utf8DecodeToUCS2(uint16_t *pOutPtr, const unsigned char *pStr) +{ + int32_t iCodeSize; + + if ((*pStr & 0x80) == 0x00) + { + pOutPtr[0] = (uint16_t)pStr[0]; + iCodeSize = 1; + } + else if ((*pStr & 0xE0) == 0xC0) + { + pOutPtr[0] = ((pStr[0] & ~0xE0) << 6) | (pStr[1] & ~0xC0); + iCodeSize = 2; + } + else if ((*pStr & 0xF0) == 0xE0) + { + pOutPtr[0] = ((pStr[0] & ~0xF0) << 12) | ((pStr[1] & ~0xC0) << 6) | (pStr[2] & ~0xC0); + iCodeSize = 3; + } + else + { + iCodeSize = 4; + } + + return(iCodeSize); +} + +/*F*************************************************************************************************/ +/*! + \Function Utf8EncodeFromUCS2CodePt + + \Description + Encode a single UCS-2 code point ("char") into a UTF-8 sequence. + + \Input uCodePt - input UCS-2 code point + \Input *pOutPtr - pointer to output for encoded UTF-8 sequence. + + \Output + int32_t - number of 8bit characters output + + \Notes + See notes for _Utf8DecodeToUCS2() + + \Version 03/26/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t Utf8EncodeFromUCS2CodePt(char *pOutPtr, uint16_t uCodePt) +{ + int32_t iCodeSize; + + if (uCodePt < 0x0080) + { + pOutPtr[0] = (char)uCodePt; + iCodeSize = 1; + } + else if (uCodePt < 0x800) + { + pOutPtr[0] = 0xC0 | (uCodePt >> 6); + pOutPtr[1] = 0x80 | (uCodePt & 0x3F); + iCodeSize = 2; + } + else + { + pOutPtr[0] = 0xE0 | (uCodePt >> 12); + pOutPtr[1] = 0x80 | ((uCodePt >> 6) & 0x3F); + pOutPtr[2] = 0x80 | (uCodePt & 0x3F); + iCodeSize = 3; + } + + return(iCodeSize); +} + +/*F*************************************************************************************************/ +/*! + \Function _Utf8Translate + + \Description + Look through translation subtables and translate uCodePt + + \Input *pOutBuf - output buffer to store 8bit translated output + \Input *pTransTbl - translation table to translate with + \Input uCodePt - UCS-2 code point to translate + \Input cReplace - character to replace untranslatable characters with (or null-termination char to strip) + + \Output + int32_t - number of ascii characters output + + \Version 03/26/03 (JLB) +*/ +/*************************************************************************************************F*/ +static int32_t _Utf8Translate(char *pOutBuf, const Utf8TransTblT *pTransTbl, uint16_t uCodePt, char cReplace) +{ + unsigned char cCode; + char *pOldBuf = pOutBuf; + int32_t bFound; + + // look through subtables + for (bFound = FALSE; pTransTbl->uRangeEnd != 0; pTransTbl++) + { + // are we in range? + if ((uCodePt >= pTransTbl->uRangeBegin) && (uCodePt <= (pTransTbl->uRangeEnd))) + { + // dereference table + uCodePt -= pTransTbl->uRangeBegin; + cCode = (unsigned char)pTransTbl->pCodeTbl[uCodePt]; + + if ((cCode == 0xFF) && (cReplace != '\0')) + { + // untranslatable - replace + *(pOutBuf++) = cReplace; + } + else + { + // translate + *(pOutBuf++) = cCode; + } + + bFound = TRUE; + break; + } + } + + // not found and replacing? + if ((bFound == FALSE) && (cReplace != '\0')) + { + // replace + *(pOutBuf++) = cReplace; + } + + // return number of characters output + return((int32_t)(pOutBuf-pOldBuf)); +} + + +/*** Public Functions ******************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function Utf8Strip + + \Description + Strip non-ASCII UTF-8 encoded data. + + \Input *pOutStr - pointer to output buffer (may be same as input buffer) + \Input iBufSize - size of output buffer in ASCII units (char) + \Input *pInStr - pointer to source string + + \Output + int32_t - number of characters in new string, or zero if no utf8 data was stripped + + \Version 03/25/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t Utf8Strip(char *pOutStr, int32_t iBufSize, const char *pInStr) +{ + return(Utf8Replace(pOutStr, iBufSize, pInStr, '\0')); +} + +/*F*************************************************************************************************/ +/*! + \Function Utf8Replace + + \Description + Replace non-ASCII UTF-8 encoded data with the given character. + + \Input *pOutStr - pointer to output buffer (may be same as input buffer) + \Input iBufSize - size of output buffer in ASCII units (char) + \Input *pInStr - pointer to source string + \Input cReplace - character to replace non-ASCII UTF-8 characters with + + \Output + int32_t - number of characters in new string, or zero if no UTF-8 data was replaced + + \Version 03/25/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t Utf8Replace(char *pOutStr, int32_t iBufSize, const char *pInStr, char cReplace) +{ + int32_t iSrcIdx, iDstIdx = 0; + + // fast scan to find any utf8 encoded data + for (iSrcIdx = 0; ((pInStr[iSrcIdx] & 0x80) == 0) && (pInStr[iSrcIdx] != '\0'); iSrcIdx++) + { + } + + // did we find any? + if (pInStr[iSrcIdx] != '\0') + { + // yes, so replace/strip it + for (iDstIdx = iSrcIdx; pInStr[iSrcIdx] != '\0' && iDstIdx < iBufSize; ) + { + // do we have utf8 data? + if (pInStr[iSrcIdx] & 0x80) + { + // figure out how many bytes of utf8 data we have + int32_t iNumBytes = _Utf8GetNumBytes(pInStr[iSrcIdx]); + + // skip them + iSrcIdx += iNumBytes; + + // replace with cReplace + if (cReplace != '\0') + { + pOutStr[iDstIdx++] = cReplace; + } + } + else + { + // normal string data - copy it + pOutStr[iDstIdx++] = pInStr[iSrcIdx++]; + } + } + + // terminate + pOutStr[iDstIdx++] = '\0'; + } + + return(iDstIdx); +} + +/*F*************************************************************************************************/ +/*! + \Function Utf8StrLen + + \Description + Returns the number of code points in a UTF-8 encoded string. + + \Input *pStr - pointer to string to get string length of + + \Output + int32_t - number of code points in string + + \Version 03/26/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t Utf8StrLen(const char *pStr) +{ + int32_t iSrcIdx, iStrLen; + + for (iSrcIdx = iStrLen = 0; pStr[iSrcIdx] != '\0'; iStrLen++) + { + if (pStr[iSrcIdx] & 0x80) + { + // figure out how many bytes of utf8 data we have + int32_t iNumBytes = _Utf8GetNumBytes(pStr[iSrcIdx]); + + // skip them + iSrcIdx += iNumBytes; + } + else + { + iSrcIdx++; + } + } + + return(iStrLen); +} + +/*F*************************************************************************************************/ +/*! + \Function Utf8EncodeFromUCS2 + + \Description + Convert a UCS-2 code point sequence into a UTF-8 code point sequence. + + \Input *pOutStr - pointer to buffer to encode string to + \Input iBufLen - length of output buffer, in char units + \Input *pInStr - pointer to string to encode + + \Output + int32_t - output string length, in char units + + \Version 03/27/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t Utf8EncodeFromUCS2(char *pOutStr, int32_t iBufLen, const uint16_t *pInStr) +{ + int32_t iStrLen; + + // a UCS-2 encoded string can generate up to three chars. + iBufLen -= 2; + + // encode + for (iStrLen = 0; (*pInStr != 0x0000) && (iStrLen < iBufLen); pInStr++) + { + iStrLen += Utf8EncodeFromUCS2CodePt(&pOutStr[iStrLen], *pInStr); + } + + // NULL terminate & return length to caller + pOutStr[iStrLen++] = '\0'; + return(iStrLen); +} + +/*F*************************************************************************************************/ +/*! + \Function Utf8DecodeToUCS2 + + \Description + Convert a UTF-8 code point sequence into a UCS-2 code point sequence. + + \Input *pOutStr - pointer to buffer to decode string to + \Input iBufLen - length of output buffer, in UCS-2 units (int16_t) + \Input *pInStr - pointer to string to decode + + \Output + int32_t - output string length, in code points + + \Version 03/26/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t Utf8DecodeToUCS2(uint16_t *pOutStr, int32_t iBufLen, const char *pInStr) +{ + int32_t iSrcIdx, iStrLen, iCodeSize; + + // ensure room for NULL terminator + iBufLen--; + + // decode string + for (iSrcIdx = iStrLen = 0; (pInStr[iSrcIdx] != '\0') && (iStrLen < iBufLen); ) + { + if ((iCodeSize = _Utf8DecodeToUCS2(&pOutStr[iStrLen], (const unsigned char *)&pInStr[iSrcIdx])) <= 3) + { + iStrLen++; + } + + iSrcIdx += iCodeSize; + } + + // NULL terminate & return length to caller + pOutStr[iStrLen++] = 0x0000; + return(iStrLen); +} + +/*F*************************************************************************************************/ +/*! + \Function Utf8EncodeFrom8Bit + + \Description + Encode the given 8bit input string to UTF-8, based on the input translation table + + \Input *pOutStr - pointer to output UTF-8 string buffer + \Input iBufLen - length of buffer + \Input *pInStr - pointer to input 8bit string + \Input *pEncodeTbl - pointer to translation table to map 8bit string to UCS-2 + + \Output + int32_t - length of output UCS-8 string + + \Version 03/28/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t Utf8EncodeFrom8Bit(char *pOutStr, int32_t iBufLen, const char *pInStr, const Utf8EncodeTblT *pEncodeTbl) +{ + int32_t iStrLen; + uint16_t uCodePt; + + // a UCS-2 encoded value can generate up to three chars. + iBufLen -= 2; + + // encode + for (iStrLen = 0; (*pInStr != 0x0000) && (iStrLen < iBufLen); pInStr++) + { + uCodePt = pEncodeTbl->uCodeTbl[*(const unsigned char *)pInStr]; + iStrLen += Utf8EncodeFromUCS2CodePt(&pOutStr[iStrLen], uCodePt); + } + + // NULL terminate & return length to caller + pOutStr[iStrLen++] = '\0'; + return(iStrLen); +} + +/*F*************************************************************************************************/ +/*! + \Function Utf8TranslateTo8Bit + + \Description + Translates the given UTF-8 sequence based on the input translation table. + + \Input *pOutStr - pointer to buffer to decode string to + \Input iBufLen - length of output buffer, in ASCII units (char) + \Input *pInStr - pointer to string to decode + \Input cReplace - \verbatim character to replace code point with if untranslateable ('\0' to strip) \endverbatim + \Input *pTransTbl - pointer to NULL-terminated translation table array + + \Output + int32_t - length of output string in ASCII characters (8bit) + + \Notes + 'pTransTbl' is expected to be a NULL-terminated array of Utf8TransTblT structures that + represent a sparse translation table from 16bit UCS-2 space to 8bit game font space. + + \Version 03/26/03 (JLB) +*/ +/*************************************************************************************************F*/ +int32_t Utf8TranslateTo8Bit(char *pOutStr, int32_t iBufLen, const char *pInStr, char cReplace, const Utf8TransTblT *pTransTbl) +{ + int32_t iSrcIdx, iDstIdx; + uint16_t uCodePt = 0; + + // ensure room for NULL terminator + iBufLen--; + + // translate string + for (iSrcIdx = iDstIdx = 0; (pInStr[iSrcIdx] != '\0') && (iDstIdx < iBufLen); ) + { + iSrcIdx += _Utf8DecodeToUCS2(&uCodePt, (const unsigned char *)&pInStr[iSrcIdx]); + iDstIdx += _Utf8Translate(&pOutStr[iDstIdx], pTransTbl, uCodePt, cReplace); + } + + // NULL terminate & return length to caller + pOutStr[iDstIdx++] = '\0'; + return(iDstIdx); +} diff --git a/src/thirdparty/dirtysdk/source/voip/pc/voipheadsetpc.c b/src/thirdparty/dirtysdk/source/voip/pc/voipheadsetpc.c new file mode 100644 index 00000000..4835b069 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/pc/voipheadsetpc.c @@ -0,0 +1,2306 @@ +/*H********************************************************************************/ +/*! + \File voipheadsetpc.c + + \Description + VoIP headset manager. + + \Copyright + Copyright Electronic Arts 2004-2011. + + \Notes + LPCM is supported only in loopback mode. + + \Version 1.0 03/30/2004 (jbrookes) First Version + \Version 2.0 10/20/2011 (jbrookes) Major rewrite for cleanup and to fix I/O bugs + \Version 3.0 07/09/2012 (akirchner) Added functionality to play buffer +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#pragma warning(push,0) +#include +#pragma warning(pop) + +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/voip/voipdef.h" +#include "DirtySDK/voip/voiptranscribe.h" +#include "DirtySDK/voip/voipnarrate.h" + +#include "voippriv.h" +#include "voipcommon.h" +#include "voipconnection.h" +#include "voippacket.h" +#include "voipmixer.h" +#include "voipconduit.h" +#include "DirtySDK/voip/voipcodec.h" +#include "voipheadset.h" + +#include "voipdvi.h" +#include "voippcm.h" + +/*** Defines **********************************************************************/ + +// Defines taken from mmddk.h - not including mmddk directly as that would +// add a dependancy on the Windows DDK +#define DRVM_MAPPER (0x2000) +#define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER + 23) +#define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER + 21) +#define DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY (0x00000001) + +#define VOIP_HEADSET_SAMPLERATE (16000) //!< sample rate; 16khz audio +#define VOIP_HEADSET_SAMPLEWIDTH (2) //!< sample size; 16-bit samples +#define VOIP_HEADSET_FRAMEDURATION (20) //!< frame duration in milliseconds; 20ms +#define VOIP_HEADSET_FRAMESAMPLES ((VOIP_HEADSET_SAMPLERATE*VOIP_HEADSET_FRAMEDURATION)/1000) //!< samples per frame (20ms; 8khz=160, 11.025khz=220.5, 16khz=320) +#define VOIP_HEADSET_FRAMESIZE (VOIP_HEADSET_FRAMESAMPLES*VOIP_HEADSET_SAMPLEWIDTH) //!< frame size in bytes; 640 +#define VOIP_HEADSET_NUMWAVEBUFFERS (5) +#define VOIP_HEADSET_MAXDEVICES (16) +#define VOIP_HEADSET_WAVEFORMAT (WAVE_FORMAT_1M16) +#define VOIP_HEADSET_PREBUFFERBLOCKS (4) + +#if defined(_WIN64) + #define VOIP_HEADSET_WAVE_MAPPER ((uint64_t)-1) +#else + #define VOIP_HEADSET_WAVE_MAPPER (WAVE_MAPPER) +#endif + +//! transmission interval in milliseconds +#define VOIP_THREAD_SLEEP_DURATION (20) + +//! headset types (input, output) +typedef enum VoipHeadsetDeviceTypeE +{ + VOIP_HEADSET_INPDEVICE, + VOIP_HEADSET_OUTDEVICE, +} VoipHeadsetDeviceTypeE; + +/*** Macros ************************************************************************/ + +/*** Type Definitions **************************************************************/ +//! playback data +typedef struct VoipHeadsetWaveDataT +{ + WAVEHDR WaveHdr; + uint8_t FrameData[VOIP_HEADSET_FRAMESIZE]; +} VoipHeadsetWaveDataT; + +//! wave caps structure; this is a combination of the in and out caps, which are identical other than dwSupport (output only) +typedef struct VoipHeadsetWaveCapsT +{ + WORD wMid; //!< manufacturer ID + WORD wPid; //!< product ID + MMVERSION vDriverVersion; //!< version of the driver + CHAR szPname[MAXPNAMELEN]; //!< product name (NULL terminated string) + DWORD dwFormats; //!< formats supported + WORD wChannels; //!< number of sources supported + WORD wReserved1; //!< packing + DWORD dwSupport; //!< functionality supported by driver +} VoipHeadsetWaveCapsT; + +//! device info +typedef struct VoipHeadsetDeviceInfoT +{ + VoipHeadsetDeviceTypeE eDevType; //!< device type + + HANDLE hDevice; //!< handle to currently open device, or NULL if no device is open + int32_t iActiveDevice; //!< device index of active device + int32_t iDeviceToOpen; //!< device index of device we want to open, or -1 for any + + int32_t iCurVolume; //!< playback volume (output devices only) + int32_t iNewVolume; //!< new volume to set (if different from iCurVolume) + + VoipHeadsetWaveDataT WaveData[VOIP_HEADSET_NUMWAVEBUFFERS]; //!< audio input/output buffer + int32_t iCurWaveBuffer; //!< current input/output buffer + + DWORD dwCheckFlag[VOIP_HEADSET_NUMWAVEBUFFERS]; //!< flag to check for empty buffer (output only) + + VoipHeadsetWaveCapsT WaveDeviceCaps[VOIP_HEADSET_MAXDEVICES]; + int32_t iNumDevices; //!< number of enumerated devices + + uint8_t bActive; //!< device is active (recording/playing) + uint8_t bCloseDevice; //!< device close requested + uint8_t bChangeDevice; //!< device change requested + uint8_t _pad; +} VoipHeadsetDeviceInfoT; + +//!< local user data space +typedef struct PCLocalVoipUserT +{ + uint32_t uPlaybackFlags; //!< mute flag every remote user is 1 bit +} PCLocalVoipUserT; + +//! remote user data space +typedef struct PCRemoteVoipUserT +{ + VoipUserT User; +} PCRemoteVoipUserT; + +//! VOIP module state data +struct VoipHeadsetRefT +{ + int32_t iMemGroup; //!< mem group + void *pMemGroupUserData; //!< mem group user data + + VoipHeadsetDeviceInfoT MicrInfo; //!< input device info + VoipHeadsetDeviceInfoT SpkrInfo; //!< output device info + + // conduit info + int32_t iMaxConduits; + VoipConduitRefT *pConduitRef; + + // mixer state + VoipMixerRefT *pMixerRef; + + // accessibility support modules + VoipTranscribeRefT *pTranscribeRef; + VoipNarrateRefT *pNarrateRef; + + // codec frame size in bytes + int32_t iCmpFrameSize; + + // module debug level + int32_t iDebugLevel; + + // which user 0-3 "owns" voip, VOIP_INVALID_LOCAL_USER_INDEX if no one + int32_t iParticipatingUserIndex; + + // boolean control options + volatile uint8_t bMicOn; //!< TRUE if the mic is on, else FALSE + uint8_t bMuted; //!< TRUE if muted (e.g. push-to-talk), else FALSE + uint8_t bLoopback; //!< TRUE if loopback is enabled, else FALSE + uint8_t bTextChatAccessibility; //!< TRUE if text chat accessibility features are enabled + uint8_t bCrossplay; //!< TRUE if cross-play is enabled, else FALSE + + uint8_t uSendSeq; //!< send sequence count + uint8_t _pad[2]; + + // user callback data + VoipHeadsetMicDataCbT *pMicDataCb; + VoipHeadsetTextDataCbT *pTextDataCb; + VoipHeadsetStatusCbT *pStatusCb; + void *pCbUserData; + + // speaker callback data + VoipSpkrCallbackT *pSpkrDataCb; + void *pSpkrCbUserData; + + // critical section for guarding access to device close/change flags + NetCritT DevChangeCrit; + + // player + int32_t iPlayerActive; + int16_t *pPlayerBuffer; + uint32_t uPlayerBufferFrameCurrent; + uint32_t uPlayerBufferFrames; + uint32_t uPlayerFirstTime; + int32_t iPlayerFirstUse; + + // STT + uint8_t bVoiceTranscriptionEnabled; + + // TTS + uint8_t bNarrating; + int32_t iNarrateWritePos; + VoipNarrateGenderE eDefaultGender; + int8_t narrateBuffer[VOIP_HEADSET_FRAMESIZE]; + + PCLocalVoipUserT aLocalUsers[VOIP_MAXLOCALUSERS]; + + //! remote user list - must come last in ref as it is variable length + int32_t iMaxRemoteUsers; + PCRemoteVoipUserT aRemoteUsers[1]; +}; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Public Variables + +// Private Variables + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetWaveGetNumDevs + + \Description + Wraps wave(In|Out)GetNumDevs() + + \Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE + + \Output + UINT - wave(In|Out)GetNumDevs() result + + \Version 10/12/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static UINT _VoipHeadsetWaveGetNumDevs(VoipHeadsetDeviceTypeE eDevType) +{ + return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInGetNumDevs() : waveOutGetNumDevs()); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetWaveGetDevCaps + + \Description + Wraps wave(In|Out)GetDevCaps() + + \Input uDeviceID - device to get capabilities of + \Input *pWaveCaps - wave capabilities (note this structure does double-duty for input and output) + \Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE + + \Output + MMRESULT - wave(In|Out)GetNumDevs() result + + \Version 10/12/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static MMRESULT _VoipHeadsetWaveGetDevCaps(UINT_PTR uDeviceID, VoipHeadsetWaveCapsT *pWaveCaps, VoipHeadsetDeviceTypeE eDevType) +{ + return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInGetDevCaps(uDeviceID, (LPWAVEINCAPSA)pWaveCaps, sizeof(WAVEINCAPSA)) : waveOutGetDevCaps(uDeviceID, (LPWAVEOUTCAPSA)pWaveCaps, sizeof(WAVEOUTCAPSA))); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetWaveOpen + + \Description + Wraps wave(In|Out)Open() + + \Input *pHandle - [out] storage for handle to opened device + \Input uDeviceID - device to open + \Input *pWaveFormat - wave format we want + \Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE + + \Output + MMRESULT - wave(In|Out)Open() result + + \Version 10/12/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static MMRESULT _VoipHeadsetWaveOpen(HANDLE *pHandle, UINT uDeviceID, LPCWAVEFORMATEX pWaveFormat, VoipHeadsetDeviceTypeE eDevType) +{ + return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInOpen((LPHWAVEIN)pHandle, uDeviceID, pWaveFormat, 0, 0, 0) : waveOutOpen((LPHWAVEOUT)pHandle, uDeviceID, pWaveFormat, 0, 0, 0)); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetWaveClose + + \Description + Wraps wave(In|Out)Close() + + \Input hHandle - handle to device to close + \Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE + + \Output + MMRESULT - wave(In|Out)Close() result + + \Version 10/12/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static MMRESULT _VoipHeadsetWaveClose(HANDLE hHandle, VoipHeadsetDeviceTypeE eDevType) +{ + return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInClose((HWAVEIN)hHandle) : waveOutClose((HWAVEOUT)hHandle)); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetWavePrepareHeader + + \Description + Wraps wave(In|Out)PrepareHeader() + + \Input hHandle - handle to device to prepare wave header for + \Input pWaveHeader - wave header to prepare + \Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE + + \Output + MMRESULT - wave(In|Out)PrepareHeader() result + + \Version 10/12/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static MMRESULT _VoipHeadsetWavePrepareHeader(HANDLE hHandle, LPWAVEHDR pWaveHeader, VoipHeadsetDeviceTypeE eDevType) +{ + return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInPrepareHeader((HWAVEIN)hHandle, pWaveHeader, sizeof(WAVEHDR)) : waveOutPrepareHeader((HWAVEOUT)hHandle, pWaveHeader, sizeof(WAVEHDR))); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetWaveUnprepareHeader + + \Description + Wraps wave(In|Out)UnprepareHeader() + + \Input hHandle - handle to device to prepare wave header for + \Input pWaveHeader - wave header to unprepare + \Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE + + \Output + MMRESULT - wave(In|Out)UnprepareHeader() result + + \Version 10/12/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static MMRESULT _VoipHeadsetWaveUnprepareHeader(HANDLE hHandle, LPWAVEHDR pWaveHeader, VoipHeadsetDeviceTypeE eDevType) +{ + return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInUnprepareHeader((HWAVEIN)hHandle, pWaveHeader, sizeof(WAVEHDR)) : waveOutUnprepareHeader((HWAVEOUT)hHandle, pWaveHeader, sizeof(WAVEHDR))); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetWaveReset + + \Description + Wraps wave(In|Out)Reset() + + \Input hHandle - handle to device to prepare wave header for + \Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE + + \Output + MMRESULT - wave(In|Out)Reset() result + + \Version 10/12/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static MMRESULT _VoipHeadsetWaveReset(HANDLE hHandle, VoipHeadsetDeviceTypeE eDevType) +{ + return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInReset((HWAVEIN)hHandle) : waveOutReset((HWAVEOUT)hHandle)); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetPrepareWaveHeaders + + \Description + Prepare wave headers for use. + + \Input *pDeviceInfo - device info + \Input iNumBuffers - number of buffers in array + \Input hDevice - handle to wave device + + \Output + int32_t - negative=failure, zero=success + + \Version 07/28/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipHeadsetPrepareWaveHeaders(VoipHeadsetDeviceInfoT *pDeviceInfo, int32_t iNumBuffers, HANDLE hDevice) +{ + HRESULT hResult; + int32_t iPlayBuf; + + for (iPlayBuf = 0; iPlayBuf < iNumBuffers; iPlayBuf++) + { + // set up the wave header + pDeviceInfo->WaveData[iPlayBuf].WaveHdr.lpData = (LPSTR)pDeviceInfo->WaveData[iPlayBuf].FrameData; + pDeviceInfo->WaveData[iPlayBuf].WaveHdr.dwBufferLength = sizeof(pDeviceInfo->WaveData[iPlayBuf].FrameData); + pDeviceInfo->WaveData[iPlayBuf].WaveHdr.dwFlags = 0L; + pDeviceInfo->WaveData[iPlayBuf].WaveHdr.dwLoops = 0L; + + // prepare the header + if ((hResult = _VoipHeadsetWavePrepareHeader(hDevice, &pDeviceInfo->WaveData[iPlayBuf].WaveHdr, pDeviceInfo->eDevType)) != MMSYSERR_NOERROR) + { + NetPrintf(("voipheadsetpc: error %d preparing %s buffer %d\n", hResult, + pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", iPlayBuf)); + return(-1); + } + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetUnprepareWaveHeaders + + \Description + Unprepare wave headers, prior to releasing them. + + \Input *pDeviceInfo - device info + \Input iNumBuffers - number of buffers in array + \Input hDevice - handle to wave device + + \Version 07/28/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipHeadsetUnprepareWaveHeaders(VoipHeadsetDeviceInfoT *pDeviceInfo, int32_t iNumBuffers, HANDLE hDevice) +{ + HRESULT hResult; + int32_t iPlayBuf; + + for (iPlayBuf = 0; iPlayBuf < iNumBuffers; iPlayBuf++) + { + if ((hResult = _VoipHeadsetWaveUnprepareHeader(hDevice, &pDeviceInfo->WaveData[iPlayBuf].WaveHdr, pDeviceInfo->eDevType)) != MMSYSERR_NOERROR) + { + NetPrintf(("voipheadsetpc: error %d unpreparing %s buffer %d\n", hResult, + pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", iPlayBuf)); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetOpenDevice + + \Description + Open a wave device. + + \Input *pDeviceInfo - device state + \Input iDevice - handle to wave device (may be WAVE_MAPPER) + \Input iNumBuffers - number of input buffers + + \Output + int32_t - negative=error, zero=success + + \Version 07/28/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipHeadsetOpenDevice(VoipHeadsetDeviceInfoT *pDeviceInfo, int32_t iDevice, int32_t iNumBuffers) +{ + WAVEFORMATEX WaveFormatEx = { WAVE_FORMAT_PCM, 1, VOIP_HEADSET_SAMPLERATE, VOIP_HEADSET_SAMPLERATE * VOIP_HEADSET_SAMPLEWIDTH, 2, 16, 0 }; + HANDLE hDevice = NULL; + HRESULT hResult; + + // open device + if ((hResult = _VoipHeadsetWaveOpen(&hDevice, iDevice, &WaveFormatEx, pDeviceInfo->eDevType)) != S_OK) + { + NetPrintf(("voipheadsetpc: error %d opening input device\n", hResult)); + return(-1); + } + + // set up input wave data + if (_VoipHeadsetPrepareWaveHeaders(pDeviceInfo, iNumBuffers, hDevice) >= 0) + { + // set volume to -1 so it will be reset + pDeviceInfo->iCurVolume = -1; + + // set active device + pDeviceInfo->iActiveDevice = iDevice; + + if (pDeviceInfo->eDevType == VOIP_HEADSET_OUTDEVICE) + { + int32_t iBuffer; + + // mark as playing + pDeviceInfo->bActive = TRUE; + + // set up check flag (we have to check for WHDR_PREPARED until we've written into the buffer) + for (iBuffer = 0; iBuffer < VOIP_HEADSET_NUMWAVEBUFFERS; iBuffer += 1) + { + pDeviceInfo->dwCheckFlag[iBuffer] = WHDR_PREPARED; + } + } + + NetPrintf(("voipheadsetpc: opened %s device\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output")); + } + else + { + _VoipHeadsetWaveClose(hDevice, pDeviceInfo->eDevType); + hDevice = NULL; + } + + pDeviceInfo->hDevice = hDevice; + return((hDevice == NULL) ? -2 : 0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetCloseDevice + + \Description + Close an open device. + + \Input *pHeadset - module state + \Input *pDeviceInfo - device info + + \Version 07/28/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipHeadsetCloseDevice(VoipHeadsetRefT *pHeadset, VoipHeadsetDeviceInfoT *pDeviceInfo) +{ + MMRESULT hResult; + int32_t iDevice; + + // no open device, nothing to do + if (pDeviceInfo->hDevice == 0) + { + return; + } + + // reset the device + if ((hResult = _VoipHeadsetWaveReset(pDeviceInfo->hDevice, pDeviceInfo->eDevType)) != MMSYSERR_NOERROR) + { + NetPrintf(("voipheadsetpc: failed to reset %s device (err=%d)\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "waveIn" : "waveOut", hResult)); + } + + // unprepare the wave headers + _VoipHeadsetUnprepareWaveHeaders(pDeviceInfo, 2, pDeviceInfo->hDevice); + + // close the device + if ((hResult = _VoipHeadsetWaveClose(pDeviceInfo->hDevice, pDeviceInfo->eDevType)) != MMSYSERR_NOERROR) + { + NetPrintf(("voipheadsetpc: failed to close %s device (err=%d)\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "waveIn" : "waveOut", hResult)); + } + + // reset device state + NetPrintf(("voipheadsetpc: closed %s device\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output")); + + pDeviceInfo->hDevice = NULL; + pDeviceInfo->bActive = FALSE; + iDevice = pDeviceInfo->iActiveDevice; + pDeviceInfo->iActiveDevice = -1; + + // trigger device inactive callback + // user index is always 0 because PC does not support MLU + pHeadset->pStatusCb(0, FALSE, pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? VOIP_HEADSET_STATUS_INPUT : VOIP_HEADSET_STATUS_OUTPUT, pHeadset->pCbUserData); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetEnumerateDevices + + \Description + Enumerate devices attached to system, and add those that are compatible + to device list. + + \Input *pHeadset - headset module state + \Input *pDeviceInfo - device info + + \Version 07/28/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipHeadsetEnumerateDevices(VoipHeadsetRefT *pHeadset, VoipHeadsetDeviceInfoT *pDeviceInfo) +{ + int32_t iDevice, iNumDevices, iAddedDevices; + VoipHeadsetWaveCapsT *pDeviceCaps; + const char *pActiveDeviceName = NULL; + int32_t iActiveDevice = -1; + + // get total number of eDevType devices + iNumDevices = _VoipHeadsetWaveGetNumDevs(pDeviceInfo->eDevType); + + // get the active device name, if a device is active + if (pDeviceInfo->iActiveDevice != -1) + { + iActiveDevice = pDeviceInfo->iActiveDevice; + pActiveDeviceName = pDeviceInfo->WaveDeviceCaps[iActiveDevice].szPname; + } + + // walk device list + for (iDevice = 0, iAddedDevices = 0; (iDevice < iNumDevices) && (iAddedDevices < VOIP_HEADSET_MAXDEVICES); iDevice++) + { + // get device capabilities + pDeviceCaps = &pDeviceInfo->WaveDeviceCaps[iAddedDevices]; + _VoipHeadsetWaveGetDevCaps(iDevice, pDeviceCaps, pDeviceInfo->eDevType); + + // print device info + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: querying %s device %d\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", iDevice)); + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: wMid=%d\n", pDeviceCaps->wMid)); + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: wPid=%d\n", pDeviceCaps->wPid)); + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: vDriverVersion=%d.%d\n", (pDeviceCaps->vDriverVersion&0xff00) >> 8, pDeviceCaps->vDriverVersion&0xff)); + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: szPname=%s\n", pDeviceCaps->szPname)); + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: dwFormats=0x%08x\n", pDeviceCaps->dwFormats)); + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: wChannels=%d\n", pDeviceCaps->wChannels)); + + // is it compatible? + if (pDeviceCaps->dwFormats & VOIP_HEADSET_WAVEFORMAT) + { + // if this device is our active input device make sure to update our iActiveDevice value; + if ((iActiveDevice != -1) && (strcmp(pActiveDeviceName, pDeviceCaps->szPname) == 0)) + { + // if the active input device index changed log the change + if (iActiveDevice != iAddedDevices) + { + pDeviceInfo->iActiveDevice = iAddedDevices; + NetPrintf(("voipheadsetpc: active %s device, %s, moved from index %d to %d\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", + pActiveDeviceName, iActiveDevice, iAddedDevices)); + } + + // already matched don't compare names again + iActiveDevice = -1; + } + + NetPrintf(("voipheadsetpc: %s device %d is compatible\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", iDevice)); + iAddedDevices++; + } + else + { + NetPrintf(("voipheadsetpc: %s device %d is not compatible\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", iDevice)); + } + + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc:\n")); + } + + // set number of devices + pDeviceInfo->iNumDevices = iAddedDevices; +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetStop + + \Description + Stop recording & playback, and close USB audio device. + + \Notes + This function is safe to call regardless of device state. + + \Version 11/03/2003 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipHeadsetStop(VoipHeadsetRefT *pHeadset) +{ + _VoipHeadsetCloseDevice(pHeadset, &pHeadset->MicrInfo); + _VoipHeadsetCloseDevice(pHeadset, &pHeadset->SpkrInfo); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetEnumerate + + \Description + Enumerate all connected audio devices. + + \Input *pHeadset - headset module state + + \Notes + Although we only support one headset, we will scan up to eight to try and + locate one that meets our needs for data format and sample rate. + + \Version 07/27/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipHeadsetEnumerate(VoipHeadsetRefT *pHeadset) +{ + NetPrintf(("voipheadsetpc: enumerating devices\n")); + + // re-enumerating will kill our existing devices, so clean up appropriately. + _VoipHeadsetStop(pHeadset); + + // enumerate input devices + _VoipHeadsetEnumerateDevices(pHeadset, &pHeadset->MicrInfo); + + // enumerate output devices + _VoipHeadsetEnumerateDevices(pHeadset, &pHeadset->SpkrInfo); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetSetCodec + + \Description + Sets the specified codec. + + \Input *pHeadset - pointer to module state + \Input iCodecIdent - codec identifier to set + + \Output + int32_t - zero=success, negative=failure + + \Version 10/26/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipHeadsetSetCodec(VoipHeadsetRefT *pHeadset, int32_t iCodecIdent) +{ + int32_t iResult; + + // pass through codec creation request + if ((iResult = VoipCodecCreate(iCodecIdent, pHeadset->iMaxConduits)) < 0) + { + return(iResult); + } + + // query codec output size + pHeadset->iCmpFrameSize = VoipCodecStatus(iCodecIdent, 'fsiz', VOIP_HEADSET_FRAMESAMPLES, NULL, 0); + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetSendEncodedAudio + + \Description + Send encoded audio on the wire, or into the conduit for local playback + in loopback mode. + + \Input *pHeadset - module state + \Input *pPacket - compressed audio data + \Input iCompBytes - size of the compressed audio data + + \Version 12/05/2018 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipHeadsetSendEncodedAudio(VoipHeadsetRefT *pHeadset, VoipMicrPacketT *pPacket, int32_t iCompBytes) +{ + if (!pHeadset->bLoopback) + { + pHeadset->pMicDataCb(&pPacket->aData, iCompBytes, NULL, 0, pHeadset->iParticipatingUserIndex, pHeadset->uSendSeq++, pHeadset->pCbUserData); + } + else + { + // set up a null user for loopback + VoipUserT VoipUser; + ds_memclr(&VoipUser, sizeof(VoipUser)); + + VoipConduitReceiveVoiceData(pHeadset->pConduitRef, &VoipUser, pPacket->aData, iCompBytes); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetNarrateCb + + \Description + VoipNarrate callback handler + + \Input *pNarrateRef - narration module state + \Input iUserIndex - local user index of user who requested narration + \Input *pSamples - sample data, or NULL if no data + \Input iSize - size of sample data in bytes, or zero if no data + \Input *pUserData - callback user data (headset module ref) + + \Version 12/05/2018 (tcho) +*/ +/********************************************************************************F*/ +static int32_t _VoipHeadsetNarrateCb(VoipNarrateRefT *pNarrateRef, int32_t iUserIndex, const int16_t *pSamples, int32_t iSize, void *pUserData) +{ + VoipHeadsetRefT *pHeadset = (VoipHeadsetRefT *)pUserData; + const int8_t *pBuffer = (const int8_t *)pSamples; + VoipMicrPacketT PacketData; + int32_t iCompBytes; + + if (iSize > 0) + { + int32_t iRemain = iSize; + while (iRemain != 0) + { + int32_t iCopySize = 0; + int8_t *pFrameBuffer = pHeadset->narrateBuffer + pHeadset->iNarrateWritePos; + + if (pHeadset->iNarrateWritePos == 0) + { + ds_memclr(pFrameBuffer, VOIP_HEADSET_FRAMESIZE); + } + + if (iRemain >= (VOIP_HEADSET_FRAMESIZE - pHeadset->iNarrateWritePos)) + { + iCopySize = VOIP_HEADSET_FRAMESIZE - pHeadset->iNarrateWritePos; + } + else + { + iCopySize = iRemain; + } + + ds_memcpy(pFrameBuffer, pBuffer + (iSize - iRemain), iCopySize); + iRemain -= iCopySize; + + if ((iCopySize == VOIP_HEADSET_FRAMESIZE) || ((pHeadset->iNarrateWritePos + iCopySize) == VOIP_HEADSET_FRAMESIZE)) + { + pHeadset->iNarrateWritePos = 0; + + // compress the input data, and if there is compressed data send it to the appropriate function + if ((iCompBytes = VoipCodecEncode(PacketData.aData, (const int16_t*)(pHeadset->narrateBuffer), VOIP_HEADSET_FRAMESAMPLES, (!pHeadset->bTextChatAccessibility && pHeadset->bVoiceTranscriptionEnabled) ? pHeadset->pTranscribeRef : NULL)) > 0) + { + _VoipHeadsetSendEncodedAudio(pHeadset, &PacketData, iCompBytes); + } + } + else + { + pHeadset->iNarrateWritePos += iCopySize; + } + } + } + else + { + pHeadset->bNarrating = (iSize == VOIPNARRATE_STREAM_START) ? TRUE : FALSE; + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetProcessPlay + + \Description + Process recording of data from the buffer and sending it to the registered + mic data user callback. + + \Input *pHeadset - pointer to headset state + + \Version 07/09/2012 (akirchner) +*/ +/********************************************************************************F*/ +static void _VoipHeadsetProcessPlay(VoipHeadsetRefT *pHeadset) +{ + VoipMicrPacketT PacketData; + int32_t iCompBytes; + uint32_t uPlayerLastTime; + uint32_t uPackets; + uint32_t uPacket; + + if (!pHeadset->iPlayerFirstUse) + { + pHeadset->iPlayerFirstUse = 1; + pHeadset->uPlayerFirstTime = NetTick(); + } + + uPlayerLastTime = NetTick(); + uPackets = (uPlayerLastTime - pHeadset->uPlayerFirstTime) / VOIP_THREAD_SLEEP_DURATION; + + for (uPacket = 0; uPacket < uPackets; uPacket++) + { + pHeadset->uPlayerBufferFrameCurrent += VOIP_HEADSET_SAMPLEWIDTH; + pHeadset->uPlayerBufferFrameCurrent = (pHeadset->uPlayerBufferFrameCurrent >= pHeadset->uPlayerBufferFrames) ? 0 : pHeadset->uPlayerBufferFrameCurrent; + + // compress the buffer data, and if there is compressed data send it to the appropriate function + if ((iCompBytes = VoipCodecEncode(PacketData.aData, &pHeadset->pPlayerBuffer[VOIP_HEADSET_FRAMESAMPLES * pHeadset->uPlayerBufferFrameCurrent], VOIP_HEADSET_FRAMESAMPLES, pHeadset->pTranscribeRef)) > 0) + { + _VoipHeadsetSendEncodedAudio(pHeadset, &PacketData, iCompBytes); + } + } + + pHeadset->uPlayerFirstTime = uPlayerLastTime; +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetProcessRecord + + \Description + Process recording of data from the mic and sending it to the registered + mic data user callback. + + \Input *pHeadset - pointer to headset state + + \Version 04/01/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipHeadsetProcessRecord(VoipHeadsetRefT *pHeadset) +{ + VoipHeadsetDeviceInfoT *pMicrInfo = &pHeadset->MicrInfo; + VoipMicrPacketT PacketData; + uint8_t FrameData[VOIP_HEADSET_FRAMESIZE]; //!< current recorded audio frame + int32_t iCompBytes; + HRESULT hResult; + + // only record if we have a recording device open + if ((pMicrInfo->hDevice == NULL) || (pHeadset->iParticipatingUserIndex == VOIP_INVALID_LOCAL_USER_INDEX)) + { + return; + } + + // read data from the mic + if (pMicrInfo->bActive == TRUE) + { + if (pHeadset->bMicOn == FALSE) + { + // tell hardware to stop recording + if ((hResult = waveInStop((HWAVEIN)pMicrInfo->hDevice)) == MMSYSERR_NOERROR) + { + // mark as not recording + NetPrintf(("voipheadsetpc: stop recording\n")); + pMicrInfo->bActive = FALSE; + } + else + { + NetPrintf(("voipheadsetpc: error %d trying to stop recording\n", hResult)); + } + } + + // pull in any waiting data + for (; pMicrInfo->WaveData[pMicrInfo->iCurWaveBuffer].WaveHdr.dwFlags & WHDR_DONE; ) + { + // copy audio data out of buffer + ds_memcpy_s(FrameData, sizeof(FrameData), &pMicrInfo->WaveData[pMicrInfo->iCurWaveBuffer].FrameData, VOIP_HEADSET_FRAMESIZE); + + // compress the input data, and if there is compressed data send it to the appropriate function + if (!pHeadset->bNarrating && (!pHeadset->bMuted || pHeadset->bLoopback)) + { + if ((iCompBytes = VoipCodecEncode(PacketData.aData, (int16_t *)FrameData, VOIP_HEADSET_FRAMESAMPLES, pHeadset->bVoiceTranscriptionEnabled ? pHeadset->pTranscribeRef : NULL)) > 0) + { + if (pHeadset->bTextChatAccessibility == FALSE) + { + _VoipHeadsetSendEncodedAudio(pHeadset, &PacketData, iCompBytes); + } + } + } + + // re-queue buffer to read more data + if ((hResult = waveInAddBuffer((HWAVEIN)pMicrInfo->hDevice, &pMicrInfo->WaveData[pMicrInfo->iCurWaveBuffer].WaveHdr, sizeof(WAVEHDR))) != MMSYSERR_NOERROR) + { + if (hResult == MMSYSERR_NODRIVER) + { + NetPrintf(("voipheadsetpc: waveInAddBuffer returned MMSYSERR_NODRIVER. Headset removed? Closing headset\n")); + _VoipHeadsetStop(pHeadset); + break; + } + else if (hResult != WAVERR_STILLPLAYING) + { + NetPrintf(("voipheadsetpc: could not add buffer %d to record queue (err=%d)\n", pMicrInfo->iCurWaveBuffer, hResult)); + } + } + + // index to next read buffer + pMicrInfo->iCurWaveBuffer = (pMicrInfo->iCurWaveBuffer + 1) % VOIP_HEADSET_NUMWAVEBUFFERS; + } + } + // if not recording and a mic-on request comes in, start recording + else if (pHeadset->bMicOn == TRUE) + { + int32_t iBuffer; + + if ((hResult = waveInStart((HWAVEIN)pMicrInfo->hDevice)) == MMSYSERR_NOERROR) + { + // mark as recording + NetPrintf(("voipheadsetpc: recording...\n")); + pMicrInfo->bActive = TRUE; + } + else + { + NetPrintf(("voipheadsetpc: error %d trying to start recording\n", hResult)); + } + + // add buffers to start recording + for (iBuffer = 0; iBuffer < VOIP_HEADSET_NUMWAVEBUFFERS; iBuffer += 1) + { + if ((hResult = waveInAddBuffer((HWAVEIN)pMicrInfo->hDevice, &pMicrInfo->WaveData[iBuffer].WaveHdr, sizeof(WAVEHDR))) != MMSYSERR_NOERROR) + { + NetPrintf(("voipheadsetpc: waveInAddBuffer for buffer %d failed err=%d\n", iBuffer, hResult)); + } + } + pMicrInfo->iCurWaveBuffer = 0; + + // reset compression state + VoipCodecReset(); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetProcessPlayback + + \Description + Process playback of data received from the network to the headset earpiece. + + \Input *pHeadset - pointer to headset state + + \Version 04/01/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipHeadsetProcessPlayback(VoipHeadsetRefT *pHeadset) +{ + VoipHeadsetDeviceInfoT *pSpkrInfo = &pHeadset->SpkrInfo; + uint8_t FrameData[VOIP_HEADSET_FRAMESIZE]; //!< current recorded audio frame + int32_t iSampBytes; + HRESULT hResult; + + // only play if we have a playback device open + if ((pSpkrInfo->hDevice == NULL) || (pHeadset->iParticipatingUserIndex == VOIP_INVALID_LOCAL_USER_INDEX)) + { + return; + } + + // write data to the audio output if it's available + if (pSpkrInfo->bActive == TRUE) + { + int32_t iLoop, iNumBufAvail; + + // update play volume, if requested + if ((pSpkrInfo->iCurVolume != pSpkrInfo->iNewVolume) && (pSpkrInfo->iNewVolume != -1)) + { + int32_t iVolume; + + iVolume = (pSpkrInfo->iNewVolume * 65535) / 100; + iVolume |= iVolume << 16; + + if ((hResult = waveOutSetVolume((HWAVEOUT)pSpkrInfo->hDevice, iVolume)) == MMSYSERR_NOERROR) + { + NetPrintf(("voipheadsetpc: changed play volume to %d\n", pSpkrInfo->iNewVolume)); + pSpkrInfo->iCurVolume = pSpkrInfo->iNewVolume; + } + else + { + NetPrintf(("voipheadsetpc: error %d trying to change play volume\n", hResult)); + } + } + + // count number of buffers available to write into + for (iLoop = 0, iNumBufAvail = 0; iLoop < VOIP_HEADSET_NUMWAVEBUFFERS; iLoop += 1) + { + iNumBufAvail += (pSpkrInfo->WaveData[iLoop].WaveHdr.dwFlags & WHDR_DONE) ? 1 : 0; + } + + // if we have space to write + for ( ; pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].WaveHdr.dwFlags & pSpkrInfo->dwCheckFlag[pSpkrInfo->iCurWaveBuffer]; ) + { + // decode and mix buffered packet data + if ((iSampBytes = VoipMixerProcess(pHeadset->pMixerRef, FrameData)) == VOIP_HEADSET_FRAMESIZE) + { + // if there's nothing playing, we want to prebuffer with silence + if (iNumBufAvail == VOIP_HEADSET_NUMWAVEBUFFERS) + { + uint8_t FrameSilenceData[VOIP_HEADSET_FRAMESIZE]; + int32_t iBlock; + + ds_memclr(FrameSilenceData, sizeof(FrameSilenceData)); + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: prebuffering %d blocks\n", VOIP_HEADSET_PREBUFFERBLOCKS)); + for (iBlock = 0; iBlock < VOIP_HEADSET_PREBUFFERBLOCKS; iBlock += 1) + { + ds_memcpy_s(pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].FrameData, sizeof(pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].FrameData), FrameSilenceData, sizeof(FrameSilenceData)); + if ((hResult = waveOutWrite((HWAVEOUT)pSpkrInfo->hDevice, &pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].WaveHdr, sizeof(WAVEHDR))) == MMSYSERR_NOERROR) + { + pSpkrInfo->dwCheckFlag[pSpkrInfo->iCurWaveBuffer] = WHDR_DONE; + pSpkrInfo->iCurWaveBuffer = (pSpkrInfo->iCurWaveBuffer + 1) % VOIP_HEADSET_NUMWAVEBUFFERS; + } + else + { + NetPrintf(("voipheadsetpc: write failed (error=%d)\n", hResult)); + } + } + iNumBufAvail = 0; + } + + // forward data to speaker callback, if callback is specified + if (pHeadset->pSpkrDataCb != NULL) + { + pHeadset->pSpkrDataCb((int16_t *)FrameData, VOIP_HEADSET_FRAMESAMPLES, pHeadset->pSpkrCbUserData); + } + + // copy data to prepared wave buffer + ds_memcpy_s(pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].FrameData, sizeof(pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].FrameData), FrameData, VOIP_HEADSET_FRAMESIZE); + + // write out wave buffer + if ((hResult = waveOutWrite((HWAVEOUT)pSpkrInfo->hDevice, &pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].WaveHdr, sizeof(WAVEHDR))) == MMSYSERR_NOERROR) + { + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: [%2d] wrote %d samples\n", pSpkrInfo->iCurWaveBuffer, VOIP_HEADSET_FRAMESAMPLES)); + pSpkrInfo->dwCheckFlag[pSpkrInfo->iCurWaveBuffer] = WHDR_DONE; + pSpkrInfo->iCurWaveBuffer = (pSpkrInfo->iCurWaveBuffer + 1) % VOIP_HEADSET_NUMWAVEBUFFERS; + } + else + { + if (hResult == MMSYSERR_NODRIVER) + { + NetPrintf(("voipheadsetpc: returned MMSYSERR_NODRIVER. Headset removed? Closing headset\n")); + _VoipHeadsetStop(pHeadset); + } + else if (hResult == WAVERR_STILLPLAYING) + { + NetPrintf(("voipheadsetpc: WAVERR_STILLPLAYING\n")); + } + else + { + NetPrintf(("voipheadsetpc: write failed (error=%d)\n", hResult)); + } + } + } + else if (iSampBytes > 0) + { + NetPrintf(("voipheadsetpc: error - got %d bytes from mixer when we were expecting %d\n", iSampBytes, VOIP_HEADSET_FRAMESIZE)); + } + else + { + // no data waiting in the mixer, so break out + break; + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetProcessDeviceChange + + \Description + Process a device change request. + + \Input *pHeadset - headset state + \Input *pDeviceInfo - device info + + \Version 10/12/2011 (jbrookes) Split from VoipHeadsetProcess() +*/ +/********************************************************************************F*/ +static void _VoipHeadsetProcessDeviceChange(VoipHeadsetRefT *pHeadset, VoipHeadsetDeviceInfoT *pDeviceInfo) +{ + // early out if no change is requested + if (!pDeviceInfo->bChangeDevice && !pDeviceInfo->bCloseDevice) + { + return; + } + + // device change requires sole access to headset critical section + NetCritEnter(&pHeadset->DevChangeCrit); + + // close the device + _VoipHeadsetCloseDevice(pHeadset, pDeviceInfo); + pDeviceInfo->bCloseDevice = FALSE; + + // process change input device request + if (pDeviceInfo->bChangeDevice) + { + if (_VoipHeadsetOpenDevice(pDeviceInfo, pDeviceInfo->iDeviceToOpen, VOIP_HEADSET_NUMWAVEBUFFERS) == 0) + { + // user index is always 0 because PC does not support MLU + pHeadset->pStatusCb(0, TRUE, pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? VOIP_HEADSET_STATUS_INPUT : VOIP_HEADSET_STATUS_OUTPUT, pHeadset->pCbUserData); + } + + pDeviceInfo->bChangeDevice = FALSE; + } + + NetCritLeave(&pHeadset->DevChangeCrit); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetProcessTranscription + + \Description + Process voice transcription + + \Input pHeadset - headset ref + + \Version 09/07/2018 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipHeadsetProcessTranscription(VoipHeadsetRefT *pHeadset) +{ + char strTranscribeBuf[VOIPTRANSCRIBE_OUTPUT_MAX]; + + if (pHeadset->pTranscribeRef != NULL) + { + // process transcription + VoipTranscribeUpdate(pHeadset->pTranscribeRef); + + // if a transcription is available, send it along + if (VoipTranscribeGet(pHeadset->pTranscribeRef, strTranscribeBuf, sizeof(strTranscribeBuf)) > 0) + { + if (pHeadset->iParticipatingUserIndex != VOIP_INVALID_LOCAL_USER_INDEX) + { + pHeadset->pTextDataCb(strTranscribeBuf, pHeadset->iParticipatingUserIndex, pHeadset->pCbUserData); + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetGetRemoteUserIndex + + \Description + Returns the user index of the remote user + + \Input *pHeadset - pHeadset ref + \Input *pRemoteUser - remote user + + \Output + int32_t - remote user index, negative if user is not found + + \Version 05/28/2019 (tcho) +*/ +/********************************************************************************F*/ +static int32_t _VoipHeadsetGetRemoteUserIndex(VoipHeadsetRefT *pHeadset, VoipUserT *pRemoteUser) +{ + int32_t iRemoteUserIndex; + + for (iRemoteUserIndex = 0; iRemoteUserIndex < pHeadset->iMaxRemoteUsers; ++iRemoteUserIndex) + { + if (VOIP_SameUser(&pHeadset->aRemoteUsers[iRemoteUserIndex].User, pRemoteUser)) + { + break; + } + } + + return((iRemoteUserIndex != pHeadset->iMaxRemoteUsers) ? iRemoteUserIndex : -1); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetPlaybackCallback + + \Description + Determines if we should be submit audio to the given mixer + + \Input *pMixer - mixer ref + \Input *pRemoteUser - remote user who sent the mic packets + \Input *pUserData - callback user data (VoipHeadsetRefT) + + \Output + uint8_t - TRUE for playback, FALSE for not to playback + + \Version 05/28/2019 (cvienneau) +*/ +/********************************************************************************F*/ +static uint8_t _VoipHeadsetPlaybackCallback(VoipMixerRefT *pMixer, VoipUserT *pRemoteUser, void *pUserData) +{ + uint8_t bPlayback = TRUE; + VoipHeadsetRefT *pHeadset = (VoipHeadsetRefT *)pUserData; + + int32_t iRemoteUserIndex; + int32_t iLocalUserIndex; + + // find the remote user index + iRemoteUserIndex = _VoipHeadsetGetRemoteUserIndex(pHeadset, pRemoteUser); + + if (iRemoteUserIndex < 0) + { + NetPrintf(("voipheadsetpc: _VoipHeadsetPlaybackCallback() cannot find the remote user iPersonaId: %lld", pRemoteUser->AccountInfo.iPersonaId)); + return(TRUE); + } + + // loop through all local users determine if ANY of them have this remote user index muted (we have a shared device on PC) + for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS; ++iLocalUserIndex) + { + // muting from +-pbk selectors + if ((pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags & (1 << iRemoteUserIndex)) == 0) + { + bPlayback = FALSE; + break; + } + } + return(bPlayback); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetCreate + + \Description + Create the headset manager. + + \Input iMaxConduits - max number of conduits + \Input *pMicDataCb - pointer to user callback to trigger when mic data is ready + \Input *pTextDataCb - pointer to user callback to trigger when transcribed text is ready + \Input *pOpaqueDataCb - pointer to user callback to trigger when opaque data is ready (ignored) + \Input *pStatusCb - pointer to user callback to trigger when headset status changes + \Input *pCbUserData - pointer to user callback data + \Input iData - platform-specific - unused for PC + + \Output + VoipHeadsetRefT * - pointer to module state, or NULL if an error occured + + \Version 03/30/2004 (jbrookes) +*/ +/********************************************************************************F*/ +VoipHeadsetRefT *VoipHeadsetCreate(int32_t iMaxConduits, VoipHeadsetMicDataCbT *pMicDataCb, VoipHeadsetTextDataCbT *pTextDataCb, VoipHeadsetOpaqueDataCbT *pOpaqueDataCb, VoipHeadsetStatusCbT *pStatusCb, void *pCbUserData, int32_t iData) +{ + VoipHeadsetRefT *pHeadset; + int32_t iMemGroup, iSize, iLocalUserIndex; + void *pMemGroupUserData; + + // Query mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // make sure we don't exceed maxconduits + if (iMaxConduits > VOIP_MAXCONNECT) + { + NetPrintf(("voipheadsetpc: request for %d conduits exceeds max\n", iMaxConduits)); + return(NULL); + } + + iSize = sizeof(*pHeadset) + (sizeof(PCRemoteVoipUserT) * (iMaxConduits + VOIP_MAX_LOW_LEVEL_CONNS - 1)); + + // allocate and clear module state + if ((pHeadset = (VoipHeadsetRefT *)DirtyMemAlloc(iSize, VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + return(NULL); + } + ds_memclr(pHeadset, iSize); + + // allocate mixer + if ((pHeadset->pMixerRef = VoipMixerCreate(16, VOIP_HEADSET_FRAMESAMPLES)) == NULL) + { + NetPrintf(("voipheadsetpc: unable to create mixer\n")); + DirtyMemFree(pHeadset, VOIP_MEMID, iMemGroup, pMemGroupUserData); + return(NULL); + } + + // allocate conduit manager + if ((pHeadset->pConduitRef = VoipConduitCreate(iMaxConduits)) == NULL) + { + NetPrintf(("voipheadsetpc: unable to allocate conduit manager\n")); + VoipMixerDestroy(pHeadset->pMixerRef); + DirtyMemFree(pHeadset, VOIP_MEMID, iMemGroup, pMemGroupUserData); + return(NULL); + } + pHeadset->iMaxConduits = iMaxConduits; + pHeadset->iMaxRemoteUsers = iMaxConduits + VOIP_MAX_LOW_LEVEL_CONNS; + VoipConduitRegisterPlaybackCb(pHeadset->pConduitRef, _VoipHeadsetPlaybackCallback, pHeadset); + + // initialize playback flags (default on for everyone) + for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS; ++iLocalUserIndex) + { + pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags = 0xFFFFFFFF; + } + + // allocate narration module + if ((pHeadset->pNarrateRef = VoipNarrateCreate(_VoipHeadsetNarrateCb, pHeadset)) == NULL) + { + VoipTranscribeDestroy(pHeadset->pTranscribeRef); + VoipConduitDestroy(pHeadset->pConduitRef); + VoipMixerDestroy(pHeadset->pMixerRef); + DirtyMemFree(pHeadset, VOIP_MEMID, iMemGroup, pMemGroupUserData); + return(NULL); + } + + // set no participating user at the start + pHeadset->iParticipatingUserIndex = VOIP_INVALID_LOCAL_USER_INDEX; + + // set TTS default gender + pHeadset->eDefaultGender = VOIPNARRATE_GENDER_MALE; + + // set mixer + VoipConduitMixerSet(pHeadset->pConduitRef, pHeadset->pMixerRef); + + // register codecs + VoipCodecRegister('dvid', &VoipDVI_CodecDef); + VoipCodecRegister('lpcm', &VoipPCM_CodecDef); + + // set up to use dvi codec by default + VoipHeadsetControl(pHeadset, 'cdec', 'dvid', 0, NULL); + + // enable microphone + VoipHeadsetControl(pHeadset, 'micr', TRUE, 0, NULL); + + // save mem info + pHeadset->iMemGroup = iMemGroup; + pHeadset->pMemGroupUserData = pMemGroupUserData; + + // save info + pHeadset->pMicDataCb = pMicDataCb; + pHeadset->pTextDataCb = pTextDataCb; + pHeadset->pStatusCb = pStatusCb; + pHeadset->pCbUserData = pCbUserData; + + // no currently active device + pHeadset->SpkrInfo.eDevType = VOIP_HEADSET_OUTDEVICE; + pHeadset->SpkrInfo.iActiveDevice = -1; + pHeadset->SpkrInfo.iDeviceToOpen = WAVE_MAPPER; + + pHeadset->MicrInfo.eDevType = VOIP_HEADSET_INPDEVICE; + pHeadset->MicrInfo.iActiveDevice = -1; + pHeadset->MicrInfo.iDeviceToOpen = WAVE_MAPPER; + + // play + pHeadset->iPlayerActive = 0; + + // set initial volume + pHeadset->SpkrInfo.iNewVolume = -1; + + // set default debuglevel + pHeadset->iDebugLevel = 1; + + // enumerate headsets on startup + _VoipHeadsetEnumerate(pHeadset); + + // init the critical section + NetCritInit(&pHeadset->DevChangeCrit, "voipheadsetpc-devchange"); + + // return module ref to caller + return(pHeadset); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetDestroy + + \Description + Destroy the headset manager. + + \Input *pHeadset - pointer to headset state + + \Version 03/31/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipHeadsetDestroy(VoipHeadsetRefT *pHeadset) +{ + int32_t iMemGroup; + void *pMemGroupUserData; + + // stop headsets + _VoipHeadsetStop(pHeadset); + + // destroy transcription ref + if (pHeadset->pTranscribeRef != NULL) + { + VoipTranscribeDestroy(pHeadset->pTranscribeRef); + } + + // destroy narrate ref + VoipNarrateDestroy(pHeadset->pNarrateRef); + + // free conduit manager + VoipConduitDestroy(pHeadset->pConduitRef); + + // free mixer + VoipMixerDestroy(pHeadset->pMixerRef); + + // free active codec + VoipCodecDestroy(); + + // kill the critical section + NetCritKill(&pHeadset->DevChangeCrit); + + // dispose of module memory + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + DirtyMemFree(pHeadset, VOIP_MEMID, iMemGroup, pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetReceiveVoiceDataCb + + \Description + Connectionlist callback to handle receiving a voice packet from a remote peer. + + \Input *pRemoteUsers - user we're receiving the voice data from + \Input iRemoteUserSize - pRemoteUsers array size + \Input iConsoleId - generic identifier for the console to which the users belong + \Input *pMicrInfo - micr info from inbound packet + \Input *pPacketData - pointer to beginning of data in packet payload + \Input *pUserData - VoipHeadsetT ref + + \Version 03/21/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipHeadsetReceiveVoiceDataCb(VoipUserT *pRemoteUsers, int32_t iRemoteUserSize, int32_t iConsoleId, VoipMicrInfoT *pMicrInfo, uint8_t *pPacketData, void *pUserData) +{ + VoipHeadsetRefT *pHeadset = (VoipHeadsetRefT *)pUserData; + uint32_t uRemoteUserIndex = 0; + uint32_t uMicrPkt; + const BYTE *pSubPacket = pPacketData; + + // if we're not playing, ignore it + if (pHeadset->SpkrInfo.bActive == FALSE) + { + #if DIRTYCODE_LOGGING + if ((pMicrInfo->uSeqn % 30) == 0) + { + NetPrintfVerbose((pHeadset->iDebugLevel, 2, "voipheadsetpc: playback disabled, discarding voice data (seqn=%d)\n", pMicrInfo->uSeqn)); + } + #endif + return; + } + + // validate subpacket size if we are dealing with a fixed-length scenario + if ((pMicrInfo->uSubPacketSize != 0xFF) && (pMicrInfo->uSubPacketSize != pHeadset->iCmpFrameSize)) + { + NetPrintf(("voipheadsetpc: discarding voice packet with %d voice bundles and mismatched sub-packet size %d (expecting %d)\n", + pMicrInfo->uNumSubPackets, pMicrInfo->uSubPacketSize, pHeadset->iCmpFrameSize)); + return; + } + + // if this is the shared user index + if (pMicrInfo->uUserIndex == VOIP_SHARED_REMOTE_INDEX) + { + int32_t iIndex; + + // find the first valid user to playback the audio + for (iIndex = 0; iIndex < iRemoteUserSize; iIndex += 1) + { + if (!VOIP_NullUser(&pRemoteUsers[iIndex])) + { + uRemoteUserIndex = iIndex; + break; + } + } + + if (iIndex == iRemoteUserSize) + { + // didn't find a remote user to play back the shared audio + NetPrintf(("voipheadsetpc: discarding voice packet from shared user because we cannot find a remote user to play it back as!\n")); + return; + } + } + else + { + uRemoteUserIndex = pMicrInfo->uUserIndex; + } + + // submit voice sub-packets + for (uMicrPkt = 0; uMicrPkt < pMicrInfo->uNumSubPackets; uMicrPkt++) + { + // get the size of the subpacket based on variable or not + uint32_t uSubPacketSize = (pMicrInfo->uSubPacketSize != 0xFF) ? pMicrInfo->uSubPacketSize : *pSubPacket++; + + // send it to conduit manager + VoipConduitReceiveVoiceData(pHeadset->pConduitRef, &pRemoteUsers[uRemoteUserIndex], pSubPacket, uSubPacketSize); + + // move to next packet + pSubPacket += uSubPacketSize; + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetSetRemoteUserVoicePlayback + + \Description + Helper function to enable or disable playback of voice from a remote user + for a given local user. + + \Input *pHeadset - pointer to headset state + \Input *pRemoteUser - remote user + \Input iLocalUserIndex - local user index + \Input bEnablePlayback - TRUE to enable voice playback. FALSE to disable voice playback. + + \Output + int32_t - negative=error, zero=success + + \Version 06/09/2019 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _VoipHeadsetSetRemoteUserVoicePlayback(VoipHeadsetRefT *pHeadset, VoipUserT *pRemoteUser, int32_t iLocalUserIndex, uint8_t bEnablePlayback) +{ + int32_t iRemoteUserIndex; + + // find the remote user index + iRemoteUserIndex = _VoipHeadsetGetRemoteUserIndex(pHeadset, pRemoteUser); + + if (iRemoteUserIndex < 0) + { + NetPrintf(("voipheadsetpc: _VoipHeadsetSetRemoteUserVoicePlayback() cannot find the remote user iPersonaId: %lld", pRemoteUser->AccountInfo.iPersonaId)); + return(-1); + } + + if (bEnablePlayback) + { + pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags |= (1 << iRemoteUserIndex); + } + else + { + pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags &= ~(1 << iRemoteUserIndex); + } + NetPrintf(("voipheadsetpc: local user[%d] playback flags now: 0x%08x.\n", iLocalUserIndex, pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags)); + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetAddRemoteTalker + + \Description + Create the game chat participant for this remote user. + + \Input *pHeadset - headset module + \Input *pRemoteUser - remote user + \Input uConsoleId - unique console identifier (local scope only, does not need to be the same on all hosts) + + \Output + int32_t - negative=error, zero=success + + \Version 06/09/2019 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _VoipHeadsetAddRemoteTalker(VoipHeadsetRefT *pHeadset, VoipUserT *pRemoteUser, uint64_t uConsoleId) +{ + int32_t iRemoteUserIndex; + PCRemoteVoipUserT *pPcRemoteUser = NULL; + + // find a duplicate entry + for (iRemoteUserIndex = 0; iRemoteUserIndex < pHeadset->iMaxRemoteUsers; ++iRemoteUserIndex) + { + if (VOIP_SameUser(&pHeadset->aRemoteUsers[iRemoteUserIndex].User, pRemoteUser)) + { + pPcRemoteUser = &pHeadset->aRemoteUsers[iRemoteUserIndex]; + break; + } + } + + // early return if we remote user already exists + if (pPcRemoteUser != NULL) + { + NetPrintf(("voipheadsetpc: adding remote talker %lld failed because it already exists\n", pRemoteUser->AccountInfo.iPersonaId)); + return(-1); + } + + // find a empty remote user entry + for (iRemoteUserIndex = 0; iRemoteUserIndex < pHeadset->iMaxRemoteUsers; ++iRemoteUserIndex) + { + if (pHeadset->aRemoteUsers[iRemoteUserIndex].User.AccountInfo.iPersonaId == 0) + { + ds_memclr(&pHeadset->aRemoteUsers[iRemoteUserIndex], sizeof(PCRemoteVoipUserT)); + ds_memcpy(&pHeadset->aRemoteUsers[iRemoteUserIndex].User, pRemoteUser, sizeof(VoipUserT)); + pPcRemoteUser = &pHeadset->aRemoteUsers[iRemoteUserIndex]; + + // register the remote user with all the conduits + VoipConduitRegisterUser(pHeadset->pConduitRef, pRemoteUser, TRUE); + + NetPrintf(("voipheadsetpc: registered remote talker %lld at remote user index %d\n", pRemoteUser->AccountInfo.iPersonaId, iRemoteUserIndex)); + break; + } + } + + if (pPcRemoteUser == NULL) + { + NetPrintf(("voipheadsetpc: adding remote talker %lld failed because aRemoteUsers is full\n", pRemoteUser->AccountInfo.iPersonaId)); + return(-2); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipHeadsetRemoveRemoteTalker + + \Description + Remove the specified remote user from the collection of users known by the chat manager. + + \Input *pHeadset - headset module + \Input *pUser - user to be removed + + \Output + int32_t - negative=error, zero=success + + \Version 11/22/2018 (mclouatre) +*/ +/********************************************************************************F*/ +static int32_t _VoipHeadsetRemoveRemoteTalker(VoipHeadsetRefT *pHeadset, VoipUserT *pUser) +{ + int32_t iRetCode = 0; + int32_t iRemoteUserIndex; + PCRemoteVoipUserT *pPcRemoteUser = NULL; + + VoipConduitRegisterUser(pHeadset->pConduitRef, pUser, FALSE); + + // find the remote user + for (iRemoteUserIndex = 0; iRemoteUserIndex < pHeadset->iMaxRemoteUsers; ++iRemoteUserIndex) + { + if (VOIP_SameUser(&pHeadset->aRemoteUsers[iRemoteUserIndex].User, pUser)) + { + pPcRemoteUser = &pHeadset->aRemoteUsers[iRemoteUserIndex]; + break; + } + } + + if (pPcRemoteUser != NULL) + { + NetPrintf(("voipheadsetpc: unregistered remote talker %lld at remote user index %d\n", pPcRemoteUser->User.AccountInfo.iPersonaId, iRemoteUserIndex)); + ds_memclr(pPcRemoteUser, sizeof(PCRemoteVoipUserT)); + } + else + { + NetPrintf(("voipheadsetpc: unregistered remote talker %lld failed because it does not exist\n", pUser->AccountInfo.iPersonaId)); + iRetCode = -1; + } + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetRegisterUserCb + + \Description + Connectionlist callback to register/unregister a new user with the + VoipConduit module. + + \Input *pRemoteUser - user to register + \Input iConsoleId - generic identifier for the console to which the user belongs (ignored) + \Input bRegister - true=register, false=unregister + \Input *pUserData - voipheadset module ref + + \Version 03/21/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipHeadsetRegisterUserCb(VoipUserT *pRemoteUser, int32_t iConsoleId, uint32_t bRegister, void *pUserData) +{ + VoipHeadsetRefT *pHeadset = (VoipHeadsetRefT *)pUserData; + + // early exit if invalid remote talker + if (VOIP_NullUser(pRemoteUser)) + { + NetPrintf(("voipheadsetpc: can't %s NULL remote talker\n", (bRegister ? "register" : "unregister"))); + return; + } + + if (bRegister) + { + _VoipHeadsetAddRemoteTalker(pHeadset, pRemoteUser, (uint64_t)iConsoleId); + } + else + { + _VoipHeadsetRemoveRemoteTalker(pHeadset, pRemoteUser); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetProcess + + \Description + Headset process function. + + \Input *pHeadset - pointer to headset state + \Input uFrameCount - process iteration counter + + \Version 03/31/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipHeadsetProcess(VoipHeadsetRefT *pHeadset, uint32_t uFrameCount) +{ + if (pHeadset->iPlayerActive) + { + // process playing + _VoipHeadsetProcessPlay(pHeadset); + } + else + { + // process recording + _VoipHeadsetProcessRecord(pHeadset); + } + + // process narration + VoipNarrateUpdate(pHeadset->pNarrateRef); + + // process transcription + _VoipHeadsetProcessTranscription(pHeadset); + + // process playback + _VoipHeadsetProcessPlayback(pHeadset); + + // process device change requests + _VoipHeadsetProcessDeviceChange(pHeadset, &pHeadset->MicrInfo); + _VoipHeadsetProcessDeviceChange(pHeadset, &pHeadset->SpkrInfo); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetSetVolume + + \Description + Sets play and record volume. + + \Input *pHeadset - pointer to headset state + \Input iPlayVol - play volume to set + \Input iRecVol - record volume to set + + \Notes + To not set a value, specify it as -1. + + \Version 03/31/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipHeadsetSetVolume(VoipHeadsetRefT *pHeadset, int32_t iPlayVol, uint32_t iRecVol) +{ + if (iPlayVol != -1) + { + pHeadset->SpkrInfo.iNewVolume = iPlayVol; + } +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetControl + + \Description + Control function. + + \Input *pHeadset - headset module state + \Input iControl - control selector + \Input iValue - control value + \Input iValue2 - control value + \Input *pValue - control value + + \Output + int32_t - selector specific, or -1 if no such selector + + \Notes + iControl can be one of the following: + + \verbatim + 'aloc' - promote user to 'participating' state, or demote user from 'participating' state + 'cdec' - create new codec + 'cide' - close voip input device + 'code' - close voip output device + 'cstm' - clear speech to text metrics in VoipSpeechToTextMetricsT + 'ctsm' - clear text to speech metrics in VoipTextToSpeechMetricsT + 'edev' - enumerate voip input/output devices + 'idev' - select voip input device + 'loop' - enable/disable loopback + 'micr' - enable/disable recording + 'mute' - enable/disable audio input muting + 'odev' - select voip output device + 'play' - enable/disable playing + 'txta' - enable/disable text chat accessibility, this also controls loopback in this context + 'tran' - enable/disable local generation of transcribed text for speech procuced by local users (speech-to-text component) + 'svol' - changes speaker volume + 'ttos' - send utf8 text in pValue as voice and initialize TTS voice + 'xply' - enable/disable crossplay (no difference on PC) + + \endverbatim + + \Version 07/28/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipHeadsetControl(VoipHeadsetRefT *pHeadset, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iControl == 'aloc') + { + int32_t iLocalUserIndex; + + if (iValue2 == 0) + { + pHeadset->iParticipatingUserIndex = VOIP_INVALID_LOCAL_USER_INDEX; + NetPrintf(("voipheadsetpc: no user participating\n", iValue)); + } + else + { + pHeadset->iParticipatingUserIndex = iValue; + NetPrintf(("voipheadsetpc: user %d, entering participating state\n", iValue)); + } + + // whenever active user status changes all mute masks will be re-calculated so lets reset our state + for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS; ++iLocalUserIndex) + { + pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags = 0xFFFFFFFF; + } + + return(0); + } + if (iControl == 'cdec') + { + return(_VoipHeadsetSetCodec(pHeadset, iValue)); + } + if ((iControl == 'cide') || (iControl == 'code')) + { + VoipHeadsetDeviceInfoT *pDeviceInfo = (iControl == 'cide') ? &pHeadset->MicrInfo : &pHeadset->SpkrInfo; + NetCritEnter(&pHeadset->DevChangeCrit); + pDeviceInfo->bCloseDevice = TRUE; + NetCritLeave(&pHeadset->DevChangeCrit); + } + if (iControl == 'cstm') + { + if (pHeadset->pTranscribeRef != NULL) + { + return(VoipTranscribeControl(pHeadset->pTranscribeRef, iControl, iValue, 0, NULL)); + } + return(-1); + } + if (iControl == 'ctsm') + { + return(VoipNarrateControl(pHeadset->pNarrateRef, iControl, 0, 0, NULL)); + } + if (iControl == 'edev') + { + _VoipHeadsetEnumerateDevices(pHeadset, &pHeadset->MicrInfo); + _VoipHeadsetEnumerateDevices(pHeadset, &pHeadset->SpkrInfo); + return(0); + } + if ((iControl == 'idev') || (iControl == 'odev')) + { + VoipHeadsetDeviceInfoT *pDeviceInfo = (iControl == 'idev') ? &pHeadset->MicrInfo : &pHeadset->SpkrInfo; + if (pDeviceInfo->iActiveDevice != iValue) + { + NetPrintf(("voipheadsetpc: '%C' selector used to change %s device from %d to %d\n", iControl, pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", + pDeviceInfo->iActiveDevice, iValue)); + NetCritEnter(&pHeadset->DevChangeCrit); + pDeviceInfo->iDeviceToOpen = iValue; + pDeviceInfo->bChangeDevice = TRUE; + NetCritLeave(&pHeadset->DevChangeCrit); + } + else + { + NetPrintf(("voipheadsetpc: '%C' selector ignored because %s device %d is already active\n", iControl, pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", + pDeviceInfo->iActiveDevice, iValue)); + } + return(0); + } + if (iControl == 'loop') + { + pHeadset->bLoopback = iValue; + NetPrintf(("voipheadsetpc: loopback mode %s\n", (iValue ? "enabled" : "disabled"))); + return(0); + } + if (iControl == 'micr') + { + pHeadset->bMicOn = (uint8_t)iValue; + NetPrintf(("voipheadsetpc: mic %s\n", (iValue ? "enabled" : "disabled"))); + return(0); + } + if (iControl == 'mute') + { + uint8_t bMuted = iValue2 ? TRUE : FALSE; + if (pHeadset->bMuted != bMuted) + { + NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: mute %s\n", (pHeadset->bMuted ? "enabled" : "disabled"))); + pHeadset->bMuted = bMuted; + } + return(0); + } + if ((iControl == '-pbk') || (iControl == '+pbk')) + { + uint8_t bVoiceEnable = (iControl == '+pbk') ? TRUE : FALSE; + int32_t iRetCode = 0; // default to success + + VoipUserT *pRemoteUser; + + if ((pValue != NULL) && (iValue < VOIP_MAXLOCALUSERS_EXTENDED)) + { + pRemoteUser = (VoipUserT *)pValue; + + // make sure the local user and the remote user are not a shared user (shared user concept not supported on pc) + if ((iValue != VOIP_SHARED_USER_INDEX) && ((pRemoteUser->AccountInfo.iPersonaId & VOIP_SHARED_USER_MASK) != VOIP_SHARED_USER_VALUE)) + { + _VoipHeadsetSetRemoteUserVoicePlayback(pHeadset, pRemoteUser, iValue, bVoiceEnable); + } + } + else + { + NetPrintf(("voipheadsetpc: VoipHeadsetControl('%C', %d) invalid arguments\n", iControl, iValue)); + iRetCode = -2; + } + return(iRetCode); + } + #if DIRTYCODE_LOGGING + if (iControl == 'spam') + { + pHeadset->iDebugLevel = iValue; + NetPrintf(("voipheadsetpc: debuglevel=%d\n", pHeadset->iDebugLevel)); + VoipConduitControl(pHeadset->pConduitRef, 'spam', iValue, pValue); + return(VoipCodecControl(VOIP_CODEC_ACTIVE, iControl, iValue, 0, NULL)); + } + #endif + if (iControl == 'txta') + { + pHeadset->bTextChatAccessibility = pHeadset->bLoopback = iValue; + NetPrintf(("voipheadsetpc: text chat accessibility mode %s\n", (iValue ? "enabled" : "disabled"))); + return(0); + } + if (iControl == 'play') + { + if (iValue) + { + pHeadset->pPlayerBuffer = (int16_t *) pValue; + pHeadset->uPlayerBufferFrameCurrent = 0; + pHeadset->uPlayerBufferFrames = iValue / (VOIP_HEADSET_SAMPLEWIDTH * VOIP_HEADSET_FRAMESAMPLES); + pHeadset->uPlayerFirstTime = 0; + + pHeadset->iPlayerActive = 1; + } + else + { + pHeadset->iPlayerActive = 0; + } + + NetPrintf(("voipheadsetpc: play %s\n", ((pHeadset->iPlayerActive) ? "enabled" : "disabled"))); + + return(0); + } + if (iControl == 'tran') + { + if (iValue != pHeadset->bVoiceTranscriptionEnabled) + { + pHeadset->bVoiceTranscriptionEnabled = iValue; + NetPrintf(("voipheadsetpc: %s voice transcription locally\n", pHeadset->bVoiceTranscriptionEnabled ? "enabling" : "disabling")); + + // create the transcription module the first time we need it + if (pHeadset->bVoiceTranscriptionEnabled && (pHeadset->pTranscribeRef == NULL)) + { + // allocate transcription module; give it 16k buffer to match ssl max frame size for efficient network transport + if ((pHeadset->pTranscribeRef = VoipTranscribeCreate(16 * 1024)) != NULL) + { + // since this is being created late, set the debug level to the current setting + #if DIRTYCODE_LOGGING + VoipTranscribeControl(pHeadset->pTranscribeRef, 'spam', pHeadset->iDebugLevel, 0, NULL); + #endif + } + else + { + NetPrintf(("voipheadsetpc: unable to allocate transcription manager\n")); + return(-1); + } + } + } + return(0); + } + if (iControl == 'svol') + { + VoipHeadsetSetVolume(pHeadset, iValue, 0); + return(0); + } + if (iControl == 'voic') + { + int iRet = 0; + const VoipSynthesizedSpeechCfgT *pCfg = (const VoipSynthesizedSpeechCfgT *)pValue; + pHeadset->eDefaultGender = (pCfg->iPersonaGender == 1) ? VOIPNARRATE_GENDER_FEMALE : VOIPNARRATE_GENDER_MALE; + + if (pCfg->iLanguagePackCode != 0) + { + iRet = VoipNarrateControl(pHeadset->pNarrateRef, 'lang', pCfg->iLanguagePackCode, 0, NULL); + } + + return(iRet); + } + if (iControl == 'ttos') + { + VoipNarrateGenderE eGender = ((iValue2 > VOIPNARRATE_GENDER_NONE) && (iValue2 < VOIPNARRATE_NUMGENDERS)) ? (VoipNarrateGenderE)iValue2 : pHeadset->eDefaultGender; + return(VoipNarrateInput(pHeadset->pNarrateRef, iValue, eGender, (const char *)pValue)); + } + if (iControl == 'uvoc') + { + VoipNarrateControl(pHeadset->pNarrateRef, 'uvoc', 0, 0, NULL); + return(0); + } + if(iControl == 'xply') + { + uint8_t bCrossplay = iValue ? TRUE : FALSE; + + if (pHeadset->bCrossplay != bCrossplay) + { + NetPrintf(("voipheadsetpc: changing crossplay mode to: %s", bCrossplay ? "crossplay" : "native")); + pHeadset->bCrossplay = bCrossplay; + } + return(0); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetStatus + + \Description + Status function. + + \Input *pHeadset - headset module state + \Input iSelect - control selector + \Input iValue - selector specific + \Input *pBuf - buffer pointer + \Input iBufSize - buffer size + + \Output + int32_t - selector specific, or -1 if no such selector + + \Notes + iSelect can be one of the following: + + \verbatim + 'ndev' - zero + 'idev' - get name of input device at index iValue (-1 returns number of input devices) + 'odev' - get name of output device at index iValue (-1 returns number of input devices) + 'idft' - get the default input device index (as specified in control panel) + 'mute' - get muted status + 'odft' - get the default output device index (as specified in control panel) + 'ruvu' - return TRUE if the given remote user (pBuf) is registered with voipheadset, FALSE if not. + 'sttm' - get the VoipSpeechToTextMetricsT via pBuf + 'ttos' - returns TRUE if TTS is active, else FALSE + 'ttsm' - get the VoipTextToSpeechMetricsT via pBuf + 'xply' - return status on crossplay + + \endverbatim + + \Version 07/28/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipHeadsetStatus(VoipHeadsetRefT *pHeadset, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + if (iSelect == 'ndev') + { + return(0); + } + if ((iSelect == 'idev') || (iSelect == 'odev')) + { + VoipHeadsetDeviceInfoT *pDeviceInfo = (iSelect == 'idev') ? &pHeadset->MicrInfo : &pHeadset->SpkrInfo; + if (iValue == -1) + { + return(pDeviceInfo->iNumDevices); + } + else if ((iValue >= 0) && (iValue < pDeviceInfo->iNumDevices)) + { + ds_memcpy(pBuf, pDeviceInfo->WaveDeviceCaps[iValue].szPname, sizeof(pDeviceInfo->WaveDeviceCaps[iValue].szPname)); + return(iValue); + } + } + if (iSelect == 'idft') + { + DWORD dwPreferredDevice=0; + DWORD dwStatusFlags=0; + DWORD dwRetVal = 0; + + dwRetVal = waveInMessage((HWAVEIN)VOIP_HEADSET_WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&dwPreferredDevice, (DWORD_PTR)&dwStatusFlags); + return((int32_t)dwPreferredDevice); + } + if (iSelect == 'mute') + { + return(pHeadset->bMuted); + } + if (iSelect == 'odft') + { + DWORD dwPreferredDevice=0; + DWORD dwStatusFlags; + DWORD dwRetVal; + uint32_t uMessage; + + switch (iValue) + { + case VOIP_DEFAULTDEVICE_VOICECOM: + dwStatusFlags = 0; + uMessage = DRVM_MAPPER_CONSOLEVOICECOM_GET; + break; + + case VOIP_DEFAULTDEVICE_VOICECOM_ONLY: + dwStatusFlags = DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY; + uMessage = DRVM_MAPPER_CONSOLEVOICECOM_GET; + break; + + case VOIP_DEFAULTDEVICE_PREFERRED: + dwStatusFlags = 0; + uMessage = DRVM_MAPPER_PREFERRED_GET; + break; + + case VOIP_DEFAULTDEVICE_PREFERRED_ONLY: + dwStatusFlags = DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY; + uMessage = DRVM_MAPPER_PREFERRED_GET; + break; + + default: + NetPrintf(("voipheadsetpc: iValue=%d, which is not a VoipHeadsetPreferredDeviceTypeE, using default\n", iValue)); + dwStatusFlags = 0; + uMessage = DRVM_MAPPER_CONSOLEVOICECOM_GET; + break; + } + + dwRetVal = waveOutMessage((HWAVEOUT)VOIP_HEADSET_WAVE_MAPPER, uMessage, (DWORD_PTR)&dwPreferredDevice, (DWORD_PTR)&dwStatusFlags); + + return((int32_t)dwPreferredDevice); + } + if (iSelect == 'ruvu') + { + int32_t iRemoteUserSpaceIndex = 0; + + for (iRemoteUserSpaceIndex = 0; iRemoteUserSpaceIndex < pHeadset->iMaxRemoteUsers; ++iRemoteUserSpaceIndex) + { + if (VOIP_SameUser(&pHeadset->aRemoteUsers[iRemoteUserSpaceIndex].User, (VoipUserT *)pBuf)) + { + // remote user found and it is registered with voipheadset + return (TRUE); + } + } + + return(FALSE); + } + if (iSelect == 'sttm') + { + if ((pHeadset->pTranscribeRef != NULL) && (iValue == pHeadset->iParticipatingUserIndex)) + { + return(VoipTranscribeStatus(pHeadset->pTranscribeRef, iSelect, iValue, pBuf, iBufSize)); + } + return(-1); + } + if (iSelect == 'ttos') + { + return(pHeadset->bNarrating); + } + if (iSelect == 'ttsm') + { + if (iValue == pHeadset->iParticipatingUserIndex) + { + return(VoipNarrateStatus(pHeadset->pNarrateRef, iSelect, iValue, pBuf, iBufSize)); + } + return(-1); + } + if (iSelect == 'xply') + { + return(pHeadset->bCrossplay); + } + // unhandled result, fallthrough to active codec + return(VoipCodecStatus(VOIP_CODEC_ACTIVE, iSelect, iValue, pBuf, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetSpkrCallback + + \Description + Set speaker output callback. + + \Input *pHeadset - headset module state + \Input *pCallback - what to call when output data is available + \Input *pUserData - user data for callback + + \Version 12/12/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipHeadsetSpkrCallback(VoipHeadsetRefT *pHeadset, VoipSpkrCallbackT *pCallback, void *pUserData) +{ + pHeadset->pSpkrDataCb = pCallback; + pHeadset->pSpkrCbUserData = pUserData; +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetConfigTranscription + + \Description + Configure the transcribe module + + \Input *pHeadset - headset module state + \Input uProfile - transcribe profile + \Input *pUrl - transcribe provider url + \Input *pKey - transcribe key + + \Version 11/06/2018 (tcho) +*/ +/********************************************************************************F*/ +void VoipHeadsetConfigTranscription(VoipHeadsetRefT *pHeadset, uint32_t uProfile, const char *pUrl, const char *pKey) +{ + VoipTranscribeConfig(uProfile, pUrl, pKey); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetConfigNarration + + \Description + Configure the transcribe module + + \Input *pHeadset - headset module state + \Input uProvider - narrate provider identifier + \Input *pUrl - narrate provider url + \Input *pKey - narrate key + + \Version 1/07/2019 (tcho) +*/ +/********************************************************************************F*/ +void VoipHeadsetConfigNarration(VoipHeadsetRefT *pHeadset, uint32_t uProvider, const char *pUrl, const char *pKey) +{ + VoipNarrateConfig((VoipNarrateProviderE)uProvider, pUrl, pKey); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetSetFirstPartyIdCallback + + \Description + Callback to tell dirtysdk what the first party id is for this user + + \Input *pHeadset - headset module state + \Input *pCallback - what to call when transcribed text is received from remote player + \Input *pUserData - user data for callback + + \Version 04/28/2020 (eesponda) +*/ +/********************************************************************************F*/ +void VoipHeadsetSetFirstPartyIdCallback(VoipHeadsetRefT *pHeadset, VoipFirstPartyIdCallbackCbT *pCallback, void *pUserData) +{ +} diff --git a/src/thirdparty/dirtysdk/source/voip/pc/voipnarratepc.cpp b/src/thirdparty/dirtysdk/source/voip/pc/voipnarratepc.cpp new file mode 100644 index 00000000..97aa5585 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/pc/voipnarratepc.cpp @@ -0,0 +1,1146 @@ +/*H********************************************************************************/ +/*! +\File voipnarratepc.cpp + + \Description + Voip narration API wrapping SAPI text to speech APIs for PC + + \Copyright + Copyright 2018 Electronic Arts + + \Version 12/4/2018 (tcho) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ +#pragma warning(push, 0) +#include +#include +#pragma warning(pop) + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtyerr.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/voip/voipdef.h" +#include "DirtySDK/voip/voipnarrate.h" +/*** Defines **********************************************************************/ +// must be the same as voipheadsetpc +#define VOIPNARRATE_SAMPLEWIDTH (2) //!< sample size; 16-bit samples +#define VOIPNARRATE_FRAMEDURATION (20) //!< frame duration in milliseconds; 20ms +#define VOIPNARRATE_FRAMESAMPLES ((VOIPNARRATE_SAMPLERATE*VOIPNARRATE_FRAMEDURATION)/1000) //!< samples per frame (20ms; 8khz=160, 11.025khz=220.5, 16khz=320) +#define VOIPNARRATE_FRAMESIZE (VOIPNARRATE_FRAMESAMPLES*VOIPNARRATE_SAMPLEWIDTH) //!< frame size in bytes; 640 +#define VOIPNARRATE_READLEN (VOIPNARRATE_FRAMESIZE+30) +#define VOIPNARRATE_BUFFER_LEN (50000) +/*** Macros ***********************************************************************/ +/*** Type Definitions *************************************************************/ + +//! narration request data +typedef struct VoipNarrateRequestT +{ + struct VoipNarrateRequestT *pNext; + VoipNarrateGenderE eGender; + char strText[VOIPNARRATE_INPUT_MAX]; +} VoipNarrateRequestT; + +//! TTS States +typedef enum VoipNarrateVoiceStateE +{ + VOIPNARRATE_VOICE_UNINITIALIZED, + VOIPNARRATE_VOICE_UNINITIALIZING, + VOIPNARRATE_VOICE_READY, + VOIPNARRATE_VOICE_BUSY, + VOIPNARRATE_VOICE_INITIALIZATION_FAILED +} VoipNarrateVoiceStateE; + +//! tts result voice stream +class VoipNarrateVoiceStream : public IStream +{ + public: + VoipNarrateVoiceStream(int32_t iMemGroup, void *pMemGroupUserData); + virtual ~VoipNarrateVoiceStream(); + + // from IStream + HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition); + HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER libNewSize); + HRESULT STDMETHODCALLTYPE Commit(DWORD grfCommitFlags); + HRESULT STDMETHODCALLTYPE Revert(void); + HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType); + HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType); + HRESULT STDMETHODCALLTYPE Clone(IStream **ppStream); + HRESULT STDMETHODCALLTYPE Stat(STATSTG *pstatstg, DWORD grfStatFlag); + HRESULT STDMETHODCALLTYPE CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten); + + // from ISequentialStream + HRESULT STDMETHODCALLTYPE Read(void *pData, ULONG uDataLen, ULONG *pRead); + HRESULT STDMETHODCALLTYPE Write(const void *pData, ULONG uDataLen, ULONG *pWritten); + + // from IUnknown + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject); + ULONG STDMETHODCALLTYPE AddRef(void); + ULONG STDMETHODCALLTYPE Release(void); + + // useful methods + uint32_t GetDataSize(); + + private: + NetCritT m_BufferCrit; + uint8_t* m_pBuffer; + uint32_t m_uBufferSize; + uint32_t m_uReadPos; + uint32_t m_uWritePos; + int32_t m_iMemGroup; + void *m_pMemGroupUserData; + LONG m_uRefCount; +}; + +struct VoipNarrateRefT +{ + int32_t iMemGroup; + void *pMemGroupUserData; + + VoipNarrateVoiceDataCbT *pVoiceDataCb; //!< user callback used to provide voice data + void *pUserData; //!< user data for user callback + + VoipNarrateRequestT *pRequest; //!< list of queued requests, if any + + VoipTextToSpeechMetricsT Metrics; //!< Usage metrics of the narration module + uint32_t uTtsStartTime; + + uint8_t bFirstSynthOfPhrase; + uint8_t bChangeLang; + int32_t iLangCode; + NetCritT VoiceCrit; + ISpVoice *pVoice; + ISpStream *pVoiceStream; + VoipNarrateVoiceStream *pBaseStream; + VoipNarrateVoiceStateE eVoiceState; + VoipNarrateGenderE eGender; +}; +/*** Private Functions ************************************************************/ +/*F********************************************************************************/ +/*! + \Function _VoipNarrateUninitialize + + \Description + Uninitialize SAPI + + \Input *pVoipNarrate - pointer to module state + + \Version 12/05/2018 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipNarrateUninitialize(VoipNarrateRefT *pVoipNarrate) +{ + // release SpStream interface + if (pVoipNarrate->pVoiceStream != NULL) + { + pVoipNarrate->pVoiceStream->Release(); + pVoipNarrate->pVoiceStream = NULL; + } + + // release SpVoice interface + if (pVoipNarrate->pVoice != NULL) + { + pVoipNarrate->pVoice->Release(); + pVoipNarrate->pVoice = NULL; + CoUninitialize(); + } + + // set state + NetCritEnter(&pVoipNarrate->VoiceCrit); + pVoipNarrate->eVoiceState = VOIPNARRATE_VOICE_UNINITIALIZED; + NetCritLeave(&pVoipNarrate->VoiceCrit); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateInitialize + + \Description + Initialize SAPI + + \Input *pVoipNarrate - pointer to module state + + \Version 12/05/2018 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipNarrateInitialize(VoipNarrateRefT *pVoipNarrate) +{ + HRESULT hResult; + WAVEFORMATEX WaveFormatEx = { WAVE_FORMAT_PCM, 1, VOIPNARRATE_SAMPLERATE, VOIPNARRATE_SAMPLERATE * VOIPNARRATE_SAMPLEWIDTH, 2, 16, 0 }; + + // initialize COM library + hResult = CoInitialize(NULL); + if (FAILED(hResult) && (hResult != RPC_E_CHANGED_MODE)) + { + NetPrintf(("voipnarratepc: failed to initialize COM library! (err=%s)\n", DirtyErrGetName(hResult))); + pVoipNarrate->eVoiceState = VOIPNARRATE_VOICE_INITIALIZATION_FAILED; + return; + } + + // create our own IStream + pVoipNarrate->pBaseStream = new (DirtyMemAlloc(sizeof(VoipNarrateVoiceStream), VOIP_MEMID, pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData)) VoipNarrateVoiceStream(pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData); + + // create SpVoice interface + if ((hResult = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoipNarrate->pVoice)) != S_OK) + { + NetPrintf(("voipnarratepc: failed to create ISpVoice interface!(err=%s)\n", DirtyErrGetName(hResult))); + pVoipNarrate->eVoiceState = VOIPNARRATE_VOICE_INITIALIZATION_FAILED; + CoUninitialize(); + return; + } + + // create SpStream interface + if ((hResult = CoCreateInstance(CLSID_SpStream, NULL, CLSCTX_ALL, IID_ISpStream, (void **)&pVoipNarrate->pVoiceStream)) != S_OK) + { + NetPrintf(("voipnarratepc: failed to create ISpStream interface!(err=%s)\n", DirtyErrGetName(hResult))); + pVoipNarrate->pVoice->Release(); + pVoipNarrate->pVoice = NULL; + pVoipNarrate->eVoiceState = VOIPNARRATE_VOICE_INITIALIZATION_FAILED; + CoUninitialize(); + return; + } + + // set the voice stream's base stream and wav format + if (pVoipNarrate->pVoiceStream->SetBaseStream(pVoipNarrate->pBaseStream, SPDFID_WaveFormatEx, &WaveFormatEx) != S_OK) + { + NetPrintf(("voipnarratepc: failed to set base stream!\n")); + } + + // set audio output of the voice interface + if (pVoipNarrate->pVoice->SetOutput(pVoipNarrate->pVoiceStream, TRUE) != S_OK) + { + NetPrintf(("voipnarratepc: failed to set voice stream!\n")); + } + + // set interest + if (pVoipNarrate->pVoice->SetInterest(SPFEI_ALL_TTS_EVENTS, NULL) != S_OK) + { + NetPrintf(("voipnarratepc: failed to set voice interest!\n")); + } + + pVoipNarrate->eVoiceState = VOIPNARRATE_VOICE_READY; +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateGenderChange + + \Description + Change sythesize voice gender (right only english is supported for now) + + \Input *pVoipNarrate - pointer to module state + \Input eGender - preferred gender for voice narration + + \Output + int32_t - negative=failure, else success + + + + \Version 12/04/2018 (tcho) +*/ +/********************************************************************************F*/ +static int32_t _VoipNarrateGenderChange(VoipNarrateRefT *pVoipNarrate, VoipNarrateGenderE eGender) +{ + wchar_t strLang[20]; + wchar_t strGender[20]; + ISpObjectToken *pVoiceToken; + IEnumSpObjectTokens *pVoiceTokenList; + + _snwprintf(strLang, sizeof(strLang), L"Language=%d", pVoipNarrate->iLangCode); + _snwprintf(strGender, sizeof(strGender), L"Gender=%s", eGender == VOIPNARRATE_GENDER_FEMALE ? L"Female" : L"Male"); + + if (SpEnumTokens(SPCAT_VOICES, strLang, strGender, &pVoiceTokenList) != S_OK) + { + NetPrintf(("voipheadsetpc: cannot retrieve voice token list\n")); + return(-1); + } + + pVoiceTokenList->Next(1, &pVoiceToken, NULL); + + if (pVoiceToken != NULL) + { + pVoipNarrate->pVoice->SetVoice(pVoiceToken); + } + else + { + NetPrintf(("voipheadsetpc: Lang:%i Gender:%s voice not found \n", pVoipNarrate->iLangCode, pVoipNarrate->eGender == VOIPNARRATE_GENDER_FEMALE ? "Female" : "Male")); + return(-2); + } + + return(0); +} + + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateRequestAdd + + \Description + Queue request for later sending + + \Input *pVoipNarrate - pointer to module state + \Input iUserIndex - local user index of user who is requesting speech synthesis + \Input eGender - preferred gender for voice narration + \Input *pText - text to be converted + + \Output + int32_t - negative=failure, else success + + \Version 12/04/2018 (tcho) +*/ +/********************************************************************************F*/ +static int32_t _VoipNarrateRequestAdd(VoipNarrateRefT *pVoipNarrate, int32_t iUserIndex, VoipNarrateGenderE eGender, const char *pText) +{ + VoipNarrateRequestT *pRequest; + + // allocate and clear the request + if ((pRequest = (VoipNarrateRequestT *)DirtyMemAlloc(sizeof(*pRequest), VOIPNARRATE_MEMID, pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData)) == NULL) + { + NetPrintf(("voipnarratepc: could not allocate request\n")); + pVoipNarrate->Metrics.uErrorCount += 1; + return(-1); + } + ds_memclr(pRequest, sizeof(*pRequest)); + + // copy the request data + ds_strnzcpy(pRequest->strText, pText, sizeof(pRequest->strText)); + pRequest->eGender = eGender; + + // add to queue + pRequest->pNext = pVoipNarrate->pRequest; + pVoipNarrate->pRequest = pRequest; + + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateRequestGet + + \Description + Get queued request + + \Input *pVoipNarrate - pointer to module state + \Input *pRequest - [out] storage for request + + \Version 12/04/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipNarrateRequestGet(VoipNarrateRefT *pVoipNarrate, VoipNarrateRequestT *pRequest) +{ + VoipNarrateRequestT **ppRequest; + // get oldest request (we add to head, so get from tail) + for (ppRequest = &pVoipNarrate->pRequest; (*ppRequest)->pNext != NULL; ppRequest = &((*ppRequest)->pNext)) + ; + // copy request + ds_memcpy_s(pRequest, sizeof(*pRequest), *ppRequest, sizeof(**ppRequest)); + // free request + DirtyMemFree(*ppRequest, VOIPNARRATE_MEMID, pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData); + // remove from list + *ppRequest = NULL; +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateStart + + \Description + Starts SAPI voice sythesis + + \Input *pVoipNarrate - pointer to module state + \Input eGender - preferred gender for voice for narration + \Input *pText - pointer to text request + + \Version 12/04/2018 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipNarrateStart(VoipNarrateRefT *pVoipNarrate, VoipNarrateGenderE eGender, const char *pText) +{ + HRESULT hResult; + int32_t iStringLength = 0; + int32_t iSizeNeeded = MultiByteToWideChar(CP_UTF8, 0, pText, -1, NULL, 0); + wchar_t *pWideText = (wchar_t *)DirtyMemAlloc((int32_t)(sizeof(wchar_t) * iSizeNeeded), VOIP_MEMID, pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData); + + iStringLength = MultiByteToWideChar(CP_UTF8, 0, pText, -1, pWideText, iSizeNeeded); + pVoipNarrate->Metrics.uEventCount++; + pVoipNarrate->Metrics.uCharCountSent += iStringLength; + + // change the gender if needed + if ((pVoipNarrate->eGender != eGender) || (pVoipNarrate->bChangeLang == TRUE)) + { + VoipNarrateGenderE eTempGender = eGender; + + if (eGender == VOIPNARRATE_GENDER_NEUTRAL) + { + eTempGender = pVoipNarrate->eGender; + } + + if (_VoipNarrateGenderChange(pVoipNarrate, eTempGender) == 0) + { + pVoipNarrate->eGender = eGender; + pVoipNarrate->bChangeLang = FALSE; + } + } + + // submit text for sythesis + hResult = pVoipNarrate->pVoice->Speak(pWideText, SPF_ASYNC, 0); + pVoipNarrate->uTtsStartTime = NetTick(); + + DirtyMemFree(pWideText, VOIP_MEMID, pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData); + + if (hResult != S_OK) + { + pVoipNarrate->Metrics.uErrorCount++; + return; + } + + pVoipNarrate->bFirstSynthOfPhrase = TRUE; + pVoipNarrate->eVoiceState = VOIPNARRATE_VOICE_BUSY; +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateProcessResult + + \Description + Receive streamed voice data and submit it to callback + + \Input *pVoipNarrate - pointer to module state + + \Version 12/04/2018 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipNarrateProcessResult(VoipNarrateRefT *pVoipNarrate) +{ + ULONG iReadLen = 0; + SPVOICESTATUS status; + uint8_t buffer[VOIPNARRATE_READLEN]; + VoipNarrateVoiceStream *pStream = (VoipNarrateVoiceStream *)(pVoipNarrate->pBaseStream); + + // get voice synth status + pVoipNarrate->pVoice->GetStatus(&status, NULL); + ds_memclr(&buffer, VOIPNARRATE_READLEN); + + if (status.dwRunningState == SPRS_DONE) + { + if (pStream->GetDataSize() != 0) + { + // time how long it took to get the tts results + if (pVoipNarrate->bFirstSynthOfPhrase) + { + pVoipNarrate->Metrics.uDelay += NetTickDiff(NetTick(), pVoipNarrate->uTtsStartTime); + } + + pStream->Read(&buffer[0], VOIPNARRATE_READLEN, &iReadLen); + pVoipNarrate->Metrics.uDurationMsRecv += ((iReadLen * 1000) / VOIPNARRATE_SAMPLERATE); + + if (iReadLen != 0) + { + // if this is the beginning of a new phrase signal stream start + if (pVoipNarrate->bFirstSynthOfPhrase == TRUE) + { + pVoipNarrate->bFirstSynthOfPhrase = FALSE; + pVoipNarrate->pVoiceDataCb(pVoipNarrate, 0, (const int16_t *)buffer, VOIPNARRATE_STREAM_START, pVoipNarrate->pUserData); + } + pVoipNarrate->pVoiceDataCb(pVoipNarrate, 0, (const int16_t *)buffer, iReadLen, pVoipNarrate->pUserData); + } + } + else + { + if (pVoipNarrate->bFirstSynthOfPhrase) + { + pVoipNarrate->Metrics.uEmptyResultCount++; + } + + // signal stream end + pVoipNarrate->pVoiceDataCb(pVoipNarrate, 0, (const int16_t *)buffer, VOIPNARRATE_STREAM_END, pVoipNarrate->pUserData); + pVoipNarrate->eVoiceState = VOIPNARRATE_VOICE_READY; + } + } +} + +/*** Public Functions *************************************************************/ +/*F********************************************************************************/ +/*! + \Function VoipNarrateCreate + + \Description + Create the narration module + + \Input *pVoiceDataCb - callback used to provide voice data + \Input *pUserData - callback user data + + \Output + VoipNarrateRefT * - new module state, or NULL + + \Version 12/04/2018 (tcho) +*/ +/********************************************************************************F*/ +VoipNarrateRefT *VoipNarrateCreate(VoipNarrateVoiceDataCbT *pVoiceDataCb, void *pUserData) +{ + VoipNarrateRefT *pVoipNarrate; + int32_t iMemGroup; + void *pMemGroupUserData; + + // query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // validate callback + if (pVoiceDataCb == NULL) + { + NetPrintf(("voipnarratepc: could not create module with null callback\n")); + return(NULL); + } + + // allocate and init module state + if ((pVoipNarrate = (VoipNarrateRefT *)DirtyMemAlloc(sizeof(*pVoipNarrate), VOIPNARRATE_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipnarratepc: could not allocate module state\n")); + return(NULL); + } + + ds_memclr(pVoipNarrate, sizeof(*pVoipNarrate)); + pVoipNarrate->iMemGroup = iMemGroup; + pVoipNarrate->pMemGroupUserData = pMemGroupUserData; + pVoipNarrate->iLangCode = 409; // 409 is enUS + pVoipNarrate->eGender = VOIPNARRATE_GENDER_MALE; + pVoipNarrate->pVoiceDataCb = pVoiceDataCb; + pVoipNarrate->pUserData = pUserData; + NetCritInit(&pVoipNarrate->VoiceCrit, "voipnarratepc-tts"); + + return(pVoipNarrate); +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateConfig + + \Description + Configure the VoipNarrate module + + \Input eProvider - VOIPNARRATE_PROVIDER_* + \Input *pUrl - pointer to url to use for tts requests + \Input *pKey - pointer to authentication key to use for tts requests + + \Version 12/04/2018 (tcho) +*/ +/********************************************************************************F*/ +void VoipNarrateConfig(VoipNarrateProviderE eProvider, const char *pUrl, const char *pKey) +{ + NetPrintf(("voipnarratepc: VoipNarrateConfig() is not implemented on PC\n")); +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateDestroy + + \Description + Destroy the VoipNarrate module + + \Input *pVoipNarrate - pointer to module state + + \Version 12/04/2018 (tcho) +*/ +/********************************************************************************F*/ +void VoipNarrateDestroy(VoipNarrateRefT *pVoipNarrate) +{ + VoipNarrateRequestT *pRequest = pVoipNarrate->pRequest; + + // free queued up request + if (pRequest != NULL) + { + for (pRequest = pVoipNarrate->pRequest; pRequest->pNext != NULL; pRequest = pRequest->pNext) + { + DirtyMemFree(pRequest, VOIPNARRATE_MEMID, pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData); + } + } + + // dispose of the crit + NetCritKill(&pVoipNarrate->VoiceCrit); + + // dispose of module memory + DirtyMemFree(pVoipNarrate, VOIPNARRATE_MEMID, pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateInput + + \Description + Input text to be convert to speech + + \Input *pVoipNarrate - pointer to module state + \Input iUserIndex - local user index of user who is requesting speech synthesis + \Input eGender - preferred gender for voice narration + \Input *pText - text to be converted + + \Output + int32_t - zero=success, otherwise=failure + + \Version 12/04/2018 (tcho) +*/ +/********************************************************************************F*/ +int32_t VoipNarrateInput(VoipNarrateRefT *pVoipNarrate, int32_t iUserIndex, VoipNarrateGenderE eGender, const char *pText) +{ + return(_VoipNarrateRequestAdd(pVoipNarrate, iUserIndex, eGender, pText)); +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateUpdate + + \Description + Update the narration module + + \Input *pVoipNarrate - pointer to module state + + \Version 12/04/2018 (tcho) +*/ +/********************************************************************************F*/ +void VoipNarrateUpdate(VoipNarrateRefT *pVoipNarrate) +{ + // initialize SAPI + if (pVoipNarrate->eVoiceState == VOIPNARRATE_VOICE_UNINITIALIZED) + { + _VoipNarrateInitialize(pVoipNarrate); + } + + // see if we need to start a queued narration request + if ((pVoipNarrate->pRequest != NULL) && (pVoipNarrate->eVoiceState == VOIPNARRATE_VOICE_READY)) + { + VoipNarrateRequestT Request; + _VoipNarrateRequestGet(pVoipNarrate, &Request); + _VoipNarrateStart(pVoipNarrate, Request.eGender, Request.strText); + } + + if (pVoipNarrate->eVoiceState == VOIPNARRATE_VOICE_BUSY) + { + _VoipNarrateProcessResult(pVoipNarrate); + } + + if (pVoipNarrate->eVoiceState == VOIPNARRATE_VOICE_UNINITIALIZING) + { + _VoipNarrateUninitialize(pVoipNarrate); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateStatus + + \Description + Get module status. + + \Input *pVoipNarrate - pointer to module state + \Input iStatus - status selector + \Input iValue - selector specific + \Input *pBuffer - selector specific + \Input iBufSize - selector specific + + \Output + int32_t - selector specific + + \Notes + Other status codes are passed down to the stream transport handler. + + \verbatim + 'ttsm' - get the VoipTextToSpeechMetricsT via pBuffer + \endverbatim + + \Version 12/05/2018 (tcho) +*/ +/********************************************************************************F*/ +int32_t VoipNarrateStatus(VoipNarrateRefT *pVoipNarrate, int32_t iStatus, int32_t iValue, void *pBuffer, int32_t iBufSize) +{ + if (iStatus == 'ttsm') + { + if ((pBuffer != NULL) && (iBufSize >= (int32_t)sizeof(VoipTextToSpeechMetricsT))) + { + ds_memcpy_s(pBuffer, iBufSize, &pVoipNarrate->Metrics, sizeof(VoipTextToSpeechMetricsT)); + return(0); + } + return(-1); + } + + return(-1); +} + +/*F********************************************************************************/ +/*! +\Function VoipNarrateControl + + \Description + Set control options + + \Input *pVoipNarrate - pointer to module state + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + iStatus can be one of the following: + + \verbatim + 'ctsm' - clear text to speech metrics in VoipTextToSpeechMetricsT + 'lang' - set language code + 'uvoc' - uninitialize SAPI + \endverbatim + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipNarrateControl(VoipNarrateRefT *pVoipNarrate, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iControl == 'ctsm') + { + ds_memclr(&(pVoipNarrate->Metrics), sizeof(pVoipNarrate->Metrics)); + return(0); + } + if (iControl == 'lang') + { + NetCritEnter(&pVoipNarrate->VoiceCrit); + if (pVoipNarrate->iLangCode != iValue) + { + pVoipNarrate->bChangeLang = TRUE; + pVoipNarrate->iLangCode = iValue; + } + NetCritLeave(&pVoipNarrate->VoiceCrit); + return(0); + } + if (iControl == 'uvoc') + { + NetCritEnter(&pVoipNarrate->VoiceCrit); + pVoipNarrate->eVoiceState = VOIPNARRATE_VOICE_UNINITIALIZING; + NetCritLeave(&pVoipNarrate->VoiceCrit); + return(0); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function VoipNarrateVoiceStream + + \Description + Constructor for VoipNarrateVoiceStream + + \Input iMemGroup - iMemGroup + \Input pMemGroupUserData - pMemGroupUserData + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +VoipNarrateVoiceStream::VoipNarrateVoiceStream(int32_t iMemGroup, void *pMemGroupUserData) +{ + m_pBuffer = NULL; + m_uReadPos = 0; + m_uWritePos = 0; + m_uBufferSize = 0; + m_uRefCount = 0; + m_iMemGroup = iMemGroup; + m_pMemGroupUserData = pMemGroupUserData; + NetCritInit(&m_BufferCrit, "voipnarrate-tts-stream"); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \fn ~VoipNarrateVoiceStream + + \Description + Destructor for VoipNarrateVoiceStream + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +VoipNarrateVoiceStream::~VoipNarrateVoiceStream() +{ + if (m_pBuffer != NULL) + { + DirtyMemFree(m_pBuffer, VOIP_MEMID, m_iMemGroup, m_pMemGroupUserData); + } + + NetCritKill(&m_BufferCrit); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function GetDataSize + + \Description + Return current size of the stream + + \Output + uint32_t - size of the stream + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +uint32_t VoipNarrateVoiceStream::GetDataSize() +{ + return m_uWritePos; +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function QueryInterface + + \Description + Returns the interface of the VoipNarrateVoiceStream + + \Input iid - interface id + \Input **ppvObject - pointer to store the pointer to the interface + + \Output + HRESULT - S_OK if successful, E_NOINTERFACE if not successful + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::QueryInterface(REFIID iid, void ** ppvObject) +{ + if (iid == __uuidof(IUnknown) + || iid == __uuidof(IStream) + || iid == __uuidof(ISequentialStream)) + { + *ppvObject = (IStream *)(this); + AddRef(); + return S_OK; + } + else + return E_NOINTERFACE; +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function AddRef + + \Description + Intcrement the ref count for VoipNarrateVoiceStream + + \Output + ULONG - returns the current ref count + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +ULONG STDMETHODCALLTYPE VoipNarrateVoiceStream::AddRef(void) +{ + return (ULONG)InterlockedIncrement(&m_uRefCount); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function Release + + \Description + Free the VoipNarrateVoiceStream + + \Output + ULONG - returns the current ref count + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +ULONG STDMETHODCALLTYPE VoipNarrateVoiceStream::Release(void) +{ + ULONG res = (ULONG)InterlockedDecrement(&m_uRefCount); + if (res == 0) + { + this->~VoipNarrateVoiceStream(); + DirtyMemFree(this, VOIP_MEMID, m_iMemGroup, m_pMemGroupUserData); + } + return res; +} + + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function Read + + \Description + Read from the stream + + \Input *pData - pointer to buffer where data will be read into + \Input uDataLen - the max size of pData + \Input *pRead - output the actual amount of data that is read + + \Output + HRESULT - S_OK if sucessful, S_FALSE if not + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::Read(void *pData, ULONG uDataLen, ULONG *pRead) +{ + if ((pData == NULL) || (pRead == NULL)) + { + return(STG_E_INVALIDPOINTER); + } + + if (NetCritTry(&m_BufferCrit) == 0) + { + return (S_OK); + } + + // remaining data is bigger than or equal to bytes requested to be read + if ((m_uWritePos - m_uReadPos) >= uDataLen) + { + *pRead = uDataLen; + } + // remaining data is smaller then bytes requested to be read + else + { + *pRead = (m_uWritePos - m_uReadPos); + } + + // check to see if we have a buffer and the read len is not zero + if ((m_pBuffer == NULL) || (*pRead == 0)) + { + *pRead = 0; + NetCritLeave(&m_BufferCrit); + return(S_FALSE); + } + + // copy data + ds_memcpy(pData, m_pBuffer, *pRead); + m_uReadPos += *pRead; + + //compact the buffer + memmove(m_pBuffer, m_pBuffer + m_uReadPos, m_uWritePos - m_uReadPos); + m_uWritePos -= m_uReadPos; + m_uReadPos = 0; + + NetCritLeave(&m_BufferCrit); + return(S_OK); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function Write + + \Description + Write to the stream + + \Input *pData - data to be written to stream + \Input uDataLen - the size of pData + \Input *pWritten - output the actual amount of data that is written + + \Output + HRESULT - S_OK if sucessful, S_FALSE if not + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::Write(const void *pData, ULONG uDataLen, ULONG *pWritten) +{ + if (pData == NULL) + { + return(STG_E_INVALIDPOINTER); + } + + NetCritEnter(&m_BufferCrit); + + // check to see if we have enough room to write the data + if ((m_uBufferSize - m_uWritePos) < uDataLen) + { + int32_t iNewBufferSize; + uint8_t *pNewBuffer; + + // set new buffer size + // we are doubling the buffer size everytime + m_uBufferSize ? (iNewBufferSize = 2 * m_uBufferSize) : (iNewBufferSize = VOIPNARRATE_BUFFER_LEN); + + // need to expand buffer + if ((pNewBuffer = (uint8_t *)DirtyMemAlloc(iNewBufferSize, VOIP_MEMID, m_iMemGroup, m_pMemGroupUserData)) == NULL) + { + NetPrintf(("voipheadsetpc: cannot allocate memory for tts stream buffer!\n")); + NetCritLeave(&m_BufferCrit); + return(STG_E_MEDIUMFULL); + } + + ds_memclr(pNewBuffer, iNewBufferSize); + + // cleanup existing pBuffer and copy contents + if (m_pBuffer != NULL) + { + ds_memcpy(pNewBuffer, m_pBuffer, m_uBufferSize); + DirtyMemFree(m_pBuffer, VOIP_MEMID, m_iMemGroup, m_pMemGroupUserData); + } + + m_pBuffer = pNewBuffer; + m_uBufferSize = iNewBufferSize; + } + + // now write the new data into the buffer + ds_memcpy(m_pBuffer + m_uWritePos, pData, uDataLen); + m_uWritePos += uDataLen; + + if (pWritten != NULL) + { + *pWritten = uDataLen; + } + + NetCritLeave(&m_BufferCrit); + return(S_OK); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function CopyTo + + \Description + Not Implementated + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) +{ + return(E_NOTIMPL); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function Stat + + \Description + Not Implementated + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::Stat(STATSTG *pstatstg, DWORD grfStatFlag) +{ + return(E_NOTIMPL); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function UnlockRegion + + \Description + Not Implementated + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) +{ + return(E_NOTIMPL); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function LockRegion + + \Description + Not Implementated + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) +{ + return(E_NOTIMPL); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function Revert + + \Description + Not Implementated + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::Revert(void) +{ + return(E_NOTIMPL); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function Commit + + \Description + Not Implementated + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::Commit(DWORD grfCommitFlags) +{ + return(E_NOTIMPL); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function SetSize + + \Description + Not Implementated + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::SetSize(ULARGE_INTEGER libNewSize) +{ + return(E_NOTIMPL); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function Clone + + \Description + Not Implementated + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::Clone(IStream **ppStream) +{ + return(E_NOTIMPL); +} + +/*F********************************************************************************/ +/*! + \relates VoipNarrateVoiceStream + \Function Seek + + \Description + Not Implementated + + \Version 05/17/2018 (tcho) +*/ +/********************************************************************************F*/ +HRESULT STDMETHODCALLTYPE VoipNarrateVoiceStream::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) +{ + // need to return S_OK becuase the voice synthesizer expect this to be implementated (but not necessary in our case) + return(S_OK); +} diff --git a/src/thirdparty/dirtysdk/source/voip/pc/voippc.c b/src/thirdparty/dirtysdk/source/voip/pc/voippc.c new file mode 100644 index 00000000..803b3911 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/pc/voippc.c @@ -0,0 +1,667 @@ +/*H********************************************************************************/ +/*! + \File voippc.c + + \Description + Voip library interface. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 07/27/2004 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#pragma warning(push,0) +#include +#pragma warning(pop) + +// dirtysock includes +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtythread.h" +#include "DirtySDK/dirtysock/netconn.h" + +// voip includes +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" +#include "DirtySDK/voip/voipcodec.h" +#include "DirtySDK/voip/voip.h" + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +//! transmission interval in milliseconds +#define VOIP_THREAD_SLEEP_DURATION (20) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! VoIP module state +struct VoipRefT +{ + VoipCommonRefT Common; //!< cross-platform voip data (must come first!) + uint8_t aIsReadyToParticipate[VOIP_MAXLOCALUSERS_EXTENDED]; //!< TRUE if user wanted to participate but was unable (on PC there can only be 1 participant) + DWORD dwThreadId; //!< thread ID + volatile int32_t iThreadState; //!< control variable used during thread creation and exit (0=waiting for start, 1=running, 2=shutdown) + + uint32_t bSpeakerOutput; //!< whether we are outputting through speakers or not +}; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Private Variables + +//! affinity of the voip thread, if unset uses the global DirtySDK thread affinity +static int32_t _Voip_iThreadAffinity = -1; + +// Public Variables + +/*** Private Functions ************************************************************/ + +/*F*************************************************************************************************/ +/*! + \Function _VoipStatusCb + + \Description + Callback to handle change of headset status. + + \Input iLocalUserIndex - headset that has changed (currently ignored) + \Input bStatus - if TRUE the headset was inserted, else if FALSE it was removed + \Input eUpdate - functionality of the headset (input, output or both) + \Input *pUserData - pointer to callback user data + + \Version 1.0 04/01/04 (JLB) First Version +*/ +/*************************************************************************************************F*/ +static void _VoipStatusCb(int32_t iLocalUserIndex, uint32_t bStatus, VoipHeadsetStatusUpdateE eUpdate, void *pUserData) +{ + VoipRefT *pRef = (VoipRefT *)pUserData; + + /* we don't support each user having their own device, we will treat all users + as they have the same device status, since it is likely shared */ + for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS; iLocalUserIndex++) + { + if (bStatus > 0) + { + // Set the appropriate flags + if (eUpdate == VOIP_HEADSET_STATUS_INPUT || eUpdate == VOIP_HEADSET_STATUS_INOUT) + { + pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] |= VOIP_LOCAL_USER_INPUTDEVICEOK; + } + + if (eUpdate == VOIP_HEADSET_STATUS_OUTPUT || eUpdate == VOIP_HEADSET_STATUS_INOUT) + { + pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] |= VOIP_LOCAL_USER_OUTPUTDEVICEOK; + } + } + else + { + // Reset the appropriate flags + if (eUpdate == VOIP_HEADSET_STATUS_INPUT || eUpdate == VOIP_HEADSET_STATUS_INOUT) + { + pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] &= ~VOIP_LOCAL_USER_INPUTDEVICEOK; + } + + if (eUpdate == VOIP_HEADSET_STATUS_OUTPUT || eUpdate == VOIP_HEADSET_STATUS_INOUT) + { + pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] &= ~VOIP_LOCAL_USER_OUTPUTDEVICEOK; + } + } + + if ((pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] & VOIP_LOCAL_USER_INPUTDEVICEOK) && + (pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] & VOIP_LOCAL_USER_OUTPUTDEVICEOK)) + { + NetPrintf(("voippc: headset active (audio in: on, audio out: on\n)")); + pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] |= VOIP_LOCAL_USER_HEADSETOK; + } + else + { + NetPrintf(("voippc: headset inactive (audio in: %s, audio out: %s\n)", + ((pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] & VOIP_LOCAL_USER_INPUTDEVICEOK) ? "on" : "off"), + ((pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] & VOIP_LOCAL_USER_OUTPUTDEVICEOK) ? "on" : "off"))); + pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] &= ~VOIP_LOCAL_USER_HEADSETOK; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipActivateLocalTalker + + \Description + Register the participating talker on the given port. + + \Input *pVoipCommon - voip common state + \Input iLocalUserIndex - local user index associated with the user + \Input *pVoipUser - VoipUserT to register + \Input bActivate - TRUE to activate, FALSE to deactivate + + \Output + int32_t - negative if failed, zero if successful + + \Version 04/25/2013 (tcho) +*/ +/********************************************************************************F*/ +static int32_t _VoipActivateLocalTalker(VoipCommonRefT *pVoipCommon, int32_t iLocalUserIndex, VoipUserT *pVoipUser, uint8_t bActivate) +{ + int32_t iResult = 0; + + if (pVoipUser != NULL) + { + VoipHeadsetControl(pVoipCommon->pHeadset, 'aloc', iLocalUserIndex, bActivate, pVoipUser); + + // mark local user as participating, or not + pVoipCommon->Connectionlist.aIsParticipating[iLocalUserIndex] = bActivate; + + // reapply playback muting config based on the channel configuration always (a user who was blocking may be gone now) + pVoipCommon->bApplyChannelConfig = TRUE; + + // announce jip add or remove event on connection that are already active + VoipConnectionReliableBroadcastUser(&pVoipCommon->Connectionlist, iLocalUserIndex, bActivate); + } + else + { + NetPrintf(("voippc: cannot %s a null user %s participating state (local user index %d)\n", + (bActivate ? "promote" : "demote"), (bActivate ? "to" : "from"), iLocalUserIndex)); + + iResult = -1; + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipThread + + \Description + Main Voip thread that updates all active connection and calls the + XHV Engine's DoWork() function. + + \Input *pParam - pointer to voip module state + + \Version 1.0 03/19/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +static void _VoipThread(void *pParam) +{ + VoipRefT *pVoip = (VoipRefT *)pParam; + uint32_t uProcessCt = 0; + uint32_t uTime0, uTime1; + int32_t iSleep; + + // wait until we're started up + while(pVoip->iThreadState == 0) + { + NetConnSleep(VOIP_THREAD_SLEEP_DURATION); + } + + // execute until we're killed + while(pVoip->iThreadState == 1) + { + uTime0 = NetTick(); + + NetCritEnter(&pVoip->Common.ThreadCrit); + NetCritEnter(&pVoip->Common.Connectionlist.NetCrit); + + if (pVoip->Common.bApplyChannelConfig) + { + pVoip->Common.bApplyChannelConfig = FALSE; + VoipCommonApplyChannelConfig((VoipCommonRefT *)pVoip); + } + + // update status of remote users + VoipCommonUpdateRemoteStatus(&pVoip->Common); + NetCritLeave(&pVoip->Common.Connectionlist.NetCrit); + + // update connections + VoipConnectionUpdate(&pVoip->Common.Connectionlist); + + // set headset mute status based on connectionlist send mask + VoipHeadsetControl(pVoip->Common.pHeadset, 'mute', 0, pVoip->Common.Connectionlist.uSendMask == 0, NULL); + + // update headset + VoipHeadsetProcess(pVoip->Common.pHeadset, uProcessCt++); + + // update ttos status + pVoip->Common.uTtsStatus = VoipHeadsetStatus(pVoip->Common.pHeadset, 'ttos', 0, NULL, 0); + + NetCritLeave(&pVoip->Common.ThreadCrit); + + uTime1 = NetTick(); + + iSleep = VOIP_THREAD_SLEEP_DURATION - NetTickDiff(uTime1, uTime0); + + // wait for next tick + if (iSleep >= 0) + { + NetConnSleep(iSleep); + } + else + { + NetPrintf(("voippc: [WARNING] Overall voip thread update took %d ms\n", NetTickDiff(uTime1, uTime0))); + } + } + + // cleanup voice and give voipheadset process time to destroy SAPI related components + VoipHeadsetControl(pVoip->Common.pHeadset, 'uvoc', 0, 0, NULL); + VoipHeadsetProcess(pVoip->Common.pHeadset, uProcessCt++); + + // indicate we're exiting + pVoip->iThreadState = 0; +} + +/*F********************************************************************************/ +/*! + \Function _VoipGetActiveParticipantIndex + + \Description + Gets the user index of the current active user (there can only be one) + + \Input *pVoip - module state + + \Output + int32_t - User index 0-4 or VOIP_INVALID_LOCAL_USER_INDEX if none. + + \Version 03/14/2019 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _VoipGetActiveParticipantIndex(VoipRefT *pVoip) +{ + uint32_t uIndex; + for (uIndex = 0; uIndex < VOIP_MAXLOCALUSERS; uIndex++) + { + if (pVoip->Common.Connectionlist.aIsParticipating[uIndex] == TRUE) + { + return(uIndex); + } + } + return(VOIP_INVALID_LOCAL_USER_INDEX); +} + +/*F********************************************************************************/ +/*! + \Function _VoipGetFirstReadyParticipantIndex + + \Description + Gets the user index of the first user who would like to be the active user + + \Input *pVoip - module state + + \Output + int32_t - User index 0-4 or VOIP_INVALID_LOCAL_USER_INDEX if none. + + \Version 03/14/2019 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _VoipGetFirstReadyParticipantIndex(VoipRefT *pVoip) +{ + uint32_t uIndex; + for (uIndex = 0; uIndex < VOIP_MAXLOCALUSERS; uIndex++) + { + if (pVoip->aIsReadyToParticipate[uIndex] == TRUE) + { + return(uIndex); + } + } + return(VOIP_INVALID_LOCAL_USER_INDEX); +} + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipStartup + + \Description + Prepare VoIP for use. + + \Input iMaxPeers - maximum number of peers supported (up to VOIP_MAXCONNECT) + \Input iData - platform-specific - unused for PC + + \Output + VoipRefT - state reference that is passed to all other functions + + \Version 1.0 03/02/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +VoipRefT *VoipStartup(int32_t iMaxPeers, int32_t iData) +{ + VoipRefT *pVoip; + VoipCommonRefT *pVoipCommon; + DirtyThreadConfigT ThreadConfig; + + // common startup + if ((pVoip = VoipCommonStartup(iMaxPeers, sizeof(*pVoip), _VoipStatusCb, iData)) == NULL) + { + return(NULL); + } + pVoipCommon = (VoipCommonRefT *)pVoip; + + // bPrivileged is always TRUE on PC + pVoipCommon->bPrivileged = TRUE; + + // configure thread parameters + ds_memclr(&ThreadConfig, sizeof(ThreadConfig)); + ThreadConfig.pName = "VoIP"; + ThreadConfig.iAffinity = (_Voip_iThreadAffinity == -1) ? NetConnStatus('affn', 0, NULL, 0) : _Voip_iThreadAffinity; + ThreadConfig.iPriority = THREAD_PRIORITY_HIGHEST; + + // create worker thread + if (DirtyThreadCreate(_VoipThread, pVoip, &ThreadConfig) == 0) + { + // tell thread to start executing + pVoip->iThreadState = 1; + } + else + { + pVoip->iThreadState = 0; + VoipShutdown(pVoip, 0); + return(NULL); + } + + // return to caller + return(pVoip); +} + +/*F********************************************************************************/ +/*! + \Function VoipShutdown + + \Description + Release all VoIP resources. + + \Input *pVoip - module state from VoipStartup + \Input uShutdownFlags - NET_SHUTDOWN_* flags + + \Version 03/02/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +void VoipShutdown(VoipRefT *pVoip, uint32_t uShutdownFlags) +{ + // make sure we're really started up + if (VoipGetRef() == NULL) + { + NetPrintf(("voippc: module shutdown called when not in a started state\n")); + return; + } + + // tell thread to shut down and wait for shutdown confirmation (if running) + if (pVoip->iThreadState == 1) + { + for (pVoip->iThreadState = 2; pVoip->iThreadState != 0; ) + { + NetPrintf(("voippc: waiting for shutdown\n")); + NetConnSleep(VOIP_THREAD_SLEEP_DURATION); + } + } + + // unregister local talker + + // common shutdown (must come last as this frees the memory) + VoipCommonShutdown(&pVoip->Common); +} + +/*F********************************************************************************/ +/*! + \Function VoipSetLocalUser + + \Description + Register/unregister specified local user with the voip sub-system. + + \Input *pVoip - module state from VoipStartup + \Input iLocalUserIndex - local user index + \Input bRegister - true to register user, false to unregister user + + \Version 04/15/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipSetLocalUser(VoipRefT *pVoip, int32_t iLocalUserIndex, uint32_t bRegister) +{ + if ((iLocalUserIndex < VOIP_MAXLOCALUSERS) && (iLocalUserIndex >= 0)) + { + NetCritEnter(&pVoip->Common.ThreadCrit); + + if (bRegister) + { + if (VOIP_NullUser(&pVoip->Common.Connectionlist.LocalUsers[iLocalUserIndex])) + { + VoipUserT voipUser; + ds_memclr(&voipUser, sizeof(voipUser)); + + voipUser.ePlatform = VOIP_PLATFORM_PC; + NetConnStatus('acid', iLocalUserIndex, &voipUser.AccountInfo.iAccountId, sizeof(voipUser.AccountInfo.iAccountId)); + NetConnStatus('peid', iLocalUserIndex, &voipUser.AccountInfo.iPersonaId, sizeof(voipUser.AccountInfo.iPersonaId)); + + // register the local talker + NetPrintf(("voippc: registering a local user %lld at index %d\n", voipUser.AccountInfo.iPersonaId, iLocalUserIndex)); + VOIP_CopyUser(&pVoip->Common.Connectionlist.LocalUsers[iLocalUserIndex], &voipUser); + } + else + { + NetPrintf(("voippc: warning -- ignoring attempt to register user index %d because user %lld is already registered at that index\n", + iLocalUserIndex, pVoip->Common.Connectionlist.LocalUsers[iLocalUserIndex].AccountInfo.iPersonaId)); + } + } + else + { + if (!VOIP_NullUser(&pVoip->Common.Connectionlist.LocalUsers[iLocalUserIndex])) + { + // if a participating user demote him first + if (pVoip->Common.Connectionlist.aIsParticipating[iLocalUserIndex] == TRUE) + { + VoipCommonActivateLocalUser(&pVoip->Common, iLocalUserIndex, FALSE); + } + + NetPrintf(("voippc: unregistering local user %lld at index %d\n", pVoip->Common.Connectionlist.LocalUsers[iLocalUserIndex].AccountInfo.iPersonaId, iLocalUserIndex)); + ds_memclr(&pVoip->Common.Connectionlist.LocalUsers[iLocalUserIndex], sizeof(pVoip->Common.Connectionlist.LocalUsers[iLocalUserIndex])); + } + else + { + NetPrintf(("voippc: warning -- ignoring attempt to unregister local user because it is not yet registered\n")); + } + } + + NetCritLeave(&pVoip->Common.ThreadCrit); + } + else + { + NetPrintf(("voippc: VoipSetLocalUser() invoked with an invalid user index %d\n", iLocalUserIndex)); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonActivateLocalUser + + \Description + Promote/demote specified local user to/from "participating" state + + \Input *pVoipCommon - voip common state + \Input iLocalUserIndex - local user index + \Input bActivate - TRUE to activate, FALSE to deactivate + + \Version 04/25/2013 (tcho) +*/ +/********************************************************************************F*/ +void VoipCommonActivateLocalUser(VoipCommonRefT *pVoipCommon, int32_t iLocalUserIndex, uint8_t bActivate) +{ + VoipRefT* pVoip = VoipGetRef(); + if ((iLocalUserIndex < NETCONN_MAXLOCALUSERS) && (iLocalUserIndex >= 0)) + { + NetCritEnter(&pVoipCommon->ThreadCrit); + + if (!VOIP_NullUser(&pVoipCommon->Connectionlist.LocalUsers[iLocalUserIndex])) + { + if (bActivate == TRUE) + { + if (_VoipGetActiveParticipantIndex(pVoip) == VOIP_INVALID_LOCAL_USER_INDEX) + { + NetPrintf(("voippc: promoting local user %lld to participating state.\n", &pVoipCommon->Connectionlist.LocalUsers[iLocalUserIndex].AccountInfo.iPersonaId)); + _VoipActivateLocalTalker(pVoipCommon, iLocalUserIndex, &pVoipCommon->Connectionlist.LocalUsers[iLocalUserIndex], TRUE); + } + else + { + NetPrintf(("voippc: a user is already active, %lld to ready state.\n", &pVoipCommon->Connectionlist.LocalUsers[iLocalUserIndex].AccountInfo.iPersonaId)); + } + pVoip->aIsReadyToParticipate[iLocalUserIndex] = TRUE; + } + else + { + NetPrintf(("voippc: demoting local user %lld from participating/ready state\n", &pVoipCommon->Connectionlist.LocalUsers[iLocalUserIndex].AccountInfo.iPersonaId)); + + // were we the active user, if so deactivate + if (_VoipGetActiveParticipantIndex(pVoip) == iLocalUserIndex) + { + _VoipActivateLocalTalker(pVoipCommon, iLocalUserIndex, &pVoipCommon->Connectionlist.LocalUsers[iLocalUserIndex], FALSE); + } + pVoip->aIsReadyToParticipate[iLocalUserIndex] = FALSE; + + // do we still have an active user? + if (_VoipGetActiveParticipantIndex(pVoip) == VOIP_INVALID_LOCAL_USER_INDEX) + { + // no, but do we have someone who still wants to be the active user? if so make them the active user + int32_t iNewParticpantIndex = _VoipGetFirstReadyParticipantIndex(pVoip); + if (iNewParticpantIndex != VOIP_INVALID_LOCAL_USER_INDEX) + { + NetPrintf(("voippc: promoting local user %lld to participating state\n", &pVoipCommon->Connectionlist.LocalUsers[iNewParticpantIndex].AccountInfo.iPersonaId)); + _VoipActivateLocalTalker(pVoipCommon, iNewParticpantIndex, &pVoipCommon->Connectionlist.LocalUsers[iNewParticpantIndex], TRUE); + } + } + } + } + else + { + NetPrintf(("voippc: VoipActivateLocalUser() failed because no local user registered at index %d\n", iLocalUserIndex)); + } + + NetCritLeave(&pVoipCommon->ThreadCrit); + } + else + { + NetPrintf(("voippc: VoipActivateLocalUser() invoked with an invalid user index %d\n", iLocalUserIndex)); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipStatus + + \Description + Return status. + + \Input *pVoip - module state from VoipStartup + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuf - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - status-specific data + + \Notes + iSelect can be one of the following: + + \verbatim + No PC-specific VoipStatus() selectors + \endverbatim + + Unhandled selectors are passed through to VoipCommonStatus(), and + further to VoipHeadsetStatus() if unhandled there. + + \Version 1.0 03/02/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipStatus(VoipRefT *pVoip, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + return(VoipCommonStatus(&pVoip->Common, iSelect, iValue, pBuf, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function VoipControl + + \Description + Set control options. + + \Input *pVoip - module state from VoipStartup + \Input iControl - status selector + \Input iValue - selector-specific + \Input *pValue - register value + + \Output + int32_t - selector-specific data + + \Notes + iControl can be one of the following: + + \verbatim + 'affn' - set the voip thread cpu affinity, please call this before voip startup is called with pVoip being null + '+pbk' - enable voice playback for a given remote user (pValue is the remote user VoipUserT). + '-pbk' - disable voice playback for a given remote user (pValue is the remote user VoipUserT). + 'cdec' - switch to new codec; passes through to VoipHeadsetControl() + 'creg' - register codec + 'plvl' - set the output power level + \endverbatim + + Unhandled selectors are passed through to VoipCommonControl(), and + further to VoipHeadsetControl() if unhandled there. + + \Version 1.0 04/08/2007 (cadam) +*/ +/********************************************************************************F*/ +int32_t VoipControl(VoipRefT *pVoip, int32_t iControl, int32_t iValue, void *pValue) +{ + if ('affn' == iControl) + { + _Voip_iThreadAffinity = iValue; + return(0); + } + if (iControl == 'cdec') + { + int32_t iResult; + // if switching codecs we require sole access to thread critical section + NetCritEnter(&pVoip->Common.ThreadCrit); + iResult = VoipHeadsetControl(pVoip->Common.pHeadset, iControl, iValue, 0, pValue); + NetCritLeave(&pVoip->Common.ThreadCrit); + return(iResult); + } + if (iControl == 'creg') + { + VoipCodecRegister(iValue, pValue); + return(0); + } + if (iControl == '+pbk') + { + return(VoipHeadsetControl(pVoip->Common.pHeadset, '+pbk', iValue, 0, (VoipUserT *)pValue)); + } + if (iControl == '-pbk') + { + return(VoipHeadsetControl(pVoip->Common.pHeadset, '-pbk', iValue, 0, (VoipUserT *)pValue)); + } + if (iControl == 'plvl') + { + int32_t iFractal; + float fValue = *(float *)pValue; + if ((fValue >= 20.0f) || (fValue < 0.0f)) + { + NetPrintf(("voippc: setting power level failed; value must be less than 20 and greater than or equal to 0.")); + return(-1); + } + iFractal = (int32_t)(fValue * (1 << VOIP_CODEC_OUTPUT_FRACTIONAL)); + VoipCodecControl(-1, iControl, iFractal, 0, NULL); + return(0); + } + + // let VoipCommonControl take a shot at it + return(VoipCommonControl(&pVoip->Common, iControl, iValue, pValue)); +} diff --git a/src/thirdparty/dirtysdk/source/voip/stub/voipcommonstub.c b/src/thirdparty/dirtysdk/source/voip/stub/voipcommonstub.c new file mode 100644 index 00000000..85ab8be8 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/stub/voipcommonstub.c @@ -0,0 +1,498 @@ +/*H********************************************************************************/ +/*! + \File voipcommonstub.c + + \Description + A stub for Voipcommon APIs + + \Copyright + Copyright (c) 2019 Electronic Arts Inc. + + \Version 02/21/2019 (tcho) First Version +*/ +/********************************************************************************H*/ +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/voip/voipdef.h" +#include "DirtySDK/voip/voip.h" + +#include "voippriv.h" +#include "voipcommon.h" + +/*F********************************************************************************/ +/*! + \Function VoipCommonGetRef + + \Description + Return current module reference. + + \Output + VoipRefT * - reference pointer, or NULL if the module is not active + + \Version 02/21/2019 (tcho) +*/ +/********************************************************************************F*/ +VoipRefT *VoipCommonGetRef(void) +{ + return(NULL); +} + +/*F********************************************************************************/ +/*! +\Function VoipCommonStartup + + \Description + Start up common functionality + + \Input iMaxPeers - maximum number of peers supported (up to VOIP_MAXCONNECT) + \Input iVoipRefSize - size of voip ref to allocate + \Input *pStatusCb - headset status callback + \Input iData - platform-specific + + \Output + VoipRefT * - voip ref if successful; else NULL + + \Version 12/02/2009 (jbrookes) +*/ +/********************************************************************************F*/ +VoipRefT *VoipCommonStartup(int32_t iMaxPeers, int32_t iVoipRefSize, VoipHeadsetStatusCbT *pStatusCb, int32_t iData) +{ + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonShutdown + + \Description + Shutdown common functionality + + \Input *pVoipCommon - common module state + + \Version 12/02/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCommonShutdown(VoipCommonRefT *pVoipCommon) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonUpdateRemoteStatus + + \Description + Process mute list, and set appropriate flags/priority for each remote user. + + \Input *pVoipCommon - pointer to module state + + \Version 08/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCommonUpdateRemoteStatus(VoipCommonRefT *pVoipCommon) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonStatus + + \Description + Return status. + + \Input *pVoipCommon - voip common state + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuf - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector-specific data + + \Version 12/02/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipCommonStatus(VoipCommonRefT *pVoipCommon, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonControl + + \Description + Set control options. + + \Input *pVoipCommon - voip common state + \Input iControl - control selector + \Input iValue - selector-specific input + \Input *pValue - selector-specific input + + \Output + int32_t - selector-specific output + + \Version 03/02/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipCommonControl(VoipCommonRefT *pVoipCommon, int32_t iControl, int32_t iValue, void *pValue) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonAddMask + + \Description + Add (OR) uAddMask into *pMask + + \Input *pMask - mask to add into + \Input uAddMask - mask to add (OR) + \Input *pMaskName - name of mask (for debug logging) + + \Version 12/03/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCommonAddMask(uint32_t *pMask, uint32_t uAddMask, const char *pMaskName) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonDelMask + + \Description + Del (&~) uDelMask from *pMask + + \Input *pMask - mask to del from + \Input uDelMask - mask to del (&~) + \Input *pMaskName - name of mask (for debug logging) + + \Version 12/03/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCommonDelMask(uint32_t *pMask, uint32_t uDelMask, const char *pMaskName) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonSetMask + + \Description + Set value of mask (with logging). + + \Input *pMask - mask to write to + \Input uNewMask - new mask value + \Input *pMaskName - name of mask (for debug logging) + + \Version 12/03/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCommonSetMask(uint32_t *pMask, uint32_t uNewMask, const char *pMaskName) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonSelectChannel + + \Description + Select the mode(send/recv) of a given channel. + + \Input *pVoipCommon - common module state + \Input iUserIndex - local user index + \Input iChannel - Channel ID (valid range: [0,63]) + \Input eMode - The mode, combination of VOIP_CHANSEND, VOIP_CHANRECV + + \Output + int32_t - number of channels remaining that this console could join + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +int32_t VoipCommonSelectChannel(VoipCommonRefT *pVoipCommon, int32_t iUserIndex, int32_t iChannel, VoipChanModeE eMode) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonApplyChannelConfig + + \Description + Setup user muting flags based on channel config + + \Input *pVoipCommon - voip module state + + \Version 12/02/2009 (jrainy) +*/ +/********************************************************************************F*/ +void VoipCommonApplyChannelConfig(VoipCommonRefT *pVoipCommon) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonResetChannels + + \Description + Resets the channels selection to defaults. Sends and receives to all + + \Input *pVoipCommon - voip common state + \Input iUserIndex - local user index + + \Version 12/07/2009 (jrainy) +*/ +/********************************************************************************F*/ +void VoipCommonResetChannels(VoipCommonRefT *pVoipCommon, int32_t iUserIndex) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonMicrophone + + \Description + Select which peers to send voice to + + \Input *pVoipCommon - voip common state + \Input uUserMicrValue - microphone bit values + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +void VoipCommonMicrophone(VoipCommonRefT *pVoipCommon, uint32_t uUserMicrValue) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonSpeaker + + \Description + Select which peers to accept voice from + + \Input *pVoipCommon - voip common state + \Input uUserSpkrValue - speaker bit values + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +void VoipCommonSpeaker(VoipCommonRefT *pVoipCommon, uint32_t uUserSpkrValue) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonConnectionSharingAddSession + + \Description + Add session id to share a specified voip connection + + \Input *pVoipCommon - voip common state + \Input iConnId - connection id + \Input uSessionId - session id we are adding + + \Output + int32_t - zero=success, negative=failure + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +int32_t VoipCommonConnectionSharingAddSession(VoipCommonRefT *pVoipCommon, int32_t iConnId, uint32_t uSessionId) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonConnectionSharingDelSession + + \Description + Remove session id from sharing a specified voip connection + + \Input *pVoipCommon - voip common state + \Input iConnId - connection id + \Input uSessionId - session id we are removing + + \Output + int32_t - zero=success, negative=failure + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +int32_t VoipCommonConnectionSharingDelSession(VoipCommonRefT* pVoipCommon, int32_t iConnId, uint32_t uSessionId) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonMapVoipServerId + + \Description + For server-based voip, maps a local conn id to a voipserver conn id + + \Input *pVoipCommon - voip common state + \Input iLocalConnId - local connection id + \Input iVoipServerConnId - voipserver connection id + + \Output + int32_t - zero=success, negative=failure + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +int32_t VoipCommonMapVoipServerId(VoipCommonRefT *pVoipCommon, int32_t iLocalConnId, int32_t iVoipServerConnId) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonSetLocalClientId + + \Description + Set local client id for connection + + \Input *pVoipCommon - voip common state + \Input iConnId - connection id + \Input uLocalClientId - local client id + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +void VoipCommonSetLocalClientId(VoipCommonRefT *pVoipCommon, int32_t iConnId, uint32_t uLocalClientId) +{ +} + +/*F*************************************************************************************************/ +/*! + \Function VoipCommonSetDisplayTranscribedTextCallback + + \Description + Set callback to be invoked when transcribed text (from local user or remote user) + is ready to be displayed locally. + + \Input *pVoipCommon - voip common state + \Input *pCallback - notification handler + \Input *pUserData - user data for handler + + \Version 05/03/2017 (mclouatre) +*/ +/*************************************************************************************************F*/ +void VoipCommonSetDisplayTranscribedTextCallback(VoipCommonRefT *pVoipCommon, VoipDisplayTranscribedTextCallbackT *pCallback, void *pUserData) +{ +} + +/*F*************************************************************************************************/ +/*! + \Function VoipCommonSetEventCallback + + \Description + Set voip event notification handler. + + \Input *pVoipCommon - voip common state + \Input *pCallback - event notification handler + \Input *pUserData - user data for handler + + \Version 02/10/2006 (jbrookes) +*/ +/*************************************************************************************************F*/ +void VoipCommonSetEventCallback(VoipCommonRefT *pVoipCommon, VoipCallbackT *pCallback, void *pUserData) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonConnect + + \Description + Connect to a peer. + + \Input *pVoipCommon - voip common state + \Input iConnID - [zero, iMaxPeers-1] for an explicit slot or VOIP_CONNID_NONE to auto-allocate + \Input uAddress - remote peer address + \Input uManglePort - port from demangler + \Input uGamePort - port to connect on + \Input uClientId - remote clientId to connect to (cannot be 0) + \Input uSessionId - session identifier (optional) + + \Output + int32_t - connection identifier (negative=error) + + \Version 1.0 03/02/2004 (jbrookes) first version + \Version 1.1 10/26/2009 (mclouatre) uClientId is no longer optional +*/ +/********************************************************************************F*/ +int32_t VoipCommonConnect(VoipCommonRefT *pVoipCommon, int32_t iConnID, uint32_t uAddress, uint32_t uManglePort, uint32_t uGamePort, uint32_t uClientId, uint32_t uSessionId) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonDisconnect + + \Description + Disconnect from peer. + + \Input *pVoipCommon - voip common state + \Input iConnID - which connection to disconnect (VOIP_CONNID_ALL for all) + \Input bSendDiscMsg - TRUE if a voip disc pkt needs to be sent, FALSE otherwise + + \Todo + Multiple connection support. + + \Version 15/01/2014 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipCommonDisconnect(VoipCommonRefT *pVoipCommon, int32_t iConnID, int32_t bSendDiscMsg) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonConnStatus + + \Description + Return information about peer connection. + + \Input *pVoipCommon - voip common state + \Input iConnID - which connection to get remote info for, or VOIP_CONNID_ALL + + \Output + int32_t - VOIP_CONN* flags, or VOIP_FLAG_INVALID if iConnID is invalid + + \Version 05/06/2014 (amakoukji) +*/ +/********************************************************************************F*/ +int32_t VoipCommonConnStatus(VoipCommonRefT *pVoipCommon, int32_t iConnID) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonRemoteUserStatus + + \Description + Return information about remote peer. + + \Input *pVoipCommon - voip common state + \Input iConnID - which connection to get remote info for, or VOIP_CONNID_ALL + \Input iRemoteUserIndex - user index at the connection iConnID + + \Output + int32_t - VOIP_REMOTE* flags, or VOIP_FLAG_INVALID if iConnID is invalid + + \Version 05/06/2014 (amakoukji) +*/ +/********************************************************************************F*/ +int32_t VoipCommonRemoteUserStatus(VoipCommonRefT *pVoipCommon, int32_t iConnID, int32_t iRemoteUserIndex) +{ + return(0); +} \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/source/voip/stub/voipheadsetstub.c b/src/thirdparty/dirtysdk/source/voip/stub/voipheadsetstub.c new file mode 100644 index 00000000..b0dfe8c5 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/stub/voipheadsetstub.c @@ -0,0 +1,328 @@ +/*H********************************************************************************/ +/*! + \File voipheadsetstub.c + + \Description + Stubbed VoIP headset manager. To be used on platform where voip connectivity + is to be exercised but not voice acquisition/playback. Typically needed + for stress testing voip connectivity with dummy voip traffic. + + \Copyright + Copyright Electronic Arts 2017. + + \Version 12/06/2017 (mclouatre) +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" +#include "voipconnection.h" +#include "voipheadset.h" + +/*** Defines **********************************************************************/ + +/*** Macros ************************************************************************/ + +/*** Type Definitions **************************************************************/ + +//! VOIP module state data +struct VoipHeadsetRefT +{ + int32_t bParticipating; //!< local user is now in "participating" state + + // speaker callback data + VoipSpkrCallbackT *pSpkrDataCb; + void *pSpkrCbUserData; +}; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Public Variables + +// Private Variables + +/*** Private Functions ************************************************************/ + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetCreate + + \Description + Create the headset manager. + + \Input iMaxConduits - max number of conduits + \Input *pMicDataCb - pointer to user callback to trigger when mic data is ready + \Input *pTextDataCb - pointer to user callback to trigger when transcribed text is ready + \Input *pOpaqueDataCb - pointer to user callback to trigger when opaque data is ready + \Input *pStatusCb - pointer to user callback to trigger when headset status changes + \Input *pCbUserData - pointer to user callback data + \Input iData - platform-specific - unused for PC + + \Output + VoipHeadsetRefT * - pointer to module state, or NULL if an error occured + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +VoipHeadsetRefT *VoipHeadsetCreate(int32_t iMaxConduits, VoipHeadsetMicDataCbT *pMicDataCb, VoipHeadsetTextDataCbT *pTextDataCb, VoipHeadsetOpaqueDataCbT *pOpaqueDataCb, VoipHeadsetStatusCbT *pStatusCb, void *pCbUserData, int32_t iData) +{ + VoipHeadsetRefT *pHeadset; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // make sure we don't exceed maxconduits + if (iMaxConduits > VOIP_MAXCONNECT) + { + NetPrintf(("voipheadsetstub: request for %d conduits exceeds max\n", iMaxConduits)); + return(NULL); + } + + // allocate and clear module state + if ((pHeadset = DirtyMemAlloc(sizeof(*pHeadset), VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + return(NULL); + } + ds_memclr(pHeadset, sizeof(*pHeadset)); + + // return module ref to caller + return(pHeadset); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetDestroy + + \Description + Destroy the headset manager. + + \Input *pHeadset - pointer to headset state + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipHeadsetDestroy(VoipHeadsetRefT *pHeadset) +{ + int32_t iMemGroup; + void *pMemGroupUserData; + + // dispose of module memory + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + DirtyMemFree(pHeadset, VOIP_MEMID, iMemGroup, pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetReceiveVoiceDataCb + + \Description + Connectionlist callback to handle receiving a voice packet from a remote peer. + + \Input *pRemoteUsers - user we're receiving the voice data from + \Input iRemoteUserSize - pRemoteUsers array size + \Input iConsoleId - generic identifier for the console to which the users belong + \Input *pMicrInfo - micr info from inbound packet + \Input *pPacketData - pointer to beginning of data in packet payload + \Input *pUserData - VoipHeadsetT ref + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipHeadsetReceiveVoiceDataCb(VoipUserT *pRemoteUsers, int32_t iRemoteUserSize, int32_t iConsoleId, VoipMicrInfoT *pMicrInfo, uint8_t *pPacketData, void *pUserData) +{ + +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetRegisterUserCb + + \Description + Connectionlist callback to register/unregister a new user with the + VoipConduit module. + + \Input *pRemoteUser - user to register + \Input iConsoleId - generic identifier for the console to which the user belongs (ignored) + \Input bRegister - true=register, false=unregister + \Input *pUserData - voipheadset module ref + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipHeadsetRegisterUserCb(VoipUserT *pRemoteUser, int32_t iConsoleId, uint32_t bRegister, void *pUserData) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetProcess + + \Description + Headset process function. + + \Input *pHeadset - pointer to headset state + \Input uFrameCount - process iteration counter + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipHeadsetProcess(VoipHeadsetRefT *pHeadset, uint32_t uFrameCount) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetSetVolume + + \Description + Sets play and record volume. + + \Input *pHeadset - pointer to headset state + \Input iPlayVol - play volume to set + \Input iRecVol - record volume to set + + \Notes + To not set a value, specify it as -1. + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipHeadsetSetVolume(VoipHeadsetRefT *pHeadset, int32_t iPlayVol, uint32_t iRecVol) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetControl + + \Description + Control function. + + \Input *pHeadset - headset module state + \Input iControl - control selector + \Input iValue - control value + \Input iValue2 - control value + \Input *pValue - control value + + \Output + int32_t - selector specific, or -1 if no such selector + + \Notes + iControl can be one of the following: + + \verbatim + 'aloc' - promote/demote to/from participating state + 'cide' - close voip input device + 'code' - close voip output device + 'edev' - enumerate voip input/output devices + 'idev' - select voip input device + 'loop' - enable/disable loopback + 'micr' - enable/disable recording + 'odev' - select voip output device + 'play' - enable/disable playing + 'svol' - changes speaker volume + \endverbatim + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t VoipHeadsetControl(VoipHeadsetRefT *pHeadset, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iControl == 'aloc') + { + pHeadset->bParticipating = ((iValue2 == 0) ? FALSE : TRUE); + + NetPrintf(("voipheadsetstub: %s participating state\n", pHeadset->bParticipating ? "entering" : "exiting")); + + return(0); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetStatus + + \Description + Status function. + + \Input *pHeadset - headset module state + \Input iSelect - control selector + \Input iValue - selector specific + \Input *pBuf - buffer pointer + \Input iBufSize - buffer size + + \Output + int32_t - selector specific, or -1 if no such selector + + \Notes + iSelect can be one of the following: + + \verbatim + 'ruvu' - always return TRUE because MLU is not supported on unix + \endverbatim + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t VoipHeadsetStatus(VoipHeadsetRefT *pHeadset, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + if (iSelect == 'ruvu') + { + return (TRUE); + } + // unhandled result + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetSpkrCallback + + \Description + Set speaker output callback. + + \Input *pHeadset - headset module state + \Input *pCallback - what to call when output data is available + \Input *pUserData - user data for callback + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipHeadsetSpkrCallback(VoipHeadsetRefT *pHeadset, VoipSpkrCallbackT *pCallback, void *pUserData) +{ + pHeadset->pSpkrDataCb = pCallback; + pHeadset->pSpkrCbUserData = pUserData; +} + +/*F********************************************************************************/ +/*! + \Function VoipHeadsetSetFirstPartyIdCallback + + \Description + Callback to tell dirtysdk what the first party id is for this user + + \Input *pHeadset - headset module state + \Input *pCallback - what to call when transcribed text is received from remote player + \Input *pUserData - user data for callback + + \Version 04/28/2020 (eesponda) +*/ +/********************************************************************************F*/ +void VoipHeadsetSetFirstPartyIdCallback(VoipHeadsetRefT *pHeadset, VoipFirstPartyIdCallbackCbT *pCallback, void *pUserData) +{ +} diff --git a/src/thirdparty/dirtysdk/source/voip/stub/voipstub.c b/src/thirdparty/dirtysdk/source/voip/stub/voipstub.c new file mode 100644 index 00000000..fcd3f5a9 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/stub/voipstub.c @@ -0,0 +1,501 @@ +/*H********************************************************************************/ +/*! + \File voipstub.c + + \Description + Voip library interface. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 0.5 03/02/2004 (jbrookes) Implemented stubbed interface. +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/voip/voip.h" +#include "DirtySDK/voip/voipcodec.h" +#include "voippriv.h" +#include "voipcommon.h" + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! VoIP module state +struct VoipRefT +{ + //! module memory group + int32_t iMemGroup; + void *pMemGroupUserData; +}; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Private Variables + +//! pointer to module state +static VoipRefT *_Voip_pRef = NULL; + +// Public Variables + + +/*** Private Functions ************************************************************/ + + +/*** Public Functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipStartup + + \Description + Prepare VoIP for use. + + \Input iMaxPeers - maximum number of peers supported (up to 32) + \Input iData - platform-specific - unused for voipstub + + \Output + VoipRefT - state reference that is passed to all other functions + + \Version 1.0 03/02/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +VoipRefT *VoipStartup(int32_t iMaxPeers, int32_t iData) +{ + VoipRefT *pVoip; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // make sure we're not already started + if (_Voip_pRef != NULL) + { + NetPrintf(("voip: module startup called when not in a shutdown state\n")); + return(NULL); + } + + // create and initialize module state + if ((pVoip = (VoipRefT *)DirtyMemAlloc(sizeof(*pVoip), VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voip: unable to allocate module state\n")); + return(NULL); + } + pVoip->iMemGroup = iMemGroup; + pVoip->pMemGroupUserData = pMemGroupUserData; + + // save ref and return to caller + _Voip_pRef = pVoip; + return(pVoip); +} + +/*F********************************************************************************/ +/*! + \Function VoipGetRef + + \Description + Return current module reference. + + \Output + VoipRefT * - reference pointer, or NULL if the module is not active + + \Version 1.0 03/08/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +VoipRefT *VoipGetRef(void) +{ + // return pointer to module state + return(_Voip_pRef); +} + +/*F********************************************************************************/ +/*! + \Function VoipShutdown + + \Description + Release all VoIP resources. + + \Input *pVoip - module state from VoipStartup + \Input uShutdownFlags - NET_SHUTDOWN_* flags + + \Version 03/02/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +void VoipShutdown(VoipRefT *pVoip, uint32_t uShutdownFlags) +{ + // make sure we're really started up + if (_Voip_pRef == NULL) + { + NetPrintf(("voip: module shutdown called when not in a started state\n")); + return; + } + + // free module memory + DirtyMemFree(pVoip, VOIP_MEMID, pVoip->iMemGroup, pVoip->pMemGroupUserData); + + // clear pointer to module state + _Voip_pRef = NULL; +} + + +/*F********************************************************************************/ +/*! + \Function VoipSetLocalUser + + \Description + Register/unregister specified local user with the voip sub-system. + + \Input *pVoip - module state from VoipStartup + \Input iLocalUserIndex - local user index (ignored because MLU not supported on PC) + \Input bRegister - ignored + + \Version 04/15/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipSetLocalUser(VoipRefT *pVoip, int32_t iLocalUserIndex, uint32_t bRegister) +{ + +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonActivateLocalUser + + \Description + Promote/demote specified local user to/from "participating" state + + \Input *pVoipCommon - voip common state + \Input iLocalUserIndex - local user index (ignored because MLU not supported on PC) + \Input bActivate - TRUE to activate, FALSE to deactivate + + \Version 04/25/2013 (tcho) +*/ +/********************************************************************************F*/ +void VoipCommonActivateLocalUser(VoipCommonRefT *pVoipCommon, int32_t iLocalUserIndex, uint8_t bActivate) +{ + +} + +/*F********************************************************************************/ +/*! + \Function VoipStatus + + \Description + Return status. + + \Input *pVoip - module state from VoipStartup + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuf - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector-specific data + + \Version 1.0 03/02/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipStatus(VoipRefT *pVoip, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + if (iSelect == 'avlb') + { + return(TRUE); + } + if (iSelect == 'from') + { + return(0); + } + if (iSelect == 'micr') + { + return(0); + } + if (iSelect == 'sock') + { + return(0); + } + if (iSelect == 'spkr') + { + return(0); + } + // unsupported selector + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function VoipControl + + \Description + Set control options. + + \Input *pVoip - module state from VoipStartup + \Input iControl - control selector + \Input iValue - selector-specific + \Input *pValue - selector-specific + + \Output + int32_t - selector-specific data + + \Version 1.0 03/02/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +int32_t VoipControl(VoipRefT *pVoip, int32_t iControl, int32_t iValue, void *pValue) +{ + return(0); +} + +/*F*************************************************************************************************/ +/*! + \Function VoipSpkrCallback + + \Description + Set speaker data callback. + + \Input *pVoip - voip module state + \Input *pCallback - data callback + \Input *pUserData - user data + + \Version 1.0 12/12/2005 (jbrookes) First Version +*/ +/*************************************************************************************************F*/ +void VoipSpkrCallback(VoipRefT *pVoip, VoipSpkrCallbackT *pCallback, void *pUserData) +{ +} + +/*F*************************************************************************************************/ +/*! + \Function VoipCodecControl + + \Description + Stub for codec control + + \Input iCodecIdent - codec identifier, or VOIP_CODEC_ACTIVE + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Version 1.0 10/11/2011 (jbrookes) First Version +*/ +/*************************************************************************************************F*/ +int32_t VoipCodecControl(int32_t iCodecIdent, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipSelectChannel + + \Description + Stub for VoipSelectChannel + + \Input *pVoip - voip module state + \Input iUserIndex - local user index + \Input iChannel - Channel ID (valid range: [0,63]) + \Input eMode - The mode, combination of VOIPGROUP_CHANSEND, VOIPGROUP_CHANRECV + + \Output + int32_t - number of channels remaining that this console could join + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +int32_t VoipSelectChannel(VoipRefT *pVoip, int32_t iUserIndex, int32_t iChannel, VoipChanModeE eMode) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipResetChannels + + \Description + Stub for VoipResetChannels + + \Input *pVoip - module ref + \Input iUserIndex - local user index + + \Version 12/07/2009 (jrainy) +*/ +/********************************************************************************F*/ +void VoipResetChannels(VoipRefT *pVoip, int32_t iUserIndex) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipLocalUserStatus + + \Description + Return information about local hardware state + + \Input *pVoip - module state from VoipStartup + \Input iLocalUserIndex - local index of the user + + \Output + int32_t - VOIP_LOCAL* flags + + \Version 1.0 03/02/2004 (jbrookes) First Version + \Version 2.0 04/25/2014 (amakoukji) Refactored for MLU +*/ +/********************************************************************************F*/ +int32_t VoipLocalUserStatus(VoipRefT *pVoip, int32_t iLocalUserIndex) +{ + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListCreate + + \Description + Creates the VoipBlockListT. + + \Output + VoipBlockListT * - The Blocked List state + + \Version 05/02/2019 (cvienneau) +*/ +/********************************************************************************F*/ +VoipBlockListT *VoipBlockListCreate(void) +{ + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListDestroy + + \Description + Free the voip block list + + \Input pVoip - voip state + + \Version 05/07/2019 (cvienneau) +*/ +/********************************************************************************F*/ +void VoipBlockListDestroy(VoipRefT *pVoip) +{ +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListAdd + + \Description + Add a user to be blocked by the local user + + \Input pVoip - voip state + \Input iLocalUserIndex - the index of the local user + \Input iBlockedAccountId - the account id of the user to be blocked + + \Output + uint8_t - TRUE if the user was successfully blocked + + \Version 05/07/2019 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t VoipBlockListAdd(VoipRefT *pVoip, int32_t iLocalUserIndex, int64_t iBlockedAccountId) +{ + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListRemove + + \Description + Remove a user that was blocked by the local user + + \Input pVoip - voip state + \Input iLocalUserIndex - the index of the local user + \Input iBlockedAccountId - the account id of the user who was blocked + + \Output + uint8_t - TRUE if the user was successfully un-blocked + + \Version 05/07/2019 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t VoipBlockListRemove(VoipRefT *pVoip, int32_t iLocalUserIndex, int64_t iBlockedAccountId) +{ + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListIsBlocked + + \Description + Check if a user is blocked by the local user + + \Input pVoip - voip state + \Input iLocalUserIndex - the index of the local user + \Input iBlockedAccountId - the account id of the user who was blocked + + \Output + uint8_t - TRUE if the user is blocked + + \Version 05/07/2019 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t VoipBlockListIsBlocked(VoipRefT *pVoip, int32_t iLocalUserIndex, int64_t iBlockedAccountId) +{ + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListClear + + \Description + Clear the blocked list for the local user + + \Input pVoip - voip state + \Input iLocalUserIndex - the index of the local user (-1 for all users) + + \Output + uint8_t - TRUE if the list was cleared + + \Version 05/07/2019 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t VoipBlockListClear(VoipRefT *pVoip, int32_t iLocalUserIndex) +{ + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function VoipRegisterFirstPartyIdCallback + + \Description + Register first party id callback + + \Input *pVoip - module ref + \Input *pCallback - first party id callback + \Input *pUserData - callback user data + + \Version 05/08/2019 (tcho) +*/ +/********************************************************************************F*/ +void VoipRegisterFirstPartyIdCallback(VoipRefT *pVoip, VoipFirstPartyIdCallbackCbT *pCallback, void *pUserData) +{ +} \ No newline at end of file diff --git a/src/thirdparty/dirtysdk/source/voip/unix/voipunix.c b/src/thirdparty/dirtysdk/source/voip/unix/voipunix.c new file mode 100644 index 00000000..02ae35c7 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/unix/voipunix.c @@ -0,0 +1,413 @@ +/*H********************************************************************************/ +/*! + \File voipunix.c + + \Description + voip interface (not production ready - for DirtyCast voip stress testing client) + + \Copyright + Copyright (c) Electronic Arts 2017. ALL RIGHTS RESERVED. + + \Version 06/08/2017 (mclouatre) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +// dirtysock includes +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +// voip includes +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" +#include "DirtySDK/voip/voip.h" + + +/*** Defines **********************************************************************/ +#define VOIP_THREAD_SLEEP_DURATION (20) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! VoIP module state +struct VoipRefT +{ + VoipCommonRefT Common; //!< cross-platform voip data (must come first!) + + // dummy traffic injection config + uint32_t uSendSeqn; //!< send sequence number + int32_t iDummyVoipSubPacketSize; //!< size of test packets (in bytes) + uint32_t uDummyVoipSendPeriod; //!< rate of artificially injected subpackets 0: disabled; [1,1024]: 0% to 100% of voip bandwidth produced over a 20-sec window + uint32_t uDummyVoipWrappingCounter; //!< 32-bit wrapping counter used to implement the right dummy send period +}; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _VoipStatusCb + + \Description + Callback to handle change of headset status. + + \Input iLocalUserIndex - headset that has changed + \Input bStatus - if TRUE the headset was inserted, else if FALSE it was removed + \Input eUpdate - functionality of the headset (input, output or both) + \Input *pUserData - pointer to callback user data + + \Notes + It is assumed that eUpdate will always be VOIP_HEADSET_STATUS_INOUT for + this platform. + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipStatusCb(int32_t iLocalUserIndex, uint32_t bStatus, VoipHeadsetStatusUpdateE eUpdate, void *pUserData) +{ + VoipRefT *pRef = (VoipRefT *)pUserData; + + if (eUpdate != VOIP_HEADSET_STATUS_INOUT) + { + NetPrintf(("voipunix: warning, unexpected headset event for this platform!\n")); + } + + if (bStatus) + { + NetPrintf(("voipunix: [%d] headset inserted\n", iLocalUserIndex)); + pRef->Common.uPortHeadsetStatus |= 1 << iLocalUserIndex; + pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] |= (VOIP_LOCAL_USER_HEADSETOK|VOIP_LOCAL_USER_INPUTDEVICEOK|VOIP_LOCAL_USER_OUTPUTDEVICEOK); + } + else + { + NetPrintf(("voipunix: [%d] headset removed\n", iLocalUserIndex)); + pRef->Common.uPortHeadsetStatus &= ~(1 << iLocalUserIndex); + pRef->Common.Connectionlist.uLocalUserStatus[iLocalUserIndex] &= ~(VOIP_LOCAL_USER_HEADSETOK|VOIP_LOCAL_USER_INPUTDEVICEOK|VOIP_LOCAL_USER_OUTPUTDEVICEOK); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipInjectDummyTraffic + + \Description + Inject dummy voip traffic on all active voip connections + + \Input *pVoip - module state + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipInjectDummyTraffic(VoipRefT *pVoip) +{ + static uint8_t DummyVoipSubPacket[VOIP_MAXMICRPKTSIZE]; + VoipConnectionlistT *pConnectionlist = &pVoip->Common.Connectionlist; + + // only inject dummy sub-packets if the 1024-large counter is in between [0, pVoip->uDummyVoipSendPeriod[ + if ((++pVoip->uDummyVoipWrappingCounter % 1024) < pVoip->uDummyVoipSendPeriod) + { + // send dummy traffic on all active connections + VoipConnectionSend(pConnectionlist, 0xFFFFFFFF, &DummyVoipSubPacket[0], pVoip->iDummyVoipSubPacketSize, NULL, 0, 0, pVoip->uSendSeqn++); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipUpdate + + \Description + Update function to be used in a single-threaded environment + + \Input *pVoip - voip ref + + \Version 01/18/2018 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipUpdate(VoipRefT *pVoip) + { + uint32_t uTime0, uTime1; + int32_t iSleep; + + uTime0 = NetTick(); + + if (pVoip->Common.bApplyChannelConfig) + { + pVoip->Common.bApplyChannelConfig = FALSE; + VoipCommonApplyChannelConfig((VoipCommonRefT *)pVoip); + } + + // update status of remote users + VoipCommonUpdateRemoteStatus(&pVoip->Common); + VoipConnectionUpdate(&pVoip->Common.Connectionlist); + + // inject dummy voip traffic for stress test purposes + _VoipInjectDummyTraffic(pVoip); + + uTime1 = NetTick(); + + iSleep = VOIP_THREAD_SLEEP_DURATION - NetTickDiff(uTime1, uTime0); + + // wait for next tick + if (iSleep >= 0) + { + NetConnSleep(iSleep); + } + else + { + NetPrintf(("voipunix: [WARNING] Overall voip update took %d ms\n", NetTickDiff(uTime1, uTime0))); + } +} + +/*** Public Functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function VoipStartup + + \Description + Prepare VoIP for use. + + \Input iMaxPeers - maximum number of peers supported (up to VOIP_MAXCONNECT) + \Input iData - platform-specific - unused on Unix + + \Output + VoipRefT - state reference that is passed to all other functions + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +VoipRefT *VoipStartup(int32_t iMaxPeers, int32_t iData) +{ + VoipRefT *pVoip; + + NetPrintf(("voipunix: WARNING - VoipUnix is not for production. Usage reserved for DirtyCast voip stress testing only!\n")); + + // common startup + if ((pVoip = VoipCommonStartup(iMaxPeers, sizeof(*pVoip), _VoipStatusCb, iData)) == NULL) + { + return(NULL); + } + + // injection of dummy traffic is disabled by default + pVoip->iDummyVoipSubPacketSize = 0; + pVoip->uDummyVoipSendPeriod = 0; + + // bPrivileged is always TRUE on unix + pVoip->Common.bPrivileged = TRUE; + + // return to caller + return(pVoip); +} + +/*F********************************************************************************/ +/*! + \Function VoipShutdown + + \Description + Release all VoIP resources. + + \Input *pVoip - module state from VoipStartup + \Input uShutdownFlags - NET_SHUTDOWN_* flags + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipShutdown(VoipRefT *pVoip, uint32_t uShutdownFlags) +{ + // make sure we're really started up + if (VoipGetRef() == NULL) + { + NetPrintf(("voipunix: module shutdown called when not in a started state\n")); + return; + } + + // common shutdown (must come last as this frees the memory) + VoipCommonShutdown(&pVoip->Common); +} + +/*F********************************************************************************/ +/*! + \Function VoipSetLocalUser + + \Description + Register/unregister specified local user with the voip sub-system. + + \Input *pVoip - module state from VoipStartup + \Input iLocalUserIndex - local user index (ignored because MLU not supported on PC) + \Input bRegister - ignored + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipSetLocalUser(VoipRefT *pVoip, int32_t iLocalUserIndex, uint32_t bRegister) +{ + if (bRegister) + { + if (VOIP_NullUser(&pVoip->Common.Connectionlist.LocalUsers[0])) + { + VoipUserT voipUser; + ds_memclr(&voipUser, sizeof(voipUser)); + + voipUser.ePlatform = VOIP_PLATFORM_LINUX; + NetConnStatus('acid', iLocalUserIndex, &voipUser.AccountInfo.iAccountId, sizeof(voipUser.AccountInfo.iAccountId)); + NetConnStatus('peid', iLocalUserIndex, &voipUser.AccountInfo.iPersonaId, sizeof(voipUser.AccountInfo.iPersonaId)); + + NetPrintf(("voipunix: registering a local user %llx at index %d\n", voipUser.AccountInfo.iPersonaId, iLocalUserIndex)); + VOIP_CopyUser(&pVoip->Common.Connectionlist.LocalUsers[0], &voipUser); + } + else + { + NetPrintf(("voipunix: warning -- ignoring attempt to register local user because user %llx is already registered\n", pVoip->Common.Connectionlist.LocalUsers[0].AccountInfo.iPersonaId)); + } + } + else + { + if (!VOIP_NullUser(&pVoip->Common.Connectionlist.LocalUsers[0])) + { + // if a participating user demote him first + if (pVoip->Common.Connectionlist.aIsParticipating[0] == TRUE) + { + VoipCommonActivateLocalUser(&pVoip->Common, 0, FALSE); + } + + NetPrintf(("voipunix: unregistering local user %llx\n", pVoip->Common.Connectionlist.LocalUsers[0].AccountInfo.iPersonaId)); + ds_memclr(&pVoip->Common.Connectionlist.LocalUsers[0], sizeof(pVoip->Common.Connectionlist.LocalUsers[0])); + } + else + { + NetPrintf(("voipunix: warning -- ignoring attempt to unregister local user because it is not yet registered\n")); + } + } +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonActivateLocalUser + + \Description + Promote/demote specified local user to/from "participating" state + + \Input *pVoipCommon - voip common state + \Input iLocalUserIndex - local user index (ignored because MLU not supported on PC) + \Input bActivate - TRUE to activate, FALSE to deactivate + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipCommonActivateLocalUser(VoipCommonRefT *pVoipCommon, int32_t iLocalUserIndex, uint8_t bActivate) +{ + if (!VOIP_NullUser(&pVoipCommon->Connectionlist.LocalUsers[0])) + { + if (bActivate == TRUE) + { + NetPrintf(("voipunix: promoting user %llx to participating state\n", &pVoipCommon->Connectionlist.LocalUsers[0].AccountInfo.iPersonaId)); + pVoipCommon->Connectionlist.aIsParticipating[0] = TRUE; + + // reapply playback muting config based on the channel configuration only if this is a participating user + pVoipCommon->bApplyChannelConfig = TRUE; + } + else + { + NetPrintf(("voipunix: demoting user %llx from participating state\n", &pVoipCommon->Connectionlist.LocalUsers[0].AccountInfo.iPersonaId)); + pVoipCommon->Connectionlist.aIsParticipating[0] = FALSE; + } + VoipHeadsetControl(pVoipCommon->pHeadset, 'aloc', 0, bActivate, &pVoipCommon->Connectionlist.LocalUsers[0]); + } + else + { + NetPrintf(("voipunix: VoipCommonActivateLocalUser() failed because no local user registered\n")); + } +} + + +/*F********************************************************************************/ +/*! + \Function VoipStatus + + \Description + Return status. + + \Input *pVoip - module state from VoipStartup + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuf - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector-specific data + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t VoipStatus(VoipRefT *pVoip, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + return(VoipCommonStatus(&pVoip->Common, iSelect, iValue, pBuf, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function VoipControl + + \Description + Set control options. + + \Input *pVoip - module state from VoipStartup + \Input iControl - control selector + \Input iValue - selector-specific + \Input *pValue - selector-specific + + \Notes + iControl can be one of the following: + + \verbatim + 'psiz' - set size of artificially injected test voip sub-packets + 'prat' - artificially injected voip sub-packets period --> 0: disabled; [1,1024]: 0% to 100% of voip bandwidth produced over a 20-sec window + 'updt' - to pump the voip sub-system in a single-threaded context + \endverbatim + + Unhandled selectors are passed through to VoipCommonControl(), and + further to VoipHeadsetControl() if unhandled there. + + \Version 06/12/2017 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t VoipControl(VoipRefT *pVoip, int32_t iControl, int32_t iValue, void *pValue) +{ + if (iControl == 'psiz') + { + NetPrintf(("voipunix: size of artificially injected test voip sub-packets changed from %d bytes to %d bytes\n", pVoip->iDummyVoipSubPacketSize, iValue)); + pVoip->iDummyVoipSubPacketSize = iValue; + return(0); + } + if (iControl == 'prat') + { + NetPrintf(("voipunix: artificially injected voip sub-packets period changed from %d/1024 to %d/1024\n", pVoip->uDummyVoipSendPeriod, iValue)); + pVoip->uDummyVoipSendPeriod = iValue; + return(0); + } + if (iControl == 'updt') + { + if (VoipGetRef()) + { + _VoipUpdate(VoipGetRef()); + return(0); + } + else + { + NetPrintf(("voipunix: module update called when not in a started state\n")); + return(-1); + } + } + return(VoipCommonControl(&pVoip->Common, iControl, iValue, pValue)); +} + + diff --git a/src/thirdparty/dirtysdk/source/voip/voip.c b/src/thirdparty/dirtysdk/source/voip/voip.c new file mode 100644 index 00000000..e4261f89 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voip.c @@ -0,0 +1,214 @@ +/*H********************************************************************************/ +/*! + \File voip.c + + \Description + Cross-platform public voip functions. + + \Copyright + Copyright (c) 2009 Electronic Arts Inc. + + \Version 12/02/2009 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#ifdef _XBOX +#include +#include +#endif + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" + +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" +#include "DirtySDK/voip/voip.h" + +/*** Defines **********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipLocalUserStatus + + \Description + Return information about local hardware state + + \Input *pVoip - module state from VoipStartup + \Input iLocalUserIndex - local index of the user + + \Output + int32_t - VOIP_LOCAL* flags + + \Version 1.0 03/02/2004 (jbrookes) First Version + \Version 2.0 04/25/2014 (amakoukji) Refactored for MLU +*/ +/********************************************************************************F*/ +int32_t VoipLocalUserStatus(VoipRefT *pVoip, int32_t iLocalUserIndex) +{ + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pVoip; + uint32_t uLocalStatus = pVoipCommon->Connectionlist.uLocalUserStatus[iLocalUserIndex]; + + return(uLocalStatus); +} + +/*F********************************************************************************/ +/*! + \Function VoipGetRef + + \Description + Return current module reference. + + \Output + VoipRefT * - reference pointer, or NULL if the module is not active + + \Version 1.0 03/08/2004 (jbrookes) First Version +*/ +/********************************************************************************F*/ +VoipRefT *VoipGetRef(void) +{ + return(VoipCommonGetRef()); +} + +/*F*************************************************************************************************/ +/*! + \Function VoipSpkrCallback + + \Description + Set speaker data callback (not supported on all platforms). + + \Input *pVoip - voip module state + \Input *pCallback - data callback + \Input *pUserData - user data + + \Version 12/12/2005 (jbrookes) +*/ +/*************************************************************************************************F*/ +void VoipSpkrCallback(VoipRefT *pVoip, VoipSpkrCallbackT *pCallback, void *pUserData) +{ + #if defined(DIRTYCODE_PC) || defined(DIRTYCODE_STADIA) + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pVoip; + VoipHeadsetSpkrCallback(pVoipCommon->pHeadset, pCallback, pUserData); + #endif +} + +/*F********************************************************************************/ +/*! + \Function VoipSelectChannel + + \Description + Select the mode(send/recv) of a given channel. + + \Input *pVoip - voip module state + \Input iUserIndex - local user index + \Input iChannel - Channel ID (valid range: [0,63]) + \Input eMode - The mode, combination of VOIP_CHANSEND, VOIP_CHANRECV + + \Output + int32_t - number of channels remaining that this console could join + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +int32_t VoipSelectChannel(VoipRefT *pVoip, int32_t iUserIndex, int32_t iChannel, VoipChanModeE eMode) +{ + return(VoipCommonSelectChannel((VoipCommonRefT*)pVoip, iUserIndex, iChannel, eMode)); +} + +/*F********************************************************************************/ +/*! + \Function VoipResetChannels + + \Description + Resets the channels selection to defaults. Sends and receives to all + + \Input *pVoip - module ref + \Input iUserIndex - local user index + + \Version 12/07/2009 (jrainy) +*/ +/********************************************************************************F*/ +void VoipResetChannels(VoipRefT *pVoip, int32_t iUserIndex) +{ + VoipCommonResetChannels((VoipCommonRefT*)pVoip, iUserIndex); +} + +/*F********************************************************************************/ +/*! + \Function VoipConfigTranscription + + \Description + Configure voice transcription + + \Input *pVoip - module ref + \Input uProfile - transcribe profile + \Input *pUrl - transcribe provider url + \Input *pKey - transcribe key + + \Version 11/06/2018 (tcho) +*/ +/********************************************************************************F*/ +void VoipConfigTranscription(VoipRefT *pVoip, uint32_t uProfile, const char *pUrl, const char *pKey) +{ + #if defined(DIRTYCODE_PC) || defined(DIRTYCODE_PS4) || defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) || defined(DIRTYCODE_STADIA) + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pVoip; + VoipHeadsetConfigTranscription(pVoipCommon->pHeadset, uProfile, pUrl, pKey); + #endif +} + +/*F********************************************************************************/ +/*! + \Function VoipConfigNarration + + \Description + Configure voice transcription + + \Input *pVoip - module ref + \Input uProvider - narration profile + \Input *pUrl - narration provider url + \Input *pKey - narration key + + \Version 11/06/2018 (tcho) +*/ +/********************************************************************************F*/ +void VoipConfigNarration(VoipRefT *pVoip, uint32_t uProvider, const char *pUrl, const char *pKey) +{ + #if defined(DIRTYCODE_PS4) || defined(DIRTYCODE_STADIA) + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pVoip; + VoipHeadsetConfigNarration(pVoipCommon->pHeadset, uProvider, pUrl, pKey); + #endif +} + +/*F********************************************************************************/ +/*! + \Function VoipRegisterFirstPartyIdCallback + + \Description + Register first party id callback + + \Input *pVoip - module ref + \Input *pCallback - first party id callback + \Input *pUserData - callback user data + + \Version 05/08/2019 (tcho) +*/ +/********************************************************************************F*/ +void VoipRegisterFirstPartyIdCallback(VoipRefT *pVoip, VoipFirstPartyIdCallbackCbT *pCallback, void *pUserData) +{ + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pVoip; + VoipHeadsetSetFirstPartyIdCallback(pVoipCommon->pHeadset, pCallback, pUserData); +} diff --git a/src/thirdparty/dirtysdk/source/voip/voipblocklist.c b/src/thirdparty/dirtysdk/source/voip/voipblocklist.c new file mode 100644 index 00000000..2aab83df --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipblocklist.c @@ -0,0 +1,423 @@ + +/*H********************************************************************************/ +/*! + \File voipblocklist.c + + \Description + Impliments blocking voip communications based off users accountid + + \Copyright + Copyright (c) 2019 Electronic Arts Inc. + + \Version 07/02/2019 (cvienneau) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include // for qsort + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" + +#include "DirtySDK/voip/voip.h" +#include "DirtySDK/voip/voipblocklist.h" + + +/*** Defines **********************************************************************/ +#define VOIPBLOCKLIST_DEFAULT_LIST_SIZE (64) +#define VOIPBLOCKLIST_MAX_LIST_SIZE (2000) + +/*** Type Definitions *************************************************************/ +typedef struct VoipBlockListUserT +{ + int64_t *pBlockList; + uint32_t uCapacity; + uint32_t uConsumed; + uint8_t bSorted; +} VoipBlockListUserT; + +typedef struct VoipBlockListT +{ + VoipBlockListUserT aUserBlockLists[VOIP_MAXLOCALUSERS]; + int32_t iMemGroup; + void *pMemGroupUserData; +} VoipBlockListT; + +/*** Variables ********************************************************************/ + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _VoipBlockListSort + + \Description + qsort callback used to sort fast lookup array. + + \Input *_pElem0 - pointer to first element to compare + \Input *_pElem1 - pointer to second element to compare + + \Output + int32_t - sort value + + \Version 06/19/2019 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _VoipBlockListSort(const void *_pElem0, const void *_pElem1) +{ + int64_t arg0 = *(const int64_t*)_pElem0; + int64_t arg1 = *(const int64_t*)_pElem1; + + if (arg0 < arg1) return -1; + if (arg0 > arg1) return 1; + return 0; +} + +/*F********************************************************************************/ +/*! + \Function _VoipBlockListFindAccountId + + \Description + Sorts the given users Blocked list, then finds the index of the BlockedAccountId + + \Input pVoip - voip state + \Input iLocalUserIndex - the index of the local user + \Input iBlockedAccountId - the account id of the user to be found + + \Output + int32_t - the index of the blocked account id, or -1 if not found + + \Version 06/20/2019 (cvienneau) +*/ +/********************************************************************************F*/ +static int32_t _VoipBlockListFindAccountId(VoipRefT *pVoip, int32_t iLocalUserIndex, int64_t iBlockedAccountId) +{ + int32_t iCheck, iLow, iHigh; + int64_t iCheckId; + VoipBlockListT *pBlockedList = ((VoipCommonRefT *)pVoip)->pBlockList; + + if (pBlockedList->aUserBlockLists[iLocalUserIndex].bSorted == FALSE) + { + qsort(pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList, pBlockedList->aUserBlockLists[iLocalUserIndex].uConsumed, sizeof(pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList[0]), _VoipBlockListSort); + pBlockedList->aUserBlockLists[iLocalUserIndex].bSorted = TRUE; + } + + // execute binary search on sorted lookup table + for (iLow = 0, iHigh = pBlockedList->aUserBlockLists[iLocalUserIndex].uConsumed - 1; iLow <= iHigh; ) + { + iCheck = iLow + ((iHigh - iLow) / 2); + if ((iCheckId = pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList[iCheck]) > iBlockedAccountId) + { + iHigh = iCheck - 1; + } + else if (iCheckId < iBlockedAccountId) + { + iLow = iCheck + 1; + } + else + { + return(iCheck); + } + } + // not found + return(-1); +} + +/*** Public functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function VoipBlockListCreate + + \Description + Creates the VoipBlockListT. + + \Output + VoipBlockListT * - The Blocked List state + + \Version 05/02/2019 (cvienneau) +*/ +/********************************************************************************F*/ +VoipBlockListT *VoipBlockListCreate(void) +{ + int32_t iMemGroup; + void *pMemGroupUserData; + VoipBlockListT *pBlockedList = NULL; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + if ((pBlockedList = DirtyMemAlloc(sizeof(*pBlockedList), VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipblocklist: could not allocate block list state.\n")); + return(NULL); + } + ds_memclr(pBlockedList, sizeof(*pBlockedList)); + pBlockedList->iMemGroup = iMemGroup; + pBlockedList->pMemGroupUserData = pMemGroupUserData; + + return(pBlockedList); +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListDestroy + + \Description + Free the voip block list + + \Input pVoip - voip state + + \Version 05/07/2019 (cvienneau) +*/ +/********************************************************************************F*/ +void VoipBlockListDestroy(VoipRefT *pVoip) +{ + VoipBlockListT *pBlockedList = ((VoipCommonRefT *)pVoip)->pBlockList; + + // to free memory used by each user + VoipBlockListClear(pVoip, -1); + + DirtyMemFree(pBlockedList, VOIP_MEMID, pBlockedList->iMemGroup, pBlockedList->pMemGroupUserData); + ((VoipCommonRefT *)pVoip)->pBlockList = NULL; +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListAdd + + \Description + Add a user to be blocked by the local user + + \Input pVoip - voip state + \Input iLocalUserIndex - the index of the local user + \Input iBlockedAccountId - the account id of the user to be blocked + + \Output + uint8_t - TRUE if the user was successfully blocked + + \Version 05/07/2019 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t VoipBlockListAdd(VoipRefT *pVoip, int32_t iLocalUserIndex, int64_t iBlockedAccountId) +{ + VoipBlockListT *pBlockedList = ((VoipCommonRefT *)pVoip)->pBlockList; + + // early out if parameters are bad + if (iLocalUserIndex < 0 || iLocalUserIndex >= VOIP_MAXLOCALUSERS) + { + NetPrintf(("voipblocklist: error, user index %d passed to VoipBlockListAdd is not valid.\n", iLocalUserIndex)); + return(FALSE); + } + + // create/resize the blocked list as needed + if (pBlockedList->aUserBlockLists[iLocalUserIndex].uCapacity < (pBlockedList->aUserBlockLists[iLocalUserIndex].uConsumed + 1)) + { + int64_t *pOldList = pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList; + uint32_t uNewCapacity = VOIPBLOCKLIST_DEFAULT_LIST_SIZE; + + // figgure out the size of the new blocked list + if (pBlockedList->aUserBlockLists[iLocalUserIndex].uCapacity != 0) + { + if (pBlockedList->aUserBlockLists[iLocalUserIndex].uCapacity == VOIPBLOCKLIST_MAX_LIST_SIZE) + { + NetPrintf(("voipblocklist: error, user index %d's has no room in blocked list to add %lld.\n", iBlockedAccountId)); + return(FALSE); + } + uNewCapacity = 2 * pBlockedList->aUserBlockLists[iLocalUserIndex].uCapacity; + + if (uNewCapacity > VOIPBLOCKLIST_MAX_LIST_SIZE) + { + uNewCapacity = VOIPBLOCKLIST_MAX_LIST_SIZE; + } + } + + // allocate the new list + if ((pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList = DirtyMemAlloc(sizeof(int64_t) * uNewCapacity, VOIP_MEMID, pBlockedList->iMemGroup, pBlockedList->pMemGroupUserData)) == NULL) + { + NetPrintf(("voipblocklist: could not allocate block list for user %d.\n", iLocalUserIndex)); + return(FALSE); + } + ds_memclr(pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList, sizeof(int64_t) * uNewCapacity); + pBlockedList->aUserBlockLists[iLocalUserIndex].uCapacity = uNewCapacity; + + // copy any contents of the old list to the new list and free it + if (pOldList != NULL) + { + ds_memcpy(pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList, pOldList, sizeof(int64_t) * pBlockedList->aUserBlockLists[iLocalUserIndex].uConsumed); + DirtyMemFree(pOldList, VOIP_MEMID, pBlockedList->iMemGroup, pBlockedList->pMemGroupUserData); + } + } + + // fill the last slot of the blocked list with the new value + pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList[pBlockedList->aUserBlockLists[iLocalUserIndex].uConsumed] = iBlockedAccountId; + pBlockedList->aUserBlockLists[iLocalUserIndex].uConsumed += 1; + pBlockedList->aUserBlockLists[iLocalUserIndex].bSorted = FALSE; + if (VoipGetRef() != NULL) + { + ((VoipCommonRefT *)pVoip)->bApplyChannelConfig = TRUE; + } + NetPrintf(("voipblocklist: user index %d blocked %lld.\n", iLocalUserIndex, iBlockedAccountId)); + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListRemove + + \Description + Remove a user that was blocked by the local user + + \Input pVoip - voip state + \Input iLocalUserIndex - the index of the local user + \Input iBlockedAccountId - the account id of the user who was blocked + + \Output + uint8_t - TRUE if the user was successfully un-blocked + + \Version 05/07/2019 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t VoipBlockListRemove(VoipRefT *pVoip, int32_t iLocalUserIndex, int64_t iBlockedAccountId) +{ + uint32_t uBlockedIndex; + VoipBlockListT *pBlockedList = ((VoipCommonRefT *)pVoip)->pBlockList; + + // early out if parameters are bad + if (iLocalUserIndex < 0 || iLocalUserIndex >= VOIP_MAXLOCALUSERS) + { + NetPrintf(("voipblocklist: error, user index %d passed to VoipBlockListRemove is not valid.\n", iLocalUserIndex)); + return(FALSE); + } + + // look for the blocked user + // note we don't use _VoipBlockListFindAccountId here since if the calling pattern is to call Remove several times in a row + // we don't want to sort it over and over and removing no longer ensuers its sorted + for (uBlockedIndex = 0; uBlockedIndex < pBlockedList->aUserBlockLists[iLocalUserIndex].uConsumed; uBlockedIndex++) + { + if (pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList[uBlockedIndex] == iBlockedAccountId) + { + // copy the last entry over the to be removed entry + pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList[uBlockedIndex] = pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList[pBlockedList->aUserBlockLists[iLocalUserIndex].uConsumed - 1]; + + //remove the last entry + pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList[pBlockedList->aUserBlockLists[iLocalUserIndex].uConsumed - 1] = 0; + pBlockedList->aUserBlockLists[iLocalUserIndex].uConsumed -= 1; + pBlockedList->aUserBlockLists[iLocalUserIndex].bSorted = FALSE; + + if (VoipGetRef() != NULL) + { + ((VoipCommonRefT *)pVoip)->bApplyChannelConfig = TRUE; + } + NetPrintf(("voipblocklist: BlockList, user index %d un-blocked %lld.\n", iLocalUserIndex, iBlockedAccountId)); + return(TRUE); + } + } + NetPrintf(("voipblocklist: BlockList warning, user index %d could not find %lld in blocked list to remove it.\n", iLocalUserIndex, iBlockedAccountId)); + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListIsBlocked + + \Description + Check if a user is blocked by the local user + + \Input pVoip - voip state + \Input iLocalUserIndex - the index of the local user + \Input iBlockedAccountId - the account id of the user who was blocked + + \Output + uint8_t - TRUE if the user is blocked + + \Version 05/07/2019 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t VoipBlockListIsBlocked(VoipRefT *pVoip, int32_t iLocalUserIndex, int64_t iBlockedAccountId) +{ + // if its the shared local user index then we really need to test all the local users + if (iLocalUserIndex == VOIP_SHARED_USER_INDEX) + { + for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS; iLocalUserIndex++) + { + if (VoipBlockListIsBlocked(pVoip, iLocalUserIndex, iBlockedAccountId) == TRUE) + { + return(TRUE); + } + } + } + // deal with individual users + else + { + // early out if parameters are bad + if (iLocalUserIndex < 0 || iLocalUserIndex >= VOIP_MAXLOCALUSERS) + { + NetPrintf(("voipblocklist: error, user index %d passed to VoipBlockListIsBlocked is not valid.\n", iLocalUserIndex)); + return(FALSE); + } + + // look for the blocked user + if (_VoipBlockListFindAccountId(pVoip, iLocalUserIndex, iBlockedAccountId) >= 0) + { + return(TRUE); + } + } + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function VoipBlockListClear + + \Description + Clear the blocked list for the local user + + \Input pVoip - voip state + \Input iLocalUserIndex - the index of the local user (-1 for all users) + + \Output + uint8_t - TRUE if the list was cleared + + \Version 05/07/2019 (cvienneau) +*/ +/********************************************************************************F*/ +uint8_t VoipBlockListClear(VoipRefT *pVoip, int32_t iLocalUserIndex) +{ + VoipBlockListT *pBlockedList = ((VoipCommonRefT *)pVoip)->pBlockList; + + if (iLocalUserIndex == -1) + { + int32_t iUserLoopIndex; + for (iUserLoopIndex = 0; iUserLoopIndex < VOIP_MAXLOCALUSERS; iUserLoopIndex++) + { + VoipBlockListClear(pVoip, iUserLoopIndex); + } + } + else + { + // early out if parameters are bad + if ((iLocalUserIndex < 0) || (iLocalUserIndex >= VOIP_MAXLOCALUSERS)) + { + NetPrintf(("voipblocklist: error, user index %d passed to VoipBlockListClear is not valid.\n", iLocalUserIndex)); + return(FALSE); + } + + // deallocate + DirtyMemFree(pBlockedList->aUserBlockLists[iLocalUserIndex].pBlockList, VOIP_MEMID, pBlockedList->iMemGroup, pBlockedList->pMemGroupUserData); + ds_memclr(&pBlockedList->aUserBlockLists[iLocalUserIndex], sizeof(pBlockedList->aUserBlockLists[iLocalUserIndex])); + NetPrintf(("voipblocklist: BlockList, cleared blocked list for user index %d.\n", iLocalUserIndex)); + } + + if (VoipGetRef() != NULL) + { + ((VoipCommonRefT *)pVoip)->bApplyChannelConfig = TRUE; + } + return(TRUE); +} diff --git a/src/thirdparty/dirtysdk/source/voip/voipcodec.c b/src/thirdparty/dirtysdk/source/voip/voipcodec.c new file mode 100644 index 00000000..6b66f738 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipcodec.c @@ -0,0 +1,564 @@ +/*H********************************************************************************/ +/*! + \File voipcodec.c + + \Description + VoIP codec manager. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 08/04/2004 (jbrookes) First version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" + +#include "DirtySDK/voip/voipdef.h" +#include "DirtySDK/voip/voiptranscribe.h" +#include "DirtySDK/voip/voipcodec.h" + +/*** Defines **********************************************************************/ + +#define VOIPCODEC_MAXENTRIES (8) +#define VOIPCODEC_TIMER (DIRTYCODE_LOGGING && FALSE) + +//! default VAD thresholds +#define VOIPCODEC_DEFAULT_VAD_AMPLITUDE_THRESHOLD (0.00015f) +#define VOIPCODEC_DEFAULT_VAD_FRAMES_THRESHOLD (32) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct VoipCodecRegT +{ + int32_t iCodecIdent; + const VoipCodecDefT *pCodecDef; + VoipCodecRefT *pCodecRef; +} VoipCodecRegT; + + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +//! codec reg table +static VoipCodecRegT _VoipCodec_RegTable[VOIPCODEC_MAXENTRIES]; + +//! number of registered codecs +static int32_t _VoipCodec_iNumCodecs = 0; + +//! active codec +static int32_t _VoipCodec_iActiveCodec = -1; + + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _VoipCodecGet + + \Description + Get the specified codec. + + \Input iCodecIdent - codec ident or VOIP_CODEC_ACTIVE + \Input *pIndex - [out, optional] storage for codec index + + \Output + VoipCodecRegT * - pointer to specified codec, or NULL + + \Version 10/10/2011 (jbrookes) +*/ +/********************************************************************************F*/ +static VoipCodecRegT *_VoipCodecGet(int32_t iCodecIdent, int32_t *pIndex) +{ + VoipCodecRegT *pRegEntry = NULL; + int32_t iCodec; + + // zero is reserved/invalid + if (iCodecIdent == 0) + { + return(NULL); + } + + // search for codec ident + if (iCodecIdent != VOIP_CODEC_ACTIVE) + { + // look up codec by ident + for (iCodec = 0; iCodec < _VoipCodec_iNumCodecs; iCodec++) + { + if (_VoipCodec_RegTable[iCodec].iCodecIdent == iCodecIdent) + { + pRegEntry = &_VoipCodec_RegTable[iCodec]; + if (pIndex != NULL) + { + *pIndex = iCodec; + } + break; + } + } + } + else if (_VoipCodec_iActiveCodec != -1) + { + pRegEntry = &_VoipCodec_RegTable[_VoipCodec_iActiveCodec]; + if (pIndex != NULL) + { + *pIndex = _VoipCodec_iActiveCodec; + } + } + + return(pRegEntry); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipCodecVadProcess16BitFrame + + \Description + Use this function to pass next frame of 16-bit voice samples to the VAD engine. + + \Input *pVoipCodecRef - codec reference pointer + \Input *pVoiceFrame - pointer to voice buffer + \Input iNumSamples - number of samples in voice frame + + \Output + uint8_t - TRUE if voice is considered silent after processing that frame, FALSE otherwise + + \Version 06/13/2012 (mclouatre) +*/ +/*************************************************************************************************F*/ +static uint8_t _VoipCodecVadProcess16BitFrame(VoipCodecRefT *pVoipCodecRef, const int16_t *pVoiceFrame, int32_t iNumSamples) +{ + int32_t iCount; + float fAmp, fPower, fPowerSum; + + fAmp = fPower = fPowerSum = 0.0f; + + // walk the frame contents and calculate the amplitude of the signal + for (iCount = 0; iCount < iNumSamples; iCount += 1) + { + fAmp = (float)abs(pVoiceFrame[iCount]); + fPowerSum += (fAmp * fAmp); + } + + fPower = (fPowerSum / (32768.0f * 32768.0f * (float)iNumSamples)); + + // save current power level for external visibility + pVoipCodecRef->fPowerLevel = fPower; + + if (fPower < pVoipCodecRef->fVadPowerThreshold) + { + if (pVoipCodecRef->iVadCumulSilenceFrames > pVoipCodecRef->iVadSilenceFramesThreshold) + { + if (!pVoipCodecRef->bVadSilent) + { + NetPrintfVerbose((pVoipCodecRef->iDebugLevel, 1, "voipcodec: silence detected (fPowerThreshold=%f, fPower=%f)\n", + pVoipCodecRef->fVadPowerThreshold, fPower)); + pVoipCodecRef->bVadSilent = TRUE; + } + } + else + { + pVoipCodecRef->iVadCumulSilenceFrames++; + } + } + else + { + if (pVoipCodecRef->bVadSilent) + { + NetPrintfVerbose((pVoipCodecRef->iDebugLevel, 1, "voipcodec: voice detected (fPowerThreshold=%f, fPower=%f)\n", + pVoipCodecRef->fVadPowerThreshold, fPower)); + pVoipCodecRef->bVadSilent = FALSE; + } + pVoipCodecRef->iVadCumulSilenceFrames = 0; + } + + return(pVoipCodecRef->bVadSilent); +} + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipCodecRegister + + \Description + Register a codec in the table. + + \Input iCodecIdent - identifier with which the codec will be tagged + \Input *pCodecDef - pointer to codec definition table + + \Version 08/04/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCodecRegister(int32_t iCodecIdent, const VoipCodecDefT *pCodecDef) +{ + int32_t iCodec; + + for (iCodec = _VoipCodec_iNumCodecs; iCodec < VOIPCODEC_MAXENTRIES; iCodec++) + { + if (_VoipCodec_RegTable[iCodec].iCodecIdent == 0) + { + NetPrintf(("voipcodec: registering codec '%C'\n", iCodecIdent)); + _VoipCodec_RegTable[iCodec].iCodecIdent = iCodecIdent; + _VoipCodec_RegTable[iCodec].pCodecDef = pCodecDef; + _VoipCodec_iNumCodecs++; + return; + } + } + + NetPrintf(("voipcodec: unable to register codec '%C'\n", iCodecIdent)); +} + +/*F********************************************************************************/ +/*! + \Function VoipCodecCreate + + \Description + Create a codec module of the given type. + + \Input iCodecIdent - identifier with which the codec will be tagged + \Input iDecodeChannels - number of decoder channels + + \Output + int32_t - zero=success, negative=failure + + \Version 08/04/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipCodecCreate(int32_t iCodecIdent, int32_t iDecodeChannels) +{ + VoipCodecRegT *pRegEntry = NULL; + int32_t iCodec = 0; + + // get codec reg entry + if ((pRegEntry = _VoipCodecGet(iCodecIdent, &iCodec)) == NULL) + { + NetPrintf(("voipcodec: codec '%C' is not registered\n", iCodecIdent)); + return(-1); + } + + // if there is an active codec, destroy it + VoipCodecDestroy(); + + // create it and return to caller + if ((pRegEntry->pCodecRef = pRegEntry->pCodecDef->pCreate(iDecodeChannels)) != NULL) + { + NetPrintf(("voipcodec: created codec '%C'\n", iCodecIdent)); + _VoipCodec_iActiveCodec = iCodec; + } + else + { + NetPrintf(("voipcodec: unable to create codec '%C'\n", iCodecIdent)); + return(-1); + } + + // initialize default values + pRegEntry->pCodecRef->fVadPowerThreshold = VOIPCODEC_DEFAULT_VAD_AMPLITUDE_THRESHOLD; + pRegEntry->pCodecRef->iVadSilenceFramesThreshold = VOIPCODEC_DEFAULT_VAD_FRAMES_THRESHOLD; + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipCodecControl + + \Description + Control codec. + + \Input iCodecIdent - codec identifier, or VOIP_CODEC_ACTIVE + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + '+vad' - enable/disable vad + 'vada' - signal level threshold used to detect silence in a single frame (in millionths) + 'vadf' - number of silence frames required for silence detection to kick in + \endverbatim + + \Version 08/09/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipCodecControl(int32_t iCodecIdent, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + VoipCodecRegT *pRegEntry; + + // make sure active codec is valid + if ((pRegEntry = _VoipCodecGet(iCodecIdent, NULL)) == NULL) + { + NetPrintf(("voipcodec: invalid codec, cannot process control message '%C'\n", iControl)); + return(-1); + } + + if (iControl == '+vad') + { + if (pRegEntry->pCodecRef->bVadEnabled != (iValue?TRUE:FALSE)) + { + NetPrintf(("voipcodec: VAD mode changed from %s to %s\n", (pRegEntry->pCodecRef->bVadEnabled?"ON":"OFF"), (iValue?"ON":"OFF"))); + pRegEntry->pCodecRef->bVadEnabled= (iValue?TRUE:FALSE); + } + return(0); + } + + #if DIRTYCODE_LOGGING + if (iControl == 'spam') + { + NetPrintf(("voipcodec: debug verbosity level changed from %d to %d\n", pRegEntry->pCodecRef->iDebugLevel, iValue)); + pRegEntry->pCodecRef->iDebugLevel = iValue; + + // do not return here, we want to pass-through to codec-specific control funct. + } + #endif + + + if (iControl == 'vada') + { + float fNewThreshold; + fNewThreshold = iValue * 0.000001f; + NetPrintf(("voipcodec: VAD signal amplitude threshold (power of) changed from %f to %f\n", pRegEntry->pCodecRef->fVadPowerThreshold, fNewThreshold)); + pRegEntry->pCodecRef->fVadPowerThreshold = fNewThreshold; + return(0); + } + + if (iControl == 'vadf') + { + NetPrintf(("voipcodec: VAD frames threshold changed from %d to %d\n", pRegEntry->pCodecRef->iVadSilenceFramesThreshold, iValue)); + pRegEntry->pCodecRef->iVadSilenceFramesThreshold = iValue; + return(0); + } + + // then call the registered codec control function + return(pRegEntry->pCodecDef->pControl(pRegEntry->pCodecRef, iControl, iValue, iValue2, pValue)); +} + +/*F********************************************************************************/ +/*! + \Function VoipCodecStatus + + \Description + Get codec status + + \Input iCodecIdent - codec identifier, or VOIP_CODEC_ACTIVE + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuffer - [out] selector-specific buffer space + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + 'coco' - returns the number of register codecs + 'plvl' - returns current voice power level (same calc as VAD threshold) + \endverbatim + + \Version 08/09/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipCodecStatus(int32_t iCodecIdent, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize) +{ + VoipCodecRegT *pRegEntry; + + // returns the number of active codecs + if (iSelect == 'coco') + { + return(_VoipCodec_iNumCodecs); + } + // make sure active codec is valid + if ((pRegEntry = _VoipCodecGet(iCodecIdent, NULL)) == NULL) + { + NetPrintf(("voipcodec: invalid codec, cannot process status message '%C'\n", iSelect)); + return(-1); + } + // returns current voice power level (same calculation as is used for VAD threshold) + if (iSelect == 'plvl') + { + return((int32_t)(pRegEntry->pCodecRef->fPowerLevel / 0.000001f)); + } + // call the registered status function + return(pRegEntry->pCodecDef->pStatus(pRegEntry->pCodecRef, iSelect, iValue, pBuffer, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function VoipCodecDestroy + + \Description + Destroy the active codec + + \Version 08/04/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCodecDestroy(void) +{ + VoipCodecRegT *pRegEntry; + + // get active codec + if ((pRegEntry = _VoipCodecGet(VOIP_CODEC_ACTIVE, NULL)) == NULL) + { + return; + } + + // call the registered destroy function + pRegEntry->pCodecRef->pCodecDef->pDestroy(pRegEntry->pCodecRef); + // clear codec ref state pointer + pRegEntry->pCodecRef = NULL; + // reset active codec index + _VoipCodec_iActiveCodec = -1; +} + +/*F********************************************************************************/ +/*! + \Function VoipCodecReset + + \Description + Resets codec state of active codec. + + \Version 08/04/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCodecReset(void) +{ + VoipCodecRegT *pRegEntry; + + // get active codec + if ((pRegEntry = _VoipCodecGet(VOIP_CODEC_ACTIVE, NULL)) == NULL) + { + return; + } + // call the registered reset function + pRegEntry->pCodecDef->pReset(pRegEntry->pCodecRef); +} + +/*F********************************************************************************/ +/*! + \Function VoipCodecEncode + + \Description + Encode input data with the active codec. + + \Input *pOutput - pointer to output buffer + \Input *pInput - pointer to input sample data + \Input iNumSamples - number of input samples + \Input *pTranscribeRef - transcription ref, if available + + \Output + int32_t - size of output data + + \Version 08/04/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipCodecEncode(uint8_t *pOutput, const int16_t *pInput, int32_t iNumSamples, VoipTranscribeRefT *pTranscribeRef) +{ + VoipCodecRegT *pRegEntry; + int32_t iResult; + #if VOIPCODEC_TIMER + uint64_t uTimer; + #endif + + // make sure active codec is valid + if ((pRegEntry = _VoipCodecGet(VOIP_CODEC_ACTIVE, NULL)) == NULL) + { + NetPrintf(("voipcodec: no active codec, cannot encode\n")); + return(0); + } + + // if VAD is enabled and we detect silence, skip the encode + if (pRegEntry->pCodecRef->bVadEnabled && (_VoipCodecVadProcess16BitFrame(pRegEntry->pCodecRef, pInput, iNumSamples))) + { + return(0); + } + + #if VOIPCODEC_TIMER + uTimer = NetTickUsec(); + #endif + + // call the registered encoder + iResult = pRegEntry->pCodecDef->pEncode(pRegEntry->pCodecRef, pOutput, pInput, iNumSamples); + + #if VOIPCODEC_TIMER + NetPrintf(("voipcodecencode: %dus\n", NetTickDiff(NetTickUsec(), uTimer))); + #endif + + // submit voice data for transcription if we were passed a transcription ref + if ((pTranscribeRef != NULL) && (iResult > 0)) + { + if (VoipTranscribeStatus(pTranscribeRef, 'cmpr', 0, NULL, 0)) + { + // transcribing compressed audio + VoipTranscribeSubmit(pTranscribeRef, pOutput, iResult); + } + else + { + // transcribing uncompressed audio + VoipTranscribeSubmit(pTranscribeRef, (const uint8_t *)pInput, iNumSamples*sizeof(*pInput)); + } + } + + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function VoipCodecDecode + + \Description + Decode input data with the active codec. + + \Input *pOutput - pointer to output buffer + \Input *pInput - pointer to input compressed data + \Input iInputBytes - size in bytes of input data + \Input iChannel - the input channel whose data we are decoding + + \Output + int32_t - number of output samples + + \Version 08/04/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipCodecDecode(int32_t *pOutput, const uint8_t *pInput, int32_t iInputBytes, int32_t iChannel) +{ + VoipCodecRegT *pRegEntry; + int32_t iResult; + #if VOIPCODEC_TIMER + uint64_t uTimer = NetTickUsec(); + #endif + + // make sure active codec is valid + if ((pRegEntry = _VoipCodecGet(VOIP_CODEC_ACTIVE, NULL)) == NULL) + { + NetPrintf(("voipcodec: no active codec, cannot decode\n")); + return(0); + } + + // call the registered decoder + iResult = pRegEntry->pCodecDef->pDecode(pRegEntry->pCodecRef, pOutput, pInput, iInputBytes, iChannel); + + #if VOIPCODEC_TIMER + NetPrintf(("voipcodecdecode: %dus\n", NetTickDiff(NetTickUsec(), uTimer))); + #endif + + return(iResult); +} + diff --git a/src/thirdparty/dirtysdk/source/voip/voipcommon.c b/src/thirdparty/dirtysdk/source/voip/voipcommon.c new file mode 100644 index 00000000..f91e3773 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipcommon.c @@ -0,0 +1,2155 @@ +/*H********************************************************************************/ +/*! + \File voipcommon.c + + \Description + Cross-platform voip data types and functions. + + \Copyright + Copyright (c) 2009 Electronic Arts Inc. + + \Version 12/02/2009 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#ifdef _XBOX +#include +#include +#endif + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/netconn.h" + +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" + +#include "DirtySDK/voip/voip.h" +#include "DirtySDK/voip/voipblocklist.h" + +/*** Defines **********************************************************************/ +#define VOIPCOMMON_CODEDCHAN_ALL (0) //! Special handling for codeded channel {(0,none),(0,none),(0,none),(0,none)} = talk/listen to everyone +#define VOIPCOMMON_CODEDCHAN_NONE (0x01010101) //! Special handling for codeded channel {(1,none),(1,none),(1,none),(1,none)} = can't talk or listen to anyone + +/*** Type Definitions *************************************************************/ + + +/*** Variables ********************************************************************/ + +//! pointer to module state +static VoipRefT *_Voip_pRef = NULL; + +//! voip memgroup +static int32_t _Voip_iMemGroup='dflt'; + +//! voip memgroup userdata +static void *_Voip_pMemGroupUserData=NULL; + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! +\Function _VoipCommonTextDataCb + + \Description + Callback to handle locally generated transcribed text. + + \Input *pStrUtf8 - pointer to text data + \Input uLocalUserIndex - local user index + \Input *pUserData - pointer to callback user data + + \Version 10/29/2018 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipCommonTextDataCb(const char *pStrUtf8, uint32_t uLocalUserIndex, void *pUserData) +{ + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pUserData; + + VoipConnectionReliableTranscribedTextMessage(&pVoipCommon->Connectionlist, uLocalUserIndex, pStrUtf8); + /* We also want locally generated transcribed text shown to its originator if that local user has + also requested transcribed text from remote users. Motivation: a hearing-impaired person requesting + transcribed text from other players also wants to see transcribed text for his own speech + (request from EA's accessibility team). + + Note on XBoxOne this data will come through _VoipCommonReceiveTextDataCb as it would for remote generated text. + */ + if (pVoipCommon->Connectionlist.bTranscribedTextRequested[uLocalUserIndex] == TRUE) + { + if (pVoipCommon->pDisplayTranscribedTextCb != NULL) + { + pVoipCommon->pDisplayTranscribedTextCb(-1, uLocalUserIndex, pStrUtf8, pVoipCommon->pDisplayTranscribedTextUserData); + } + } +} + +/*F********************************************************************************/ +/*! +\Function _VoipCommonMicDataCb + + \Description + Callback to handle locally acquired mic data. + + \Input *pVoiceData - pointer to mic data + \Input iDataSize - size of mic data + \Input *pMetaData - pointer to platform-specific metadata (can be NULL) + \Input iMetaDataSize - size of platform-specifici metadata + \Input uLocalUserIndex - local user index + \Input uSendSeqn - send sequence + \Input *pUserData - pointer to callback user data + + \Version 10/29/2018 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipCommonMicDataCb(const void *pVoiceData, int32_t iDataSize, const void *pMetaData, int32_t iMetaDataSize, uint32_t uLocalUserIndex, uint8_t uSendSeqn, void *pUserData) +{ + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pUserData; + + if (pVoipCommon != NULL) + { + VoipConnectionSend(&pVoipCommon->Connectionlist, 0xFFFFFFFF, (uint8_t *)pVoiceData, iDataSize, (uint8_t *)pMetaData, iMetaDataSize, uLocalUserIndex, uSendSeqn); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipCommonOpaqueDataCb + + \Description + Callback to handle locally generated opaque data to be sent reliably to other voip peers. + + \Input pOpaqueData - data buffer + \Input iOpaqueDataSize - opaque data size in bytes + \Input uSendMask - mask identifying which connections to send the data to + \Input bReliable - TRUE if reliable transmission needed, FALSE otherwise + \Input uSendSeqn - send sequence + \Input *pUserData - user data + + \Version 11/22/2018 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipCommonOpaqueDataCb(const uint8_t *pOpaqueData, int32_t iOpaqueDataSize, uint32_t uSendMask, uint8_t bReliable, uint8_t uSendSeqn, void *pUserData) +{ + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pUserData; + + int32_t iConnID; + + if (bReliable) + { + for (iConnID = 0; iConnID < pVoipCommon->Connectionlist.iMaxConnections; iConnID++) + { + if (uSendMask & (1 << iConnID)) + { + VoipConnectionReliableSendOpaque(&pVoipCommon->Connectionlist, iConnID, pOpaqueData, iOpaqueDataSize); + } + } + } + else + { + /* The xboxone unreliable date frames are opaque to us. Consequently, we do not know if they contain encode voice from a specific + local user or from multiple local users. Therfore, we always invoke VoipConnectionSend() with the local user index being + VOIP_SHARED_USER_INDEX. By doing so, we guarantee that VOIP_REMOTE_USER_RECVVOICE and VOIP_LOCAL_USER_SENDVOICE gets updated + consistently for all participating users on a given console. We loose the per-user granularity for that flag, but it's + the best we can do. */ + VoipConnectionSend(&pVoipCommon->Connectionlist, uSendMask, pOpaqueData, iOpaqueDataSize, NULL, 0, VOIP_SHARED_USER_INDEX, uSendSeqn); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipCommonEncode + + \Description + Packs the channel selection for sending to a remote machine + + \Input iChannels[] - the array of channels to encode + \Input uModes[] - the array of modes to encode + + \Output + uint32_t - the coded channels + + \Version 12/07/2009 (jrainy) +*/ +/********************************************************************************F*/ +static uint32_t _VoipCommonEncode(int32_t iChannels[], uint32_t uModes[]) +{ + uint32_t codedChannel = 0; + int32_t iIndex; + + for(iIndex = 0; iIndex < VOIP_MAX_CONCURRENT_CHANNEL; iIndex++) + { + codedChannel |= (iChannels[iIndex] & 0x3f) << (8 * iIndex); + codedChannel |= ((uModes[iIndex] & 0x3) << 6) << (8 * iIndex); + } + + return(codedChannel); +} + +/*F********************************************************************************/ +/*! + \Function _VoipCommonDecode + + \Description + Unpacks the channel selection received by a remote machine + + \Input uCodedChannel - the coded channels + \Input iChannels[] - the array of channels to fill up + \Input uModes[] - the array of modes to fill up + + \Version 12/07/2009 (jrainy) +*/ +/********************************************************************************F*/ +static void _VoipCommonDecode(uint32_t uCodedChannel, int32_t iChannels[], uint32_t uModes[]) +{ + int32_t iIndex; + for(iIndex = 0; iIndex < VOIP_MAX_CONCURRENT_CHANNEL; iIndex++) + { + iChannels[iIndex] = (uCodedChannel >> (8 * iIndex)) & 0x3f; + uModes[iIndex] = ((uCodedChannel >> (8 * iIndex)) >> 6) & 0x3; + } +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonProcessChannelChange + + \Description + Unpacks the channel selection received by a remote machine + + \Input pVoipCommon - voip common state + \Input iConnId - connection id + + \Version 12/07/2009 (jrainy) +*/ +/********************************************************************************F*/ +void VoipCommonProcessChannelChange(VoipCommonRefT *pVoipCommon, int32_t iConnId) +{ + int32_t iUserIndex; + + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++) + { + uint32_t uInputOuputVar; + #if DIRTYCODE_LOGGING + uint32_t uNewChannelConfig, uOldChannelConfig; + #endif + + uInputOuputVar = (uint32_t)iUserIndex; + VoipStatus(VoipGetRef(), 'rchn', iConnId, &uInputOuputVar, sizeof(uInputOuputVar)); + + #if DIRTYCODE_LOGGING + uNewChannelConfig = uInputOuputVar; + uOldChannelConfig = pVoipCommon->uRemoteChannelSelection[iConnId][iUserIndex]; + + NetPrintf(("voipcommon: got remote channels 0x%08x (old config: 0x%08x)for user index %d on low-level conn id %d\n", + uNewChannelConfig, uOldChannelConfig, iUserIndex, iConnId)); + #endif + + pVoipCommon->uRemoteChannelSelection[iConnId][iUserIndex] = uInputOuputVar; + } + + VoipCommonApplyChannelConfig(pVoipCommon); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipIdle + + \Description + NetConn idle function to update the Voip module. + + This function is designed to handle issuing user callbacks based on events such as + headset insertion and removal. It is implemented as a NetConn idle function instead + of as a part of the Voip thread so that callbacks will be generated from the same + thread that DirtySock operates in. + + \Input *pData - pointer to module state + \Input uTick - current tick count + + \Notes + This function is installed as a NetConn Idle function. NetConnIdle() + must be regularly polled for this function to be called. + + \Version 02/10/2006 (jbrookes) +*/ +/*************************************************************************************************F*/ +static void _VoipIdle(void *pData, uint32_t uTick) +{ + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pData; + int32_t iIndex; + + // check for state changes and trigger callback + if (pVoipCommon->uLastHsetStatus != pVoipCommon->uPortHeadsetStatus) + { + pVoipCommon->pCallback((VoipRefT *)pData, VOIP_CBTYPE_HSETEVENT, pVoipCommon->uPortHeadsetStatus, pVoipCommon->pUserData); + pVoipCommon->uLastHsetStatus = pVoipCommon->uPortHeadsetStatus; + } + + if (pVoipCommon->uLastFrom != pVoipCommon->Connectionlist.uRecvVoice) + { + pVoipCommon->pCallback((VoipRefT *)pData, VOIP_CBTYPE_FROMEVENT, pVoipCommon->Connectionlist.uRecvVoice, pVoipCommon->pUserData); + pVoipCommon->uLastFrom = pVoipCommon->Connectionlist.uRecvVoice; + } + + if (pVoipCommon->uLastTtsStatus != pVoipCommon->uTtsStatus) + { + pVoipCommon->pCallback((VoipRefT *)pData, VOIP_CBTYPE_TTOSEVENT, pVoipCommon->uTtsStatus, pVoipCommon->pUserData); + pVoipCommon->uLastTtsStatus = pVoipCommon->uTtsStatus; + } + + for (iIndex = 0; iIndex < VOIP_MAXLOCALUSERS; ++iIndex) + { + if (pVoipCommon->uLastLocalStatus[iIndex] != pVoipCommon->Connectionlist.uLocalUserStatus[iIndex]) + { + if ((pVoipCommon->uLastLocalStatus[iIndex] ^ pVoipCommon->Connectionlist.uLocalUserStatus[iIndex]) & VOIP_LOCAL_USER_SENDVOICE) + { + pVoipCommon->pCallback((VoipRefT *)pData, VOIP_CBTYPE_SENDEVENT, pVoipCommon->Connectionlist.uLocalUserStatus[iIndex] & VOIP_LOCAL_USER_SENDVOICE, pVoipCommon->pUserData); + } + pVoipCommon->uLastLocalStatus[iIndex] = pVoipCommon->Connectionlist.uLocalUserStatus[iIndex]; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipCommonChannelMatch + + \Description + Returns whether a given channel selection is to be received and/or sent to + + \Input *pVoipCommon - voip common state + \Input iUserIndex - local user index + \Input uCodedChannel - coded channels of remote user + + \Output + uint32_t - the coded channels + + \Version 12/07/2009 (jrainy) +*/ +/********************************************************************************F*/ +static VoipChanModeE _VoipCommonChannelMatch(VoipCommonRefT *pVoipCommon, int32_t iUserIndex, uint32_t uCodedChannel) +{ + int32_t iIndexRemote; + int32_t iIndexLocal; + VoipChanModeE eMode = 0; + int32_t iChannels[VOIP_MAX_CONCURRENT_CHANNEL]; + uint32_t uModes[VOIP_MAX_CONCURRENT_CHANNEL]; + + // Voip traffic sent on channels {(0,none),(0,none),(0,none),(0,none)} = talk/listen to everyone = VOIPCOMMON_CODEDCHAN_ALL + // Voip traffic blocked on channels {(1,none),(1,none),(1,none),(1,none)} = can't talk or listen = VOIPCOMMON_CODEDCHAN_NONE + // is assumed to be meant for everybody. + // enables default behaviour for teams not using channels. + if ((uCodedChannel == VOIPCOMMON_CODEDCHAN_ALL) || (pVoipCommon->uLocalChannelSelection[iUserIndex] == VOIPCOMMON_CODEDCHAN_ALL)) + { + return(VOIP_CHANSEND|VOIP_CHANRECV); + } + else if ((uCodedChannel == VOIPCOMMON_CODEDCHAN_NONE) || (pVoipCommon->uLocalChannelSelection[iUserIndex] == VOIPCOMMON_CODEDCHAN_NONE)) + { + return(VOIP_CHANNONE); + } + + _VoipCommonDecode(uCodedChannel, iChannels, uModes); + + // for all our channels, and all the channels of the remote party + for(iIndexRemote = 0; iIndexRemote < VOIP_MAX_CONCURRENT_CHANNEL; iIndexRemote++) + { + for(iIndexLocal = 0; iIndexLocal < VOIP_MAX_CONCURRENT_CHANNEL; iIndexLocal++) + { + // if their channel numbers match + if (pVoipCommon->iLocalChannels[iUserIndex][iIndexLocal] == iChannels[iIndexRemote]) + { + // if we're sending on it and they're receiving on it + if ((pVoipCommon->uLocalModes[iUserIndex][iIndexLocal] & VOIP_CHANSEND) && (uModes[iIndexRemote] & VOIP_CHANRECV)) + { + // let's send + eMode |= VOIP_CHANSEND; + } + // if we're receiving on it and they're send on it + if ((pVoipCommon->uLocalModes[iUserIndex][iIndexLocal] & VOIP_CHANRECV) && (uModes[iIndexRemote] & VOIP_CHANSEND)) + { + // let's receive + eMode |= VOIP_CHANRECV; + } + } + } + } + + return(eMode); +} + + +/*F********************************************************************************/ +/*! + \Function _VoipCommonComputeEffectiveUserSendMask + + \Description + Compute effective user send mask from two other masks: user-selected + send mask and mask obtained from voip channel config + + \Input *pVoipCommon - voip common state + + \Version 12/07/2009 (jrainy) +*/ +/********************************************************************************F*/ +static void _VoipCommonComputeEffectiveUserSendMask(VoipCommonRefT *pVoipCommon) +{ + VoipCommonSetMask(&pVoipCommon->Connectionlist.uUserSendMask, pVoipCommon->uUserMicrValue & pVoipCommon->uChanSendMask, "usendmask"); + #if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + pVoipCommon->Connectionlist.bApplyRelFromMuting = TRUE; // to reflect these changes in the communication relationship + #endif +} + +/*F********************************************************************************/ +/*! + \Function _VoipCommonComputeDefaultSharedChannelConfig + + \Description + Computes the default shared channel config. This is the logical "and" of all + local user's channel modes + + \Input *pVoipCommon - voip common state + + \Notes + Loops through all the channels and "and" the channel mode of all the valid users on the + that particular channel together. Only if everyone is subcribed to the channel do we set + the mode for the shared user. + + \Version 12/11/2014 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipCommonComputeDefaultSharedChannelConfig(VoipCommonRefT *pVoipCommon) +{ + // only bother to do any of this if we are in fact using the "default shared channel config", which is more of a behavior than a static thing + if (pVoipCommon->bUseDefaultSharedChannelConfig) + { + int32_t iLocalUserIndex; + int32_t iChannel; + int32_t iChannelSlot; + int32_t iEmptySlot = 0; + + _VoipCommonDecode(VOIPCOMMON_CODEDCHAN_NONE, pVoipCommon->iDefaultSharedChannels, pVoipCommon->uDefaultSharedModes); + + // loop through all the channel from 1 to 63 + for (iChannel = 1; iChannel < VOIP_MAXLOCALCHANNEL; ++iChannel) + { + int32_t iValidUser = 0; + int32_t iChannelSubscriber = 0; + int32_t iChannelAllUser = 0; + uint32_t uSharedChannelMode = VOIP_CHANSENDRECV; + + // only compute the shared channel config if we still have a free slot + if (iEmptySlot != VOIP_MAX_CONCURRENT_CHANNEL) + { + // loop through all the local users excluding the shared user + for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS; ++iLocalUserIndex) + { + if (!VOIP_NullUser((VoipUserT *)&pVoipCommon->Connectionlist.LocalUsers[iLocalUserIndex])) + { + ++iValidUser; + + // the user is valid loop through his subscribed channels + for (iChannelSlot = 0; iChannelSlot < VOIP_MAX_CONCURRENT_CHANNEL; ++iChannelSlot) + { + // check to see if we are in an VOIPCOMMON_CODEDCHAN_ALL state, in which case consider treating the shared user as a VOIPCOMMON_CODEDCHAN_ALL + if (_VoipCommonEncode(pVoipCommon->iLocalChannels[iLocalUserIndex], pVoipCommon->uLocalModes[iLocalUserIndex]) == VOIPCOMMON_CODEDCHAN_ALL) + { + ++iChannelAllUser; + break; + } + // check to see if the user is actually a member of the channel, in which case consider using this channel for the shared user too + else if (pVoipCommon->iLocalChannels[iLocalUserIndex][iChannelSlot] == iChannel) + { + ++iChannelSubscriber; + uSharedChannelMode &= pVoipCommon->uLocalModes[iLocalUserIndex][iChannelSlot]; + break; + } + } + } + } + + // if everyone is all open, then the shared channel is all open too + if ((iValidUser == iChannelAllUser) && (iChannelAllUser != 0)) + { + _VoipCommonDecode(VOIPCOMMON_CODEDCHAN_ALL, pVoipCommon->iDefaultSharedChannels, pVoipCommon->uDefaultSharedModes); + } + // if everyone is subscribed to the channel (or all open) then the uSharedChannelMode is valid + else if ((iValidUser == (iChannelSubscriber + iChannelAllUser)) && (iChannelSubscriber != 0)) + { + // if we were set to VOIPCOMMON_CODEDCHAN_NONE but now we are going to make changes, clear out to VOIPCOMMON_CODEDCHAN_ALL first + if (_VoipCommonEncode(pVoipCommon->iDefaultSharedChannels, pVoipCommon->uDefaultSharedModes) == VOIPCOMMON_CODEDCHAN_NONE) + { + _VoipCommonDecode(VOIPCOMMON_CODEDCHAN_ALL, pVoipCommon->iDefaultSharedChannels, pVoipCommon->uDefaultSharedModes); + } + pVoipCommon->iDefaultSharedChannels[iEmptySlot] = iChannel; + pVoipCommon->uDefaultSharedModes[iEmptySlot] = uSharedChannelMode; + ++iEmptySlot; + } + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipCommonComputeEffectiveUserRecvMask + + \Description + Compute effective user recv mask from two other masks: user-selected + recv mask and mask obtained from voip channel config + + \Input *pVoipCommon - voip common state + + \Version 12/07/2009 (jrainy) +*/ +/********************************************************************************F*/ +static void _VoipCommonComputeEffectiveUserRecvMask(VoipCommonRefT *pVoipCommon) +{ + VoipCommonSetMask(&pVoipCommon->Connectionlist.uUserRecvMask, pVoipCommon->uUserSpkrValue & pVoipCommon->uChanRecvMask, "urecvmask"); + #if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + pVoipCommon->Connectionlist.bApplyRelFromMuting = TRUE; // to reflect these changes in the communication relationship + #endif +} + +/*F********************************************************************************/ +/*! + \Function _VoipCommonSetChannels + + \Description + Set the active voip channel (coded value of channels and modes) + + \Input *pVoipCommon - voip common state + \Input uChannels - channels value we are setting for the user + \Input iUserIndex - index of user we are performing the operation on + + \Output + int32_t - zero=success, negative=failure + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _VoipCommonSetChannels(VoipCommonRefT *pVoipCommon, uint32_t uChannels, int32_t iUserIndex) +{ + if ((iUserIndex >= 0) && (iUserIndex < VOIP_MAXLOCALUSERS)) + { + NetPrintf(("voipcommon: setting voip channels (0x%08x) for local user index %d\n", uChannels, iUserIndex)); + pVoipCommon->Connectionlist.aChannels[iUserIndex] = uChannels; + return(0); + } + else if (iUserIndex == VOIP_SHARED_USER_INDEX) + { + NetPrintf(("voipcommon: setting voip channels (0x%08x) for local shared user\n", uChannels)); + pVoipCommon->Connectionlist.aChannels[iUserIndex] = uChannels; + return(0); + } + else + { + NetPrintf(("voipcommon: warning - setting voip channels for invalid local user index %d\n", iUserIndex)); + return(-1); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipCommonSetSharedUserChannelConfig + + \Description + Set the shared user channel config to custom or default based on the + bUseDefaultSharedChannelConfig flag. + + \Input *pVoipCommon - voip common state + + \Version 12/16/2014 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipCommonSetSharedUserChannelConfig(VoipCommonRefT *pVoipCommon) +{ + int32_t iMaxConcurrentChannel; + int32_t iSharedUserIndex = VOIP_SHARED_USER_INDEX; + + for (iMaxConcurrentChannel = 0; iMaxConcurrentChannel < VOIP_MAX_CONCURRENT_CHANNEL; ++iMaxConcurrentChannel) + { + if (pVoipCommon->bUseDefaultSharedChannelConfig) + { + pVoipCommon->iLocalChannels[iSharedUserIndex][iMaxConcurrentChannel] = pVoipCommon->iDefaultSharedChannels[iMaxConcurrentChannel]; + pVoipCommon->uLocalModes[iSharedUserIndex][iMaxConcurrentChannel] = pVoipCommon->uDefaultSharedModes[iMaxConcurrentChannel]; + } + else + { + pVoipCommon->iLocalChannels[iSharedUserIndex][iMaxConcurrentChannel] = pVoipCommon->iCustomSharedChannels[iMaxConcurrentChannel]; + pVoipCommon->uLocalModes[iSharedUserIndex][iMaxConcurrentChannel] = pVoipCommon->uCustomSharedModes[iMaxConcurrentChannel]; + } + } + + // encdoe the channel selection and set the active voip channels + pVoipCommon->uLocalChannelSelection[iSharedUserIndex] = _VoipCommonEncode(pVoipCommon->iLocalChannels[iSharedUserIndex], pVoipCommon->uLocalModes[iSharedUserIndex]); + _VoipCommonSetChannels(pVoipCommon, pVoipCommon->uLocalChannelSelection[iSharedUserIndex], iSharedUserIndex); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipCommonCallback + + \Description + Default (empty) user callback function. + + \Input *pVoip - voip module state + \Input eCbType - type of event + \Input iValue - event-specific information + \Input *pUserData - callback user data + + \Output + None. + + \Version 02/10/2006 (jbrookes) +*/ +/*************************************************************************************************F*/ +static void _VoipCommonCallback(VoipRefT *pVoip, VoipCbTypeE eCbType, int32_t iValue, void *pUserData) +{ +} + +#if defined(DIRTYCODE_PS4) +/*F********************************************************************************/ +/*! + \Function _VoipCommonDisplayRemoteTextDataCb + + \Description + Callback to handle receiving a transcribed text packet from a remote peer. + (once permission check has been validated in voipheadset) + + \Input *pRemoteUser - transcribed text's originator + \Input *pStrUtf - transcribed text + \Input *pUserData - user data + + \Version 04/04/2019 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipCommonDisplayRemoteTextDataCb(const VoipUserT *pRemoteUser, const char *pStrUtf8, void *pUserData) +{ + int32_t iConnectionIndex, iRemoteUserIndex = 0; + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pUserData; + uint32_t bFound = FALSE; + + for (iConnectionIndex = 0; iConnectionIndex < pVoipCommon->Connectionlist.iMaxConnections; iConnectionIndex++) + { + VoipConnectionT *pConnection = &pVoipCommon->Connectionlist.pConnections[iConnectionIndex]; + + for (iRemoteUserIndex = 0; iRemoteUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iRemoteUserIndex++) + { + if (VOIP_SameUser(pRemoteUser, (VoipUserT *)&pConnection->RemoteUsers[iRemoteUserIndex])) + { + bFound = TRUE; + break; + } + } + + if (bFound == TRUE) + { + break; + } + } + + if (bFound == TRUE) + { + if (pVoipCommon->pDisplayTranscribedTextCb != NULL) + { + pVoipCommon->pDisplayTranscribedTextCb(iConnectionIndex, iRemoteUserIndex, pStrUtf8, pVoipCommon->pDisplayTranscribedTextUserData); + } + } + else + { + NetPrintf(("voipcommon: failed to find matching remote user for originator of transcribed text\n")); + } +} +#endif + +#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) +/*F********************************************************************************/ +/*! + \Function _VoipCommonReceiveTextDataCb + + \Description + Callback to handle receiving a transcribed text packet from a remote peer. + + \Input *pSourceUser - transcribed text's originator + \Input *pStrUtf - transcribed text + \Input *pUserData - user data + + \Version 05/02/2017 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipCommonReceiveTextDataNativeCb(const VoipUserT *pSourceUser, const char *pStrUtf8, void *pUserData) +{ + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pUserData; + + if (pVoipCommon->pDisplayTranscribedTextCb != NULL) + { + int32_t iConnectionIndex, iUserIndex; + + // see if a local user sent this text (seeing what they said) + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS; iUserIndex++) + { + int64_t iLocalPersonaId; + if (NetConnStatus('peid', iUserIndex, &iLocalPersonaId, sizeof(iLocalPersonaId)) == 0) + { + if (iLocalPersonaId == pSourceUser->AccountInfo.iPersonaId) + { + pVoipCommon->pDisplayTranscribedTextCb(-1, iUserIndex, pStrUtf8, pVoipCommon->pDisplayTranscribedTextUserData); + return; + } + } + } + + // search for a remote user who sent this text + for (iConnectionIndex = 0; iConnectionIndex < pVoipCommon->Connectionlist.iMaxConnections; iConnectionIndex++) + { + VoipConnectionT *pConnection = &pVoipCommon->Connectionlist.pConnections[iConnectionIndex]; + + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++) + { + if (VOIP_SameUser(pSourceUser, &pConnection->RemoteUsers[iUserIndex])) + { + pVoipCommon->pDisplayTranscribedTextCb(iConnectionIndex, iUserIndex, pStrUtf8, pVoipCommon->pDisplayTranscribedTextUserData); + return; + } + } + } + + // we didn't find a local or remote user who sent this text, just drop it but complain + NetPrintf(("voipcommon: failed to find matching originator of transcribed text; user: %lld, text: %s\n", pSourceUser->AccountInfo.iPersonaId, pStrUtf8)); + } +} +#endif + +/*F********************************************************************************/ +/*! + \Function _VoipCommonReceiveTextDataCb + + \Description + Callback to handle receiving a transcribed text packet from a remote peer. + + \Input iConnId - connection index + \Input iRemoteUserIndex - index of remote user + \Input *pStrUtf8 - pointer to beginning of text + \Input *pUserData - user data + + \Version 05/02/2017 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipCommonReceiveTextDataCb(int32_t iConnId, int32_t iRemoteUserIndex, const char *pStrUtf8, void *pUserData) +{ + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)pUserData; + + if (pVoipCommon->pDisplayTranscribedTextCb != NULL) + { + #if defined(DIRTYCODE_PS4) + VoipUserT VoipUser; + uint32_t uConnUserPair; + + // initialize variable used with VoipStatus('rcvu'). most-significant 16 bits = remote user index, least-significant 16 bits = conn index + uConnUserPair = iConnId; + uConnUserPair |= (iRemoteUserIndex << 16); + + // find out if there is a valid remote user for that connId/userId pair + if (VoipStatus(VoipGetRef(), 'rcvu', (int32_t)uConnUserPair, &VoipUser, sizeof(VoipUser)) == 0) + { + VoipHeadsetTranscribedTextPermissionCheck(pVoipCommon->pHeadset, &VoipUser, pStrUtf8); + } + else + { + NetPrintf(("voipcommon: 'transcribed text received' event dropped because voipheadset can't find remote user index %d on connection %d\n", iRemoteUserIndex, iConnId)); + } + #else + pVoipCommon->pDisplayTranscribedTextCb(iConnId, iRemoteUserIndex, pStrUtf8, pVoipCommon->pDisplayTranscribedTextUserData); + #endif + + } +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipCommonGetRef + + \Description + Return current module reference. + + \Output + VoipRefT * - reference pointer, or NULL if the module is not active + + \Version 03/08/2004 (jbrookes) +*/ +/********************************************************************************F*/ +VoipRefT *VoipCommonGetRef(void) +{ + // return pointer to module state + return(_Voip_pRef); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonStartup + + \Description + Start up common functionality + + \Input iMaxPeers - maximum number of peers supported (up to VOIP_MAXCONNECT) + \Input iVoipRefSize - size of voip ref to allocate + \Input *pStatusCb - headset status callback + \Input iData - platform-specific + + \Output + VoipRefT * - voip ref if successful; else NULL + + \Version 12/02/2009 (jbrookes) +*/ +/********************************************************************************F*/ +VoipRefT *VoipCommonStartup(int32_t iMaxPeers, int32_t iVoipRefSize, VoipHeadsetStatusCbT *pStatusCb, int32_t iData) +{ + int32_t iMemGroup; + void *pMemGroupUserData; + VoipCommonRefT *pVoipCommon; + int32_t i; + + // make sure we're not already started + if (_Voip_pRef != NULL) + { + NetPrintf(("voipcommon: module startup called when not in a shutdown state\n")); + return(NULL); + } + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // create and initialize module state + if ((pVoipCommon = (VoipCommonRefT *)DirtyMemAlloc(iVoipRefSize, VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipcommon: unable to allocate module state\n")); + return(NULL); + } + ds_memclr(pVoipCommon, iVoipRefSize); + + // set voip memgroup + VoipCommonMemGroupSet(iMemGroup, pMemGroupUserData); + + // by default, user-selected micr and spkr flag always default to ON + pVoipCommon->uUserMicrValue = 0xFFFFFFFF; + pVoipCommon->uUserSpkrValue = 0xFFFFFFFF; + + // create thread critical section + NetCritInit(&pVoipCommon->ThreadCrit, "voip"); + + // create block list + if ((pVoipCommon->pBlockList = VoipBlockListCreate()) == NULL) + { + NetPrintf(("voipcommon: unable to allocate block list\n")); + VoipCommonShutdown(pVoipCommon); + return(NULL); + } + + // create connection list + if (VoipConnectionStartup(&pVoipCommon->Connectionlist, iMaxPeers) < 0) + { + NetPrintf(("voipcommon: unable to allocate connectionlist\n")); + VoipCommonShutdown(pVoipCommon); + return(NULL); + } + + // create headset module + if ((pVoipCommon->pHeadset = VoipHeadsetCreate(iMaxPeers, &_VoipCommonMicDataCb, &_VoipCommonTextDataCb, &_VoipCommonOpaqueDataCb, pStatusCb, pVoipCommon, iData)) == NULL) + { + NetPrintf(("voipcommon: unable to create VoipHeadset layer\n")); + VoipCommonShutdown(pVoipCommon); + return(NULL); + } + + // turn on default shared channel config by default + pVoipCommon->bUseDefaultSharedChannelConfig = TRUE; + + // set up connectionlist callback interface +#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + VoipConnectionSetCallbacks(&pVoipCommon->Connectionlist, &VoipHeadsetReceiveVoiceDataCb, (void *)pVoipCommon->pHeadset, + NULL, NULL, + &VoipHeadsetRegisterUserCb, (void *)pVoipCommon->pHeadset, + &VoipHeadsetReceiveOpaqueDataCb, (void *)pVoipCommon->pHeadset); + + VoipHeadsetSetTranscribedTextReceivedCallback(pVoipCommon->pHeadset, &_VoipCommonReceiveTextDataNativeCb, (void *)pVoipCommon); +#else + #if defined(DIRTYCODE_PS4) + VoipHeadsetSetTranscribedTextReceivedCallback(pVoipCommon->pHeadset, &_VoipCommonDisplayRemoteTextDataCb, (void *)pVoipCommon); + #endif + + VoipConnectionSetCallbacks(&pVoipCommon->Connectionlist, &VoipHeadsetReceiveVoiceDataCb, (void *)pVoipCommon->pHeadset, + &_VoipCommonReceiveTextDataCb, (void *)pVoipCommon, + &VoipHeadsetRegisterUserCb, (void *)pVoipCommon->pHeadset, + NULL, (void *)pVoipCommon->pHeadset); +#endif + + // add callback idle function + VoipCommonSetEventCallback(pVoipCommon, NULL, NULL); + pVoipCommon->uLastFrom = pVoipCommon->Connectionlist.uRecvVoice; + for (i = 0; i < VOIP_MAXLOCALUSERS; ++i) + { + pVoipCommon->uLastLocalStatus[i] = pVoipCommon->Connectionlist.uLocalUserStatus[i]; + } + NetConnIdleAdd(_VoipIdle, pVoipCommon); + + // save ref + _Voip_pRef = (VoipRefT *)pVoipCommon; + return(_Voip_pRef); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonShutdown + + \Description + Shutdown common functionality + + \Input *pVoipCommon - common module state + + \Version 12/02/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCommonShutdown(VoipCommonRefT *pVoipCommon) +{ + void *pMemGroupUserData; + int32_t iMemGroup; + + // del callback idle function + if (_Voip_pRef) + { + NetConnIdleDel(_VoipIdle, pVoipCommon); + } + + // destroy headset module + if (pVoipCommon->pHeadset) + { + VoipHeadsetDestroy(pVoipCommon->pHeadset); + } + + // shut down connectionlist + if (pVoipCommon->Connectionlist.pConnections) + { + VoipConnectionShutdown(&pVoipCommon->Connectionlist); + } + + // destroy the block list + if (pVoipCommon->pBlockList) + { + VoipBlockListDestroy((VoipRefT *)pVoipCommon); + } + + // destroy critical section + NetCritKill(&pVoipCommon->ThreadCrit); + + // free module memory + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + DirtyMemFree(pVoipCommon, VOIP_MEMID, iMemGroup, pMemGroupUserData); + + // clear pointer to module state + _Voip_pRef = NULL; +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonMemGroupQuery + + \Description + Get VoIP mem group data. + + \Input *pMemGroup - [OUT param] pointer to variable to be filled with mem group id + \Input **ppMemGroupUserData - [OUT param] pointer to variable to be filled with pointer to user data + + \Version 1.0 11/11/2005 (jbrookes) First Version + \Version 1.1 11/18/2008 (mclouatre) returned values now passed in [OUT] parameters +*/ +/********************************************************************************F*/ +void VoipCommonMemGroupQuery(int32_t *pMemGroup, void **ppMemGroupUserData) +{ + *pMemGroup = _Voip_iMemGroup; + *ppMemGroupUserData = _Voip_pMemGroupUserData; +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonMemGroupSet + + \Description + Set VoIP mem group data. + + \Input iMemGroup - mem group to set + \Input *pMemGroupUserData - user data associated with mem group + + \Version 1.0 11/11/2005 (jbrookes) First Version + \Version 1.1 11/18/2008 (mclouatre) Adding second parameter (user data) +*/ +/********************************************************************************F*/ +void VoipCommonMemGroupSet(int32_t iMemGroup, void *pMemGroupUserData) +{ + _Voip_iMemGroup = iMemGroup; + _Voip_pMemGroupUserData = pMemGroupUserData; +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonUpdateRemoteStatus + + \Description + Process mute list, and set appropriate flags/priority for each remote user. + + \Input *pVoipCommon - pointer to module state + + \Version 08/23/2005 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCommonUpdateRemoteStatus(VoipCommonRefT *pVoipCommon) +{ + uint32_t uSendMask, uRecvMask, uChannelMask, bEnabled; + int32_t iConnection; + int32_t bTextTranscriptionEnabled = FALSE; + int32_t iLocalUserIndex; + + // process all active channels + for (iConnection = 0; iConnection < pVoipCommon->Connectionlist.iMaxConnections; iConnection++) + { + VoipConnectionT *pConnection = &pVoipCommon->Connectionlist.pConnections[iConnection]; + + // if not active, don't process + if (pConnection->eState != ST_ACTV) + { + continue; + } + + // calculate channel mask + uChannelMask = 1 << iConnection; + + // decide whether this channel should be enabled or not + bEnabled = pVoipCommon->bPrivileged; + + // set send/recv masks based on enable + user send override + if (bEnabled && ((pVoipCommon->Connectionlist.uUserSendMask & uChannelMask) != 0)) + { + uSendMask = pVoipCommon->Connectionlist.uSendMask | uChannelMask; + } + else + { + uSendMask = pVoipCommon->Connectionlist.uSendMask & ~uChannelMask; + } + + // set send/recv masks based on enable + user recv override + if (bEnabled && ((pVoipCommon->Connectionlist.uUserRecvMask & uChannelMask) != 0)) + { + uRecvMask = pVoipCommon->Connectionlist.uRecvMask | uChannelMask; + } + else + { + uRecvMask = pVoipCommon->Connectionlist.uRecvMask & ~uChannelMask; + } + + // set send/recv masks and priority + VoipConnectionSetSendMask(&pVoipCommon->Connectionlist, uSendMask); + VoipConnectionSetRecvMask(&pVoipCommon->Connectionlist, uRecvMask); + + if ((pConnection->bTranscribedTextRequested) && (pVoipCommon->Connectionlist.uSendMask & (1 << iConnection))) + { + // enable local text transcription because at least one remote user requested transcribed text + bTextTranscriptionEnabled = TRUE; + } + } + + /* We also want text transcription locally enabled if any local user is requesting text transcription + from remote users. Motivation: a hearing-impaired person requesting transcribed text from other + players also wants to see transcribed text for his own speech (request from EA's accessibility team). */ + for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS; iLocalUserIndex++) + { + if (pVoipCommon->Connectionlist.bTranscribedTextRequested[iLocalUserIndex] == TRUE) + { + bTextTranscriptionEnabled = TRUE; + } + } + + if (bTextTranscriptionEnabled != pVoipCommon->bTextTranscriptionEnabled) + { + NetPrintf(("voipcommon: %s text transcription locally\n", bTextTranscriptionEnabled?"enabling":"disabling")); + pVoipCommon->bTextTranscriptionEnabled = bTextTranscriptionEnabled; + VoipHeadsetControl(pVoipCommon->pHeadset, 'tran', bTextTranscriptionEnabled, 0, NULL); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonStatus + + \Description + Return status. + + \Input *pVoipCommon - voip common state + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuf - [out] storage for selector-specific output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector-specific data + + \Notes + Allowed selectors: + + \verbatim + 'avlb' - whether a given ConnID is available + 'chan' - retrieve the active voip channel (coded value of channels and modes) + 'chnc' - return the count of channel slots used by the voip group manager + 'chnl' - return the channel id and channel mode associated with specified channel slot + 'from' - bitfield indicating which peers are talking + 'hset' - bitfield indicating which ports have headsets + 'lprt' - return local socket port + 'luvu' - return voipuser of specified local user + 'maxc' - return max connections + 'mgrp' - voip memgroup user id + 'mgud' - voip memgroup user data + 'micr' - who we're sending voice to + 'rchn' - retrieve the active voip channel of a remote peer (coded value of channels and modes) + 'rcvu' - return voipuser of specified remote user (connection and user index) + 'shch' - return bUseDefaultSharedChannelConfig which indicates if the default shared channel config is used + 'sock' - voip socket ref + 'spkr' - who we're accepting voice from + 'umic' - user-specified microphone send list + 'uspk' - user-specified microphone recv list + \endverbatim + + \Version 12/02/2009 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipCommonStatus(VoipCommonRefT *pVoipCommon, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + if (iSelect == 'avlb') + { + return(VoipConnectionCanAllocate(&pVoipCommon->Connectionlist, iValue)); + } + if (iSelect == 'chan') + { + return(pVoipCommon->Connectionlist.aChannels[iValue]); + } + if (iSelect == 'chnc') + { + return(VOIP_MAX_CONCURRENT_CHANNEL); + } + if (iSelect == 'chnl') + { + int32_t iUserIndex = iValue & 0xFFFF; + int32_t iChannelSlot = iValue >> 16; + + if (!pBuf) + { + NetPrintf(("voipcommon: pBuf must be valid with the 'chnl' selector\n")); + return(-1); + } + + if ((unsigned)iBufSize < sizeof(VoipChanModeE)) + { + NetPrintf(("voipcommon: user buffer used with the 'chnl' selector is too small (expected size = %d; current size =%d)\n", + sizeof(VoipChanModeE), iBufSize)); + return(-2); + } + + if ((iChannelSlot < 0) || (iChannelSlot >= VOIP_MAX_CONCURRENT_CHANNEL)) + { + NetPrintf(("voipcommon: invalid slot id (%d) used with 'chnl' selector; valid range is [0,%d]\n", iChannelSlot, VOIP_MAX_CONCURRENT_CHANNEL-1)); + return(-3); + } + + *(VoipChanModeE *)pBuf = pVoipCommon->uLocalModes[iUserIndex][iChannelSlot]; + return(pVoipCommon->iLocalChannels[iUserIndex][iChannelSlot]); + } + if (iSelect == 'from') + { + return(pVoipCommon->Connectionlist.uRecvVoice); + } + if (iSelect == 'hset') + { + uint32_t uPortHeadsetStatus = pVoipCommon->uPortHeadsetStatus; + return(uPortHeadsetStatus); + } + if (iSelect == 'lprt') + { + return(pVoipCommon->Connectionlist.uBindPort); + } + if (iSelect == 'luvu') + { + if (pBuf) + { + if (iBufSize >= (signed)sizeof(VoipUserT)) + { + if ((iValue < VOIP_MAXLOCALUSERS_EXTENDED) && (!VOIP_NullUser((VoipUserT *)(&pVoipCommon->Connectionlist.LocalUsers[iValue])))) + { + // copy user id in caller-provided buffer + ds_memcpy(pBuf, &pVoipCommon->Connectionlist.LocalUsers[iValue], sizeof(VoipUserT)); + return(0); + } + } + else + { + NetPrintf(("voipcommon: VoipCommonStatus('luvu') error --> iBufSize (%d) not big enough (needs to be at least %d bytes)\n", + iBufSize, sizeof(VoipUserT))); + } + } + else + { + NetPrintf(("voipcommon: VoipCommonStatus('luvu') error --> pBuf cannot be NULL\n")); + } + + return(-1); + } + if (iSelect == 'maxc') + { + return(pVoipCommon->Connectionlist.iMaxConnections); + } + if (iSelect == 'mgrp') + { + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // return mem group id + return(iMemGroup); + } + if (iSelect == 'mgud') + { + // make sure user-provided buffer is large enough to receive a pointer + if ((pBuf != NULL) && (iBufSize >= (signed)sizeof(void *))) + { + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // fill [out] parameter with pointer to mem group user data + *((void **)pBuf) = pMemGroupUserData; + return(0); + } + else + { + // unhandled + return(-1); + } + } + if (iSelect == 'micr') + { + return(pVoipCommon->Connectionlist.uSendMask); + } + if (iSelect == 'rchn') + { + if (pBuf) + { + if (iBufSize == sizeof(uint32_t)) + { + // pBut is used as an input and an output parameter + // input -> user index + // output -> returned channels + int32_t iUserIndex = *(uint32_t *)pBuf; + *(uint32_t *)pBuf = pVoipCommon->Connectionlist.pConnections[iValue].aRecvChannels[iUserIndex]; + return(0); + } + else + { + NetPrintf(("voipcommon: VoipCommonStatus('rchn') error for conn id %d --> iBufSize (%d) does not match expected size (%d)\n", + iValue, iBufSize, sizeof(int32_t))); + } + } + else + { + NetPrintf(("voipcommon: VoipCommonStatus('rchn') error for conn id %d --> pBuf cannot be NULL\n", iValue)); + } + + return(-1); + } + if (iSelect == 'rcvu') + { + int32_t iConnIndex = iValue & 0xFFFF; + int32_t iRemoteUserIndex = iValue >> 16; + + if (pBuf) + { + if (iBufSize >= (signed)sizeof(VoipUserT)) + { + if (pVoipCommon->Connectionlist.pConnections[iConnIndex].eState != ST_DISC) + { + if ((iRemoteUserIndex < VOIP_MAXLOCALUSERS_EXTENDED) && (!VOIP_NullUser((VoipUserT *)(&pVoipCommon->Connectionlist.pConnections[iConnIndex].RemoteUsers[iRemoteUserIndex])))) + { + // copy user id in caller-provided buffer + ds_memcpy(pBuf, &pVoipCommon->Connectionlist.pConnections[iConnIndex].RemoteUsers[iRemoteUserIndex], sizeof(VoipUserT)); + return(0); + } + } + else + { + NetPrintf(("voipcommon: VoipCommonStatus('rcvu') error for conn id %d and remote user index %d --> connection is not active\n", + iConnIndex, iRemoteUserIndex)); + } + } + else + { + NetPrintf(("voipcommon: VoipCommonStatus('rcvu') error for conn id %d and remote user index %d --> iBufSize (%d) not big enough (needs to be at least %d bytes)\n", + iConnIndex, iRemoteUserIndex, iBufSize, sizeof(VoipUserT))); + } + } + else + { + NetPrintf(("voipcommon: VoipCommonStatus('rcvu') error for conn id %d and remote user index %d --> pBuf cannot be NULL\n", iConnIndex, iRemoteUserIndex)); + } + + return(-1); + } + if (iSelect == 'shch') + { + return(pVoipCommon->bUseDefaultSharedChannelConfig); + } + if (iSelect == 'sock') + { + if (pBuf != NULL) + { + if (iBufSize >= (signed)sizeof(pVoipCommon->Connectionlist.pSocket)) + { + ds_memcpy(pBuf, &pVoipCommon->Connectionlist.pSocket, sizeof(pVoipCommon->Connectionlist.pSocket)); + } + else + { + NetPrintf(("voipcommon: socket reference cannot be copied because user buffer is not large enough\n")); + return(-1); + } + } + else + { + NetPrintf(("voipcommon: socket reference cannot be copied because user buffer pointer is NULL\n")); + return(-1); + } + return(0); + } + if (iSelect == 'spkr') + { + return(pVoipCommon->Connectionlist.uRecvMask); + } + if (iSelect == 'umic') + { + return(pVoipCommon->uUserMicrValue); + } + if (iSelect == 'uspk') + { + return(pVoipCommon->uUserSpkrValue); + } + // unrecognized selector, so pass through to voipheadset + return(VoipHeadsetStatus(pVoipCommon->pHeadset, iSelect, iValue, pBuf, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonControl + + \Description + Set control options. + + \Input *pVoipCommon - voip common state + \Input iControl - control selector + \Input iValue - selector-specific input + \Input *pValue - selector-specific input + + \Output + int32_t - selector-specific output + + \Notes + iControl can be one of the following: + + \verbatim + 'clid' - set local client id + 'flsh' - send currently queued voice data immediately (iValue=connection id) + 'shch' - turn on or the of the default shared channel config. iValue = TRUE to turn, FALSE to turn off + 'stot' - turn on/off (*pValue) STT for local user specified with iValue + 'time' - set data timeout in milliseconds + 'xply' - toggle cross play + \endverbatim + + Unhandled selectors are passed through to VoipHeadsetControl() + + \Version 03/02/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipCommonControl(VoipCommonRefT *pVoipCommon, int32_t iControl, int32_t iValue, void *pValue) +{ + if (iControl == 'clid') + { + if ((pVoipCommon->Connectionlist.uClientId != 0) && (pVoipCommon->Connectionlist.uClientId != (unsigned)iValue)) + { + NetPrintf(("voipcommon: warning - local client id is being changed from %d to %d\n", pVoipCommon->Connectionlist.uClientId, iValue)); + } + pVoipCommon->Connectionlist.uClientId = (unsigned)iValue; + return(0); + } + if (iControl == 'flsh') + { + return(VoipConnectionFlush(&pVoipCommon->Connectionlist, iValue)); + } + if (iControl == 'shch') + { + pVoipCommon->bUseDefaultSharedChannelConfig = iValue; + pVoipCommon->bApplyChannelConfig = TRUE; + NetPrintf(("voipcommon: VoipCommonControl('shch') default shared channel config is %s\n", pVoipCommon->bUseDefaultSharedChannelConfig ? "on" : "off")); + return(0); + } + if (iControl == 'stot') + { + uint32_t bEnabled = TRUE; // default to 'enabled' + int32_t iUserLocalIndex = iValue; + + if (pValue != NULL) + { + bEnabled = *(uint32_t *)pValue; + } + + if ((iUserLocalIndex >= 0) && (iUserLocalIndex < VOIP_MAXLOCALUSERS)) + { + NetPrintf(("voipcommon: %s STT for local user index %d\n", (bEnabled ?"enabling":"disabling"), iUserLocalIndex)); + pVoipCommon->Connectionlist.bTranscribedTextRequested[iUserLocalIndex] = bEnabled; + return(0); + } + else + { + NetPrintf(("voipcommon: warning invalid local user index %d used with VoipControl('stot')\n", iUserLocalIndex)); + return(-1); + } + } + if (iControl == 'time') + { + pVoipCommon->Connectionlist.iDataTimeout = iValue; + return(0); + } + if (iControl == 'xply') + { + int32_t iRet; + NetCritEnter(&pVoipCommon->ThreadCrit); + #if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + if (iValue == TRUE) + { + VoipConnectionSetTextCallback(&pVoipCommon->Connectionlist, &_VoipCommonReceiveTextDataCb, (void *)pVoipCommon); + } + else + { + VoipConnectionSetTextCallback(&pVoipCommon->Connectionlist, NULL, NULL); + } + #endif + iRet = VoipHeadsetControl(pVoipCommon->pHeadset, 'xply', iValue, 0, pValue); + NetCritLeave(&pVoipCommon->ThreadCrit); + + return(iRet); + } + + // unhandled selectors are passed through to voipheadset + if (pVoipCommon) + { + return(VoipHeadsetControl(pVoipCommon->pHeadset, iControl, iValue, 0, pValue)); + } + else + { + // some voipheadset control selectors can be used before a call to VoipStartup() + return(VoipHeadsetControl(NULL, iControl, iValue, 0, pValue)); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonAddMask + + \Description + Add (OR) uAddMask into *pMask + + \Input *pMask - mask to add into + \Input uAddMask - mask to add (OR) + \Input *pMaskName - name of mask (for debug logging) + + \Version 12/03/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCommonAddMask(uint32_t *pMask, uint32_t uAddMask, const char *pMaskName) +{ + VoipCommonSetMask(pMask, *pMask | uAddMask, pMaskName); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonDelMask + + \Description + Del (&~) uDelMask from *pMask + + \Input *pMask - mask to del from + \Input uDelMask - mask to del (&~) + \Input *pMaskName - name of mask (for debug logging) + + \Version 12/03/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCommonDelMask(uint32_t *pMask, uint32_t uDelMask, const char *pMaskName) +{ + VoipCommonSetMask(pMask, *pMask & ~uDelMask, pMaskName); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonSetMask + + \Description + Set value of mask (with logging). + + \Input *pMask - mask to write to + \Input uNewMask - new mask value + \Input *pMaskName - name of mask (for debug logging) + + \Version 12/03/2009 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipCommonSetMask(uint32_t *pMask, uint32_t uNewMask, const char *pMaskName) +{ + NetPrintf(("voipcommon: %s update: 0x%08x->0x%08x\n", pMaskName, *pMask, uNewMask)); + *pMask = uNewMask; +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonSelectChannel + + \Description + Select the mode(send/recv) of a given channel. + + \Input *pVoipCommon - common module state + \Input iUserIndex - local user index + \Input iChannel - Channel ID (valid range: [0,63]) + \Input eMode - The mode, combination of VOIP_CHANSEND, VOIP_CHANRECV + + \Output + int32_t - number of channels remaining that this console could join + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +int32_t VoipCommonSelectChannel(VoipCommonRefT *pVoipCommon, int32_t iUserIndex, int32_t iChannel, VoipChanModeE eMode) +{ + int32_t iIndex; + int32_t iSlot = VOIP_MAX_CONCURRENT_CHANNEL; + int32_t iCount = 0; + + // if the shared user index is 0xff, the current platform does not support shared channel config feature + if (iUserIndex != VOIP_SHARED_USER_INDEX) + { + if ((iUserIndex < 0) || (iUserIndex >= VOIP_MAXLOCALUSERS)) + { + NetPrintf(("voipcommon: [channel] warning - attempt to set invalid local channels index %d\n", iUserIndex)); + return(-3); + } + } + + // enforcing the valid ranges + eMode &= VOIP_CHANSENDRECV; + iChannel &= 0x3f; + + // find the slot to store the specified channel and count the slots remaining. + for(iIndex = 0; iIndex < VOIP_MAX_CONCURRENT_CHANNEL; iIndex++) + { + // remember either the slot with the channel we want to set, or if we have found nothing, an empty slot + if ( ((pVoipCommon->uLocalModes[iUserIndex][iIndex] != VOIP_CHANNONE) && (pVoipCommon->iLocalChannels[iUserIndex][iIndex] == iChannel)) || + ((pVoipCommon->uLocalModes[iUserIndex][iIndex] == VOIP_CHANNONE) && (iSlot == VOIP_MAX_CONCURRENT_CHANNEL)) ) + { + iSlot = iIndex; + } + // and take the opportunity to count the free slots + if (pVoipCommon->uLocalModes[iUserIndex][iIndex] == VOIP_CHANNONE) + { + iCount++; + } + } + + // no more slots to store the channel selection or + // the given channel doesn't exist + if (iSlot == VOIP_MAX_CONCURRENT_CHANNEL) + { + return(-1); + } + + //count if we're taking a spot. + if ((pVoipCommon->uLocalModes[iUserIndex][iSlot] == VOIP_CHANNONE) && (eMode != VOIP_CHANNONE)) + { + iCount--; + } + //count if we're freeing a spot. + if ((pVoipCommon->uLocalModes[iUserIndex][iSlot] != VOIP_CHANNONE) && (eMode == VOIP_CHANNONE)) + { + iCount++; + iChannel = 0; // when freeing a channel reset it back to the 0 to bring us towards the default state + } + + // if we are trying to set the shared channel + if (iUserIndex == VOIP_SHARED_USER_INDEX) + { + pVoipCommon->iCustomSharedChannels[iSlot] = iChannel; + pVoipCommon->uCustomSharedModes[iSlot] = eMode; + } + // set the channel and mode in selected slot + else + { + pVoipCommon->iLocalChannels[iUserIndex][iSlot] = iChannel; + pVoipCommon->uLocalModes[iUserIndex][iSlot] = eMode; + + NetPrintf(("voipcommon: [channel] set local channel at slot %d: channelId=%d, channelMode=%u for local user index %d\n", + iSlot, pVoipCommon->iLocalChannels[iUserIndex][iSlot], pVoipCommon->uLocalModes[iUserIndex][iSlot], iUserIndex)); + pVoipCommon->uLocalChannelSelection[iUserIndex] = _VoipCommonEncode(pVoipCommon->iLocalChannels[iUserIndex], pVoipCommon->uLocalModes[iUserIndex]); + NetPrintf(("voipcommon: [channel] set local channels 0x%08x for local user index %d\n", pVoipCommon->uLocalChannelSelection[iUserIndex], iUserIndex)); + } + + _VoipCommonSetChannels(pVoipCommon, pVoipCommon->uLocalChannelSelection[iUserIndex], iUserIndex); + pVoipCommon->bApplyChannelConfig = TRUE; + + return(iCount); + +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonApplyChannelConfig + + \Description + Setup user muting flags based on channel config + + \Input *pVoipCommon - voip module state + + \Todo + Rename this function to indicate that we are applying the peer to peer + mutes instead of just applying channel configurations. The name we have + now no longer makes sense. + + \Version 12/02/2009 (jrainy) +*/ +/********************************************************************************F*/ +void VoipCommonApplyChannelConfig(VoipCommonRefT *pVoipCommon) +{ + static uint32_t uLastChanSendMask = 0; + static uint32_t uLastChanRecvMask = 0; + int32_t iConnIndex, iLocalUserIndex, iRemoteUserIndex; + uint32_t uConnUserPair; + uint8_t bSocialBlocked = FALSE; + VoipChanModeE eMatch; + VoipUserT LocalUser; + + pVoipCommon->uChanRecvMask = pVoipCommon->uChanSendMask = 0; + pVoipCommon->uUserSendMask = 0; + + // setup the shared channel, to reflect the channel changes that may have occurred of the local users + _VoipCommonComputeDefaultSharedChannelConfig(pVoipCommon); + _VoipCommonSetSharedUserChannelConfig(pVoipCommon); + + for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iLocalUserIndex++) + { + // find out if there is a local user for that local user index + if (VoipStatus(VoipGetRef(), 'luvu', iLocalUserIndex, &LocalUser, sizeof(LocalUser)) == 0) + { + for(iConnIndex = 0; iConnIndex < VoipStatus(VoipGetRef(), 'maxc', 0, NULL, 0); iConnIndex++) + { + if (pVoipCommon->Connectionlist.pConnections[iConnIndex].eState != ST_DISC) + { + for (iRemoteUserIndex = 0; iRemoteUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iRemoteUserIndex++) + { + // initialize variable used with VoipStatus('rcvu'). most-significant 16 bits = remote user index, least-significant 16 bits = conn index + VoipUserT RemoteUser; + uConnUserPair = iConnIndex; + uConnUserPair |= (iRemoteUserIndex << 16); + + // find out if there is a valid remote user for that connId/userId pair + if (VoipStatus(VoipGetRef(), 'rcvu', (int32_t)uConnUserPair, &RemoteUser, sizeof(RemoteUser)) == 0) + { + uint8_t bFirstPartyBlocked = FALSE; + eMatch = _VoipCommonChannelMatch(pVoipCommon, iLocalUserIndex, pVoipCommon->uRemoteChannelSelection[iConnIndex][iRemoteUserIndex]); + + // does the local user have that remote user blocked, if so do not alow voip between them? + bSocialBlocked = VoipBlockListIsBlocked((VoipRefT *)pVoipCommon, iLocalUserIndex, RemoteUser.AccountInfo.iAccountId); + + // apply first party mute list block + if (VoipHeadsetStatus(pVoipCommon->pHeadset, 'fpml', iLocalUserIndex, &RemoteUser, sizeof(RemoteUser)) > 0) + { + bFirstPartyBlocked = TRUE; + } + + // update send mask + if (!bFirstPartyBlocked && !bSocialBlocked && (eMatch & VOIP_CHANSEND)) + { + pVoipCommon->uChanSendMask |= (1 << iConnIndex); + pVoipCommon->uUserSendMask |= (1 << iLocalUserIndex); + } + + // update receive mask and user-specific playback config + if (!bFirstPartyBlocked && !bSocialBlocked && (eMatch & VOIP_CHANRECV)) + { + pVoipCommon->uChanRecvMask |= (1 << iConnIndex); + + // remote users exist in the connection list before they are registered with voipheadset later in the voip thread + // therefore we must make sure the remote user is registered with voip headset before applying voice playback + if (VoipHeadsetStatus(pVoipCommon->pHeadset, 'ruvu', 0, &RemoteUser, sizeof(RemoteUser))) + { + // enable playback of the remote user's voice for the specified local user + VoipControl(VoipGetRef(), '+pbk', iLocalUserIndex, &RemoteUser); + } + } + else + { + // remote users exist in the connection list before they are registered with voipheadset later in the voip thread + // therefore we must make sure the remote user is registered with voip headset before applying voice playback + if (VoipHeadsetStatus(pVoipCommon->pHeadset, 'ruvu', 0, &RemoteUser, sizeof(RemoteUser))) + { + // disable playback of the remote user's voice for the specified local user + VoipControl(VoipGetRef(), '-pbk', iLocalUserIndex, &RemoteUser); + } + } + } // if + } // for + } // if + } // for + } // if + } // for + + if (uLastChanSendMask != pVoipCommon->uChanSendMask) + { + NetPrintf(("voipcommon: uChanSendMask is now 0x%08x\n", pVoipCommon->uChanSendMask)); + uLastChanSendMask = pVoipCommon->uChanSendMask; + } + + if (uLastChanRecvMask != pVoipCommon->uChanRecvMask) + { + NetPrintf(("voipcommon: uChanRecvMask is now 0x%08x\n", pVoipCommon->uChanRecvMask)); + uLastChanRecvMask = pVoipCommon->uChanRecvMask; + } + + _VoipCommonComputeEffectiveUserSendMask(pVoipCommon); + _VoipCommonComputeEffectiveUserRecvMask(pVoipCommon); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonResetChannels + + \Description + Resets the channels selection to defaults. Sends and receives to all + + \Input *pVoipCommon - voip common state + \Input iUserIndex - local user index + + \Version 12/07/2009 (jrainy) +*/ +/********************************************************************************F*/ +void VoipCommonResetChannels(VoipCommonRefT *pVoipCommon, int32_t iUserIndex) +{ + // if the default config is on ignore the channel reset on the shared user + if ((pVoipCommon->bUseDefaultSharedChannelConfig) && (iUserIndex == VOIP_SHARED_USER_INDEX)) + { + return; + } + + if ((iUserIndex < 0 || iUserIndex >= VOIP_MAXLOCALUSERS) && iUserIndex != VOIP_SHARED_USER_INDEX) + { + NetPrintf(("voipcommon: [channel] warning - attempt to reset invalid local channels index %d\n", iUserIndex)); + return; + } + + _VoipCommonDecode(VOIPCOMMON_CODEDCHAN_ALL, pVoipCommon->iLocalChannels[iUserIndex], pVoipCommon->uLocalModes[iUserIndex]); + + pVoipCommon->uLocalChannelSelection[iUserIndex] = _VoipCommonEncode(pVoipCommon->iLocalChannels[iUserIndex], pVoipCommon->uLocalModes[iUserIndex]); + NetPrintf(("voipcommon: [channel] set local channels 0x%08x\n", pVoipCommon->uLocalChannelSelection[iUserIndex])); + + if (VoipGetRef() != NULL) + { + _VoipCommonSetChannels(pVoipCommon, pVoipCommon->uLocalChannelSelection[iUserIndex], iUserIndex); + pVoipCommon->bApplyChannelConfig = TRUE; + } +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonMicrophone + + \Description + Select which peers to send voice to + + \Input *pVoipCommon - voip common state + \Input uUserMicrValue - microphone bit values + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +void VoipCommonMicrophone(VoipCommonRefT *pVoipCommon, uint32_t uUserMicrValue) +{ + NetPrintf(("voipcommon: uUserMicrValue changed from 0x%08x to 0x%08x\n", pVoipCommon->uUserMicrValue, uUserMicrValue)); + pVoipCommon->uUserMicrValue = uUserMicrValue; + _VoipCommonComputeEffectiveUserSendMask(pVoipCommon); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonSpeaker + + \Description + Select which peers to accept voice from + + \Input *pVoipCommon - voip common state + \Input uUserSpkrValue - speaker bit values + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +void VoipCommonSpeaker(VoipCommonRefT *pVoipCommon, uint32_t uUserSpkrValue) +{ + NetPrintf(("voipcommon: uUserSpkrValue changed from 0x%08x to 0x%08x\n", pVoipCommon->uUserSpkrValue, uUserSpkrValue)); + pVoipCommon->uUserSpkrValue = uUserSpkrValue; + _VoipCommonComputeEffectiveUserRecvMask(pVoipCommon); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonConnectionSharingAddSession + + \Description + Add session id to share a specified voip connection + + \Input *pVoipCommon - voip common state + \Input iConnId - connection id + \Input uSessionId - session id we are adding + + \Output + int32_t - zero=success, negative=failure + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +int32_t VoipCommonConnectionSharingAddSession(VoipCommonRefT *pVoipCommon, int32_t iConnId, uint32_t uSessionId) +{ + int32_t iRetCode; + + // acquire critical section to modify ConnectionList + NetCritEnter(&pVoipCommon->ThreadCrit); + + iRetCode = VoipConnectionAddSessionId(&pVoipCommon->Connectionlist, iConnId, uSessionId); + + // release critical section to modify ConnectionList + NetCritLeave(&pVoipCommon->ThreadCrit); + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonConnectionSharingDelSession + + \Description + Remove session id from sharing a specified voip connection + + \Input *pVoipCommon - voip common state + \Input iConnId - connection id + \Input uSessionId - session id we are removing + + \Output + int32_t - zero=success, negative=failure + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +int32_t VoipCommonConnectionSharingDelSession(VoipCommonRefT* pVoipCommon, int32_t iConnId, uint32_t uSessionId) +{ + int32_t iRetCode; + + // acquire critical section to modify ConnectionList + NetCritEnter(&pVoipCommon->ThreadCrit); + + iRetCode = VoipConnectionDeleteSessionId(&pVoipCommon->Connectionlist, iConnId, uSessionId); + + // release critical section to modify ConnectionList + NetCritLeave(&pVoipCommon->ThreadCrit); + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonMapVoipServerId + + \Description + For server-based voip, maps a local conn id to a voipserver conn id + + \Input *pVoipCommon - voip common state + \Input iLocalConnId - local connection id + \Input iVoipServerConnId - voipserver connection id + + \Output + int32_t - zero=success, negative=failure + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +int32_t VoipCommonMapVoipServerId(VoipCommonRefT *pVoipCommon, int32_t iLocalConnId, int32_t iVoipServerConnId) +{ + if (pVoipCommon->Connectionlist.pConnections[iLocalConnId].eState != ST_DISC) + { + NetPrintf(("voipcommon: mapping local conn id %d to voipserver conn id %d\n", iLocalConnId, iVoipServerConnId)); + pVoipCommon->Connectionlist.pConnections[iLocalConnId].iVoipServerConnId = iVoipServerConnId; + return(0); + } + else + { + NetPrintf(("voipcommon: warning - mapping local conn id to voipserver conn id ignored because connection state is ST_DISC\n")); + return(-1); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonSetLocalClientId + + \Description + Set local client id for connection + + \Input *pVoipCommon - voip common state + \Input iConnId - connection id + \Input uLocalClientId - local client id + + \Version 01/30/2019 (eesponda) +*/ +/********************************************************************************F*/ +void VoipCommonSetLocalClientId(VoipCommonRefT *pVoipCommon, int32_t iConnId, uint32_t uLocalClientId) +{ + if (pVoipCommon->Connectionlist.pConnections[iConnId].bIsLocalClientIdValid) + { + NetPrintf(("voipcommon: warning - local client id 0x%08x is being replaced with 0x%08x for conn id %d\n", + pVoipCommon->Connectionlist.pConnections[iConnId].uLocalClientId, uLocalClientId, iConnId)); + } + else + { + NetPrintf(("voipcommon: assigning local client id 0x%08x to conn id %d\n", uLocalClientId, iConnId)); + } + pVoipCommon->Connectionlist.pConnections[iConnId].uLocalClientId = uLocalClientId; + pVoipCommon->Connectionlist.pConnections[iConnId].bIsLocalClientIdValid = TRUE; +} + +/*F*************************************************************************************************/ +/*! + \Function VoipCommonSetDisplayTranscribedTextCallback + + \Description + Set callback to be invoked when transcribed text (from local user or remote user) + is ready to be displayed locally. + + \Input *pVoipCommon - voip common state + \Input *pCallback - notification handler + \Input *pUserData - user data for handler + + \Version 05/03/2017 (mclouatre) +*/ +/*************************************************************************************************F*/ +void VoipCommonSetDisplayTranscribedTextCallback(VoipCommonRefT *pVoipCommon, VoipDisplayTranscribedTextCallbackT *pCallback, void *pUserData) +{ + // acquire critical section + NetCritEnter(&pVoipCommon->ThreadCrit); + + if (pCallback == NULL) + { + pVoipCommon->pDisplayTranscribedTextCb = NULL; + pVoipCommon->pDisplayTranscribedTextUserData = NULL; + } + else + { + pVoipCommon->pDisplayTranscribedTextCb = pCallback; + pVoipCommon->pDisplayTranscribedTextUserData = pUserData; + } + + // release critical section + NetCritLeave(&pVoipCommon->ThreadCrit); +} + +/*F*************************************************************************************************/ +/*! + \Function VoipCommonSetEventCallback + + \Description + Set voip event notification handler. + + \Input *pVoipCommon - voip common state + \Input *pCallback - event notification handler + \Input *pUserData - user data for handler + + \Version 02/10/2006 (jbrookes) +*/ +/*************************************************************************************************F*/ +void VoipCommonSetEventCallback(VoipCommonRefT *pVoipCommon, VoipCallbackT *pCallback, void *pUserData) +{ + // acquire critical section + NetCritEnter(&pVoipCommon->ThreadCrit); + + if (pCallback == NULL) + { + pVoipCommon->pCallback = _VoipCommonCallback; + pVoipCommon->pUserData = NULL; + } + else + { + pVoipCommon->pCallback = pCallback; + pVoipCommon->pUserData = pUserData; + } + + // release critical section + NetCritLeave(&pVoipCommon->ThreadCrit); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonConnect + + \Description + Connect to a peer. + + \Input *pVoipCommon - voip common state + \Input iConnID - [zero, iMaxPeers-1] for an explicit slot or VOIP_CONNID_NONE to auto-allocate + \Input uAddress - remote peer address + \Input uManglePort - port from demangler + \Input uGamePort - port to connect on + \Input uClientId - remote clientId to connect to (cannot be 0) + \Input uSessionId - session identifier (optional) + + \Output + int32_t - connection identifier (negative=error) + + \Version 1.0 03/02/2004 (jbrookes) first version + \Version 1.1 10/26/2009 (mclouatre) uClientId is no longer optional +*/ +/********************************************************************************F*/ +int32_t VoipCommonConnect(VoipCommonRefT *pVoipCommon, int32_t iConnID, uint32_t uAddress, uint32_t uManglePort, uint32_t uGamePort, uint32_t uClientId, uint32_t uSessionId) +{ + // acquire critical section to modify ConnectionList + NetCritEnter(&pVoipCommon->ThreadCrit); + + // initiate connection + iConnID = VoipConnectionStart(&pVoipCommon->Connectionlist, iConnID, uAddress, uManglePort, uGamePort, uClientId, uSessionId); + + // release critical section + NetCritLeave(&pVoipCommon->ThreadCrit); + + // return connection ID to caller + return(iConnID); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonDisconnect + + \Description + Disconnect from peer. + + \Input *pVoipCommon - voip common state + \Input iConnID - which connection to disconnect (VOIP_CONNID_ALL for all) + \Input bSendDiscMsg - TRUE if a voip disc pkt needs to be sent, FALSE otherwise + + \Todo + Multiple connection support. + + \Version 15/01/2014 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipCommonDisconnect(VoipCommonRefT *pVoipCommon, int32_t iConnID, int32_t bSendDiscMsg) +{ + // acquire critical section to modify ConnectionList + NetCritEnter(&pVoipCommon->ThreadCrit); + + // shut down connection + VoipConnectionStop(&pVoipCommon->Connectionlist, iConnID, bSendDiscMsg); + + // release critical section + NetCritLeave(&pVoipCommon->ThreadCrit); +} +/*F********************************************************************************/ +/*! + \Function VoipCommonRemoteUserStatus + + \Description + Return information about remote peer. + + \Input *pVoipCommon - voip common state + \Input iConnID - which connection to get remote info for, or VOIP_CONNID_ALL + \Input iRemoteUserIndex - user index at the connection iConnID + + \Output + int32_t - VOIP_REMOTE* flags, or VOIP_FLAG_INVALID if iConnID is invalid + + \Version 05/06/2014 (amakoukji) +*/ +/********************************************************************************F*/ +int32_t VoipCommonRemoteUserStatus(VoipCommonRefT *pVoipCommon, int32_t iConnID, int32_t iRemoteUserIndex) +{ + int32_t iRemoteStatus = VOIP_FLAG_INVALID; + + if (pVoipCommon != NULL && pVoipCommon->Connectionlist.pConnections != NULL) + { + if (iConnID == VOIP_CONNID_ALL) + { + for (iConnID = 0, iRemoteStatus = 0; iConnID < pVoipCommon->Connectionlist.iMaxConnections; iConnID++) + { + iRemoteStatus |= (int32_t)pVoipCommon->Connectionlist.pConnections[iConnID].uRemoteUserStatus[iRemoteUserIndex]; + } + } + else if ((iConnID >= 0) && (iConnID < pVoipCommon->Connectionlist.iMaxConnections)) + { + iRemoteStatus = pVoipCommon->Connectionlist.pConnections[iConnID].uRemoteUserStatus[iRemoteUserIndex]; + } + } + + return(iRemoteStatus); +} + +/*F********************************************************************************/ +/*! + \Function VoipCommonConnStatus + + \Description + Return information about peer connection. + + \Input *pVoipCommon - voip common state + \Input iConnID - which connection to get remote info for, or VOIP_CONNID_ALL + + \Output + int32_t - VOIP_CONN* flags, or VOIP_FLAG_INVALID if iConnID is invalid + + \Version 05/06/2014 (amakoukji) +*/ +/********************************************************************************F*/ +int32_t VoipCommonConnStatus(VoipCommonRefT *pVoipCommon, int32_t iConnID) +{ + int32_t iRemoteStatus = VOIP_FLAG_INVALID; + + if (pVoipCommon != NULL && pVoipCommon->Connectionlist.pConnections != NULL) + { + if (iConnID == VOIP_CONNID_ALL) + { + for (iConnID = 0, iRemoteStatus = 0; iConnID < pVoipCommon->Connectionlist.iMaxConnections; iConnID++) + { + iRemoteStatus |= (int32_t)pVoipCommon->Connectionlist.pConnections[iConnID].uRemoteConnStatus; + } + } + else if ((iConnID >= 0) && (iConnID < pVoipCommon->Connectionlist.iMaxConnections)) + { + iRemoteStatus = pVoipCommon->Connectionlist.pConnections[iConnID].uRemoteConnStatus; + } + } + + return(iRemoteStatus); +} diff --git a/src/thirdparty/dirtysdk/source/voip/voipcommon.h b/src/thirdparty/dirtysdk/source/voip/voipcommon.h new file mode 100644 index 00000000..e132df9c --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipcommon.h @@ -0,0 +1,171 @@ +/*H********************************************************************************/ +/*! + \File voipcommon.h + + \Description + Cross-platform voip data types and private functions. + + \Copyright + Copyright (c) 2009 Electronic Arts Inc. + + \Version 12/02/2009 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _voipcommon_h +#define _voipcommon_h + +/*** Include files ****************************************************************/ + +#include "voipconnection.h" +#include "voipheadset.h" +#include "DirtySDK/voip/voip.h" +#include "DirtySDK/voip/voipblocklist.h" + + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + + +typedef struct VoipCommonRefT +{ + VoipConnectionlistT Connectionlist; //!< virtual connection list + VoipHeadsetRefT *pHeadset; //!< voip headset manager + VoipBlockListT *pBlockList; //!< muting based on persona id + + VoipCallbackT *pCallback; //!< voip event callback (optional) + void *pUserData; //!< user data for voip event callback + + VoipDisplayTranscribedTextCallbackT *pDisplayTranscribedTextCb; //!< callback invoked when some transcribed text (from a remote user or from a local user) is ready to be displayed locally (optional) + void *pDisplayTranscribedTextUserData; //!< user data associated with callback invoked when some transcribed text (from a remote user or from a local user) is ready to be displayed locally (optional) + + NetCritT ThreadCrit; //!< critical section used for thread synchronization + uint8_t bApplyChannelConfig; //!< apply channel configs on the next voip thread pass + + uint32_t uLastFrom; //!< most recent from status, used for callback tracking + uint32_t uLastHsetStatus; //!< most recent headset status, used for callback tracking + uint32_t uLastLocalStatus[VOIP_MAXLOCALUSERS]; //!< most recent local status, used for callback tracking + uint32_t uPortHeadsetStatus; //!< bitfield indicating which ports have headsets + uint32_t uLastTtsStatus; //!< most recent TTS status, used for callback tracking + uint32_t uTtsStatus; //!< current TTS status (bitmask of local user TTS flag) + uint32_t uUserSendMask; //!< current sending status (bitmask of local user send flag) + + uint8_t bUseDefaultSharedChannelConfig; //!< whether we use the default shared config or not (only supported on xbox one) + uint8_t bPrivileged; //!< whether we are communication-privileged or not + uint8_t bTextTranscriptionEnabled; //!< whether text transcription is enabled or not locally + uint8_t _pad[1]; + + uint32_t uRemoteChannelSelection[VOIP_MAX_LOW_LEVEL_CONNS][VOIP_MAXLOCALUSERS_EXTENDED]; //voipconduit; avoid name clash with new API +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" +#include "voipconnection.h" +#include "voipmixer.h" +#include "voipdvi.h" + +#include "voipconduit.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +//! name of a voip conduit +#define VOIP_ConduitName(_iConduit) ('a' + (_iConduit)) + +/*** Type Definitions *************************************************************/ +//! headset conduit info +typedef struct VoipConduitT +{ + VoipUserT PacketUser; +} VoipConduitT; + +//! conduit module state +struct VoipConduitRefT +{ + VoipMixerRefT *apMixers[VOIP_MAXLOCALUSERS]; + VoipConduitPlaybackCbT *pConduitPlayback; + void *pUserData; + int32_t iNumConduits; + int32_t iVerbosity; + VoipConduitT Conduits[1]; +}; + + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Public Variables + +// Private Variables + +/*** Private Functions ************************************************************/ + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipConduitCreate + + \Description + Create voice conduit manager. + + \Input iMaxConduits - number of conduits to support + + \Output + VoipConduitRefT * - pointer to module state, or NULL if an error occurred + + \Version 07/29/2004 (jbrookes) Split from voipheadset.c +*/ +/********************************************************************************F*/ +VoipConduitRefT *VoipConduitCreate(int32_t iMaxConduits) +{ + VoipConduitRefT *pConduitRef; + int32_t iRefSize; + int32_t iMemGroup; + void *pMemGroupUserData; + + // calculate size + iRefSize = sizeof(VoipConduitRefT) + (sizeof(VoipConduitT) * (iMaxConduits-1)); + + // allocate and clear module state + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + if ((pConduitRef = DirtyMemAlloc(iRefSize, VOIP_MEMID, iMemGroup, pMemGroupUserData )) == NULL) + { + return(NULL); + } + ds_memclr(pConduitRef, iRefSize); + + // init ref & return to caller + pConduitRef->iNumConduits = iMaxConduits; + return(pConduitRef); +} + +/*F********************************************************************************/ +/*! + \Function VoipConduitMixerSet + + \Description + Assign a mixer to the conduit + + \Input *pConduitRef - pointer to conduit manager + \Input *pMixerRef - pointer to mixer to assign + + \Version 10/26/2011 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConduitMixerSet(VoipConduitRefT *pConduitRef, VoipMixerRefT *pMixerRef) +{ + int32_t iMixerIndex; + + // find an empty mixer in the array + for (iMixerIndex = 0; iMixerIndex < VOIP_MAXLOCALUSERS; ++iMixerIndex) + { + if (pConduitRef->apMixers[iMixerIndex] == NULL) + { + pConduitRef->apMixers[iMixerIndex] = pMixerRef; + break; + } + } + + if (iMixerIndex == VOIP_MAXLOCALUSERS) + { + NetPrintf(("voipconduit: VoipConduitMixerSet() cannot add mixer %p!\n", pMixerRef)); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipConduitMixerUnset + + \Description + Unassign a mixer to the conduit + + \Input *pConduitRef - pointer to conduit manager + \Input *pMixerRef - pointer to mixer to unassign + + \Version 03/12/2019 (tcho) +*/ +/********************************************************************************F*/ +void VoipConduitMixerUnset(VoipConduitRefT *pConduitRef, VoipMixerRefT *pMixerRef) +{ + int32_t iMixerIndex; + + // find an empty mixer in the array + for (iMixerIndex = 0; iMixerIndex < VOIP_MAXLOCALUSERS; ++iMixerIndex) + { + if (pConduitRef->apMixers[iMixerIndex] == pMixerRef) + { + pConduitRef->apMixers[iMixerIndex] = NULL; + break; + } + } + + if (iMixerIndex == VOIP_MAXLOCALUSERS) + { + NetPrintf(("voipconduit: VoipConduitMixerUnset() cannot find mixer %p!\n", pMixerRef)); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipConduitDestroy + + \Description + Destroy voice conduit manager. + + \Input *pConduitRef - pointer to conduit manager + + \Version 07/29/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConduitDestroy(VoipConduitRefT *pConduitRef) +{ + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + DirtyMemFree(pConduitRef, VOIP_MEMID, iMemGroup, pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function VoipConduitReceiveVoiceData + + \Description + Receive voice data, and send it to the mixer. + + \Input *pConduitRef - pointer to conduit manager + \Input *pRemoteUser - user data came from + \Input *pData - incoming voice data packet + \Input iDataSize - voice data size + + \Version 03/21/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConduitReceiveVoiceData(VoipConduitRefT *pConduitRef, VoipUserT *pRemoteUser, const uint8_t *pData, int32_t iDataSize) +{ + VoipConduitT *pConduit; + int32_t iConduit; + int32_t iMixerIndex; + + // copy to matching slot in queue + for (iConduit = 0; iConduit < pConduitRef->iNumConduits; iConduit++) + { + pConduit = &pConduitRef->Conduits[iConduit]; + if (VOIP_SameUser(&pConduit->PacketUser, pRemoteUser)) + { + int32_t iConduitMask = 1 << iConduit; + + for (iMixerIndex = 0; iMixerIndex < VOIP_MAXLOCALUSERS; ++iMixerIndex) + { + // decompress the frame and accumulate to the current mixbuffer + if (pConduitRef->apMixers[iMixerIndex] != NULL) + { + uint8_t bPlayback = TRUE; + + if (pConduitRef->pConduitPlayback != NULL) + { + bPlayback = pConduitRef->pConduitPlayback(pConduitRef->apMixers[iMixerIndex], pRemoteUser, pConduitRef->pUserData); + } + + if (bPlayback) + { + if (VoipMixerAccumulate(pConduitRef->apMixers[iMixerIndex], (uint8_t *)pData, iDataSize, iConduitMask, iConduit) < 0) + { + NetPrintfVerbose((pConduitRef->iVerbosity, 3, "voipconduit: [%c] index[%d], discarding packet due to mixbuffer overflow\n", VOIP_ConduitName(iConduit), iMixerIndex)); + } + } + } + } + break; + } + } + + #if DIRTYCODE_LOGGING + if (iConduit == pConduitRef->iNumConduits) + { + NetPrintf(("voipconduit: could not find a conduit for voice packet from user '%lld'\n", pRemoteUser->AccountInfo.iPersonaId)); + } + #endif +} + +/*F********************************************************************************/ +/*! + \Function VoipConduitRegisterUser + + \Description + Register/unregister a remote user with the conduit manager. + + \Input *pConduitRef - pointer to conduit manager + \Input *pRemoteUser - remote user + \Input bRegister - if TRUE, register user, else unregister user. + + \Version 03/21/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConduitRegisterUser(VoipConduitRefT *pConduitRef, VoipUserT *pRemoteUser, uint32_t bRegister) +{ + VoipUserT *pConduitUser; + int32_t iConduit; + + // find an open slot + for (iConduit = 0; iConduit < pConduitRef->iNumConduits; iConduit++) + { + // ref conduit user + pConduitUser = &pConduitRef->Conduits[iConduit].PacketUser; + + if (bRegister) + { + if (VOIP_NullUser(pConduitUser)) + { + NetPrintf(("voipconduit: [%c] registering user '%lld'\n", VOIP_ConduitName(iConduit), pRemoteUser->AccountInfo.iPersonaId)); + VOIP_CopyUser(pConduitUser, pRemoteUser); + break; + } + } + else + { + if (VOIP_SameUser(pConduitUser, pRemoteUser)) + { + NetPrintf(("voipconduit: [%c] unregistering user '%lld'\n", VOIP_ConduitName(iConduit), pRemoteUser->AccountInfo.iPersonaId)); + VOIP_ClearUser(pConduitUser); + break; + } + } + } + + #if DIRTYCODE_LOGGING + if (iConduit >= pConduitRef->iNumConduits) + { + NetPrintf(("voipconduit: could not %s user '%lld'\n", + (bRegister) ? "register" : "unregister", + pRemoteUser->AccountInfo.iPersonaId)); + } + #endif +} + +/*F********************************************************************************/ +/*! + \Function VoipConduitRegisterPlaybackCb + + \Description + Register/unregister a remote user with the conduit manager. + + \Input *pConduitRef - pointer to conduit manager + \Input *pPlaybackCallback - playback callback + \Input *pUserData - callback user data + + \Version 06/24/2019 (tcho) +*/ +/********************************************************************************F*/ +void VoipConduitRegisterPlaybackCb(VoipConduitRefT *pConduitRef, VoipConduitPlaybackCbT *pPlaybackCallback, void *pUserData) +{ + pConduitRef->pConduitPlayback = pPlaybackCallback; + pConduitRef->pUserData = pUserData; +} + + +/*F********************************************************************************/ +/*! + \Function VoipConduitControl + + \Description + Control function. + + \Input *pConduitRef - pointer to conduit manager + \Input iControl - control selector + \Input iValue - control value + \Input *pValue - control value + + \Output + int32_t - selector specific, or -1 if no such selector + + \Notes + iControl can be one of the following: + + \verbatim + 'spam' - set the debug level + 'rmcb' - register playback callback + 'rmcu' - register playback call back user data + \endverbatim + + \Version 06/03/2016 (amakoukji) +*/ +/********************************************************************************F*/ +int32_t VoipConduitControl(VoipConduitRefT *pConduitRef, int32_t iControl, int32_t iValue, void *pValue) +{ + int32_t iReturn = -1; + if (iControl == 'spam') + { + pConduitRef->iVerbosity = iValue; + iReturn = 0; + } + return(iReturn); +} diff --git a/src/thirdparty/dirtysdk/source/voip/voipconduit.h b/src/thirdparty/dirtysdk/source/voip/voipconduit.h new file mode 100644 index 00000000..3b6c1bc1 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipconduit.h @@ -0,0 +1,69 @@ +/*H********************************************************************************/ +/*! + \File voipconduit.h + + \Description + VoIP data packet definitions. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 07/29/2004 (jbrookes) Split from voipheadset + \Version 12/01/2009 (jbrookes) voipchannel->voipconduit; avoid name clash with new API +*/ +/********************************************************************************H*/ + +#ifndef _voipconduit_h +#define _voipconduit_h + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ +//! playback callback +typedef uint8_t(VoipConduitPlaybackCbT)(VoipMixerRefT *pMixer, VoipUserT *pRemoteUser, void *pUserData); + +//! conduit manager module state +typedef struct VoipConduitRefT VoipConduitRefT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create conduit manager +VoipConduitRefT *VoipConduitCreate(int32_t iMaxConduits); + +// set conduit mixer +void VoipConduitMixerSet(VoipConduitRefT *pConduitRef, VoipMixerRefT *pMixerRef); + +// unset a conduit mixer +void VoipConduitMixerUnset(VoipConduitRefT *pConduitRef, VoipMixerRefT *pMixerRef); + +// destroy conduit manager +void VoipConduitDestroy(VoipConduitRefT *pConduitRef); + +// receive voice data +void VoipConduitReceiveVoiceData(VoipConduitRefT *pConduitRef, VoipUserT *pRemoteUser, const uint8_t *pData, int32_t iDataSize); + +// register a remote user +void VoipConduitRegisterUser(VoipConduitRefT *pConduitRef, VoipUserT *pRemoteUser, uint32_t bRegister); + +// register playback callback +void VoipConduitRegisterPlaybackCb(VoipConduitRefT *pConduitRef, VoipConduitPlaybackCbT *pPlaybackCallback, void *pUserData); + +// control setters +int32_t VoipConduitControl(VoipConduitRefT *pConduitRef, int32_t iControl, int32_t iValue, void *pValue); + +#ifdef __cplusplus +}; +#endif + +#endif // _voipconduit_h + diff --git a/src/thirdparty/dirtysdk/source/voip/voipconnection.c b/src/thirdparty/dirtysdk/source/voip/voipconnection.c new file mode 100644 index 00000000..d4c36baf --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipconnection.c @@ -0,0 +1,4040 @@ +/*H********************************************************************************/ +/*! + \File voipconnection.c + + \Description + VoIP virtual connection manager. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Notes + \verbatim + + None of the packet types used by DirtySDK's voip implementation is reliable, i.e. delivery to the other end of the connection is not guaranteed. + There exists some sort of reliability during the connection establishment handshaking: voip connect packets (VoipConnPacketT) are being re-sent + until packets are detected as received by the other end of the connection. This mechanism is specific to the connection establishment and is + not generic enough for re-use after connection establishment. + + MLU VoIP join-in-progress (local user added to voip console-to-console connection after connection established) requires some + post-initial-handshaking reliability. For that purpose specifically, a reliability scheme was implemented with protocol components + piggy-backed on voip micr packets and voip ping packets. Those protocol components are: DATA, ACK and ACKCNF. + + DATA (producer->consumer): data to be delivered reliably (tagged with a sequence number) + ACK (consumer->producer): acked sequence number + ACKCNF (producer->consumer): acked sequence number confirmation (can be leveraged by the consumer to stop acking and eliminate the associated acking overhead) + + From the perspective of a game console producing reliable data, an "outbound reliable data flow" on a given connection looks like this: + + Reliable data producer (local) Reliable Data Consumer (remote) + + ------ DATA -----> + <------ ACK ----- + ------ ACKCNF -----> + + From the perspective of a game console consuming reliable data, an "inbound reliable data flow" on a given connection looks like this: + + Reliable data consumer (local) Reliable Data Producer (remote) + + <------ DATA ----- + ------ ACK -----> + <------ ACKCNF ----- + + ACK and ACKCNF protocol components are added, when necessary, as a pair to the beginning of any ping or voip packet. When the packet + is flagged with VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF, then it contains at least one such entry. The exact number of entries is + captured in the first byte of the packet payload. Then each entry consists of a 4-byte destination client id and a 1-byte field where: + bit 7 = ACKCNF (1 means "continue acking", 0 means "you can stop acking") + bits 0-6 = ACK (acked seq number in the 1 to 127 range - 0 means "unused" or "invalid") + + DATA protocol component is also added at the beginning of the voip packet payload (after ACK+ACkNF if any). When the packet + is flagged with VOIP_PACKET_RELIABLE_FLAG_DATA, then it contains at least one such entry. The exact number of entries is + captured in the first byte of the packet payload (or in the first byte following the ACK+ACKCNF portion). When a DATA entry + is created, it is queued with the SAME sequence number in the outbound queue of all connections. The use of the SAME + sequence number is key in not having to specifiy what destination console the entry is for. For P2P connectivity this is not + really a concern as packets are exchanged directly between consoles. But for voipserver-based connectivity, the voip micr + packets are sent once to the voipserver and then rebroadcasted from there to all other peers. Consequently it is important that + the sequence number that the DATA entries are tagged with are meaningful to all remote peers. + For voipserver-based voip: + Have a look at addition comments in _VoipReliableDataOutProcess() describing the logics used to add DATA entries + to voip micr packets sent to the voipserver. + \endverbatim + + \Version 1.0 03/17/2004 (jbrookes) First Version + \Version 1.1 01/06/2008 (mclouatre) Created VoipConnectionAddRef(), VoipConnectionRemoveRef(), VoipConnectionResetRef(), VoipConnectionGetFallbackAddr() + \Version 1.2 10/26/2009 (mclouatre) Renamed from xbox/voipconnection.c to xenon/voipconnectionxenon.c + \Version 1.3 11/13/2009 (jbrookes) Merged Xenon back into common version + \Version 1.4 09/29/2014 (mclouatre) Addes support for voip reliable data +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#ifdef _XBOX +#include +#include +#endif + +#include +#include +#include // for wcslen() + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" + +#include "voipconnection.h" + +/*** Defines **********************************************************************/ + +#define VOIP_PACKET_VERSION ('j') //!< current packet version +#define VOIP_PING_RATE (500) //!< send ping packets twice a second +#define VOIP_TIMEOUT (15*1000) //!< default voip data timeout +#define VOIP_CONNTIMEOUT (10*1000) //!< connection timeout +#define VOIP_MSPERPACKET (100) //!< number of ms per network packet (100ms = 10hz) + +//! enable for verbose debugging +#define VOIP_CONNECTION_DEBUG (DIRTYCODE_DEBUG && FALSE) +#define VOIP_RELIABLE_DEBUG (DIRTYCODE_DEBUG && FALSE) + +#define VOIP_IPPROTO (IPPROTO_IP) + +/*** Macros ***********************************************************************/ + +//! compare packet types +#define VOIP_SamePacketType(_packetHead1, _packetHead2) \ + (!memcmp((_packetHead1)->aType, (_packetHead2)->aType, sizeof((_packetHead1)->aType))) + +//! get connection ID +#define VOIP_ConnID(_pConnectionlist, _pConnection) ((uint32_t)((_pConnection)-(_pConnectionlist)->pConnections)) + +/*** Type Definitions *************************************************************/ + +#if DIRTYCODE_LOGGING +const char *_strReliableType[] = +{ + "USERADD", + "USERREM", + "OPAQUE ", + "TEXT " +}; +#endif + +/*** Function Prototypes **********************************************************/ + +static void _VoipConnectionStop(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iConnID, int32_t bSendDiscMsg); +static void _VoipEncodeU16(uint8_t *pValue, uint16_t uValue); +static uint16_t _VoipDecodeU16(const uint8_t *pValue); + +/*** Variables ********************************************************************/ + +//! VoIP conn packet header +static VoipPacketHeadT _Voip_ConnPacket = +{ + { 'C', 'O', VOIP_PACKET_VERSION }, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, {0, 0} +}; + +//! VoIP disc packet header +static VoipPacketHeadT _Voip_DiscPacket = +{ + { 'D', 'S', 'C' }, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, {0, 0} +}; + +//! VoIP ping packet header +static VoipPacketHeadT _Voip_PingPacket = +{ + { 'P', 'N', 'G' }, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, {0, 0} +}; + +//! VoIP mic data packet header +static VoipPacketHeadT _Voip_MicrPacket = +{ + { 'M', 'I', 'C' }, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, {0, 0} +}; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _VoipEncodeLocalHeadsetStatus + + \Description + Encode the uLocalUserStatus fields in the packet header + + \Input *pConnectionlist - information read from here to be written to the packet header + \Input *pHead - packet header to write to + + \Version 11/20/2008 (cvienneau) +*/ +/********************************************************************************F*/ +static void _VoipEncodeLocalHeadsetStatus(VoipConnectionlistT *pConnectionlist, VoipPacketHeadT *pHead) +{ + uint16_t uFlags = 0; + int32_t i = 0; + for (i = 0; i < VOIP_MAXLOCALUSERS; ++i) + { + if (pConnectionlist->uLocalUserStatus[i] & VOIP_LOCAL_USER_HEADSETOK) + { + uFlags |= (1 << i); + } + } + + // Encode 16 for byte ordering + _VoipEncodeU16(pHead->aHeadsetStat, uFlags); +} + +/*F********************************************************************************/ +/*! + \Function _VoipDecodeRemoteHeadsetStatus + + \Description + Reads aHeadsetStat field from an incoming packet and sets appropriate state + about the remote connection. + + \Input *pConnection - connection to write state to + \Input *pHead - packet header to read information from + + \Version 11/20/2008 (cvienneau) +*/ +/********************************************************************************F*/ +static void _VoipDecodeRemoteHeadsetStatus(VoipConnectionT *pConnection, VoipPacketHeadT *pHead) +{ + uint32_t iIndex; + uint16_t uFlags = _VoipDecodeU16(pHead->aHeadsetStat); + + for (iIndex = 0; iIndex < VOIP_MAXLOCALUSERS; ++iIndex) + { + uint16_t uMask = 1 << iIndex; + if (uFlags & uMask) + { + pConnection->uRemoteUserStatus[iIndex] |= VOIP_REMOTE_USER_HEADSETOK; //on + } + else + { + pConnection->uRemoteUserStatus[iIndex] &= ~VOIP_REMOTE_USER_HEADSETOK; //off + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipDecodeU64 + + \Description + Decode uint64_t from given packet structure. + + \Input *pValue - network-order data field + + \Output + uint64_t - decoded value + + \Version 07/03/2019 (tcho) +*/ +/********************************************************************************F*/ +static uint64_t _VoipDecodeU64(const uint8_t *pValue) +{ + uint64_t uValue = (uint64_t)pValue[0] << 56; + uValue |= (uint64_t)pValue[1] << 48; + uValue |= (uint64_t)pValue[2] << 40; + uValue |= (uint64_t)pValue[3] << 32; + uValue |= (uint64_t)pValue[4] << 24; + uValue |= (uint64_t)pValue[5] << 16; + uValue |= (uint64_t)pValue[6] << 8; + uValue |= (uint64_t)pValue[7]; + return(uValue); +} + +/*F********************************************************************************/ +/*! + \Function _VoipDecodeU32 + + \Description + Decode uint32_t from given packet structure. + + \Input *pValue - network-order data field + + \Output + uint32_t - decoded value + + \Version 06/02/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _VoipDecodeU32(const uint8_t *pValue) +{ + uint32_t uValue = pValue[0] << 24; + uValue |= pValue[1] << 16; + uValue |= pValue[2] << 8; + uValue |= pValue[3]; + return(uValue); +} + +/*F********************************************************************************/ +/*! + \Function _VoipDecodeU16 + + \Description + Decode uint16_t from given packet structure. + + \Input *pValue - network-order data field + + \Output + uint16_t - decoded value + + \Version 05/02/2014 (jbrookes) +*/ +/********************************************************************************F*/ +static uint16_t _VoipDecodeU16(const uint8_t *pValue) +{ + uint16_t uValue = pValue[0] << 8; + uValue |= pValue[1]; + return(uValue); +} + +/*F********************************************************************************/ +/*! + \Function _VoipDecodeVoipUser + + \Description + Decode a voip user to a voip user packet + + \Input *pUserPacket - input voip user packet to decode + \Input *pUser - voip user to storge the decoded result + + \Version 07/03/2019 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipDecodeVoipUser(VoipUserPacketT *pUserPacket, VoipUserT *pUser) +{ + pUser->AccountInfo.iAccountId = (int64_t)_VoipDecodeU64(pUserPacket->aAccountId); + pUser->AccountInfo.iPersonaId = (int64_t)_VoipDecodeU64(pUserPacket->aPersonaId); + pUser->uFlags = _VoipDecodeU32(pUserPacket->aFlags); + pUser->ePlatform = _VoipDecodeU32(pUserPacket->aPlatform); +} + +/*F********************************************************************************/ +/*! + \Function _VoipEncodeU64 + + \Description + Encode given uint64_t in network order + + \Input *pValue - storage for network-order u64 + \Input uValue - u64 value to encode + + \Version 07/03/2019 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipEncodeU64(uint8_t *pValue, uint64_t uValue) +{ + pValue[0] = (uint8_t)(uValue >> 56); + pValue[1] = (uint8_t)(uValue >> 48); + pValue[2] = (uint8_t)(uValue >> 40); + pValue[3] = (uint8_t)(uValue >> 32); + pValue[4] = (uint8_t)(uValue >> 24); + pValue[5] = (uint8_t)(uValue >> 16); + pValue[6] = (uint8_t)(uValue >> 8); + pValue[7] = (uint8_t)(uValue); +} + +/*F********************************************************************************/ +/*! + \Function _VoipEncodeU32 + + \Description + Encode given uint32_t in network order + + \Input *pValue - storage for network-order u32 + \Input uValue - u32 value to encode + + \Version 06/02/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipEncodeU32(uint8_t *pValue, uint32_t uValue) +{ + pValue[0] = (uint8_t)(uValue >> 24); + pValue[1] = (uint8_t)(uValue >> 16); + pValue[2] = (uint8_t)(uValue >> 8); + pValue[3] = (uint8_t)uValue; +} + +/*F********************************************************************************/ +/*! + \Function _VoipEncodeU16 + + \Description + Encode given uint16_t in network order + + \Input *pValue - storage for network-order u32 + \Input uValue - u16 value to encode + + \Version 05/02/2014 (amakoukji) +*/ +/********************************************************************************F*/ +static void _VoipEncodeU16(uint8_t *pValue, uint16_t uValue) +{ + pValue[0] = (uint8_t)(uValue >> 8); + pValue[1] = (uint8_t)uValue; +} + +/*F********************************************************************************/ +/*! + \Function _VoipEncodeVoipUser + + \Description + Encodes a voip user to a voip user packet + + \Input *pUserPacket - output voip user packet + \Input *pUser - user to encode + + \Version 07/03/2019 (tcho) +*/ +/********************************************************************************F*/ +static void _VoipEncodeVoipUser(VoipUserPacketT *pUserPacket, VoipUserT *pUser) +{ + _VoipEncodeU64(pUserPacket->aAccountId, (uint64_t) pUser->AccountInfo.iAccountId); + _VoipEncodeU64(pUserPacket->aPersonaId, (uint64_t) pUser->AccountInfo.iPersonaId); + _VoipEncodeU32(pUserPacket->aFlags, pUser->uFlags); + _VoipEncodeU32(pUserPacket->aPlatform, pUser->ePlatform); +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionIncrementReliableSeqNb + + \Description + Return the next seq nb following the specified seq nb. + Sequence number validity range: [1,127] (0 = invalid) + + \Input uSeqNb - input sequence number + + \Output + uint8_t - output sequence number + + \Version 09/18/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t _VoipConnectionIncrementReliableSeqNb(uint8_t uSeqNb) +{ + if (uSeqNb == 127) + { + return(1); + } + else + { + return(uSeqNb + 1); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataEnqueue + + \Description + Add a reliable data entry to the tail of the specified linked list. + + \Input **pListHead - pointe to head of list + \Input *pNewEntry - entry to be added + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipReliableDataEnqueue(LinkedReliableDataT **pListHead, LinkedReliableDataT *pNewEntry) +{ + if (*pListHead == NULL) + { + *pListHead = pNewEntry; + } + else + { + // find tail of list and append + LinkedReliableDataT *pCurrent = *pListHead; + while (pCurrent->pNext != NULL) + { + pCurrent = pCurrent->pNext; + } + pCurrent->pNext = pNewEntry; + } + + pNewEntry->pNext = NULL; +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataDequeue + + \Description + Remove a reliable data entry from the tail of the specified linked list. + + \Input **pListHead - pointer to head of list + + \Output + LinkedReliableDataT * - pointer to returned buffer; NULL if error + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static LinkedReliableDataT *_VoipReliableDataDequeue(LinkedReliableDataT **pListHead) +{ + LinkedReliableDataT *pRemovedEntry = NULL; + + // return head of list to caller + if (*pListHead) + { + pRemovedEntry = *pListHead; + *pListHead = (*pListHead)->pNext; + pRemovedEntry->pNext = NULL; + } + + return(pRemovedEntry); +} + +/*F********************************************************************************/ +/*! + \Function _VoipGetReliableDataBufferFromFreePool + + \Description + Obtain a free reliable data buffer from the free pool. + (Extract from head of linked list) + + \Input *pConnectionlist - connection list + \Input bAllocIfEmpty - enable/disable allocating buffer in pool is empty + + \Output + LinkedReliableDataT * - pointer to returned buffer; NULL if error + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static LinkedReliableDataT *_VoipGetReliableDataBufferFromFreePool(VoipConnectionlistT *pConnectionlist, uint8_t bAllocIfEmpty) +{ + LinkedReliableDataT *pReliableDataBuffer; + int32_t iMemGroup; + void *pMemGroupUserData; + + // query current mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // get a buffer from the pool of free buffer + pReliableDataBuffer = _VoipReliableDataDequeue(&pConnectionlist->pFreeReliableDataPool); + + // if pool is empty, allocate a new entry + if ((pReliableDataBuffer == NULL) && (bAllocIfEmpty != FALSE)) + { + if ((pReliableDataBuffer = (LinkedReliableDataT *)DirtyMemAlloc(sizeof(*pReliableDataBuffer), VOIP_MEMID, iMemGroup, pMemGroupUserData)) != NULL) + { + ds_memclr(pReliableDataBuffer, sizeof(*pReliableDataBuffer)); + + #if VOIP_RELIABLE_DEBUG + NetPrintf(("voipconnection: new free reliable data buffer (%p) added to free pool\n", pReliableDataBuffer)); + #endif + } + else + { + NetPrintf(("voipconnection: error, unable to allocate reliable data buffer\n")); + } + } + + #if VOIP_RELIABLE_DEBUG + if (pReliableDataBuffer) + { + NetPrintf(("voipconnection: reliable data buffer (%p) obtained from free pool\n", pReliableDataBuffer)); + } + else + { + NetPrintf(("voipconnection: free pool of reliable data buffers is now empty\n")); + } + #endif + + return(pReliableDataBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _VoipReturnReliableDataBufferToFreePool + + \Description + Return buffer to free pool. (Append to tail of linked list) + + \Input *pConnectionlist - connection list + \Input *pFreeBuffer - buffer to be returned to pool + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipReturnReliableDataBufferToFreePool(VoipConnectionlistT *pConnectionlist, LinkedReliableDataT *pFreeBuffer) +{ + _VoipReliableDataEnqueue(&pConnectionlist->pFreeReliableDataPool, pFreeBuffer); + + #if VOIP_RELIABLE_DEBUG + NetPrintf(("voipconnection: reliable data buffer (%p) returned to free pool\n", pFreeBuffer)); + #endif +} + +/*F********************************************************************************/ +/*! + \Function _VoipPacketGetWritePtr + + \Description + Calculate next write position in the specified voip packet + + \Input *pMicrPacket - packet pointer + + \Output + uint8_t * - next write position in the packet + + \Version 07/18/2013 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t *_VoipPacketGetWritePtr(VoipMicrPacketT *pMicrPacket) +{ + uint8_t *pWrite = &pMicrPacket->aData[0]; + + // is the packet buffer already filled with ACK+ACKCNF protocol components used for the reliability mechanism? + if (pMicrPacket->Head.uFlags & VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF) + { + int32_t iReliableAckEntriesCount = *pWrite++; + pWrite += (iReliableAckEntriesCount * sizeof(ReliableAckT)); + } + + // is the packet buffer already filled with the DATA protocol component used for the reliability mechanism? + if (pMicrPacket->Head.uFlags & VOIP_PACKET_RELIABLE_FLAG_DATA) + { + int32_t iReliableDataEntriesCount = *pWrite++; + int32_t iReliableDataEntryIndex; + + for (iReliableDataEntryIndex = 0; iReliableDataEntryIndex < iReliableDataEntriesCount; iReliableDataEntryIndex++) + { + ReliableDataT *pCurrent = (ReliableDataT *)pWrite; + uint32_t uSize = _VoipDecodeU16(&pCurrent->info.uSize[0]); + + pWrite += sizeof(pCurrent->info) + uSize; + } + } + + // is the packet buffer already filled with some metadata? + if (pMicrPacket->Head.uFlags & VOIP_PACKET_STATUS_FLAG_METADATA) + { + int32_t iMetaDataSize; + iMetaDataSize = *pWrite++; + pWrite += iMetaDataSize; + } + + // is the packet buffer already filled with some sub-pkts? + if (pMicrPacket->MicrInfo.uNumSubPackets != 0) + { + uint8_t bVariableSubPktLen = ((pMicrPacket->MicrInfo.uSubPacketSize == 0xFF) ? TRUE : FALSE); // check if sub-pkts in this packet are fixed length or variable length + + // if necessary, jump over sub-pkts already packed in the packet buffer + if (bVariableSubPktLen) + { + int32_t iSubPktIndex, iSubPacketSize; + + // find where we need to write; each sub-packet is prepended (one byte) with its length + for (iSubPktIndex = 0; iSubPktIndex < pMicrPacket->MicrInfo.uNumSubPackets; iSubPktIndex++) + { + iSubPacketSize = *pWrite; + pWrite += (iSubPacketSize + 1); + } + } + else + { + pWrite += (pMicrPacket->MicrInfo.uNumSubPackets * pMicrPacket->MicrInfo.uSubPacketSize); + } + } + + return(pWrite); +} + + +/*F********************************************************************************/ +/*! + \Function _VoipPacketGetMetaDataPtr + + \Description + Find position of metadata in the specified voip packet + + \Input *pMicrPacket - packet pointer + + \Output + uint8_t * - ptr to metadata + + \Version 09/25/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t *_VoipPacketGetMetaDataPtr(VoipMicrPacketT *pMicrPacket) +{ + uint8_t *pMetaData = &pMicrPacket->aData[0]; + + // is the packet buffer already filled with ACK+ACKCNF protocol components used for the reliability mechanism? + if (pMicrPacket->Head.uFlags & VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF) + { + int32_t iReliableAckEntriesCount = *pMetaData++; + pMetaData += (iReliableAckEntriesCount * sizeof(ReliableAckT)); + } + + // is the packet buffer already filled with the DATA protocol component used for the reliability mechanism? + if (pMicrPacket->Head.uFlags & VOIP_PACKET_RELIABLE_FLAG_DATA) + { + int32_t iReliableDataEntriesCount = *pMetaData++; + int32_t iReliableDataEntryIndex; + + for (iReliableDataEntryIndex = 0; iReliableDataEntryIndex < iReliableDataEntriesCount; iReliableDataEntryIndex++) + { + ReliableDataT *pCurrent = (ReliableDataT *)pMetaData; + uint32_t uSize = _VoipDecodeU16(&pCurrent->info.uSize[0]); + + pMetaData += sizeof(pCurrent->info) + uSize; + } + } + + return(pMetaData); +} + +/*F********************************************************************************/ +/*! + \Function _VoipPrepareVoipServerSendMask + + \Description + Convert local send mask to voip-server ready send mask + + \Input *pConnectionlist - connection list + + \Output + uint32_t - send mask to be packed in voip packet sent to voip server + + \Version 10/27/2006 (mclouatre) +*/ +/********************************************************************************F*/ +static uint32_t _VoipPrepareVoipServerSendMask(VoipConnectionlistT *pConnectionlist) +{ + int32_t iLocalConnId, iVoipServerConnId; + uint32_t uLocalSendMask = pConnectionlist->uSendMask; + uint32_t uVoipServerSendMask = 0; + + for (iLocalConnId = 0; iLocalConnId < pConnectionlist->iMaxConnections; iLocalConnId++) + { + iVoipServerConnId = pConnectionlist->pConnections[iLocalConnId].iVoipServerConnId; + + // make sure connection is used by voip server + if (iVoipServerConnId != VOIP_CONNID_NONE) + { + // make sure local connection has send bit set + if (uLocalSendMask & (1 << iLocalConnId)) + { + // set the proper bit in the send mask for the voip server + uVoipServerSendMask |= (1 << iVoipServerConnId); + } + } + } + + return(uVoipServerSendMask); +} + + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionIsTranscribedTextRequested + + \Description + Returns TRUE if at least one local user is requesting text + transcription from remote talkers. + + \Input *pConnectionlist - pointer to connectionlist + + \Output + uint32_t - TRUE or FALSE + + \Version 05/05/2017 (mclouatre) +*/ +/********************************************************************************F*/ +static uint32_t _VoipConnectionIsTranscribedTextRequested(VoipConnectionlistT *pConnectionlist) +{ + + int32_t iUserIndex; + + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS; iUserIndex++) + { + if (pConnectionlist->aIsParticipating[iUserIndex] != FALSE) + { + if (pConnectionlist->bTranscribedTextRequested[iUserIndex] != FALSE) + { + return(TRUE); + } + } + } + + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionAllocate + + \Description + Allocate an empty connection for use. + + \Input *pConnectionlist - pointer to connectionlist + \Input iConnID - connection to allocate (or VOIP_CONNID_*) + + \Output + VoipConnectionT * - pointer to connection to use or NULL + + \Version 03/17/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static VoipConnectionT *_VoipConnectionAllocate(VoipConnectionlistT *pConnectionlist, int32_t iConnID) +{ + VoipConnectionT *pConnection = NULL; + + // get a connection ID + pConnection = NULL; + if (iConnID == VOIP_CONNID_NONE) + { + // find a free connection slot + for (iConnID = 0; iConnID < pConnectionlist->iMaxConnections; iConnID++) + { + // if this is an unallocated connection + if (pConnectionlist->pConnections[iConnID].eState == ST_DISC) + { + pConnection = &pConnectionlist->pConnections[iConnID]; + break; + } + } + + // make sure we found a connection + if (pConnection == NULL) + { + NetPrintf(("voipconnection: out of connections\n")); + } + } + else if ((iConnID >= 0) && (iConnID < pConnectionlist->iMaxConnections)) + { + // if we're currently connected, complain + if (pConnectionlist->pConnections[iConnID].eState != ST_DISC) + { + NetPrintf(("voipconnection: [%d] connection not available, currently used for clientId=%d\n", iConnID, pConnectionlist->pConnections[iConnID].uRemoteClientId)); + return(NULL); + } + + // ref connection + pConnection = &pConnectionlist->pConnections[iConnID]; + } + else + { + NetPrintf(("voipconnection: %d is an invalid connection id\n", iConnID)); + } + + // return connection ref to caller + return(pConnection); +} + +/*F********************************************************************************/ +/*! + \Function _VoipSocketSendto + + \Description + Send data with the given socket. + + \Input *pSocket - socket to send on + \Input uClientId - client identifier + \Input *pBuf - data to send + \Input iLen - size of data to send + \Input iFlags - send flags + \Input *pTo - address to send data to + \Input iToLen - length of address structure + + \Output + int32_t - amount of data sent + + \Version 11/30/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipSocketSendto(SocketT *pSocket, uint32_t uClientId, VoipPacketHeadT *pBuf, int32_t iLen, int32_t iFlags, struct sockaddr *pTo, int32_t iToLen) +{ + // make sure socket exists + if (pSocket == NULL) + { + return(0); + } + + // set connection identifier (goes in all packets) + _VoipEncodeU32(pBuf->aClientId, uClientId); + + // send the packet + return(SocketSendto(pSocket, (char *)pBuf, iLen, iFlags, pTo, iToLen)); +} + + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionMatchSessionId + + \Description + Check whether specified session ID matchs the voip connection. + + \Input *pConnectionlist - connectionlist + \Input iConnId - connection ID + \Input uSessionId - session ID + + \Output + uint8_t - TRUE for match, FALSE for no match + + \Version 08/30/2011 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t _VoipConnectionMatchSessionId(VoipConnectionlistT *pConnectionlist, int32_t iConnId, uint32_t uSessionId) +{ + int32_t iSessionIndex; + int32_t bMatch = FALSE; + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnId]; + + NetCritEnter(&pConnectionlist->NetCrit); + + for (iSessionIndex = 0; iSessionIndex < VOIP_MAXSESSIONIDS; iSessionIndex++) + { + if (pConnection->uSessionId[iSessionIndex] == uSessionId) + { + bMatch = TRUE; + } + } + + NetCritLeave(&pConnectionlist->NetCrit); + + return(bMatch); +} + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _VoipConnectionPrintRemoteUsers + + \Description + Print remote users + + \Input *pConnection - connection to transition + \Input iConnID - index of connection + + \Version 03/11/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipConnectionPrintRemoteUsers(VoipConnectionT *pConnection, int32_t iConnID) +{ + int32_t iIndex; + + for (iIndex = 0; iIndex < pConnection->iMaxRemoteUsers; iIndex++) + { + if (!VOIP_NullUser((VoipUserT *)(&pConnection->RemoteUsers[iIndex]))) + { + NetPrintf(("voipconnection: [%d] remote user #%d --> %lld\n", iConnID, iIndex, ((VoipUserT *)&pConnection->RemoteUsers[iIndex])->AccountInfo.iPersonaId)); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionPrintSessionIds + + \Description + Prints all session IDs sharing this voip connection + + \Input *pConnectionlist - connectionlist + \Input iConnId - connection id + + \Version 08/30/2011 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipConnectionPrintSessionIds(VoipConnectionlistT *pConnectionlist, int32_t iConnId) +{ + int32_t iSessionIndex; + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnId]; + + NetCritEnter(&pConnectionlist->NetCrit); + + if (pConnection->uSessionId[0] != 0) + { + NetPrintf(("voipconnection: [%d] IDs of sessions sharing this voip connection are: ", iConnId)); + for (iSessionIndex = 0; iSessionIndex < VOIP_MAXSESSIONIDS; iSessionIndex++) + { + if (pConnection->uSessionId[iSessionIndex] != 0) + { + NetPrintf(("0x%08x\n", pConnection->uSessionId[iSessionIndex])); + } + else + { + break; + } + } + } + else + { + NetPrintf(("voipconnection: [%d] there is no session ID associated with this connection\n", iConnId)); + } + + NetCritLeave(&pConnectionlist->NetCrit); +} +#endif // DIRTYCODE_LOGGING + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionReliableDataEnqueue + + \Description + Add a reliable data entry to the outbound queue of the + specified connection. (Append to tail of linked list) + + \Input *pConnectionlist - connection list + \Input iConnID - connection identifier (VOIP_CONNID_ALL for all) + \Input *pReliableData - pointer to reliable data to be enqueued + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipConnectionReliableDataEnqueue(VoipConnectionlistT *pConnectionlist, int32_t iConnID, ReliableDataT *pReliableData) +{ + int32_t iConnectionIndex; + uint32_t uCurrentTick = NetTick(); + + if (iConnID == VOIP_CONNID_ALL) + { + iConnectionIndex = 0; + } + else + { + iConnectionIndex = iConnID; + } + + while (iConnectionIndex < pConnectionlist->iMaxConnections) + { + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnectionIndex]; + + if (pConnection->eState == ST_ACTV || pConnection->eState == ST_CONN) + { + LinkedReliableDataT *pNewEntry = _VoipGetReliableDataBufferFromFreePool(pConnectionlist, TRUE); + + if (pNewEntry) + { + // fill with input data + ds_memcpy_s(&pNewEntry->data, sizeof(pNewEntry->data), pReliableData, sizeof(*pReliableData)); + + // fill sequence number + pNewEntry->data.info.uSeq = pConnection->uOutSeqNb; + pConnection->uOutSeqNb = _VoipConnectionIncrementReliableSeqNb(pConnection->uOutSeqNb); + + // set remote peer id + _VoipEncodeU32(&pNewEntry->data.info.aRemoteClientId[0], pConnection->uRemoteClientId); + + // fill enqueue tick + pNewEntry->uEnqueueTick = uCurrentTick; + + // add to oubtound queue + _VoipReliableDataEnqueue(&pConnection->pOutboundReliableDataQueue, pNewEntry); + + #if VOIP_RELIABLE_DEBUG + NetPrintf(("voipconnection: [%d] reliable data buffer (%p) added to outbound queue (seq# %03d, %s, tick=%u)\n", iConnectionIndex, pNewEntry, + pNewEntry->data.info.uSeq, _strReliableType[pNewEntry->data.info.uType], pNewEntry->uEnqueueTick)); + #endif + + /* + In the case of voipserver-based topology, to improve responsiveness and increase chances of reliable data + making it quick to other end, we alter the last send tick on the connection. This should result in a + ping packets being sent immediately, and help with reliable data reaching other end for voip connections + that see their voip traffic squelched by the voip server. + */ + if (pConnection->iVoipServerConnId != VOIP_CONNID_NONE) + { + #if VOIP_RELIABLE_DEBUG + NetPrintf(("voipconnection: [%d] last send tick altered to speed up sending of reliable data via ping packet\n", iConnectionIndex)); + #endif + pConnection->uLastSend = pConnection->uLastSend - VOIP_PING_RATE; + } + } + else + { + NetPrintf(("voipconnection: [%d] failed to obtain resources for enqueueing new outbound reliable data\n", iConnectionIndex)); + } + } + + // break out of while loop if we are dealing with a single connection only + if (iConnID == VOIP_CONNID_ALL) + { + iConnectionIndex++; + } + else + { + break; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionReliableDataDequeue + + \Description + Remove reliable data entry from the outbound queue of the + specified connection. (Remove entry at head of linked list) + + \Input *pConnectionlist - connection list + \Input iConnID - connection identifier (VOIP_CONNID_ALL for all) + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipConnectionReliableDataDequeue(VoipConnectionlistT *pConnectionlist, int32_t iConnID) +{ + LinkedReliableDataT *pRemovedEntry = _VoipReliableDataDequeue(&pConnectionlist->pConnections[iConnID].pOutboundReliableDataQueue); + + #if VOIP_RELIABLE_DEBUG + NetPrintf(("voipconnection: [%d] reliable data buffer (%p) removed from outbound queue (seq# %03d, %s)\n", iConnID, pRemovedEntry, + pRemovedEntry->data.info.uSeq, _strReliableType[pRemovedEntry->data.info.uType])); + #endif + + _VoipReturnReliableDataBufferToFreePool(pConnectionlist, pRemovedEntry); +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataPackACKandACKCNF + + \Description + Outbound processing of reliablity data - deals with ACK+ACKCNF protocol + components specifically. + + \Input *pConnectionlist - connectionlist ref + \Input iConnID - id of connection to be acked; VOIP_CONNID_ALL to ack all connections + \Input *pFlags - pointer to uFlags field of the outbound packet + \Input bPing - TRUE if packet is a ping packet, FALSE if packet is a micr packet + \Input *pWrite - pointer to next byte to be written in packet payload + \Input iSpaceLeftInPkt - amount of space left in packet payload + + \Output + uint8_t * - pointer to first byte after the newly added ACK + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t * _VoipReliableDataPackACKandACKCNF(VoipConnectionlistT *pConnectionlist, int32_t iConnID, uint8_t *pFlags, uint32_t bPing, uint8_t *pWrite, int32_t iSpaceLeftInPkt) +{ + uint8_t uEntryCount = 0; + uint8_t *pEntryCount = pWrite; // remember where the entry count needs to be written if any entry added to packet buffer + uint32_t uSpaceLeftInPkt = (uint32_t)iSpaceLeftInPkt; // amount of space still available in packet for reliable data + int32_t iConnectionIndex; + + // skip the byte where the entry count is supposed to be written + pWrite++; + + if (iConnID == VOIP_CONNID_ALL) + { + iConnectionIndex = 0; + } + else + { + iConnectionIndex = iConnID; + } + + while (iConnectionIndex < pConnectionlist->iMaxConnections) + { + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnectionIndex]; + uint32_t bAckOrAckCnfNeeded = FALSE; + + // if our outbound queue is not empty or if the peer at the remote end of the connection is still acking us, then + // add ACK+ACKCNF protocol component to packet. + if ((pConnectionlist->pConnections[iConnectionIndex].pOutboundReliableDataQueue != NULL) || pConnection->bPeerIsAcking) + { + bAckOrAckCnfNeeded = TRUE; + } + + // make sure there is enough space left in destination packet buffer + if (sizeof(ReliableAckT) < uSpaceLeftInPkt) + { + // do we need to send ACK or ACKCNF to this remote client + if (pConnection->bPeerNeedsAck || bAckOrAckCnfNeeded) + { + ReliableAckT ackEntry; + + // set remote peer id + _VoipEncodeU32(&ackEntry.aRemoteClientId[0], pConnection->uRemoteClientId); + + // Fill in ACK value + if (pConnection->bPeerNeedsAck) + { + /* validty range: [1,127] + In a scenario where we have no yet received inbound reliable traffic from remote peer, pConnection->uInSeqNb + is 0 and we end up initializing ackEntry.uAckedSeq with 0 which means 'ignore this field, no sequence number acked' */ + ackEntry.uAckedSeq = pConnection->uInSeqNb; + } + else + { + // means: 'ignore this field, no sequence number acked' + ackEntry.uAckedSeq = 0; + } + + // set ACKCNF + if (bAckOrAckCnfNeeded) + { + // if our outbound queue is not empty, flag that we are still expecting acks back + if (pConnection->pOutboundReliableDataQueue != NULL) + { + ackEntry.uAckedSeq |= VOIP_RELIABLE_ACKCNF_MASK; + } + } + + // add entry in the packet + ds_memcpy_s(pWrite, uSpaceLeftInPkt, &ackEntry, sizeof(ackEntry)); + pWrite += sizeof(ackEntry); + + // removed space used from space left + uSpaceLeftInPkt -= sizeof(ackEntry); + + // increment entry count + uEntryCount += 1; + + // flag that packet contains at least one ACK+ACKCNF protocol component entry + *pFlags |= VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF; + + #if VOIP_RELIABLE_DEBUG + if (pConnection->bPeerNeedsAck) + { + NetPrintf(("voipconnection: [%d] reliable ACK+ACKCNF (acked seq# %03d, %s) added to outbound %s packet for clientId=0x%08x\n", iConnectionIndex, + (ackEntry.uAckedSeq & 0x7F), ((pConnection->pOutboundReliableDataQueue != NULL)?"SendAck":"HaltAck"), (bPing?"PNG":"MIC"), pConnection->uRemoteClientId)); + } + else + { + NetPrintf(("voipconnection: [%d] reliable ACK+ACKCNF (acked seq# INV, %s) added to outbound %s packet for clientId=0x%08x\n", iConnectionIndex, + ((pConnection->pOutboundReliableDataQueue != NULL) ?"SendAck":"HaltAck"), (bPing?"PNG":"MIC"), pConnection->uRemoteClientId)); + } + #endif + } + } + else + { + NetPrintf(("voipconnection: warning - failed to fit all ACK entries in outbound %s packet\n", (bPing?"PNG":"MIC"))); + break; // no space left in packet, force while loop to exit + } + + // break out of while loop if we are dealing with a single connection only + if (iConnID == VOIP_CONNID_ALL) + { + iConnectionIndex++; + } + else + { + break; + } + } + + if (uEntryCount == 0) + { + // reposition pWrite to its initial value + pWrite = pEntryCount; + } + else + { + // write the entry count in the first byte that the input pWrite was pointing to + *pEntryCount = uEntryCount; + } + + return(pWrite); +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataPackDATA + + \Description + Outbound processing of reliablity data - deals with DATA protocol component specifically. + + \Input *pConnectionlist - connectionlist ref + \Input iConnID - id of connection on which the outbound packet is being sent; CANNOT be VOIP_CONNID_ALL + \Input *pFlags - pointer to uFlags field of the outbound packet + \Input bPing - TRUE if packet is a ping packet, FALSE if packet is a micr packet + \Input *pWrite - pointer to next byte to be written in packet payload + \Input iSpaceLeftInPkt - amount of space left in packet payload + + \Output + uint8_t * - pointer to first byte after the newly added DATA + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t * _VoipReliableDataPackDATA(VoipConnectionlistT *pConnectionlist, int32_t iConnID, uint8_t *pFlags, uint32_t bPing, uint8_t *pWrite, int32_t iSpaceLeftInPkt) +{ + uint8_t uEntryCount = 0; + uint8_t *pEntryCount = pWrite; // remember where the entry count needs to be written if any entry added to packet buffer + uint32_t uSpaceLeftInPkt = (uint32_t)iSpaceLeftInPkt; // amount of space still available in packet for reliable data + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnID]; + LinkedReliableDataT *pCurrent = pConnection->pOutboundReliableDataQueue; + + // skip the byte where the entry count is supposed to be written + pWrite++; + + // find how many entries from the outbound reliable data queue fit in the outbound packet + while (pCurrent != NULL) + { + uint16_t uSize = _VoipDecodeU16(&pCurrent->data.info.uSize[0]); + + if (uSize < uSpaceLeftInPkt) + { + // pack reliable data info + ds_memcpy(pWrite, &pCurrent->data.info, sizeof(pCurrent->data.info)); + pWrite += sizeof(pCurrent->data.info); + + // pack reliable data payload + ds_memcpy(pWrite, pCurrent->data.aData, uSize); + pWrite += uSize; + + // removed space used from space left + uSpaceLeftInPkt -= uSize; + + // increment the entry count in the packet + uEntryCount += 1; + + // flag that packet contains at least one DATA protocol component entry + *pFlags |= VOIP_PACKET_RELIABLE_FLAG_DATA; + + #if VOIP_RELIABLE_DEBUG + { + int32_t uClientId = _VoipDecodeU32(&pCurrent->data.info.aRemoteClientId[0]); + NetPrintf(("voipconnection: [%d] reliable DATA (target: 0x%08x, seq# %03d, %s, %d bytes) added to outbound %s packet\n", iConnID, uClientId, pCurrent->data.info.uSeq, + _strReliableType[pCurrent->data.info.uType], uSize, (bPing?"PNG":"MIC"))); + } + #endif + + // move to next pending entry + pCurrent = pCurrent->pNext; + } + else + { + NetPrintf(("voipconnection: [%d] warning - failed to fit all DATA entries in outbound %s packet (last entry size = %d)\n", + iConnID, (bPing?"PNG":"MIC"), uSize)); + pCurrent = NULL; // no space left in packet, force while loop to exit + } + } + + if (uEntryCount == 0) + { + // reposition pWrite to its initial value + pWrite = pEntryCount; + } + else + { + // write the entry count in the first byte that the input pWrite was pointing to + *pEntryCount = uEntryCount; + } + + return(pWrite); +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataOutProcess + + \Description + Outbound processing of reliablity data. To be invoked every time a + ping packet or a micr packet is sent. Reliability protocol components + (ACK, ACKCNF, DATA) are going to be added to the packet if necessary. + + \Input *pConnectionlist - connectionlist ref + \Input iConnID - connection id; VOIP_CONNID_ALL if packet is being sent to voipserver + \Input *pPacketHeader - pointer to header of the outbound packet + \Input *pPacketPayload - pointer to first payload byte of outbound packet + \Input iPayloadCapacity - amount of bytes we can fit in the payload + + \Output + uint8_t * - pointer to first byte after reliable data in packet payload + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t * _VoipReliableDataOutProcess(VoipConnectionlistT *pConnectionlist, int32_t iConnID, VoipPacketHeadT *pPacketHeader, uint8_t *pPacketPayload, int32_t iPayloadCapacity) +{ + int32_t iSpaceLeftInPkt = iPayloadCapacity; + uint32_t bPing = ((pPacketHeader->aType[0] == 'P' && pPacketHeader->aType[1] == 'N' && pPacketHeader->aType[2] == 'G') ? TRUE : FALSE); + uint8_t *pWrite; + + // add ACK and ACKCNF to packet as necessary + pWrite = _VoipReliableDataPackACKandACKCNF(pConnectionlist, iConnID, &pPacketHeader->uFlags, bPing, pPacketPayload, iSpaceLeftInPkt); + iSpaceLeftInPkt -= (pWrite - pPacketPayload); + + if (iConnID == VOIP_CONNID_ALL) + { + int32_t iConnectionIndex; + int32_t iLargestTickDiff = 0; + + /* + When voip traffic flows through the voip server, we need to pick a local voip connection + to pack the outbound reliable data entries from. Each connection have its own outbound + reliable queue. They are all populated with same entries tagged with same sequence numbers. + But the acking progress on each queue may progress differently. For instance, queue X may + have entry seq nb 10 acked, but queue Y may still be waiting for entry seq nb 8 to be acked. + + The best we can do is pick the connection that has the oldest pending reliable data. Other + connections will benefit of this too if there are still waiting for ack of that entry, or + they will rely on the next ping packet to go out. + */ + + for (iConnectionIndex = 0; iConnectionIndex < pConnectionlist->iMaxConnections; iConnectionIndex++) + { + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnectionIndex]; + + if ((pConnection->eState == ST_ACTV) && (pConnection->pOutboundReliableDataQueue != NULL)) + { + int32_t iTickDiff = NetTickDiff(NetTick(), pConnection->pOutboundReliableDataQueue->uEnqueueTick); + + if (iLargestTickDiff < iTickDiff) + { + iLargestTickDiff = iTickDiff; + iConnID = iConnectionIndex; + } + } + } + // if above for loop did not match on any connection, fallback to connection index 0 + if (iConnID == VOIP_CONNID_ALL) + { + iConnID = 0; + } + } + + pWrite = _VoipReliableDataPackDATA(pConnectionlist, iConnID, &pPacketHeader->uFlags, bPing, pWrite, iSpaceLeftInPkt); + + return(pWrite); +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataInboundUserAddProcess + + \Description + Inbound processing of reliablity data of type VOIP_RELIABLE_TYPE_USERADD. + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - connection on which the packet was received + \Input *pReliableData - pointer to inbound reliable data entry + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipReliableDataInboundUserAddProcess(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, ReliableDataT *pReliableData) +{ + uint8_t uUserIndex = pReliableData->aData[0]; // first byte is user index + VoipUserT *pRemoteUser = (VoipUserT *)(&pReliableData->aData[0] + 1); + + if (VOIP_NullUser((VoipUserT *)&pConnection->RemoteUsers[uUserIndex])) + { + int32_t iConnID = pConnection - &pConnectionlist->pConnections[0]; + + // update collection of remote users for this connection + VOIP_CopyUser(&pConnection->RemoteUsers[uUserIndex], pRemoteUser); + + // register the user + pConnectionlist->pRegUserCb((VoipUserT *)&pConnection->RemoteUsers[uUserIndex], (uint32_t)iConnID, TRUE, pConnectionlist->pRegUserUserData); + + // force re-application of channel config since we have a new remote player + ((VoipCommonRefT *)VoipGetRef())->bApplyChannelConfig = TRUE; + +#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + // reapply player-to-player xone comm relationships + pConnectionlist->bApplyRelFromMuting = TRUE; +#endif + // set mute list to be updated + pConnectionlist->bUpdateMute = TRUE; + + #if DIRTYCODE_LOGGING + _VoipConnectionPrintRemoteUsers(pConnection, (pConnection - &pConnectionlist->pConnections[0])); + #endif + } + else + { + NetPrintf(("voipconnection: [%d] join-in-progress for remote user %lld failed because index %d already used by %lld\n", (pConnection - &pConnectionlist->pConnections[0]), + ((VoipUserT *)pRemoteUser)->AccountInfo.iPersonaId, uUserIndex, ((VoipUserT *)&pConnection->RemoteUsers[uUserIndex])->AccountInfo.iPersonaId)); + } +} + + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataInboundUserRemProcess + + \Description + Inbound processing of reliablity data of type VOIP_RELIABLE_TYPE_USERREM. + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - connection on which the packet was received + \Input *pReliableData - pointer to inbound reliable data entry + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipReliableDataInboundUserRemProcess(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, ReliableDataT *pReliableData) +{ + uint8_t uUserIndex = pReliableData->aData[0]; // first byte is user index + VoipUserT *pRemoteUser = (VoipUserT *)(&pReliableData->aData[0] + 1); + + if (!VOIP_NullUser((VoipUserT *)&pConnection->RemoteUsers[uUserIndex])) + { + if (memcmp(pRemoteUser, &pConnection->RemoteUsers[uUserIndex], sizeof(*pRemoteUser)) == 0) + { + int32_t iConnID = pConnection - &pConnectionlist->pConnections[0]; + + // unregister the user + pConnectionlist->pRegUserCb((VoipUserT *)&pConnection->RemoteUsers[uUserIndex], (uint32_t)iConnID, FALSE, pConnectionlist->pRegUserUserData); + + // force re-application of channel config since we lost a remote player + ((VoipCommonRefT *)VoipGetRef())->bApplyChannelConfig = TRUE; + +#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + // reapply player-to-player xone comm relationships + pConnectionlist->bApplyRelFromMuting = TRUE; +#endif + + // set mute list to be updated + pConnectionlist->bUpdateMute = TRUE; + + // clear the user + VOIP_ClearUser((VoipUserT *)&pConnection->RemoteUsers[uUserIndex]); + + #if DIRTYCODE_LOGGING + _VoipConnectionPrintRemoteUsers(pConnection, (pConnection - &pConnectionlist->pConnections[0])); + #endif + } + else + { + NetPrintf(("voipconnection: [%d] leave-in-progress for remote user %lld failed because remote user at index %d rather is %lld\n", (pConnection - &pConnectionlist->pConnections[0]), + ((VoipUserT *)pRemoteUser)->AccountInfo.iPersonaId, uUserIndex, ((VoipUserT *)&pConnection->RemoteUsers[uUserIndex])->AccountInfo.iPersonaId)); + } + } + else + { + NetPrintf(("voipconnection: [%d] leave-in-progress for remote user %lld failed because no remote user at index %d\n", (pConnection - &pConnectionlist->pConnections[0]), + ((VoipUserT *)pRemoteUser)->AccountInfo.iPersonaId, uUserIndex)); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataInboundOpaqueProcess + + \Description + Inbound processing of reliablity data of type VOIP_RELIABLE_TYPE_OPAQUE. + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - connection on which the packet was received + \Input *pReliableData - pointer to inbound reliable data entry + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipReliableDataInboundOpaqueProcess(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, ReliableDataT *pReliableData) +{ + uint16_t uDataSize = _VoipDecodeU16(&pReliableData->info.uSize[0]); + int32_t iConnID = pConnection - &pConnectionlist->pConnections[0]; + pConnectionlist->pOpaqueCb(iConnID, &pReliableData->aData[0], uDataSize, pConnectionlist->pOpaqueUserData); +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataInboundTextProcess + + \Description + Inbound processing of reliablity data of type VOIP_RELIABLE_TYPE_TEXT. + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - connection on which the packet was received + \Input *pReliableData - pointer to inbound reliable data entry + + \Version 05/02/2017 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipReliableDataInboundTextProcess(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, ReliableDataT *pReliableData) +{ + uint8_t uUserIndex = pReliableData->aData[0]; // first byte is user index + uint16_t uTextSize = _VoipDecodeU16(&pReliableData->info.uSize[0]) - 1; // text size is payload size minus the byte used for the user index + + if (uTextSize > 0) + { + if (_VoipConnectionIsTranscribedTextRequested(pConnectionlist)) + { + int32_t iConnID = pConnection - &pConnectionlist->pConnections[0]; + + if (pConnectionlist->uRecvMask & (1 << iConnID)) + { + pConnectionlist->pTextCb(iConnID, uUserIndex, (char *)&pReliableData->aData[1], (void *)pConnectionlist->pTextUserData); + } + else + { + NetPrintf(("voipconnection: inbound transcribed text dropped because connection (%d) is muted\n", iConnID)); + } + } + else + { + NetPrintf(("voipconnection: inbound transcribed text dropped because none of the local players requested for it (normal when voip is routed through DirytCast voipserver)\n")); + } + } + else + { + NetPrintf(("voipconnection: inbound transcribed text dropped because decoding resulted in an empty string\n")); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataUnpackACKandACKCNF + + \Description + Inbound processing of reliablity data - deals with ACK+ACKCNF protocol + components specifically. + + \Input *pConnectionlist - connectionlist ref + \Input uSrcClientId - client id of originator + \Input uFlags - uFlags from the inbound packet + \Input bPing - TRUE if packet is a ping packet, FALSE if packet is a micr packet + \Input *pRead - pointer to next byte to be read in packet payload + + \Output + uint8_t * - pointer to first byte after ACK + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t * _VoipReliableDataUnpackACKandACKCNF(VoipConnectionlistT *pConnectionlist, uint32_t uSrcClientId, uint8_t uFlags, uint32_t bPing, uint8_t *pRead) +{ + int32_t iConnectionIndex; + VoipConnectionT *pConnection = NULL; + uint32_t bPeerIsAckingUs = FALSE; + + // find connection matching the source client id + for (iConnectionIndex = 0; iConnectionIndex < pConnectionlist->iMaxConnections; iConnectionIndex++) + { + if (pConnectionlist->pConnections[iConnectionIndex].uRemoteClientId == uSrcClientId) + { + pConnection = &pConnectionlist->pConnections[iConnectionIndex]; + break; + } + } + + // is the packet buffer already filled with ACK+ACKCNF protocol components used for the reliability mechanism? + if (uFlags & VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF) + { + int32_t iReliableAckEntriesCount = *pRead++; + int32_t iReliableAckEntryIndex; + + for (iReliableAckEntryIndex = 0; iReliableAckEntryIndex < iReliableAckEntriesCount; iReliableAckEntryIndex++) + { + uint32_t uClientId; + ReliableAckT *pReliableAck = (ReliableAckT *)pRead; + + pRead += sizeof(ReliableAckT); + + if (pConnection != NULL) + { + // determine our local client id from the remote client's point of view + uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId; + + // extract client id from ACK+ACKCNF protocol component entry + uClientId = _VoipDecodeU32(&pReliableAck->aRemoteClientId[0]); + + // ignore the entry if it is not for us + if (uClientId == uLocalClientId) + { + uint8_t uAckedSeq; + + // deal with ACKCNF + pConnection->bPeerNeedsAck = ((pReliableAck->uAckedSeq & VOIP_RELIABLE_ACKCNF_MASK) ? TRUE : FALSE); + + // deal with ACK + uAckedSeq = pReliableAck->uAckedSeq & ~VOIP_RELIABLE_ACKCNF_MASK; + if (uAckedSeq != 0) + { + #if VOIP_RELIABLE_DEBUG + NetPrintf(("voipconnection: [%d] reliable ACK+ACKCNF (acked seq# %03d, %s) found in inbound %s packet from clientId=0x%08x\n", + iConnectionIndex, uAckedSeq, (pConnection->bPeerNeedsAck ?"SendAck":"HaltAck"), (bPing?"PNG":"MIC"), uSrcClientId)); + #endif + bPeerIsAckingUs = TRUE; + + while ( (pConnection->pOutboundReliableDataQueue != NULL) && + (uAckedSeq >= pConnection->pOutboundReliableDataQueue->data.info.uSeq) ) + { + _VoipConnectionReliableDataDequeue(pConnectionlist, iConnectionIndex); + } + } + else + { + #if VOIP_RELIABLE_DEBUG + NetPrintf(("voipconnection: [%d] reliable ACK+ACKCNF (acked seq# INV, %s) found in inbound %s packet from clientId=0x%08x\n", + iConnectionIndex, (pConnection->bPeerNeedsAck ?"SendAck":"HaltAck"), (bPing?"PNG":"MIC"), uSrcClientId)); + #endif + } + } + } + else + { + NetPrintf(("voipconnection: [%d] dropped inbound ACK+ACKCNF - failed to find connection matching originating clientId=0x%08x\n", iConnectionIndex, uSrcClientId)); + } + } + } + + if (pConnection != NULL) + { + pConnection->bPeerIsAcking = bPeerIsAckingUs; + } + + return(pRead); +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataUnpackDATA + + \Description + Inbound processing of reliablity data - deals with DATA protocol component specifically. + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - connection on which the packet was received + \Input uFlags - uFlags from the inbound packet + \Input bPing - TRUE if packet is a ping packet, FALSE if packet is a micr packet + \Input *pRead - pointer to next byte to be read in packet payload + + \Output + uint8_t * - pointer to first byte after DATA + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t * _VoipReliableDataUnpackDATA(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, uint8_t uFlags, uint32_t bPing, uint8_t *pRead) +{ + // is the packet buffer already filled with the ACK protocol component used for the reliability mechanism? + if (uFlags & VOIP_PACKET_RELIABLE_FLAG_DATA) + { + int32_t iReliableDataEntriesCount = *pRead++; + int32_t iReliableDataEntryIndex; + + for (iReliableDataEntryIndex = 0; iReliableDataEntryIndex < iReliableDataEntriesCount; iReliableDataEntryIndex++) + { + ReliableDataT *pReliableData = (ReliableDataT *)pRead; + uint16_t uSize = _VoipDecodeU16(&pReliableData->info.uSize[0]); + uint32_t uClientId = _VoipDecodeU32(&pReliableData->info.aRemoteClientId[0]); + uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId; + + // move read pointer ahead + pRead += sizeof(pReliableData->info) + uSize; + + // make sure this is for us + if (uClientId == uLocalClientId) + { + if (pReliableData->info.uSeq == _VoipConnectionIncrementReliableSeqNb(pConnection->uInSeqNb)) + { + // update next expected inbound reliable seq nb + pConnection->uInSeqNb = pReliableData->info.uSeq; + + #if VOIP_RELIABLE_DEBUG + NetPrintf(("voipconnection: [%d] reliable DATA (seq# %03d, %s, %d bytes) found in inbound %s packet\n", (pConnection - &pConnectionlist->pConnections[0]), + pReliableData->info.uSeq, _strReliableType[pReliableData->info.uType], uSize, (bPing?"PNG":"MIC"))); + #endif + + if (pReliableData->info.uType == VOIP_RELIABLE_TYPE_USERADD) + { + _VoipReliableDataInboundUserAddProcess(pConnectionlist, pConnection, pReliableData); + } + else if (pReliableData->info.uType == VOIP_RELIABLE_TYPE_USERREM) + { + _VoipReliableDataInboundUserRemProcess(pConnectionlist, pConnection, pReliableData); + } + else if (pReliableData->info.uType == VOIP_RELIABLE_TYPE_OPAQUE) + { + _VoipReliableDataInboundOpaqueProcess(pConnectionlist, pConnection, pReliableData); + } + else if (pReliableData->info.uType == VOIP_RELIABLE_TYPE_TEXT) + { + _VoipReliableDataInboundTextProcess(pConnectionlist, pConnection, pReliableData); + } + else + { + NetPrintf(("voipconnection: [%d] unsupported inbound reliable data type (%d)\n", (pConnection - &pConnectionlist->pConnections[0]), pReliableData->info.uType)); + } + } + else + { + #if VOIP_RELIABLE_DEBUG + NetPrintf(("voipconnection: [%d] dropped inbound reliable data embedded in %s packet - seq nb mismatch (got: %d - expected: %d)\n", + (pConnection - &pConnectionlist->pConnections[0]), (bPing ? "PNG" : "MIC"), pReliableData->info.uSeq, _VoipConnectionIncrementReliableSeqNb(pConnection->uInSeqNb))); + #endif + } + } + else + { + #if VOIP_RELIABLE_DEBUG + NetPrintf(("voipconnection: [%d] dropped inbound reliable data embedded in %s packet - client id mismatch (target: 0x%08x - local: 0x%08x)\n", + (pConnection - &pConnectionlist->pConnections[0]), (bPing ? "PNG" : "MIC"), uClientId, uLocalClientId)); + #endif + } + } + } + + return(pRead); +} + +/*F********************************************************************************/ +/*! + \Function _VoipReliableDataInProcess + + \Description + Inbound processing of reliablity data. To be invoked every time a + ping packet or a micr packet is received. Reliability protocol components + (ACK, ACKCNF, DATA) included in the packet are going to be processed appropriately. + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - connection on which the packet was received + \Input *pPacketHeader - pointer to header of the inbound packet + \Input *pPacketPayload - pointer to first payload byte of inbound packet + + \Output + uint8_t - first byte following reliable data in packet payload + + \Version 09/14/2014 (mclouatre) +*/ +/********************************************************************************F*/ +static uint8_t * _VoipReliableDataInProcess(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, VoipPacketHeadT *pPacketHeader, uint8_t *pPacketPayload) +{ + // note: for voipserver-based topology, uSrcClientId may not be same as pConnection->uClientId + uint32_t uSrcClientId = _VoipDecodeU32(&pPacketHeader->aClientId[0]); + uint32_t bPing = ((pPacketHeader->aType[0] == 'P' && pPacketHeader->aType[1] == 'N' && pPacketHeader->aType[2] == 'G') ? TRUE : FALSE); + + uint8_t *pRead = _VoipReliableDataUnpackACKandACKCNF(pConnectionlist, uSrcClientId, pPacketHeader->uFlags, bPing, pPacketPayload); + + pRead = _VoipReliableDataUnpackDATA(pConnectionlist, pConnection, pPacketHeader->uFlags, bPing, pRead); + + return(pRead); +} + +/*F********************************************************************************/ +/*! + \Function _VoipUpdateUserFlags + + \Description + Update the flags field for each user just before serialization. + + \Input *pConnectionlist - connectionlist ref + + \Version 10/02/2019 (cvienneau) +*/ +/********************************************************************************F*/ +static void _VoipUpdateUserFlags(VoipConnectionlistT *pConnectionlist) +{ + int32_t iUserIndex; + uint8_t bCrossPlay = VoipStatus(VoipGetRef(), 'xply', 0, NULL, 0); + + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++) + { + if (pConnectionlist->aIsParticipating[iUserIndex] == TRUE) + { + // is cross play setup for the local users + if (bCrossPlay) + { + pConnectionlist->LocalUsers[iUserIndex].uFlags |= VOIPUSER_FLAG_CROSSPLAY; + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionConn + + \Description + Send a connection packet to peer + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - pointer to connection to connect on + \Input uTick - current tick count + + \Version 03/20/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipConnectionConn(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, uint32_t uTick) +{ + VoipConnPacketT ConnPacket; + int32_t iUserIndex; + uint32_t uLocalClientId = 0; + uint32_t uSize = sizeof(ConnPacket) - sizeof(ConnPacket.LocalUsers) + (sizeof(*ConnPacket.LocalUsers) * VOIP_MAXLOCALUSERS_EXTENDED); + + // output diagnostic info + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) sending connect packet to remote console (%a:%d)\n", + VOIP_ConnID(pConnectionlist, pConnection), pConnection->uSessionId[0], + SocketNtohl(pConnection->SendAddr.sin_addr.s_addr), SocketNtohs(pConnection->SendAddr.sin_port))); + + // send a connection packet + ds_memclr(&ConnPacket, sizeof(VoipConnPacketT)); + ds_memcpy_s(&ConnPacket.Head, sizeof(ConnPacket.Head), &_Voip_ConnPacket, sizeof(_Voip_ConnPacket)); + _VoipEncodeU32(ConnPacket.aRemoteClientId, pConnection->uRemoteClientId); + _VoipEncodeU32(ConnPacket.Head.aSessionId, pConnection->uSessionId[0]); + _VoipEncodeLocalHeadsetStatus(pConnectionlist, &(ConnPacket.Head)); + if (_VoipConnectionIsTranscribedTextRequested(pConnectionlist)) + { + ConnPacket.Head.uFlags |= VOIP_PACKET_STATUS_FLAG_STT; + } + _VoipUpdateUserFlags(pConnectionlist); + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++) + { + if (pConnectionlist->aIsParticipating[iUserIndex] != FALSE) + { + _VoipEncodeVoipUser(&ConnPacket.LocalUsers[iUserIndex], &pConnectionlist->LocalUsers[iUserIndex]); + } + } + + ConnPacket.bConnected = pConnection->bConnPktRecv; + + // extended local users always have an extra slot allocated and are not the same as maxlocalusers + ConnPacket.uNumLocalUsers = VOIP_MAXLOCALUSERS_EXTENDED; + + #if VOIP_CONNECTION_DEBUG + NetPrintMem(&ConnPacket, sizeof(ConnPacket), "connection packet data"); + #endif + uLocalClientId = pConnection->uLocalClientId == 0 ? pConnectionlist->uClientId : pConnection->uLocalClientId; + pConnection->iSendResult = _VoipSocketSendto(pConnectionlist->pSocket, uLocalClientId, &ConnPacket.Head, uSize, 0, (struct sockaddr *)&pConnection->SendAddr, sizeof(pConnection->SendAddr)); + + // update timestamp + pConnection->uLastSend = uTick; +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionDisc + + \Description + Send a disconnection request to peer. + + \Input iConnId - connection id + \Input uClientId - client id + \Input uRemoteClientId - remote client id + \Input uSessionId - session identifier + \Input *pSendAddr - address to send disconnect to + \Input *pSocket - socket to send request with + + \Version 03/21/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipConnectionDisc(int32_t iConnId, uint32_t uClientId, uint32_t uRemoteClientId, uint32_t uSessionId, struct sockaddr_in *pSendAddr, SocketT *pSocket) +{ + VoipDiscPacketT DiscPacket; + + // send a disconnect packet + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) sending disconnect packet to %a:%d\n", + iConnId, uSessionId, SocketNtohl(pSendAddr->sin_addr.s_addr), SocketNtohs(pSendAddr->sin_port))); + + // set up disc packet + ds_memcpy_s(&DiscPacket.Head, sizeof(DiscPacket.Head), &_Voip_DiscPacket, sizeof(_Voip_DiscPacket)); + _VoipEncodeU32(DiscPacket.aRemoteClientId, uRemoteClientId); + _VoipEncodeU32(DiscPacket.Head.aSessionId, uSessionId); + + // send disc packet + _VoipSocketSendto(pSocket, uClientId, &DiscPacket.Head, sizeof(DiscPacket), 0, (struct sockaddr *)pSendAddr, sizeof(*pSendAddr)); +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionInit + + \Description + Transition from connecting to connected. + + \Input *pConnectionlist - connectionlist connection is a part of + \Input *pConnection - connection to transition + \Input iConnID - index of connection + + \Version 03/21/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipConnectionInit(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iConnID) +{ + int32_t iLocalUserIndex; + + NetPrintf(("voipconnection: [%d] connection established; talking to remote user(s)\n", iConnID)); + + #if DIRTYCODE_LOGGING + _VoipConnectionPrintRemoteUsers(pConnection, iConnID); + #endif + + // register the new remote users with voipheadset + VoipConnectionRegisterRemoteTalkers(pConnectionlist, iConnID, TRUE); + + // we want to call this before the connection goes to state ST_ACTV + // to avoid race conditions where the voipthread would start working with this conn before the channel config is updated + VoipCommonApplyChannelConfig((VoipCommonRefT *)VoipGetRef()); + + // update connection flags + pConnection->uRemoteConnStatus = VOIP_CONN_CONNECTED; + + // flag that this connection shall now be considered when updating friend status bitmask + if (pConnectionlist->iFriendConnId == VOIP_CONNID_NONE) + { + pConnectionlist->iFriendConnId = iConnID; + } + else + { + pConnectionlist->iFriendConnId = VOIP_CONNID_ALL; + } + + // initialize timer used for sending at a fixed 10hz rate + for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iLocalUserIndex++) + { + pConnection->aVoiceSendTimer[iLocalUserIndex] = NetTick(); + } + + /* + To avoid multi-threading race conditions, it is important to only move the connection state to ST_ACTV at the end of + the function (after initializing all connection vars and updating channel config) because some code exercised by the + _VoipThread (like _VoipUpdateRemoteStatus()) may start to use these fields concurrently as soon as the connection state + becomes ST_ACTV + */ + pConnection->eState = ST_ACTV; +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionPing + + \Description + Ping a connected remote peer. + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - pointer to connection to send on + \Input uTick - current tick count + + \Version 03/20/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipConnectionPing(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, uint32_t uTick) +{ + VoipPingPacketT PingPacket; + int32_t iUserIndex; + uint32_t uChannelUserIndices = 0; + uint8_t *pWrite = PingPacket.aData; + const uint8_t *pEnd = pWrite+sizeof(PingPacket.aData); + uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId; + + #if VOIP_CONNECTION_DEBUG + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) pinging %a:%d\n", + VOIP_ConnID(pConnectionlist, pConnection), pConnection->uSessionId[0], + SocketNtohl(pConnection->SendAddr.sin_addr.s_addr), SocketNtohs(pConnection->SendAddr.sin_port))); + #endif + + // set up ping packet + ds_memcpy_s(&PingPacket.Head, sizeof(PingPacket.Head), &_Voip_PingPacket, sizeof(_Voip_PingPacket)); + _VoipEncodeLocalHeadsetStatus(pConnectionlist, &(PingPacket.Head)); + _VoipEncodeU32(PingPacket.aRemoteClientId, pConnection->uRemoteClientId); + _VoipEncodeU32(PingPacket.Head.aSessionId, pConnection->uSessionId[0]); + if (_VoipConnectionIsTranscribedTextRequested(pConnectionlist)) + { + PingPacket.Head.uFlags |= VOIP_PACKET_STATUS_FLAG_STT; + } + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++) + { + /* write the channel information for the participating user indices and + set the bit so the receiving side knows which index it corresponds to */ + if (pConnectionlist->aIsParticipating[iUserIndex]) + { + uChannelUserIndices |= 1 << iUserIndex; + _VoipEncodeU32(pWrite, pConnectionlist->aChannels[iUserIndex]); + pWrite += 4; + } + } + _VoipEncodeU32(PingPacket.aChannelUserIndices, uChannelUserIndices); + + // if needed, add reliable protocol components to this ping packet + pWrite = _VoipReliableDataOutProcess(pConnectionlist, (pConnection - &pConnectionlist->pConnections[0]), &PingPacket.Head, pWrite, pEnd - pWrite); + + // send a 'ping' packet + pConnection->iSendResult = _VoipSocketSendto(pConnectionlist->pSocket, uLocalClientId, &PingPacket.Head, (pWrite - (uint8_t *)&PingPacket), 0, (struct sockaddr *)&pConnection->SendAddr, sizeof(pConnection->SendAddr)); + + // update send timestamp and local status flag + pConnection->uLastSend = uTick; + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS; ++iUserIndex) + { + if (NetTickDiff(uTick, pConnectionlist->uLocalUserLastSend[iUserIndex]) > VOIP_PING_RATE) + { + pConnectionlist->uLocalUserStatus[iUserIndex] &= ~VOIP_LOCAL_USER_SENDVOICE; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionIsSendPktReady + + \Description + Determine if we should send the current packet. + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - connection pointer + \Input iUserIndex - index of local user that generated the voice to be sent + \Input uTick - current tick count + + \Output + uint32_t - TRUE if ready, FALSE otherwise. + + \Version 10/01/2011 (mclouatre) +*/ +/********************************************************************************F*/ +static uint32_t _VoipConnectionIsSendPktReady(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iUserIndex, uint32_t uTick) +{ + VoipMicrPacketT *pVoipPkt = &pConnection->VoipMicrPacket[iUserIndex]; + uint8_t *pWrite = _VoipPacketGetWritePtr(pVoipPkt); + uint8_t bVariableSubPktLen = ((pVoipPkt->MicrInfo.uSubPacketSize == 0xFF) ? TRUE : FALSE); // check if sub-pkts in this packet are fixed length or variable length + int32_t iEstimatedResultingPacketPayloadSize; + + // if an additional sub-packet would cause us to overflow the sub-packet buffer, send packet + if (bVariableSubPktLen) + { + // is next packet going to overflow? 1 byte for prepended size + estimated subpkt size + iEstimatedResultingPacketPayloadSize = (pWrite + 1 + pConnection->iMaxSubPktSize) - pVoipPkt->aData; + } + else + { + iEstimatedResultingPacketPayloadSize = (pWrite + pVoipPkt->MicrInfo.uSubPacketSize) - pVoipPkt->aData; + } + + if (iEstimatedResultingPacketPayloadSize > (signed)sizeof(pVoipPkt->aData)) + { + return(TRUE); + } + + // if 10hz timer has elapsed, send packet + if ((pVoipPkt->MicrInfo.uNumSubPackets > 0) && (NetTickDiff(uTick, pConnection->aVoiceSendTimer[iUserIndex]) >= 0)) + { + return(TRUE); + } + + // not yet ready to send + return(FALSE); +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionSendSingle + + \Description + Send a single buffered voice packet, if it is time to send it. + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - pointer to connection to send on + \Input iUserIndex - index of the local user that generated the voice data to be sent + \Input uTick - current tick count + \Input bFlush - flush data + + \Version 03/20/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipConnectionSendSingle(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iUserIndex, uint32_t uTick, uint32_t bFlush) +{ + // ref current buffered packet + VoipMicrPacketT *pPacket = &pConnection->VoipMicrPacket[iUserIndex]; + uint32_t bSendPacket = FALSE; + int32_t iParticipatingUserIndex; + uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId; + + // should we send? + if (_VoipConnectionIsSendPktReady(pConnectionlist, pConnection, iUserIndex, uTick) || (bFlush == TRUE)) + { + bSendPacket = TRUE; + } + + // send packet? + if (bSendPacket == TRUE) + { + int32_t iPacketSize, iValidPayloadSize; + uint32_t uVoipServerReadySendMask = 0; + + // set packet sequence + pPacket->MicrInfo.uSeqn = pConnection->uSendSeqn++; + + // set the user index + if (iUserIndex == VOIP_SHARED_USER_INDEX) + { + pPacket->MicrInfo.uUserIndex = VOIP_SHARED_REMOTE_INDEX; + } + else + { + pPacket->MicrInfo.uUserIndex = iUserIndex; + } + + // set the channels + _VoipEncodeU32(pPacket->MicrInfo.channels.aChannelId, pConnectionlist->aChannels[iUserIndex]); + + // set the session id + _VoipEncodeU32(pPacket->Head.aSessionId, pConnection->uSessionId[0]); + + // flag whether one of the local users is requesting transcribed text + if (_VoipConnectionIsTranscribedTextRequested(pConnectionlist)) + { + pPacket->Head.uFlags |= VOIP_PACKET_STATUS_FLAG_STT; + } + + // set send mask for VoipServer use + if (pConnection->iVoipServerConnId != VOIP_CONNID_NONE) + { + uVoipServerReadySendMask = _VoipPrepareVoipServerSendMask(pConnectionlist); + } + _VoipEncodeU32(pPacket->MicrInfo.aSendMask, uVoipServerReadySendMask); + _VoipEncodeLocalHeadsetStatus(pConnectionlist, &pPacket->Head); + + #if VOIP_CONNECTION_DEBUG + // display verbose status (metered to about once every three seconds) + if ((pConnection->uSendSeqn % 30) == 0) + { + char strSubPktSize[32]; + sprintf(strSubPktSize, "size %d (bytes)", pPacket->MicrInfo.uSubPacketSize); + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) local usr %d sending pkt (seq#:%d) with %d sub-pkt(s) of %s to %a:%d | voip channels:0x%08x\n", + VOIP_ConnID(pConnectionlist, pConnection), pConnection->uSessionId[0], iUserIndex, pPacket->MicrInfo.uSeqn, pPacket->MicrInfo.uNumSubPackets, + (pPacket->MicrInfo.uSubPacketSize == 0xFF ? "variable-length" : strSubPktSize), SocketNtohl(pConnection->SendAddr.sin_addr.s_addr), SocketNtohs(pConnection->SendAddr.sin_port), + pConnectionlist->aChannels[iUserIndex])); + } + #endif + + // send voice packet + iValidPayloadSize = _VoipPacketGetWritePtr(&pConnection->VoipMicrPacket[iUserIndex]) - &pPacket->aData[0]; + iPacketSize = sizeof(pConnection->VoipMicrPacket[iUserIndex]) - sizeof(pPacket->aData) + iValidPayloadSize; + pConnection->iSendResult = _VoipSocketSendto(pConnectionlist->pSocket, uLocalClientId, &pPacket->Head, + iPacketSize, 0, (struct sockaddr *)&pConnection->SendAddr, sizeof(pConnection->SendAddr)); + + // update send info -- don't update uLastSend in server mode, to force pinging even when voice is being sent + if (pConnection->iVoipServerConnId == VOIP_CONNID_NONE) + { + pConnection->uLastSend = uTick; + } + + // if packet contains reliable data, don't update aVoiceSendTimer to make sure we retry sending voip sub-packets soon (in case reliable data took all the place in the packet payload) + if ((pPacket->Head.uFlags & VOIP_PACKET_RELIABLE_FLAG_DATA) == 0) + { + pConnection->aVoiceSendTimer[iUserIndex] = uTick + VOIP_MSPERPACKET; + } + + // if the voice packet is from a shared user set all the participating user's status + if (iUserIndex == VOIP_SHARED_USER_INDEX) + { + for (iParticipatingUserIndex = 0; iParticipatingUserIndex < VOIP_MAXLOCALUSERS; ++iParticipatingUserIndex) + { + if (pConnectionlist->aIsParticipating[iParticipatingUserIndex] == TRUE) + { + #if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + /* For xboxone, opaque traffic is always sent as if it was originated from the + "shared user". We need to query VoipHeadset (via VoipCommon) to find out which + local user is exactly detected as talking by the MS game chat 2's chat manager + and consequently involved in generating the current outbound flow of voip packets. */ + if (VoipCommonStatus((VoipCommonRefT *)VoipGetRef(), 'talk', iParticipatingUserIndex, NULL, 0)) + #endif + { + pConnectionlist->uLocalUserStatus[iParticipatingUserIndex] |= VOIP_LOCAL_USER_SENDVOICE; + pConnectionlist->uLocalUserLastSend[iParticipatingUserIndex] = uTick; + } + } + } + } + else + { + pConnectionlist->uLocalUserStatus[iUserIndex] |= VOIP_LOCAL_USER_SENDVOICE; + pConnectionlist->uLocalUserLastSend[iUserIndex] = uTick; + } + + // reset buffer used to build voip micr packet + ds_memclr(&pPacket->MicrInfo, sizeof(pPacket->MicrInfo)); // clear micr packet info + pPacket->Head.uFlags = 0; // clear flags in packet header + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionRecvSingle + + \Description + Handle data incoming on a connection. + + \Input *pConnectionlist - connectionlist connection belongs to + \Input *pConnection - pointer to connection to receive on + \Input *pSendAddr - remote address data was received from + \Input *pPacket - packet data that was received + \Input iSize - size of data received + + \Version 03/21/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipConnectionRecvSingle(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, struct sockaddr_in *pSendAddr, VoipPacketBufferT *pPacket, int32_t iSize) +{ + int32_t iConnID = VOIP_ConnID(pConnectionlist, pConnection); + uint32_t uSessionId = _VoipDecodeU32(pPacket->VoipPacketHead.aSessionId); + #if DIRTYCODE_LOGGING + uint32_t uClientId = _VoipDecodeU32(pPacket->VoipPacketHead.aClientId); + const char *pStrPktType; + #endif + uint32_t uTick; + uint8_t uPktType; + uint32_t uIndex; + + // note time of arrival + uTick = NetTick(); + + // count the packet + pConnection->iRecvPackets += 1; + pConnection->iRecvData += iSize; + + // did port change? + if (pConnection->SendAddr.sin_port != pSendAddr->sin_port) + { + NetPrintf(("voipconnection: [%d] port remap #%d: %d bytes from %d instead of %d\n", iConnID, + pConnection->iChangePort++, iSize, + SocketNtohs(pSendAddr->sin_port), + SocketNtohs(pConnection->SendAddr.sin_port))); + + // send future to remapped port + pConnection->SendAddr.sin_port = pSendAddr->sin_port; + + // reset counter of packets received + pConnection->iRecvPackets = 0; + } + + if (pPacket->VoipPacketHead.uFlags & VOIP_PACKET_STATUS_FLAG_STT) + { + #if DIRTYCODE_LOGGING + if (pConnection->bTranscribedTextRequested == FALSE) + { + NetPrintf(("voipconnection: [%d] one or several of the remote peers on that connection are requesting transcribed text\n", iConnID)); + } + #endif + pConnection->bTranscribedTextRequested = TRUE; + } + else + { + #if DIRTYCODE_LOGGING + if (pConnection->bTranscribedTextRequested != FALSE) + { + NetPrintf(("voipconnection: [%d] remote peer(s) are no longer requesting transcribed text\n", iConnID)); + } + #endif + pConnection->bTranscribedTextRequested = FALSE; + } + + // identify type of incoming packet (connect, disconnect, ping, mic or unknown) + if (VOIP_SamePacketType(&pPacket->VoipPacketHead, &_Voip_ConnPacket)) + { + uPktType = 'C'; // connection packet + #if DIRTYCODE_LOGGING + pStrPktType = "connect"; + #endif + } + else if (VOIP_SamePacketType(&pPacket->VoipPacketHead, &_Voip_PingPacket)) + { + uPktType = 'P'; // ping packet + #if DIRTYCODE_LOGGING + pStrPktType = "ping"; + #endif + } + else if (VOIP_SamePacketType(&pPacket->VoipPacketHead, &_Voip_MicrPacket)) + { + uPktType = 'M'; // data packet + #if DIRTYCODE_LOGGING + pStrPktType = "data"; + #endif + } + else if (VOIP_SamePacketType(&pPacket->VoipPacketHead, &_Voip_DiscPacket)) + { + uPktType = 'D'; // disconnect packet + #if DIRTYCODE_LOGGING + pStrPktType = "disconnect"; + #endif + } + else + { + NetPrintf(("voipconnection: [%d] received unknown packet type 0x%02x%02x%02x\n", iConnID, + pPacket->VoipPacketHead.aType[0], pPacket->VoipPacketHead.aType[1], pPacket->VoipPacketHead.aType[2])); + return; + } + + // ignore packet if session id does not match + if (_VoipConnectionMatchSessionId(pConnectionlist, iConnID, uSessionId) == FALSE) + { + #if DIRTYCODE_LOGGING + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) ignoring packet because of session IDs mismatch\n", + iConnID, uSessionId, pStrPktType)); + + // print set of sessions sharing this connection + _VoipConnectionPrintSessionIds(pConnectionlist, iConnID); + + NetPrintMem(pPacket, iSize, "packet data"); + #endif + + return; + } + + // handle incoming packet + switch (uPktType) + { + // connect packet + case 'C': + { + VoipConnPacketT *pConnPacket = &pPacket->VoipConnPacket; + int32_t iRemoteUserIndex; + + // got a connection packet + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) received conn packet bConnected=%d eState=%d uClientId=0x%08x\n", + iConnID, uSessionId, pConnPacket->bConnected, pConnection->eState, uClientId)); + + // copy remote user's info + pConnection->iMaxRemoteUsers = pConnPacket->uNumLocalUsers - 1; + pConnection->iMaxRemoteUsersExt = pConnection->iMaxRemoteUsers; + + for (iRemoteUserIndex = 0; iRemoteUserIndex < pConnection->iMaxRemoteUsersExt; iRemoteUserIndex++) + { + _VoipDecodeVoipUser(&pConnPacket->LocalUsers[iRemoteUserIndex], &pConnection->RemoteUsers[iRemoteUserIndex]); + } + + _VoipDecodeRemoteHeadsetStatus(pConnection, &pConnPacket->Head); + + // flag that we received Conn packet from remote party + pConnection->bConnPktRecv = TRUE; + + // if they've received data from us, consider the connection established + if ((pConnPacket->bConnected) && (pConnection->eState == ST_CONN)) + { + _VoipConnectionInit(pConnectionlist, pConnection, iConnID); + } + + // if peer no more sees us as connected... go back to ST_CONN state and hope for connection to go back up + if ((!pConnPacket->bConnected) && (pConnection->eState == ST_ACTV)) + { + NetPrintf(("voipconnection: [%d] voip connection lost! try to re-establish. connection back to ST_CONN state (from ST_ACTV)... \n", iConnID)); + pConnection->eState = ST_CONN; + } + + // update last received time + pConnection->uLastRecv = uTick; + } + break; + + // ping packet + case 'P': + { + VoipPingPacketT *pPingPacket = &pPacket->VoipPingPacket; + int32_t iUserIndex; + uint32_t uChannelUserIndices; + const uint8_t *pRead = pPingPacket->aData; + + #if VOIP_CONNECTION_DEBUG + char strGlobalVoipChannelConfig[80]; + char strUserVoipChannelConfig[20]; + ds_memclr(strGlobalVoipChannelConfig, sizeof(strGlobalVoipChannelConfig)); + ds_memclr(strUserVoipChannelConfig, sizeof(strUserVoipChannelConfig)); + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) received ping packet from clientId=0x%08x\n", iConnID, uSessionId, uClientId)); + #endif + + if (pConnection->bConnPktRecv) + { + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef(); + + // if we receive a ping in connection state, that means we're connected + if (pConnection->eState == ST_CONN) + { + _VoipConnectionInit(pConnectionlist, pConnection, iConnID); + } + + // update connection status flags + _VoipDecodeRemoteHeadsetStatus(pConnection, &pPingPacket->Head); + + // check to see if VOIP_REMOTE_USER_RECVVOICE flag should be deactivated for other users + for (uIndex = 0; uIndex < VOIP_MAXLOCALUSERS; ++uIndex) + { + if (NetTickDiff(uTick, pConnection->uUserLastRecv[uIndex]) > VOIP_PING_RATE) + { + pConnection->uRemoteUserStatus[uIndex] &= ~VOIP_REMOTE_USER_RECVVOICE; + } + } + + pConnection->uRemoteConnStatus |= VOIP_CONN_BROADCONN; + pConnection->uLastRecv = uTick; + + // read the bitset to figure which channel configurations need to be updated + uChannelUserIndices = _VoipDecodeU32(pPingPacket->aChannelUserIndices); + + // update channel config for each remote user and remote shared user + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++) + { + if ((uChannelUserIndices >> iUserIndex) & 1) + { + pConnection->aRecvChannels[iUserIndex] = _VoipDecodeU32(pRead); + pRead += 4; + #if VOIP_CONNECTION_DEBUG + ds_snzprintf(strUserVoipChannelConfig, sizeof(strUserVoipChannelConfig), "user#%d:0x%08x ", iUserIndex, pConnection->aRecvChannels[iUserIndex]); + ds_strnzcat(strGlobalVoipChannelConfig, strUserVoipChannelConfig, sizeof(strGlobalVoipChannelConfig)); + #endif + } + } + #if VOIP_CONNECTION_DEBUG + NetPrintf((" voip channels: %s\n", strGlobalVoipChannelConfig)); + #endif + + // process any reliable protocol components that may be included in this ping packet + _VoipReliableDataInProcess(pConnectionlist, pConnection, &pPingPacket->Head, (uint8_t *)pRead); + + // update the channel + if (memcmp(&pConnection->aRecvChannels, &pConnection->aLastRecvChannels, sizeof(pConnection->aRecvChannels))) + { + VoipCommonProcessChannelChange(pVoipCommon, iConnID); + ds_memcpy_s(&pConnection->aLastRecvChannels, sizeof(pConnection->aLastRecvChannels), &pConnection->aRecvChannels, sizeof(pConnection->aRecvChannels)); + VoipCommonUpdateRemoteStatus(pVoipCommon); + } + } + else + { + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) ping packet from clientId=0x%08x ignored because conn packet not yet received\n", + iConnID, uSessionId, uClientId)); + } + } + break; + + // data packet + case 'M': + { + VoipMicrPacketT *pMicrPacket = &pPacket->VoipMicrPacket; + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef(); + + #if VOIP_CONNECTION_DEBUG + if ((pConnection->iRecvPackets % 30) == 0) + { + char strSubPktSize[32]; + sprintf(strSubPktSize, "size %d (bytes)", pMicrPacket->MicrInfo.uSubPacketSize); + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) received pkt (seq#:%d) with %d sub-pkt(s) of %s from remote usr idx %d | voip channels:0x%08x\n", + iConnID, uSessionId, pMicrPacket->MicrInfo.uSeqn, pMicrPacket->MicrInfo.uNumSubPackets, (pMicrPacket->MicrInfo.uSubPacketSize == 0xFF ? "variable-length" : strSubPktSize), + pMicrPacket->MicrInfo.uUserIndex, _VoipDecodeU32(pMicrPacket->MicrInfo.channels.aChannelId))); + NetPrintMem(pMicrPacket, iSize, "mic packet data"); + } + #endif + + if (pConnection->bConnPktRecv) + { + uint8_t *pRead; + int32_t iRemoteUserIndex; + uint32_t uLocalHeadsetStatusAggregate = 0; + + // if we receive voice data in connection state, that means we're connected + if (pConnection->eState == ST_CONN) + { + _VoipConnectionInit(pConnectionlist, pConnection, iConnID); + } + + // update connection status flags + _VoipDecodeRemoteHeadsetStatus(pConnection, &pMicrPacket->Head); + pConnection->uRemoteConnStatus |= (VOIP_CONN_ACTIVE|VOIP_CONN_BROADCONN); + if (pMicrPacket->MicrInfo.uUserIndex == VOIP_SHARED_REMOTE_INDEX) + { + pConnection->aRecvChannels[VOIP_SHARED_USER_INDEX] = _VoipDecodeU32(pMicrPacket->MicrInfo.channels.aChannelId); + } + else + { + pConnection->aRecvChannels[pMicrPacket->MicrInfo.uUserIndex] = _VoipDecodeU32(pMicrPacket->MicrInfo.channels.aChannelId); + } + + // update the channel + if (memcmp(&pConnection->aRecvChannels, &pConnection->aLastRecvChannels, sizeof(pConnection->aRecvChannels))) + { + VoipCommonProcessChannelChange(pVoipCommon, iConnID); + ds_memcpy_s(&pConnection->aLastRecvChannels, sizeof(pConnection->aLastRecvChannels), &pConnection->aRecvChannels, sizeof(pConnection->aRecvChannels)); + VoipCommonUpdateRemoteStatus(pVoipCommon); + } + + pConnection->uLastRecv = uTick; + + // validate and update packet sequence number + if (pMicrPacket->MicrInfo.uSeqn != (uint8_t)(pConnection->uRecvSeqn+1)) + { + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) got packet seq # %d while expecting packet seq # %d\n", + iConnID, uSessionId, pMicrPacket->MicrInfo.uSeqn, (uint8_t)pConnection->uRecvSeqn+1)); + } + pConnection->uRecvSeqn = pMicrPacket->MicrInfo.uSeqn; + + // process any reliable protocol components that may be included in this MIC packet + pRead = _VoipReliableDataInProcess(pConnectionlist, pConnection, &pMicrPacket->Head, &pMicrPacket->aData[0]); + + /* is the connection active, and are we sending to it? + xboxone: After rebasing VoipHeadsetXboxOne to MS game chat 2, per-connection muting + is handled by VoipHeadsetXboxOne (See usage of +snd, -snd, +rcv, -rcv). + But for crossplay we want the muting to be applied here just like other plaforms. + Testing has shown that apply te send mask and +snd, -snd, +rcv, -rcv has no negative effect.*/ + if (pConnectionlist->uRecvMask & (1 << iConnID)) + { + // if the voice packet is from a remote shared user update all the remote user's status + if (pMicrPacket->MicrInfo.uUserIndex == VOIP_SHARED_REMOTE_INDEX) + { + for (iRemoteUserIndex = 0; iRemoteUserIndex < pConnection->iMaxRemoteUsers; ++iRemoteUserIndex) + { + VoipUserT *pVoipUser = (VoipUserT *)&pConnection->RemoteUsers[iRemoteUserIndex]; + + if (!VOIP_NullUser(pVoipUser)) + { + #if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + /* For xboxone, opaque traffic is always received as if it was originated from the + "shared user". We need to query VoipHeadset (via VoipCommon) to find out which + remote user is exactly detected as talking by the MS game chat 2's chat manager + and consequently involved in generating the current inbound flow of voip packets. */ + if (VoipCommonStatus((VoipCommonRefT *)VoipGetRef(), 'talk', 0, pVoipUser, sizeof(*pVoipUser))) + #endif + { + pConnection->uRemoteUserStatus[iRemoteUserIndex] |= VOIP_REMOTE_USER_RECVVOICE; + pConnection->uUserLastRecv[iRemoteUserIndex] = uTick; + } + } + } + } + else + { + pConnection->uUserLastRecv[pMicrPacket->MicrInfo.uUserIndex] = uTick; + pConnection->uRemoteUserStatus[pMicrPacket->MicrInfo.uUserIndex] |= VOIP_REMOTE_USER_RECVVOICE; + } + + // if there is at least one headset capable of outputting voice data, forward the packet along, ignore otherwise + // we do this to ignore spamming on PC (ticket GOS-30000) + for (uIndex = 0; uIndex < VOIP_MAXLOCALUSERS; ++uIndex) + { + uLocalHeadsetStatusAggregate |= pVoipCommon->Connectionlist.uLocalUserStatus[uIndex]; + } + + if (uLocalHeadsetStatusAggregate & VOIP_LOCAL_USER_OUTPUTDEVICEOK) + { + pConnectionlist->pVoiceCb((VoipUserT *)&pConnection->RemoteUsers[0], pConnection->iMaxRemoteUsers, iConnID, &pMicrPacket->MicrInfo, pRead, pConnectionlist->pVoiceUserData); + } + } + + // check to see if VOIP_REMOTE_USER_RECVVOICE flag should be deactivated for other users + for (uIndex = 0; uIndex < VOIP_MAXLOCALUSERS; ++uIndex) + { + if (NetTickDiff(uTick, pConnection->uUserLastRecv[uIndex]) > VOIP_PING_RATE) + { + pConnection->uRemoteUserStatus[uIndex] &= ~VOIP_REMOTE_USER_RECVVOICE; + } + } + } + else + { + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) voip data packet from clientId=0x%08x ignored because conn packet not yet received\n", + iConnID, uSessionId, uClientId)); + } + } + break; + + case 'D': + { + VoipDiscPacketT *pDiscPacket = &pPacket->VoipDiscPacket; + uint32_t uRemoteClientId = _VoipDecodeU32(pDiscPacket->aRemoteClientId); + uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId; + if (uRemoteClientId == uLocalClientId) + { + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) remote peer 0x%08x has disconnected from 0x%08x\n", + iConnID, uSessionId, uClientId, uRemoteClientId)); + + // In this specific case, we know that remote peer is already in 'disconnected' state. + // No need to send back a DISC msg. Therefore, set bSendDiscMsg parameter to FALSE. + _VoipConnectionStop(pConnectionlist, &pConnectionlist->pConnections[iConnID], iConnID, FALSE); + } + else + { + // this should only occurs with multiple machines on the same PC and voip being routed to the incorrect client + NetPrintf(("voipconnection: [%d] (sess id 0x%08x) remote peer 0x%08x has disconnected from 0x%08x, but we are 0x%08x\n", + iConnID, uSessionId, uClientId, uRemoteClientId, pConnectionlist->uClientId)); + } + } + break; + + default: + { + NetPrintf(("voipconnection: [%d] critical error! this path can't be taken in _VoipConnectionRecvSing()\n")); + } + break; + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionUpdateSingle + + \Description + Update a virtual connection. + + \Input *pConnectionlist - connectionlist + \Input *pConnection - pointer to connection to update + \Input iConnId - index of connection to update + \Input uTick - current tick count + + \Output + uint32_t - nonzero=receiving voice, zero=not receiving voice + + \Version 03/20/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _VoipConnectionUpdateSingle(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iConnId, uint32_t uTick) +{ + int32_t iTimeout; + int32_t iUserIndex; + uint8_t bIsReceivingVoice = FALSE; + + // make sure it's a valid connection + if (pConnection->eState == ST_DISC) + { + return(0); + } + + // see if we need to send a conn or ping packet + if ((uTick - pConnection->uLastSend) > VOIP_PING_RATE) + { + // if we're connecting, send connection packet + if (pConnection->eState == ST_CONN) + { + _VoipConnectionConn(pConnectionlist, pConnection, uTick); + } + else + { + _VoipConnectionPing(pConnectionlist, pConnection, uTick); + } + } + + // see if we need to send any buffered voice data + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++) + { + _VoipConnectionSendSingle(pConnectionlist, pConnection, iUserIndex, uTick, FALSE); + } + + // see if we need to time out the connection + iTimeout = (pConnection->eState == ST_ACTV) ? pConnectionlist->iDataTimeout : VOIP_CONNTIMEOUT; + if (NetTickDiff(uTick, pConnection->uLastRecv) > iTimeout) + { + NetPrintf(("voipconnection: [%d] timing out connection due to inactivity\n", iConnId)); + _VoipConnectionStop(pConnectionlist, &pConnectionlist->pConnections[iConnId], iConnId, TRUE); + } + + // return if we are receiving voice or not + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS; iUserIndex++) + { + if (pConnection->uRemoteUserStatus[iUserIndex] & VOIP_REMOTE_USER_RECVVOICE) + { + bIsReceivingVoice = TRUE; + break; + } + } + return((bIsReceivingVoice == TRUE) ? (1 << iConnId) : 0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionRecv + + \Description + Receive data and forward it to the appropriate connection. + + \Input *pConnectionlist - connectionlist + + \Output + int32_t - amount of data received + + \Version 03/21/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipConnectionRecv(VoipConnectionlistT *pConnectionlist) +{ + struct sockaddr_in RecvAddr; + int32_t iAddrLen, iRecvSize; + + VoipPacketBufferT RecvPacket; + + // no socket? no way to receive. + if (pConnectionlist->pSocket == NULL) + { + return(0); + } + + // try and receive from a peer + if ((iRecvSize = SocketRecvfrom(pConnectionlist->pSocket, (char *)&RecvPacket, sizeof(RecvPacket), 0, (struct sockaddr *)&RecvAddr, (iAddrLen=sizeof(RecvAddr),&iAddrLen))) > 0) + { + VoipConnectionT *pConnection; + int32_t iConnID; + + // extract client identifier + uint32_t uClientId = _VoipDecodeU32(RecvPacket.VoipPacketHead.aClientId); + + // find the matching connection + for (iConnID = 0, pConnection = NULL; iConnID < pConnectionlist->iMaxConnections; iConnID++) + { + pConnection = &pConnectionlist->pConnections[iConnID]; + + // if the clientID matches, this is the connection + if (uClientId == pConnection->uRemoteClientId) + { + break; + } + } + + // if we found a matching connection + if (iConnID < pConnectionlist->iMaxConnections) + { + // give the data to the connection + _VoipConnectionRecvSingle(pConnectionlist, pConnection, &RecvAddr, &RecvPacket, iRecvSize); + } + else if (!VOIP_SamePacketType(&RecvPacket.VoipPacketHead, &_Voip_ConnPacket) && !VOIP_SamePacketType(&RecvPacket.VoipPacketHead, &_Voip_DiscPacket)) + { + NetPrintf(("voipconnection: ignoring 0x%02x%02x%02x packet from address %a:%d clientId=0x%08x\n", + RecvPacket.VoipPacketHead.aType[0], RecvPacket.VoipPacketHead.aType[1], RecvPacket.VoipPacketHead.aType[2], + SocketNtohl(RecvAddr.sin_addr.s_addr), SocketNtohs(RecvAddr.sin_port), + uClientId)); + NetPrintMem(&RecvPacket, iRecvSize, "packet data"); + } + } + + // return data length + return(iRecvSize); +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionRecvCallback + + \Description + Voip socket recv event callback. + + \Input *pSocket - voip socket + \Input iFlags - unused + \Input *pRef - connectionlist ref + + \Output + int32_t - zero + + \Version 12/16/2005 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipConnectionRecvCallback(SocketT *pSocket, int32_t iFlags, void *pRef) +{ + VoipConnectionlistT *pConnectionlist = (VoipConnectionlistT *)pRef; + + // see if we have exclusive access + if (NetCritTry(&pConnectionlist->NetCrit)) + { + // try and receive data + _VoipConnectionRecv(pConnectionlist); + + // free access + NetCritLeave(&pConnectionlist->NetCrit); + } + else + { + NetPrintf(("voipconnection: _VoipConnectionRecvCallback() could not acquire connectionlist critical section\n")); + } + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionTrySocketClose + + \Description + Check if the voip socket can be closed. The close will occur when all the + connections are closed. Moved from the voip loop, to also be done immediately + upon connection stop. + + \Input *pConnectionlist - connectionlist ref + + \Output + int32_t - zero + + \Version 06/18/2009 (jrainy) +*/ +/********************************************************************************F*/ +static void _VoipConnectionTrySocketClose(VoipConnectionlistT *pConnectionlist) +{ + int32_t iConnId, iNumConnections; + + NetCritEnter(&pConnectionlist->NetCrit); + + // count number of active connections + for (iConnId = 0, iNumConnections = 0; iConnId < pConnectionlist->iMaxConnections; iConnId++) + { + if (pConnectionlist->pConnections[iConnId].eState != ST_DISC) + { + iNumConnections++; + } + } + + // if no connections left, kill socket + if ((iNumConnections == 0) && (pConnectionlist->pSocket != NULL)) + { + NetPrintf(("voipconnection: closing socket in _VoipConnectionTrySocketClose()\n")); + SocketClose(pConnectionlist->pSocket); + pConnectionlist->pSocket = NULL; + pConnectionlist->uBindPort = 0; + } + NetCritLeave(&pConnectionlist->NetCrit); +} + +/*F********************************************************************************/ +/*! + \Function _VoipConnectionStop + + \Description + Stop a connection. + + \Input *pConnectionlist - connectionlist ref + \Input *pConnection - connection to stop + \Input iConnID - index of connection to stop, or VOIP_CONNID_ALL to stop all connections + \Input bSendDiscMsg - TRUE - send DISC msg to peer; FALSE - do not send DISC msg to peer + + \Notes + Loss of the single unreliable disconnect packet sent in this function + is not critical, as the connection manager will respond to unexpected + packets that are not connect/disconnect packets (ie pings and voice + packets) with disconnection requests. + + \Version 05/10/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipConnectionStop(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iConnID, int32_t bSendDiscMsg) +{ + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef(); + int32_t i = 0; + + NetCritEnter(&pConnectionlist->NetCrit); + + // make sure we're not already disconnected + if (pConnection->eState == ST_DISC) + { + NetPrintf(("voipconnection: [%d] disconnection attempt canceled because connection already in state ST_DISC!\n", iConnID)); + NetCritLeave(&pConnectionlist->NetCrit); + return; + } + + if (bSendDiscMsg) + { + // send a disconnection packet + uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId; + _VoipConnectionDisc(iConnID, uLocalClientId, pConnection->uRemoteClientId, pConnection->uSessionId[0], &pConnection->SendAddr, pConnectionlist->pSocket); + } + else + { + NetPrintf(("voipconnection: [%d] no need to send disconnect message to %a:%d\n", iConnID, + SocketNtohl(pConnection->SendAddr.sin_addr.s_addr), SocketNtohs(pConnection->SendAddr.sin_port))); + } + + // set to disconnected before unregister + pConnection->eState = ST_DISC; + pConnection->bConnPktRecv = FALSE; + + // subtract connection identifier from send/recv masks + VoipConnectionSetSendMask(pConnectionlist, pConnectionlist->uSendMask & ~(1 << iConnID)); + VoipConnectionSetRecvMask(pConnectionlist, pConnectionlist->uRecvMask & ~(1 << iConnID)); + + // unregister all the remote users on this connection with voipheadset + VoipConnectionRegisterRemoteTalkers(pConnectionlist, iConnID, FALSE); + + // clear outbound reliable queue + while (pConnection->pOutboundReliableDataQueue != NULL) + { + _VoipConnectionReliableDataDequeue(pConnectionlist, iConnID); + } + + // clear connection and mark as disconnected + ds_memclr(pConnection, sizeof(*pConnection)); + pConnection->uRemoteConnStatus = VOIP_CONN_STOPPED; + pConnection->iVoipServerConnId = VOIP_CONNID_NONE; + for ( i = 0; i < VOIP_MAXLOCALUSERS; ++i) + { + pConnectionlist->uLocalUserStatus[i] &= ~VOIP_LOCAL_USER_SENDVOICE; + pConnectionlist->uLocalUserLastSend[i] = 0; + } + + // clear channel config maintained by VoipCommon + ds_memclr(&pVoipCommon->uRemoteChannelSelection[iConnID], sizeof(pVoipCommon->uRemoteChannelSelection[iConnID])); + + NetCritLeave(&pConnectionlist->NetCrit); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipConnectionStartup + + \Description + Startup a connectionlist. + + \Input *pConnectionlist - connectionlist to init + \Input iMaxPeers - max number of connections to support + + \Output + int32_t - zero=success, negative=error + + \Version 03/20/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipConnectionStartup(VoipConnectionlistT *pConnectionlist, int32_t iMaxPeers) +{ + int32_t iConnId; + int32_t iMemGroup; + void *pMemGroupUserData; + + //Check to see if Max Peer has exceeded VOIP_MAXCONNECT + if (iMaxPeers > VOIP_MAXCONNECT) + { + NetPrintf(("voipconnection: error, max peer exceeded VOIP_MAXCONNECT.\n")); + return(-2); + } + + // Query current mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init connectionlist + if ((pConnectionlist->pConnections = (VoipConnectionT *)DirtyMemAlloc(sizeof(VoipConnectionT) * iMaxPeers, VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipconnection: error, unable to allocate connectionlist\n")); + return(-1); + } + ds_memclr(pConnectionlist->pConnections, sizeof(VoipConnectionT) * iMaxPeers); + + pConnectionlist->iFriendConnId = VOIP_CONNID_NONE; + + // reset local-to-server conn id mappings + for (iConnId = 0; iConnId < iMaxPeers; iConnId++) + { + pConnectionlist->pConnections[iConnId].iVoipServerConnId = VOIP_CONNID_NONE; + } + + // init critical section + NetCritInit(&pConnectionlist->NetCrit, "voipconnection"); + + // set default timeout + pConnectionlist->iDataTimeout = VOIP_TIMEOUT; + + // set max peers + pConnectionlist->iMaxConnections = iMaxPeers; + + NetPrintf(("voipconnection: max connections = %d max reliable data size = %d max ping payload size = %d\n", + pConnectionlist->iMaxConnections, VOIP_MAXRELIABLEDATA, VOIP_MAXPINGPKTSIZE)); + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionSetCallbacks + + \Description + Set required recv voice and reg user callbacks. + + \Input *pConnectionlist - connectionlist to register callbacks for + \Input *pVoiceCb - voice callback to register + \Input *pVoiceUserData - user data to be passed to the voice callback + \Input *pTextCb - text callback to register + \Input *pTextUserData - user data to be passed to the text callback + \Input *pRegUserCb - user reg callback to register + \Input *pRegUserUserData - user data to be passed to the reguser callback + \Input *pOpaqueCb - opaque data callback to register + \Input *pOpaqueUserData - user data to be passed to the opaquedata callback + + \Version 03/20/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConnectionSetCallbacks(VoipConnectionlistT *pConnectionlist, VoipConnectRecvVoiceCbT *pVoiceCb, void *pVoiceUserData, + VoipConnectRecvTextCbT *pTextCb, void *pTextUserData, + VoipConnectRegUserCbT *pRegUserCb, void *pRegUserUserData, + VoipConnectRecvOpaqueCbT *pOpaqueCb, void *pOpaqueUserData) +{ + // save callbacks and callback user data + pConnectionlist->pRegUserCb = pRegUserCb; + pConnectionlist->pRegUserUserData = pRegUserUserData; + pConnectionlist->pVoiceCb = pVoiceCb; + pConnectionlist->pVoiceUserData = pVoiceUserData; + pConnectionlist->pTextCb = pTextCb; + pConnectionlist->pTextUserData = pTextUserData; + pConnectionlist->pOpaqueCb = pOpaqueCb; + pConnectionlist->pOpaqueUserData = pOpaqueUserData; +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionSetTextCallback + + \Description + Set required text callbacks for X1 crossplay asscessibility + Should be set to null for native mode + + \Input *pConnectionlist - connectionlist to register callbacks for + \Input *pTextCb - text callback to register + \Input *pTextUserData - user data to be passed to the text callback + + \Version 04/10/2019 (tcho) +*/ +/********************************************************************************F*/ +void VoipConnectionSetTextCallback(VoipConnectionlistT *pConnectionlist, VoipConnectRecvTextCbT *pTextCb, void *pTextUserData) +{ + // save callbacks and callback user data + pConnectionlist->pTextCb = pTextCb; + pConnectionlist->pTextUserData = pTextUserData; +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionShutdown + + \Description + Shutdown a connectionlist + + \Input *pConnectionlist - connectionlist to close + + \Version 03/20/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConnectionShutdown(VoipConnectionlistT *pConnectionlist) +{ + int32_t iMemGroup; + void *pMemGroupUserData; + LinkedReliableDataT *pEntry; + + // query current mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // clear free pool of reliable data entries + while ((pEntry = _VoipGetReliableDataBufferFromFreePool(pConnectionlist, FALSE)) != NULL) + { + DirtyMemFree(pEntry, VOIP_MEMID, iMemGroup, pMemGroupUserData); + } + + // dispose of connectionlist + DirtyMemFree(pConnectionlist->pConnections, VOIP_MEMID, iMemGroup, pMemGroupUserData); + + // dispose of socket + if (pConnectionlist->pSocket) + { + NetPrintf(("voipconnection: closing socket in VoipConnectionShutdown()\n")); + SocketClose(pConnectionlist->pSocket); + } + + // release critical section + NetCritKill(&pConnectionlist->NetCrit); + + // clear connectionlist + ds_memclr(pConnectionlist, sizeof(*pConnectionlist)); +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionCanAllocate + + \Description + Check whether a given Connection ID can be allocated in this Connection List + + \Input *pConnectionlist - connection list ref + \Input iConnID - connection index + + \Output + uint8_t - Whether a connection can be allocated with given ConnID + + \Version 02/19/2008 (jrainy) +*/ +/********************************************************************************F*/ +uint8_t VoipConnectionCanAllocate(VoipConnectionlistT *pConnectionlist, int32_t iConnID) +{ + if ((iConnID < 0) || (iConnID >= pConnectionlist->iMaxConnections)) + { + return(FALSE); + } + + return(pConnectionlist->pConnections[iConnID].eState == ST_DISC); +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionStart + + \Description + Start a connection to a peer. + + \Input *pConnectionlist - connection list ref + \Input iConnID - connection index + \Input uAddr - address to connect to + \Input uConnPort - connection port + \Input uBindPort - local bind port + \Input uClientId - id of client to connect to + \Input uSessionId - session id (cannot be 0) + + \Output + int32_t - connection identifier on success, negative=failure + + \Version 03/18/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipConnectionStart(VoipConnectionlistT *pConnectionlist, int32_t iConnID, uint32_t uAddr, uint32_t uConnPort, uint32_t uBindPort, uint32_t uClientId, uint32_t uSessionId) +{ + VoipConnectionT *pConnection; + struct sockaddr BindAddr; + int32_t iUserIndex; + + // sanity check: make sure local and remote clientIDs are different + if (pConnectionlist->uClientId == uClientId) + { + NetPrintf(("voipconnection: local client id (%d) and remote client id (%d) can't be same\n", pConnectionlist->uClientId, uClientId)); + return(-1); + } + + // if we haven't allocated a socket yet, do so now + if (pConnectionlist->pSocket == NULL) + { + int32_t iResult; + + // open the socket + if ((pConnectionlist->pSocket = SocketOpen(AF_INET, SOCK_DGRAM, VOIP_IPPROTO)) == NULL) + { + NetPrintf(("voipconnection: error creating socket\n")); + return(-2); + } + + // bind the socket + SockaddrInit(&BindAddr, AF_INET); + SockaddrInSetPort(&BindAddr, uBindPort); + if ((iResult = SocketBind(pConnectionlist->pSocket, &BindAddr, sizeof(BindAddr))) != SOCKERR_NONE) + { + NetPrintf(("voipconnection: error %d binding socket to port %d, trying random\n", iResult, uBindPort)); + SockaddrInSetPort(&BindAddr, 0); + if ((iResult = SocketBind(pConnectionlist->pSocket, &BindAddr, sizeof(BindAddr))) != SOCKERR_NONE) + { + NetPrintf(("voipconnection: error %d binding socket\n", iResult)); + SocketClose(pConnectionlist->pSocket); + pConnectionlist->pSocket = NULL; + return(-3); + } + } + + // retrieve bound port + SocketInfo(pConnectionlist->pSocket, 'bind', 0, &BindAddr, sizeof(BindAddr)); + uBindPort = SockaddrInGetPort(&BindAddr); + NetPrintf(("voipconnection: bound socket to port %d\n", uBindPort)); + + // save local port + pConnectionlist->uBindPort = uBindPort; + + // setup for socket events + SocketCallback(pConnectionlist->pSocket, CALLB_RECV, 5000, pConnectionlist, &_VoipConnectionRecvCallback); + } + + // make sure bind matches our previous bind + if (uBindPort != pConnectionlist->uBindPort) + { + NetPrintf(("voipconnection: warning, only one global bind port is currently supported, using previously specified port\n")); + } + + // convert address and ports to network form + uAddr = SocketHtonl(uAddr); + uConnPort = SocketHtons(uConnPort); + + /* + Guarding the following code with NetCrit proved to be required to protect against + _VoipConnectionStop() being invoked from the socket async recv thread upon reception + of a DISC packet (typically belonging to a previous session) on that connection. + + We did not include the above portion of this function within this critical + usage function, because atomic access to it is guaranteed by the threadcrit + being lock externally. + */ + NetCritEnter(&pConnectionlist->NetCrit); + + // allocate a connection + if ((pConnection = _VoipConnectionAllocate(pConnectionlist, iConnID)) == NULL) + { + NetCritLeave(&pConnectionlist->NetCrit); + + NetPrintf(("voipconnection: alloc failed\n")); + return(-4); + } + + iConnID = VOIP_ConnID(pConnectionlist, pConnection); + + // init the connection + ds_memclr(pConnection, sizeof(*pConnection)); + pConnection->iVoipServerConnId = VOIP_CONNID_NONE; + pConnection->SendAddr.sin_family = AF_INET; + pConnection->SendAddr.sin_addr.s_addr = uAddr; + pConnection->SendAddr.sin_port = uConnPort; + pConnection->uRemoteClientId = uClientId; + + // sequence number for reliable data ranges from 1 to 127 + pConnection->uOutSeqNb = 1; // use 1 as the first outbound sequence number + pConnection->uInSeqNb = 0; // seq nb of last received inbound message. validity range : [1,127]. 0 means no inbound traffic received yet + + // add specified session ID to the set of sessions sharing this connection + VoipConnectionAddSessionId(pConnectionlist, iConnID, uSessionId); + + pConnection->uRecvSeqn = 0xFFFFFFFF; // intialize last receive sequence number with -1. + pConnection->uRemoteConnStatus = 0; + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS; ++iUserIndex) + { + pConnection->uRemoteUserStatus[iUserIndex] = 0; + } + pConnection->eState = ST_CONN; + pConnection->uLastRecv = NetTick(); + + // set up to transmit mic data + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++) + { + ds_memcpy_s(&pConnection->VoipMicrPacket[iUserIndex].Head, sizeof(pConnection->VoipMicrPacket[iUserIndex].Head), &_Voip_MicrPacket, sizeof(_Voip_MicrPacket)); + } + + // output connect message + NetPrintf(("voipconnection: [%d] connecting to %a:%d localId=0x%08x remoteId=0x%08x\n", iConnID, + SocketNtohl(pConnection->SendAddr.sin_addr.s_addr), SocketNtohs(pConnection->SendAddr.sin_port), + pConnection->uLocalClientId, pConnection->uRemoteClientId)); + + NetCritLeave(&pConnectionlist->NetCrit); + + // return connection ID to caller + return(iConnID); +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionUpdate + + \Description + Update all connections + + \Input *pConnectionlist - connection list + + \Version 03/18/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConnectionUpdate(VoipConnectionlistT *pConnectionlist) +{ + int32_t iConnId; + uint32_t uTick; + + // get sole access + NetCritEnter(&pConnectionlist->NetCrit); + + // receive any incoming data + while(_VoipConnectionRecv(pConnectionlist) > 0) + ; + + _VoipConnectionTrySocketClose(pConnectionlist); + + // relinquish sole access + NetCritLeave(&pConnectionlist->NetCrit); + + // update connection status for all connections, keeping track of who we are receiving voice from + for (iConnId = 0, uTick = NetTick(), pConnectionlist->uRecvVoice = 0; iConnId < pConnectionlist->iMaxConnections; iConnId++) + { + pConnectionlist->uRecvVoice |= _VoipConnectionUpdateSingle(pConnectionlist, &pConnectionlist->pConnections[iConnId], iConnId, uTick); + } + + #if !defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) + /* check if we need to time out talking status + on xbox one this is handled in the voipxbxone _VoipUpdateLocalStatus() + */ + for (int32_t i = 0; i < VOIP_MAXLOCALUSERS; ++i) + { + if ((pConnectionlist->uLocalUserStatus[i] & VOIP_LOCAL_USER_TALKING) && (NetTickDiff(uTick, pConnectionlist->uLastVoiceTime[i]) > VOIP_TALKTIMEOUT)) + { + pConnectionlist->uLocalUserStatus[i] &= ~VOIP_LOCAL_USER_TALKING; + } + } + #endif +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionStop + + \Description + Stop a connection with a peer. + + \Input *pConnectionlist - connection list ref + \Input iConnID - connection to stop, or VOIP_CONNID_ALL to stop all connections + \Input bSendDiscMsg - TRUE - send DISC msg to peer; FALSE - do not send DISC msg to peer + + \Version 03/18/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConnectionStop(VoipConnectionlistT *pConnectionlist, int32_t iConnID, int32_t bSendDiscMsg) +{ + if (iConnID == VOIP_CONNID_ALL) + { + // disconnect from all current connections + for (iConnID = 0; iConnID < pConnectionlist->iMaxConnections; iConnID++) + { + _VoipConnectionStop(pConnectionlist, &pConnectionlist->pConnections[iConnID], iConnID, bSendDiscMsg); + } + } + else if ((iConnID >= 0) && (iConnID < pConnectionlist->iMaxConnections)) + { + // disconnect from the given connection + _VoipConnectionStop(pConnectionlist, &pConnectionlist->pConnections[iConnID], iConnID, bSendDiscMsg); + } + else + { + NetPrintf(("voipconnection: disconnect with iConnID=%d is invalid\n", iConnID)); + } + + _VoipConnectionTrySocketClose(pConnectionlist); +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionSend + + \Description + Send data to peer. + + \Input *pConnectionlist - connectionlist to send to + \Input uSendMask - mask of connections to send to + \Input *pVoiceData - pointer to data to send + \Input iDataSize - size of data to send + \Input *pMetaData - pointer to metadata to be added to voip packet + \Input iMetaDataSize - size of metadata + \Input uUserIndex - local user index + \Input uSendSeqn - seq nb + + \Version 03/17/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConnectionSend(VoipConnectionlistT *pConnectionlist, uint32_t uSendMask, const uint8_t *pVoiceData, int32_t iDataSize, const uint8_t *pMetaData, int32_t iMetaDataSize, uint32_t uUserIndex, uint8_t uSendSeqn) +{ + uint32_t uCurTick = NetTick(); + int32_t iConnID; + int32_t iParticipatingUserIndex; + uint8_t bSentToVoipServer = FALSE; + + // early exit if metadata is too big + if ((pMetaData != NULL) && (iMetaDataSize > 256)) + { + NetPrintf(("voipconnection: critical error! metadata is too big\n")); + return; + } + + // early exit if the user index is invalid + if (uUserIndex >= VOIP_MAXLOCALUSERS_EXTENDED) + { + return; + } + + // if we are dealing with the remote user update all the user's last voice receive time + if (uUserIndex == VOIP_SHARED_USER_INDEX) + { + for (iParticipatingUserIndex = 0; iParticipatingUserIndex < VOIP_MAXLOCALUSERS; ++iParticipatingUserIndex) + { + if (pConnectionlist->aIsParticipating[iParticipatingUserIndex] == TRUE) + { + pConnectionlist->uLastVoiceTime[iParticipatingUserIndex] = uCurTick; + } + } + } + else + { + pConnectionlist->uLastVoiceTime[uUserIndex] = uCurTick; + } + + // loop through all connections + for (iConnID = 0; iConnID < pConnectionlist->iMaxConnections; iConnID++) + { + // ref the connection + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnID]; + + // skip connections that are not set in the sendmask parameter + if ((uSendMask & (1 << iConnID)) == 0) + { + continue; + } + + // for connections via voip server, send only to the first connection + // todo: amakoukji, for CCS phase 2 we will need to remember what voip server we already sent the packet to and skip + if ((pConnection->iVoipServerConnId != VOIP_CONNID_NONE) && (bSentToVoipServer != FALSE)) + { + continue; + } + + /* is the connection active, and are we sending to it? + xboxone: After rebasing VoipHeadsetXboxOne to MS game chat 2, per-connection muting + is handled by VoipHeadsetXboxOne (See usage of +snd, -snd, +rcv, -rcv). + But for crossplay we want the muting to be applied here just like other plaforms. + Testing has shown that apply te send mask and +snd, -snd, +rcv, -rcv has no negative effect.*/ + + if ((pConnection->eState == ST_ACTV) && (pConnectionlist->uSendMask & (1 << iConnID))) + { + uint8_t *pWrite; // write position in voip packet + VoipMicrPacketT *pMicrPacket = &pConnection->VoipMicrPacket[uUserIndex]; + uint8_t bVariableSubPktLen; + int32_t iResult; + + // set talking flag + // if the voice packet is from a shared user set all the participating user status to be talking + if (uUserIndex == VOIP_SHARED_USER_INDEX) + { + for (iParticipatingUserIndex = 0; iParticipatingUserIndex < VOIP_MAXLOCALUSERS; ++iParticipatingUserIndex) + { + if (pConnectionlist->aIsParticipating[iParticipatingUserIndex] == TRUE) + { + pConnectionlist->uLocalUserStatus[iParticipatingUserIndex] |= VOIP_LOCAL_USER_TALKING; + } + } + } + else + { + pConnectionlist->uLocalUserStatus[uUserIndex] |= VOIP_LOCAL_USER_TALKING; + } + + // record maximum outgoing sub-pkt size on this connection + if (iDataSize > pConnection->iMaxSubPktSize) + { + NetPrintf(("voipconnection: [%d] max recorded outbound sub-pkt size increased from %d to %d\n", iConnID, pConnection->iMaxSubPktSize, iDataSize)); + pConnection->iMaxSubPktSize = iDataSize; + } + + // is new metadata different than metadata already in packet + if ((pMicrPacket->MicrInfo.uNumSubPackets != 0) && (pMetaData != NULL)) + { + uint8_t bDifferentMetaData = FALSE; + uint8_t *pPackedMetaData = _VoipPacketGetMetaDataPtr(pMicrPacket); + + #if DIRTYCODE_LOGGING + if (!(pMicrPacket->Head.uFlags & VOIP_PACKET_STATUS_FLAG_METADATA)) + { + NetPrintf(("voipconnection: [%d] critical error - metadata expected in packet but missing\n", iConnID)); + } + #endif + + if (iMetaDataSize == *pPackedMetaData) + { + if (memcmp(pMetaData, (pPackedMetaData+1), iMetaDataSize) != 0) + { + bDifferentMetaData = TRUE; + } + } + else + { + bDifferentMetaData = TRUE; + } + + if (bDifferentMetaData) + { + NetPrintf(("voipconnection: [%d] flushing pending voip packet because new sub-packet has different metadata\n", iConnID)); + _VoipConnectionSendSingle(pConnectionlist, pConnection, uUserIndex, uCurTick, TRUE); + } + } + + // if no voice sub-packets are queued yet + if (pMicrPacket->MicrInfo.uNumSubPackets == 0) + { + // if needed, add reliable protocol components to this voip packet + if (pConnection->iVoipServerConnId != VOIP_CONNID_NONE) + { + // packet routed through voip server + pWrite = _VoipReliableDataOutProcess(pConnectionlist, VOIP_CONNID_ALL, &pMicrPacket->Head, &pMicrPacket->aData[0], sizeof(pMicrPacket->aData)); + } + else + { + // p2p packet + pWrite = _VoipReliableDataOutProcess(pConnectionlist, iConnID, &pMicrPacket->Head, &pMicrPacket->aData[0], sizeof(pMicrPacket->aData)); + } + + // if the voice timer has elapsed, reset it + if (NetTickDiff(uCurTick, pConnection->aVoiceSendTimer[uUserIndex]) >= 0) + { + pConnection->aVoiceSendTimer[uUserIndex] = uCurTick + VOIP_MSPERPACKET; + } + + // if necessary, add metadata before we start appending the first packet + if (pMetaData != NULL) + { + // store metadata length in packet buffer + *pWrite++ = (unsigned)iMetaDataSize; + + // copy metadata into packet buffer + ds_memcpy(pWrite, pMetaData, iMetaDataSize); + pWrite += iMetaDataSize; + + // set flag signaling that payload starts with metadata size/metadata pair + pMicrPacket->Head.uFlags |= VOIP_PACKET_STATUS_FLAG_METADATA; + } + } + else + { + pWrite = _VoipPacketGetWritePtr(pMicrPacket); + } + + // does this platform work with variable length sub-pkts + if ((iResult = VoipStatus(VoipGetRef(), 'vlen', 0, &bVariableSubPktLen, sizeof(bVariableSubPktLen))) < 0) + { + // if selector is not supported, assume fixed length + bVariableSubPktLen = FALSE; + } + + if (bVariableSubPktLen) + { + // MicrInfo.uSubPacketSize == 0xFF means that sub-pkts may have different sizes + pMicrPacket->MicrInfo.uSubPacketSize = 0xFF; + } + else + { + #if DIRTYCODE_LOGGING + if (pMicrPacket->MicrInfo.uSubPacketSize != 0 && pMicrPacket->MicrInfo.uSubPacketSize != iDataSize) + { + NetPrintf(("voipconnection: [%d] critical error - sub-packets have different size -> %d vs %d\n", iConnID, pMicrPacket->MicrInfo.uSubPacketSize, iDataSize)); + } + if (pMicrPacket->MicrInfo.uSubPacketSize == 0xFF) + { + NetPrintf(("voipconnection: [%d] critical error - sub-pkt size conflicting with fixed-length sub-pkt mode\n", iConnID)); + } + #endif + pMicrPacket->MicrInfo.uSubPacketSize = (unsigned)iDataSize; + } + + if (bVariableSubPktLen) + { + // store sub-pkt length in packet buffer + *pWrite = (unsigned)iDataSize; + + // copy data into packet buffer + ds_memcpy(pWrite+1, pVoiceData, iDataSize); + + #if DIRTYCODE_LOGGING + if ((pWrite + 1 + iDataSize) >= ((&pConnection->VoipMicrPacket[uUserIndex].aData[0]) + sizeof(pConnection->VoipMicrPacket[uUserIndex].aData))) + { + NetPrintf(("voipconnection: [%d] critical error - sub-packet packing overflowed!\n", iConnID)); + } + #endif + } + else + { + // copy data into packet buffer + ds_memcpy(pWrite, pVoiceData, iDataSize); + } + + // increment sub-packet count + pMicrPacket->MicrInfo.uNumSubPackets += 1; + + // identify local user index that generated the voice data + pMicrPacket->MicrInfo.uUserIndex = uUserIndex; + + // see if we need to send any buffered voice data + _VoipConnectionSendSingle(pConnectionlist, pConnection, uUserIndex, uCurTick, FALSE); + + if (pConnection->iVoipServerConnId != VOIP_CONNID_NONE) + { + // mark game server as served + bSentToVoipServer = TRUE; + } + } + } +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionFlush + + \Description + Send currently queued voice data, if any. + + \Input *pConnectionlist - connectionlist to send to + \Input iConnID - connection ident of connection to flush + + \Output + int32_t - number of voice packets flushed + + \Notes + Currently only useful on Xenon + + \Version 01/04/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipConnectionFlush(VoipConnectionlistT *pConnectionlist, int32_t iConnID) +{ + int32_t iUserIndex; + int32_t iNumSubPackets = 0; + int32_t iTotalNumSubPackets = 0; + + // ref the connection + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnID]; + + for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++) + { + iNumSubPackets = (signed)pConnection->VoipMicrPacket[iUserIndex].MicrInfo.uNumSubPackets; + + /* is the connection active, and are we sending to it? + xboxone: Never "mute" here, i.e. skip applying pConnectionlist->uSendMask. + Per-connection muting handling is rather deferred to VoipHeadsetXboxOne (See usage of +snd, -snd, +rcv, -rcv). + After rebasing VoipHeadsetXboxOne to MS game chat 2, we found out that the integration + with our per-connection muting here was not behaving properly during unmuting: resuming + submission of data frames to MS game chat 2 resulted in a ~ 5-sec delay before speech + would resume. */ + if ((pConnection->eState == ST_ACTV) && (iNumSubPackets > 0) + #if !defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) + && (pConnectionlist->uSendMask & (1 << iConnID)) + #endif + ) + { + // see if we need to send any buffered voice data + _VoipConnectionSendSingle(pConnectionlist, pConnection, iUserIndex, NetTick(), TRUE); + + // increment total number of sub-packets flushed for the connection + iTotalNumSubPackets += iNumSubPackets; + } + } + + // return total number of sub-packets flushed for the connection + return(iTotalNumSubPackets); +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionRegisterRemoteTalkers + + \Description + Register/unregister remote users associated with the given connection. + + \Input *pConnectionlist - connectionlist to send to + \Input iConnID - connection ident of connection to flush + \Input bRegister - if TRUE register talkers, else unregister them + + \Version 01/04/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConnectionRegisterRemoteTalkers(VoipConnectionlistT *pConnectionlist, int32_t iConnID, uint32_t bRegister) +{ + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnID]; + int32_t iUser; + + // register any remote users that are part of the connection + for (iUser = 0; iUser < pConnection->iMaxRemoteUsersExt; iUser++) + { + VoipUserT* pRemoteUser = (VoipUserT *)(&pConnection->RemoteUsers[iUser]); + // if no user, don't register + if (VOIP_NullUser(pRemoteUser)) + { + continue; + } + + #if DIRTYCODE_LOGGING + // if the user shouldn't be joining us, complain + if (pRemoteUser->ePlatform != VOIP_LOCAL_PLATFORM) // a platform that doesn't match the local platform is joining us + { + // is cross play setup properly for the remote user + if ((pRemoteUser->uFlags & VOIPUSER_FLAG_CROSSPLAY) == FALSE) + { + NetPrintf(("voipconnection: [%d] error, platform %d, persona %lld attempted to join, but cross play is not enabled remotely.\n", iConnID, pRemoteUser->ePlatform, pRemoteUser->AccountInfo.iPersonaId)); + } + + // is cross play setup properly for the local user + if (VoipStatus(VoipGetRef(), 'xply', 0, NULL, 0) == FALSE) + { + NetPrintf(("voipconnection: [%d] error, platform %d, persona %lld attempted to join, but cross play is not enabled locally.\n", iConnID, pRemoteUser->ePlatform, pRemoteUser->AccountInfo.iPersonaId)); + } + } + else if (((pRemoteUser->uFlags & VOIPUSER_FLAG_CROSSPLAY) == TRUE) != (VoipStatus(VoipGetRef(), 'xply', 0, NULL, 0) == TRUE)) // the platform matches, but the cross play settings should match too + { + NetPrintf(("voipconnection: [%d] error, local and remote user have mismatched cross play settings, platform %d, persona %lld.\n", iConnID, pRemoteUser->ePlatform, pRemoteUser->AccountInfo.iPersonaId)); + } + #endif + + // register the user + pConnectionlist->pRegUserCb((VoipUserT *)&pConnection->RemoteUsers[iUser], iConnID, bRegister, pConnectionlist->pRegUserUserData); + +#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) + // reapply player-to-player xone comm relationships + pConnectionlist->bApplyRelFromMuting = TRUE; +#endif + } + + // set mute list to be updated + pConnectionlist->bUpdateMute = TRUE; +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionSetSendMask + + \Description + Set connections to send to. + + \Input *pConnectionlist - connectionlist to send to + \Input uSendMask - connection send mask + + \Version 03/22/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConnectionSetSendMask(VoipConnectionlistT *pConnectionlist, uint32_t uSendMask) +{ + if (pConnectionlist->uSendMask != uSendMask) + { + VoipCommonSetMask(&pConnectionlist->uSendMask, uSendMask, "sendmask"); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionSetRecvMask + + \Description + Set connections to receive from. + + \Input *pConnectionlist - connectionlist to send to + \Input uRecvMask - connection receive mask + + \Version 03/22/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipConnectionSetRecvMask(VoipConnectionlistT *pConnectionlist, uint32_t uRecvMask) +{ + if (pConnectionlist->uRecvMask != uRecvMask) + { + VoipCommonSetMask(&pConnectionlist->uRecvMask, uRecvMask, "recvmask"); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionAddSessionId + + \Description + Add a session ID the set of higher level sessions sharing the specified VoIP connection. + + \Input *pConnectionlist - connectionlist + \Input iConnId - connection ID + \Input uSessionId - session ID to be added + + \Output + int32_t - 0 for success, negative for failure + + \Notes + Maintaning a set of session IDs per voip connection is required to support cases + where two consoles are involved in a P2P game and a pg simultaneously. Both of these + constructs will have a different session ID over the same shared voip connection. + Under some specific race conditions affecting the order at which Blaze messages are + processed by each game client, it is very possible that the pg session id is setup + first on one side and second on the other side thus leading to VOIP connectivity + failures if the voip connection construct is not supporting multiple concurrent + session IDs. + + \Version 08/30/2011 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t VoipConnectionAddSessionId(VoipConnectionlistT *pConnectionlist, int32_t iConnId, uint32_t uSessionId) +{ + int32_t iSessionIndex; + int32_t iRetCode = -1; + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnId]; + + NetCritEnter(&pConnectionlist->NetCrit); + + for (iSessionIndex = 0; iSessionIndex < VOIP_MAXSESSIONIDS; iSessionIndex++) + { + if (pConnection->uSessionId[iSessionIndex] == 0) + { + // free spot, insert session ID here + pConnection->uSessionId[iSessionIndex] = uSessionId; + + NetPrintf(("voipconnection: [%d] added 0x%08x to set of sessions sharing this voip connection\n", iConnId, uSessionId)); + + iRetCode = 0; + break; + } + } + + if (iRetCode != 0) + { + NetPrintf(("voipconnection: [%d] warning - 0x%08x could not be added to set of sessions sharing this voip connection because the list is full.\n", iConnId, uSessionId)); + } + + #if DIRTYCODE_LOGGING + // print set of sessions sharing this connection + _VoipConnectionPrintSessionIds(pConnectionlist, iConnId); + #endif + + NetCritLeave(&pConnectionlist->NetCrit); + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionDeleteSessionId + + \Description + Delete a session ID from the set of sessions sharing this voip connection. + + \Input *pConnectionlist - connectionlist + \Input iConnId - connection id + \Input uSessionId - session ID to be deleted + + \Output + int32_t - 0 for success, negative for failure + + \Version 08/30/2011 (mclouatre) +*/ +/********************************************************************************F*/ +int32_t VoipConnectionDeleteSessionId(VoipConnectionlistT *pConnectionlist, int32_t iConnId, uint32_t uSessionId) +{ + int32_t iSessionIndex; + int32_t iRetCode = -1; + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnId]; + + NetCritEnter(&pConnectionlist->NetCrit); + + // now delete this address from the list of fallbacks + for(iSessionIndex = 0; iSessionIndex < VOIP_MAXSESSIONIDS; iSessionIndex++) + { + if (pConnection->uSessionId[iSessionIndex] == uSessionId) + { + int32_t iSessionIndex2; + + // move all following session IDs one cell backward in the array + for(iSessionIndex2 = iSessionIndex; iSessionIndex2 < VOIP_MAXSESSIONIDS; iSessionIndex2++) + { + if (iSessionIndex2 == VOIP_MAXSESSIONIDS-1) + { + // last entry, reset to 0 + pConnection->uSessionId[iSessionIndex2] = 0; + } + else + { + pConnection->uSessionId[iSessionIndex2] = pConnection->uSessionId[iSessionIndex2+1]; + } + } + + NetPrintf(("voipconnection: [%d] removed 0x%08x from set of sessions sharing this voip connection\n", iConnId, uSessionId)); + + iRetCode = 0; + break; + } + } + + if (iRetCode != 0) + { + NetPrintf(("voipconnection: [%d] warning - 0x%08x not deleted because not found in set of sessions sharing this voip connection\n", iConnId, uSessionId)); + } + + #if DIRTYCODE_LOGGING + // print set of sessions sharing this connection + _VoipConnectionPrintSessionIds(pConnectionlist, iConnId); + #endif + + NetCritLeave(&pConnectionlist->NetCrit); + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionReliableBroadcastUser + + \Description + Use this function to broadcast local user join-in-progress/leave-in-progress + data that needs to be sent reliably on all active connections (or connections in ST_CONN state). + + \Input *pConnectionlist - connectionlist + \Input uLocalUserIndex - local user index + \Input bParticipating - TRUE if user joined-in-progress, FALSE if user left-in-progress + + \Version 09/18/2014 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipConnectionReliableBroadcastUser(VoipConnectionlistT *pConnectionlist, uint8_t uLocalUserIndex, uint32_t bParticipating) +{ + ReliableDataT ReliableData; + uint8_t *pWrite = &ReliableData.aData[0]; + + // fill type + ReliableData.info.uType = (bParticipating ? VOIP_RELIABLE_TYPE_USERADD : VOIP_RELIABLE_TYPE_USERREM); + + // fill payload with user index first + *pWrite++ = uLocalUserIndex; + + // then append voip user + ds_memcpy_s(pWrite, sizeof(ReliableData.aData), &pConnectionlist->LocalUsers[uLocalUserIndex], sizeof(pConnectionlist->LocalUsers[uLocalUserIndex])); + pWrite = pWrite + sizeof(pConnectionlist->LocalUsers[uLocalUserIndex]); + + // fill size + _VoipEncodeU16(&ReliableData.info.uSize[0], (pWrite - &ReliableData.aData[0])); + + // enqueue for transmission on all connections (will also fill in seq number) + _VoipConnectionReliableDataEnqueue(pConnectionlist, VOIP_CONNID_ALL, &ReliableData); +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionReliableTranscribedTextMessage + + \Description + Use this function to reliably broadcast a transcribed text message + (originated from a local user) on all connections that requested for it. + + \Input *pConnectionlist - connectionlist + \Input uLocalUserIndex - local user index + \Input *pStrUtf8 - pointer to text message to be sent + + \Version 09/18/2014 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipConnectionReliableTranscribedTextMessage(VoipConnectionlistT *pConnectionlist, uint8_t uLocalUserIndex, const char *pStrUtf8) +{ + int32_t iConnectionIndex; + ReliableDataT ReliableData; + uint8_t *pWrite = &ReliableData.aData[0]; + + // fill type + ReliableData.info.uType = VOIP_RELIABLE_TYPE_TEXT; + + // fill payload with user index first + *pWrite++ = uLocalUserIndex; + ds_strnzcpy((char *)pWrite, pStrUtf8, VOIP_MAXRELIABLEDATA - 1); + pWrite += strlen((const char *)pWrite)+1; + + // fill size of payload + _VoipEncodeU16(&ReliableData.info.uSize[0], (pWrite - &ReliableData.aData[0])); + + // enqueue for transmission only on connections with users that requested for transcribed text + for (iConnectionIndex = 0; iConnectionIndex < pConnectionlist->iMaxConnections; iConnectionIndex++) + { + VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnectionIndex]; + + if ((pConnection->eState == ST_ACTV) && pConnection->bTranscribedTextRequested && (pConnectionlist->uSendMask & (1 << iConnectionIndex))) + { + _VoipConnectionReliableDataEnqueue(pConnectionlist, iConnectionIndex, &ReliableData); + } + } +} + +/*F********************************************************************************/ +/*! + \Function VoipConnectionReliableSendOpaque + + \Description + Use this function to send opaque data reliably over specified connection. + + \Input *pConnectionlist - connectionlist + \Input iConnID - connection to send the data over + \Input *pData - pointer to buffer filled with reliable data to be broadcasted + \Input uDataSize - reliable data size (in bytes) + + \Version 09/18/2014 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipConnectionReliableSendOpaque(VoipConnectionlistT *pConnectionlist, int32_t iConnID, const uint8_t *pData, uint16_t uDataSize) +{ + ReliableDataT ReliableData; + + // fill type + ReliableData.info.uType = VOIP_RELIABLE_TYPE_OPAQUE; + + // fill size + _VoipEncodeU16(&ReliableData.info.uSize[0], uDataSize); + + // fill payload + ds_memcpy_s(&ReliableData.aData[0], sizeof(ReliableData.aData), pData, uDataSize); + + // enqueue for transmission on all connections (will also fill in seq number) + _VoipConnectionReliableDataEnqueue(pConnectionlist, iConnID, &ReliableData); +} + diff --git a/src/thirdparty/dirtysdk/source/voip/voipconnection.h b/src/thirdparty/dirtysdk/source/voip/voipconnection.h new file mode 100644 index 00000000..446a36c5 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipconnection.h @@ -0,0 +1,246 @@ +/*H********************************************************************************/ +/*! + \File voipconnection.h + + \Description + VoIP virtual connection manager. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 03/17/2004 (jbrookes) First Version + \Version 1.1 12/10/2008 (mclouatre) Added ref count to VOIP connections. + \Version 1.2 01/06/2009 (mclouatre) Added field RemoteAddrFallbacks to VoipConnectionT structure. Create related functions + \Version 1.3 10/26/2009 (mclouatre) Renamed from xbox/voipconnection.h to xenon/voipconnectionxenon.h +*/ +/********************************************************************************H*/ + +#ifndef _voipconnection_h +#define _voipconnection_h + +/*** Include files ****************************************************************/ + +#include "voippacket.h" + +/*** Defines **********************************************************************/ +//! maximum number of session IDs per VOIP connection +#define VOIP_MAXSESSIONIDS (8) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! voice receive callback +typedef void (VoipConnectRecvVoiceCbT)(VoipUserT *pRemoteUsers, int32_t iRemoteUserSize, int32_t iConnId, VoipMicrInfoT *pMicrInfo, uint8_t *pPacketData, void *pUserData); + +//! transcribed text recv callback +typedef void (VoipConnectRecvOpaqueCbT)(int32_t iConnId, const uint8_t *pOpaqueData, int32_t iOpaqueDataSize, void *pUserData); + +//! transcribed text recv callback +typedef void (VoipConnectRecvTextCbT)(int32_t iConnId, int32_t iRemoteUserIndex, const char *pStrUtf8, void *pUserData); + +//! new remote user register callback +typedef void (VoipConnectRegUserCbT)(VoipUserT *pRemoteUser, int32_t iConnId, uint32_t bRegister, void *pUserData); + +typedef struct SendAddrFallbackT +{ + uint32_t SendAddr; //!< send address + uint8_t bReachable; //!< FALSE if voip detected the IP addr as being unreachable +} SendAddrFallbackT; + +//! type used to create linked list of reliable data entries +typedef struct LinkedReliableDataT +{ + struct LinkedReliableDataT *pNext; // +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" +#include "voipdvi.h" + +/*** Defines **********************************************************************/ + +#define DIV_RANGE (4) +#define MAX_RANGE (8191) + +//! encode/decode information +#define VOIPDVI_FRAME_SIZE (8) + +/*** Macros ***********************************************************************/ + +#define CLIP(x,lo,hi) \ + if (x < lo) \ + { \ + x = lo; \ + } \ + else if (x > hi) \ + { \ + x = hi; \ + } + + +/*** Type Definitions *************************************************************/ + +//! DVI compression state +typedef struct DVICompStateT +{ + int16_t iEstimate; + int16_t iStepIndex; +} DVICompStateT; + +typedef struct VoipDVIStateT +{ + VoipCodecRefT CodecState; + + // codec-specifc data goes here + DVICompStateT EncodeState; + int32_t iOutputVolume; +} VoipDVIStateT; + + +/*** Function Prototypes **********************************************************/ + +static VoipCodecRefT *_VoipDVICreate(int32_t iDecoderChannels); +static void _VoipDVIDestroy(VoipCodecRefT *pCodecState); +static int32_t _VoipDVIEncodeBlock3(VoipCodecRefT *pCodecState, uint8_t *pOut, const int16_t *pInp, int32_t iNumSamples); +static int32_t _VoipDVIDecodeBlock3(VoipCodecRefT *pCodecState, int32_t *pOut, const uint8_t *pInp, int32_t iInputBytes, int32_t iChannel); +static void _VoipDVIReset(VoipCodecRefT *pCodecState); +static int32_t _VoipDVIControl(VoipCodecRefT *pCodecState, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); +static int32_t _VoipDVIStatus(VoipCodecRefT *pCodecState, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize); + +/*** Variables ********************************************************************/ + +// Public variables + +//! public DVI codec definition +const VoipCodecDefT VoipDVI_CodecDef = +{ + _VoipDVICreate, + _VoipDVIDestroy, + _VoipDVIEncodeBlock3, + _VoipDVIDecodeBlock3, + _VoipDVIReset, + _VoipDVIControl, + _VoipDVIStatus, +}; + +// Private variables + +static int16_t _StepLimit3 = 0; +static int16_t _StepTable3[64]; +static int16_t _StepIndex3[4] = { -2, -1, 2, 5 }; + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _DeltaEncode3 + + \Description + Encode the prediction error into a 3-bit value + + \Input iStep - current step size + \Input iDelta - error delta to encode + + \Output + int32_t - Three bit encoded value + + \Version 05/13/2003 (gschaefer) +*/ +/********************************************************************************F*/ +static __inline int32_t _DeltaEncode3(int32_t iStep, int32_t iDelta) +{ + int32_t iEncode = 0; + + // check for negative direction + if (iDelta < 0) + { + iEncode |= 4; + iDelta = -iDelta; + } + + // primary delta shift + if (iDelta >= iStep) + { + iEncode |= 2; + iDelta -= iStep; + } + + // secondary delta shift + iStep >>= 1; + if (iDelta >= iStep) + { + iEncode |= 1; + iDelta -= iStep; + } + + // return encoded value + return(iEncode); +} + +/*F********************************************************************************/ +/*! + \Function _DeltaDecode3 + + \Description + Decode 3-bit value into prediction error delta + + \Input iStep - current step size + \Input iEncode - encoded 3-bit value + + \Output + int32_t - Prediction error delta + + \Version 105/13/2003 (gschaefer) +*/ +/********************************************************************************F*/ +static __inline int32_t _DeltaDecode3(int32_t iStep, int32_t iEncode) +{ + int32_t iDelta = 0; + + // primary delta shift + if (iEncode & 2) + { + iDelta += iStep; + } + + // secondary delta shift + iStep >>= 1; + if (iEncode & 1) + { + iDelta += iStep; + } + + // always add final fraction to reduce truncation error + iDelta += (iStep >> 1); + + // handle negative shift + if (iEncode & 4) + { + iDelta = -iDelta; + } + return(iDelta); +} + +/*F********************************************************************************/ +/*! + \Function _VoipDVISetupBlock3 + + \Description + Dynamically build the initial quantization table + + \Version 05/13/2003 (gschaefer) +*/ +/********************************************************************************F*/ +static void _VoipDVISetupBlock3(void) +{ + int32_t iStep; + int32_t iLast = 0; + + // reset number of table entries + _StepLimit3 = 0; + + // use fixed point math to allow step = step * 1.2 + for (iStep = 300; iStep < MAX_RANGE*100; iStep = (iStep * 120) / 100) + { + // skip non integer changes + if ((iStep/100) != iLast) + { + iLast = iStep/100; + _StepTable3[_StepLimit3++] = iLast; + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipDVICreate + + \Description + Create Voip DVI state + + \Input iDecodeChannels - number of channels to support decoding for + + \Output + VoipCodecRefT * - pointer to new codec state, or NULL if failure + + \Version 08/05/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static VoipCodecRefT *_VoipDVICreate(int32_t iDecodeChannels) +{ + VoipDVIStateT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + + // set up DVI tables + _VoipDVISetupBlock3(); + + // allocate and clear state + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + if ((pState = (VoipDVIStateT *) DirtyMemAlloc (sizeof(*pState), VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipdvi: unable to allocate state\n")); + return(NULL); + } + ds_memclr(pState, sizeof(*pState)); + + // set up codec state + pState->CodecState.pCodecDef = &VoipDVI_CodecDef; + pState->CodecState.iDecodeChannels = iDecodeChannels; + + // set the output level + pState->iOutputVolume = 1 << VOIP_CODEC_OUTPUT_FRACTIONAL; + + // return generic state ref to caller + return(&pState->CodecState); +} + +/*F********************************************************************************/ +/*! + \Function _VoipDVIDestroy + + \Description + Destroy given Voip DVI codec state + + \Input *pCodecState - pointer to state to destroy + + \Version 08/05/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipDVIDestroy(VoipCodecRefT *pCodecState) +{ + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + DirtyMemFree(pCodecState, VOIP_MEMID, iMemGroup, pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function _VoipDVIEncodeBlock3 + + \Description + Encode a 16-bit linear PCM sample into a 3-bit value using ADPCM. + + \Input *pCodecState - module state + \Input *pOut - packed output buffer + \Input *pInp - 16-bit mono sample buffer pointer + \Input iNumSamples - number of samples to compress + + \Output + int32_t - size of compressed data in bytes + + \Version 05/13/2003 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _VoipDVIEncodeBlock3(VoipCodecRefT *pCodecState, uint8_t *pOut, const int16_t *pInp, int32_t iNumSamples) +{ + int32_t iCount; + int32_t iIndex; + int32_t iDelta; + int32_t iSample; + int32_t iEstimate; + int32_t iStepIndex; + unsigned char Stage[8]; + unsigned char *pBeg = pOut; + DVICompStateT *pCompState = &((VoipDVIStateT *)pCodecState)->EncodeState; + + // DVI encoder requires input samples to be a multiple of VOIPDVI_FRAME_SIZE + if ((iNumSamples % VOIPDVI_FRAME_SIZE) != 0) + { + NetPrintf(("voipdvi: error - dvi encoder can only encode multiples of %d samples (%d submitted).\n", VOIPDVI_FRAME_SIZE, iNumSamples)); + return(0); + } + + // save initial state to output buffer + ds_memcpy(pOut, pCompState, sizeof(*pCompState)); + pOut += sizeof(*pCompState); + + // fetch initial state + iEstimate = pCompState->iEstimate; + iStepIndex = pCompState->iStepIndex; + + for (iCount = 0; iCount < iNumSamples; ++iCount) + { + // calc delta from previous estimate + iDelta = (*pInp++ / DIV_RANGE) - iEstimate; + CLIP(iDelta, -MAX_RANGE, MAX_RANGE); + // encode the sample + iSample = _DeltaEncode3(_StepTable3[iStepIndex], iDelta); + // adjust the estimate based on decoded sample (since this is what decoded will use) + iEstimate += _DeltaDecode3(_StepTable3[iStepIndex], iSample); + CLIP(iEstimate, -MAX_RANGE, MAX_RANGE); + + #if 0 //$$ debug diagnostic to help tune/debug algorithm + NetPrintf(("sample #%d: orig=%5d comp=%5d err=%4d delta=%4d idx=%2d step=%5d code=%02x\n", + iCount, pInp[-1], iEstimate, pInp[-1]-iEstimate, iDelta, + iStepIndex, _StepTable3[iStepIndex], iSample)); + #endif + + // adjust the step size for next iteration + iStepIndex += _StepIndex3[iSample & 3]; + CLIP(iStepIndex, 0, _StepLimit3-1); + // save into staging buffer + iIndex = iCount&7; + Stage[iIndex] = iSample; + // see if we need to store the output + if (iIndex == 7) + { + pOut[0] = (Stage[0]<<0) | (Stage[1]<<3) | (Stage[2]<<6); + pOut[1] = (Stage[2]>>2) | (Stage[3]<<1) | (Stage[4]<<4) | (Stage[5]<<7); + pOut[2] = (Stage[5]>>1) | (Stage[6]<<2) | (Stage[7]<<5); + pOut += 3; + } + } + + // update with current state + pCompState->iEstimate = iEstimate; + pCompState->iStepIndex = iStepIndex; + + // return the length + return(pOut - pBeg); +} + +/*F********************************************************************************/ +/*! + \Function _VoipDVIDecodeBlock3 + + \Description + Decode 3-bit ADPCM packed data and accumulate into a 32-bit buffer of 16-bit linear PCM + samples. + + \Input *pCodecState - module state + \Input *pOut - 32-bit linear PCM sample accumulation buffer + \Input *pInp - packed data from encoder + \Input iInputBytes - to be completed + \Input iChannel - ignored + + \Output + int32_t - number of samples decoded + + \Version 05/13/2003 (gschaefer) +*/ +/********************************************************************************F*/ +static int32_t _VoipDVIDecodeBlock3(VoipCodecRefT *pCodecState, int32_t *pOut, const unsigned char *pInp, int32_t iInputBytes, int32_t iChannel) +{ + VoipDVIStateT *pState = (VoipDVIStateT *)pCodecState; + int32_t iCount, iNumSamples; + int32_t iIndex; + int32_t iInput; + int32_t iEstimate; + int32_t iStepIndex; + int32_t *pBeg = pOut; + unsigned char Stage[VOIPDVI_FRAME_SIZE]; + DVICompStateT DecodeState; + + // first four bytes of input are DVI state data + ds_memcpy(&DecodeState, pInp, sizeof(DecodeState)); + pInp += sizeof(DecodeState); + iInputBytes -= sizeof(DecodeState); + + if ((iInputBytes % 3) != 0) + { + NetPrintf(("voipdvi: dvi decoder can only decode multiples of 3 bytes (%d submitted)\n", iInputBytes)); + return(0); + } + + // read state + iEstimate = DecodeState.iEstimate; + iStepIndex = DecodeState.iStepIndex; + + // calculate number of output samples based on the input data size + iNumSamples = (iInputBytes * VOIPDVI_FRAME_SIZE)/3; + + // loop based on decoded data size + for (iCount = 0; iCount < iNumSamples; ++iCount) + { + // see if we need to fetch data + iIndex = iCount & 7; + if (iIndex == 0) + { + Stage[0] = pInp[0]; + Stage[1] = pInp[0]>>3; + Stage[2] = (pInp[0]>>6) | (pInp[1]<<2); + Stage[3] = pInp[1]>>1; + Stage[4] = pInp[1]>>4; + Stage[5] = (pInp[1]>>7) | (pInp[2]<<1); + Stage[6] = pInp[2]>>2; + Stage[7] = pInp[2]>>5; + pInp += 3; + } + + // get input byte + iInput = (Stage[iIndex] & 7); + // decode to delta and add to previous estimate + iEstimate += _DeltaDecode3(_StepTable3[iStepIndex], iInput); + CLIP(iEstimate, -MAX_RANGE, MAX_RANGE); + // update the index based on delta direction + iStepIndex += _StepIndex3[iInput & 3]; + CLIP(iStepIndex, 0, _StepLimit3-1); + // scale the volume using the output level and save to output buffer + *pOut++ += ((iEstimate * pState->iOutputVolume) >> VOIP_CODEC_OUTPUT_FRACTIONAL) * DIV_RANGE; + } + + // return the length + return(pOut - pBeg); +} + +/*F********************************************************************************/ +/*! + \Function _VoipDVIReset + + \Description + Reset DVI Encoder state. + + \Input *pCodecState - pointer to DVI module + + \Version 08/05/2004 +*/ +/********************************************************************************F*/ +static void _VoipDVIReset(VoipCodecRefT *pCodecState) +{ + VoipDVIStateT *pState = (VoipDVIStateT *)pCodecState; + ds_memclr(&pState->EncodeState, sizeof(pState->EncodeState)); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipDVIControl + + \Description + Modifies parameters of the codec + + \Input *pCodecState - pointer to decode state + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + 'plvl' - Set the output power level + \endverbatim + + \Version 03/12/2008 (grouse) +*/ +/*************************************************************************************************F*/ +static int32_t _VoipDVIControl(VoipCodecRefT *pCodecState, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + VoipDVIStateT *pState = (VoipDVIStateT *)pCodecState; + + if (iControl == 'plvl') + { + pState->iOutputVolume = iValue; + return(0); + } + + NetPrintf(("voipdvi: unhandled control selector '%C'\n", iControl)); + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipDVIStatus + + \Description + Get codec status + + \Input *pCodecState - pointer to decode state + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuffer - [out] storage for selector output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iSelect can be one of the following: + + \verbatim + 'fsiz' - size of encoder output / decoder input in bytes (iValue=samples per frame) + \endverbatim + + \Version 10/11/2011 (jbrookes) +*/ +/*************************************************************************************************F*/ +static int32_t _VoipDVIStatus(VoipCodecRefT *pCodecState, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize) +{ + VoipDVIStateT *pState = (VoipDVIStateT *)pCodecState; + + // these options require module state + if (pState != NULL) + { + if (iSelect == 'fsiz') + { + // 3 bits per sample + 4 bytes of overhead per frame + return(((iValue*3)/VOIPDVI_FRAME_SIZE) + sizeof(DVICompStateT)); + } + } + NetPrintfVerbose((pState->CodecState.iDebugLevel, 1, "voipdvi: unhandled status selector '%C'\n", iSelect)); + return(-1); +} + diff --git a/src/thirdparty/dirtysdk/source/voip/voipdvi.h b/src/thirdparty/dirtysdk/source/voip/voipdvi.h new file mode 100644 index 00000000..56d81d08 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipdvi.h @@ -0,0 +1,43 @@ +/*H********************************************************************************/ +/*! + \File voipdvi.h + + \Description + Table based 16:3 ADPCM compression originally based off EAC SIMEX code, + modified by Greg Schaefer. + + \Copyright + Copyright (c) Electronic Arts 2003-2004. ALL RIGHTS RESERVED. + + \Version 1.0 11/01/2002 (ischmidt) First version (based on SIMEX by Dave Mercier) + \Version 2.0 05/13/2003 (gschaefer) Rewrite to 16:3 (from 16:4) +*/ +/********************************************************************************H*/ + +#ifndef _voipdvi_h +#define _voipdvi_h + +/*** Include files ****************************************************************/ + +#include "DirtySDK/voip/voipcodec.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ +#if defined(__cplusplus) +extern "C" { +#endif + +extern const VoipCodecDefT VoipDVI_CodecDef; + +#if defined(__cplusplus) +}; +#endif + +/*** Functions ********************************************************************/ + +#endif // _voipdvi_h diff --git a/src/thirdparty/dirtysdk/source/voip/voipgroup.c b/src/thirdparty/dirtysdk/source/voip/voipgroup.c new file mode 100644 index 00000000..9bbbd3c7 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipgroup.c @@ -0,0 +1,2075 @@ +/*H********************************************************************************/ +/*! + \File voipgroup.c + + \Description + A module that handles logical grouping of voip connections. Each module that wants to deal + with a block of voip connections uses a voipgroup. This reduces the singleton nature + of voip.h + + \Copyright + Copyright (c) 2007 Electronic Arts Inc. + + \Version 12/18/07 (jrainy) +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +// dirtysock includes +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +// voip includes +#include "DirtySDK/voip/voip.h" +#include "DirtySDK/voip/voipgroup.h" +#include "voippriv.h" +#include "voipcommon.h" + +/*** Defines **********************************************************************/ + +#define MAX_CLIENTS_PER_GROUP (32) + +#define MAX_REF_PER_LOW_LEVEL_CONNS (8) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! high-level connection state +typedef enum VoipGroupConnStateE +{ + VOIPGROUP_CONNSTATE_ACTIVE, //iMaxGroups; iGroup++) + { + if ((pManager->aGroups[iGroup].bUsed) && (pManager->aGroups[iGroup].pGroupCallback)) + { + if (eCbType == VOIP_CBTYPE_FROMEVENT) + { + iNewValue = 0; + for(iConnId = 0; iConnId < MAX_CLIENTS_PER_GROUP; iConnId++) + { + iMappedConnId = _VoipGroupMatch(&pManager->aGroups[iGroup], iConnId); + if (iMappedConnId != VOIP_CONNID_NONE) + { + // lookup bit indexed by mapped value, and set the bit before mapping. + iNewValue |= (((iValue & (1 << iMappedConnId)) ? 1 : 0) << iConnId); + } + } + } + else + { + iNewValue = iValue; + } + + pManager->aGroups[iGroup].pGroupCallback(pVoip, eCbType, iNewValue, pManager->aGroups[iGroup].pGroupCallbackData); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipGroupManagerDisplayTranscribedTextCallback + + \Description + Callback registered with low-level voip transcribed text callback + + \Input iLowLevelConnId - connection index (-1 if originator is local) + \Input iUserIndex - index of local/remote user from which the transcribed text is originated + \Input *pStrUtf8 - received transcribed text + \Input *pUserData - pointer to the VoipGroupManager + + \Output + int32_t - TRUE + + \Version 05/03/2017 (mclouatre) +*/ +/********************************************************************************F*/ +static int32_t _VoipGroupManagerDisplayTranscribedTextCallback(int32_t iLowLevelConnId, int32_t iUserIndex, const char *pStrUtf8, void *pUserData) +{ + VoipGroupManagerT *pManager = (VoipGroupManagerT *)pUserData; + int32_t iGroup; + uint8_t bDisplayed; + + for (iGroup = 0, bDisplayed = FALSE; (iGroup < pManager->iMaxGroups) && !bDisplayed; iGroup++) + { + if ((pManager->aGroups[iGroup].bUsed) && (pManager->aGroups[iGroup].pDisplayTranscribedTextCb)) + { + if (iLowLevelConnId != -1) + { + int32_t iHighLevelConnId; + + for (iHighLevelConnId = 0; iHighLevelConnId < MAX_CLIENTS_PER_GROUP; iHighLevelConnId++) + { + // is the entry valid and is it associated with the low level conn id we are interested in + if ((pManager->aGroups[iGroup].aConnections[iHighLevelConnId].uClientId != 0) && + (pManager->aGroups[iGroup].aConnections[iHighLevelConnId].iMappedLowLevelConnId == iLowLevelConnId)) + { + bDisplayed = pManager->aGroups[iGroup].pDisplayTranscribedTextCb(iHighLevelConnId, iUserIndex, pStrUtf8, pManager->aGroups[iGroup].pDisplayTranscribedTextUserData); + break; + } + } + } + else + { + // display transcribed text from local user + bDisplayed = pManager->aGroups[iGroup].pDisplayTranscribedTextCb(-1, iUserIndex, pStrUtf8, pManager->aGroups[iGroup].pDisplayTranscribedTextUserData); + } + } + } + return(TRUE); +} + +/*F********************************************************************************/ +/*! + \Function _VoipGroupManagerCreate + + \Description + Creates the single VoipGroupManager. + + \Input iMaxGroups - the maximum number of voip groups to allocate + + \Output + VoipGroupManagerT * - The VoipGroup Manager + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +static VoipGroupManagerT *_VoipGroupManagerCreate(int8_t iMaxGroups) +{ + if (_VoipGroupManager_pRef == NULL) + { + int32_t iMemGroup, iMemSize; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // calculate size of state + iMemSize = sizeof(*_VoipGroupManager_pRef) + (sizeof(_VoipGroupManager_pRef->aGroups[0]) * (iMaxGroups - 1)); + + if ((_VoipGroupManager_pRef = DirtyMemAlloc(iMemSize, VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipgroup: could not allocate module state\n")); + return(NULL); + } + ds_memclr(_VoipGroupManager_pRef, iMemSize); + + _VoipGroupManager_pRef->iMaxGroups = iMaxGroups; + _VoipGroupManager_pRef->iMemGroup = iMemGroup; + _VoipGroupManager_pRef->pMemGroupUserData = pMemGroupUserData; + + // register callback with low-level VOIP module if it exists + if (VoipGetRef() != NULL) + { + VoipCommonSetEventCallback((VoipCommonRefT *)VoipGetRef(), _VoipGroupManagerEventCallback, _VoipGroupManager_pRef); + VoipCommonSetDisplayTranscribedTextCallback((VoipCommonRefT *)VoipGetRef(), _VoipGroupManagerDisplayTranscribedTextCallback, _VoipGroupManager_pRef); + } + } + + return(_VoipGroupManager_pRef); +} + + + +/*F********************************************************************************/ +/*! + \Function _VoipGroupFindFreeHighLevelConn + + \Description + Find a free high level connection spot in the given voipgroup. + + \Input *pVoipGroup - the voipgroup to inspect for a free connection spot + + \Output + int32_t - failure: VOIP_CONNID_NONE, success: conn id of high-level conn found + + \Version 10/04/2010 (mclouatre) +*/ +/********************************************************************************F*/ +static int32_t _VoipGroupFindFreeHighLevelConn(VoipGroupRefT *pVoipGroup) +{ + int32_t iConnId; + + for(iConnId = 0; iConnId < MAX_CLIENTS_PER_GROUP; iConnId++) + { + if (pVoipGroup->aConnections[iConnId].uClientId == 0) + { + break; + } + } + + if (iConnId == MAX_CLIENTS_PER_GROUP) + { + NetPrintf(("voipgroup: [%p] warning - voipgroup is full\n", pVoipGroup)); + return(VOIP_CONNID_NONE); + } + + return(iConnId); +} + +/*F********************************************************************************/ +/*! + \Function _VoipGroupFindUsedHighLevelConn + + \Description + In the given voipgroiup, find the high level connection associated with + specified client id. + + \Input *pVoipGroup - the voipgroup to inspect for a free connection spot + \Input uClientId - client id to look for + + \Output + int32_t - failure: VOIP_CONNID_NONE, success: conn id of high-level conn found + + \Version 10/04/2010 (mclouatre) +*/ +/********************************************************************************F*/ +static int32_t _VoipGroupFindUsedHighLevelConn(VoipGroupRefT *pVoipGroup, uint32_t uClientId) +{ + int32_t iConnId; + + for(iConnId = 0; iConnId < MAX_CLIENTS_PER_GROUP; iConnId++) + { + if (pVoipGroup->aConnections[iConnId].uClientId == uClientId) + { + break; + } + } + + if (iConnId == MAX_CLIENTS_PER_GROUP) + { + return(VOIP_CONNID_NONE); + } + + return(iConnId); +} + + +/*F********************************************************************************/ +/*! + \Function _VoipGroupLowLevelConnInUse + + \Description + Tests whether a voip-level ConnId is in use. + + \Input iConnId - voip-level ConnId + + \Output + uint8_t - TRUE or FALSE + + \Version 12/02/2009 (jrainy) +*/ +/********************************************************************************F*/ +static uint8_t _VoipGroupLowLevelConnInUse(int32_t iConnId) +{ + return(_VoipGroupManager_pRef->aLowLevelConnReferences[iConnId][0] != NULL); +} + +/*F********************************************************************************/ +/*! + \Function _VoipGroupManagerGetGroup + + \Description + Allocates a new VoipGroup for the given manager + + \Input *pVoipGroupManager - The manager to allocate the group from + + \Output + VoipGroupRefT* - The allocated VoipGroup + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +static VoipGroupRefT* _VoipGroupManagerGetGroup(VoipGroupManagerT *pVoipGroupManager) +{ + int32_t iIndex; + + if (pVoipGroupManager->iNumGroups < pVoipGroupManager->iMaxGroups) + { + for (iIndex = 0; (iIndex < pVoipGroupManager->iMaxGroups); iIndex++) + { + if (!pVoipGroupManager->aGroups[iIndex].bUsed) + { + return(&pVoipGroupManager->aGroups[iIndex]); + } + } + } + + NetPrintf(("voipgroup: no voipgroup available\n")); + return(NULL); +} + +#if DIRTYCODE_LOGGING +/*F********************************************************************************/ +/*! + \Function _VoipGroupPrintRefsToLowLevelConn + + \Description + Prints all references to a given low-level voip connection + + \Input iLowLevelConnId - id of low-level connection + + \Version 11/12/2009 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipGroupPrintRefsToLowLevelConn(int32_t iLowLevelConnId) +{ + int32_t iRefIndex; + VoipGroupManagerT *pManager = _VoipGroupManager_pRef; + + if (_VoipGroupLowLevelConnInUse(iLowLevelConnId)) + { + NetPrintf(("voipgroup: references to low-level voip conn %d are: ", iLowLevelConnId)); + for (iRefIndex = 0; iRefIndex < MAX_REF_PER_LOW_LEVEL_CONNS; iRefIndex++) + { + if (pManager->aLowLevelConnReferences[iLowLevelConnId][iRefIndex] != NULL) + { + NetPrintf(("%p ", pManager->aLowLevelConnReferences[iLowLevelConnId][iRefIndex])); + } + else + { + break; + } + } + NetPrintf(("\n")); + } + else + { + NetPrintf(("voipgroup: there is no reference to low-level voip conn %d\n", iLowLevelConnId)); + } +} +#endif + +/*F********************************************************************************/ +/*! + \Function _VoipGroupManagerShutdown + + \Description + free the singleton VoipGroupManager. + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +static void _VoipGroupManagerShutdown(void) +{ + if (_VoipGroupManager_pRef != NULL) + { + // remove callback registered with low-level VOIP module + if (VoipGetRef() != NULL) + { + VoipCommonSetEventCallback((VoipCommonRefT *)VoipGetRef(), NULL, NULL); + VoipCommonSetDisplayTranscribedTextCallback((VoipCommonRefT *)VoipGetRef(), NULL, NULL); + } + + DirtyMemFree(_VoipGroupManager_pRef, VOIP_MEMID, _VoipGroupManager_pRef->iMemGroup, _VoipGroupManager_pRef->pMemGroupUserData); + _VoipGroupManager_pRef = NULL; + + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipGroupManagerReleaseGroup + + \Description + Frees a VoipGroup for the given manager + + \Input *pManager - The manager to allocate the group from + \Input *pVoipGroup - The VoipGroup to free + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +static void _VoipGroupManagerReleaseGroup(VoipGroupManagerT *pManager, VoipGroupRefT* pVoipGroup) +{ + int32_t iSlot; + int32_t iUser; + + NetPrintf(("voipgroup: [%p] remove all connections and clear all mappings\n", pVoipGroup)); + + // remove all connections + for(iSlot = 0; iSlot < MAX_CLIENTS_PER_GROUP; iSlot++) + { + if (pVoipGroup->aConnections[iSlot].uClientId != 0) + { + VoipGroupDisconnect(pVoipGroup, iSlot); + } + } + + // deactivate active refs + for(iUser = 0; iUser < VOIP_MAXLOCALUSERS; iUser++) + { + if (pVoipGroup->aParticipatingUser[iUser] == TRUE) + { + VoipGroupActivateLocalUser(pVoipGroup, iUser, FALSE); + } + } + + // clear voipgroup entry + ds_memclr(pVoipGroup, sizeof(VoipGroupRefT)); + + // shutdown the voipgroup manager if there are no groups left + if ((--pManager->iNumGroups) == 0) + { + _VoipGroupManagerShutdown(); + } + + return; +} + +/*F********************************************************************************/ +/*! + \Function _VoipGroupMatch + + \Description + Converts between VoipGroup ID and Voip ID. + + \Input *pVoipGroup - The VoipGroup the iConnId indexes in + \Input iConnId - The VoipGroup ID + + \Output + int32_t - The corresponding Voip ID + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +static int32_t _VoipGroupMatch(VoipGroupRefT *pVoipGroup, int32_t iConnId) +{ + int32_t iIndex, iConn, iRetVal = VOIP_CONNID_NONE; + uint32_t uClientId; + VoipGroupManagerT *pManager = _VoipGroupManager_pRef; + + if ((iConnId < 0) || (iConnId >= MAX_CLIENTS_PER_GROUP)) + { + return(VOIP_CONNID_NONE); + } + + uClientId = pVoipGroup->aConnections[iConnId].uClientId; + if (uClientId == 0) + { + return(VOIP_CONNID_NONE); + } + + // if this connection is suspended look for the active group + // for this iConnId to get the proper low level conn id + if (pVoipGroup->aConnections[iConnId].eConnState == VOIPGROUP_CONNSTATE_SUSPENDED) + { + for (iIndex = 0; iIndex < pManager->iMaxGroups; iIndex++) + { + for (iConn = 0; iConn < MAX_CLIENTS_PER_GROUP; iConn++) + { + if ( (pManager->aGroups[iIndex].aConnections[iConn].uClientId == uClientId) && + (pManager->aGroups[iIndex].aConnections[iConn].eConnState == VOIPGROUP_CONNSTATE_ACTIVE) ) + { + iRetVal = pManager->aGroups[iIndex].aConnections[iConn].iMappedLowLevelConnId; + break; + } + } + } + } + else + { + iRetVal = pVoipGroup->aConnections[iConnId].iMappedLowLevelConnId; + } + + return(iRetVal); +} + +/*F********************************************************************************/ +/*! + \Function _VoipGroupFindCollision + + \Description + Looks for another group with a low-level connection to a given client id + + \Input *pVoipGroup - pointer to voipgroup looking for a colliding voipgroup + \Input uClientId - the client id to match + \Input eConnState - connection state to match + \Input *pCollidingConnId - output parameter to be filled with id of colliding connection if applicable (invalid if return value is NULL) + + \Output + VoipGroupRefT* - group reference if found, NULL if no collision + + \Version 011/11/2009 (mclouatre) +*/ +/********************************************************************************F*/ +static VoipGroupRefT* _VoipGroupFindCollision(VoipGroupRefT *pVoipGroup, uint32_t uClientId, VoipGroupConnStateE eConnState, int32_t *pCollidingConnId) +{ + int32_t iIndex, iConnId; + VoipGroupManagerT *pManager = _VoipGroupManager_pRef; + + for (iIndex = 0; iIndex < pManager->iMaxGroups; iIndex++) + { + for (iConnId = 0; iConnId < MAX_CLIENTS_PER_GROUP; iConnId++) + { + if ((pManager->aGroups[iIndex].aConnections[iConnId].uClientId == uClientId) && + (pManager->aGroups[iIndex].aConnections[iConnId].eConnState == eConnState)) + { + // do not allow a voipgroup to collide with itself + if (&pManager->aGroups[iIndex] == pVoipGroup) + { + continue; + } + + NetPrintf(("voipgroup: [%p] collision found with voipgroup %p for client id 0x%08x\n", pVoipGroup, &pManager->aGroups[iIndex], uClientId)); + *pCollidingConnId = iConnId; + return(&pManager->aGroups[iIndex]); + } + } + } + + *pCollidingConnId = VOIP_CONNID_NONE; + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function _VoipGroupAddRefToLowLevelConn + + \Description + Add a reference to a low level connection + + \Input iLowLevelConnId - low level conn id + \Input *pVoipGroup - voipgroup to be added as a reference to the low-level connection + + \Output + int32_t - 0 for success, -1 for failure + + \Version 11/12/2009 (mclouatre) +*/ +/********************************************************************************F*/ +static int32_t _VoipGroupAddRefToLowLevelConn(int32_t iLowLevelConnId, VoipGroupRefT * pVoipGroup) +{ + int32_t iRefIndex; + int32_t iRetCode = 0; // default to succes + VoipGroupManagerT *pManager = _VoipGroupManager_pRef; + + for (iRefIndex = 0; iRefIndex < MAX_REF_PER_LOW_LEVEL_CONNS; iRefIndex++) + { + // if this ref already is in the table, fake a failure + if (pManager->aLowLevelConnReferences[iLowLevelConnId][iRefIndex] == pVoipGroup) + { + iRetCode = -1; + NetPrintf(("voipgroup: [%p] error - current voipgroup already exists as a reference for low-level voip conn %d\n", pVoipGroup, iLowLevelConnId)); + break; + } + + // look for a free spot to find our ref + if (pManager->aLowLevelConnReferences[iLowLevelConnId][iRefIndex] == NULL) + { + pManager->aLowLevelConnReferences[iLowLevelConnId][iRefIndex] = pVoipGroup; + NetPrintf(("voipgroup: [%p] added current voipgroup as a reference for low-level voip conn %d\n", pVoipGroup, iLowLevelConnId)); + break; + } + } + + if (iRefIndex == MAX_REF_PER_LOW_LEVEL_CONNS) + { + iRetCode = -1; + + NetPrintf(("voipgroup: [%p] critical error - cannot add current voipgroup as a reference for low-level conn %d, max number of references exceeded\n", + pVoipGroup, iLowLevelConnId)); + } + + #if DIRTYCODE_LOGGING + _VoipGroupPrintRefsToLowLevelConn(iLowLevelConnId); + #endif + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function _VoipGroupRemoveRefToLowLevelConn + + \Description + Remove a reference to a low level connection + + \Input iLowLevelConnId - low level conn id + \Input *pVoipGroup - voipgroup to be added as a reference to the low-level connection + + \Version 11/12/2009 (mclouatre) +*/ +/********************************************************************************F*/ +static void _VoipGroupRemoveRefToLowLevelConn(int32_t iLowLevelConnId, VoipGroupRefT * pVoipGroup) +{ + int32_t iRefIndex; + VoipGroupManagerT *pManager = _VoipGroupManager_pRef; + + for(iRefIndex = 0; iRefIndex < MAX_REF_PER_LOW_LEVEL_CONNS; iRefIndex++) + { + if (pManager->aLowLevelConnReferences[iLowLevelConnId][iRefIndex] == pVoipGroup) + { + int32_t iRefIndex2; + + // move all following references one cell backward in the array + for(iRefIndex2 = iRefIndex; iRefIndex2 < MAX_REF_PER_LOW_LEVEL_CONNS; iRefIndex2++) + { + if (iRefIndex2 == MAX_REF_PER_LOW_LEVEL_CONNS-1) + { + // last entry, reset to NULL + pManager->aLowLevelConnReferences[iLowLevelConnId][iRefIndex2] = NULL; + } + else + { + pManager->aLowLevelConnReferences[iLowLevelConnId][iRefIndex2] = pManager->aLowLevelConnReferences[iLowLevelConnId][iRefIndex2+1]; + } + } + + NetPrintf(("voipgroup: [%p] current voipgroup no more referencing low-level voip conn %d\n", pVoipGroup, iLowLevelConnId)); + #if DIRTYCODE_LOGGING + _VoipGroupPrintRefsToLowLevelConn(iLowLevelConnId); + #endif + + break; + } + } + + if (iRefIndex == MAX_REF_PER_LOW_LEVEL_CONNS) + { + NetPrintf(("voipgroup: [%p] warning - current voipgroup not found as a valid reference for low-level conn %d\n", pVoipGroup, iLowLevelConnId)); + } +} + +/*** Public Functions *************************************************************/ + +/*F********************************************************************************/ +/*! + \Function VoipGroupCreate + + \Description + allocates a VoipGroup + + \Input iMaxGroups - the maximum number of groups we can have active at one time + + \Output + VoipGroupRefT* - Allocated VoipGroup + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +VoipGroupRefT* VoipGroupCreate(int8_t iMaxGroups) +{ + VoipGroupManagerT *pManager = _VoipGroupManagerCreate(iMaxGroups); + VoipGroupRefT *pNewlyCreated; + + if ((pNewlyCreated = _VoipGroupManagerGetGroup(pManager)) != NULL) + { + pManager->iNumGroups++; + pNewlyCreated->bUsed = TRUE; + } + + return(pNewlyCreated); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupDestroy + + \Description + Deallocates a VoipGroup + + \Input *pVoipGroup - VoipGroup to Deallocate + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +void VoipGroupDestroy(VoipGroupRefT *pVoipGroup) +{ + _VoipGroupManagerReleaseGroup(_VoipGroupManager_pRef, pVoipGroup); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupSetEventCallback + + \Description + Sets the callback and userdata for the specified group + + \Input *pVoipGroup - VoipGroup to use + \Input *pCallback - pointer to the callback to use + \Input *pUserData - pointer to the user specified data + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +void VoipGroupSetEventCallback(VoipGroupRefT *pVoipGroup, VoipCallbackT *pCallback, void *pUserData) +{ + pVoipGroup->pGroupCallback = pCallback; + pVoipGroup->pGroupCallbackData = pUserData; + + VoipCommonSetEventCallback((VoipCommonRefT *)VoipGetRef(), _VoipGroupManagerEventCallback, _VoipGroupManager_pRef); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupSetDisplayTranscribedTextCallback + + \Description + Sets the callback and userdata for the specified group + + \Input *pVoipGroup - VoipGroup to use + \Input *pCallback - pointer to the callback to use + \Input *pUserData - pointer to the user specified data + + \Version 05/03/2017 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipGroupSetDisplayTranscribedTextCallback(VoipGroupRefT *pVoipGroup, VoipDisplayTranscribedTextCallbackT *pCallback, void *pUserData) +{ + + pVoipGroup->pDisplayTranscribedTextCb = pCallback; + pVoipGroup->pDisplayTranscribedTextUserData = pUserData; + + VoipCommonSetDisplayTranscribedTextCallback((VoipCommonRefT *)VoipGetRef(), _VoipGroupManagerDisplayTranscribedTextCallback, _VoipGroupManager_pRef); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupSetConnSharingEventCallback + + \Description + Sets the connection sharing callback and userdata for the specified group + + \Input *pVoipGroup - voipgroup ref + \Input *pCallback - pointer to the callback to use + \Input *pUserData - pointer to the user specified data + + \Notes + The callback registered with this function must invoke VoipGroupSuspend() + when eCbType=VOIPGROUP_CBTYPE_CONNSUSPEND, and it must call VoipGroupResume() + when eCbType=VOIPGROUP_CBTYPE_CONNRESUME. + + \Version 11/11/2009 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipGroupSetConnSharingEventCallback(VoipGroupRefT *pVoipGroup, ConnSharingCallbackT *pCallback, void *pUserData) +{ + pVoipGroup->pConnSharingCallback = pCallback; + pVoipGroup->pConnSharingCallbackData = pUserData; +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupConnect + + \Description + Set up a connection, using VoipGroup + + \Input *pVoipGroup - The VoipGroup the flags apply to + \Input iConnId - connection index + \Input uAddress - address to connect to + \Input uManglePort - port to connect on + \Input uGamePort - port to connect on + \Input uClientId - client identifier + \Input uSessionId - session identifier (optional) + \Input bIsConnectivityHosted - is the connection through a relay server + \Input uLowLevelConnectivityId - low level remote client id + + \Output + int32_t - the VoipGroup connid the connection was made with + + \Version 11/03/2015 (amakoukji) +*/ +/********************************************************************************F*/ +int32_t VoipGroupConnect(VoipGroupRefT *pVoipGroup, int32_t iConnId, uint32_t uAddress, uint32_t uManglePort, uint32_t uGamePort, uint32_t uClientId, uint32_t uSessionId, uint8_t bIsConnectivityHosted, uint32_t uLowLevelConnectivityId) +{ + int32_t iMappedLowLevelConnId = VOIP_CONNID_NONE; + int32_t iHighLevelConnIdInUse = VOIP_CONNID_NONE; + int32_t iCollidingConnId; + VoipGroupRefT *pCollidingVoipGroup; + uint8_t bMuted = FALSE; + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef(); + + NetPrintf(("voipgroup: [%p] connecting high-level conn %d (sess id 0x%08x)\n", pVoipGroup, iConnId, uSessionId)); + + // is the specified client id already known by the current voipgroup? + // note: typically this occurs when the caller perform successive calls to VoipGroupConnect() while + // trying different port obtained from the demangler. + if ((iHighLevelConnIdInUse = _VoipGroupFindUsedHighLevelConn(pVoipGroup, uClientId)) != VOIP_CONNID_NONE) + { + if (iConnId == VOIP_CONNID_NONE) + { + iConnId = iHighLevelConnIdInUse; + } + + if (iHighLevelConnIdInUse == iConnId) + { + NetPrintf(("voipgroup: [%p] current voipgroup already knows about client id 0x%08x (low level id 0x%08x) with high-level conn id %d\n", + pVoipGroup, uClientId, uLowLevelConnectivityId, iConnId)); + + // if connection is "suspended", early exit faking a success + if (pVoipGroup->aConnections[iConnId].eConnState == VOIPGROUP_CONNSTATE_SUSPENDED) + { + NetPrintf(("voipgroup: [%p] high-level conn %d (sess id 0x%08x) is suspended, assume low-level connectivity is up\n", pVoipGroup, iConnId, pVoipGroup->aConnections[iConnId].uSessionId)); + return(iConnId); + } + } + else + { + NetPrintf(("voipgroup: [%p] critical error - current voipgroup already knows about client id x%08x (low level id 0x%08x) but conn ids do not match (%d != %d)\n", + pVoipGroup, uClientId, uLowLevelConnectivityId, iHighLevelConnIdInUse, iConnId)); + return(-4); + } + } + + // if the user doesn't specify a conn id, use the first unmapped one. + if (iConnId == VOIP_CONNID_NONE) + { + if ((iConnId = _VoipGroupFindFreeHighLevelConn(pVoipGroup)) == VOIP_CONNID_NONE) + { + NetPrintf(("voipgroup: [%p] warning - voipgroup is full\n", pVoipGroup)); + return(-1); + } + } + + // check for collision: verify if another voipgroup deals with the same client + if ( (pCollidingVoipGroup = _VoipGroupFindCollision(pVoipGroup, uClientId, VOIPGROUP_CONNSTATE_ACTIVE, &iCollidingConnId)) != NULL ) + { + // if there is a collision check the mute status of the colliding voipgroup's on the connId so we can reset the status if we suspend + bMuted = VoipGroupIsMutedByConnId(pCollidingVoipGroup, iCollidingConnId); + + /* If we are re-establishing voip from a hosted voip conn to a p2p voip conn, and if we know that the original + hosted voip conn was resulting from a failing p2p voip conn, then we know this new targeted p2p voip conn will fail. + Consequently, to avoid an unnecessary 10-sec p2p connection handshaking delay (which results in novoip between the users during that + time because the original hosted voip connection gets suspended), we just return an error. The assumption being that blaze, upon + detecting the connection failure, will immediately resume connectivity but hosted this time. */ + if ((pCollidingVoipGroup->iCcMode == VOIPGROUP_CCMODE_HOSTEDFALLBACK) && pCollidingVoipGroup->aConnections[iCollidingConnId].bIsConnectivityHosted && !bIsConnectivityHosted) + { + NetPrintf(("voipgroup: [%p] failing VoipGroupConnect() because we anticipate the target p2p conn to fail... we instead prefer waiting for hosted conn to hosted conn transition\n", pVoipGroup)); + return(-2); + } + + // if both voipgroups do not share same bServer / bTunnel attributes + // OR if the previous or current connection is connectivity hosted, + // then connection re-establishment kicks-in + if ( (pCollidingVoipGroup->bServer != pVoipGroup->bServer) || + (pCollidingVoipGroup->bTunnel != pVoipGroup->bTunnel) || + bIsConnectivityHosted || + pCollidingVoipGroup->aConnections[iCollidingConnId].bIsConnectivityHosted) + { + /* + Note: + Voipgroups with bServer attribute set have precedence over voipgroups with bServer attribute not set, + i.e. voip connectivity via voipserver is always preferred over p2p voip connectivity. Therefore, a newly + created voip connection goes straight to "suspended state" if there already exists a voip connection + via the voipserver for the same client. Or, if the newly created voip connection is to be established + through the voipserver, then any existing p2p voip connection to the same client is first moved to + "suspended" state, and only then the voip connection through the voipserver is established. + */ + + // is the colliding voipgroup already using the voipserver? + // note: bServer will never be the true for connectivity hosted connection + if (pCollidingVoipGroup->bServer) + { + // no need to proceed with current connection establishment because the colliding voipgroup + // already has voip connectivity via the voipserver, so it has precedence. just mark current + // connection as "SUSPENDED" such that it can be resumed later if needed. + pVoipGroup->aConnections[iConnId].eConnState = VOIPGROUP_CONNSTATE_SUSPENDED; + pVoipGroup->aConnections[iConnId].iMappedLowLevelConnId = VOIP_CONNID_NONE; + pVoipGroup->aConnections[iConnId].uClientId = uClientId; + pVoipGroup->aConnections[iConnId].uLowLevelConnectivityId = uLowLevelConnectivityId; + pVoipGroup->aConnections[iConnId].uSessionId = uSessionId; + pVoipGroup->aConnections[iConnId].bIsConnectivityHosted = bIsConnectivityHosted; + + NetPrintf(("voipgroup: [%p] early suspension of high-level conn %d (sess id 0x%08x) - there already exists a low-level voip conn through the voipserver for client id 0x%08x (low level id 0x%08x).\n", + pVoipGroup, iConnId, uSessionId, uClientId, uLowLevelConnectivityId)); + return(iConnId); // fake success + } + else + { + // apply crossplay flag + VoipCommonControl(pVoipCommon, 'xply', pVoipGroup->bCrossplay, NULL); + + // let the colliding voipgroup entity know that it has to suspend its connection right away + // because the current voipgroup will be creating a new one with new connection parameters + pCollidingVoipGroup->pConnSharingCallback(pCollidingVoipGroup, VOIPGROUP_CBTYPE_CONNSUSPEND, + iCollidingConnId, pCollidingVoipGroup->pConnSharingCallbackData, FALSE, FALSE); + } + } + else + { + // both voipgroups can share the underlying low-level voip connection + iMappedLowLevelConnId = pCollidingVoipGroup->aConnections[iCollidingConnId].iMappedLowLevelConnId; + + // add session ID to the set of sessions sharing the voip connection + VoipCommonConnectionSharingAddSession(pVoipCommon, iMappedLowLevelConnId, uSessionId); + } + } + + if (iMappedLowLevelConnId < 0) + { + // apply crossplay flag + VoipCommonControl(pVoipCommon, 'xply', pVoipGroup->bCrossplay, NULL); + + // only call VoipCommonConnect() with a valid conn id if any of the following two condidions is met: + // 1- VoipCommonConnect() was already called for that low-level connection (i.e iHighLevelConnIdInUse is valid) + // 2- The high-level conn id is directly available in voip and there is currently no reference to it + // (there is indeed a small transition time during which a connection may go to DISC state and a voip goup still refers to it) + if ( (iHighLevelConnIdInUse != VOIP_CONNID_NONE) || + (VoipStatus(VoipGetRef(), 'avlb', iConnId, NULL, 0) && !_VoipGroupLowLevelConnInUse(iConnId)) ) + { + // conn id is available, use it! + iMappedLowLevelConnId = VoipCommonConnect(pVoipCommon, iConnId, uAddress, uManglePort, uGamePort, uLowLevelConnectivityId, uSessionId); + } + else + { + // else, let the voip module select a low-level voip conn id for us. + iMappedLowLevelConnId = VoipCommonConnect(pVoipCommon, VOIP_CONNID_NONE, uAddress, uManglePort, uGamePort, uLowLevelConnectivityId, uSessionId); + } + } + + if (iMappedLowLevelConnId < 0) + { + NetPrintf(("voipgroup: [%p] VoipGroupConnect() failed obtaining a valid low-level conn id\n", pVoipGroup)); + return(-3); + } + + NetPrintf(("voipgroup: [%p] high-level conn id %d (sess id 0x%08x) mapped to low-level conn id %d --> address %a, clientId=0x%08x (low level id 0x%08x).\n", + pVoipGroup, iConnId, uSessionId, iMappedLowLevelConnId, uAddress, uClientId, uLowLevelConnectivityId)); + + // set the local client id for the mapping + VoipCommonSetLocalClientId(pVoipCommon, iMappedLowLevelConnId, pVoipGroup->aLowLevelConnectivityId[iConnId]); + + // save the mapping. + pVoipGroup->aConnections[iConnId].iMappedLowLevelConnId = iMappedLowLevelConnId; + if (pVoipGroup->bServer == TRUE || bIsConnectivityHosted == TRUE) + { + // let the voip module know about the local-to-voipserver conn id mapping + VoipCommonMapVoipServerId(pVoipCommon, pVoipGroup->aConnections[iConnId].iMappedLowLevelConnId, iConnId); + } + + // save other connection-specific data + pVoipGroup->aConnections[iConnId].uClientId = uClientId; + pVoipGroup->aConnections[iConnId].uLowLevelConnectivityId = uLowLevelConnectivityId; + pVoipGroup->aConnections[iConnId].uSessionId = uSessionId; + pVoipGroup->aConnections[iConnId].bIsConnectivityHosted = bIsConnectivityHosted; + + // if this is the first attempt to connect, let's just add ourself as an additional reference for this connection + if ((iHighLevelConnIdInUse == VOIP_CONNID_NONE) && (_VoipGroupAddRefToLowLevelConn(iMappedLowLevelConnId, pVoipGroup) < 0)) + { + NetPrintf(("voipgroup: [%p] VoipGroupConnect() failed to add a new reference to the low-level conn %d\n", pVoipGroup, iMappedLowLevelConnId)); + VoipGroupDisconnect(pVoipGroup, iConnId); + return(-4); + } + + // don't initialize the user masks if there was a collision since they'll already be set + if (!bMuted) + { + _VoipGroupManager_pRef->uUserRecvMask |= (1 << iMappedLowLevelConnId); + VoipCommonSpeaker(pVoipCommon, pVoipGroup->bSilent ? 0x00000000 : _VoipGroupManager_pRef->uUserRecvMask); + _VoipGroupManager_pRef->uUserSendMask |= (1 << iMappedLowLevelConnId); + VoipCommonMicrophone(pVoipCommon, pVoipGroup->bSilent ? 0x00000000 : _VoipGroupManager_pRef->uUserSendMask); + } + + // return the high-level connection id used + return(iConnId); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupResume + + \Description + resume a connection + + \Input *pVoipGroup - voipgroup ref + \Input iConnId - connection index + \Input uAddress - address to connect to + \Input uManglePort - port to connect on + \Input uGamePort - port to connect on + \Input uClientId - client identifier + \Input uSessionId - session identifier (optional) + \Input bIsConnectivityHosted - TRUE is connection is connectivity hosted + + \Notes + Muting logic is intentionally excluded from the resume. It is currently + handled by ConnApi. + + \Version 01/26/2016 (amakoukji) +*/ +/********************************************************************************F*/ +void VoipGroupResume(VoipGroupRefT *pVoipGroup, int32_t iConnId, uint32_t uAddress, uint32_t uManglePort, uint32_t uGamePort, uint32_t uClientId, uint32_t uSessionId, uint8_t bIsConnectivityHosted) +{ + int32_t iMappedLowLevelConnId = -1; + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef(); + + NetPrintf(("voipgroup: [%p] resuming high-level conn %d (sess id 0x%08x)\n", pVoipGroup, iConnId, uSessionId)); + + #if DIRTYCODE_LOGGING + if (pVoipGroup->aConnections[iConnId].uClientId != uClientId) + { + NetPrintf(("voipgroup: [%p] warning connection being resumed has inconsistent client id: 0x%08x should be 0x%08x\n", + pVoipGroup, uClientId, pVoipGroup->aConnections[iConnId].uClientId)); + } + if (pVoipGroup->aConnections[iConnId].uSessionId != uSessionId) + { + NetPrintf(("voipgroup: [%p] warning connection being resumed has inconsistent session id: 0x%08x should be 0x%08x\n", + pVoipGroup, uSessionId, pVoipGroup->aConnections[iConnId].uSessionId)); + } + #endif + + // force connection back to active state + pVoipGroup->aConnections[iConnId].eConnState = VOIPGROUP_CONNSTATE_ACTIVE; + + // check if the high-level conn id is directly available in voip and if there is currently no reference to it + // (there is indeed a small transition time during which a connection may go to DISC state and a voip goup still refers to it) + if (VoipStatus(VoipGetRef(), 'avlb', iConnId, NULL, 0) && !_VoipGroupLowLevelConnInUse(iConnId)) + { + // conn id is available, use it! + iMappedLowLevelConnId = VoipCommonConnect(pVoipCommon, iConnId, uAddress, uManglePort, uGamePort, pVoipGroup->aConnections[iConnId].uLowLevelConnectivityId, uSessionId); + } + else + { + // else, let Voip allocate one. + iMappedLowLevelConnId = VoipCommonConnect(pVoipCommon, VOIP_CONNID_NONE, uAddress, uManglePort, uGamePort, pVoipGroup->aConnections[iConnId].uLowLevelConnectivityId, uSessionId); + } + + if (iMappedLowLevelConnId < 0) + { + NetPrintf(("voipgroup: [%p] VoipGroupResume() failed obtaining a valid low-level conn id\n", pVoipGroup)); + return; + } + + NetPrintf(("voipgroup: [%p] high-level conn id %d (sess id 0x%08x) mapped to low-level conn id %d --> address %a, clientId=0x%08x (low level id 0x%08x).\n", + pVoipGroup, iConnId, uSessionId, iMappedLowLevelConnId, uAddress, uClientId, pVoipGroup->aConnections[iConnId].uLowLevelConnectivityId)); + + // set the local client id for the mapping + VoipCommonSetLocalClientId(pVoipCommon, iMappedLowLevelConnId, pVoipGroup->aLowLevelConnectivityId[iConnId]); + + // save the mapping. + pVoipGroup->aConnections[iConnId].iMappedLowLevelConnId = iMappedLowLevelConnId; + if (pVoipGroup->bServer == TRUE || bIsConnectivityHosted == TRUE) + { + // let the voip module know about the local-to-voipserver conn id mapping + VoipCommonMapVoipServerId(pVoipCommon, pVoipGroup->aConnections[iConnId].iMappedLowLevelConnId, iConnId); + } + + // let's just add ourself as an additional reference for this connection + if ((_VoipGroupAddRefToLowLevelConn(iMappedLowLevelConnId, pVoipGroup)) < 0) + { + NetPrintf(("voipgroup: [%p] VoipGroupResume() failed adding a new reference\n", pVoipGroup)); + return; + } + + return; +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupDisconnect + + \Description + Teardown a connection, using VoipGroups. + + \Input *pVoipGroup - The VoipGroup the flags apply to + \Input iConnId - connection index + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +void VoipGroupDisconnect(VoipGroupRefT *pVoipGroup, int32_t iConnId) +{ + int32_t iCollidingConnId; + int32_t iMappedLowLevelConnId; + VoipGroupRefT *pCollidingVoipGroup; + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef(); + + NetPrintf(("voipgroup: [%p] disconnecting high-level conn %d (sess id 0x%08x)\n", pVoipGroup, iConnId, pVoipGroup->aConnections[iConnId].uSessionId)); + + // if high-level connection is already suspended, skip disconnection and just clean up corresponding data in the group + if (pVoipGroup->aConnections[iConnId].eConnState != VOIPGROUP_CONNSTATE_SUSPENDED) + { + iMappedLowLevelConnId = _VoipGroupMatch(pVoipGroup, iConnId); + + if (iMappedLowLevelConnId != VOIP_CONNID_NONE) + { + // remove voipgroup from low-level connection's reference set + _VoipGroupRemoveRefToLowLevelConn(iMappedLowLevelConnId, pVoipGroup); + + // only proceed if the ref count for this low-level conn is 0 + if (!_VoipGroupLowLevelConnInUse(iMappedLowLevelConnId)) + { + uint8_t bMuted = VoipGroupIsMutedByConnId(pVoipGroup, iConnId); + VoipCommonDisconnect(pVoipCommon, iMappedLowLevelConnId, TRUE); + + _VoipGroupManager_pRef->uUserRecvMask &= ~(1 << iMappedLowLevelConnId); + VoipCommonSpeaker(pVoipCommon, pVoipGroup->bSilent ? 0x00000000 : _VoipGroupManager_pRef->uUserRecvMask); + _VoipGroupManager_pRef->uUserSendMask &= ~(1 << iMappedLowLevelConnId); + VoipCommonMicrophone(pVoipCommon, pVoipGroup->bSilent ? 0x00000000 : _VoipGroupManager_pRef->uUserSendMask); + + // scan other voipgroups and resume any suspended connection to the same client id + if ( (pCollidingVoipGroup = _VoipGroupFindCollision(pVoipGroup, pVoipGroup->aConnections[iConnId].uClientId, VOIPGROUP_CONNSTATE_SUSPENDED, &iCollidingConnId)) != NULL ) + { + // apply colliding voip group crossplay flag + VoipCommonControl(pVoipCommon, 'xply', pCollidingVoipGroup->bCrossplay, NULL); + + // let colliding voipgroup entity that it can resume its connection right away as the current + // voipgroup has just disconnected a connection with different connection parameters + pCollidingVoipGroup->pConnSharingCallback(pCollidingVoipGroup, VOIPGROUP_CBTYPE_CONNRESUME, + iCollidingConnId, pCollidingVoipGroup->pConnSharingCallbackData, bMuted, bMuted); + } + } + else + { + // remove session ID from the set of sessions sharing the low-level voip connection + // note: don't call this function earlier in time (before VoipCommonDisconnect) because the session id is needed for building the disconnect message + VoipCommonConnectionSharingDelSession(pVoipCommon, iMappedLowLevelConnId, pVoipGroup->aConnections[iConnId].uSessionId); + + NetPrintf(("voipgroup: [%p] skipped call to VoipCommonDisconnect() because low-level conn ref count has not reached 0\n", pVoipGroup)); + } + + pVoipGroup->aConnections[iConnId].iMappedLowLevelConnId = VOIP_CONNID_NONE; + } + else + { + NetPrintf(("voipgroup: [%p] warning - VoipGroupDisconnect() dealing with a corrupted connection entry\n", pVoipGroup)); + } + } + else + { + NetPrintf(("voipgroup: [%p] skipped call to VoipCommonDisconnect() because high-level conn %d is suspended\n", pVoipGroup, iConnId)); + } + + // free the high-level connection entry + pVoipGroup->aConnections[iConnId].eConnState = VOIPGROUP_CONNSTATE_ACTIVE; + pVoipGroup->aConnections[iConnId].uClientId = 0; + pVoipGroup->aConnections[iConnId].uLowLevelConnectivityId = 0; + pVoipGroup->aConnections[iConnId].uSessionId = 0; + pVoipGroup->aConnections[iConnId].bIsConnectivityHosted = FALSE; + + return; +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupSuspend + + \Description + suspend a connection + + \Input *pVoipGroup - voipgroup ref + \Input iConnId - connection index + + \Version 11/11/2009 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipGroupSuspend(VoipGroupRefT *pVoipGroup, int32_t iConnId) +{ + int32_t iMappedLowLevelConnId; + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef(); + + NetPrintf(("voipgroup: [%p] suspending high-level conn %d (sess id 0x%08x)\n", pVoipGroup, iConnId, pVoipGroup->aConnections[iConnId].uSessionId)); + + // force connection state back to suspended + iMappedLowLevelConnId = _VoipGroupMatch(pVoipGroup, iConnId); + + if (iMappedLowLevelConnId != VOIP_CONNID_NONE) + { + pVoipGroup->aConnections[iConnId].eConnState = VOIPGROUP_CONNSTATE_SUSPENDED; + + // remove voipgroup from low-level connection's reference set + _VoipGroupRemoveRefToLowLevelConn(iMappedLowLevelConnId, pVoipGroup); + + if (_VoipGroupLowLevelConnInUse(iMappedLowLevelConnId)) + { + /* + note: + This scenario can occur if the low-level connection has already been disconnected by the other peer. + In such a case, the voipgroup is not aware of the state of the low-level connection, and it still + maintains the high-level connection entry as the corresponding connapi client is maintained in DISC + state by the upper ConnApi layer. Now when asked to go to suspended state because of a conflict with another + newly created voipgroup, it is very possible that the low-level connection has already been re-used and has + other voipgroups referring to it. + */ + NetPrintf(("voipgroup: [%p] VoipGroupSuspend() skips call to VoipCommonDisconnect() because low-level conn ref count has not reached 0\n", pVoipGroup)); + } + else + { + /* + note: + bSendDiscMsg is FALSE here because we don't want the voip disc pkt to result in the + other end of the connection having its connapi voip connection status moved to + CONNAPI_STATUS_DISC state. + */ + VoipCommonDisconnect(pVoipCommon, iMappedLowLevelConnId, FALSE); + + _VoipGroupManager_pRef->uUserRecvMask &= ~(1 << iMappedLowLevelConnId); + VoipCommonSpeaker(pVoipCommon, pVoipGroup->bSilent ? 0x00000000 : _VoipGroupManager_pRef->uUserRecvMask); + _VoipGroupManager_pRef->uUserSendMask &= ~(1 << iMappedLowLevelConnId); + VoipCommonMicrophone(pVoipCommon, pVoipGroup->bSilent ? 0x00000000 : _VoipGroupManager_pRef->uUserSendMask); + + pVoipGroup->aConnections[iConnId].iMappedLowLevelConnId = VOIP_CONNID_NONE; + } + } + else + { + NetPrintf(("voipgroup: [%p] low level conn id not found\n", pVoipGroup)); + } + + return; +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupMuteByClientId3 + + \Description + mutes a connection by the client id and mute flag + + \Input uClientId - client id + \Input bMute - TRUE to mute FALSE to umute + \Input uMuteFlag - mute flags (VOIPGROUP_INBOUND_MUTE, VOIPGROUP_OUTBOUND_MUTE, VOIPGROUP_TWOWAY_MUTE) + + \Output + int32_t - negative=error, zero=success + + \Version 10/30/2017 (amakoukji) +*/ +/********************************************************************************F*/ +int32_t VoipGroupMuteByClientId3(uint32_t uClientId, uint8_t bMute, uint32_t uMuteFlag) +{ + VoipGroupManagerT* pManager = _VoipGroupManager_pRef; + VoipGroupRefT *pVoipGroup = NULL; + int32_t iConnId = MAX_CLIENTS_PER_GROUP; + int32_t iGroupIndex; + + if (uClientId == (uint32_t)VOIP_CONNID_ALL) + { + iConnId = (int32_t)VOIP_CONNID_ALL; + } + else + { + for (iGroupIndex = 0; iGroupIndex < pManager->iMaxGroups; ++iGroupIndex) + { + pVoipGroup = &pManager->aGroups[iGroupIndex]; + + // look for the client in this voip group + if (pVoipGroup->bUsed) + { + for (iConnId = 0; iConnId < MAX_CLIENTS_PER_GROUP; iConnId++) + { + if (pVoipGroup->aConnections[iConnId].uClientId == uClientId) + { + break; + } + } + + // found a match in the inner for-loop, stop looking + if (iConnId != MAX_CLIENTS_PER_GROUP) + { + break; + } + } + } + + // if the client is not found in any voip group return an error + if ((iGroupIndex == pManager->iMaxGroups) && (iConnId == MAX_CLIENTS_PER_GROUP)) + { + return(-1); + } + } + + return(VoipGroupMuteByConnId2(pVoipGroup, iConnId, bMute, uMuteFlag)); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupMuteByClientId2 + + \Description + mutes a connection by the client id and mute flag + + \Input *pVoipGroup - voipgroup ref + \Input uClientId - client id + \Input bMute - TRUE to mute FALSE to umute + \Input uMuteFlag - mute flags (VOIPGROUP_INBOUND_MUTE, VOIPGROUP_OUTBOUND_MUTE, VOIPGROUP_TWOWAY_MUTE) + + \Output + int32_t - negative=error, zero=success + + \Version 11/02/2015 (tcho) +*/ +/********************************************************************************F*/ +int32_t VoipGroupMuteByClientId2(VoipGroupRefT *pVoipGroup, uint32_t uClientId, uint8_t bMute, uint32_t uMuteFlag) +{ + int32_t iConnId; + + if (uClientId == (uint32_t)VOIP_CONNID_ALL) + { + iConnId = (int32_t)VOIP_CONNID_ALL; + } + else + { + for (iConnId = 0; iConnId < MAX_CLIENTS_PER_GROUP; iConnId++) + { + if (pVoipGroup->aConnections[iConnId].uClientId == uClientId) + { + break; + } + } + + if (iConnId == MAX_CLIENTS_PER_GROUP) + { + return(-1); + } + } + + return(VoipGroupMuteByConnId2(pVoipGroup, iConnId, bMute, uMuteFlag)); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupMuteByClientId + + \Description + 2-way mutes a connection by the client id + + \Input *pVoipGroup - voipgroup ref + \Input iClientId - client id + \Input uMute - TRUE to mute FALSE to umute + + \Output + int32_t - negative=error, zero=success + + \Version 11/02/2015 (tcho) +*/ +/********************************************************************************F*/ +int32_t VoipGroupMuteByClientId(VoipGroupRefT *pVoipGroup, uint32_t iClientId, uint8_t uMute) +{ + return(VoipGroupMuteByClientId2(pVoipGroup, iClientId, uMute, VOIPGROUP_TWOWAY_MUTE)); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupMuteByConnId2 + + \Description + mutes a connection by the high level connection id and mute flag + + \Input *pVoipGroup - voipgroup ref + \Input iConnId - connection index + \Input bMute - TRUE to mute FALSE to umute + \Input uMuteFlag - mute flags (VOIPGROUP_INBOUND_MUTE, VOIPGROUP_OUTBOUND_MUTE, VOIPGROUP_TWOWAY_MUTE) + + \Output + int32_t - negative=error, zero=success + + \Version 11/02/2015 (tcho) +*/ +/********************************************************************************F*/ +int32_t VoipGroupMuteByConnId2(VoipGroupRefT *pVoipGroup, uint32_t iConnId, uint8_t bMute, uint32_t uMuteFlag) +{ + VoipGroupManagerT* pManager = _VoipGroupManager_pRef; + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef(); + int32_t iMappedLowLevelConnId; + int32_t iIndex; + + iMappedLowLevelConnId = ((iConnId == (uint32_t)VOIP_CONNID_ALL) ? (int32_t)VOIP_CONNID_ALL : _VoipGroupMatch(pVoipGroup, iConnId)); + + if (iMappedLowLevelConnId == (int32_t)VOIP_CONNID_NONE) + { + return(-1); + } + + if (bMute) + { + if (iMappedLowLevelConnId == (int32_t)VOIP_CONNID_ALL) + { + // mute all + if (uMuteFlag & VOIPGROUP_INBOUND_MUTE) + { + pManager->uUserRecvMask = 0x00000000; + } + + if (uMuteFlag & VOIPGROUP_OUTBOUND_MUTE) + { + pManager->uUserSendMask = 0x00000000; + } + } + else + { + if (uMuteFlag & VOIPGROUP_INBOUND_MUTE) + { + pManager->uUserRecvMask &= ~(1 << iMappedLowLevelConnId); + } + + if (uMuteFlag & VOIPGROUP_OUTBOUND_MUTE) + { + pManager->uUserSendMask &= ~(1 << iMappedLowLevelConnId); + } + } + } + else + { + if (iMappedLowLevelConnId == VOIP_CONNID_ALL) + { + // unmute all connIds + for (iIndex = 0; iIndex < MAX_CLIENTS_PER_GROUP; ++iIndex) + { + if (uMuteFlag & VOIPGROUP_INBOUND_MUTE) + { + pManager->uUserRecvMask |= (1 << pVoipGroup->aConnections[iIndex].iMappedLowLevelConnId); + } + + if (uMuteFlag & VOIPGROUP_OUTBOUND_MUTE) + { + pManager->uUserSendMask |= (1 << pVoipGroup->aConnections[iIndex].iMappedLowLevelConnId); + } + } + } + else + { + if (uMuteFlag & VOIPGROUP_INBOUND_MUTE) + { + pManager->uUserRecvMask |= (1 << iMappedLowLevelConnId); + } + + if (uMuteFlag & VOIPGROUP_OUTBOUND_MUTE) + { + pManager->uUserSendMask |= (1 << iMappedLowLevelConnId); + } + } + } + VoipCommonSpeaker(pVoipCommon, pVoipGroup->bSilent ? 0x00000000 : pManager->uUserRecvMask); + VoipCommonMicrophone(pVoipCommon, pVoipGroup->bSilent ? 0x00000000 : pManager->uUserSendMask); + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupMuteByConnId + + \Description + 2-way mutes a connection by the high level connection id + + \Input *pVoipGroup - voipgroup ref + \Input iConnId - connection index + \Input uMute - TRUE to mute FALSE to umute + + \Output + int32_t - negative=error, zero=success + + \Version 11/02/2015 (tcho) +*/ +/********************************************************************************F*/ +int32_t VoipGroupMuteByConnId(VoipGroupRefT *pVoipGroup, uint32_t iConnId, uint8_t uMute) +{ + return (VoipGroupMuteByConnId2(pVoipGroup, iConnId, uMute, VOIPGROUP_TWOWAY_MUTE)); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupIsMutedByClientId2 + + \Description + reutrns the mute flag for the connection specified by the client id + + \Input *pVoipGroup - voipgroup ref + \Input iClientId - connection index + + \Output + uint8_t - mute flag + + \Version 11/02/2015 (tcho) +*/ +/********************************************************************************F*/ +uint32_t VoipGroupIsMutedByClientId2(VoipGroupRefT *pVoipGroup, uint32_t iClientId) +{ + int32_t iConnId; + + for(iConnId = 0; iConnId < MAX_CLIENTS_PER_GROUP; iConnId++) + { + if (pVoipGroup->aConnections[iConnId].uClientId == iClientId) + { + break; + } + } + + if (iConnId == MAX_CLIENTS_PER_GROUP) + { + return(FALSE); + } + + return(VoipGroupIsMutedByConnId2(pVoipGroup, iConnId)); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupIsMutedByClientId + + \Description + Is the connection specified by the client id muted or not (1-way or 2-way) + + \Input *pVoipGroup - voipgroup ref + \Input uClientId - connection index + + \Output + int32_t - TRUE only if muted 2-ways + + \Version 11/02/2015 (tcho) +*/ +/********************************************************************************F*/ +uint8_t VoipGroupIsMutedByClientId(VoipGroupRefT *pVoipGroup, uint32_t uClientId) +{ + int32_t iConnId; + + for(iConnId = 0; iConnId < MAX_CLIENTS_PER_GROUP; iConnId++) + { + if (pVoipGroup->aConnections[iConnId].uClientId == uClientId) + { + break; + } + } + + if (iConnId == MAX_CLIENTS_PER_GROUP) + { + return(FALSE); + } + + return(VoipGroupIsMutedByConnId(pVoipGroup, iConnId)); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupIsMutedByConnId2 + + \Description + reutrns the mute flag for the connection specified by the high level connection id + + \Input *pVoipGroup - voipgroup ref + \Input iConnId - connection index + + \Output + uint32_t - mute flag + + \Version 11/02/2015 (tcho) +*/ +/********************************************************************************F*/ +uint32_t VoipGroupIsMutedByConnId2(VoipGroupRefT *pVoipGroup, uint32_t iConnId) +{ + uint8_t uMuteFlag = 0; + int32_t iMappedLowLevelConnId; + VoipGroupManagerT* pManager = _VoipGroupManager_pRef; + + iMappedLowLevelConnId = _VoipGroupMatch(pVoipGroup, iConnId); + + if (iMappedLowLevelConnId == VOIP_CONNID_NONE) + { + return(0); + } + + if ((pManager->uUserRecvMask & (1 << iMappedLowLevelConnId)) == 0) + { + uMuteFlag |= VOIPGROUP_INBOUND_MUTE; + } + + if ((pManager->uUserSendMask & (1 << iMappedLowLevelConnId)) == 0) + { + uMuteFlag |= VOIPGROUP_OUTBOUND_MUTE; + } + + return(uMuteFlag); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupIsMutedByConnId + + \Description + Is the connection specified by the conn id muted or not (1-way or 2-way) + + \Input *pVoipGroup - voipgroup ref + \Input iConnId - connection index + + \Output + int32_t - TRUE only if it is 2-way muted + + \Version 11/02/2015 (tcho) +*/ +/********************************************************************************F*/ +uint8_t VoipGroupIsMutedByConnId(VoipGroupRefT *pVoipGroup, uint32_t iConnId) +{ + uint32_t uMuteFlag = VoipGroupIsMutedByConnId2(pVoipGroup, iConnId); + + if (uMuteFlag == VOIPGROUP_TWOWAY_MUTE) + { + return(TRUE); + } + else + { + return(FALSE); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupMuteAll + + \Description + Mute all users or restore previous mute settings from before a + previous call to VoipGroupMuteAll. This stacks with other VoipGroupMute + functionality. It does not erase the previous muting state, and therefore + is best used for short term muting of all users. + + \Input *pVoipGroup - voipgroup ref + \Input bActive - TRUE to mute all users, FALSE to restore mute settings + + \Version 10/30/2017 (amakoukji) +*/ +/********************************************************************************F*/ +void VoipGroupMuteAll(VoipGroupRefT *pVoipGroup, uint8_t bActive) +{ + VoipGroupManagerT* pManager = _VoipGroupManager_pRef; + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef(); + + if (pVoipGroup->bSilent != bActive) + { + pVoipGroup->bSilent = bActive; + VoipCommonSpeaker(pVoipCommon, pVoipGroup->bSilent ? 0x00000000 : pManager->uUserRecvMask); + VoipCommonMicrophone(pVoipCommon, pVoipGroup->bSilent ? 0x00000000 : pManager->uUserSendMask); + } +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupConnStatus + + \Description + Passes through to VoipConnRemote, afer adjusting connection ID + + \Input *pVoipGroup - The VoipGroup + \Input iConnId - The connection ID + + \Output + int32_t - VOIP_REMOTE_* flags, or VOIP_FLAG_INVALID if iConnId is invalid + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +int32_t VoipGroupConnStatus(VoipGroupRefT *pVoipGroup, int32_t iConnId) +{ + int32_t iMappedConnId = _VoipGroupMatch(pVoipGroup, iConnId); + + if (iMappedConnId == VOIP_CONNID_NONE) + { + return(VOIP_FLAG_INVALID); + } + + return(VoipCommonConnStatus((VoipCommonRefT *)VoipGetRef(), iMappedConnId)); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupRemoteUserStatus + + \Description + Passes through to VoipUserRemote, afer adjusting connection ID + + \Input *pVoipGroup - The VoipGroup + \Input iConnId - The connection ID + \Input iRemoteUserIndex - User index + + \Output + int32_t - VOIP_REMOTE_* flags, or VOIP_FLAG_INVALID if iConnId is invalid + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +int32_t VoipGroupRemoteUserStatus(VoipGroupRefT *pVoipGroup, int32_t iConnId, int32_t iRemoteUserIndex) +{ + int32_t iMappedConnId = _VoipGroupMatch(pVoipGroup, iConnId); + + if (iMappedConnId == VOIP_CONNID_NONE) + { + return(VOIP_FLAG_INVALID); + } + + return(VoipCommonRemoteUserStatus((VoipCommonRefT *)VoipGetRef(), iMappedConnId, iRemoteUserIndex)); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupLocalUserStatus + + \Description + return information about local hardware state + + \Input *pVoipGroup - The VoipGroup + \Input iLocalUserIndex - User index + + \Output + int32_t - VOIP_LOCAL_* flags + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +int32_t VoipGroupLocalUserStatus(VoipGroupRefT *pVoipGroup, int32_t iLocalUserIndex) +{ + return(VoipLocalUserStatus(VoipGetRef(), iLocalUserIndex)); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupStatus + + \Description + Passes through to VoipStatus, for historical reasons + + \Input *pVoipGroup - The VoipGroup + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuf - selector-specific + \Input iBufSize - size of the pBuf input + + \Output + int32_t - selector-specific data + + \Notes + iSelect can be one of the following: + + \verbatim + rchn: returns the active channel of the remote peer + rcvu: returns the voipuser of the remote peer + slnt: returns if the group is silent + \endverbatim + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +int32_t VoipGroupStatus(VoipGroupRefT *pVoipGroup, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + int32_t iRetCode = -1; // default to error + + if (iSelect == 'rchn') + { + // convert high-level id to low-level id + int32_t iMappedConnId = _VoipGroupMatch(pVoipGroup, iValue); + + if (iMappedConnId != VOIP_CONNID_NONE) + { + iRetCode = VoipStatus(VoipGetRef(), iSelect, iMappedConnId, pBuf, iBufSize); + } + } + else if (iSelect == 'rcvu') + { + int32_t iMappedConnId; + int32_t iHighLevelConnId = iValue & 0xFFFF; + int32_t iRemoteUserIndex = iValue >> 16; + + // convert high-level id to low-level id + iMappedConnId = _VoipGroupMatch(pVoipGroup, iHighLevelConnId); + + if (iMappedConnId != VOIP_CONNID_NONE) + { + // rebuild input parameter with converted conn index + iValue = iMappedConnId; + iValue |= (iRemoteUserIndex << 16); + + iRetCode = VoipStatus(VoipGetRef(), iSelect, iValue, pBuf, iBufSize); + } + } + else if (iSelect == 'slnt') + { + iRetCode = pVoipGroup->bSilent; + } + else + { + iRetCode = VoipStatus(VoipGetRef(), iSelect, iValue, pBuf, iBufSize); + } + + return(iRetCode); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupControl + + \Description + Passes through to VoipControl, afer adjusting connection ID and flags. + + \Input *pVoipGroup - The VoipGroup + \Input iControl - status selector + \Input iValue - selector-specific + \Input iValue2 - selector-specific + \Input *pValue - selector-specific + + \Output + int32_t - selector-specific data + + \Notes + iControl can be one of the following: + + \verbatim + ccmd: set the CC mode (VOIPGROUP_CCMODE_*) + lusr: register local voip user + serv: flag voipgroup as using voipserver + tunl: flag voipgroup as using tunnel + vcid: set the local id (pValue) for the voip connection with client (iValue) + \endverbatim + + \Version 01/31/2007 (jrainy) +*/ +/********************************************************************************F*/ +int32_t VoipGroupControl(VoipGroupRefT *pVoipGroup, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + VoipGroupManagerT *pManager = _VoipGroupManager_pRef; + + if (iControl == 'ccmd') + { + #if DIRTYCODE_LOGGING + static const char *_VoipGroupCcModeNames[] = + { + "VOIPGROUP_CCMODE_PEERONLY", + "VOIPGROUP_CCMODE_HOSTEDONLY", + "VOIPGROUP_CCMODE_HOSTEDFALLBACK" + }; + #endif + + NetPrintf(("voipgroup: [%p] CC mode = %d (%s)\n", pVoipGroup, iValue, _VoipGroupCcModeNames[iValue])); + pVoipGroup->iCcMode = iValue; + return(0); + } + if (iControl == 'lusr') + { + if (VoipGetRef() == NULL) + { + return(-1); + } + + // only proceed with performing the operation for the first group created or + // for the last group being destroyed as groups cannot have different voip + // local users. + if (pManager->iNumGroups != 1) + { + NetPrintf(("voipgroup: [%p] skipping %s of voip local user and %d, because multiple voipgroups exist\n", + pVoipGroup, (iValue2 ? "registration" : "unregistration"), iValue)); + return(-1); + } + + VoipSetLocalUser(VoipGetRef(), iValue, iValue2); + return(0); + } + if (iControl == 'serv') + { + pVoipGroup->bServer = iValue; + return(0); + } + if (iControl == 'tunl') + { + pVoipGroup->bTunnel = iValue; + return(0); + } + if (iControl == 'vcid') + { + NetPrintf(("voipgroup: [%p] set local client id 0x%08x for conn id %d\n", pVoipGroup, *(int32_t*)pValue, iValue)); + pVoipGroup->aLowLevelConnectivityId[iValue] = *(uint32_t*)pValue; + return(0); + } + if (iControl == 'xply') + { + pVoipGroup->bCrossplay = iValue ? TRUE : FALSE; + return(0); + } + if (VoipGetRef() != NULL) + { + return(VoipControl(VoipGetRef(), iControl, iValue, pValue)); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function VoipGroupActivateLocalUser + + \Description + Refcounted access to VoipCommonActivateLocalUser() + + \Input *pVoipGroup - The VoipGroup (if voipgroup is null and bActivate is false we will deactivate the user from all the voipgroup) + \Input iLocalUserIndex - local user index + \Input bActivate - TRUE to activate, FALSE to deactivate + + \Version 05/12/2014 (mclouatre) +*/ +/********************************************************************************F*/ +void VoipGroupActivateLocalUser(VoipGroupRefT *pVoipGroup, int32_t iLocalUserIndex, uint8_t bActivate) +{ + VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef(); + + if ((iLocalUserIndex >= 0) && (iLocalUserIndex < VOIP_MAXLOCALUSERS)) + { + if (bActivate && (pVoipGroup != NULL)) + { + // sanity check + if (_VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex] < 0) + { + NetPrintf(("voipgroup: [%p] warning, VoipGroupActivateLocalUser invalid ref count for user %d, %d\n", pVoipGroup, iLocalUserIndex, _VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex])); + _VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex] = 0; + } + + // increment ref count + ++(_VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex]); + + pVoipGroup->aParticipatingUser[iLocalUserIndex] = TRUE; + + NetPrintf(("voipgroup: [%p] VoipGroupActivateLocalUser incrementing ref count for user %d to %d\n", pVoipGroup, iLocalUserIndex, _VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex])); + + // activate underlying voip ref if required + if (_VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex] <= 1) + { + VoipCommonActivateLocalUser(pVoipCommon, iLocalUserIndex, bActivate); + } + } + else if (_VoipGroupManager_pRef != NULL) + { + // sanity check + if (_VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex] <= 0) + { + NetPrintf(("voipgroup: [%p] warning, deactivating local user %d with ref count %d\n", pVoipGroup, iLocalUserIndex, _VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex])); + _VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex] = 0; + } + else + { + if (pVoipGroup == NULL) + { + int32_t iGroupIndex = 0; + VoipGroupManagerT *pVoipGroupManager = _VoipGroupManager_pRef; + + for (iGroupIndex = 0; iGroupIndex < pVoipGroupManager->iMaxGroups; ++iGroupIndex) + { + if (pVoipGroupManager->aGroups[iGroupIndex].aParticipatingUser[iLocalUserIndex] == TRUE) + { + // decrement ref count + --(pVoipGroupManager->aActiveRefCount[iLocalUserIndex]); + pVoipGroupManager->aGroups[iGroupIndex].aParticipatingUser[iLocalUserIndex] = FALSE; + } + } + + NetPrintf(("voipgroup: VoipGroupActivateLocalUser deactivating user %d from all voipgroups\n", iLocalUserIndex)); + } + else + { + if (pVoipGroup->aParticipatingUser[iLocalUserIndex] == TRUE) + { + // decrement ref count + --(_VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex]); + pVoipGroup->aParticipatingUser[iLocalUserIndex] = FALSE; + NetPrintf(("voipgroup: [%p] VoipGroupActivateLocalUser decrementing ref count for user %d to %d\n", pVoipGroup, iLocalUserIndex, _VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex])); + } + } + + // deactivate underlying voip ref if required + if (_VoipGroupManager_pRef->aActiveRefCount[iLocalUserIndex] <= 0) + { + VoipCommonActivateLocalUser(pVoipCommon, iLocalUserIndex, bActivate); + } + } + } + } +} diff --git a/src/thirdparty/dirtysdk/source/voip/voipheadset.h b/src/thirdparty/dirtysdk/source/voip/voipheadset.h new file mode 100644 index 00000000..d07996a3 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipheadset.h @@ -0,0 +1,121 @@ +/*H********************************************************************************/ +/*! + \File voipheadset.h + + \Description + VoIP headset manager. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 03/31/2004 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _voipheadset_h +#define _voipheadset_h + +/*** Include files ****************************************************************/ + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! voip headset update status +typedef enum VoipHeadsetStatusUpdateE +{ + VOIP_HEADSET_STATUS_INPUT = 1, + VOIP_HEADSET_STATUS_OUTPUT, + VOIP_HEADSET_STATUS_INOUT +} VoipHeadsetStatusUpdateE; + +//! opaque headset module ref +typedef struct VoipHeadsetRefT VoipHeadsetRefT; + +//! mic data ready callback function prototype +typedef void (VoipHeadsetMicDataCbT)(const void *pVoiceData, int32_t iDataSize, const void *pMetaData, int32_t iMetaDataSize, uint32_t uLocalUserIndex, uint8_t uSendSeqn, void *pUserData); + +//! locally generated transcribed text ready callback function prototype +typedef void (VoipHeadsetTextDataCbT)(const char *pTranscribedTextUtf8, uint32_t uLocalUserIndex, void *pUserData); + +#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) +//! remote-generated transcribed text received callback function prototype +typedef void (VoipHeadsetTranscribedTextReceivedCbT)(VoipUserT *pUser, const char *pText, void *pUserData); +#endif + +//! opaque data ready callback function prototype +typedef void (VoipHeadsetOpaqueDataCbT)(const uint8_t *pOpaqueData, int32_t iOpaqueDataSize, uint32_t uSendMask, uint8_t bReliable, uint8_t uSendSeqn, void *pUserData); + +#if defined(DIRTYCODE_PS4) +//! remote-generated transcribed text received callback function prototype +typedef void (VoipHeadsetTranscribedTextReceivedCbT)(const VoipUserT *pUser, const char *pText, void *pUserData); +#endif + +//! headset status change callback +typedef void (VoipHeadsetStatusCbT)(int32_t iLocalUserIndex, uint32_t bStatus, VoipHeadsetStatusUpdateE eUpdate, void *pUserData); + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create headset module +VoipHeadsetRefT *VoipHeadsetCreate(int32_t iMaxChannels, VoipHeadsetMicDataCbT *pMicDataCb, VoipHeadsetTextDataCbT *pTextDataCb, VoipHeadsetOpaqueDataCbT *pOpaqueDataCb, VoipHeadsetStatusCbT *pStatusCb, void *pCbUserData, int32_t iData); + +// destroy headset module +void VoipHeadsetDestroy(VoipHeadsetRefT *pHeadset); + +// callback to handle receiving voice data +void VoipHeadsetReceiveVoiceDataCb(VoipUserT *pRemoteUsers, int32_t iRemoteUserSize, int32_t iConsoleId, VoipMicrInfoT *pMicrInfo, uint8_t *pPacketData, void *pUserData); + +// callback to handle receiving opaque data +void VoipHeadsetReceiveOpaqueDataCb(int32_t iConsoleId, const uint8_t *pOpaqueData, int32_t iOpaqueDataSize, void *pUserData); + +// callback to handle registering of a new user +void VoipHeadsetRegisterUserCb(VoipUserT *pRemoteUser, int32_t iConsoleID, uint32_t bRegister, void *pUserData); + +// process function to update headset(s) +void VoipHeadsetProcess(VoipHeadsetRefT *pHeadset, uint32_t uFrameCount); + +// set play/rec volumes (-1 to keep current setting) +void VoipHeadsetSetVolume(VoipHeadsetRefT *pHeadset, int32_t iPlayVol, uint32_t iRecVol); + +// control function +int32_t VoipHeadsetControl(VoipHeadsetRefT *pHeadset, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); + +// status function +int32_t VoipHeadsetStatus(VoipHeadsetRefT *pHeadset, int32_t iSelect, int32_t iData, void *pBuf, int32_t iBufSize); + +// set speaker output callback (only available on some platforms) +void VoipHeadsetSpkrCallback(VoipHeadsetRefT *pHeadset, VoipSpkrCallbackT *pCallback, void *pUserData); + +// configure transcribe +void VoipHeadsetConfigTranscription(VoipHeadsetRefT *pHeadset, uint32_t uProfile, const char *pUrl, const char *pKey); + +// configure narrate +void VoipHeadsetConfigNarration(VoipHeadsetRefT *pHeadset, uint32_t uProvider, const char *pUrl, const char *pKey); + +#if defined(DIRTYCODE_PS4) +// validate communication permissions before display text from a remote user +void VoipHeadsetTranscribedTextPermissionCheck(VoipHeadsetRefT *pHeadset, VoipUserT *pOriginator, const char *pStrUtf8); +#endif + +#if defined(DIRTYCODE_XBOXONE)|| defined(DIRTYCODE_PS4) || defined(DIRTYCODE_GDK) +// set callback to be invoked by voipheadsetxboxone when inbound transcribed text has passed communication permission check +void VoipHeadsetSetTranscribedTextReceivedCallback(VoipHeadsetRefT *pHeadset, VoipHeadsetTranscribedTextReceivedCbT *pCallback, void *pUserData); +#endif + +// callback to tell dirtysdk what the first party id is for this user +void VoipHeadsetSetFirstPartyIdCallback(VoipHeadsetRefT *pHeadset, VoipFirstPartyIdCallbackCbT *pPlaybackCallback, void *pUserData); + +#ifdef __cplusplus +}; +#endif + +#endif // _voipheadset_h + diff --git a/src/thirdparty/dirtysdk/source/voip/voipmixer.c b/src/thirdparty/dirtysdk/source/voip/voipmixer.c new file mode 100644 index 00000000..a6189513 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipmixer.c @@ -0,0 +1,364 @@ +/*H********************************************************************************/ +/*! + \File voipmixer.c + + \Description + VoIP audio mixer + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 07/29/2004 (jbrookes) Split from voipheadset.c +*/ +/********************************************************************************H*/ + + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" +#include "voipmixer.h" + +/*** Defines **********************************************************************/ + +//! max number of supported mixbuffers +#define VOIP_MAXMIXBUFFERS (16) + +/*** Macros ***********************************************************************/ + +//! TRUE if value is a power of two, else FALSE +#define VOIP_PowerOf2(_val) ( ((_val) & -(_val)) == (_val) ) + +//! index the next mixbuffer +#define VOIP_NextMixBuffer(_pRef, _iCur) (((_iCur) + 1) % ((_pRef)->iNumMixBuffers)) + +//! absolute value of an 32bit integer +#define VOIP_Abs32(_val) (((_val) ^ ((_val) >> 31)) - ((_val) >> 31)) + +/*** Type Definitions *************************************************************/ + +typedef struct VoipMixBufferT +{ + int32_t *pMixBuffer; + int32_t iMixCount; + int32_t iMixMask; +} VoipMixBufferT; + +//! VOIP module state data +struct VoipMixerRefT +{ + //! number of mixbuffers + int32_t iNumMixBuffers; + + //! current mix buffer to mix into + int32_t iCurMixBuffer; + + //! size of a single mixbuffer + int32_t iMixBufferSize; + + //! mixbuffer list + VoipMixBufferT MixBuffers[VOIP_MAXMIXBUFFERS]; +}; + +/*** Function Prototypes **********************************************************/ + +/*** Variables ********************************************************************/ + +// Public Variables + +// Private Variables + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _VoipMixerPostMixSingle + + \Description + Processes the integer accumulation buffer in the case where only one + data packet was accumulated. + + \Input *pDst - [out] output play buffer + \Input *pSrc - mixbuffer pointer + \Input iNumSamples - number of samples to mix + + \Output + None. + + \Version 04/02/2004 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipMixerPostMixSingle(int16_t *pDst, int32_t *pSrc, int32_t iNumSamples) +{ + int32_t iData; + + // process data + for (iData = 0; iData < iNumSamples; iData++) + { + pDst[iData] = (int16_t)pSrc[iData]; + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipMixerPostMixMulti + + \Description + Processes the integer accumulation buffer in the case where more than one + packet was accumulated. + + \Input *pDst - [out] output play buffer + \Input *pSrc - mixbuffer pointer + \Input uBufMax - largest absolute value in sample buffer + \Input iNumSamples - number of samples to mix + + \Output + None. + + \Version 04/02/2004 (jbrookes) Split from VoipProcessHeadset() +*/ +/********************************************************************************F*/ +static void _VoipMixerPostMixMulti(int16_t *pDst, int32_t *pSrc, uint32_t uBufMax, int32_t iNumSamples) +{ + int32_t iData; + int32_t iMult; + int64_t lVal; + + // calculate scaler + iMult = (0x7fffffff/uBufMax)*32767; + + // scale buffer data + for (iData = 0; iData < iNumSamples; iData++) + { + lVal = (int64_t)(pSrc[iData]<<1) * (int64_t)iMult; + pDst[iData] = (int32_t)(lVal >> 32); + } +} + + +/*** Public functions **************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipMixerCreate + + \Description + Create the mixer module. + + \Input uMixBuffers - number of mixbuffers to implement + \Input uFrameSamples - size of a frame in samples + + \Output + VoipMixerRefT * - pointer to mixer module, or NULL if an error occurred + + \Version 07/29/2004 (jbrookes) +*/ +/********************************************************************************F*/ +VoipMixerRefT *VoipMixerCreate(uint32_t uMixBuffers, uint32_t uFrameSamples) +{ + VoipMixerRefT *pMixerRef; + int32_t iMixBuffer, iMixBufferSize; + int32_t iMemGroup; + void *pMemGroupUserData; + + // make sure mixbuffer count is between 2 and VOIP_MAXMIXBUFFERS + if (uMixBuffers < 2) + { + NetPrintf(("voipmixer: clamping create request of %d mixbuffers to 2\n", uMixBuffers)); + uMixBuffers = 2; + } + if (uMixBuffers > VOIP_MAXMIXBUFFERS) + { + NetPrintf(("voipmixer: clamping create request of %d mixbuffers to %d\n", uMixBuffers, VOIP_MAXMIXBUFFERS)); + uMixBuffers = VOIP_MAXMIXBUFFERS; + } + + // allocate and wipe module state + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + if ((pMixerRef = DirtyMemAlloc(sizeof(*pMixerRef), VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipmixer: unable to allocate %d bytes for mixer state\n", sizeof(*pMixerRef))); + return(NULL); + } + ds_memclr(pMixerRef, sizeof(*pMixerRef)); + + // init ref + pMixerRef->iNumMixBuffers = uMixBuffers; + pMixerRef->iMixBufferSize = uFrameSamples; + + // allocate mix buffers + iMixBufferSize = pMixerRef->iNumMixBuffers * pMixerRef->iMixBufferSize * sizeof(int32_t); + if ((pMixerRef->MixBuffers[0].pMixBuffer = DirtyMemAlloc(iMixBufferSize, VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipmixer: unable to allocate %d bytes for %d mixbuffers\n", iMixBufferSize, pMixerRef->iNumMixBuffers)); + DirtyMemFree(pMixerRef, VOIP_MEMID, iMemGroup, pMemGroupUserData); + return(NULL); + } + ds_memclr(pMixerRef->MixBuffers[0].pMixBuffer, iMixBufferSize); + + // set up mixbuffers 1...N + for (iMixBuffer = 1; iMixBuffer < pMixerRef->iNumMixBuffers; iMixBuffer++) + { + pMixerRef->MixBuffers[iMixBuffer].pMixBuffer = pMixerRef->MixBuffers[iMixBuffer-1].pMixBuffer + pMixerRef->iMixBufferSize; + } + + // return module state to caller + return(pMixerRef); +} + +/*F********************************************************************************/ +/*! + \Function VoipMixerDestroy + + \Description + Destroy the mixer module. + + \Input *pMixerRef - pointer to mixer module + + \Output + None. + + \Version 07/29/2004 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipMixerDestroy(VoipMixerRefT *pMixerRef) +{ + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + DirtyMemFree(pMixerRef->MixBuffers[0].pMixBuffer, VOIP_MEMID, iMemGroup, pMemGroupUserData); + DirtyMemFree(pMixerRef, VOIP_MEMID, iMemGroup, pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function VoipMixerAccumulate + + \Description + Decodes and accumulates incoming data into mixbuffer. + + \Input *pMixerRef - pointer to mixer module + \Input *pInputData - pointer to input voice data + \Input iDataSize - size of incoming voice data + \Input iMixMask - bitmask uniquely representing who is doing the writing + \Input iChannel - input channel number + + \Output + int32_t - negative=error, else next mixbuffer to write into + + \Version 07/29/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipMixerAccumulate(VoipMixerRefT *pMixerRef, uint8_t *pInputData, int32_t iDataSize, int32_t iMixMask, int32_t iChannel) +{ + int32_t iNumSamples; + int32_t iIndex, iMixBuffer; + + iMixBuffer = pMixerRef->iCurMixBuffer; + + // find the first spot that we can write into for this conduit + for (iIndex = 0; iIndex != pMixerRef->iNumMixBuffers; iIndex++) + { + // if we've found a spot + if (!(pMixerRef->MixBuffers[iMixBuffer].iMixMask & iMixMask)) + { + break; + } + + iMixBuffer = (iMixBuffer + 1) % pMixerRef->iNumMixBuffers; + } + + // make sure we're not accumulating into a mixbuffer we've already written into + if (iIndex == pMixerRef->iNumMixBuffers) + { + return(-1); + } + + // decode into mixbuffer + iNumSamples = VoipCodecDecode(pMixerRef->MixBuffers[iMixBuffer].pMixBuffer, pInputData, iDataSize, iChannel); + + // validate size + if (iNumSamples != pMixerRef->iMixBufferSize) + { + NetPrintf(("voipmixer: warning, expecting %d samples but got %d samples\n", pMixerRef->iMixBufferSize, iNumSamples)); + } + + // increment the mixcount for the mixbuffer and mark that we've written into it + pMixerRef->MixBuffers[iMixBuffer].iMixCount += 1; + pMixerRef->MixBuffers[iMixBuffer].iMixMask |= iMixMask; + + // swap this channel to the next mixbuffer + iMixBuffer = VOIP_NextMixBuffer(pMixerRef, iMixBuffer); + return(iMixBuffer); +} + +/*F********************************************************************************/ +/*! + \Function VoipMixerProcess + + \Description + Mixes queued data into one output buffer suitable for playback. + + \Input *pMixerRef - pointer to mixer + \Input *pFrameData - pointer to output + + \Output + int32_t - number of bytes written to output buffer + + \Version 04/02/2004 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipMixerProcess(VoipMixerRefT *pMixerRef, uint8_t *pFrameData) +{ + uint32_t uAbsVal, uBufMax; + int32_t *pMixBuffer; + int32_t iSample; + + // make sure we have data in the mixbuffer + if (pMixerRef->MixBuffers[pMixerRef->iCurMixBuffer].iMixCount == 0) + { + return(0); + } + pMixBuffer = pMixerRef->MixBuffers[pMixerRef->iCurMixBuffer].pMixBuffer; + + // calculate the buffer absolute max + for (iSample = 0, uBufMax = 0; iSample < pMixerRef->iMixBufferSize; iSample++) + { + uAbsVal = (uint32_t)VOIP_Abs32(pMixBuffer[iSample]); + if (uBufMax < uAbsVal) + { + uBufMax = uAbsVal; + } + } + + // if the buffer max was less than our max sample value + if (uBufMax < 32767) + { + _VoipMixerPostMixSingle((int16_t *)pFrameData, pMixBuffer, pMixerRef->iMixBufferSize); + } + else + { + _VoipMixerPostMixMulti((int16_t *)pFrameData, pMixBuffer, uBufMax, pMixerRef->iMixBufferSize); + } + + // reset current mixbuffer + ds_memclr(pMixBuffer, pMixerRef->iMixBufferSize*sizeof(int32_t)); + pMixerRef->MixBuffers[pMixerRef->iCurMixBuffer].iMixCount = 0; + pMixerRef->MixBuffers[pMixerRef->iCurMixBuffer].iMixMask = 0; + + // index next mixbuffer + pMixerRef->iCurMixBuffer = VOIP_NextMixBuffer(pMixerRef, pMixerRef->iCurMixBuffer); + return(pMixerRef->iMixBufferSize*2); +} diff --git a/src/thirdparty/dirtysdk/source/voip/voipmixer.h b/src/thirdparty/dirtysdk/source/voip/voipmixer.h new file mode 100644 index 00000000..b39fb437 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipmixer.h @@ -0,0 +1,57 @@ +/*H********************************************************************************/ +/*! + \File voipmixer.h + + \Description + VoIP data packet definitions. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 07/29/2004 (jbrookes) Split from voipheadset +*/ +/********************************************************************************H*/ + +#ifndef _voipmixer_h +#define _voipmixer_h + +/*** Include files ****************************************************************/ + +#include "DirtySDK/voip/voipcodec.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +//! opaque module state ref +typedef struct VoipMixerRefT VoipMixerRefT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +// create module state +VoipMixerRefT *VoipMixerCreate(uint32_t uMixBuffers, uint32_t uFrameSamples); + +// destroy module state +void VoipMixerDestroy(VoipMixerRefT *pMixerRef); + +// accumulate data into mixbuffer +int32_t VoipMixerAccumulate(VoipMixerRefT *pMixerRef, unsigned char *pInputData, int32_t iDataSize, int32_t iMixMask, int32_t iChannel); + +// process mix data currently in accumulator and return a mixed frame +int32_t VoipMixerProcess(VoipMixerRefT *pMixerRef, unsigned char *pFrameData); + +#ifdef __cplusplus +}; +#endif + +#endif // _voipmixer_h + + diff --git a/src/thirdparty/dirtysdk/source/voip/voipnarrate.c b/src/thirdparty/dirtysdk/source/voip/voipnarrate.c new file mode 100644 index 00000000..51507cf4 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voipnarrate.c @@ -0,0 +1,1169 @@ +/*H********************************************************************************/ +/*! + \File voipnarrate.c + + \Description + Voip narration API wrapping Cloud-based text-to-speech services, supporting + IBM Watson, Microsoft Speech Service, Google Speech, and Amazon Polly. + Narration requests may be up to 255 characters in length, and overlapping + requests are queued in order. + + \Notes + References + + IBM Watson: + Text-to Speech-API: https://www.ibm.com/watson/developercloud/text-to-speech/api/v1/curl.html + + Microsoft Speech Service: + Text-to-Speech How-To: https://docs.microsoft.com/en-us/azure/cognitive-services/Speech-Service/how-to-text-to-speech + + Google Text-to-Speech + Text-to-Speech API: https://cloud.google.com/text-to-speech/docs/reference/rest/ + + Amazon Polly + SynthesizeSpeech API: https://docs.aws.amazon.com/polly/latest/dg/API_SynthesizeSpeech.html + Amazon Endpoint Names: https://docs.aws.amazon.com/general/latest/gr/rande.html + VoiceId List: https://docs.aws.amazon.com/polly/latest/dg/API_SynthesizeSpeech.html#polly-SynthesizeSpeech-request-VoiceId + + WAV: + WAVE file format: https://en.wikipedia.org/wiki/WAV#RIFF_WAVE + + \Copyright + Copyright 2018 Electronic Arts + + \Version 10/25/2018 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/proto/protostream.h" +#include "DirtySDK/util/aws.h" +#include "DirtySDK/util/base64.h" +#include "DirtySDK/util/jsonformat.h" +#include "DirtySDK/util/jsonparse.h" +#include "DirtySDK/voip/voipdef.h" + +#include "DirtySDK/voip/voipnarrate.h" + +/*** Defines **********************************************************************/ + +//! protostream minimum data amount (for base64 decoding; four is the minimum amount but that produces one and a half samples, so we choose eight, BUT... +#define VOIPNARRATE_MINBUF (8) + +//! how many ms of audio received should we treat as being empty audio (for metrics) +#define VOIPNARRATE_EMPTY_AUDIO_THRESHOLD_MS (300) + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct VoipNarrateConfigT +{ + VoipNarrateProviderE eProvider; //!< configured provider + char strUrl[256]; //!< URL for text-to-speech request + char strKey[128]; //!< API key required for service authentication +} VoipNarrateConfigT; + +//! narration request data +typedef struct VoipNarrateRequestT +{ + struct VoipNarrateRequestT *pNext; + VoipNarrateGenderE eGender; + char strText[VOIPNARRATE_INPUT_MAX]; + int8_t iUserIndex; +} VoipNarrateRequestT; + +struct VoipNarrateRefT +{ + int32_t iMemGroup; + void *pMemGroupUserData; + + ProtoStreamRefT *pProtoStream; //!< stream transport module to handle buffered download of audio data + + VoipNarrateVoiceDataCbT *pVoiceDataCb; //!< user callback used to provide voice data + void *pUserData; //!< user data for user callback + + VoipNarrateRequestT *pRequest; //!< list of queued requests, if any + VoipNarrateConfigT Config; //!< module configuration (provider and credentials) + + char strHead[256]; //!< http head for narration request + char strBody[512]; //!< http body for narration request + const char *pBody; //!< pointer to start of body (may not match buffer start) + + VoipTextToSpeechMetricsT Metrics; //!< Usage metrics of the narration module + uint32_t uTtsStartTime; //!< time when we sent the request + + uint8_t aVoiceBuf[160*3*2]; //!< base64 decode buffer, sized for one 30ms frame of 16khz 16bit voice audio, also a multiple of three bytes to accomodate base64 4->3 ratio + int32_t iVoiceOff; //!< read offset in buffered voice data + int32_t iVoiceLen; //!< end of buffered voice data + int32_t iSamplesInPhrase; //!< total number of samples received for this phrase + + uint8_t bStart; //!< TRUE if start of stream download, else FALSE + uint8_t bActive; //!< TRUE if stream is active, else FALSE + int8_t iUserIndex; //!< index of local user current request is being made for + int8_t iVerbose; //!< verbose debug level (debug only) +}; + +/*** Variables ********************************************************************/ + +//! global config state +static VoipNarrateConfigT _VoipNarrate_Config = { VOIPNARRATE_PROVIDER_NONE, "", "" }; + +/*** Private Functions ************************************************************/ + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateCustomHeaderCb + + \Description + Custom header callback used to sign AWS requests + + \Input *pState - http module state + \Input *pHeader - pointer to http header buffer + \Input uHeaderSize - size of http header buffer + \Input *pData - pointer to data (unused) + \Input iDataLen - data length (unused) + \Input *pUserRef - voipnarrate ref + + \Output + int32_t - output header length + + \Version 12/28/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipNarrateCustomHeaderCb(ProtoHttpRefT *pState, char *pHeader, uint32_t uHeaderSize, const char *pData, int64_t iDataLen, void *pUserRef) +{ + VoipNarrateRefT *pVoipNarrate = (VoipNarrateRefT *)pUserRef; + int32_t iHdrLen = (int32_t)strlen(pHeader); + + // if amazon and we have room, sign the request + if ((pVoipNarrate->Config.eProvider != VOIPNARRATE_PROVIDER_AMAZON) || (uHeaderSize < (unsigned)iHdrLen)) + { + return(iHdrLen); + } + + // sign the request and return the updated size + iHdrLen += AWSSignSigV4(pHeader, uHeaderSize, pVoipNarrate->pBody, pVoipNarrate->Config.strKey, "polly", NULL); + // return size to protohttp + return(iHdrLen); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateSkipWavHeader + + \Description + Return offset past WAV header in input data + + \Input *pData - pointer to wav header + \Input iDataLen - length of data + + \Output + int32_t - offset past WAV header, or zero + + \Version 11/06/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipNarrateSkipWavHeader(const uint8_t *pData, int32_t iDataLen) +{ + int32_t iOffset = 0, iChkLen; + uint8_t bFoundData; + + // validate and skip RIFF/WAVE header + if ((iDataLen < 12) || ds_strnicmp((const char *)pData, "RIFF", 4) || ds_strnicmp((const char *)pData+8, "WAVE", 4)) + { + return(0); + } + iOffset += 12; + + // process chunks + for (bFoundData = FALSE; iOffset < (iDataLen+12); iOffset += iChkLen+8) + { + // get chunk length + iChkLen = pData[iOffset+4]; + iChkLen |= pData[iOffset+5]<<8; + iChkLen |= pData[iOffset+6]<<16; + iChkLen |= pData[iOffset+7]<<24; + + // look for data chunk + if (!ds_strnicmp((const char *)pData+iOffset, "data", 4)) + { + bFoundData = TRUE; + iOffset += 8; + break; + } + } + return(bFoundData ? iOffset : 0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateBasicAuth + + \Description + Encode Basic HTTP authorization header as per https://tools.ietf.org/html/rfc7617 + + \Input *pBuffer - [out] output buffer for encoded base64 string + \Input iBufSize - size of output buffer + \Input *pUser - user identifer + \Input *pPass - user password + + \Output + const char * - pointer to output buffer + + \Version 10/25/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipNarrateBasicAuth(char *pBuffer, int32_t iBufSize, const char *pUser, const char *pPass) +{ + char strAuth[128]; + ds_snzprintf(strAuth, sizeof(strAuth), "%s:%s", pUser, pPass); + Base64Encode2(strAuth, (int32_t)strlen(strAuth), pBuffer, iBufSize); + return(pBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateBase64Decode + + \Description + Decode Base64-encoded voice data + + \Input *pVoipNarrate - pointer to module state + \Input *pOutput - [out] buffer to hold decoded voice data + \Input *pOutSize - [in/out] output buffer length, size of output data + \Input *pInput - base64-encoded input data + \Input iInpSize - input buffer length + + \Output + int32_t - negative=failure, else input bytes consumed + + \Version 10/27/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipNarrateBase64Decode(VoipNarrateRefT *pVoipNarrate, char *pOutput, int32_t *pOutSize, const char *pInput, int32_t iInpSize) +{ + static const char _strJson[] = "\"audioContent\":"; + const char *pInput2, *pInpEnd = pInput + iInpSize; + int32_t iInpOff = 0; + + // if we have the beginning of json envelope, skip it + if ((pInput2 = strstr(pInput, _strJson)) != NULL) + { + // skip json header + pInput2 += sizeof(_strJson); + // skip to base64 data + for (; (*pInput2 != '"') && (pInput2 < pInpEnd); pInput2 += 1) + ; + if (*pInput2 != '"') + { + return(-1); + } + // skip quote + pInput2 += 1; + // remember to consume this in addition to base64 data + iInpOff = pInput2 - pInput; + pInput = pInput2; + } + // if we have end of json envelope, trim it + if ((pInput2 = strchr(pInput, '"')) != NULL) + { + // handle end of data + if (pInput2 == pInput) + { + iInpOff = iInpSize; + } + iInpSize = pInput2-pInput; + } + + // constrain input size to what will fit in output buffer + if (iInpSize > Base64EncodedSize(*pOutSize)) + { + iInpSize = Base64EncodedSize(*pOutSize); + } + + // make sure input size is a multiple of four to produce an integral number of output bytes + iInpSize &= ~0x03; + + // base64 decode and save output size + *pOutSize = Base64Decode3(pInput, iInpSize, pOutput, *pOutSize); + // return number of bytes of input consumed + return(iInpSize+iInpOff); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateRequestAdd + + \Description + Queue request for later sending + + \Input *pVoipNarrate - pointer to module state + \Input iUserIndex - local user index of user who is requesting speech synthesis + \Input eGender - preferred gender for voice narration + \Input *pText - text to be converted + + \Output + int32_t - negative=failure, else success + + \Version 11/09/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipNarrateRequestAdd(VoipNarrateRefT *pVoipNarrate, int32_t iUserIndex, VoipNarrateGenderE eGender, const char *pText) +{ + VoipNarrateRequestT *pRequest; + + // allocate and clear the request + if ((pRequest = DirtyMemAlloc(sizeof(*pRequest), VOIPNARRATE_MEMID, pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData)) == NULL) + { + NetPrintf(("voipnarrate: could not allocate request\n")); + pVoipNarrate->Metrics.uErrorCount += 1; + return(-1); + } + ds_memclr(pRequest, sizeof(*pRequest)); + + // copy the request data + ds_strnzcpy(pRequest->strText, pText, sizeof(pRequest->strText)); + pRequest->iUserIndex = iUserIndex; + pRequest->eGender = eGender; + + // add to queue + pRequest->pNext = pVoipNarrate->pRequest; + pVoipNarrate->pRequest = pRequest; + + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateRequestGet + + \Description + Get queued request + + \Input *pVoipNarrate - pointer to module state + \Input *pRequest - [out] storage for request (may be null) + + \Version 11/09/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipNarrateRequestGet(VoipNarrateRefT *pVoipNarrate, VoipNarrateRequestT *pRequest) +{ + VoipNarrateRequestT **ppRequest; + // get oldest request (we add to head, so get from tail) + for (ppRequest = &pVoipNarrate->pRequest; (*ppRequest)->pNext != NULL; ppRequest = &((*ppRequest)->pNext)) + ; + // copy request + if (pRequest != NULL) + { + ds_memcpy_s(pRequest, sizeof(*pRequest), *ppRequest, sizeof(**ppRequest)); + } + // free request + DirtyMemFree(*ppRequest, VOIPNARRATE_MEMID, pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData); + // remove from list + *ppRequest = NULL; +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateFormatHeadWatson + + \Description + Format connection header for IBM Watson Speech service + Ref: https://console.bluemix.net/docs/services/text-to-speech/http.html#usingHTTP + + \Input *pVoipNarrate - pointer to module state + \Input *pUrl - [out] buffer for formatted url + \Input iUrlLen - length of url buffer + \Input *pHead - [out] buffer for formatted request header + \Input iHeadLen - length of header buffer + \Input eGender - preferred gender for voice narration + + \Output + int32_t - negative=failure, else success + + \Version 11/07/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipNarrateFormatHeadWatson(VoipNarrateRefT *pVoipNarrate, char *pUrl, int32_t iUrlLen, char *pHead, int32_t iHeadLen, VoipNarrateGenderE eGender) +{ + char strAuth[128]; + int32_t iOffset=0; + + // encode Basic authorization string with string apikey: + _VoipNarrateBasicAuth(strAuth, sizeof(strAuth), "apikey", pVoipNarrate->Config.strKey); + + // format request header + iOffset += ds_snzprintf(pHead+iOffset, iHeadLen-iOffset, "Content-Type: application/json\r\n"); + iOffset += ds_snzprintf(pHead+iOffset, iHeadLen-iOffset, "Accept: audio/wav; rate=%d\r\n", VOIPNARRATE_SAMPLERATE); + iOffset += ds_snzprintf(pHead+iOffset, iHeadLen-iOffset, "Authorization: Basic %s\r\n", strAuth); + + // format url with voice based on preferred gender + ds_snzprintf(pUrl, iUrlLen, "%s?voice=%s", pVoipNarrate->Config.strUrl, (eGender == VOIPNARRATE_GENDER_FEMALE) ? "en-US_AllisonVoice" : "en-US_MichaelVoice"); + return(pUrl); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateFormatBodyWatson + + \Description + Format request body for IBM Watson Speech service + + \Input *pVoipNarrate - pointer to module state + \Input *pBody - [out] buffer to hold request body + \Input iBodyLen - buffer length + \Input *pText - pointer to text request + + \Output + int32_t - negative=failure, else success + + \Version 11/07/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_VoipNarrateFormatBodyWatson(VoipNarrateRefT *pVoipNarrate, char *pBody, int32_t iBodyLen, const char *pText) +{ + JsonInit(pBody, iBodyLen, JSON_FL_WHITESPACE); + JsonAddStr(pBody, "text", pText); + pBody = JsonFinish(pBody); + return(pBody); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateFormatHeadMicrosoft + + \Description + Format connection header for Microsoft Speech service + + \Input *pVoipNarrate - pointer to module state + \Input *pUrl - [out] buffer for formatted url + \Input iUrlLen - length of url buffer + \Input *pHead - [out] buffer for formatted request header + \Input iHeadLen - length of header buffer + + \Output + int32_t - negative=failure, else success + + \Version 10/25/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipNarrateFormatHeadMicrosoft(VoipNarrateRefT *pVoipNarrate, char *pUrl, int32_t iUrlLen, char *pHead, int32_t iHeadLen) +{ + int32_t iOffset=0; + + // format request header + iOffset += ds_snzprintf(pHead+iOffset, iHeadLen-iOffset, "Content-Type: application/ssml+xml\r\n"); + iOffset += ds_snzprintf(pHead+iOffset, iHeadLen-iOffset, "X-Microsoft-OutputFormat: raw-%dkhz-16bit-mono-pcm\r\n", VOIPNARRATE_SAMPLERATE/1000); + iOffset += ds_snzprintf(pHead+iOffset, iHeadLen-iOffset, "Ocp-Apim-Subscription-Key: %s\r\n", pVoipNarrate->Config.strKey); + + // copy url + ds_strnzcpy(pUrl, pVoipNarrate->Config.strUrl, iUrlLen); + return(pUrl); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateFormatBodyMicrosoft + + \Description + Format request body for Microsoft Speech service + + \Input *pVoipNarrate - pointer to module state + \Input *pBody - [out] buffer to hold request body + \Input iBodyLen - buffer length + \Input eGender - preferred gender for voice narration + \Input *pText - pointer to text request + + \Output + int32_t - negative=failure, else success + + \Version 10/25/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_VoipNarrateFormatBodyMicrosoft(VoipNarrateRefT *pVoipNarrate, char *pBody, int32_t iBodyLen, VoipNarrateGenderE eGender, const char *pText) +{ + int32_t iOffset=0; + // format request body + iOffset += ds_snzprintf(pBody+iOffset, iBodyLen-iOffset, ""); + iOffset += ds_snzprintf(pBody+iOffset, iBodyLen-iOffset, "%s", + (eGender == VOIPNARRATE_GENDER_FEMALE) ? "JessaRUS" : "BenjaminRUS", pText); + iOffset += ds_snzprintf(pBody+iOffset, iBodyLen-iOffset, ""); + // return pointer to body + return(pBody); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateFormatHeadGoogle + + \Description + Format connection header for Google Text to Speech + + \Input *pVoipNarrate - pointer to module state + \Input *pUrl - [out] buffer for formatted url + \Input iUrlLen - length of url buffer + \Input *pHead - [out] buffer for formatted request header + \Input iHeadLen - length of header buffer + + \Output + int32_t - negative=failure, else success + + \Version 10/25/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipNarrateFormatHeadGoogle(VoipNarrateRefT *pVoipNarrate, char *pUrl, int32_t iUrlLen, char *pHead, int32_t iHeadLen) +{ + // format request header + *pHead = '\0'; + // format request url + ds_snzprintf(pUrl, iUrlLen, "%s?key=%s", pVoipNarrate->Config.strUrl, pVoipNarrate->Config.strKey); + // return url + return(pUrl); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateFormatBodyGoogle + + \Description + Format request body for Google text-to-speech request + + \Input *pVoipNarrate - pointer to module state + \Input *pBody - [out] buffer to hold request body + \Input iBodyLen - buffer length + \Input eGender - preferred gender for voice narration + \Input *pText - pointer to text request + + \Output + int32_t - negative=failure, else success + + \Notes + Ref: https://cloud.google.com/text-to-speech/docs/reference/rest/ + + \Version 10/25/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static char * _VoipNarrateFormatBodyGoogle(VoipNarrateRefT *pVoipNarrate, char *pBody, int32_t iBodyLen, VoipNarrateGenderE eGender, const char *pText) +{ + static const char *_strGender[VOIPNARRATE_NUMGENDERS] = { "SSML_VOICE_GENDER_UNSPECIFIED", "FEMALE", "MALE", "NEUTRAL" }; + static const char *_strVoice[VOIPNARRATE_NUMGENDERS] = { "en-US-Standard-D", "en-US-Standard-C", "en-US-Standard-B", "en-US-Standard-D" }; + JsonInit(pBody, iBodyLen, JSON_FL_WHITESPACE); + + JsonObjectStart(pBody, "input"); + JsonAddStr(pBody, "text", pText); + JsonObjectEnd(pBody); + + JsonObjectStart(pBody, "voice"); + JsonAddStr(pBody, "languageCode", "en-US"); + JsonAddStr(pBody, "name", _strVoice[eGender]); + JsonAddStr(pBody, "ssmlGender", _strGender[eGender]); // we specify gender here, but it is unclear if it does anything + JsonObjectEnd(pBody); + + JsonObjectStart(pBody, "audioConfig"); + JsonAddStr(pBody, "audioEncoding", "LINEAR16"); + JsonAddInt(pBody, "sampleRateHertz", VOIPNARRATE_SAMPLERATE); + JsonObjectEnd(pBody); + + pBody = JsonFinish(pBody); + return(pBody); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateFormatHeadAmazon + + \Description + Format connection header for Amazon Polly + + \Input *pVoipNarrate - pointer to module state + \Input *pUrl - [out] buffer for formatted url + \Input iUrlLen - length of url buffer + \Input *pHead - [out] buffer for formatted request header + \Input iHeadLen - length of header buffer + + \Output + int32_t - negative=failure, else success + + \Version 11/21/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipNarrateFormatHeadAmazon(VoipNarrateRefT *pVoipNarrate, char *pUrl, int32_t iUrlLen, char *pHead, int32_t iHeadLen) +{ + int32_t iOffset=0; + // format request header + iOffset += ds_snzprintf(pHead+iOffset, iHeadLen-iOffset, "Content-Type: application/json\r\n"); + iOffset += ds_snzprintf(pHead+iOffset, iHeadLen-iOffset, "Accept: audio/wav; rate=%d\r\n", VOIPNARRATE_SAMPLERATE); + // copy url + ds_strnzcpy(pUrl, pVoipNarrate->Config.strUrl, iUrlLen); + return(pUrl); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateFormatBodyAmazon + + \Description + Format request body for Amazon Polly + + \Input *pVoipNarrate - pointer to module state + \Input *pBody - [out] buffer to hold request body + \Input iBodyLen - buffer length + \Input eGender - preferred gender for voice narration + \Input *pText - pointer to text request + + \Output + int32_t - negative=failure, else success + + \Version 12/21/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static char *_VoipNarrateFormatBodyAmazon(VoipNarrateRefT *pVoipNarrate, char *pBody, int32_t iBodyLen, VoipNarrateGenderE eGender, const char *pText) +{ + JsonInit(pBody, iBodyLen, JSON_FL_WHITESPACE); + JsonAddStr(pBody, "OutputFormat", "pcm"); + JsonAddStr(pBody, "Text", pText); + JsonAddStr(pBody, "VoiceId", (eGender == VOIPNARRATE_GENDER_FEMALE) ? "Joanna" : "Joey"); + pBody = JsonFinish(pBody); + return(pBody); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateStreamCallbackGoogle + + \Description + Decode Base64-encoded voice data + + \Input *pVoipNarrate - pointer to module state + \Input eStatus - ProtoStream status + \Input *pData - base64-encoded input data + \Input iDataSize - input buffer length + + \Output + int32_t - negative=failure, else number of input bytes consumed + + \Version 10/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipNarrateStreamCallbackGoogle(VoipNarrateRefT *pVoipNarrate, ProtoStreamStatusE eStatus, const uint8_t *pData, int32_t iDataSize) +{ + int32_t iDataRead, iDataDecoded; + + // submit any base64-decoded data we have first + if (pVoipNarrate->iVoiceLen > 0) + { + // if start of stream, see if we need to skip WAV header + if (pVoipNarrate->bStart) + { + pVoipNarrate->iVoiceOff = _VoipNarrateSkipWavHeader(pVoipNarrate->aVoiceBuf, pVoipNarrate->iVoiceLen); + pVoipNarrate->iVoiceLen -= pVoipNarrate->iVoiceOff; + pVoipNarrate->bStart = FALSE; + } + // pass data to user + iDataRead = pVoipNarrate->pVoiceDataCb(pVoipNarrate, pVoipNarrate->iUserIndex, (const int16_t *)(pVoipNarrate->aVoiceBuf+pVoipNarrate->iVoiceOff), pVoipNarrate->iVoiceLen, pVoipNarrate->pUserData); + // mark data as read + pVoipNarrate->iVoiceOff += iDataRead; + pVoipNarrate->iVoiceLen -= iDataRead; + } + // if we don't have data to decode, or we still have decoded voice data that hasn't been consumed yet, don't decode more + if ((pVoipNarrate->iVoiceLen > 0) || (iDataSize <= 0)) + { + return(0); + } + pVoipNarrate->iVoiceOff = 0; + + // base64-decode voice data + if ((iDataRead = _VoipNarrateBase64Decode(pVoipNarrate, (char *)pVoipNarrate->aVoiceBuf, (iDataDecoded = sizeof(pVoipNarrate->aVoiceBuf), &iDataDecoded), (const char *)pData, iDataSize)) >= 0) + { + pVoipNarrate->iVoiceLen = iDataDecoded; + pVoipNarrate->iSamplesInPhrase += iDataDecoded; + pData = pVoipNarrate->aVoiceBuf; + } + else + { + NetPrintf(("voipnarrate: error; could not base64 decode data\n")); + NetPrintMem(pData, iDataSize, "base64 data"); + pVoipNarrate->Metrics.uErrorCount += 1; + } + return(iDataRead); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateStreamCallback + + \Description + Receive streamed voice data and submit it to callback + + \Input *pProtoStream - ProtoStream module state + \Input eStatus - ProtoStream status + \Input *pData - base64-encoded input data + \Input iDataSize - input buffer length + \Input *pUserData - callback user data (VoipNarrate module ref) + + \Output + int32_t - negative=failure, else number of input bytes consumed + + \Version 10/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipNarrateStreamCallback(ProtoStreamRefT *pProtoStream, ProtoStreamStatusE eStatus, const uint8_t *pData, int32_t iDataSize, void *pUserData) +{ + VoipNarrateRefT *pVoipNarrate = (VoipNarrateRefT *)pUserData; + int32_t iDataRead, iResult; + char strError[256] = ""; + + // handle start callback notification + if (eStatus == PROTOSTREAM_STATUS_BEGIN) + { + pVoipNarrate->iSamplesInPhrase = 0; + pVoipNarrate->Metrics.uDelay += NetTickDiff(NetTick(), pVoipNarrate->uTtsStartTime); + pVoipNarrate->pVoiceDataCb(pVoipNarrate, pVoipNarrate->iUserIndex, (const int16_t *)pData, VOIPNARRATE_STREAM_START, pVoipNarrate->pUserData); + } + // handle end callback notification + if (eStatus == PROTOSTREAM_STATUS_DONE) + { + // save metrics + int32_t iPhraseDuration = ((pVoipNarrate->iSamplesInPhrase * 1000) / VOIPNARRATE_SAMPLERATE); + pVoipNarrate->Metrics.uDurationMsRecv += iPhraseDuration; + if (iPhraseDuration < VOIPNARRATE_EMPTY_AUDIO_THRESHOLD_MS) + { + pVoipNarrate->Metrics.uEmptyResultCount += 1; + } + + // signal end of stream + pVoipNarrate->pVoiceDataCb(pVoipNarrate, pVoipNarrate->iUserIndex, (const int16_t *)pData, VOIPNARRATE_STREAM_END, pVoipNarrate->pUserData); + pVoipNarrate->bActive = FALSE; + + // check for a completion result that is not successful, and log error response (if any) to debug output + if ((iResult = ProtoStreamStatus(pProtoStream, 'code', NULL, 0)) != PROTOHTTP_RESPONSE_SUCCESSFUL) + { + ProtoStreamStatus(pProtoStream, 'serr', strError, sizeof(strError)); + NetPrintf(("voipnarrate: stream failed with http result %d:\n%s\n", iResult, strError)); + pVoipNarrate->Metrics.uErrorCount += 1; + } + } + + // read data and pass it to callback, processing as necessary + for (iDataRead = 0, iResult = 1; (iResult > 0) && (iDataSize > 0); ) + { + if (pVoipNarrate->Config.eProvider != VOIPNARRATE_PROVIDER_GOOGLE) + { + // if start of stream, see if we need to skip WAV header + if ((pVoipNarrate->bStart) && (iDataSize > 0)) + { + iDataRead = _VoipNarrateSkipWavHeader(pData, iDataSize); + pData += iDataRead; + iDataSize -= iDataRead; + pVoipNarrate->bStart = FALSE; + } + iResult = pVoipNarrate->pVoiceDataCb(pVoipNarrate, pVoipNarrate->iUserIndex, (const int16_t *)pData, iDataSize, pVoipNarrate->pUserData); + pVoipNarrate->iSamplesInPhrase += iResult; + } + else + { + // google-specific processing to deal with base64 encoded audio + iResult = _VoipNarrateStreamCallbackGoogle(pVoipNarrate, eStatus, pData, iDataSize); + } + + iDataRead += iResult; + iDataSize -= iResult; + pData += iResult; + } + return(iDataRead); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateStart + + \Description + Receive streamed voice data and submit it to callback + + \Input *pVoipNarrate - pointer to module state + \Input iUserIndex - local user index of user who is requesting speech synthesis + \Input eGender - preferred gender for voice for narration + \Input *pText - pointer to text request + + \Output + int32_t - ProtoStream request result + + \Version 10/25/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipNarrateStart(VoipNarrateRefT *pVoipNarrate, int32_t iUserIndex, VoipNarrateGenderE eGender, const char *pText) +{ + const char *pUrl, *pReq; + char strUrl[256]; + int32_t iResult; + + // format header/url and request body + if (pVoipNarrate->Config.eProvider == VOIPNARRATE_PROVIDER_MICROSOFT) + { + pUrl = _VoipNarrateFormatHeadMicrosoft(pVoipNarrate, strUrl, sizeof(strUrl), pVoipNarrate->strHead, sizeof(pVoipNarrate->strHead)); + pReq = _VoipNarrateFormatBodyMicrosoft(pVoipNarrate, pVoipNarrate->strBody, sizeof(pVoipNarrate->strBody), eGender, pText); + } + else if (pVoipNarrate->Config.eProvider == VOIPNARRATE_PROVIDER_GOOGLE) + { + pUrl = _VoipNarrateFormatHeadGoogle(pVoipNarrate, strUrl, sizeof(strUrl), pVoipNarrate->strHead, sizeof(pVoipNarrate->strHead)); + pReq = _VoipNarrateFormatBodyGoogle(pVoipNarrate, pVoipNarrate->strBody, sizeof(pVoipNarrate->strBody), eGender, pText); + } + else if (pVoipNarrate->Config.eProvider == VOIPNARRATE_PROVIDER_IBMWATSON) + { + pUrl = _VoipNarrateFormatHeadWatson(pVoipNarrate, strUrl, sizeof(strUrl), pVoipNarrate->strHead, sizeof(pVoipNarrate->strHead), eGender); + pReq = _VoipNarrateFormatBodyWatson(pVoipNarrate, pVoipNarrate->strBody, sizeof(pVoipNarrate->strBody), pText); + } + else if (pVoipNarrate->Config.eProvider == VOIPNARRATE_PROVIDER_AMAZON) + { + pUrl = _VoipNarrateFormatHeadAmazon(pVoipNarrate, strUrl, sizeof(strUrl), pVoipNarrate->strHead, sizeof(pVoipNarrate->strHead)); + pReq = _VoipNarrateFormatBodyAmazon(pVoipNarrate, pVoipNarrate->strBody, sizeof(pVoipNarrate->strBody), eGender, pText); + } + else + { + NetPrintf(("voipnarrate: undefined provider\n")); + return(-1); + } + NetPrintfVerbose((pVoipNarrate->iVerbose, 1, "voipnarrate: request body\n%s\n", pReq)); + pVoipNarrate->pBody = pReq; + + // set request header + ProtoStreamControl(pVoipNarrate->pProtoStream, 'apnd', 0, 0, pVoipNarrate->strHead); + + pVoipNarrate->Metrics.uEventCount += 1; + pVoipNarrate->Metrics.uCharCountSent += (uint32_t)strlen(pText); + pVoipNarrate->uTtsStartTime = NetTick(); + + // make the request + if ((iResult = ProtoStreamOpen2(pVoipNarrate->pProtoStream, pUrl, pReq, PROTOSTREAM_FREQ_ONCE)) >= 0) + { + // mark as stream start and active + pVoipNarrate->bStart = pVoipNarrate->bActive = TRUE; + } + else + { + NetPrintf(("voipnarrate: failed to open stream\n")); + pVoipNarrate->Metrics.uErrorCount += 1; + } + + // return to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipNarrateConfig + + \Description + Configure the VoipNarrate module + + \Input *pVoipNarrate - pointer to module state + \Input *pConfig - module configuration to set + + \Output + uint32_t - TRUE if configured successfully + + \Version 11/07/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _VoipNarrateConfig(VoipNarrateRefT *pVoipNarrate, VoipNarrateConfigT *pConfig) +{ + uint8_t uRet = TRUE; + NetCritEnter(NULL); + if (pConfig->eProvider != VOIPNARRATE_PROVIDER_NONE) + { + ds_memcpy_s(&pVoipNarrate->Config, sizeof(pVoipNarrate->Config), pConfig, sizeof(*pConfig)); + } + else + { + NetPrintfVerbose((pVoipNarrate->iVerbose, 0, "voipnarrate: narration disabled\n")); + ds_memclr(&pVoipNarrate->Config, sizeof(pVoipNarrate->Config)); + uRet = FALSE; + } + NetCritLeave(NULL); + return(uRet); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipNarrateCreate + + \Description + Create the narration module + + \Input *pVoiceDataCb - callback used to provide voice data + \Input *pUserData - callback user data + + \Output + VoipNarrateRefT * - new module state, or NULL + + \Version 10/25/2018 (jbrookes) +*/ +/********************************************************************************F*/ +VoipNarrateRefT *VoipNarrateCreate(VoipNarrateVoiceDataCbT *pVoiceDataCb, void *pUserData) +{ + VoipNarrateRefT *pVoipNarrate; + int32_t iMemGroup; + void *pMemGroupUserData; + + // query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // validate callback + if (pVoiceDataCb == NULL) + { + NetPrintf(("voipnarrate: could not create module with null callback\n")); + return(NULL); + } + + // allocate and init module state + if ((pVoipNarrate = DirtyMemAlloc(sizeof(*pVoipNarrate), VOIPNARRATE_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voipnarrate: could not allocate module state\n")); + return(NULL); + } + ds_memclr(pVoipNarrate, sizeof(*pVoipNarrate)); + pVoipNarrate->iMemGroup = iMemGroup; + pVoipNarrate->pMemGroupUserData = pMemGroupUserData; + pVoipNarrate->pVoiceDataCb = pVoiceDataCb; + pVoipNarrate->pUserData = pUserData; + pVoipNarrate->iVerbose = 1; + + // allocate streaming module with a buffer to hold up to 1s of 16khz 16bit streaming audio + if ((pVoipNarrate->pProtoStream = ProtoStreamCreate(16*2*1024)) == NULL) + { + VoipNarrateDestroy(pVoipNarrate); + return(NULL); + } + // set protostream callback with a 20ms call rate + ProtoStreamSetCallback(pVoipNarrate->pProtoStream, 20, _VoipNarrateStreamCallback, pVoipNarrate); + // set protostream minimum data amount (for base64 decoding; four is the minimum amount but that produces one and a half samples, so we choose eight) + ProtoStreamControl(pVoipNarrate->pProtoStream, 'minb', 8, 0, NULL); + // set protostream debug level + ProtoStreamControl(pVoipNarrate->pProtoStream, 'spam', 1, 0, NULL); + // set keepalive + ProtoStreamControl(pVoipNarrate->pProtoStream, 'keep', 1, 0, NULL); + // set protostream http custom header callback, used to sign AWS requests + ProtoStreamSetHttpCallback(pVoipNarrate->pProtoStream, _VoipNarrateCustomHeaderCb, NULL, pVoipNarrate); + + // configure for particular provider + if (!_VoipNarrateConfig(pVoipNarrate, &_VoipNarrate_Config)) + { + NetPrintf(("voipnarrate: could not configure for provider\n")); + VoipNarrateDestroy(pVoipNarrate); + return(NULL); + } + + // return ref to caller + return(pVoipNarrate); +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateConfig + + \Description + Set global state to configure the VoipNarrate modules + + \Input eProvider - VOIPNARRATE_PROVIDER_* (VOIPNARRATE_PROVIDER_NONE to disable) + \Input *pUrl - pointer to url to use for tts requests + \Input *pKey - pointer to authentication key to use for tts requests + + \Version 11/07/2018 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipNarrateConfig(VoipNarrateProviderE eProvider, const char *pUrl, const char *pKey) +{ + NetCritEnter(NULL); + _VoipNarrate_Config.eProvider = eProvider; + ds_strnzcpy(_VoipNarrate_Config.strUrl, pUrl, sizeof(_VoipNarrate_Config.strUrl)); + ds_strnzcpy(_VoipNarrate_Config.strKey, pKey, sizeof(_VoipNarrate_Config.strKey)); + NetCritLeave(NULL); +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateDestroy + + \Description + Destroy the VoipNarrate module + + \Input *pVoipNarrate - pointer to module state + + \Version 10/25/2018 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipNarrateDestroy(VoipNarrateRefT *pVoipNarrate) +{ + // destroy protostream module, if allocated + if (pVoipNarrate->pProtoStream != NULL) + { + ProtoStreamDestroy(pVoipNarrate->pProtoStream); + } + // release any queued requests + while (pVoipNarrate->pRequest != NULL) + { + _VoipNarrateRequestGet(pVoipNarrate, NULL); + } + // dispose of module memory + DirtyMemFree(pVoipNarrate, VOIPNARRATE_MEMID, pVoipNarrate->iMemGroup, pVoipNarrate->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateInput + + \Description + Input text to be convert to speech + + \Input *pVoipNarrate - pointer to module state + \Input iUserIndex - local user index of user who is requesting speech synthesis + \Input eGender - preferred gender for voice narration + \Input *pText - text to be converted + + \Output + int32_t - zero=success, otherwise=failure + + \Version 10/25/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipNarrateInput(VoipNarrateRefT *pVoipNarrate, int32_t iUserIndex, VoipNarrateGenderE eGender, const char *pText) +{ + // make sure a provider is configured + if (pVoipNarrate->Config.eProvider == VOIPNARRATE_PROVIDER_NONE) + { + NetPrintfVerbose((pVoipNarrate->iVerbose, 0, "voipnarrate: no provider configured\n")); + return(-1); + } + // handle if there is already narration ongoing + if (pVoipNarrate->bActive) + { + NetPrintfVerbose((pVoipNarrate->iVerbose, 1, "voipnarrate: queueing request '%s'\n", pText)); + return(_VoipNarrateRequestAdd(pVoipNarrate, iUserIndex, eGender, pText)); + } + // if ready, start the request + return(_VoipNarrateStart(pVoipNarrate, iUserIndex, eGender, pText)); +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateStatus + + \Description + Get module status. + + \Input *pVoipNarrate - pointer to module state + \Input iStatus - status selector + \Input iValue - selector specific + \Input *pBuffer - selector specific + \Input iBufSize - selector specific + + \Output + int32_t - selector specific + + \Notes + Other status codes are passed down to the stream transport handler. + + \verbatim + 'ttsm' - get the VoipTextToSpeechMetricsT via pBuffer + \endverbatim + + \Version 11/15/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipNarrateStatus(VoipNarrateRefT *pVoipNarrate, int32_t iStatus, int32_t iValue, void *pBuffer, int32_t iBufSize) +{ + if (iStatus == 'ttsm') + { + if ((pBuffer != NULL) && (iBufSize >= (int32_t)sizeof(VoipTextToSpeechMetricsT))) + { + ds_memcpy_s(pBuffer, iBufSize, &pVoipNarrate->Metrics, sizeof(VoipTextToSpeechMetricsT)); + return(0); + } + return(-1); + } + return(ProtoStreamStatus(pVoipNarrate->pProtoStream, iStatus, pBuffer, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateControl + + \Description + Set control options + + \Input *pVoipNarrate - pointer to module state + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + iStatus can be one of the following: + + \verbatim + 'ctsm' - clear text to speech metrics in VoipTextToSpeechMetricsT + 'spam' - set verbose debug level (debug only) + \endverbatim + + Unhandled codes are passed through to the stream transport handler + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipNarrateControl(VoipNarrateRefT *pVoipNarrate, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + if (iControl == 'ctsm') + { + ds_memclr(&(pVoipNarrate->Metrics), sizeof(pVoipNarrate->Metrics)); + return(0); + } + #if DIRTYCODE_LOGGING + // set verbosity for us and pass through to stream transport handler + if (iControl == 'spam') + { + pVoipNarrate->iVerbose = iValue; + } + #endif + // if not handled, let stream transport handler take a stab at it + return(ProtoStreamControl(pVoipNarrate->pProtoStream, iControl, iValue, iValue2, pValue)); +} + +/*F********************************************************************************/ +/*! + \Function VoipNarrateUpdate + + \Description + Update the narration module + + \Input *pVoipNarrate - pointer to module state + + \Version 10/25/2018 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipNarrateUpdate(VoipNarrateRefT *pVoipNarrate) +{ + // see if we need to start a queued narration request + if ((pVoipNarrate->pRequest != NULL) && !pVoipNarrate->bActive) + { + VoipNarrateRequestT Request; + _VoipNarrateRequestGet(pVoipNarrate, &Request); + _VoipNarrateStart(pVoipNarrate, Request.iUserIndex, Request.eGender, Request.strText); + } + // give life to stream module + ProtoStreamUpdate(pVoipNarrate->pProtoStream); +} diff --git a/src/thirdparty/dirtysdk/source/voip/voippacket.h b/src/thirdparty/dirtysdk/source/voip/voippacket.h new file mode 100644 index 00000000..b7ba2061 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voippacket.h @@ -0,0 +1,162 @@ +/*H********************************************************************************/ +/*! + \File voippacket.h + + \Description + VoIP data packet definitions. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 03/17/2004 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +#ifndef _voippacket_h +#define _voippacket_h + +/*** Include files ****************************************************************/ + +#include "DirtySDK/dirtysock/dirtynet.h" + +/*** Defines **********************************************************************/ + +//! maximum voice payload size +#define VOIP_MAXMICRPKTSIZE (SOCKET_MAXUDPRECV - sizeof(VoipPacketHeadT) - sizeof(VoipMicrInfoT)) + +//! maximum ping payload size +#define VOIP_MAXPINGPKTSIZE (SOCKET_MAXUDPRECV - sizeof(VoipPacketHeadT) - 4 /* aRemoteClientId */ - 4 /* aChannelUserIndices */) + +//! VoIP Flags bit field included in VoIP packets +#define VOIP_PACKET_STATUS_FLAG_METADATA (1) //!< packet contains platform-specific metadata (unreliable) +#define VOIP_PACKET_STATUS_FLAG_STT (2) //!< at least one user on the originating side as requested transcribed text to be sent (Speech-to-text accessibility feature) +#define VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF (32) //!< packet contains ACK+ACKCNF protocol components (reliability mechanism) +#define VOIP_PACKET_RELIABLE_FLAG_DATA (64) //!< packet contains DATA protocol component (reliability mechanism) + +//! range of supported reliable data types +#define VOIP_RELIABLE_TYPE_USERADD (0) //!< reliable data contains newly added local user on the originating console (used for voip join-in-progress) +#define VOIP_RELIABLE_TYPE_USERREM (1) //!< reliable data contains newly removed local user on the originating console (used for voip leave-in-progress) +#define VOIP_RELIABLE_TYPE_OPAQUE (2) //!< reliable data contains platform-specific data blob that needs to be transmitted reliably +#define VOIP_RELIABLE_TYPE_TEXT (3) //!< reliable data contains transcribed text (Speech-to-Text) that needs to be sent reliably + +//! mask used to manipulate ACKCNF bit for uAckedSeq field of ReliableAckT type +#define VOIP_RELIABLE_ACKCNF_MASK (0x80) + + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ +//! VoIP data packet header +typedef struct VoipPacketHeadT +{ + uint8_t aType[3]; //!< type/version info + uint8_t uFlags; //!< flags + uint8_t aClientId[4]; //!< source clientId + uint8_t aSessionId[4]; //!< session ID + uint8_t aHeadsetStat[2]; //!< headset status bitfield (1 bit per local user) +} VoipPacketHeadT; + + +//! VoIP channel config +typedef struct VoipPacketChanT +{ + uint8_t aChannelId[4]; //!< packet channelId +} VoipPacketChanT; + +//! VoIP user packet +typedef struct VoipUserPacketT +{ + uint8_t aAccountId[8]; //!< the nucleus account id (unique per account) + uint8_t aPersonaId[8]; //!< the nucleus persona id (globally unique) + uint8_t aFlags[4]; //!< a bit field contaning VOIPUSER_FLAG_* + uint8_t aPlatform[4]; //!< what platform is this user running on +} VoipUserPacketT; + +//! VoIP connection packet +typedef struct VoipConnPacketT +{ + VoipPacketHeadT Head; + uint8_t aRemoteClientId[4]; + uint8_t bConnected; + uint8_t uNumLocalUsers; // this is the max number of users including the extended user + VoipUserPacketT LocalUsers[32]; +} VoipConnPacketT; + +//! VoIP ping packet +typedef struct VoipPingPacketT +{ + VoipPacketHeadT Head; + uint8_t aRemoteClientId[4]; + uint8_t aChannelUserIndices[4]; //!< a bitset that corresponds to the user indices for the channel data in aData + uint8_t aData[VOIP_MAXPINGPKTSIZE]; //!< ping packet data (this is where piggy-backed channel & reliable data is added when needed) +} VoipPingPacketT; + +//! VoIP disc packet +typedef struct VoipDiscPacketT +{ + VoipPacketHeadT Head; + uint8_t aRemoteClientId[4]; +} VoipDiscPacketT; + +//! VoIP micr packet info +typedef struct VoipMicrInfoT +{ + uint8_t aSendMask[4]; //!< lsb->msb, one bit per user, used for voip server + VoipPacketChanT channels; //!< packet channelId + uint8_t uSeqn; //!< voice packet sequence number + uint8_t uNumSubPackets; //!< current number of sub-packets packed in this packet + uint8_t uSubPacketSize; //!< size of sub-packets (same fixed-size for all sub-packets) + uint8_t uUserIndex; //!< index of user that generated all the sub-packets in this packet +} VoipMicrInfoT; + +//! VoIP data packet +typedef struct VoipMicrPacketT +{ + VoipPacketHeadT Head; //!< packet header + VoipMicrInfoT MicrInfo; //!< micr packet info + uint8_t aData[VOIP_MAXMICRPKTSIZE]; //!< voice packet data (this is where sub-packets are bundled) +} VoipMicrPacketT; + +//! VoipPacketBufferT: a buffer that can(does) contain any type of voip packet +typedef union VoipPacketBufferT +{ + VoipPacketHeadT VoipPacketHead; + VoipConnPacketT VoipConnPacket; + VoipPingPacketT VoipPingPacket; + VoipDiscPacketT VoipDiscPacket; + VoipMicrPacketT VoipMicrPacket; +} VoipPacketBufferT; + +//! reliable data info +typedef struct ReliableDataInfoT +{ + uint8_t aRemoteClientId[4]; //!< remote peer to which this data entry is for (required for 1x send to voipserver) + uint8_t uSeq; //!< sequence number + uint8_t uType; //!< type (VOIP_RELIABLE_TYPE_*) + uint8_t uSize[2]; //!< size of payload +} ReliableDataInfoT; + +//! reliable mechanism - ack entry +typedef struct ReliableAckT +{ + uint8_t aRemoteClientId[4]; //!< remote peer to which this ack entry is for + uint8_t uAckedSeq; //!< bit 7 = ACKCNF flag; bit 0 to 6 = [1,127] seq number (seq nb 0 is invalid - used when only the ACKCNF portion is valid) +} ReliableAckT; + +//! maximum size of a single reliable data entry (in bytes) is what can fit in a PING packet (taking into account that there can be up to 32 ReliableAckT entries before it) +//! assumption: very large reliable data entries may not fit in MIC packets (because of prefixed metadata) but will eventually be sent over a PING packet. +#define VOIP_MAXRELIABLEDATA (VOIP_MAXPINGPKTSIZE - (VOIP_MAXCONNECT * sizeof(ReliableAckT))) + +//! reliable mechanism - data entry +typedef struct ReliableDataT +{ + ReliableDataInfoT info; + uint8_t aData[VOIP_MAXRELIABLEDATA]; +} ReliableDataT; + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#endif // _voippacket_h + diff --git a/src/thirdparty/dirtysdk/source/voip/voippcm.c b/src/thirdparty/dirtysdk/source/voip/voippcm.c new file mode 100644 index 00000000..8569d5d9 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voippcm.c @@ -0,0 +1,300 @@ +/*H********************************************************************************/ +/*! + \File voippcm.c + + \Description + PCM codec (ie, pass-through). + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 08/04/2004 (jbrookes) First version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" + +#include "DirtySDK/voip/voipdef.h" +#include "voippriv.h" +#include "voipcommon.h" +#include "voippcm.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +typedef struct VoipPCMStateT +{ + VoipCodecRefT CodecState; + + int32_t iOutputVolume; +} VoipPCMStateT; + +/*** Function Prototypes **********************************************************/ + +static VoipCodecRefT *_VoipPCMCreate(int32_t iNumDecoders); +static void _VoipPCMDestroy(VoipCodecRefT *pState); +static int32_t _VoipPCMEncodeBlock(VoipCodecRefT *pState, uint8_t *pOut, const int16_t *pInp, int32_t iNumSamples); +static int32_t _VoipPCMDecodeBlock(VoipCodecRefT *pState, int32_t *pOut, const uint8_t *pInp, int32_t iInputBytes, int32_t iChannel); +static void _VoipPCMReset(VoipCodecRefT *pState); +static int32_t _VoipPCMControl(VoipCodecRefT *pState, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); +static int32_t _VoipPCMStatus(VoipCodecRefT *pCodecState, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize); + +/*** Variables ********************************************************************/ + +// Public variables + +const VoipCodecDefT VoipPCM_CodecDef = +{ + _VoipPCMCreate, + _VoipPCMDestroy, + _VoipPCMEncodeBlock, + _VoipPCMDecodeBlock, + _VoipPCMReset, + _VoipPCMControl, + _VoipPCMStatus, +}; + +/*** Private Functions ************************************************************/ + + +/*F*************************************************************************************************/ +/*! + \Function _VoipPCMCreate + + \Description + Create a PCM codec state. + + \Input iDecodeChannels - number of decoder channels + + \Output + VoipCodecStateT * - pointer to pcm codec state + + \Version 08/04/2004 (jbrookes) +*/ +/*************************************************************************************************F*/ +static VoipCodecRefT *_VoipPCMCreate(int32_t iDecodeChannels) +{ + VoipPCMStateT *pState; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + pState = (VoipPCMStateT *) DirtyMemAlloc (sizeof(*pState), VOIP_MEMID, iMemGroup, pMemGroupUserData); + pState->CodecState.pCodecDef = &VoipPCM_CodecDef; + pState->CodecState.iDecodeChannels = iDecodeChannels; + + // set the output level + pState->iOutputVolume = 1 << VOIP_CODEC_OUTPUT_FRACTIONAL; + + return(&pState->CodecState); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipPCMDestroy + + \Description + Destroy a PCM codec state. + + \Input *pState - state to destroy + + \Version 08/04/2004 (jbrookes) +*/ +/*************************************************************************************************F*/ +static void _VoipPCMDestroy(VoipCodecRefT *pState) +{ + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + DirtyMemFree(pState, VOIP_MEMID, iMemGroup, pMemGroupUserData); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipPCMEncodeBlock + + \Description + Encode a 16-bit linear PCM buffer. + + \Input *pState - pointer to encode state + \Input *pOut - pointer to output buffer + \Input *pInp - pointer to input buffer + \Input iNumSamples - number of samples to encode + + \Output int32_t - size of compressed data in bytes + + \Version 08/04/2004 (jbrookes) +*/ +/*************************************************************************************************F*/ +static int32_t _VoipPCMEncodeBlock(VoipCodecRefT *pState, uint8_t *pOut, const int16_t *pInp, int32_t iNumSamples) +{ + int32_t iCount; + uint8_t *pBeg = pOut; + + // copy data to output buffer + for (iCount = 0; iCount < iNumSamples; iCount += 1) + { + *pOut++ = (pInp[iCount] & 0xff00) >> 8; + *pOut++ = (pInp[iCount] & 0xff); + } + + // return the length + return(pOut - pBeg); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipPCMDecodeBlock + + \Description + Decode input to 16-bit linear PCM samples, and accumulate in the given output buffer. + + \Input *pState - pointer to decode state + \Input *pOut - pointer to output buffer + \Input *pInp - pointer to input buffer + \Input iInputBytes - size of input data + \Input iChannel - ignored + + \Output + int32_t - number of samples decoded + + \Version 08/04/2004 (jbrookes) +*/ +/*************************************************************************************************F*/ +static int32_t _VoipPCMDecodeBlock(VoipCodecRefT *pState, int32_t *pOut, const uint8_t *pInp, int32_t iInputBytes, int32_t iChannel) +{ + VoipPCMStateT *pPCMState = (VoipPCMStateT *)pState; + int32_t iCount, iSample; + + // decode input and add it to output + for (iCount = 0; iCount < iInputBytes/2; iCount++) + { + // read sample + iSample = *pInp++ << 8; + iSample |= *pInp++; + + // sign extend + iSample = (iSample << 16) >> 16; + + // write out with output scaling + pOut[iCount] += ((iSample * pPCMState->iOutputVolume) >> VOIP_CODEC_OUTPUT_FRACTIONAL); + } + + // return the number of samples decoded + return(iCount); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipPCMReset + + \Description + Resets codec state. + + \Input *pState - pointer to decode state + + \Version 08/04/2004 (jbrookes) +*/ +/*************************************************************************************************F*/ +static void _VoipPCMReset(VoipCodecRefT *pState) +{ + +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipPCMControl + + \Description + Modifies parameters of the codec + + \Input *pCodecState - pointer to decode state + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + 'plvl' - Set the output power level + \endverbatim + + \Version 03/12/2008 (grouse) +*/ +/*************************************************************************************************F*/ +static int32_t _VoipPCMControl(VoipCodecRefT *pCodecState, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + VoipPCMStateT *pState = (VoipPCMStateT *)pCodecState; + + if (iControl == 'plvl') + { + pState->iOutputVolume = iValue; + return(0); + } + NetPrintf(("voippcm: unhandled control selector '%C'\n", iControl)); + return(-1); +} + +/*F*************************************************************************************************/ +/*! + \Function _VoipPCMStatus + + \Description + Get codec status + + \Input *pCodecState - pointer to decode state + \Input iSelect - status selector + \Input iValue - selector-specific + \Input *pBuffer - [out] storage for selector output + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iSelect can be one of the following: + + \verbatim + 'fsiz' - size of encoder output / decoder input in bytes (iValue=samples per frame) + \endverbatim + + \Version 10/11/2011 (jbrookes) +*/ +/*************************************************************************************************F*/ +static int32_t _VoipPCMStatus(VoipCodecRefT *pCodecState, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize) +{ + VoipPCMStateT *pModuleState = (VoipPCMStateT *)pCodecState; + + // these options require module state + if (pModuleState != NULL) + { + if (iSelect == 'fsiz') + { + return(iValue*2); + } + } + NetPrintfVerbose((pModuleState->CodecState.iDebugLevel, 1, "voippcm: unhandled status selector '%C'\n", iSelect)); + return(-1); +} + diff --git a/src/thirdparty/dirtysdk/source/voip/voippcm.h b/src/thirdparty/dirtysdk/source/voip/voippcm.h new file mode 100644 index 00000000..9e2cddc5 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voippcm.h @@ -0,0 +1,42 @@ +/*H********************************************************************************/ +/*! + \File voippcm.h + + \Description + Table based 16:3 ADPCM compression originally based off EAC SIMEX code, + modified by Greg Schaefer. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 08/04/2004 (jbrookes) First version +*/ +/********************************************************************************H*/ + +#ifndef _voippcm_h +#define _voippcm_h + +/*** Include files ****************************************************************/ + +#include "DirtySDK/voip/voipcodec.h" + +/*** Defines **********************************************************************/ + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/*** Variables ********************************************************************/ +#ifdef __cplusplus +extern "C" { +#endif + +extern const VoipCodecDefT VoipPCM_CodecDef; + +#ifdef __cplusplus +}; +#endif + +/*** Functions ********************************************************************/ + +#endif // _voippcm_h diff --git a/src/thirdparty/dirtysdk/source/voip/voippriv.h b/src/thirdparty/dirtysdk/source/voip/voippriv.h new file mode 100644 index 00000000..a1323901 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voippriv.h @@ -0,0 +1,75 @@ +/*H********************************************************************************/ +/*! + \File voippriv.h + + \Description + Header for private VoIP functions. + + \Copyright + Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED. + + \Version 1.0 03/17/2004 (jbrookes) First Version + \Version 1.1 11/18/2008 (mclouatre) Adding user data concept to mem group support +*/ +/********************************************************************************H*/ + +#ifndef _voippriv_h +#define _voippriv_h + +/*** Include files ****************************************************************/ +#include "DirtySDK/dirtysock/netconn.h" + +/*** Defines **********************************************************************/ +#define VOIPUSER_FLAG_CROSSPLAY (1<<0) //!< is this voip user running in crossplay mode? + +// enumerate supported platforms +// always add new platforms at the end and never recycle values +typedef enum VoipPlatformTypeE +{ + VOIP_PLATFORM_LINUX = 1, // linux is a stub for the stress client + VOIP_PLATFORM_PC = 2, + VOIP_PLATFORM_PS4 = 3, + VOIP_PLATFORM_XBOXONE = 4 +} VoipPlatformTypeE; + +// used to identify our local platform +#if defined(DIRTYCODE_LINUX) +#define VOIP_LOCAL_PLATFORM VOIP_PLATFORM_LINUX +#elif defined(DIRTYCODE_PC) +#define VOIP_LOCAL_PLATFORM VOIP_PLATFORM_PC +#elif defined(DIRTYCODE_PS4) +#define VOIP_LOCAL_PLATFORM VOIP_PLATFORM_PS4 +#elif defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK) // $$todo$$ should we treat xbsx as a separate platform? +#define VOIP_LOCAL_PLATFORM VOIP_PLATFORM_XBOXONE +#endif + +/*** Macros ***********************************************************************/ + +//! copy VoipUserTs +#define VOIP_CopyUser(_pUser1, _pUser2) (ds_memcpy_s(_pUser1, sizeof(*_pUser1), _pUser2, sizeof(*_pUser2))) + +//! clear VoipUserT +#define VOIP_ClearUser(_pUser1) (ds_memclr(_pUser1, sizeof(*_pUser1))) + +//! return if VoipUserT is NULL or not +#define VOIP_NullUser(_pUser1) ((_pUser1)->AccountInfo.iPersonaId == 0) + +//! compare VoipUserTs +#define VOIP_SameUser(_pUser1, _pUser2) ((_pUser1)->AccountInfo.iPersonaId == (_pUser2)->AccountInfo.iPersonaId) + +/*** Type Definitions *************************************************************/ + +typedef struct VoipUserT +{ + NetConnAccountInfoT AccountInfo; //!< account info + uint32_t uFlags; //!< a bit field contaning VOIPUSER_FLAG_* + VoipPlatformTypeE ePlatform; //!< what platform is this user running on +} VoipUserT; + + +/*** Variables ********************************************************************/ + +/*** Functions ********************************************************************/ + +#endif // _voippriv_h + diff --git a/src/thirdparty/dirtysdk/source/voip/voiptranscribe.c b/src/thirdparty/dirtysdk/source/voip/voiptranscribe.c new file mode 100644 index 00000000..67685ba9 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voiptranscribe.c @@ -0,0 +1,3414 @@ +/*H********************************************************************************/ +/*! + \File voiptranscribe.c + + \Description + VoIP transcription API wrapping Cloud-based speech-to-text services, supporting + IBM Watson, Microsoft Speech Service, Google Speech, and Amazon Transcribe. + + \Notes + References + + Google Speech-to-Text: + Main page: https://cloud.google.com/speech-to-text/docs/ + REST API: https://cloud.google.com/speech-to-text/docs/reference/rest/ + gRPC API: https://cloud.google.com/speech-to-text/docs/reference/rpc/ + Protobuf definitions: https://github.com/googleapis/googleapis/blob/master/google/cloud/speech/v1/cloud_speech.proto + Audio Formats: https://cloud.google.com/speech-to-text/docs/reference/rest/v1/RecognitionConfig#AudioEncoding + + IBM Watson: + Speech to Text API: https://www.ibm.com/watson/developercloud/speech-to-text/api/v1/curl.html + HTTP interface: https://console.bluemix.net/docs/services/speech-to-text/http.html + WebSockets interface: https://console.bluemix.net/docs/services/speech-to-text/websockets.html + Audio Formats: https://console.bluemix.net/docs/services/speech-to-text/audio-formats.html + + Microsoft Speech Service: + Main page: https://docs.microsoft.com/en-us/azure/cognitive-services/Speech-Service/ + REST API: https://docs.microsoft.com/en-us/azure/cognitive-services/Speech-Service/rest-apis + Speech Service WebSocket protocol: https://docs.microsoft.com/en-us/azure/cognitive-services/speech/api-reference-rest/websocketprotocol + + Amazon Transcribe: + Main Page: https://docs.aws.amazon.com/transcribe/latest/dg/what-is-transcribe.html + Streaming Transcription: https://docs.aws.amazon.com/transcribe/latest/dg/streaming.html + StartStreamTranscription: https://docs.aws.amazon.com/transcribe/latest/dg/API_streaming_StartStreamTranscription.html + NOTE: Amazon public API documentation is as of this writing not fully correct; the streaming + format is completely different than what is described and there are other minor changes. + Streaming Format: https://docs.aws.amazon.com/transcribe/latest/dg/streaming-format.html + + Ogg/Opus: + Ogg file format: https://tools.ietf.org/html/rfc3533 + Ogg encapsulation for the Opus Audio Codec: https://tools.ietf.org/html/rfc7845.html + Definition of the Opus Audio Codec: https://tools.ietf.org/html/rfc6716 + + WAV: + WAVE file format: https://en.wikipedia.org/wiki/WAV#RIFF_WAVE + + \Copyright + Copyright 2018 Electronic Arts + + \Version 08/30/2018 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/util/aws.h" +#include "DirtySDK/util/base64.h" +#include "DirtySDK/util/jsonparse.h" + +#include "DirtySDK/proto/protohttp.h" +#include "DirtySDK/proto/protohttp2.h" +#include "DirtySDK/proto/protowebsocket.h" +#include "DirtySDK/proto/protossl.h" + +#include "DirtySDK/util/protobufcommon.h" +#include "DirtySDK/util/protobufread.h" +#include "DirtySDK/util/protobufwrite.h" + +#include "DirtySDK/crypt/cryptrand.h" //$$temp + +#include "DirtySDK/voip/voipdef.h" +#include "DirtySDK/voip/voiptranscribe.h" + +/*** Defines **********************************************************************/ + +#define VOIPTRANSCRIBE_MAXURL (1024) +#define VOIPTRANSCRIBE_MINBUFFER (8*32*1024) //!< buffering for up to eight seconds of uncompressed audio +#define VOIPTRANSCRIBE_SENDTIMEOUT (100) //!< milliseconds of silence audio before we consider an active recording sequence to be complete +#define VOIPTRANSCRIBE_WAIT (-100) //!< replaces PROTHTTP(2)_WAIT + +#define VOIPTRANSCRIBE_CONSECEMPTY (3) //!< default number of consecutive empty results before we backoff +#define VOIPTRANSCRIBE_CONSECERROR (3) //!< default number of consecutive request failures before we backoff + +#define VOIPTRANSCRIBE_AUDIORATE (16000) //!< audio rate in samples per second + +/*! maximum number of samples (eight seconds worth) we allow in a single request; we limit this in case a user's VAD + is not effective as well as to limit the size of the audio buffers required. if we don't break the requests up, + the user will wait indefinitely for a very long transcription result. if the user's microphone is too sensitive + and picking up music, background noise etc continuously, breaking up the requests will cause multiple transactions + with non-voice data to be sent, and will trigger backoff due to empty transcription results being received */ +#define VOIPTRANSCRIBE_MAXREQSAMPLES (8*VOIPTRANSCRIBE_AUDIORATE) + +#define OGG_HEAD_LENGTH (26) //!< header length +#define OGG_HEAD_TYPE_OFFSET (5) //!< offset of type within header +#define OGG_HEAD_GPOS_OFFSET (6) //!< offset of granule position within header + +#define OGG_PAGE_SEG_MAX (255) //!< maximum number of pages in an ogg segment table +#define OGG_PAGE_SEG_DEF (50) //!< 50 pages with each page being 20ms of audio equals one second of audio per page + +#define OGG_TYPE_DAT (0x00) //!< data page +#define OGG_TYPE_CNT (0x01) //!< continuation +#define OGG_TYPE_BOS (0x02) //!< beginning of stream +#define OGG_TYPE_EOS (0x04) //!< end of stream + +/*** Macros ***********************************************************************/ + +/*** Type Definitions *************************************************************/ + +/* + Transport stream API used to provide a single interface to HTTP, HTTP2, and WebSocket stream transport +*/ + +// forward declaration for transport type +typedef struct TransportT TransportT; + +// transport API +typedef void *(TransportCreate)(int32_t iBufSize); +typedef void (TransportDestroy)(void *pState); +typedef int32_t (TransportConnect)(void *pState, const char *pUrl); +typedef void (TransportDisconnect)(void *pState); +typedef void (TransportUpdate)(void *pState); +typedef int32_t (TransportRequest)(void *pState, const char *pUrl, const char *pBuffer, int32_t iLength, int32_t *pRequestId); +typedef int32_t (TransportSend)(void *pState, int32_t iRequestId, const char *pBuffer, int32_t iLength); +typedef int32_t (TransportRecv)(void *pState, int32_t iRequestId, char *pBuffer, int32_t iLength); +typedef int32_t (TransportStatus)(void *pState, int32_t iRequestId, int32_t iStatus, void *pBuffer, int32_t iBufSize); +typedef int32_t (TransportControl)(void *pState, int32_t iRequestId, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue); + +//! supported transport types +typedef enum TransportE +{ + TRANSPORT_HTTP = 0, + TRANSPORT_HTTP2, + TRANSPORT_WEBSOCKETS, + TRANSPORT_NUMPROTOCOLS +} TransportE; + +//! transport class +struct TransportT +{ + TransportE eTransport; + void *pState; + int32_t iStreamId; + TransportCreate *Create; + TransportDestroy *Destroy; + TransportConnect *Connect; + TransportDisconnect *Disconnect; + TransportUpdate *Update; + TransportRequest *Request; + TransportSend *Send; + TransportRecv *Recv; + TransportStatus *Status; + TransportControl *Control; +}; + +//! ogg file writer +typedef struct OggWriterT +{ + uint8_t *pBuffer; //!< ogg write buffer + uint8_t *pHeader; //!< pointer to current header + uint8_t *pChecksum; //!< pointer to current header checksum + uint8_t *pSegmentTable; //!< pointer to current segment table + uint64_t uGranulePos; //!< ogg granule position in units of 48khz audio samples + uint32_t uPageSeqn; //!< monotonically increasing page number + uint32_t uSerial; //!< stream serial number + int32_t iBufLen; //!< ogg buffer length + int32_t iBufOff; //!< ogg write offset + int32_t iBufAudioStart; //!< start of buffered audio + int32_t iNumSegments; //!< number of segments written to current Ogg page +} OggWriterT; + +/* + Voip transcription types +*/ + +//! buffer to hold audio data while it is being submitted +typedef struct VoipBufferT +{ + uint8_t *pBuffer; //!< buffer memory + int32_t iBufLen; //!< buffer length + int32_t iBufOff; //!< writing offset within buffer (buffering audio) + int32_t iBufInp; //!< reading offset within buffer (sending buffered audio) + int32_t iNumSamples; //!< number of samples in buffer + int8_t iBuffer; //!< buffer index + uint8_t bRecStarting; //!< TRUE if recording is starting + uint8_t bRecFinished; //!< TRUE if recording is finished + uint8_t bRecFull; //!< TRUE if recording buffer is full + uint8_t bMinDiscard; //!< minimum discard status for this buffer + uint8_t _pad[3]; + OggWriterT OggWriter; //!< ogg writer type, used for writing compressed audio +} VoipBufferT; + +typedef struct VoipTranscribeConfigT +{ + uint32_t uProfile; //!> transcription profile + char strUrl[VOIPTRANSCRIBE_MAXURL]; //!< url to access transcription service + char strKey[128]; //!< api key for access to transcription service +} VoipTranscribeConfigT; + +//! module state memory +struct VoipTranscribeRefT +{ + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + // module states + enum + { + ST_FAIL=-1, //!< fail + ST_IDLE, //!< idle + ST_CONN, //!< connecting + ST_SEND, //!< sending voice data + ST_RECV //!< receiving transcription result + } eState; + + int32_t iTimeout; //!< current http timeout + uint32_t uVoipTick; //!< timestamp when last voice sample was submitted + + uint32_t uProfile; //!> transcription profile + VoipTranscribeProviderE eProvider; //!< transcription provider + VoipTranscribeFormatE eFormat; //!< audio format + VoipTranscribeTransportE eTransport; //!< transport protocol + int32_t iAudioRate; //!< sampling rate of audio in hz + + char strUrl[VOIPTRANSCRIBE_MAXURL]; //!< url to access transcription service + char strKey[128]; //!< api key for access to transcription service + char strAudioFormat[64]; //!< current audio format e.g. audio/li16 + + int32_t iConsecErrorCt; //!< number of consecutive request failures + int32_t iConsecErrorMax; //!< maximum number of consecutive request failures before we backoff + int32_t iConsecEmptyCt; //!< number of consecutive empty request results + int32_t iConsecEmptyMax; //!< maximum number of consecutive empty request results before we backoff + uint32_t uBackoffTimer; //!< millisecond counter tracking when current backoff time expires + + uint8_t bConnected; //!< connected to transcription service + uint8_t bCompressed; //!< TRUE if transcribing compressed audio, else FALSE + uint8_t bMinDiscard; //!< true if minimum voice sample discard enabled + int8_t iRecBuffer; //!< current recording buffer + int8_t iSndBuffer; //!< current sending buffer + int8_t iVerbose; //!< verbose debug level (debug only) + uint8_t _pad[2]; + + AWSSignInfoT AWSSignInfo; //!< AWS Signing info for current request + + TransportT Transport; //!< transport object + VoipSpeechToTextMetricsT Metrics; //!< metrics object + uint32_t uSttStartTime; //!< time we finished sending the request + + uint16_t aJsonParseBuf[2*1024]; //!< buffer to parse JSON results + char strResponse[16*1024*4]; //!< transcription server response $$temp - increased 4x for large Amazon partial results + char strTranscription[VOIPTRANSCRIBE_OUTPUT_MAX]; //!< transcription result + char strSessionId[128]; //!< AWS session id + + VoipBufferT VoipBuffer[2]; //!< voip audio double buffer + VoipBufferT VoipBufferSnd; //!< voip send buffer (amazon & google need audio encoded) +}; + +/*** Variables ********************************************************************/ + +//! global config state +static VoipTranscribeConfigT _VoipTranscribe_Config = { VOIPTRANSCRIBE_PROFILE_DISABLED, "", "" }; + +//! table for calculating Ogg CRC checksum +static const uint32_t _Ogg_CRCTable[256] = +{ + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +//! Ogg Opus Identity Header +static const uint8_t _aOggOpusIdentHeader[] = +{ + // magic numbers + 'O', 'p', 'u', 's', + 'H', 'e', 'a', 'd', + 1, // version + 1, // output channel count + 0, 0, // pre-skip + 0x80, 0x3e, 0x00, 0x00, // input sample rate; 16khz + 0, 0, // output gain + 0, // output channel mapping +}; + +//! Ogg Opus Comment Header +static const uint8_t _aOggOpusCommentHeader[] = +{ + // magic numbers + 'O', 'p', 'u', 's', + 'T', 'a', 'g', 's', + // vendor string length + 2, 0, 0, 0, + // vendor string + 'E', 'A', + // user comment list length + 0, 0, 0, 0 +}; + +//! Baltimore Cybertrust Root CA, needed for Microsoft Speech +static const char _strCyberTrustRootCA[] = +{ + "-----BEGIN CERTIFICATE-----" + "MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ" + "RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD" + "VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX" + "DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y" + "ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy" + "VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr" + "mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr" + "IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK" + "mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu" + "XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy" + "dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye" + "jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1" + "BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3" + "DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92" + "9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx" + "jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0" + "Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz" + "ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS" + "R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp" + "-----END CERTIFICATE-----" +}; + +//! GlobalSign Root CA R2, needed for Google +static const char _strGlobalSignRootCAR2[] = +{ + "-----BEGIN CERTIFICATE-----" + "MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G" + "A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp" + "Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1" + "MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG" + "A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI" + "hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL" + "v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8" + "eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq" + "tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd" + "C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa" + "zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB" + "mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH" + "V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n" + "bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG" + "3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs" + "J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO" + "291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS" + "ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd" + "AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7" + "TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==" + "-----END CERTIFICATE-----" +}; + +//! Amazon Root CA R1, needed for Amazon Transcribe +static const char _strAmazonRootCAR1[] = +{ + "-----BEGIN CERTIFICATE-----" + "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF" + "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6" + "b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL" + "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv" + "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj" + "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM" + "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw" + "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6" + "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L" + "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm" + "jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC" + "AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA" + "A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI" + "U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs" + "N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv" + "o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU" + "5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy" + "rqXRfboQnoZsG4q5WTP468SQvvG5" + "-----END CERTIFICATE-----" +}; + + +/*** Private Functions ************************************************************/ + +/* + Transport wrapper class providing protocol-agnostic API for using HTTP, HTTP2, or WebSockets for stream transport +*/ + +/* + TransportRequest +*/ +static int32_t _TransportHttpRequest(void *pProtoHttp, const char *pUrl, const char *pBuffer, int32_t iLength, int32_t *pRequestId) +{ + return(ProtoHttpPost(pProtoHttp, pUrl, (iLength != PROTOHTTP_STREAM_BEGIN) ? pBuffer : NULL, iLength, FALSE)); +} + +static int32_t _TransportHttp2Request(void *pProtoHttp2, const char *pUrl, const char *pBuffer, int32_t iLength, int32_t *pRequestId) +{ + return(ProtoHttp2Request(pProtoHttp2, pUrl, NULL, PROTOHTTP2_STREAM_BEGIN, PROTOHTTP_REQUESTTYPE_POST, pRequestId)); +} + +static int32_t _TransportWebSocketRequest(void *pState, const char *pUrl, const char *pBuffer, int32_t iLength, int32_t *pRequestId) +{ + return(ProtoWebSocketSendText(pState, pBuffer)); +} + +/* + TransportSend +*/ +static int32_t _TransportHttpSend(void *pState, int32_t iRequestId, const char *pBuffer, int32_t iLength) +{ + return(ProtoHttpSend(pState, pBuffer, iLength)); +} + +static int32_t _TransportHttp2Send(void *pState, int32_t iRequestId, const char *pBuffer, int32_t iLength) +{ + int32_t iResult = ProtoHttp2Send(pState, iRequestId, (const uint8_t *)pBuffer, iLength); + return(iResult); +} + +static int32_t _TransportWebSocketSend(void *pState, int32_t iRequestId, const char *pBuffer, int32_t iLength) +{ + return(ProtoWebSocketSend(pState, pBuffer, iLength)); +} + +/* + TransportRecv - note, a zero result returned by one of these functions indicates completion with no data +*/ +static int32_t _TransportHttpRecv(void *pState, int32_t iRequestId, char *pBuffer, int32_t iLength) +{ + int32_t iResult = ProtoHttpRecvAll(pState, pBuffer, iLength); + return((iResult != PROTOHTTP_RECVWAIT) ? iResult : VOIPTRANSCRIBE_WAIT); +} + +static int32_t _TransportHttp2Recv(void *pState, int32_t iRequestId, char *pBuffer, int32_t iLength) +{ + int32_t iResult = ProtoHttp2RecvAll(pState, iRequestId, (uint8_t *)pBuffer, iLength); + return((iResult != PROTOHTTP2_RECVWAIT) ? iResult : VOIPTRANSCRIBE_WAIT); +} + +static int32_t _TransportWebSocketRecv(void *pState, int32_t iRequestId, char *pBuffer, int32_t iLength) +{ + int32_t iResult = ProtoWebSocketRecv(pState, pBuffer, iLength); + return((iResult != 0) ? iResult : VOIPTRANSCRIBE_WAIT); +} + +/* + TransportStatus +*/ +static int32_t _TransportHttpStatus(void *pState, int32_t iRequestId, int32_t iStatus, void *pBuffer, int32_t iBufSize) +{ + return(ProtoHttpStatus(pState, iStatus, pBuffer, iBufSize)); +} + +static int32_t _TransportWebSocketStatus(void *pState, int32_t iRequestId, int32_t iStatus, void *pBuffer, int32_t iBufSize) +{ + return(ProtoWebSocketStatus(pState, iStatus, pBuffer, iBufSize)); +} + +/* + TransportControl +*/ +static int32_t _TransportHttpControl(void *pState, int32_t iRequestId, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + return(ProtoHttpControl(pState, iControl, iValue, iValue2, pValue)); +} + +static int32_t _TransportWebSocketControl(void *pState, int32_t iRequestId, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + return(ProtoWebSocketControl(pState, iControl, iValue, iValue2, pValue)); +} + + +/*F********************************************************************************/ +/*! + \Function _TransportInit + + \Description + Init Transport handler for specified transport method + + \Input *pTransport - transport handler structure + \Input eTransport - transport handler type + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _TransportInit(TransportT *pTransport, TransportE eTransport) +{ + pTransport->eTransport = eTransport; + switch (eTransport) + { + case TRANSPORT_HTTP: + pTransport->Create = (TransportCreate *)ProtoHttpCreate; + pTransport->Destroy = (TransportDestroy *)ProtoHttpDestroy; + pTransport->Connect = NULL; + pTransport->Disconnect = NULL; + pTransport->Update = (TransportUpdate *)ProtoHttpUpdate; + pTransport->Request = _TransportHttpRequest; + pTransport->Send = _TransportHttpSend; + pTransport->Recv = _TransportHttpRecv; + pTransport->Status = _TransportHttpStatus; + pTransport->Control = _TransportHttpControl; + break; + case TRANSPORT_HTTP2: + pTransport->Create = (TransportCreate *)ProtoHttp2Create; + pTransport->Destroy = (TransportDestroy *)ProtoHttp2Destroy; + pTransport->Connect = NULL; + pTransport->Disconnect = NULL; + pTransport->Update = (TransportUpdate *)ProtoHttp2Update; + pTransport->Request = _TransportHttp2Request; + pTransport->Send = _TransportHttp2Send; + pTransport->Recv = _TransportHttp2Recv; + pTransport->Status = (TransportStatus *)ProtoHttp2Status; + pTransport->Control = (TransportControl *)ProtoHttp2Control; + break; + case TRANSPORT_WEBSOCKETS: + pTransport->Create = (TransportCreate *)ProtoWebSocketCreate; + pTransport->Destroy = (TransportDestroy *)ProtoWebSocketDestroy; + pTransport->Connect = (TransportConnect *)ProtoWebSocketConnect; + pTransport->Disconnect = (TransportDisconnect *)ProtoWebSocketDisconnect; + pTransport->Update = (TransportUpdate *)ProtoWebSocketUpdate; + pTransport->Request = _TransportWebSocketRequest; + pTransport->Send = _TransportWebSocketSend; + pTransport->Recv = _TransportWebSocketRecv; + pTransport->Status = _TransportWebSocketStatus; + pTransport->Control = _TransportWebSocketControl; + break; + default: + NetPrintf(("transport: init error\n")); + break; + } +} + +/* + Misc functions we may need for Microsoft when using WebSockets +*/ + +/*F********************************************************************************/ +/*! + \Function _GenerateUUID + + \Description + Generate a type Version 4 UUID as per + https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random) + + \Input *pBuffer - [out] storage for UUID + \Input iBufLen - buffer length + \Input bDashes - include dashes if true + + \Version 09/15/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _GenerateUUID(char *pBuffer, int32_t iBufLen, uint8_t bDashes) +{ + uint32_t uRand[4]; + +#if 0 + // generate 128 bits of vaguely random data + int32_t iRand; + for (iRand = 0; iRand < 4; iRand += 1) + { + uRand[iRand] = NetRand(0xffffffff); + } +#else + CryptRandGet((uint8_t *)uRand, sizeof(uRand)); +#endif + + /* fixup: set the four most significant bits of the 7th byte to 0100'B, so the high nibble is "4" + set the two most significant bits of the 9th byte to 10'B, so the high nibble will be one of "8", "9", "A", or "B". */ + uRand[1] &= ~0xf000; + uRand[1] |= 0x4000; + uRand[2] &= ~0xc0000000; + uRand[2] |= 0x80000000; + + // format it out + if (bDashes) + { + ds_snzprintf(pBuffer, iBufLen, "%08x-%04x-%04x-%04x-%04x%0x", uRand[0], uRand[1]>>16, uRand[1]&0xff, uRand[2]>>16, uRand[2]&0xff, uRand[3]); + } + else + { + ds_snzprintf(pBuffer, iBufLen, "%08x%08x%08x%08x", uRand[0], uRand[1], uRand[2], uRand[3]); + } +} + +/*F********************************************************************************/ +/*! + \Function _GenerateTimestamp + + \Description + Generate a timestamp following 8601 format plus milliseconds + + \Input *pBuffer - [out] storage for timestamp + \Input iBufLen - buffer length + + \Version 09/15/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _GenerateTimestamp(char *pBuffer, int32_t iBufLen) +{ + struct tm CurTime; + char strMillis[8]; + int32_t iMillis; + // get current time... this is equivalent to time(0) + //$$todo - make sure this is UTC + NetPlattimeToTimeMs(&CurTime, &iMillis); + // convert to ISO_8601 + ds_timetostr(&CurTime, TIMETOSTRING_CONVERSION_ISO_8601, 1, pBuffer, iBufLen); + // append milliseconds + ds_snzprintf(strMillis, sizeof(strMillis), "%d", iMillis); + ds_strnzcat(pBuffer, strMillis, iBufLen); +} + +/* + Wave functions to encapsulate our PCM16 audio in a WAV header +*/ + +/*F********************************************************************************/ +/*! + \Function _WaveWriteHeader + + \Description + Write WAV header into output buffer + + \Input *pBuffer - [out] buffer to write header to + \Input iBufLen - length of buffer + \Input iAudioRate - audio rate in hz + \Input iDataSize - size of all data (audio+headers) + + \Output + int32_t - size of header + + \Version 09/13/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _WaveWriteHeader(uint8_t *pBuffer, int32_t iBufLen, int32_t iAudioRate, int32_t iDataSize) +{ + int32_t iOffset=0, iSize; + static const int32_t _Wav_iFmtLen = 4+4+2+2+4+4+2+4; //! wav format chunk length + + // write group id + pBuffer[iOffset++] = 'R'; + pBuffer[iOffset++] = 'I'; + pBuffer[iOffset++] = 'F'; + pBuffer[iOffset++] = 'F'; + // write total length as counted after size field + iSize = iDataSize-iOffset-4; + pBuffer[iOffset++] = (uint8_t)(iSize); + pBuffer[iOffset++] = (uint8_t)(iSize>>8); + pBuffer[iOffset++] = (uint8_t)(iSize>>16); + pBuffer[iOffset++] = (uint8_t)(iSize>>24); + // write RIFF type + pBuffer[iOffset++] = 'W'; + pBuffer[iOffset++] = 'A'; + pBuffer[iOffset++] = 'V'; + pBuffer[iOffset++] = 'E'; + + // write format chunk + + // format group id + pBuffer[iOffset++] = 'f'; + pBuffer[iOffset++] = 'm'; + pBuffer[iOffset++] = 't'; + pBuffer[iOffset++] = ' '; + // write chunk size as counted after size field + iSize = _Wav_iFmtLen-8; + pBuffer[iOffset++] = (uint8_t)(iSize); + pBuffer[iOffset++] = (uint8_t)(iSize>>8); + pBuffer[iOffset++] = (uint8_t)(iSize>>16); + pBuffer[iOffset++] = (uint8_t)(iSize>>24); + // format tag (16 bit, always 1) + pBuffer[iOffset++] = 1; + pBuffer[iOffset++] = 0; + // channels (16 bit) + pBuffer[iOffset++] = 1; + pBuffer[iOffset++] = 0; + // sampling rate + pBuffer[iOffset++] = (uint8_t)(iAudioRate); + pBuffer[iOffset++] = (uint8_t)(iAudioRate>>8); + pBuffer[iOffset++] = (uint8_t)(iAudioRate>>16); + pBuffer[iOffset++] = (uint8_t)(iAudioRate>>24); + // average bytes per second - rate x 2 + pBuffer[iOffset++] = (uint8_t)(iAudioRate*2); + pBuffer[iOffset++] = (uint8_t)((iAudioRate*2)>>8); + pBuffer[iOffset++] = (uint8_t)((iAudioRate*2)>>16); + pBuffer[iOffset++] = (uint8_t)((iAudioRate*2)>>24); + // block alignment (bytes per sample) + pBuffer[iOffset++] = 2; + pBuffer[iOffset++] = 0; + // bits per sample + pBuffer[iOffset++] = 0x10; + pBuffer[iOffset++] = 0; + pBuffer[iOffset++] = 0; + pBuffer[iOffset++] = 0; + + // write data chunk + pBuffer[iOffset++] = 'd'; + pBuffer[iOffset++] = 'a'; + pBuffer[iOffset++] = 't'; + pBuffer[iOffset++] = 'a'; + // write data size + iSize = iDataSize-iOffset-4; + pBuffer[iOffset++] = (uint8_t)(iSize); + pBuffer[iOffset++] = (uint8_t)(iSize>>8); + pBuffer[iOffset++] = (uint8_t)(iSize>>16); + pBuffer[iOffset++] = (uint8_t)(iSize>>24); + + // return header offset + return(iOffset); +} + +/*F********************************************************************************/ +/*! + \Function _WaveWriteOpen + + \Description + Writes required WAV header and returns offset to the start of data. + + \Input *pVoipBuffer - voip buffer to write header to + \Input iAudioRate - audio rate in hz + + \Output + int32_t - offset to end of data in buffer + + \Version 09/13/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _WaveWriteOpen(VoipBufferT *pVoipBuffer, int32_t iAudioRate) +{ + // we write an arbitrarily large size here as we don't know the data size in advance; both microsoft and watson support this + return(_WaveWriteHeader(pVoipBuffer->pBuffer, pVoipBuffer->iBufLen, iAudioRate, 1024*1024)); +} + +/* + Ogg/Opus functions to encapsulate our Opus codec data in the proper format for upload +*/ + +/*F********************************************************************************/ +/*! + \Function _OggWriteChecksum + + \Description + Calculate Ogg CRC32 on specified data, and write to output buffer in + little-endian + + \Input *pChecksum - [out] output buffer for crc32 checksum + \Input *pBuffer - data to checksum + \Input iBufSize - amount of data to checksum + + \Version 09/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _OggWriteChecksum(uint8_t *pChecksum, const uint8_t *pBuffer, int32_t iBufSize) +{ + uint32_t uChecksum; + int32_t iByte; + + // calculate crc32 + for (iByte = 0, uChecksum = 0; iByte < iBufSize; iByte += 1) + { + uChecksum = (uChecksum<<8)^_Ogg_CRCTable[((uChecksum>>24)&0xff)^pBuffer[iByte]]; + } + + // write crc32 + pChecksum[0] = (uint8_t)(uChecksum); + pChecksum[1] = (uint8_t)(uChecksum>>8); + pChecksum[2] = (uint8_t)(uChecksum>>16); + pChecksum[3] = (uint8_t)(uChecksum>>24); +} + +/*F********************************************************************************/ +/*! + \Function _OggWriteGranulePosition + + \Description + Write granule position to buffer + + \Input *pBuffer - [out] pointer to write location + \Input uGranulePos - granule position to write + + \Version 09/13/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _OggWriteGranulePosition(uint8_t *pBuffer, uint64_t uGranulePos) +{ + pBuffer[0] = (uint8_t)(uGranulePos); + pBuffer[1] = (uint8_t)(uGranulePos>>8); + pBuffer[2] = (uint8_t)(uGranulePos>>16); + pBuffer[3] = (uint8_t)(uGranulePos>>24); + pBuffer[4] = (uint8_t)(uGranulePos>>32); + pBuffer[5] = (uint8_t)(uGranulePos>>40); + pBuffer[6] = (uint8_t)(uGranulePos>>48); + pBuffer[7] = (uint8_t)(uGranulePos>>56); +} + +/*F********************************************************************************/ +/*! + \Function _OggWriteHeader + + \Description + Write Ogg header into output buffer + + \Input *pWriter - ogg writer + \Input *pBuffer - [out] buffer to write header to + \Input iHeaderOffset - offset to write header within buffer + \Input uType - page type (OGG_TYPE_*) + \Input iNumSegments - number of segments in segment table + \Input iDataSize - size of page data, or zero if it is not yet known + + \Output + int32_t - offset past end of header + + \Version 09/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _OggWriteHeader(OggWriterT *pWriter, uint8_t *pBuffer, int32_t iHeaderOffset, uint8_t uType, int32_t iNumSegments, int32_t iDataSize) +{ + int32_t iOffset = iHeaderOffset; + + // save pointer to current header + pWriter->pHeader = pBuffer+iHeaderOffset; + + // write capture pattern + pBuffer[iOffset++] = 'O'; + pBuffer[iOffset++] = 'g'; + pBuffer[iOffset++] = 'g'; + pBuffer[iOffset++] = 'S'; + // write version (always zero) + pBuffer[iOffset++] = 0; + // write header type - 1=continuation, 2=beginning of stream, 4=end of stream + pBuffer[iOffset++] = uType; + // reserve space for granule position + ds_memclr(pBuffer+iOffset, 8); + iOffset += 8; + // write bitstream serial number (32 bit) + pBuffer[iOffset++] = (uint8_t)(pWriter->uSerial); + pBuffer[iOffset++] = (uint8_t)(pWriter->uSerial>>8); + pBuffer[iOffset++] = (uint8_t)(pWriter->uSerial>>16); + pBuffer[iOffset++] = (uint8_t)(pWriter->uSerial>>24); + // write page sequence number + pBuffer[iOffset++] = (uint8_t)(pWriter->uPageSeqn); + pBuffer[iOffset++] = (uint8_t)(pWriter->uPageSeqn>>8); + pBuffer[iOffset++] = (uint8_t)(pWriter->uPageSeqn>>16); + pBuffer[iOffset++] = (uint8_t)(pWriter->uPageSeqn>>24); + pWriter->uPageSeqn += 1; + // write blank 32 bit checksum and save pointer to it + pWriter->pChecksum = pBuffer+iOffset; + pBuffer[iOffset++] = 0; + pBuffer[iOffset++] = 0; + pBuffer[iOffset++] = 0; + pBuffer[iOffset++] = 0; + // write page segments count + pBuffer[iOffset++] = iNumSegments; + // save segment table pointer + pWriter->pSegmentTable = pBuffer+iOffset; + // copy in page segments or reserve space if no segment table included + if (iNumSegments == 1) + { + pBuffer[iOffset] = (uint8_t)iDataSize; + } + else + { + ds_memclr(pBuffer+iOffset, iNumSegments); //$$temp - for debugging, doesn't really need to be cleared + } + // move offset past segment table + iOffset += iNumSegments; + + // return offset past header + return(iOffset); +} + +/*F********************************************************************************/ +/*! + \Function _OggWriteOpen + + \Description + Set up a buffer for writing Ogg-encapsulated data + + \Input *pWriter - ogg writer + \Input *pBuffer - buffer data will be written to + \Input *iBufLen - length of buffer + + \Output + int32_t - offset in buffer where audio data writing starts + + \Version 09/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _OggWriteOpen(OggWriterT *pWriter, uint8_t *pBuffer, int32_t iBufLen) +{ + // reset ogg segment counter + pWriter->iNumSegments = 0; + // set ogg buffer info + pWriter->pBuffer = pBuffer; + pWriter->iBufOff = 0; + pWriter->iBufLen = iBufLen; + // set bitstream serial number + pWriter->uSerial = NetRand(0xffffffff); + // reset page sequence + pWriter->uPageSeqn = 0; + // return offset to caller + return(pWriter->iBufOff); +} + +/*F********************************************************************************/ +/*! + \Function _OggWriteSegment + + \Description + Write an Opus audio segment to the current page + + \Input *pWriter - ogg writer + \Input *pData - opus data + \Input *iDataLen - length of opus data + \Input iVerbose - debug verbosity level + + \Output + int32_t - negative=buffer full, positive=updated offset in bytes if page is complete, else zero + + \Version 09/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _OggWriteSegment(OggWriterT *pWriter, const uint8_t *pData, int32_t iDataLen, int32_t iVerbose) +{ + // if we're at the beginning of a page, write the header + if (pWriter->iNumSegments == 0) + { + pWriter->iBufOff = _OggWriteHeader(pWriter, pWriter->pBuffer, pWriter->iBufOff, 0, OGG_PAGE_SEG_DEF, 0); + } + // bail if we don't have room for the segment + if ((pWriter->iBufOff+iDataLen) > pWriter->iBufLen) + { + NetPrintfVerbose((iVerbose, 1, "voiptranscribe: ogg/opus writer full\n")); + return(-1); + } + // copy data to buffer + ds_memcpy_s(pWriter->pBuffer+pWriter->iBufOff, pWriter->iBufLen-pWriter->iBufOff, pData, iDataLen); + pWriter->iBufOff += iDataLen; + // add to segment table + pWriter->pSegmentTable[pWriter->iNumSegments++] = (uint8_t)iDataLen; + // add to granule position + pWriter->uGranulePos += 960; //$$temp - assume 20ms audio == 960 samples @48khz + // if we've filled up the page, calculate the CRC and reset for the new page + if (pWriter->iNumSegments == OGG_PAGE_SEG_DEF) + { + NetPrintfVerbose((iVerbose, 1, "voiptranscribe: wrote ogg/opus page with %d segments and length %d\n", pWriter->iNumSegments, pWriter->pBuffer+pWriter->iBufOff-pWriter->pHeader)); + // write updated granule position + _OggWriteGranulePosition(pWriter->pHeader+OGG_HEAD_GPOS_OFFSET, pWriter->uGranulePos); + // calculate the crc32 checksum + _OggWriteChecksum(pWriter->pChecksum, pWriter->pHeader, pWriter->pBuffer+pWriter->iBufOff-pWriter->pHeader); + // reset segment count + pWriter->iNumSegments = 0; + // return updated offset to caller, only once we've finalized the page + return(pWriter->iBufOff); + } + // return zero for unfinalized page + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _OggOpusWriteHeader + + \Description + Write an ogg/opus header + + \Input *pWriter - ogg writer + \Input *pBuffer - buffer data will be written to + \Input iOffset - offset to write header + \Input iBufLen - length of buffer + \Input uType - header type (OGG_TYPE_*) + \Input *pOpusHeader - pointer to body of header we are writing + \Input iOpusHeaderLen - size of body we're writing + + \Output + int32_t - offset in buffer following header + + \Version 09/13/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _OggOpusWriteHeader(OggWriterT *pWriter, uint8_t *pBuffer, int32_t iOffset, int32_t iBufLen, uint8_t uType, const uint8_t *pOpusHeader, int32_t iOpusHeaderLen) +{ + int32_t iHeaderOffset = iOffset; + // write the header + iOffset = _OggWriteHeader(pWriter, pWriter->pBuffer, iOffset, uType, 1, iOpusHeaderLen); + // copy the data + ds_memcpy_s(pWriter->pBuffer+iOffset, pWriter->iBufLen-iOffset, pOpusHeader, iOpusHeaderLen); + iOffset += iOpusHeaderLen; + // calculate the crc32 checksum + _OggWriteChecksum(pWriter->pChecksum, pBuffer+iHeaderOffset, iOffset-iHeaderOffset); + // return offset to caller + return(iOffset); +} + +/*F********************************************************************************/ +/*! + \Function _OggOpusWriteOpen + + \Description + Open an Ogg/Opus header for writing as per + https://tools.ietf.org/html/rfc7845.html#section-5.1 + + \Input *pWriter - ogg writer + \Input *pBuffer - buffer data will be written to + \Input *iBufLen - length of buffer + + \Output + int32_t - offset in buffer where audio data writing starts + + \Version 09/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _OggOpusWriteOpen(OggWriterT *pWriter, uint8_t *pBuffer, int32_t iBufLen) +{ + // initialize for writing + pWriter->iBufOff = _OggWriteOpen(pWriter, pBuffer, iBufLen); + // write Ogg Opus Ident Header + pWriter->iBufOff = _OggOpusWriteHeader(pWriter, pWriter->pBuffer, pWriter->iBufOff, pWriter->iBufLen, OGG_TYPE_BOS, _aOggOpusIdentHeader, sizeof(_aOggOpusIdentHeader)); + // write Ogg Opus Comment Header + pWriter->iBufOff = _OggOpusWriteHeader(pWriter, pWriter->pBuffer, pWriter->iBufOff, pWriter->iBufLen, OGG_TYPE_DAT, _aOggOpusCommentHeader, sizeof(_aOggOpusCommentHeader)); + // return offset to caller + return(pWriter->iBufOff); +} + +/*F********************************************************************************/ +/*! + \Function _OggOpusWriteFinish + + \Description + Fixes up final page and marks it as end of stream. + + \Input *pWriter - ogg writer + \Input iVerbose - debug verbosity level + + \Output + int32_t - offset to end of data + + \Version 09/12/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _OggOpusWriteFinish(OggWriterT *pWriter, int32_t iVerbose) +{ + // if we have a partially-filled page, finish it here + if (pWriter->iNumSegments > 0) + { + int32_t iEmptySegments = OGG_PAGE_SEG_DEF-pWriter->iNumSegments; + int32_t iMoveSize = (pWriter->pBuffer+pWriter->iBufOff) - (pWriter->pSegmentTable+OGG_PAGE_SEG_DEF); + // contract to remove unwritten segment table entries + memmove(pWriter->pSegmentTable+pWriter->iNumSegments, pWriter->pSegmentTable+OGG_PAGE_SEG_DEF, iMoveSize); + pWriter->iBufOff -= iEmptySegments; + NetPrintfVerbose((iVerbose, 1, "voiptranscribe: wrote ogg/opus page with %d segments and length %d\n", pWriter->iNumSegments, pWriter->pBuffer+pWriter->iBufOff-pWriter->pHeader)); + // update segment count + pWriter->pHeader[OGG_HEAD_LENGTH] = (uint8_t)pWriter->iNumSegments; + // update granule position + _OggWriteGranulePosition(pWriter->pHeader+OGG_HEAD_GPOS_OFFSET, pWriter->uGranulePos); + // write CRC for last page + _OggWriteChecksum(pWriter->pChecksum, pWriter->pHeader, pWriter->pBuffer+pWriter->iBufOff-pWriter->pHeader); + // reset segment count + pWriter->iNumSegments = 0; + } + // mark final page as end of stream + pWriter->pHeader[OGG_HEAD_TYPE_OFFSET] = OGG_TYPE_EOS; + + // return offset to start of data + return(pWriter->iBufOff); +} + +/* + Voip Transcription +*/ + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeBufferReset + + \Description + Reset voip buffer state + + \Input *pVoipTranscribe - module state + \Input *pVoipBuffer - buffer to initialize + + \Version 10/22/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipTranscribeBufferReset(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBuffer) +{ + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: [%d] resetting buffer\n", pVoipBuffer->iBuffer)); + pVoipBuffer->iBufOff = 0; + pVoipBuffer->iBufInp = 0; + pVoipBuffer->iNumSamples = 0; + pVoipBuffer->bRecStarting = TRUE; + pVoipBuffer->bRecFinished = FALSE; + pVoipBuffer->bRecFull = FALSE; + pVoipBuffer->bMinDiscard = TRUE; +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeBufferInit + + \Description + Allocate and initialize voip buffer + + \Input *pVoipTranscribe - module state + \Input *pVoipBuffer - buffer to initialize + \Input iBufSize - size of streaming buffer + \Input iBuffer - buffer index to set up + + \Output + int32_t - zero=failure, else success + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeBufferInit(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBuffer, int32_t iBufSize, int32_t iBuffer) +{ + pVoipBuffer->iBuffer = iBuffer; + pVoipBuffer->iBufLen = iBufSize; + _VoipTranscribeBufferReset(pVoipTranscribe, pVoipBuffer); + return((pVoipBuffer->pBuffer = DirtyMemAlloc(iBufSize, VOIPTRANSCRIBE_MEMID, pVoipTranscribe->iMemGroup, pVoipTranscribe->pMemGroupUserData)) != NULL); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeCustomHeaderCb + + \Description + Custom header callback used to sign AWS requests + + \Input *pState - http module state + \Input *pHeader - pointer to http header buffer + \Input uHeaderSize - size of http header buffer + \Input *pData - pointer to data (unused) + \Input iDataLen - data length (unused) + \Input *pUserRef - voiptranscribe ref + + \Output + int32_t - output header length + + \Version 12/28/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeCustomHeaderCb(ProtoHttp2RefT *pState, char *pHeader, uint32_t uHeaderSize, const uint8_t *pData, int64_t iDataLen, void *pUserRef) +{ + VoipTranscribeRefT *pVoipTranscribe = (VoipTranscribeRefT *)pUserRef; + int32_t iHdrLen = (int32_t)strlen(pHeader); + + // if we have room, sign the request + if (uHeaderSize < (unsigned)iHdrLen) + { + return(iHdrLen); + } + + // sign the request and return the updated size + iHdrLen += AWSSignSigV4(pHeader, uHeaderSize, "", pVoipTranscribe->strKey, "transcribe", &pVoipTranscribe->AWSSignInfo); + // return size to protohttp + return(iHdrLen); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeTransportInit + + \Description + Init transport module + + \Input *pVoipTranscribe - pointer to module state + + \Output + int32_t - negative=failure, else success + + \Version 09/17/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeTransportInit(VoipTranscribeRefT *pVoipTranscribe) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + + // init transport class + pVoipTranscribe->eTransport = VOIPTRANSCRIBE_PROFILE_TRANSPORT(pVoipTranscribe->uProfile); + if (pVoipTranscribe->eTransport == VOIPTRANSCRIBE_TRANSPORT_HTTP) + { + _TransportInit(pTransport, TRANSPORT_HTTP); + } + else if (pVoipTranscribe->eTransport == VOIPTRANSCRIBE_TRANSPORT_HTTP2) + { + _TransportInit(pTransport, TRANSPORT_HTTP2); + } + else if (pVoipTranscribe->eTransport == VOIPTRANSCRIBE_TRANSPORT_WEBSOCKETS) + { + _TransportInit(pTransport, TRANSPORT_WEBSOCKETS); + } + + // allocate transport ref; give it a big enough buffer to max out SSL frame size + if ((pTransport->pState = pTransport->Create(16*1024)) == NULL) + { + NetPrintf(("voiptranscribe: could not allocate transport module\n")); + VoipTranscribeDestroy(pVoipTranscribe); + return(-1); + } + + // perform transport-specific initialization + if (pTransport->eTransport == TRANSPORT_HTTP) + { + // don't request connection close + pTransport->Control(pTransport->pState, pTransport->iStreamId, 'keep', 1, 0, NULL); + // enable reuse on put/post + pTransport->Control(pTransport->pState, pTransport->iStreamId, 'rput', 1, 0, NULL); + } + else if (pTransport->eTransport == TRANSPORT_WEBSOCKETS) + { + // increase temporary input buffer used for connection establishment to allow for header info + pTransport->Control(pTransport->pState, pTransport->iStreamId, 'ires', 2*1024, 0, NULL); + } + + // perform provider-specific initialization + if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_AMAZON) + { + // set request header callback for AWS signing + ProtoHttp2Callback(pTransport->pState, _VoipTranscribeCustomHeaderCb, NULL, pVoipTranscribe); + } + + // set common transport parameters + pVoipTranscribe->iTimeout = 60*1000; + pTransport->Control(pTransport->pState, pTransport->iStreamId, 'time', pVoipTranscribe->iTimeout, 0, NULL); + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeTransportCleanup + + \Description + Cleanup transport module + + \Input *pVoipTranscribe - pointer to module state + + \Version 12/13/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipTranscribeTransportCleanup(VoipTranscribeRefT *pVoipTranscribe) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + + // destroy previous transport ref, if allocated + if (pTransport->pState != NULL) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: cleaning up previous transport state\n")); + pTransport->Destroy(pTransport->pState); + } + + // reset transport state + ds_memclr(&pVoipTranscribe->Transport, sizeof(pVoipTranscribe->Transport)); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeBasicAuth + + \Description + Encode Basic HTTP authorization header as per https://tools.ietf.org/html/rfc7617 + + \Input *pBuffer - [out] output buffer for encoded base64 string + \Input iBufSize - size of output buffer + \Input *pUser - user identifer + \Input *pPass - user password + + \Output + const char * - pointer to output buffer + + \Version 02/27/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipTranscribeBasicAuth(char *pBuffer, int32_t iBufSize, const char *pUser, const char *pPass) +{ + char strAuth[128]; + ds_snzprintf(strAuth, sizeof(strAuth), "%s:%s", pUser, pPass); + Base64Encode2(strAuth, (int32_t)strlen(strAuth), pBuffer, iBufSize); + return(pBuffer); +} + +/*F********************************************************************************/ +/*! +\Function _VoipTranscribeParseResponseWatson + + \Description + Parse response from IBM Watson transcription service + + \Input *pVoipTranscribe - pointer to module state + \Input *pResponse - server response + \Input *pResult - parse result buffer + \Input iResultSize - length of result buffer + + \Output + int32_t - negative=failure, zero=listening, else success + + \Notes + A zero result indicates an intermediate response ("listening") that should + be consumed while remaining in the receiving state. + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeParseResponseWatson(VoipTranscribeRefT *pVoipTranscribe, const char *pResponse, char *pResult, int32_t iResultSize) +{ + const char *pCurrent, *pAlt; + uint16_t *pJsonParseBuf; + int32_t iResult = -1; + char strText[128], *pText; + + // parse the response + if (JsonParse(pVoipTranscribe->aJsonParseBuf, sizeof(pVoipTranscribe->aJsonParseBuf)/sizeof(pVoipTranscribe->aJsonParseBuf[0]), pResponse, -1) == 0) + { + NetPrintf(("voiptranscribe: warning: parse results truncated\n")); + } + pJsonParseBuf = pVoipTranscribe->aJsonParseBuf; + + if ((pCurrent = JsonFind2(pJsonParseBuf, NULL, "results[", 0)) != NULL) + { + if ((pAlt = JsonFind2(pJsonParseBuf, pCurrent, ".alternatives[", 0)) != NULL) + { + JsonGetString(JsonFind2(pJsonParseBuf, pAlt, ".transcript", 0), pResult, iResultSize, ""); + + /* as per https://cloud.ibm.com/docs/services/speech-to-text?topic=speech-to-text-basic-response#hesitation, results + can include %HESITATION in some circumstances. we don't want that, so we remove it from the output if detected. + note it seems that smart_formatting also removes it, but we leave this here in case that changes at some point */ + for (pText = pVoipTranscribe->strTranscription; (pText = ds_stristr(pText, "%HESITATION")) != NULL; ) + { + iResult = (int32_t)strlen(pText)+1; + memmove(pText, pText+12, iResult-12); + } + } + iResult = 1; + } + else if ((pCurrent = JsonFind(pJsonParseBuf, "state")) != NULL) + { + JsonGetString(pCurrent, strText, sizeof(strText), ""); + if (!strcmp(strText, "listening")) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: state: listening\n")); + iResult = 0; + } + } + else + { + *pResult = '\0'; + if ((pCurrent = JsonFind(pJsonParseBuf, "error")) != NULL) + { + JsonGetString(pCurrent, pResult, iResultSize, ""); + // if a timeout, don't consider it an error + if (!ds_stricmp(pVoipTranscribe->strTranscription, "Session timed out.")) + { + pVoipTranscribe->strTranscription[0] = '\0'; + iResult = 0; + } + } + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeParseResponseMicrosoft + + \Description + Parse response from Microsoft Speech transcription service + + \Input *pVoipTranscribe - pointer to module state + \Input *pResponse - server response + \Input *pResult - parse result buffer + \Input iResultSize - length of result buffer + + \Output + int32_t - negative=failure, else success + + \Notes + RecognitionStatus: Success, NoMatch, InitialSilenceTimeout, BabbleTimeout, Error + + DisplayText represents the recognized phrase after capitalization, punctuation, and + inverse-text-normalization have been applied and profanity has been masked with + asterisks. The DisplayText field is present only if the RecognitionStatus field has + the value Success. + + Ref: https://docs.microsoft.com/en-us/azure/cognitive-services/speech/concepts#transcription-responses + + \Version 09/15/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeParseResponseMicrosoft(VoipTranscribeRefT *pVoipTranscribe, const char *pResponse, char *pResult, int32_t iResultSize) +{ + int32_t iResult = -1; + const char *pCurrent; + char strText[128]; + uint16_t *pJsonParseBuf; + + // parse the response + if (JsonParse(pVoipTranscribe->aJsonParseBuf, sizeof(pVoipTranscribe->aJsonParseBuf)/sizeof(pVoipTranscribe->aJsonParseBuf[0]), pResponse, -1) == 0) + { + NetPrintf(("voiptranscribe: warning: parse results truncated\n")); + } + pJsonParseBuf = pVoipTranscribe->aJsonParseBuf; + + // get status + if ((pCurrent = JsonFind2(pJsonParseBuf, NULL, "RecognitionStatus", 0)) != NULL) + { + JsonGetString(pCurrent, strText, sizeof(strText), ""); + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: RecognitionStatus=%s\n", strText)); + if (strcmp(strText, "Error")) + { + iResult = 1; + } + } + // get display text + if ((pCurrent = JsonFind2(pJsonParseBuf, NULL, "DisplayText", 0)) != NULL) + { + JsonGetString(pCurrent, pResult, iResultSize, ""); + iResult = 1; + } + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeParseResponseGoogleJson + + \Description + Parse response from Google Cloud transcription service + + \Input *pVoipTranscribe - pointer to module state + \Input *pResponse - server response + \Input *pResult - parse result buffer + \Input iResultSize - length of result buffer + + \Output + int32_t - negative=failure, else success + + \Notes + Ref: https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1#google.cloud.speech.v1.StreamingRecognizeResponse + + \Version 09/27/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeParseResponseGoogleJson(VoipTranscribeRefT *pVoipTranscribe, const char *pResponse, char *pResult, int32_t iResultSize) +{ + const char *pCurrent, *pAlt; + uint16_t *pJsonParseBuf; + int32_t iResult = -1; + + // parse the response + if (JsonParse(pVoipTranscribe->aJsonParseBuf, sizeof(pVoipTranscribe->aJsonParseBuf)/sizeof(pVoipTranscribe->aJsonParseBuf[0]), pResponse, -1) == 0) + { + NetPrintf(("voiptranscribe: warning: parse results truncated\n")); + } + pJsonParseBuf = pVoipTranscribe->aJsonParseBuf; + + // check for transcript result + if ((pCurrent = JsonFind2(pJsonParseBuf, NULL, "results[", 0)) != NULL) + { + if ((pAlt = JsonFind2(pJsonParseBuf, pCurrent, ".alternatives[", 0)) != NULL) + { + JsonGetString(JsonFind2(pJsonParseBuf, pAlt, ".transcript", 0), pResult, iResultSize, ""); + } + iResult = 1; + } + // process error, if there is one + else if ((pCurrent = JsonFind2(pJsonParseBuf, NULL, "error", 0)) != NULL) + { + char strText[128]; + int32_t iCode = JsonGetInteger(JsonFind2(pJsonParseBuf, pCurrent, ".code", 0), 0); + JsonGetString(JsonFind2(pJsonParseBuf, pCurrent, ".message", 0), strText, sizeof(strText), ""); + ds_snzprintf(pResult, iResultSize, "error %d (%s)", iCode, strText); + } + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeParseResponseGoogleProtobuf + + \Description + Parse response from Google Cloud transcription service + + \Input *pVoipTranscribe - pointer to module state + \Input *pResponse - server response + \Input iResponseSize - server response length + \Input *pResult - parse result buffer + \Input iResultSize - length of result buffer + + \Output + int32_t - negative=failure, else success + + \Notes + See file header for response format and protobuf definition reference. + + \Version 10/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeParseResponseGoogleProtobuf(VoipTranscribeRefT *pVoipTranscribe, const char *pResponse, int32_t iResponseSize, char *pResult, int32_t iResultSize) +{ + ProtobufReadT Reader, Msg, Msg2; + const uint8_t *pCurrent = NULL, *pCurrent2 = NULL; + const uint8_t *pBuffer = (const uint8_t *)pResponse; + int32_t iMsgSize, iResult=-1; + + // an empty response means the audio produced no transcription; this indicates the request is complete, so we return completion + if (iResponseSize == 0) + { + return(1); + } + + // get message size (skipping compression) + if ((pBuffer = ProtobufCommonReadSize(pBuffer+1, iResponseSize-1, &iMsgSize)) == NULL) + { + return(iResult); + } + ProtobufReadInit(&Reader, pBuffer, iMsgSize); + + // pull out the error info if included + if (ProtobufReadMessage(&Reader, ProtobufReadFind(&Reader, 1 /* error */), &Msg) != NULL) + { + char strText[128]; + int32_t iCode = ProtobufReadVarint(&Msg, ProtobufReadFind(&Msg, 1 /* code */)); + ProtobufReadString(&Msg, ProtobufReadFind(&Msg, 2 /* message */), strText, sizeof(strText)); + ds_snzprintf(pResult, iResultSize, "error %d (%s)", iCode, strText); + } + + // read repeated results + if ((pCurrent = ProtobufReadMessage(&Reader, ProtobufReadFind2(&Reader, 2 /* results */, pCurrent), &Msg)) != NULL) + { + // read repeated alternatives + if ((pCurrent2 = ProtobufReadMessage(&Msg, ProtobufReadFind2(&Msg, 1 /* alternatives */, pCurrent2), &Msg2)) != NULL) + { + ProtobufReadString(&Msg2, ProtobufReadFind(&Msg2, 1 /* transcript */), pResult, iResultSize); + iResult = 1; + } + } + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeParseResponseAmazonJson + + \Description + Parse JSON response from Amazon Transcribe service, after being extracted from + binary event. + + \Input *pVoipTranscribe - pointer to module state + \Input *pResponse - server response + \Input *pResult - parse result buffer + \Input iResultSize - length of result buffer + + \Output + int32_t - negative=failure, zero=listening, else success + + \Version 01/17/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeParseResponseAmazonJson(VoipTranscribeRefT *pVoipTranscribe, const char *pResponse, char *pResult, int32_t iResultSize) +{ + const char *pCurrent, *pAlt; + uint16_t *pJsonParseBuf; + int32_t iResult = -1; + + // parse the response + if (JsonParse(pVoipTranscribe->aJsonParseBuf, sizeof(pVoipTranscribe->aJsonParseBuf)/sizeof(pVoipTranscribe->aJsonParseBuf[0]), pResponse, -1) == 0) + { + NetPrintf(("voiptranscribe: warning: parse results truncated\n")); + } + pJsonParseBuf = pVoipTranscribe->aJsonParseBuf; + + // check for transcript response + if ((pCurrent = JsonFind2(pJsonParseBuf, NULL, "Transcript.Results[", 0)) != NULL) + { + // swallow intermediate/empty results + iResult = 0; + // check for completion + if (((pAlt = JsonFind2(pJsonParseBuf, pCurrent, ".IsPartial", 0)) != NULL) && !JsonGetBoolean(pAlt, FALSE)) + { + if ((pAlt = JsonFind2(pJsonParseBuf, pCurrent, ".Alternatives[", 0)) != NULL) + { + JsonGetString(JsonFind2(pJsonParseBuf, pAlt, ".Transcript", 0), pResult, iResultSize, ""); + } + iResult = 1; + } + } + else if ((pCurrent = JsonFind2(pJsonParseBuf, NULL, "Message", 0)) != NULL) + { + // get error message result + JsonGetString(pCurrent, pResult, iResultSize, ""); + } + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeParseResponseAmazon + + \Description + Parse binary event response from Amazon + + \Input *pVoipTranscribe - pointer to module state + \Input *pResponse - server response + \Input iResponseSize - server response length + \Input *pResult - parse result buffer + \Input iResultSize - length of result buffer + + \Output + int32_t - negative=failure, else success + + \Version 01/17/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeParseResponseAmazon(VoipTranscribeRefT *pVoipTranscribe, const char *pResponse, int32_t iResponseSize, char *pResult, int32_t iResultSize) +{ + char strEventType[32], strHeader[512], strMessage[4096]; + int32_t iMessageLen, iOffset, iReadResult, iResult; + TransportT *pTransport = &pVoipTranscribe->Transport; + + /* get session id from response header, to use in future requests; amazon recommends this as it can improve + transcription accuracy across requests */ + if (pTransport->Status(pTransport->pState, pTransport->iStreamId, 'htxt', strHeader, sizeof(strHeader)) != -1) + { + ProtoHttpGetHeaderValue(NULL, strHeader, "x-amzn-transcribe-session-id", pVoipTranscribe->strSessionId, sizeof(pVoipTranscribe->strSessionId), NULL); + } + // parse error response + if ((iResult = pTransport->Status(pTransport->pState, pTransport->iStreamId, 'code', strHeader, sizeof(strHeader))) != PROTOHTTP_RESPONSE_OK) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 0, "voiptranscribe: received %d result\n", iResult)); + } + // read binary events from response data + for (iOffset = 0, iResult = 0; (iOffset < iResponseSize) && (iResult == 0); iOffset += iReadResult) + { + if ((iReadResult = AWSReadEvent((const uint8_t *)pResponse+iOffset, iResponseSize-iOffset, strEventType, sizeof(strEventType), strMessage, (iMessageLen=(int32_t)sizeof(strMessage), &iMessageLen))) > 0) + { + if (!ds_stricmp(strEventType, "TranscriptEvent")) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: %s\n", strMessage)); + iResult = _VoipTranscribeParseResponseAmazonJson(pVoipTranscribe, strMessage, pResult, iResultSize); + } + if (!ds_stricmp(strEventType, "BadRequestException")) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 0, "voiptranscribe: BadRequestException:\n%s\n", strMessage)); + iResult = _VoipTranscribeParseResponseAmazonJson(pVoipTranscribe, strMessage, pResult, iResultSize); + } + } + else + { + break; + } + } + + return((iResult >= 0) ? 1 : -1); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeParseResponse + + \Description + Parse response from transcription service + + \Input *pVoipTranscribe - pointer to module state + \Input *pResponse - server response + \Input iResponseSize - server response length + \Input *pResult - parse result buffer + \Input iResultSize - length of result buffer + + \Output + int32_t - negative=failure, zero=continue receiving, else success + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeParseResponse(VoipTranscribeRefT *pVoipTranscribe, const char *pResponse, int32_t iResponseSize, char *pResult, int32_t iResultSize) +{ + int32_t iResult = -1; + if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_IBMWATSON) + { + iResult = _VoipTranscribeParseResponseWatson(pVoipTranscribe, pResponse, pResult, iResultSize); + } + else if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_MICROSOFT) + { + iResult = _VoipTranscribeParseResponseMicrosoft(pVoipTranscribe, pResponse, pResult, iResultSize); + } + else if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_GOOGLE) + { + iResult = (pVoipTranscribe->eTransport == VOIPTRANSCRIBE_TRANSPORT_HTTP) ? _VoipTranscribeParseResponseGoogleJson(pVoipTranscribe, pResponse, pResult, iResultSize) : _VoipTranscribeParseResponseGoogleProtobuf(pVoipTranscribe, pResponse, iResponseSize, pResult, iResultSize); + } + else if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_AMAZON) + { + iResult = _VoipTranscribeParseResponseAmazon(pVoipTranscribe, pResponse, iResponseSize, pResult, iResultSize); + } + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeFormatHeaderWatson + + \Description + Format connection header for Watson service + + \Input *pVoipTranscribe - pointer to module state + \Input *pBuffer - [out] buffer to hold formatted header + \Input iBufLen - buffer length + + \Output + int32_t - negative=failure, else success + + \Version 09/17/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipTranscribeFormatHeaderWatson(VoipTranscribeRefT *pVoipTranscribe, char *pBuffer, int32_t iBufLen) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + char strAuth[128]; + int32_t iOffset; + + /* note: pre-encoded auth strings are 68 characters in length, the auth keys are 44 chars. we use this to decide whether + to do the encode or not. this code should be removed in the future once pre-encoded auth keys are no longer in use */ + + // encode Basic authorization string with string apikey: + if (strlen(pVoipTranscribe->strKey) < 68) + { + _VoipTranscribeBasicAuth(strAuth, sizeof(strAuth), "apikey", pVoipTranscribe->strKey); + } + else // just copy it + { + ds_strnzcpy(strAuth, pVoipTranscribe->strKey, sizeof(strAuth)); + } + + // format request header + iOffset = ds_snzprintf(pBuffer, iBufLen, "Authorization: Basic %s\r\n", strAuth); + // set http-specific options + if (pTransport->eTransport == TRANSPORT_HTTP) + { + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "Content-Type: %s\r\n", pVoipTranscribe->strAudioFormat); + } + // return transport-specific url + return(pVoipTranscribe->strUrl); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeFormatHeaderMicrosoft + + \Description + Format connection header for Microsoft Speech service + + \Input *pVoipTranscribe - pointer to module state + \Input *pBuffer - [out] buffer to hold formatted header + \Input iBufLen - buffer length + + \Output + int32_t - negative=failure, else success + + \Version 09/17/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipTranscribeFormatHeaderMicrosoft(VoipTranscribeRefT *pVoipTranscribe, char *pBuffer, int32_t iBufLen) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + int32_t iOffset=0; + + // set http-specific options + if (pTransport->eTransport == TRANSPORT_HTTP) + { + // format request header + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "Accept: application/json;text/xml\r\n"); + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "Content-Type: %s\r\n", pVoipTranscribe->strAudioFormat); + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "Ocp-Apim-Subscription-Key: %s\r\n", pVoipTranscribe->strKey); + } + // set websockets-specific options + else if (pTransport->eTransport == TRANSPORT_WEBSOCKETS) + { + char strUUID[36], strTimestamp[36]; + // get a UUID + _GenerateUUID(strUUID, sizeof(strUUID), FALSE); + _GenerateTimestamp(strTimestamp, sizeof(strTimestamp)); + // format request header + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "X-ConnectionId: %s\r\n", strUUID); + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "X-Timestamp: %s\r\n", strTimestamp); + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "Content-Type: %s\r\n", pVoipTranscribe->strAudioFormat); + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "Ocp-Apim-Subscription-Key: %s\r\n", pVoipTranscribe->strKey); + } + // return transport-specific url + return(pVoipTranscribe->strUrl); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeFormatHeaderGoogle + + \Description + Format connection header for Google service + + \Input *pVoipTranscribe - pointer to module state + \Input *pBuffer - [out] buffer to hold formatted header + \Input iBufLen - buffer length + + \Output + int32_t - negative=failure, else success + + \Version 09/18/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipTranscribeFormatHeaderGoogle(VoipTranscribeRefT *pVoipTranscribe, char *pBuffer, int32_t iBufLen) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + static char strUrl[256] = ""; + const char *pUrl; + + // format url with api key + if (pTransport->eTransport == TRANSPORT_HTTP) + { + ds_snzprintf(strUrl, sizeof(strUrl), "%s?key=%s", pVoipTranscribe->strUrl, pVoipTranscribe->strKey); + pUrl = strUrl; + } + else + { + pUrl = pVoipTranscribe->strUrl; + ds_snzprintf(pBuffer, iBufLen, "te: trailers\r\ncontent-type: application/grpc\r\nX-Goog-Api-Key: %s\r\n", pVoipTranscribe->strKey); + } + // return url + return(pUrl); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeFormatHeaderAmazon + + \Description + Format connection header for Amazon Transcribe service + + \Input *pVoipTranscribe - pointer to module state + \Input *pBuffer - [out] buffer to hold formatted header + \Input iBufLen - buffer length + + \Output + int32_t - negative=failure, else success + + \Version 09/17/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipTranscribeFormatHeaderAmazon(VoipTranscribeRefT *pVoipTranscribe, char *pBuffer, int32_t iBufLen) +{ + int32_t iOffset=0; + + // format request header + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "content-type: application/x-amz-json-1.1\r\n"); + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "x-amzn-content-sha256: STREAMING-AWS4-HMAC-SHA256-EVENTS\r\n"); + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "x-amzn-target: com.amazonaws.transcribe.Transcribe.StartStreamTranscription\r\n"); + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "x-amzn-transcribe-language-code: en-US\r\n"); + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "x-amzn-transcribe-media-encoding: pcm\r\n"); + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "x-amzn-transcribe-sample-rate: %d\r\n", pVoipTranscribe->iAudioRate); + if (pVoipTranscribe->strSessionId[0] != '\0') + { + iOffset += ds_snzprintf(pBuffer+iOffset, iBufLen-iOffset, "x-amzn-transcribe-session-id: %s\r\n", pVoipTranscribe->strSessionId); + } + + // return transport-specific url + return(pVoipTranscribe->strUrl); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeSetHeader + + \Description + Set connection header; note that this might be used in the Connect() or + Request() call depending on whether we are using a connection-oriented + protocol or not. + + \Input *pVoipTranscribe - pointer to module state + \Input *pTransport - transport handler + + \Output + int32_t - negative=failure, else success + + \Version 09/17/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static const char *_VoipTranscribeSetHeader(VoipTranscribeRefT *pVoipTranscribe, TransportT *pTransport) +{ + char strHeader[512] = ""; + const char *pUrl = NULL; + // format header with provider-specific fields + if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_IBMWATSON) + { + pUrl = _VoipTranscribeFormatHeaderWatson(pVoipTranscribe, strHeader, sizeof(strHeader)); + } + else if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_MICROSOFT) + { + pUrl = _VoipTranscribeFormatHeaderMicrosoft(pVoipTranscribe, strHeader, sizeof(strHeader)); + } + else if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_GOOGLE) + { + pUrl = _VoipTranscribeFormatHeaderGoogle(pVoipTranscribe, strHeader, sizeof(strHeader)); + } + else if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_AMAZON) + { + pUrl = _VoipTranscribeFormatHeaderAmazon(pVoipTranscribe, strHeader, sizeof(strHeader)); + } + // set the header + pTransport->Control(pTransport->pState, pTransport->iStreamId, 'apnd', 0, 0, strHeader); + // return request header + return(pUrl); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeConnectCheck + + \Description + Check for connection completion, for protocols that require an explicit connection + + \Input *pVoipTranscribe - pointer to module state + \Input *pTransport - transport handler + + \Output + int32_t - negative=failure, zero=connecting, else success + + \Version 09/06/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeConnectCheck(VoipTranscribeRefT *pVoipTranscribe, TransportT *pTransport) +{ + int32_t iResult = (pTransport->eTransport == TRANSPORT_WEBSOCKETS) ? pTransport->Status(pTransport->pState, pTransport->iStreamId, 'stat', NULL, 0) : 1; + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeConnect + + \Description + Open a connection to a transcription service, if we're not already connected + + \Input *pVoipTranscribe - pointer to module state + + \Output + int32_t - negative=failure, else success + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeConnect(VoipTranscribeRefT *pVoipTranscribe) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + const char *pUrl; + int32_t iResult; + + // up the logging level + pTransport->Control(pTransport->pState, pTransport->iStreamId, 'spam', 1, 0, NULL); + + // early out if we're already connected or don't need to connect + if ((iResult = _VoipTranscribeConnectCheck(pVoipTranscribe, pTransport)) > 0) + { + return(1); + } + // set connect headers + if ((pUrl = _VoipTranscribeSetHeader(pVoipTranscribe, pTransport)) == NULL) + { + return(-1); + } + + // make the connection request + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: connecting to %s\n", pUrl)); + if ((iResult = pTransport->Connect(pTransport->pState, pUrl)) < 0) + { + NetPrintf(("voiptranscribe: error connecting to '%s'\n", pUrl)); + return(iResult); + } + + // return result code to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeRequest + + \Description + Make a request against transcription service + + \Input *pVoipTranscribe - pointer to module state + + \Output + int32_t - negative=failure, else success + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeRequest(VoipTranscribeRefT *pVoipTranscribe) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + char strRequest[128] = "", *pRequest = strRequest; + int32_t iRequestLen=0, iResult; + const char *pUrl; + + // set request headers + if ((pUrl = _VoipTranscribeSetHeader(pVoipTranscribe, pTransport)) == NULL) + { + return(-1); + } + + // set content-type for watson+websockets + if ((pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_IBMWATSON) && (pTransport->eTransport == TRANSPORT_WEBSOCKETS)) + { + // format websocket request body + iRequestLen = ds_snzprintf(strRequest, sizeof(strRequest), "{ \"action\": \"start\", \"content-type\": \"%s\", \"smart_formatting\": true }", pVoipTranscribe->strAudioFormat); + } + + // http transfers are streaming (use chunked encoding) + if (pTransport->eTransport == TRANSPORT_HTTP) + { + iRequestLen = PROTOHTTP_STREAM_BEGIN; + } + + // start the request + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: sending request\n")); + if ((iResult = pTransport->Request(pTransport->pState, pUrl, pRequest, iRequestLen, &pTransport->iStreamId)) < 0) + { + NetPrintf(("voiptranscribe: error %d issuing request'%s'\n", iResult, pUrl)); + return(iResult); + } + + // return result code to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeSubmitRaw + + \Description + Submit uncompressed voice data to be transcribed + + \Input *pVoipTranscribe - pointer to module state + \Input *pVoipBuffer - buffer to write to + \Input *pBuffer - voice data to be transcribed + \Input iBufLen - size of voice data in bytes + + \Output + int32_t - number of bytes copied + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t _VoipTranscribeSubmitRaw(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBuffer, const uint8_t *pBuffer, int32_t iBufLen) +{ + // start of buffer processing + if (pVoipBuffer->bRecStarting) + { + // reserve a WAV header to encapsulate the data + if (pVoipTranscribe->eFormat == VOIPTRANSCRIBE_FORMAT_WAV16) + { + pVoipBuffer->iBufOff = _WaveWriteOpen(pVoipBuffer, pVoipTranscribe->iAudioRate); + } + pVoipBuffer->bRecStarting = FALSE; + } + + // copy data to output buffer + ds_memcpy(pVoipBuffer->pBuffer+pVoipBuffer->iBufOff, pBuffer, iBufLen); + + // adjust buffer parameters + pVoipBuffer->iBufOff += iBufLen; + + // note if we're full + if (pVoipBuffer->iBufOff == pVoipBuffer->iBufLen) + { + pVoipBuffer->bRecFull = TRUE; + } + + // return amount copied to caller + return(iBufLen); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeSubmitOpus + + \Description + Submit Opus voice data to be transcribed + + \Input *pVoipTranscribe - pointer to module state + \Input *pVoipBuffer - buffer to write to + \Input *pBuffer - voice data to be transcribed + \Input iBufLen - size of voice data in bytes + + \Output + int32_t - number of bytes copied + + \Version 09/10/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t _VoipTranscribeSubmitOpus(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBuffer, const uint8_t *pBuffer, int32_t iBufLen) +{ + OggWriterT *pOggWriter = &pVoipBuffer->OggWriter; + int32_t iResult; + + // if we're at the start of the buffer, reserve an ogg header to encapsulate the opus data + if (pVoipBuffer->bRecStarting) + { + pVoipBuffer->iBufOff = _OggOpusWriteOpen(pOggWriter, pVoipBuffer->pBuffer, pVoipBuffer->iBufLen); + pVoipBuffer->bRecStarting = FALSE; + } + + // write voice bundle as an ogg segment + if ((iResult = _OggWriteSegment(pOggWriter, pBuffer, iBufLen, pVoipTranscribe->iVerbose)) > 0) + { + pVoipBuffer->iBufOff = iResult; + } + else if (iResult < 0) + { + pVoipBuffer->bRecFull = TRUE; + } + + // return amount copied to caller + return(iBufLen); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeSubmit + + \Description + Submit voice data to be transcribed + + \Input *pVoipTranscribe - pointer to module state + \Input *pBuffer - voice data to be transcribed + \Input iBufLen - size of voice data in bytes + + \Output + int32_t - number of bytes copied + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeSubmit(VoipTranscribeRefT *pVoipTranscribe, const uint8_t *pBuffer, int32_t iBufLen) +{ + VoipBufferT *pVoipBuffer = &pVoipTranscribe->VoipBuffer[pVoipTranscribe->iRecBuffer]; + int32_t iBufAvail = pVoipBuffer->iBufLen - pVoipBuffer->iBufOff; + int32_t iResult; + + // determine amount of data to copy + if (iBufLen > iBufAvail) + { + NetPrintf(("voiptranscribe: [%d] warning; truncating input from %d to %d bytes\n", pVoipBuffer->iBuffer, iBufLen, iBufAvail)); + iBufLen = iBufAvail; + } + // make sure we have something to submit + if (iBufLen == 0) + { + return(0); + } + + NetPrintfVerbose((pVoipTranscribe->iVerbose, 3, "voiptranscribe: [%d] copy [0x%04x,0x%04x]\n", pVoipBuffer->iBuffer, pVoipBuffer->iBufOff, pVoipBuffer->iBufOff+iBufLen)); + + // submit data to buffer + if (!pVoipTranscribe->bCompressed) + { + iResult = _VoipTranscribeSubmitRaw(pVoipTranscribe, pVoipBuffer, pBuffer, iBufLen); + } + else + { + iResult = _VoipTranscribeSubmitOpus(pVoipTranscribe, pVoipBuffer, pBuffer, iBufLen); + } + + // keep track of samples submitted; if compressed assume 20ms of samples at 16khz + if (iResult > 0) + { + pVoipBuffer->iNumSamples += !pVoipTranscribe->bCompressed ? iBufLen/2 : 320; + } + + // update voip timestamp + pVoipTranscribe->uVoipTick = NetTick(); + + // return result to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeSubmitFinish + + \Description + Finish processing of data submission + + \Input *pVoipTranscribe - pointer to module state + \Input *pVoipBuffer - buffer to write to + + \Output + int32_t - negative=skip, else process + + \Version 09/13/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeSubmitFinish(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBuffer) +{ + // if we have less than a second of audio don't send it + if ((pVoipBuffer->iNumSamples < pVoipTranscribe->iAudioRate) && pVoipBuffer->bMinDiscard) + { + NetPrintf(("voiptranscribe: [%d] discarding short audio segment with only %d samples\n", pVoipBuffer->iBuffer, pVoipBuffer->iNumSamples)); + _VoipTranscribeBufferReset(pVoipTranscribe, pVoipBuffer); + return(-1); + } + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: [%d] submit finish\n", pVoipBuffer->iBuffer)); + + // record metrics + pVoipTranscribe->Metrics.uEventCount += 1; + pVoipTranscribe->Metrics.uDurationMsSent += ((pVoipBuffer->iNumSamples * 1000) / pVoipTranscribe->iAudioRate); + pVoipTranscribe->uSttStartTime = NetTick(); + + // handle specific audio format requirements + if (pVoipTranscribe->eFormat == VOIPTRANSCRIBE_FORMAT_OPUS) + { + pVoipBuffer->iBufOff = _OggOpusWriteFinish(&pVoipBuffer->OggWriter, pVoipTranscribe->iVerbose); + } + + // finalize current buffer + pVoipBuffer->bRecFinished = TRUE; + // set current buffer as send buffer + pVoipTranscribe->iSndBuffer = pVoipTranscribe->iRecBuffer; + // move to next recording buffer + pVoipTranscribe->iRecBuffer = (pVoipTranscribe->iRecBuffer+1)%2; + // reset the buffer + _VoipTranscribeBufferReset(pVoipTranscribe, &pVoipTranscribe->VoipBuffer[pVoipTranscribe->iRecBuffer]); + + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeBackoffSet + + \Description + Set backoff timer on failure or empty result, if appropriate + + \Input *pVoipTranscribe - pointer to module state + + \Version 12/05/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipTranscribeBackoffSet(VoipTranscribeRefT *pVoipTranscribe) +{ + int32_t iCount, iEmptyCt, iErrorCt; + // see if we need to set the backoff timer + iEmptyCt = pVoipTranscribe->iConsecEmptyCt - pVoipTranscribe->iConsecEmptyMax; + iErrorCt = pVoipTranscribe->iConsecErrorCt - pVoipTranscribe->iConsecErrorMax; + // pick the biggest of the two + iCount = DS_MAX(iEmptyCt, iErrorCt); + // if positive, calculate backoff timer + if (iCount > 0) + { + // 2^n backoff on failures above the max + iCount = (1 << iCount) * 1000; + // clamp to maximum of sixty seconds + iCount = DS_MIN(iCount, 60*1000); + // set the backoff timer and make sure it doesn't equal zero (reserved for disabled status) + if ((pVoipTranscribe->uBackoffTimer = NetTick()+iCount) == 0) + { + pVoipTranscribe->uBackoffTimer = 1; + } + + NetPrintfVerbose((pVoipTranscribe->iVerbose, 0, "voiptranscribe: setting backoff timer to +%dms\n", iCount)); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeBackoffCheck + + \Description + Get if backoff is enabled + + \Input *pVoipTranscribe - pointer to module state + \Input *pVoipBuffer - voip buffer + + \Output + int32_t - zero if backoff is enabled, else one + + \Version 12/05/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeBackoffCheck(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBuffer) +{ + if (pVoipTranscribe->uBackoffTimer != 0) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 0, "voiptranscribe: [%d] discarding audio segment with %d samples due to backoff\n", pVoipBuffer->iBuffer, pVoipBuffer->iNumSamples)); + _VoipTranscribeBufferReset(pVoipTranscribe, pVoipBuffer); + } + return(pVoipTranscribe->uBackoffTimer != 0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeSendFinish + + \Description + Complete the send request + + \Input *pVoipTranscribe - pointer to module state + + \Output + int32_t - negative=failure, zero=retry, else success + + \Version 09/08/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeSendFinish(VoipTranscribeRefT *pVoipTranscribe) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + char strRequest[128]; + int32_t iRequestLen, iResult = 0; + + if ((pTransport->eTransport == TRANSPORT_HTTP) || (pTransport->eTransport == TRANSPORT_HTTP2)) + { + if ((iResult = pTransport->Send(pTransport->pState, pTransport->iStreamId, NULL, PROTOHTTP_STREAM_END)) == 0) + { + // a successful STREAM_END returns zero, we want to return nonzero so the caller knows the operation completed successfully + iResult = 1; + } + } + if (pTransport->eTransport == TRANSPORT_WEBSOCKETS) + { + iRequestLen = (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_IBMWATSON) ? ds_snzprintf(strRequest, sizeof(strRequest), "{ \"action\": \"stop\" }") : 0; + iResult = _TransportWebSocketRequest(pTransport->pState, NULL, strRequest, iRequestLen, NULL); + } + + // return result code to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeBase64Start + + \Description + Format start of Base64 JSON envelope + + \Input *pVoipTranscribe - pointer to module state + \Input *pVoipBuffer - buffer to write to + + \Output + int32_t - size of output data + + \Version 12/16/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeBase64Start(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBuffer) +{ + int32_t iResult = ds_snzprintf((char *)pVoipBuffer->pBuffer, pVoipBuffer->iBufLen, "{ \"config\": { \"encoding\": \"%s\", \"sampleRateHertz\": %d, \"languageCode\": \"en-US\", \"profanity_filter\": \"true\" }, \"audio\": { \"content\": \"", + pVoipTranscribe->strAudioFormat, pVoipTranscribe->iAudioRate); + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeBase64Encode + + \Description + Base64 encode audio data + + \Input *pVoipBufferOut - buffer to hold encoded output + \Input *pVoipBufferInp - buffer holding binary source data + + \Output + int32_t - length of encoded data + + \Version 12/16/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeBase64Encode(VoipBufferT *pVoipBufferOut, VoipBufferT *pVoipBufferInp) +{ + int32_t iInpLen = pVoipBufferInp->iBufOff-pVoipBufferInp->iBufInp; + int32_t iOutLen = pVoipBufferOut->iBufLen-pVoipBufferOut->iBufOff-4; // save room for terminating "}} at end of base64 encoded input + // pick smallest of input and output length + iInpLen = DS_MIN(iInpLen, Base64DecodedSize(iOutLen-1)); + // make sure input length is a multiple of three; this ensures we have an integral output length with no padding + if (iInpLen > 3) + { + iInpLen = (iInpLen/3)*3; + } + // encode into output buffer + iOutLen = Base64Encode2((const char *)pVoipBufferInp->pBuffer+pVoipBufferInp->iBufInp, iInpLen, (char *)pVoipBufferOut->pBuffer+pVoipBufferOut->iBufOff, iOutLen); + // update input buffer offset + pVoipBufferInp->iBufInp += iInpLen; + // return output buffer offset; + return(iOutLen); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeProtobufStart + + \Description + Format start of Protobuf envelope + + \Input *pVoipTranscribe - pointer to module state + \Input *pVoipBuffer - buffer to encode + + \Output + int32_t - number of bytes in encoded output + + \Notes + See file header for request format and protobuf definition reference. + + \Version 10/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeProtobufStart(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBuffer) +{ + static const uint8_t _aAudioFormatTypes[VOIPTRANSCRIBE_NUMFORMATS] = { 0xff, 1, 0xff, 6 }; + ProtobufWriteRefT *pEncoder; + int32_t iSize=0; + + // write audio settings + pVoipBuffer->pBuffer[0] = 0; + if ((pEncoder = ProtobufWriteCreate(pVoipBuffer->pBuffer+1, pVoipBuffer->iBufLen-1, TRUE)) != NULL) + { + ProtobufWriteMessageBegin(pEncoder, 1 /* streaming_config */); + ProtobufWriteMessageBegin(pEncoder, 1 /* config */); + ProtobufWriteVarint(pEncoder, _aAudioFormatTypes[pVoipTranscribe->eFormat], 1 /* encoding */); + ProtobufWriteVarint(pEncoder, VOIPTRANSCRIBE_AUDIORATE, 2 /* sample_rate_hertz */); + ProtobufWriteString(pEncoder, "en-US", (signed)strlen("en-US"), 3 /* language_code */); + ProtobufWriteVarint(pEncoder, TRUE, 5 /* profanity_filter */); + ProtobufWriteMessageEnd(pEncoder); + ProtobufWriteMessageEnd(pEncoder); + iSize = ProtobufWriteDestroy(pEncoder) + 1; + } + + // return size to caller + return(iSize); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeProtobufEncode + + \Description + Protobuf encode audio data + + \Input *pVoipBufferOut - buffer to hold encoded output + \Input *pVoipBufferInp - buffer holding binary source data + + \Output + int32_t - number of bytes in encoded output + + \Notes + See file header for request format and protobuf definition reference. + + \Version 10/02/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeProtobufEncode(VoipBufferT *pVoipBufferOut, VoipBufferT *pVoipBufferInp) +{ + ProtobufWriteRefT *pEncoder; + int32_t iBufAvail, iBufWrite, iSize=0; + const int32_t _iMaxProtobufOverhead = 1+4+1+2; // compression byte+protobuf length+audio field tag+audio data size (progressive encoded max 2048) + // early out if no data available or buffer full + if ((pVoipBufferInp->iBufInp == pVoipBufferInp->iBufOff) || (pVoipBufferOut->iBufOff == pVoipBufferOut->iBufLen)) + { + return(0); + } + // calculate output buffer space available + iBufAvail = pVoipBufferOut->iBufLen-pVoipBufferOut->iBufOff; + // calculate how much we're going to write (min of available output buffer minus overhead and available input data) + iBufWrite = DS_MIN(iBufAvail-_iMaxProtobufOverhead, pVoipBufferInp->iBufOff-pVoipBufferInp->iBufInp); + // write audio data + pVoipBufferOut->pBuffer[pVoipBufferOut->iBufOff] = 0; + if ((pEncoder = ProtobufWriteCreate(pVoipBufferOut->pBuffer+pVoipBufferOut->iBufOff+1, iBufAvail, TRUE)) != NULL) + { + ProtobufWriteBytes(pEncoder, pVoipBufferInp->pBuffer+pVoipBufferInp->iBufInp, iBufWrite, 2 /* audio_content */); + iSize = ProtobufWriteDestroy(pEncoder) + 1; + // update input buffer offset + pVoipBufferInp->iBufInp += iBufWrite; + } + // return size to caller + return(iSize); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeAwsEncode + + \Description + AWS encode audio data in signed binary event format + + \Input *pVoipTranscribe - module state + \Input *pVoipBufferOut - buffer to hold encoded output + \Input *pVoipBufferInp - buffer holding binary source data (NULL to write empty chunk) + + \Output + int32_t - number of bytes in encoded output + + \Version 01/16/2019 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeAwsEncode(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBufferOut, VoipBufferT *pVoipBufferInp) +{ + int32_t iBufOut, iInpWrite=0; + const uint8_t *pInpData = NULL; + // point to input data and calculate size to encode; if no input we write an empty chunk + if (pVoipBufferInp != NULL) + { + // require send buffer to be empty to ensure our sends are full size + if (pVoipBufferOut->iBufOff != 0) + { + return(0); + } + // locate data to read + pInpData = pVoipBufferInp->pBuffer+pVoipBufferInp->iBufInp; + // calculate how much input we have to write + iInpWrite = pVoipBufferInp->iBufOff-pVoipBufferInp->iBufInp; + } + // write signed audioevent chunk + iBufOut = AWSWriteEvent(pVoipBufferOut->pBuffer+pVoipBufferOut->iBufOff, pVoipBufferOut->iBufLen-pVoipBufferOut->iBufOff, pInpData, &iInpWrite, "AudioEvent", &pVoipTranscribe->AWSSignInfo); + // consume input + if (pVoipBufferInp != NULL) + { + pVoipBufferInp->iBufInp += iInpWrite; + } + // return size of output written + return(iBufOut); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeSendEncode + + \Description + Special encoding for providers that need it in either base64/protobuf + (Google) or binary event (Amazon) format. + + \Input *pVoipTranscribe - module state + \Input *pTransport - transport ref + \Input *pVoipBufferSrc - buffer of data to encode for sending + + \Output + VoipBufferT * - pointer to VoipBuffer to send from + + \Version 12/16/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static VoipBufferT *_VoipTranscribeSendEncode(VoipTranscribeRefT *pVoipTranscribe, TransportT *pTransport, VoipBufferT *pVoipBufferSrc) +{ + VoipBufferT *pVoipBufferSnd = &pVoipTranscribe->VoipBufferSnd; + + // if send buffer has been emptied, reset + if (pVoipBufferSnd->iBufInp == pVoipBufferSnd->iBufOff) + { + pVoipBufferSnd->iBufInp = pVoipBufferSnd->iBufOff = 0; + } + + // encode the audio - this consumes data from the source buffer and writes encoded audio into the send buffer + if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_GOOGLE) + { + // if we're at the start of a send, we need to prefix the audio with the encoding + if (pVoipBufferSrc->iBufInp == 0) + { + pVoipBufferSnd->iBufOff = (pTransport->eTransport == TRANSPORT_HTTP) ? _VoipTranscribeBase64Start(pVoipTranscribe, pVoipBufferSnd) : _VoipTranscribeProtobufStart(pVoipTranscribe, pVoipBufferSnd); + } + // encode the audio based on transport type + pVoipBufferSnd->iBufOff += (pTransport->eTransport == TRANSPORT_HTTP) ? _VoipTranscribeBase64Encode(pVoipBufferSnd, pVoipBufferSrc) : _VoipTranscribeProtobufEncode(pVoipBufferSnd, pVoipBufferSrc); + } + else // Amazon + { + pVoipBufferSnd->iBufOff += _VoipTranscribeAwsEncode(pVoipTranscribe, pVoipBufferSnd, pVoipBufferSrc); + } + + // if recording is finished and we've sent all the data, finish the request + if (pVoipBufferSrc->bRecFinished && (pVoipBufferSrc->iBufInp == pVoipBufferSrc->iBufOff)) + { + if ((pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_GOOGLE) && (pTransport->eTransport == TRANSPORT_HTTP)) + { + pVoipBufferSnd->iBufOff += ds_snzprintf((char *)pVoipBufferSnd->pBuffer+pVoipBufferSnd->iBufOff, pVoipBufferSnd->iBufLen-pVoipBufferSnd->iBufOff, "\"}}"); + } + else if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_AMAZON) + { + pVoipBufferSnd->iBufOff += _VoipTranscribeAwsEncode(pVoipTranscribe, pVoipBufferSnd, NULL); + } + } + + // return send voipbuffer + return(pVoipBufferSnd); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeSend + + \Description + Send a transcription request + + \Input *pVoipTranscribe - module state + \Input *pVoipBuffer - buffer to send + + \Output + int32_t - negative=failure, else success + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeSend(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBuffer) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + int32_t iResult=0; + + // amazon and google need audio encoded for transport; do that here + if ((pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_AMAZON) || (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_GOOGLE)) + { + pVoipBuffer = _VoipTranscribeSendEncode(pVoipTranscribe, pTransport, pVoipBuffer); + } + + // if we have data to send, send it + if (pVoipBuffer->iBufInp < pVoipBuffer->iBufOff) + { + iResult = pTransport->Send(pTransport->pState, pTransport->iStreamId, (const char *)pVoipBuffer->pBuffer+pVoipBuffer->iBufInp, pVoipBuffer->iBufOff-pVoipBuffer->iBufInp); + if (iResult > 0) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: [%d] sent [0x%04x,0x%04x]\n", pVoipBuffer->iBuffer, pVoipBuffer->iBufInp, pVoipBuffer->iBufInp+iResult)); + pVoipBuffer->iBufInp += iResult; + } + else if (iResult < 0) + { + NetPrintf(("voiptranscribe: Send() returned %d\n", iResult)); + } + } + + // return result code to caller + return(iResult); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeUpdateBackoff + + \Description + Do backoff processing + + \Input *pVoipTranscribe - pointer to module state + + \Version 12/05/2018 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipTranscribeUpdateBackoff(VoipTranscribeRefT *pVoipTranscribe) +{ + // do not process if backoff timer is not set + if (pVoipTranscribe->uBackoffTimer == 0) + { + return; + } + // reset/clear backoff timer on expiration + if (NetTickDiff(pVoipTranscribe->uBackoffTimer, NetTick()) <= 0) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 0, "voiptranscribe: clearing backoff timer\n")); + pVoipTranscribe->uBackoffTimer = 0; + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeUpdateRecord + + \Description + Update recording of audio data; this function tracks if the recording + should be considered done for this buffer due to the silence timeout + being exceeded, and finalizes the audio buffer. + + \Input *pVoipTranscribe - module state + \Input uCurTick - current tick count + + \Version 12/14/2018 (jbrookes) Split from VoipTranscribeUpdate() +*/ +/********************************************************************************F*/ +static void _VoipTranscribeUpdateRecord(VoipTranscribeRefT *pVoipTranscribe, uint32_t uCurTick) +{ + VoipBufferT *pVoipBuffer = &pVoipTranscribe->VoipBuffer[pVoipTranscribe->iRecBuffer]; + #if DIRTYCODE_LOGGING + static const char *_strStates[] = { "ST_FAIL", "ST_IDLE", "ST_CONN", "ST_SEND", "ST_RECV" }; + #endif + + // see if we have any audio to process + if (pVoipBuffer->iNumSamples == 0) + { + return; + } + // see if we're done submitting data on active recording buffer + if ((pVoipBuffer->bRecStarting || (NetTickDiff(uCurTick, pVoipTranscribe->uVoipTick) < VOIPTRANSCRIBE_SENDTIMEOUT)) && !pVoipBuffer->bRecFull && (pVoipBuffer->iNumSamples < VOIPTRANSCRIBE_MAXREQSAMPLES)) + { + return; + } + /* if this buffer is ready to submit, but our other buffer is in a non-idle state, we gate + submitting the buffer until the other buffer is idle (not connecting/sending/receiving) */ + if ((pVoipTranscribe->eState != ST_IDLE) && (pVoipTranscribe->iSndBuffer != pVoipTranscribe->iRecBuffer)) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 0, "voiptranscribe: [%d] waiting to finish submitting due to being in state %s(%d)\n", pVoipBuffer->iBuffer, _strStates[pVoipTranscribe->eState+1], pVoipTranscribe->eState)); + return; + } + // check to see if we should squelch this + if (_VoipTranscribeBackoffCheck(pVoipTranscribe, pVoipBuffer)) + { + return; + } + + // finish transcribing audio + _VoipTranscribeSubmitFinish(pVoipTranscribe, pVoipBuffer); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeUpdateSend + + \Description + Update sending of audio data. This function meters send size to a minimum + amount for network efficiency, and handles complention of sending when + all of the recorded data has been sent. + + \Input *pVoipTranscribe - module state + \Input *pVoipBuffer - pointer to voipbuffer being sent + + \Output + int32_t - updated state + + \Version 12/14/2018 (jbrookes) Split from VoipTranscribeUpdate() +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeUpdateSend(VoipTranscribeRefT *pVoipTranscribe, VoipBufferT *pVoipBuffer) +{ + int32_t iResult, iState=ST_SEND; + // wait until we have enough data to send (or if we are done recording) + if (((pVoipBuffer->iBufOff-pVoipBuffer->iBufInp) < 1280) && !pVoipBuffer->bRecFinished) + { + return(iState); + } + // send the data + if ((iResult = _VoipTranscribeSend(pVoipTranscribe, pVoipBuffer)) < 0) + { + NetPrintf(("voiptranscribe: [%d] send failed result=%d\n", iResult)); + return(ST_FAIL); + } + // see if we're done + if ((pVoipBuffer->iBufInp == pVoipBuffer->iBufOff) && pVoipBuffer->bRecFinished) + { + // finish sending process and transition to receive state + if ((iResult = _VoipTranscribeSendFinish(pVoipTranscribe)) > 0) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: [%d] send complete result=%d\n", pVoipBuffer->iBuffer, iResult)); + iState = ST_RECV; + } + else if (iResult < 0) + { + iState = ST_FAIL; + } + else + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: [%d] could not send finish; will try again\n", pVoipBuffer->iBuffer)); + } + } + // if transitioning to recv, reset buffer + if (iState == ST_RECV) + { + _VoipTranscribeBufferReset(pVoipTranscribe, pVoipBuffer); + pVoipTranscribe->iSndBuffer = -1; + } + return(iState); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeUpdateRecv + + \Description + Update receiving of transcription response. + + \Input *pVoipTranscribe - module state + + \Output + int32_t - updated state + + \Version 12/14/2018 (jbrookes) Split from VoipTranscribeUpdate() +*/ +/********************************************************************************F*/ +static int32_t _VoipTranscribeUpdateRecv(VoipTranscribeRefT *pVoipTranscribe) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + int32_t iResult, iState=pVoipTranscribe->eState; + + // see if there's anything to receive + if ((iResult = pTransport->Recv(pTransport->pState, pTransport->iStreamId, pVoipTranscribe->strResponse, sizeof(pVoipTranscribe->strResponse))) >= 0) + { + // null terminate and log response if we're expecting text + if (pTransport->eTransport != TRANSPORT_HTTP2) + { + pVoipTranscribe->strResponse[iResult] = '\0'; + NetPrintfVerbose((pVoipTranscribe->iVerbose, 2, "voiptranscribe: response (%d bytes)\n%s\n", iResult, pVoipTranscribe->strResponse)); + } + + // parse the result + if ((iResult = _VoipTranscribeParseResponse(pVoipTranscribe, pVoipTranscribe->strResponse, iResult, pVoipTranscribe->strTranscription, sizeof(pVoipTranscribe->strTranscription))) > 0) + { + // update transcription length metric + uint32_t uTranscriptionLength = (uint32_t)strnlen(pVoipTranscribe->strTranscription, sizeof(pVoipTranscribe->strTranscription)); + pVoipTranscribe->Metrics.uCharCountRecv += uTranscriptionLength; + pVoipTranscribe->Metrics.uDelay += NetTickDiff(NetTick(), pVoipTranscribe->uSttStartTime); + if (uTranscriptionLength == 0) + { + // keep track of number of consecutive empty results + pVoipTranscribe->iConsecEmptyCt += 1; + // update overall empty result count + pVoipTranscribe->Metrics.uEmptyResultCount += 1; + // set backoff if appropriate + _VoipTranscribeBackoffSet(pVoipTranscribe); + } + else + { + // reset consecutive empty result tracker + pVoipTranscribe->iConsecEmptyCt = 0; + } + // reset consecutive error count metric + pVoipTranscribe->iConsecErrorCt = 0; + // log transcription and transition back to idle state + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: transcript=%s\n", pVoipTranscribe->strTranscription)); + iState = ST_IDLE; + } + else if (iResult < 0) + { + NetPrintf(("voiptranscribe: service error: %s\n", pVoipTranscribe->strTranscription)); + pVoipTranscribe->strTranscription[0] = '\0'; + iState = ST_FAIL; + } + + // clean up transaction if http2 + if (pTransport->eTransport == TRANSPORT_HTTP2) + { + ProtoHttp2StreamFree(pTransport->pState, pTransport->iStreamId); + pTransport->iStreamId = PROTOHTTP2_INVALID_STREAMID; + } + } + else if ((iResult < 0) && (iResult != VOIPTRANSCRIBE_WAIT)) + { + NetPrintf(("voiptranscribe: recv() returned %d\n", iResult)); + iState = ST_FAIL; + } + // return updated state + return(iState); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTranscribeConfig + + \Description + Configure the VoipTranscribe module for use. This call is required to + specify the provider, url, and credentials that will be used to access + the transcription service. + + \Input *pVoipTranscribe - pointer to module state + \Input uProfile - transcribe profile (VOIPTRANSCRIBE_PROFILE_DISABLED to disable) + \Input *pUrl - transcribe provider url + \Input *pCred - transcribe credentials + + \Output + uint32_t - TRUE if configured successfully + + \Version 11/08/2018 (tcho) +*/ +/********************************************************************************F*/ +static uint32_t _VoipTranscribeConfig(VoipTranscribeRefT *pVoipTranscribe, uint32_t uProfile, const char *pUrl, const char *pCred) +{ + NetCritEnter(NULL); + + // clean up previous transport state + _VoipTranscribeTransportCleanup(pVoipTranscribe); + + // save configuration parameters + if (VOIPTRANSCRIBE_PROFILE_PROVIDER(uProfile) != VOIPTRANSCRIBE_PROVIDER_NONE) + { + pVoipTranscribe->uProfile = uProfile; + ds_strnzcpy(pVoipTranscribe->strKey, pCred, sizeof(pVoipTranscribe->strKey)); + ds_strnzcpy(pVoipTranscribe->strUrl, pUrl, sizeof(pVoipTranscribe->strUrl)); + } + else + { + NetPrintf(("voiptranscribe: disabled\n")); + pVoipTranscribe->uProfile = uProfile; + ds_memclr(pVoipTranscribe->strKey, sizeof(pVoipTranscribe->strKey)); + ds_memclr(pVoipTranscribe->strUrl, sizeof(pVoipTranscribe->strUrl)); + NetCritLeave(NULL); + return(FALSE); + } + + // set provider info + pVoipTranscribe->eProvider = VOIPTRANSCRIBE_PROFILE_PROVIDER(pVoipTranscribe->uProfile); + if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_MICROSOFT) + { + // install CA certificate required to access microsoft servers + ProtoSSLSetCACert((const uint8_t *)_strCyberTrustRootCA, sizeof(_strCyberTrustRootCA)); + } + else if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_GOOGLE) + { + // install CA certificate required to access Google Speech-to-text server + ProtoSSLSetCACert((const uint8_t *)_strGlobalSignRootCAR2, sizeof(_strGlobalSignRootCAR2)); + } + else if (pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_AMAZON) + { + // install CA certificate required to access Amazon Transcribe + ProtoSSLSetCACert((const uint8_t *)_strAmazonRootCAR1, sizeof(_strAmazonRootCAR1)); + } + + // init transport class + if (_VoipTranscribeTransportInit(pVoipTranscribe) < 0) + { + NetPrintf(("voiptranscribe: could not initialize transport module\n")); + VoipTranscribeDestroy(pVoipTranscribe); + NetCritLeave(NULL); + return(FALSE); + } + + // set audio parameters + pVoipTranscribe->iAudioRate = VOIPTRANSCRIBE_AUDIORATE; + pVoipTranscribe->eFormat = VOIPTRANSCRIBE_PROFILE_FORMAT(pVoipTranscribe->uProfile); + if (pVoipTranscribe->eProvider != VOIPTRANSCRIBE_PROVIDER_GOOGLE) + { + if (pVoipTranscribe->eFormat == VOIPTRANSCRIBE_FORMAT_LI16) + { + ds_snzprintf(pVoipTranscribe->strAudioFormat, sizeof(pVoipTranscribe->strAudioFormat), "audio/l16; rate=%d; endianness=little-endian", pVoipTranscribe->iAudioRate); + pVoipTranscribe->bCompressed = FALSE; + } + else if (pVoipTranscribe->eFormat == VOIPTRANSCRIBE_FORMAT_WAV16) + { + ds_snzprintf(pVoipTranscribe->strAudioFormat, sizeof(pVoipTranscribe->strAudioFormat), "audio/wav; codec=audio/pcm; samplerate=%d", pVoipTranscribe->iAudioRate); + pVoipTranscribe->bCompressed = FALSE; + } + else if (pVoipTranscribe->eFormat == VOIPTRANSCRIBE_FORMAT_OPUS) + { + ds_strnzcpy(pVoipTranscribe->strAudioFormat, "audio/ogg; codecs=opus", sizeof(pVoipTranscribe->strAudioFormat)); + pVoipTranscribe->bCompressed = TRUE; + } + } + else + { + if (pVoipTranscribe->eFormat == VOIPTRANSCRIBE_FORMAT_LI16) + { + ds_strnzcpy(pVoipTranscribe->strAudioFormat, "LINEAR16", sizeof(pVoipTranscribe->strAudioFormat)); + pVoipTranscribe->bCompressed = FALSE; + } + else if (pVoipTranscribe->eFormat == VOIPTRANSCRIBE_FORMAT_OPUS) + { + ds_strnzcpy(pVoipTranscribe->strAudioFormat, "OGG_OPUS", sizeof(pVoipTranscribe->strAudioFormat)); + pVoipTranscribe->bCompressed = TRUE; + } + } + NetCritLeave(NULL); + + return(TRUE); +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipTranscribeCreate + + \Description + Create the stream module + + \Input iBufSize - size of streaming buffer (at least VOIPTRANSCRIBE_MINBUFFER) + + \Output + VoipTranscribeRefT * - new module state, or NULL + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +VoipTranscribeRefT *VoipTranscribeCreate(int32_t iBufSize) +{ + VoipTranscribeRefT *pVoipTranscribe; + void *pMemGroupUserData; + int32_t iMemGroup; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // enforce minimum buffer size + if (iBufSize < VOIPTRANSCRIBE_MINBUFFER) + { + iBufSize = VOIPTRANSCRIBE_MINBUFFER; + } + + // allocate and init module state + if ((pVoipTranscribe = DirtyMemAlloc(sizeof(*pVoipTranscribe), VOIPTRANSCRIBE_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voiptranscribe: could not allocate module state\n")); + return(NULL); + } + ds_memclr(pVoipTranscribe, sizeof(*pVoipTranscribe)); + pVoipTranscribe->iMemGroup = iMemGroup; + pVoipTranscribe->pMemGroupUserData = pMemGroupUserData; + + // allocate and initialize buffers + if (!_VoipTranscribeBufferInit(pVoipTranscribe, &pVoipTranscribe->VoipBuffer[0], iBufSize, 0) || !_VoipTranscribeBufferInit(pVoipTranscribe, &pVoipTranscribe->VoipBuffer[1], iBufSize, 1)) + { + NetPrintf(("voiptranscribe: could not allocate voip buffers\n")); + VoipTranscribeDestroy(pVoipTranscribe); + return(NULL); + } + // allocate and initialize voip send buffer; this is used for google, which needs data to be encoded + if (!_VoipTranscribeBufferInit(pVoipTranscribe, &pVoipTranscribe->VoipBufferSnd, 2*1024, -1)) + { + NetPrintf(("voiptranscribe: could not allocate voip send buffer\n")); + VoipTranscribeDestroy(pVoipTranscribe); + return(NULL); + } + + // init other state variables + pVoipTranscribe->bMinDiscard = TRUE; + pVoipTranscribe->iConsecEmptyMax = VOIPTRANSCRIBE_CONSECEMPTY; + pVoipTranscribe->iConsecErrorMax = VOIPTRANSCRIBE_CONSECERROR; + pVoipTranscribe->iSndBuffer = -1; + pVoipTranscribe->iVerbose = 1; + + // configure for particular provider + if (!_VoipTranscribeConfig(pVoipTranscribe, _VoipTranscribe_Config.uProfile, _VoipTranscribe_Config.strUrl, _VoipTranscribe_Config.strKey)) + { + NetPrintf(("voiptranscribe: could not configure for provider\n")); + VoipTranscribeDestroy(pVoipTranscribe); + return(NULL); + } + + // return ref to caller + return(pVoipTranscribe); +} + +/*F********************************************************************************/ +/*! + \Function VoipTranscribeConfig + + \Description + Set global configuration of transcription service. This call is required to + specify the provider, url, and credentials that will be used to access + the transcription service. + + \Input uProfile - transcribe profile (VOIPTRANSCRIBE_PROFILE_DISABLED to disable) + \Input *pUrl - transcribe provider url + \Input *pKey - transcribe access key + + \Version 11/08/2018 (tcho) +*/ +/********************************************************************************F*/ +void VoipTranscribeConfig(uint32_t uProfile, const char *pUrl, const char *pKey) +{ + NetCritEnter(NULL); + _VoipTranscribe_Config.uProfile = uProfile; + ds_strnzcpy(_VoipTranscribe_Config.strKey, pKey, sizeof(_VoipTranscribe_Config.strKey)); + ds_strnzcpy(_VoipTranscribe_Config.strUrl, pUrl, sizeof(_VoipTranscribe_Config.strUrl)); + NetCritLeave(NULL); +} + +/*F********************************************************************************/ +/*! + \Function VoipTranscribeDestroy + + \Description + Destroy the VoipTranscribe module + + \Input *pVoipTranscribe - pointer to module state + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipTranscribeDestroy(VoipTranscribeRefT *pVoipTranscribe) +{ + // dispose of audio buffers + if (pVoipTranscribe->VoipBuffer[0].pBuffer != NULL) + { + DirtyMemFree(pVoipTranscribe->VoipBuffer[0].pBuffer, VOIPTRANSCRIBE_MEMID, pVoipTranscribe->iMemGroup, pVoipTranscribe->pMemGroupUserData); + } + if (pVoipTranscribe->VoipBuffer[1].pBuffer != NULL) + { + DirtyMemFree(pVoipTranscribe->VoipBuffer[1].pBuffer, VOIPTRANSCRIBE_MEMID, pVoipTranscribe->iMemGroup, pVoipTranscribe->pMemGroupUserData); + } + if (pVoipTranscribe->VoipBufferSnd.pBuffer != NULL) + { + DirtyMemFree(pVoipTranscribe->VoipBufferSnd.pBuffer, VOIPTRANSCRIBE_MEMID, pVoipTranscribe->iMemGroup, pVoipTranscribe->pMemGroupUserData); + } + + // cleanup transport state + _VoipTranscribeTransportCleanup(pVoipTranscribe); + + // dispose of module memory + DirtyMemFree(pVoipTranscribe, VOIPTRANSCRIBE_MEMID, pVoipTranscribe->iMemGroup, pVoipTranscribe->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function VoipTranscribeSubmit + + \Description + Submit voice data to be transcribed + + \Input *pVoipTranscribe - pointer to module state + \Input *pBuffer - voice data to be transcribed + \Input iBufLen - size of voice data in bytes + + \Output + int32_t - number of bytes copied + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipTranscribeSubmit(VoipTranscribeRefT *pVoipTranscribe, const uint8_t *pBuffer, int32_t iBufLen) +{ + // submit and return amount copied to caller + return(_VoipTranscribeSubmit(pVoipTranscribe, pBuffer, iBufLen)); +} + +/*F********************************************************************************/ +/*! + \Function VoipTranscribeGet + + \Description + Get transcription if available; if a transcription is available, this + call copies it and clears it. + + \Input *pVoipTranscribe - pointer to module state + \Input *pBuffer - [out] output buffer + \Input iBufLen - size of output buffer + + \Output + int32_t - zero=no transcription, else transcription copied + + \Version 09/07/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipTranscribeGet(VoipTranscribeRefT *pVoipTranscribe, char *pBuffer, int32_t iBufLen) +{ + if (pVoipTranscribe->strTranscription[0] == '\0') + { + return(0); + } + ds_strnzcpy((char *)pBuffer, pVoipTranscribe->strTranscription, iBufLen); + pVoipTranscribe->strTranscription[0] = '\0'; + return(1); +} + +/*F********************************************************************************/ +/*! + \Function VoipTranscribeStatus + + \Description + Get module status. + + \Input *pVoipTranscribe - pointer to module state + \Input iStatus - status selector + \Input iValue - selector specific + \Input *pBuffer - selector specific + \Input iBufSize - selector specific + + \Output + int32_t - selector specific + + \Notes + iStatus can be one of the following: + + \verbatim + 'cmpr' - most recent http result code + 'sttm' - get the VoipSpeechToTextMetricsT via pBuf + \endverbatim + + Unrecognized codes are passed down to the transport handler + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipTranscribeStatus(VoipTranscribeRefT *pVoipTranscribe, int32_t iStatus, int32_t iValue, void *pBuffer, int32_t iBufSize) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + // return whether audio format is compressed or not + if (iStatus == 'cmpr') + { + return(pVoipTranscribe->bCompressed); + } + if (iStatus == 'sttm') + { + if ((pBuffer != NULL) && (iBufSize >= (int32_t)sizeof(VoipSpeechToTextMetricsT))) + { + ds_memcpy_s(pBuffer, iBufSize, &pVoipTranscribe->Metrics, sizeof(VoipSpeechToTextMetricsT)); + return(0); + } + return(-1); + } + return(pTransport->Status(pTransport->pState, pTransport->iStreamId, iStatus, pBuffer, iBufSize)); +} + +/*F********************************************************************************/ +/*! + \Function VoipTranscribeControl + + \Description + Set control options + + \Input *pVoipTranscribe - pointer to module state + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Output + int32_t - selector specific + + \Notes + iStatus can be one of the following: + + \verbatim + 'cstm' - clear speech to text metrics in VoipSpeechToTextMetricsT + 'spam' - set verbose debug level (debug only) + 'time' - set timeout value + 'vdis' - set voice discard on minimum threshold (default=TRUE) + \endverbatim + + Unhandled codes are passed through to the transport handler + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipTranscribeControl(VoipTranscribeRefT *pVoipTranscribe, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + + if (iControl == 'cstm') + { + ds_memclr(&(pVoipTranscribe->Metrics), sizeof(pVoipTranscribe->Metrics)); + return(0); + } + #if DIRTYCODE_LOGGING + // set verbosity for us and pass through to transport handler + if (iControl == 'spam') + { + pVoipTranscribe->iVerbose = iValue; + } + #endif + if (iControl == 'time') + { + // remember most recent timeout value, and pass through to transport handler + pVoipTranscribe->iTimeout = iValue; + } + if (iControl == 'vdis') + { + uint8_t bDiscard = iValue ? TRUE : FALSE; + if (pVoipTranscribe->bMinDiscard != bDiscard) + { + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: min discard %s\n", bDiscard ? "enabled" : "disabled")); + pVoipTranscribe->bMinDiscard = bDiscard; + } + return(0); + } + // if not handled, let transport handler take a stab at it + return(pTransport->Control(pTransport->pState, pTransport->iStreamId, iControl, iValue, iValue2, pValue)); +} + +/*F********************************************************************************/ +/*! + \Function VoipTranscribeUpdate + + \Description + Update the VoipTranscribe module + + \Input *pVoipTranscribe - pointer to module state + + \Version 08/30/2018 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipTranscribeUpdate(VoipTranscribeRefT *pVoipTranscribe) +{ + TransportT *pTransport = &pVoipTranscribe->Transport; + uint32_t uCurTick = NetTick(); + int32_t iResult; + + // give time to transport module + pTransport->Update(pTransport->pState); + + // update backoff processing + _VoipTranscribeUpdateBackoff(pVoipTranscribe); + + // update recording processing + _VoipTranscribeUpdateRecord(pVoipTranscribe, uCurTick); + + /* if we have a websockets connection to watson and are in the idle state, we receive to consume "listening" responses that come after transcription + responses. if we do not read these responses, the unread data prevents us from detecting if the server has timed out the connection on us, and + results in the next transcription request failing */ + if ((pVoipTranscribe->eProvider == VOIPTRANSCRIBE_PROVIDER_IBMWATSON) && (pVoipTranscribe->eTransport == VOIPTRANSCRIBE_TRANSPORT_WEBSOCKETS) && + (pVoipTranscribe->eState == ST_IDLE) && (_VoipTranscribeConnectCheck(pVoipTranscribe, &pVoipTranscribe->Transport) > 0)) + { + pVoipTranscribe->eState = _VoipTranscribeUpdateRecv(pVoipTranscribe); + } + + // check for enough data in the current record buffer to start request + if ((pVoipTranscribe->eState == ST_IDLE) && (pVoipTranscribe->iSndBuffer == -1)) + { + VoipBufferT *pVoipBuffer = &pVoipTranscribe->VoipBuffer[pVoipTranscribe->iRecBuffer]; + // see if we have enough data in our current record buffer to start sending (minimum one second) + if ((pVoipBuffer->iNumSamples < pVoipTranscribe->iAudioRate) && pVoipTranscribe->bMinDiscard) + { + return; + } + // if backoff timer is set, defer sending as we might end up squelching it + if (pVoipTranscribe->uBackoffTimer != 0) + { + return; + } + // set send buffer + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: [%d] starting transcription request on recording buffer\n", pVoipBuffer->iBuffer)); + pVoipTranscribe->iSndBuffer = pVoipTranscribe->iRecBuffer; + } + + // if we're in idle state and have an assigned send buffer, start the request + if ((pVoipTranscribe->eState == ST_IDLE) && (pVoipTranscribe->iSndBuffer != -1)) + { + VoipBufferT *pVoipBuffer = &pVoipTranscribe->VoipBuffer[pVoipTranscribe->iSndBuffer]; + NetPrintfVerbose((pVoipTranscribe->iVerbose, 1, "voiptranscribe: [%d] starting transcription request\n", pVoipBuffer->iBuffer)); + // copy mindiscard flag for this buffer + pVoipBuffer->bMinDiscard = pVoipTranscribe->bMinDiscard; + // perform explicit connection for transport handlers that require it + pVoipTranscribe->eState = (_VoipTranscribeConnect(pVoipTranscribe) >= 0) ? ST_CONN : ST_FAIL; + } + + // update module in connecting state + if (pVoipTranscribe->eState == ST_CONN) + { + // check for connection completion for transport handlers that require it + if ((iResult = _VoipTranscribeConnectCheck(pVoipTranscribe, &pVoipTranscribe->Transport)) < 0) + { + pVoipTranscribe->eState = ST_FAIL; + return; + } + else if (iResult == 0) + { + return; + } + + // make transcription request and transition to sending voice data for transcription if successful + pVoipTranscribe->eState = (_VoipTranscribeRequest(pVoipTranscribe) >= 0) ? ST_SEND : ST_FAIL; + } + + // update while sending the transcription request + if (pVoipTranscribe->eState == ST_SEND) + { + pVoipTranscribe->eState = _VoipTranscribeUpdateSend(pVoipTranscribe, &pVoipTranscribe->VoipBuffer[pVoipTranscribe->iSndBuffer]); + } + + // update while receiving the transcription response + if (pVoipTranscribe->eState == ST_RECV) + { + pVoipTranscribe->eState = _VoipTranscribeUpdateRecv(pVoipTranscribe); + } + + // update when in failed state + if (pVoipTranscribe->eState == ST_FAIL) + { + // keep track of number of consecutive failures + pVoipTranscribe->iConsecErrorCt += 1; + // update overall failure count + pVoipTranscribe->Metrics.uErrorCount += 1; + // set backoff if appropriate + _VoipTranscribeBackoffSet(pVoipTranscribe); + // reset current record buffer + _VoipTranscribeBufferReset(pVoipTranscribe, &pVoipTranscribe->VoipBuffer[pVoipTranscribe->iRecBuffer]); + // go back to idle state + pVoipTranscribe->eState = ST_IDLE; + } +} diff --git a/src/thirdparty/dirtysdk/source/voip/voiptunnel.c b/src/thirdparty/dirtysdk/source/voip/voiptunnel.c new file mode 100644 index 00000000..7bc5d590 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/voip/voiptunnel.c @@ -0,0 +1,2239 @@ +/*H********************************************************************************/ +/*! + \File voiptunnel.c + + \Description + This module implements the main logic for the VoipTunnel server. + + Description forthcoming. + + \Copyright + Copyright (c) 2006 Electronic Arts Inc. + + \Version 03/24/2006 (jbrookes) First Version +*/ +/********************************************************************************H*/ + +/*** Include files ****************************************************************/ + +#include +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/dirtysock/dirtymem.h" +#include "DirtySDK/dirtysock/dirtynet.h" +#include "DirtySDK/dirtysock/netconn.h" + +#include "DirtySDK/voip/voipdef.h" +#include "DirtySDK/voip/voiptunnel.h" +#include "voippriv.h" +#include "voippacket.h" + +/*** Defines **********************************************************************/ + +//! define this as TRUE to enable lookup table test code +#define VOIPTUNNEL_LOOKUP_TEST (FALSE) + +//! voiptunnel broadcast flags +#define VOIPTUNNEL_GAMEFLAG_BROADCAST_ALL 0 +#define VOIPTUNNEL_GAMEFLAG_BROADCAST_ALLOTHERS 1 +#define VOIPTUNNEL_GAMEFLAG_SEND_SINGLE 2 +#define VOIPTUNNEL_GAMEFLAG_SEND_MULTI 4 +#define VOIPTUNNEL_GAMEFLAG_MASK 0xf + +#define VOIPTUNNEL_GAMEFLAG_VDP 16 + +/*** Type Definitions *************************************************************/ + +//! VoIP packet data that this module cares about (header + first 4 bytes) +typedef struct VoipTunnelPacketT +{ + VoipPacketHeadT Head; //!< packet header + uint8_t aRemoteClientId[4]; //!< present in all packets except for mic packets, which have the send mask here instead +} VoipTunnelPacketT; + +//! client lookup table element +typedef struct VoipTunnelLookupElemT +{ + uint32_t uClientId; + uint32_t uClientIdx; +} VoipTunnelLookupElemT; + +//! module state +struct VoipTunnelRefT +{ + // module memory group + int32_t iMemGroup; //!< module mem group id + void *pMemGroupUserData; //!< user data associated with mem group + + SocketT *pVoipSocket; //!< virtual socket for receiving tunneled voip data + VoipTunnelCallbackT *pCallback; //!< optional voiptunnel event callback + void *pUserData; //!< user data for voiptunnel callback + uint32_t uLocalClientId; //!< local client id (used for tunnel connectivity) + uint16_t uVoipPort; //!< virtual voip port + uint16_t uVoiceRecvTimeout; //!< number of milliseconds before clearing RECVVOICE flag + uint8_t uDebugLevel; //!< debug level + uint8_t bPortSniff; //!< TRUE if port sniffing enabled, else FALSE (default FALSE) + uint8_t _pad[2]; + int32_t iNumClients; //!< number of tunnel clients + int32_t iNumTalkingClients; //!< number of "talking" clients (i.e. client with VOIPTUNNEL_CLIENTFLAG_RECVVOICE flag set) + int32_t iMaxClients; //!< maximum number tunnel clients + int32_t iMaxVoiceBroadcasters; //!< maximum number of players can be talking at once in a single game + VoipTunnelLookupElemT *pLookupTable; //!< fast-lookup table + int32_t iNumGames; //!< current number of games + int32_t iMaxGames; //!< maximum number of supported gameservers + uint32_t uVoiceDataDropMetric; //!< the number of voice packets that were not rebroadcast globally due to max broadcasters constraint + uint32_t uVoiceMaxTalkersMetric;//!< the number of time the max broadcasters event was reached + VoipTunnelGameT *pGameList; //!< list of games + VoipTunnelClientT ClientList[1]; //!< variable-length tunnel client list -- MUST COME LAST IN THE STRUCTURE +}; + +/*** Variables ********************************************************************/ + + +/*** Private Functions ************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelEventCallback + + \Description + Call user event callback, if it is available. + + \Input *pVoipTunnel - voiptunnel ref + \Input eEvent - event triggering callback + \Input *pClient - pointer to client associated with event + \Input iDataSize - size of packet data associated with event + + \Version 03/01/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipTunnelEventCallback(VoipTunnelRefT *pVoipTunnel, VoipTunnelEventE eEvent, VoipTunnelClientT *pClient, int32_t iDataSize) +{ + if (pVoipTunnel->pCallback != NULL) + { + VoipTunnelEventDataT EventData; + EventData.eEvent = eEvent; + EventData.pClient = pClient; + EventData.iDataSize = iDataSize; + pVoipTunnel->pCallback(pVoipTunnel, &EventData, pVoipTunnel->pUserData); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelClientListSend + + \Description + Broadcast input data to all other clients in our session. + + \Input *pVoipTunnel - voiptunnel ref + \Input *pSrcClient - pointer to client that sent the data + \Input uDstClientId - clientId to send to, if SEND_SINGLE + \Input *pPacketData - pointer to packet data + \Input iPacketSize - size of packet data + \Input *pAddr - address data came from + \Input uSendFlag - send flags + + \Version 03/31/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipTunnelClientListSend(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pSrcClient, uint32_t uDstClientId, const char *pPacketData, int32_t iPacketSize, struct sockaddr *pAddr, uint32_t uSendFlag) +{ + VoipTunnelClientT *pClient; + int32_t iClient, iResult; + + // if vdp, restore the header + if (uSendFlag & VOIPTUNNEL_GAMEFLAG_VDP) + { + pPacketData -= 2; + iPacketSize += 2; + } + + // unicast or broadcast? + if (!(uSendFlag & VOIPTUNNEL_GAMEFLAG_SEND_SINGLE)) + { + VoipTunnelGameT *pGame = &pVoipTunnel->pGameList[pSrcClient->iGameIdx]; + + // forward data data to other clients in session + for (iClient = 0; iClient < VOIPTUNNEL_MAXGROUPSIZE; iClient++) + { + // make sure the client is active + if (pGame->bClientActive[iClient] == FALSE) + { + continue; + } + + // ref client + if ((pClient = VoipTunnelClientListMatchId(pVoipTunnel, pGame->aClientList[iClient])) == NULL) + { + continue; + } + + // don't send data to source client + if ((uSendFlag & VOIPTUNNEL_GAMEFLAG_BROADCAST_ALLOTHERS) && (pClient == pSrcClient)) + { + continue; + } + + // if send mask not set for this client, skip them + if ((pSrcClient->uSendMask & (1 << (unsigned)iClient)) == 0) + { + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: client send mask mute of voice from clientId=0x%08x to clientId=0x%08x\n", pSrcClient->uClientId, pClient->uClientId)); + continue; + } + + // if game send mask not set for this client, skip them + if ((pSrcClient->uGameSendMask & (1 << (unsigned)iClient)) == 0) + { + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: game send mask mute of voice from clientId=0x%08x to clientId=0x%08x\n", pSrcClient->uClientId, pClient->uClientId)); + continue; + } + + // rewrite address to send to this client + SockaddrInSetAddr(pAddr, pClient->uRemoteAddr); + // if port sniffing is enabled, rewrite port as well + if (pVoipTunnel->bPortSniff) + { + // don't send if we don't have a port to send to + if (pClient->uRemoteVoipPort == 0) + { + continue; + } + SockaddrInSetPort(pAddr, pClient->uRemoteVoipPort); + } + + if ((iResult = SocketSendto(pVoipTunnel->pVoipSocket, pPacketData, iPacketSize, 0, pAddr, sizeof(*pAddr))) == iPacketSize) + { + // call user callback + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_SENDVOICE, pClient, iPacketSize); + } + else + { + NetPrintf(("voiptunnel: send of %d byte voice packet from clientId=0x%08x to clientId=0x%08x failed (err=%d)\n", iPacketSize, pSrcClient->uClientId, pClient->uClientId, iResult)); + } + } + } + else + { + if ((pClient = VoipTunnelClientListMatchId(pVoipTunnel, uDstClientId)) != NULL) + { + // rewrite address to send to this client + SockaddrInSetAddr(pAddr, pClient->uRemoteAddr); + // if port sniffing is enabled, rewrite port as well + if (pVoipTunnel->bPortSniff) + { + // don't send if we don't have a port to send to + if (pClient->uRemoteVoipPort == 0) + { + return; + } + SockaddrInSetPort(pAddr, pClient->uRemoteVoipPort); + } + + if ((iResult = SocketSendto(pVoipTunnel->pVoipSocket, pPacketData, iPacketSize, 0, pAddr, sizeof(*pAddr))) == iPacketSize) + { + // call user callback + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_SENDVOICE, pClient, iPacketSize); + } + else + { + NetPrintf(("voiptunnel: send of %d byte voice packet from clientId=0x%08x to clientId=0x%08x failed (err=%d)\n", iPacketSize, pSrcClient->uClientId, pClient->uClientId, iResult)); + } + } + else + { + NetPrintf(("voiptunnel: unable to ref client with id=0x%08x for unicast send\n", uDstClientId)); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelValidatePacketType + + \Description + Returns whether the data points to a valid packet type or not. + + \Input *pPacketData - pointer to type to evaluate + + \Version 08/24/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTunnelValidatePacketType(const char *pPacketData) +{ + return(!memcmp(pPacketData, "CO", 2) || !memcmp(pPacketData, "DSC", 3) || !memcmp(pPacketData, "PNG", 3) || !memcmp(pPacketData, "MIC", 3)); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelValidatePacket + + \Description + Locate packet data and validate packet + + \Input *pVoipTunnel - voip tunnel + \Input *pPacketData - pointer to packet head + \Input iPacketSize - size of received data + \Input pVdpHeader - vdp header + + \Output + const VoipTunnelPacketT *pPacket - pointer to packet + + \Version 08/24/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static VoipTunnelPacketT *_VoipTunnelValidatePacket(VoipTunnelRefT *pVoipTunnel, char *pPacketData, int32_t iPacketSize, uint32_t *pVdpHeader) +{ + // try and determine if there is a VDP header or not + if (_VoipTunnelValidatePacketType(pPacketData+2)) + { + // VDP header... so adjust the pointer to account for it + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 2, "voiptunnel: vdp header detected\n")); + pPacketData += 2; + iPacketSize -= 2; + *pVdpHeader = TRUE; + } + else if (_VoipTunnelValidatePacketType(pPacketData)) + { + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 2, "voiptunnel: no vdp header\n")); + *pVdpHeader = FALSE; + } + else + { + NetPrintf(("voiptunnel: unknown VoIP packet type; discarding\n")); + return(NULL); + } + + // validate size + if (iPacketSize < (signed)sizeof(VoipTunnelPacketT)) + { + NetPrintf(("voiptunnel: voip packet size is too small; discarding\n")); + return(NULL); + } + + return((VoipTunnelPacketT *)pPacketData); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelGetClientId + + \Description + Read client ID from voice packet + + \Input *pPacket - pointer to packet + \Input iPacketSize - size of received data + + \Output + int32_t - clientId, or zero + + \Version 05/16/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static uint32_t _VoipTunnelGetClientId(const VoipTunnelPacketT *pPacket, int32_t iPacketSize) +{ + uint32_t uClientId; + + // extract the client identifier + uClientId = pPacket->Head.aClientId[0] << 24; + uClientId |= pPacket->Head.aClientId[1] << 16; + uClientId |= pPacket->Head.aClientId[2] << 8; + uClientId |= pPacket->Head.aClientId[3]; + + // return to caller + return(uClientId); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelLookupSort + + \Description + qsort callback used to sort fast lookup array. + + \Input *_pElem0 - pointer to first element to compare + \Input *_pElem1 - pointer to second element to compare + + \Output + int32_t - sort value (one or minus one) + + \Version 04/03/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTunnelLookupSort(const void *_pElem0, const void *_pElem1) +{ + const VoipTunnelLookupElemT *pElem0 = (const VoipTunnelLookupElemT *)_pElem0; + const VoipTunnelLookupElemT *pElem1 = (const VoipTunnelLookupElemT *)_pElem1; + + return((pElem0->uClientId > pElem1->uClientId) ? 1 : -1); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelLookupClient + + \Description + Lookup a client given the clientId using binary search on sorted array. + + \Input *pVoipTunnel - module state + \Input uClientId - client identifier + + \Output + VoipTunnelClientT * - pointer to found client, or NULL if not found + + \Version 04/03/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static VoipTunnelClientT *_VoipTunnelLookupClient(VoipTunnelRefT *pVoipTunnel, uint32_t uClientId) +{ + int32_t iCheck, iLow, iHigh; + uint32_t uCheckId; + + // execute binary search on sorted lookup table + for (iLow = 0, iHigh = pVoipTunnel->iNumClients-1; iLow <= iHigh; ) + { + iCheck = iLow + ((iHigh - iLow) / 2); + if ((uCheckId = pVoipTunnel->pLookupTable[iCheck].uClientId) > uClientId) + { + iHigh = iCheck - 1; + } + else if (uCheckId < uClientId) + { + iLow = iCheck + 1; + } + else + { + #if VOIPTUNNEL_LOOKUP_TEST + NetPrintf(("voiptunnel: lookup found client id=0x%08x\n", uClientId)); + #endif + return(&pVoipTunnel->ClientList[pVoipTunnel->pLookupTable[iCheck].uClientIdx]); + } + } + + // not found + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: lookup could not find client id=0x%08x\n", uClientId)); + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelLookupBuild + + \Description + Build a lookup table sorted by clientId for fast lookups. + + \Input *pVoipTunnel - module state + + \Version 04/03/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipTunnelLookupBuild(VoipTunnelRefT *pVoipTunnel) +{ + int32_t iClient; + + // only build it if the table has been allocated + if (pVoipTunnel->pLookupTable == NULL) + { + return; + } + + // build unsorted table + for (iClient = 0; iClient < pVoipTunnel->iNumClients; iClient++) + { + pVoipTunnel->pLookupTable[iClient].uClientId = pVoipTunnel->ClientList[iClient].uClientId; + pVoipTunnel->pLookupTable[iClient].uClientIdx = (unsigned)iClient; + } + + // sort it by clientId + qsort(pVoipTunnel->pLookupTable, iClient, sizeof(pVoipTunnel->pLookupTable[0]), _VoipTunnelLookupSort); +} + +#if VOIPTUNNEL_LOOKUP_TEST +/*F********************************************************************************/ +/*! + \Function _VoipTunnelLookupTest + + \Description + Test code to run lookup code through a few simple test cases. + + \Input *pVoipTunnel - module state + + \Version 04/03/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipTunnelLookupTest(VoipTunnelRefT *pVoipTunnel) +{ + VoipTunnelLookupElemT _LookupTable[32]; + + // set up temp table for testing + pVoipTunnel->pLookupTable = _LookupTable; + + // test no elements + pVoipTunnel->iNumClients = 0; + _VoipTunnelLookupBuild(pVoipTunnel); + _VoipTunnelLookupClient(pVoipTunnel, 0); + + // test one element + pVoipTunnel->iNumClients = 1; + pVoipTunnel->ClientList[0].uClientId = 0xdeadbeef; + _VoipTunnelLookupBuild(pVoipTunnel); + _VoipTunnelLookupClient(pVoipTunnel, 0xdeadbeef); + + // test two elements + pVoipTunnel->iNumClients = 2; + pVoipTunnel->ClientList[1].uClientId = 0xcacabeef; + _VoipTunnelLookupBuild(pVoipTunnel); + _VoipTunnelLookupClient(pVoipTunnel, 0xdeadbeef); + _VoipTunnelLookupClient(pVoipTunnel, 0xcacabeef); + + // test three elements + pVoipTunnel->iNumClients = 3; + pVoipTunnel->ClientList[2].uClientId = 0xf000beef; + _VoipTunnelLookupBuild(pVoipTunnel); + _VoipTunnelLookupClient(pVoipTunnel, 0xdeadbeef); + _VoipTunnelLookupClient(pVoipTunnel, 0xcacabeef); + _VoipTunnelLookupClient(pVoipTunnel, 0xf000beef); + + // test many elements + pVoipTunnel->iNumClients = 8; + pVoipTunnel->ClientList[3].uClientId = 0xf000b00f; + pVoipTunnel->ClientList[4].uClientId = 0xf000baaf; + pVoipTunnel->ClientList[5].uClientId = 0xeaa0beef; + pVoipTunnel->ClientList[6].uClientId = 0x0000beef; + pVoipTunnel->ClientList[7].uClientId = 0x1000beef; + _VoipTunnelLookupBuild(pVoipTunnel); + _VoipTunnelLookupClient(pVoipTunnel, 0xdeadbeef); + _VoipTunnelLookupClient(pVoipTunnel, 0xcacabeef); + _VoipTunnelLookupClient(pVoipTunnel, 0xf000beef); + _VoipTunnelLookupClient(pVoipTunnel, 0xf000b00f); + _VoipTunnelLookupClient(pVoipTunnel, 0xf000baaf); + _VoipTunnelLookupClient(pVoipTunnel, 0xeaa0beef); + _VoipTunnelLookupClient(pVoipTunnel, 0x0000beef); + _VoipTunnelLookupClient(pVoipTunnel, 0x1000beef); + + // reset + pVoipTunnel->iNumClients = 0; + pVoipTunnel->pLookupTable = NULL; + ds_memclr(pVoipTunnel->ClientList, sizeof(pVoipTunnel->ClientList)); +} +#endif + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelReleaseVoipBroadcastingSlotForTargetClient + + \Description + Releases the broadcasting slot that the client possesses as an originator + when sending voip to the target client (consumer). + + \Input *pVoipTunnel - module state + \Input *pSourceClient - source client + \Input iTargetClientIndex - client index to remove + + \Output + int32_t - updated sendmask for the source client. + + \Notes + voice squelching: There are only 4 broadcasting slots per consumer client. + Only 4 originator clients at a time can send voip to a specific + consumer client. + + \Version 02/01/2019 (amakoukji) +*/ +/********************************************************************************F*/ +static uint32_t _VoipTunnelReleaseVoipBroadcastingSlotForTargetClient(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pSourceClient, int32_t iTargetClientIndex) +{ + // if this is a client we're talking to + if ((pSourceClient->uSendMask) & (1 << iTargetClientIndex)) + { + VoipTunnelClientT *pTargetClient = VoipTunnelClientListMatchId(pVoipTunnel, pSourceClient->aClientIds[iTargetClientIndex]); + + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: 0x%08x is not talking to 0x%08x anymore.\n", pSourceClient->uClientId, pTargetClient ? pTargetClient->uClientId : 0xFFFFFFFF)); + + pSourceClient->uSendMask &= ~(1 << iTargetClientIndex); + if (pTargetClient) + { + pTargetClient->iNumTalker--; + if (pTargetClient->uFlags & VOIPTUNNEL_CLIENTFLAG_MAX_VOICES_REACHED) + { + pTargetClient->uFlags &= ~VOIPTUNNEL_CLIENTFLAG_MAX_VOICES_REACHED; + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_AVLBVOICE, pTargetClient, pSourceClient->iGameIdx); + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: 0x%08x is accepting new talkers again.\n", pTargetClient->uClientId)); + } + } + } + + return(pSourceClient->uSendMask); +} + + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelAcquireVoipBroadcastingSlot + + \Description + Allocates sendmask slots to the target clients if some are available. + + \Input *pVoipTunnel - module state + \Input *pClient - client ref + \Input uTargetSendMask - the clients we want to send to + + \Output + int32_t - acquired sendmask for the required client. + + \Notes + voice squelching: There are only 4 broadcasting slots per consumer client. + Only 4 originator clients at a time can send voip to a specific + consumer client. + + \Version 01/15/2010 (cvienneau) +*/ +/********************************************************************************F*/ +static uint32_t _VoipTunnelAcquireVoipBroadcastingSlot(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pClient, uint32_t uTargetSendMask) +{ + int32_t iIndex; + uint32_t uAcquiredSendMask = pClient->uSendMask; + VoipTunnelGameT *pGame = &pVoipTunnel->pGameList[pClient->iGameIdx]; + uint32_t uToReleaseMask = 0; + uint32_t uToAddMask = 0; + + if (uAcquiredSendMask == uTargetSendMask) + { + return(uAcquiredSendMask); + } + + // if there's one or more targets we no longer need, release + uToReleaseMask = (uAcquiredSendMask & (~uTargetSendMask)); + if (uToReleaseMask != 0) + { + for (iIndex = 0; iIndex < VOIPTUNNEL_MAXGROUPSIZE; iIndex++) + { + // if this is a client we'd like to release but haven't yet + if (uToReleaseMask & (1 << iIndex)) + { + uAcquiredSendMask = _VoipTunnelReleaseVoipBroadcastingSlotForTargetClient(pVoipTunnel, pClient, iIndex); + } + } + } + + // if there's one or more target we have not yet acquired, try to acquire + uToAddMask = ((~uAcquiredSendMask) & uTargetSendMask); + if (uToAddMask != 0) + { + for (iIndex = 0; iIndex < VOIPTUNNEL_MAXGROUPSIZE; iIndex++) + { + // if this is a client we'd like to talk to, but don't already + if (uToAddMask & (1 << iIndex)) + { + VoipTunnelClientT *pTargetClient = VoipTunnelClientListMatchId(pVoipTunnel, pClient->aClientIds[iIndex]); + + if (pTargetClient) + { + if (pTargetClient->iNumTalker < pVoipTunnel->iMaxVoiceBroadcasters) + { + pTargetClient->iNumTalker++; + + uAcquiredSendMask |= (1 << iIndex); + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: 0x%08x is now talking to 0x%08x\n", pClient->uClientId, pTargetClient->uClientId)); + } + else + { + // we've just reached the maximum number of clients allowed to send, send an event and set the flag + if (!(pTargetClient->uFlags & VOIPTUNNEL_CLIENTFLAG_MAX_VOICES_REACHED)) + { + pTargetClient->uFlags |= VOIPTUNNEL_CLIENTFLAG_MAX_VOICES_REACHED; + pVoipTunnel->uVoiceMaxTalkersMetric++; + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_MAXDVOICE, pTargetClient, pClient->iGameIdx); + + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: 0x%08x is not taking new talkers (will not get voice from 0x%08x)\n", pTargetClient->uClientId, pClient->uClientId)); + } + + pVoipTunnel->uVoiceDataDropMetric++; + pGame->uVoiceDataDropMetric++; + } + } + } + } + } + + return(uAcquiredSendMask); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelReleaseAllVoipBroadcastingSlots + + \Description + Releases all broadcasting slots that the client possesses as an originator. + + \Input *pVoipTunnel - module state + \Input *pClient - client ref + + \Notes + voice squelching: There are only 4 broadcasting slot per consumer clients. + Only 4 originator clients at a time can send voip to a specific + consumer client. + + \Version 01/15/2010 (cvienneau) +*/ +/********************************************************************************F*/ +static void _VoipTunnelReleaseAllVoipBroadcastingSlots(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pClient) +{ + int32_t iIndex; + + for(iIndex = 0; iIndex < VOIPTUNNEL_MAXGROUPSIZE; iIndex++) + { + // if this is a client we're talking to + if ((pClient->uSendMask) & (1 << iIndex)) + { + _VoipTunnelReleaseVoipBroadcastingSlotForTargetClient(pVoipTunnel, pClient, iIndex); + } + } +} + \ +/*F********************************************************************************/ +/*! + \Function _VoipTunnelRouteVoipPacket + + \Description + Routes incoming VoIP packet to appropriate destination(s). + + \Input *pVoipTunnel - voip tunnel ref + \Input *pClient - source client + \Input *pPacket - voip packet + \Input iPacketSize - size of packet + \Input *pRecvAddr - source address + \Input uCurTick - current tick + \Input bVdpHeader - TRUE if there was a VDP header, else FALSE + + \Version 03/27/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipTunnelRouteVoipPacket(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pClient, const VoipTunnelPacketT *pPacket, int32_t iPacketSize, struct sockaddr *pRecvAddr, uint32_t uCurTick, uint32_t bVdpHeader) +{ + uint32_t uSendFlag = (bVdpHeader) ? VOIPTUNNEL_GAMEFLAG_VDP : 0; + + // see if we have a packet type that is unicast + if (!memcmp(pPacket->Head.aType, "CO", 2) || !memcmp(pPacket->Head.aType, "PNG", 3) || !memcmp(pPacket->Head.aType, "DSC", 3)) + { + uint32_t uRemoteClientId; + + // extract voip packet version from connection packet if we haven't done so already + if ((pPacket->Head.aType[0] == 'C') && (pClient->uPacketVersion == 0)) + { + pClient->uPacketVersion = pPacket->Head.aType[2]; + NetPrintf(("voiptunnel: detected voip packet format '%c' from clientId=0x%08x\n", (char)pClient->uPacketVersion, pClient->uClientId)); + } + + // extract the remote client identifier + uRemoteClientId = pPacket->aRemoteClientId[0] << 24; + uRemoteClientId |= pPacket->aRemoteClientId[1] << 16; + uRemoteClientId |= pPacket->aRemoteClientId[2] << 8; + uRemoteClientId |= pPacket->aRemoteClientId[3]; + + NetPrintfVerbose((pVoipTunnel->uDebugLevel, pPacket->Head.aType[0] == 'P' ? 2 : 1, "voiptunnel: forwarding %d byte unicast packet of type %c%c%c from clientId=0x%08x to clientId=0x%08x\n", + iPacketSize, pPacket->Head.aType[0], pPacket->Head.aType[1], pPacket->Head.aType[2], + pClient->uClientId, uRemoteClientId)); + + // forward to other clients in our group + _VoipTunnelClientListSend(pVoipTunnel, pClient, uRemoteClientId, (const char *)pPacket, iPacketSize, pRecvAddr, uSendFlag|VOIPTUNNEL_GAMEFLAG_SEND_SINGLE); + } + else if (!memcmp(pPacket->Head.aType, "MIC", 3)) + { + uint32_t uSendMask = (uint32_t)-1; + + // update recv voice timestamp and mark that we are receiving voip mic data from this client + pClient->uLastRecvVoice = uCurTick; + if ((pClient->uFlags & VOIPTUNNEL_CLIENTFLAG_RECVVOICE) == 0) + { + pClient->uFlags |= VOIPTUNNEL_CLIENTFLAG_RECVVOICE; + pVoipTunnel->iNumTalkingClients++; + pVoipTunnel->pGameList[pClient->iGameIdx].iNumTalkingClients++; + } + + + // extract send mask if packet version supports it + if (pClient->uPacketVersion >= (unsigned)'c') + { + uSendMask = pPacket->aRemoteClientId[0] << 24; + uSendMask |= pPacket->aRemoteClientId[1] << 16; + uSendMask |= pPacket->aRemoteClientId[2] << 8; + uSendMask |= pPacket->aRemoteClientId[3]; + } + + // update the client we have permission to send to + pClient->uSendMask = _VoipTunnelAcquireVoipBroadcastingSlot(pVoipTunnel, pClient, uSendMask); + + if (pClient->uSendMask != 0) + { + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: broadcasting voip packet %d from clientId=0x%08x\n", pPacket->aRemoteClientId[0], pClient->uClientId)); + + // broadcast to all other clients in our group + _VoipTunnelClientListSend(pVoipTunnel, pClient, 0, (const char *)pPacket, iPacketSize, pRecvAddr, uSendFlag|VOIPTUNNEL_GAMEFLAG_BROADCAST_ALLOTHERS); + } + else + { + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: failed to acquire voip broadcasting slot for voip packet %d from clientId=0x%08x\n", pPacket->aRemoteClientId[0], pClient->uClientId)); + } + } + else + { + NetPrintf(("voiptunnel: unknown VoIP packet type; ignoring\n")); + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelVoipRecvCallback + + \Description + Callback handler for data received on the virtual Voip socket. + + \Input *pSocket - pointer to socket + \Input iFlags - unused + \Input *_pRef - voiptunnel ref + + \Output + int32_t - zero + + \Version 03/27/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTunnelVoipRecvCallback(SocketT *pSocket, int32_t iFlags, void *_pRef) +{ + VoipTunnelRefT *pVoipTunnel = (VoipTunnelRefT *)_pRef; + int32_t iAddrLen = sizeof(struct sockaddr), iRecvLen; + char aPacketData[SOCKET_MAXUDPRECV]; + const VoipTunnelPacketT *pPacket; + VoipTunnelClientT *pClient; + struct sockaddr RecvAddr; + uint32_t uClientId, bVdpHeader=0; + uint32_t uCurTick = NetTick(); + + // got any input? + if ((iRecvLen = SocketRecvfrom(pSocket, aPacketData, sizeof(aPacketData), 0, &RecvAddr, &iAddrLen)) <= 0) + { + return(0); + } + + // validate the packet + if ((pPacket = _VoipTunnelValidatePacket(pVoipTunnel, aPacketData, iRecvLen, &bVdpHeader)) == NULL) + { + return(0); + } + + // extract client identifier from voip packet header + uClientId = _VoipTunnelGetClientId(pPacket, iRecvLen); + + // if we don't have a client with this id yet, bail + if ((pClient = VoipTunnelClientListMatchId(pVoipTunnel, uClientId)) == NULL) + { + NetPrintf(("voiptunnel: ignoring '%c%c%c' voip packet from %a:%d with unregistered id=0x%08x\n", + pPacket->Head.aType[0], pPacket->Head.aType[1], pPacket->Head.aType[2], + SockaddrInGetAddr(&RecvAddr), SockaddrInGetPort(&RecvAddr), uClientId)); + return(0); + } + + // if the address isn't set yet, set it now + if (pClient->uRemoteAddr != (unsigned)SockaddrInGetAddr(&RecvAddr)) + { + // get source client address + pClient->uRemoteAddr = SockaddrInGetAddr(&RecvAddr); + NetPrintf(("voiptunnel: matching clientId=0x%08x to addr %a\n", pClient->uClientId, pClient->uRemoteAddr)); + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_MATCHADDR, pClient, 0); + } + // if the voip port isn't set yet, set it now + if (pClient->uRemoteVoipPort != SockaddrInGetPort(&RecvAddr)) + { + // get source client port + pClient->uRemoteVoipPort = SockaddrInGetPort(&RecvAddr); + NetPrintf(("voiptunnel: matching clientId=0x%08x to port %d\n", pClient->uClientId, pClient->uRemoteVoipPort)); + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_MATCHPORT, pClient, 0); + } + + // call user callback + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_RECVVOICE, pClient, iRecvLen); + + // forward voip data to others in our group according to packet type + _VoipTunnelRouteVoipPacket(pVoipTunnel, pClient, pPacket, iRecvLen, &RecvAddr, uCurTick, bVdpHeader); + + // update source client timestamp + pClient->uLastUpdate = uCurTick; + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelPrintGameClientList + + \Description + Prints the clients in the game + + \Input *pVoipTunnel - voiptunnel ref + \Input iGameIdx - index of game print clients from + + \Version 01/28/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _VoipTunnelPrintGameClientList(VoipTunnelRefT *pVoipTunnel, int32_t iGameIdx) +{ + int32_t iClient; + const VoipTunnelGameT *pGame = &pVoipTunnel->pGameList[iGameIdx]; + + NetPrintf(("voiptunnel: game %d clients\n", iGameIdx)); + for (iClient = 0; iClient < VOIPTUNNEL_MAXGROUPSIZE; iClient++) + { + if (pGame->aClientList[iClient] != 0) + { + NetPrintf(("voiptunnel: [%d] 0x%08x %s\n", iClient, pGame->aClientList[iClient], pGame->bClientActive[iClient] ? "ACTIVE" : "INACTIVE")); + } + } +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelClientGameDel + + \Description + Remove client from the game list + + \Input *pVoipTunnel - voiptunnel ref + \Input *pClient - client to remove + \Input iGameIdx - index of game to remove client from + + \Output + int32_t - negative=error, else success + + \Version 06/10/2007 (jbrookes) +*/ +/********************************************************************************F*/ +static int32_t _VoipTunnelClientGameDel(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pClient, int32_t iGameIdx) +{ + // remove entry from game list + VoipTunnelGameT *pGame = &pVoipTunnel->pGameList[iGameIdx]; + int32_t iGameClient; + + // scan array to find client id + for (iGameClient = 0; iGameClient < VOIPTUNNEL_MAXGROUPSIZE; iGameClient++) + { + // found client? + if (pGame->aClientList[iGameClient] == pClient->uClientId) + { + NetPrintf(("voiptunnel: removing clientId=0x%08x from game %d\n", pClient->uClientId, iGameIdx)); + + // if the user was talking as they left, free up that broadcasting slot + _VoipTunnelReleaseAllVoipBroadcastingSlots(pVoipTunnel, pClient); + + // expire recvvoice flag? + if (pClient->uFlags & VOIPTUNNEL_CLIENTFLAG_RECVVOICE) + { + pClient->uFlags &= ~VOIPTUNNEL_CLIENTFLAG_RECVVOICE; + pVoipTunnel->iNumTalkingClients--; + pVoipTunnel->pGameList[iGameIdx].iNumTalkingClients--; + + // catch a potential error if this number goes below 0. + // this occured in GOS-31044 so we place this safeguard here + // if this occurs the metric can no longer be relied upon to be + // accurate but should still be close to the appropriate value, + // rather than negative, which will be problematic for the dashboard. + if (pVoipTunnel->iNumTalkingClients < 0) + { + NetPrintf(("voiptunnel: [%p] error tracking number of talking clients, game %d\n", pVoipTunnel, iGameIdx)); + pVoipTunnel->iNumTalkingClients = 0; + } + } + + // clear entry from game list + pGame->aClientList[iGameClient] = 0; + pGame->bClientActive[iGameClient] = FALSE; + + // decrement client count + pGame->iNumClients -= 1; + // debug echo of game client list after delete + if (pVoipTunnel->uDebugLevel > 0) + { + _VoipTunnelPrintGameClientList(pVoipTunnel, iGameIdx); + } + // update send masks for other clients in this game + for (iGameClient = 0; iGameClient < VOIPTUNNEL_MAXGROUPSIZE; iGameClient++) + { + // make sure the client is active + if (pGame->bClientActive[iGameClient] == FALSE) + { + continue; + } + + // ref the client + if ((pClient = VoipTunnelClientListMatchId(pVoipTunnel, pGame->aClientList[iGameClient])) != NULL) + { + // refresh send mask + VoipTunnelClientRefreshSendMask(pVoipTunnel, pClient); + } + } + return(0); + } + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelSuspendGame + + \Description + Suspends the current active game + + \Input *pVoipTunnel - voiptunnel ref + \Input *pClient - the client we are updating + + \Output + int32_t - zero=success, else=failure + + \Version 01/19/2016 (eesponda) +*/ +/********************************************************************************F*/ +static int32_t _VoipTunnelSuspendGame(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pClient) +{ + int32_t iGameClient; + VoipTunnelSuspendInfoT *pSuspendInfo = &pClient->aSuspendedData[pClient->iNumSuspended]; + VoipTunnelGameT *pGame = &pVoipTunnel->pGameList[pClient->iGameIdx]; + + if (pSuspendInfo->iGameIdx >= 0) + { + NetPrintf(("voiptunnel: invalid data in suspend information for client id=0x%08x game already suspended at this slot\n", pClient->uClientId)); + return(-1); + } + + // refcount the client + NetPrintf(("voiptunnel: refcounting client id=0x%08x suspending game %d\n", pClient->uClientId, pClient->iGameIdx)); + + // update suspended information + pSuspendInfo->iGameIdx = pClient->iGameIdx; + ds_strnzcpy(pSuspendInfo->strTunnelKey, pClient->strTunnelKey, sizeof(pSuspendInfo->strTunnelKey)); + ds_memcpy_s(pSuspendInfo->aClientIds, sizeof(pSuspendInfo->aClientIds), pClient->aClientIds, sizeof(pClient->aClientIds)); + pSuspendInfo->iNumClients = pClient->iNumClients; + pClient->iNumSuspended += 1; + + // deactivate client in game for gamesend mask calculation + for (iGameClient = 0; iGameClient < VOIPTUNNEL_MAXGROUPSIZE; iGameClient += 1) + { + if (pGame->aClientList[iGameClient] == pClient->uClientId) + { + pGame->bClientActive[iGameClient] = FALSE; + break; + } + } + // update send masks for other clients in this game (old) + for (iGameClient = 0; iGameClient < VOIPTUNNEL_MAXGROUPSIZE; iGameClient += 1) + { + VoipTunnelClientT *pClientInfo; + + // make sure the client is active + if (pGame->bClientActive[iGameClient] == FALSE) + { + continue; + } + + // ref the client + if ((pClientInfo = VoipTunnelClientListMatchId(pVoipTunnel, pGame->aClientList[iGameClient])) != NULL) + { + // refresh send mask + VoipTunnelClientRefreshSendMask(pVoipTunnel, pClientInfo); + } + } + return(0); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelUnsuspendGame + + \Description + Takes the last suspended game for the client and makes it active + + \Input *pVoipTunnel - voiptunnel ref + \Input *pClient - the client we are updating + + \Version 01/19/2016 (eesponda) +*/ +/********************************************************************************F*/ +static void _VoipTunnelUnsuspendGame(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pClient) +{ + int32_t iGameClient; + VoipTunnelSuspendInfoT *pSuspendInfo = &pClient->aSuspendedData[pClient->iNumSuspended - 1]; + VoipTunnelGameT *pGame = &pVoipTunnel->pGameList[pSuspendInfo->iGameIdx]; + + NetPrintf(("voiptunnel: decrementing refcount client id=0x%08x unsuspending game %d\n", pClient->uClientId, pSuspendInfo->iGameIdx)); + + // make suspend information the active information + pClient->iGameIdx = pSuspendInfo->iGameIdx; + ds_strnzcpy(pClient->strTunnelKey, pSuspendInfo->strTunnelKey, sizeof(pClient->strTunnelKey)); + ds_memcpy(pClient->aClientIds, pSuspendInfo->aClientIds, sizeof(pClient->aClientIds)); + pClient->iNumClients = pSuspendInfo->iNumClients; + + // active our client in new active game + for (iGameClient = 0; iGameClient < VOIPTUNNEL_MAXGROUPSIZE; iGameClient += 1) + { + // found client? + if (pGame->aClientList[iGameClient] == pClient->uClientId) + { + pGame->bClientActive[iGameClient] = TRUE; + break; + } + } + + // update send masks for active clients + for (iGameClient = 0; iGameClient < VOIPTUNNEL_MAXGROUPSIZE; iGameClient += 1) + { + VoipTunnelClientT *pClientInfo; + + if (pGame->bClientActive[iGameClient] == FALSE) + { + continue; + } + + if ((pClientInfo = VoipTunnelClientListMatchId(pVoipTunnel, pGame->aClientList[iGameClient])) != NULL) + { + VoipTunnelClientRefreshSendMask(pVoipTunnel, pClientInfo); + } + } + + // clear out the data + ds_memset(pSuspendInfo, -1, sizeof(*pSuspendInfo)); + + // update number of suspended games + pClient->iNumSuspended -= 1; +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelSocketOpen + + \Description + Open a DirtySock socket. + + \Input *pVoipTunnel - module state + \Input uPort - port to bind + \Input *pCallback - socket callback function + + \Output + SocketT * - socket ref, or NULL + + \Version 03/27/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static SocketT *_VoipTunnelSocketOpen(VoipTunnelRefT *pVoipTunnel, uint16_t uPort, int32_t (*pCallback)(SocketT *pSocket, int32_t iFlags, void *pRef)) +{ + struct sockaddr BindAddr; + SocketT *pSocket; + int32_t iResult; + + // open the socket + if ((pSocket = SocketOpen(AF_INET, SOCK_DGRAM, IPPROTO_IP)) == NULL) + { + NetPrintf(("voiptunnel: unable to open socket\n")); + return(NULL); + } + + // bind socket to specified port + SockaddrInit(&BindAddr, AF_INET); + SockaddrInSetPort(&BindAddr, uPort); + if ((iResult = SocketBind(pSocket, &BindAddr, sizeof(BindAddr))) != SOCKERR_NONE) + { + NetPrintf(("voiptunnel: error %d binding to port\n", iResult)); + SocketClose(pSocket); + return(NULL); + } + + // set up for socket callback events + SocketCallback(pSocket, CALLB_RECV, 100, pVoipTunnel, pCallback); + + // return ref to caller + return(pSocket); +} + +/*F********************************************************************************/ +/*! + \Function _VoipTunnelClientListUpdate + + \Description + Perform update processing on client list + + \Input *pVoipTunnel - module state + + \Version 09/18/2006 (jbrookes) +*/ +/********************************************************************************F*/ +static void _VoipTunnelClientListUpdate(VoipTunnelRefT *pVoipTunnel) +{ + VoipTunnelClientT *pClient; + uint32_t uCurTick; + int32_t iClient; + + // loop through clientlist + for (iClient = 0, uCurTick = NetTick(); iClient < pVoipTunnel->iNumClients; iClient++) + { + // ref client + pClient = &pVoipTunnel->ClientList[iClient]; + + // expire recvvoice flag? + if (pClient->uFlags & VOIPTUNNEL_CLIENTFLAG_RECVVOICE) + { + if (NetTickDiff(uCurTick, pClient->uLastRecvVoice) > (int32_t)pVoipTunnel->uVoiceRecvTimeout) + { + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: stopped receiving voice from clientId=0x%08x\n", pClient->uClientId)); + pClient->uFlags &= ~VOIPTUNNEL_CLIENTFLAG_RECVVOICE; + pVoipTunnel->iNumTalkingClients--; + pVoipTunnel->pGameList[pClient->iGameIdx].iNumTalkingClients--; + _VoipTunnelReleaseAllVoipBroadcastingSlots(pVoipTunnel, pClient); + + // catch a potential error if this number goes below 0. + // this occured in GOS-31044 so we place this safeguard here + // if this occurs the metric can no longer be relied upon to be + // accurate but should still be close to the appropriate value, + // rather than negative, which will be problematic for the dashboard. + if (pVoipTunnel->iNumTalkingClients < 0) + { + NetPrintf(("voiptunnel: [%p] error tracking number of talking clients\n", pVoipTunnel)); + pVoipTunnel->iNumTalkingClients = 0; + } + } + } + + /* check for connection death (have previously received data, but have not received + data for at least the timeout period. timeout is client timeout plus five seconds + so that clients that are being removed from the game don't produce this warning */ + if ((pClient->uRemoteVoipPort != 0) && ((signed)(uCurTick - pClient->uLastUpdate) > 15*1000)) + { + NetPrintf(("voiptunnel: clientId=0x%08x voice connection has gone dead\n", pClient->uClientId)); + /* clear voip point so 1) we don't send to them 2) we don't warn again and 3) in + the unlikely event data starts flowing again somehow, we get notified by a new + match event */ + pClient->uRemoteVoipPort = 0; + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_DEADVOICE, pClient, 0); + } + } +} + + +/*** Public functions *************************************************************/ + + +/*F********************************************************************************/ +/*! + \Function VoipTunnelCreate + + \Description + Create the VoipTunnel module. + + \Input uVoipPort - local virtual voip port + \Input iMaxClients - max number of clients allowed + \Input iMaxGames - max number of games allowed + + \Output + VoipTunnelRefT * - new module state pointer, or null + + \Version 04/06/2006 (jbrookes) +*/ +/********************************************************************************F*/ +VoipTunnelRefT *VoipTunnelCreate(uint32_t uVoipPort, int32_t iMaxClients, int32_t iMaxGames) +{ + VoipTunnelRefT *pVoipTunnel; + int32_t iMemSize = sizeof(*pVoipTunnel) + (sizeof(pVoipTunnel->ClientList[0]) * (iMaxClients - 1)); + int32_t iGameListSize; + int32_t iMemGroup; + void *pMemGroupUserData; + + // Query current mem group data + DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); + + // allocate and init module state + if ((pVoipTunnel = DirtyMemAlloc(iMemSize, VOIPTUNNEL_MEMID, iMemGroup, pMemGroupUserData)) == NULL) + { + NetPrintf(("voiptunnel: could not allocate module state\n")); + return(NULL); + } + ds_memclr(pVoipTunnel, iMemSize); + pVoipTunnel->iMemGroup = iMemGroup; + pVoipTunnel->pMemGroupUserData = pMemGroupUserData; + pVoipTunnel->uVoipPort = uVoipPort; + pVoipTunnel->iMaxClients = iMaxClients; + pVoipTunnel->iMaxGames = iMaxGames; + pVoipTunnel->iMaxVoiceBroadcasters = VOIPTUNNEL_MAX_BROADCASTING_VOICES_DEFAULT; + pVoipTunnel->uVoiceRecvTimeout = VOIPTUNNEL_RECVVOICE_TIMEOUT_DEFAULT; + + // test lookup table + #if VOIPTUNNEL_LOOKUP_TEST + _VoipTunnelLookupTest(pVoipTunnel); + #endif + + // allocate game list + iGameListSize = sizeof(*pVoipTunnel->pGameList) * iMaxGames; + if ((pVoipTunnel->pGameList = DirtyMemAlloc(iGameListSize, VOIPTUNNEL_MEMID, pVoipTunnel->iMemGroup, pVoipTunnel->pMemGroupUserData)) == NULL) + { + NetPrintf(("voiptunnel: unable to allocate game list\n")); + // destroy anything that was started + VoipTunnelDestroy(pVoipTunnel); + return(NULL); + } + ds_memset(pVoipTunnel->pGameList, -1, iGameListSize); + + // allocate fast lookup table if warranted + if (iMaxClients >= 32) + { + if ((pVoipTunnel->pLookupTable = DirtyMemAlloc(sizeof(*pVoipTunnel->pLookupTable) * iMaxClients, VOIPTUNNEL_MEMID, pVoipTunnel->iMemGroup, pVoipTunnel->pMemGroupUserData)) == NULL) + { + NetPrintf(("voiptunnel: unable to allocate fast lookup table\n")); + } + } + + // create voip socket + if ((pVoipTunnel->pVoipSocket = _VoipTunnelSocketOpen(pVoipTunnel, uVoipPort, _VoipTunnelVoipRecvCallback)) == NULL) + { + // destroy anything that was started + VoipTunnelDestroy(pVoipTunnel); + return(NULL); + } + + // return ref to caller + return(pVoipTunnel); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelCallback + + \Description + Set optional voiptunnel event callback + + \Input *pVoipTunnel - voiptunnel ref + \Input *pCallback - callback pointer + \Input *pUserData - callback user data + + \Version 03/24/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipTunnelCallback(VoipTunnelRefT *pVoipTunnel, VoipTunnelCallbackT *pCallback, void *pUserData) +{ + pVoipTunnel->pCallback = pCallback; + pVoipTunnel->pUserData = pUserData; +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelDestroy + + \Description + Shutdown this VoipTunnel. + + \Input *pVoipTunnel - voiptunnel ref + + \Version 03/24/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipTunnelDestroy(VoipTunnelRefT *pVoipTunnel) +{ + // destroy lookup table + if (pVoipTunnel->pLookupTable != NULL) + { + DirtyMemFree(pVoipTunnel->pLookupTable, VOIPTUNNEL_MEMID, pVoipTunnel->iMemGroup, pVoipTunnel->pMemGroupUserData); + } + // destroy game list + if (pVoipTunnel->pGameList != NULL) + { + DirtyMemFree(pVoipTunnel->pGameList, VOIPTUNNEL_MEMID, pVoipTunnel->iMemGroup, pVoipTunnel->pMemGroupUserData); + } + // destroy virtual voip socket + if (pVoipTunnel->pVoipSocket != NULL) + { + SocketClose(pVoipTunnel->pVoipSocket); + } + // dispose of module memory + DirtyMemFree(pVoipTunnel, VOIPTUNNEL_MEMID, pVoipTunnel->iMemGroup, pVoipTunnel->pMemGroupUserData); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelClientListAdd + + \Description + Add a new client to the client list + + \Input *pVoipTunnel - voiptunnel ref + \Input *pClientInfo - new client info + \Input **ppNewClient - [out] new client pointer (optional; can be NULL) + + \Output + int32_t - negative=error, else success + + \Version 03/27/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipTunnelClientListAdd(VoipTunnelRefT *pVoipTunnel, const VoipTunnelClientT *pClientInfo, VoipTunnelClientT **ppNewClient) +{ + return(VoipTunnelClientListAdd2(pVoipTunnel, pClientInfo, ppNewClient, 0)); +} + + +/*F********************************************************************************/ +/*! + \Function VoipTunnelClientListAdd2 + + \Description + Add a new client to the client list + + \Input *pVoipTunnel - voiptunnel ref, at the first empty spot past a given index + \Input *pClientInfo - new client info + \Input **ppNewClient - [out] new client pointer (optional; can be NULL) + \Input iStartIndex - index to add at + + \Output + int32_t - negative=error, else success + + \Version 10/18/2011 (jrainy) +*/ +/********************************************************************************F*/ +int32_t VoipTunnelClientListAdd2(VoipTunnelRefT *pVoipTunnel, const VoipTunnelClientT *pClientInfo, VoipTunnelClientT **ppNewClient, int32_t iStartIndex) +{ + VoipTunnelClientT *pNewClient; + VoipTunnelGameT *pGame; + int32_t iGameClient; + + // make sure there's room in the list + if (pVoipTunnel->iNumClients >= pVoipTunnel->iMaxClients) + { + NetPrintf(("voiptunnel: max client limit exceeded\n")); + return(-1); + } + + // make sure game index is valid + if (pClientInfo->iGameIdx >= pVoipTunnel->iMaxGames) + { + NetPrintf(("voiptunnel: invalid game index %d\n", pClientInfo->iGameIdx)); + return(-2); + } + + // make sure game has been initialized and there's room in the game + pGame = &pVoipTunnel->pGameList[pClientInfo->iGameIdx]; + if (pGame->iNumClients < 0) + { + NetPrintf(("voiptunnel: could not add clientId=0x%08x to game %d when game has not been initialized\n", pClientInfo->uClientId, pClientInfo->iGameIdx)); + return(-3); + } + if (pGame->iNumClients >= VOIPTUNNEL_MAXGROUPSIZE) + { + NetPrintf(("voiptunnel: max game size exceeded trying to add clientId=0x%08x to game %d\n", pClientInfo->uClientId, pClientInfo->iGameIdx)); + return(-4); + } + + // make sure client does not already appear in the game list + for (iGameClient = 0; iGameClient < VOIPTUNNEL_MAXGROUPSIZE; iGameClient++) + { + if (pClientInfo->uClientId == pGame->aClientList[iGameClient]) + { + NetPrintf(("voiptunnel: could not add clientId=0x%08x to game %d when client is already in the game at index %d\n", pClientInfo->uClientId, pClientInfo->iGameIdx, iGameClient)); + return(-5); + } + } + + if (iStartIndex < 0) + { + NetPrintf(("voiptunnel: could not accept negative start index %d\n", iStartIndex)); + return(-7); + } + + // check for clients that are already tracked by the voiptunnel + if ((pNewClient = VoipTunnelClientListMatchId(pVoipTunnel, pClientInfo->uClientId)) == NULL) + { + // allocate new entry + NetPrintf(("voiptunnel: adding new client id=0x%08x\n", pClientInfo->uClientId)); + pNewClient = &pVoipTunnel->ClientList[pVoipTunnel->iNumClients]; + + // initialize new entry + ds_memcpy_s(pNewClient, sizeof(*pNewClient), pClientInfo, sizeof(*pClientInfo)); + + // default send mask to all disabled + // at every packet received, this will be recomputed + pNewClient->uSendMask = 0; + pNewClient->iNumTalker = 0; + + // increment client counts + pVoipTunnel->iNumClients += 1; + + // initialize suspended data + ds_memset(pNewClient->aSuspendedData, -1, sizeof(pNewClient->aSuspendedData)); + } + else if (pNewClient->iNumSuspended >= VOIPTUNNEL_MAXSUSPENDED) + { + NetPrintf(("voiptunnel: no more space to suspend game %d for clientId=0x%08x\n", pNewClient->uClientId, pClientInfo->iGameIdx)); + return(-8); + } + else + { + if (_VoipTunnelSuspendGame(pVoipTunnel, pNewClient) == 0) + { + // update current information + pNewClient->iGameIdx = pClientInfo->iGameIdx; + ds_strnzcpy(pNewClient->strTunnelKey, pClientInfo->strTunnelKey, sizeof(pNewClient->strTunnelKey)); + ds_memclr(pNewClient->aClientIds, sizeof(pNewClient->aClientIds)); // reset the ids so it can be updated later + pNewClient->iNumClients = 0; + } + else + { + // we couldn't suspend, treat as failure + return(-9); + } + } + + // add client to game list in first available slot + for (iGameClient = iStartIndex; (iGameClient < VOIPTUNNEL_MAXGROUPSIZE) && (pGame->aClientList[iGameClient] != 0); iGameClient++) + ; + if (iGameClient < VOIPTUNNEL_MAXGROUPSIZE) + { + pGame->aClientList[iGameClient] = pNewClient->uClientId; + pGame->bClientActive[iGameClient] = TRUE; + } + else + { + NetPrintf(("voiptunnel: could not add clientId=0x%08x to game list for game %d\n", pNewClient->uClientId, pClientInfo->iGameIdx)); + return(-6); + } + + // increment game counts + pGame->iNumClients += 1; + + // debug echo of game client list after add + if (pVoipTunnel->uDebugLevel > 0) + { + _VoipTunnelPrintGameClientList(pVoipTunnel, pNewClient->iGameIdx); + } + + // rebuild lookup table + _VoipTunnelLookupBuild(pVoipTunnel); + + // notify application of add + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_ADDCLIENT, pNewClient, 0); + + // write back new client pointer + if (ppNewClient != NULL) + { + *ppNewClient = pNewClient; + } + // return success + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelClientListDel + + \Description + Remove client from the client list given the context of a game + + \Input *pVoipTunnel - voiptunnel ref + \Input *pClient - client to remove + \Input iGameIdx - index of the game we are removing client from + + \Version 04/10/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipTunnelClientListDel(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pClient, int32_t iGameIdx) +{ + int32_t iGame = -1; + // get client index from client pointer + int32_t iClient = (int32_t)(pClient - pVoipTunnel->ClientList); + + // make sure index is valid + if ((iClient < 0) || (iClient >= pVoipTunnel->iMaxClients)) + { + NetPrintf(("voiptunnel: invalid client index %d specified for deletion\n", iClient)); + return; + } + + // if invalid then take the active game index from the client + if (iGameIdx < 0) + { + iGameIdx = pClient->iGameIdx; + } + + // if we are not removing from the active game then we have to + // make sure to find the game in the suspended data + if (pClient->iGameIdx != iGameIdx) + { + for (iGame = 0; iGame < pClient->iNumSuspended; iGame += 1) + { + if (pClient->aSuspendedData[iGame].iGameIdx == iGameIdx) + { + break; + } + } + if (iGame == pClient->iNumSuspended) + { + NetPrintfVerbose((pVoipTunnel->uDebugLevel, 1, "voiptunnel: could not find game %d for client 0x%08x suspended data\n", iGameIdx, pClient->uClientId)); + return; + } + } + + // notify application of upcoming deletion to allow for deletion of any associate resources + // pass in the slot (non-zero based) to signal if we are removing active/inactive game + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_DELCLIENT, pClient, iGame+1); + + // remove entry from game + if (_VoipTunnelClientGameDel(pVoipTunnel, pClient, iGameIdx) < 0) + { + NetPrintf(("voiptunnel: could not find clientId=0x%08x in game %d\n", pClient->uClientId, pClient->iGameIdx)); + } + + // if deleting from the client's active game use the normal delete logic + if (pClient->iGameIdx == iGameIdx) + { + // unsuspend a game if any are available + if (pClient->iNumSuspended != 0) + { + _VoipTunnelUnsuspendGame(pVoipTunnel, pClient); + return; + } + + // remove entry from client list + if (iClient != (pVoipTunnel->iNumClients - 1)) + { + int32_t iNumMoveClients = (pVoipTunnel->iNumClients - 1) - iClient; + + // move the clients to remove the gap + memmove(pClient, pClient + 1, iNumMoveClients * sizeof(VoipTunnelClientT)); + } + // decrement count + pVoipTunnel->iNumClients -= 1; + + // rebuild lookup table + _VoipTunnelLookupBuild(pVoipTunnel); + } + // otherwise remove the suspended game from being tracked + else + { + // only remove the gap if not at the end + if (iGame != (pClient->iNumSuspended - 1)) + { + int32_t iNumMove = (pClient->iNumSuspended - 1) - iGame; + + // move the information to remove the gap + memmove(&pClient->aSuspendedData[iGame], &pClient->aSuspendedData[iGame+1], iNumMove * sizeof(pClient->aSuspendedData[0])); + } + else + { + /* since we are removing from the end (the slot is not being overwritten), we need to clear out the data so + further logic does not believe we are suspended at this slot */ + ds_memset(&pClient->aSuspendedData[pClient->iNumSuspended - 1], -1, sizeof(*pClient->aSuspendedData)); + } + // decrement count + pClient->iNumSuspended -= 1; + } +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelGameListAdd + + \Description + Add a new game. + + \Input *pVoipTunnel - voiptunnel ref + \Input iGameIdx - index of game whose clients are to be removed + + \Output + int32_t - negative=error, else success + + \Version 04/27/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipTunnelGameListAdd(VoipTunnelRefT *pVoipTunnel, int32_t iGameIdx) +{ + return(VoipTunnelGameListAdd2(pVoipTunnel, iGameIdx, 0)); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelGameListAdd2 + + \Description + Add a new game. + + \Input *pVoipTunnel - voiptunnel ref + \Input iGameIdx - index of game whose clients are to be removed + \Input uGameId - unique game identifier + + \Output + int32_t - negative=error, else success + + \Version 12/01/2015 (eesponda) +*/ +/********************************************************************************F*/ +int32_t VoipTunnelGameListAdd2(VoipTunnelRefT *pVoipTunnel, int32_t iGameIdx, uint32_t uGameId) +{ + if (pVoipTunnel->iNumGames >= pVoipTunnel->iMaxGames) + { + NetPrintf(("voiptunnel: game list overflow -- could not add game\n")); + return(-1); + } + if (pVoipTunnel->pGameList[iGameIdx].iNumClients != -1) + { + NetPrintf(("voiptunnel: refusing game %d add request; game is already active\n", iGameIdx)); + return(-2); + } + pVoipTunnel->iNumGames++; + ds_memclr(&pVoipTunnel->pGameList[iGameIdx], sizeof(pVoipTunnel->pGameList[0])); + pVoipTunnel->pGameList[iGameIdx].uGameId = uGameId; + + // notify application of add + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_ADDGAME, NULL, iGameIdx); + + return(0); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelGameListDel + + \Description + Remove all clients from the client list who have the given GameServer index + + \Input *pVoipTunnel - voiptunnel ref + \Input iGameIdx - index of game whose clients are to be removed + + \Output + int32_t - number of clients deleted + + \Version 04/27/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipTunnelGameListDel(VoipTunnelRefT *pVoipTunnel, int32_t iGameIdx) +{ + int32_t iClient, iClientsDeleted; + if( iGameIdx < pVoipTunnel->iMaxGames) + { + // iterate through client list, and delete all clients associated with iGameServer + for (iClient = 0, iClientsDeleted = 0; iClient < pVoipTunnel->iNumClients; ) + { + VoipTunnelClientT *pClient = &pVoipTunnel->ClientList[iClient]; + // not a client we're looking for? + if (pClient->iGameIdx == iGameIdx) + { + // delete client from list + VoipTunnelClientListDel(pVoipTunnel, pClient, iGameIdx); + + // increment deleted count + iClientsDeleted += 1; + } + else + { + // delete client from list (suspended) + VoipTunnelClientListDel(pVoipTunnel, pClient, iGameIdx); + + // move to next client as when in an inactive game a client is not removed + iClient++; + } + } + + // notify application of upcoming deletion to allow for deletion of any associated resources + _VoipTunnelEventCallback(pVoipTunnel, VOIPTUNNEL_EVENT_DELGAME, NULL, iGameIdx); + + // wipe game and mark as unallocated + ds_memclr(&pVoipTunnel->pGameList[iGameIdx], sizeof(pVoipTunnel->pGameList[0])); + pVoipTunnel->pGameList[iGameIdx].iNumClients = -1; + pVoipTunnel->iNumGames--; + } + else + { + iClientsDeleted = 0; + NetPrintf(("voiptunnel: skipping delete of game %d, out of bounds.\n", iGameIdx)); + } + + // return number of clients deleted + return(iClientsDeleted); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelClientListMatchAddr + + \Description + Return client matching given address. + + \Input *pVoipTunnel - voiptunnel ref + \Input uRemoteAddr - address incoming packet originated from + + \Output + VoipTunnelClientT * - pointer to new client, or NULL + + \Version 03/27/2006 (jbrookes) +*/ +/********************************************************************************F*/ +VoipTunnelClientT *VoipTunnelClientListMatchAddr(VoipTunnelRefT *pVoipTunnel, uint32_t uRemoteAddr) +{ + VoipTunnelClientT *pClient; + int32_t iClientId; + + // iterate through client list and look for a match + for (iClientId = 0, pClient = &pVoipTunnel->ClientList[iClientId]; iClientId < pVoipTunnel->iNumClients; iClientId++, pClient++) + { + // is there a match? + if (pClient->uRemoteAddr == uRemoteAddr) + { + return(pClient); + } + } + + // no match + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelClientListMatchId + + \Description + Return client matching given address. + + \Input *pVoipTunnel - voiptunnel ref + \Input uClientId - unique client identifier + + \Output + VoipTunnelClientT * - pointer to matching client, or NULL + + \Version 03/27/2006 (jbrookes) +*/ +/********************************************************************************F*/ +VoipTunnelClientT *VoipTunnelClientListMatchId(VoipTunnelRefT *pVoipTunnel, uint32_t uClientId) +{ + VoipTunnelClientT *pClient; + int32_t iClient; + + // ignore invalid clientId + if (uClientId == 0) + { + return(NULL); + } + + // use lookup? + if (pVoipTunnel->pLookupTable != NULL) + { + pClient = _VoipTunnelLookupClient(pVoipTunnel, uClientId); + } + else + { + // iterate through client list and look for a match + for (iClient = 0, pClient = NULL; iClient < pVoipTunnel->iNumClients; iClient++) + { + // is there a match? + if (pVoipTunnel->ClientList[iClient].uClientId == uClientId) + { + pClient = &pVoipTunnel->ClientList[iClient]; + break; + } + } + } + + // return client to caller, or NULL if no match + return(pClient); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelClientListMatchIndex + + \Description + Return client at the given index. + + \Input *pVoipTunnel - voiptunnel ref + \Input uClientIndex - index of client in client list + + \Output + VoipTunnelClientT * - pointer to client, or NULL + + \Version 03/13/2007 (jbrookes) +*/ +/********************************************************************************F*/ +VoipTunnelClientT *VoipTunnelClientListMatchIndex(VoipTunnelRefT *pVoipTunnel, uint32_t uClientIndex) +{ + VoipTunnelClientT *pClient = NULL; + if (uClientIndex < (unsigned)pVoipTunnel->iNumClients) + { + pClient = &pVoipTunnel->ClientList[uClientIndex]; + } + return(pClient); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelClientListMatchSockaddr + + \Description + Return client matching the address and port in the specified sockaddr + + \Input *pVoipTunnel - voiptunnel ref + \Input *pSockaddr - sockaddr containing addr and port to match + + \Output + VoipTunnelClientT * - pointer to matching client, or NULL + + \Version 03/13/2007 (jbrookes) +*/ +/********************************************************************************F*/ +VoipTunnelClientT *VoipTunnelClientListMatchSockaddr(VoipTunnelRefT *pVoipTunnel, struct sockaddr *pSockaddr) +{ + VoipTunnelClientT *pClient; + int32_t iClientId; + uint32_t uAddr = SockaddrInGetAddr(pSockaddr); + uint16_t uPort = SockaddrInGetPort(pSockaddr); + + // iterate through client list and look for a match + for (iClientId = 0, pClient = &pVoipTunnel->ClientList[iClientId]; iClientId < pVoipTunnel->iNumClients; iClientId++, pClient++) + { + // is there a match? + if ((pClient->uRemoteAddr == uAddr) && (pClient->uRemoteGamePort == uPort)) + { + return(pClient); + } + } + + // no match + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelClientListMatchFunc + + \Description + Return client matching criteria of matching function + + \Input *pVoipTunnel - voiptunnel ref + \Input *pMatchFunc - function to call to indicate if we have a match or not + \Input *pUserData - user data to pass to match function + + \Output + VoipTunnelClientT * - pointer to found client, or NULL + + \Version 04/10/2007 (jbrookes) +*/ +/********************************************************************************F*/ +VoipTunnelClientT *VoipTunnelClientListMatchFunc(VoipTunnelRefT *pVoipTunnel, VoipTunnelMatchFuncT *pMatchFunc, void *pUserData) +{ + VoipTunnelClientT *pClient; + int32_t iClientId; + + // iterate through client list and look for a match + for (iClientId = 0, pClient = &pVoipTunnel->ClientList[iClientId]; iClientId < pVoipTunnel->iNumClients; iClientId++, pClient++) + { + // is there a match? + if (pMatchFunc(pClient, pUserData) == 0) + { + return(pClient); + } + } + + // no match + return(NULL); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelGameListMatchIndex + + \Description + Return game at the given index. + + \Input *pVoipTunnel - voiptunnel ref + \Input iGameIdx - index of game in game list + + \Output + VoipTunnelGameT * - pointer to game, or NULL + + \Version 12/01/2015 (eesponda) +*/ +/********************************************************************************F*/ +VoipTunnelGameT *VoipTunnelGameListMatchIndex(VoipTunnelRefT *pVoipTunnel, int32_t iGameIdx) +{ + VoipTunnelGameT *pGame = NULL; + if (iGameIdx < pVoipTunnel->iMaxGames) + { + pGame = &pVoipTunnel->pGameList[iGameIdx]; + } + return(pGame); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelGameListMatchId + + \Description + Return game matching given game identifier. + + \Input *pVoipTunnel - voiptunnel ref + \Input uGameId - unique game identifier + \Input *pGameIdx - [out] index the game was at + + \Output + VoipTunnelGameT * - pointer to matching game, or NULL + + \Version 12/01/2015 (eesponda) +*/ +/********************************************************************************F*/ +VoipTunnelGameT *VoipTunnelGameListMatchId(VoipTunnelRefT *pVoipTunnel, uint32_t uGameId, int32_t *pGameIdx) +{ + VoipTunnelGameT *pGame; + int32_t iGameIdx; + + // ignore invalid game id + if (uGameId == 0) + { + return(NULL); + } + // initialize if valid + if (pGameIdx != NULL) + { + *pGameIdx = -1; + } + + // iterate through game list and look for a match + for (iGameIdx = 0, pGame = NULL; iGameIdx < pVoipTunnel->iMaxGames; iGameIdx += 1) + { + if (pVoipTunnel->pGameList[iGameIdx].uGameId == uGameId) + { + pGame = &pVoipTunnel->pGameList[iGameIdx]; + if (pGameIdx != NULL) + { + *pGameIdx = iGameIdx; + } + + break; + } + } + + return(pGame); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelClientRefreshSendMask + + \Description + Let api know client's send list has been updated + + \Input *pVoipTunnel - voiptunnel ref + \Input *pClient - client whose send list has been updated + + \Version 06/10/2007 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipTunnelClientRefreshSendMask(VoipTunnelRefT *pVoipTunnel, VoipTunnelClientT *pClient) +{ + VoipTunnelGameT *pGame = &pVoipTunnel->pGameList[pClient->iGameIdx]; + uint32_t bInList, uGameClientId, uGameSendMask; + int32_t iGameClient, iSendClient; + + // iterate through game list and rebuild client's send mask + for (iGameClient = 0, uGameSendMask = 0; iGameClient < VOIPTUNNEL_MAXGROUPSIZE; iGameClient++) + { + // skip empty or inactive entries + if (pGame->aClientList[iGameClient] == 0 || pGame->bClientActive[iGameClient] == FALSE) + { + continue; + } + // iterate through client's send list + for (bInList = FALSE, iSendClient = 0, uGameClientId = pGame->aClientList[iGameClient]; iSendClient < VOIPTUNNEL_MAXGROUPSIZE; iSendClient++) + { + if (uGameClientId == (unsigned)pClient->aClientIds[iSendClient]) + { + bInList = TRUE; + break; + } + } + // update game send mask + uGameSendMask |= bInList << iGameClient; + } + // write back updated mask + NetPrintf(("voiptunnel: setting clientId=0x%08x gamesend mask to 0x%08x\n", pClient->uClientId, uGameSendMask)); + pClient->uGameSendMask = uGameSendMask; +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelStatus + + \Description + Get module status + + \Input *pVoipTunnel - pointer to module state + \Input iSelect - status selector + \Input iValue - selector specific + \Input *pBuf - [out] - selector specific + \Input iBufSize - size of output buffer + + \Output + int32_t - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + 'game' - copies game info for game index=iValue into buffer; returns zero on success, negative on failure + 'vddm' - retuns the number of voip packets that were not rebroadcast due to limits on max number of senders per game + 'vmtm' - returns the voip max talker metric (number of times the limit on talkers was reached) + 'mgam' - returns the max number of games the voiptunnel supports + 'musr' - returns the max number of clients the voiptunnel supports + 'ngam' - returns the total game count + 'nusr' - if iValue==-1, returns total user count, else returns count of users w/ gameserver==iValue + 'sock' - returns voip socket ref (copied into buffer), if available + 'talk' - returns the current number of active clients that are tagged as "talking" + \endverbatim + + \Version 04/10/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipTunnelStatus(VoipTunnelRefT *pVoipTunnel, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) +{ + if ((iSelect == 'game') && (pBuf != NULL) && (iBufSize == sizeof(VoipTunnelGameT)) && (iValue < pVoipTunnel->iMaxGames)) + { + ds_memcpy(pBuf, &pVoipTunnel->pGameList[iValue], iBufSize); + return(0); + } + if (iSelect == 'mgam') + { + return(pVoipTunnel->iMaxGames); + } + if (iSelect == 'musr') + { + return(pVoipTunnel->iMaxClients); + } + if (iSelect == 'ngam') + { + return(pVoipTunnel->iNumGames); + } + if (iSelect == 'nusr') + { + int32_t iNumClients; + if (iValue == -1) + { + iNumClients = pVoipTunnel->iNumClients; + } + else if (iValue < pVoipTunnel->iMaxGames) + { + iNumClients = pVoipTunnel->pGameList[iValue].iNumClients; + } + else + { + NetPrintf(("voiptunnel: invalid game index (%d) passed to VoipTunnelStatus('nusr') selector\n", iValue)); + iNumClients = -1; + } + return(iNumClients); + } + if (iSelect == 'vddm') + { + return(pVoipTunnel->uVoiceDataDropMetric); + } + if (iSelect == 'vmtm') + { + return(pVoipTunnel->uVoiceMaxTalkersMetric); + } + if ((iSelect == 'sock') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(pVoipTunnel->pVoipSocket))) + { + ds_memcpy(pBuf, &pVoipTunnel->pVoipSocket, sizeof(pVoipTunnel->pVoipSocket)); + return(0); + } + if (iSelect == 'talk') + { + int32_t iNumTalkingClients = -1; // default to error + if (iValue == -1) + { + iNumTalkingClients = pVoipTunnel->iNumTalkingClients; + } + else if (iValue < pVoipTunnel->iMaxGames) + { + iNumTalkingClients = pVoipTunnel->pGameList[iValue].iNumTalkingClients; + } + else + { + NetPrintf(("voiptunnel: invalid game index (%d) passed to VoipTunnelStatus('talk')\n", iValue)); + } + return(iNumTalkingClients); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelControl + + \Description + Control the module + + \Input *pVoipTunnel - pointer to module state + \Input iControl - control selector + \Input iValue - selector specific + \Input iValue2 - selector specific + \Input *pValue - selector specific + + \Notes + iControl can be one of the following: + + \verbatim + 'bvma' - set the maximum number of simultaneous voice broadcasters per game + 'rtim' - set voice receive timeout value (iValue = timeout in ms) + 'spam' - set debug level (iValue = level) + 'xnat' - set port sniffing enable/disable (iValue = TRUE/FALSE, default FALSE) + \endverbatim + + \Version 04/09/2006 (jbrookes) +*/ +/********************************************************************************F*/ +int32_t VoipTunnelControl(VoipTunnelRefT *pVoipTunnel, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue) +{ + if (iControl == 'bvma') + { + NetPrintf(("voiptunnel: maximum number of simultaneous voice broadcasters changed from %d to %d\n", pVoipTunnel->iMaxVoiceBroadcasters, iValue)); + pVoipTunnel->iMaxVoiceBroadcasters = iValue; + return(0); + } + if (iControl == 'rtim') + { + pVoipTunnel->uVoiceRecvTimeout = iValue; + return(0); + } + if (iControl == 'spam') + { + pVoipTunnel->uDebugLevel = iValue; + return(0); + } + if (iControl == 'xnat') + { + NetPrintf(("voiptunnel: port sniffing %s\n", iValue ? "enabled" : "disabled")); + pVoipTunnel->bPortSniff = iValue; + return(0); + } + return(-1); +} + +/*F********************************************************************************/ +/*! + \Function VoipTunnelUpdate + + \Description + Update the module + + \Input *pVoipTunnel - voiptunnel ref + + \Version 03/24/2006 (jbrookes) +*/ +/********************************************************************************F*/ +void VoipTunnelUpdate(VoipTunnelRefT *pVoipTunnel) +{ + // update clientlist + _VoipTunnelClientListUpdate(pVoipTunnel); +} + diff --git a/src/thirdparty/dirtysdk/source/xml/xmlformat.c b/src/thirdparty/dirtysdk/source/xml/xmlformat.c new file mode 100644 index 00000000..584d24ff --- /dev/null +++ b/src/thirdparty/dirtysdk/source/xml/xmlformat.c @@ -0,0 +1,1411 @@ +/*H*************************************************************************************/ +/*! + \File xmlformat.c + + \Description + This module formats simple Xml, in a linear fashion, using a character buffer + that the client provides. See header for more information. + + \Copyright + Copyright (c) Electronic Arts 2004-2008. ALL RIGHTS RESERVED. + + \Notes + \verbatim + When XmlInit() is called, a 24-bytes header is added at the beginning of the + client-provided buffer. The header is an ascii string that looks like this: + + where: + AAAAAAAA is the offset field + BBBBBBBB is the buffer size field + CC is the indent field + DD is the flag field + Those first 24 bytes are replaced by whitespaces when XmlFinish() is called. + + Upon buffer overrun: + 1. _XmlIndent: negative is returned and buffer remain untouched; + 2. _XmlEncodeString: negative is returned and buffer[0] has been reset to '\0' (if length(buffer) > 0); + 3. snzprintf: zero/negative is returned and buffer[0] has been reset to '\0' (if length(buffer) > 0). + + Required buffer capacity for xml = length(xml) + 1. + That means if length(buffer) <= length(xml), buffer-overrun occurs. + \endverbatim + + \Version 01/30/2004 (jbertrand) First Version +*/ +/*************************************************************************************H*/ + +/*** Include files *********************************************************************/ + +#include +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/xml/xmlformat.h" + +/*** Defines ***************************************************************************/ +#define XML_HEADER_LEN 24 +#define XML_MAX_TAG_LENGTH 128 +#define XML_MAX_FLOAT_LENGTH 128 +#define XML_MAX_DATE_LENGTH 32 + +/*** Type Definitions ******************************************************************/ + +/*** Variables *************************************************************************/ +static const unsigned char _Xml_Hex2AsciiTable[16] = "0123456789abcdef"; + +// ascii->hex conversion table for 7bit ASCII characters +static const unsigned char _Xml_Ascii2HexTable[128] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +// 7bit ASCII characters that should be encoded ([1..31 minus tab, cr, lf], <, =, >, &, ", and DEL) +static const unsigned char _Xml_EncodeStringTable[128] = +{ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +}; + +/*** Private Functions *****************************************************************/ +static int32_t _XmlGetOffset(char *pXml) +{ + return((_Xml_Ascii2HexTable[(int32_t)pXml[1]] << 28) | + (_Xml_Ascii2HexTable[(int32_t)pXml[2]] << 24) | + (_Xml_Ascii2HexTable[(int32_t)pXml[3]] << 20) | + (_Xml_Ascii2HexTable[(int32_t)pXml[4]] << 16) | + (_Xml_Ascii2HexTable[(int32_t)pXml[5]] << 12) | + (_Xml_Ascii2HexTable[(int32_t)pXml[6]] << 8) | + (_Xml_Ascii2HexTable[(int32_t)pXml[7]] << 4) | + _Xml_Ascii2HexTable[(int32_t)pXml[8]]); +} + +static int32_t _XmlGetLength(char *pXml) +{ + return((_Xml_Ascii2HexTable[(int32_t)pXml[9]] << 28) | + (_Xml_Ascii2HexTable[(int32_t)pXml[10]] << 24) | + (_Xml_Ascii2HexTable[(int32_t)pXml[11]] << 20) | + (_Xml_Ascii2HexTable[(int32_t)pXml[12]] << 16) | + (_Xml_Ascii2HexTable[(int32_t)pXml[13]] << 12) | + (_Xml_Ascii2HexTable[(int32_t)pXml[14]] << 8) | + (_Xml_Ascii2HexTable[(int32_t)pXml[15]] << 4) | + _Xml_Ascii2HexTable[(int32_t)pXml[16]]); +} + +static int32_t _XmlGetIndent(char *pXml) +{ + return((_Xml_Ascii2HexTable[(int32_t)pXml[17]] << 4) | + _Xml_Ascii2HexTable[(int32_t)pXml[18]]); +} + +static int32_t _XmlGetFlags(char *pXml) +{ + return((_Xml_Ascii2HexTable[(int32_t)pXml[19]] << 4) | + _Xml_Ascii2HexTable[(int32_t)pXml[20]]); +} + +static void _XmlSetOffset(char *pXml, int32_t iOff) +{ + pXml[1] = _Xml_Hex2AsciiTable[(iOff>>28)&15]; + pXml[2] = _Xml_Hex2AsciiTable[(iOff>>24)&15]; + pXml[3] = _Xml_Hex2AsciiTable[(iOff>>20)&15]; + pXml[4] = _Xml_Hex2AsciiTable[(iOff>>16)&15]; + pXml[5] = _Xml_Hex2AsciiTable[(iOff>>12)&15]; + pXml[6] = _Xml_Hex2AsciiTable[(iOff>>8)&15]; + pXml[7] = _Xml_Hex2AsciiTable[(iOff>>4)&15]; + pXml[8] = _Xml_Hex2AsciiTable[(iOff>>0)&15]; +} + +static void _XmlSetLength(char *pXml, int32_t iLen) +{ + pXml[9] = _Xml_Hex2AsciiTable[(iLen>>28)&15]; + pXml[10] = _Xml_Hex2AsciiTable[(iLen>>24)&15]; + pXml[11] = _Xml_Hex2AsciiTable[(iLen>>20)&15]; + pXml[12] = _Xml_Hex2AsciiTable[(iLen>>16)&15]; + pXml[13] = _Xml_Hex2AsciiTable[(iLen>>12)&15]; + pXml[14] = _Xml_Hex2AsciiTable[(iLen>>8)&15]; + pXml[15] = _Xml_Hex2AsciiTable[(iLen>>4)&15]; + pXml[16] = _Xml_Hex2AsciiTable[(iLen>>0)&15]; +} + +static void _XmlSetIndent(char *pXml, int32_t iIndent) +{ + pXml[17] = _Xml_Hex2AsciiTable[(iIndent >> 4) & 15]; + pXml[18] = _Xml_Hex2AsciiTable[(iIndent >> 0) & 15]; +} + +static void _XmlSetFlags(char *pXml, int32_t iFlags) +{ + pXml[19] = _Xml_Hex2AsciiTable[(iFlags >> 4) & 15]; + pXml[20] = _Xml_Hex2AsciiTable[(iFlags >> 0) & 15]; +} + +/*F*************************************************************************************/ +/*! + \Function _XmlValidHeader + + \Description + Check whether buffer is initialized with a valid header. + + \Input *pBuffer - buffer to which xml will be written + + \Output + int32_t - returns 1 if there is a valid header, 0 otherwise +*/ +/*************************************************************************************F*/ +static int32_t _XmlValidHeader(const char *pBuffer) +{ + // Make sure all the header components exist + if ((pBuffer[0] != '<') || (pBuffer[XML_HEADER_LEN-3] != ' ') || (pBuffer[XML_HEADER_LEN-2] != '/') || (pBuffer[XML_HEADER_LEN-1] != '>')) + return(0); + else + return(1); +} + +/*F*************************************************************************************/ +/*! + \Function _XmlOpenTag + + \Description + Check whether there is an open tag in the buffer. + + \Input *pBuffer - buffer to which xml will be written + \Input iOffset - current offset within the buffer + + \Output + int32_t - returns 1 if an open tag exists in the buffer, 0 otherwise +*/ +/*************************************************************************************F*/ +static int32_t _XmlOpenTag(const char *pBuffer, int32_t iOffset) +{ + int32_t iBackPos; + int32_t iBeginCount = 0; + int32_t iEndCount = 0; + + for (iBackPos = (iOffset-1); iBackPos >= XML_HEADER_LEN; iBackPos--) + { + // Count all closing tags (ended tag with no children will be counted as starting and closing tag) + if ( ((pBuffer[iBackPos] == '>') && (pBuffer[iBackPos-1] == '/')) + || ((pBuffer[iBackPos] == '/') && (pBuffer[iBackPos-1] == '<'))) + { + iEndCount++; + iBackPos--; + } + // Count all starting tags + else if (pBuffer[iBackPos] == '<') + { + iBeginCount++; + // If there are more open tags than close tags, return true + if (iBeginCount > iEndCount) + { + return(1); + } + } + } + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function _XmlOpenForAttr + + \Description + Check whether the previous tag can still have attributes written. + + \Input *pBuffer - buffer to which xml will be written + \Input iOffset - current offset within the buffer + + \Output + int32_t - returns 1 if the previous tag can still have attributes written, 0 otherwise +*/ +/*************************************************************************************F*/ +static int32_t _XmlOpenForAttr(const char *pBuffer, int32_t iOffset) +{ + int32_t iBackPos = iOffset-1; + + if ( iOffset < 1 ) + return(0); + + // Buffer does not end with a tag, child text exists + if (pBuffer[iBackPos] != '>') + return(0); + + while (iBackPos >= XML_HEADER_LEN) + { + // Last tag in buffer is already closed + if ((pBuffer[iBackPos] == '>') && (pBuffer[iBackPos-1] == '/')) + return(0); + // Buffer ends with an open tag and no children + else if (pBuffer[iBackPos] == '<') + return(1); + else + iBackPos--; + } + // No tags found in buffer + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function _XmlUpdateHeader + + \Description + Update the XML header to reflect the number of characters written. If the buffer + was full, this function will update the header to indicate the full buffer and will + return the XML_ERR_FULL error code. + + \Input *pBuffer - buffer to which xml will be written + \Input iNumChars - number of characters written, -1 for buffer filled + \Input iLength - length of the buffer + \Input iOffset - current offset within the buffer + \Input iIndent - current indent level + + \Output + int32_t - returns XML_ERR_FULL if buffer was filled, XML_ERR_NONE otherwise +*/ +/*************************************************************************************F*/ +static int32_t _XmlUpdateHeader(char *pBuffer, int32_t iNumChars, int32_t iLength, int32_t iOffset, int32_t iIndent) +{ + if (iNumChars < 0) + { + pBuffer[iLength-1] = 0; + iOffset = iLength; + _XmlSetOffset(pBuffer, iOffset); + return XML_ERR_FULL; + } + else + { + iOffset += iNumChars; + _XmlSetOffset(pBuffer, iOffset); + _XmlSetIndent(pBuffer, iIndent); + return XML_ERR_NONE; + } +} + +/*F****************************************************************************/ +/*! + \Function _XmlIndent + + \Description + Add the appropriate level of whitespace to indent the current tag if + the whitespace flag is enabled for the buffer. + + \Input *pBuffer - buffer to which xml will be written + \Input iLength - length of the buffer + \Input iOffset - current offset within the buffer + \Input iIndent - current indent level + \Input iFlags - flags + + \Output + int32_t - negative=buffer overrun, zero/positive=number of characters written +*/ +/****************************************************************************F*/ +static int32_t _XmlIndent(char *pBuffer, int32_t iLength, int32_t iOffset, int32_t iIndent, int32_t iFlags) +{ + int32_t iNumChars = 0; + +#if DIRTYCODE_LOGGING + if (iIndent < 0) + { + NetPrintf(("xmlformat: warning -- invalid indent level (%d) in _XmlIndent\n", iIndent)); + } +#endif + + if ((iFlags & XML_FL_WHITESPACE) == 0) + iNumChars = 0; + else if (iIndent <= 0) + { + if ((iLength - iOffset) > 1) + { + iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "\n"); + } + else + { + iNumChars = -1; + } + } + else + { + if ((iLength - iOffset) > ((iIndent * 2) + 1)) + { + iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "\n%*c", iIndent * 2, ' '); + } + else + { + iNumChars = -1; + } + } + + return(iNumChars); +} + +/*F*************************************************************************************/ +/*! + \Function _XmlFormatInsert + + \Description + Insert a preformatted item + + \Input *pBuffer - the xml buffer + \Input *pValue - raw string to insert (must be correctly preformatted) + + \Output int32_t - return code. +*/ +/*************************************************************************************F*/ +static int32_t _XmlFormatInsert(char *pBuffer, const char *pValue) +{ + int32_t iWidth, iCount; + int32_t iOffset, iLength, iIndent, iFlags; + + // make sure there is a header and extract fields + if (!_XmlValidHeader(pBuffer)) + return(XML_ERR_UNINIT); + + iOffset = _XmlGetOffset(pBuffer); + iLength = _XmlGetLength(pBuffer); + iIndent = _XmlGetIndent(pBuffer); + iFlags = _XmlGetFlags(pBuffer); + + // there must be an open tag in the buffer + if (!_XmlOpenTag(pBuffer, iOffset)) + { + return(XML_ERR_NOT_OPEN); + } + + // check if there's enough room for the insertion to complete successfully + iWidth = (int32_t)strlen(pValue); + // figure out indent size + iWidth += ((pValue[0] == '<') && (iFlags & XML_FL_WHITESPACE)) ? (1 + iIndent * 2) : 0; + // we must be able to insert completely + if ((iLength - iOffset) <= iWidth) + { + return(XML_ERR_FULL); + } + + // buffer is good, now start to determine the insertion type + // 1. value, handle indent if necessary + if (pValue[0] == '<') + { + if (iFlags & XML_FL_WHITESPACE) + { + pBuffer[iOffset++] = '\n'; + for (iCount = 0; iCount < iIndent; ++iCount) + { + pBuffer[iOffset++] = ' '; + pBuffer[iOffset++] = ' '; + } + } + } + // 2. attr="value"> + else if (strchr(pValue, '"') != NULL) + { + // the tag must be open for accepting attributes (no children and no element text set) + if (!_XmlOpenForAttr(pBuffer, iOffset)) + { + return(XML_ERR_ATTR_POSITION); + } + // replace the close tag '>' to ' ' for input (pValue[] must look like: attr="value">) + pBuffer[iOffset - 1] = ' '; + } + + // for all insertion types, insert the string + for (iCount = 0; pValue[iCount] != 0; ++iCount) + { + pBuffer[iOffset++] = pValue[iCount]; + } + pBuffer[iOffset] = 0; + + // update offset and return + _XmlSetOffset(pBuffer, iOffset); + return(XML_ERR_NONE); +} + +/*F*************************************************************************************/ +/*! + \Function _XmlEncodeString + + \Description + Encode a string with %xx as needed for reserved characters + + \Input *pBuffer - the xml buffer + \Input iLength - number of eight-bit characters available in buffer + \Input *_pSrc - source string to add to buffer + + \Output int32_t - negative=not enough buffer, zero/positive=encoded length. +*/ +/*************************************************************************************F*/ +static int32_t _XmlEncodeString(char *pBuffer, int32_t iLength, const char *_pSrc) +{ + char * const pBackup = pBuffer; + const uint8_t *pSource = (const uint8_t *)_pSrc; + int32_t iRemain; + + // encode the string + for (iRemain = iLength; (iRemain > 1) && (*pSource != '\0'); ++pSource) + { + if ((*pSource < 128) && (_Xml_EncodeStringTable[*pSource])) + { + // hex encode all out of range values + if (iRemain > 6) + { + *pBuffer++ = '&'; + *pBuffer++ = '#'; + *pBuffer++ = 'x'; + *pBuffer++ = _Xml_Hex2AsciiTable[(*pSource)>>4]; + *pBuffer++ = _Xml_Hex2AsciiTable[(*pSource)&0xF]; + *pBuffer++ = ';'; + iRemain -= 6; + } + else // buffer overrun + { + break; + } + } + else + { + // normal encoding + *pBuffer++ = *pSource; + --iRemain; + } + } + + // make sure space for terminator + if (iRemain > 0) + { + *pBuffer = 0; + } + + // if character(s) remains, return negative to indicate buffer-overrun. + if (*pSource != '\0') + { + // if buffer has been changed, reset the change(s) + if (pBackup != pBuffer) + { + *pBackup = '\0'; + } + return(-1); + } + + // return encoded size + return(iLength-iRemain); +} + +/*** Public Functions ******************************************************************/ + +/*F*************************************************************************************/ +/*! + \Function XmlInit + + \Description + Initialize the Xml Buffer. This MUST be called prior to any other calls. + Client can allocate the buffer on stack, but should not access it directly. ( see XmlData() ). + + \Input *pBuffer - buffer to which xml will be written. + \Input iBufLen size of buffer passed in. + \Input uFlags - encoding flags (XMLFORMAT_FL_xxx) +*/ +/*************************************************************************************F*/ +void XmlInit(char *pBuffer, int32_t iBufLen, unsigned char uFlags) +{ + int32_t iOffset; + + ds_memclr(pBuffer, iBufLen); + if (iBufLen > XML_HEADER_LEN) { + iOffset = ds_snzprintf(pBuffer, iBufLen - 1, "<00000000000000000000 />"); + _XmlSetOffset(pBuffer, iOffset); + _XmlSetLength(pBuffer, iBufLen); + _XmlSetFlags(pBuffer, uFlags); + } +} + +/*F*************************************************************************************/ +/*! + \Function XmlBufSizeIncrease + + \Description + Notify the xmlformat API that the buffer size was increased. + + \Input *pBuffer - buffer to which xml is being written. + \Input iNewBufLen new size for that buffer + + \Version 03/30/2010 (mclouatre) +*/ +/*************************************************************************************F*/ +void XmlBufSizeIncrease(char *pBuffer, int32_t iNewBufLen) +{ + _XmlSetLength(pBuffer, iNewBufLen); +} + +/*F*************************************************************************************/ +/*! + \Function XmlFinish + + \Description + Signal completion of output to this buffer. Clear the XML API hidden + data. + + \Input *pBuffer buffer to which xml was written. + + \Output char* - pointer to real XML data (skipping past header) +*/ +/*************************************************************************************F*/ +char *XmlFinish(char *pBuffer) +{ + if (_XmlValidHeader(pBuffer)) + { + ds_memset(pBuffer, ' ', XML_HEADER_LEN); + pBuffer += XML_HEADER_LEN; + } + return(pBuffer); +} + +/*F*************************************************************************************/ +/*! + \Function XmlTagStart + + \Description + Start an xml element (node), to which you can add other elements or attributes. + Must be ended with XmlTagEnd. + + \Input *pBuffer - xml buffer. + \Input *pName - Name of the tag. + + \Output + int32_t - return code. + + \Todo + Consider using var-args for attributes? +*/ +/*************************************************************************************F*/ +int32_t XmlTagStart(char *pBuffer, const char *pName) +{ + int32_t iOffset, iLength, iNumChars, iIndent, iFlags, iTempLength; + + if (!_XmlValidHeader(pBuffer)) + return(XML_ERR_UNINIT); + + iOffset = _XmlGetOffset(pBuffer); + iLength = _XmlGetLength(pBuffer); + iIndent = _XmlGetIndent(pBuffer); + iFlags = _XmlGetFlags(pBuffer); + + if ((iNumChars = _XmlIndent(pBuffer, iLength, iOffset, iIndent++, iFlags)) < 0) + { + return(XML_ERR_FULL); + } + + if ((iTempLength = ds_snzprintf(pBuffer + iOffset + iNumChars, iLength - iOffset - iNumChars, "<%s>", pName)) <= 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(XML_ERR_FULL); + } + iNumChars += iTempLength; + + return _XmlUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent); +} + +/*F*************************************************************************************/ +/*! + \Function XmlTagEnd + + \Description + End the current element or tag -- must have an outstanding open tag. + + \Input *pBuffer - xml buffer. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlTagEnd(char *pBuffer) +{ + int32_t iOffset, iLength, iNumChars; + int32_t iBackPos; + int32_t iEndCount = 0; + int32_t iBeginCount = 0; + int32_t iIndent; + int32_t iFlags; + + if (!_XmlValidHeader(pBuffer)) + return(XML_ERR_UNINIT); + + iOffset = _XmlGetOffset(pBuffer); + iLength = _XmlGetLength(pBuffer); + iIndent = _XmlGetIndent(pBuffer); + iFlags = _XmlGetFlags(pBuffer); + iIndent--; + + for (iBackPos = (iOffset-1); iBackPos >= XML_HEADER_LEN; iBackPos--) + { + // Count all closing tags (ended tag with no children will be counted as starting and closing tag) + if ( ((pBuffer[iBackPos] == '>') && (pBuffer[iBackPos-1] == '/')) + || ((pBuffer[iBackPos] == '/') && (pBuffer[iBackPos-1] == '<'))) + { + iEndCount++; + iBackPos--; + } + // Count all starting tags, if there are more starting than closing tags, we have found the tag to close + else if (pBuffer[iBackPos] == '<') + { + iBeginCount++; + if (iBeginCount > iEndCount) + { + if ((iLength - iOffset) < 3) // "/>" + { + // nothing changed, return(XML_ERR_FULL) directly + return(XML_ERR_FULL); + } + + // If the last tag in the buffer is open and has no children, close it + if ((iEndCount == 0) && (pBuffer[iOffset-1] == '>')) + { + pBuffer[iOffset-1] = ' '; + iNumChars = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, "/>"); + return(_XmlUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent)); + } + // The open tag has children, so create a new closing tag + else + { + char tagBuffer[XML_MAX_TAG_LENGTH + 4]; + int32_t iTagLen = 0; + tagBuffer[iTagLen++] = '<'; + tagBuffer[iTagLen++] = '/'; + tagBuffer[iTagLen] = '0'; + // Scan forward to obtain the name for the closing tag + do + { + iBackPos++; + tagBuffer[iTagLen] = pBuffer[iBackPos]; + iTagLen++; + } while ((iBackPos < iLength) && (pBuffer[iBackPos+1] != ' ') && (pBuffer[iBackPos+1] != '>') && (iTagLen < XML_MAX_TAG_LENGTH-1)); + tagBuffer[iTagLen++] = '>'; + tagBuffer[iTagLen] = 0; + if (((iNumChars = _XmlIndent(pBuffer, iLength, iOffset, iIndent, iFlags)) < 0) + || ((iLength - iOffset) <= (iTagLen + iNumChars))) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(XML_ERR_FULL); + } + iNumChars += ds_snzprintf(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, "%s", tagBuffer); + return(_XmlUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent)); + } + } + } + } + return(0); +} + +/*F*************************************************************************************/ +/*! + \Function XmlElemAddString + + \Description + Add a complete, contained text element. Builds start and end tag. + Nothing can be appended to this tag. + + \Input *pBuffer - xml buffer. + \Input *pElemName - name of the element (tag). + \Input *pValue - value of the element. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlElemAddString(char *pBuffer, const char *pElemName, const char *pValue) +{ + int32_t iOffset, iLength, iNumChars, iIndent, iFlags, iTempLength; + + if (!_XmlValidHeader(pBuffer)) + return(XML_ERR_UNINIT); + + iOffset = _XmlGetOffset(pBuffer); + iLength = _XmlGetLength(pBuffer); + iIndent = _XmlGetIndent(pBuffer); + iFlags = _XmlGetFlags(pBuffer); + + // there must be an open tag in the buffer + if (_XmlOpenTag(pBuffer, iOffset)) + { + if ((iNumChars = _XmlIndent(pBuffer, iLength, iOffset, iIndent, iFlags)) < 0) + { + return(XML_ERR_FULL); + } + + // + if ((iTempLength = ds_snzprintf(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, "<%s>", pElemName)) <= 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(XML_ERR_FULL); + } + iNumChars += iTempLength; + + // Value + if ((iTempLength = _XmlEncodeString(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, pValue)) < 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(XML_ERR_FULL); + } + iNumChars += iTempLength; + + // + if ((iTempLength = ds_snzprintf(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, "", pElemName)) <= 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(XML_ERR_FULL); + } + iNumChars += iTempLength; + + // all done successfully + return(_XmlUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent)); + } + else + { + return(XML_ERR_NOT_OPEN); + } +} + +/*F*************************************************************************************/ +/*! + \Function XmlElemAddInt + + \Description + Add a complete, contained integer element. Builds start and end tag. + Nothing can be appended to this tag. + + \Input *pBuffer - xml buffer. + \Input *pElemName - name of the element (tag). + \Input iValue - integer value of the element. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlElemAddInt(char *pBuffer, const char *pElemName, int32_t iValue) +{ + char strInt[32]; // [32] is large enough for int32_t string + + ds_snzprintf(strInt, sizeof(strInt), "%d", iValue); + return(XmlElemAddString(pBuffer, pElemName, strInt)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlElemAddFloat + + \Description + Add a complete, contained decimal element. Builds start and end tag. + Nothing can be appended to this tag. Format it using formatSpec. + + \Input *pBuffer - xml buffer. + \Input *pElemName - name of the element (tag). + \Input *pFormatSpec - Spec for formatting the float (see sprintf). + \Input fValue - float value of the element. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlElemAddFloat(char *pBuffer, const char *pElemName, const char *pFormatSpec, float fValue) +{ + char strFloat[XML_MAX_FLOAT_LENGTH]; + + if (ds_snzprintf(strFloat, sizeof(strFloat), pFormatSpec, fValue) <= 0) + { + return(XML_ERR_INVALID_PARAM); + } + return(XmlElemAddString(pBuffer, pElemName, strFloat)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlElemAddDate + + \Description + Add a complete, contained Date element, using epoch date. Builds start and end tag. + Nothing can be appended to this tag. + + \Input *pBuffer - xml buffer. + \Input *pElemName - name of the element (tag). + \Input uEpochDate - date/time since epoch. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlElemAddDate(char *pBuffer, const char *pElemName, uint32_t uEpochDate) +{ + struct tm *time1, time2; + char strDate[XML_MAX_DATE_LENGTH]; + + if ((time1 = ds_secstotime(&time2, uEpochDate)) == NULL) + { + return(XML_ERR_INVALID_PARAM); + } + + ds_snzprintf(strDate, + sizeof(strDate), + "%04d-%02d-%02dT%02d:%02d:%02dZ", + time1->tm_year + 1900, + time1->tm_mon + 1, + time1->tm_mday, + time1->tm_hour, + time1->tm_min, + time1->tm_sec); + return(XmlElemAddString(pBuffer, pElemName, strDate)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlAttrSetString + + \Description + Set a text attribute for an open element (started tag). + Must be called before Element text is set. + + \Input *pBuffer - xml buffer. + \Input *pAttrName - name of the attribute to be added. + \Input *pValue - string value of the element. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlAttrSetString(char *pBuffer, const char *pAttrName, const char *pValue) +{ + int32_t iOffset, iLength, iNumChars, iIndent; + int32_t iInsert; + + if (!_XmlValidHeader(pBuffer)) + return(XML_ERR_UNINIT); + + iOffset = iInsert = _XmlGetOffset(pBuffer); + iLength = _XmlGetLength(pBuffer); + iIndent = _XmlGetIndent(pBuffer); + + // there must be an open tag in the buffer + if (_XmlOpenTag(pBuffer, iOffset)) + { + // the tag must be open for accepting attributes (no children and no element text set) + if (_XmlOpenForAttr(pBuffer, iOffset)) + { + // insert the tag + if ((iNumChars = ds_snzprintf(pBuffer+iInsert, iLength-iInsert, "%s=\"", pAttrName)) <= 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(XML_ERR_FULL); + } + iInsert += iNumChars; + + // insert the value + if ((iNumChars = _XmlEncodeString(pBuffer+iInsert, iLength-iInsert, pValue)) < 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(XML_ERR_FULL); + } + iInsert += iNumChars; + + // see if there is room for final terminator -- if so, include entire field + if ((iInsert + 3) > iLength) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(XML_ERR_FULL); + } + // --> attr="value --> + pBuffer[iOffset-1] = ' '; + pBuffer[iInsert+0] = '"'; + pBuffer[iInsert+1] = '>'; + pBuffer[iInsert+2] = '\0'; + + // update the header + return(_XmlUpdateHeader(pBuffer, iInsert-iOffset+2, iLength, iOffset, iIndent)); + } + else + return(XML_ERR_ATTR_POSITION); + } + else + { + return(XML_ERR_NOT_OPEN); + } +} + +/*F*************************************************************************************/ +/*! + \Function XmlAttrSetStringRaw + + \Description + Set a text attribute for an open element (started tag) with no text encoding + being performed on the input string. Must be called before Element text is set. + + \Input *pBuffer - xml buffer. + \Input *pAttr - attribute to be added (expected format: attrName="attrValue"). + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlAttrSetStringRaw(char *pBuffer, const char *pAttr) +{ + char strStringRaw[256]; + + if (ds_snzprintf(strStringRaw, sizeof(strStringRaw), "%s>", pAttr) <= 0) + { + return(XML_ERR_INVALID_PARAM); + } + return(_XmlFormatInsert(pBuffer, strStringRaw)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlAttrSetInt + + \Description + Set an integer attribute for an open element (started tag). + Must be called before Element text is set. + + \Input *pBuffer - xml buffer. + \Input *pAttrName - name of the attribute to be added. + \Input iValue - integer value to be set. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlAttrSetInt(char *pBuffer, const char *pAttrName, int32_t iValue) +{ + char strInt[32]; // [32] is large enough for int32_t string + + ds_snzprintf(strInt, sizeof(strInt), "%d", iValue); + return(XmlAttrSetString(pBuffer, pAttrName, strInt)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlAttrSetFloat + + \Description + Set a decimal attribute for an open element (started tag). + Must be called before Element text is set. Format using formatSpec. + + \Input *pBuffer - xml buffer. + \Input *pAttrName - name of the attribute (tag). + \Input *pFormatSpec - Spec for formatting the float (see sprintf). + \Input fValue - float value of the attribute. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlAttrSetFloat(char *pBuffer, const char *pAttrName, const char *pFormatSpec, float fValue) +{ + char strFloat[XML_MAX_FLOAT_LENGTH]; + + if (ds_snzprintf(strFloat, sizeof(strFloat), pFormatSpec, fValue) <= 0) + { + return(XML_ERR_INVALID_PARAM); + } + return(XmlAttrSetString(pBuffer, pAttrName, strFloat)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlAttrSetDate + + \Description + Set a date attribute for an open element (started tag). + Must be called before Element text is set. + + \Input *pBuffer - xml buffer. + \Input *pAttrName - name of the attribute to be added. + \Input uEpochDate - date value to be set. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlAttrSetDate(char *pBuffer, const char *pAttrName, uint32_t uEpochDate) +{ + struct tm *time1, time2; + char strDate[XML_MAX_DATE_LENGTH]; + + if ((time1 = ds_secstotime(&time2, uEpochDate)) == NULL) + { + return(XML_ERR_INVALID_PARAM); + } + + ds_snzprintf(strDate, + sizeof(strDate), + "%04d-%02d-%02dT%02d:%02d:%02dZ", + time1->tm_year + 1900, + time1->tm_mon + 1, + time1->tm_mday, + time1->tm_hour, + time1->tm_min, + time1->tm_sec); + return(XmlAttrSetString(pBuffer, pAttrName, strDate)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlAttrSetAddr + + \Description + Set an IP address attribute (in dot notation) + + \Input *pBuffer - xml buffer. + \Input *pAttrName - name of the attribute to be added. + \Input uAddr - address value to be set. + + \Output int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlAttrSetAddr(char *pBuffer, const char *pAttrName, uint32_t uAddr) +{ + char strAddr[32]; //[32] is large enough for xxx.xxx.xxx.xxx + + ds_snzprintf(strAddr, sizeof(strAddr), "%a", uAddr); + return(XmlAttrSetString(pBuffer, pAttrName, strAddr)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlElemSetString + + \Description + Set text value for an open element (started tag). + Must have an open or started tag, uses element from previous start tag call. + To be used if element is simple text node with some attributes. + + \Input *pBuffer - xml buffer. + \Input *pValue - value of the element. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlElemSetString(char *pBuffer, const char *pValue) +{ + int32_t iOffset, iLength, iNumChars, iIndent, iInsert; + + if (!_XmlValidHeader(pBuffer)) + return(XML_ERR_UNINIT); + + iOffset = iInsert = _XmlGetOffset(pBuffer); + iLength = _XmlGetLength(pBuffer); + iIndent = _XmlGetIndent(pBuffer); + + // there must be an open tag in the buffer + if (_XmlOpenTag(pBuffer, iOffset)) + { + // insert the value + if ((iNumChars = _XmlEncodeString(pBuffer + iInsert, iLength - iInsert, pValue)) < 0) + { + pBuffer[iOffset] = '\0'; // reset all changes + return(XML_ERR_FULL); + } + iInsert += iNumChars; + + // update the header + return(_XmlUpdateHeader(pBuffer, iInsert-iOffset, iLength, iOffset, iIndent)); + } + else + return(XML_ERR_NOT_OPEN); +} + +/*F*************************************************************************************/ +/*! + \Function XmlElemSetStringRaw + + \Description + Set text value for an open element (started tag), with no encoding being + performed on the input string. Must have an open or started tag, uses element + from previous start tag call. To be used if element is simple text node with some + attributes. + + \Input *pBuffer - xml buffer. + \Input *pValue - value of the element. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlElemSetStringRaw(char *pBuffer, const char *pValue) +{ + return(_XmlFormatInsert(pBuffer, pValue)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlElemSetInt + + \Description + Set integer value for an open element (started tag). + Must have an open or started tag, uses element from previous start tag call. + To be used if element is a simple node with some attributes. + + \Input *pBuffer - xml buffer. + \Input iValue - integer value of the element. + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlElemSetInt(char *pBuffer, int32_t iValue) +{ + char strInt[32]; + + ds_snzprintf(strInt, sizeof(strInt), "%d", iValue); + return(XmlElemSetString(pBuffer, strInt)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlElemSetAddr + + \Description + Set an IP address value (in dot notation) for an open element. + + \Input *pBuffer - xml buffer. + \Input uAddr - address to set + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlElemSetAddr(char *pBuffer, uint32_t uAddr) +{ + char strAddr[32]; + + ds_snzprintf(strAddr, sizeof(strAddr), "%a", uAddr); + return(XmlElemSetString(pBuffer, strAddr)); +} + + +/*F*************************************************************************************/ +/*! + \Function XmlElemSetDate + + \Description + Set a date for an open element. Must have an open element (started tag). + + \Input *pBuffer - xml buffer. + \Input uEpoch - date to set + + \Output + int32_t - return code. +*/ +/*************************************************************************************F*/ +int32_t XmlElemSetDate(char *pBuffer, uint32_t uEpoch) +{ + struct tm Date; + char strDate[XML_MAX_DATE_LENGTH]; + + if (ds_secstotime(&Date, uEpoch) == NULL) + { + return(XML_ERR_INVALID_PARAM); + } + ds_snzprintf(strDate, + sizeof(strDate), + "%04d-%02d-%02dT%02d:%02d:%02dZ", + Date.tm_year + 1900, + Date.tm_mon + 1, + Date.tm_mday, + Date.tm_hour, + Date.tm_min, + Date.tm_sec); + return(XmlElemSetString(pBuffer, strDate)); +} + +/*F*************************************************************************************/ +/*! + \Function XmlFormatVPrintf + + \Description + \verbatim + Takes a format string and variable parameter list and formats into syntax + correct XML doing optimizations like removing empty tags (i.e., + becomes ). The "v" (variable args) version can be called by other + functions that use "..." in the their prototype. + \endverbatim + + \Input *pXmlBuff - xml output buffer + \Input iBufLen - length of Xml output buffer (negative=append to existing buffer) + \Input *pFormat - printf style format string + \Input pFmtArgs - variable argument list (use va_start / va_end to get) + + \Output char* - pointer to formatted buffer +*/ +/*************************************************************************************F*/ +char *XmlFormatVPrintf(char *pXmlBuff, int32_t iBufLen, const char *pFormat, va_list pFmtArgs) +{ + int32_t iToken; + int32_t iIndex; + char strName[65] = ""; + const char *pName; + const char *pParse; + unsigned char uFlags = 0; + /* table of allowed name characters. see http://www.w3.org/TR/xml/#charsets + for information on name characters allowed by the XML 1.0 specification */ + static char _ConvName[128] = + " " " " + " -. " "0123456789: " + " ABCDEFGHIJKLMNO" "PQRSTUVWXYZ _" + " abcdefghijklmno" "pqrstuvwxyz "; + + // determine if whitespace indenting is desired + if (*pFormat == ' ') + { + uFlags |= XML_FL_WHITESPACE; + ++pFormat; + } + + // start the formatting + if (iBufLen >= 0) + XmlInit(pXmlBuff, iBufLen, uFlags); + + // parse the format string + for (pParse = pFormat; *pParse != 0; ++pParse) + { + // look for tag open + if (pParse[0] == '<') + { + for (iIndex = 0; (iIndex < (signed)(sizeof(strName) - 1)) && (pParse[iIndex+1] > 0) && (pParse[iIndex+1] < 127); ++iIndex) + { + if (_ConvName[(int32_t)pParse[iIndex+1]] <= ' ') + break; + strName[iIndex] = pParse[iIndex+1]; + } + strName[iIndex] = '\0'; + if (iIndex > 0) + { + XmlTagStart(pXmlBuff, strName); + } + } + + // parse name for assignments + if (pParse[0] == '=') + { + // find start of name + for (pName = pParse; (pName != pFormat) && (pName[-1] > 0) && (pName[-1] < 127); --pName) + { + if (_ConvName[pName[-1]&127] <= ' ') + break; + } + // copy and format name in private buffer + for (iIndex = 0; (iIndex < (signed)(sizeof(strName) - 1)) && (pName+iIndex != pParse); ++iIndex) + { + strName[iIndex] = pName[iIndex]; + } + strName[iIndex] = '\0'; + } + + // grab next 3 characters as a token + iToken = ' '; + iToken = (iToken << 8) | pParse[0]; + iToken = (iToken << 8) | pParse[1]; + iToken = (iToken << 8) | pParse[2]; + + // handle format tokens + if ((pParse[0] == '/') && (pParse[1] == '>')) + { + XmlTagEnd(pXmlBuff); + } + else if (iToken == ' >%s') + { + const char *pString = va_arg(pFmtArgs, const char *); + XmlElemSetString(pXmlBuff, pString); + } + else if (iToken == ' >%d') + { + int32_t iInteger = va_arg(pFmtArgs, int32_t); + XmlElemSetInt(pXmlBuff, iInteger); + } + else if (iToken == ' >%e') + { + uint32_t uEpoch = va_arg(pFmtArgs, uint32_t); + if (uEpoch == 0) + uEpoch = (uint32_t)ds_timeinsecs(); + XmlElemSetDate(pXmlBuff, uEpoch); + } + else if (iToken == ' >%a') + { + uint32_t uAddr = va_arg(pFmtArgs, uint32_t); + XmlElemSetAddr(pXmlBuff, uAddr); + } + else if (strName[0] == '\0') + { + } + else if (iToken == ' =%s') + { + const char *pString = va_arg(pFmtArgs, const char *); + XmlAttrSetString(pXmlBuff, strName, pString); + } + else if (iToken == ' =%d') + { + int32_t iInteger = va_arg(pFmtArgs, int32_t); + XmlAttrSetInt(pXmlBuff, strName, iInteger); + } + else if (iToken == ' =%e') + { + uint32_t uEpoch = va_arg(pFmtArgs, uint32_t); + if (uEpoch == 0) + uEpoch = (uint32_t)ds_timeinsecs(); + XmlAttrSetDate(pXmlBuff, strName, uEpoch); + } + else if (iToken == ' =%a') + { + uint32_t uAddr = va_arg(pFmtArgs, uint32_t); + XmlAttrSetAddr(pXmlBuff, strName, uAddr); + } + } + + // finish up and return data pointer + if (iBufLen >= 0) + pXmlBuff = XmlFinish(pXmlBuff); + return(pXmlBuff); +} + +/*F*************************************************************************************/ +/*! + \Function XmlFormatPrintf + + \Description + \verbatim + Takes a format string and variable parameter list and formats into syntax + correct XML doing optimizations like removing empty tags (i.e., + becomes ). + \endverbatim + + \Input *pXmlBuff - xml output buffer + \Input iBufLen - length of Xml output buffer (negative=append to existing buffer) + \Input *pFormat - printf style format string + + \Output char* - pointer to formatted buffer +*/ +/*************************************************************************************F*/ +char *XmlFormatPrintf(char *pXmlBuff, int32_t iBufLen, const char *pFormat, ...) +{ + char *pResult; + va_list pFmtArgs; + + // setup varargs and call real function + va_start(pFmtArgs, pFormat); + pResult = XmlFormatVPrintf(pXmlBuff, iBufLen, pFormat, pFmtArgs); + va_end(pFmtArgs); + + // return pointer to start of valid XML data (skips leading spaces) + return(pResult); +} diff --git a/src/thirdparty/dirtysdk/source/xml/xmlparse.c b/src/thirdparty/dirtysdk/source/xml/xmlparse.c new file mode 100644 index 00000000..aceac9b3 --- /dev/null +++ b/src/thirdparty/dirtysdk/source/xml/xmlparse.c @@ -0,0 +1,1813 @@ +/*H*************************************************************************************************/ +/*! + \File xmlparse.c + + \Description + \verbatim + This is a simple XML parser for use in controlled situations. It should not + be used with arbitrary XML from uncontrolled hosts (i.e., test its parsing against + a particular host before using it). This only implement a small subset of full + XML and while suitable for many applications, it is easily confused by badly + formed XML or by some of the legitimate but convoluted XML conventions. + + In particular, this parser cannot handle degenerate empty elements (i.e.,

+ will confuse it). Empty elements must be of proper XML form

. Only the + predefined entity types (< > & ' ") are supported. This module + does not support unicode values. + \endverbatim + + \Copyright + Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED. + + \Version 1.0 01/30/2002 (gschaefer) First Version +*/ +/*************************************************************************************************H*/ + + +/*** Include files ********************************************************************************/ + +#include +#include + +#include "DirtySDK/platform.h" +#include "DirtySDK/dirtysock/dirtylib.h" +#include "DirtySDK/xml/xmlparse.h" + +/*** Type Definitions *****************************************************************************/ + +/*** Variables ************************************************************************************/ + +static const unsigned char _Xml_TopDecode[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 16, 32, 48, 64, 80, 96,112,128,144, 0, 0, 0, 0, 0, 0, + 0,160,176,192,208,224,240, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0,160,176,192,208,224,240, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +static const unsigned char _Xml_BtmDecode[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +// +static const uint8_t _Xml_CDataTrailer[3] = +{ ']', ']', '>' }; + +/*** Private Functions ****************************************************************************/ + +/*F************************************************************************************************/ +/*! + \Function _ParseNumber + + \Description + Convert a number from string to integer form + + \Input pData - pointer to source string + \Input pValue - pointer to output number + + \Output - Pointer to next string element + + \Version 04/19/2002 (GWS) +*/ +/************************************************************************************************F*/ +static const unsigned char *_ParseNumber(const unsigned char *pData, int32_t *pValue) +{ + for (*pValue = 0; (*pData >= '0') && (*pData <= '9'); ++pData) + { + *pValue = (*pValue * 10) + (*pData & 15); + } + return(pData); +} + +/*F************************************************************************************************/ +/*! + \Function _XmlContentChar + + \Description + Translate an encoded content character to ASCII. + + \Input pXml - pointer to raw xml source + \Input pData - pointer to translated output character + + \Output - Pointer to next xml source item + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +static const unsigned char *_XmlContentChar(const unsigned char *pXml, unsigned char *pData) +{ + uint32_t uNumber; + + // default to bogus + *pData = '~'; + + // coded values + if (pXml[0] == '#') + { + // hex + if (pXml[1] == 'x') + { + // convert hex to integer + for (uNumber = 0, pXml += 2; (*pXml != '\0') && (_Xml_BtmDecode[*pXml] > 0); ++pXml) + { + uNumber = (uNumber << 4) | _Xml_BtmDecode[*pXml]; + } + // truncate to 8 bits + *pData = (unsigned char)uNumber; + } + // decimal + else + { + // convert decimal to integer + for (uNumber = 0, pXml += 1; (*pXml >= '0') && (*pXml <= '9'); ++pXml) + { + uNumber = (uNumber * 10) + (*pXml & 15); + } + // truncate to 8 bits + *pData = (unsigned char)uNumber; + } + } + // & + else if ((pXml[0] == 'a') && (pXml[1] == 'm') && (pXml[2] == 'p')) + { + pXml += 3; + *pData = '&'; + } + // ' + else if ((pXml[0] == 'a') && (pXml[1] == 'p') && (pXml[2] == 'o') && (pXml[3] == 's')) + { + pXml += 4; + *pData = '\''; + } + // " + else if ((pXml[0] == 'q') && (pXml[1] == 'u') && (pXml[2] == 'o') && (pXml[3] == 't')) + { + pXml += 4; + *pData = '"'; + } + // < + else if ((pXml[0] == 'l') && (pXml[1] == 't')) + { + pXml += 2; + *pData = '<'; + } + // > + else if ((pXml[0] == 'g') && (pXml[1] == 't')) + { + pXml += 2; + *pData = '>'; + } + + // skip the terminator if present + if (*pXml == ';') + { + pXml += 1; + } + + // return updated pointer + return(pXml); +} + + +/*F************************************************************************************************/ +/*! + \Function _XmlContentFind + + \Description + Locate the content area for the current element + + \Input _pXml - pointer to xml element (start tag) + + \Output - Pointer to content area (NULL=no content) + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +static const unsigned char *_XmlContentFind(const char *_pXml) +{ + const unsigned char *pXml = (const unsigned char *)_pXml; + + // we should be pointing at an element start + if ((pXml == NULL) || (*pXml != '<')) + { + pXml = NULL; + } + // find end of element + else + { + while ((*pXml != 0) && (*pXml != '>')) + { + ++pXml; + } + if (*pXml != 0) + { + pXml = (pXml[-1] == '/' ? NULL : pXml+1); + } + } + // return content pointer + return(pXml); +} + + +/*F************************************************************************************************/ +/*! + \Function _XmlAttribFind + + \Description + Locate a named attribute within an element + + \Input _pXml - pointer to xml element (start tag) + \Input _pAttrib - pointer to attribute to find + + \Output + unsigned char * - pointer to attribute value (NULL=no content) + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +static const unsigned char *_XmlAttribFind(const char *_pXml, const char *_pAttrib) +{ + int32_t iIndex; + unsigned char uTerm; + const unsigned char *pMatch = NULL; + const unsigned char *pXml = (const unsigned char *)_pXml; + const unsigned char *pAttrib = (const unsigned char *)_pAttrib; + + // we should be pointing at an element start + if ((pXml != NULL) && (*pXml == '<')) + { + // skip past element name + for (; (*pXml != 0) && (*pXml > ' '); ++pXml) + ; + + // parse all the attributes + for (;;) + { + // skip past the white space + for (; (*pXml != 0) && (*pXml <= ' '); ++pXml) + ; + + // see if this is the attribute we want + for (iIndex = 0; (pXml[iIndex] != 0) && (pXml[iIndex] == pAttrib[iIndex]); ++iIndex) + ; + + // see if we reached end of attribute name + if (pAttrib[iIndex] == 0) + { + // skip past white space + for (pXml += iIndex; (*pXml != 0) && (*pXml <= ' '); ++pXml) + ; + // see if we found the matching attribute + if (*pXml == '=') + { + // find the start of the data + for (pMatch = pXml+1; (*pMatch != 0) && (*pMatch <= ' '); ++pMatch) + ; + break; + } + } + + // find end of attribute name + for (; (*pXml != 0) && (*pXml != '>') && (*pXml != '='); ++pXml) + ; + + // make sure this is an attribute assignment + if (*pXml != '=') + { + break; + } + + // skip equals and white space + for (++pXml; (*pXml != 0) && (*pXml <= ' '); ++pXml) + ; + + // see if the data is quoted + if ((*pXml == '"') || (*pXml == '\'')) + { + // find ending quote + for (uTerm = *pXml++; (*pXml != 0) && (*pXml != uTerm); ++pXml) + ; + // if we found a quote, skip it + if (*pXml == uTerm) + { + ++pXml; + } + } + // just skip till next space + else + { + for (; (*pXml != 0) && (*pXml > ' '); ++pXml) + ; + } + } + } + + // return the match point + return(pMatch); +} + +/*F************************************************************************************************/ +/*! + \Function _XmlSkipCDataHeader + + \Description + Determine if the string points to a CDATA header + + \Input pXml - pointer to an XML content string + \Input ppContent - pointer to pointer to the start of the content string + + \Output - TRUE if the string demarks a CDATA header + + \Version 12/14/2006 (kcarpenter) +*/ +/************************************************************************************************F*/ +static int32_t _XmlSkipCDataHeader(const unsigned char *pXml, const unsigned char **ppContent) +{ + int32_t iIndex; + + // Compare the CDATA header char-by-char + for (iIndex = 0; iIndex < (signed)sizeof(_Xml_CDataHeader); iIndex++) + { + if (pXml[iIndex] != _Xml_CDataHeader[iIndex]) + { + return(FALSE); + } + } + + // Pass out content start pointer, if possible + if (ppContent != NULL) + { + *ppContent = pXml + sizeof(_Xml_CDataHeader); + } + return(TRUE); +} + +/*F************************************************************************************************/ +/*! + \Function _XmlSkipCDataTrailer + + \Description + Determine if the string points to a CDATA trailer + + \Input pXml - pointer to the possible end of an XML content string + \Input ppAfterContent - pointer to pointer to the data immediately after the trailer + + \Output - TRUE if the string demarks a CDATA trailer + + \Version 12/14/2006 (kcarpenter) +*/ +/************************************************************************************************F*/ +static int32_t _XmlSkipCDataTrailer(const unsigned char *pXml, const unsigned char **ppAfterContent) +{ + int32_t iIndex; + + // Compare the CDATA trailer char-by-char + for (iIndex = 0; iIndex < (signed)sizeof(_Xml_CDataTrailer); iIndex++) + { + if (pXml[iIndex] != _Xml_CDataTrailer[iIndex]) + { + return(FALSE); + } + } + + // Pass out pointer to after CDATA trailer, if possible + if (ppAfterContent != NULL) + { + *ppAfterContent = pXml + sizeof(_Xml_CDataTrailer); + } + return(TRUE); +} + +#if DIRTYCODE_LOGGING +/*F*************************************************************************************/ +/*! + \Function _XmlPrintFmtLine + + \Description + Print current line buffer to debug output + + \Input *pLine - line buffer to print + \Input iLevel - current xml depth + \Input *pStrPrefix - string to prefix each line with + + \Version 09/16/2010 (jbrookes) +*/ +/*************************************************************************************F*/ +static void _XmlPrintFmtLine(const char *pLine, int32_t iLevel, const char *pStrPrefix) +{ + // declare const string of 64 spaces which gives us 16 levels of indentiation with four spaces per indent + const char strIndent[] = + " " + " " + " " + " "; + int32_t iOffset; + + // figure out offset into tabs string based on level + if ((iOffset = ((sizeof(strIndent)-1)/4 - iLevel)) < 0) + { + iOffset = 0; + } + iOffset *= 4; + + // output the string + NetPrintf(("%s%s%s\r\n", pStrPrefix, strIndent + iOffset, pLine)); +} +#endif + +#if DIRTYCODE_LOGGING +/*F*************************************************************************************/ +/*! + \Function _XmlPrintFmtInsertChar + + \Description + Insert a character into the print buffer. + + \Input **ppLine - current insertion point + \Input **ppXml - current read point + \Input *pLineStart - start of line buffer + \Input iBufSize - size of line buffer + \Input iLevel - current xml depth + \Input *pStrPrefix - string to prefix each line with + + \Version 09/16/2010 (jbrookes) +*/ +/*************************************************************************************F*/ +static void _XmlPrintFmtInsertChar(char **ppLine, const unsigned char **ppXml, char *pLineStart, int32_t iBufSize, int32_t iLevel, const char *pStrPrefix) +{ + // add to line + **ppLine = **ppXml; + *ppLine += 1; + *ppXml += 1; + + // check for full buffer + if ((*ppLine - pLineStart) == (iBufSize - 1)) + { + // flush current line and continue + **ppLine = '\0'; + _XmlPrintFmtLine(pLineStart, iLevel, pStrPrefix); + pLineStart[0] = ' '; + *ppLine = pLineStart + 1; + } +} +#endif + +#if DIRTYCODE_LOGGING +/*F*************************************************************************************/ +/*! + \Function _XmlPrintFmt + + \Description + Takes a as input an XML buffer, and prints out a "nicely" formatted version of + the XML data to debug output. Each line of output is appended to text generated + by the format string and arguments. + + \Input *pXml - xml to print + \Input *pStrPrefix - string to prefix each line with + + \Version 09/16/2010 (jbrookes) +*/ +/*************************************************************************************F*/ +static void _XmlPrintFmt(const unsigned char *pXml, const char *pStrPrefix) +{ + const char *pTagStart, *pTagEnd, *pTmpBuf; + int32_t iLevel, iNewLevel; + char strLine[132], *pLine; + + // skip any leading whitespace + while ((*pXml == ' ') || (*pXml == '\t')) + { + pXml += 1; + } + + // add xml to buffer + for (iLevel = 0; *pXml != '\0'; ) + { + // copy to line buffer until tag end, crlf, or eos + pLine = strLine; + pTagStart = (*pXml == '<') ? (const char *)pXml+1 : NULL; + pTagEnd = NULL; + + // check for close tag + iNewLevel = ((*pXml == '<') && (*(pXml+1) == '/')) ? iLevel-2 : iLevel; + for (; (*pXml != '>') && (*pXml != '\r') && (*pXml != '\0'); ) + { + if ((*pXml == ' ') && (pTagEnd == NULL)) + { + pTagEnd = (const char *)pXml; + } + _XmlPrintFmtInsertChar(&pLine, &pXml, strLine, sizeof(strLine), iLevel, pStrPrefix); + } + if (*pXml == '>') + { + if ((*(pXml-1) != '/') && (*(pXml-1) != '?')) + { + iNewLevel += 1; + } + if (pTagEnd == NULL) + { + pTagEnd = (const char *)pXml; + } + _XmlPrintFmtInsertChar(&pLine, &pXml, strLine, sizeof(strLine), iLevel, pStrPrefix); + } + // scan ahead and see if we have a matching end tag + if (pTagStart != NULL) + { + pTmpBuf = (const char *)pXml; + while ((*pTmpBuf != '>') && (*pTmpBuf != '\r') && (*pTmpBuf != '\0')) + { + if ((*pTmpBuf == '<') && (*(pTmpBuf+1) == '/') && (!strncmp((const char *)pTagStart, (const char *)pTmpBuf+2, (size_t)(pTagEnd-pTagStart)))) + { + while ((*pXml != '>') && (*pXml != '\r') && (*pXml != '\0')) + { + _XmlPrintFmtInsertChar(&pLine, &pXml, strLine, sizeof(strLine), iLevel, pStrPrefix); + } + if (*pXml == '>') + { + _XmlPrintFmtInsertChar(&pLine, &pXml, strLine, sizeof(strLine), iLevel, pStrPrefix); + } + iNewLevel--; + break; + } + pTmpBuf++; + } + } + // skip whitespace + while ((*pXml == '\r') || (*pXml == '\n') || (*pXml == '\t') || (*pXml == ' ')) + { + pXml++; + } + // null terminate + *pLine = '\0'; + + // indent based on level + if (iNewLevel < iLevel) + { + iLevel = iNewLevel; + } + + // output line + _XmlPrintFmtLine(strLine, iLevel, pStrPrefix); + + // index to new level + iLevel = iNewLevel; + } +} +#endif + +/*F************************************************************************************************/ +/*! + \Function _XmlSkip + + \Description + Skip to the next xml element (used to enumerate lists) + + \Input *pXml - pointer to xml element (start tag) + \Input *pValid - [optional, out] TRUE if element being skipped is complete, else FALSE + + \Output - Pointer to next element at same level + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +static const unsigned char *_XmlSkip(const unsigned char *pXml, uint32_t *pValid) +{ + int32_t iIndex; + uint32_t uValid; + + // allow NULL pValid + if (pValid == NULL) + { + pValid = &uValid; + } + + // assume invalid + *pValid = FALSE; + + // make sure reference is valid + if ((pXml == NULL) || (*pXml != '<')) + { + return(NULL); + } + + // handle xml decl + if (pXml[1] == '?') + { + // find the end sequence + for (pXml += 2; (pXml[0] != 0) && (pXml[0] != '?') && (pXml[1] != '>'); ++pXml) + ; + // skip the terminator + if (pXml[0] != 0) + { + pXml += 2; + } + } + + // handle doctype/comment + else if ((pXml[0] == '<') && (pXml[1] == '!')) + { + // count past the < and > + for (iIndex = 1, ++pXml; (pXml[0] != 0) && (iIndex > 0); ++pXml) + { + if (pXml[0] == '>') + { + iIndex -= 1; + } + if (pXml[0] == '<') + { + iIndex += 1; + } + } + } + + // handle regular element + else + { + // count past < and > + for (iIndex = 1, ++pXml; (iIndex > 0) && (pXml[0] != 0);) + { + // if we found an element start + if (pXml[0] == '<') + { + // see if this is CDATA, and if so, skip it + if (_XmlSkipCDataHeader(pXml, &pXml)) + { + // skip over the CDATA section + while (TRUE) + { + if (*pXml == 0) + { + break; + } + if ((*pXml == ']') && _XmlSkipCDataTrailer(pXml, &pXml)) + { + break; + } + ++pXml; + } + continue; + } + // if we hit a doctype/comment, then skip it + if ((pXml[0] == '<') && (pXml[1] == '!')) + { + if ((pXml = _XmlSkip(pXml, NULL)) == NULL) + { + return(pXml); + } + else + { + continue; + } + } + + // adjust the indent level + iIndex += (pXml[1] != '/' ? 1 : -1); + // find the end of the element + for (++pXml; (pXml[0] != 0) && (pXml[0] != '>'); ++pXml) + ; + // dont gobble /> but do skip > + if (pXml[-1] == '/') + { + --pXml; + } + else if (pXml[0] == '>') + { + ++pXml; + } + } + // this is the end of an empty element + else if ((pXml[0] == '/') && (pXml[1] == '>')) + { + iIndex -= 1; + pXml += 2; + } + else + { + ++pXml; + } + } + + // valid? + if (*pXml != '\0') + { + *pValid = 1; + } + else if ((iIndex <= 1) && (pXml[-1] == '>')) + { + *pValid = 1; + } + } + + // skip white space + for (; (*pXml != 0) && (*pXml <= ' '); ++pXml) + ; + + // see if we ran out of data + if (*pXml == 0) + { + pXml = NULL; + } + + // return new position + return(pXml); +} + + +/*** Public functions *****************************************************************************/ + + +/*F************************************************************************************************/ +/*! + \Function XmlSkip + + \Description + Skip to the next xml element (used to enumerate lists) + + \Input *pXml - pointer to xml element (start tag) + + \Output - Pointer to next element at same level + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +const char *XmlSkip(const char *pXml) +{ + return((const char *)_XmlSkip((const unsigned char *)pXml, NULL)); +} + +/*F************************************************************************************************/ +/*! + \Function XmlComplete + + \Description + Determines if the xml element pointed to is complete + + \Input *pXml - pointer to xml element (start tag) + + \Output + uint32_t - TRUE if the element is complete, else FALSE + + \Version 09/24/2010 (jbrookes) +*/ +/************************************************************************************************F*/ +uint32_t XmlComplete(const char *pXml) +{ + uint32_t uValid; + _XmlSkip((const unsigned char *)pXml, &uValid); + return(uValid); +} + +/*F************************************************************************************************/ +/*! + \Function XmlFind + + \Description + Find an element within an xml document + + \Input _pXml - pointer to xml document + \Input _pName - element name (x.y.z notation) + + \Output - Pointer to named element start + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +const char *XmlFind(const char *_pXml, const char *_pName) +{ + int32_t iXmlIndex, iMatchIndex; + const unsigned char *pMatch = NULL; + const unsigned char *pXml = (const unsigned char *)_pXml; + const unsigned char *pName = (const unsigned char *)_pName; + + // make sure record is valid + if ((pXml == NULL) || (pXml[0] == 0)) + { + return(NULL); + } + + // make sure name is valid + if ((pName == NULL) || (pName[0] == 0)) + { + return(NULL); + } + + // allow "." to specify current tag + if ((*pName == '.') && (*pXml == '<')) + { + pName += 1; + pXml += 1; + } + + // scan the data + while (pXml != NULL) + { + // locate the first marker + for (; (pXml[0] != '<') && (pXml[0] != 0); ++pXml) + ; + + // if we hit an xml decl then skip it + if ((pXml[0] == '<') && (pXml[1] == '?')) + { + pXml = _XmlSkip(pXml, NULL); + continue; + } + + // if we hit a doctype/comment, then skip it + if ((pXml[0] == '<') && (pXml[1] == '!')) + { + pXml = _XmlSkip(pXml, NULL); + continue; + } + + // if we hit an end tag, then search must be over + if ((pXml[0] == '<') && (pXml[1] == '/')) + { + break; + } + + // skip past leading < + if (pXml[0] != 0) + { + ++pXml; + } + + // see if we found the matching element + for (iXmlIndex = iMatchIndex = 0; (pXml[iXmlIndex] != '\0') && (pName[iMatchIndex] != '\0'); ++iXmlIndex, ++iMatchIndex) + { + // wildcard match? + if ((pName[iMatchIndex] == '%') && (pName[iMatchIndex+1] == '*')) + { + // consume all input until the subsequent character + for (iMatchIndex += 2; (pXml[iXmlIndex] != '\0') && (pXml[iXmlIndex] != pName[iMatchIndex]); ++iXmlIndex) + ; + } + else if (pXml[iXmlIndex] != pName[iMatchIndex]) + { + break; + } + } + + // handle end of data case + if (pXml[iXmlIndex] == '\0') + { + break; + } + + // see if we matched + if ((pXml[iXmlIndex] <= ' ') || (pXml[iXmlIndex] == '>') || (pXml[iXmlIndex] == '/')) + { + // handle leaf match + if (pName[iMatchIndex] == '\0') + { + pMatch = pXml-1; + break; + } + + // handle node match + if ((pName[iMatchIndex] == '.') && (pXml[iXmlIndex] != '/')) + { + // locate the end of the start tag + for (; (pXml[0] != '\0') && (pXml[0] != '>'); ++pXml) + ; + // see if this is the stop point + if (pName[iMatchIndex+1] == '\0') + { + pMatch = (*pXml ? pXml+1 : NULL); + break; + } + + // search within this area recursively + if (pXml[0] != '\0') + { + pMatch = (const unsigned char *)XmlFind((const char *)pXml+1, (const char *)pName+iMatchIndex+1); + } + break; + } + + } + + // skip past mismatch element + pXml = _XmlSkip(pXml-1, NULL); + } + + // point to matching data + return((const char *)pMatch); +} + + +/*F************************************************************************************************/ +/*! + \Function XmlNext + + \Description + Skip to next element with same name as current + + \Input _pXml - pointer to element start + + \Output - Pointer to next element start + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +const char *XmlNext(const char *_pXml) +{ + int32_t iIndex; + const unsigned char *pMatch; + const unsigned char *pXml = (const unsigned char *)_pXml; + + // make sure we are at element start + for (; (*pXml != 0) && (*pXml != '<'); ++pXml) + ; + + // locate next tag that matches this one + for (pMatch = _XmlSkip(pXml, NULL); pMatch != NULL; pMatch = _XmlSkip(pMatch, NULL)) + { + // skip to the element start + for (; (*pMatch != 0) && (*pMatch != '<'); ++pMatch) + ; + + // see if we found next element of same name + for (iIndex = 0; (pMatch[iIndex] > ' ') && (pMatch[iIndex] != '>') && (pXml[iIndex] > ' ') && (pXml[iIndex] != '>') && (pMatch[iIndex] == pXml[iIndex]); ++iIndex) + ; + + // check for complete match + if (((pMatch[iIndex] <= ' ') || (pMatch[iIndex] == '>')) && ((pXml[iIndex] <= ' ') || (pXml[iIndex] == '>'))) + { + break; + } + } + + // return the match + return((const char *)pMatch); +} + + +/*F************************************************************************************************/ +/*! + \Function XmlStep + + \Description + Step over tag (regardless of the tab being a start tag or a end tag). + + \Input *_pXml - pointer to tag opening character + + \Output + const char * - pointer to byte following tag closing character or NULL if at end of document + + \Version 09/16/2010 (jbrookes) +*/ +/************************************************************************************************F*/ +const char *XmlStep(const char *_pXml) +{ + const unsigned char *pXml = (const unsigned char *)_pXml; + + // make sure we are at tag opening character + for (; (*pXml != 0) && (*pXml != '<'); ++pXml) + ; + + // step to tag closing character + for (; (*pXml != '\0') && (*pXml != '>'); ++pXml) + ; + + // skip past end, return NULL if at end of document + return(((*pXml != '\0') && (*(pXml+1) != '\0')) ? (const char *)pXml + 1 : NULL); +} + +/*F************************************************************************************************/ +/*! + \Function XmlContentGetString + + \Description + Return element contents as a string + + \Input pXml - pointer to xml document + \Input _pBuffer - string output buffer + \Input iLength - length of output buffer + \Input pDefault - default value if (pXml == NULL) + + \Output + int32_t - length of string (-1=nothing copied) + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +int32_t XmlContentGetString(const char *pXml, char *_pBuffer, int32_t iLength, const char *pDefault) +{ + int32_t iLen; + const unsigned char *pData; + unsigned char *pBuffer = (unsigned char *)_pBuffer; + int32_t bInCDataSection; + + // validate buffer + if ((pBuffer == NULL) || (iLength < 1)) + { + return(-1); + } + + // locate the content area + pData = _XmlContentFind(pXml); + if (pData != NULL) + { + // skip leading white space + while ((*pData != 0) && (*pData <= ' ')) + { + ++pData; + } + + // Skip CDATA header, if any + bInCDataSection = _XmlSkipCDataHeader(pData, &pData); + + // convert the string + for (iLen = 1; (iLen < iLength) && (*pData != 0); ++iLen) + { + if (bInCDataSection) + { + // Just copy the data until we reach the end trailer marker + if (_XmlSkipCDataTrailer(pData, NULL)) + { + break; + } + + *pBuffer++ = *pData++; + } + else + { + if (*pData == '<') + { + break; + } + + if (*pData == '&') + { + pData = _XmlContentChar(pData+1, pBuffer++); + } + else + { + *pBuffer++ = *pData++; + } + } + } + // remove trailing white space + while ((iLen > 1) && (pBuffer[-1] <= ' ')) + { + --iLen; + --pBuffer; + } + // terminate buffer + *pBuffer = 0; + } + else if (pDefault != NULL) + { + // copy over the string + for (iLen = 1; (iLen < iLength) && (*pDefault != 0); ++iLen) + { + *pBuffer++ = *pDefault++; + } + // terminate buffer + *pBuffer = 0; + } + else + { + // leave the old string + iLen = 0; + } + + // return length (without terminator) + return(iLen-1); +} + + +/*F************************************************************************************************/ +/*! + \Function XmlContentGetInteger + + \Description + Return element contents as an integer + + \Input pXml - pointer to xml document + \Input iDefault - default value if (pXml == NULL) + + \Output + int32_t - element contents as integer + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +int32_t XmlContentGetInteger(const char *pXml, int32_t iDefault) +{ + return((int32_t)XmlContentGetInteger64(pXml, iDefault)); +} + +/*F************************************************************************************************/ +/*! + \Function XmlContentGetInteger64 + + \Description + Return element contents as an integer (64-bit) + + \Input pXml - pointer to xml document + \Input iDefault - default value if (pXml == NULL) + + \Output + int64_t - element contents as integer +*/ +/************************************************************************************************F*/ +int64_t XmlContentGetInteger64(const char *pXml, int64_t iDefault) +{ + int32_t iSign = 1; + uint64_t uNumber; + + const uint8_t *pData; + + // locate the content area + if ((pData = _XmlContentFind(pXml)) == NULL) + { + return(iDefault); + } + + // skip leading whitespace + while ((*pData != 0) && (*pData <= ' ')) + { + ++pData; + } + + // check for a sign value + if (*pData == '+') + { + iSign = 1; + ++pData; + } + if (*pData == '-') + { + iSign = -1; + ++pData; + } + + // parse the number + for (uNumber = 0; (*pData >= '0') && (*pData <= '9'); ++pData) + { + uNumber = (uNumber * 10) + (*pData & 15); + } + + // return final number + return (iSign*uNumber); +} + +/*F************************************************************************************************/ +/*! + \Function XmlContentGetToken + + \Description + Return element contents as a token (a packed sequence of characters) + + \Input pXml - pointer to xml document + \Input iDefault - default value if (pXml == NULL) + + \Output + int32_t - element contents as token + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +int32_t XmlContentGetToken(const char *pXml, int32_t iDefault) +{ + int32_t iToken = ' '; + const unsigned char *pData; + + // locate the content area + if ((pData = _XmlContentFind(pXml)) == NULL) + { + return(iDefault); + } + + // skip leading white space + while ((*pData != 0) && (*pData <= ' ')) + { + ++pData; + } + + // parse the number + while ((*pData > ' ') && (*pData != '<')) + { + iToken = (iToken << 8) | *pData++; + } + + // return the token + return(iToken); +} + + +/*F************************************************************************************************/ +/*! + \Function XmlContentGetDate + + \Description + Return epoch seconds for a date + + \Input pXml - pointer to xml document + \Input uDefault - default value if (pXml == NULL) + + \Output + uint32_t - element contents as epoch seconds + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +uint32_t XmlContentGetDate(const char *pXml, uint32_t uDefault) +{ + struct tm tm; + const unsigned char *pData; + + // locate the content area + if ((pData = _XmlContentFind(pXml)) == NULL) + { + return(uDefault); + } + + // skip leading white space + while ((*pData != 0) && (*pData <= ' ')) + { + ++pData; + } + + // set the unused fields + tm.tm_isdst = -1; + tm.tm_wday = 0; + tm.tm_yday = 0; + + // extract the date + pData = _ParseNumber(pData, &tm.tm_year); + if ((*pData == '.') || (*pData == '-')) + ++pData; + pData = _ParseNumber(pData, &tm.tm_mon); + if ((*pData == '.') || (*pData == '-')) + ++pData; + pData = _ParseNumber(pData, &tm.tm_mday); + if ((*pData == ' ') || (*pData == 'T')) + ++pData; + pData = _ParseNumber(pData, &tm.tm_hour); + if (*pData == ':') + ++pData; + pData = _ParseNumber(pData, &tm.tm_min); + if (*pData == ':') + ++pData; + _ParseNumber(pData, &tm.tm_sec); + + // validate the fields + if ((tm.tm_year < 1970) || (tm.tm_year > 2099) || (tm.tm_mon < 1) || (tm.tm_mon > 12) || (tm.tm_mday < 1) || (tm.tm_mday > 31)) + { + return(uDefault); + } + if ((tm.tm_hour < 0) || (tm.tm_hour > 23) || (tm.tm_min < 0) || (tm.tm_min > 59) || (tm.tm_sec < 0) || (tm.tm_sec > 61)) + { + return(uDefault); + } + + // return epoch time + tm.tm_mon -= 1; + tm.tm_year -= 1900; + return((uint32_t)ds_timetosecs(&tm)); +} + +/*F************************************************************************************************/ +/*! + \Function XmlContentGetAddress + + \Description + Parse element contents as an internet dot-notation address and return as an integer. + + \Input pXml - pointer to xml document + \Input iDefault - default value if (pXml == NULL) + + \Output + int32_t - element contents as integer + + \Version 10/07/2005 (JLB) +*/ +/************************************************************************************************F*/ +int32_t XmlContentGetAddress(const char *pXml, int32_t iDefault) +{ + const unsigned char *pData; + uint32_t iAddr, iQuad, iValue; + + // locate the content area + if ((pData = _XmlContentFind(pXml)) == NULL) + { + return(iDefault); + } + + // parse address + for (iAddr = iQuad = 0; iQuad < 4; iQuad++, pData++) + { + // parse current digit + for (iValue = 0; (*pData >= '0') && (*pData <= '9'); pData++) + { + iValue = (iValue*10) + (*pData & 15); + } + // verify digit + if ((iQuad < 3) && (*pData != '.')) + { + iAddr = iDefault; + break; + } + // accumulate digit in address + iAddr <<= 8; + iAddr |= iValue; + } + + // return to caller + return(iAddr); +} + +/*F************************************************************************************************/ +/*! + \Function XmlContentGetBinary + + \Description + Return binary encoded data + + \Input pXml - pointer to xml document + \Input pBuffer - pointer to buffer to copy binary data to + \Input iLength - length of buffer pointed to by pBuffer + + \Output + int32_t - negative if error, length otherwise + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +int32_t XmlContentGetBinary(const char *pXml, char *pBuffer, int32_t iLength) +{ + int32_t iCount; + const unsigned char *pData; + + // locate the content area + if ((pData = _XmlContentFind(pXml)) == NULL) + { + return(0); + } + + // skip leading white space + while ((*pData != 0) && (*pData <= ' ')) + { + ++pData; + } + + // see if they just want the length + if (pBuffer == NULL) + { + for (iCount = 0; (pData[0] >= '0') && (pData[1] >= '0'); pData += 2) + { + ++iCount; + } + return(iCount); + } + + // make sure buffer has a valid length + if (iLength < 0) + { + return(-1); + } + + // attempt to copy over data + for (iCount = 0; (iCount < iLength) && (pData[0] >= '0') && (pData[1] >= '0'); ++iCount) + { + *pBuffer++ = _Xml_TopDecode[pData[0]] | _Xml_BtmDecode[pData[1]]; + pData += 2; + } + + // return the length + return(iCount); +} + +/*F************************************************************************************************/ +/*! + \Function XmlAttribGetString + + \Description + Return element attribute as a string + + \Input pXml - pointer to xml element + \Input pAttrib - name of attribute + \Input pBuffer - output string buffer + \Input iLength - length of string buffer + \Input pDefault - default string if (pXml == NULL || pAttrib not found) + + \Output + int32_t - length of result string (-1=nothing copied) + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +int32_t XmlAttribGetString(const char *pXml, const char *pAttrib, char *pBuffer, int32_t iLength, const char *pDefault) +{ + int32_t iLen; + unsigned char uTerm; + const unsigned char *pData; + + // validate buffer + if ((pBuffer == NULL) || (iLength < 1)) + { + return(-1); + } + + // locate the content area + pData = _XmlAttribFind(pXml, pAttrib); + if (pData != NULL) + { + // skip leading white space + while ((*pData != 0) && (*pData <= ' ')) + { + ++pData; + } + + // see if there is a terminator + if ((*pData == '"') || (*pData == '\'')) + { + uTerm = *pData++; + } + else + { + uTerm = 0; + } + + // convert the string + for (iLen = 1; (iLen < iLength) && (*pData != uTerm) && (*pData != 0) && (*pData != '>'); ++iLen) + { + if (*pData == '&') + { + pData = _XmlContentChar(pData+1, (unsigned char *)pBuffer++); + } + else + { + *pBuffer++ = *pData++; + } + } + // terminate buffer + *pBuffer = 0; + } + else if (pDefault != NULL) + { + // copy over the string + for (iLen = 1; (iLen < iLength) && (*pDefault != 0); ++iLen) + { + *pBuffer++ = *pDefault++; + } + // terminate buffer + *pBuffer = 0; + } + else + { + // leave the old value + iLen = 0; + } + + // return length (without terminator) + return(iLen-1); +} + + +/*F************************************************************************************************/ +/*! + \Function XmlAttribGetInteger + + \Description + Return element attribute as an integer + + \Input pXml - pointer to xml element + \Input pAttrib - name of attribute + \Input iDefault - default value if (pXml == NULL || pAttrib not found) + + \Output + int32_t - attibute value as integer + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +int32_t XmlAttribGetInteger(const char *pXml, const char *pAttrib, int32_t iDefault) +{ + return((int32_t)XmlAttribGetInteger64(pXml, pAttrib, iDefault)); +} + +/*F************************************************************************************************/ +/*! + \Function XmlAttribGetInteger64 + + \Description + Return element attribute as an integer (64-bit) + + \Input pXml - pointer to xml element + \Input pAttrib - name of attribute + \Input iDefault - default value if (pXml == NULL || pAttrib not found) + + \Output + int64_t - attibute value as integer +*/ +/************************************************************************************************F*/ +int64_t XmlAttribGetInteger64(const char *pXml, const char *pAttrib, int64_t iDefault) +{ + int32_t iSign = 1; + uint64_t uNumber; + const unsigned char *pData; + + // locate the content area + pData = _XmlAttribFind(pXml, pAttrib); + if (pData == NULL) + { + return(iDefault); + } + + // skip leading white space + while ((*pData != 0) && (*pData <= ' ')) + { + ++pData; + } + + // skip terminator if present + if ((*pData == '"') || (*pData == '\'')) + { + ++pData; + } + + // check for a sign value + if (*pData == '+') + { + iSign = 1; + ++pData; + } + if (*pData == '-') + { + iSign = -1; + ++pData; + } + + // parse the number + for (uNumber = 0; (*pData >= '0') && (*pData <= '9'); ++pData) + { + uNumber = (uNumber * 10) + (*pData & 15); + } + + // check for symbol true + if (((pData[0]|32) == 't') && ((pData[1]|32) == 'r') && ((pData[2]|32) == 'u') && ((pData[3]|32) == 'e')) + { + iSign = 1; + uNumber = 1; + } + + // check for symbol false + if (((pData[0]|32) == 'f') && ((pData[1]|32) == 'a') && ((pData[2]|32) == 'l') && ((pData[3]|32) == 's') && ((pData[4]|32) == 'e')) + { + iSign = 1; + uNumber = 0; + } + + // return final value + return(iSign*uNumber); +} + + +/*F************************************************************************************************/ +/*! + \Function XmlAttribGetToken + + \Description + Return element attribute as a token + + \Input pXml - pointer to xml element + \Input pAttrib - name of attribute + \Input iDefault - default value if (pXml == NULL || pAttrib not found) + + \Output + int32_t - attribute value as a token + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +int32_t XmlAttribGetToken(const char *pXml, const char *pAttrib, int32_t iDefault) +{ + int32_t iToken = ' '; + unsigned char uTerm = 0; + const unsigned char *pData; + + // locate the content area + pData = _XmlAttribFind(pXml, pAttrib); + if (pData == NULL) + { + return(iDefault); + } + + // skip leading white space + while ((*pData != 0) && (*pData <= ' ')) + { + ++pData; + } + + // skip terminator if present + if ((*pData == '"') || (*pData == '\'')) + { + uTerm = *pData++; + } + + // parse the number + while ((*pData > ' ') && (*pData != uTerm) && (*pData != '>') && (*pData != 0)) + { + iToken = (iToken << 8) | *pData++; + } + + // return final value + return(iToken); +} + + +/*F************************************************************************************************/ +/*! + \Function XmlAttribGetDate + + \Description + Return epoch seconds for a date + + \Input pXml - pointer to xml element + \Input pAttrib - name of attribute + \Input uDefault - default value if (pXml == NULL || pAttrib not found) + + \Output + uint32_t - attribute value as epoch seconds + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +uint32_t XmlAttribGetDate(const char *pXml, const char *pAttrib, uint32_t uDefault) +{ + struct tm tm; + const unsigned char *pData; + + // locate the content area + pData = _XmlAttribFind(pXml, pAttrib); + if (pData == NULL) + { + return(uDefault); + } + + // skip leading white space + while ((*pData != 0) && (*pData <= ' ')) + { + ++pData; + } + + // skip terminator if present + if ((*pData == '"') || (*pData == '\'')) + { + ++pData; + } + + // set the unused fields + tm.tm_isdst = -1; + tm.tm_wday = 0; + tm.tm_yday = 0; + + // extract the date + pData = _ParseNumber(pData, &tm.tm_year); + if ((*pData == '.') || (*pData == '-')) + ++pData; + pData = _ParseNumber(pData, &tm.tm_mon); + if ((*pData == '.') || (*pData == '-')) + ++pData; + pData = _ParseNumber(pData, &tm.tm_mday); + if ((*pData == ' ') || (*pData == 'T')) + ++pData; + pData = _ParseNumber(pData, &tm.tm_hour); + if (*pData == ':') + ++pData; + pData = _ParseNumber(pData, &tm.tm_min); + if (*pData == ':') + ++pData; + _ParseNumber(pData, &tm.tm_sec); + + // validate the fields + if ((tm.tm_year < 1970) || (tm.tm_year > 2099) || (tm.tm_mon < 1) || (tm.tm_mon > 12) || (tm.tm_mday < 1) || (tm.tm_mday > 31)) + { + return(uDefault); + } + if ((tm.tm_hour < 0) || (tm.tm_hour > 23) || (tm.tm_min < 0) || (tm.tm_min > 59) || (tm.tm_sec < 0) || (tm.tm_sec > 61)) + { + return(uDefault); + } + + // return epoch time + tm.tm_mon -= 1; + tm.tm_year -= 1900; + return((uint32_t)ds_timetosecs(&tm)); +} + + +////////////////////////////////////////////////////////////////////////////////////////// + + +/*F************************************************************************************************/ +/*! + \Function XmlConvEpoch2Date + + \Description + Convert epoch to date components + + \Input uEpoch - epoch to convert to date components + \Input pYear - pointer to storage for year + \Input pMonth - pointer to storage for month + \Input pDay - pointer to storage for day + \Input pHour - pointer to storage for hour + \Input pMinute - pointer to storage for minute + \Input pSecond - pointer to storage for second + + \Output + int32_t - negative=error, zero=success + + \Version 01/30/2002 (GWS) +*/ +/************************************************************************************************F*/ +int32_t XmlConvEpoch2Date(uint32_t uEpoch, int32_t *pYear, int32_t *pMonth, int32_t *pDay, int32_t *pHour, int32_t *pMinute, int32_t *pSecond) +{ + int32_t iResult = -1; + struct tm tm; + + // convert to calendar time + if (ds_secstotime(&tm, uEpoch) != NULL) + { + // return the fields + if (pYear != NULL) + *pYear = tm.tm_year; + if (pMonth != NULL) + *pMonth = tm.tm_mon; + if (pDay != NULL) + *pDay = tm.tm_mday; + if (pHour != NULL) + *pHour = tm.tm_hour; + if (pMinute != NULL) + *pMinute = tm.tm_min; + if (pSecond != NULL) + *pSecond = tm.tm_sec; + // return success + iResult = 0; + } + + return(iResult); +} + +#if DIRTYCODE_LOGGING +/*F*************************************************************************************/ +/*! + \Function XmlPrintFmtCode + + \Description + Takes a as input an XML buffer, and prints out a "nicely" formatted version of + the XML data to debug output. Each line of output is appended to text generated + by the format string and arguments. + + \Input *pXml - xml to print + \Input *pFormat - printf style format string + \Input ... - variable-argument list + + \Version 09/15/2010 (jbrookes) +*/ +/*************************************************************************************F*/ +void XmlPrintFmtCode(const char *pXml, const char *pFormat, ...) +{ + char strPrefix[128]; + va_list pFmtArgs; + + // format prefix string + va_start(pFmtArgs, pFormat); + ds_vsnzprintf(strPrefix, sizeof(strPrefix), pFormat, pFmtArgs); + va_end(pFmtArgs); + + _XmlPrintFmt((const unsigned char *)pXml, strPrefix); +} +#endif diff --git a/src/thirdparty/dirtysdk/support.txt b/src/thirdparty/dirtysdk/support.txt new file mode 100644 index 00000000..f04de934 --- /dev/null +++ b/src/thirdparty/dirtysdk/support.txt @@ -0,0 +1,5 @@ +DirtySock SDK Support + +Please contact GS-DirtySock Support for all support inquiries. +The DirtySock Customer Portal is located at https://developer.ea.com/display/dirtysock/DirtySock+Customer+Portal +The GS Support webpage is located at https://developer.ea.com/display/TEAMS/GS+Support which describes the GS support process. diff --git a/src/thirdparty/dirtysdk/version.txt b/src/thirdparty/dirtysdk/version.txt new file mode 100644 index 00000000..379ebdc6 --- /dev/null +++ b/src/thirdparty/dirtysdk/version.txt @@ -0,0 +1,2 @@ +EADP Game Services DirtySDK 15.1.6.0.5 +August 19, 2020 diff --git a/src/thirdparty/ea/EABase/LICENSE b/src/thirdparty/ea/EABase/LICENSE new file mode 100644 index 00000000..738e3686 --- /dev/null +++ b/src/thirdparty/ea/EABase/LICENSE @@ -0,0 +1,25 @@ +Copyright (C) 2002-2013 Electronic Arts Inc + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/src/thirdparty/ea/EABase/config/eacompiler.h b/src/thirdparty/ea/EABase/config/eacompiler.h new file mode 100644 index 00000000..f10b7453 --- /dev/null +++ b/src/thirdparty/ea/EABase/config/eacompiler.h @@ -0,0 +1,1778 @@ +/*----------------------------------------------------------------------------- + * config/eacompiler.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *----------------------------------------------------------------------------- + * Currently supported defines include: + * EA_COMPILER_GNUC + * EA_COMPILER_ARM + * EA_COMPILER_EDG + * EA_COMPILER_SN + * EA_COMPILER_MSVC + * EA_COMPILER_METROWERKS + * EA_COMPILER_INTEL + * EA_COMPILER_BORLANDC + * EA_COMPILER_IBM + * EA_COMPILER_QNX + * EA_COMPILER_GREEN_HILLS + * EA_COMPILER_CLANG + * EA_COMPILER_CLANG_CL + * + * EA_COMPILER_VERSION = + * EA_COMPILER_NAME = + * EA_COMPILER_STRING = + * + * EA_COMPILER_VA_COPY_REQUIRED + * + * C++98/03 functionality + * EA_COMPILER_NO_STATIC_CONSTANTS + * EA_COMPILER_NO_TEMPLATE_SPECIALIZATION + * EA_COMPILER_NO_TEMPLATE_PARTIAL_SPECIALIZATION + * EA_COMPILER_NO_MEMBER_TEMPLATES + * EA_COMPILER_NO_MEMBER_TEMPLATE_SPECIALIZATION + * EA_COMPILER_NO_TEMPLATE_TEMPLATES + * EA_COMPILER_NO_MEMBER_TEMPLATE_FRIENDS + * EA_COMPILER_NO_VOID_RETURNS + * EA_COMPILER_NO_COVARIANT_RETURN_TYPE + * EA_COMPILER_NO_DEDUCED_TYPENAME + * EA_COMPILER_NO_ARGUMENT_DEPENDENT_LOOKUP + * EA_COMPILER_NO_EXCEPTION_STD_NAMESPACE + * EA_COMPILER_NO_EXPLICIT_FUNCTION_TEMPLATE_ARGUMENTS + * EA_COMPILER_NO_RTTI + * EA_COMPILER_NO_EXCEPTIONS + * EA_COMPILER_NO_NEW_THROW_SPEC + * EA_THROW_SPEC_NEW / EA_THROW_SPEC_DELETE + * EA_COMPILER_NO_UNWIND + * EA_COMPILER_NO_STANDARD_CPP_LIBRARY + * EA_COMPILER_NO_STATIC_VARIABLE_INIT + * EA_COMPILER_NO_STATIC_FUNCTION_INIT + * EA_COMPILER_NO_VARIADIC_MACROS + * + * C++11 functionality + * EA_COMPILER_NO_RVALUE_REFERENCES + * EA_COMPILER_NO_EXTERN_TEMPLATE + * EA_COMPILER_NO_RANGE_BASED_FOR_LOOP + * EA_COMPILER_NO_CONSTEXPR + * EA_COMPILER_NO_OVERRIDE + * EA_COMPILER_NO_INHERITANCE_FINAL + * EA_COMPILER_NO_NULLPTR + * EA_COMPILER_NO_AUTO + * EA_COMPILER_NO_DECLTYPE + * EA_COMPILER_NO_DEFAULTED_FUNCTIONS + * EA_COMPILER_NO_DELETED_FUNCTIONS + * EA_COMPILER_NO_LAMBDA_EXPRESSIONS + * EA_COMPILER_NO_TRAILING_RETURN_TYPES + * EA_COMPILER_NO_STRONGLY_TYPED_ENUMS + * EA_COMPILER_NO_FORWARD_DECLARED_ENUMS + * EA_COMPILER_NO_VARIADIC_TEMPLATES + * EA_COMPILER_NO_TEMPLATE_ALIASES + * EA_COMPILER_NO_INITIALIZER_LISTS + * EA_COMPILER_NO_NORETURN + * EA_COMPILER_NO_CARRIES_DEPENDENCY + * EA_COMPILER_NO_FALLTHROUGH + * EA_COMPILER_NO_NODISCARD + * EA_COMPILER_NO_MAYBE_UNUSED + * EA_COMPILER_NO_NONSTATIC_MEMBER_INITIALIZERS + * EA_COMPILER_NO_RIGHT_ANGLE_BRACKETS + * EA_COMPILER_NO_ALIGNOF + * EA_COMPILER_NO_ALIGNAS + * EA_COMPILER_NO_DELEGATING_CONSTRUCTORS + * EA_COMPILER_NO_INHERITING_CONSTRUCTORS + * EA_COMPILER_NO_USER_DEFINED_LITERALS + * EA_COMPILER_NO_STANDARD_LAYOUT_TYPES + * EA_COMPILER_NO_EXTENDED_SIZEOF + * EA_COMPILER_NO_INLINE_NAMESPACES + * EA_COMPILER_NO_UNRESTRICTED_UNIONS + * EA_COMPILER_NO_EXPLICIT_CONVERSION_OPERATORS + * EA_COMPILER_NO_FUNCTION_TEMPLATE_DEFAULT_ARGS + * EA_COMPILER_NO_LOCAL_CLASS_TEMPLATE_PARAMETERS + * EA_COMPILER_NO_NOEXCEPT + * EA_COMPILER_NO_RAW_LITERALS + * EA_COMPILER_NO_UNICODE_STRING_LITERALS + * EA_COMPILER_NO_NEW_CHARACTER_TYPES + * EA_COMPILER_NO_UNICODE_CHAR_NAME_LITERALS + * EA_COMPILER_NO_UNIFIED_INITIALIZATION_SYNTAX + * EA_COMPILER_NO_EXTENDED_FRIEND_DECLARATIONS + * + * C++14 functionality + * EA_COMPILER_NO_VARIABLE_TEMPLATES + * + * C++17 functionality + * EA_COMPILER_NO_INLINE_VARIABLES + * EA_COMPILER_NO_ALIGNED_NEW + * + * C++20 functionality + * EA_COMPILER_NO_DESIGNATED_INITIALIZERS + * + *----------------------------------------------------------------------------- + * + * Supplemental documentation + * EA_COMPILER_NO_STATIC_CONSTANTS + * Code such as this is legal, but some compilers fail to compile it: + * struct A{ static const a = 1; }; + * + * EA_COMPILER_NO_TEMPLATE_SPECIALIZATION + * Some compilers fail to allow template specialization, such as with this: + * template void DoSomething(U u); + * void DoSomething(int x); + * + * EA_COMPILER_NO_TEMPLATE_PARTIAL_SPECIALIZATION + * Some compilers fail to allow partial template specialization, such as with this: + * template class vector{ }; // Primary templated class. + * template class vector{ }; // Partially specialized version. + * + * EA_COMPILER_NO_MEMBER_TEMPLATES + * Some compilers fail to allow member template functions such as this: + * struct A{ template void DoSomething(U u); }; + * + * EA_COMPILER_NO_MEMBER_TEMPLATE_SPECIALIZATION + * Some compilers fail to allow member template specialization, such as with this: + * struct A{ + * template void DoSomething(U u); + * void DoSomething(int x); + * }; + * + * EA_COMPILER_NO_TEMPLATE_TEMPLATES + * Code such as this is legal: + * template class U> + * U SomeFunction(const U x) { return x.DoSomething(); } + * + * EA_COMPILER_NO_MEMBER_TEMPLATE_FRIENDS + * Some compilers fail to compile templated friends, as with this: + * struct A{ template friend class SomeFriend; }; + * This is described in the C++ Standard at 14.5.3. + * + * EA_COMPILER_NO_VOID_RETURNS + * This is legal C++: + * void DoNothing1(){ }; + * void DoNothing2(){ return DoNothing1(); } + * + * EA_COMPILER_NO_COVARIANT_RETURN_TYPE + * See the C++ standard sec 10.3,p5. + * + * EA_COMPILER_NO_DEDUCED_TYPENAME + * Some compilers don't support the use of 'typename' for + * dependent types in deduced contexts, as with this: + * template void Function(T, typename T::type); + * + * EA_COMPILER_NO_ARGUMENT_DEPENDENT_LOOKUP + * Also known as Koenig lookup. Basically, if you have a function + * that is a namespace and you call that function without prefixing + * it with the namespace the compiler should look at any arguments + * you pass to that function call and search their namespace *first* + * to see if the given function exists there. + * + * EA_COMPILER_NO_EXCEPTION_STD_NAMESPACE + * is in namespace std. Some std libraries fail to + * put the contents of in namespace std. The following + * code should normally be legal: + * void Function(){ std::terminate(); } + * + * EA_COMPILER_NO_EXPLICIT_FUNCTION_TEMPLATE_ARGUMENTS + * Some compilers fail to execute DoSomething() properly, though they + * succeed in compiling it, as with this: + * template + * bool DoSomething(int j){ return i == j; }; + * DoSomething<1>(2); + * + * EA_COMPILER_NO_EXCEPTIONS + * The compiler is configured to disallow the use of try/throw/catch + * syntax (often to improve performance). Use of such syntax in this + * case will cause a compilation error. + * + * EA_COMPILER_NO_UNWIND + * The compiler is configured to allow the use of try/throw/catch + * syntax and behaviour but disables the generation of stack unwinding + * code for responding to exceptions (often to improve performance). + * + *---------------------------------------------------------------------------*/ + +#ifndef INCLUDED_eacompiler_H +#define INCLUDED_eacompiler_H + + #include + + // Note: This is used to generate the EA_COMPILER_STRING macros + #ifndef INTERNAL_STRINGIZE + #define INTERNAL_STRINGIZE(x) INTERNAL_PRIMITIVE_STRINGIZE(x) + #endif + #ifndef INTERNAL_PRIMITIVE_STRINGIZE + #define INTERNAL_PRIMITIVE_STRINGIZE(x) #x + #endif + + // EA_COMPILER_HAS_FEATURE + #ifndef EA_COMPILER_HAS_FEATURE + #if defined(__clang__) + #define EA_COMPILER_HAS_FEATURE(x) __has_feature(x) + #else + #define EA_COMPILER_HAS_FEATURE(x) 0 + #endif + #endif + + + // EA_COMPILER_HAS_BUILTIN + #ifndef EA_COMPILER_HAS_BUILTIN + #if defined(__clang__) + #define EA_COMPILER_HAS_BUILTIN(x) __has_builtin(x) + #else + #define EA_COMPILER_HAS_BUILTIN(x) 0 + #endif + #endif + + + // EDG (EDG compiler front-end, used by other compilers such as SN) + #if defined(__EDG_VERSION__) + #define EA_COMPILER_EDG 1 + + #if defined(_MSC_VER) + #define EA_COMPILER_EDG_VC_MODE 1 + #endif + #if defined(__GNUC__) + #define EA_COMPILER_EDG_GCC_MODE 1 + #endif + #endif + + // EA_COMPILER_WINRTCX_ENABLED + // + // Defined as 1 if the compiler has its available C++/CX support enabled, else undefined. + // This specifically means the corresponding compilation unit has been built with Windows Runtime + // Components enabled, usually via the '-ZW' compiler flags being used. This option allows for using + // ref counted hat-type '^' objects and other C++/CX specific keywords like "ref new" + #if !defined(EA_COMPILER_WINRTCX_ENABLED) && defined(__cplusplus_winrt) + #define EA_COMPILER_WINRTCX_ENABLED 1 + #endif + + + // EA_COMPILER_CPP11_ENABLED + // + // Defined as 1 if the compiler has its available C++11 support enabled, else undefined. + // This does not mean that all of C++11 or any particular feature of C++11 is supported + // by the compiler. It means that whatever C++11 support the compiler has is enabled. + // This also includes existing and older compilers that still identify C++11 as C++0x. + // + // We cannot use (__cplusplus >= 201103L) alone because some compiler vendors have + // decided to not define __cplusplus like thus until they have fully completed their + // C++11 support. + // + #if !defined(EA_COMPILER_CPP11_ENABLED) && defined(__cplusplus) + #if (__cplusplus >= 201103L) // Clang and GCC defines this like so in C++11 mode. + #define EA_COMPILER_CPP11_ENABLED 1 + #elif defined(__GNUC__) && defined(__GXX_EXPERIMENTAL_CXX0X__) + #define EA_COMPILER_CPP11_ENABLED 1 + #elif defined(_MSC_VER) && _MSC_VER >= 1600 // Microsoft unilaterally enables its C++11 support; there is no way to disable it. + #define EA_COMPILER_CPP11_ENABLED 1 + #elif defined(__EDG_VERSION__) // && ??? + // To do: Is there a generic way to determine this? + #endif + #endif + + + // EA_COMPILER_CPP14_ENABLED + // + // Defined as 1 if the compiler has its available C++14 support enabled, else undefined. + // This does not mean that all of C++14 or any particular feature of C++14 is supported + // by the compiler. It means that whatever C++14 support the compiler has is enabled. + // + // We cannot use (__cplusplus >= 201402L) alone because some compiler vendors have + // decided to not define __cplusplus like thus until they have fully completed their + // C++14 support. + #if !defined(EA_COMPILER_CPP14_ENABLED) && defined(__cplusplus) + #if (__cplusplus >= 201402L) // Clang and GCC defines this like so in C++14 mode. + #define EA_COMPILER_CPP14_ENABLED 1 + #elif defined(_MSC_VER) && (_MSC_VER >= 1900) // VS2015+ + #define EA_COMPILER_CPP14_ENABLED 1 + #endif + #endif + + + // EA_COMPILER_CPP17_ENABLED + // + // Defined as 1 if the compiler has its available C++17 support enabled, else undefined. + // This does not mean that all of C++17 or any particular feature of C++17 is supported + // by the compiler. It means that whatever C++17 support the compiler has is enabled. + // + // We cannot use (__cplusplus >= 201703L) alone because some compiler vendors have + // decided to not define __cplusplus like thus until they have fully completed their + // C++17 support. + #if !defined(EA_COMPILER_CPP17_ENABLED) && defined(__cplusplus) + #if (__cplusplus >= 201703L) + #define EA_COMPILER_CPP17_ENABLED 1 + #elif defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L) // C++17+ + #define EA_COMPILER_CPP17_ENABLED 1 + #endif + #endif + + + // EA_COMPILER_CPP20_ENABLED + // + // Defined as 1 if the compiler has its available C++20 support enabled, else undefined. + // This does not mean that all of C++20 or any particular feature of C++20 is supported + // by the compiler. It means that whatever C++20 support the compiler has is enabled. + // + // We cannot use (__cplusplus >= 202003L) alone because some compiler vendors have + // decided to not define __cplusplus like thus until they have fully completed their + // C++20 support. + #if !defined(EA_COMPILER_CPP20_ENABLED) && defined(__cplusplus) + // TODO(rparoin): enable once a C++20 value for the __cplusplus macro has been published + // #if (__cplusplus >= 202003L) + // #define EA_COMPILER_CPP20_ENABLED 1 + // #elif defined(_MSVC_LANG) && (_MSVC_LANG >= 202003L) // C++20+ + // #define EA_COMPILER_CPP20_ENABLED 1 + // #endif + #endif + + + + #if defined(__ARMCC_VERSION) + // Note that this refers to the ARM RVCT compiler (armcc or armcpp), but there + // are other compilers that target ARM processors, such as GCC and Microsoft VC++. + // If you want to detect compiling for the ARM processor, check for EA_PROCESSOR_ARM + // being defined. + // This compiler is also identified by defined(__CC_ARM) || defined(__ARMCC__). + #define EA_COMPILER_RVCT 1 + #define EA_COMPILER_ARM 1 + #define EA_COMPILER_VERSION __ARMCC_VERSION + #define EA_COMPILER_NAME "RVCT" + //#define EA_COMPILER_STRING (defined below) + + // Clang's GCC-compatible driver. + #elif defined(__clang__) && !defined(_MSC_VER) + #define EA_COMPILER_CLANG 1 + #define EA_COMPILER_VERSION (__clang_major__ * 100 + __clang_minor__) + #define EA_COMPILER_NAME "clang" + #define EA_COMPILER_STRING EA_COMPILER_NAME __clang_version__ + + // GCC (a.k.a. GNUC) + #elif defined(__GNUC__) // GCC compilers exist for many platforms. + #define EA_COMPILER_GNUC 1 + #define EA_COMPILER_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__) + #define EA_COMPILER_NAME "GCC" + #define EA_COMPILER_STRING EA_COMPILER_NAME " compiler, version " INTERNAL_STRINGIZE( __GNUC__ ) "." INTERNAL_STRINGIZE( __GNUC_MINOR__ ) + + #if (__GNUC__ == 2) && (__GNUC_MINOR__ < 95) // If GCC < 2.95... + #define EA_COMPILER_NO_MEMBER_TEMPLATES 1 + #endif + #if (__GNUC__ == 2) && (__GNUC_MINOR__ <= 97) // If GCC <= 2.97... + #define EA_COMPILER_NO_MEMBER_TEMPLATE_FRIENDS 1 + #endif + #if (__GNUC__ == 3) && ((__GNUC_MINOR__ == 1) || (__GNUC_MINOR__ == 2)) // If GCC 3.1 or 3.2 (but not pre 3.1 or post 3.2)... + #define EA_COMPILER_NO_EXPLICIT_FUNCTION_TEMPLATE_ARGUMENTS 1 + #endif + + // Borland C++ + #elif defined(__BORLANDC__) + #define EA_COMPILER_BORLANDC 1 + #define EA_COMPILER_VERSION __BORLANDC__ + #define EA_COMPILER_NAME "Borland C" + //#define EA_COMPILER_STRING (defined below) + + #if (__BORLANDC__ <= 0x0550) // If Borland C++ Builder 4 and 5... + #define EA_COMPILER_NO_MEMBER_TEMPLATE_FRIENDS 1 + #endif + #if (__BORLANDC__ >= 0x561) && (__BORLANDC__ < 0x600) + #define EA_COMPILER_NO_MEMBER_FUNCTION_SPECIALIZATION 1 + #endif + + + // Intel C++ + // The Intel Windows compiler masquerades as VC++ and defines _MSC_VER. + // The Intel compiler is based on the EDG compiler front-end. + #elif defined(__ICL) || defined(__ICC) + #define EA_COMPILER_INTEL 1 + + // Should we enable the following? We probably should do so since enabling it does a lot more good than harm + // for users. The Intel Windows compiler does a pretty good job of emulating VC++ and so the user would likely + // have to handle few special cases where the Intel compiler doesn't emulate VC++ correctly. + #if defined(_MSC_VER) + #define EA_COMPILER_MSVC 1 + #define EA_COMPILER_MICROSOFT 1 + #endif + + // Should we enable the following? This isn't as clear because as of this writing we don't know if the Intel + // compiler truly emulates GCC well enough that enabling this does more good than harm. + #if defined(__GNUC__) + #define EA_COMPILER_GNUC 1 + #endif + + #if defined(__ICL) + #define EA_COMPILER_VERSION __ICL + #elif defined(__ICC) + #define EA_COMPILER_VERSION __ICC + #endif + #define EA_COMPILER_NAME "Intel C++" + #if defined(_MSC_VER) + #define EA_COMPILER_STRING EA_COMPILER_NAME " compiler, version " INTERNAL_STRINGIZE( EA_COMPILER_VERSION ) ", EDG version " INTERNAL_STRINGIZE( __EDG_VERSION__ ) ", VC++ version " INTERNAL_STRINGIZE( _MSC_VER ) + #elif defined(__GNUC__) + #define EA_COMPILER_STRING EA_COMPILER_NAME " compiler, version " INTERNAL_STRINGIZE( EA_COMPILER_VERSION ) ", EDG version " INTERNAL_STRINGIZE( __EDG_VERSION__ ) ", GCC version " INTERNAL_STRINGIZE( __GNUC__ ) + #else + #define EA_COMPILER_STRING EA_COMPILER_NAME " compiler, version " INTERNAL_STRINGIZE( EA_COMPILER_VERSION ) ", EDG version " INTERNAL_STRINGIZE( __EDG_VERSION__ ) + #endif + + + #elif defined(_MSC_VER) + #define EA_COMPILER_MSVC 1 + #define EA_COMPILER_MICROSOFT 1 + #define EA_COMPILER_VERSION _MSC_VER + #define EA_COMPILER_NAME "Microsoft Visual C++" + //#define EA_COMPILER_STRING (defined below) + + #if defined(__clang__) + // Clang's MSVC-compatible driver. + #define EA_COMPILER_CLANG_CL 1 + #endif + + #define EA_STANDARD_LIBRARY_MSVC 1 + #define EA_STANDARD_LIBRARY_MICROSOFT 1 + + #if (_MSC_VER <= 1200) // If VC6.x and earlier... + #if (_MSC_VER < 1200) + #define EA_COMPILER_MSVCOLD 1 + #else + #define EA_COMPILER_MSVC6 1 + #endif + + #if (_MSC_VER < 1200) // If VC5.x or earlier... + #define EA_COMPILER_NO_TEMPLATE_SPECIALIZATION 1 + #endif + #define EA_COMPILER_NO_EXPLICIT_FUNCTION_TEMPLATE_ARGUMENTS 1 // The compiler compiles this OK, but executes it wrong. Fixed in VC7.0 + #define EA_COMPILER_NO_VOID_RETURNS 1 // The compiler fails to compile such cases. Fixed in VC7.0 + #define EA_COMPILER_NO_EXCEPTION_STD_NAMESPACE 1 // The compiler fails to compile such cases. Fixed in VC7.0 + #define EA_COMPILER_NO_DEDUCED_TYPENAME 1 // The compiler fails to compile such cases. Fixed in VC7.0 + #define EA_COMPILER_NO_STATIC_CONSTANTS 1 // The compiler fails to compile such cases. Fixed in VC7.0 + #define EA_COMPILER_NO_COVARIANT_RETURN_TYPE 1 // The compiler fails to compile such cases. Fixed in VC7.1 + #define EA_COMPILER_NO_ARGUMENT_DEPENDENT_LOOKUP 1 // The compiler compiles this OK, but executes it wrong. Fixed in VC7.1 + #define EA_COMPILER_NO_TEMPLATE_TEMPLATES 1 // The compiler fails to compile such cases. Fixed in VC7.1 + #define EA_COMPILER_NO_TEMPLATE_PARTIAL_SPECIALIZATION 1 // The compiler fails to compile such cases. Fixed in VC7.1 + #define EA_COMPILER_NO_MEMBER_TEMPLATE_FRIENDS 1 // The compiler fails to compile such cases. Fixed in VC7.1 + //#define EA_COMPILER_NO_MEMBER_TEMPLATES 1 // VC6.x supports member templates properly 95% of the time. So do we flag the remaining 5%? + //#define EA_COMPILER_NO_MEMBER_TEMPLATE_SPECIALIZATION 1 // VC6.x supports member templates properly 95% of the time. So do we flag the remaining 5%? + + #elif (_MSC_VER <= 1300) // If VC7.0 and earlier... + #define EA_COMPILER_MSVC7 1 + + #define EA_COMPILER_NO_COVARIANT_RETURN_TYPE 1 // The compiler fails to compile such cases. Fixed in VC7.1 + #define EA_COMPILER_NO_ARGUMENT_DEPENDENT_LOOKUP 1 // The compiler compiles this OK, but executes it wrong. Fixed in VC7.1 + #define EA_COMPILER_NO_TEMPLATE_TEMPLATES 1 // The compiler fails to compile such cases. Fixed in VC7.1 + #define EA_COMPILER_NO_TEMPLATE_PARTIAL_SPECIALIZATION 1 // The compiler fails to compile such cases. Fixed in VC7.1 + #define EA_COMPILER_NO_MEMBER_TEMPLATE_FRIENDS 1 // The compiler fails to compile such cases. Fixed in VC7.1 + #define EA_COMPILER_NO_MEMBER_FUNCTION_SPECIALIZATION 1 // This is the case only for VC7.0 and not VC6 or VC7.1+. Fixed in VC7.1 + //#define EA_COMPILER_NO_MEMBER_TEMPLATES 1 // VC7.0 supports member templates properly 95% of the time. So do we flag the remaining 5%? + + #elif (_MSC_VER < 1400) // VS2003 _MSC_VER of 1300 means VC7 (VS2003) + // The VC7.1 and later compiler is fairly close to the C++ standard + // and thus has no compiler limitations that we are concerned about. + #define EA_COMPILER_MSVC7_2003 1 + #define EA_COMPILER_MSVC7_1 1 + + #elif (_MSC_VER < 1500) // VS2005 _MSC_VER of 1400 means VC8 (VS2005) + #define EA_COMPILER_MSVC8_2005 1 + #define EA_COMPILER_MSVC8_0 1 + + #elif (_MSC_VER < 1600) // VS2008. _MSC_VER of 1500 means VC9 (VS2008) + #define EA_COMPILER_MSVC9_2008 1 + #define EA_COMPILER_MSVC9_0 1 + + #elif (_MSC_VER < 1700) // VS2010 _MSC_VER of 1600 means VC10 (VS2010) + #define EA_COMPILER_MSVC_2010 1 + #define EA_COMPILER_MSVC10_0 1 + + #elif (_MSC_VER < 1800) // VS2012 _MSC_VER of 1700 means VS2011/VS2012 + #define EA_COMPILER_MSVC_2011 1 // Microsoft changed the name to VS2012 before shipping, despite referring to it as VS2011 up to just a few weeks before shipping. + #define EA_COMPILER_MSVC11_0 1 + #define EA_COMPILER_MSVC_2012 1 + #define EA_COMPILER_MSVC12_0 1 + + #elif (_MSC_VER < 1900) // VS2013 _MSC_VER of 1800 means VS2013 + #define EA_COMPILER_MSVC_2013 1 + #define EA_COMPILER_MSVC13_0 1 + + #elif (_MSC_VER < 1910) // VS2015 _MSC_VER of 1900 means VS2015 + #define EA_COMPILER_MSVC_2015 1 + #define EA_COMPILER_MSVC14_0 1 + + #elif (_MSC_VER < 1911) // VS2017 _MSC_VER of 1910 means VS2017 + #define EA_COMPILER_MSVC_2017 1 + #define EA_COMPILER_MSVC15_0 1 + + #endif + + + // IBM + #elif defined(__xlC__) + #define EA_COMPILER_IBM 1 + #define EA_COMPILER_NAME "IBM XL C" + #define EA_COMPILER_VERSION __xlC__ + #define EA_COMPILER_STRING "IBM XL C compiler, version " INTERNAL_STRINGIZE( __xlC__ ) + + // Unknown + #else // Else the compiler is unknown + + #define EA_COMPILER_VERSION 0 + #define EA_COMPILER_NAME "Unknown" + + #endif + + #ifndef EA_COMPILER_STRING + #define EA_COMPILER_STRING EA_COMPILER_NAME " compiler, version " INTERNAL_STRINGIZE(EA_COMPILER_VERSION) + #endif + + + // Deprecated definitions + // For backwards compatibility, should be supported for at least the life of EABase v2.0.x. + #ifndef EA_COMPILER_NO_TEMPLATE_PARTIAL_SPECIALIZATION + #define EA_COMPILER_PARTIAL_TEMPLATE_SPECIALIZATION 1 + #endif + #ifndef EA_COMPILER_NO_TEMPLATE_SPECIALIZATION + #define EA_COMPILER_TEMPLATE_SPECIALIZATION 1 + #endif + #ifndef EA_COMPILER_NO_MEMBER_TEMPLATES + #define EA_COMPILER_MEMBER_TEMPLATES 1 + #endif + #ifndef EA_COMPILER_NO_MEMBER_TEMPLATE_SPECIALIZATION + #define EA_COMPILER_MEMBER_TEMPLATE_SPECIALIZATION 1 + #endif + + + + /////////////////////////////////////////////////////////////////////////////// + // EA_COMPILER_VA_COPY_REQUIRED + // + // Defines whether va_copy must be used to copy or save va_list objects between uses. + // Some compilers on some platforms implement va_list whereby its contents + // are destroyed upon usage, even if passed by value to another function. + // With these compilers you can use va_copy to save and restore a va_list. + // Known compiler/platforms that destroy va_list contents upon usage include: + // CodeWarrior on PowerPC + // GCC on x86-64 + // However, va_copy is part of the C99 standard and not part of earlier C and + // C++ standards. So not all compilers support it. VC++ doesn't support va_copy, + // but it turns out that VC++ doesn't usually need it on the platforms it supports, + // and va_copy can usually be implemented via memcpy(va_list, va_list) with VC++. + /////////////////////////////////////////////////////////////////////////////// + + #ifndef EA_COMPILER_VA_COPY_REQUIRED + #if ((defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__)) && (!defined(__i386__) || defined(__x86_64__)) && !defined(__ppc__) && !defined(__PPC__) && !defined(__PPC64__) + #define EA_COMPILER_VA_COPY_REQUIRED 1 + #endif + #endif + + + // EA_COMPILER_NO_RTTI + // + // If EA_COMPILER_NO_RTTI is defined, then RTTI (run-time type information) + // is not available (possibly due to being disabled by the user). + // + #if defined(__EDG_VERSION__) && !defined(__RTTI) + #define EA_COMPILER_NO_RTTI 1 + #elif defined(__clang__) && !EA_COMPILER_HAS_FEATURE(cxx_rtti) + #define EA_COMPILER_NO_RTTI 1 + #elif defined(__IBMCPP__) && !defined(__RTTI_ALL__) + #define EA_COMPILER_NO_RTTI 1 + #elif defined(__GXX_ABI_VERSION) && !defined(__GXX_RTTI) + #define EA_COMPILER_NO_RTTI 1 + #elif defined(_MSC_VER) && !defined(_CPPRTTI) + #define EA_COMPILER_NO_RTTI 1 + #elif defined(__ARMCC_VERSION) && defined(__TARGET_CPU_MPCORE) && !defined(__RTTI) + #define EA_COMPILER_NO_RTTI 1 + #endif + + + + // EA_COMPILER_NO_EXCEPTIONS / EA_COMPILER_NO_UNWIND + // + // If EA_COMPILER_NO_EXCEPTIONS is defined, then the compiler is + // configured to not recognize C++ exception-handling statements + // such as try/catch/throw. Thus, when EA_COMPILER_NO_EXCEPTIONS is + // defined, code that attempts to use exception handling statements + // will usually cause a compilation error. If is often desirable + // for projects to disable exception handling because exception + // handling causes extra code and/or data generation which might + // not be needed, especially if it is known that exceptions won't + // be happening. When writing code that is to be portable between + // systems of which some enable exception handling while others + // don't, check for EA_COMPILER_NO_EXCEPTIONS being defined. + // + #if !defined(EA_COMPILER_NO_EXCEPTIONS) && !defined(EA_COMPILER_NO_UNWIND) + #if defined(EA_COMPILER_GNUC) && defined(_NO_EX) // GCC on some platforms defines _NO_EX when exceptions are disabled. + #define EA_COMPILER_NO_EXCEPTIONS 1 + + #elif (defined(EA_COMPILER_CLANG) || defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_INTEL) || defined(EA_COMPILER_RVCT)) && !defined(__EXCEPTIONS) // GCC and most EDG-based compilers define __EXCEPTIONS when exception handling is enabled. + #define EA_COMPILER_NO_EXCEPTIONS 1 + + #elif (defined(EA_COMPILER_MSVC)) && !defined(_CPPUNWIND) + #define EA_COMPILER_NO_UNWIND 1 + + #endif // EA_COMPILER_NO_EXCEPTIONS / EA_COMPILER_NO_UNWIND + #endif // !defined(EA_COMPILER_NO_EXCEPTIONS) && !defined(EA_COMPILER_NO_UNWIND) + + + // ------------------------------------------------------------------------ + // EA_DISABLE_ALL_VC_WARNINGS / EA_RESTORE_ALL_VC_WARNINGS + // + // Disable and re-enable all warning(s) within code. + // + // Example usage: + // EA_DISABLE_ALL_VC_WARNINGS() + // + // EA_RESTORE_ALL_VC_WARNINGS() + // + //This is duplicated from EABase's eacompilertraits.h + #ifndef EA_DISABLE_ALL_VC_WARNINGS + #if defined(_MSC_VER) + #define EA_DISABLE_ALL_VC_WARNINGS() \ + __pragma(warning(push, 0)) \ + __pragma(warning(disable: 4244 4265 4267 4350 4472 4509 4548 4623 4710 4985 6320 4755 4625 4626 4702)) // Some warnings need to be explicitly called out. + #else + #define EA_DISABLE_ALL_VC_WARNINGS() + #endif + #endif + + //This is duplicated from EABase's eacompilertraits.h + #ifndef EA_RESTORE_ALL_VC_WARNINGS + #if defined(_MSC_VER) + #define EA_RESTORE_ALL_VC_WARNINGS() \ + __pragma(warning(pop)) + #else + #define EA_RESTORE_ALL_VC_WARNINGS() + #endif + #endif + + // Dinkumware + //This is duplicated from EABase's eahave.h + #if !defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && !defined(EA_NO_HAVE_DINKUMWARE_CPP_LIBRARY) + #if defined(__cplusplus) + EA_DISABLE_ALL_VC_WARNINGS() + #include // Need to trigger the compilation of yvals.h without directly using because it might not exist. + EA_RESTORE_ALL_VC_WARNINGS() + #endif + + #if defined(__cplusplus) && defined(_CPPLIB_VER) /* If using the Dinkumware Standard library... */ + #define EA_HAVE_DINKUMWARE_CPP_LIBRARY 1 + #else + #define EA_NO_HAVE_DINKUMWARE_CPP_LIBRARY 1 + #endif + #endif + + + // EA_COMPILER_NO_ALIGNED_NEW + // + // + #if !defined(EA_COMPILER_NO_ALIGNED_NEW) + #if defined(_HAS_ALIGNED_NEW) && _HAS_ALIGNED_NEW // VS2017 15.5 Preview + // supported. + #elif defined(EA_COMPILER_CPP17_ENABLED) + // supported. + #else + #define EA_COMPILER_NO_ALIGNED_NEW 1 + #endif + #endif + + // EA_COMPILER_NO_NEW_THROW_SPEC / EA_THROW_SPEC_NEW / EA_THROW_SPEC_DELETE + // + // If defined then the compiler's version of operator new is not decorated + // with a throw specification. This is useful for us to know because we + // often want to write our own overloaded operator new implementations. + // We need such operator new overrides to be declared identically to the + // way the compiler is defining operator new itself. + // + // Example usage: + // void* operator new(std::size_t) EA_THROW_SPEC_NEW(std::bad_alloc); + // void* operator new[](std::size_t) EA_THROW_SPEC_NEW(std::bad_alloc); + // void* operator new(std::size_t, const std::nothrow_t&) EA_THROW_SPEC_NEW_NONE(); + // void* operator new[](std::size_t, const std::nothrow_t&) EA_THROW_SPEC_NEW_NONE(); + // void operator delete(void*) EA_THROW_SPEC_DELETE_NONE(); + // void operator delete[](void*) EA_THROW_SPEC_DELETE_NONE(); + // void operator delete(void*, const std::nothrow_t&) EA_THROW_SPEC_DELETE_NONE(); + // void operator delete[](void*, const std::nothrow_t&) EA_THROW_SPEC_DELETE_NONE(); + // + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) + #if defined(_MSC_VER) && (_MSC_VER >= 1912) // VS2017 15.3+ + #define EA_THROW_SPEC_NEW(x) noexcept(false) + #define EA_THROW_SPEC_NEW_NONE() noexcept + #define EA_THROW_SPEC_DELETE_NONE() noexcept + + #elif defined(_MSC_VER) && (_MSC_VER >= 1910) // VS2017+ + #define EA_THROW_SPEC_NEW(x) throw(x) + #define EA_THROW_SPEC_NEW_NONE() throw() + #define EA_THROW_SPEC_DELETE_NONE() throw() + + #else + #if defined(EA_PLATFORM_SONY) + #define EA_THROW_SPEC_NEW(X) _THROWS(X) + #elif defined(_MSC_VER) + // Disabled warning "nonstandard extension used: 'throw (...)'" as this warning is a W4 warning which is usually off by default + // and doesn't convey any important information but will still complain when building with /Wall (which most teams do) + #define EA_THROW_SPEC_NEW(X) __pragma(warning(push)) __pragma(warning(disable: 4987)) _THROWS(X) __pragma(warning(pop)) + #else + #define EA_THROW_SPEC_NEW(X) _THROW1(X) + #endif + #define EA_THROW_SPEC_NEW_NONE() _THROW0() + #define EA_THROW_SPEC_DELETE_NONE() _THROW0() + + #endif + #elif defined(EA_COMPILER_NO_EXCEPTIONS) && !defined(EA_COMPILER_RVCT) && !defined(EA_PLATFORM_LINUX) && !defined(EA_PLATFORM_APPLE) && !defined(EA_PLATFORM_NX) + #define EA_COMPILER_NO_NEW_THROW_SPEC 1 + + #define EA_THROW_SPEC_NEW(x) + #define EA_THROW_SPEC_NEW_NONE() + #define EA_THROW_SPEC_DELETE_NONE() + #else + #define EA_THROW_SPEC_NEW(x) throw(x) + #define EA_THROW_SPEC_NEW_NONE() throw() + #define EA_THROW_SPEC_DELETE_NONE() throw() + #endif + + + // EA_COMPILER_NO_STANDARD_CPP_LIBRARY + // + // If defined, then the compiler doesn't provide a Standard C++ library. + // + #if defined(EA_PLATFORM_ANDROID) + // Disabled because EA's eaconfig/android_config/android_sdk packages currently + // don't support linking STL libraries. Perhaps we can figure out what linker arguments + // are needed for an app so we can manually specify them and then re-enable this code. + //#include + // + //#if (__ANDROID_API__ < 9) // Earlier versions of Android provide no std C++ STL implementation. + #define EA_COMPILER_NO_STANDARD_CPP_LIBRARY 1 + //#endif + #endif + + + // EA_COMPILER_NO_STATIC_VARIABLE_INIT + // + // If defined, it means that global or static C++ variables will be + // constructed. Not all compiler/platorm combinations support this. + // User code that needs to be portable must avoid having C++ variables + // that construct before main. + // + //#if defined(EA_PLATFORM_MOBILE) + // #define EA_COMPILER_NO_STATIC_VARIABLE_INIT 1 + //#endif + + + // EA_COMPILER_NO_STATIC_FUNCTION_INIT + // + // If defined, it means that functions marked as startup functions + // (e.g. __attribute__((constructor)) in GCC) are supported. It may + // be that some compiler/platform combinations don't support this. + // + //#if defined(XXX) // So far, all compiler/platforms we use support this. + // #define EA_COMPILER_NO_STATIC_VARIABLE_INIT 1 + //#endif + + // EA_COMPILER_NO_VARIADIC_MACROS + // + // If defined, the compiler doesn't support C99/C++11 variadic macros. + // With a variadic macro, you can do this: + // #define MY_PRINTF(format, ...) printf(format, __VA_ARGS__) + // + #if !defined(EA_COMPILER_NO_VARIADIC_MACROS) + #if defined(_MSC_VER) && (_MSC_VER < 1500) // If earlier than VS2008.. + #define EA_COMPILER_NO_VARIADIC_MACROS 1 + #elif defined(__GNUC__) && (((__GNUC__ * 100) + __GNUC_MINOR__)) < 401 // If earlier than GCC 4.1.. + #define EA_COMPILER_NO_VARIADIC_MACROS 1 + #elif defined(EA_COMPILER_EDG) // Includes other compilers + // variadic macros are supported + #endif + #endif + + + // EA_COMPILER_NO_RVALUE_REFERENCES + // + // If defined, the compiler doesn't fully support C++11 rvalue reference semantics. + // This applies to the compiler only and not the Standard Library in use with the compiler, + // which is required by the Standard to have some support itself. + // + #if !defined(EA_COMPILER_NO_RVALUE_REFERENCES) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (_MSC_VER >= 1600) // VS2010+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 403) // EDG 4.3+. + // supported. Earlier EDG supported a subset of rvalue references. Implicit move constructors and assignment operators aren't supported until EDG 4.5. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && EA_COMPILER_HAS_FEATURE(cxx_rvalue_references) + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4005) // GCC 4.5+ + // supported. + #else + #define EA_COMPILER_NO_RVALUE_REFERENCES 1 + #endif + #endif + + + // EA_COMPILER_NO_EXTERN_TEMPLATE + // + // If defined, the compiler doesn't support C++11 extern template. + // With extern templates, you can do this: + // extern template void DoSomething(KnownType u); + // + #if !defined(EA_COMPILER_NO_EXTERN_TEMPLATE) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (_MSC_VER >= 1700) // VS2012+... + // Extern template is supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 401) // EDG 4.1+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && defined(__apple_build_version__) && (EA_COMPILER_VERSION >= 401) + // Extern template is supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && !defined(__apple_build_version__) // Clang other than Apple's Clang + // Extern template is supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4006) // GCC 4.6+ + // Extern template is supported. + #else + #define EA_COMPILER_NO_EXTERN_TEMPLATE 1 + #endif + #endif + + + // EA_COMPILER_NO_RANGE_BASED_FOR_LOOP + // + // If defined, the compiler doesn't support C++11 range-based for loops. + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2930.html + // You must #include for range-based for loops to work. + // Example usage: + // #include + // #include + // std::vector floatVector; + // for(float& f : floatVector) + // f += 1.0; + // + #if !defined(EA_COMPILER_NO_RANGE_BASED_FOR_LOOP) + #if defined(EA_COMPILER_CPP11_ENABLED) && (defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1700)) // VS2012+... + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 405) // EDG 4.5+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && (defined(__clang__) && (EA_COMPILER_VERSION >= 300)) // Clang 3.x+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && (defined(__GNUC__) && (EA_COMPILER_VERSION >= 4006)) // GCC 4.6+ + // supported. + #else + #define EA_COMPILER_NO_RANGE_BASED_FOR_LOOP 1 + #endif + #endif + + + // EA_COMPILER_NO_CONSTEXPR + // + // Refers to C++11 = constexpr (const expression) declarations. + // + #if !defined(EA_COMPILER_NO_CONSTEXPR) + #if defined(EA_COMPILER_CPP11_ENABLED) && (defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1900)) // VS2015+... Not present in VC++ up to and including VS2013. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 406) // EDG 4.6+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && EA_COMPILER_HAS_FEATURE(cxx_constexpr) + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4006) // GCC 4.6+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1900) // VS 2015+ + // supported. + #else + #define EA_COMPILER_NO_CONSTEXPR 1 + #endif + #endif + + + // EA_COMPILER_NO_CONSTEXPR_IF + // + // Refers to C++17 = constexpr if(const expression) conditionals. + // + #if !defined(EA_COMPILER_NO_CONSTEXPR_IF) + #if defined(EA_COMPILER_CPP17_ENABLED) && (defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1911)) // VS2017 15.3+ + // supported. + #elif defined(EA_COMPILER_CPP17_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 309) // Clang 3.9+ + // supported. + #elif defined(EA_COMPILER_CPP17_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 7000) // GCC 7+ + // supported. + #else + #define EA_COMPILER_NO_CONSTEXPR_IF 1 + #endif + #endif + + + // EA_COMPILER_NO_OVERRIDE + // + // Refers to the C++11 override specifier. + // + #ifndef EA_COMPILER_NO_OVERRIDE + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION > 1600) // VC++ > VS2010, even without C++11 support. VS2010 does support override, however will generate warnings due to the keyword being 'non-standard' + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4007) // GCC 4.7+ + // supported. + #else + #define EA_COMPILER_NO_OVERRIDE 1 + #endif + #endif + + + // EA_COMPILER_NO_INHERITANCE_FINAL + // + // Refers to the C++11 final specifier. + // + #ifndef EA_COMPILER_NO_INHERITANCE_FINAL + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1500) // VS2008+, even without C++11 support. + // supported, though you need to use EA_INHERITANCE_FINAL for it to work with VS versions prior to 2012. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+ + // supported + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4007) // GCC 4.7+ + // supported + #else + #define EA_COMPILER_NO_INHERITANCE_FINAL 1 + #endif + #endif + + + // EA_COMPILER_NO_AUTO + // + // Refers to C++11 auto. + // + #if !defined(EA_COMPILER_NO_AUTO) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1600) // VS2010+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 401) // EDG 4.1+. + // supported with the exception of the usage of braced initializer lists as of EDG 4.3. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+, including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported. + #else + #define EA_COMPILER_NO_AUTO 1 + #endif + #endif + + + // EA_COMPILER_NO_NULLPTR + // + // Refers to C++11 nullptr (which is a built in type). std::nullptr_t is defined in C++11 . + // Note that implements a portable nullptr implementation. + // + #if !defined(EA_COMPILER_NO_NULLPTR) + #if (defined(_MSC_VER) && (_MSC_VER >= 1600)) && defined(EA_COMPILER_CPP11_ENABLED) + // supported + #elif defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4006) && defined(EA_COMPILER_CPP11_ENABLED) + // supported + #elif defined(__clang__) && defined(EA_COMPILER_CPP11_ENABLED) + // supported + #elif defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 403) && defined(EA_COMPILER_CPP11_ENABLED) + // supported + #else + #define EA_COMPILER_NO_NULLPTR 1 + #endif + #endif + + + // EA_COMPILER_NO_DECLTYPE + // + // Refers to C++11 decltype. + // + #if !defined(EA_COMPILER_NO_DECLTYPE) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1600) // VS2010+ + // supported, though VS2010 doesn't support the spec completely as specified in the final standard. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 401) // EDG 4.1+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+, including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4003) // GCC 4.3+ + // supported. + #else + #define EA_COMPILER_NO_DECLTYPE 1 + #endif + #endif + + + + // EA_COMPILER_NO_DEFAULTED_FUNCTIONS + // EA_COMPILER_NO_DELETED_FUNCTIONS + // + // Refers to C++11 = default and = delete function declarations. + // + #if !defined(EA_COMPILER_NO_DEFAULTED_FUNCTIONS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+ + // supported, but as of VS2013 it isn't supported for defaulted move constructors and move assignment operators. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 401) // EDG 4.1+. + // supported, but as of EDG 4.3 it isn't supported for defaulted move constructors and move assignment operators until EDG 4.5. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) // Clang 3.0+, including Apple's Clang + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported. + #else + // VC++ doesn't support it as of VS2012. + #define EA_COMPILER_NO_DEFAULTED_FUNCTIONS 1 + #endif + #endif + + #if !defined(EA_COMPILER_NO_DELETED_FUNCTIONS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+ + // supported, but as of VS2013 it isn't supported for defaulted move constructors and move assignment operators. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 401) // EDG 4.1+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported. + #else + // VC++ doesn't support it as of VS2012. + #define EA_COMPILER_NO_DELETED_FUNCTIONS 1 + #endif + #endif + + + // EA_COMPILER_NO_LAMBDA_EXPRESSIONS + // + // Refers to C++11 lambda expressions. + // + #if !defined(EA_COMPILER_NO_LAMBDA_EXPRESSIONS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1600) // VS2010+ + // supported, though VS2010 doesn't support the spec completely as specified in the final standard. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 401) // EDG 4.1+. + // supported. However, converting lambdas to function pointers is not supported until EDG 4.5. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 301) && !defined(__apple_build_version__) // Clang 3.1+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported. + #else + #define EA_COMPILER_NO_LAMBDA_EXPRESSIONS 1 + #endif + #endif + + + // EA_COMPILER_NO_TRAILING_RETURN_TYPES + // + // Refers to C++11 trailing-return-type. Also sometimes referred to as "incomplete return type". + // + #if !defined(EA_COMPILER_NO_TRAILING_RETURN_TYPES) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1600) // VS2010+ + // supported, though VS2010 doesn't support the spec completely as specified in the final standard. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 402) // EDG 4.2+. + // supported. However, use of "this" in trailing return types is not supported untiil EDG 4.4 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 301) && !defined(__apple_build_version__) // Clang 3.1+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported. + #else + #define EA_COMPILER_NO_TRAILING_RETURN_TYPES 1 + #endif + #endif + + + // EA_COMPILER_NO_STRONGLY_TYPED_ENUMS + // + // Refers to C++11 strongly typed enums, which includes enum classes and sized enums. Doesn't include forward-declared enums. + // + #if !defined(EA_COMPILER_NO_STRONGLY_TYPED_ENUMS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1700) // VS2012+ + // supported. A subset of this is actually supported by VS2010. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 400) // EDG 4.0+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+, including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported. + #else + #define EA_COMPILER_NO_STRONGLY_TYPED_ENUMS 1 + #endif + #endif + + + // EA_COMPILER_NO_FORWARD_DECLARED_ENUMS + // + // Refers to C++11 forward declared enums. + // + #if !defined(EA_COMPILER_NO_FORWARD_DECLARED_ENUMS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1700) // VS2012+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 405) // EDG 4.5+. + // supported. EDG 4.3 supports basic forward-declared enums, but not forward-declared strongly typed enums. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 301) && !defined(__apple_build_version__) // Clang 3.1+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4006) // GCC 4.6+ + // supported. + #else + #define EA_COMPILER_NO_FORWARD_DECLARED_ENUMS 1 + #endif + #endif + + + // EA_COMPILER_NO_VARIADIC_TEMPLATES + // + // Refers to C++11 variadic templates. + // + #if !defined(EA_COMPILER_NO_VARIADIC_TEMPLATES) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (_MSC_FULL_VER == 170051025) // VS2012 November Preview for Windows only. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 403) // EDG 4.3+. + // supported, though 4.1 has partial support for variadic templates. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+, including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported, though GCC 4.3 has partial support for variadic templates. + #else + #define EA_COMPILER_NO_VARIADIC_TEMPLATES 1 + #endif + #endif + + + // EA_COMPILER_NO_TEMPLATE_ALIASES + // + // Refers to C++11 alias templates. + // Example alias template usage: + // template + // using Dictionary = eastl::map; + // + // Dictionary StringIntDictionary; + // + #if !defined(EA_COMPILER_NO_TEMPLATE_ALIASES) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 402) // EDG 4.2+. + // supported, though 4.1 has partial support for variadic templates. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4007) // GCC 4.7+ + // supported, though GCC 4.3 has partial support for variadic templates. + #else + #define EA_COMPILER_NO_TEMPLATE_ALIASES 1 + #endif + #endif + + + // EA_COMPILER_NO_VARIABLE_TEMPLATES + // + // Refers to C++14 variable templates. + // Example variable template usage: + // template + // constexpr T pi = T(3.1415926535897932385); + // + #if !defined(EA_COMPILER_NO_VARIABLE_TEMPLATES) + #if defined(_MSC_VER) && (_MSC_FULL_VER >= 190023918) // VS2015 Update 2 and above. + // supported. + #elif defined(EA_COMPILER_CPP14_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 304) && !defined(__apple_build_version__) // Clang 3.4+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP14_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 5000) // GCC 5+ + // supported. + #elif !defined(EA_COMPILER_CPP14_ENABLED) + #define EA_COMPILER_NO_VARIABLE_TEMPLATES 1 + #endif + #endif + + + // EA_COMPILER_NO_INLINE_VARIABLES + // + // Refers to C++17 inline variables that allows the definition of variables in header files + // + // Example usage: + // struct Foo + // { + // static inline constexpr int kConstant = 42; // no out of class definition + // }; + // + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4424.pdf + // http://en.cppreference.com/w/cpp/language/inline + // + #if !defined(EA_COMPILER_NO_INLINE_VARIABLES) + #define EA_COMPILER_NO_INLINE_VARIABLES 1 + #endif + + + // EA_COMPILER_NO_INITIALIZER_LISTS + // + // Refers to C++11 initializer lists. + // This refers to the compiler support for this and not the Standard Library support (std::initializer_list). + // + #if !defined(EA_COMPILER_NO_INITIALIZER_LISTS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (_MSC_FULL_VER == 170051025) // VS2012 November Preview for Windows only. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 405) // EDG 4.5+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 301) && !defined(__apple_build_version__) // Clang 3.1+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported, though GCC 4.3 has partial support for it. + #else + #define EA_COMPILER_NO_INITIALIZER_LISTS 1 + #endif + #endif + + + // EA_COMPILER_NO_NORETURN + // + // Refers to C++11 declaration attribute: noreturn. + // http://en.cppreference.com/w/cpp/language/attributes + // http://blog.aaronballman.com/2011/09/understanding-attributes/ + // + #if !defined(EA_COMPILER_NO_NORETURN) + #if defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1300) // VS2003+ + // supported via __declspec(noreturn). You need to use that or EA_NORETURN. VC++ up to VS2013 doesn't support any C++11 attribute types. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 402) // EDG 4.2+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4008) // GCC 4.8+ + // supported. + #else + #define EA_COMPILER_NO_NORETURN 1 + #endif + #endif + + + // EA_COMPILER_NO_CARRIES_DEPENDENCY + // + // Refers to C++11 declaration attribute: carries_dependency. + // http://en.cppreference.com/w/cpp/language/attributes + // http://blog.aaronballman.com/2011/09/understanding-attributes/ + // + #if !defined(EA_COMPILER_NO_CARRIES_DEPENDENCY) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 402) // EDG 4.2+. + // supported; stricter than other compilers in its usage. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + // Currently GNUC doesn't appear to support this attribute. + //#elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4008) // GCC 4.8+ + // // supported. + #else + #define EA_COMPILER_NO_CARRIES_DEPENDENCY 1 + #endif + #endif + + + // EA_COMPILER_NO_FALLTHROUGH + // + // Refers to C++17 declaration attribute: fallthrough. + // http://en.cppreference.com/w/cpp/language/attributes + // + #if !defined(EA_COMPILER_NO_FALLTHROUGH) + #if defined(EA_COMPILER_CPP17_ENABLED) + // supported. + #else + #define EA_COMPILER_NO_FALLTHROUGH 1 + #endif + #endif + + + // EA_COMPILER_NO_NODISCARD + // + // Refers to C++17 declaration attribute: nodiscard. + // http://en.cppreference.com/w/cpp/language/attributes + // + #if !defined(EA_COMPILER_NO_NODISCARD) + #if defined(EA_COMPILER_CPP17_ENABLED) + // supported. + #else + #define EA_COMPILER_NO_NODISCARD 1 + #endif + #endif + + + // EA_COMPILER_NO_MAYBE_UNUSED + // + // Refers to C++17 declaration attribute: maybe_unused. + // http://en.cppreference.com/w/cpp/language/attributes + // + #if !defined(EA_COMPILER_NO_MAYBE_UNUSED) + #if defined(EA_COMPILER_CPP17_ENABLED) + // supported. + #elif defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1912) // VS2017 15.3+ + // supported. + #else + #define EA_COMPILER_NO_MAYBE_UNUSED 1 + #endif + #endif + + + // EA_COMPILER_NO_STRUCTURED_BINDING + // + // Indicates if target compiler supports the C++17 "structured binding" language feature. + // https://en.cppreference.com/w/cpp/language/structured_binding + // + // + #if !defined(EA_COMPILER_NO_STRUCTURED_BINDING) + #if defined(EA_COMPILER_CPP17_ENABLED) + // supported. + #elif defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1912) // VS2017 15.3+ + // supported. + #else + #define EA_COMPILER_NO_STRUCTURED_BINDING 1 + #endif + #endif + + + // EA_COMPILER_NO_DESIGNATED_INITIALIZERS + // + // Indicates the target compiler supports the C++20 "designated initializer" language feature. + // https://en.cppreference.com/w/cpp/language/aggregate_initialization + // + // Example: + // struct A { int x; int y; }; + // A a = { .y = 42, .x = 1 }; + // + #if !defined(EA_COMPILER_NO_DESIGNATED_INITIALIZERS) + #if defined(EA_COMPILER_CPP20_ENABLED) + // supported. + #else + #define EA_COMPILER_NO_DESIGNATED_INITIALIZERS 1 + #endif + #endif + + + // EA_COMPILER_NO_NONSTATIC_MEMBER_INITIALIZERS + // + // Refers to C++11 declaration attribute: carries_dependency. + // http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2756.htm + // + #if !defined(EA_COMPILER_NO_NONSTATIC_MEMBER_INITIALIZERS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4007) // GCC 4.7+ + // supported. + #else + #define EA_COMPILER_NO_NONSTATIC_MEMBER_INITIALIZERS 1 + #endif + #endif + + + // EA_COMPILER_NO_RIGHT_ANGLE_BRACKETS + // + // Defines if the compiler supports >> (as opposed to > >) in template + // declarations such as typedef eastl::list> ListList; + // + #if !defined(EA_COMPILER_NO_RIGHT_ANGLE_BRACKETS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1600) // VS2010+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 401) // EDG 4.1+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+, including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4003) // GCC 4.3+ + // supported. + #else + #define EA_COMPILER_NO_RIGHT_ANGLE_BRACKETS 1 + #endif + #endif + + + // EA_COMPILER_NO_ALIGNOF + // + // Refers specifically to C++11 alignof and not old compiler extensions such as __alignof__(). + // However, EABase provides a portable EA_ALIGN_OF which works for all compilers. + // + #if !defined(EA_COMPILER_NO_ALIGNOF) + // Not supported by VC++ as of VS2013, though EA_ALIGN_OF is supported on all coompilers as an alternative. + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+, including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4005) // GCC 4.5+ + // supported. + #else + #define EA_COMPILER_NO_ALIGNOF 1 + #endif + #endif + + + // EA_COMPILER_NO_ALIGNAS + // + // Refers to C++11 alignas. + // + #if !defined(EA_COMPILER_NO_ALIGNAS) + // Not supported by VC++ as of VS2013. + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4008) // GCC 4.8+ + // supported. + #else + #define EA_COMPILER_NO_ALIGNAS 1 + #endif + #endif + + + // EA_COMPILER_NO_DELEGATING_CONSTRUCTORS + // + // Refers to C++11 constructor delegation. + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1986.pdf + // https://www.ibm.com/developerworks/mydeveloperworks/blogs/5894415f-be62-4bc0-81c5-3956e82276f3/entry/c_0x_delegating_constructors + // + #if !defined(EA_COMPILER_NO_DELEGATING_CONSTRUCTORS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 407) // EDG 4.7+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4007) // GCC 4.7+ + // supported. + #else + #define EA_COMPILER_NO_DELEGATING_CONSTRUCTORS 1 + #endif + #endif + + + // EA_COMPILER_NO_INHERITING_CONSTRUCTORS + // + // Refers to C++11 constructor inheritance via 'using'. + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2540.htm + // + #if !defined(EA_COMPILER_NO_INHERITING_CONSTRUCTORS) + // Not supported by VC++ as of VS2013. + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && EA_COMPILER_HAS_FEATURE(cxx_inheriting_constructors) // Clang + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4008) // GCC 4.8+ + // supported. + #else + #define EA_COMPILER_NO_INHERITING_CONSTRUCTORS 1 + #endif + #endif + + + // EA_COMPILER_NO_USER_DEFINED_LITERALS + // + // http://en.cppreference.com/w/cpp/language/user_literal + // http://stackoverflow.com/questions/237804/what-new-capabilities-do-user-defined-literals-add-to-c + // + #if !defined(EA_COMPILER_NO_USER_DEFINED_LITERALS) + // Not supported by VC++ as of VS2013. + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 301) && !defined(__apple_build_version__) // Clang 3.1+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4007) // GCC 4.7+ + // supported. + #else + #define EA_COMPILER_NO_USER_DEFINED_LITERALS 1 + #endif + #endif + + + // EA_COMPILER_NO_STANDARD_LAYOUT_TYPES + // a.k.a. POD relaxation + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2342.htm + // + #if !defined(EA_COMPILER_NO_STANDARD_LAYOUT_TYPES) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1700) // VS2012+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4005) // GCC 4.5+ + // supported. + #else + #define EA_COMPILER_NO_STANDARD_LAYOUT_TYPES 1 + #endif + #endif + + + // EA_COMPILER_NO_EXTENDED_SIZEOF + // + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2253.html + // Allows you to do this: sizeof(SomeClass::mSomeMember) + // + #if !defined(EA_COMPILER_NO_EXTENDED_SIZEOF) + // Not supported by VC++ as of VS2013. + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + // Versions of EDG prior to 4.5 only support extended sizeof in non-member functions. Full support was added in 4.5 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 405) // EDG 4.5+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 301) && !defined(__apple_build_version__) // Clang 3.1+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4005) // GCC 4.5+ + // supported. + #else + #define EA_COMPILER_NO_EXTENDED_SIZEOF 1 + #endif + #endif + + + // EA_COMPILER_NO_INLINE_NAMESPACES + // + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2535.htm + // http://blog.aaronballman.com/2011/07/inline-namespaces/ + // + #if !defined(EA_COMPILER_NO_INLINE_NAMESPACES) + // Not supported by VC++ as of VS2013. + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 405) // EDG 4.5+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+, including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported. + #else + #define EA_COMPILER_NO_INLINE_NAMESPACES 1 + #endif + #endif + + + // EA_COMPILER_NO_UNRESTRICTED_UNIONS + // + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf + // + #if !defined(EA_COMPILER_NO_UNRESTRICTED_UNIONS) + // Not supported by VC++ as of VS2013. + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 406) // EDG 4.6+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 301) && !defined(__apple_build_version__) // Clang 3.1+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4006) // GCC 4.6+ + // supported. + #else + #define EA_COMPILER_NO_UNRESTRICTED_UNIONS 1 + #endif + #endif + + + // EA_COMPILER_NO_EXPLICIT_CONVERSION_OPERATORS + // + // http://en.wikipedia.org/wiki/C%2B%2B11#Explicit_conversion_operators + // + #if !defined(EA_COMPILER_NO_EXPLICIT_CONVERSION_OPERATORS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (_MSC_FULL_VER == 170051025) // VS2012 November Preview for Windows only. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 404) // EDG 4.4+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4005) // GCC 4.5+ + // supported. + #else + #define EA_COMPILER_NO_EXPLICIT_CONVERSION_OPERATORS 1 + #endif + #endif + + + // EA_COMPILER_NO_FUNCTION_TEMPLATE_DEFAULT_ARGS + // + // The compiler does not support default template arguments for function templates. + // http://stackoverflow.com/questions/2447458/default-template-arguments-for-function-templates + // + #if !defined(EA_COMPILER_NO_FUNCTION_TEMPLATE_DEFAULT_ARGS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 403) // EDG 4.4+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+, including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4003) // GCC 4.3+ + // supported. + #else + #define EA_COMPILER_NO_FUNCTION_TEMPLATE_DEFAULT_ARGS 1 + #endif + #endif + + + // EA_COMPILER_NO_LOCAL_CLASS_TEMPLATE_PARAMETERS + // + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2657.htm + // http://stackoverflow.com/questions/5751977/local-type-as-template-arguments-in-c + // + #if !defined(EA_COMPILER_NO_LOCAL_CLASS_TEMPLATE_PARAMETERS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1600) // VS2010+ + // supported. + #if (EA_COMPILER_VERSION < 1700) // VS2010 generates a warning, but the C++ language now allows it. + #pragma warning(disable: 4836) // nonstandard extension used: local types or unnamed types cannot be used as template arguments. + #endif + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 402) // EDG 4.2+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+, including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4005) // GCC 4.5+ + // supported. + #else + #define EA_COMPILER_NO_LOCAL_CLASS_TEMPLATE_PARAMETERS 1 + #endif + #endif + + + // EA_COMPILER_NO_NOEXCEPT + // + // C++11 noexcept + // http://en.cppreference.com/w/cpp/language/attributes + // http://en.cppreference.com/w/cpp/language/noexcept + // + #if !defined(EA_COMPILER_NO_NOEXCEPT) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1900) // VS2014+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 405) // EDG 4.5+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4006) // GCC 4.6+ + // supported. + #else + #define EA_COMPILER_NO_NOEXCEPT 1 + #endif + #endif + + + // EA_COMPILER_NO_RAW_LITERALS + // + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2442.htm + // http://en.wikipedia.org/wiki/C%2B%2B11#New_string_literals + // + #if !defined(EA_COMPILER_NO_RAW_LITERALS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 407) // EDG 4.7+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4005) // GCC 4.5+ + // supported. + #else + #define EA_COMPILER_NO_RAW_LITERALS 1 + #endif + #endif + + + // EA_COMPILER_NO_UNICODE_STRING_LITERALS + // + // http://en.wikipedia.org/wiki/C%2B%2B11#New_string_literals + // + #if !defined(EA_COMPILER_NO_UNICODE_STRING_LITERALS) + // Not supported by VC++ as of VS2013. + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 407) // EDG 4.7+. + // supported. It's not clear if it's v4.4 or v4.7 that adds this support. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 300) && !defined(__apple_build_version__) // Clang 3.0+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 407) // EDG 4.7+. + // supported. It's not clear if it's v4.4 or v4.7 that adds this support. + #else + #define EA_COMPILER_NO_UNICODE_STRING_LITERALS 1 + #endif + #endif + + + // EA_COMPILER_NO_NEW_CHARACTER_TYPES + // + // Refers to char16_t and char32_t as true native types (and not something simply typedef'd from uint16_t and uint32_t). + // http://en.cppreference.com/w/cpp/language/types + // + #if !defined(EA_COMPILER_NO_NEW_CHARACTER_TYPES) + #if defined(EA_COMPILER_NO_UNICODE_STRING_LITERALS) // Some compilers have had support for char16_t prior to support for u"", but it's not useful to have the former without the latter. + #define EA_COMPILER_NO_NEW_CHARACTER_TYPES 1 + #endif + #endif + + + // EA_COMPILER_NO_UNICODE_CHAR_NAME_LITERALS + // + // C++ 11 relaxed \u\U sequences in strings. + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2170.html + // + #if !defined(EA_COMPILER_NO_UNICODE_CHAR_NAME_LITERALS) + // VC++ up till at least VS2013 supports \u and \U but supports them wrong with respect to the C++11 Standard. + + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 301) && !defined(__apple_build_version__) // Clang 3.1+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4005) // GCC 4.5+ + // supported. + #else + #define EA_COMPILER_NO_UNICODE_CHAR_NAME_LITERALS 1 + #endif + #endif + + + // EA_COMPILER_NO_UNIFIED_INITIALIZATION_SYNTAX + // + // http://en.wikipedia.org/wiki/C%2B%2B11#Uniform_initialization + // + #if !defined(EA_COMPILER_NO_UNIFIED_INITIALIZATION_SYNTAX) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1800) // VS2013+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 401) && defined(__apple_build_version__) // Apple clang 4.1+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 301) && !defined(__apple_build_version__) // Clang 3.1+, not including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4004) // GCC 4.4+ + // supported. + #else + #define EA_COMPILER_NO_UNIFIED_INITIALIZATION_SYNTAX 1 + #endif + #endif + + + // EA_COMPILER_NO_EXTENDED_FRIEND_DECLARATIONS + // + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1791.pdf + // + #if !defined(EA_COMPILER_NO_EXTENDED_FRIEND_DECLARATIONS) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1600) // VS2010+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 401) // EDG 4.1+. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && (EA_COMPILER_VERSION >= 209) // Clang 2.9+, including Apple's Clang. + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4007) // GCC 4.7+ + // supported. + #else + #define EA_COMPILER_NO_EXTENDED_FRIEND_DECLARATIONS 1 + #endif + #endif + + + // EA_COMPILER_NO_THREAD_LOCAL + // + // Refers specifically to C++ thread_local, which is like compiler __thread implementations except + // that it also supports non-trivial classes (e.g. with ctors). EA_COMPILER_NO_THREAD_LOCAL refers + // specifically to full C++11 thread_local support. The EAThread package provides a wrapper for + // __thread via EA_THREAD_LOCAL (which unfortunately sounds like C++ thread_local). + // + // https://en.cppreference.com/w/cpp/keyword/thread_local + // + #if !defined(EA_COMPILER_NO_THREAD_LOCAL) + #if defined(EA_COMPILER_CPP11_ENABLED) && defined(__clang__) && EA_COMPILER_HAS_FEATURE(cxx_thread_local) + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(_MSC_VER) && (EA_COMPILER_VERSION >= 1900) // VS2015+ + // supported. + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(__GNUC__) && (EA_COMPILER_VERSION >= 4008) // GCC 4.8+ + // supported. + #else + #define EA_COMPILER_NO_THREAD_LOCAL 1 + #endif + #endif + + +#endif // INCLUDED_eacompiler_H + + + + + diff --git a/src/thirdparty/ea/EABase/config/eacompilertraits.h b/src/thirdparty/ea/EABase/config/eacompilertraits.h new file mode 100644 index 00000000..2a196835 --- /dev/null +++ b/src/thirdparty/ea/EABase/config/eacompilertraits.h @@ -0,0 +1,2603 @@ +/*----------------------------------------------------------------------------- + * config/eacompilertraits.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *----------------------------------------------------------------------------- + * Currently supported defines include: + * EA_PREPROCESSOR_JOIN + * + * EA_COMPILER_IS_ANSIC + * EA_COMPILER_IS_C99 + * EA_COMPILER_IS_C11 + * EA_COMPILER_HAS_C99_TYPES + * EA_COMPILER_IS_CPLUSPLUS + * EA_COMPILER_MANAGED_CPP + * EA_COMPILER_INTMAX_SIZE + * EA_OFFSETOF + * EA_SIZEOF_MEMBER + * + * EA_ALIGN_OF() + * EA_ALIGN_MAX_STATIC / EA_ALIGN_MAX_AUTOMATIC + * EA_ALIGN() / EA_PREFIX_ALIGN() / EA_POSTFIX_ALIGN() + * EA_ALIGNED() + * EA_PACKED() + * + * EA_LIKELY() + * EA_UNLIKELY() + * EA_INIT_PRIORITY() + * EA_MAY_ALIAS() + * EA_ASSUME() + * EA_ANALYSIS_ASSUME() + * EA_PURE + * EA_WEAK + * EA_UNUSED() + * EA_EMPTY() + * + * EA_WCHAR_T_NON_NATIVE + * EA_WCHAR_SIZE = + * + * EA_RESTRICT + * EA_DEPRECATED / EA_PREFIX_DEPRECATED / EA_POSTFIX_DEPRECATED + * EA_FORCE_INLINE / EA_PREFIX_FORCE_INLINE / EA_POSTFIX_FORCE_INLINE + * EA_NO_INLINE / EA_PREFIX_NO_INLINE / EA_POSTFIX_NO_INLINE + * EA_NO_VTABLE / EA_CLASS_NO_VTABLE / EA_STRUCT_NO_VTABLE + * EA_PASCAL + * EA_PASCAL_FUNC() + * EA_SSE = [0 | 1] + * EA_IMPORT + * EA_EXPORT + * EA_PRAGMA_ONCE_SUPPORTED + * EA_ONCE + * EA_OVERRIDE + * EA_INHERITANCE_FINAL + * EA_SEALED + * EA_ABSTRACT + * EA_CONSTEXPR / EA_CONSTEXPR_OR_CONST + * EA_CONSTEXPR_IF + * EA_EXTERN_TEMPLATE + * EA_NOEXCEPT + * EA_NORETURN + * EA_CARRIES_DEPENDENCY + * EA_NON_COPYABLE / struct EANonCopyable + * EA_OPTIMIZE_OFF / EA_OPTIMIZE_ON + * EA_SIGNED_RIGHT_SHIFT_IS_UNSIGNED + * + * EA_DISABLE_VC_WARNING / EA_RESTORE_VC_WARNING / EA_DISABLE_ALL_VC_WARNINGS / EA_RESTORE_ALL_VC_WARNINGS + * EA_DISABLE_GCC_WARNING / EA_RESTORE_GCC_WARNING + * EA_DISABLE_CLANG_WARNING / EA_RESTORE_CLANG_WARNING + * EA_DISABLE_SN_WARNING / EA_RESTORE_SN_WARNING / EA_DISABLE_ALL_SN_WARNINGS / EA_RESTORE_ALL_SN_WARNINGS + * EA_DISABLE_GHS_WARNING / EA_RESTORE_GHS_WARNING + * EA_DISABLE_EDG_WARNING / EA_RESTORE_EDG_WARNING + * EA_DISABLE_CW_WARNING / EA_RESTORE_CW_WARNING + * + * EA_DISABLE_DEFAULT_CTOR + * EA_DISABLE_COPY_CTOR + * EA_DISABLE_MOVE_CTOR + * EA_DISABLE_ASSIGNMENT_OPERATOR + * EA_DISABLE_MOVE_OPERATOR + * + * Todo: + * Find a way to reliably detect wchar_t size at preprocessor time and + * implement it below for EA_WCHAR_SIZE. + * + * Todo: + * Find out how to support EA_PASCAL and EA_PASCAL_FUNC for systems in + * which it hasn't yet been found out for. + *---------------------------------------------------------------------------*/ + + +#ifndef INCLUDED_eacompilertraits_H +#define INCLUDED_eacompilertraits_H + + #include + #include + + + // Metrowerks uses #defines in its core C header files to define + // the kind of information we need below (e.g. C99 compatibility) + + + + // Determine if this compiler is ANSI C compliant and if it is C99 compliant. + #if defined(__STDC__) + #define EA_COMPILER_IS_ANSIC 1 // The compiler claims to be ANSI C + + // Is the compiler a C99 compiler or equivalent? + // From ISO/IEC 9899:1999: + // 6.10.8 Predefined macro names + // __STDC_VERSION__ The integer constant 199901L. (150) + // + // 150) This macro was not specified in ISO/IEC 9899:1990 and was + // specified as 199409L in ISO/IEC 9899/AMD1:1995. The intention + // is that this will remain an integer constant of type long int + // that is increased with each revision of this International Standard. + // + #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + #define EA_COMPILER_IS_C99 1 + #endif + + // Is the compiler a C11 compiler? + // From ISO/IEC 9899:2011: + // Page 176, 6.10.8.1 (Predefined macro names) : + // __STDC_VERSION__ The integer constant 201112L. (178) + // + #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) + #define EA_COMPILER_IS_C11 1 + #endif + #endif + + // Some compilers (e.g. GCC) define __USE_ISOC99 if they are not + // strictly C99 compilers (or are simply C++ compilers) but are set + // to use C99 functionality. Metrowerks defines _MSL_C99 as 1 in + // this case, but 0 otherwise. + #if (defined(__USE_ISOC99) || (defined(_MSL_C99) && (_MSL_C99 == 1))) && !defined(EA_COMPILER_IS_C99) + #define EA_COMPILER_IS_C99 1 + #endif + + // Metrowerks defines C99 types (e.g. intptr_t) instrinsically when in C99 mode (-lang C99 on the command line). + #if (defined(_MSL_C99) && (_MSL_C99 == 1)) + #define EA_COMPILER_HAS_C99_TYPES 1 + #endif + + #if defined(__GNUC__) + #if (((__GNUC__ * 100) + __GNUC_MINOR__) >= 302) // Also, GCC defines _HAS_C9X. + #define EA_COMPILER_HAS_C99_TYPES 1 // The compiler is not necessarily a C99 compiler, but it defines C99 types. + + #ifndef __STDC_LIMIT_MACROS + #define __STDC_LIMIT_MACROS 1 + #endif + + #ifndef __STDC_CONSTANT_MACROS + #define __STDC_CONSTANT_MACROS 1 // This tells the GCC compiler that we want it to use its native C99 types. + #endif + #endif + #endif + + #if defined(_MSC_VER) && (_MSC_VER >= 1600) + #define EA_COMPILER_HAS_C99_TYPES 1 + #endif + + #ifdef __cplusplus + #define EA_COMPILER_IS_CPLUSPLUS 1 + #endif + + + // ------------------------------------------------------------------------ + // EA_PREPROCESSOR_JOIN + // + // This macro joins the two arguments together, even when one of + // the arguments is itself a macro (see 16.3.1 in C++98 standard). + // This is often used to create a unique name with __LINE__. + // + // For example, this declaration: + // char EA_PREPROCESSOR_JOIN(unique_, __LINE__); + // expands to this: + // char unique_73; + // + // Note that all versions of MSVC++ up to at least version 7.1 + // fail to properly compile macros that use __LINE__ in them + // when the "program database for edit and continue" option + // is enabled. The result is that __LINE__ gets converted to + // something like __LINE__(Var+37). + // + #ifndef EA_PREPROCESSOR_JOIN + #define EA_PREPROCESSOR_JOIN(a, b) EA_PREPROCESSOR_JOIN1(a, b) + #define EA_PREPROCESSOR_JOIN1(a, b) EA_PREPROCESSOR_JOIN2(a, b) + #define EA_PREPROCESSOR_JOIN2(a, b) a##b + #endif + + + // ------------------------------------------------------------------------ + // EA_STRINGIFY + // + // Example usage: + // printf("Line: %s", EA_STRINGIFY(__LINE__)); + // + #ifndef EA_STRINGIFY + #define EA_STRINGIFY(x) EA_STRINGIFYIMPL(x) + #define EA_STRINGIFYIMPL(x) #x + #endif + + + // ------------------------------------------------------------------------ + // EA_IDENTITY + // + #ifndef EA_IDENTITY + #define EA_IDENTITY(x) x + #endif + + + // ------------------------------------------------------------------------ + // EA_COMPILER_MANAGED_CPP + // Defined if this is being compiled with Managed C++ extensions + #ifdef EA_COMPILER_MSVC + #if EA_COMPILER_VERSION >= 1300 + #ifdef _MANAGED + #define EA_COMPILER_MANAGED_CPP 1 + #endif + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_COMPILER_INTMAX_SIZE + // + // This is related to the concept of intmax_t uintmax_t, but is available + // in preprocessor form as opposed to compile-time form. At compile-time + // you can use intmax_t and uintmax_t to use the actual types. + // + #if defined(__GNUC__) && defined(__x86_64__) + #define EA_COMPILER_INTMAX_SIZE 16 // intmax_t is __int128_t (GCC extension) and is 16 bytes. + #else + #define EA_COMPILER_INTMAX_SIZE 8 // intmax_t is int64_t and is 8 bytes. + #endif + + + + // ------------------------------------------------------------------------ + // EA_LPAREN / EA_RPAREN / EA_COMMA / EA_SEMI + // + // These are used for using special characters in macro-using expressions. + // Note that this macro intentionally uses (), as in some cases it can't + // work unless it does. + // + // Example usage: + // int x = SOME_MACRO(SomeTemplate); + // + #ifndef EA_LPAREN + #define EA_LPAREN() ( + #endif + #ifndef EA_RPAREN + #define EA_RPAREN() ) + #endif + #ifndef EA_COMMA + #define EA_COMMA() , + #endif + #ifndef EA_SEMI + #define EA_SEMI() ; + #endif + + + + + // ------------------------------------------------------------------------ + // EA_OFFSETOF + // Implements a portable version of the non-standard offsetof macro. + // + // The offsetof macro is guaranteed to only work with POD types. However, we wish to use + // it for non-POD types but where we know that offsetof will still work for the cases + // in which we use it. GCC unilaterally gives a warning when using offsetof with a non-POD, + // even if the given usage happens to work. So we make a workaround version of offsetof + // here for GCC which has the same effect but tricks the compiler into not issuing the warning. + // The 65536 does the compiler fooling; the reinterpret_cast prevents the possibility of + // an overloaded operator& for the class getting in the way. + // + // Example usage: + // struct A{ int x; int y; }; + // size_t n = EA_OFFSETOF(A, y); + // + #if defined(__GNUC__) // We can't use GCC 4's __builtin_offsetof because it mistakenly complains about non-PODs that are really PODs. + #define EA_OFFSETOF(struct_, member_) ((size_t)(((uintptr_t)&reinterpret_cast((((struct_*)65536)->member_))) - 65536)) + #else + #define EA_OFFSETOF(struct_, member_) offsetof(struct_, member_) + #endif + + // ------------------------------------------------------------------------ + // EA_SIZEOF_MEMBER + // Implements a portable way to determine the size of a member. + // + // The EA_SIZEOF_MEMBER simply returns the size of a member within a class or struct; member + // access rules still apply. We offer two approaches depending on the compiler's support for non-static member + // initializers although most C++11 compilers support this. + // + // Example usage: + // struct A{ int x; int y; }; + // size_t n = EA_SIZEOF_MEMBER(A, y); + // + #ifndef EA_COMPILER_NO_EXTENDED_SIZEOF + #define EA_SIZEOF_MEMBER(struct_, member_) (sizeof(struct_::member_)) + #else + #define EA_SIZEOF_MEMBER(struct_, member_) (sizeof(((struct_*)0)->member_)) + #endif + + // ------------------------------------------------------------------------ + // alignment expressions + // + // Here we define + // EA_ALIGN_OF(type) // Returns size_t. + // EA_ALIGN_MAX_STATIC // The max align value that the compiler will respect for EA_ALIGN for static data (global and static variables). Some compilers allow high values, some allow no more than 8. EA_ALIGN_MIN is assumed to be 1. + // EA_ALIGN_MAX_AUTOMATIC // The max align value for automatic variables (variables declared as local to a function). + // EA_ALIGN(n) // Used as a prefix. n is byte alignment, with being a power of two. Most of the time you can use this and avoid using EA_PREFIX_ALIGN/EA_POSTFIX_ALIGN. + // EA_ALIGNED(t, v, n) // Type, variable, alignment. Used to align an instance. You should need this only for unusual compilers. + // EA_PACKED // Specifies that the given structure be packed (and not have its members aligned). + // + // Also we define the following for rare cases that it's needed. + // EA_PREFIX_ALIGN(n) // n is byte alignment, with being a power of two. You should need this only for unusual compilers. + // EA_POSTFIX_ALIGN(n) // Valid values for n are 1, 2, 4, 8, etc. You should need this only for unusual compilers. + // + // Example usage: + // size_t x = EA_ALIGN_OF(int); Non-aligned equivalents. Meaning + // EA_PREFIX_ALIGN(8) int x = 5; int x = 5; Align x on 8 for compilers that require prefix attributes. Can just use EA_ALIGN instead. + // EA_ALIGN(8) int x; int x; Align x on 8 for compilers that allow prefix attributes. + // int x EA_POSTFIX_ALIGN(8); int x; Align x on 8 for compilers that require postfix attributes. + // int x EA_POSTFIX_ALIGN(8) = 5; int x = 5; Align x on 8 for compilers that require postfix attributes. + // int x EA_POSTFIX_ALIGN(8)(5); int x(5); Align x on 8 for compilers that require postfix attributes. + // struct EA_PREFIX_ALIGN(8) X { int x; } EA_POSTFIX_ALIGN(8); struct X { int x; }; Define X as a struct which is aligned on 8 when used. + // EA_ALIGNED(int, x, 8) = 5; int x = 5; Align x on 8. + // EA_ALIGNED(int, x, 16)(5); int x(5); Align x on 16. + // EA_ALIGNED(int, x[3], 16); int x[3]; Align x array on 16. + // EA_ALIGNED(int, x[3], 16) = { 1, 2, 3 }; int x[3] = { 1, 2, 3 }; Align x array on 16. + // int x[3] EA_PACKED; int x[3]; Pack the 3 ints of the x array. GCC doesn't seem to support packing of int arrays. + // struct EA_ALIGN(32) X { int x; int y; }; struct X { int x; }; Define A as a struct which is aligned on 32 when used. + // EA_ALIGN(32) struct X { int x; int y; } Z; struct X { int x; } Z; Define A as a struct, and align the instance Z on 32. + // struct X { int x EA_PACKED; int y EA_PACKED; }; struct X { int x; int y; }; Pack the x and y members of struct X. + // struct X { int x; int y; } EA_PACKED; struct X { int x; int y; }; Pack the members of struct X. + // typedef EA_ALIGNED(int, int16, 16); int16 n16; typedef int int16; int16 n16; Define int16 as an int which is aligned on 16. + // typedef EA_ALIGNED(X, X16, 16); X16 x16; typedef X X16; X16 x16; Define X16 as an X which is aligned on 16. + + #if !defined(EA_ALIGN_MAX) // If the user hasn't globally set an alternative value... + #if defined(EA_PROCESSOR_ARM) // ARM compilers in general tend to limit automatic variables to 8 or less. + #define EA_ALIGN_MAX_STATIC 1048576 + #define EA_ALIGN_MAX_AUTOMATIC 1 // Typically they support only built-in natural aligment types (both arm-eabi and apple-abi). + #elif defined(EA_PLATFORM_APPLE) + #define EA_ALIGN_MAX_STATIC 1048576 + #define EA_ALIGN_MAX_AUTOMATIC 16 + #else + #define EA_ALIGN_MAX_STATIC 1048576 // Arbitrarily high value. What is the actual max? + #define EA_ALIGN_MAX_AUTOMATIC 1048576 + #endif + #endif + + // EDG intends to be compatible with GCC but has a bug whereby it + // fails to support calling a constructor in an aligned declaration when + // using postfix alignment attributes. Prefix works for alignment, but does not align + // the size like postfix does. Prefix also fails on templates. So gcc style post fix + // is still used, but the user will need to use EA_POSTFIX_ALIGN before the constructor parameters. + #if defined(__GNUC__) && (__GNUC__ < 3) + #define EA_ALIGN_OF(type) ((size_t)__alignof__(type)) + #define EA_ALIGN(n) + #define EA_PREFIX_ALIGN(n) + #define EA_POSTFIX_ALIGN(n) __attribute__((aligned(n))) + #define EA_ALIGNED(variable_type, variable, n) variable_type variable __attribute__((aligned(n))) + #define EA_PACKED __attribute__((packed)) + + // GCC 3.x+, IBM, and clang support prefix attributes. + #elif (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__xlC__) || defined(__clang__) + #define EA_ALIGN_OF(type) ((size_t)__alignof__(type)) + #define EA_ALIGN(n) __attribute__((aligned(n))) + #define EA_PREFIX_ALIGN(n) + #define EA_POSTFIX_ALIGN(n) __attribute__((aligned(n))) + #define EA_ALIGNED(variable_type, variable, n) variable_type variable __attribute__((aligned(n))) + #define EA_PACKED __attribute__((packed)) + + // Metrowerks supports prefix attributes. + // Metrowerks does not support packed alignment attributes. + #elif defined(EA_COMPILER_INTEL) || defined(EA_PLATFORM_XBOX) || (defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1300)) + #define EA_ALIGN_OF(type) ((size_t)__alignof(type)) + #define EA_ALIGN(n) __declspec(align(n)) + #define EA_PREFIX_ALIGN(n) EA_ALIGN(n) + #define EA_POSTFIX_ALIGN(n) + #define EA_ALIGNED(variable_type, variable, n) EA_ALIGN(n) variable_type variable + #define EA_PACKED // See EA_PRAGMA_PACK_VC for an alternative. + + // Arm brand compiler + #elif defined(EA_COMPILER_ARM) + #define EA_ALIGN_OF(type) ((size_t)__ALIGNOF__(type)) + #define EA_ALIGN(n) __align(n) + #define EA_PREFIX_ALIGN(n) __align(n) + #define EA_POSTFIX_ALIGN(n) + #define EA_ALIGNED(variable_type, variable, n) __align(n) variable_type variable + #define EA_PACKED __packed + + #else // Unusual compilers + // There is nothing we can do about some of these. This is not as bad a problem as it seems. + // If the given platform/compiler doesn't support alignment specifications, then it's somewhat + // likely that alignment doesn't matter for that platform. Otherwise they would have defined + // functionality to manipulate alignment. + #define EA_ALIGN(n) + #define EA_PREFIX_ALIGN(n) + #define EA_POSTFIX_ALIGN(n) + #define EA_ALIGNED(variable_type, variable, n) variable_type variable + #define EA_PACKED + + #ifdef __cplusplus + template struct EAAlignOf1 { enum { s = sizeof (T), value = s ^ (s & (s - 1)) }; }; + template struct EAAlignOf2; + template struct helper { template struct Val { enum { value = size_diff }; }; }; + template <> struct helper<0> { template struct Val { enum { value = EAAlignOf2::value }; }; }; + template struct EAAlignOf2 { struct Big { T x; char c; }; + enum { diff = sizeof (Big) - sizeof (T), value = helper::template Val::value }; }; + template struct EAAlignof3 { enum { x = EAAlignOf2::value, y = EAAlignOf1::value, value = x < y ? x : y }; }; + #define EA_ALIGN_OF(type) ((size_t)EAAlignof3::value) + + #else + // C implementation of EA_ALIGN_OF + // This implementation works for most cases, but doesn't directly work + // for types such as function pointer declarations. To work with those + // types you need to typedef the type and then use the typedef in EA_ALIGN_OF. + #define EA_ALIGN_OF(type) ((size_t)offsetof(struct { char c; type m; }, m)) + #endif + #endif + + // EA_PRAGMA_PACK_VC + // + // Wraps #pragma pack in a way that allows for cleaner code. + // + // Example usage: + // EA_PRAGMA_PACK_VC(push, 1) + // struct X{ char c; int i; }; + // EA_PRAGMA_PACK_VC(pop) + // + #if !defined(EA_PRAGMA_PACK_VC) + #if defined(EA_COMPILER_MSVC) + #define EA_PRAGMA_PACK_VC(...) __pragma(pack(__VA_ARGS__)) + #elif !defined(EA_COMPILER_NO_VARIADIC_MACROS) + #define EA_PRAGMA_PACK_VC(...) + #else + // No support. However, all compilers of significance to us support variadic macros. + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_LIKELY / EA_UNLIKELY + // + // Defined as a macro which gives a hint to the compiler for branch + // prediction. GCC gives you the ability to manually give a hint to + // the compiler about the result of a comparison, though it's often + // best to compile shipping code with profiling feedback under both + // GCC (-fprofile-arcs) and VC++ (/LTCG:PGO, etc.). However, there + // are times when you feel very sure that a boolean expression will + // usually evaluate to either true or false and can help the compiler + // by using an explicity directive... + // + // Example usage: + // if(EA_LIKELY(a == 0)) // Tell the compiler that a will usually equal 0. + // { ... } + // + // Example usage: + // if(EA_UNLIKELY(a == 0)) // Tell the compiler that a will usually not equal 0. + // { ... } + // + #ifndef EA_LIKELY + #if (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) + #if defined(__cplusplus) + #define EA_LIKELY(x) __builtin_expect(!!(x), true) + #define EA_UNLIKELY(x) __builtin_expect(!!(x), false) + #else + #define EA_LIKELY(x) __builtin_expect(!!(x), 1) + #define EA_UNLIKELY(x) __builtin_expect(!!(x), 0) + #endif + #else + #define EA_LIKELY(x) (x) + #define EA_UNLIKELY(x) (x) + #endif + #endif + + // ------------------------------------------------------------------------ + // EA_HAS_INCLUDE_AVAILABLE + // + // Used to guard against the EA_HAS_INCLUDE() macro on compilers that do not + // support said feature. + // + // Example usage: + // + // #if EA_HAS_INCLUDE_AVAILABLE + // #if EA_HAS_INCLUDE("myinclude.h") + // #include "myinclude.h" + // #endif + // #endif + #if !defined(EA_HAS_INCLUDE_AVAILABLE) + #if defined(EA_COMPILER_CPP17_ENABLED) || defined(EA_COMPILER_CLANG) || defined(EA_COMPILER_GNUC) + #define EA_HAS_INCLUDE_AVAILABLE 1 + #else + #define EA_HAS_INCLUDE_AVAILABLE 0 + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_HAS_INCLUDE + // + // May be used in #if and #elif expressions to test for the existence + // of the header referenced in the operand. If possible it evaluates to a + // non-zero value and zero otherwise. The operand is the same form as the file + // in a #include directive. + // + // Example usage: + // + // #if EA_HAS_INCLUDE("myinclude.h") + // #include "myinclude.h" + // #endif + // + // #if EA_HAS_INCLUDE() + // #include + // #endif + + #if !defined(EA_HAS_INCLUDE) + #if defined(EA_COMPILER_CPP17_ENABLED) + #define EA_HAS_INCLUDE(x) __has_include(x) + #elif defined(EA_COMPILER_CLANG) + #define EA_HAS_INCLUDE(x) __has_include(x) + #elif defined(EA_COMPILER_GNUC) + #define EA_HAS_INCLUDE(x) __has_include(x) + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_INIT_PRIORITY_AVAILABLE + // + // This value is either not defined, or defined to 1. + // Defines if the GCC attribute init_priority is supported by the compiler. + // + #if !defined(EA_INIT_PRIORITY_AVAILABLE) + #if defined(__GNUC__) && !defined(__EDG__) // EDG typically #defines __GNUC__ but doesn't implement init_priority. + #define EA_INIT_PRIORITY_AVAILABLE 1 + #elif defined(__clang__) + #define EA_INIT_PRIORITY_AVAILABLE 1 // Clang implements init_priority + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_INIT_PRIORITY + // + // This is simply a wrapper for the GCC init_priority attribute that allows + // multiplatform code to be easier to read. This attribute doesn't apply + // to VC++ because VC++ uses file-level pragmas to control init ordering. + // + // Example usage: + // SomeClass gSomeClass EA_INIT_PRIORITY(2000); + // + #if !defined(EA_INIT_PRIORITY) + #if defined(EA_INIT_PRIORITY_AVAILABLE) + #define EA_INIT_PRIORITY(x) __attribute__ ((init_priority (x))) + #else + #define EA_INIT_PRIORITY(x) + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_INIT_SEG_AVAILABLE + // + // + #if !defined(EA_INIT_SEG_AVAILABLE) + #if defined(_MSC_VER) + #define EA_INIT_SEG_AVAILABLE 1 + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_INIT_SEG + // + // Specifies a keyword or code section that affects the order in which startup code is executed. + // + // https://docs.microsoft.com/en-us/cpp/preprocessor/init-seg?view=vs-2019 + // + // Example: + // EA_INIT_SEG(compiler) MyType gMyTypeGlobal; + // EA_INIT_SEG("my_section") MyOtherType gMyOtherTypeGlobal; + // + #if !defined(EA_INIT_SEG) + #if defined(EA_INIT_SEG_AVAILABLE) + #define EA_INIT_SEG(x) \ + __pragma(warning(push)) __pragma(warning(disable : 4074)) __pragma(warning(disable : 4075)) __pragma(init_seg(x)) \ + __pragma(warning(pop)) + #else + #define EA_INIT_SEG(x) + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_MAY_ALIAS_AVAILABLE + // + // Defined as 0, 1, or 2. + // Defines if the GCC attribute may_alias is supported by the compiler. + // Consists of a value 0 (unsupported, shouldn't be used), 1 (some support), + // or 2 (full proper support). + // + #ifndef EA_MAY_ALIAS_AVAILABLE + #if defined(__GNUC__) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 303) + #if !defined(__EDG__) // define it as 1 while defining GCC's support as 2. + #define EA_MAY_ALIAS_AVAILABLE 2 + #else + #define EA_MAY_ALIAS_AVAILABLE 0 + #endif + #else + #define EA_MAY_ALIAS_AVAILABLE 0 + #endif + #endif + + + // EA_MAY_ALIAS + // + // Defined as a macro that wraps the GCC may_alias attribute. This attribute + // has no significance for VC++ because VC++ doesn't support the concept of + // strict aliasing. Users should avoid writing code that breaks strict + // aliasing rules; EA_MAY_ALIAS is for cases with no alternative. + // + // Example usage: + // void* EA_MAY_ALIAS gPtr = NULL; + // + // Example usage: + // typedef void* EA_MAY_ALIAS pvoid_may_alias; + // pvoid_may_alias gPtr = NULL; + // + #if EA_MAY_ALIAS_AVAILABLE + #define EA_MAY_ALIAS __attribute__((__may_alias__)) + #else + #define EA_MAY_ALIAS + #endif + + + // ------------------------------------------------------------------------ + // EA_ASSUME + // + // This acts the same as the VC++ __assume directive and is implemented + // simply as a wrapper around it to allow portable usage of it and to take + // advantage of it if and when it appears in other compilers. + // + // Example usage: + // void Function(int a) { + // switch(a) { + // case 1: + // DoSomething(1); + // break; + // case 2: + // DoSomething(-1); + // break; + // default: + // EA_ASSUME(0); // This tells the optimizer that the default cannot be reached. + // } + // } + // + #ifndef EA_ASSUME + #if defined(_MSC_VER) && (_MSC_VER >= 1300) // If VC7.0 and later + #define EA_ASSUME(x) __assume(x) + #else + #define EA_ASSUME(x) + #endif + #endif + + + + // ------------------------------------------------------------------------ + // EA_ANALYSIS_ASSUME + // + // This acts the same as the VC++ __analysis_assume directive and is implemented + // simply as a wrapper around it to allow portable usage of it and to take + // advantage of it if and when it appears in other compilers. + // + // Example usage: + // char Function(char* p) { + // EA_ANALYSIS_ASSUME(p != NULL); + // return *p; + // } + // + #ifndef EA_ANALYSIS_ASSUME + #if defined(_MSC_VER) && (_MSC_VER >= 1300) // If VC7.0 and later + #define EA_ANALYSIS_ASSUME(x) __analysis_assume(!!(x)) // !! because that allows for convertible-to-bool in addition to bool. + #else + #define EA_ANALYSIS_ASSUME(x) + #endif + #endif + + + + // ------------------------------------------------------------------------ + // EA_DISABLE_VC_WARNING / EA_RESTORE_VC_WARNING + // + // Disable and re-enable warning(s) within code. + // This is simply a wrapper for VC++ #pragma warning(disable: nnnn) for the + // purpose of making code easier to read due to avoiding nested compiler ifdefs + // directly in code. + // + // Example usage: + // EA_DISABLE_VC_WARNING(4127 3244) + // + // EA_RESTORE_VC_WARNING() + // + #ifndef EA_DISABLE_VC_WARNING + #if defined(_MSC_VER) + #define EA_DISABLE_VC_WARNING(w) \ + __pragma(warning(push)) \ + __pragma(warning(disable:w)) + #else + #define EA_DISABLE_VC_WARNING(w) + #endif + #endif + + #ifndef EA_RESTORE_VC_WARNING + #if defined(_MSC_VER) + #define EA_RESTORE_VC_WARNING() \ + __pragma(warning(pop)) + #else + #define EA_RESTORE_VC_WARNING() + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_ENABLE_VC_WARNING_AS_ERROR / EA_DISABLE_VC_WARNING_AS_ERROR + // + // Disable and re-enable treating a warning as error within code. + // This is simply a wrapper for VC++ #pragma warning(error: nnnn) for the + // purpose of making code easier to read due to avoiding nested compiler ifdefs + // directly in code. + // + // Example usage: + // EA_ENABLE_VC_WARNING_AS_ERROR(4996) + // + // EA_DISABLE_VC_WARNING_AS_ERROR() + // + #ifndef EA_ENABLE_VC_WARNING_AS_ERROR + #if defined(_MSC_VER) + #define EA_ENABLE_VC_WARNING_AS_ERROR(w) \ + __pragma(warning(push)) \ + __pragma(warning(error:w)) + #else + #define EA_ENABLE_VC_WARNING_AS_ERROR(w) + #endif + #endif + + #ifndef EA_DISABLE_VC_WARNING_AS_ERROR + #if defined(_MSC_VER) + #define EA_DISABLE_VC_WARNING_AS_ERROR() \ + __pragma(warning(pop)) + #else + #define EA_DISABLE_VC_WARNING_AS_ERROR() + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_DISABLE_GCC_WARNING / EA_RESTORE_GCC_WARNING + // + // Example usage: + // // Only one warning can be ignored per statement, due to how GCC works. + // EA_DISABLE_GCC_WARNING(-Wuninitialized) + // EA_DISABLE_GCC_WARNING(-Wunused) + // + // EA_RESTORE_GCC_WARNING() + // EA_RESTORE_GCC_WARNING() + // + #ifndef EA_DISABLE_GCC_WARNING + #if defined(EA_COMPILER_GNUC) + #define EAGCCWHELP0(x) #x + #define EAGCCWHELP1(x) EAGCCWHELP0(GCC diagnostic ignored x) + #define EAGCCWHELP2(x) EAGCCWHELP1(#x) + #endif + + #if defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4006) // Can't test directly for __GNUC__ because some compilers lie. + #define EA_DISABLE_GCC_WARNING(w) \ + _Pragma("GCC diagnostic push") \ + _Pragma(EAGCCWHELP2(w)) + #elif defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4004) + #define EA_DISABLE_GCC_WARNING(w) \ + _Pragma(EAGCCWHELP2(w)) + #else + #define EA_DISABLE_GCC_WARNING(w) + #endif + #endif + + #ifndef EA_RESTORE_GCC_WARNING + #if defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4006) + #define EA_RESTORE_GCC_WARNING() \ + _Pragma("GCC diagnostic pop") + #else + #define EA_RESTORE_GCC_WARNING() + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_DISABLE_ALL_GCC_WARNINGS / EA_RESTORE_ALL_GCC_WARNINGS + // + // This isn't possible except via using _Pragma("GCC system_header"), though + // that has some limitations in how it works. Another means is to manually + // disable individual warnings within a GCC diagnostic push statement. + // GCC doesn't have as many warnings as VC++ and EDG and so this may be feasible. + // ------------------------------------------------------------------------ + + + // ------------------------------------------------------------------------ + // EA_ENABLE_GCC_WARNING_AS_ERROR / EA_DISABLE_GCC_WARNING_AS_ERROR + // + // Example usage: + // // Only one warning can be treated as an error per statement, due to how GCC works. + // EA_ENABLE_GCC_WARNING_AS_ERROR(-Wuninitialized) + // EA_ENABLE_GCC_WARNING_AS_ERROR(-Wunused) + // + // EA_DISABLE_GCC_WARNING_AS_ERROR() + // EA_DISABLE_GCC_WARNING_AS_ERROR() + // + #ifndef EA_ENABLE_GCC_WARNING_AS_ERROR + #if defined(EA_COMPILER_GNUC) + #define EAGCCWERRORHELP0(x) #x + #define EAGCCWERRORHELP1(x) EAGCCWERRORHELP0(GCC diagnostic error x) + #define EAGCCWERRORHELP2(x) EAGCCWERRORHELP1(#x) + #endif + + #if defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4006) // Can't test directly for __GNUC__ because some compilers lie. + #define EA_ENABLE_GCC_WARNING_AS_ERROR(w) \ + _Pragma("GCC diagnostic push") \ + _Pragma(EAGCCWERRORHELP2(w)) + #elif defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4004) + #define EA_DISABLE_GCC_WARNING(w) \ + _Pragma(EAGCCWERRORHELP2(w)) + #else + #define EA_DISABLE_GCC_WARNING(w) + #endif + #endif + + #ifndef EA_DISABLE_GCC_WARNING_AS_ERROR + #if defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4006) + #define EA_DISABLE_GCC_WARNING_AS_ERROR() \ + _Pragma("GCC diagnostic pop") + #else + #define EA_DISABLE_GCC_WARNING_AS_ERROR() + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_DISABLE_CLANG_WARNING / EA_RESTORE_CLANG_WARNING + // + // Example usage: + // // Only one warning can be ignored per statement, due to how clang works. + // EA_DISABLE_CLANG_WARNING(-Wuninitialized) + // EA_DISABLE_CLANG_WARNING(-Wunused) + // + // EA_RESTORE_CLANG_WARNING() + // EA_RESTORE_CLANG_WARNING() + // + #ifndef EA_DISABLE_CLANG_WARNING + #if defined(EA_COMPILER_CLANG) || defined(EA_COMPILER_CLANG_CL) + #define EACLANGWHELP0(x) #x + #define EACLANGWHELP1(x) EACLANGWHELP0(clang diagnostic ignored x) + #define EACLANGWHELP2(x) EACLANGWHELP1(#x) + + #define EA_DISABLE_CLANG_WARNING(w) \ + _Pragma("clang diagnostic push") \ + _Pragma(EACLANGWHELP2(-Wunknown-warning-option))\ + _Pragma(EACLANGWHELP2(w)) + #else + #define EA_DISABLE_CLANG_WARNING(w) + #endif + #endif + + #ifndef EA_RESTORE_CLANG_WARNING + #if defined(EA_COMPILER_CLANG) || defined(EA_COMPILER_CLANG_CL) + #define EA_RESTORE_CLANG_WARNING() \ + _Pragma("clang diagnostic pop") + #else + #define EA_RESTORE_CLANG_WARNING() + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_DISABLE_ALL_CLANG_WARNINGS / EA_RESTORE_ALL_CLANG_WARNINGS + // + // The situation for clang is the same as for GCC. See above. + // ------------------------------------------------------------------------ + + + // ------------------------------------------------------------------------ + // EA_ENABLE_CLANG_WARNING_AS_ERROR / EA_DISABLE_CLANG_WARNING_AS_ERROR + // + // Example usage: + // // Only one warning can be treated as an error per statement, due to how clang works. + // EA_ENABLE_CLANG_WARNING_AS_ERROR(-Wuninitialized) + // EA_ENABLE_CLANG_WARNING_AS_ERROR(-Wunused) + // + // EA_DISABLE_CLANG_WARNING_AS_ERROR() + // EA_DISABLE_CLANG_WARNING_AS_ERROR() + // + #ifndef EA_ENABLE_CLANG_WARNING_AS_ERROR + #if defined(EA_COMPILER_CLANG) || defined(EA_COMPILER_CLANG_CL) + #define EACLANGWERRORHELP0(x) #x + #define EACLANGWERRORHELP1(x) EACLANGWERRORHELP0(clang diagnostic error x) + #define EACLANGWERRORHELP2(x) EACLANGWERRORHELP1(#x) + + #define EA_ENABLE_CLANG_WARNING_AS_ERROR(w) \ + _Pragma("clang diagnostic push") \ + _Pragma(EACLANGWERRORHELP2(w)) + #else + #define EA_DISABLE_CLANG_WARNING(w) + #endif + #endif + + #ifndef EA_DISABLE_CLANG_WARNING_AS_ERROR + #if defined(EA_COMPILER_CLANG) || defined(EA_COMPILER_CLANG_CL) + #define EA_DISABLE_CLANG_WARNING_AS_ERROR() \ + _Pragma("clang diagnostic pop") + #else + #define EA_DISABLE_CLANG_WARNING_AS_ERROR() + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_DISABLE_SN_WARNING / EA_RESTORE_SN_WARNING + // + // Note that we define this macro specifically for the SN compiler instead of + // having a generic one for EDG-based compilers. The reason for this is that + // while SN is indeed based on EDG, SN has different warning value mappings + // and thus warning 1234 for SN is not the same as 1234 for all other EDG compilers. + // + // Example usage: + // // Currently we are limited to one warning per line. + // EA_DISABLE_SN_WARNING(1787) + // EA_DISABLE_SN_WARNING(552) + // + // EA_RESTORE_SN_WARNING() + // EA_RESTORE_SN_WARNING() + // + #ifndef EA_DISABLE_SN_WARNING + #if defined(EA_COMPILER_SN) + #define EASNWHELP0(x) #x + #define EASNWHELP1(x) EASNWHELP0(diag_suppress x) + + #define EA_DISABLE_SN_WARNING(w) \ + _Pragma("control %push diag") \ + _Pragma(EASNWHELP1(w)) + #else + #define EA_DISABLE_SN_WARNING(w) + #endif + #endif + + #ifndef EA_RESTORE_SN_WARNING + #if defined(EA_COMPILER_SN) + #define EA_RESTORE_SN_WARNING() \ + _Pragma("control %pop diag") + #else + #define EA_RESTORE_SN_WARNING() + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_DISABLE_ALL_SN_WARNINGS / EA_RESTORE_ALL_SN_WARNINGS + // + // Example usage: + // EA_DISABLE_ALL_SN_WARNINGS() + // + // EA_RESTORE_ALL_SN_WARNINGS() + // + #ifndef EA_DISABLE_ALL_SN_WARNINGS + #if defined(EA_COMPILER_SN) + #define EA_DISABLE_ALL_SN_WARNINGS() \ + _Pragma("control %push diag") \ + _Pragma("control diag=0") + #else + #define EA_DISABLE_ALL_SN_WARNINGS() + #endif + #endif + + #ifndef EA_RESTORE_ALL_SN_WARNINGS + #if defined(EA_COMPILER_SN) + #define EA_RESTORE_ALL_SN_WARNINGS() \ + _Pragma("control %pop diag") + #else + #define EA_RESTORE_ALL_SN_WARNINGS() + #endif + #endif + + + + // ------------------------------------------------------------------------ + // EA_DISABLE_GHS_WARNING / EA_RESTORE_GHS_WARNING + // + // Disable warnings from the Green Hills compiler. + // + // Example usage: + // EA_DISABLE_GHS_WARNING(193) + // EA_DISABLE_GHS_WARNING(236, 5323) + // + // EA_RESTORE_GHS_WARNING() + // EA_RESTORE_GHS_WARNING() + // + #ifndef EA_DISABLE_GHS_WARNING + #if defined(EA_COMPILER_GREEN_HILLS) + #define EAGHSHELP0(x) #x + #define EAGHSHELP1(x) EAGHSHELP0(ghs nowarning x) + + #define EA_DISABLE_GHS_WARNING(w) \ + _Pragma(EAGHSHELP1(w)) + #else + #define EA_DISABLE_GHS_WARNING(w) + #endif + #endif + + #ifndef EA_RESTORE_GHS_WARNING + #if defined(EA_COMPILER_GREEN_HILLS) + #define EA_RESTORE_GHS_WARNING() \ + _Pragma("ghs endnowarning") + #else + #define EA_RESTORE_GHS_WARNING() + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_DISABLE_ALL_GHS_WARNINGS / EA_RESTORE_ALL_GHS_WARNINGS + // + // #ifndef EA_DISABLE_ALL_GHS_WARNINGS + // #if defined(EA_COMPILER_GREEN_HILLS) + // #define EA_DISABLE_ALL_GHS_WARNINGS(w) \_ + // _Pragma("_________") + // #else + // #define EA_DISABLE_ALL_GHS_WARNINGS(w) + // #endif + // #endif + // + // #ifndef EA_RESTORE_ALL_GHS_WARNINGS + // #if defined(EA_COMPILER_GREEN_HILLS) + // #define EA_RESTORE_ALL_GHS_WARNINGS() \_ + // _Pragma("_________") + // #else + // #define EA_RESTORE_ALL_GHS_WARNINGS() + // #endif + // #endif + + + + // ------------------------------------------------------------------------ + // EA_DISABLE_EDG_WARNING / EA_RESTORE_EDG_WARNING + // + // Example usage: + // // Currently we are limited to one warning per line. + // EA_DISABLE_EDG_WARNING(193) + // EA_DISABLE_EDG_WARNING(236) + // + // EA_RESTORE_EDG_WARNING() + // EA_RESTORE_EDG_WARNING() + // + #ifndef EA_DISABLE_EDG_WARNING + // EDG-based compilers are inconsistent in how the implement warning pragmas. + #if defined(EA_COMPILER_EDG) && !defined(EA_COMPILER_INTEL) && !defined(EA_COMPILER_RVCT) + #define EAEDGWHELP0(x) #x + #define EAEDGWHELP1(x) EAEDGWHELP0(diag_suppress x) + + #define EA_DISABLE_EDG_WARNING(w) \ + _Pragma("control %push diag") \ + _Pragma(EAEDGWHELP1(w)) + #else + #define EA_DISABLE_EDG_WARNING(w) + #endif + #endif + + #ifndef EA_RESTORE_EDG_WARNING + #if defined(EA_COMPILER_EDG) && !defined(EA_COMPILER_INTEL) && !defined(EA_COMPILER_RVCT) + #define EA_RESTORE_EDG_WARNING() \ + _Pragma("control %pop diag") + #else + #define EA_RESTORE_EDG_WARNING() + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_DISABLE_ALL_EDG_WARNINGS / EA_RESTORE_ALL_EDG_WARNINGS + // + //#ifndef EA_DISABLE_ALL_EDG_WARNINGS + // #if defined(EA_COMPILER_EDG) && !defined(EA_COMPILER_SN) + // #define EA_DISABLE_ALL_EDG_WARNINGS(w) \_ + // _Pragma("_________") + // #else + // #define EA_DISABLE_ALL_EDG_WARNINGS(w) + // #endif + //#endif + // + //#ifndef EA_RESTORE_ALL_EDG_WARNINGS + // #if defined(EA_COMPILER_EDG) && !defined(EA_COMPILER_SN) + // #define EA_RESTORE_ALL_EDG_WARNINGS() \_ + // _Pragma("_________") + // #else + // #define EA_RESTORE_ALL_EDG_WARNINGS() + // #endif + //#endif + + + + // ------------------------------------------------------------------------ + // EA_DISABLE_CW_WARNING / EA_RESTORE_CW_WARNING + // + // Note that this macro can only control warnings via numbers and not by + // names. The reason for this is that the compiler's syntax for such + // warnings is not the same as for numbers. + // + // Example usage: + // // Currently we are limited to one warning per line and must also specify the warning in the restore macro. + // EA_DISABLE_CW_WARNING(10317) + // EA_DISABLE_CW_WARNING(10324) + // + // EA_RESTORE_CW_WARNING(10317) + // EA_RESTORE_CW_WARNING(10324) + // + #ifndef EA_DISABLE_CW_WARNING + #define EA_DISABLE_CW_WARNING(w) + #endif + + #ifndef EA_RESTORE_CW_WARNING + + #define EA_RESTORE_CW_WARNING(w) + + #endif + + + // ------------------------------------------------------------------------ + // EA_DISABLE_ALL_CW_WARNINGS / EA_RESTORE_ALL_CW_WARNINGS + // + #ifndef EA_DISABLE_ALL_CW_WARNINGS + #define EA_DISABLE_ALL_CW_WARNINGS() + + #endif + + #ifndef EA_RESTORE_ALL_CW_WARNINGS + #define EA_RESTORE_ALL_CW_WARNINGS() + #endif + + + + // ------------------------------------------------------------------------ + // EA_PURE + // + // This acts the same as the GCC __attribute__ ((pure)) directive and is + // implemented simply as a wrapper around it to allow portable usage of + // it and to take advantage of it if and when it appears in other compilers. + // + // A "pure" function is one that has no effects except its return value and + // its return value is a function of only the function's parameters or + // non-volatile global variables. Any parameter or global variable access + // must be read-only. Loop optimization and subexpression elimination can be + // applied to such functions. A common example is strlen(): Given identical + // inputs, the function's return value (its only effect) is invariant across + // multiple invocations and thus can be pulled out of a loop and called but once. + // + // Example usage: + // EA_PURE void Function(); + // + #ifndef EA_PURE + #if defined(EA_COMPILER_GNUC) + #define EA_PURE __attribute__((pure)) + #elif defined(EA_COMPILER_ARM) // Arm brand compiler for ARM CPU + #define EA_PURE __pure + #else + #define EA_PURE + #endif + #endif + + + + // ------------------------------------------------------------------------ + // EA_WEAK + // EA_WEAK_SUPPORTED -- defined as 0 or 1. + // + // GCC + // The weak attribute causes the declaration to be emitted as a weak + // symbol rather than a global. This is primarily useful in defining + // library functions which can be overridden in user code, though it + // can also be used with non-function declarations. + // + // VC++ + // At link time, if multiple definitions of a COMDAT are seen, the linker + // picks one and discards the rest. If the linker option /OPT:REF + // is selected, then COMDAT elimination will occur to remove all the + // unreferenced data items in the linker output. + // + // Example usage: + // EA_WEAK void Function(); + // + #ifndef EA_WEAK + #if defined(_MSC_VER) && (_MSC_VER >= 1300) // If VC7.0 and later + #define EA_WEAK __declspec(selectany) + #define EA_WEAK_SUPPORTED 1 + #elif defined(_MSC_VER) || (defined(__GNUC__) && defined(__CYGWIN__)) + #define EA_WEAK + #define EA_WEAK_SUPPORTED 0 + #elif defined(EA_COMPILER_ARM) // Arm brand compiler for ARM CPU + #define EA_WEAK __weak + #define EA_WEAK_SUPPORTED 1 + #else // GCC and IBM compilers, others. + #define EA_WEAK __attribute__((weak)) + #define EA_WEAK_SUPPORTED 1 + #endif + #endif + + + + // ------------------------------------------------------------------------ + // EA_UNUSED + // + // Makes compiler warnings about unused variables go away. + // + // Example usage: + // void Function(int x) + // { + // int y; + // EA_UNUSED(x); + // EA_UNUSED(y); + // } + // + #ifndef EA_UNUSED + // The EDG solution below is pretty weak and needs to be augmented or replaced. + // It can't handle the C language, is limited to places where template declarations + // can be used, and requires the type x to be usable as a functions reference argument. + #if defined(__cplusplus) && defined(__EDG__) + template + inline void EABaseUnused(T const volatile & x) { (void)x; } + #define EA_UNUSED(x) EABaseUnused(x) + #else + #define EA_UNUSED(x) (void)x + #endif + #endif + + + + // ------------------------------------------------------------------------ + // EA_EMPTY + // + // Allows for a null statement, usually for the purpose of avoiding compiler warnings. + // + // Example usage: + // #ifdef EA_DEBUG + // #define MyDebugPrintf(x, y) printf(x, y) + // #else + // #define MyDebugPrintf(x, y) EA_EMPTY + // #endif + // + #ifndef EA_EMPTY + #define EA_EMPTY (void)0 + #endif + + + // ------------------------------------------------------------------------ + // EA_CURRENT_FUNCTION + // + // Provides a consistent way to get the current function name as a macro + // like the __FILE__ and __LINE__ macros work. The C99 standard specifies + // that __func__ be provided by the compiler, but most compilers don't yet + // follow that convention. However, many compilers have an alternative. + // + // We also define EA_CURRENT_FUNCTION_SUPPORTED for when it is not possible + // to have EA_CURRENT_FUNCTION work as expected. + // + // Defined inside a function because otherwise the macro might not be + // defined and code below might not compile. This happens with some + // compilers. + // + #ifndef EA_CURRENT_FUNCTION + #if defined __GNUC__ || (defined __ICC && __ICC >= 600) + #define EA_CURRENT_FUNCTION __PRETTY_FUNCTION__ + #elif defined(__FUNCSIG__) + #define EA_CURRENT_FUNCTION __FUNCSIG__ + #elif (defined __INTEL_COMPILER && __INTEL_COMPILER >= 600) || (defined __IBMCPP__ && __IBMCPP__ >= 500) || (defined __CWCC__ && __CWCC__ >= 0x4200) + #define EA_CURRENT_FUNCTION __FUNCTION__ + #elif defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901 + #define EA_CURRENT_FUNCTION __func__ + #else + #define EA_CURRENT_FUNCTION "(unknown function)" + #endif + #endif + + + // ------------------------------------------------------------------------ + // wchar_t + // Here we define: + // EA_WCHAR_T_NON_NATIVE + // EA_WCHAR_SIZE = + // + #ifndef EA_WCHAR_T_NON_NATIVE + // Compilers that always implement wchar_t as native include: + // COMEAU, new SN, and other EDG-based compilers. + // GCC + // Borland + // SunPro + // IBM Visual Age + #if defined(EA_COMPILER_INTEL) + #if (EA_COMPILER_VERSION < 700) + #define EA_WCHAR_T_NON_NATIVE 1 + #else + #if (!defined(_WCHAR_T_DEFINED) && !defined(_WCHAR_T)) + #define EA_WCHAR_T_NON_NATIVE 1 + #endif + #endif + #elif defined(EA_COMPILER_MSVC) || (defined(EA_COMPILER_CLANG) && defined(EA_PLATFORM_WINDOWS)) + #ifndef _NATIVE_WCHAR_T_DEFINED + #define EA_WCHAR_T_NON_NATIVE 1 + #endif + #elif defined(__EDG_VERSION__) && (!defined(_WCHAR_T) && (__EDG_VERSION__ < 400)) // EDG prior to v4 uses _WCHAR_T to indicate if wchar_t is native. v4+ may define something else, but we're not currently aware of it. + #define EA_WCHAR_T_NON_NATIVE 1 + #endif + #endif + + #ifndef EA_WCHAR_SIZE // If the user hasn't specified that it is a given size... + #if defined(__WCHAR_MAX__) // GCC defines this for most platforms. + #if (__WCHAR_MAX__ == 2147483647) || (__WCHAR_MAX__ == 4294967295) + #define EA_WCHAR_SIZE 4 + #elif (__WCHAR_MAX__ == 32767) || (__WCHAR_MAX__ == 65535) + #define EA_WCHAR_SIZE 2 + #elif (__WCHAR_MAX__ == 127) || (__WCHAR_MAX__ == 255) + #define EA_WCHAR_SIZE 1 + #else + #define EA_WCHAR_SIZE 4 + #endif + #elif defined(WCHAR_MAX) // The SN and Arm compilers define this. + #if (WCHAR_MAX == 2147483647) || (WCHAR_MAX == 4294967295) + #define EA_WCHAR_SIZE 4 + #elif (WCHAR_MAX == 32767) || (WCHAR_MAX == 65535) + #define EA_WCHAR_SIZE 2 + #elif (WCHAR_MAX == 127) || (WCHAR_MAX == 255) + #define EA_WCHAR_SIZE 1 + #else + #define EA_WCHAR_SIZE 4 + #endif + #elif defined(__WCHAR_BIT) // Green Hills (and other versions of EDG?) uses this. + #if (__WCHAR_BIT == 16) + #define EA_WCHAR_SIZE 2 + #elif (__WCHAR_BIT == 32) + #define EA_WCHAR_SIZE 4 + #elif (__WCHAR_BIT == 8) + #define EA_WCHAR_SIZE 1 + #else + #define EA_WCHAR_SIZE 4 + #endif + #elif defined(_WCMAX) // The SN and Arm compilers define this. + #if (_WCMAX == 2147483647) || (_WCMAX == 4294967295) + #define EA_WCHAR_SIZE 4 + #elif (_WCMAX == 32767) || (_WCMAX == 65535) + #define EA_WCHAR_SIZE 2 + #elif (_WCMAX == 127) || (_WCMAX == 255) + #define EA_WCHAR_SIZE 1 + #else + #define EA_WCHAR_SIZE 4 + #endif + #elif defined(EA_PLATFORM_UNIX) + // It is standard on Unix to have wchar_t be int32_t or uint32_t. + // All versions of GNUC default to a 32 bit wchar_t, but EA has used + // the -fshort-wchar GCC command line option to force it to 16 bit. + // If you know that the compiler is set to use a wchar_t of other than + // the default, you need to manually define EA_WCHAR_SIZE for the build. + #define EA_WCHAR_SIZE 4 + #else + // It is standard on Windows to have wchar_t be uint16_t. GCC + // defines wchar_t as int by default. Electronic Arts has + // standardized on wchar_t being an unsigned 16 bit value on all + // console platforms. Given that there is currently no known way to + // tell at preprocessor time what the size of wchar_t is, we declare + // it to be 2, as this is the Electronic Arts standard. If you have + // EA_WCHAR_SIZE != sizeof(wchar_t), then your code might not be + // broken, but it also won't work with wchar libraries and data from + // other parts of EA. Under GCC, you can force wchar_t to two bytes + // with the -fshort-wchar compiler argument. + #define EA_WCHAR_SIZE 2 + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_RESTRICT + // + // The C99 standard defines a new keyword, restrict, which allows for the + // improvement of code generation regarding memory usage. Compilers can + // generate significantly faster code when you are able to use restrict. + // + // Example usage: + // void DoSomething(char* EA_RESTRICT p1, char* EA_RESTRICT p2); + // + #ifndef EA_RESTRICT + #if defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1400) // If VC8 (VS2005) or later... + #define EA_RESTRICT __restrict + #elif defined(EA_COMPILER_CLANG) + #define EA_RESTRICT __restrict + #elif defined(EA_COMPILER_GNUC) // Includes GCC and other compilers emulating GCC. + #define EA_RESTRICT __restrict // GCC defines 'restrict' (as opposed to __restrict) in C99 mode only. + #elif defined(EA_COMPILER_ARM) + #define EA_RESTRICT __restrict + #elif defined(EA_COMPILER_IS_C99) + #define EA_RESTRICT restrict + #else + // If the compiler didn't support restricted pointers, defining EA_RESTRICT + // away would result in compiling and running fine but you just wouldn't + // the same level of optimization. On the other hand, all the major compilers + // support restricted pointers. + #define EA_RESTRICT + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_DEPRECATED // Used as a prefix. + // EA_PREFIX_DEPRECATED // You should need this only for unusual compilers. + // EA_POSTFIX_DEPRECATED // You should need this only for unusual compilers. + // EA_DEPRECATED_MESSAGE // Used as a prefix and provides a deprecation message. + // + // Example usage: + // EA_DEPRECATED void Function(); + // EA_DEPRECATED_MESSAGE("Use 1.0v API instead") void Function(); + // + // or for maximum portability: + // EA_PREFIX_DEPRECATED void Function() EA_POSTFIX_DEPRECATED; + // + + #ifndef EA_DEPRECATED + #if defined(EA_COMPILER_CPP14_ENABLED) + #define EA_DEPRECATED [[deprecated]] + #elif defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION > 1300) // If VC7 (VS2003) or later... + #define EA_DEPRECATED __declspec(deprecated) + #elif defined(EA_COMPILER_MSVC) + #define EA_DEPRECATED + #else + #define EA_DEPRECATED __attribute__((deprecated)) + #endif + #endif + + #ifndef EA_PREFIX_DEPRECATED + #if defined(EA_COMPILER_CPP14_ENABLED) + #define EA_PREFIX_DEPRECATED [[deprecated]] + #define EA_POSTFIX_DEPRECATED + #elif defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION > 1300) // If VC7 (VS2003) or later... + #define EA_PREFIX_DEPRECATED __declspec(deprecated) + #define EA_POSTFIX_DEPRECATED + #elif defined(EA_COMPILER_MSVC) + #define EA_PREFIX_DEPRECATED + #define EA_POSTFIX_DEPRECATED + #else + #define EA_PREFIX_DEPRECATED + #define EA_POSTFIX_DEPRECATED __attribute__((deprecated)) + #endif + #endif + + #ifndef EA_DEPRECATED_MESSAGE + #if defined(EA_COMPILER_CPP14_ENABLED) + #define EA_DEPRECATED_MESSAGE(msg) [[deprecated(#msg)]] + #else + // Compiler does not support depreaction messages, explicitly drop the msg but still mark the function as deprecated + #define EA_DEPRECATED_MESSAGE(msg) EA_DEPRECATED + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_FORCE_INLINE // Used as a prefix. + // EA_PREFIX_FORCE_INLINE // You should need this only for unusual compilers. + // EA_POSTFIX_FORCE_INLINE // You should need this only for unusual compilers. + // + // Example usage: + // EA_FORCE_INLINE void Foo(); // Implementation elsewhere. + // EA_PREFIX_FORCE_INLINE void Foo() EA_POSTFIX_FORCE_INLINE; // Implementation elsewhere. + // + // Note that when the prefix version of this function is used, it replaces + // the regular C++ 'inline' statement. Thus you should not use both the + // C++ inline statement and this macro with the same function declaration. + // + // To force inline usage under GCC 3.1+, you use this: + // inline void Foo() __attribute__((always_inline)); + // or + // inline __attribute__((always_inline)) void Foo(); + // + // The CodeWarrior compiler doesn't have the concept of forcing inlining per function. + // + #ifndef EA_FORCE_INLINE + #if defined(EA_COMPILER_MSVC) + #define EA_FORCE_INLINE __forceinline + #elif defined(EA_COMPILER_GNUC) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 301) || defined(EA_COMPILER_CLANG) + #if defined(__cplusplus) + #define EA_FORCE_INLINE inline __attribute__((always_inline)) + #else + #define EA_FORCE_INLINE __inline__ __attribute__((always_inline)) + #endif + #else + #if defined(__cplusplus) + #define EA_FORCE_INLINE inline + #else + #define EA_FORCE_INLINE __inline + #endif + #endif + #endif + + #if defined(EA_COMPILER_GNUC) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 301) || defined(EA_COMPILER_CLANG) + #define EA_PREFIX_FORCE_INLINE inline + #define EA_POSTFIX_FORCE_INLINE __attribute__((always_inline)) + #else + #define EA_PREFIX_FORCE_INLINE inline + #define EA_POSTFIX_FORCE_INLINE + #endif + + + // ------------------------------------------------------------------------ + // EA_FORCE_INLINE_LAMBDA + // + // EA_FORCE_INLINE_LAMBDA is used to force inline a call to a lambda when possible. + // Force inlining a lambda can be useful to reduce overhead in situations where a lambda may + // may only be called once, or inlining allows the compiler to apply other optimizations that wouldn't + // otherwise be possible. + // + // The ability to force inline a lambda is currently only available on a subset of compilers. + // + // Example usage: + // + // auto lambdaFunction = []() EA_FORCE_INLINE_LAMBDA + // { + // }; + // + #ifndef EA_FORCE_INLINE_LAMBDA + #if defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + #define EA_FORCE_INLINE_LAMBDA __attribute__((always_inline)) + #else + #define EA_FORCE_INLINE_LAMBDA + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_NO_INLINE // Used as a prefix. + // EA_PREFIX_NO_INLINE // You should need this only for unusual compilers. + // EA_POSTFIX_NO_INLINE // You should need this only for unusual compilers. + // + // Example usage: + // EA_NO_INLINE void Foo(); // Implementation elsewhere. + // EA_PREFIX_NO_INLINE void Foo() EA_POSTFIX_NO_INLINE; // Implementation elsewhere. + // + // That this declaration is incompatbile with C++ 'inline' and any + // variant of EA_FORCE_INLINE. + // + // To disable inline usage under VC++ priof to VS2005, you need to use this: + // #pragma inline_depth(0) // Disable inlining. + // void Foo() { ... } + // #pragma inline_depth() // Restore to default. + // + // Since there is no easy way to disable inlining on a function-by-function + // basis in VC++ prior to VS2005, the best strategy is to write platform-specific + // #ifdefs in the code or to disable inlining for a given module and enable + // functions individually with EA_FORCE_INLINE. + // + #ifndef EA_NO_INLINE + #if defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1400) // If VC8 (VS2005) or later... + #define EA_NO_INLINE __declspec(noinline) + #elif defined(EA_COMPILER_MSVC) + #define EA_NO_INLINE + #else + #define EA_NO_INLINE __attribute__((noinline)) + #endif + #endif + + #if defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1400) // If VC8 (VS2005) or later... + #define EA_PREFIX_NO_INLINE __declspec(noinline) + #define EA_POSTFIX_NO_INLINE + #elif defined(EA_COMPILER_MSVC) + #define EA_PREFIX_NO_INLINE + #define EA_POSTFIX_NO_INLINE + #else + #define EA_PREFIX_NO_INLINE + #define EA_POSTFIX_NO_INLINE __attribute__((noinline)) + #endif + + + // ------------------------------------------------------------------------ + // EA_NO_VTABLE + // + // Example usage: + // class EA_NO_VTABLE X { + // virtual void InterfaceFunction(); + // }; + // + // EA_CLASS_NO_VTABLE(X) { + // virtual void InterfaceFunction(); + // }; + // + #ifdef EA_COMPILER_MSVC + #define EA_NO_VTABLE __declspec(novtable) + #define EA_CLASS_NO_VTABLE(x) class __declspec(novtable) x + #define EA_STRUCT_NO_VTABLE(x) struct __declspec(novtable) x + #else + #define EA_NO_VTABLE + #define EA_CLASS_NO_VTABLE(x) class x + #define EA_STRUCT_NO_VTABLE(x) struct x + #endif + + + // ------------------------------------------------------------------------ + // EA_PASCAL + // + // Also known on PC platforms as stdcall. + // This convention causes the compiler to assume that the called function + // will pop off the stack space used to pass arguments, unless it takes a + // variable number of arguments. + // + // Example usage: + // this: + // void DoNothing(int x); + // void DoNothing(int x){} + // would be written as this: + // void EA_PASCAL_FUNC(DoNothing(int x)); + // void EA_PASCAL_FUNC(DoNothing(int x)){} + // + #ifndef EA_PASCAL + #if defined(EA_COMPILER_MSVC) + #define EA_PASCAL __stdcall + #elif defined(EA_COMPILER_GNUC) && defined(EA_PROCESSOR_X86) + #define EA_PASCAL __attribute__((stdcall)) + #else + // Some compilers simply don't support pascal calling convention. + // As a result, there isn't an issue here, since the specification of + // pascal calling convention is for the purpose of disambiguating the + // calling convention that is applied. + #define EA_PASCAL + #endif + #endif + + #ifndef EA_PASCAL_FUNC + #if defined(EA_COMPILER_MSVC) + #define EA_PASCAL_FUNC(funcname_and_paramlist) __stdcall funcname_and_paramlist + #elif defined(EA_COMPILER_GNUC) && defined(EA_PROCESSOR_X86) + #define EA_PASCAL_FUNC(funcname_and_paramlist) __attribute__((stdcall)) funcname_and_paramlist + #else + #define EA_PASCAL_FUNC(funcname_and_paramlist) funcname_and_paramlist + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_SSE + // Visual C Processor Packs define _MSC_FULL_VER and are needed for SSE + // Intel C also has SSE support. + // EA_SSE is used to select FPU or SSE versions in hw_select.inl + // + // EA_SSE defines the level of SSE support: + // 0 indicates no SSE support + // 1 indicates SSE1 is supported + // 2 indicates SSE2 is supported + // 3 indicates SSE3 (or greater) is supported + // + // Note: SSE support beyond SSE3 can't be properly represented as a single + // version number. Instead users should use specific SSE defines (e.g. + // EA_SSE4_2) to detect what specific support is available. EA_SSE being + // equal to 3 really only indicates that SSE3 or greater is supported. + #ifndef EA_SSE + #if defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + #if defined(__SSE3__) + #define EA_SSE 3 + #elif defined(__SSE2__) + #define EA_SSE 2 + #elif defined(__SSE__) && __SSE__ + #define EA_SSE 1 + #else + #define EA_SSE 0 + #endif + #elif (defined(EA_SSE3) && EA_SSE3) || defined EA_PLATFORM_XBOXONE || defined EA_PLATFORM_XBSX + #define EA_SSE 3 + #elif defined(EA_SSE2) && EA_SSE2 + #define EA_SSE 2 + #elif defined(EA_PROCESSOR_X86) && defined(_MSC_FULL_VER) && !defined(__NOSSE__) && defined(_M_IX86_FP) + #define EA_SSE _M_IX86_FP + #elif defined(EA_PROCESSOR_X86) && defined(EA_COMPILER_INTEL) && !defined(__NOSSE__) + #define EA_SSE 1 + #elif defined(EA_PROCESSOR_X86_64) + // All x64 processors support SSE2 or higher + #define EA_SSE 2 + #else + #define EA_SSE 0 + #endif + #endif + + // ------------------------------------------------------------------------ + // We define separate defines for SSE support beyond SSE1. These defines + // are particularly useful for detecting SSE4.x features since there isn't + // a single concept of SSE4. + // + // The following SSE defines are always defined. 0 indicates the + // feature/level of SSE is not supported, and 1 indicates support is + // available. + #ifndef EA_SSE2 + #if EA_SSE >= 2 + #define EA_SSE2 1 + #else + #define EA_SSE2 0 + #endif + #endif + #ifndef EA_SSE3 + #if EA_SSE >= 3 + #define EA_SSE3 1 + #else + #define EA_SSE3 0 + #endif + #endif + #ifndef EA_SSSE3 + #if defined __SSSE3__ || defined EA_PLATFORM_XBOXONE || defined EA_PLATFORM_XBSX + #define EA_SSSE3 1 + #else + #define EA_SSSE3 0 + #endif + #endif + #ifndef EA_SSE4_1 + #if defined __SSE4_1__ || defined EA_PLATFORM_XBOXONE || defined EA_PLATFORM_XBSX + #define EA_SSE4_1 1 + #else + #define EA_SSE4_1 0 + #endif + #endif + #ifndef EA_SSE4_2 + #if defined __SSE4_2__ || defined EA_PLATFORM_XBOXONE || defined EA_PLATFORM_XBSX + #define EA_SSE4_2 1 + #else + #define EA_SSE4_2 0 + #endif + #endif + #ifndef EA_SSE4A + #if defined __SSE4A__ || defined EA_PLATFORM_XBOXONE || defined EA_PLATFORM_XBSX + #define EA_SSE4A 1 + #else + #define EA_SSE4A 0 + #endif + #endif + + // ------------------------------------------------------------------------ + // EA_AVX + // EA_AVX may be used to determine if Advanced Vector Extensions are available for the target architecture + // + // EA_AVX defines the level of AVX support: + // 0 indicates no AVX support + // 1 indicates AVX1 is supported + // 2 indicates AVX2 is supported + #ifndef EA_AVX + #if defined __AVX2__ + #define EA_AVX 2 + #elif defined __AVX__ || defined EA_PLATFORM_XBOXONE || defined EA_PLATFORM_XBSX + #define EA_AVX 1 + #else + #define EA_AVX 0 + #endif + #endif + #ifndef EA_AVX2 + #if EA_AVX >= 2 + #define EA_AVX2 1 + #else + #define EA_AVX2 0 + #endif + #endif + + // EA_FP16C may be used to determine the existence of float <-> half conversion operations on an x86 CPU. + // (For example to determine if _mm_cvtph_ps or _mm_cvtps_ph could be used.) + #ifndef EA_FP16C + #if defined __F16C__ || defined EA_PLATFORM_XBOXONE || defined EA_PLATFORM_XBSX + #define EA_FP16C 1 + #else + #define EA_FP16C 0 + #endif + #endif + + // EA_FP128 may be used to determine if __float128 is a supported type for use. This type is enabled by a GCC extension (_GLIBCXX_USE_FLOAT128) + // but has support by some implementations of clang (__FLOAT128__) + // PS4 does not support __float128 as of SDK 5.500 https://ps4.siedev.net/resources/documents/SDK/5.500/CPU_Compiler_ABI-Overview/0003.html + #ifndef EA_FP128 + #if (defined __FLOAT128__ || defined _GLIBCXX_USE_FLOAT128) && !defined(EA_PLATFORM_SONY) + #define EA_FP128 1 + #else + #define EA_FP128 0 + #endif + #endif + + // ------------------------------------------------------------------------ + // EA_ABM + // EA_ABM may be used to determine if Advanced Bit Manipulation sets are available for the target architecture (POPCNT, LZCNT) + // + #ifndef EA_ABM + #if defined(__ABM__) || defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_SONY) || defined(EA_PLATFORM_XBSX) + #define EA_ABM 1 + #else + #define EA_ABM 0 + #endif + #endif + + // ------------------------------------------------------------------------ + // EA_NEON + // EA_NEON may be used to determine if NEON is supported. + #ifndef EA_NEON + #if defined(__ARM_NEON__) || defined(__ARM_NEON) + #define EA_NEON 1 + #else + #define EA_NEON 0 + #endif + #endif + + // ------------------------------------------------------------------------ + // EA_BMI + // EA_BMI may be used to determine if Bit Manipulation Instruction sets are available for the target architecture + // + // EA_BMI defines the level of BMI support: + // 0 indicates no BMI support + // 1 indicates BMI1 is supported + // 2 indicates BMI2 is supported + #ifndef EA_BMI + #if defined(__BMI2__) + #define EA_BMI 2 + #elif defined(__BMI__) || defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + #define EA_BMI 1 + #else + #define EA_BMI 0 + #endif + #endif + #ifndef EA_BMI2 + #if EA_BMI >= 2 + #define EA_BMI2 1 + #else + #define EA_BMI2 0 + #endif + #endif + + // ------------------------------------------------------------------------ + // EA_FMA3 + // EA_FMA3 may be used to determine if Fused Multiply Add operations are available for the target architecture + // __FMA__ is defined only by GCC, Clang, and ICC; MSVC only defines __AVX__ and __AVX2__ + // FMA3 was introduced alongside AVX2 on Intel Haswell + // All AMD processors support FMA3 if AVX2 is also supported + // + // EA_FMA3 defines the level of FMA3 support: + // 0 indicates no FMA3 support + // 1 indicates FMA3 is supported + #ifndef EA_FMA3 + #if defined(__FMA__) || EA_AVX2 >= 1 + #define EA_FMA3 1 + #else + #define EA_FMA3 0 + #endif + #endif + + // ------------------------------------------------------------------------ + // EA_TBM + // EA_TBM may be used to determine if Trailing Bit Manipulation instructions are available for the target architecture + #ifndef EA_TBM + #if defined(__TBM__) + #define EA_TBM 1 + #else + #define EA_TBM 0 + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_IMPORT + // import declaration specification + // specifies that the declared symbol is imported from another dynamic library. + #ifndef EA_IMPORT + #if defined(EA_COMPILER_MSVC) + #define EA_IMPORT __declspec(dllimport) + #else + #define EA_IMPORT + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_EXPORT + // export declaration specification + // specifies that the declared symbol is exported from the current dynamic library. + // this is not the same as the C++ export keyword. The C++ export keyword has been + // removed from the language as of C++11. + #ifndef EA_EXPORT + #if defined(EA_COMPILER_MSVC) + #define EA_EXPORT __declspec(dllexport) + #else + #define EA_EXPORT + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_PRAGMA_ONCE_SUPPORTED + // + // This is a wrapper for the #pragma once preprocessor directive. + // It allows for some compilers (in particular VC++) to implement signifcantly + // faster include file preprocessing. #pragma once can be used to replace + // header include guards or to augment them. However, #pragma once isn't + // necessarily supported by all compilers and isn't guaranteed to be so in + // the future, so using #pragma once to replace traditional include guards + // is not strictly portable. Note that a direct #define for #pragma once is + // impossible with VC++, due to limitations, but can be done with other + // compilers/preprocessors via _Pragma("once"). + // + // Example usage (which includes traditional header guards for portability): + // #ifndef SOMEPACKAGE_SOMEHEADER_H + // #define SOMEPACKAGE_SOMEHEADER_H + // + // #if defined(EA_PRAGMA_ONCE_SUPPORTED) + // #pragma once + // #endif + // + // + // + // #endif + // + #if defined(_MSC_VER) || defined(__GNUC__) || defined(__EDG__) || defined(__APPLE__) + #define EA_PRAGMA_ONCE_SUPPORTED 1 + #endif + + + + // ------------------------------------------------------------------------ + // EA_ONCE + // + // Example usage (which includes traditional header guards for portability): + // #ifndef SOMEPACKAGE_SOMEHEADER_H + // #define SOMEPACKAGE_SOMEHEADER_H + // + // EA_ONCE() + // + // + // + // #endif + // + #if defined(EA_PRAGMA_ONCE_SUPPORTED) + #if defined(_MSC_VER) + #define EA_ONCE() __pragma(once) + #else + #define EA_ONCE() // _Pragma("once") It turns out that _Pragma("once") isn't supported by many compilers. + #endif + #endif + + + + // ------------------------------------------------------------------------ + // EA_OVERRIDE + // + // C++11 override + // See http://msdn.microsoft.com/en-us/library/jj678987.aspx for more information. + // You can use EA_FINAL_OVERRIDE to combine usage of EA_OVERRIDE and EA_INHERITANCE_FINAL in a single statement. + // + // Example usage: + // struct B { virtual void f(int); }; + // struct D : B { void f(int) EA_OVERRIDE; }; + // + #ifndef EA_OVERRIDE + #if defined(EA_COMPILER_NO_OVERRIDE) + #define EA_OVERRIDE + #else + #define EA_OVERRIDE override + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_INHERITANCE_FINAL + // + // Portably wraps the C++11 final specifier. + // See http://msdn.microsoft.com/en-us/library/jj678985.aspx for more information. + // You can use EA_FINAL_OVERRIDE to combine usage of EA_OVERRIDE and EA_INHERITANCE_FINAL in a single statement. + // This is not called EA_FINAL because that term is used within EA to denote debug/release/final builds. + // + // Example usage: + // struct B { virtual void f() EA_INHERITANCE_FINAL; }; + // + #ifndef EA_INHERITANCE_FINAL + #if defined(EA_COMPILER_NO_INHERITANCE_FINAL) + #define EA_INHERITANCE_FINAL + #elif (defined(_MSC_VER) && (EA_COMPILER_VERSION < 1700)) // Pre-VS2012 + #define EA_INHERITANCE_FINAL sealed + #else + #define EA_INHERITANCE_FINAL final + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_FINAL_OVERRIDE + // + // Portably wraps the C++11 override final specifiers combined. + // + // Example usage: + // struct A { virtual void f(); }; + // struct B : public A { virtual void f() EA_FINAL_OVERRIDE; }; + // + #ifndef EA_FINAL_OVERRIDE + #define EA_FINAL_OVERRIDE EA_OVERRIDE EA_INHERITANCE_FINAL + #endif + + + // ------------------------------------------------------------------------ + // EA_SEALED + // + // This is deprecated, as the C++11 Standard has final (EA_INHERITANCE_FINAL) instead. + // See http://msdn.microsoft.com/en-us/library/0w2w91tf.aspx for more information. + // Example usage: + // struct B { virtual void f() EA_SEALED; }; + // + #ifndef EA_SEALED + #if defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1400) // VS2005 (VC8) and later + #define EA_SEALED sealed + #else + #define EA_SEALED + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_ABSTRACT + // + // This is a Microsoft language extension. + // See http://msdn.microsoft.com/en-us/library/b0z6b513.aspx for more information. + // Example usage: + // struct X EA_ABSTRACT { virtual void f(){} }; + // + #ifndef EA_ABSTRACT + #if defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1400) // VS2005 (VC8) and later + #define EA_ABSTRACT abstract + #else + #define EA_ABSTRACT + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_CONSTEXPR + // EA_CONSTEXPR_OR_CONST + // + // Portable wrapper for C++11's 'constexpr' support. + // + // See http://www.cprogramming.com/c++11/c++11-compile-time-processing-with-constexpr.html for more information. + // Example usage: + // EA_CONSTEXPR int GetValue() { return 37; } + // EA_CONSTEXPR_OR_CONST double gValue = std::sin(kTwoPi); + // + #if !defined(EA_CONSTEXPR) + #if defined(EA_COMPILER_NO_CONSTEXPR) + #define EA_CONSTEXPR + #else + #define EA_CONSTEXPR constexpr + #endif + #endif + + #if !defined(EA_CONSTEXPR_OR_CONST) + #if defined(EA_COMPILER_NO_CONSTEXPR) + #define EA_CONSTEXPR_OR_CONST const + #else + #define EA_CONSTEXPR_OR_CONST constexpr + #endif + #endif + + // ------------------------------------------------------------------------ + // EA_CONSTEXPR_IF + // + // Portable wrapper for C++17's 'constexpr if' support. + // + // https://en.cppreference.com/w/cpp/language/if + // + // Example usage: + // + // EA_CONSTEXPR_IF(eastl::is_copy_constructible_v) + // { ... } + // + #if !defined(EA_CONSTEXPR_IF) + #if defined(EA_COMPILER_NO_CONSTEXPR_IF) + #define EA_CONSTEXPR_IF(predicate) if ((predicate)) + #else + #define EA_CONSTEXPR_IF(predicate) if constexpr ((predicate)) + #endif + #endif + + + + // ------------------------------------------------------------------------ + // EA_EXTERN_TEMPLATE + // + // Portable wrapper for C++11's 'extern template' support. + // + // Example usage: + // EA_EXTERN_TEMPLATE(class basic_string); + // + #if !defined(EA_EXTERN_TEMPLATE) + #if defined(EA_COMPILER_NO_EXTERN_TEMPLATE) + #define EA_EXTERN_TEMPLATE(declaration) + #else + #define EA_EXTERN_TEMPLATE(declaration) extern template declaration + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_NOEXCEPT + // EA_NOEXCEPT_IF(predicate) + // EA_NOEXCEPT_EXPR(expression) + // + // Portable wrapper for C++11 noexcept + // http://en.cppreference.com/w/cpp/language/noexcept + // http://en.cppreference.com/w/cpp/language/noexcept_spec + // + // Example usage: + // EA_NOEXCEPT + // EA_NOEXCEPT_IF(predicate) + // EA_NOEXCEPT_EXPR(expression) + // + // This function never throws an exception. + // void DoNothing() EA_NOEXCEPT + // { } + // + // This function throws an exception of T::T() throws an exception. + // template + // void DoNothing() EA_NOEXCEPT_IF(EA_NOEXCEPT_EXPR(T())) + // { T t; } + // + #if !defined(EA_NOEXCEPT) + #if defined(EA_COMPILER_NO_NOEXCEPT) + #define EA_NOEXCEPT + #define EA_NOEXCEPT_IF(predicate) + #define EA_NOEXCEPT_EXPR(expression) false + #else + #define EA_NOEXCEPT noexcept + #define EA_NOEXCEPT_IF(predicate) noexcept((predicate)) + #define EA_NOEXCEPT_EXPR(expression) noexcept((expression)) + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_NORETURN + // + // Wraps the C++11 noreturn attribute. See EA_COMPILER_NO_NORETURN + // http://en.cppreference.com/w/cpp/language/attributes + // http://msdn.microsoft.com/en-us/library/k6ktzx3s%28v=vs.80%29.aspx + // http://blog.aaronballman.com/2011/09/understanding-attributes/ + // + // Example usage: + // EA_NORETURN void SomeFunction() + // { throw "error"; } + // + #if !defined(EA_NORETURN) + #if defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1300) // VS2003 (VC7) and later + #define EA_NORETURN __declspec(noreturn) + #elif defined(EA_COMPILER_NO_NORETURN) + #define EA_NORETURN + #else + #define EA_NORETURN [[noreturn]] + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_CARRIES_DEPENDENCY + // + // Wraps the C++11 carries_dependency attribute + // http://en.cppreference.com/w/cpp/language/attributes + // http://blog.aaronballman.com/2011/09/understanding-attributes/ + // + // Example usage: + // EA_CARRIES_DEPENDENCY int* SomeFunction() + // { return &mX; } + // + // + #if !defined(EA_CARRIES_DEPENDENCY) + #if defined(EA_COMPILER_NO_CARRIES_DEPENDENCY) + #define EA_CARRIES_DEPENDENCY + #else + #define EA_CARRIES_DEPENDENCY [[carries_dependency]] + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_FALLTHROUGH + // + // [[fallthrough] is a C++17 standard attribute that appears in switch + // statements to indicate that the fallthrough from the previous case in the + // switch statement is intentially and not a bug. + // + // http://en.cppreference.com/w/cpp/language/attributes + // + // Example usage: + // void f(int n) + // { + // switch(n) + // { + // case 1: + // DoCase1(); + // // Compiler may generate a warning for fallthrough behaviour + // + // case 2: + // DoCase2(); + // + // EA_FALLTHROUGH; + // case 3: + // DoCase3(); + // } + // } + // + #if !defined(EA_FALLTHROUGH) + #if defined(EA_COMPILER_NO_FALLTHROUGH) + #define EA_FALLTHROUGH + #else + #define EA_FALLTHROUGH [[fallthrough]] + #endif + #endif + + + + // ------------------------------------------------------------------------ + // EA_NODISCARD + // + // [[nodiscard]] is a C++17 standard attribute that can be applied to a + // function declaration, enum, or class declaration. If a any of the list + // previously are returned from a function (without the user explicitly + // casting to void) the addition of the [[nodiscard]] attribute encourages + // the compiler to generate a warning about the user discarding the return + // value. This is a useful practice to encourage client code to check API + // error codes. + // + // http://en.cppreference.com/w/cpp/language/attributes + // + // Example usage: + // + // EA_NODISCARD int baz() { return 42; } + // + // void foo() + // { + // baz(); // warning: ignoring return value of function declared with 'nodiscard' attribute + // } + // + #if !defined(EA_NODISCARD) + #if defined(EA_COMPILER_NO_NODISCARD) + #define EA_NODISCARD + #else + #define EA_NODISCARD [[nodiscard]] + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_MAYBE_UNUSED + // + // [[maybe_unused]] is a C++17 standard attribute that suppresses warnings + // on unused entities that are declared as maybe_unused. + // + // http://en.cppreference.com/w/cpp/language/attributes + // + // Example usage: + // void foo(EA_MAYBE_UNUSED int i) + // { + // assert(i == 42); // warning suppressed when asserts disabled. + // } + // + #if !defined(EA_MAYBE_UNUSED) + #if defined(EA_COMPILER_NO_MAYBE_UNUSED) + #define EA_MAYBE_UNUSED + #else + #define EA_MAYBE_UNUSED [[maybe_unused]] + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_NO_UBSAN + // + // The LLVM/Clang undefined behaviour sanitizer will not analyse a function tagged with the following attribute. + // + // https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#disabling-instrumentation-with-attribute-no-sanitize-undefined + // + // Example usage: + // EA_NO_UBSAN int SomeFunction() { ... } + // + #ifndef EA_NO_UBSAN + #if defined(EA_COMPILER_CLANG) + #define EA_NO_UBSAN __attribute__((no_sanitize("undefined"))) + #else + #define EA_NO_UBSAN + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_NO_ASAN + // + // The LLVM/Clang address sanitizer will not analyse a function tagged with the following attribute. + // + // https://clang.llvm.org/docs/AddressSanitizer.html#disabling-instrumentation-with-attribute-no-sanitize-address + // + // Example usage: + // EA_NO_ASAN int SomeFunction() { ... } + // + #ifndef EA_NO_ASAN + #if defined(EA_COMPILER_CLANG) + #define EA_NO_ASAN __attribute__((no_sanitize("address"))) + #else + #define EA_NO_ASAN + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_ASAN_ENABLED + // + // Defined as 0 or 1. It's value depends on the compile environment. + // Specifies whether the code is being built with Clang's Address Sanitizer. + // + #if defined(__has_feature) + #if __has_feature(address_sanitizer) + #define EA_ASAN_ENABLED 1 + #else + #define EA_ASAN_ENABLED 0 + #endif + #else + #define EA_ASAN_ENABLED 0 + #endif + + + // ------------------------------------------------------------------------ + // EA_NON_COPYABLE + // + // This macro defines as a class as not being copy-constructable + // or assignable. This is useful for preventing class instances + // from being passed to functions by value, is useful for preventing + // compiler warnings by some compilers about the inability to + // auto-generate a copy constructor and assignment, and is useful + // for simply declaring in the interface that copy semantics are + // not supported by the class. Your class needs to have at least a + // default constructor when using this macro. + // + // Beware that this class works by declaring a private: section of + // the class in the case of compilers that don't support C++11 deleted + // functions. + // + // Note: With some pre-C++11 compilers (e.g. Green Hills), you may need + // to manually define an instances of the hidden functions, even + // though they are not used. + // + // Example usage: + // class Widget { + // Widget(); + // . . . + // EA_NON_COPYABLE(Widget) + // }; + // + #if !defined(EA_NON_COPYABLE) + #if defined(EA_COMPILER_NO_DELETED_FUNCTIONS) + #define EA_NON_COPYABLE(EAClass_) \ + private: \ + EA_DISABLE_VC_WARNING(4822); /* local class member function does not have a body */ \ + EAClass_(const EAClass_&); \ + void operator=(const EAClass_&); \ + EA_RESTORE_VC_WARNING(); + #else + #define EA_NON_COPYABLE(EAClass_) \ + EA_DISABLE_VC_WARNING(4822); /* local class member function does not have a body */ \ + EAClass_(const EAClass_&) = delete; \ + void operator=(const EAClass_&) = delete; \ + EA_RESTORE_VC_WARNING(); + #endif + #endif + + + // ------------------------------------------------------------------------ + // EA_FUNCTION_DELETE + // + // Semi-portable way of specifying a deleted function which allows for + // cleaner code in class declarations. + // + // Example usage: + // + // class Example + // { + // private: // For portability with pre-C++11 compilers, make the function private. + // void foo() EA_FUNCTION_DELETE; + // }; + // + // Note: EA_FUNCTION_DELETE'd functions should be private to prevent the + // functions from being called even when the compiler does not support + // deleted functions. Some compilers (e.g. Green Hills) that don't support + // C++11 deleted functions can require that you define the function, + // which you can do in the associated source file for the class. + // + #if defined(EA_COMPILER_NO_DELETED_FUNCTIONS) + #define EA_FUNCTION_DELETE + #else + #define EA_FUNCTION_DELETE = delete + #endif + + // ------------------------------------------------------------------------ + // EA_DISABLE_DEFAULT_CTOR + // + // Disables the compiler generated default constructor. This macro is + // provided to improve portability and clarify intent of code. + // + // Example usage: + // + // class Example + // { + // private: + // EA_DISABLE_DEFAULT_CTOR(Example); + // }; + // + #define EA_DISABLE_DEFAULT_CTOR(ClassName) ClassName() EA_FUNCTION_DELETE + + // ------------------------------------------------------------------------ + // EA_DISABLE_COPY_CTOR + // + // Disables the compiler generated copy constructor. This macro is + // provided to improve portability and clarify intent of code. + // + // Example usage: + // + // class Example + // { + // private: + // EA_DISABLE_COPY_CTOR(Example); + // }; + // + #define EA_DISABLE_COPY_CTOR(ClassName) ClassName(const ClassName &) EA_FUNCTION_DELETE + + // ------------------------------------------------------------------------ + // EA_DISABLE_MOVE_CTOR + // + // Disables the compiler generated move constructor. This macro is + // provided to improve portability and clarify intent of code. + // + // Example usage: + // + // class Example + // { + // private: + // EA_DISABLE_MOVE_CTOR(Example); + // }; + // + #define EA_DISABLE_MOVE_CTOR(ClassName) ClassName(ClassName&&) EA_FUNCTION_DELETE + + // ------------------------------------------------------------------------ + // EA_DISABLE_ASSIGNMENT_OPERATOR + // + // Disables the compiler generated assignment operator. This macro is + // provided to improve portability and clarify intent of code. + // + // Example usage: + // + // class Example + // { + // private: + // EA_DISABLE_ASSIGNMENT_OPERATOR(Example); + // }; + // + #define EA_DISABLE_ASSIGNMENT_OPERATOR(ClassName) ClassName & operator=(const ClassName &) EA_FUNCTION_DELETE + + // ------------------------------------------------------------------------ + // EA_DISABLE_MOVE_OPERATOR + // + // Disables the compiler generated move operator. This macro is + // provided to improve portability and clarify intent of code. + // + // Example usage: + // + // class Example + // { + // private: + // EA_DISABLE_MOVE_OPERATOR(Example); + // }; + // + #define EA_DISABLE_MOVE_OPERATOR(ClassName) ClassName & operator=(ClassName&&) EA_FUNCTION_DELETE + + // ------------------------------------------------------------------------ + // EANonCopyable + // + // Declares a class as not supporting copy construction or assignment. + // May be more reliable with some situations that EA_NON_COPYABLE alone, + // though it may result in more code generation. + // + // Note that VC++ will generate warning C4625 and C4626 if you use EANonCopyable + // and you are compiling with /W4 and /Wall. There is no resolution but + // to redelare EA_NON_COPYABLE in your subclass or disable the warnings with + // code like this: + // EA_DISABLE_VC_WARNING(4625 4626) + // ... + // EA_RESTORE_VC_WARNING() + // + // Example usage: + // struct Widget : EANonCopyable { + // . . . + // }; + // + #ifdef __cplusplus + struct EANonCopyable + { + #if defined(EA_COMPILER_NO_DEFAULTED_FUNCTIONS) || defined(__EDG__) + // EDG doesn't appear to behave properly for the case of defaulted constructors; + // it generates a mistaken warning about missing default constructors. + EANonCopyable() {} // Putting {} here has the downside that it allows a class to create itself, + ~EANonCopyable() {} // but avoids linker errors that can occur with some compilers (e.g. Green Hills). + #else + EANonCopyable() = default; + ~EANonCopyable() = default; + #endif + + EA_NON_COPYABLE(EANonCopyable) + }; + #endif + + + // ------------------------------------------------------------------------ + // EA_OPTIMIZE_OFF / EA_OPTIMIZE_ON + // + // Implements portable inline optimization enabling/disabling. + // Usage of these macros must be in order OFF then ON. This is + // because the OFF macro pushes a set of settings and the ON + // macro pops them. The nesting of OFF/ON sets (e.g. OFF, OFF, ON, ON) + // is not guaranteed to work on all platforms. + // + // This is often used to allow debugging of some code that's + // otherwise compiled with undebuggable optimizations. It's also + // useful for working around compiler code generation problems + // that occur in optimized builds. + // + // Some compilers (e.g. VC++) don't allow doing this within a function and + // so the usage must be outside a function, as with the example below. + // GCC on x86 appears to have some problem with argument passing when + // using EA_OPTIMIZE_OFF in optimized builds. + // + // Example usage: + // // Disable optimizations for SomeFunction. + // EA_OPTIMIZE_OFF() + // void SomeFunction() + // { + // ... + // } + // EA_OPTIMIZE_ON() + // + #if !defined(EA_OPTIMIZE_OFF) + #if defined(EA_COMPILER_MSVC) + #define EA_OPTIMIZE_OFF() __pragma(optimize("", off)) + #elif defined(__ghs) + #define EA_OPTIMIZE_OFF() _Pragma("ghs ZO") + #elif defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION > 4004) && (defined(__i386__) || defined(__x86_64__)) // GCC 4.4+ - Seems to work only on x86/Linux so far. However, GCC 4.4 itself appears broken and screws up parameter passing conventions. + #define EA_OPTIMIZE_OFF() \ + _Pragma("GCC push_options") \ + _Pragma("GCC optimize 0") + #elif defined(EA_COMPILER_CLANG) && (!defined(EA_PLATFORM_ANDROID) || (EA_COMPILER_VERSION >= 380)) + #define EA_OPTIMIZE_OFF() \ + EA_DISABLE_CLANG_WARNING(-Wunknown-pragmas) \ + _Pragma("clang optimize off") \ + EA_RESTORE_CLANG_WARNING() + #else + #define EA_OPTIMIZE_OFF() + #endif + #endif + + #if !defined(EA_OPTIMIZE_ON) + #if defined(EA_COMPILER_MSVC) + #define EA_OPTIMIZE_ON() __pragma(optimize("", on)) + #elif defined(__ghs) + #define EA_OPTIMIZE_ON() _Pragma("ghs revertoptions") + #elif defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION > 4004) && (defined(__i386__) || defined(__x86_64__)) // GCC 4.4+ - Seems to work only on x86/Linux so far. However, GCC 4.4 itself appears broken and screws up parameter passing conventions. + #define EA_OPTIMIZE_ON() _Pragma("GCC pop_options") + #elif defined(EA_COMPILER_CLANG) && (!defined(EA_PLATFORM_ANDROID) || (EA_COMPILER_VERSION >= 380)) + #define EA_OPTIMIZE_ON() \ + EA_DISABLE_CLANG_WARNING(-Wunknown-pragmas) \ + _Pragma("clang optimize on") \ + EA_RESTORE_CLANG_WARNING() + #else + #define EA_OPTIMIZE_ON() + #endif + #endif + + + + // ------------------------------------------------------------------------ + // EA_SIGNED_RIGHT_SHIFT_IS_UNSIGNED + // + // Defined if right shifts of signed integers (i.e. arithmetic shifts) fail + // to propogate the high bit downward, and thus preserve sign. Most hardware + // and their corresponding compilers do this. + // + // + +#endif // Header include guard + + + + + + + + + + diff --git a/src/thirdparty/ea/EABase/config/eaplatform.h b/src/thirdparty/ea/EABase/config/eaplatform.h new file mode 100644 index 00000000..aef33aad --- /dev/null +++ b/src/thirdparty/ea/EABase/config/eaplatform.h @@ -0,0 +1,851 @@ +/*----------------------------------------------------------------------------- + * config/eaplatform.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *----------------------------------------------------------------------------- + * Currently supported platform indentification defines include: + */ +#ifdef EA_PLATFORM_PS4 // ifdef for code stripping purposes +// EA_PLATFORM_PS4 (EA_PLATFORM_KETTLE) +#endif +#ifdef EA_PLATFORM_XBOXONE // ifdef for code stripping purposes + // EA_PLATFORM_XBOXONE (EA_PLATFORM_CAPILANO) + // EA_PLATFORM_XBOXONE_XDK (EA_PLATFORM_CAPILANO_XDK), set by capilano_config package + // EA_PLATFORM_XBOXONE_ADK (EA_PLATFORM_CAPILANO_ADK), set by capilano_config package +#endif +#ifdef EA_PLATFORM_NX // ifdef for code stripping purposes + // EA_PLATFORM_NX +#endif +#ifdef EA_PLATFORM_XBSX // ifdef for code stripping purposes + // EA_PLATFORM_XBSX +#endif +// EA_PLATFORM_ANDROID +// EA_PLATFORM_APPLE +// EA_PLATFORM_IPHONE +// EA_PLATFORM_IPHONE_SIMULATOR +// EA_PLATFORM_OSX +// EA_PLATFORM_LINUX +// EA_PLATFORM_SAMSUNG_TV +// EA_PLATFORM_WINDOWS +// EA_PLATFORM_WIN32 +// EA_PLATFORM_WIN64 +// EA_PLATFORM_WINDOWS_PHONE +// EA_PLATFORM_WINRT +// EA_PLATFORM_SUN +// EA_PLATFORM_LRB (Larrabee) +// EA_PLATFORM_POSIX (pseudo-platform; may be defined along with another platform like EA_PLATFORM_LINUX, EA_PLATFORM_UNIX, EA_PLATFORM_QNX) +// EA_PLATFORM_UNIX (pseudo-platform; may be defined along with another platform like EA_PLATFORM_LINUX) +// EA_PLATFORM_CYGWIN (pseudo-platform; may be defined along with another platform like EA_PLATFORM_LINUX) +// EA_PLATFORM_MINGW (pseudo-platform; may be defined along with another platform like EA_PLATFORM_WINDOWS) +// EA_PLATFORM_MICROSOFT (pseudo-platform; may be defined along with another platform like EA_PLATFORM_WINDOWS) +// +// EA_ABI_ARM_LINUX (a.k.a. "eabi". for all platforms that use the CodeSourcery GNU/Linux toolchain, like Android) +// EA_ABI_ARM_APPLE (similar to eabi but not identical) +// EA_ABI_ARM64_APPLE (similar to eabi but not identical) https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html +// EA_ABI_ARM_WINCE (similar to eabi but not identical) +// +// Other definitions emanated from this file inclue: +// EA_PLATFORM_NAME = +// EA_PLATFORM_DESCRIPTION = +// EA_PROCESSOR_XXX +// EA_MISALIGNED_SUPPORT_LEVEL=0|1|2 +// EA_SYSTEM_LITTLE_ENDIAN | EA_SYSTEM_BIG_ENDIAN +// EA_ASM_STYLE_ATT | EA_ASM_STYLE_INTEL | EA_ASM_STYLE_MOTOROLA +// EA_PLATFORM_PTR_SIZE = +// EA_PLATFORM_WORD_SIZE = +// EA_CACHE_LINE_SIZE = +//--------------------------------------------------------------------------- + +/* + EA_PLATFORM_MOBILE + EA_PLATFORM_MOBILE is a peer to EA_PLATORM_DESKTOP and EA_PLATFORM_CONSOLE. Their definition is qualitative rather + than quantitative, and refers to the general (usually weaker) capabilities of the machine. Mobile devices have a + similar set of weaknesses that are useful to generally categorize. The primary motivation is to avoid code that + tests for multiple mobile platforms on a line and needs to be updated every time we get a new one. + For example, mobile platforms tend to have weaker ARM processors, don't have full multiple processor support, + are hand-held, don't have mice (though may have touch screens or basic cursor controls), have writable solid + state permanent storage. Production user code shouldn't have too many expectations about the meaning of this define. + + EA_PLATFORM_DESKTOP + This is similar to EA_PLATFORM_MOBILE in its qualitative nature and refers to platforms that are powerful. + For example, they nearly always have virtual memory, mapped memory, hundreds of GB of writable disk storage, + TCP/IP network connections, mice, keyboards, 512+ MB of RAM, multiprocessing, multiple display support. + Production user code shouldn't have too many expectations about the meaning of this define. + + EA_PLATFORM_CONSOLE + This is similar to EA_PLATFORM_MOBILE in its qualitative nature and refers to platforms that are consoles. + This means platforms that are connected to TVs, are fairly powerful (especially graphics-wise), are tightly + controlled by vendors, tend not to have mapped memory, tend to have TCP/IP, don't have multiple process support + though they might have multiple CPUs, support TV output only. Production user code shouldn't have too many + expectations about the meaning of this define. + +*/ + + +#ifndef INCLUDED_eaplatform_H +#define INCLUDED_eaplatform_H + + +// Cygwin +// This is a pseudo-platform which will be defined along with EA_PLATFORM_LINUX when +// using the Cygwin build environment. +#if defined(__CYGWIN__) + #define EA_PLATFORM_CYGWIN 1 + #define EA_PLATFORM_DESKTOP 1 +#endif + +// MinGW +// This is a pseudo-platform which will be defined along with EA_PLATFORM_WINDOWS when +// using the MinGW Windows build environment. +#if defined(__MINGW32__) || defined(__MINGW64__) + #define EA_PLATFORM_MINGW 1 + #define EA_PLATFORM_DESKTOP 1 +#endif + +#if defined(EA_PLATFORM_PS4) || defined(__ORBIS__) || defined(EA_PLATFORM_KETTLE) + // PlayStation 4 + // Orbis was Sony's code-name for the platform, which is now obsolete. + // Kettle was an EA-specific code-name for the platform, which is now obsolete. + #if defined(EA_PLATFORM_PS4) + #undef EA_PLATFORM_PS4 + #endif + #define EA_PLATFORM_PS4 1 + + // Backward compatibility: + #if defined(EA_PLATFORM_KETTLE) + #undef EA_PLATFORM_KETTLE + #endif + // End backward compatbility + + #define EA_PLATFORM_KETTLE 1 + #define EA_PLATFORM_NAME "PS4" + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "PS4 on x64" + #define EA_PLATFORM_CONSOLE 1 + #define EA_PLATFORM_SONY 1 + #define EA_PLATFORM_POSIX 1 + // #define EA_POSIX_THREADS_AVAILABLE 1 // POSIX threading API is available but discouraged. Sony indicated use of the scePthreads* API is preferred. + #define EA_PROCESSOR_X86_64 1 + #if defined(__GNUC__) || defined(__clang__) + #define EA_ASM_STYLE_ATT 1 + #endif + +#elif defined(EA_PLATFORM_PS5) + + #define EA_PLATFORM_PS5 1 + #define EA_PLATFORM_NAME "PS5" + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "PS5 on x64" + #define EA_PLATFORM_CONSOLE 1 + #define EA_PLATFORM_SONY 1 + #define EA_PLATFORM_POSIX 1 + // #define EA_POSIX_THREADS_AVAILABLE 1 // POSIX threading API is available but discouraged. Sony indicated use of the scePthreads* API is preferred. + #define EA_PROCESSOR_X86_64 1 + #if defined(__GNUC__) || defined(__clang__) + #define EA_ASM_STYLE_ATT 1 + #endif + +#elif defined(EA_PLATFORM_XBOXONE) || defined(_DURANGO) || defined(_XBOX_ONE) || defined(EA_PLATFORM_CAPILANO) || defined(_GAMING_XBOX_XBOXONE) + // XBox One + // Durango was Microsoft's code-name for the platform, which is now obsolete. + // Microsoft uses _DURANGO instead of some variation of _XBOX, though it's not natively defined by the compiler. + // Capilano was an EA-specific code-name for the platform, which is now obsolete. + #if defined(EA_PLATFORM_XBOXONE) + #undef EA_PLATFORM_XBOXONE + #endif + #define EA_PLATFORM_XBOXONE 1 + + // Backward compatibility: + #if defined(EA_PLATFORM_CAPILANO) + #undef EA_PLATFORM_CAPILANO + #endif + #define EA_PLATFORM_CAPILANO 1 + #if defined(EA_PLATFORM_CAPILANO_XDK) && !defined(EA_PLATFORM_XBOXONE_XDK) + #define EA_PLATFORM_XBOXONE_XDK 1 + #endif + #if defined(EA_PLATFORM_CAPILANO_ADK) && !defined(EA_PLATFORM_XBOXONE_ADK) + #define EA_PLATFORM_XBOXONE_ADK 1 + #endif + // End backward compatibility + + #if !defined(_DURANGO) + #define _DURANGO + #endif + #define EA_PLATFORM_NAME "XBox One" + //#define EA_PROCESSOR_X86 Currently our policy is that we don't define this, even though x64 is something of a superset of x86. + #define EA_PROCESSOR_X86_64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "XBox One on x64" + #define EA_ASM_STYLE_INTEL 1 + #define EA_PLATFORM_CONSOLE 1 + #define EA_PLATFORM_MICROSOFT 1 + + // WINAPI_FAMILY defines - mirrored from winapifamily.h + #define EA_WINAPI_FAMILY_APP 1000 + #define EA_WINAPI_FAMILY_DESKTOP_APP 1001 + #define EA_WINAPI_FAMILY_PHONE_APP 1002 + #define EA_WINAPI_FAMILY_TV_APP 1003 + #define EA_WINAPI_FAMILY_TV_TITLE 1004 + #define EA_WINAPI_FAMILY_GAMES 1006 + + #if defined(WINAPI_FAMILY) + #include + #if defined(WINAPI_FAMILY_TV_TITLE) && WINAPI_FAMILY == WINAPI_FAMILY_TV_TITLE + #define EA_WINAPI_FAMILY EA_WINAPI_FAMILY_TV_TITLE + #elif defined(WINAPI_FAMILY_DESKTOP_APP) && WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP + #define EA_WINAPI_FAMILY EA_WINAPI_FAMILY_DESKTOP_APP + #elif defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES + #define EA_WINAPI_FAMILY EA_WINAPI_FAMILY_GAMES + #else + #error Unsupported WINAPI_FAMILY + #endif + #else + #error WINAPI_FAMILY should always be defined on Capilano. + #endif + + // Macro to determine if a partition is enabled. + #define EA_WINAPI_FAMILY_PARTITION(Partition) (Partition) + + #if EA_WINAPI_FAMILY == EA_WINAPI_FAMILY_DESKTOP_APP + #define EA_WINAPI_PARTITION_CORE 1 + #define EA_WINAPI_PARTITION_DESKTOP 1 + #define EA_WINAPI_PARTITION_APP 1 + #define EA_WINAPI_PARTITION_PC_APP 0 + #define EA_WIANPI_PARTITION_PHONE 0 + #define EA_WINAPI_PARTITION_TV_APP 0 + #define EA_WINAPI_PARTITION_TV_TITLE 0 + #define EA_WINAPI_PARTITION_GAMES 0 + #elif EA_WINAPI_FAMILY == EA_WINAPI_FAMILY_TV_TITLE + #define EA_WINAPI_PARTITION_CORE 1 + #define EA_WINAPI_PARTITION_DESKTOP 0 + #define EA_WINAPI_PARTITION_APP 0 + #define EA_WINAPI_PARTITION_PC_APP 0 + #define EA_WIANPI_PARTITION_PHONE 0 + #define EA_WINAPI_PARTITION_TV_APP 0 + #define EA_WINAPI_PARTITION_TV_TITLE 1 + #define EA_WINAPI_PARTITION_GAMES 0 + #elif EA_WINAPI_FAMILY == EA_WINAPI_FAMILY_GAMES + #define EA_WINAPI_PARTITION_CORE 1 + #define EA_WINAPI_PARTITION_DESKTOP 0 + #define EA_WINAPI_PARTITION_APP 0 + #define EA_WINAPI_PARTITION_PC_APP 0 + #define EA_WIANPI_PARTITION_PHONE 0 + #define EA_WINAPI_PARTITION_TV_APP 0 + #define EA_WINAPI_PARTITION_TV_TITLE 0 + #define EA_WINAPI_PARTITION_GAMES 1 + #else + #error Unsupported WINAPI_FAMILY + #endif + + #if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_GAMES) + #define EA_PLATFORM_GDK 1 + #define EA_PLATFORM_XBOX_GDK 1 + #endif + + #if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_TV_TITLE) + #define EA_PLATFORM_XBOXONE_XDK 1 + #endif +#elif defined(EA_PLATFORM_XBSX) || defined(_GAMING_XBOX_SCARLETT) + + #if !defined(_GAMING_XBOX_SCARLETT) + #define _GAMING_XBOX_SCARLETT + #endif + + #define EA_PLATFORM_GDK 1 + #define EA_PLATFORM_XBOX_GDK 1 + + #define EA_PLATFORM_NAME "XBox Series X" + #define EA_PROCESSOR_X86_64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "XBox Series X on x64" + #define EA_ASM_STYLE_INTEL 1 + #define EA_PLATFORM_CONSOLE 1 + #define EA_PLATFORM_MICROSOFT 1 + + // WINAPI_FAMILY defines - mirrored from winapifamily.h + #define EA_WINAPI_FAMILY_APP 1000 + #define EA_WINAPI_FAMILY_DESKTOP_APP 1001 + #define EA_WINAPI_FAMILY_PHONE_APP 1002 + #define EA_WINAPI_FAMILY_TV_APP 1003 + #define EA_WINAPI_FAMILY_TV_TITLE 1004 + #define EA_WINAPI_FAMILY_GAMES 1006 + + #if defined(WINAPI_FAMILY) + #include + #define EA_WINAPI_FAMILY EA_WINAPI_FAMILY_GAMES + #else + #error WINAPI_FAMILY should always be defined on Capilano. + #endif + + // Macro to determine if a partition is enabled. + #define EA_WINAPI_FAMILY_PARTITION(Partition) (Partition) + + #define EA_WINAPI_PARTITION_CORE 1 + #define EA_WINAPI_PARTITION_DESKTOP 0 + #define EA_WINAPI_PARTITION_APP 0 + #define EA_WINAPI_PARTITION_PC_APP 0 + #define EA_WIANPI_PARTITION_PHONE 0 + #define EA_WINAPI_PARTITION_TV_APP 0 + #define EA_WINAPI_PARTITION_TV_TITLE 0 + #define EA_WINAPI_PARTITION_GAMES 1 + +// Larrabee // This part to be removed once __LRB__ is supported by the Larrabee compiler in 2009. +#elif defined(EA_PLATFORM_LRB) || defined(__LRB__) || (defined(__EDG__) && defined(__ICC) && defined(__x86_64__)) + #undef EA_PLATFORM_LRB + #define EA_PLATFORM_LRB 1 + #define EA_PLATFORM_NAME "Larrabee" + #define EA_PLATFORM_DESCRIPTION "Larrabee on LRB1" + #define EA_PROCESSOR_X86_64 1 + #if defined(BYTE_ORDER) && (BYTE_ORDER == 4321) + #define EA_SYSTEM_BIG_ENDIAN 1 + #else + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #endif + #define EA_PROCESSOR_LRB 1 + #define EA_PROCESSOR_LRB1 1 // Larrabee version 1 + #define EA_ASM_STYLE_ATT 1 // Both types of asm style + #define EA_ASM_STYLE_INTEL 1 // are supported. + #define EA_PLATFORM_DESKTOP 1 + +// Android (Google phone OS) +#elif defined(EA_PLATFORM_ANDROID) || defined(__ANDROID__) + #undef EA_PLATFORM_ANDROID + #define EA_PLATFORM_ANDROID 1 + #define EA_PLATFORM_LINUX 1 + #define EA_PLATFORM_UNIX 1 + #define EA_PLATFORM_POSIX 1 + #define EA_PLATFORM_NAME "Android" + #define EA_ASM_STYLE_ATT 1 + #if defined(__arm__) + #define EA_ABI_ARM_LINUX 1 // a.k.a. "ARM eabi" + #define EA_PROCESSOR_ARM32 1 + #define EA_PLATFORM_DESCRIPTION "Android on ARM" + #elif defined(__aarch64__) + #define EA_PROCESSOR_ARM64 1 + #define EA_PLATFORM_DESCRIPTION "Android on ARM64" + #elif defined(__i386__) + #define EA_PROCESSOR_X86 1 + #define EA_PLATFORM_DESCRIPTION "Android on x86" + #elif defined(__x86_64) + #define EA_PROCESSOR_X86_64 1 + #define EA_PLATFORM_DESCRIPTION "Android on x64" + #else + #error Unknown processor + #endif + #if !defined(EA_SYSTEM_BIG_ENDIAN) && !defined(EA_SYSTEM_LITTLE_ENDIAN) + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #endif + #define EA_PLATFORM_MOBILE 1 + +// Samsung SMART TV - a Linux-based smart TV +#elif defined(EA_PLATFORM_SAMSUNG_TV) + #undef EA_PLATFORM_SAMSUNG_TV + #define EA_PLATFORM_SAMSUNG_TV 1 + #define EA_PLATFORM_LINUX 1 + #define EA_PLATFORM_UNIX 1 + #define EA_PLATFORM_POSIX 1 + #define EA_PLATFORM_NAME "SamsungTV" + #define EA_PLATFORM_DESCRIPTION "Samsung SMART TV on ARM" + #define EA_ASM_STYLE_ATT 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PROCESSOR_ARM32 1 + #define EA_ABI_ARM_LINUX 1 // a.k.a. "ARM eabi" + #define EA_PROCESSOR_ARM7 1 + +#elif defined(__APPLE__) && __APPLE__ + #include + + // Apple family of operating systems. + #define EA_PLATFORM_APPLE + #define EA_PLATFORM_POSIX 1 + + // iPhone + // TARGET_OS_IPHONE will be undefined on an unknown compiler, and will be defined on gcc. + #if defined(EA_PLATFORM_IPHONE) || defined(__IPHONE__) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || (defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR) + #undef EA_PLATFORM_IPHONE + #define EA_PLATFORM_IPHONE 1 + #define EA_PLATFORM_NAME "iPhone" + #define EA_ASM_STYLE_ATT 1 + #define EA_POSIX_THREADS_AVAILABLE 1 + #if defined(__arm__) + #define EA_ABI_ARM_APPLE 1 + #define EA_PROCESSOR_ARM32 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "iPhone on ARM" + #elif defined(__aarch64__) || defined(__AARCH64) + #define EA_ABI_ARM64_APPLE 1 + #define EA_PROCESSOR_ARM64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "iPhone on ARM64" + #elif defined(__i386__) + #define EA_PLATFORM_IPHONE_SIMULATOR 1 + #define EA_PROCESSOR_X86 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "iPhone simulator on x86" + #elif defined(__x86_64) || defined(__amd64) + #define EA_PROCESSOR_X86_64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "iPhone simulator on x64" + #else + #error Unknown processor + #endif + #define EA_PLATFORM_MOBILE 1 + + // Macintosh OSX + // TARGET_OS_MAC is defined by the Metrowerks and older AppleC compilers. + // Howerver, TARGET_OS_MAC is defined to be 1 in all cases. + // __i386__ and __intel__ are defined by the GCC compiler. + // __dest_os is defined by the Metrowerks compiler. + // __MACH__ is defined by the Metrowerks and GCC compilers. + // powerc and __powerc are defined by the Metrowerks and GCC compilers. + #elif defined(EA_PLATFORM_OSX) || defined(__MACH__) || (defined(__MSL__) && (__dest_os == __mac_os_x)) + #undef EA_PLATFORM_OSX + #define EA_PLATFORM_OSX 1 + #define EA_PLATFORM_UNIX 1 + #define EA_PLATFORM_POSIX 1 + //#define EA_PLATFORM_BSD 1 We don't currently define this. OSX has some BSD history but a lot of the API is different. + #define EA_PLATFORM_NAME "OSX" + #if defined(__i386__) || defined(__intel__) + #define EA_PROCESSOR_X86 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "OSX on x86" + #elif defined(__x86_64) || defined(__amd64) + #define EA_PROCESSOR_X86_64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "OSX on x64" + #elif defined(__arm__) + #define EA_ABI_ARM_APPLE 1 + #define EA_PROCESSOR_ARM32 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "OSX on ARM" + #elif defined(__aarch64__) || defined(__AARCH64) + #define EA_ABI_ARM64_APPLE 1 + #define EA_PROCESSOR_ARM64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "OSX on ARM64" + #elif defined(__POWERPC64__) || defined(__powerpc64__) + #define EA_PROCESSOR_POWERPC 1 + #define EA_PROCESSOR_POWERPC_64 1 + #define EA_SYSTEM_BIG_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "OSX on PowerPC 64" + #elif defined(__POWERPC__) || defined(__powerpc__) + #define EA_PROCESSOR_POWERPC 1 + #define EA_PROCESSOR_POWERPC_32 1 + #define EA_SYSTEM_BIG_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "OSX on PowerPC" + #else + #error Unknown processor + #endif + #if defined(__GNUC__) + #define EA_ASM_STYLE_ATT 1 + #else + #define EA_ASM_STYLE_MOTOROLA 1 + #endif + #define EA_PLATFORM_DESKTOP 1 + #else + #error Unknown Apple Platform + #endif + +// Linux +// __linux and __linux__ are defined by the GCC and Borland compiler. +// __i386__ and __intel__ are defined by the GCC compiler. +// __i386__ is defined by the Metrowerks compiler. +// _M_IX86 is defined by the Borland compiler. +// __sparc__ is defined by the GCC compiler. +// __powerpc__ is defined by the GCC compiler. +// __ARM_EABI__ is defined by GCC on an ARM v6l (Raspberry Pi 1) +// __ARM_ARCH_7A__ is defined by GCC on an ARM v7l (Raspberry Pi 2) +#elif defined(EA_PLATFORM_LINUX) || (defined(__linux) || defined(__linux__)) + #undef EA_PLATFORM_LINUX + #define EA_PLATFORM_LINUX 1 + #define EA_PLATFORM_UNIX 1 + #define EA_PLATFORM_POSIX 1 + #define EA_PLATFORM_NAME "Linux" + #if defined(__i386__) || defined(__intel__) || defined(_M_IX86) + #define EA_PROCESSOR_X86 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Linux on x86" + #elif defined(__ARM_ARCH_7A__) || defined(__ARM_EABI__) + #define EA_ABI_ARM_LINUX 1 + #define EA_PROCESSOR_ARM32 1 + #define EA_PLATFORM_DESCRIPTION "Linux on ARM 6/7 32-bits" + #elif defined(__aarch64__) || defined(__AARCH64) + #define EA_PROCESSOR_ARM64 1 + #define EA_PLATFORM_DESCRIPTION "Linux on ARM64" + #elif defined(__x86_64__) + #define EA_PROCESSOR_X86_64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Linux on x64" + #elif defined(__powerpc64__) + #define EA_PROCESSOR_POWERPC 1 + #define EA_PROCESSOR_POWERPC_64 1 + #define EA_SYSTEM_BIG_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Linux on PowerPC 64" + #elif defined(__powerpc__) + #define EA_PROCESSOR_POWERPC 1 + #define EA_PROCESSOR_POWERPC_32 1 + #define EA_SYSTEM_BIG_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Linux on PowerPC" + #else + #error Unknown processor + #error Unknown endianness + #endif + #if defined(__GNUC__) + #define EA_ASM_STYLE_ATT 1 + #endif + #define EA_PLATFORM_DESKTOP 1 + + +#elif defined(EA_PLATFORM_BSD) || (defined(__BSD__) || defined(__FreeBSD__)) + #undef EA_PLATFORM_BSD + #define EA_PLATFORM_BSD 1 + #define EA_PLATFORM_UNIX 1 + #define EA_PLATFORM_POSIX 1 // BSD's posix complaince is not identical to Linux's + #define EA_PLATFORM_NAME "BSD Unix" + #if defined(__i386__) || defined(__intel__) + #define EA_PROCESSOR_X86 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "BSD on x86" + #elif defined(__x86_64__) + #define EA_PROCESSOR_X86_64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "BSD on x64" + #elif defined(__powerpc64__) + #define EA_PROCESSOR_POWERPC 1 + #define EA_PROCESSOR_POWERPC_64 1 + #define EA_SYSTEM_BIG_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "BSD on PowerPC 64" + #elif defined(__powerpc__) + #define EA_PROCESSOR_POWERPC 1 + #define EA_PROCESSOR_POWERPC_32 1 + #define EA_SYSTEM_BIG_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "BSD on PowerPC" + #else + #error Unknown processor + #error Unknown endianness + #endif + #if !defined(EA_PLATFORM_FREEBSD) && defined(__FreeBSD__) + #define EA_PLATFORM_FREEBSD 1 // This is a variation of BSD. + #endif + #if defined(__GNUC__) + #define EA_ASM_STYLE_ATT 1 + #endif + #define EA_PLATFORM_DESKTOP 1 + + +#elif defined(EA_PLATFORM_WINDOWS_PHONE) + #undef EA_PLATFORM_WINDOWS_PHONE + #define EA_PLATFORM_WINDOWS_PHONE 1 + #define EA_PLATFORM_NAME "Windows Phone" + #if defined(_M_AMD64) || defined(_AMD64_) || defined(__x86_64__) + #define EA_PROCESSOR_X86_64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Windows Phone on x64" + #elif defined(_M_IX86) || defined(_X86_) + #define EA_PROCESSOR_X86 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Windows Phone on X86" + #elif defined(_M_ARM) + #define EA_ABI_ARM_WINCE 1 + #define EA_PROCESSOR_ARM32 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Windows Phone on ARM" + #else //Possibly other Windows Phone variants + #error Unknown processor + #error Unknown endianness + #endif + #define EA_PLATFORM_MICROSOFT 1 + + // WINAPI_FAMILY defines - mirrored from winapifamily.h + #define EA_WINAPI_FAMILY_APP 1 + #define EA_WINAPI_FAMILY_DESKTOP_APP 2 + #define EA_WINAPI_FAMILY_PHONE_APP 3 + + #if defined(WINAPI_FAMILY) + #include + #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP + #define EA_WINAPI_FAMILY EA_WINAPI_FAMILY_PHONE_APP + #else + #error Unsupported WINAPI_FAMILY for Windows Phone + #endif + #else + #error WINAPI_FAMILY should always be defined on Windows Phone. + #endif + + // Macro to determine if a partition is enabled. + #define EA_WINAPI_FAMILY_PARTITION(Partition) (Partition) + + // Enable the appropriate partitions for the current family + #if EA_WINAPI_FAMILY == EA_WINAPI_FAMILY_PHONE_APP + # define EA_WINAPI_PARTITION_CORE 1 + # define EA_WINAPI_PARTITION_PHONE 1 + # define EA_WINAPI_PARTITION_APP 1 + #else + # error Unsupported WINAPI_FAMILY for Windows Phone + #endif + + +// Windows +// _WIN32 is defined by the VC++, Intel and GCC compilers. +// _WIN64 is defined by the VC++, Intel and GCC compilers. +// __WIN32__ is defined by the Borland compiler. +// __INTEL__ is defined by the Metrowerks compiler. +// _M_IX86, _M_AMD64 and _M_IA64 are defined by the VC++, Intel, and Borland compilers. +// _X86_, _AMD64_, and _IA64_ are defined by the Metrowerks compiler. +// _M_ARM is defined by the VC++ compiler. +#elif (defined(EA_PLATFORM_WINDOWS) || (defined(_WIN32) || defined(__WIN32__) || defined(_WIN64))) && !defined(_XBOX) + #undef EA_PLATFORM_WINDOWS + #define EA_PLATFORM_WINDOWS 1 + #define EA_PLATFORM_NAME "Windows" + #ifdef _WIN64 // VC++ defines both _WIN32 and _WIN64 when compiling for Win64. + #define EA_PLATFORM_WIN64 1 + #else + #define EA_PLATFORM_WIN32 1 + #endif + #if defined(_M_AMD64) || defined(_AMD64_) || defined(__x86_64__) + #define EA_PROCESSOR_X86_64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Windows on x64" + #elif defined(_M_IX86) || defined(_X86_) + #define EA_PROCESSOR_X86 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Windows on X86" + #elif defined(_M_IA64) || defined(_IA64_) + #define EA_PROCESSOR_IA64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Windows on IA-64" + #elif defined(_M_ARM) + #define EA_ABI_ARM_WINCE 1 + #define EA_PROCESSOR_ARM32 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Windows on ARM" + #elif defined(_M_ARM64) + #define EA_PROCESSOR_ARM64 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "Windows on ARM64" + #else //Possibly other Windows CE variants + #error Unknown processor + #error Unknown endianness + #endif + #if defined(__GNUC__) + #define EA_ASM_STYLE_ATT 1 + #elif defined(_MSC_VER) || defined(__BORLANDC__) || defined(__ICL) + #define EA_ASM_STYLE_INTEL 1 + #endif + #define EA_PLATFORM_DESKTOP 1 + #define EA_PLATFORM_MICROSOFT 1 + + // WINAPI_FAMILY defines to support Windows 8 Metro Apps - mirroring winapifamily.h in the Windows 8 SDK + #define EA_WINAPI_FAMILY_APP 1000 + #define EA_WINAPI_FAMILY_DESKTOP_APP 1001 + #define EA_WINAPI_FAMILY_GAMES 1006 + + #if defined(WINAPI_FAMILY) + #if defined(_MSC_VER) + #pragma warning(push, 0) + #endif + #include + #if defined(_MSC_VER) + #pragma warning(pop) + #endif + #if defined(WINAPI_FAMILY_DESKTOP_APP) && WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP + #define EA_WINAPI_FAMILY EA_WINAPI_FAMILY_DESKTOP_APP + #elif defined(WINAPI_FAMILY_APP) && WINAPI_FAMILY == WINAPI_FAMILY_APP + #define EA_WINAPI_FAMILY EA_WINAPI_FAMILY_APP + #elif defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES + #define EA_WINAPI_FAMILY EA_WINAPI_FAMILY_GAMES + #else + #error Unsupported WINAPI_FAMILY + #endif + #else + #define EA_WINAPI_FAMILY EA_WINAPI_FAMILY_DESKTOP_APP + #endif + + #define EA_WINAPI_PARTITION_DESKTOP 1 + #define EA_WINAPI_PARTITION_APP 1 + #define EA_WINAPI_PARTITION_GAMES (EA_WINAPI_FAMILY == EA_WINAPI_FAMILY_GAMES) + + #define EA_WINAPI_FAMILY_PARTITION(Partition) (Partition) + + // EA_PLATFORM_WINRT + // This is a subset of Windows which is used for tablets and the "Metro" (restricted) Windows user interface. + // WinRT doesn't doesn't have access to the Windows "desktop" API, but WinRT can nevertheless run on + // desktop computers in addition to tablets. The Windows Phone API is a subset of WinRT and is not included + // in it due to it being only a part of the API. + #if defined(__cplusplus_winrt) + #define EA_PLATFORM_WINRT 1 + #endif + +// Sun (Solaris) +// __SUNPRO_CC is defined by the Sun compiler. +// __sun is defined by the GCC compiler. +// __i386 is defined by the Sun and GCC compilers. +// __sparc is defined by the Sun and GCC compilers. +#elif defined(EA_PLATFORM_SUN) || (defined(__SUNPRO_CC) || defined(__sun)) + #undef EA_PLATFORM_SUN + #define EA_PLATFORM_SUN 1 + #define EA_PLATFORM_UNIX 1 + #define EA_PLATFORM_POSIX 1 + #define EA_PLATFORM_NAME "SUN" + #if defined(__i386) + #define EA_PROCESSOR_X86 1 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "SUN on x86" + #elif defined(__sparc) + #define EA_PROCESSOR_SPARC 1 + #define EA_SYSTEM_BIG_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "SUN on Sparc" + #else + #error Unknown processor + #error Unknown endianness + #endif + #define EA_PLATFORM_DESKTOP 1 + +#elif defined(EA_PLATFORM_NX) || defined(__NX) + #if defined(EA_PLATFORM_NX) + #undef EA_PLATFORM_NX + #endif + #define EA_PLATFORM_NX 1 + #define EA_PLATFORM_NAME "NX" + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_CONSOLE 1 + #define EA_PLATFORM_NINTENDO 1 + #define EA_PLATFORM_POSIX 1 + #define EA_POSIX_THREADS_AVAILABLE 1 + + #if defined(__aarch64__) + #define EA_PROCESSOR_ARM64 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "NX on ARM64" + #else + #define EA_PROCESSOR_ARM32 + #define EA_PROCESSOR_ARM7 + #define EA_SYSTEM_LITTLE_ENDIAN 1 + #define EA_PLATFORM_DESCRIPTION "NX on ARM" + #endif + + #if defined(__GNUC__) || defined(__clang__) + #define EA_ASM_STYLE_ATT 1 + #endif + +#else + #error Unknown platform + #error Unknown processor + #error Unknown endianness +#endif + +#ifndef EA_PROCESSOR_ARM + #if defined(EA_PROCESSOR_ARM32) || defined(EA_PROCESSOR_ARM64) || defined(EA_PROCESSOR_ARM7) + #define EA_PROCESSOR_ARM + #endif +#endif + +// EA_PLATFORM_PTR_SIZE +// Platform pointer size; same as sizeof(void*). +// This is not the same as sizeof(int), as int is usually 32 bits on +// even 64 bit platforms. +// +// _WIN64 is defined by Win64 compilers, such as VC++. +// _M_IA64 is defined by VC++ and Intel compilers for IA64 processors. +// __LP64__ is defined by HP compilers for the LP64 standard. +// _LP64 is defined by the GCC and Sun compilers for the LP64 standard. +// __ia64__ is defined by the GCC compiler for IA64 processors. +// __arch64__ is defined by the Sparc compiler for 64 bit processors. +// __mips64__ is defined by the GCC compiler for MIPS processors. +// __powerpc64__ is defined by the GCC compiler for PowerPC processors. +// __64BIT__ is defined by the AIX compiler for 64 bit processors. +// __sizeof_ptr is defined by the ARM compiler (armcc, armcpp). +// +#ifndef EA_PLATFORM_PTR_SIZE + #if defined(__WORDSIZE) // Defined by some variations of GCC. + #define EA_PLATFORM_PTR_SIZE ((__WORDSIZE) / 8) + #elif defined(_WIN64) || defined(__LP64__) || defined(_LP64) || defined(_M_IA64) || defined(__ia64__) || defined(__arch64__) || defined(__aarch64__) || defined(__mips64__) || defined(__64BIT__) || defined(__Ptr_Is_64) + #define EA_PLATFORM_PTR_SIZE 8 + #elif defined(__CC_ARM) && (__sizeof_ptr == 8) + #define EA_PLATFORM_PTR_SIZE 8 + #else + #define EA_PLATFORM_PTR_SIZE 4 + #endif +#endif + + + +// EA_PLATFORM_WORD_SIZE +// This defines the size of a machine word. This will be the same as +// the size of registers on the machine but not necessarily the same +// as the size of pointers on the machine. A number of 64 bit platforms +// have 64 bit registers but 32 bit pointers. +// +#ifndef EA_PLATFORM_WORD_SIZE + #define EA_PLATFORM_WORD_SIZE EA_PLATFORM_PTR_SIZE +#endif + +// EA_PLATFORM_MIN_MALLOC_ALIGNMENT +// This defines the minimal alignment that the platform's malloc +// implementation will return. This should be used when writing custom +// allocators to ensure that the alignment matches that of malloc +#ifndef EA_PLATFORM_MIN_MALLOC_ALIGNMENT + #if defined(EA_PLATFORM_APPLE) + #define EA_PLATFORM_MIN_MALLOC_ALIGNMENT 16 + #elif defined(EA_PLATFORM_ANDROID) && defined(EA_PROCESSOR_ARM) + #define EA_PLATFORM_MIN_MALLOC_ALIGNMENT 8 + #elif defined(EA_PLATFORM_ANDROID) && defined(EA_PROCESSOR_X86_64) + #define EA_PLATFORM_MIN_MALLOC_ALIGNMENT 8 + #else + #define EA_PLATFORM_MIN_MALLOC_ALIGNMENT (EA_PLATFORM_PTR_SIZE * 2) + #endif +#endif + + +// EA_MISALIGNED_SUPPORT_LEVEL +// Specifies if the processor can read and write built-in types that aren't +// naturally aligned. +// 0 - not supported. Likely causes an exception. +// 1 - supported but slow. +// 2 - supported and fast. +// +#ifndef EA_MISALIGNED_SUPPORT_LEVEL + #if defined(EA_PROCESSOR_X86_64) + #define EA_MISALIGNED_SUPPORT_LEVEL 2 + #else + #define EA_MISALIGNED_SUPPORT_LEVEL 0 + #endif +#endif + +// Macro to determine if a Windows API partition is enabled. Always false on non Microsoft platforms. +#if !defined(EA_WINAPI_FAMILY_PARTITION) + #define EA_WINAPI_FAMILY_PARTITION(Partition) (0) +#endif + + +// EA_CACHE_LINE_SIZE +// Specifies the cache line size broken down by compile target. +// This the expected best guess values for the targets that we can make at compilation time. + +#ifndef EA_CACHE_LINE_SIZE + #if defined(EA_PROCESSOR_X86) + #define EA_CACHE_LINE_SIZE 32 // This is the minimum possible value. + #elif defined(EA_PROCESSOR_X86_64) + #define EA_CACHE_LINE_SIZE 64 // This is the minimum possible value + #elif defined(EA_PROCESSOR_ARM32) + #define EA_CACHE_LINE_SIZE 32 // This varies between implementations and is usually 32 or 64. + #elif defined(EA_PROCESSOR_ARM64) + #define EA_CACHE_LINE_SIZE 64 // Cache line Cortex-A8 (64 bytes) http://shervinemami.info/armAssembly.html however this remains to be mostly an assumption at this stage + #elif (EA_PLATFORM_WORD_SIZE == 4) + #define EA_CACHE_LINE_SIZE 32 // This is the minimum possible value + #else + #define EA_CACHE_LINE_SIZE 64 // This is the minimum possible value + #endif +#endif + + +#endif // INCLUDED_eaplatform_H + + + + + + + + + diff --git a/src/thirdparty/ea/EABase/eabase.h b/src/thirdparty/ea/EABase/eabase.h new file mode 100644 index 00000000..7febdddf --- /dev/null +++ b/src/thirdparty/ea/EABase/eabase.h @@ -0,0 +1,1024 @@ +/*----------------------------------------------------------------------------- + * eabase.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *---------------------------------------------------------------------------*/ + + +#ifndef INCLUDED_eabase_H +#define INCLUDED_eabase_H + + +// Identify the compiler and declare the EA_COMPILER_xxxx defines +#include + +// Identify traits which this compiler supports, or does not support +#include + +// Identify the platform and declare the EA_xxxx defines +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +// Always include version.h for backwards compatibility. +#include + +// Define common SI unit macros +#include + + +// ------------------------------------------------------------------------ +// The C++ standard defines size_t as a built-in type. Some compilers are +// not standards-compliant in this respect, so we need an additional include. +// The case is similar with wchar_t under C++. + +#if defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_MSVC) || defined(EA_WCHAR_T_NON_NATIVE) || defined(EA_PLATFORM_SONY) + #if defined(EA_COMPILER_MSVC) + #pragma warning(push, 0) + #pragma warning(disable: 4265 4365 4836 4574) + #endif + #include + #if defined(EA_COMPILER_MSVC) + #pragma warning(pop) + #endif +#endif + +// ------------------------------------------------------------------------ +// Include stddef.h on Apple's clang compiler to ensure the ptrdiff_t type +// is defined. +#if defined(EA_COMPILER_CLANG) && defined(EA_PLATFORM_APPLE) + #include +#endif + +// ------------------------------------------------------------------------ +// Include assert.h on C11 supported compilers so we may allow static_assert usage +// http://en.cppreference.com/w/c/error/static_assert +// C11 standard(ISO / IEC 9899:2011) : +// 7.2/3 Diagnostics (p : 186) +#if !defined(__cplusplus) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201100L + #include +#endif + + +// ------------------------------------------------------------------------ +// By default, GCC defines NULL as ((void*)0), which is the +// C definition. This causes all sort of problems for C++ code, so it is +// worked around by undefining NULL. + +#if defined(NULL) + #undef NULL +#endif + + +// ------------------------------------------------------------------------ +// Define the NULL pointer. This is normally defined in , but we +// don't want to force a global dependency on that header, so the definition +// is duplicated here. + +#if defined(__cplusplus) + #define NULL 0 +#else + #define NULL ((void*)0) +#endif + + +// ------------------------------------------------------------------------ +// C98/99 Standard typedefs. From the ANSI ISO/IEC 9899 standards document +// Most recent versions of the gcc-compiler come with these defined in +// inttypes.h or stddef.h. Determining if they are predefined can be +// tricky, so we expect some problems on non-standard compilers + +//#if (defined(_INTTYPES_H) || defined(_INTTYPES_H_)) && !defined(PRId64) +// #error " was #included before eabase.h, but without __STDC_FORMAT_MACROS #defined. You must #include eabase.h or an equivalent before #including C99 headers, or you must define __STDC_FORMAT_MACRO before #including system headrs." +//#endif + +// ------------------------------------------------------------------------ +// We need to test this after we potentially include stddef.h, otherwise we +// would have put this into the compilertraits header. +#if !defined(EA_COMPILER_HAS_INTTYPES) && (!defined(_MSC_VER) || (_MSC_VER > 1500)) && (defined(EA_COMPILER_IS_C99) || defined(INT8_MIN) || defined(EA_COMPILER_HAS_C99_TYPES) || defined(_SN_STDINT_H)) + #define EA_COMPILER_HAS_INTTYPES +#endif + +#ifdef EA_COMPILER_HAS_INTTYPES // If the compiler supports inttypes... + // ------------------------------------------------------------------------ + // Include the stdint header to define and derive the required types. + // Additionally include inttypes.h as many compilers, including variations + // of GCC define things in inttypes.h that the C99 standard says goes + // in stdint.h. + // + // The C99 standard specifies that inttypes.h only define printf/scanf + // format macros if __STDC_FORMAT_MACROS is defined before #including + // inttypes.h. For consistency, we do that here. + #ifndef __STDC_FORMAT_MACROS + #define __STDC_FORMAT_MACROS + #endif + // The GCC PSP compiler defines standard int types (e.g. uint32_t) but not PRId8, etc. + // MSVC added support for inttypes.h header in VS2013. + #if !defined(EA_COMPILER_MSVC) || (defined(EA_COMPILER_MSVC) && EA_COMPILER_VERSION >= 1800) + #include // PRId8, SCNd8, etc. + #endif + #if defined(_MSC_VER) + #pragma warning(push, 0) + #endif + #include // int32_t, INT64_C, UINT8_MAX, etc. + #include // float_t, double_t, etc. + #include // FLT_EVAL_METHOD. + #if defined(_MSC_VER) + #pragma warning(pop) + #endif + + #if !defined(FLT_EVAL_METHOD) && (defined(__FLT_EVAL_METHOD__) || defined(_FEVAL)) // GCC 3.x defines __FLT_EVAL_METHOD__ instead of the C99 standard FLT_EVAL_METHOD. + #ifdef __FLT_EVAL_METHOD__ + #define FLT_EVAL_METHOD __FLT_EVAL_METHOD__ + #else + #define FLT_EVAL_METHOD _FEVAL + #endif + #endif + + // MinGW GCC (up to at least v4.3.0-20080502) mistakenly neglects to define float_t and double_t. + // This appears to be an acknowledged bug as of March 2008 and is scheduled to be fixed. + // Similarly, Android uses a mix of custom standard library headers which prior to SDK API level 21 + // don't define float_t and double_t. + #if defined(__MINGW32__) || (defined(EA_PLATFORM_ANDROID) && !(defined(EA_ANDROID_SDK_LEVEL) && EA_ANDROID_SDK_LEVEL >= 21)) + #if defined(__FLT_EVAL_METHOD__) + #if(__FLT_EVAL_METHOD__== 0) + typedef float float_t; + typedef double double_t; + #elif(__FLT_EVAL_METHOD__ == 1) + typedef double float_t; + typedef double double_t; + #elif(__FLT_EVAL_METHOD__ == 2) + typedef long double float_t; + typedef long double double_t; + #endif + #else + typedef float float_t; + typedef double double_t; + #endif + #endif + + // The CodeSourcery definitions of PRIxPTR and SCNxPTR are broken for 32 bit systems. + #if defined(__SIZEOF_SIZE_T__) && (__SIZEOF_SIZE_T__ == 4) && (defined(__have_long64) || defined(__have_longlong64)) + #undef PRIdPTR + #define PRIdPTR "d" + #undef PRIiPTR + #define PRIiPTR "i" + #undef PRIoPTR + #define PRIoPTR "o" + #undef PRIuPTR + #define PRIuPTR "u" + #undef PRIxPTR + #define PRIxPTR "x" + #undef PRIXPTR + #define PRIXPTR "X" + + #undef SCNdPTR + #define SCNdPTR "d" + #undef SCNiPTR + #define SCNiPTR "i" + #undef SCNoPTR + #define SCNoPTR "o" + #undef SCNuPTR + #define SCNuPTR "u" + #undef SCNxPTR + #define SCNxPTR "x" + #endif +#else // else we must implement types ourselves. + + #if !defined(__BIT_TYPES_DEFINED__) && !defined(__int8_t_defined) + typedef signed char int8_t; //< 8 bit signed integer + #endif + #if !defined( __int8_t_defined ) + typedef signed short int16_t; //< 16 bit signed integer + typedef signed int int32_t; //< 32 bit signed integer. This works for both 32 bit and 64 bit platforms, as we assume the LP64 is followed. + #define __int8_t_defined + #endif + typedef unsigned char uint8_t; //< 8 bit unsigned integer + typedef unsigned short uint16_t; //< 16 bit unsigned integer + #if !defined( __uint32_t_defined ) + typedef unsigned int uint32_t; //< 32 bit unsigned integer. This works for both 32 bit and 64 bit platforms, as we assume the LP64 is followed. + #define __uint32_t_defined + #endif + + // According to the C98/99 standard, FLT_EVAL_METHOD defines control the + // width used for floating point _t types. + #if defined(_MSC_VER) && _MSC_VER >= 1800 + // MSVC's math.h provides float_t, double_t under this condition. + #elif defined(FLT_EVAL_METHOD) + #if (FLT_EVAL_METHOD == 0) + typedef float float_t; + typedef double double_t; + #elif (FLT_EVAL_METHOD == 1) + typedef double float_t; + typedef double double_t; + #elif (FLT_EVAL_METHOD == 2) + typedef long double float_t; + typedef long double double_t; + #endif + #endif + + #if defined(EA_PLATFORM_SUN) || defined(EA_PLATFORM_SGI) + #if (EA_PLATFORM_PTR_SIZE == 4) + typedef signed long long int64_t; + typedef unsigned long long uint64_t; + #else + typedef signed long int64_t; + typedef unsigned long uint64_t; + #endif + + #elif defined(EA_COMPILER_MSVC) + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; + + #else + typedef signed long long int64_t; + typedef unsigned long long uint64_t; + #endif +#endif + + +// ------------------------------------------------------------------------ +// macros for declaring constants in a portable way. +// +// e.g. int64_t x = INT64_C(1234567812345678); +// e.g. int64_t x = INT64_C(0x1111111122222222); +// e.g. uint64_t x = UINT64_C(0x1111111122222222); +// +// Microsoft VC++'s definitions of INT8_C/UINT8_C/INT16_C/UINT16_C are like so: +// #define INT8_C(x) (x) +// #define INT16_C(x) (x) +// #define UINT8_C(x) (x) +// #define UINT16_C(x) (x) +// To consider: undefine Microsoft's and use the casting versions below. +// ------------------------------------------------------------------------ + +#ifndef INT8_C_DEFINED // If the user hasn't already defined these... + #define INT8_C_DEFINED + + #ifndef INT8_C + #define INT8_C(x) int8_t(x) // For the majority of compilers and platforms, long is 32 bits and long long is 64 bits. + #endif + #ifndef UINT8_C + #define UINT8_C(x) uint8_t(x) + #endif + #ifndef INT16_C + #define INT16_C(x) int16_t(x) + #endif + #ifndef UINT16_C + #define UINT16_C(x) uint16_t(x) // Possibly we should make this be uint16_t(x##u). Let's see how compilers react before changing this. + #endif + #ifndef INT32_C + #define INT32_C(x) x##L + #endif + #ifndef UINT32_C + #define UINT32_C(x) x##UL + #endif + #ifndef INT64_C + #define INT64_C(x) x##LL // The way to deal with this is to compare ULONG_MAX to 0xffffffff and if not equal, then remove the L. + #endif + #ifndef UINT64_C + #define UINT64_C(x) x##ULL // We need to follow a similar approach for LL. + #endif + #ifndef UINTMAX_C + #define UINTMAX_C(x) UINT64_C(x) + #endif +#endif + +// ------------------------------------------------------------------------ +// type sizes +#ifndef INT8_MAX_DEFINED // If the user hasn't already defined these... + #define INT8_MAX_DEFINED + + // The value must be 2^(n-1)-1 + #ifndef INT8_MAX + #define INT8_MAX 127 + #endif + #ifndef INT16_MAX + #define INT16_MAX 32767 + #endif + #ifndef INT32_MAX + #define INT32_MAX 2147483647 + #endif + #ifndef INT64_MAX + #define INT64_MAX INT64_C(9223372036854775807) + #endif + #ifndef INTMAX_MAX + #define INTMAX_MAX INT64_MAX + #endif + #ifndef INTPTR_MAX + #if EA_PLATFORM_PTR_SIZE == 4 + #define INTPTR_MAX INT32_MAX + #else + #define INTPTR_MAX INT64_MAX + #endif + #endif + + // The value must be either -2^(n-1) or 1-2(n-1). + #ifndef INT8_MIN + #define INT8_MIN -128 + #endif + #ifndef INT16_MIN + #define INT16_MIN -32768 + #endif + #ifndef INT32_MIN + #define INT32_MIN (-INT32_MAX - 1) // -2147483648 + #endif + #ifndef INT64_MIN + #define INT64_MIN (-INT64_MAX - 1) // -9223372036854775808 + #endif + #ifndef INTMAX_MIN + #define INTMAX_MIN INT64_MIN + #endif + #ifndef INTPTR_MIN + #if EA_PLATFORM_PTR_SIZE == 4 + #define INTPTR_MIN INT32_MIN + #else + #define INTPTR_MIN INT64_MIN + #endif + #endif + + // The value must be 2^n-1 + #ifndef UINT8_MAX + #define UINT8_MAX 0xffU // 255 + #endif + #ifndef UINT16_MAX + #define UINT16_MAX 0xffffU // 65535 + #endif + #ifndef UINT32_MAX + #define UINT32_MAX UINT32_C(0xffffffff) // 4294967295 + #endif + #ifndef UINT64_MAX + #define UINT64_MAX UINT64_C(0xffffffffffffffff) // 18446744073709551615 + #endif + #ifndef UINTMAX_MAX + #define UINTMAX_MAX UINT64_MAX + #endif + #ifndef UINTPTR_MAX + #if EA_PLATFORM_PTR_SIZE == 4 + #define UINTPTR_MAX UINT32_MAX + #else + #define UINTPTR_MAX UINT64_MAX + #endif + #endif +#endif + +#ifndef FLT_EVAL_METHOD + #define FLT_EVAL_METHOD 0 + typedef float float_t; + typedef double double_t; +#endif + +#if defined(EA_COMPILER_HAS_INTTYPES) && (!defined(EA_COMPILER_MSVC) || (defined(EA_COMPILER_MSVC) && EA_COMPILER_VERSION >= 1800)) + #define EA_COMPILER_HAS_C99_FORMAT_MACROS +#endif + +#ifndef EA_COMPILER_HAS_C99_FORMAT_MACROS + // ------------------------------------------------------------------------ + // sized printf and scanf format specifiers + // See the C99 standard, section 7.8.1 -- Macros for format specifiers. + // + // The C99 standard specifies that inttypes.h only define printf/scanf + // format macros if __STDC_FORMAT_MACROS is defined before #including + // inttypes.h. For consistency, we define both __STDC_FORMAT_MACROS and + // the printf format specifiers here. We also skip the "least/most" + // variations of these specifiers, as we've decided to do so with + // basic types. + // + // For 64 bit systems, we assume the LP64 standard is followed + // (as opposed to ILP64, etc.) For 32 bit systems, we assume the + // ILP32 standard is followed. See: + // http://www.opengroup.org/public/tech/aspen/lp64_wp.htm + // for information about this. Thus, on both 32 and 64 bit platforms, + // %l refers to 32 bit data while %ll refers to 64 bit data. + + #ifndef __STDC_FORMAT_MACROS + #define __STDC_FORMAT_MACROS + #endif + + #if defined(EA_COMPILER_MSVC) // VC++ 7.1+ understands long long as a data type but doesn't accept %ll as a printf specifier. + #define EA_PRI_64_LENGTH_SPECIFIER "I64" + #define EA_SCN_64_LENGTH_SPECIFIER "I64" + #else + #define EA_PRI_64_LENGTH_SPECIFIER "ll" + #define EA_SCN_64_LENGTH_SPECIFIER "ll" + #endif // It turns out that some platforms use %q to represent a 64 bit value, but these are not relevant to us at this time. + + // Printf format specifiers + #if defined(EA_COMPILER_IS_C99) || defined(EA_COMPILER_GNUC) + #define PRId8 "hhd" + #define PRIi8 "hhi" + #define PRIo8 "hho" + #define PRIu8 "hhu" + #define PRIx8 "hhx" + #define PRIX8 "hhX" + #else // VC++, Borland, etc. which have no way to specify 8 bit values other than %c. + #define PRId8 "c" // This may not work properly but it at least will not crash. Try using 16 bit versions instead. + #define PRIi8 "c" // " + #define PRIo8 "o" // " + #define PRIu8 "u" // " + #define PRIx8 "x" // " + #define PRIX8 "X" // " + #endif + + #define PRId16 "hd" + #define PRIi16 "hi" + #define PRIo16 "ho" + #define PRIu16 "hu" + #define PRIx16 "hx" + #define PRIX16 "hX" + + #define PRId32 "d" // This works for both 32 bit and 64 bit systems, as we assume LP64 conventions. + #define PRIi32 "i" + #define PRIo32 "o" + #define PRIu32 "u" + #define PRIx32 "x" + #define PRIX32 "X" + + #define PRId64 EA_PRI_64_LENGTH_SPECIFIER "d" + #define PRIi64 EA_PRI_64_LENGTH_SPECIFIER "i" + #define PRIo64 EA_PRI_64_LENGTH_SPECIFIER "o" + #define PRIu64 EA_PRI_64_LENGTH_SPECIFIER "u" + #define PRIx64 EA_PRI_64_LENGTH_SPECIFIER "x" + #define PRIX64 EA_PRI_64_LENGTH_SPECIFIER "X" + + #if (EA_PLATFORM_PTR_SIZE == 4) + #define PRIdPTR PRId32 // Usage of pointer values will generate warnings with + #define PRIiPTR PRIi32 // some compilers because they are defined in terms of + #define PRIoPTR PRIo32 // integers. However, you can't simply use "p" because + #define PRIuPTR PRIu32 // 'p' is interpreted in a specific and often different + #define PRIxPTR PRIx32 // way by the library. + #define PRIXPTR PRIX32 + #elif (EA_PLATFORM_PTR_SIZE == 8) + #define PRIdPTR PRId64 + #define PRIiPTR PRIi64 + #define PRIoPTR PRIo64 + #define PRIuPTR PRIu64 + #define PRIxPTR PRIx64 + #define PRIXPTR PRIX64 + #endif + + // Scanf format specifiers + #if defined(EA_COMPILER_IS_C99) || defined(EA_COMPILER_GNUC) + #define SCNd8 "hhd" + #define SCNi8 "hhi" + #define SCNo8 "hho" + #define SCNu8 "hhu" + #define SCNx8 "hhx" + #else // VC++, Borland, etc. which have no way to specify 8 bit values other than %c. + #define SCNd8 "c" // This will not work properly but it at least will not crash. Try using 16 bit versions instead. + #define SCNi8 "c" // " + #define SCNo8 "c" // " + #define SCNu8 "c" // " + #define SCNx8 "c" // " + #endif + + #define SCNd16 "hd" + #define SCNi16 "hi" + #define SCNo16 "ho" + #define SCNu16 "hu" + #define SCNx16 "hx" + + #define SCNd32 "d" // This works for both 32 bit and 64 bit systems, as we assume LP64 conventions. + #define SCNi32 "i" + #define SCNo32 "o" + #define SCNu32 "u" + #define SCNx32 "x" + + #define SCNd64 EA_SCN_64_LENGTH_SPECIFIER "d" + #define SCNi64 EA_SCN_64_LENGTH_SPECIFIER "i" + #define SCNo64 EA_SCN_64_LENGTH_SPECIFIER "o" + #define SCNu64 EA_SCN_64_LENGTH_SPECIFIER "u" + #define SCNx64 EA_SCN_64_LENGTH_SPECIFIER "x" + + #if defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1900) + #define SCNdPTR PRIdPTR + #define SCNiPTR PRIiPTR + #define SCNoPTR PRIoPTR + #define SCNuPTR PRIuPTR + #define SCNxPTR PRIxPTR + #elif (EA_PLATFORM_PTR_SIZE == 4) + #define SCNdPTR SCNd32 // Usage of pointer values will generate warnings with + #define SCNiPTR SCNi32 // some compilers because they are defined in terms of + #define SCNoPTR SCNo32 // integers. However, you can't simply use "p" because + #define SCNuPTR SCNu32 // 'p' is interpreted in a specific and often different + #define SCNxPTR SCNx32 // way by the library. + #elif (EA_PLATFORM_PTR_SIZE == 8) + #define SCNdPTR SCNd64 + #define SCNiPTR SCNi64 + #define SCNoPTR SCNo64 + #define SCNuPTR SCNu64 + #define SCNxPTR SCNx64 + #endif +#endif + + +// ------------------------------------------------------------------------ +// bool8_t +// The definition of a bool8_t is controversial with some, as it doesn't +// act just like built-in bool. For example, you can assign -100 to it. +// +#ifndef BOOL8_T_DEFINED // If the user hasn't already defined this... + #define BOOL8_T_DEFINED + #if defined(EA_COMPILER_MSVC) || (defined(EA_COMPILER_INTEL) && defined(EA_PLATFORM_WINDOWS)) + #if defined(__cplusplus) + typedef bool bool8_t; + #else + typedef int8_t bool8_t; + #endif + #else // EA_COMPILER_GNUC generally uses 4 bytes per bool. + typedef int8_t bool8_t; + #endif +#endif + + +// ------------------------------------------------------------------------ +// intptr_t / uintptr_t +// Integer type guaranteed to be big enough to hold +// a native pointer ( intptr_t is defined in STDDEF.H ) +// +#if !defined(_INTPTR_T_DEFINED) && !defined(_intptr_t_defined) && !defined(EA_COMPILER_HAS_C99_TYPES) + #if (EA_PLATFORM_PTR_SIZE == 4) + typedef int32_t intptr_t; + #elif (EA_PLATFORM_PTR_SIZE == 8) + typedef int64_t intptr_t; + #endif + + #define _intptr_t_defined + #define _INTPTR_T_DEFINED +#endif + +#if !defined(_UINTPTR_T_DEFINED) && !defined(_uintptr_t_defined) && !defined(EA_COMPILER_HAS_C99_TYPES) + #if (EA_PLATFORM_PTR_SIZE == 4) + typedef uint32_t uintptr_t; + #elif (EA_PLATFORM_PTR_SIZE == 8) + typedef uint64_t uintptr_t; + #endif + + #define _uintptr_t_defined + #define _UINTPTR_T_DEFINED +#endif + +#if !defined(EA_COMPILER_HAS_INTTYPES) + #ifndef INTMAX_T_DEFINED + #define INTMAX_T_DEFINED + + // At this time, all supported compilers have int64_t as the max + // integer type. Some compilers support a 128 bit integer type, + // but in some cases it is not a true int128_t but rather a + // crippled data type. Also, it turns out that Unix 64 bit ABIs + // require that intmax_t be int64_t and nothing larger. So we + // play it safe here and set intmax_t to int64_t, even though + // an int128_t type may exist. + + typedef int64_t intmax_t; + typedef uint64_t uintmax_t; + #endif +#endif + + +// ------------------------------------------------------------------------ +// ssize_t +// signed equivalent to size_t. +// This is defined by GCC (except the QNX implementation of GCC) but not by other compilers. +// +#if !defined(__GNUC__) + // As of this writing, all non-GCC compilers significant to us implement + // uintptr_t the same as size_t. However, this isn't guaranteed to be + // so for all compilers, as size_t may be based on int, long, or long long. + #if !defined(_SSIZE_T_) && !defined(_SSIZE_T_DEFINED) + #define _SSIZE_T_ + #define _SSIZE_T_DEFINED + + #if defined(_MSC_VER) && (EA_PLATFORM_PTR_SIZE == 8) + typedef __int64 ssize_t; + #else + typedef long ssize_t; + #endif + #endif +#else + #include +#endif + + +// ------------------------------------------------------------------------ +// Character types +// +#if defined(EA_COMPILER_MSVC) + #if defined(EA_WCHAR_T_NON_NATIVE) + // In this case, wchar_t is not defined unless we include + // wchar.h or if the compiler makes it built-in. + #ifdef EA_COMPILER_MSVC + #pragma warning(push, 3) + #endif + #include + #ifdef EA_COMPILER_MSVC + #pragma warning(pop) + #endif + #endif +#endif + + +// ------------------------------------------------------------------------ +// char8_t -- Guaranteed to be equal to the compiler's char data type. +// Some compilers implement char8_t as unsigned, though char +// is usually set to be signed. +// +// char16_t -- This is set to be an unsigned 16 bit value. If the compiler +// has wchar_t as an unsigned 16 bit value, then char16_t is +// set to be the same thing as wchar_t in order to allow the +// user to use char16_t with standard wchar_t functions. +// +// char32_t -- This is set to be an unsigned 32 bit value. If the compiler +// has wchar_t as an unsigned 32 bit value, then char32_t is +// set to be the same thing as wchar_t in order to allow the +// user to use char32_t with standard wchar_t functions. +// +// EA_CHAR8_UNIQUE +// EA_CHAR16_NATIVE +// EA_CHAR32_NATIVE +// EA_WCHAR_UNIQUE +// +// VS2010 unilaterally defines char16_t and char32_t in its yvals.h header +// unless _HAS_CHAR16_T_LANGUAGE_SUPPORT or _CHAR16T are defined. +// However, VS2010 does not support the C++0x u"" and U"" string literals, +// which makes its definition of char16_t and char32_t somewhat useless. +// Until VC++ supports string literals, the build system should define +// _CHAR16T and let EABase define char16_t and EA_CHAR16. +// +// GCC defines char16_t and char32_t in the C compiler in -std=gnu99 mode, +// as __CHAR16_TYPE__ and __CHAR32_TYPE__, and for the C++ compiler +// in -std=c++0x and -std=gnu++0x modes, as char16_t and char32_t too. +// +// The EA_WCHAR_UNIQUE symbol is defined to 1 if wchar_t is distinct from +// char8_t, char16_t, and char32_t, and defined to 0 if not. In some cases, +// if the compiler does not support char16_t/char32_t, one of these two types +// is typically a typedef or define of wchar_t. For compilers that support +// the C++11 unicode character types often overloads must be provided to +// support existing code that passes a wide char string to a function that +// takes a unicode string. +// +// The EA_CHAR8_UNIQUE symbol is defined to 1 if char8_t is distinct type +// from char in the type system, and defined to 0 if otherwise. + +#if !defined(EA_CHAR16_NATIVE) + // To do: Change this to be based on EA_COMPILER_NO_NEW_CHARACTER_TYPES. + #if defined(_MSC_VER) && (_MSC_VER >= 1600) && defined(_HAS_CHAR16_T_LANGUAGE_SUPPORT) && _HAS_CHAR16_T_LANGUAGE_SUPPORT // VS2010+ + #define EA_CHAR16_NATIVE 1 + #elif defined(EA_COMPILER_CLANG) && defined(EA_COMPILER_CPP11_ENABLED) + #if __has_feature(cxx_unicode_literals) + #define EA_CHAR16_NATIVE 1 + #elif (EA_COMPILER_VERSION >= 300) && !(defined(EA_PLATFORM_IPHONE) || defined(EA_PLATFORM_OSX)) + #define EA_CHAR16_NATIVE 1 + #elif defined(EA_PLATFORM_APPLE) + #define EA_CHAR16_NATIVE 1 + #else + #define EA_CHAR16_NATIVE 0 + #endif + #elif defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 404) && defined(__CHAR16_TYPE__) && defined(EA_COMPILER_CPP11_ENABLED)// EDG 4.4+. + #define EA_CHAR16_NATIVE 1 + #elif defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4004) && !defined(EA_COMPILER_EDG) && (defined(EA_COMPILER_CPP11_ENABLED) || defined(__STDC_VERSION__)) // g++ (C++ compiler) 4.4+ with -std=c++0x or gcc (C compiler) 4.4+ with -std=gnu99 + #define EA_CHAR16_NATIVE 1 + #elif defined(EA_COMPILER_SN) && (__EDG_VERSION__ >= 408) && defined(EA_COMPILER_CPP11_ENABLED) + #define EA_CHAR16_NATIVE 1 + #else + #define EA_CHAR16_NATIVE 0 + #endif +#endif + +#if !defined(EA_CHAR32_NATIVE) // Microsoft currently ties char32_t language support to char16_t language support. So we use CHAR16_T here. + // To do: Change this to be based on EA_COMPILER_NO_NEW_CHARACTER_TYPES. + #if defined(_MSC_VER) && (_MSC_VER >= 1600) && defined(_HAS_CHAR16_T_LANGUAGE_SUPPORT) && _HAS_CHAR16_T_LANGUAGE_SUPPORT // VS2010+ + #define EA_CHAR32_NATIVE 1 + #elif defined(EA_COMPILER_CLANG) && defined(EA_COMPILER_CPP11_ENABLED) + #if __has_feature(cxx_unicode_literals) + #define EA_CHAR32_NATIVE 1 + #elif (EA_COMPILER_VERSION >= 300) && !(defined(EA_PLATFORM_IPHONE) || defined(EA_PLATFORM_OSX)) + #define EA_CHAR32_NATIVE 1 + #elif defined(EA_PLATFORM_APPLE) + #define EA_CHAR32_NATIVE 1 + #else + #define EA_CHAR32_NATIVE 0 + #endif + #elif defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 404) && defined(__CHAR32_TYPE__) && defined(EA_COMPILER_CPP11_ENABLED)// EDG 4.4+. + #define EA_CHAR32_NATIVE 1 + #elif defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4004) && !defined(EA_COMPILER_EDG) && (defined(EA_COMPILER_CPP11_ENABLED) || defined(__STDC_VERSION__)) // g++ (C++ compiler) 4.4+ with -std=c++0x or gcc (C compiler) 4.4+ with -std=gnu99 + #define EA_CHAR32_NATIVE 1 +#elif defined(EA_COMPILER_SN) && (__EDG_VERSION__ >= 408) && defined(EA_COMPILER_CPP11_ENABLED) + #define EA_CHAR32_NATIVE 1 + #else + #define EA_CHAR32_NATIVE 0 + #endif +#endif + + +#if EA_CHAR16_NATIVE || EA_CHAR32_NATIVE + #define EA_WCHAR_UNIQUE 1 +#else + #define EA_WCHAR_UNIQUE 0 +#endif + + +// EA_CHAR8_UNIQUE +// +// Check for char8_t support in the cpp type system. Moving forward from c++20, +// the char8_t type allows users to overload function for character encoding. +// +// EA_CHAR8_UNIQUE is 1 when the type is a unique in the type system and +// can there be used as a valid overload. EA_CHAR8_UNIQUE is 0 otherwise. +// +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0482r6.html +// +#ifdef __cpp_char8_t + #define CHAR8_T_DEFINED + #define EA_CHAR8_UNIQUE 1 +#else + #define EA_CHAR8_UNIQUE 0 +#endif + + +#ifndef CHAR8_T_DEFINED // If the user hasn't already defined these... + #define CHAR8_T_DEFINED + #if defined(EA_PLATFORM_APPLE) + #define char8_t char // The Apple debugger is too stupid to realize char8_t is typedef'd to char, so we #define it. + #else + typedef char char8_t; + #endif + + #if EA_CHAR16_NATIVE + // In C++, char16_t and char32_t are already defined by the compiler. + // In MS C, char16_t and char32_t are already defined by the compiler/standard library. + // In GCC C, __CHAR16_TYPE__ and __CHAR32_TYPE__ are defined instead, and we must define char16_t and char32_t from these. + #if defined(__GNUC__) && !defined(__GXX_EXPERIMENTAL_CXX0X__) && defined(__CHAR16_TYPE__) // If using GCC and compiling in C... + typedef __CHAR16_TYPE__ char16_t; + typedef __CHAR32_TYPE__ char32_t; + #endif + #elif (EA_WCHAR_SIZE == 2) + #if (defined(_MSC_VER) && (_MSC_VER >= 1600)) // if VS2010+ or using platforms that use Dinkumware under a compiler that doesn't natively support C++11 char16_t. + #if !defined(_CHAR16T) + #define _CHAR16T + #endif + #if !defined(_HAS_CHAR16_T_LANGUAGE_SUPPORT) || !_HAS_CHAR16_T_LANGUAGE_SUPPORT + typedef wchar_t char16_t; + typedef uint32_t char32_t; + #endif + #else + typedef wchar_t char16_t; + typedef uint32_t char32_t; + #endif + #else + typedef uint16_t char16_t; + #if defined(__cplusplus) + typedef wchar_t char32_t; + #else + typedef uint32_t char32_t; + #endif + #endif +#endif + + +// CHAR8_MIN, CHAR8_MAX, etc. +// +#define EA_LIMITS_DIGITS_S(T) ((sizeof(T) * 8) - 1) +#define EA_LIMITS_DIGITS_U(T) ((sizeof(T) * 8)) +#define EA_LIMITS_DIGITS(T) ((EA_LIMITS_IS_SIGNED(T) ? EA_LIMITS_DIGITS_S(T) : EA_LIMITS_DIGITS_U(T))) +#define EA_LIMITS_IS_SIGNED(T) ((T)(-1) < 0) +#define EA_LIMITS_MIN_S(T) ((T)((T)1 << EA_LIMITS_DIGITS_S(T))) +#define EA_LIMITS_MIN_U(T) ((T)0) +#define EA_LIMITS_MIN(T) ((EA_LIMITS_IS_SIGNED(T) ? EA_LIMITS_MIN_S(T) : EA_LIMITS_MIN_U(T))) +#define EA_LIMITS_MAX_S(T) ((T)(((((T)1 << (EA_LIMITS_DIGITS(T) - 1)) - 1) << 1) + 1)) +#define EA_LIMITS_MAX_U(T) ((T)~(T)0) +#define EA_LIMITS_MAX(T) ((EA_LIMITS_IS_SIGNED(T) ? EA_LIMITS_MAX_S(T) : EA_LIMITS_MAX_U(T))) + +#if !defined(CHAR8_MIN) + #define CHAR8_MIN EA_LIMITS_MIN(char8_t) +#endif + +#if !defined(CHAR8_MAX) + #define CHAR8_MAX EA_LIMITS_MAX(char8_t) +#endif + +#if !defined(CHAR16_MIN) + #define CHAR16_MIN EA_LIMITS_MIN(char16_t) +#endif + +#if !defined(CHAR16_MAX) + #define CHAR16_MAX EA_LIMITS_MAX(char16_t) +#endif + +#if !defined(CHAR32_MIN) + #define CHAR32_MIN EA_LIMITS_MIN(char32_t) +#endif + +#if !defined(CHAR32_MAX) + #define CHAR32_MAX EA_LIMITS_MAX(char32_t) +#endif + + + +// EA_CHAR8 / EA_CHAR16 / EA_CHAR32 / EA_WCHAR +// +// Supports usage of portable string constants. +// +// Example usage: +// const char16_t* str = EA_CHAR16("Hello world"); +// const char32_t* str = EA_CHAR32("Hello world"); +// const char16_t c = EA_CHAR16('\x3001'); +// const char32_t c = EA_CHAR32('\x3001'); +// +#ifndef EA_CHAR8 + #if EA_CHAR8_UNIQUE + #define EA_CHAR8(s) u8 ## s + #else + #define EA_CHAR8(s) s + #endif +#endif + +#ifndef EA_WCHAR + #define EA_WCHAR_(s) L ## s + #define EA_WCHAR(s) EA_WCHAR_(s) +#endif + +#ifndef EA_CHAR16 + #if EA_CHAR16_NATIVE && !defined(_MSC_VER) // Microsoft doesn't support char16_t string literals. + #define EA_CHAR16_(s) u ## s + #define EA_CHAR16(s) EA_CHAR16_(s) + #elif (EA_WCHAR_SIZE == 2) + #if defined(_MSC_VER) && (_MSC_VER >= 1900) && defined(__cplusplus) // VS2015 supports u"" string literals. + #define EA_CHAR16_(s) u ## s + #define EA_CHAR16(s) EA_CHAR16_(s) + #else + #define EA_CHAR16_(s) L ## s + #define EA_CHAR16(s) EA_CHAR16_(s) + #endif + #else + //#define EA_CHAR16(s) // Impossible to implement efficiently. + #endif +#endif + +#ifndef EA_CHAR32 + #if EA_CHAR32_NATIVE && !defined(_MSC_VER) // Microsoft doesn't support char32_t string literals. + #define EA_CHAR32_(s) U ## s + #define EA_CHAR32(s) EA_CHAR32_(s) + #elif (EA_WCHAR_SIZE == 2) + #if defined(_MSC_VER) && (_MSC_VER >= 1900) && defined(__cplusplus) // VS2015 supports u"" string literals. + #define EA_CHAR32_(s) U ## s + #define EA_CHAR32(s) EA_CHAR32_(s) + #else + //#define EA_CHAR32(s) // Impossible to implement. + #endif + #elif (EA_WCHAR_SIZE == 4) + #define EA_CHAR32_(s) L ## s + #define EA_CHAR32(s) EA_CHAR32_(s) + #else + #error Unexpected size of wchar_t + #endif +#endif + +// EAText8 / EAText16 +// +// Provided for backwards compatibility with older code. +// +#if defined(EABASE_ENABLE_EATEXT_MACROS) + #define EAText8(x) x + #define EAChar8(x) x + + #define EAText16(x) EA_CHAR16(x) + #define EAChar16(x) EA_CHAR16(x) +#endif + + + + +// ------------------------------------------------------------------------ +// EAArrayCount +// +// Returns the count of items in a built-in C array. This is a common technique +// which is often used to help properly calculate the number of items in an +// array at runtime in order to prevent overruns, etc. +// +// Example usage: +// int array[75]; +// size_t arrayCount = EAArrayCount(array); // arrayCount is 75. +// +#if defined(EA_COMPILER_NO_CONSTEXPR) + #ifndef EAArrayCount + #define EAArrayCount(x) (sizeof(x) / sizeof(x[0])) + #endif +#else + // This C++11 version is a little smarter than the macro version above; + // it can tell the difference between arrays and pointers. Other simpler + // templated versions have failed in various subtle ways. + + template + char (&EAArraySizeHelper(T (&x)[N]))[N]; + + template + char (&EAArraySizeHelper(T (&&x)[N]))[N]; + + #define EAArrayCount(x) (sizeof(EAArraySizeHelper(x))) +#endif + + +// ------------------------------------------------------------------------ +// static_assert +// +// C++11 static_assert (a.k.a. compile-time assert). +// +// Specification: +// void static_assert(bool const_expression, const char* description); +// +// Example usage: +// static_assert(sizeof(int) == 4, "int must be 32 bits"); +// +#if defined(_MSC_VER) && (_MSC_VER >= 1600) && defined(__cplusplus) + // static_assert is defined by the compiler for both C and C++. +#elif !defined(__cplusplus) && defined(EA_PLATFORM_ANDROID) && ((defined(__STDC_VERSION__) && __STDC_VERSION__ < 201100L) || !defined(__STDC_VERSION__)) + // AndroidNDK does not support static_assert despite claiming it's a C11 compiler + #define NEED_CUSTOM_STATIC_ASSERT +#elif defined(__clang__) && defined(__cplusplus) + // We need to separate these checks on a new line, as the pre-processor on other compilers will fail on the _has_feature macros + #if !(__has_feature(cxx_static_assert) || __has_extension(cxx_static_assert)) + #define NEED_CUSTOM_STATIC_ASSERT + #endif +#elif defined(__GNUC__) && (defined(__GXX_EXPERIMENTAL_CXX0X__) || (defined(__cplusplus) && (__cplusplus >= 201103L))) + // static_assert is defined by the compiler. +#elif defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 401) && defined(EA_COMPILER_CPP11_ENABLED) + // static_assert is defined by the compiler. +#elif !defined(__cplusplus) && defined(__GLIBC__) && defined(__USE_ISOC11) + // static_assert is defined by the compiler. +#elif !defined(__cplusplus) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201100L + // static_assert is defined by the compiler. +#else + #define NEED_CUSTOM_STATIC_ASSERT +#endif + +#ifdef NEED_CUSTOM_STATIC_ASSERT + #ifdef __GNUC__ + // On GCC the 'unused' attribute can be used to indicate a typedef is not actually used + // (such as in the static_assert implementation below). New versions of GCC generate + // warnings for unused typedefs in function/method scopes. + #define EA_STATIC_ASSERT_UNUSED_ATTRIBUTE __attribute__((unused)) + #else + #define EA_STATIC_ASSERT_UNUSED_ATTRIBUTE + #endif + #define EA_STATIC_ASSERT_TOKEN_PASTE(a,b) a ## b + #define EA_STATIC_ASSERT_CONCATENATE_HELPER(a,b) EA_STATIC_ASSERT_TOKEN_PASTE(a,b) + + #if defined(__COUNTER__) // If this extension is available, which allows multiple statements per line... + #define static_assert(expression, description) typedef char EA_STATIC_ASSERT_CONCATENATE_HELPER(compileTimeAssert,__COUNTER__) [((expression) != 0) ? 1 : -1] EA_STATIC_ASSERT_UNUSED_ATTRIBUTE + #else + #define static_assert(expression, description) typedef char EA_STATIC_ASSERT_CONCATENATE_HELPER(compileTimeAssert,__LINE__) [((expression) != 0) ? 1 : -1] EA_STATIC_ASSERT_UNUSED_ATTRIBUTE + #endif + + #undef NEED_CUSTOM_STATIC_ASSERT +#endif + +// ------------------------------------------------------------------------ +// EA_IS_ENABLED +// +// EA_IS_ENABLED is intended to be used for detecting if compile time features are enabled or disabled. +// +// It has some advantages over using a standard #if or #ifdef tests: +// 1) Fails to compile when passes numeric macro values. Valid options are strictly enabled or disabled. +// 2) Fails to compile when passed undefined macro values rather than disabling by default +// 3) Fails to compile when the passed macro is defined to but empty +// +// To use the macro, the calling code should create a define for the feature to enable or disable. This feature define +// must be set to either EA_ENABLED or EA_DISABLED. (Do not try to set the feature define directly to some other +// value.) +// +// Note: These macros are analogous to the Frostbite macro FB_USING used in combination with FB_OFF / FB_ON and are +// designed to be compatible to support gradual migration. +// +// Example usage: +// +// // The USER_PROVIDED_FEATURE_DEFINE should be defined as either +// // EA_ENABLED or EA_DISABLED. +// #define USER_PROVIDED_FEATURE_DEFINE EA_ENABLED +// +// #if EA_IS_ENABLED(USER_PROVIDED_FEATURE_DEFINE) +// // USER_PROVIDED_FEATURE_DEFINE is enabled +// #else +// // USER_PROVIDED_FEATURE_DEFINE is disabled +// #endif +// +#define EA_ENABLED 111- +#define EA_DISABLED 333- +// NOTE: Numeric values for x will produce a parse error while empty values produce a divide by zero, and the test is a bool for proper negation behavior +#define EA_IS_ENABLED(x) (333 == 333 * 111 / ((x 0) * (((x 0) == 333 ? 1 : 0) + ((x 0) == 111 ? 1 : 0)))) + + + +// Define int128_t / uint128_t types. +// NOTE(rparolin): include file at the end because we want all the signed integral types defined. +#ifdef __cplusplus + #include +#endif + +#endif // Header include guard + + + + diff --git a/src/thirdparty/ea/EABase/eadeprecated.h b/src/thirdparty/ea/EABase/eadeprecated.h new file mode 100644 index 00000000..aff3682d --- /dev/null +++ b/src/thirdparty/ea/EABase/eadeprecated.h @@ -0,0 +1,181 @@ +/*----------------------------------------------------------------------------- + * eadeprecatedbase.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *---------------------------------------------------------------------------*/ + +#pragma once +#ifndef INCLUDED_eadeprecated_H +#define INCLUDED_eadeprecated_H + +#include + + +// ------------------------------------------------------------------------ +// Documentation on deprecated attribute: https://en.cppreference.com/w/cpp/language/attributes/deprecated +// Documentation on SimVer version numbers: http://simver.org/ +// +// These macros provide a structured formatting to C++ deprecated annotation messages. This ensures +// that the required information is presented in a standard format for developers and tools. +// +// Example usage: +// +// Current package version : current_ver +// Future version for code removal : major_ver, minor_ver, change_ver +// Deprecation comment : "" +// +// EA_DEPRECATED(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated function") +// void TestFunc() {} +// +// EA_DEPRECATED(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated typedef") +// typedef int TestTypedef; +// +// EA_DEPRECATED(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated variable") +// int TestVariable; +// +// EA_DEPRECATED_STRUCT(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated struct") +// TestStruct {}; +// +// EA_DEPRECATED_CLASS(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated class") +// TestClass {}; +// +// union TestUnion +// { +// EA_DEPRECATED(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated data member") int n; +// }; +// +// EA_DEPRECATED_ENUM(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated enumeration") +// TestEnumeration { TestEnumeration_Value1, TestEnumeration_Value2 }; +// +// enum TestEnumerator +// { +// TestEnumerator_Value1 EA_DEPRECATED_ENUMVALUE(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated enum value") = 5, +// TestEnumerator_Value2 = 4 +// }; +// +// EA_DISABLE_DEPRECATED(current_ver, major_ver, minor_ver, change_ver, tag, "Suppress the deprecated warning until the given Release") +// void TestFunc() {} +// EA_RESTORE_DEPRECATED() +// + +// ------------------------------------------------------------------------ +// Create an integer version number which can be compared with numerical operators +#define EA_CREATE_VERSION(MAJOR, MINOR, PATCH) \ + (((MAJOR) * 1000000) + (((MINOR) + 1) * 10000) + (((PATCH) + 1) * 100)) + +// ------------------------------------------------------------------------ +// INTERNAL MACROS - DO NOT USE DIRECTLY +// +// When EA_DEPRECATED_API_EXPIRED_IS_ERROR this macro produce a static asset on code that is past the expiry date. + +#if defined(EA_DEPRECATED_API_EXPIRED_IS_ERROR) && EA_DEPRECATED_API_EXPIRED_IS_ERROR + #define EA_DEPRECATED_BEFORETYPE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation) \ + static_assert(_moduleVersion < EA_CREATE_VERSION(_major_version,_minor_version,_patch_version), "This API has been deprecated and needs to be removed"); +#else + #define EA_DEPRECATED_BEFORETYPE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation) +#endif + + +// ------------------------------------------------------------------------ +// INTERNAL MACROS - DO NOT USE DIRECTLY +// +// When EA_IGNORE_DEPRECATION is set deprecation annotation will not be produced + +#if defined(EA_IGNORE_DEPRECATION) && EA_IGNORE_DEPRECATION + #define EA_DEPRECATED_AFTERTYPE(_major_version, _minor_version, _patch_version, _annotation, _msg) +#else + #define EA_DEPRECATED_AFTERTYPE(_major_version, _minor_version, _patch_version, _annotation, _msg) \ + EA_DEPRECATED_MESSAGE(_msg. This API will be removed in _major_version._minor_version._patch_version _annotation) +#endif + +// ------------------------------------------------------------------------ +// INTERNAL MACROS - DO NOT USE DIRECTLY +// +// Simple case + +#define EA_DEPRECATED_SIMPLE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg) \ + EA_DEPRECATED_BEFORETYPE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation) \ + EA_DEPRECATED_AFTERTYPE(_major_version, _minor_version, _patch_version, _annotation, _msg) + + +// ------------------------------------------------------------------------ +// INTERNAL MACROS - DO NOT USE DIRECTLY +// +// Macro which inserts the keyword to correctly format the deprecation annotation +#define EA_DEPRECATED_TYPE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg, _keyword) \ + EA_DEPRECATED_BEFORETYPE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation) \ + _keyword \ + EA_DEPRECATED_AFTERTYPE(_major_version, _minor_version, _patch_version, _annotation, _msg) + + + +// ------------------------------------------------------------------------ +// PUBLIC MACROS +// See file header comment for example usage. + +// ------------------------------------------------------------------------ +// EA_DEPRECATED(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated function") +// void TestFunc() {} +// +// EA_DEPRECATED(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated typedef") +// typedef int TestTypedef; +// +// EA_DEPRECATED(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated variable") +// int TestVariable; + +#define EA_DEPRECATED_API(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg) \ + EA_STRINGIFY(EA_DEPRECATED_SIMPLE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg)) + + +// ------------------------------------------------------------------------ +// EA_DEPRECATED_STRUCT(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated struct") +// TestStruct {}; + +#define EA_DEPRECATED_STRUCT(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg) \ + EA_STRINGIFY(EA_DEPRECATED_TYPE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg, struct)) + + +// ------------------------------------------------------------------------ +// EA_DEPRECATED_CLASS(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated class") +// TestClass {}; + +#define EA_DEPRECATED_CLASS(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg) \ + EA_STRINGIFY(EA_DEPRECATED_TYPE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg, class)) + + +// ------------------------------------------------------------------------ +// EA_DEPRECATED_ENUM(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated enumeration") +// TestEnumeration { TestEnumeration_Value1, TestEnumeration_Value2 }; + +#define EA_DEPRECATED_ENUM(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg) \ + EA_STRINGIFY(EA_DEPRECATED_TYPE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg, enum)) + + +// ------------------------------------------------------------------------ +// enum TestEnumerator +// { +// TestEnumerator_Value1 EA_DEPRECATED_ENUMVALUE(current_ver, major_ver, minor_ver, change_ver, tag, "Do not use deprecated enum value") = 5, +// TestEnumerator_Value2 = 4 +// }; +#define EA_DEPRECATED_ENUMVALUE(_value, _moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg) \ + _value EA_STRINGIFY(EA_DEPRECATED_AFTERTYPE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation)) + + +// ------------------------------------------------------------------------ +// Suppress deprecated warnings around a block of code, see file comment for full usage. +// EA_DISABLE_DEPRECATED(current_ver, major_ver, minor_ver, change_ver, tag, "Suppress the deprecated warning until the given Release") + +#define EA_DISABLE_DEPRECATED(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation, _msg) \ + EA_STRINGIFY(EA_DEPRECATED_BEFORETYPE(_moduleVersion, _major_version, _minor_version, _patch_version, _annotation)) \ + EA_DISABLE_VC_WARNING(4996); \ + EA_DISABLE_CLANG_WARNING(-Wdeprecated-declarations); + +// ------------------------------------------------------------------------ +// Restore the compiler warnings +// EA_RESTORE_DEPRECATED() + +#define EA_RESTORE_DEPRECATED() \ + EA_RESTORE_CLANG_WARNING(); \ + EA_RESTORE_VC_WARNING(); + +#endif // Header include guard \ No newline at end of file diff --git a/src/thirdparty/ea/EABase/eahave.h b/src/thirdparty/ea/EABase/eahave.h new file mode 100644 index 00000000..b25e515b --- /dev/null +++ b/src/thirdparty/ea/EABase/eahave.h @@ -0,0 +1,881 @@ +/*----------------------------------------------------------------------------- + * eahave.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *---------------------------------------------------------------------------*/ + + +/*----------------------------------------------------------------------------- + This file's functionality is preliminary and won't be considered stable until + a future EABase version. + *---------------------------------------------------------------------------*/ + + +/*----------------------------------------------------------------------------- + This header identifies if the given facilities are available in the + standard build environment the current compiler/linker/standard library/ + operating system combination. This file may in some cases #include standard + headers in order to make availability determinations, such as to check + compiler or SDK version numbers. However, it cannot be perfect. + This header does not identify compiler features, as those are defined in + eacompiler.h and eacompilertraits.h. Rather this header is about library support. + This header does not identify platform or library conventions either, such + as whether the file paths use \ or / for directory separators. + + We provide three types of HAVE features here: + + - EA_HAVE_XXX_FEATURE - Have compiler feature. + Identifies if the compiler has or lacks some feature in the + current build. Sometimes you need to check to see if the + compiler is running in some mode in able to write portable code + against it. For example, some compilers (e.g. VC++) have a + mode in which all language extensions are disabled. If you want + to write code that works with that but still uses the extensions + when available then you can check #if defined(EA_HAVE_EXTENSIONS_FEATURE). + Features can be forcibly cancelled via EA_NO_HAVE_XXX_FEATURE. + EA_NO_HAVE is useful for a build system or user to override the + defaults because it happens to know better. + + - EA_HAVE_XXX_H - Have header file information. + Identifies if a given header file is available to the current + compile configuration. For example, some compilers provide a + malloc.h header, while others don't. For the former we define + EA_HAVE_MALLOC_H, while for the latter it remains undefined. + If a header is missing then it may still be that the functions + the header usually declares are declared in some other header. + EA_HAVE_XXX does not include the possibility that our own code + provides versions of these headers, and in fact a purpose of + EA_HAVE_XXX is to decide if we should be using our own because + the system doesn't provide one. + Header availability can be forcibly cancelled via EA_NO_HAVE_XXX_H. + EA_NO_HAVE is useful for a build system or user to override the + defaults because it happens to know better. + + - EA_HAVE_XXX_DECL - Have function declaration information. + Identifies if a given function declaration is provided by + the current compile configuration. For example, some compiler + standard libraries declare a wcslen function, while others + don't. For the former we define EA_HAVE_WCSLEN_DECL, while for + the latter it remains undefined. If a declaration of a function + is missing then we assume the implementation is missing as well. + EA_HAVE_XXX_DECL does not include the possibility that our + own code provides versions of these declarations, and in fact a + purpose of EA_HAVE_XXX_DECL is to decide if we should be using + our own because the system doesn't provide one. + Declaration availability can be forcibly cancelled via EA_NO_HAVE_XXX_DECL. + EA_NO_HAVE is useful for a build system or user to override the + defaults because it happens to know better. + + - EA_HAVE_XXX_IMPL - Have function implementation information. + Identifies if a given function implementation is provided by + the current compile and link configuration. For example, it's + commonly the case that console platforms declare a getenv function + but don't provide a linkable implementation. + In this case the user needs to provide such a function manually + as part of the link. If the implementation is available then + we define EA_HAVE_GETENV_IMPL, otherwise it remains undefined. + Beware that sometimes a function may not seem to be present in + the Standard Library but in reality you need to link some auxiliary + provided library for it. An example of this is the Unix real-time + functions such as clock_gettime. + EA_HAVE_XXX_IMPL does not include the possibility that our + own code provides versions of these implementations, and in fact a + purpose of EA_HAVE_XXX_IMPL is to decide if we should be using + our own because the system doesn't provide one. + Implementation availability can be forcibly cancelled via EA_NO_HAVE_XXX_IMPL. + EA_NO_HAVE is useful for a build system or user to override the + defaults because it happens to know better. + + It's not practical to define EA_HAVE macros for every possible header, + declaration, and implementation, and so the user must simply know that + some headers, declarations, and implementations tend to require EA_HAVE + checking. Nearly every C Standard Library we've seen has a + header, a strlen declaration, and a linkable strlen implementation, + so there's no need to provide EA_HAVE support for this. On the other hand + it's commonly the case that the C Standard Library doesn't have a malloc.h + header or an inet_ntop declaration. + +---------------------------------------------------------------------------*/ + + +#ifndef INCLUDED_eahave_H +#define INCLUDED_eahave_H + + +#include + + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +/* EA_HAVE_XXX_FEATURE */ + +#if !defined(EA_HAVE_EXTENSIONS_FEATURE) && !defined(EA_NO_HAVE_EXTENSIONS_FEATURE) + #define EA_HAVE_EXTENSIONS_FEATURE 1 +#endif + + +/* EA_HAVE_XXX_LIBRARY */ + +// Dinkumware +#if !defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && !defined(EA_NO_HAVE_DINKUMWARE_CPP_LIBRARY) + #if defined(__cplusplus) + EA_DISABLE_ALL_VC_WARNINGS() + #include // Need to trigger the compilation of yvals.h without directly using because it might not exist. + EA_RESTORE_ALL_VC_WARNINGS() + #endif + + #if defined(__cplusplus) && defined(_CPPLIB_VER) /* If using the Dinkumware Standard library... */ + #define EA_HAVE_DINKUMWARE_CPP_LIBRARY 1 + #else + #define EA_NO_HAVE_DINKUMWARE_CPP_LIBRARY 1 + #endif +#endif + +// GCC libstdc++ +#if !defined(EA_HAVE_LIBSTDCPP_LIBRARY) && !defined(EA_NO_HAVE_LIBSTDCPP_LIBRARY) + #if defined(__GLIBCXX__) /* If using libstdc++ ... */ + #define EA_HAVE_LIBSTDCPP_LIBRARY 1 + #else + #define EA_NO_HAVE_LIBSTDCPP_LIBRARY 1 + #endif +#endif + +// Clang libc++ +#if !defined(EA_HAVE_LIBCPP_LIBRARY) && !defined(EA_NO_HAVE_LIBCPP_LIBRARY) + #if EA_HAS_INCLUDE_AVAILABLE + #if EA_HAS_INCLUDE(<__config>) + #define EA_HAVE_LIBCPP_LIBRARY 1 // We could also #include and check if defined(_LIBCPP_VERSION). + #endif + #endif + + #if !defined(EA_HAVE_LIBCPP_LIBRARY) + #define EA_NO_HAVE_LIBCPP_LIBRARY 1 + #endif +#endif + + +/* EA_HAVE_XXX_H */ + +// #include +#if !defined(EA_HAVE_SYS_TYPES_H) && !defined(EA_NO_HAVE_SYS_TYPES_H) + #define EA_HAVE_SYS_TYPES_H 1 +#endif + +// #include (and not sys/io.h or asm/io.h) +#if !defined(EA_HAVE_IO_H) && !defined(EA_NO_HAVE_IO_H) + // Unix doesn't have Microsoft's but has the same functionality in and . + #if defined(EA_PLATFORM_MICROSOFT) + #define EA_HAVE_IO_H 1 + #else + #define EA_NO_HAVE_IO_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_INTTYPES_H) && !defined(EA_NO_HAVE_INTTYPES_H) + #if !defined(EA_PLATFORM_MICROSOFT) + #define EA_HAVE_INTTYPES_H 1 + #else + #define EA_NO_HAVE_INTTYPES_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_UNISTD_H) && !defined(EA_NO_HAVE_UNISTD_H) + #if defined(EA_PLATFORM_UNIX) + #define EA_HAVE_UNISTD_H 1 + #else + #define EA_NO_HAVE_UNISTD_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_SYS_TIME_H) && !defined(EA_NO_HAVE_SYS_TIME_H) + #if !defined(EA_PLATFORM_MICROSOFT) && !defined(_CPPLIB_VER) /* _CPPLIB_VER indicates Dinkumware. */ + #define EA_HAVE_SYS_TIME_H 1 /* defines struct timeval */ + #else + #define EA_NO_HAVE_SYS_TIME_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_SYS_PTRACE_H) && !defined(EA_NO_HAVE_SYS_PTRACE_H) + #if defined(EA_PLATFORM_UNIX) && !defined(__CYGWIN__) && (defined(EA_PLATFORM_DESKTOP) || defined(EA_PLATFORM_SERVER)) + #define EA_HAVE_SYS_PTRACE_H 1 /* declares the ptrace function */ + #else + #define EA_NO_HAVE_SYS_PTRACE_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_SYS_STAT_H) && !defined(EA_NO_HAVE_SYS_STAT_H) + #if (defined(EA_PLATFORM_UNIX) && !(defined(EA_PLATFORM_SONY) && defined(EA_PLATFORM_CONSOLE))) || defined(__APPLE__) || defined(EA_PLATFORM_ANDROID) + #define EA_HAVE_SYS_STAT_H 1 /* declares the stat struct and function */ + #else + #define EA_NO_HAVE_SYS_STAT_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_LOCALE_H) && !defined(EA_NO_HAVE_LOCALE_H) + #define EA_HAVE_LOCALE_H 1 +#endif + +// #include +#if !defined(EA_HAVE_SIGNAL_H) && !defined(EA_NO_HAVE_SIGNAL_H) + #if !defined(EA_PLATFORM_BSD) && !defined(EA_PLATFORM_SONY) && !defined(EA_PLATFORM_NX) + #define EA_HAVE_SIGNAL_H 1 + #else + #define EA_NO_HAVE_SIGNAL_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_SYS_SIGNAL_H) && !defined(EA_NO_HAVE_SYS_SIGNAL_H) + #if defined(EA_PLATFORM_BSD) || defined(EA_PLATFORM_SONY) + #define EA_HAVE_SYS_SIGNAL_H 1 + #else + #define EA_NO_HAVE_SYS_SIGNAL_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_PTHREAD_H) && !defined(EA_NO_HAVE_PTHREAD_H) + #if defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_APPLE) || defined(EA_PLATFORM_POSIX) + #define EA_HAVE_PTHREAD_H 1 /* It can be had under Microsoft/Windows with the http://sourceware.org/pthreads-win32/ library */ + #else + #define EA_NO_HAVE_PTHREAD_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_WCHAR_H) && !defined(EA_NO_HAVE_WCHAR_H) + #if defined(EA_PLATFORM_DESKTOP) && defined(EA_PLATFORM_UNIX) && defined(EA_PLATFORM_SONY) && defined(EA_PLATFORM_APPLE) + #define EA_HAVE_WCHAR_H 1 + #else + #define EA_NO_HAVE_WCHAR_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_MALLOC_H) && !defined(EA_NO_HAVE_MALLOC_H) + #if defined(_MSC_VER) || defined(__MINGW32__) + #define EA_HAVE_MALLOC_H 1 + #else + #define EA_NO_HAVE_MALLOC_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_ALLOCA_H) && !defined(EA_NO_HAVE_ALLOCA_H) + #if !defined(EA_HAVE_MALLOC_H) && !defined(EA_PLATFORM_SONY) + #define EA_HAVE_ALLOCA_H 1 + #else + #define EA_NO_HAVE_ALLOCA_H 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_EXECINFO_H) && !defined(EA_NO_HAVE_EXECINFO_H) + #if (defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_OSX)) && !defined(EA_PLATFORM_ANDROID) + #define EA_HAVE_EXECINFO_H 1 + #else + #define EA_NO_HAVE_EXECINFO_H 1 + #endif +#endif + +// #include (Unix semaphore support) +#if !defined(EA_HAVE_SEMAPHORE_H) && !defined(EA_NO_HAVE_SEMAPHORE_H) + #if defined(EA_PLATFORM_UNIX) + #define EA_HAVE_SEMAPHORE_H 1 + #else + #define EA_NO_HAVE_SEMAPHORE_H 1 + #endif +#endif + +// #include (Unix semaphore support) +#if !defined(EA_HAVE_DIRENT_H) && !defined(EA_NO_HAVE_DIRENT_H) + #if defined(EA_PLATFORM_UNIX) && !defined(EA_PLATFORM_CONSOLE) + #define EA_HAVE_DIRENT_H 1 + #else + #define EA_NO_HAVE_DIRENT_H 1 + #endif +#endif + +// #include , , , +#if !defined(EA_HAVE_CPP11_CONTAINERS) && !defined(EA_NO_HAVE_CPP11_CONTAINERS) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_CONTAINERS 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4004) // Actually GCC 4.3 supports array and unordered_ + #define EA_HAVE_CPP11_CONTAINERS 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_CONTAINERS 1 + #else + #define EA_NO_HAVE_CPP11_CONTAINERS 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_ATOMIC) && !defined(EA_NO_HAVE_CPP11_ATOMIC) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 540) // Dinkumware. VS2012+ + #define EA_HAVE_CPP11_ATOMIC 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4007) + #define EA_HAVE_CPP11_ATOMIC 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_ATOMIC 1 + #else + #define EA_NO_HAVE_CPP11_ATOMIC 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_CONDITION_VARIABLE) && !defined(EA_NO_HAVE_CPP11_CONDITION_VARIABLE) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 540) // Dinkumware. VS2012+ + #define EA_HAVE_CPP11_CONDITION_VARIABLE 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4007) + #define EA_HAVE_CPP11_CONDITION_VARIABLE 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_CONDITION_VARIABLE 1 + #else + #define EA_NO_HAVE_CPP11_CONDITION_VARIABLE 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_MUTEX) && !defined(EA_NO_HAVE_CPP11_MUTEX) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 540) // Dinkumware. VS2012+ + #define EA_HAVE_CPP11_MUTEX 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4007) + #define EA_HAVE_CPP11_MUTEX 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_MUTEX 1 + #else + #define EA_NO_HAVE_CPP11_MUTEX 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_THREAD) && !defined(EA_NO_HAVE_CPP11_THREAD) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 540) // Dinkumware. VS2012+ + #define EA_HAVE_CPP11_THREAD 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4007) + #define EA_HAVE_CPP11_THREAD 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_THREAD 1 + #else + #define EA_NO_HAVE_CPP11_THREAD 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_FUTURE) && !defined(EA_NO_HAVE_CPP11_FUTURE) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 540) // Dinkumware. VS2012+ + #define EA_HAVE_CPP11_FUTURE 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4005) + #define EA_HAVE_CPP11_FUTURE 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_FUTURE 1 + #else + #define EA_NO_HAVE_CPP11_FUTURE 1 + #endif +#endif + + +// #include +#if !defined(EA_HAVE_CPP11_TYPE_TRAITS) && !defined(EA_NO_HAVE_CPP11_TYPE_TRAITS) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 540) // Dinkumware. VS2012+ + #define EA_HAVE_CPP11_TYPE_TRAITS 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4007) // Prior versions of libstdc++ have incomplete support for C++11 type traits. + #define EA_HAVE_CPP11_TYPE_TRAITS 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_TYPE_TRAITS 1 + #else + #define EA_NO_HAVE_CPP11_TYPE_TRAITS 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_TUPLES) && !defined(EA_NO_HAVE_CPP11_TUPLES) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_TUPLES 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4003) + #define EA_HAVE_CPP11_TUPLES 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_TUPLES 1 + #else + #define EA_NO_HAVE_CPP11_TUPLES 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_REGEX) && !defined(EA_NO_HAVE_CPP11_REGEX) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 540) && (defined(_HAS_EXCEPTIONS) && _HAS_EXCEPTIONS) // Dinkumware. VS2012+ + #define EA_HAVE_CPP11_REGEX 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4003) + #define EA_HAVE_CPP11_REGEX 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_REGEX 1 + #else + #define EA_NO_HAVE_CPP11_REGEX 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_RANDOM) && !defined(EA_NO_HAVE_CPP11_RANDOM) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_RANDOM 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4005) + #define EA_HAVE_CPP11_RANDOM 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_RANDOM 1 + #else + #define EA_NO_HAVE_CPP11_RANDOM 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_CHRONO) && !defined(EA_NO_HAVE_CPP11_CHRONO) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 540) // Dinkumware. VS2012+ + #define EA_HAVE_CPP11_CHRONO 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4007) // chrono was broken in glibc prior to 4.7. + #define EA_HAVE_CPP11_CHRONO 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_CHRONO 1 + #else + #define EA_NO_HAVE_CPP11_CHRONO 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_SCOPED_ALLOCATOR) && !defined(EA_NO_HAVE_CPP11_SCOPED_ALLOCATOR) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 540) // Dinkumware. VS2012+ + #define EA_HAVE_CPP11_SCOPED_ALLOCATOR 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4007) + #define EA_HAVE_CPP11_SCOPED_ALLOCATOR 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_SCOPED_ALLOCATOR 1 + #else + #define EA_NO_HAVE_CPP11_SCOPED_ALLOCATOR 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_INITIALIZER_LIST) && !defined(EA_NO_HAVE_CPP11_INITIALIZER_LIST) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) && !defined(EA_COMPILER_NO_INITIALIZER_LISTS) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_INITIALIZER_LIST 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_CLANG) && (EA_COMPILER_VERSION >= 301) && !defined(EA_COMPILER_NO_INITIALIZER_LISTS) && !defined(EA_PLATFORM_APPLE) + #define EA_HAVE_CPP11_INITIALIZER_LIST 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBCPP_LIBRARY) && defined(EA_COMPILER_CLANG) && (EA_COMPILER_VERSION >= 301) && !defined(EA_COMPILER_NO_INITIALIZER_LISTS) && !defined(EA_PLATFORM_APPLE) + #define EA_HAVE_CPP11_INITIALIZER_LIST 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4004) && !defined(EA_COMPILER_NO_INITIALIZER_LISTS) && !defined(EA_PLATFORM_APPLE) + #define EA_HAVE_CPP11_INITIALIZER_LIST 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) && !defined(EA_COMPILER_NO_INITIALIZER_LISTS) + #define EA_HAVE_CPP11_INITIALIZER_LIST 1 + #else + #define EA_NO_HAVE_CPP11_INITIALIZER_LIST 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_SYSTEM_ERROR) && !defined(EA_NO_HAVE_CPP11_SYSTEM_ERROR) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) && !(defined(_HAS_CPP0X) && _HAS_CPP0X) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_SYSTEM_ERROR 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_CLANG) && (EA_COMPILER_VERSION >= 301) && !defined(EA_PLATFORM_APPLE) + #define EA_HAVE_CPP11_SYSTEM_ERROR 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4004) && !defined(EA_PLATFORM_APPLE) + #define EA_HAVE_CPP11_SYSTEM_ERROR 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_SYSTEM_ERROR 1 + #else + #define EA_NO_HAVE_CPP11_SYSTEM_ERROR 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_CODECVT) && !defined(EA_NO_HAVE_CPP11_CODECVT) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_CODECVT 1 + // Future versions of libc++ may support this header. However, at the moment there isn't + // a reliable way of detecting if this header is available. + //#elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4008) + // #define EA_HAVE_CPP11_CODECVT 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_CODECVT 1 + #else + #define EA_NO_HAVE_CPP11_CODECVT 1 + #endif +#endif + +// #include +#if !defined(EA_HAVE_CPP11_TYPEINDEX) && !defined(EA_NO_HAVE_CPP11_TYPEINDEX) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_TYPEINDEX 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4006) + #define EA_HAVE_CPP11_TYPEINDEX 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_TYPEINDEX 1 + #else + #define EA_NO_HAVE_CPP11_TYPEINDEX 1 + #endif +#endif + + + + +/* EA_HAVE_XXX_DECL */ + +#if !defined(EA_HAVE_mkstemps_DECL) && !defined(EA_NO_HAVE_mkstemps_DECL) + #if defined(EA_PLATFORM_APPLE) || defined(EA_PLATFORM_SUN) + #define EA_HAVE_mkstemps_DECL 1 + #else + #define EA_NO_HAVE_mkstemps_DECL 1 + #endif +#endif + +#if !defined(EA_HAVE_gettimeofday_DECL) && !defined(EA_NO_HAVE_gettimeofday_DECL) + #if defined(EA_PLATFORM_POSIX) /* Posix means Linux, Unix, and Macintosh OSX, among others (including Linux-based mobile platforms). */ + #define EA_HAVE_gettimeofday_DECL 1 + #else + #define EA_NO_HAVE_gettimeofday_DECL 1 + #endif +#endif + +#if !defined(EA_HAVE_strcasecmp_DECL) && !defined(EA_NO_HAVE_strcasecmp_DECL) + #if !defined(EA_PLATFORM_MICROSOFT) + #define EA_HAVE_strcasecmp_DECL 1 /* This is found as stricmp when not found as strcasecmp */ + #define EA_HAVE_strncasecmp_DECL 1 + #else + #define EA_HAVE_stricmp_DECL 1 + #define EA_HAVE_strnicmp_DECL 1 + #endif +#endif + +#if !defined(EA_HAVE_mmap_DECL) && !defined(EA_NO_HAVE_mmap_DECL) + #if defined(EA_PLATFORM_POSIX) + #define EA_HAVE_mmap_DECL 1 /* mmap functionality varies significantly between systems. */ + #else + #define EA_NO_HAVE_mmap_DECL 1 + #endif +#endif + +#if !defined(EA_HAVE_fopen_DECL) && !defined(EA_NO_HAVE_fopen_DECL) + #define EA_HAVE_fopen_DECL 1 /* C FILE functionality such as fopen */ +#endif + +#if !defined(EA_HAVE_ISNAN) && !defined(EA_NO_HAVE_ISNAN) + #if defined(EA_PLATFORM_MICROSOFT) && !defined(EA_PLATFORM_MINGW) + #define EA_HAVE_ISNAN(x) _isnan(x) /* declared in */ + #define EA_HAVE_ISINF(x) !_finite(x) + #elif defined(EA_PLATFORM_APPLE) + #define EA_HAVE_ISNAN(x) std::isnan(x) /* declared in */ + #define EA_HAVE_ISINF(x) std::isinf(x) + #elif defined(EA_PLATFORM_ANDROID) + #define EA_HAVE_ISNAN(x) __builtin_isnan(x) /* There are a number of standard libraries for Android and it's hard to tell them apart, so just go with builtins */ + #define EA_HAVE_ISINF(x) __builtin_isinf(x) + #elif defined(__GNUC__) && defined(__CYGWIN__) + #define EA_HAVE_ISNAN(x) __isnand(x) /* declared nowhere, it seems. */ + #define EA_HAVE_ISINF(x) __isinfd(x) + #else + #define EA_HAVE_ISNAN(x) std::isnan(x) /* declared in */ + #define EA_HAVE_ISINF(x) std::isinf(x) + #endif +#endif + +#if !defined(EA_HAVE_itoa_DECL) && !defined(EA_NO_HAVE_itoa_DECL) + #if defined(EA_COMPILER_MSVC) + #define EA_HAVE_itoa_DECL 1 + #else + #define EA_NO_HAVE_itoa_DECL 1 + #endif +#endif + +#if !defined(EA_HAVE_nanosleep_DECL) && !defined(EA_NO_HAVE_nanosleep_DECL) + #if (defined(EA_PLATFORM_UNIX) && !defined(EA_PLATFORM_SONY)) || defined(EA_PLATFORM_IPHONE) || defined(EA_PLATFORM_OSX) || defined(EA_PLATFORM_SONY) || defined(EA_PLATFORM_NX) + #define EA_HAVE_nanosleep_DECL 1 + #else + #define EA_NO_HAVE_nanosleep_DECL 1 + #endif +#endif + +#if !defined(EA_HAVE_utime_DECL) && !defined(EA_NO_HAVE_utime_DECL) + #if defined(EA_PLATFORM_MICROSOFT) + #define EA_HAVE_utime_DECL _utime + #elif EA_PLATFORM_UNIX + #define EA_HAVE_utime_DECL utime + #else + #define EA_NO_HAVE_utime_DECL 1 + #endif +#endif + +#if !defined(EA_HAVE_ftruncate_DECL) && !defined(EA_NO_HAVE_ftruncate_DECL) + #if !defined(__MINGW32__) + #define EA_HAVE_ftruncate_DECL 1 + #else + #define EA_NO_HAVE_ftruncate_DECL 1 + #endif +#endif + +#if !defined(EA_HAVE_localtime_DECL) && !defined(EA_NO_HAVE_localtime_DECL) + #define EA_HAVE_localtime_DECL 1 +#endif + +#if !defined(EA_HAVE_pthread_getattr_np_DECL) && !defined(EA_NO_HAVE_pthread_getattr_np_DECL) + #if defined(EA_PLATFORM_LINUX) + #define EA_HAVE_pthread_getattr_np_DECL 1 + #else + #define EA_NO_HAVE_pthread_getattr_np_DECL 1 + #endif +#endif + + + +/* EA_HAVE_XXX_IMPL*/ + +#if !defined(EA_HAVE_WCHAR_IMPL) && !defined(EA_NO_HAVE_WCHAR_IMPL) + #if defined(EA_PLATFORM_DESKTOP) + #define EA_HAVE_WCHAR_IMPL 1 /* Specifies if wchar_t string functions are provided, such as wcslen, wprintf, etc. Implies EA_HAVE_WCHAR_H */ + #else + #define EA_NO_HAVE_WCHAR_IMPL 1 + #endif +#endif + +#if !defined(EA_HAVE_getenv_IMPL) && !defined(EA_NO_HAVE_getenv_IMPL) + #if (defined(EA_PLATFORM_DESKTOP) || defined(EA_PLATFORM_UNIX)) && !defined(EA_PLATFORM_WINRT) + #define EA_HAVE_getenv_IMPL 1 + #else + #define EA_NO_HAVE_getenv_IMPL 1 + #endif +#endif + +#if !defined(EA_HAVE_setenv_IMPL) && !defined(EA_NO_HAVE_setenv_IMPL) + #if defined(EA_PLATFORM_UNIX) && defined(EA_PLATFORM_POSIX) + #define EA_HAVE_setenv_IMPL 1 + #else + #define EA_NO_HAVE_setenv_IMPL 1 + #endif +#endif + +#if !defined(EA_HAVE_unsetenv_IMPL) && !defined(EA_NO_HAVE_unsetenv_IMPL) + #if defined(EA_PLATFORM_UNIX) && defined(EA_PLATFORM_POSIX) + #define EA_HAVE_unsetenv_IMPL 1 + #else + #define EA_NO_HAVE_unsetenv_IMPL 1 + #endif +#endif + +#if !defined(EA_HAVE_putenv_IMPL) && !defined(EA_NO_HAVE_putenv_IMPL) + #if (defined(EA_PLATFORM_DESKTOP) || defined(EA_PLATFORM_UNIX)) && !defined(EA_PLATFORM_WINRT) + #define EA_HAVE_putenv_IMPL 1 /* With Microsoft compilers you may need to use _putenv, as they have deprecated putenv. */ + #else + #define EA_NO_HAVE_putenv_IMPL 1 + #endif +#endif + +#if !defined(EA_HAVE_time_IMPL) && !defined(EA_NO_HAVE_time_IMPL) + #if defined(EA_PLATFORM_NX) + #define EA_NO_HAVE_time_IMPL 1 + #else + #define EA_HAVE_time_IMPL 1 + #define EA_HAVE_clock_IMPL 1 + #endif +#endif + +// fopen() +#if !defined(EA_HAVE_fopen_IMPL) && !defined(EA_NO_HAVE_fopen_IMPL) + #define EA_HAVE_fopen_IMPL 1 /* C FILE functionality such as fopen */ +#endif + +// inet_ntop() +#if !defined(EA_HAVE_inet_ntop_IMPL) && !defined(EA_NO_HAVE_inet_ntop_IMPL) + #if (defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_POSIX)) && !defined(EA_PLATFORM_SONY) && !defined(EA_PLATFORM_NX) + #define EA_HAVE_inet_ntop_IMPL 1 /* This doesn't identify if the platform SDK has some alternative function that does the same thing; */ + #define EA_HAVE_inet_pton_IMPL 1 /* it identifies strictly the inet_ntop and inet_pton functions. For example, Microsoft has InetNtop in */ + #else + #define EA_NO_HAVE_inet_ntop_IMPL 1 + #define EA_NO_HAVE_inet_pton_IMPL 1 + #endif +#endif + +// clock_gettime() +#if !defined(EA_HAVE_clock_gettime_IMPL) && !defined(EA_NO_HAVE_clock_gettime_IMPL) + #if defined(EA_PLATFORM_LINUX) || defined(__CYGWIN__) || (defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)) || (defined(EA_PLATFORM_POSIX) && defined(_CPPLIB_VER) /*Dinkumware*/) + #define EA_HAVE_clock_gettime_IMPL 1 /* You need to link the 'rt' library to get this */ + #else + #define EA_NO_HAVE_clock_gettime_IMPL 1 + #endif +#endif + +#if !defined(EA_HAVE_getcwd_IMPL) && !defined(EA_NO_HAVE_getcwd_IMPL) + #if (defined(EA_PLATFORM_DESKTOP) || defined(EA_PLATFORM_UNIX)) && !defined(EA_PLATFORM_ANDROID) && !defined(EA_PLATFORM_WINRT) + #define EA_HAVE_getcwd_IMPL 1 /* With Microsoft compilers you may need to use _getcwd, as they have deprecated getcwd. And in any case it's present at */ + #else + #define EA_NO_HAVE_getcwd_IMPL 1 + #endif +#endif + +#if !defined(EA_HAVE_tmpnam_IMPL) && !defined(EA_NO_HAVE_tmpnam_IMPL) + #if (defined(EA_PLATFORM_DESKTOP) || defined(EA_PLATFORM_UNIX)) && !defined(EA_PLATFORM_ANDROID) + #define EA_HAVE_tmpnam_IMPL 1 + #else + #define EA_NO_HAVE_tmpnam_IMPL 1 + #endif +#endif + +// nullptr, the built-in C++11 type. +// This EA_HAVE is deprecated, as EA_COMPILER_NO_NULLPTR is more appropriate, given that nullptr is a compiler-level feature and not a library feature. +#if !defined(EA_HAVE_nullptr_IMPL) && !defined(EA_NO_HAVE_nullptr_IMPL) + #if defined(EA_COMPILER_NO_NULLPTR) + #define EA_NO_HAVE_nullptr_IMPL 1 + #else + #define EA_HAVE_nullptr_IMPL 1 + #endif +#endif + +// std::nullptr_t +// Note that implements a portable nullptr implementation, but this +// EA_HAVE specifically refers to std::nullptr_t from the standard libraries. +#if !defined(EA_HAVE_nullptr_t_IMPL) && !defined(EA_NO_HAVE_nullptr_t_IMPL) + #if defined(EA_COMPILER_CPP11_ENABLED) + // VS2010+ with its default Dinkumware standard library. + #if defined(_MSC_VER) && (_MSC_VER >= 1600) && defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) + #define EA_HAVE_nullptr_t_IMPL 1 + + #elif defined(EA_HAVE_LIBCPP_LIBRARY) // clang/llvm libc++ + #define EA_HAVE_nullptr_t_IMPL 1 + + #elif defined(EA_HAVE_LIBSTDCPP_LIBRARY) // GNU libstdc++ + // Unfortunately __GLIBCXX__ date values don't go strictly in version ordering. + #if (__GLIBCXX__ >= 20110325) && (__GLIBCXX__ != 20120702) && (__GLIBCXX__ != 20110428) + #define EA_HAVE_nullptr_t_IMPL 1 + #else + #define EA_NO_HAVE_nullptr_t_IMPL 1 + #endif + + // We simply assume that the standard library (e.g. Dinkumware) provides std::nullptr_t. + #elif defined(__clang__) + #define EA_HAVE_nullptr_t_IMPL 1 + + // With GCC compiler >= 4.6, std::nullptr_t is always defined in , in practice. + #elif defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4006) + #define EA_HAVE_nullptr_t_IMPL 1 + + // The EDG compiler provides nullptr, but uses an older standard library that doesn't support std::nullptr_t. + #elif defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 403) + #define EA_HAVE_nullptr_t_IMPL 1 + + #else + #define EA_NO_HAVE_nullptr_t_IMPL 1 + #endif + #else + #define EA_NO_HAVE_nullptr_t_IMPL 1 + #endif +#endif + +// std::terminate +#if !defined(EA_HAVE_std_terminate_IMPL) && !defined(EA_NO_HAVE_std_terminate_IMPL) + #if !defined(EA_PLATFORM_IPHONE) && !defined(EA_PLATFORM_ANDROID) + #define EA_HAVE_std_terminate_IMPL 1 /* iOS doesn't appear to provide an implementation for std::terminate under the armv6 target. */ + #else + #define EA_NO_HAVE_std_terminate_IMPL 1 + #endif +#endif + +// : std::begin, std::end, std::prev, std::next, std::move_iterator. +#if !defined(EA_HAVE_CPP11_ITERATOR_IMPL) && !defined(EA_NO_HAVE_CPP11_ITERATOR_IMPL) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) && !(defined(_HAS_CPP0X) && _HAS_CPP0X) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_ITERATOR_IMPL 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4006) + #define EA_HAVE_CPP11_ITERATOR_IMPL 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_ITERATOR_IMPL 1 + #else + #define EA_NO_HAVE_CPP11_ITERATOR_IMPL 1 + #endif +#endif + +// : std::weak_ptr, std::shared_ptr, std::unique_ptr, std::bad_weak_ptr, std::owner_less +#if !defined(EA_HAVE_CPP11_SMART_POINTER_IMPL) && !defined(EA_NO_HAVE_CPP11_SMART_POINTER_IMPL) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) && !(defined(_HAS_CPP0X) && _HAS_CPP0X) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_SMART_POINTER_IMPL 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4004) + #define EA_HAVE_CPP11_SMART_POINTER_IMPL 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_SMART_POINTER_IMPL 1 + #else + #define EA_NO_HAVE_CPP11_SMART_POINTER_IMPL 1 + #endif +#endif + +// : std::function, std::mem_fn, std::bad_function_call, std::is_bind_expression, std::is_placeholder, std::reference_wrapper, std::hash, std::bind, std::ref, std::cref. +#if !defined(EA_HAVE_CPP11_FUNCTIONAL_IMPL) && !defined(EA_NO_HAVE_CPP11_FUNCTIONAL_IMPL) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) && !(defined(_HAS_CPP0X) && _HAS_CPP0X) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_FUNCTIONAL_IMPL 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4004) + #define EA_HAVE_CPP11_FUNCTIONAL_IMPL 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_FUNCTIONAL_IMPL 1 + #else + #define EA_NO_HAVE_CPP11_FUNCTIONAL_IMPL 1 + #endif +#endif + +// std::current_exception, std::rethrow_exception, std::exception_ptr, std::make_exception_ptr +#if !defined(EA_HAVE_CPP11_EXCEPTION_IMPL) && !defined(EA_NO_HAVE_CPP11_EXCEPTION_IMPL) + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) && (_CPPLIB_VER >= 520) && !(defined(_HAS_CPP0X) && _HAS_CPP0X) // Dinkumware. VS2010+ + #define EA_HAVE_CPP11_EXCEPTION_IMPL 1 + #elif defined(EA_COMPILER_CPP11_ENABLED) && defined(EA_HAVE_LIBSTDCPP_LIBRARY) && defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 4004) + #define EA_HAVE_CPP11_EXCEPTION_IMPL 1 + #elif defined(EA_HAVE_LIBCPP_LIBRARY) && (_LIBCPP_VERSION >= 1) + #define EA_HAVE_CPP11_EXCEPTION_IMPL 1 + #else + #define EA_NO_HAVE_CPP11_EXCEPTION_IMPL 1 + #endif +#endif + + + + +/* Implementations that all platforms seem to have: */ +/* + alloca + malloc + calloc + strtoll + strtoull + vsprintf + vsnprintf +*/ + +/* Implementations that we don't care about: */ +/* + bcopy -- Just use memmove or some customized equivalent. bcopy offers no practical benefit. + strlcpy -- So few platforms have this built-in that we get no benefit from using it. Use EA::StdC::Strlcpy instead. + strlcat -- " +*/ + + + +/*----------------------------------------------------------------------------- + EABASE_USER_HAVE_HEADER + + This allows the user to define a header file to be #included after the + eahave.h's contents are compiled. A primary use of this is to override + the contents of this header file. You can define the overhead header + file name in-code or define it globally as part of your build file. + + Example usage: + #define EABASE_USER_HAVE_HEADER "MyHaveOverrides.h" + #include +---------------------------------------------------------------------------*/ + +#ifdef EABASE_USER_HAVE_HEADER + #include EABASE_USER_HAVE_HEADER +#endif + + +#endif /* Header include guard */ + + + diff --git a/src/thirdparty/ea/EABase/earesult.h b/src/thirdparty/ea/EABase/earesult.h new file mode 100644 index 00000000..d08b3460 --- /dev/null +++ b/src/thirdparty/ea/EABase/earesult.h @@ -0,0 +1,62 @@ +/*----------------------------------------------------------------------------- + * earesult.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *---------------------------------------------------------------------------*/ + + +#ifndef INCLUDED_earesult_H +#define INCLUDED_earesult_H + + +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once /* Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. */ +#endif + + + +/* This result type is width-compatible with most systems. */ +typedef int32_t ea_result_type; + + +namespace EA +{ + typedef int32_t result_type; + + enum + { +#ifndef SUCCESS + // Deprecated + // Note: a public MS header has created a define of this name which causes a build error. Fortunately they + // define it to 0 which is compatible. + // see: WindowsSDK\8.1.51641-fb\installed\Include\um\RasError.h + SUCCESS = 0, +#endif + // Deprecated + FAILURE = -1, + + // These values are now the preferred constants + EA_SUCCESS = 0, + EA_FAILURE = -1, + }; +} + + +/* Macro to simplify testing for success. */ +#ifndef EA_SUCCEEDED + #define EA_SUCCEEDED(result) ((result) >= 0) +#endif + +/* Macro to simplfify testing for general failure. */ +#ifndef EA_FAILED + #define EA_FAILED(result) ((result) < 0) +#endif + + +#endif + + + + diff --git a/src/thirdparty/ea/EABase/eastdarg.h b/src/thirdparty/ea/EABase/eastdarg.h new file mode 100644 index 00000000..c175093e --- /dev/null +++ b/src/thirdparty/ea/EABase/eastdarg.h @@ -0,0 +1,99 @@ +/*----------------------------------------------------------------------------- + * eastdarg.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *---------------------------------------------------------------------------*/ + + +#ifndef INCLUDED_eastdarg_H +#define INCLUDED_eastdarg_H + + +#include +#include + + +// VA_ARG_COUNT +// +// Returns the number of arguments passed to a macro's ... argument. +// This applies to macros only and not functions. +// +// Example usage: +// assert(VA_ARG_COUNT() == 0); +// assert(VA_ARG_COUNT(a) == 1); +// assert(VA_ARG_COUNT(a, b) == 2); +// assert(VA_ARG_COUNT(a, b, c) == 3); +// +#if !defined(VA_ARG_COUNT) + #define VA_ARG_COUNT(...) VA_ARG_COUNT_II((VA_ARG_COUNT_PREFIX_ ## __VA_ARGS__ ## _VA_ARG_COUNT_POSTFIX,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)) + #define VA_ARG_COUNT_II(__args) VA_ARG_COUNT_I __args + #define VA_ARG_COUNT_PREFIX__VA_ARG_COUNT_POSTFIX ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 + #define VA_ARG_COUNT_I(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,N,...) N +#endif + + +// va_copy +// +// va_copy is required by C++11 +// C++11 and C99 require va_copy to be #defined and implemented. +// http://en.cppreference.com/w/cpp/utility/variadic/va_copy +// +// Example usage: +// void Func(char* p, ...){ +// va_list args, argsCopy; +// va_start(args, p); +// va_copy(argsCopy, args); +// (use args) +// (use argsCopy, which acts the same as args) +// va_end(args); +// va_end(argsCopy); +// } +// +#ifndef va_copy + #if defined(__va_copy) // GCC and others define this for non-C99 compatibility. + #define va_copy(dest, src) __va_copy((dest), (src)) + #else + // This may not work for some platforms, depending on their ABI. + // It works for Microsoft x86,x64, and PowerPC-based platforms. + #define va_copy(dest, src) memcpy(&(dest), &(src), sizeof(va_list)) + #endif +#endif + + + +// va_list_reference +// +// va_list_reference is not part of the C or C++ standards. +// It allows you to pass a va_list by reference to another +// function instead of by value. You cannot simply use va_list& +// as that won't work with many va_list implementations because +// they are implemented as arrays (which can't be passed by +// reference to a function without decaying to a pointer). +// +// Example usage: +// void Test(va_list_reference args){ +// printf("%d", va_arg(args, int)); +// } +// void Func(char* p, ...){ +// va_list args; +// va_start(args, p); +// Test(args); // Upon return args will be modified. +// va_end(args); +// } +#ifndef va_list_reference + #if defined(EA_PLATFORM_MICROSOFT) || (EA_PLATFORM_PTR_SIZE == 4) || (defined(EA_PLATFORM_APPLE) && defined(EA_PROCESSOR_ARM64)) || defined(EA_PLATFORM_NX) || (defined(EA_PLATFORM_ANDROID) && defined(EA_PROCESSOR_ARM64)) + // This is required for platform ABIs in which va_list is a struct or pointer. + #define va_list_reference va_list& + #else + // This is required for platform ABIs in which va_list is defined to be an array. + #define va_list_reference va_list + #endif +#endif + + + + +#endif /* Header include guard */ + + + diff --git a/src/thirdparty/ea/EABase/eaunits.h b/src/thirdparty/ea/EABase/eaunits.h new file mode 100644 index 00000000..22357234 --- /dev/null +++ b/src/thirdparty/ea/EABase/eaunits.h @@ -0,0 +1,54 @@ +/*----------------------------------------------------------------------------- + * eaunits.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *---------------------------------------------------------------------------*/ + + +#ifndef INCLUDED_eaunits_h +#define INCLUDED_eaunits_h + +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +// Defining common SI unit macros. +// +// The mebibyte is a multiple of the unit byte for digital information. Technically a +// megabyte (MB) is a power of ten, while a mebibyte (MiB) is a power of two, +// appropriate for binary machines. Many Linux distributions use the unit, but it is +// not widely acknowledged within the industry or media. +// Reference: https://en.wikipedia.org/wiki/Mebibyte +// +// Examples: +// auto size1 = EA_KILOBYTE(16); +// auto size2 = EA_MEGABYTE(128); +// auto size3 = EA_MEBIBYTE(8); +// auto size4 = EA_GIBIBYTE(8); + +// define byte for completeness +#define EA_BYTE(x) (x) + +// Decimal SI units +#define EA_KILOBYTE(x) (size_t(x) * 1000) +#define EA_MEGABYTE(x) (size_t(x) * 1000 * 1000) +#define EA_GIGABYTE(x) (size_t(x) * 1000 * 1000 * 1000) +#define EA_TERABYTE(x) (size_t(x) * 1000 * 1000 * 1000 * 1000) +#define EA_PETABYTE(x) (size_t(x) * 1000 * 1000 * 1000 * 1000 * 1000) +#define EA_EXABYTE(x) (size_t(x) * 1000 * 1000 * 1000 * 1000 * 1000 * 1000) + +// Binary SI units +#define EA_KIBIBYTE(x) (size_t(x) * 1024) +#define EA_MEBIBYTE(x) (size_t(x) * 1024 * 1024) +#define EA_GIBIBYTE(x) (size_t(x) * 1024 * 1024 * 1024) +#define EA_TEBIBYTE(x) (size_t(x) * 1024 * 1024 * 1024 * 1024) +#define EA_PEBIBYTE(x) (size_t(x) * 1024 * 1024 * 1024 * 1024 * 1024) +#define EA_EXBIBYTE(x) (size_t(x) * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) + +#endif // INCLUDED_earesult_H + + + + diff --git a/src/thirdparty/ea/EABase/int128.h b/src/thirdparty/ea/EABase/int128.h new file mode 100644 index 00000000..334c7202 --- /dev/null +++ b/src/thirdparty/ea/EABase/int128.h @@ -0,0 +1,1268 @@ +/*----------------------------------------------------------------------------- + * eaint128_t.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *---------------------------------------------------------------------------*/ + + +#ifndef INCLUDED_int128_h +#define INCLUDED_int128_h + + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// EA_INT128_INTRINSIC_AVAILABLE +// +#if (EA_COMPILER_INTMAX_SIZE >= 16) && (defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG)) + // __int128_t/__uint128_t is supported + #define EA_INT128_INTRINSIC_AVAILABLE 1 +#else + #define EA_INT128_INTRINSIC_AVAILABLE 0 +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// EA_INT128_ALIGNAS +// +#if EA_INT128_INTRINSIC_AVAILABLE && !defined(EA_COMPILER_NO_ALIGNAS) + #define EA_INT128_ALIGNAS alignas(unsigned __int128) +#else + #define EA_INT128_ALIGNAS +#endif + + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// EA_HAVE_INT128 +// +// Indicates that EABase implements 128-bit integer types +// +#define EA_HAVE_INT128 1 + + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// uint128_t_base +// +struct EA_INT128_ALIGNAS int128_t_base +{ + // Constructors / destructors + int128_t_base() = default; + int128_t_base(uint32_t nPart0, uint32_t nPart1, uint32_t nPart2, uint32_t nPart3); + int128_t_base(uint64_t nPart0, uint64_t nPart1); + int128_t_base(uint8_t value); + int128_t_base(uint16_t value); + int128_t_base(uint32_t value); + int128_t_base(uint64_t value); + int128_t_base(const int128_t_base& value) = default; + + // Assignment operator + int128_t_base& operator=(const int128_t_base& value) = default; + + // Explicit operators to convert back to basic types + EA_CONSTEXPR explicit operator bool() const; + EA_CONSTEXPR explicit operator char() const; + EA_CONSTEXPR explicit operator int() const; + EA_CONSTEXPR explicit operator long() const; + EA_CONSTEXPR explicit operator long long() const; + EA_CONSTEXPR explicit operator short() const; + EA_CONSTEXPR explicit operator signed char() const; + EA_CONSTEXPR explicit operator unsigned char() const; + EA_CONSTEXPR explicit operator unsigned int() const; + EA_CONSTEXPR explicit operator unsigned long long() const; + EA_CONSTEXPR explicit operator unsigned long() const; + EA_CONSTEXPR explicit operator unsigned short() const; +#if EA_WCHAR_UNIQUE + // EA_CONSTEXPR explicit operator char16_t() const; + // EA_CONSTEXPR explicit operator char32_t() const; + // EA_CONSTEXPR explicit operator wchar_t() const; +#endif + EA_CONSTEXPR explicit operator float() const; + EA_CONSTEXPR explicit operator double() const; + EA_CONSTEXPR explicit operator long double() const; +#if EA_INT128_INTRINSIC_AVAILABLE + EA_CONSTEXPR explicit operator __int128() const; + EA_CONSTEXPR explicit operator unsigned __int128() const; +#endif + + // Math operators + static void OperatorPlus (const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result); + static void OperatorMinus(const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result); + static void OperatorMul (const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result); + + // Shift operators + static void OperatorShiftRight(const int128_t_base& value, int nShift, int128_t_base& result); + static void OperatorShiftLeft (const int128_t_base& value, int nShift, int128_t_base& result); + + // Unary arithmetic/logic operators + bool operator!() const; + + // Logical operators + static void OperatorXOR(const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result); + static void OperatorOR (const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result); + static void OperatorAND(const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result); + + bool IsZero() const; + void SetZero(); + void TwosComplement(); + void InverseTwosComplement(); + + int GetBit(int nIndex) const; + void SetBit(int nIndex, int value); + +protected: + void DoubleToUint128(double value); + + EA_CONSTEXPR uint64_t Low() const + { + return mPart0; + } + + EA_CONSTEXPR uint64_t High() const + { + return mPart1; + } + +protected: + #ifdef EA_SYSTEM_BIG_ENDIAN + uint64_t mPart1; // Most significant byte. + uint64_t mPart0; // Least significant byte. + #else + uint64_t mPart0; // Least significant byte. + uint64_t mPart1; // Most significant byte. + #endif +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// int128_t +// +// Implements signed 128 bit integer. +// +struct int128_t : public int128_t_base +{ + // Constructors / destructors + using int128_t_base::int128_t_base; + + // Assignment operator + using int128_t_base::operator=; + + // Unary arithmetic/logic operators + int128_t operator-() const; + int128_t& operator++(); + int128_t& operator--(); + int128_t operator++(int); + int128_t operator--(int); + int128_t operator~() const; + int128_t operator+() const; + + // Math operators + int128_t operator+ (const int128_t& other); + int128_t operator- (const int128_t& other); + int128_t operator* (const int128_t& other); + int128_t operator/ (const int128_t& other); + int128_t operator% (const int128_t& other); + int128_t& operator+=(const int128_t& other); + int128_t& operator-=(const int128_t& other); + int128_t& operator*=(const int128_t& other); + int128_t& operator/=(const int128_t& other); + int128_t& operator%=(const int128_t& other); + + // Shift operators + int128_t operator>> (int nShift) const; + int128_t operator<< (int nShift) const; + int128_t& operator>>=(int nShift); + int128_t& operator<<=(int nShift); + + // Logical operators + int128_t operator^ (const int128_t& other) const; + int128_t operator| (const int128_t& other) const; + int128_t operator& (const int128_t& other) const; + int128_t& operator^=(const int128_t& other); + int128_t& operator|=(const int128_t& other); + int128_t& operator&=(const int128_t& other); + + // Equality operators + bool operator==(const int128_t& other) const; + bool operator!=(const int128_t& other) const; + bool operator> (const int128_t& other) const; + bool operator>=(const int128_t& other) const; + bool operator< (const int128_t& other) const; + bool operator<=(const int128_t& other) const; + +protected: + int compare(const int128_t& other) const; + void Negate(); + void Modulus(const int128_t& divisor, int128_t& quotient, int128_t& remainder) const; + bool IsNegative() const; // Returns true for value < 0 + bool IsPositive() const; // Returns true for value >= 0 +}; + + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// uint128_t +// +// Implements unsigned 128 bit integer. +// +struct uint128_t : public int128_t_base +{ + // Constructors / destructors + using int128_t_base::int128_t_base; + + // Assignment operator + using int128_t_base::operator=; + + // Unary arithmetic/logic operators + uint128_t operator-() const; + uint128_t& operator++(); + uint128_t& operator--(); + uint128_t operator++(int); + uint128_t operator--(int); + uint128_t operator~() const; + uint128_t operator+() const; + + // Math operators + uint128_t operator+ (const uint128_t& other); + uint128_t operator- (const uint128_t& other); + uint128_t operator* (const uint128_t& other); + uint128_t operator/ (const uint128_t& other); + uint128_t operator% (const uint128_t& other); + uint128_t& operator+=(const uint128_t& other); + uint128_t& operator-=(const uint128_t& other); + uint128_t& operator*=(const uint128_t& other); + uint128_t& operator/=(const uint128_t& other); + uint128_t& operator%=(const uint128_t& other); + + // Shift operators + uint128_t operator>> (int nShift) const; + uint128_t operator<< (int nShift) const; + uint128_t& operator>>=(int nShift); + uint128_t& operator<<=(int nShift); + + // Logical operators + uint128_t operator^ (const uint128_t& other) const; + uint128_t operator| (const uint128_t& other) const; + uint128_t operator& (const uint128_t& other) const; + uint128_t& operator^=(const uint128_t& other); + uint128_t& operator|=(const uint128_t& other); + uint128_t& operator&=(const uint128_t& other); + + // Equality operators + bool operator==(const uint128_t& other) const; + bool operator!=(const uint128_t& other) const; + bool operator> (const uint128_t& other) const; + bool operator>=(const uint128_t& other) const; + bool operator< (const uint128_t& other) const; + bool operator<=(const uint128_t& other) const; + +protected: + int compare(const uint128_t& other) const; + void Negate(); + void Modulus(const uint128_t& divisor, uint128_t& quotient, uint128_t& remainder) const; + bool IsNegative() const; // Returns true for value < 0 + bool IsPositive() const; // Returns true for value >= 0 +}; + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// uint128_t_base implementation +/////////////////////////////////////////////////////////////////////////////////////////////////////// +EA_CONSTEXPR inline int128_t_base::operator bool() const { return mPart0 || mPart1; } +EA_CONSTEXPR inline int128_t_base::operator char() const { return static_cast(Low()); } +#if EA_WCHAR_UNIQUE +// EA_CONSTEXPR inline int128_t_base::operator char16_t() const { return static_cast(Low()); } +// EA_CONSTEXPR inline int128_t_base::operator char32_t() const { return static_cast(Low()); } +// EA_CONSTEXPR inline int128_t_base::operator wchar_t() const { return static_cast(Low()); } +#endif +EA_CONSTEXPR inline int128_t_base::operator int() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator long() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator long long() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator short() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator signed char() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator unsigned char() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator unsigned int() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator unsigned long long() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator unsigned long() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator unsigned short() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator float() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator double() const { return static_cast(Low()); } +EA_CONSTEXPR inline int128_t_base::operator long double() const { return static_cast(Low()); } +#if EA_INT128_INTRINSIC_AVAILABLE +EA_CONSTEXPR inline int128_t_base::operator __int128() const { return static_cast<__int128>(Low()); } +EA_CONSTEXPR inline int128_t_base::operator unsigned __int128() const { return static_cast(Low()); } +#endif + +inline void int128_t_base::SetBit(int nIndex, int value) +{ + // EA_ASSERT((nIndex >= 0) && (nIndex < 128)); + + const uint64_t nBitMask = ((uint64_t)1 << (nIndex % 64)); + + if(nIndex < 64) + { + if(value) + mPart0 = mPart0 | nBitMask; + else + mPart0 = mPart0 & ~nBitMask; + } + else if(nIndex < 128) + { + if(value) + mPart1 = mPart1 | nBitMask; + else + mPart1 = mPart1 & ~nBitMask; + } +} + +inline int int128_t_base::GetBit(int nIndex) const +{ + // EA_ASSERT((nIndex >= 0) && (nIndex < 128)); + + const uint64_t nBitMask = ((uint64_t)1 << (nIndex % 64)); + + if(nIndex < 64) + return ((mPart0 & nBitMask) ? 1 : 0); + else if(nIndex < 128) + return ((mPart1 & nBitMask) ? 1 : 0); + return 0; +} + +inline int128_t_base::int128_t_base(uint32_t nPart0, uint32_t nPart1, uint32_t nPart2, uint32_t nPart3) +{ + mPart1 = ((uint64_t)nPart3 << 32) + nPart2; + mPart0 = ((uint64_t)nPart1 << 32) + nPart0; +} + +inline int128_t_base::int128_t_base(uint64_t nPart0, uint64_t nPart1) +{ + mPart1 = nPart1; + mPart0 = nPart0; +} + +inline int128_t_base::int128_t_base(uint8_t value) +{ + mPart1 = 0; + mPart0 = value; +} + +inline int128_t_base::int128_t_base(uint16_t value) +{ + mPart1 = 0; + mPart0 = value; +} + +inline int128_t_base::int128_t_base(uint32_t value) +{ + mPart1 = 0; + mPart0 = value; +} + +inline int128_t_base::int128_t_base(uint64_t value) +{ + mPart1 = 0; + mPart0 = value; +} + +/////////////////////////////////////////////////////////////////////////////// +// OperatorPlus +// +// Returns: (value1 + value2) into result. +// The output 'result' *is* allowed to point to the same memory as one of the inputs. +// To consider: Fix 'defect' of this function whereby it doesn't implement overflow wraparound. +// +inline void int128_t_base::OperatorPlus(const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result) +{ + uint64_t t = value1.mPart0 + value2.mPart0; + uint64_t nCarry = (t < value1.mPart0) && (t < value2.mPart0); + result.mPart0 = t; + result.mPart1 = value1.mPart1 + value2.mPart1 + nCarry; +} + +/////////////////////////////////////////////////////////////////////////////// +// OperatorMinus +// +// Returns: (value1 - value2) into result. +// The output 'result' *is* allowed to point to the same memory as one of the inputs. +// To consider: Fix 'defect' of this function whereby it doesn't implement overflow wraparound. +// +inline void int128_t_base::OperatorMinus(const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result) +{ + uint64_t t = (value1.mPart0 - value2.mPart0); + uint64_t nCarry = (value1.mPart0 < value2.mPart0) ? 1u : 0u; + result.mPart0 = t; + result.mPart1 = (value1.mPart1 - value2.mPart1) - nCarry; +} + +/////////////////////////////////////////////////////////////////////////////// +// OperatorMul +// +// 64 bit systems: +// This is how it would be able to work if we could get a 128 bit result from +// two 64 bit values. None of the 64 bit systems that we are currently working +// with have C language support for multiplying two 64 bit numbers and retrieving +// the 128 bit result. However, many 64 bit platforms have support at the asm +// level for doing such a thing. +// Part 1 Part 0 +// 0000000000000002 0000000000000001 +// x 0000000000000002 0000000000000001 +// ------------------------------------------- +// | 0000000000000002 0000000000000001 +// + 0000000000000004 | 0000000000000002 (0000000000000000) +// ------------------------------------------------------------------------- +// +inline void int128_t_base::OperatorMul(const int128_t_base& a, const int128_t_base& b, int128_t_base& result) +{ + // To consider: Use compiler or OS-provided custom functionality here, such as + // Windows UnsignedMultiply128 and GCC's built-in int128_t. + + #if defined(DISABLED_PLATFORM_WIN64) + // To do: Implement x86-64 asm here. + + #else + // Else we are stuck doing something less efficient. In this case we + // fall back to doing 32 bit multiplies as with 32 bit platforms. + result = (a.mPart0 & 0xffffffff) * (b.mPart0 & 0xffffffff); + int128_t v01 = (a.mPart0 & 0xffffffff) * ((b.mPart0 >> 32) & 0xffffffff); + int128_t v02 = (a.mPart0 & 0xffffffff) * (b.mPart1 & 0xffffffff); + int128_t v03 = (a.mPart0 & 0xffffffff) * ((b.mPart1 >> 32) & 0xffffffff); + + int128_t v10 = ((a.mPart0 >> 32) & 0xffffffff) * (b.mPart0 & 0xffffffff); + int128_t v11 = ((a.mPart0 >> 32) & 0xffffffff) * ((b.mPart0 >> 32) & 0xffffffff); + int128_t v12 = ((a.mPart0 >> 32) & 0xffffffff) * (b.mPart1 & 0xffffffff); + + int128_t v20 = (a.mPart1 & 0xffffffff) * (b.mPart0 & 0xffffffff); + int128_t v21 = (a.mPart1 & 0xffffffff) * ((b.mPart0 >> 32) & 0xffffffff); + + int128_t v30 = ((a.mPart1 >> 32) & 0xffffffff) * (b.mPart0 & 0xffffffff); + + // Do row addition, shifting as needed. + OperatorPlus(result, v01 << 32, result); + OperatorPlus(result, v02 << 64, result); + OperatorPlus(result, v03 << 96, result); + + OperatorPlus(result, v10 << 32, result); + OperatorPlus(result, v11 << 64, result); + OperatorPlus(result, v12 << 96, result); + + OperatorPlus(result, v20 << 64, result); + OperatorPlus(result, v21 << 96, result); + + OperatorPlus(result, v30 << 96, result); + #endif +} + +/////////////////////////////////////////////////////////////////////////////// +// OperatorShiftRight +// +// Returns: value >> nShift into result +// The output 'result' may *not* be the same as one the input. +// With rightward shifts of negative numbers, shift in zero from the left side. +// +inline void int128_t_base::OperatorShiftRight(const int128_t_base& value, int nShift, int128_t_base& result) +{ + if(nShift >= 0) + { + if(nShift < 64) + { // 0 - 63 + result.mPart1 = (value.mPart1 >> nShift); + + if(nShift == 0) + result.mPart0 = (value.mPart0 >> nShift); + else + result.mPart0 = (value.mPart0 >> nShift) | (value.mPart1 << (64 - nShift)); + } + else + { // 64+ + result.mPart1 = 0; + result.mPart0 = (value.mPart1 >> (nShift - 64)); + } + } + else // (nShift < 0) + OperatorShiftLeft(value, -nShift, result); +} + + +/////////////////////////////////////////////////////////////////////////////// +// OperatorShiftRight +// +// Returns: value << nShift into result +// The output 'result' may *not* be the same as one the input. +// With rightward shifts of negative numbers, shift in zero from the left side. +// +inline void int128_t_base::OperatorShiftLeft(const int128_t_base& value, int nShift, int128_t_base& result) +{ + if(nShift >= 0) + { + if(nShift < 64) + { + if(nShift) // We need to have a special case because CPUs convert a shift by 64 to a no-op. + { + // 1 - 63 + result.mPart0 = (value.mPart0 << nShift); + result.mPart1 = (value.mPart1 << nShift) | (value.mPart0 >> (64 - nShift)); + } + else + { + result.mPart0 = value.mPart0; + result.mPart1 = value.mPart1; + } + } + else + { // 64+ + result.mPart0 = 0; + result.mPart1 = (value.mPart0 << (nShift - 64)); + } + } + else // (nShift < 0) + OperatorShiftRight(value, -nShift, result); +} + + +inline bool int128_t_base::operator!() const +{ + return (mPart0 == 0) && (mPart1 == 0); +} + + +/////////////////////////////////////////////////////////////////////////////// +// OperatorXOR +// +// Returns: value1 ^ value2 into result +// The output 'result' may be the same as one the input. +// +inline void int128_t_base::OperatorXOR(const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result) +{ + result.mPart0 = (value1.mPart0 ^ value2.mPart0); + result.mPart1 = (value1.mPart1 ^ value2.mPart1); +} + + +/////////////////////////////////////////////////////////////////////////////// +// OperatorOR +// +// Returns: value1 | value2 into result +// The output 'result' may be the same as one the input. +// +inline void int128_t_base::OperatorOR(const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result) +{ + result.mPart0 = (value1.mPart0 | value2.mPart0); + result.mPart1 = (value1.mPart1 | value2.mPart1); +} + + +/////////////////////////////////////////////////////////////////////////////// +// OperatorAND +// +// Returns: value1 & value2 into result +// The output 'result' may be the same as one the input. +// +inline void int128_t_base::OperatorAND(const int128_t_base& value1, const int128_t_base& value2, int128_t_base& result) +{ + result.mPart0 = (value1.mPart0 & value2.mPart0); + result.mPart1 = (value1.mPart1 & value2.mPart1); +} + + +inline bool int128_t_base::IsZero() const +{ + return (mPart0 == 0) && // Check mPart0 first as this will likely yield faster execution. + (mPart1 == 0); +} + + +inline void int128_t_base::SetZero() +{ + mPart1 = 0; + mPart0 = 0; +} + + +inline void int128_t_base::TwosComplement() +{ + mPart1 = ~mPart1; + mPart0 = ~mPart0; + + // What we want to do, but isn't available at this level: + // operator++(); + // Alternative: + int128_t_base one((uint32_t)1); + OperatorPlus(*this, one, *this); +} + + +inline void int128_t_base::InverseTwosComplement() +{ + // What we want to do, but isn't available at this level: + // operator--(); + // Alternative: + int128_t_base one((uint32_t)1); + OperatorMinus(*this, one, *this); + + mPart1 = ~mPart1; + mPart0 = ~mPart0; +} + + +inline void int128_t_base::DoubleToUint128(double value) +{ + // Currently this function is limited to 64 bits of integer input. + // We need to make a better version of this function. Perhaps we should implement + // it via dissecting the IEEE floating point format (sign, exponent, matissa). + // EA_ASSERT(fabs(value) < 18446744073709551616.0); // Assert that the input is <= 64 bits of integer. + + mPart1 = 0; + mPart0 = (value >= 0 ? (uint64_t)value : (uint64_t)-value); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// uint128_t implementation +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +inline uint128_t uint128_t::operator^(const uint128_t& other) const +{ + uint128_t temp; + uint128_t::OperatorXOR(*this, other, temp); + return temp; +} + +inline uint128_t uint128_t::operator|(const uint128_t& other) const +{ + uint128_t temp; + uint128_t::OperatorOR(*this, other, temp); + return temp; +} + +inline uint128_t uint128_t::operator&(const uint128_t& other) const +{ + uint128_t temp; + uint128_t::OperatorAND(*this, other, temp); + return temp; +} + +inline uint128_t& uint128_t::operator^=(const uint128_t& value) +{ + OperatorXOR(*this, value, *this); + return *this; +} + +inline uint128_t& uint128_t::operator|=(const uint128_t& value) +{ + OperatorOR(*this, value, *this); + return *this; +} + +inline uint128_t& uint128_t::operator&=(const uint128_t& value) +{ + OperatorAND(*this, value, *this); + return *this; +} + +// With rightward shifts of negative numbers, shift in zero from the left side. +inline uint128_t uint128_t::operator>>(int nShift) const +{ + uint128_t temp; + OperatorShiftRight(*this, nShift, temp); + return temp; +} + +// With rightward shifts of negative numbers, shift in zero from the left side. +inline uint128_t uint128_t::operator<<(int nShift) const +{ + uint128_t temp; + OperatorShiftLeft(*this, nShift, temp); + return temp; +} + +inline uint128_t& uint128_t::operator>>=(int nShift) +{ + uint128_t temp; + OperatorShiftRight(*this, nShift, temp); + *this = temp; + return *this; +} + +inline uint128_t& uint128_t::operator<<=(int nShift) +{ + uint128_t temp; + OperatorShiftLeft(*this, nShift, temp); + *this = temp; + return *this; +} + +inline uint128_t& uint128_t::operator+=(const uint128_t& value) +{ + OperatorPlus(*this, value, *this); + return *this; +} + +inline uint128_t& uint128_t::operator-=(const uint128_t& value) +{ + OperatorMinus(*this, value, *this); + return *this; +} + +inline uint128_t& uint128_t::operator*=(const uint128_t& value) +{ + *this = *this * value; + return *this; +} + +inline uint128_t& uint128_t::operator/=(const uint128_t& value) +{ + *this = *this / value; + return *this; +} + +inline uint128_t& uint128_t::operator%=(const uint128_t& value) +{ + *this = *this % value; + return *this; +} + +inline uint128_t uint128_t::operator+(const uint128_t& other) +{ + uint128_t temp; + uint128_t::OperatorPlus(*this, other, temp); + return temp; +} + +inline uint128_t uint128_t::operator-(const uint128_t& other) +{ + uint128_t temp; + uint128_t::OperatorMinus(*this, other, temp); + return temp; +} + +inline uint128_t uint128_t::operator*(const uint128_t& other) +{ + uint128_t returnValue; + int128_t_base::OperatorMul(*this, other, returnValue); + return returnValue; +} + +inline uint128_t uint128_t::operator/(const uint128_t& other) +{ + uint128_t remainder; + uint128_t quotient; + this->Modulus(other, quotient, remainder); + return quotient; +} + +inline uint128_t uint128_t::operator%(const uint128_t& other) +{ + uint128_t remainder; + uint128_t quotient; + this->Modulus(other, quotient, remainder); + return remainder; +} + +inline uint128_t uint128_t::operator+() const +{ + return *this; +} + +inline uint128_t uint128_t::operator~() const +{ + return uint128_t(~mPart0, ~mPart1); +} + +inline uint128_t& uint128_t::operator--() +{ + int128_t_base one((uint32_t)1); + OperatorMinus(*this, one, *this); + return *this; +} + +inline uint128_t uint128_t::operator--(int) +{ + uint128_t temp((uint32_t)1); + OperatorMinus(*this, temp, temp); + return temp; +} + +inline uint128_t uint128_t::operator++(int) +{ + uint128_t prev = *this; + uint128_t temp((uint32_t)1); + OperatorPlus(*this, temp, *this); + return prev; +} + +inline uint128_t& uint128_t::operator++() +{ + int128_t_base one((uint32_t)1); + OperatorPlus(*this, one, *this); + return *this; +} + +inline void uint128_t::Negate() +{ + TwosComplement(); +} + +inline uint128_t uint128_t::operator-() const +{ + uint128_t returnValue(*this); + returnValue.Negate(); + return returnValue; +} + +// This function forms the basis of all logical comparison functions. +// If value1 < value2, the return value is -1. +// If value1 == value2, the return value is 0. +// If value1 > value2, the return value is 1. +inline int uint128_t::compare(const uint128_t& other) const +{ + // Compare individual parts. At this point, the two numbers have the same sign. + if(mPart1 == other.mPart1) + { + if(mPart0 == other.mPart0) + return 0; + else if(mPart0 > other.mPart0) + return 1; + // return -1; //Just fall through to the end. + } + else if(mPart1 > other.mPart1) + return 1; + return -1; +} + +EA_DISABLE_VC_WARNING(4723) // warning C4723: potential divide by 0 +inline void uint128_t::Modulus(const uint128_t& divisor, uint128_t& quotient, uint128_t& remainder) const +{ + uint128_t tempDividend(*this); + uint128_t tempDivisor(divisor); + + if(tempDivisor.IsZero()) + { + // Force a divide by zero exception. + // We know that tempDivisor.mPart0 is zero. + quotient.mPart0 /= tempDivisor.mPart0; + } + else if(tempDividend.IsZero()) + { + quotient = uint128_t((uint32_t)0); + remainder = uint128_t((uint32_t)0); + } + else + { + remainder.SetZero(); + + for(int i(0); i < 128; i++) + { + remainder += (uint32_t)tempDividend.GetBit(127 - i); + const bool bBit(remainder >= tempDivisor); + quotient.SetBit(127 - i, bBit); + + if(bBit) + remainder -= tempDivisor; + + if((i != 127) && !remainder.IsZero()) + remainder <<= 1; + } + } +} +EA_RESTORE_VC_WARNING() + +inline bool uint128_t::operator==(const uint128_t& other) const +{ + return (mPart0 == other.mPart0) && // Check mPart0 first as this will likely yield faster execution. + (mPart1 == other.mPart1); +} + +inline bool uint128_t::operator< (const uint128_t& other) const { return (compare(other) < 0); } +inline bool uint128_t::operator!=(const uint128_t& other) const { return !(*this == other); } +inline bool uint128_t::operator> (const uint128_t& other) const { return other < *this; } +inline bool uint128_t::operator>=(const uint128_t& other) const { return !(*this < other); } +inline bool uint128_t::operator<=(const uint128_t& other) const { return !(other < *this); } + +inline bool uint128_t::IsNegative() const +{ // True if value < 0 + return false; +} + +inline bool uint128_t::IsPositive() const +{ + // True of value >= 0 + return true; +} + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// int128_t implementation +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +inline void int128_t::Negate() +{ + if (IsPositive()) + TwosComplement(); + else + InverseTwosComplement(); +} + +inline int128_t int128_t::operator-() const +{ + int128_t returnValue(*this); + returnValue.Negate(); + return returnValue; +} + +inline int128_t& int128_t::operator++() +{ + int128_t_base one((uint32_t)1); + OperatorPlus(*this, one, *this); + return *this; +} + +inline int128_t& int128_t::operator--() +{ + int128_t_base one((uint32_t)1); + OperatorMinus(*this, one, *this); + return *this; +} + +inline int128_t int128_t::operator++(int) +{ + int128_t prev = *this; + int128_t temp((uint32_t)1); + OperatorPlus(*this, temp, *this); + return prev; +} + +inline int128_t int128_t::operator--(int) +{ + int128_t temp((uint32_t)1); + OperatorMinus(*this, temp, temp); + return temp; +} + +inline int128_t int128_t::operator+() const +{ + return *this; +} + +inline int128_t int128_t::operator~() const +{ + return int128_t(~mPart0, ~mPart1); +} + +inline int128_t int128_t::operator+(const int128_t& other) +{ + int128_t temp; + int128_t::OperatorPlus(*this, other, temp); + return temp; +} + +inline int128_t int128_t::operator-(const int128_t& other) +{ + int128_t temp; + int128_t::OperatorMinus(*this, other, temp); + return temp; +} + +// This function forms the basis of all logical comparison functions. +// If value1 < value2, the return value is -1. +// If value1 == value2, the return value is 0. +// If value1 > value2, the return value is 1. +inline int int128_t::compare(const int128_t& other) const +{ + // Cache some values. Positive means >= 0. Negative means < 0 and thus means '!positive'. + const bool bValue1IsPositive( IsPositive()); + const bool bValue2IsPositive(other.IsPositive()); + + // Do positive/negative tests. + if(bValue1IsPositive != bValue2IsPositive) + return bValue1IsPositive ? 1 : -1; + + // Compare individual parts. At this point, the two numbers have the same sign. + if(mPart1 == other.mPart1) + { + if(mPart0 == other.mPart0) + return 0; + else if(mPart0 > other.mPart0) + return 1; + // return -1; //Just fall through to the end. + } + else if(mPart1 > other.mPart1) + return 1; + return -1; +} + +inline bool int128_t::operator==(const int128_t& other) const +{ + return (mPart0 == other.mPart0) && // Check mPart0 first as this will likely yield faster execution. + (mPart1 == other.mPart1); +} + +inline bool int128_t::operator!=(const int128_t& other) const +{ + return (mPart0 != other.mPart0) || // Check mPart0 first as this will likely yield faster execution. + (mPart1 != other.mPart1); +} + +inline bool int128_t::operator>(const int128_t& other) const +{ + return (compare(other) > 0); +} + +inline bool int128_t::operator>=(const int128_t& other) const +{ + return (compare(other) >= 0); +} + +inline bool int128_t::operator<(const int128_t& other) const +{ + return (compare(other) < 0); +} + +inline bool int128_t::operator<=(const int128_t& other) const +{ + return (compare(other) <= 0); +} + +inline bool int128_t::IsNegative() const +{ // True if value < 0 + return ((mPart1 & UINT64_C(0x8000000000000000)) != 0); +} + +inline bool int128_t::IsPositive() const +{ // True of value >= 0 + return ((mPart1 & UINT64_C(0x8000000000000000)) == 0); +} + +inline int128_t int128_t::operator*(const int128_t& other) +{ + int128_t a(*this); + int128_t b(other); + int128_t returnValue; + + // Correctly handle negative values + bool bANegative(false); + bool bBNegative(false); + + if(a.IsNegative()) + { + bANegative = true; + a.Negate(); + } + + if(b.IsNegative()) + { + bBNegative = true; + b.Negate(); + } + + int128_t_base::OperatorMul(a, b, returnValue); + + // Do negation as needed. + if(bANegative != bBNegative) + returnValue.Negate(); + + return returnValue; +} + +inline int128_t int128_t::operator/(const int128_t& other) +{ + int128_t remainder; + int128_t quotient; + this->Modulus(other, quotient, remainder); + return quotient; +} + +inline int128_t int128_t::operator<<(int nShift) const +{ + int128_t temp; + OperatorShiftLeft(*this, nShift, temp); + return temp; +} + +inline int128_t& int128_t::operator+=(const int128_t& value) +{ + OperatorPlus(*this, value, *this); + return *this; +} + +inline int128_t& int128_t::operator-=(const int128_t& value) +{ + OperatorMinus(*this, value, *this); + return *this; +} + +inline int128_t& int128_t::operator<<=(int nShift) +{ + int128_t temp; + OperatorShiftLeft(*this, nShift, temp); + *this = temp; + return *this; +} + +inline int128_t& int128_t::operator*=(const int128_t& value) +{ + *this = *this * value; + return *this; +} + +inline int128_t& int128_t::operator%=(const int128_t& value) +{ + *this = *this % value; + return *this; +} + +inline int128_t int128_t::operator%(const int128_t& other) +{ + int128_t remainder; + int128_t quotient; + this->Modulus(other, quotient, remainder); + return remainder; +} + +inline int128_t& int128_t::operator/=(const int128_t& value) +{ + *this = *this / value; + return *this; +} + +// With rightward shifts of negative numbers, shift in zero from the left side. +inline int128_t int128_t::operator>>(int nShift) const +{ + int128_t temp; + OperatorShiftRight(*this, nShift, temp); + return temp; +} + +inline int128_t& int128_t::operator>>=(int nShift) +{ + int128_t temp; + OperatorShiftRight(*this, nShift, temp); + *this = temp; + return *this; +} + +inline int128_t int128_t::operator^(const int128_t& other) const +{ + int128_t temp; + int128_t::OperatorXOR(*this, other, temp); + return temp; +} + +inline int128_t int128_t::operator|(const int128_t& other) const +{ + int128_t temp; + int128_t::OperatorOR(*this, other, temp); + return temp; +} + + +inline int128_t int128_t::operator&(const int128_t& other) const +{ + int128_t temp; + int128_t::OperatorAND(*this, other, temp); + return temp; +} + +inline int128_t& int128_t::operator^=(const int128_t& value) +{ + OperatorXOR(*this, value, *this); + return *this; +} + +inline int128_t& int128_t::operator|=(const int128_t& value) +{ + OperatorOR(*this, value, *this); + return *this; +} + +inline int128_t& int128_t::operator&=(const int128_t& value) +{ + OperatorAND(*this, value, *this); + return *this; +} + +EA_DISABLE_VC_WARNING(4723) // warning C4723: potential divide by 0 +inline void int128_t::Modulus(const int128_t& divisor, int128_t& quotient, int128_t& remainder) const +{ + int128_t tempDividend(*this); + int128_t tempDivisor(divisor); + + bool bDividendNegative = false; + bool bDivisorNegative = false; + + if(tempDividend.IsNegative()) + { + bDividendNegative = true; + tempDividend.Negate(); + } + if(tempDivisor.IsNegative()) + { + bDivisorNegative = true; + tempDivisor.Negate(); + } + + // Handle the special cases + if(tempDivisor.IsZero()) + { + // Force a divide by zero exception. + // We know that tempDivisor.mPart0 is zero. + quotient.mPart0 /= tempDivisor.mPart0; + } + else if(tempDividend.IsZero()) + { + quotient = int128_t((uint32_t)0); + remainder = int128_t((uint32_t)0); + } + else + { + remainder.SetZero(); + + for(int i(0); i < 128; i++) + { + remainder += (uint32_t)tempDividend.GetBit(127 - i); + const bool bBit(remainder >= tempDivisor); + quotient.SetBit(127 - i, bBit); + + if(bBit) + remainder -= tempDivisor; + + if((i != 127) && !remainder.IsZero()) + remainder <<= 1; + } + } + + if((bDividendNegative && !bDivisorNegative) || (!bDividendNegative && bDivisorNegative)) + { + // Ensure the following formula applies for negative dividends + // dividend = divisor * quotient + remainder + quotient.Negate(); + } +} +EA_RESTORE_VC_WARNING() + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// INT128_C / UINT128_C +// +// The C99 language defines macros for portably defining constants of +// sized numeric types. For example, there might be: +// #define UINT64_C(x) x##ULL +// Since our int128 data type is not a built-in type, we can't define a +// UINT128_C macro as something that pastes ULLL at the end of the digits. +// Instead we define it to create a temporary that is constructed from a +// string of the digits. This will work in most cases that suffix pasting +// would work. +// +/* EA_CONSTEXPR */ inline uint128_t UINT128_C(uint64_t nPart1, uint64_t nPart0) { return uint128_t(nPart0, nPart1); } +/* EA_CONSTEXPR */ inline int128_t INT128_C(int64_t nPart1, int64_t nPart0) { return int128_t(static_cast(nPart0), static_cast(nPart1)); } + + + + +#endif // INCLUDED_int128_h + diff --git a/src/thirdparty/ea/EABase/nullptr.h b/src/thirdparty/ea/EABase/nullptr.h new file mode 100644 index 00000000..d6629d50 --- /dev/null +++ b/src/thirdparty/ea/EABase/nullptr.h @@ -0,0 +1,102 @@ +/*----------------------------------------------------------------------------- + * nullptr.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *---------------------------------------------------------------------------*/ + + +#include +#include + + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once /* Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. */ +#endif + + +#if defined(EA_COMPILER_CPP11_ENABLED) && !defined(EA_COMPILER_NO_NULLPTR) && !defined(EA_HAVE_nullptr_t_IMPL) + // The compiler supports nullptr, but the standard library doesn't implement a declaration for std::nullptr_t. So we provide one. + namespace std { typedef decltype(nullptr) nullptr_t; } +#endif + + + +#if defined(EA_COMPILER_NO_NULLPTR) // If the compiler lacks a native version... + + namespace std + { + class nullptr_t + { + public: + template // When tested a pointer, acts as 0. + operator T*() const + { return 0; } + + template // When tested as a member pointer, acts as 0. + operator T C::*() const + { return 0; } + + typedef void* (nullptr_t::*bool_)() const; + operator bool_() const // An rvalue of type std::nullptr_t can be converted to an rvalue of type bool; the resulting value is false. + { return false; } // We can't use operator bool(){ return false; } because bool is convertable to int which breaks other required functionality. + + // We can't enable this without generating warnings about nullptr being uninitialized after being used when created without "= {}". + //void* mSizeofVoidPtr; // sizeof(nullptr_t) == sizeof(void*). Needs to be public if nullptr_t is to be a POD. + + private: + void operator&() const; // Address cannot be taken. + }; + + inline nullptr_t nullptr_get() + { + nullptr_t n = { }; // std::nullptr exists. + return n; + } + + #if !defined(nullptr) // If somebody hasn't already defined nullptr in a custom way... + #define nullptr nullptr_get() + #endif + + } // namespace std + + + template + inline bool operator==(T* p, const std::nullptr_t) + { return p == 0; } + + template + inline bool operator==(const std::nullptr_t, T* p) + { return p == 0; } + + template + inline bool operator==(T U::* p, const std::nullptr_t) + { return p == 0; } + + template + inline bool operator==(const std::nullptr_t, T U::* p) + { return p == 0; } + + inline bool operator==(const std::nullptr_t, const std::nullptr_t) + { return true; } + + inline bool operator!=(const std::nullptr_t, const std::nullptr_t) + { return false; } + + inline bool operator<(const std::nullptr_t, const std::nullptr_t) + { return false; } + + inline bool operator>(const std::nullptr_t, const std::nullptr_t) + { return false; } + + inline bool operator<=(const std::nullptr_t, const std::nullptr_t) + { return true; } + + inline bool operator>=(const std::nullptr_t, const std::nullptr_t) + { return true; } + + + using std::nullptr_t; // exported to global namespace. + using std::nullptr_get; // exported to global namespace. + +#endif // EA_COMPILER_NO_NULLPTR + diff --git a/src/thirdparty/ea/EABase/version.h b/src/thirdparty/ea/EABase/version.h new file mode 100644 index 00000000..4c8111be --- /dev/null +++ b/src/thirdparty/ea/EABase/version.h @@ -0,0 +1,36 @@ +/*----------------------------------------------------------------------------- + * version.h + * + * Copyright (c) Electronic Arts Inc. All rights reserved. + *---------------------------------------------------------------------------*/ + +#ifndef INCLUDED_EABASE_VERSION_H +#define INCLUDED_EABASE_VERSION_H + +/////////////////////////////////////////////////////////////////////////////// +// EABASE_VERSION +// +// We more or less follow the conventional EA packaging approach to versioning +// here. A primary distinction here is that minor versions are defined as two +// digit entities (e.g. .03") instead of minimal digit entities ".3"). The logic +// here is that the value is a counter and not a floating point fraction. +// Note that the major version doesn't have leading zeros. +// +// Example version strings: +// "0.91.00" // Major version 0, minor version 91, patch version 0. +// "1.00.00" // Major version 1, minor and patch version 0. +// "3.10.02" // Major version 3, minor version 10, patch version 02. +// "12.03.01" // Major version 12, minor version 03, patch version +// +// Example usage: +// printf("EABASE version: %s", EABASE_VERSION); +// printf("EABASE version: %d.%d.%d", EABASE_VERSION_N / 10000 % 100, EABASE_VERSION_N / 100 % 100, EABASE_VERSION_N % 100); +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef EABASE_VERSION + #define EABASE_VERSION "2.10.01" + #define EABASE_VERSION_N 21001 +#endif + +#endif diff --git a/src/thirdparty/ea/EAThread/.gitignore b/src/thirdparty/ea/EAThread/.gitignore new file mode 100644 index 00000000..8d148cdc --- /dev/null +++ b/src/thirdparty/ea/EAThread/.gitignore @@ -0,0 +1,49 @@ +tags +cscope.out +**/*.swp +**/*.swo +.swp +*.swp +.swo +.TMP +-.d +eastl_build_out +build_bench +bench.bat +build.bat +.p4config + +## CMake generated files +CMakeCache.txt +cmake_install.cmake + +## Patch files +*.patch + +## For Visual Studio Generated projects +*.sln +**/*.vcxproj +**/*.vcxproj.filters +*.VC.opendb +*.sdf +**/*.suo +**/*.user +.vs/* +**/Debug/* +CMakeFiles/* +EASTL.dir/** +RelWithDebInfo/* +Release/* +Win32/* +x64/* +MinSizeRel/* +build*/* +Testing/* +%ALLUSERSPROFILE%/* + +# Buck +/buck-out/ +/.buckd/ +/buckaroo/ +.buckconfig.local +BUCKAROO_DEPS diff --git a/src/thirdparty/ea/EAThread/.p4ignore b/src/thirdparty/ea/EAThread/.p4ignore new file mode 100644 index 00000000..55cf7354 --- /dev/null +++ b/src/thirdparty/ea/EAThread/.p4ignore @@ -0,0 +1 @@ +tags \ No newline at end of file diff --git a/src/thirdparty/ea/EAThread/3RDPARTYLICENSES.TXT b/src/thirdparty/ea/EAThread/3RDPARTYLICENSES.TXT new file mode 100644 index 00000000..5512dc85 --- /dev/null +++ b/src/thirdparty/ea/EAThread/3RDPARTYLICENSES.TXT @@ -0,0 +1,21 @@ +Additional licenses also apply to this software package as detailed below. + +-------------------------------------------------------------------------- +Copyright (c) 2015 Jeff Preshing + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgement in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +-------------------------------------------------------------------------- diff --git a/src/thirdparty/ea/EAThread/CMakeLists.txt b/src/thirdparty/ea/EAThread/CMakeLists.txt new file mode 100644 index 00000000..bb56026e --- /dev/null +++ b/src/thirdparty/ea/EAThread/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required( VERSION 3.16 ) +add_module( "lib" "EAThread" "" ${FOLDER_CONTEXT} TRUE TRUE ) + +start_sources() + +add_sources( SOURCE_GROUP "Base" + "source/eathread.cpp" + "source/eathread_barrier.cpp" + "source/eathread_callstack.cpp" + "source/eathread_condition.cpp" + "source/eathread_futex.cpp" + "source/eathread_mutex.cpp" + "source/eathread_pool.cpp" + "source/eathread_rwmutex.cpp" + "source/eathread_rwmutex_ip.cpp" + "source/eathread_semaphore.cpp" + "source/eathread_storage.cpp" + "source/eathread_thread.cpp" + "source/version.cpp" +) + +add_sources( SOURCE_GROUP "Base/Include" + "include/eathread/eathread.h" + "include/eathread/eathread_atomic.h" + "include/eathread/eathread_barrier.h" + "include/eathread/eathread_callstack.h" + "include/eathread/eathread_callstack_context.h" + "include/eathread/eathread_condition.h" + "include/eathread/eathread_futex.h" + "include/eathread/eathread_list.h" + "include/eathread/eathread_mutex.h" + "include/eathread/eathread_pool.h" + "include/eathread/eathread_rwmutex.h" + "include/eathread/eathread_rwmutex_ip.h" + "include/eathread/eathread_rwsemalock.h" + "include/eathread/eathread_rwspinlock.h" + "include/eathread/eathread_rwspinlockw.h" + "include/eathread/eathread_semaphore.h" + "include/eathread/eathread_spinlock.h" + "include/eathread/eathread_storage.h" + "include/eathread/eathread_sync.h" + "include/eathread/eathread_thread.h" + "include/eathread/shared_array_mt.h" + "include/eathread/shared_ptr_mt.h" + "include/eathread/version.h" +) + +add_sources( SOURCE_GROUP "Base/Include/Internal" + "include/eathread/internal/config.h" + "include/eathread/internal/dllinfo.h" + "include/eathread/internal/eathread_atomic_standalone.h" + "include/eathread/internal/eathread_atomic_standalone_gcc.h" + "include/eathread/internal/eathread_atomic_standalone_msvc.h" + "include/eathread/internal/eathread_global.h" + "include/eathread/internal/timings.h" +) + +#add_sources( SOURCE_GROUP "PC64" +# "source/pc/eathread_callstack_win64.cpp" +# "source/pc/eathread_mutex_pc.cpp" +# "source/pc/eathread_pc.cpp" +# "source/pc/eathread_semaphore_pc.cpp" +# "source/pc/eathread_thread_pc.cpp" +#) + +#add_sources( SOURCE_GROUP "PC64/Include" +# "include/eathread/x86-64/eathread_atomic_x86-64.h" +# "include/eathread/x86-64/eathread_sync_x86-64.h" +#) + +# add_sources( SOURCE_GROUP "CPP11" +# "source/cpp11/eathread_cpp11.cpp" +# "source/cpp11/eathread_mutex_cpp11.cpp" +# "source/cpp11/eathread_semaphore_cpp11.cpp" +# "source/cpp11/eathread_thread_cpp11.cpp" +# ) +# +# add_sources( SOURCE_GROUP "CPP11/Include" +# "include/eathread/cpp11/eathread_atomic_cpp11.h" +# ) + +end_sources() +thirdparty_suppress_warnings() + +target_include_directories( ${PROJECT_NAME} PRIVATE + "${THIRDPARTY_SOURCE_DIR}/ea/" + "${THIRDPARTY_SOURCE_DIR}/ea/EAThread/include/" +) diff --git a/src/thirdparty/ea/EAThread/LICENSE b/src/thirdparty/ea/EAThread/LICENSE new file mode 100644 index 00000000..93ab2288 --- /dev/null +++ b/src/thirdparty/ea/EAThread/LICENSE @@ -0,0 +1,27 @@ +/* +Copyright (C) 2017 Electronic Arts Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ diff --git a/src/thirdparty/ea/EAThread/include/eathread/cpp11/eathread_atomic_cpp11.h b/src/thirdparty/ea/EAThread/include/eathread/cpp11/eathread_atomic_cpp11.h new file mode 100644 index 00000000..8c9b2ae8 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/cpp11/eathread_atomic_cpp11.h @@ -0,0 +1,131 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// eathread_atomic.h +// +// Defines functionality for thread-safe primitive operations. +// +// EAThread atomics do NOT imply the use of read/write barriers. This is +// partly due to historical reasons and partly due to EAThread's internal +// code being optimized for not using barriers. +// +// In future, we are considering migrating the atomics interface which +// defaults atomics to use full read/write barriers while allowing users +// to opt-out of full barrier usage. The new C++11 interface already provides +// similar interfaces. +// +// http://en.cppreference.com/w/cpp/atomic/memory_order +// +// Created by Rob Parolin +///////////////////////////////////////////////////////////////////////////// + +#ifndef EATHREAD_INTERNAL_EATHREAD_ATOMIC_H +#define EATHREAD_INTERNAL_EATHREAD_ATOMIC_H + +#include +#include +#include +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +#define EA_THREAD_ATOMIC_IMPLEMENTED + +namespace EA +{ + namespace Thread + { + /// class AtomicInt + /// + /// Implements thread-safe access to an integer and primary operations on that integer. + /// AtomicIntegers are commonly used as lightweight flags and signals between threads + /// or as the synchronization object for spinlocks. Those familiar with the Win32 API + /// will find that AtomicInt32 is essentially a platform independent interface to + /// the Win32 InterlockedXXX family of functions. Those familiar with Linux may + /// find that AtomicInt32 is essentially a platform independent interface to atomic_t + /// functionality. + /// + /// Note that the reference implementation defined here is itself not thread-safe. + /// A thread-safe version requires platform-specific code. + /// + /// Example usage + /// AtomicInt32 i = 0; + /// + /// ++i; + /// i--; + /// i += 7; + /// i -= 3; + /// i = 2; + /// + /// int x = i.GetValue(); + /// i.Increment(); + /// bool oldValueWas6 = i.SetValueConditional(3, 6); + /// i.Add(4); + /// + template + class AtomicInt + { + public: + typedef AtomicInt ThisType; + typedef T ValueType; + + /// AtomicInt + /// Empty constructor. Intentionally leaves mValue in an unspecified state. + /// This is done so that an AtomicInt acts like a standard built-in integer. + AtomicInt() + {} + + AtomicInt(ValueType n) + { SetValue(n); } + + AtomicInt(const ThisType& x) + { SetValue(x.GetValue()); } + + AtomicInt& operator=(const ThisType& x) + { SetValue(x.GetValue()); return *this; } + + ValueType GetValue() const + { return mValue.load(); } + + ValueType GetValueRaw() const + { return mValue; } + + ValueType SetValue(ValueType n) + { return mValue.exchange(n); } + + bool SetValueConditional(ValueType n, ValueType condition) + { return mValue.compare_exchange_strong(condition, n); } + + ValueType Increment() + { return mValue.operator++(); } + + ValueType Decrement() + { return mValue.operator--(); } + + ValueType Add(ValueType n) + { return mValue.fetch_add(n) + n; } + + // operators + inline operator const ValueType() const { return GetValue(); } + inline ValueType operator =(ValueType n) { return mValue.operator=(n); } + inline ValueType operator+=(ValueType n) { return mValue.operator+=(n); } + inline ValueType operator-=(ValueType n) { return mValue.operator-=(n); } + inline ValueType operator++() { return mValue.operator++(); } + inline ValueType operator++(int) { return mValue.operator++(0); } + inline ValueType operator--() { return mValue.operator--(); } + inline ValueType operator--(int) { return mValue.operator--(0); } + + protected: + std::atomic mValue; + }; + + } // namespace Thread +} // namespace EA + + +#endif // EATHREAD_INTERNAL_EATHREAD_ATOMIC_H + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread.h b/src/thirdparty/ea/EAThread/include/eathread/eathread.h new file mode 100644 index 00000000..a0853f61 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread.h @@ -0,0 +1,832 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// eathread.h +// +// Created by Paul Pedriana, Maxis +// +// Provides some base global definitions for the EA::Thread library. +// +// Design +// Many of the design criteria for EA::Thread is based on the design of the +// Posix threading standard. The Posix threading standard is designed to +// work portably on a wide range of operating systems and hardware, including +// embedded systems and realtime environments. As such, Posix threads generally +// represent a competent model to follow where possible. Windows and various +// other platforms have independent multi-threading systems which are taken +// into account here as well. If something exists in Windows but doesn't +// exist here (e.g. Thread suspend/resume), there is a decent chance that it +// is by design and for some good reason. +// +// C++ +// There are a number of C++ libraries devoted to multithreading. Usually the +// goal of these libraries is provide a platform independent interface which +// simplifies the most common usage patterns and helps prevent common errors. +// Some of these libraries are basic wrappers around existing C APIs while +// others provide a new and different paradigm. We take the former approach +// here, as it is provides more or less the same functionality but provides +// it in a straightforward way that is easily approached by those familiar +// with platform-specific APIs. This approach has been referred to as the +// "Wrapper Facade Pattern". +// +// Condition Variables +// Posix condition variables are implemented via the Condition class. Condition +// is essentially the Java and C# name for Posix' condition variables. For some +// people, a condition variable may seem similar to a Win32 Signal. In actuality +// they are similar but there is one critical difference: a Signal does not +// atomically unlock a mutex as part of the signaling process. This results in +// problematic race conditions that make reliable producer/consumer systems +// impossible to implement. +// +// Signals +// As of this writing, there isn't a Win32-like Signal class. The reason for this +// is that Semaphore does most or all the duty that Signal does and is a little +// more portable, given that Signals exist only on Win32 and not elsewhere. +// +// Timeouts +// Timeouts are specified as absolute times and not relative times. This may +// not be how Win32 threading works but it is what's proper and is how Posix +// threading works. From the OpenGroup online pthread documentation on this: +// An absolute time measure was chosen for specifying the +// timeout parameter for two reasons. First, a relative time +// measure can be easily implemented on top of a function +// that specifies absolute time, but there is a race +// condition associated with specifying an absolute timeout +// on top of a function that specifies relative timeouts. +// For example, assume that clock_gettime() returns the +// current time and cond_relative_timed_wait() uses relative +// timeouts: +// clock_gettime(CLOCK_REALTIME, &now); +// reltime = sleep_til_this_absolute_time - now; +// cond_relative_timed_wait(c, m, &reltime); +// If the thread is preempted between the first statement and +// the last statement, the thread blocks for too long. Blocking, +// however, is irrelevant if an absolute timeout is used. +// An absolute timeout also need not be recomputed if it is used +// multiple times in a loop, such as that enclosing a condition wait. +// For cases when the system clock is advanced discontinuously by +// an operator, it is expected that implementations process any +// timed wait expiring at an intervening time as if that time had +// actually occurred. +// +// General Threads +// For detailed information about threads, it is recommended that you read +// various competent sources of information about multithreading and +// multiprocessing. +// Programming with POSIX(R) Threads, by David R. Butenhof +// http://www.opengroup.org/onlinepubs/007904975/basedefs/pthread.h.html +// usenet: comp.programming.threads +// http://www.openmp.org/index.cgi?faq +// http://www.lambdacs.com/cpt/MFAQ.html +// http://www.lambdacs.com/cpt/FAQ.html +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/processes_and_threads.asp +// +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_H +#define EATHREAD_EATHREAD_H + +#include + +#if !EA_THREADS_AVAILABLE + // Do nothing +#elif EA_USE_CPP11_CONCURRENCY + EA_DISABLE_VC_WARNING(4265 4365 4836 4571 4625 4626 4628 4193 4127 4548 4574 4946 4350) + #include + #include + EA_RESTORE_VC_WARNING() +#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) // Dinkumware doesn't usually provide gettimeofday or + #include // clock_gettime + #elif defined(EA_PLATFORM_UNIX) + #include // gettimeofday + #endif +#endif +#if defined(EA_PLATFORM_APPLE) + #include +#endif +#if defined(EA_PLATFORM_SONY) + #include "sdk_version.h" + #include +#endif +#include +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +// EA_THREAD_PREEMPTIVE / EA_THREAD_COOPERATIVE +// +// Defined or not defined. +// +// EA_THREAD_COOPERATIVE means that threads are not time-sliced by the +// operating system. If there exist multiple threads of the same priority +// then they will need to wait, sleep, or yield in order for the others +// to get time. See enum Scheduling and EATHREAD_SCHED for more info. +// +// EA_THREAD_PREEMPTIVE means that threads are time-sliced by the operating +// system at runtime. If there exist multiple threads of the same priority +// then the operating system will split execution time between them. +// See enum Scheduling and EATHREAD_SCHED for more info. +// +#if !EA_THREADS_AVAILABLE + #define EA_THREAD_COOPERATIVE +#else + #define EA_THREAD_PREEMPTIVE +#endif + + +/// namespace EA +/// +/// This is the standard Electronic Arts C++ namespace. +/// +namespace EA +{ + namespace Allocator + { + class ICoreAllocator; + } + + /// namespace Thread + /// + /// This is the standard Electronic Arts Thread C++ namespace. + /// + namespace Thread + { + /// Scheduling + /// Defines scheduling types supported by the given platform. + /// These are defined in detail by the Posix standard, with the + /// exception of Coop, which is added here. FIFO scheduling + /// is the most classic for game development, as it allows for + /// thread priorities and well-behaved synchronization primitives, + /// but it doesn't do time-slicing. The problem with time slicing + /// is that threads are pre-empted in the middle of work and this + /// hurts execution performance and cache performance. + /// + enum Scheduling + { + kSchedulingFIFO = 1, /// There is no automatic time-slicing; thread priorities control execution and context switches. + kSchedulingRR = 2, /// Same as FIFO but is periodic time-slicing. + kSchedulingSporadic = 4, /// Complex scheduling control. See the Posix standard. + kSchedulingTS = 8, /// a.k.a. SCHED_OTHER. Usually same as FIFO or RR except that thread priorities and execution can be temporarily modified. + kSchedulingCoop = 16 /// The user must control thread scheduling beyond the use of synchronization primitives. + }; + + #if defined(EA_PLATFORM_UNIX) + #define EATHREAD_SCHED kSchedulingFIFO + + #elif defined(EA_PLATFORM_MICROSOFT) + #define EATHREAD_SCHED kSchedulingRR + + #else + #define EATHREAD_SCHED kSchedulingFIFO + + #endif + + + // EATHREAD_MULTIPROCESSING_OS + // + // Defined as 0 or 1. + // Indicates whether the OS supports multiple concurrent processes, which may be in + // addition to supporting multiple threads within a process. + // Some platforms support multiple concurrently loaded processes but don't support + // running these processes concurrently. We don't currently count this as a + // multiprocessing OS. + #ifndef EATHREAD_MULTIPROCESSING_OS + #if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_UNIX) + #define EATHREAD_MULTIPROCESSING_OS 1 + #else + #define EATHREAD_MULTIPROCESSING_OS 0 + #endif + #endif + + // EATHREAD_OTHER_THREAD_NAMING_SUPPORTED + // + // Defined as 0 or 1. + // Indicates whether the OS supports setting the thread name from a different + // thread (set to 1) or if the name can be set only from the curren thread (set to 0) + #ifndef EATHREAD_OTHER_THREAD_NAMING_SUPPORTED + #if defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_APPLE) + #define EATHREAD_OTHER_THREAD_NAMING_SUPPORTED 0 + #else + #define EATHREAD_OTHER_THREAD_NAMING_SUPPORTED 1 + #endif + #endif + + // Uint / Int + // Defines a machine-word sized integer, useful for operations that are as efficient + // as possible on the given machine. Note that the C99 intfastNN_t types aren't sufficient, + // as they are defined by compilers in an undesirable way for the processors we work with. + #if !defined(EA_PLATFORM_WORD_SIZE) || (EA_PLATFORM_WORD_SIZE == 4) + typedef uint32_t Uint; + typedef int32_t Int; + #else + typedef uint64_t Uint; + typedef int64_t Int; + #endif + + + /// ThreadId + /// Uniquely identifies a thread throughout the system and is used by the EAThread API + /// to identify threads in a way equal to system provided thread ids. A ThreadId is the + /// same as a system thread id and can be used in direct system threading API calls. + #if !EA_THREADS_AVAILABLE + typedef int ThreadId; + #elif EA_USE_CPP11_CONCURRENCY + typedef std::thread::id ThreadId; + #elif defined(EA_PLATFORM_SONY) + typedef uint64_t ThreadId; + #elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + typedef pthread_t ThreadId; + #elif defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE + typedef void* ThreadId; // This is really HANDLE, but HANDLE is the same as void* and we can avoid an expensive #include here. + #else + typedef int ThreadId; + #endif + + + // ThreadId constants + #if EA_USE_CPP11_CONCURRENCY + const ThreadId kThreadIdInvalid = ThreadId(); /// Special ThreadId indicating an invalid thread identifier. + #else + const ThreadId kThreadIdInvalid = ThreadId(0); /// Special ThreadId indicating an invalid thread identifier. + const ThreadId kThreadIdCurrent = ThreadId(INT_MAX); /// Special ThreadId indicating the current thread. + const ThreadId kThreadIdAny = ThreadId(INT_MAX - 1); /// Special ThreadId indicating no thread in particular. + #endif + + /// SysThreadId + /// It turns out that Microsoft operating systems (Windows, XBox, XBox 360) + /// have two different ways to identify a thread: HANDLE and DWORD. Some API + /// functions take thread HANDLES, while others take what Microsoft calls + /// thread ids (DWORDs). EAThread ThreadId is a HANDLE, as that is used for + /// more of the core threading APIs. However, some OS-level APIs accept instead + /// the DWORD thread id. + #if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE && !EA_USE_CPP11_CONCURRENCY + typedef uint32_t SysThreadId; + const SysThreadId kSysThreadIdInvalid = SysThreadId(0); /// Special SysThreadId indicating an invalid thread identifier. + #elif defined(EA_PLATFORM_SONY) + typedef ScePthread SysThreadId; + const SysThreadId kSysThreadIdInvalid = { 0 }; /// Special SysThreadId indicating an invalid thread identifier. + #elif defined(EA_PLATFORM_APPLE) + typedef thread_act_t SysThreadId; // thread_act_t is useful for calling mach APIs such as thread_policy_set() with. + const SysThreadId kSysThreadIdInvalid = SysThreadId(0); /// Special SysThreadId indicating an invalid thread identifier. + #elif EA_USE_CPP11_CONCURRENCY + typedef std::thread::native_handle_type SysThreadId; + const SysThreadId kSysThreadIdInvalid = { 0 }; /// Special SysThreadId indicating an invalid thread identifier. + + // For MSVC, native_handle_type is not a primitive type so we define operator== and operator!= for convenience. + // We use an auto converting proxy type for comparisons to avoid errors when native_handle_type is a built in type. + bool Equals(const SysThreadId& a, const SysThreadId& b); + struct SysThreadIdProxy + { + SysThreadIdProxy(const SysThreadId& id_) : id(id_) {} + SysThreadId id; + }; + inline bool operator==(const SysThreadId& lhs, const SysThreadIdProxy& rhs) { return Equals(lhs, rhs.id); } + inline bool operator!=(const SysThreadId& lhs, const SysThreadIdProxy& rhs) { return !Equals(lhs, rhs.id); } + #else + typedef ThreadId SysThreadId; + const SysThreadId kSysThreadIdInvalid = SysThreadId(0); /// Special SysThreadId indicating an invalid thread identifier. + #endif + + /// ThreadUniqueId + /// Uniquely identifies a thread throughout the system, but in a way that is not + /// necessarily compatible with system thread id identification. Sometimes it is + /// costly to work with system thread ids whereas all you want is some integer + /// that is unique between threads and you don't need to use it for system calls. + /// See the EAThreadGetUniqueId macro/function for usage. + typedef Uint ThreadUniqueId; + + // ThreadUniqueId constants + const ThreadUniqueId kThreadUniqueIdInvalid = 0; /// Special ThreadUniqueId indicating an invalid thread identifier. + + + // Time constants + // Milliseconds are the units of time in EAThread. While every generation of computers + // results in faster computers and thus milliseconds become an increasingly large number + // compared to the computer speed, computer multithreading is still largely done at the + // millisecond level, due to it still being a small value relative to human perception. + // We may reconsider this some time in the future and provide an option to have ThreadTime + // be specified in finer units, such as microseconds. + #if EA_USE_CPP11_CONCURRENCY + typedef std::chrono::milliseconds::rep ThreadTime; /// Current storage mechanism for time used by thread timeout functions. Units are milliseconds. + const ThreadTime kTimeoutImmediate = std::chrono::milliseconds::zero().count();/// Used to specify to functions to return immediately if the operation could not be done. + const ThreadTime kTimeoutNone = std::chrono::milliseconds::max().count(); /// Used to specify to functions to block without a timeout (i.e. block forever). + const ThreadTime kTimeoutYield = std::chrono::milliseconds::zero().count(); /// This is used with ThreadSleep to minimally yield to threads of equivalent priority. + + #define EA_THREADTIME_AS_INT64(t) ((int64_t)(t)) + #define EA_THREADTIME_AS_DOUBLE(t) ((double)(t)) + + #elif defined(EA_PLATFORM_SONY) && EA_THREADS_AVAILABLE + typedef double ThreadTime; // SceKernelUseconds maps to unsigned int + static_assert(sizeof(ThreadTime) >= sizeof(unsigned int), "ThreadTime not large enough for uint32_t representation of milliseconds for platform portablity"); + + const ThreadTime kTimeoutImmediate = 0; + const ThreadTime kTimeoutNone = DBL_MAX; + const ThreadTime kTimeoutYield = 0.000001; // 1 nanosecond in terms of a millisecond + + #define EA_THREADTIME_AS_UINT_MICROSECONDS(t) ((unsigned int)((t) * 1000.0)) /// Returns the milliseconds time as uint in microseconds. + #define EA_THREADTIME_AS_INT64(t) ((int64_t)(t)) /// Returns the unconverted milliseconds time as a int64_t. + #define EA_THREADTIME_AS_DOUBLE(t) (t) /// Returns the time as double milliseconds. May include a fraction component. + #define EA_TIMESPEC_AS_UINT(t) ((unsigned int)(((t).tv_sec * 1000) + ((t).tv_nsec / 1000000))) /// Returns the time as uint in milliseconds. + #define EA_TIMESPEC_AS_DOUBLE_IN_MS(t) ( (((t).tv_sec * 1000000000ull) + ((t).tv_nsec))/1000000.0) /// Returns the time as uint in milliseconds. + + #elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && EA_THREADS_AVAILABLE + struct ThreadTime : public timespec + { + typedef int seconds_t; // To consider: change to uint64_t or maybe long. + typedef int nseconds_t; + + ThreadTime() { tv_sec = 0; tv_nsec = 0; } + ThreadTime(const timespec& ts) { tv_sec = ts.tv_sec; tv_nsec = ts.tv_nsec; } + ThreadTime(seconds_t nSeconds, nseconds_t nNanoseconds) { tv_sec = (long)nSeconds; tv_nsec = (long)nNanoseconds; } + ThreadTime(const int64_t& nMilliseconds) { tv_sec = (long)(nMilliseconds / 1000); tv_nsec = (long)((nMilliseconds - (tv_sec * 1000)) * 1000000); } + ThreadTime& operator+=(const int64_t& nMilliseconds) { long lTemp((long)nMilliseconds / 1000); tv_sec += lTemp; tv_nsec += (long)((nMilliseconds - (lTemp * 1000)) * 1000000); if(tv_nsec >= 1000000000){ tv_sec++; tv_nsec -= 1000000000; } return *this; } + ThreadTime& operator-=(const int64_t& nMilliseconds) { long lTemp((long)nMilliseconds / 1000); tv_sec -= lTemp; tv_nsec -= (long)((nMilliseconds - (lTemp * 1000)) * 1000000); if(tv_nsec < 0) { tv_sec--; tv_nsec += 1000000000; } return *this; } + ThreadTime& operator+=(const ThreadTime& tt) { tv_sec += tt.tv_sec; tv_nsec += tt.tv_nsec; if(tv_nsec >= 1000000000){ tv_sec++; tv_nsec -= 1000000000; } return *this; } + ThreadTime& operator-=(const ThreadTime& tt) { tv_sec -= tt.tv_sec; tv_nsec -= tt.tv_nsec; if(tv_nsec < 0) { tv_sec--; tv_nsec += 1000000000; } return *this; } + }; + inline ThreadTime operator+ (const ThreadTime& tt1, const ThreadTime& tt2) { ThreadTime ttR(tt1); ttR += tt2; return ttR; } + inline ThreadTime operator+ (const ThreadTime& tt, const int64_t& nMilliseconds){ ThreadTime ttR(tt); ttR += nMilliseconds; return ttR; } + inline ThreadTime operator- (const ThreadTime& tt1, const ThreadTime& tt2) { ThreadTime ttR(tt1); ttR -= tt2; return ttR; } + inline ThreadTime operator- (const ThreadTime& tt, const int64_t& nMilliseconds){ ThreadTime ttR(tt); ttR -= nMilliseconds; return ttR; } + inline bool operator==(const ThreadTime& tt1, const ThreadTime& tt2) { return (tt1.tv_nsec == tt2.tv_nsec) && (tt1.tv_sec == tt2.tv_sec); } // These comparisons assume that the nsec value is normalized (always between 0 && 1000000000). + inline bool operator!=(const ThreadTime& tt1, const ThreadTime& tt2) { return (tt1.tv_nsec != tt2.tv_nsec) || (tt1.tv_sec != tt2.tv_sec); } + inline bool operator< (const ThreadTime& tt1, const ThreadTime& tt2) { return (tt1.tv_sec == tt2.tv_sec) ? (tt1.tv_nsec < tt2.tv_nsec) : (tt1.tv_sec < tt2.tv_sec); } + inline bool operator> (const ThreadTime& tt1, const ThreadTime& tt2) { return (tt1.tv_sec == tt2.tv_sec) ? (tt1.tv_nsec > tt2.tv_nsec) : (tt1.tv_sec > tt2.tv_sec); } + inline bool operator<=(const ThreadTime& tt1, const ThreadTime& tt2) { return (tt1.tv_sec == tt2.tv_sec) ? (tt1.tv_nsec <= tt2.tv_nsec) : (tt1.tv_sec <= tt2.tv_sec); } + inline bool operator>=(const ThreadTime& tt1, const ThreadTime& tt2) { return (tt1.tv_sec == tt2.tv_sec) ? (tt1.tv_nsec >= tt2.tv_nsec) : (tt1.tv_sec >= tt2.tv_sec); } + + const ThreadTime kTimeoutImmediate(0, 0); /// Used to specify to functions to return immediately if the operation could not be done. + const ThreadTime kTimeoutNone(INT_MAX, INT_MAX); /// Used to specify to functions to block without a timeout (i.e. block forever). + const ThreadTime kTimeoutYield(0, 0); /// Used to specify to ThreadSleep to yield to threads of equivalent priority. + + #define EA_THREADTIME_AS_INT64(t) ((int64_t)(((t).tv_sec * 1000) + ((t).tv_nsec / 1000000))) /// Returns the time as int64_t milliseconds. + #define EA_THREADTIME_AS_INT64_MICROSECONDS(t) ((int64_t)(((t).tv_sec * 1000000) + (((t).tv_nsec / 1000)))) /// Returns the time as int64_t microseconds. + #define EA_THREADTIME_AS_DOUBLE(t) (((t).tv_sec * 1000.0) + ((t).tv_nsec / 1000000.0)) /// Returns the time as double milliseconds. + + #elif defined(EA_PLATFORM_MICROSOFT) && defined(EA_PROCESSOR_X86_64) + typedef uint64_t ThreadTime; /// Current storage mechanism for time used by thread timeout functions. Units are milliseconds. + const ThreadTime kTimeoutImmediate = 0; /// Used to specify to functions to return immediately if the operation could not be done. + const ThreadTime kTimeoutNone = UINT64_MAX; /// Used to specify to functions to block without a timeout (i.e. block forever). + const ThreadTime kTimeoutYield = 0; /// This is used with ThreadSleep to minimally yield to threads of equivalent priority. + + #define EA_THREADTIME_AS_INT64(t) ((int64_t)(t)) + #define EA_THREADTIME_AS_DOUBLE(t) ((double)(t)) + + #else + typedef unsigned ThreadTime; /// Current storage mechanism for time used by thread timeout functions. Units are milliseconds. + const ThreadTime kTimeoutImmediate = 0; /// Used to specify to functions to return immediately if the operation could not be done. + const ThreadTime kTimeoutNone = UINT_MAX; /// Used to specify to functions to block without a timeout (i.e. block forever). + const ThreadTime kTimeoutYield = 0; /// This is used with ThreadSleep to minimally yield to threads of equivalent priority. + + #define EA_THREADTIME_AS_INT64(t) ((int64_t)(t)) + #define EA_THREADTIME_AS_DOUBLE(t) ((double)(t)) + + #endif + + #if defined(EA_PLATFORM_MICROSOFT) /// Can be removed from C++11 Concurrency builds once full C++11 implementation is completed + uint32_t RelativeTimeoutFromAbsoluteTimeout(ThreadTime absoluteTimeout); + #endif + + // Thread priority constants + // There is a standardized mechanism to convert system-specific thread + // priorities to these platform-independent priorities and back without + // loss of precision or behaviour. The convention is that kThreadPriorityDefault + // equates to the system-specific normal thread priority. Thus for Microsoft + // APIs a thread with priority kThreadPriorityDefault will be of Microsoft + // priority THREAD_PRIORITY_NORMAL. A thread with an EAThread priority + // of kThreadPriorityDefault + 1 will have a Microsoft priority of THREAD_PRIORITY_NORMAL + 1. + // The only difference is that with EAThread all platforms are standardized on + // kThreadPriorityDefault as the normal value and that higher EAThread priority + // integral values mean higher thread priorities for running threads. This last + // item is of significance because Sony platforms natively define lower integers + // to mean higher thread priorities. With EAThread you get consistent behaviour + // across platforms and thus kThreadPriorityDefault + 1 always results in a + // thread that runs at priority of one level higher. On Sony platforms, this + 1 + // gets translated to a - 1 when calling the Sony native thread priority API. + // EAThread priorities have no mandated integral bounds, though + // kThreadPriorityMin and kThreadPriorityMax are defined as convenient practical + // endpoints for users. Users should not generally use hard-coded constants to + // refer to EAThread priorities much like it's best not to use hard-coded + // constants to refer to platform-specific native thread priorities. Also, users + // generally want to avoid manipulating thread priorities to the extent possible + // and use conventional synchronization primitives to control execution. + // Similarly, wildly varying thread priorities such as +100 are not likely to + // achieve much and are not likely to be very portable. + // + const int kThreadPriorityUnknown = INT_MIN; /// Invalid or unknown priority. + const int kThreadPriorityMin = -128; /// Minimum thread priority enumerated by EAThread. In practice, a valid thread priority can be anything other than kThreadPriorityUnknown. + const int kThreadPriorityDefault = 0; /// Default (a.k.a. normal) thread priority. + const int kThreadPriorityMax = 127; /// Maximum thread priority enumerated by EAThread. In practice, a valid thread priority can be anything other than kThreadPriorityUnknown. + + + + /// kSysThreadPriorityDefault + /// Defines the platform-specific default thread priority. + #if defined(EA_PLATFORM_SONY) + const int kSysThreadPriorityDefault = 700; + #elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + const int kSysThreadPriorityDefault = 0; // Some Unix variants use values other than zero, but these are not relevant. + #elif defined(EA_PLATFORM_MICROSOFT) + const int kSysThreadPriorityDefault = 0; // Same as THREAD_PRIORITY_NORMAL + #else + const int kSysThreadPriorityDefault = 0; + #endif + + + // The following functions are standalone and not static members of the thread class + // because they are potentially used by multiple threading primitives and we don't + // want to create a dependency of threading primitives on class Thread. + + /// GetThreadId + /// Gets the thread ID for the current thread. This thread ID should + /// be unique throughout the system. + EATHREADLIB_API ThreadId GetThreadId(); + + + /// GetSysThreadId + /// Gets the operating system thread id associated with the given ThreadId. + /// It turns out that Microsoft operating systems (Windows, XBox, XBox 360) + /// have two different ways to identify a thread: HANDLE and DWORD. Some API + /// functions take thread HANDLES, while others take what Microsoft calls + /// thread ids (DWORDs). EAThread ThreadId is a HANDLE, as that is used for + /// more of the core threading APIs. However, some OS-level APIs accept instead + /// the DWORD thread id. This function returns the OS thread id for a given + /// EAThread ThreadId. In the case of Microsoft OSs, this returns a DWORD from + /// a HANDLE and with other OSs this function simply returns the ThreadId. + /// Returns a valid SysThreadId or kSysThreadIdInvalid if the input id is invalid. + EATHREADLIB_API SysThreadId GetSysThreadId(ThreadId id); + + + /// GetThreadId + /// + /// This is a portable function to convert between ThreadId's and SysThreadId's. + /// For platforms that do not differentiate between these two types no conversion is attempted. + EATHREADLIB_API ThreadId GetThreadId(SysThreadId id); + + + /// GetSysThreadId + /// Gets the SysThreadId for the current thread. This thread ID should + /// be unique throughout the system. + EATHREADLIB_API SysThreadId GetSysThreadId(); + + + /// GetThreadPriority + /// Gets the priority of the current thread. + /// This function can return any int except for kThreadPriorityUnknown, as the + /// current thread's priority will always be knowable. A return value of kThreadPriorityDefault + /// means that this thread is of normal (a.k.a. default) priority. + /// See the documentation for thread priority constants (e.g. kThreadPriorityDefault) + /// for more information about thread priority values and behaviour. + EATHREADLIB_API int GetThreadPriority(); + + + /// SetThreadPriority + /// Sets the priority of the current thread. + /// Accepts any integer priority value except kThreadPriorityUnknown. + /// On some platforms, this function will automatically convert any invalid + /// priority for that particular platform to a valid one. A normal (a.k.a. default) thread + /// priority is identified by kThreadPriorityDefault. + /// See the documentation for thread priority constants (e.g. kThreadPriorityDefault) + /// for more information about thread priority values and behaviour. + EATHREADLIB_API bool SetThreadPriority(int nPriority); + + + /// GetThreadStackBase + /// Returns the base address of the current thread's stack. + /// Recall that on all supported platforms that the stack grows downward + /// and thus that the stack base address is of a higher value than the + /// stack's contents. + EATHREADLIB_API void* GetThreadStackBase(); + + + // Thread processor constants + const int kProcessorDefault = -1; /// Use the default processor for the platform. On many platforms, the default is to not be tied to any specific processor, but other threads can only ever be bound to a single processor. + const int kProcessorAny = -2; /// Run the thread on any processor. Many platforms will switch threads between processors dynamically. + + + /// SetThreadProcessor + /// Sets the processor the current thread should run on. Valid values + /// are kThreadProcessorDefault, kThreadProcessorAny, or a processor + /// index in the range of [0, processor count). If the input value + /// is >= the processor count, it will be reduced to be a modulo of + /// the processor count. Any other invalid value will cause the processor + /// to be set to zero. + /// This function isn't guaranteed to restrict the thread from executing + /// on the given processor for all platforms. Some platforms don't support + /// assigning thread processor affinity, while with others (e.g. Windows using + /// SetThreadIdealProcessor) the OS tries to comply but will use a different + /// processor when the assigned one is unavailable. + EATHREADLIB_API void SetThreadProcessor(int nProcessor); + + + /// GetThreadProcessor + /// Returns the (possibly virtual) CPU index that the thread is currently + /// running on. Different systems may have differing definitions of what + /// a unique processor is. Some CPUs have multiple sub-CPUs (e.g. "cores") + /// which are treated as unique processors by the system. + /// Many systems switch threads between processors dynamically; thus it's + /// possible that the thread may be on a different CPU by the time this + /// function returns. + /// Lastly, some systems don't provide the ability to detect what processor + /// the current thread is running on; in these cases this function returns 0. + EATHREADLIB_API int GetThreadProcessor(); + + + /// GetProcessorCount + /// Returns the (possibly virtual) CPU count that the current system has. + /// Some systems (e.g. Posix, Unix) don't expose an ability to tell how + /// many processors there are; in these cases this function returns 1. + /// This function returns the number of currently active processors. + /// Some systems can modify the number of active processors dynamically. + EATHREADLIB_API int GetProcessorCount(); + + + /// kThreadAffinityMaskAny + /// Defines the thread affinity mask that enables the thread + /// to float on all available processors. + typedef uint64_t ThreadAffinityMask; + const ThreadAffinityMask kThreadAffinityMaskAny = ~0ULL; + + /// GetAvailableCpuAffinityMask + /// Returns the thread affinity mask with only the available + /// processors. + /// If a theoretical platform reserves core 7 (counting from 0) in an + /// 8 core system, the affinity mask would be 0x7f. + EATHREADLIB_API ThreadAffinityMask GetAvailableCpuAffinityMask(); + + /// SetThreadAffinityMask + /// + /// The nAffinityMask is a bit field where each bit designates a processor. + /// + /// This function isn't guaranteed to restrict the thread from executing + /// on the given processor for all platforms. Some platforms don't support + /// assigning thread processor affinity, while with others (e.g. Windows using + /// SetThreadIdealProcessor) the OS tries to comply but will use a different + /// processor when the assigned one is unavailable. + EATHREADLIB_API void SetThreadAffinityMask(ThreadAffinityMask nAffinityMask); + EATHREADLIB_API void SetThreadAffinityMask(const EA::Thread::ThreadId& id, ThreadAffinityMask nAffinityMask); + + + /// GetThreadAffinityMask + /// + /// Returns the current thread affinity mask specified by the user. + EATHREADLIB_API ThreadAffinityMask GetThreadAffinityMask(); + EATHREADLIB_API ThreadAffinityMask GetThreadAffinityMask(const EA::Thread::ThreadId& id); + + + /// GetName + /// Returns the name of the thread assigned by the SetName function. + /// If the thread was not named by the SetName function, then the name is empty (""). + EATHREADLIB_API const char* GetThreadName(); + EATHREADLIB_API const char* GetThreadName(const EA::Thread::ThreadId& id); + + + /// SetThreadName + /// + /// Sets a descriptive name or the thread. On some platforms this name is passed on + /// to the debugging tools so they can see this name. The name length, including a + /// terminating 0 char, is limited to EATHREAD_NAME_SIZE characters. Any characters + /// beyond that are ignored. + /// + /// You can set the name of a Thread object only if it has already begun. You can + /// also set the name with the Begin function via the ThreadParameters argument to + /// Begin. This design is in order to simplify the implementation, but being able + /// to set ThreadParameters before Begin is something that can be considered in the + /// future. + /// + /// Some platforms (e.g. Linux) have the restriction that this function works + /// properly only when called by the same thread that you want to name. Given this + /// situation, the most portable way to use this SetName function is to either + /// always call it from the thread to be named or to use the ThreadParameters to + /// give the thread a name before it is started and let the started thread name + /// itself. + // + // + // + EATHREADLIB_API void SetThreadName(const char* pName); + EATHREADLIB_API void SetThreadName(const EA::Thread::ThreadId& id, const char* pName); + + + /// ThreadSleep + /// Puts the current thread to sleep for an amount of time hinted at + /// by the time argument. The timeout is merely a hint and the system + /// thread scheduler might return well before the sleep time has elapsed. + /// The input 'timeRelative' refers to a relative time and not an + /// absolute time such as used by EAThread mutexes, semaphores, etc. + /// This is for consistency with other threading systems such as Posix and Win32. + /// A sleep time of zero has the same effect as simply yielding to other + /// available threads. + /// + EATHREADLIB_API void ThreadSleep(const ThreadTime& timeRelative = kTimeoutImmediate); + + + /// ThreadCooperativeYield + /// On platforms that use cooperative multithreading instead of + /// pre-emptive multithreading, this function maps to ThreadSleep(0). + /// On pre-emptive platforms, this function is a no-op. The intention + /// is to allow cooperative multithreaded systems to yield manually + /// in order for other threads to run, but also not to penalize + /// pre-emptive systems that don't need such manual yielding. If you + /// want to forcefully yield on a pre-emptive system, call ThreadSleep(0). + #ifdef EA_THREAD_COOPERATIVE + #define ThreadCooperativeYield() EA::Thread::ThreadSleep(EA::Thread::kTimeoutYield) + #else + #define ThreadCooperativeYield() + #endif + + + /// End + /// This function provides a way for a thread to end itself. + EATHREADLIB_API void ThreadEnd(intptr_t threadReturnValue); + + + /// GetThreadTime + /// Gets the current absolute time in milliseconds. + /// This is required for working with absolute timeouts, for example. + /// To specify a timeout that is relative to the current time, simply + /// add time (in milliseconds) to the return value of GetThreadTime. + /// Alternatively, you can use ConvertRelativeTime to calculate an absolute time. + EATHREADLIB_API ThreadTime GetThreadTime(); + + + /// ConvertRelativeTime + /// Given a relative time (in milliseconds), this function returns an + /// absolute time (in milliseconds). + /// Example usage: + /// mutex.Lock(ConvertRelativeTime(1000)); + EATHREADLIB_API inline ThreadTime ConvertRelativeTime(const ThreadTime& timeRelative) + { + return GetThreadTime() + timeRelative; + } + + /// SetAssertionFailureFunction + /// Allows the user to specify a callback function to trap assertion failures. + /// You can use this to glue your own assertion system into this system. + typedef void (*AssertionFailureFunction)(const char* pExpression, void* pContext); + EATHREADLIB_API void SetAssertionFailureFunction(AssertionFailureFunction pAssertionFailureFunction, void* pContext); + + + /// AssertionFailure + /// Triggers an assertion failure. This function is generally intended for internal + /// use but is available so that related code can use the same system. + EATHREADLIB_API void AssertionFailure(const char* pExpression); + EATHREADLIB_API void AssertionFailureV(const char* pFormat, ...); + + + + + /// Allocator + /// This is the same as (the first four functions of) ICoreAllocator. + /// If the allocator is set via SetAllocator, then it must be done before + /// any other thread operations which might allocate memory are done. + /// Typically this includes creating objects via factory functions and + /// creating threads whereby you specify that thread resources be allocated for you.. + class Allocator + { + public: + virtual ~Allocator() {} + virtual void* Alloc(size_t size, const char* name = 0, unsigned int flags = 0) = 0; + virtual void* Alloc(size_t size, const char* name, unsigned int flags, + unsigned int align, unsigned int alignOffset = 0) = 0; + virtual void Free(void* block, size_t size=0) = 0; + }; + + EATHREADLIB_API void SetAllocator(Allocator* pAllocator); + EATHREADLIB_API Allocator* GetAllocator(); + + EATHREADLIB_API void SetAllocator(EA::Allocator::ICoreAllocator* pAllocator); + + } // namespace Thread + +} // namespace EA + + + +/// EAThreadGetUniqueId +/// +/// Gets a value that is unique per thread but isn't necessarily the system-recognized +/// thread id. This function is at least as fast as GetThreadId, and on some platforms +/// is potentially significantly faster due to being implemented in inline asm which +/// avoids a system function call which may cause an instruction cache miss penalty. +/// This function is useful for creating very fast implementations of some kinds of +/// threading constructs. It's implemented as a macro instead of a function in order +/// to optimizing inlining success across all platforms and compilers. +/// +/// This function is guaranteed to yield a valid value; there are no error conditions. +/// +/// This macro acts as if it were declared as a function like this: +/// void EAThreadGetUniqueId(ThreadUniqueId& result); +/// +/// Example usage: +/// ThreadUniqueId x; +/// EAThreadGetUniqueId(x); +/// +#if EA_USE_CPP11_CONCURRENCY + #define EAThreadGetUniqueId(dest) (dest = static_cast(std::hash()(std::this_thread::get_id()))) + +#elif defined(EA_PLATFORM_WINDOWS) && defined(EA_COMPILER_MSVC) && !defined(EA_PLATFORM_WIN64) + + // Reference implementation: + //extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(); + //#define EAThreadGetUniqueId(dest) dest = (ThreadUniqueId)(uintptr_t)GetCurrentThreadId() + + // Fast implementation: + extern "C" unsigned long __readfsdword(unsigned long offset); + #pragma intrinsic(__readfsdword) + #define EAThreadGetUniqueId(dest) dest = (EA::Thread::ThreadUniqueId)(uintptr_t)__readfsdword(0x18) + +#elif defined(EA_COMPILER_MSVC) && defined(EA_PROCESSOR_X86_64) + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() + #define EAThreadGetUniqueId(dest) dest = (EA::Thread::ThreadUniqueId)(uintptr_t)__readgsqword(0x30) + // Could also use dest = (EA::Thread::ThreadUniqueId)NtCurrentTeb(), but that would require #including , which is very heavy. + +#else + + // Reference implementation: + #define EAThreadGetUniqueId(dest) dest = (EA::Thread::ThreadUniqueId)(uintptr_t)EA::Thread::GetThreadId() + +#endif + + +// EAThreadIdToString +// Convert a thread id to a string suitable for use with printf like functions, e.g.: +// printf("%s", EAThreadIdToString(myThreadId)); +// This macro is intended for debugging purposes and makes no guarantees about performance +// or how a thread id is mapped to a string. +namespace EA +{ + namespace Thread + { + namespace detail + { + struct EATHREADLIB_API ThreadIdToStringBuffer + { + public: + enum { BufSize = 32 }; + explicit ThreadIdToStringBuffer(EA::Thread::ThreadId threadId); + const char* c_str() const { return mBuf; } + private: + char mBuf[BufSize]; + }; + + struct EATHREADLIB_API SysThreadIdToStringBuffer + { + public: + enum { BufSize = 32 }; + explicit SysThreadIdToStringBuffer(EA::Thread::SysThreadId sysThreadId); + const char* c_str() const { return mBuf; } + private: + char mBuf[BufSize]; + }; + } + } +} + +#if !defined(EAThreadThreadIdToString) + #define EAThreadThreadIdToString(threadId) (EA::Thread::detail::ThreadIdToStringBuffer(threadId).c_str()) +#endif +#if !defined(EAThreadSysThreadIdToString) + #define EAThreadSysThreadIdToString(sysThreadId) (EA::Thread::detail::SysThreadIdToStringBuffer(sysThreadId).c_str()) +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// Inline functions +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE + // We implement GetSysThreadId in our associated .cpp file. +#elif defined(EA_PLATFORM_SONY) + // We implement GetSysThreadId in our associated .cpp file. +#elif defined(EA_PLATFORM_APPLE) + // We implement GetSysThreadId in our associated .cpp file. +#elif EA_USE_CPP11_CONCURRENCY + // We implement GetSysThreadId in our associated .cpp file. +#else + inline EA::Thread::SysThreadId EA::Thread::GetSysThreadId(ThreadId id) + { + return id; + } + + inline EA::Thread::SysThreadId EA::Thread::GetSysThreadId() + { + return GetThreadId(); // ThreadId == SysThreadId in this case + } +#endif + +#endif // EATHREAD_EATHREAD_H + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_atomic.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_atomic.h new file mode 100644 index 00000000..cef132f9 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_atomic.h @@ -0,0 +1,467 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Defines functionality for thread-safe primitive operations. +// +// EAThread atomics do NOT imply the use of read/write barriers. This is +// partly due to historical reasons and partly due to EAThread's internal +// code being optimized for not using barriers. +// +// In future, we are considering migrating the atomics interface which +// defaults atomics to use full read/write barriers while allowing users +// to opt-out of full barrier usage. The new C++11 interface already provides +// similar interfaces. +// +// http://en.cppreference.com/w/cpp/atomic/memory_order +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_ATOMIC_H +#define EATHREAD_EATHREAD_ATOMIC_H + + +#include +EA_DISABLE_ALL_VC_WARNINGS() +#include +EA_RESTORE_ALL_VC_WARNINGS() +#include +#include +#include + + +#if !EA_THREADS_AVAILABLE + // Do nothing. Let the default implementation below be used. +#elif defined(EA_USE_COMMON_ATOMICINT_IMPLEMENTATION) && EA_USE_COMMON_ATOMICINT_IMPLEMENTATION + #include +#elif defined(EA_PROCESSOR_X86) || (defined(EA_PLATFORM_WINRT) && defined(EA_PROCESSOR_ARM)) + #include +#elif defined(EA_PROCESSOR_X86_64) + #include +#elif defined(EA_PLATFORM_ANDROID) && EATHREAD_C11_ATOMICS_AVAILABLE + #include // Android API 21+ only support C11 atomics +#else + #error Platform not supported yet. +#endif + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + + +// EATHREAD_ATOMIC_128_SUPPORTED +// +// Defined as 0 or 1. Defined as 1 whenever possible for the given compiler/platform combination. +// Defines if 128 bit atomic operations are supported. +// Such operations are only ever supported on 64 bit platforms. +// +#ifndef EATHREAD_ATOMIC_128_SUPPORTED // If not defined by one of the above headers... + #define EATHREAD_ATOMIC_128_SUPPORTED 0 +#endif + +namespace EA +{ + namespace Thread + { + enum Atomic64Implementation + { + kAtomic64Emulated, + kAtomic64Native + }; + + /// SetDoubleWordAtomicsImplementation + /// Some platforms have multiple implementations, some of which support + /// double word atomics and some that don't. For example, certain ARM + /// processors will support the ldrexd/strexd atomic instructions but + /// others will not. + EATHREADLIB_API void SetAtomic64Implementation(Atomic64Implementation implementation); + } +} + + +#if !defined(EA_THREAD_ATOMIC_IMPLEMENTED) // If there wasn't a processor-specific version already defined... + + // Fail the build if atomics aren't being defined for the given platform/compiler. + // If we need to add an exception here, we can add an appropriate ifdef. + static_assert(false, "atomic operations must be defined for this platform."); + + namespace EA + { + namespace Thread + { + /// Standalone atomic functions + /// These act the same as the class functions below. + /// The T return values are the previous value, except for the + /// AtomicFetchSwap function which returns the swapped out value. + /// + /// T AtomicGetValue(volatile T*); + /// T AtomicGetValue(const volatile T*); + /// void AtomicSetValue(volatile T*, T value); + /// T AtomicFetchIncrement(volatile T*); + /// T AtomicFetchDecrement(volatile T*); + /// T AtomicFetchAdd(volatile T*, T value); + /// T AtomicFetchSub(volatile T*, T value); + /// T AtomicFetchOr(volatile T*, T value); + /// T AtomicFetchAnd(volatile T*, T value); + /// T AtomicFetchXor(volatile T*, T value); + /// T AtomicFetchSwap(volatile T*, T value); + /// T AtomicFetchSwapConditional(volatile T*, T value, T condition); + /// bool AtomicSetValueConditional(volatile T*, T value, T condition); + + + + /// class AtomicInt + /// + /// Implements thread-safe access to an integer and primary operations on that integer. + /// AtomicIntegers are commonly used as lightweight flags and signals between threads + /// or as the synchronization object for spinlocks. Those familiar with the Win32 API + /// will find that AtomicInt32 is essentially a platform independent interface to + /// the Win32 InterlockedXXX family of functions. Those familiar with Linux may + /// find that AtomicInt32 is essentially a platform independent interface to atomic_t + /// functionality. + /// + /// Note that the reference implementation defined here is itself not thread-safe. + /// A thread-safe version requires platform-specific code. + /// + /// Example usage + /// AtomicInt32 i = 0; + /// + /// ++i; + /// i--; + /// i += 7; + /// i -= 3; + /// i = 2; + /// + /// int x = i.GetValue(); + /// i.Increment(); + /// bool oldValueWas6 = i.SetValueConditional(3, 6); + /// i.Add(4); + /// + template + class EATHREADLIB_API AtomicInt + { + public: + /// ThisType + /// A typedef for this class type itself, for usage clarity. + typedef AtomicInt ThisType; + + + /// ValueType + /// A typedef for the basic object we work with. + typedef T ValueType; + + + /// AtomicInt + /// Empty constructor. Intentionally leaves mValue in an unspecified state. + /// This is done so that an AtomicInt acts like a standard built-in integer. + AtomicInt() + {} + + + /// AtomicInt + /// Constructs with an intial value. + AtomicInt(ValueType n) + : mValue(n) {} + + + /// AtomicInt + /// Copy ctor. Uses GetValue to read the value, and thus is synchronized. + AtomicInt(const ThisType& x) + : mValue(x.GetValue()) {} + + + /// AtomicInt + /// Assignment operator. Uses GetValue to read the value, and thus is synchronized. + AtomicInt& operator=(const ThisType& x) + { mValue = x.GetValue(); return *this; } + + + /// GetValue + /// Safely gets the current value. A platform-specific version of + /// this might need to do something more than just read the value. + ValueType GetValue() const + { return mValue; } + + + /// GetValueRaw + /// "Unsafely" gets the current value. This is useful for algorithms + /// that want to poll the value in a high performance way before + /// reading or setting the value in a more costly thread-safe way. + /// You should not use this function when attempting to do thread-safe + /// atomic operations. + ValueType GetValueRaw() const + { return mValue; } + + + /// SetValue + /// Safely sets a new value. Returns the old value. Note that due to + /// expected multithreaded accesses, a call to GetValue after SetValue + /// might return a different value then what was set with SetValue. + /// This of course depends on your situation. + ValueType SetValue(ValueType n) + { + const ValueType nOldValue(mValue); + mValue = n; + return nOldValue; + } + + + /// SetValueConditional + /// Safely set the value to a new value if the original value is equal to + /// a condition value. Returns true if the condition was met and the + /// assignment occurred. The comparison and value setting are done as + /// an atomic operation and thus another thread cannot intervene between + /// the two as would be the case with simple C code. + bool SetValueConditional(ValueType n, ValueType condition) + { + if(mValue == condition) + { + mValue = n; + return true; + } + return false; + } + + + /// Increment + /// Safely increments the value. Returns the new value. + /// This function acts the same as the C++ pre-increment operator. + ValueType Increment() + { return ++mValue; } + + + /// Decrement + /// Safely decrements the value. Returns the new value. + /// This function acts the same as the C++ pre-decrement operator. + ValueType Decrement() + { return --mValue; } + + + /// Add + /// Safely adds a value, which can be negative. Returns the new value. + /// You can implement subtraction with this function by using a negative argument. + ValueType Add(ValueType n) + { return (mValue += n); } + + + /// operators + /// These allow an AtomicInt object to safely act like a built-in type. + /// + /// Note: The operators for AtomicInt behaves differently than standard + /// C++ operators in that it will always return a ValueType instead + /// of a reference. + /// + /// cast operator + /// Returns the AtomicInt value as an integral type. This allows the + /// AtomicInt to behave like a standard built-in integer type. + operator const ValueType() const + { return mValue; } + + /// operator = + /// Assigns a new value and returns the value after the operation. + /// + ValueType operator=(ValueType n) + { + mValue = n; + return n; + } + + /// pre-increment operator+= + /// Adds a value to the AtomicInt and returns the value after the operation. + /// + /// This function doesn't obey the C++ standard in that it does not return + /// a reference, but rather the value of the AtomicInt after the + /// operation is complete. It must be noted that this design is motivated by + /// the fact that it is unsafe to rely on the returned value being equal to + /// the previous value + n, as another thread might have modified the AtomicInt + /// immediately after the subtraction operation. So rather than returning the + /// reference of AtomicInt, the function returns a copy of the AtomicInt value + /// used in the function. + ValueType operator+=(ValueType n) + { + mValue += n; + return mValue; + } + + /// pre-increment operator-= + /// Subtracts a value to the AtomicInt and returns the value after the operation. + /// + /// This function doesn't obey the C++ standard in that it does not return + // a reference, but rather the value of the AtomicInt after the + /// operation is complete. It must be noted that this design is motivated by + /// the fact that it is unsafe to rely on the returned value being equal to + /// the previous value - n, as another thread might have modified the AtomicInt + /// immediately after the subtraction operation. So rather than returning the + /// reference of AtomicInt, the function returns a copy of the AtomicInt value + /// used in the function. + ValueType operator-=(ValueType n) + { + mValue -= n; + return mValue; + } + + /// pre-increment operator++ + /// Increments the AtomicInt. + /// + /// This function doesn't obey the C++ standard in that it does not return + // a reference, but rather the value of the AtomicInt after the + /// operation is complete. It must be noted that this design is motivated by + /// the fact that it is unsafe to rely on the returned value being equal to + /// the previous value + 1, as another thread might have modified the AtomicInt + /// immediately after the subtraction operation. So rather than returning the + /// reference of AtomicInt, the function returns a copy of the AtomicInt value + /// used in the function. + ValueType operator++() + { return ++mValue; } + + /// post-increment operator++ + /// Increments the AtomicInt and returns the value of the AtomicInt before + /// the increment operation. + /// + /// This function doesn't obey the C++ standard in that it does not return + // a reference, but rather the value of the AtomicInt after the + /// operation is complete. It must be noted that this design is motivated by + /// the fact that it is unsafe to rely on the returned value being equal to + /// the previous value, as another thread might have modified the AtomicInt + /// immediately after the subtraction operation. So rather than returning the + /// reference of AtomicInt, the function returns a copy of the AtomicInt value + /// used in the function. + ValueType operator++(int) + { return mValue++; } + + /// pre-increment operator-- + /// Decrements the AtomicInt. + /// + /// This function doesn't obey the C++ standard in that it does not return + // a reference, but rather the value of the AtomicInt after the + /// operation is complete. It must be noted that this design is motivated by + /// the fact that it is unsafe to rely on the returned value being equal to + /// the previous value - 1, as another thread might have modified the AtomicInt + /// immediately after the subtraction operation. So rather than returning the + /// reference of AtomicInt, the function returns a copy of the AtomicInt value + /// used in the function. + ValueType operator--() + { return --mValue; } + + /// post-increment operator-- + /// Increments the AtomicInt and returns the value of the AtomicInt before + /// the increment operation. + /// + /// This function doesn't obey the C++ standard in that it does not return + // a reference, but rather the value of the AtomicInt after the + /// operation is complete. It must be noted that this design is motivated by + /// the fact that it is unsafe to rely on the returned value being equal to + /// the previous value, as another thread might have modified the AtomicInt + /// immediately after the subtraction operation. So rather than returning the + /// reference of AtomicInt, the function returns a copy of the AtomicInt value + /// used in the function. + ValueType operator--(int) + { return mValue--;} + + protected: + volatile ValueType mValue; /// May not be the same on all platforms. + }; + + + } // namespace Thread + + } // namespace EA + +#endif // #if EA_THREAD_ATOMIC_IMPLEMENTED + + + + + +namespace EA +{ + namespace Thread + { + + // Typedefs + typedef AtomicInt AtomicInt32; /// int32_t atomic integer. + typedef AtomicInt AtomicUint32; /// uint32_t atomic integer. + typedef AtomicInt AtomicInt64; /// int64_t atomic integer. + typedef AtomicInt AtomicUint64; /// uint64_t atomic integer. + + #if !defined(EA_PLATFORM_WORD_SIZE) || (EA_PLATFORM_WORD_SIZE == 4) + typedef AtomicInt32 AtomicIWord; + typedef AtomicUint32 AtomicUWord; + #else + typedef AtomicInt64 AtomicIWord; + typedef AtomicUint64 AtomicUWord; + #endif + + #if !defined(EA_PLATFORM_PTR_SIZE) || (EA_PLATFORM_PTR_SIZE == 4) + typedef AtomicInt32 AtomicIntPtr; + typedef AtomicUint32 AtomicUintPtr; + #else + typedef AtomicInt64 AtomicIntPtr; + typedef AtomicUint64 AtomicUintPtr; + #endif + + + // VC++ yields spurious warnings about void* being cast to an integer type and vice-versa. + // These warnings are baseless because we check for platform pointer size above. + EA_DISABLE_VC_WARNING(4311 4312 4251) + + + /// class AtomicPointer + /// + /// For simplicity of the current implementation, we simply have AtomicPointer map + /// to AtomicInt32. This is reasonably safe because AtomicInt32 uses intptr_t + /// as its ValueType and there are no foreseeble supported platforms in which + /// intptr_t will not exist or be possible as a data type. + /// + class EATHREADLIB_API AtomicPointer : public AtomicIntPtr + { + public: + typedef void* PointerValueType; + + AtomicPointer(void* p = NULL) + : AtomicIntPtr(static_cast(reinterpret_cast(p))) {} + + AtomicPointer& operator=(void* p) + { AtomicIntPtr::operator=(static_cast(reinterpret_cast(p))); return *this; } + + operator const void*() const // It's debateable whether this should be supported. + { return (void*)AtomicIntPtr::GetValue(); } + + void* GetValue() const + { return (void*)AtomicIntPtr::GetValue(); } + + void* GetValueRaw() const + { return (void*)AtomicIntPtr::GetValueRaw(); } + + void* SetValue(void* p) + { return (void*)AtomicIntPtr::SetValue(static_cast(reinterpret_cast(p))); } + + bool SetValueConditional(void* p, void* pCondition) + { return AtomicIntPtr::SetValueConditional(static_cast(reinterpret_cast(p)), static_cast(reinterpret_cast(pCondition))); } + }; + + + EA_RESTORE_VC_WARNING() + + } // namespace Thread + +} // namespace EA + + +#endif // EATHREAD_EATHREAD_ATOMIC_H + + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_barrier.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_barrier.h new file mode 100644 index 00000000..dd716d1f --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_barrier.h @@ -0,0 +1,248 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements Posix-style barriers. +// Note that thread synchronization barriers are different from +// memory synchronization barriers (a.k.a. fences). +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_BARRIER_H +#define EATHREAD_EATHREAD_BARRIER_H + + +#include +#include + + +#if defined(EA_DLL) && defined(EA_COMPILER_MSVC) + // Suppress warning about class 'AtomicInt32' needs to have a + // dll-interface to be used by clients of class which have a templated member. + // + // These templates cannot be instantiated outside of the DLL. If you try, a + // link error will result. This compiler warning is intended to notify users + // of this. + EA_DISABLE_VC_WARNING(4251) +#endif + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +///////////////////////////////////////////////////////////////////////// +/// EABarrierData +/// +/// This is used internally by class Barrier. +/// Todo: Consider moving this declaration into a platform-specific +/// header file. +/// + +#if defined(EA_PLATFORM_SONY) + #include + #include + + // We implement the barrier manually, as not all Posix thread implementations + // have barriers and even those that have it lack a timeout wait version. + struct EABarrierData{ + ScePthreadCond mCV; // Wait for barrier. + ScePthreadMutex mMutex; // Control access to barrier. + int mnHeight; // Number of threads required. + int mnCurrent; // Current number of threads. As threads wait, this value decreases towards zero. + unsigned long mnCycle; // Cycle count. + bool mbValid; // True if valid. + + EABarrierData(); + }; + +#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && EA_THREADS_AVAILABLE + #include + + // We implement the barrier manually, as not all Posix threads implemetnation + // have barrers and even those that have it lack a timeout wait version. + struct EABarrierData{ + pthread_cond_t mCV; // Wait for barrier. + pthread_mutex_t mMutex; // Control access to barrier. + int mnHeight; // Number of threads required. + int mnCurrent; // Current number of threads. As threads wait, this value decreases towards zero. + unsigned long mnCycle; // Cycle count. + bool mbValid; // True if valid. + + EABarrierData(); + }; + +#else // All other platforms + #include + #include + + struct EATHREADLIB_API EABarrierData{ + EA::Thread::AtomicInt32 mnCurrent; // Current number of threads. As threads wait, this value decreases towards zero. + int mnHeight; // Number of threads required. + EA::Thread::AtomicInt32 mnIndex; // Which semaphore we are using. + EA::Thread::Semaphore mSemaphore0; // First semaphore. We can't use an array of Semaphores, because that would + EA::Thread::Semaphore mSemaphore1; // Second semaphore. intefere with our ability to initialize them our way. + EABarrierData(); + + private: + // Prevent default generation of these functions by declaring but not defining them. + EABarrierData(const EABarrierData& rhs); // copy constructor + EABarrierData& operator=(const EABarrierData& rhs); // assignment operator + }; + +#endif + + + + +namespace EA +{ + namespace Thread + { + /// BarrierParameters + /// Specifies barrier settings. + struct EATHREADLIB_API BarrierParameters + { + int mHeight; /// Barrier 'height'. Refers to number of threads which must wait before being released. + bool mbIntraProcess; /// True if the semaphore is intra-process, else inter-process. + char mName[16]; /// Barrier name, applicable only to platforms that recognize named synchronization objects. + + BarrierParameters(int height = 0, bool bIntraProcess = true, const char* pName = NULL); + }; + + + /// Barrier + /// A Barrier is a synchronization point for a set of threads. A barrier has + /// a count associated with it and threads call the wait function until the + /// given count of threads have reached the wait point. Then all threads + /// are released. The first thread released is given a special return value + /// that identifies it uniquely so that one-time work can be done. + /// + /// A primary use of barriers is to spread out work between a number of threads + /// and wait until the work is complete. For example, if you want to find and + /// count all objects of a given kind in a large grid, you might have four + /// threads each work on a quadrant and wait on the barrier until all are + /// finished. This particular example is more practical on SMP systems than + /// uniprocessor systems, but there are also uniprocessor uses. It should be + /// noted, however, that a Barrier synchronizes the completion of -threads-, + /// and not necessarily the completion of -tasks-. There may or may not be + /// a direct correspondence between the two. + /// + class EATHREADLIB_API Barrier + { + public: + enum Result{ + kResultPrimary = 0, /// The barrier wait suceeded and this thread is the designated solitary primary thread. Similar to Posix "serial" thread. + kResultSecondary = 1, /// The barrier wait suceeded and this thread is one of the secondary threads. + kResultError = -1, /// The wait resulted in error, due to various possible reasons. + kResultTimeout = -2 /// The barrier wait timed out. + }; + + /// Barrier + /// For immediate default initialization, use no args. + /// For custom immediate initialization, supply a first argument. + /// For deferred initialization, use Barrier(NULL, false) then later call Init. + /// For deferred initialization of an array of objects, create an empty + /// subclass whose default constructor chains back to Barrier(NULL, false). + Barrier(const BarrierParameters* pBarrierParameters = NULL, bool bDefaultParameters = true); + + /// Barrier + /// This is a constructor which initializes the Barrier to a specific height + /// and intializes the other Barrier parameters to default values. See the + /// BarrierParameters struct for info on these default values. + Barrier(int height); + + /// ~Barrier + /// Destroys an existing Barrier. The Barrier must not be waited on + /// by any thread, otherwise the resulting behaviour is undefined. + ~Barrier(); + + /// Init + /// Initializes the Barrier; used in cases where it cannot be initialized + /// via the constructor (as in the case with default construction or + /// array initialization. + bool Init(const BarrierParameters* pBarrierParameters); + + /// Wait + /// Causes the current thread to wait until the designated number of threads have called Wait. + /// + /// Returns one of enum Result. + /// + /// A timeout means that the thread gives up its contribution to the height while + /// waiting for the full height to be achieved. A timeout of zero means that a thread + /// only succeeds if it is the final thread (the one which puts the height to full); + /// otherwise the call returns with a timeout. + /// + /// Note that the timeout is specified in absolute time and not relative time. + /// + /// Note also that due to the way thread scheduling works -- particularly in a + /// time-sliced threading environment -- that the timeout value is a hint and + /// the actual amount of time passed before the timeout occurs may be significantly + /// more or less than the specified timeout time. + /// + Result Wait(const ThreadTime& timeoutAbsolute = kTimeoutNone); + + /// GetPlatformData + /// Returns the platform-specific data handle for debugging uses or + /// other cases whereby special (and non-portable) uses are required. + void* GetPlatformData() + { return &mBarrierData; } + + protected: + EABarrierData mBarrierData; + + private: + // Objects of this class are not copyable. + Barrier(const Barrier&){} + Barrier& operator=(const Barrier&){ return *this; } + }; + + + /// BarrierFactory + /// + /// Implements a factory-based creation and destruction mechanism for class Barrier. + /// A primary use of this would be to allow the Barrier implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + class EATHREADLIB_API BarrierFactory + { + public: + static Barrier* CreateBarrier(); // Internally implemented as: return new Barrier; + static void DestroyBarrier(Barrier* pBarrier); // Internally implemented as: delete pBarrier; + + static size_t GetBarrierSize(); // Internally implemented as: return sizeof(Barrier); + static Barrier* ConstructBarrier(void* pMemory); // Internally implemented as: return new(pMemory) Barrier; + static void DestructBarrier(Barrier* pBarrier); // Internally implemented as: pBarrier->~Barrier(); + }; + + + } // namespace Thread + +} // namespace EA + + +#if defined(EA_DLL) && defined(EA_COMPILER_MSVC) + // re-enable warning(s) disabled above. + EA_RESTORE_VC_WARNING() +#endif + + +#endif // EATHREAD_EATHREAD_BARRIER_H + + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_callstack.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_callstack.h new file mode 100644 index 00000000..037da7c3 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_callstack.h @@ -0,0 +1,170 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +#ifndef EATHREAD_EATHREAD_CALLSTACK_H +#define EATHREAD_EATHREAD_CALLSTACK_H + +#include +#include +#include + +namespace EA +{ + namespace Thread + { + #if defined(EA_PLATFORM_MICROSOFT) + /// This function is the same as EA::Thread::GetSysThreadId(ThreadId id). + /// This function converts from one type of Microsoft thread identifier to another. + /// threadId is the same as EA::Thread::ThreadId and is a Microsoft thread HANDLE. + /// The return value is a Microsoft DWORD thread id which is the same as EA::Thread::SysThreadId. + /// Upon failure, the return value will be zero. + EATHREADLIB_API uint32_t GetThreadIdFromThreadHandle(intptr_t threadId); + #endif + + /// EAGetInstructionPointer / GetInstructionPointer + /// + /// Returns the current instruction pointer (a.k.a. program counter). + /// This function is implemented as a macro, it acts as if its declaration + /// were like so: + /// void EAGetInstructionPointer(void*& p); + /// + /// For portability, this function should only be used as a standalone + /// statement on its own line. + /// + /// These functions return NULL/0 on error or if getting the value is not possible + /// + /// Example usage: + /// void* pInstruction; + /// EAGetInstructionPointer(pInstruction); + /// + #if defined(EA_COMPILER_MSVC) + + EATHREADLIB_API EA_NO_INLINE void GetInstructionPointer(void*& pInstruction); + + #define EAGetInstructionPointer(p) EA::Thread::GetInstructionPointer(p) + + #elif defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + + EATHREADLIB_API EA_NO_INLINE void GetInstructionPointer(void*& pInstruction); + + #define EAGetInstructionPointer(p) EA::Thread::GetInstructionPointer(p) + + #elif defined(EA_COMPILER_ARM) || defined(EA_COMPILER_RVCT) + + #define EAGetInstructionPointer(p) \ + { \ + p = static_cast(__current_pc()); \ + } + + EA_FORCE_INLINE void GetInstructionPointer(void*& p) + { + EAGetInstructionPointer(p); + } + + #else + + #error "EAGetInstructionPointer() Not Supported On The Given Platform/Compiler!!!" + + #endif + + + /// EASetStackBase / SetStackBase / GetStackBase / GetStackLimit + /// + /// EASetStackBase as a macro and acts as if its declaration were like so: + /// void EASetStackBase(); + /// + /// EASetStackBase sets the current stack pointer as the bottom (beginning) + /// of the stack. Depending on the platform, the "bottom" may be up or down + /// depending on whether the stack grows upward or downward (usually it grows + /// downward and so "bottom" actually refers to an address that is above child + /// stack frames in memory. + /// This function is intended to be called on application startup as early as + /// possible, and in each created thread, as early as possible. Its purpose + /// is to record the beginning stack pointer because the platform doesn't provide + /// APIs to tell what it is, and we need to know it (e.g. so we don't overrun + /// it during stack unwinds). + /// + /// For portability, EASetStackBase should be used only as a standalone + /// statement on its own line, as it may include statements that can't work otherwise. + /// + /// Example usage: + /// int main(int argc, char** argv) { + /// EASetStackBase(); + /// . . . + /// } + /// + /// SetStackBase is a function which lets you explicitly set a stack bottom instead + /// of doing it automatically with EASetStackBase. If you pass NULL for pStackBase + /// then the function uses its stack location during its execution, which will be + /// a little less optimal than calling EASetStackBase. + /// + /// GetStackBase returns the stack bottom set by EASetStackBase or SetStackBase. + /// It returns NULL if no stack bottom was set or could be set. + /// + /// GetStackLimit returns the current stack "top", which will be lower than the stack + /// bottom in memory if the platform grows its stack downward. + + EATHREADLIB_API void SetStackBase(void* pStackBase); + inline void SetStackBase(uintptr_t pStackBase){ SetStackBase((void*)pStackBase); } + EATHREADLIB_API void* GetStackBase(); + EATHREADLIB_API void* GetStackLimit(); + + + #if defined(EA_COMPILER_MSVC) && defined(EA_PROCESSOR_X86) + #define EASetStackBase() \ + { \ + void* esp; \ + __asm { mov esp, ESP } \ + ::EA::Thread::SetStackBase(esp); \ + } + + #elif defined(EA_COMPILER_MSVC) && (defined(EA_PROCESSOR_X86_64) || defined(EA_PROCESSOR_ARM)) + // This implementation uses SetStackBase(NULL), which internally retrieves the stack pointer. + #define EASetStackBase() \ + { \ + ::EA::Thread::SetStackBase((void*)NULL); \ + } \ + + #elif defined(EA_COMPILER_ARM) // ARM compiler + + #define EASetStackBase() \ + ::EA::Thread::SetStackBase((void*)__current_sp()) + + #elif defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) // This covers EA_PLATFORM_UNIX, EA_PLATFORM_OSX + + #define EASetStackBase() \ + ::EA::Thread::SetStackBase((void*)__builtin_frame_address(0)); + + #else + // This implementation uses SetStackBase(NULL), which internally retrieves the stack pointer. + #define EASetStackBase() \ + { \ + ::EA::Thread::SetStackBase((void*)NULL); \ + } \ + + #endif + + #if defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_APPLE) || defined(EA_PLATFORM_SONY) + // GetPthreadStackInfo + // + // With some implementations of pthread, the stack base is returned by pthread as NULL if it's the main thread, + // or possibly if it's a thread you created but didn't call pthread_attr_setstack manually to provide your + // own stack. It's impossible for us to tell here whether will be such a NULL return value, so we just do what + // we can and the user nees to beware that a NULL return value means that the system doesn't provide the + // given information for the current thread. This function returns false and sets pBase and pLimit to NULL in + // the case that the thread base and limit weren't returned by the system or were returned as NULL. + + bool GetPthreadStackInfo(void** pBase, void** pLimit); + #endif + + } // namespace Thread + +} // namespace EA + + +#endif // Header include guard. diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_callstack_context.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_callstack_context.h new file mode 100644 index 00000000..117c8751 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_callstack_context.h @@ -0,0 +1,21 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + +#ifndef EATHREAD_EATHREAD_CALLSTACK_CONTEXT_H +#define EATHREAD_EATHREAD_CALLSTACK_CONTEXT_H + + +#include +#include +#include + +#endif // Header include guard. + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_condition.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_condition.h new file mode 100644 index 00000000..a076f5fa --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_condition.h @@ -0,0 +1,243 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements a condition variable in the style of Posix condition variables +// and Java and C# thread Monitors (Java objects and C# monitors have built-in +// locks and pthreads condition variables and EAThread::Conditions and Posix +// condition variables do not. A Condition is usually the appropriate thread +// synchronization mechanism for producer/consumer situations whereby one +// or more threads create data for one or more other threads to work on, +// such as is the case with a message queue. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_CONDITION_H +#define EATHREAD_EATHREAD_CONDITION_H + + +#include +#include +#include + + +#if defined(EA_DLL) && defined(EA_COMPILER_MSVC) + // Suppress warning about class 'EA::Thread::simple_list' needs to have + // dll-interface to be used by clients of class which have a templated member. + // + // These templates cannot be instantiated outside of the DLL. If you try, a + // link error will result. This compiler warning is intended to notify users + // of this. + EA_DISABLE_VC_WARNING(4251) +#endif + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +///////////////////////////////////////////////////////////////////////// +/// EAConditionData +/// +/// This is used internally by class Condition. +/// Todo: Consider moving this declaration into a platform-specific +/// header file. +/// +#if defined(EA_PLATFORM_SONY) + // Condition variables are built into Posix/Unix. + #include + #include + + struct EAConditionData + { + ScePthreadCond mCV; + EAConditionData(); + }; + +#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && EA_THREADS_AVAILABLE + // Condition variables are built into Posix/Unix. + #include + + struct EAConditionData + { + pthread_cond_t mCV; + EAConditionData(); + }; + +#else // All other platforms + #include + #include + + struct EATHREADLIB_API EAConditionData + { + EA::Thread::AtomicInt32 mnWaitersBlocked; + int mnWaitersToUnblock; + int mnWaitersDone; + EA::Thread::Semaphore mSemaphoreBlockQueue; + EA::Thread::Semaphore mSemaphoreBlockLock; + EA::Thread::Mutex mUnblockLock; + + EAConditionData(); + + private: + // Prevent default generation of these functions by declaring but not defining them. + EAConditionData(const EAConditionData& rhs); // copy constructor + EAConditionData& operator=(const EAConditionData& rhs); // assignment operator + }; + +#endif + + + +namespace EA +{ + namespace Thread + { +#if defined(EA_PLATFORM_SONY) + static const int CONDITION_VARIABLE_NAME_LENGTH_MAX = 31; +#else + static const int CONDITION_VARIABLE_NAME_LENGTH_MAX = 15; +#endif + /// ConditionParameters + /// Specifies condition variable settings. + struct EATHREADLIB_API ConditionParameters + { + bool mbIntraProcess; /// True if the Condition is intra-process, else inter-process. + char mName[CONDITION_VARIABLE_NAME_LENGTH_MAX + 1]; /// Condition name, applicable only to platforms that recognize named synchronization objects. + + ConditionParameters(bool bIntraProcess = true, const char* pName = NULL); + }; + + + /// Condition + /// Implements a condition variable thread synchronization primitive. A condition variable is usually the + /// appropriate thread synchronization mechanism for producer/consumer situations whereby one or more + /// threads create data for one or more other threads to work on, such as is the case with a message queue. + /// + /// To use a condition variable to wait for resource, you Lock the Mutex for that resource, then (in a loop) + /// check and Wait on a condition variable that you associate with the mutex. Upon calling Wait, + /// the Lock will be released so that other threads can adjust the resource. Upon return from Wait, + /// the Mutex is re-locked for the caller. To use a Condition to signal a change in something, you simply + /// call the Signal function. In the case of Signal(false), one blocking waiter will be released, + /// whereas with Signal(true), all blocking waiters will be released. Upon release of single or multiple + /// waiting threads, the Lock is contested for by all of them, so in the case or more than one waiter, + /// only one will immediately come away with ownership of the lock. + class EATHREADLIB_API Condition + { + public: + enum Result + { + kResultOK = 0, + kResultError = -1, + kResultTimeout = -2 + }; + + /// Condition + /// For immediate default initialization, use no args. + /// For custom immediate initialization, supply a first argument. + /// For deferred initialization, use Condition(NULL, false) then later call Init. + /// For deferred initialization of an array of objects, create an empty + /// subclass whose default constructor chains back to Condition(NULL, false). + Condition(const ConditionParameters* pConditionParameters = NULL, bool bDefaultParameters = true); + + /// ~Condition + /// Destroys the Condition object. If any threads that are blocking while waiting on + /// while the Condition is destroyed, the resulting behaviour is undefined. + ~Condition(); + + /// Init + /// Initializes the Condition. + bool Init(const ConditionParameters* pConditionParameters); + + /// Wait + /// Waits for the Condition with timeout. You must have a Mutex + /// (that you conceptually associate with the resource) locked before + /// calling this function or else the resulting behaviour is undefined. + /// Within a while loop, check the resource state and call Wait if the + /// necessary condition is not met. + /// + /// The call to Wait associates the Condition with your mutex, so it can + /// then unlock the mutex/resource (allows another thread to fill the resource). + /// + /// Upon non-error return of Wait, the mutex will be re-locked by the calling + /// thread, even if the result is a timeout. Upon returning from wait, before + /// doing any processing as a result of a Signal, your loop should always re-check + /// the resource state. The Posix Wait specification explicitly notes + /// that uncommon 'spurious wakeups' are possible and so should be tested + /// for. It impossible to test for a spurious wakeup from within this Wait + /// function, as this function can't know the resource state that caused the + /// Signal to occur. + /// + /// It should be noted that upon a kResultOK return from Wait, the user should + /// not assume that what the user was waiting on is still available. The signaling + /// of a Condition should be considered merely a hint to the waiter that the user + /// can probably proceed. Also, the user should usually call Wait only if the + /// user has nothing to wait for; the user should check for this before calling Wait. + /// + /// Note that the timeout is specified in absolute time and not relative time. + /// + /// Note also that due to the way thread scheduling works -- particularly in a + /// time-sliced threading environment -- that the timeout value is a hint and + /// the actual amount of time passed before the timeout occurs may be significantly + /// more or less than the specified timeout time. + /// + Result Wait(Mutex* pMutex, const ThreadTime& timeoutAbsolute = kTimeoutNone); + + /// Signal + /// Releases one or all waiters, depending on the input 'bBroadcast' argument. + /// The waiters will then contest for the Lock. + bool Signal(bool bBroadcast = false); + + /// GetPlatformData + /// Returns the platform-specific data handle for debugging uses or + /// other cases whereby special (and non-portable) uses are required. + void* GetPlatformData() + { return &mConditionData; } + + protected: + EAConditionData mConditionData; + + private: + // Objects of this class are not copyable. + Condition(const Condition&){} + Condition& operator=(const Condition&){ return *this; } + }; + + + /// ConditionFactory + /// + /// Implements a factory-based creation and destruction mechanism for class Condition. + /// A primary use of this would be to allow the Condition implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + class EATHREADLIB_API ConditionFactory + { + public: + static Condition* CreateCondition(); // Internally implemented as: return new Condition; + static void DestroyCondition(Condition* pCondition); // Internally implemented as: delete pCondition; + + static size_t GetConditionSize(); // Internally implemented as: return sizeof(Condition); + static Condition* ConstructCondition(void* pMemory); // Internally implemented as: return new(pMemory) Condition; + static void DestructCondition(Condition* pCondition); // Internally implemented as: pCondition->~Condition(); + }; + + + } // namespace Thread + +} // namespace EA + + + +#if defined(EA_DLL) && defined(EA_COMPILER_MSVC) + // re-enable warning 4251 (it's a level-1 warning and should not be suppressed globally) + EA_RESTORE_VC_WARNING() +#endif + + + +#endif // EATHREAD_EATHREAD_CONDITION_H diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_futex.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_futex.h new file mode 100644 index 00000000..7ef90d05 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_futex.h @@ -0,0 +1,797 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements a fast, user-space mutex. Also known as a lightweight mutex. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_FUTEX_H +#define EATHREAD_EATHREAD_FUTEX_H + +#include +#include +#include +#include +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_MANUAL_FUTEX_ENABLED +// +// Defined as 0 or 1. +// If enabled then Futex is implemented with atomics and semaphores instead of +// via a direct system-supported lightweight mutex implementation. +// +#ifndef EATHREAD_MANUAL_FUTEX_ENABLED + #if defined(EA_PLATFORM_MICROSOFT) // VC++ has CriticalSection, which is a futex. + #define EATHREAD_MANUAL_FUTEX_ENABLED 0 // Currently 0 because that's best. Can be set to 1. + #elif defined(EA_PLATFORM_SONY) + #define EATHREAD_MANUAL_FUTEX_ENABLED 0 // Allows us to have a spin count. + #else + #define EATHREAD_MANUAL_FUTEX_ENABLED 1 // Set to 1 until we can resolve any dependencies such as PPMalloc. + #endif +#endif +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_FUTEX_SPIN_COUNT +// +#ifndef EATHREAD_FUTEX_SPIN_COUNT + #define EATHREAD_FUTEX_SPIN_COUNT 256 +#endif +/////////////////////////////////////////////////////////////////////////////// + + + +///////////////////////////////////////////////////////////////////////// +/// Futex data +/// +/// This is used internally by class Futex. +/// Note that we don't use an EAThread semaphore, as the direct semaphore +/// we use here is more direct and avoid inefficiencies that result from +/// the possibility of EAThread semaphores being optimized for being +/// standalone. +/// +#if !EA_THREADS_AVAILABLE + #define EA_THREAD_NONTHREADED_FUTEX 1 + + #if EATHREAD_MANUAL_FUTEX_ENABLED + struct EAFutexSemaphore + { + int mnCount; + }; + #endif + +#elif EA_USE_CPP11_CONCURRENCY + EA_DISABLE_VC_WARNING(4265 4365 4836 4571 4625 4626 4628 4193 4127 4548) + #include + EA_RESTORE_VC_WARNING() + +#elif defined(EA_PLATFORM_APPLE) + #if EATHREAD_MANUAL_FUTEX_ENABLED + #include + typedef EA::Thread::Semaphore EAFutexSemaphore; + #endif + +#elif defined(EA_PLATFORM_SONY) + #if EATHREAD_MANUAL_FUTEX_ENABLED + #include + #include + + typedef SceKernelSema EAFutexSemaphore; + #endif + +#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #if EATHREAD_MANUAL_FUTEX_ENABLED + #include + typedef sem_t EAFutexSemaphore; + #endif + +#elif defined(EA_PLATFORM_MICROSOFT) + + // We avoid #including heavy system headers, as this file is a common header itself. + + extern "C" + { + #if defined(EA_COMPILER_GNUC) + // Mingw declares these slightly differently. + struct _CRITICAL_SECTION; + __declspec(dllimport) int __stdcall InitializeCriticalSectionAndSpinCount(_CRITICAL_SECTION* pCriticalSection, unsigned long dwSpinCount); + __declspec(dllimport) void __stdcall InitializeCriticalSection(_CRITICAL_SECTION* pCriticalSection); + __declspec(dllimport) void __stdcall DeleteCriticalSection(_CRITICAL_SECTION* pCriticalSection); + __declspec(dllimport) void __stdcall EnterCriticalSection(_CRITICAL_SECTION* pCriticalSection); + __declspec(dllimport) void __stdcall LeaveCriticalSection(_CRITICAL_SECTION* pCriticalSection); + __declspec(dllimport) int __stdcall TryEnterCriticalSection(_CRITICAL_SECTION* pCriticalSection); + #else + #if !defined _Must_inspect_result_ + #define _Must_inspect_result_ + #endif + + struct _RTL_CRITICAL_SECTION; + __declspec(dllimport) _Must_inspect_result_ int __stdcall InitializeCriticalSectionAndSpinCount(_Out_ _RTL_CRITICAL_SECTION* pCriticalSection, _In_ unsigned long dwSpinCount); + __declspec(dllimport) void __stdcall InitializeCriticalSection(_Out_ _RTL_CRITICAL_SECTION* pCriticalSection); + __declspec(dllimport) void __stdcall DeleteCriticalSection(_Inout_ _RTL_CRITICAL_SECTION* pCriticalSection); + __declspec(dllimport) void __stdcall EnterCriticalSection(_Inout_ _RTL_CRITICAL_SECTION* pCriticalSection); + __declspec(dllimport) void __stdcall LeaveCriticalSection(_Inout_ _RTL_CRITICAL_SECTION* pCriticalSection); + __declspec(dllimport) int __stdcall TryEnterCriticalSection(_Inout_ _RTL_CRITICAL_SECTION* pCriticalSection); + #endif + + __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(); + } + + #if EATHREAD_MANUAL_FUTEX_ENABLED + typedef void* EAFutexSemaphore; // void* instead of HANDLE to avoid #including windows headers. + #endif + +#else + #define EA_THREAD_NONTHREADED_FUTEX 1 + + #if EATHREAD_MANUAL_FUTEX_ENABLED + struct EAFutexSemaphore + { + int mnCount; + }; + #endif +#endif +///////////////////////////////////////////////////////////////////////// + + + + +namespace EA +{ + namespace Thread + { + #if defined(EA_COMPILER_MSVC) && EA_PLATFORM_PTR_SIZE == 8 + static const int FUTEX_PLATFORM_DATA_SIZE = 40; // CRITICAL_SECTION is 40 bytes on Win64. + #elif defined(EA_COMPILER_MSVC) && EA_PLATFORM_PTR_SIZE == 4 + static const int FUTEX_PLATFORM_DATA_SIZE = 32; // CRITICAL_SECTION is 24 bytes on Win32 and 28 bytes on XBox 360. + #endif + + + /// class Futex + /// + /// A Futex is a fast user-space mutex. It works by attempting to use + /// atomic integer updates for the common case whereby the mutex is + /// not already locked. If the mutex is already locked then the futex + /// drops down to waiting on a system-level semaphore. The result is + /// that uncontested locking operations can be significantly faster + /// than contested locks. Contested locks are slightly slower than in + /// the case of a formal mutex, but usually not by much. + /// + /// The Futex acts the same as a conventional mutex with respect to + /// memory synchronization. Specifically: + /// - A Lock or successful TryLock implies a read barrier (i.e. acquire). + /// - A second lock by the same thread implies no barrier. + /// - A failed TryLock implies no barrier. + /// - A final unlock by a thread implies a write barrier (i.e. release). + /// - A non-final unlock by a thread implies no barrier. + /// + /// Futex limitations relative to Mutexes: + /// - Futexes cannot be inter-process. + /// - Futexes cannot be named. + /// - Futexes cannot participate in condition variables. A special + /// condition variable could be made that works with them, though. + /// - Futex locks don't have timeouts. This could probably be + /// added with some work, though users generally shouldn't need timeouts. + /// + class EATHREADLIB_API Futex + { + public: + enum Result + { + kResultTimeout = -2 + }; + + /// Futex + /// + /// Creates a Futex. There are no creation options. + /// + Futex(); + + /// ~Futex + /// + /// Destroys an existing futex. The futex must not be locked by any thread + /// upon this call, otherwise the resulting behaviour is undefined. + /// + ~Futex(); + + /// TryLock + /// + /// Tries to lock the futex; returns true if possible. + /// This function always returns immediately. It will return false if + /// the futex is locked by another thread, and it will return true + /// if the futex is not locked or is already locked by the current thread. + /// + bool TryLock(); + + /// Lock + /// + /// Locks the futex; returns the new lock count. + /// If the futex is locked by another thread, this call will block until + /// the futex is unlocked. If the futex is not locked or is locked by the + /// current thread, this call will return immediately. + /// + void Lock(); + + /// Lock + /// + /// Tries to lock the futex until the given time. + /// If the futex is locked by another thread, this call will block until + /// the futex is unlocked or the given time has passed. If the futex is not locked + /// or is locked by the current thread, this call will return immediately. + /// + /// Return value: + /// kResultTimeout Timeout + /// > 0 The new lock count. + int Lock(const ThreadTime& timeoutAbsolute); + + /// Unlock + /// + /// Unlocks the futex. The futex must already be locked at least once by + /// the calling thread. Otherwise the behaviour is not defined. + /// + void Unlock(); + + /// GetLockCount + /// + /// Returns the number of locks on the futex. The return value from this + /// function is only reliable if the calling thread already has one lock on + /// the futex. Otherwise the returned value may not represent actual value + /// at any point in time, as other threads lock or unlock the futex soon after the call. + /// + int GetLockCount() const; + + /// HasLock + /// Returns true if the current thread has the futex locked. + bool HasLock() const; + + /// SetSpinCount + /// Specifies how many times we spin while waiting to acquire the lock. + void SetSpinCount(Uint spinCount); + + protected: + #if EATHREAD_MANUAL_FUTEX_ENABLED + void CreateFSemaphore(); + void DestroyFSemaphore(); + void SignalFSemaphore(); + void WaitFSemaphore(); + bool WaitFSemaphore(const ThreadTime& timeoutAbsolute); + void OnLockAcquired(ThreadUniqueId threadUniqueId); + #endif + + private: + // Objects of this class are not copyable. + Futex(const Futex&){} + Futex& operator=(const Futex&){ return *this; } + + protected: + #if EATHREAD_MANUAL_FUTEX_ENABLED + AtomicUWord mUseCount; /// Not the same thing as lock count, as waiters increment this value. + uint16_t mRecursionCount; /// The number of times the lock-owning thread has the mutex. This is currently uint16_t for backward compatibility with PPMalloc. + uint16_t mSpinCount; /// The number of times we spin while waiting for the lock. To do: Change these to be uint32_t once PPMalloc is no longer dependent on this. + ThreadUniqueId mThreadUniqueId; /// Unique id for owning thread; not necessarily same as type ThreadId. + EAFutexSemaphore mSemaphore; /// OS-level semaphore that waiters wait on when lock attempts failed. + #else + + #if EA_USE_CPP11_CONCURRENCY + std::recursive_timed_mutex mMutex; + int mnLockCount; + std::thread::id mLockingThread; + #elif defined(EA_COMPILER_MSVC) && defined(EA_PLATFORM_MICROSOFT) // In the case of Microsoft platforms, we just use CRITICAL_SECTION, as it is essentially a futex. + // We use raw structure math because otherwise we'd expose the user to system headers, + // which breaks code and bloats builds. We validate our math in eathread_futex.cpp. + #if EA_PLATFORM_PTR_SIZE == 8 + uint64_t mCRITICAL_SECTION[FUTEX_PLATFORM_DATA_SIZE / sizeof(uint64_t)]; + #else + uint64_t mCRITICAL_SECTION[FUTEX_PLATFORM_DATA_SIZE / sizeof(uint64_t)]; + #endif + #elif defined(EA_PLATFORM_SONY) + EA::Thread::Mutex mMutex; + Uint mSpinCount; + #else + #define EAT_FUTEX_USE_MUTEX 1 + EA::Thread::Mutex mMutex; + #endif + #endif + }; + + + + /// FutexFactory + /// + /// Implements a factory-based creation and destruction mechanism for class Futex. + /// A primary use of this would be to allow the Futex implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + /// + class EATHREADLIB_API FutexFactory + { + public: + static Futex* CreateFutex(); // Internally implemented as: return new Futex; + static void DestroyFutex(Futex* pFutex); // Internally implemented as: delete pFutex; + + static size_t GetFutexSize(); // Internally implemented as: return sizeof(Futex); + static Futex* ConstructFutex(void* pMemory); // Internally implemented as: return new(pMemory) Futex; + static void DestructFutex(Futex* pFutex); // Internally implemented as: pFutex->~Futex(); + }; + + + + /// class AutoFutex + /// An AutoFutex locks the Futex in its constructor and + /// unlocks the Futex in its destructor (when it goes out of scope). + class EATHREADLIB_API AutoFutex + { + public: + AutoFutex(Futex& futex); + ~AutoFutex(); + + protected: + Futex& mFutex; + + // Prevent copying by default, as copying is dangerous. + AutoFutex(const AutoFutex&); + const AutoFutex& operator=(const AutoFutex&); + }; + + + } // namespace Thread + +} // namespace EA + + + + + + +/////////////////////////////////////////////////////////////////////////////// +// Inlines +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// EAFutexReadBarrier +// +// For futexes, which are intended to be used only in user-space and without +// talking to IO devices, DMA memory, or uncached memory, we directly use +// memory barriers. + #define EAFutexReadBarrier EAReadBarrier + #define EAFutexWriteBarrier EAWriteBarrier + #define EAFutexReadWriteBarrier EAReadWriteBarrier +/////////////////////////////////////////////////////////////////////////////// + + + +namespace EA +{ + namespace Thread + { + #if EATHREAD_MANUAL_FUTEX_ENABLED + + inline Futex::Futex() + : mUseCount(0), + mRecursionCount(0), + mSpinCount(EATHREAD_FUTEX_SPIN_COUNT), + mThreadUniqueId(kThreadUniqueIdInvalid), + mSemaphore() + { + CreateFSemaphore(); + } + + + inline Futex::~Futex() + { + EAT_ASSERT(mUseCount == 0); + + DestroyFSemaphore(); + } + + + inline void Futex::OnLockAcquired(ThreadUniqueId threadUniqueId) + { + EAFutexReadBarrier(); + mThreadUniqueId = threadUniqueId; + mRecursionCount = 1; + } + + + inline bool Futex::TryLock() + { + ThreadUniqueId threadUniqueId; + EAThreadGetUniqueId(threadUniqueId); + + if(mUseCount.SetValueConditional(1, 0)) // If we could acquire the lock... (set it to 1 if it's 0) + { + OnLockAcquired(threadUniqueId); + return true; + } + + // This only happens in the case of recursion on the same thread + // This is threadsafe because the only case where this equality passes + // is when this value was set on this thread anyway. + if(EATHREAD_LIKELY(mThreadUniqueId == threadUniqueId)) // If it turns out that we already have the lock... + { + ++mUseCount; + ++mRecursionCount; + return true; + } + + return false; + } + + + inline void Futex::Lock() + { + ThreadUniqueId threadUniqueId; + EAThreadGetUniqueId(threadUniqueId); + + if(mSpinCount) // If we have spinning enabled (usually true)... + { + if(mUseCount.SetValueConditional(1, 0)) // If we could acquire the lock... (set it to 1 if it's 0) + { + OnLockAcquired(threadUniqueId); + return; + } + + if(mThreadUniqueId != threadUniqueId) // Don't spin if we already have the lock. + { + for(Uint count = mSpinCount; count > 0; count--) // Implement a spin lock for a number of tries. + { + // We use GetValueRaw calls below instead of atomics because we don't want atomic behavior. + if(mUseCount.GetValueRaw() > 1) // If there are multiple waiters, don't bother spinning any more, as they are already spinning themselves. + break; + + if(mUseCount.GetValueRaw() == 0) // If it looks like the lock is now free, try to acquire it. + { + if(mUseCount.SetValueConditional(1, 0)) // If we could acquire the lock... (set it to 1 if it's 0) + { + OnLockAcquired(threadUniqueId); + return; + } + } + + EAProcessorPause(); + } + } + } + + if(++mUseCount > 1) // If we could not get the lock (previous value of mUseCount was >= 1 and not 0) or we already had the lock... + { + if(mThreadUniqueId == threadUniqueId) // If we already have the lock... + { + mRecursionCount++; + return; + } + WaitFSemaphore(); + } + // Else the increment was from 0 to 1, and we own the lock. + OnLockAcquired(threadUniqueId); + } + + + + + inline int Futex::Lock(const ThreadTime& timeoutAbsolute) + { + if(timeoutAbsolute == kTimeoutNone) + { + Lock(); + return (int)mRecursionCount; + } + else if(timeoutAbsolute == kTimeoutImmediate) + { + if(TryLock()) + return (int)mRecursionCount; + else + return kResultTimeout; + } + else + { + ThreadUniqueId threadUniqueId; + EAThreadGetUniqueId(threadUniqueId); + + if(++mUseCount > 1) // If we could not get the lock (previous value of mUseCount was >= 1 and not 0) or we already had the lock... + { + if(mThreadUniqueId == threadUniqueId) // If we already have the lock... + return (int)++mRecursionCount; + + if(!WaitFSemaphore(timeoutAbsolute)) + { + --mUseCount; + return kResultTimeout; + } + } + // Else the increment was from 0 to 1, and we own the lock. + OnLockAcquired(threadUniqueId); + return 1; // Return mRecursionCount. + } + } + + + inline void Futex::Unlock() + { + #if EAT_ASSERT_ENABLED + ThreadUniqueId threadUniqueId; + EAThreadGetUniqueId(threadUniqueId); + EAT_ASSERT(mThreadUniqueId == threadUniqueId); + EAT_ASSERT((mRecursionCount > 0) && (mUseCount > 0)); + #endif + + if(EATHREAD_LIKELY(--mRecursionCount == 0)) + { + mThreadUniqueId = kThreadUniqueIdInvalid; + + // after the decrement below we will no longer own the lock + EAFutexWriteBarrier(); + if(EATHREAD_UNLIKELY(--mUseCount > 0)) + SignalFSemaphore(); + } + else + { + // this thread still owns the lock, was recursive + --mUseCount; + } + } + + + inline int Futex::GetLockCount() const + { + // No atomic operation or memory barrier required, as this function only + // has validity if it is being called from the lock-owning thread. However, + // we don't at this time choose to assert that mThreadUniqueId == GetThreadId(). + return (int)mRecursionCount; + } + + + inline bool Futex::HasLock() const + { + ThreadUniqueId threadUniqueId; + EAThreadGetUniqueId(threadUniqueId); + + return (mThreadUniqueId == threadUniqueId); + } + + + inline void Futex::SetSpinCount(Uint spinCount) + { + mSpinCount = spinCount; + } + + #else // #if EATHREAD_MANUAL_FUTEX_ENABLED + + #if EA_USE_CPP11_CONCURRENCY + + inline Futex::Futex() : mnLockCount(0) {} + + inline Futex::~Futex() { EAT_ASSERT(!GetLockCount()); } + + inline bool Futex::TryLock() + { + if (mMutex.try_lock()) + { + EAT_ASSERT(mnLockCount >= 0); + EAT_ASSERT(mnLockCount == 0 || mLockingThread == std::this_thread::get_id()); + ++mnLockCount; + mLockingThread = std::this_thread::get_id(); + return true; + } + + return false; + } + + inline void Futex::Lock() { mMutex.lock(); mLockingThread = std::this_thread::get_id(); ++mnLockCount; } + + inline int Futex::Lock(const ThreadTime& timeoutAbsolute) + { + if (timeoutAbsolute == kTimeoutNone) + { + if (!mMutex.try_lock()) + { + return kResultTimeout; + } + } + else + { + std::chrono::milliseconds timeoutAbsoluteMs(timeoutAbsolute); + std::chrono::time_point timeout_time(timeoutAbsoluteMs); + if (!mMutex.try_lock_until(timeout_time)) + { + return kResultTimeout; + } + } + + EAT_ASSERT(mnLockCount >= 0); + EAT_ASSERT(mnLockCount == 0 || mLockingThread == std::this_thread::get_id()); + mLockingThread = std::this_thread::get_id(); + return ++mnLockCount; // This is safe to do because we have the lock. + } + + inline void Futex::Unlock() + { + EAT_ASSERT(HasLock()); + --mnLockCount; + if (mnLockCount == 0) + mLockingThread = std::thread::id(); + mMutex.unlock(); + } + + inline int Futex::GetLockCount() const { return mnLockCount; } + + inline bool Futex::HasLock() const + { + if ((mnLockCount > 0) && (std::this_thread::get_id() == mLockingThread)) + return true; + return false; + } + + inline void Futex::SetSpinCount(Uint) + { + // Not supported + } + + #elif defined(EA_COMPILER_MSVC) && defined(EA_PLATFORM_MICROSOFT) // Win32, Win64, etc. + + inline Futex::Futex() + { + // We use InitializeCriticalSectionAndSpinCount, as that has resulted in improved performance in practice on multiprocessors systems. + int rv = InitializeCriticalSectionAndSpinCount((_RTL_CRITICAL_SECTION*)mCRITICAL_SECTION, EATHREAD_FUTEX_SPIN_COUNT); + EAT_ASSERT(rv != 0); + EA_UNUSED(rv); + } + + inline Futex::~Futex() + { + EAT_ASSERT(!GetLockCount()); + DeleteCriticalSection((_RTL_CRITICAL_SECTION*)mCRITICAL_SECTION); + } + + inline bool Futex::TryLock() + { + return TryEnterCriticalSection((_RTL_CRITICAL_SECTION*)mCRITICAL_SECTION) != 0; + } + + inline void Futex::Lock() + { + EnterCriticalSection((_RTL_CRITICAL_SECTION*)mCRITICAL_SECTION); + } + + inline int Futex::Lock(const ThreadTime& timeoutAbsolute) + { + if(timeoutAbsolute == kTimeoutNone) + { + Lock(); + return GetLockCount(); + } + else if(timeoutAbsolute == kTimeoutImmediate) + { + if(TryLock()) + return GetLockCount(); + else + return kResultTimeout; + } + else + { + while(!TryLock()) + { + if(GetThreadTime() >= timeoutAbsolute) + return kResultTimeout; + ThreadSleep(1); + } + return GetLockCount(); + } + } + + inline void Futex::Unlock() + { + EAT_ASSERT(HasLock()); + LeaveCriticalSection((_RTL_CRITICAL_SECTION*)mCRITICAL_SECTION); + } + + inline int Futex::GetLockCount() const + { + // Return the RecursionCount member of RTL_CRITICAL_SECTION. + + // We use raw structure math because otherwise we'd expose the user to system headers, + // which breaks code and bloats builds. We validate our math in eathread_futex.cpp. + #if EA_PLATFORM_PTR_SIZE == 8 + return *((int*)mCRITICAL_SECTION + 3); + #else + return *((int*)mCRITICAL_SECTION + 2); + #endif + } + + inline bool Futex::HasLock() const + { + // Check the OwningThread member of RTL_CRITICAL_SECTION. + + // We use raw structure math because otherwise we'd expose the user to system headers, + // which breaks code and bloats builds. We validate our math in eathread_futex.cpp. + #if EA_PLATFORM_PTR_SIZE == 8 + return (*((uint32_t*)mCRITICAL_SECTION + 4) == (uintptr_t)GetCurrentThreadId()); + #else + return (*((uint32_t*)mCRITICAL_SECTION + 3) == (uintptr_t)GetCurrentThreadId()); + #endif + } + + inline void Futex::SetSpinCount(Uint) + { + // Not supported + } + + #elif defined(EAT_FUTEX_USE_MUTEX) + + inline Futex::Futex() + { } + + inline Futex::~Futex() + { } + + inline bool Futex::TryLock() + { return mMutex.Lock(EA::Thread::kTimeoutImmediate) > 0; } + + inline void Futex::Lock() + { mMutex.Lock(); } + + inline int Futex::Lock(const ThreadTime& timeoutAbsolute) + { return mMutex.Lock(timeoutAbsolute); } + + inline void Futex::Unlock() + { mMutex.Unlock(); } + + inline int Futex::GetLockCount() const + { return mMutex.GetLockCount(); } + + inline bool Futex::HasLock() const + { return mMutex.HasLock(); } + + inline void Futex::SetSpinCount(Uint) + { } + + #endif // EA_COMPILER_MSVC + + #endif // EATHREAD_MANUAL_FUTEX_ENABLED + + + + inline AutoFutex::AutoFutex(Futex& futex) + : mFutex(futex) + { + mFutex.Lock(); + } + + inline AutoFutex::~AutoFutex() + { + mFutex.Unlock(); + } + + } // namespace Thread + +} // namespace EA + + + +#endif // EATHREAD_EATHREAD_FUTEX_H + + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_list.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_list.h new file mode 100644 index 00000000..03dab79a --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_list.h @@ -0,0 +1,323 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// This is a small templated list implementation which suffices for our +// purposes but is not optimal. It is present in order to avoid dependencies +// on external libraries. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_LIST_H +#define EATHREAD_EATHREAD_LIST_H + + +#include +#include +#include // size_t, etc. +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +namespace EA +{ + namespace Thread + { + + namespace details + { + /// Default allocator implementation used by the simple_list class + template + struct ListDefaultAllocatorImpl + { + template + struct rebind { typedef ListDefaultAllocatorImpl other; }; + + T* construct() + { + Allocator* pAllocator = GetAllocator(); + + if(pAllocator) + return new(pAllocator->Alloc(sizeof(T))) T; + else + return new T; + } + + void destroy(T* obj) + { + Allocator* pAllocator = GetAllocator(); + + if(pAllocator) + { + obj->~T(); + pAllocator->Free(obj); + } + else + delete obj; + } + }; + } + + + /// Simple version of an STL bidirectional list. + /// Implemented to avoid dependency on container implementations. + /// + /// This implementation has some non-stl standard methods like find. + /// + template > + class simple_list + { + simple_list(const simple_list&); + simple_list& operator=(const simple_list&); + + protected: + struct list_node + { + T mValue; + list_node* mpPrev; + list_node* mpNext; + }; + + typedef list_node node_t; + typedef typename Allocator::template rebind::other allocator_t; + + allocator_t mAllocator; + node_t* mpNodeHead; + node_t* mpNodeTail; + size_t mnSize; + + public: + typedef T value_type; //< list value type + typedef const T const_value_type; //< constant list value type + typedef const T& const_value_ref_type; //< constant reference list value type + + struct const_iterator; + struct iterator; + friend struct const_iterator; + friend struct iterator; + + + struct const_iterator + { + friend class simple_list; + + const_iterator() + : mpNode(NULL) + { } + + const_iterator(const const_iterator& rhs) + : mpNode(rhs.mpNode) + { } + + const_iterator& operator=(const const_iterator& rhs) + { + mpNode = rhs.mpNode; + return *this; + } + + const T& operator*() const + { return mpNode->mValue; } + + const T* operator->() const + { return &**this; } + + bool operator==(const const_iterator& rhs) const + { return rhs.mpNode == mpNode; } + + bool operator!=(const const_iterator& rhs) const + { return rhs.mpNode != mpNode; } + + const_iterator& operator++() + { + mpNode = mpNode->mpNext; + return *this; + } + + protected: + const node_t* mpNode; + + protected: + const_iterator(node_t* pNode) + : mpNode(pNode) + { } + + const_iterator& operator=(const node_t* pNode) + { + mpNode = pNode; + return *this; + } + }; // const_iterator + + + + struct iterator : public const_iterator + { + friend class simple_list; + + iterator() + : const_iterator(){ } + + iterator(const const_iterator& rhs) + : const_iterator(rhs) + { } + + iterator& operator=(const const_iterator& rhs) + { + *static_cast(this)= rhs; + return *this; + } + + T& operator*() const + { return const_cast(**static_cast(this)); } + + T& operator->() const + { return const_cast(&**static_cast(this)); } + + iterator& operator++() + { + ++(*static_cast(this)); + return *this; + } + + protected: + iterator(node_t* pNode) + : const_iterator(pNode) + { } + + iterator& operator=(node_t* pNode) + { + const_cast(*this) = pNode; + return *this; + } + }; // iterator + + + + simple_list() + : mnSize(0) + { + mpNodeHead = mAllocator.construct(); + mpNodeTail = mAllocator.construct(); + mpNodeHead->mpNext = mpNodeTail; + mpNodeHead->mpPrev = mpNodeTail; + mpNodeTail->mpNext = mpNodeHead; + mpNodeTail->mpPrev = mpNodeHead; + } + + ~simple_list() + { + clear(); + mAllocator.destroy(mpNodeHead); + mAllocator.destroy(mpNodeTail); + } + + bool empty() const + { return mpNodeHead->mpNext == mpNodeTail; } + + void push_back(const T& value) + { + node_t* const pNode = mAllocator.construct(); + pNode->mValue = value; + pNode->mpPrev = mpNodeTail->mpPrev; + pNode->mpNext = mpNodeTail; + pNode->mpPrev->mpNext = pNode; + mpNodeTail->mpPrev = pNode; + ++mnSize; + } + + void push_front(const T& value) + { + node_t* const pNode = mAllocator.construct(); + pNode->mValue = value; + pNode->mpPrev = mpNodeHead; + pNode->mpNext = mpNodeHead->mpNext; + mpNodeHead->mpNext = pNode; + ++mnSize; + } + + void pop_front() + { + if(!empty()) + { + node_t* const pNode = mpNodeHead->mpNext; + mpNodeHead->mpNext = pNode->mpNext; + pNode->mpNext->mpPrev = mpNodeHead; + mAllocator.destroy(pNode); + --mnSize; + } + } + + size_t size() const + { return mnSize; } + + iterator erase(iterator& iter) + { + if(!empty()) + { + node_t* const pNext = iter.mpNode->mpNext; + iter.mpNode->mpNext->mpPrev = iter.mpNode->mpPrev; + iter.mpNode->mpPrev->mpNext = iter.mpNode->mpNext; + --mnSize; + mAllocator.destroy(const_cast(iter.mpNode)); + return pNext; + } + return end(); + } + + void clear() + { + if(!empty()) + { + node_t* pNode = mpNodeHead->mpNext; + + while(pNode != mpNodeTail) + { + node_t* const pNext = pNode->mpNext; + pNode->mpNext->mpPrev = pNode->mpPrev; + pNode->mpPrev->mpNext = pNext; + mAllocator.destroy(pNode); + pNode = pNext; + } + mnSize = 0; + } + } + + T& front() const + { return mpNodeHead->mpNext->mValue; } + + const const_iterator begin() const + { return mpNodeHead->mpNext; } + + const const_iterator end() const + { return mpNodeTail; } + + /// returns end()if not found + iterator find(const T& element) + { + iterator iter = begin(); + while((iter != end()) && !(element == *iter)) + ++iter; + return iter; + } + + }; // simple_list + + } // namespace Thread + +} // namespace EA + + +#endif // EATHREAD_EATHREAD_LIST_H + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_mutex.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_mutex.h new file mode 100644 index 00000000..32f6526d --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_mutex.h @@ -0,0 +1,341 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements a lightweight mutex. +///////////////////////////////////////////////////////////////////////////// + +// TODO(rparolin): Consider adding support for static thread safety analysis. +// https://clang.llvm.org/docs/ThreadSafetyAnalysis.html + + +#ifndef EATHREAD_EATHREAD_MUTEX_H +#define EATHREAD_EATHREAD_MUTEX_H + +#if defined(EA_COMPILER_MSVC) +#include // #include math.h because VC++ has a header file but that requires math.h to be #included before some other headers, lest you get a warning. +#endif +#include +#include +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +///////////////////////////////////////////////////////////////////////// +/// EAMutexData +/// +/// This is used internally by class Mutex. +/// Todo: Consider moving this declaration into a platform-specific +/// header file. +/// +#if !EA_THREADS_AVAILABLE + #define EA_THREAD_NONTHREADED_MUTEX 1 + + struct EAMutexData + { + int mnLockCount; + + EAMutexData(); + }; + +#elif EA_USE_CPP11_CONCURRENCY + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() + + #if defined EA_PLATFORM_MICROSOFT + #ifdef CreateMutex + #undef CreateMutex // Windows #defines CreateMutex to CreateMutexA or CreateMutexW. + #endif + #endif + + struct EAMutexData + { + std::recursive_timed_mutex mMutex; + int mnLockCount; + #if EAT_ASSERT_ENABLED + EA::Thread::ThreadId mThreadId; // This value is only valid in debug builds. + #endif + + EAMutexData(); + + private: + EAMutexData(const EAMutexData&); + EAMutexData& operator=(const EAMutexData&); + }; + +#elif defined(EA_PLATFORM_SONY) + #include + #include + + struct EAMutexData + { + ScePthreadMutex mMutex; + int mnLockCount; + #if EAT_ASSERT_ENABLED + EA::Thread::ThreadId mThreadId; // This value is only valid in debug builds. + #endif + + EAMutexData(); + void SimulateLock(bool bLock); + }; + +#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + + #if defined(EA_PLATFORM_WINDOWS) + #ifdef CreateMutex + #undef CreateMutex // Windows #defines CreateMutex to CreateMutexA or CreateMutexW. + #endif + #endif + + struct EAMutexData + { + pthread_mutex_t mMutex; + int mnLockCount; + #if EAT_ASSERT_ENABLED + EA::Thread::ThreadId mThreadId; // This value is only valid in debug builds. + #endif + + EAMutexData(); + void SimulateLock(bool bLock); + }; + +#elif defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE + + #if defined(EA_PROCESSOR_X86_64) || defined(EA_PROCESSOR_ARM64) + static const int MUTEX_PLATFORM_DATA_SIZE = 40; // CRITICAL_SECTION is 40 bytes on Win64 & ARM64. + #else + static const int MUTEX_PLATFORM_DATA_SIZE = 32; // CRITICAL_SECTION is 24 bytes on Win32, 28 bytes on XBox 360. + #endif + + #ifdef CreateMutex + #undef CreateMutex // Windows #defines CreateMutex to CreateMutexA or CreateMutexW. + #endif + + struct EATHREADLIB_API EAMutexData + { + uint64_t mData[MUTEX_PLATFORM_DATA_SIZE / sizeof(uint64_t)]; // Holds either CRITICAL_SECTION or HANDLE if mbIntraProcess is true or false, respectively. + int mnLockCount; + bool mbIntraProcess; + #if EAT_ASSERT_ENABLED + EA::Thread::ThreadId mThreadId; // This value is only valid in debug builds. + EA::Thread::SysThreadId mSysThreadId; // This value is only valid in debug builds. + #endif + + EAMutexData(); + }; + +#else + #define EA_THREAD_NONTHREADED_MUTEX 1 + + struct EAMutexData + { + int mnLockCount; + + EAMutexData(); + }; + + + +#endif +///////////////////////////////////////////////////////////////////////// + + + + +namespace EA +{ + namespace Thread + { + /// MutexParameters + /// Specifies mutex settings. + struct EATHREADLIB_API MutexParameters + { + bool mbIntraProcess; /// True if the mutex is intra-process, else inter-process. + char mName[128]; /// Mutex name, applicable only to platforms that recognize named synchronization objects. + + MutexParameters(bool bIntraProcess = true, const char* pName = NULL); + }; + + + /// class Mutex + /// + /// Mutex are assumed to always be 'recursive', meaning that a given thread + /// can lock the mutex more than once. If you want a specifically non-recursive + /// mutex, you can use a semaphore with a lock count of 1. + class EATHREADLIB_API Mutex + { + public: + enum Result + { + kResultError = -1, + kResultTimeout = -2 + }; + + /// Mutex + /// For immediate default initialization, use no args. + /// For custom immediate initialization, supply a first argument. + /// For deferred initialization, use Mutex(NULL, false) then later call Init. + /// For deferred initialization of an array of objects, create an empty + /// subclass whose default constructor chains back to Mutex(NULL, false). + Mutex(const MutexParameters* pMutexParameters = NULL, bool bDefaultParameters = true); + + /// ~Mutex + /// Destroys an existing mutex. The mutex must not be locked by any thread, + /// otherwise the resulting behaviour is undefined. + ~Mutex(); + + /// Init + /// Initializes the mutex if not done so in the constructor. + /// This should only be called in the case that this class was constructed + /// with RWMutex(NULL, false). + bool Init(const MutexParameters* pMutexParameters); + + /// Lock + /// Locks the mutex, with a timeout specified. This function will + /// return immediately if the mutex is not locked or if the calling + /// thread already has it locked at least once. If the mutex is + /// locked by another thread, this function will block until the mutex + /// is unlocked by the owning thread or until the timeout time has + /// passed. This function may return before the specified timeout has passed + /// and so should not be implicitly used as a timer. Some platforms may + /// return immediately if the timeout is specified as anything but kTimeoutNone. + /// + /// Note that the timeout is specified in absolute time and not relative time. + /// + /// Note also that due to the way thread scheduling works -- particularly in a + /// time-sliced threading environment -- that the timeout value is a hint and + /// the actual amount of time passed before the timeout occurs may be significantly + /// more or less than the specified timeout time. + /// + /// Return value: + /// kResultError Error + /// kResultTimeout Timeout + /// > 0 The new lock count. + int Lock(const ThreadTime& timeoutAbsolute = EA::Thread::kTimeoutNone); + + /// Unlock + /// Unlocks the mutex. The mutex must already be locked at least once by + /// the calling thread. Otherwise the behaviour is not defined. + /// Return value is the lock count value immediately upon unlock. + int Unlock(); + + /// GetLockCount + /// Returns the number of locks on the mutex. The return value from this + /// function is only reliable if the calling thread already has one lock on + /// the critical section. Otherwise the value could be changing as other + /// threads lock or unlock the mutex soon after the call. + /// This function is useful in debugging and asserting and useful for backing + /// out of recursive locks under the case of exceptions and other abortive + /// situations. This function will not necessarily call memory synchronization + /// primitives (e.g. ReadBarrier) itself on systems that require SMP synchronization. + int GetLockCount() const; + + + /// HasLock + /// Returns true if the current thread has the mutex locked. + /// This function is reliable only in a debug build whereby + /// EAT_ASSERT_ENABLED is defined to 1. This function can thus + /// only be used in debugging situations whereby you want to + /// assert that you have a mutex locked or not. To make this + /// function work in a non-debug environment would necessitate + /// adding an undesirable amount of code and data. + bool HasLock() const; + + /// GetPlatformData + /// Returns the platform-specific data handle for debugging uses or + /// other cases whereby special (and non-portable) uses are required. + void* GetPlatformData() + { return &mMutexData; } + + protected: + EAMutexData mMutexData; + + private: + // Objects of this class are not copyable. + Mutex(const Mutex&){} + Mutex& operator=(const Mutex&){ return *this; } + }; + + + + /// MutexFactory + /// + /// Implements a factory-based creation and destruction mechanism for class Mutex. + /// A primary use of this would be to allow the Mutex implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + class EATHREADLIB_API MutexFactory + { + public: + static Mutex* CreateMutex(); // Internally implemented as: return new Mutex; + static void DestroyMutex(Mutex* pMutex); // Internally implemented as: delete pMutex; + + static size_t GetMutexSize(); // Internally implemented as: return sizeof(Mutex); + static Mutex* ConstructMutex(void* pMemory); // Internally implemented as: return new(pMemory) Mutex; + static void DestructMutex(Mutex* pMutex); // Internally implemented as: pMutex->~Mutex(); + }; + + + } // namespace Thread + +} // namespace EA + + + + + +namespace EA +{ + namespace Thread + { + /// class AutoMutex + /// An AutoMutex locks the Mutex in its constructor and + /// unlocks the Mutex in its destructor (when it goes out of scope). + class EATHREADLIB_API AutoMutex + { + public: + inline AutoMutex(Mutex& mutex) + : mMutex(mutex) + { mMutex.Lock(); } + + inline ~AutoMutex() + { mMutex.Unlock(); } + + protected: + Mutex& mMutex; + + // Prevent copying by default, as copying is dangerous. + AutoMutex(const AutoMutex&); + const AutoMutex& operator=(const AutoMutex&); + }; + + } // namespace Thread + +} // namespace EA + + + +#endif // EATHREAD_EATHREAD_MUTEX_H + + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_pool.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_pool.h new file mode 100644 index 00000000..9cceadf0 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_pool.h @@ -0,0 +1,301 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements a classic thread pool. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_POOL_H +#define EATHREAD_EATHREAD_POOL_H + + +#ifndef EATHREAD_EATHREAD_THREAD_H + #include +#endif +#ifndef EATHREAD_EATHREAD_CONDITION_H + #include +#endif +#ifndef EATHREAD_EATHREAD_ATOMIC_H + #include +#endif +#ifndef EATHREAD_EATHREAD_LIST_H + #include +#endif +#include + + +#if defined(EA_DLL) && defined(EA_COMPILER_MSVC) + // Suppress warning about class 'EA::Thread::simple_list' needs to have + // dll-interface to be used by clients of class which have a templated member. + // + // These templates cannot be instantiated outside of the DLL. If you try, a + // link error will result. This compiler warning is intended to notify users + // of this. + EA_DISABLE_VC_WARNING(4251) +#endif + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +///////////////////////////////////////////////////////////////////////////// +// EA_THREAD_POOL_MAX_SIZE +// +// Defines the maximum number of threads the pool can have. +// Currently we have a limit of at most N threads in a pool, in order to +// simplify memory management issues. +// +#ifndef EA_THREAD_POOL_MAX_SIZE + #define EA_THREAD_POOL_MAX_SIZE 16 +#endif + + + +namespace EA +{ + namespace Thread + { + /// ThreadPoolParameters + /// Specifies how a thread pool is initialized + struct EATHREADLIB_API ThreadPoolParameters + { + unsigned mnMinCount; /// Default is kDefaultMinCount. + unsigned mnMaxCount; /// Default is kDefaultMaxCount. + unsigned mnInitialCount; /// Default is kDefaultInitialCount + ThreadTime mnIdleTimeoutMilliseconds; /// Default is kDefaultIdleTimeout. This is a relative time, not an absolute time. Can be a millisecond value or Thread::kTimeoutNone or Thread::kTimeoutImmediate. + unsigned mnProcessorMask; /// Default is 0xffffffff. Controls which processors we are allowed to create threads on. Default is all processors. + ThreadParameters mDefaultThreadParameters; /// Currently only the mnStackSize, mnPriority, and mpName fields from ThreadParameters are used. + + ThreadPoolParameters(); + + private: + // Prevent default generation of these functions by not defining them + ThreadPoolParameters(const ThreadPoolParameters& rhs); // copy constructor + ThreadPoolParameters& operator=(const ThreadPoolParameters& rhs); // assignment operator + }; + + + /// class ThreadPool + /// + /// Implements a conventional thread pool. Thread pools are useful for situations where + /// thread creation and destruction is common and the application speed would improve + /// by using pre-made threads that are ready to execute. + class EATHREADLIB_API ThreadPool + { + public: + enum Default + { + kDefaultMinCount = 0, + kDefaultMaxCount = 4, + kDefaultInitialCount = 0, + kDefaultIdleTimeout = 60000, // Milliseconds + kDefaultProcessorMask = 0xffffffff + }; + + enum Result + { + kResultOK = 0, + kResultError = -1, + kResultTimeout = -2, + kResultDeferred = -3 + }; + + enum JobWait + { + kJobWaitNone, /// Wait for no jobs to complete, including those currently running. + kJobWaitCurrent, /// Wait for currently proceeding jobs to complete but not those that haven't started. + kJobWaitAll /// Wait for all jobs to complete, including those that haven't yet begun. + }; + + /// ThreadPool + /// For immediate default initialization, use no args. + /// For custom immediate initialization, supply a first argument. + /// For deferred initialization, use ThreadPool(NULL, false) then later call Init. + /// For deferred initialization of an array of objects, create an empty + /// subclass whose default constructor chains back to ThreadPool(NULL, false). + ThreadPool(const ThreadPoolParameters* pThreadPoolParameters = NULL, bool bDefaultParameters = true); + + /// ~ThreadPool + /// Destroys the thread pool. Waits for any busy threads to complete. + ~ThreadPool(); + + /// Init + /// Initializes the thread pool with given characteristics. If the thread pool is + /// already initialized, this updates the settings. + bool Init(const ThreadPoolParameters* pThreadPoolParameters); + + /// Shutdown + /// Disables the thread pool, waits for busy threads to complete, destroys all threads. + /// + /// If bWaitForAllJobs is true, then Shutdown will wait until all jobs, including + /// jobs that haven't been started yet, to complete. Otherwise, only currently + /// proceeding jobs will be completed. + /// + /// Note that the timeout is specified in absolute time and not relative time. + /// + /// Note also that due to the way thread scheduling works -- particularly in a + /// time-sliced threading environment -- that the timeout value is a hint and + /// the actual amount of time passed before the timeout occurs may be significantly + /// more or less than the specified timeout time. + /// + bool Shutdown(JobWait jobWait = kJobWaitAll, const ThreadTime& timeoutAbsolute = kTimeoutNone); + + /// Begin + /// Starts a thread from the pool with the given parameters. + /// Returns kResultError or a job id of >= kResultOK. A return of kResultDeferred is + /// possible if the number of active threads is greater or equal to the max count. + /// If input ppThread is non-NULL and return value is >= kResultOK, the returned thread + /// will be the thread used for the job. Else the returned thread pointer will be NULL. + /// If input bEnabledDeferred is false but the max count of active theads has been + /// reached, a new thread is nevertheless created. + int Begin(IRunnable* pRunnable, void* pContext = NULL, Thread** ppThread = NULL, bool bEnableDeferred = false); + int Begin(RunnableFunction pFunction, void* pContext = NULL, Thread** ppThread = NULL, bool bEnableDeferred = false); + + /// WaitForJobCompletion + /// Waits for an individual job or for all jobs (job id of -1) to complete. + /// If a job id is given which doesn't correspond to any existing job, + /// the job is assumed to have been completed and the wait completes immediately. + /// If new jobs are added while the wait is occurring, this function will wait + /// for those jobs to complete as well. jobWait is valid only if nJob is -1. + /// Note that the timeout is specified in absolute time and not relative time. + /// Returns one of enum Result. + int WaitForJobCompletion(int nJob = -1, JobWait jobWait = kJobWaitAll, const ThreadTime& timeoutAbsolute = kTimeoutNone); + + /// Pause + /// Enables or disables the activation of threads from the pool. + /// When paused, calls to Begin will return kResultDeferred instead of kResultOK. + void Pause(bool bPause); + + /// Locks the thread pool thread list. + void Lock(); + void Unlock(); + + struct Job + { + int mnJobID; /// Unique job id. + IRunnable* mpRunnable; /// User-supplied IRunnable. This is an alternative to mpFunction. + RunnableFunction mpFunction; /// User-supplied function. This is an alternative to mpRunnable. + void* mpContext; /// User-supplied context. + + Job(); + }; + + struct ThreadInfo + { + volatile bool mbActive; /// True if the thread is currently busy working on a job. + volatile bool mbQuit; /// If set to true then this thread should quit at the next opportunity. + //bool mbPersistent; /// If true then this thread is never quit at runtime. False by default. + Thread* mpThread; /// The Thread itself. + ThreadPool* mpThreadPool; /// The ThreadPool that owns this thread. + Job mCurrentJob; /// The most recent job a thread is or was working on. + + ThreadInfo(); + }; + + /// AddThread + /// Adds a new thread with the given ThreadParameters. + /// The return value is not safe to use unless this function is called + /// and the result used within a Lock/Unlock pair. + /// It's the user's responsibility to supply ThreadParameters that are sane. + /// If bBeginThread is true, then the Thread is started via a call to + /// pThreadInfo->mpThread->Begin(ThreadFunction, pThreadInfo, &tp); + /// Otherwise the user is expected to manually start the thread. + ThreadInfo* AddThread(const ThreadParameters& tp, bool bBeginThread); + + // Gets the ThreadInfo for the nth Thread identified by index. + // You must call this function and use the info within a Lock/Unlock pair + // on the thread pool. + ThreadInfo* GetThreadInfo(int index); + + // Unless you call this function while the Pool is locked (via Lock), the return + // value may be out of date by the time you read it. + int GetThreadCount(); + + protected: + typedef EA::Thread::simple_list JobList; + typedef EA::Thread::simple_list ThreadInfoList; + + // Member functions + static intptr_t ThreadFunction(void* pContext); + ThreadInfo* CreateThreadInfo(); + void SetupThreadParameters(ThreadParameters& tp); + void AdjustThreadCount(unsigned nCount); + Result QueueJob(const Job& job, Thread** ppThread, bool bEnableDeferred); + void AddThread(ThreadInfo* pThreadInfo); + void RemoveThread(ThreadInfo* pThreadInfo); + void FixThreads(); + + // Member data + bool mbInitialized; // + uint32_t mnMinCount; // Min number of threads to have available. + uint32_t mnMaxCount; // Max number of threads to have available. + AtomicInt32 mnCurrentCount; // Current number of threads available. + AtomicInt32 mnActiveCount; // Current number of threads busy with jobs. + ThreadTime mnIdleTimeoutMilliseconds; // Timeout before quitting threads that have had no jobs. + uint32_t mnProcessorMask; // If mask is not 0xffffffff then we manually round-robin assign processors. + uint32_t mnProcessorCount; // The number of processors currently present. + uint32_t mnNextProcessor; // Used if we are manually round-robin assigning processors. + AtomicInt32 mnPauseCount; // A positive value means we pause working on jobs. + AtomicInt32 mnLastJobID; // + ThreadParameters mDefaultThreadParameters; // + Condition mThreadCondition; // Manages signalling mJobList. + Mutex mThreadMutex; // Guards manipulation of mThreadInfoList and mJobList. + ThreadInfoList mThreadInfoList; // List of threads in our pool. + JobList mJobList; // List of waiting jobs. + + private: + // Prevent default generation of these functions by not defining them + ThreadPool(const ThreadPool& rhs); // copy constructor + ThreadPool& operator=(const ThreadPool& rhs); // assignment operator + }; + + + + /// ThreadPoolFactory + /// + /// Implements a factory-based creation and destruction mechanism for class ThreadPool. + /// A primary use of this would be to allow the ThreadPool implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + class EATHREADLIB_API ThreadPoolFactory + { + public: + static ThreadPool* CreateThreadPool(); // Internally implemented as: return new ThreadPool; + static void DestroyThreadPool(ThreadPool* pThreadPool); // Internally implemented as: delete pThreadPool; + + static size_t GetThreadPoolSize(); // Internally implemented as: return sizeof(ThreadPool); + static ThreadPool* ConstructThreadPool(void* pMemory); // Internally implemented as: return new(pMemory) ThreadPool; + static void DestructThreadPool(ThreadPool* pThreadPool); // Internally implemented as: pThreadPool->~ThreadPool(); + }; + + } // namespace Thread + +} // namespace EA + + + +#if defined(EA_DLL) && defined(EA_COMPILER_MSVC) + // re-enable warning 4251 (it's a level-1 warning and should not be suppressed globally) + EA_RESTORE_VC_WARNING() +#endif + + +#endif // EATHREAD_EATHREAD_POOL_H + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_rwmutex.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_rwmutex.h new file mode 100644 index 00000000..2e5bdf2a --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_rwmutex.h @@ -0,0 +1,221 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements a lightweight mutex with multiple reads but single writer. +// This allows for high performance systems whereby the consumers of data +// are more common than the producers of data. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_RWMUTEX_H +#define EATHREAD_EATHREAD_RWMUTEX_H + +#include +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +///////////////////////////////////////////////////////////////////////// +/// EARWMutexData +/// +/// This is used internally by class RWMutex. +/// Todo: Consider moving this declaration into a platform-specific +/// header file. +/// + #include + #include + + struct EATHREADLIB_API EARWMutexData + { + int mnReadWaiters; + int mnWriteWaiters; + int mnReaders; + EA::Thread::ThreadId mThreadIdWriter; + EA::Thread::Mutex mMutex; + EA::Thread::Condition mReadCondition; + EA::Thread::Condition mWriteCondition; + + EARWMutexData(); + + private: + // Prevent default generation of these functions by declaring but not defining them. + EARWMutexData(const EARWMutexData& rhs); // copy constructor + EARWMutexData& operator=(const EARWMutexData& rhs); // assignment operator + }; +///////////////////////////////////////////////////////////////////////// + + + +namespace EA +{ + namespace Thread + { + /// RWMutexParameters + /// Specifies rwlock settings. + struct EATHREADLIB_API RWMutexParameters + { + bool mbIntraProcess; /// True if the mutex is intra-process, else inter-process. + char mName[16]; /// Mutex name, applicable only to platforms that recognize named synchronization objects. + + RWMutexParameters(bool bIntraProcess = true, const char* pName = NULL); + }; + + + /// class RWMutex + /// Implements a multiple reader / single writer mutex. + /// This allows for significantly higher performance when data to be protected + /// is read much more frequently than written. In this case, a waiting writer + /// gets top priority and all new readers block after a waiter starts waiting. + class EATHREADLIB_API RWMutex + { + public: + enum Result + { + kResultError = -1, + kResultTimeout = -2 + }; + + enum LockType + { + kLockTypeNone = 0, + kLockTypeRead = 1, + kLockTypeWrite = 2 + }; + + /// RWMutex + /// For immediate default initialization, use no args. + /// For custom immediate initialization, supply a first argument. + /// For deferred initialization, use RWMutex(NULL, false) then later call Init. + /// For deferred initialization of an array of objects, create an empty + /// subclass whose default constructor chains back to RWMutex(NULL, false). + RWMutex(const RWMutexParameters* pRWMutexParameters = NULL, bool bDefaultParameters = true); + + /// ~RWMutex + /// Destroys an existing mutex. The mutex must not be locked by any thread, + /// otherwise the resulting behaviour is undefined. + ~RWMutex(); + + /// Init + /// Initializes the mutex if not done so in the constructor. + /// This should only be called in the case that this class was constructed + /// with RWMutex(NULL, false). + bool Init(const RWMutexParameters* pRWMutexParameters); + + /// Lock + /// Returns the new lock count for the given lock type. + /// + /// Note that the timeout is specified in absolute time and not relative time. + /// + /// Note also that due to the way thread scheduling works -- particularly in a + /// time-sliced threading environment -- that the timeout value is a hint and + /// the actual amount of time passed before the timeout occurs may be significantly + /// more or less than the specified timeout time. + /// + int Lock(LockType lockType, const ThreadTime& timeoutAbsolute = EA::Thread::kTimeoutNone); + + /// Unlock + /// Unlocks the mutex. The mutex must already be locked by the + /// calling thread. Otherwise the behaviour is not defined. + /// Return value is the lock count value immediately upon unlock + /// or is one of enum Result. + int Unlock(); + + /// GetLockCount + int GetLockCount(LockType lockType); + + /// GetPlatformData + /// Returns the platform-specific data handle for debugging uses or + /// other cases whereby special (and non-portable) uses are required. + void* GetPlatformData() + { return &mRWMutexData; } + + protected: + EARWMutexData mRWMutexData; + + private: + // Objects of this class are not copyable. + RWMutex(const RWMutex&){} + RWMutex& operator=(const RWMutex&){ return *this; } + }; + + + /// RWMutexFactory + /// + /// Implements a factory-based creation and destruction mechanism for class RWMutex. + /// A primary use of this would be to allow the RWMutex implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + class EATHREADLIB_API RWMutexFactory + { + public: + static RWMutex* CreateRWMutex(); // Internally implemented as: return new RWMutex; + static void DestroyRWMutex(RWMutex* pRWMutex); // Internally implemented as: delete pRWMutex; + + static size_t GetRWMutexSize(); // Internally implemented as: return sizeof(RWMutex); + static RWMutex* ConstructRWMutex(void* pMemory); // Internally implemented as: return new(pMemory) RWMutex; + static void DestructRWMutex(RWMutex* pRWMutex); // Internally implemented as: pRWMutex->~RWMutex(); + }; + + + } // namespace Thread + +} // namespace EA + + + + +namespace EA +{ + namespace Thread + { + /// class AutoRWMutex + /// An AutoRWMutex locks the RWMutex in its constructor and + /// unlocks the AutoRWMutex in its destructor (when it goes out of scope). + class AutoRWMutex + { + public: + AutoRWMutex(RWMutex& mutex, RWMutex::LockType lockType) + : mMutex(mutex) + { mMutex.Lock(lockType); } + + ~AutoRWMutex() + { mMutex.Unlock(); } + + protected: + RWMutex& mMutex; + + // Prevent copying by default, as copying is dangerous. + AutoRWMutex(const AutoRWMutex&); + const AutoRWMutex& operator=(const AutoRWMutex&); + }; + + } // namespace Thread + +} // namespace EA + + + + +#endif // EATHREAD_EATHREAD_RWMUTEX_H + + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_rwmutex_ip.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_rwmutex_ip.h new file mode 100644 index 00000000..5f2639a5 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_rwmutex_ip.h @@ -0,0 +1,415 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements an interprocess mutex with multiple reads but single writer. +// This allows for high performance systems whereby the consumers of mpData +// are more common than the producers of mpData. +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef EATHREAD_EATHREAD_RWMUTEX_IP_H +#define EATHREAD_EATHREAD_RWMUTEX_IP_H + + +#include +#include +#include +#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() +#endif + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +// We have to be careful about disabling this warning. Sometimes the warning is meaningful; sometimes it isn't. +// 4251: class (some template) needs to have dll-interface to be used by clients. +// 6054: String 'argument 2' might not be zero-terminated +EA_DISABLE_VC_WARNING(4251 6054) + +namespace EA +{ + namespace Thread + { + #if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) + + template + class Shared + { + public: + Shared(); + Shared(const char* pName); + ~Shared(); + + bool Init(const char* pName); + void Shutdown(); + bool IsNew() const { return mbCreated; } + T* operator->() { return static_cast(mpData); } + + protected: + uint32_t& GetRefCount(); + + Shared(const Shared&); + Shared& operator=(const Shared&); + + protected: + HANDLE mMapping; + void* mpData; + bool mbCreated; + char mName[32]; + T* mpT; // For debug purposes only. + }; + + + template + inline Shared::Shared() + : mMapping(NULL) + , mpData(NULL) + , mbCreated(false) + , mpT(NULL) + { + } + + + template + inline Shared::Shared(const char* pName) + : mMapping(NULL) + , mpData(NULL) + , mbCreated(false) + , mpT(NULL) + { + Init(pName); + } + + + template + inline Shared::~Shared() + { + Shutdown(); + } + + + template + inline bool Shared::Init(const char* pName) + { + bool bReturnValue = false; + + if(pName) + strncpy(mName, pName, sizeof(mName)); + else + mName[0] = 0; + mName[sizeof(mName) - 1] = 0; + + char mutexName[sizeof(mName) + 16]; + strcpy(mutexName, mName); + strcat(mutexName, ".SharedMutex"); + HANDLE hMutex = CreateMutexA(NULL, FALSE, mutexName); + EAT_ASSERT(hMutex != NULL); + if(hMutex != NULL) + { + WaitForSingleObject(hMutex, INFINITE); // This lock should always be fast, as it belongs to us and we only hold onto it very temporarily. + + const size_t kDataSize = sizeof(T) + 8; // Add bytes so that we can store a ref-count of our own after the mpData. + mMapping = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, kDataSize, mName); + + if(mMapping) + { + mbCreated = (GetLastError() != ERROR_ALREADY_EXISTS); + mpData = MapViewOfFile(mMapping, FILE_MAP_ALL_ACCESS, 0, 0, kDataSize); + + uint32_t& refCount = GetRefCount(); // The ref count is stored at the end of the mapped data. + + if(mbCreated) // If we were the first one to create this, then construct it. + { + new(mpData) T; + refCount = 1; + } + else + refCount++; + + mpT = static_cast(mpData); // For debug purposes only. + + bReturnValue = true; + } + + ReleaseMutex(hMutex); + CloseHandle(hMutex); + } + + return bReturnValue; + } + + + template + inline void Shared::Shutdown() + { + char mutexName[sizeof(mName) + 16]; + strcpy(mutexName, mName); + strcat(mutexName, ".SharedMutex"); + HANDLE hMutex = CreateMutexA(NULL, FALSE, mutexName); + EAT_ASSERT(hMutex != NULL); + if(hMutex != NULL) + { + WaitForSingleObject(hMutex, INFINITE); // This lock should always be fast, as it belongs to us and we only hold onto it very temporarily. + + if(mMapping) + { + if(mpData) + { + uint32_t& refCount = GetRefCount(); // The ref count is stored at the end of the mapped data. + + if(refCount == 1) // If we are the last to use it, + static_cast(mpData)->~T(); + else + refCount--; + + UnmapViewOfFile(mpData); + mpData = NULL; + } + + CloseHandle(mMapping); + mMapping = 0; + } + + ReleaseMutex(hMutex); + CloseHandle(hMutex); + } + } + + template + inline uint32_t& Shared::GetRefCount() + { + // There will be space after T because we allocated it in Init. + uint32_t* pData32 = (uint32_t*)(((uintptr_t)mpData + sizeof(T) + 3) & ~3); // Round up to next 32 bit boundary. + return *pData32; + } + + #else + + template + class Shared + { + public: + Shared() { } + Shared(const char*) { } + + bool Init(const char*) { return true; } + void Shutdown() { } + bool IsNew() const { return true; } + T* operator->() { return &mT; } + + T mT; + }; + + #endif // #if defined(EA_PLATFORM_WINDOWS) + + } // namespace Thread + +} // namespace EA + + + +namespace EA +{ + namespace Thread + { + ///////////////////////////////////////////////////////////////////////// + /// EARWMutexIPData + /// + #if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) + + struct EATHREADLIB_API SharedData + { + int mnReadWaiters; + int mnWriteWaiters; + int mnReaders; + DWORD mThreadIdWriter; // Need to use a thread id instead of a thread handle. + + SharedData() : mnReadWaiters(0), mnWriteWaiters(0), mnReaders(0), mThreadIdWriter(EA::Thread::kSysThreadIdInvalid) { } + }; + + struct EATHREADLIB_API EARWMutexIPData + { + Shared mSharedData; + HANDLE mMutex; + HANDLE mReadSemaphore; + HANDLE mWriteSemaphore; + + EARWMutexIPData(); + ~EARWMutexIPData(); + + bool Init(const char* pName); + void Shutdown(); + + private: + EARWMutexIPData(const EARWMutexIPData&); + EARWMutexIPData& operator=(const EARWMutexIPData&); + }; + + #else + + struct EATHREADLIB_API EARWMutexIPData + { + EARWMutexIPData(){} + + private: + EARWMutexIPData(const EARWMutexIPData&); + EARWMutexIPData& operator=(const EARWMutexIPData&); + }; + + #endif + + + /// RWMutexParameters + struct EATHREADLIB_API RWMutexIPParameters + { + bool mbIntraProcess; /// True if the mutex is intra-process, else inter-process. + char mName[16]; /// Mutex name, applicable only to platforms that recognize named synchronization objects. + + RWMutexIPParameters(bool bIntraProcess = true, const char* pName = NULL); + }; + + + /// class RWMutexIP + /// Implements an interprocess multiple reader / single writer mutex. + /// This allows for significantly higher performance when mpData to be protected + /// is read much more frequently than written. In this case, a waiting writer + /// gets top priority and all new readers block after a waiter starts waiting. + class EATHREADLIB_API RWMutexIP + { + public: + enum Result + { + kResultError = -1, + kResultTimeout = -2 + }; + + enum LockType + { + kLockTypeNone = 0, + kLockTypeRead = 1, + kLockTypeWrite = 2 + }; + + /// RWMutexIP + /// For immediate default initialization, use no args. + /// For custom immediate initialization, supply a first argument. + /// For deferred initialization, use RWMutexIP(NULL, false) then later call Init. + /// For deferred initialization of an array of objects, create an empty + /// subclass whose default constructor chains back to RWMutexIP(NULL, false). + RWMutexIP(const RWMutexIPParameters* pRWMutexIPParameters = NULL, bool bDefaultParameters = true); + + /// ~RWMutexIP + /// Destroys an existing mutex. The mutex must not be locked by any thread, + /// otherwise the resulting behaviour is undefined. + ~RWMutexIP(); + + /// Init + /// Initializes the mutex if not done so in the constructor. + /// This should only be called in the case that this class was constructed + /// with RWMutexIP(NULL, false). + bool Init(const RWMutexIPParameters* pRWMutexIPParameters); + + /// Lock + /// Returns the new lock count for the given lock type. + /// + /// Note that the timeout is specified in absolute time and not relative time. + /// + /// Note also that due to the way thread scheduling works -- particularly in a + /// time-sliced threading environment -- that the timeout value is a hint and + /// the actual amount of time passed before the timeout occurs may be significantly + /// more or less than the specified timeout time. + /// + int Lock(LockType lockType, const ThreadTime& timeoutAbsolute = EA::Thread::kTimeoutNone); + + /// Unlock + /// Unlocks the mutex. The mutex must already be locked by the + /// calling thread. Otherwise the behaviour is not defined. + /// Return value is the lock count value immediately upon unlock + /// or is one of enum Result. + int Unlock(); + + /// GetLockCount + int GetLockCount(LockType lockType); + + /// GetPlatformData + /// Returns the platform-specific mpData handle for debugging uses or + /// other cases whereby special (and non-portable) uses are required. + void* GetPlatformData() + { return &mRWMutexIPData; } + + protected: + EARWMutexIPData mRWMutexIPData; + + private: + // Objects of this class are not copyable. + RWMutexIP(const RWMutexIP&){} + RWMutexIP& operator=(const RWMutexIP&){ return *this; } + }; + + + /// RWMutexIPFactory + /// + /// Implements a factory-based creation and destruction mechanism for class RWMutexIP. + /// A primary use of this would be to allow the RWMutexIP implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + class EATHREADLIB_API RWMutexIPFactory + { + public: + static RWMutexIP* CreateRWMutexIP(); // Internally implemented as: return new RWMutexIP; + static void DestroyRWMutexIP(RWMutexIP* pRWMutex); // Internally implemented as: delete pRWMutex; + + static size_t GetRWMutexIPSize(); // Internally implemented as: return sizeof(RWMutexIP); + static RWMutexIP* ConstructRWMutexIP(void* pMemory); // Internally implemented as: return new(pMemory) RWMutexIP; + static void DestructRWMutexIP(RWMutexIP* pRWMutex); // Internally implemented as: pRWMutex->~RWMutexIP(); + }; + + + } // namespace Thread + +} // namespace EA + + + + +namespace EA +{ + namespace Thread + { + /// class AutoRWMutexIP + /// An AutoRWMutex locks the RWMutexIP in its constructor and + /// unlocks the AutoRWMutex in its destructor (when it goes out of scope). + class AutoRWMutexIP + { + public: + AutoRWMutexIP(RWMutexIP& mutex, RWMutexIP::LockType lockType) + : mMutex(mutex) + { mMutex.Lock(lockType); } + + ~AutoRWMutexIP() + { mMutex.Unlock(); } + + protected: + RWMutexIP& mMutex; + + // Prevent copying by default, as copying is dangerous. + AutoRWMutexIP(const AutoRWMutexIP&); + const AutoRWMutexIP& operator=(const AutoRWMutexIP&); + }; + + } // namespace Thread + +} // namespace EA + +EA_RESTORE_VC_WARNING() + +#endif // EATHREAD_EATHREAD_RWMUTEX_IP_H diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_rwsemalock.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_rwsemalock.h new file mode 100644 index 00000000..b53ae6a1 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_rwsemalock.h @@ -0,0 +1,253 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +//--------------------------------------------------------- +// For conditions of distribution and use, see +// https://github.com/preshing/cpp11-on-multicore/blob/master/LICENSE +//--------------------------------------------------------- + +#ifndef EATHREAD_EATHREAD_RWSEMALOCK_H +#define EATHREAD_EATHREAD_RWSEMALOCK_H + +#include "eathread_atomic.h" +#include "eathread_semaphore.h" + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +namespace EA +{ + namespace Thread + { + //--------------------------------------------------------- + // RWSemaLock + //--------------------------------------------------------- + class RWSemaLock + { + public: + RWSemaLock() : mStatus(0) {} + RWSemaLock(const RWSemaLock&) = delete; + RWSemaLock(RWSemaLock&&) = delete; + RWSemaLock& operator=(const RWSemaLock&) = delete; + RWSemaLock& operator=(RWSemaLock&&) = delete; + + void ReadLock() + { + Status oldStatus, newStatus; + do + { + oldStatus.data = mStatus.GetValue(); + newStatus.data = oldStatus.data; + + if (oldStatus.writers > 0) + { + newStatus.waitToRead++; + } + else + { + newStatus.readers++; + } + // CAS until successful. On failure, oldStatus will be updated with the latest value. + } + while (!mStatus.SetValueConditional(newStatus.data, oldStatus.data)); + + if (oldStatus.writers > 0) + { + mReadSema.Wait(); + } + } + + bool ReadTryLock() + { + Status oldStatus, newStatus; + do + { + oldStatus.data = mStatus.GetValue(); + newStatus.data = oldStatus.data; + + if (oldStatus.writers > 0) + { + return false; + } + else + { + newStatus.readers++; + } + // CAS until successful. On failure, oldStatus will be updated with the latest value. + } + while (!mStatus.SetValueConditional(newStatus.data, oldStatus.data)); + + return true; + } + + void ReadUnlock() + { + Status oldStatus; + oldStatus.data = mStatus.Add(-Status::kIncrementRead) + Status::kIncrementRead; + + EAT_ASSERT(oldStatus.readers > 0); + if (oldStatus.readers == 1 && oldStatus.writers > 0) + { + mWriteSema.Post(); + } + } + + void WriteLock() + { + Status oldStatus; + oldStatus.data = mStatus.Add(Status::kIncrementWrite) - Status::kIncrementWrite; + EAT_ASSERT(oldStatus.writers + 1 <= Status::kMaximum); + if (oldStatus.readers > 0 || oldStatus.writers > 0) + { + mWriteSema.Wait(); + } + } + + bool WriteTryLock() + { + Status oldStatus, newStatus; + do + { + oldStatus.data = mStatus.GetValue(); + newStatus.data = oldStatus.data; + + if (oldStatus.writers > 0 || oldStatus.readers > 0) + { + return false; + } + else + { + newStatus.writers++; + } + // CAS until successful. On failure, oldStatus will be updated with the latest value. + } + while (!mStatus.SetValueConditional(newStatus.data, oldStatus.data)); + + return true; + } + + void WriteUnlock() + { + uint32_t waitToRead = 0; + Status oldStatus, newStatus; + do + { + oldStatus.data = mStatus.GetValue(); + EAT_ASSERT(oldStatus.readers == 0); + newStatus.data = oldStatus.data; + newStatus.writers--; + waitToRead = oldStatus.waitToRead; + if (waitToRead > 0) + { + newStatus.waitToRead = 0; + newStatus.readers = waitToRead; + } + // CAS until successful. On failure, oldStatus will be updated with the latest value. + } + while (!mStatus.SetValueConditional(newStatus.data, oldStatus.data)); + + if (waitToRead > 0) + { + mReadSema.Post(waitToRead); + } + else if (oldStatus.writers > 1) + { + mWriteSema.Post(); + } + } + + // NOTE(rparolin): + // Since the RWSemaLock uses atomics to update its status flags before blocking on a semaphore, you cannot + // rely on the answer the IsReadLocked/IsWriteLocked gives you. It's at a best a guess and you can't rely + // on it for any kind of validation checks which limits its usefulness. In addition, the original + // implementation from Preshing does not include such functionality. + // + // bool IsReadLocked() {...} + // bool IsWriteLocked() {...} + + protected: + EA_DISABLE_VC_WARNING(4201) // warning C4201: nonstandard extension used: nameless struct/union + union Status + { + enum + { + kIncrementRead = 1, + kIncrementWaitToRead = 1 << 10, + kIncrementWrite = 1 << 20, + kMaximum = (1 << 10) - 1, + }; + + struct + { + int readers : 10; // 10-bits = 1024 + int waitToRead : 10; + int writers : 10; + int pad : 2; + }; + + int data; + }; + EA_RESTORE_VC_WARNING() + + AtomicInt32 mStatus; + Semaphore mReadSema; // semaphores are non-copyable + Semaphore mWriteSema; // semaphores are non-copyable + }; + + + //--------------------------------------------------------- + // ReadLockGuard + //--------------------------------------------------------- + class AutoSemaReadLock + { + private: + RWSemaLock& m_lock; + + public: + AutoSemaReadLock(const AutoSemaReadLock&) = delete; + AutoSemaReadLock(AutoSemaReadLock&&) = delete; + AutoSemaReadLock& operator=(const AutoSemaReadLock&) = delete; + AutoSemaReadLock& operator=(AutoSemaReadLock&&) = delete; + + AutoSemaReadLock(RWSemaLock& lock) : m_lock(lock) + { + m_lock.ReadLock(); + } + + ~AutoSemaReadLock() + { + m_lock.ReadUnlock(); + } + }; + + + //--------------------------------------------------------- + // WriteLockGuard + //--------------------------------------------------------- + class AutoSemaWriteLock + { + private: + RWSemaLock& m_lock; + + public: + AutoSemaWriteLock(const AutoSemaWriteLock&) = delete; + AutoSemaWriteLock(AutoSemaWriteLock&&) = delete; + AutoSemaWriteLock& operator=(const AutoSemaWriteLock&) = delete; + AutoSemaWriteLock& operator=(AutoSemaWriteLock&&) = delete; + + AutoSemaWriteLock(RWSemaLock& lock) : m_lock(lock) + { + m_lock.WriteLock(); + } + + ~AutoSemaWriteLock() + { + m_lock.WriteUnlock(); + } + }; + } +} + +#endif // EATHREAD_EATHREAD_RWSEMALOCK_H diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_rwspinlock.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_rwspinlock.h new file mode 100644 index 00000000..e1c0b05e --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_rwspinlock.h @@ -0,0 +1,393 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements an efficient proper multithread-safe spinlock which supports +// multiple readers but a single writer. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_RWSPINLOCK_H +#define EATHREAD_EATHREAD_RWSPINLOCK_H + +#include +#include +#include +#include +#include + +EA_DISABLE_VC_WARNING(4100) // (Compiler claims pRWSpinLock is unreferenced) + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +namespace EA +{ + namespace Thread + { + /// class RWSpinLock + /// + /// A RWSpinLock is like a SpinLock except you can have multiple + /// readers but a single exclusive writer. This is very beneficial for + /// situations whereby there are many consumers of some data but only + /// one producer of the data. Unlock many thread-level read/write lock + /// implementations, this spin lock, like many others, follows the most + /// lean approach and does not do arbitration or fairness. The result is + /// that if you have many readers who are constantly locking the read + /// lock, write lock attempts may not be able to succeed. So you need to + /// be careful in how you use this. + /// + /// We take a page from the Linux kernel here and implement read/write + /// locks via a mechanism that uses a 'bias' value and limits the number + /// of total readers to 2^24-1, or 16,777,214. This shouldn't be a problem. + /// When the spinlock is unlocked, the value is 0x01000000. + /// Readers decrement the lock by one each, so when the spinlock is + /// read-locked, the value is between 1 and 0x00ffffff. Writers decrement + /// the lock by 0x01000000, so when a spinlock is write-locked, the value + /// must be zero. It must be zero because there can only be one writer + /// and because there can be no readers when there is a writer. When a + /// reader attempts to get a read-lock, it decrements the lock count and + /// examines the new value. If the new value is < 0, then there was a + /// write-lock present and so the reader immediately increments the lock + /// count and tries again later. There are two results that come about due + /// to this: + /// 1) In the case of 32 bit integers, if by some wild chance of nature + /// there are 256 or more reader threads and there is a writer thread + /// with a write lock and every one of the reader threads executes + /// the same decrement and compare to < 0 at the same time, then the + /// 257th thread will mistakenly think that there isn't a write lock. + /// 2) The logic to test if a write-lock is taken is not to compare + /// against zero but to compare against (> -255 and <= 0). This is + /// because readers will occasionally be 'mistakenly' decrementing + /// the lock while trying to obtain read access. + /// + /// We thus have the following possible values: + /// 0 < value < 0x01000000 ----> read-locked + /// value == 0x01000000 ----> unlocked + /// 0x01000000 < value <= 0 ----> write-locked + /// + class RWSpinLock + { + public: + RWSpinLock(); + + // This function cannot be called while the current thread + // already has a write lock, else this function will hang. + // This function can be called if the current thread already + // has a read lock, though all read locks must be matched by unlocks. + void ReadLock(); + + // This function cannot be called while the current thread + // already has a write lock, else this function will hang. + // This function can be called if the current thread already + // has a read lock (in which case it will always succeed), + // though all read locks must be matched by unlocks. + bool ReadTryLock(); + + // Returns true if any thread currently has a read lock. + // The return value is subject to be out of date by the + // time it is read by the current thread, unless the current + // thread has a read lock. If IsReadLocked is true, then + // at that moment IsWriteLocked is necessarily false. + // If IsReadLocked is false, IsWriteLock may be either true or false. + bool IsReadLocked() const; + + // Unlocks for reading, as a match to ReadLock or a successful + // ReadTryLock. All read locks must be matched by ReadUnlock with + // the same thread that has the read lock. + void ReadUnlock(); + + // This function cannot be called while the current thread + // already has a read or write lock, else this function will hang. + void WriteLock(); + + // If this function is called while the current thread already + // has a read or write lock, it will always return false. + bool WriteTryLock(); + + // If this function returns true, then IsReadLocked must at that moment + // be false. + bool IsWriteLocked() const; + + // Matches WriteLock or a successful WriteTryLock. + void WriteUnlock(); + + // Returns the address of mValue. This value should be read for + // diagnostic purposes only and should not be written. + void* GetPlatformData(); + + public: + enum Value + { + kValueUnlocked = 0x01000000 + }; + + AtomicInt32 mValue; + }; + + + + /// RWSpinLockFactory + /// + /// Implements a factory-based creation and destruction mechanism for class RWSpinlock. + /// A primary use of this would be to allow the RWSpinlock implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + /// + class EATHREADLIB_API RWSpinLockFactory + { + public: + static RWSpinLock* CreateRWSpinLock(); + static void DestroyRWSpinLock(RWSpinLock* pRWSpinLock); + static size_t GetRWSpinLockSize(); + static RWSpinLock* ConstructRWSpinLock(void* pMemory); + static void DestructRWSpinLock(RWSpinLock* pRWSpinLock); + }; + + + + /// class AutoRWSpinLock + /// + /// Example usage: + /// void Function() { + /// AutoRWSpinLock autoLock(AutoRWSpinLock::kLockTypeRead); + /// // Do something + /// } + /// + class AutoRWSpinLock + { + public: + enum LockType + { + kLockTypeRead, + kLockTypeWrite + }; + + AutoRWSpinLock(RWSpinLock& spinLock, LockType lockType) ; + ~AutoRWSpinLock(); + + protected: + RWSpinLock& mSpinLock; + LockType mLockType; + + // Prevent copying by default, as copying is dangerous. + AutoRWSpinLock(const AutoRWSpinLock&); + const AutoRWSpinLock& operator=(const AutoRWSpinLock&); + }; + + } // namespace Thread + +} // namespace EA + + + + + + +/////////////////////////////////////////////////////////////////////////////// +// inlines +/////////////////////////////////////////////////////////////////////////////// + +namespace EA +{ + namespace Thread + { + + /////////////////////////////////////////////////////////////////////// + // RWSpinLock + /////////////////////////////////////////////////////////////////////// + + inline + RWSpinLock::RWSpinLock() + : mValue(kValueUnlocked) + { + } + + + inline + void RWSpinLock::ReadLock() + { + Top: // Due to modern processor branch prediction, the compiler will optimize better for true branches and so we do a manual goto loop here. + if((unsigned)mValue.Decrement() < kValueUnlocked) + return; + mValue.Increment(); + while(mValue.GetValueRaw() <= 0){ // It is better to do this polling loop as a first check than to + #ifdef EA_THREAD_COOPERATIVE // do an atomic decrement repeatedly, as the atomic lock is + ThreadSleep(); // potentially not a cheap thing due to potential bus locks on some platforms.. + #else + EAProcessorPause(); // We don't check for EA_TARGET_SMP here and instead sleep if not defined because you probably shouldn't be using a spinlock on a pre-emptive system unless it is a multi-processing system. + #endif + } + goto Top; + } + + + inline + bool RWSpinLock::ReadTryLock() + { + const unsigned nNewValue = (unsigned)mValue.Decrement(); + if(nNewValue < kValueUnlocked) // Given that nNewValue is unsigned, we don't need to test for < 0. + return true; + mValue.Increment(); + return false; + } + + + inline + bool RWSpinLock::IsReadLocked() const + { + const unsigned nValue = (unsigned)mValue.GetValue(); + return ((nValue - 1) < (kValueUnlocked - 1)); // Given that nNewValue is unsigned, this is faster than comparing ((n > 0) && (n < kValueUnlocked)), due to the presence of only one comparison instead of two. + } + + + inline + void RWSpinLock::ReadUnlock() + { + mValue.Increment(); + } + + + inline + void RWSpinLock::WriteLock() + { + Top: + if(mValue.Add(-kValueUnlocked) == 0) + return; + mValue.Add(kValueUnlocked); + while(mValue.GetValueRaw() != kValueUnlocked){ // It is better to do this polling loop as a first check than to + #ifdef EA_THREAD_COOPERATIVE // do an atomic decrement repeatedly, as the atomic lock is + ThreadSleep(); // potentially not a cheap thing due to potential bus locks on some platforms.. + #else + EAProcessorPause(); // We don't check for EA_TARGET_SMP here and instead sleep if not defined because you probably shouldn't be using a spinlock on a pre-emptive system unless it is a multi-processing system. + #endif + } + goto Top; + } + + + inline + bool RWSpinLock::WriteTryLock() + { + if(mValue.Add(-kValueUnlocked) == 0) + return true; + mValue.Add(kValueUnlocked); + return false; + } + + + inline + bool RWSpinLock::IsWriteLocked() const + { + return (mValue.GetValue() <= 0); // This fails to work if 127 threads at once are in the middle of a failed write lock attempt. + } + + + inline + void RWSpinLock::WriteUnlock() + { + mValue.Add(kValueUnlocked); + } + + + inline + void* RWSpinLock::GetPlatformData() + { + return &mValue; + } + + + + /////////////////////////////////////////////////////////////////////// + // RWSpinLockFactory + /////////////////////////////////////////////////////////////////////// + + inline + RWSpinLock* RWSpinLockFactory::CreateRWSpinLock() + { + Allocator* pAllocator = GetAllocator(); + + if(pAllocator) + return new(pAllocator->Alloc(sizeof(RWSpinLock))) RWSpinLock; + else + return new RWSpinLock; + } + + + inline + void RWSpinLockFactory::DestroyRWSpinLock(RWSpinLock* pRWSpinLock) + { + Allocator* pAllocator = GetAllocator(); + + if(pAllocator) + { + pRWSpinLock->~RWSpinLock(); + pAllocator->Free(pRWSpinLock); + } + else + delete pRWSpinLock; + } + + + inline + size_t RWSpinLockFactory::GetRWSpinLockSize() + { + return sizeof(RWSpinLock); + } + + + inline + RWSpinLock* RWSpinLockFactory::ConstructRWSpinLock(void* pMemory) + { + return new(pMemory) RWSpinLock; + } + + + inline + void RWSpinLockFactory::DestructRWSpinLock(RWSpinLock* pRWSpinLock) + { + pRWSpinLock->~RWSpinLock(); + } + + + + + /////////////////////////////////////////////////////////////////////// + // AutoRWSpinLock + /////////////////////////////////////////////////////////////////////// + + inline + AutoRWSpinLock::AutoRWSpinLock(RWSpinLock& spinLock, LockType lockType) + : mSpinLock(spinLock), mLockType(lockType) + { + if(mLockType == kLockTypeRead) + mSpinLock.ReadLock(); + else + mSpinLock.WriteLock(); + } + + + inline + AutoRWSpinLock::~AutoRWSpinLock() + { + if(mLockType == kLockTypeRead) + mSpinLock.ReadUnlock(); + else + mSpinLock.WriteUnlock(); + } + + + } // namespace Thread + +} // namespace EA + +EA_RESTORE_VC_WARNING() + +#endif // EATHREAD_EATHREAD_RWSPINLOCK_H diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_rwspinlockw.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_rwspinlockw.h new file mode 100644 index 00000000..00deb831 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_rwspinlockw.h @@ -0,0 +1,430 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements an efficient proper multithread-safe spinlock which supports +// multiple simultaneous readers but a single writer, where writers get +// priority over readers. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_RWSPINLOCKW_H +#define EATHREAD_EATHREAD_RWSPINLOCKW_H + +#include +#include +#include +#include +#include + +EA_DISABLE_VC_WARNING(4100) // (Compiler claims pRWSpinLockW is unreferenced) + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +namespace EA +{ + namespace Thread + { + /// class RWSpinLockW + /// + /// This class differs from RWSpinLock in that it gives writers priority. + /// In exchange for that feature, this version doesn't allow recursive + /// read locks and it becomes inefficient due to excessive spinning if + /// there are very many simultaneous readers. + /// + /// A RWSpinLockW is like a SpinLock except you can have multiple + /// readers but a single exclusive writer. This is very beneficial for + /// situations whereby there are many consumers of some data but only + /// one producer of the data. Unlock many thread-level read/write lock + /// implementations, this spin lock, like many others, follows the most + /// lean approach and does not do arbitration or fairness. The result is + /// that if you have many readers who are constantly locking the read + /// lock, write lock attempts may not be able to succeed. So you need to + /// be careful in how you use this. + /// + /// Note the usage of GetValueRaw in the source code for this class. + /// Use of GetValueRaw instead of GetValue is due to a tradeoff that + /// has been chosen. GetValueRaw does not come with memory read barrier + /// and thus the read value may be out of date. This is OK because it's + /// only used as a rule of thumb to help decide what synchronization + /// primitive to use next. This results in significantly faster execution + /// because only one memory synchronization primitive is typically + /// executed instead of two. The problem with GetValueRaw, however, + /// is that in cases where there is very high locking activity from + /// many threads simultaneously GetValueRaw usage could result in + /// a "bad guess" as to what to do next and can also result in a lot + /// of spinning, even infinite spinning in the most pathological case. + /// However, in practice on the platforms that target this situation + /// is unlikely to the point of being virtually impossible in practice. + /// And if it was possible then we recommend the user use a different + /// mechanism, such as the regular EAThread RWSpinLockW. + /// + class RWSpinLockW + { + public: + RWSpinLockW(); + + // This function cannot be called while the current thread + // already has a write lock, else this function will hang. + // Nor can this function can be called if the current thread + // already has a read lock, as it can result in a hang. + void ReadLock(); + + // This function cannot be called while the current thread + // already has a write lock, else this function will hang. + // Nor can this function can be called if the current thread + // already has a read lock, as it can result in a hang. + bool ReadTryLock(); + + // If this function returns true, then IsReadLocked must at that moment + // be false. + bool IsReadLocked() const; + + void ReadUnlock(); + + // This function cannot be called while the current thread + // already has a read or write lock, else this function will hang. + void WriteLock(); + + // If this function is called while the current thread already + // has a read or write lock, it will always return false. + bool WriteTryLock(); + + // If this function returns true, then IsReadLocked must at that moment + // be false. + bool IsWriteLocked() const; + + void WriteUnlock(); + + /// Returns the platform-specific data handle for debugging uses or + /// other cases whereby special (and non-portable) uses are required. + void* GetPlatformData(); + + protected: + enum Value + { + kWriteLockBit = 0x80000000, + kWriteWaitingInc = 0x00010000, + kReadLockInc = 0x00000001, + kWriteWaitingMask = 0x7FFF0000, + kReadLockMask = 0x0000FFFF, + kLockAllMask = kWriteLockBit | kReadLockMask, + kWriteAllMask = kWriteLockBit | kWriteWaitingMask, + }; + + AtomicInt32 mValue; + }; + + + + /// RWSpinLockWFactory + /// + /// Implements a factory-based creation and destruction mechanism for class RWSpinLockW. + /// A primary use of this would be to allow the RWSpinLockW implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + /// + class EATHREADLIB_API RWSpinLockWFactory + { + public: + static RWSpinLockW* CreateRWSpinLockW(); + static void DestroyRWSpinLockW(RWSpinLockW* pRWSpinLockW); + static size_t GetRWSpinLockWSize(); + static RWSpinLockW* ConstructRWSpinLockW(void* pMemory); + static void DestructRWSpinLockW(RWSpinLockW* pRWSpinLockW); + }; + + + + /// class AutoRWSpinLockW + /// + /// Example usage: + /// void Function() { + /// AutoRWSpinLockW autoLock(AutoRWSpinLockW::kLockTypeRead); + /// // Do something + /// } + /// + class AutoRWSpinLockW + { + public: + enum LockType + { + kLockTypeRead, + kLockTypeWrite + }; + + AutoRWSpinLockW(RWSpinLockW& SpinLockW, LockType lockType); + ~AutoRWSpinLockW(); + + protected: + RWSpinLockW& mSpinLockW; + LockType mLockType; + + // Prevent copying by default, as copying is dangerous. + AutoRWSpinLockW(const AutoRWSpinLockW&); + const AutoRWSpinLockW& operator=(const AutoRWSpinLockW&); + }; + + } // namespace Thread + +} // namespace EA + + + + + + +/////////////////////////////////////////////////////////////////////////////// +// inlines +/////////////////////////////////////////////////////////////////////////////// + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + + + /////////////////////////////////////////////////////////////////////// + // RWSpinLockW + /////////////////////////////////////////////////////////////////////// + + inline + RWSpinLockW::RWSpinLockW() + : mValue(0) + { + } + + + inline + void RWSpinLockW::ReadLock() + { + int32_t currVal = mValue.GetValueRaw(); // See not above about GetValueRaw usage. + + // If there is no writer nor waiting writers, attempt a read lock. + if( (currVal & kWriteAllMask) == 0 ) + { + if( mValue.SetValueConditional( currVal + kReadLockInc, currVal ) ) + return; + } + + // Spin until there is no writer and no waiting writers. + // By waiting till there no read or write lockers, we tend to avoid the case + // whereby readers starve out writers. The downside is that a lot of read + // activity can cause read parallelism to be reduced and read threads waiting + // for each other. + do + { + EA_THREAD_DO_SPIN(); + currVal = mValue.GetValue(); // or EAReadBarrier(); mValue.GetValueRaw(); + }while (currVal & kWriteAllMask); + + // At this point, we ignore waiting writers and take the lock if we + // can. Any waiting writers that have shown up right as we execute this + // code aren't given any priority over us, unlike above where they are. + for( ;; ) + { + // This code has a small problem in that a large number of simultaneous + // frequently locking/unlocking readers can cause this code to spin + // a lot (in theory, indefinitely). However, in practice our use cases + // and target hardware shouldn't cause this to happen. + if( (currVal & kWriteLockBit) == 0 ) + { + if( mValue.SetValueConditional( currVal + kReadLockInc, currVal ) ) + return; + } + + EA_THREAD_DO_SPIN(); + currVal = mValue.GetValue(); // or EAReadBarrier(); mValue.GetValueRaw(); + } + } + + + inline + bool RWSpinLockW::ReadTryLock() + { + int32_t currVal = mValue.GetValueRaw(); + + // If there is no writer nor waiting writers, attempt a read lock. + if( (currVal & kWriteAllMask) == 0 ) + { + if( mValue.SetValueConditional( currVal + kReadLockInc, currVal ) ) + return true; + } + + return false; + } + + + inline + bool RWSpinLockW::IsReadLocked() const + { + // This return value has only diagnostic meaning. It cannot be used for thread synchronization purposes. + return ((mValue.GetValueRaw() & kReadLockMask) != 0); + } + + + inline + void RWSpinLockW::ReadUnlock() + { + EAT_ASSERT(IsReadLocked()); // This can't tell us if the current thread was one of the lockers. But it's better than nothing as a debug test. + mValue.Add( -kReadLockInc ); + } + + + inline + void RWSpinLockW::WriteLock() + { + int32_t currVal = mValue.GetValueRaw(); + + // If there is no writer, waiting writers, nor readers, attempt a write lock. + if( (currVal & kLockAllMask) == 0 ) + { + if( mValue.SetValueConditional( currVal | kWriteLockBit, currVal ) ) + return; + } + + // Post a waiting write. This will make new readers spin until all existing + // readers have released their lock, so that we get an even chance. + mValue.Add( kWriteWaitingInc ); + + // Spin until we get the lock. + for( ;; ) + { + if( (currVal & kLockAllMask) == 0 ) + { + if( mValue.SetValueConditional( (currVal | kWriteLockBit) - kWriteWaitingInc, currVal ) ) + return; + } + + EA_THREAD_DO_SPIN(); + currVal = mValue.GetValue(); // or EAReadBarrier(); mValue.GetValueRaw(); + } + } + + + inline + bool RWSpinLockW::WriteTryLock() + { + int32_t currVal = mValue.GetValueRaw(); + + // If there is no writer, waiting writers, nor readers, attempt a write lock. + if( (currVal & kLockAllMask) == 0 ) + { + if( mValue.SetValueConditional( currVal | kWriteLockBit, currVal ) ) + return true; + } + + return false; + } + + + inline + bool RWSpinLockW::IsWriteLocked() const + { + // This return value has only diagnostic meaning. It cannot be used for thread synchronization purposes. + return ( (mValue.GetValueRaw() & kWriteLockBit) != 0 ); + } + + + inline + void RWSpinLockW::WriteUnlock() + { + EAT_ASSERT(IsWriteLocked()); + mValue.Add( -kWriteLockBit ); + } + + + inline + void* RWSpinLockW::GetPlatformData() + { + return &mValue; + } + + + + /////////////////////////////////////////////////////////////////////// + // RWSpinLockFactory + /////////////////////////////////////////////////////////////////////// + + inline + RWSpinLockW* RWSpinLockWFactory::CreateRWSpinLockW() + { + if(gpAllocator) + return new(gpAllocator->Alloc(sizeof(RWSpinLockW))) RWSpinLockW; + else + return new RWSpinLockW; + } + + inline + void RWSpinLockWFactory::DestroyRWSpinLockW(RWSpinLockW* pRWSpinLock) + { + if(gpAllocator) + { + pRWSpinLock->~RWSpinLockW(); + gpAllocator->Free(pRWSpinLock); + } + else + delete pRWSpinLock; + } + + inline + size_t RWSpinLockWFactory::GetRWSpinLockWSize() + { + return sizeof(RWSpinLockW); + } + + inline + RWSpinLockW* RWSpinLockWFactory::ConstructRWSpinLockW(void* pMemory) + { + return new(pMemory) RWSpinLockW; + } + + inline + void RWSpinLockWFactory::DestructRWSpinLockW(RWSpinLockW* pRWSpinLock) + { + pRWSpinLock->~RWSpinLockW(); + } + + + + /////////////////////////////////////////////////////////////////////// + // AutoRWSpinLock + /////////////////////////////////////////////////////////////////////// + + inline + AutoRWSpinLockW::AutoRWSpinLockW(RWSpinLockW& spinLock, LockType lockType) + : mSpinLockW(spinLock), mLockType(lockType) + { + if(mLockType == kLockTypeRead) + mSpinLockW.ReadLock(); + else + mSpinLockW.WriteLock(); + } + + + inline + AutoRWSpinLockW::~AutoRWSpinLockW() + { + if(mLockType == kLockTypeRead) + mSpinLockW.ReadUnlock(); + else + mSpinLockW.WriteUnlock(); + } + + + } // namespace Thread + +} // namespace EA + +EA_RESTORE_VC_WARNING() + +#endif // Header include guard diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_semaphore.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_semaphore.h new file mode 100644 index 00000000..02eeeb2e --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_semaphore.h @@ -0,0 +1,338 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements a semaphore thread synchronization class. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_SEMAPHORE_H +#define EATHREAD_EATHREAD_SEMAPHORE_H + + +#include +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_USE_SYNTHESIZED_SEMAPHORE +// +// Defined as 0 or 1. Defined as 1 if the OS provides no native semaphore support. +// +#ifndef EATHREAD_USE_SYNTHESIZED_SEMAPHORE + #define EATHREAD_USE_SYNTHESIZED_SEMAPHORE 0 +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_FAST_MS_SEMAPHORE_ENABLED +// +// Defined as 0 or 1. +// Enables the usage of a faster intra-process semaphore on Microsoft platforms. +// By faster we mean that it is typically 10x or more faster. +// Has the downside that it is not interchangeable with the SEMAPHORE built-in +// type and it's behaviour won't be strictly identical. +// Even if this option is enabled, you can still get the built-in behaviour +// of Microsoft semaphores by specifying the semaphore as inter-process. +// +#ifndef EATHREAD_FAST_MS_SEMAPHORE_ENABLED + #define EATHREAD_FAST_MS_SEMAPHORE_ENABLED 1 +#endif + + +///////////////////////////////////////////////////////////////////////// +/// EASemaphoreData +/// +/// This is used internally by class Semaphore. +/// Todo: Consider moving this declaration into a platform-specific +/// header file. +/// +#if !EA_THREADS_AVAILABLE + struct EASemaphoreData + { + volatile int mnCount; + int mnMaxCount; + + EASemaphoreData(); + }; + +#elif EATHREAD_USE_SYNTHESIZED_SEMAPHORE + #include + #include + #include + + struct EASemaphoreData + { + EA::Thread::Condition mCV; + EA::Thread::Mutex mMutex; + EA::Thread::AtomicInt32 mnCount; + int mnMaxCount; + bool mbValid; + + EASemaphoreData(); + }; + +#elif defined(EA_PLATFORM_APPLE) + + #include + #include + + struct EASemaphoreData + { + semaphore_t mSemaphore; + EA::Thread::AtomicInt32 mnCount; + int mnMaxCount; + bool mbIntraProcess; + + EASemaphoreData(); + }; + +#elif defined(EA_PLATFORM_SONY) + #include + #include + #include + struct EASemaphoreData + { + SceKernelSema mSemaphore; + + int mnMaxCount; + EA::Thread::AtomicInt32 mnCount; + + EASemaphoreData(); + }; + +#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + #include + + #if defined(EA_PLATFORM_WINDOWS) + #ifdef CreateSemaphore + #undef CreateSemaphore // Windows #defines CreateSemaphore to CreateSemaphoreA or CreateSemaphoreW. + #endif + #endif + + struct EASemaphoreData + { + sem_t mSemaphore; + EA::Thread::AtomicInt32 mnCount; + int mnMaxCount; + bool mbIntraProcess; + + EASemaphoreData(); + }; + +#elif defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE + #ifdef CreateSemaphore + #undef CreateSemaphore // Windows #defines CreateSemaphore to CreateSemaphoreA or CreateSemaphoreW. + #endif + + struct EATHREADLIB_API EASemaphoreData + { + void* mhSemaphore; // We use void* instead of HANDLE in order to avoid #including windows.h. HANDLE is typedef'd to (void*) on all Windows-like platforms. + int32_t mnCount; // Number of available posts. Under the fast semaphore pathway, a negative value means there are waiters. + int32_t mnCancelCount; // Used by fast semaphore logic. Is the deferred cancel count. + int32_t mnMaxCount; // + bool mbIntraProcess; // Used under Windows, which can have multiple processes. Always true for XBox. + + EASemaphoreData(); + void UpdateCancelCount(int32_t n); + }; + +#endif +///////////////////////////////////////////////////////////////////////// + + + + + +namespace EA +{ + namespace Thread + { + /// SemaphoreParameters + /// Specifies semaphore settings. + struct EATHREADLIB_API SemaphoreParameters + { + int mInitialCount; /// Initial available count + int mMaxCount; /// Max possible count. Defaults to INT_MAX. + bool mbIntraProcess; /// True if the semaphore is intra-process, else inter-process. + char mName[16]; /// Semaphore name, applicable only to platforms that recognize named synchronization objects. + + SemaphoreParameters(int initialCount = 0, bool bIntraProcess = true, const char* pName = NULL); + }; + + + /// class Semaphore + /// A semaphore is an object which has an associated count which is >= 0 and + /// a value > 0 means that a thread can 'grab' the semaphore and decrement its + /// value by one. A value of 0 means that threads must wait until another thread + /// 'un-grabs' the semaphore. Thus a semaphore is like a car rental agency which + /// has a limited number of cars for rent and if they are out of cars, you have + /// to wait until one of the renters returns their car. + class EATHREADLIB_API Semaphore + { + public: + enum Result{ + kResultError = -1, + kResultTimeout = -2 + }; + + /// Semaphore + /// For immediate default initialization, use no args. + /// For custom immediate initialization, supply a first argument. + /// For deferred initialization, use Semaphore(NULL, false) then later call Init. + /// For deferred initialization of an array of objects, create an empty + /// subclass whose default constructor chains back to Semaphore(NULL, false). + Semaphore(const SemaphoreParameters* pSemaphoreParameters = NULL, bool bDefaultParameters = true); + + /// Semaphore + /// This is a constructor which initializes the Semaphore to a specific count + /// and intializes the other Semaphore parameters to default values. See the + /// SemaphoreParameters struct for info on these default values. + Semaphore(int initialCount); + + /// ~Semaphore + /// Destroys an existing semaphore. The semaphore must not be locked + /// by any thread, otherwise the resulting behaviour is undefined. + ~Semaphore(); + + /// Init + /// Initializes the semaphore with given parameters. + bool Init(const SemaphoreParameters* pSemaphoreParameters); + + /// Wait + /// Locks the semaphore (reducing its count by one) or gives up trying to + /// lock it after a given timeout has expired. If the semaphore count is > 0 + /// then the count will be reduced by one. If the semaphore count is 0, the + /// call will block until another thread unlocks it or the timeout expires. + /// + /// Note that the timeout is specified in absolute time and not relative time. + /// + /// Note also that due to the way thread scheduling works -- particularly in a + /// time-sliced threading environment -- that the timeout value is a hint and + /// the actual amount of time passed before the timeout occurs may be significantly + /// more or less than the specified timeout time. + /// + /// Return value: + /// kResultError The semaphore could not be obtained due to error. + /// kResultTimeout The semaphore could not be obtained due to timeout. + /// >= 0 The new count for the semaphore. + /// + /// It's possible that two threads waiting on the same semaphore will return + /// with a result of zero. Thus you cannot rely on the semaphore's return value + /// to ascertain which was the last thread to return from the Wait. + int Wait(const ThreadTime& timeoutAbsolute = kTimeoutNone); + + /// Post + /// Increments the signalled value of the semaphore by the count. + /// Returns the available count after the operation has completed. + /// Returns kResultError upon error. A Wait is often eventually + /// followed by a corresponding Post. + /// For the case of count being greater than 1, not all platforms + /// act the same. If count results in exceeding the max count then + /// kResultError is returned. Some platforms return kResultError + /// before any of account is applied, while others return + /// kResultError after some of count has been applied. + int Post(int count = 1); + + /// GetCount + /// Returns current number of available locks associated with the semaphore. + /// This is useful for debugging and for quick polling checks of the + /// status of the semaphore. This value changes over time as multiple + /// threads wait and post to the semaphore. This value cannot be trusted + /// to exactly represent the count upon its return if multiple threads are + /// using this Semaphore at the time. + int GetCount() const; + + /// GetPlatformData + /// Returns the platform-specific data handle for debugging uses or + /// other cases whereby special (and non-portable) uses are required. + void* GetPlatformData() + { return &mSemaphoreData; } + + // Objects of this class are not copyable. + Semaphore(const Semaphore&) = delete; + Semaphore& operator=(const Semaphore&) = delete; + + protected: + EASemaphoreData mSemaphoreData; + }; + + + /// SemaphoreFactory + /// + /// Implements a factory-based creation and destruction mechanism for class Semaphore. + /// A primary use of this would be to allow the Semaphore implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + class EATHREADLIB_API SemaphoreFactory + { + public: + static Semaphore* CreateSemaphore(); // Internally implemented as: return new Semaphore; + static void DestroySemaphore(Semaphore* pSemaphore); // Internally implemented as: delete pSemaphore; + + static size_t GetSemaphoreSize(); // Internally implemented as: return sizeof(Semaphore); + static Semaphore* ConstructSemaphore(void* pMemory); // Internally implemented as: return new(pMemory) Semaphore; + static void DestructSemaphore(Semaphore* pSemaphore); // Internally implemented as: pSemaphore->~Semaphore(); + }; + + + } // namespace Thread + +} // namespace EA + + + + +namespace EA +{ + namespace Thread + { + /// class AutoSemaphore + /// An AutoSemaphore grabs the Semaphore in its constructor and posts + /// the Semaphore once in its destructor (when it goes out of scope). + class EATHREADLIB_API AutoSemaphore + { + public: + AutoSemaphore(Semaphore& semaphore) + : mSemaphore(semaphore) + { mSemaphore.Wait(); } + + ~AutoSemaphore() + { mSemaphore.Post(1); } + + protected: + Semaphore& mSemaphore; + + // Prevent copying by default, as copying is dangerous. + AutoSemaphore(const AutoSemaphore&); + const AutoSemaphore& operator=(const AutoSemaphore&); + }; + + } // namespace Thread + +} // namespace EA + +#endif // EATHREAD_EATHREAD_SEMAPHORE_H + + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_spinlock.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_spinlock.h new file mode 100644 index 00000000..9ec55fd4 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_spinlock.h @@ -0,0 +1,319 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Implements an efficient proper multithread-safe spinlock. +// +// A spin lock is the lightest form of mutex available. The Lock operation is +// simply a loop that waits to set a shared variable. SpinLocks are not +// recursive (i.e. they can only be locked once by a thread) and are +// intra-process only. You have to be careful using spin locks because if you +// have a high priority thread that calls Lock while a lower priority thread +// has the same lock, then on many systems the higher priority thread will +// use up all the CPU time waiting for the lock and the lower priority thread +// will not get the CPU time needed to free the lock. +// +// From Usenet: +// A spinlock is a machine-specific "optimized" form of mutex +// ("MUTual EXclusion" device). However, you should never use +// a spinlock unless you know that you have multiple threads +// and that you're running on a multiprocessor. Otherwise, at +// best you're wasting a lot of time. A spinlock is great for +// "highly parallel" algorithms like matrix decompositions, +// where the application (or runtime) "knows" (or at least goes +// to lengths to ensure) that the threads participating are all +// running at the same time. Unless you know that, (and, if your +// code doesn't create threads, you CAN'T know that), don't even +// think of using a spinlock." +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_SPINLOCK_H +#define EATHREAD_EATHREAD_SPINLOCK_H + + +#include +#include +#include // include new for placement new operator + +#if defined(EA_PROCESSOR_X86) + // The reference x86 code works fine, as there is little that assembly + // code can do to improve it by much, assuming that the code is compiled + // in an optimized way. With VC7 on the PC platform, compiling with + // optimization set to 'minimize size' and most other optimizations + // enabled yielded code that was similar to Intel reference asm code. + // However, when the compiler was set to minimize size and enable inlining, + // it created an implementation of the Lock function that was less optimal. + // #include +#elif defined(EA_PROCESSOR_IA64) + // The reference code below is probably fine. + // #include +#endif + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +// The above header files would define EA_THREAD_SPINLOCK_IMPLEMENTED. +#if !defined(EA_THREAD_SPINLOCK_IMPLEMENTED) + + // We provide an implementation that works for all systems but is less optimal. + #include + #include + + namespace EA + { + namespace Thread + { + /// class SpinLock + /// + /// Spinlocks are high-performance locks designed for special circumstances. + /// As such, they are not 'recursive' -- you cannot lock a spinlock twice. + /// Spinlocks have no explicit awareness of threading, but they are explicitly + /// thread-safe. + /// + /// You do not want to use spin locks as a *general* replacement for mutexes or + /// critical sections, even if you know your mutex use won't be recursive. + /// The reason for this is due to thread scheduling and thread priority issues. + /// A spinlock is not a kernel- or threading-kernel-level object and thus while + /// this gives it a certain amount of speed, it also means that if you have a + /// low priority thread thread with a spinlock locked and a high priority thread + /// waiting for the spinlock, the program will hang, possibly indefinitely, + /// because the thread scheduler is giving all its time to the high priority + /// thread which happens to be stuck. + /// + /// On the other hand, when judiciously used, a spin lock can yield significantly + /// higher performance than general mutexes, especially on platforms where mutex + /// locking is particularly expensive or on multiprocessing systems. + /// + class SpinLock + { + protected: // Declared at the top because otherwise some compilers fail to compile inline functions below. + AtomicInt32 mAI; /// A value of 0 means unlocked, while 1 means locked. + + public: + SpinLock(); + + void Lock(); + bool TryLock(); + bool IsLocked(); + void Unlock(); + + void* GetPlatformData(); + }; + + + /// SpinLockFactory + /// + /// Implements a factory-based creation and destruction mechanism for class Spinlock. + /// A primary use of this would be to allow the Spinlock implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + class EATHREADLIB_API SpinLockFactory + { + public: + static SpinLock* CreateSpinLock(); + static void DestroySpinLock(SpinLock* pSpinLock); + + static size_t GetSpinLockSize(); + static SpinLock* ConstructSpinLock(void* pMemory); + + static void DestructSpinLock(SpinLock* pSpinLock); + }; + + } // namespace Thread + + } // namespace EA + + +#endif // EA_THREAD_SPINLOCK_IMPLEMENTED + + + +namespace EA +{ + namespace Thread + { + /// class AutoSpinLock + /// An AutoSpinLock locks the SpinLock in its constructor and + /// unlocks the SpinLock in its destructor (when it goes out of scope). + class AutoSpinLock + { + public: + AutoSpinLock(SpinLock& spinLock); + ~AutoSpinLock(); + + protected: + SpinLock& mSpinLock; + + protected: + // Prevent copying by default, as copying is dangerous. + AutoSpinLock(const AutoSpinLock&); + const AutoSpinLock& operator=(const AutoSpinLock&); + }; + + } // namespace Thread + +} // namespace EA + + + + + + +/////////////////////////////////////////////////////////////////////////////// +// inlines +/////////////////////////////////////////////////////////////////////////////// + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + + + /////////////////////////////////////////////////////////////////////// + // SpinLock + /////////////////////////////////////////////////////////////////////// + + inline + SpinLock::SpinLock() + : mAI(0) + { + } + + inline + void SpinLock::Lock() + { + Top: // Due to modern processor branch prediction, the compiler will optimize better for true branches and so we do a manual goto loop here. + if(mAI.SetValueConditional(1, 0)) + return; + + // The loop below is present because the SetValueConditional + // call above is likely to be significantly more expensive and + // thus we benefit by polling before attempting the real thing. + // This is a common practice and is recommended by Intel, etc. + while (mAI.GetValue() != 0) + { + #ifdef EA_THREAD_COOPERATIVE + ThreadSleep(); + #else + EAProcessorPause(); + #endif + } + goto Top; + } + + inline + bool SpinLock::TryLock() + { + return mAI.SetValueConditional(1, 0); + } + + inline + bool SpinLock::IsLocked() + { + return mAI.GetValueRaw() != 0; + } + + inline + void SpinLock::Unlock() + { + EAT_ASSERT(IsLocked()); + mAI.SetValue(0); + } + + inline + void* SpinLock::GetPlatformData() + { + return &mAI; + } + + + /////////////////////////////////////////////////////////////////////// + // SpinLockFactory + /////////////////////////////////////////////////////////////////////// + + inline + SpinLock* SpinLockFactory::CreateSpinLock() + { + if(gpAllocator) + return new(gpAllocator->Alloc(sizeof(SpinLock))) SpinLock; + else + return new SpinLock; + } + + inline + void SpinLockFactory::DestroySpinLock(SpinLock* pSpinLock) + { + if(gpAllocator) + { + pSpinLock->~SpinLock(); + gpAllocator->Free(pSpinLock); + } + else + delete pSpinLock; + } + + inline + size_t SpinLockFactory::GetSpinLockSize() + { + return sizeof(SpinLock); + } + + inline + SpinLock* SpinLockFactory::ConstructSpinLock(void* pMemory) + { + return new(pMemory) SpinLock; + } + + EA_DISABLE_VC_WARNING(4100) // Compiler mistakenly claims pSpinLock is unreferenced + inline + void SpinLockFactory::DestructSpinLock(SpinLock* pSpinLock) + { + pSpinLock->~SpinLock(); + } + EA_RESTORE_VC_WARNING() + + + /////////////////////////////////////////////////////////////////////// + // AutoSpinLock + /////////////////////////////////////////////////////////////////////// + + inline + AutoSpinLock::AutoSpinLock(SpinLock& spinLock) + : mSpinLock(spinLock) + { + mSpinLock.Lock(); + } + + inline + AutoSpinLock::~AutoSpinLock() + { + mSpinLock.Unlock(); + } + + } // namespace Thread + +} // namespace EA + +#endif // EATHREAD_EATHREAD_SPINLOCK_H + + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_storage.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_storage.h new file mode 100644 index 00000000..914604b3 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_storage.h @@ -0,0 +1,334 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Defines thread-local storage and related concepts in a platform-independent +// and thread-safe manner. +// +// As of this writing (10/2003), documentation concerning thread-local +// storage implementations under GCC, pthreads, and MSVC/Windows can be found at: +// http://gcc.gnu.org/onlinedocs/gcc-3.3.2/gcc/Thread-Local.html#Thread-Local +// http://java.icmc.sc.usp.br/library/books/ibm_pthreads/users-33.htm#324811 +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/_core_Thread_Local_Storage_.28.TLS.29.asp +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_STORAGE_H +#define EATHREAD_EATHREAD_STORAGE_H + + +#include + +EA_DISABLE_VC_WARNING(4574) +#include +EA_RESTORE_VC_WARNING() + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +namespace EA +{ + namespace Thread + { + ///////////////////////////////////////////////////////////////////////// + /// EA_THREAD_LOCAL + /// + /// Documentation (partially culled from online information): + /// Thread Local Storage (a.k.a. TLS and Thread Specific Storage) is a + /// mechanism by which each thread in a multithreaded process allocates + /// storage for thread-specific data. In standard multithreaded programs, + /// data is shared among all threads of a given process, whereas thread + /// local storage is the mechanism for allocating per-thread data. + /// + /// The EA_THREAD_LOCAL specifier may be used alone, with the extern or + /// static specifiers, but with no other storage class specifier. + /// When used with extern or static, EA_THREAD_LOCAL must appear + /// immediately after the other storage class specifier. + /// + /// The EA_THREAD_LOCAL specifier may be applied to any global, file-scoped + /// static, function-scoped static, or static data member of a class. + /// It may not be applied to block-scoped automatic or non-static data member. + /// + /// When the address-of operator is applied to a thread-local variable, + /// it is evaluated at run-time and returns the address of the current + /// thread's instance of that variable. An address so obtained may be used + /// by any thread. When a thread terminates, any pointers to thread-local + /// variables in that thread become invalid. + /// + /// No static initialization may refer to the address of a thread-local variable. + /// In C++, if an initializer is present for a thread-local variable, + /// it must be a constant-expression, as defined in 5.19.2 of the ANSI/ISO C++ standard. + /// + /// Windows has special considerations for using thread local storage in a DLL. + /// + /// Example usage: + /// #if defined(EA_THREAD_LOCAL) + /// EA_THREAD_LOCAL int n = 0; // OK + /// extern EA_THREAD_LOCAL struct Data s; // OK + /// static EA_THREAD_LOCAL char* p; // OK + /// EA_THREAD_LOCAL int i = sizeof(i); // OK. + /// EA_THREAD_LOCAL std::string s("hello"); // Bad -- Can't be used for initialized objects. + /// EA_THREAD_LOCAL int Function(); // Bad -- Can't be used as return value. + /// void Function(){ EA_THREAD_LOCAL int i = 0; } // Bad -- Can't be used in function. + /// void Function(EA_THREAD_LOCAL int i){ } // Bad -- can't be used as argument. + /// extern int i; EA_THREAD_LOCAL int i; // Bad -- Declarations differ. + /// int EA_THREAD_LOCAL i; // Bad -- Can't be used as a type modifier. + /// EA_THREAD_LOCAL int i = i; // Bad -- Can't reference self before initialization. + /// #else + /// Need to use EA::Thread::ThreadLocalStorage. + /// #endif + + #if !EA_THREADS_AVAILABLE + #define EA_THREAD_LOCAL + + #elif !defined(EA_COMPILER_NO_THREAD_LOCAL) || EA_USE_CPP11_CONCURRENCY + #define EA_THREAD_LOCAL thread_local + + #elif defined(EA_COMPILER_MSVC) + // This appears to be supported by VC++, Borland C++. + // And it is supported by all compilers for the Windows platform. + #define EA_THREAD_LOCAL __declspec(thread) + + #else + // Else don't define it as anything. This will result in a compilation + // error reporting the problem. We cannot simply #define away the + // EA_THEAD_LOCAL term, as doing so would defeat the purpose of the + // specifier. Dynamic thread local storage is a more flexible and + // portable solution to the problem. + // #define EA_THREAD_LOCAL + #endif + ///////////////////////////////////////////////////////////////////////// + + } // namespace Thread + +} // namespace EA + + + + + +///////////////////////////////////////////////////////////////////////// +/// EAThreadLocalStorageData +/// +/// This is used internally by class ThreadLocalStorage. +/// Todo: Consider moving this declaration into a platform-specific +/// header file. +/// +#if defined(EA_PLATFORM_SONY) + #include + + struct EAThreadLocalStorageData{ + ScePthreadKey mKey; // This is usually a pointer. + int mResult; // Result of call to scePthreadKeyCreate, so we can know if mKey is valid. + }; +#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && !defined(EA_PLATFORM_NX) + // In this case we will be using pthread_key_create, pthread_key_delete, pthread_getspecific, pthread_setspecific. + #include + + struct EAThreadLocalStorageData{ + pthread_key_t mKey; // This is usually a pointer. + int mResult; // Result of call to pthread_key_create, so we can know if mKey is valid. + }; + +#elif defined(EA_PLATFORM_MICROSOFT) && !defined(EA_PLATFORM_WINDOWS_PHONE) && !(defined(EA_PLATFORM_WINDOWS) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)) + // In this case we will be using TlsAlloc, TlsFree, TlsGetValue, TlsSetValue. + typedef uint32_t EAThreadLocalStorageData; + +#elif (!EA_THREADS_AVAILABLE || defined(EA_PLATFORM_CONSOLE)) && !defined(EA_PLATFORM_NX) + #include + + struct EAThreadLocalStorageData + { + struct ThreadToDataPair + { + EA::Thread::ThreadUniqueId mThreadID; + const void* mpData; + }; + #ifndef EA_TLS_MAX_COUNT + #define EA_TLS_MAX_COUNT 16 // This is the max number of threads that might want to use the given thread-local-storage item. + #endif + ThreadToDataPair* GetTLSEntry(bool bCreateIfNotFound); + ThreadToDataPair mDataArray[EA_TLS_MAX_COUNT]; + int mDataArrayCount; + }; + +#else // STL version which uses less memory but uses heap memory. + + // If you use this version, then you want to make sure your STL is using new/delete + // by default and then make sure you are globally mapping new/delete to your + // custom allocation system. STLPort, for example, tends to want to use its + // own internal allocator which is non-optimal for serious uses. + + EA_DISABLE_VC_WARNING(4574 4350) + #include // Note that this dependency on STL map is only present if you use this pathway, which is disabled by default. + EA_RESTORE_VC_WARNING() + + #include + #include + + struct EAThreadLocalStorageData + { + EAThreadLocalStorageData() : mThreadToDataMap(NULL) {} + ~EAThreadLocalStorageData() { delete mThreadToDataMap; mThreadToDataMap = NULL; } + void** GetTLSEntry(bool bCreateIfNotFound); + // We allocate this map only when needed + // This prevents too early allocations before our allocator initialization + std::map *mThreadToDataMap; + EA::Thread::Futex mFutex; + private: + // Disable copy and assignment + EAThreadLocalStorageData(const EAThreadLocalStorageData&); + EAThreadLocalStorageData operator=(const EAThreadLocalStorageData&); + }; +#endif +///////////////////////////////////////////////////////////////////////// + + + +namespace EA +{ + namespace Thread + { + ///////////////////////////////////////////////////////////////////////// + /// class ThreadLocalStorage + /// + /// This is a class that lets you store a pointer to data uniquely for + /// each thread. It thus allows access to a pointer as if it were local + /// but each thread gets its own copy. + /// + /// The implementation behind this class maps to the PThreads API under + /// Unix-like systems, maps to the Windows TLS SPI under Windows, and + /// maps to a custom implementation otherwise. The PThreads API has a + /// mechanism whereby you can set a callback to execute when a thread + /// exits; the callback will call the callback once for each pointer + /// that was stored in all thread local storage objects. Due to the + /// general weaknesses of the PThread mechanism and due to our interest + /// in being as lean as possible, we don't support automatic callbacks + /// such as with PThreads. The same effect can be achieved manually + /// when needed. + /// + /// Example usage: + /// ThreadLocalStorage tls; + /// void* pValue; + /// bool bResult; + /// + /// pValue = tls.GetValue(); // Return value will be NULL. + /// bResult = tls.SetValue(NULL); // This is fine and bResult should be true. + /// pValue = tls.GetValue(); // Return value will be NULL. + /// bResult = tls.SetValue(pSomeObject); // Set thread-specific value to pSomeObject. + /// bResult = tls.SetValue(pOtherObject); // Set thread-specific value to pOtherObject. + /// pValue = tls.GetValue(); // Return value will be pOtherObject. + /// bResult = tls.SetValue(NULL); // This is fine and bResult should be true. + /// + class EATHREADLIB_API ThreadLocalStorage + { + public: + ThreadLocalStorage(); + ~ThreadLocalStorage(); + + /// GetValue + /// Returns the pointer previous stored via GetValue or returns NULL if there + /// is not stored value or if the user stored NULL. + void* GetValue(); + + /// SetValue + /// Stores a pointer, returns true if the storage was possible. In general, + /// the only reason that false would ever be returned is if there wasn't + /// sufficient memory remaining for the operation. When a thread exits, + /// it should call SetValue(NULL), as there is currently no mechanism to + /// automatically detect thread exits on some platforms and thus there is + /// no way to automatically clear these values. + bool SetValue(const void* pData); + + /// GetPlatformData + /// Returns the platform-specific thread local storage handle for debugging + /// uses or other cases whereby special (and non-portable) uses are required. + void* GetPlatformData() + { return &mTLSData; } + + protected: + EAThreadLocalStorageData mTLSData; + + private: + // Disable copy and assignment + ThreadLocalStorage(const ThreadLocalStorage&); + ThreadLocalStorage operator=(const ThreadLocalStorage&); + }; + ///////////////////////////////////////////////////////////////////////// + + + + /// ThreadLocalStorageFactory + /// + /// Implements a factory-based creation and destruction mechanism for class ThreadLocalStorage. + /// A primary use of this would be to allow the ThreadLocalStorage implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + class EATHREADLIB_API ThreadLocalStorageFactory + { + public: + static ThreadLocalStorage* CreateThreadLocalStorage(); // Internally implemented as: return new ThreadLocalStorage; + static void DestroyThreadLocalStorage(ThreadLocalStorage* pTLS); // Internally implemented as: delete pTLS; + + static size_t GetThreadLocalStorageSize(); // Internally implemented as: return sizeof(ThreadLocalStorage); + static ThreadLocalStorage* ConstructThreadLocalStorage(void* pMemory); // Internally implemented as: return new(pMemory) ThreadLocalStorage; + static void DestructThreadLocalStorage(ThreadLocalStorage* pTLS); // Internally implemented as: pTLS->~ThreadLocalStorage(); + }; + + + + // ThreadLocalPointer + // This is a class that adds pointer type awareness to ThreadLocalStorage. + // The interface is designed to look like the standard auto_ptr class. + // + // The following is disabled until we provide a way to enumerate and delete + // the pointers when the object goes out of scope or delete the thread-specific + // pointer when the thread ends. Both are require before this class fully acts + // as one would expect. + // + //template + //class ThreadLocalPointer + //{ + //public: + // T* get() const { return static_cast(mTLS.GetValue()); } + // T* operator->() const { return static_cast(mTLS.GetValue()); } + // T& operator*() const { return *static_cast(mTLS.GetValue()); } + // void reset(T* pNew = 0){ + // T* const pTemp = get(); + // if(pNew != pTemp){ + // delete pTemp; + // mTLS.SetValue(pTemp); + // } + // } + // + //protected: + // ThreadLocalStorage mTLS; + // + //private: + // ThreadLocalPointer(const ThreadLocalPointer&); + // const ThreadLocalPointer& operator=(const ThreadLocalPointer&); + //}; + ///////////////////////////////////////////////////////////////////////// + + + } // namespace Thread + +} // namespace EA + + +#endif // #ifdef EATHREAD_EATHREAD_STORAGE_H + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_sync.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_sync.h new file mode 100644 index 00000000..84b0ac1f --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_sync.h @@ -0,0 +1,270 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Functionality related to memory and code generation synchronization. +// +// Overview (partially taken from Usenet) +// On all modern hardware, a store instruction does not necessarily result +// in an immediate write to main memory, or even to the (processor specific) +// cache. A store instruction simply places a write request in a request +// queue, and continues. (Future reads in the same processor will check if +// there is a write to the same address in this queue, and fetch it, rather +// than reading from memory. Reads from another processor, however, can't +// see this queue.) Generally, the ordering of requests in this queue is +// not guaranteed, although some hardware offers stricter guarantees. +// Thus, you must do something to ensure that the writes actually occur. +// This is called a write barrier, and generally takes the form of a special +// instruction. +// +// And of course, just because you have written the data to main memory +// doesn't mean that some other processor, executing a different thread, +// doesn't have a stale copy in its cache, and use that for a read. Before +// reading the variables, you need to ensure that the processor has the +// most recent copy in its cache. This is called a read barrier, and +// again, takes the form of a special hardware instruction. A number of +// architectures (e.g. Intel x86-32) still guarantee read consistency -- +// all of the processors "listen" on the main memory bus, and if there is +// a write, automatically purge the corresponding data from their cache. +// But not all. +// +// Note that if you are writing data within a operating system-level +// locked mutex, the lock and unlock of the mutex will synchronize memory +// for you, thus eliminating the need for you to execute read and/or write +// barriers. However, mutex locking and its associated thread stalling is +// a potentially inefficient operation when in some cases you could simply +// write the memory from one thread and read it from another without +// using mutexes around the data access. Some systems let you write memory +// from one thread and read it from another (without you using mutexes) +// without using memory barriers, but others (notably SMP) will not let you +// get away with this, even if you put a mutex around the write. In these +// cases you need read/write barriers. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_EATHREAD_SYNC_H +#define EATHREAD_EATHREAD_SYNC_H + + +// Note +// These functions are not placed in a C++ namespace but instead are standalone. +// The reason for this is that these are usually implemented as #defines of +// C or asm code or implemented as compiler intrinsics. We however document +// these functions here as if they are simply functions. The actual platform- +// specific declarations are in the appropriate platform-specific directory. + +#include +#include + +#if !EA_THREADS_AVAILABLE + // Do nothing. +#elif defined(EA_PROCESSOR_X86) + #include +#elif defined(EA_PROCESSOR_X86_64) + #include +#elif defined(EA_PROCESSOR_IA64) + #include +#elif defined(EA_PLATFORM_APPLE) + #include +#elif defined(EA_PROCESSOR_ARM) + #include +#endif + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +// EA_THREAD_DO_SPIN +// +// Provides a macro which maps to whatever processor idle functionality the given platform requires. +// +// Example usage: +// EA_THREAD_DO_SPIN(); +// +#ifndef EA_THREAD_DO_SPIN + #ifdef EA_THREAD_COOPERATIVE + #define EA_THREAD_DO_SPIN() ThreadSleep() + #else + #define EA_THREAD_DO_SPIN() EAProcessorPause() // We don't check for EA_TARGET_SMP here and instead sleep if not defined because you probably shouldn't be using a spinlock on a pre-emptive system unless it is a multi-processing system. + #endif +#endif + + + +// The above header files would define EA_THREAD_SYNC_IMPLEMENTED. +#if !defined(EA_THREAD_SYNC_IMPLEMENTED) + // Perhaps it should be considered too serious of an error to allow compilation + // to continue. If so, then we should enable the #error below. + // #error EA_THREAD_SYNC_IMPLEMENTED not defined. + + + /// EAProcessorPause + /// + /// \Declaration + /// void EAProcessorPause(); + /// + /// \Description + /// This statement causes the processor to efficiently (as much as possible) + /// execute a no-op (a.k.a nop or noop). These are particularly useful in + /// spin-wait loops. Without a proper pause, some processors suffer severe + /// performance penalties while executing spin-wait loops such as those in + /// simple spin locks. Many processors have specialized pause instructions + /// (e.g. Intel x86 P4 'pause' or 'asm rep nop') that can be taken advantage + /// of here. + /// + /// \Example + /// while (!flag) { + /// EAProcessorPause(); + /// } + #define EAProcessorPause() + + + + /// EAReadBarrier + /// + /// \Declaration + /// void EAReadBarrier(); + /// + /// \Description + /// A read barrier ensures that neither software nor hardware perform a memory + /// read prior to the read barrier and that recent writes to main memory are + /// immediately seen (and not using stale cached data) by the processor executing + /// the read barrier. This generally does not mean a (performance draining) + /// invalidation of the entire cache but does possibly mean invalidating any cache + /// that refers to main memory which has changed. Thus, there is a performance + /// cost but considering the use of this operation, this is the most efficient + /// way of achieving the effect. + /// + /// \Example + /// The following function will operate fine on some multiprocessing systems but + /// hang (possibly indefinitely) on other multiprocessing systems unless the + /// EAReadBarrier call is present. + /// + /// void ThreadFunction() { + /// extern volatile int gFlag; + /// while(gFlag == 0){ // Wait for separate thread to write to gSomeFlag. + /// EAProcessorPause(); + /// EAReadBarrier(); + /// // Do memory sharing operations with other threads here. + /// } + /// } + #define EAReadBarrier() + + + + + + /// EAWriteBarrier + /// + /// \Declaration + /// void EAWriteBarrier(); + /// + /// \Description + /// A write barrier ensures that neither software nor hardware delay a memory + /// write operation past the barrier. If you want your memory write committed + /// to main memory immediately, this statement will have that effect. As such, + /// this is something like a flush of the current processor's write cache. + /// Note that flushing memory from a processor's cache to main memory like this + /// doesn't cause a second processor to immediately see the changed values in + /// main memory, as the second processor has a read cache between it and main + /// memory. Thus, a second processor would need to execute a read barrier if it + /// wants to see the updates immediately. + #define EAWriteBarrier() + + + + + + /// EAReadWriteBarrier + /// + /// Declaration + /// void EAReadWriteBarrier(); + /// + /// Description + /// A read/write barrier has the same effect as both a read barrier and a write + /// barrier at once. A read barrier ensures that neither software nor hardware + /// perform a memory read prior to the read barrier, while a write barrier + /// ensures that neither software nor hardware delay a memory write operation + /// past the barrier. A ReadWriteBarrier specifically acts like a WriteBarrier + /// followed by a ReadBarrier, despite the name ReadWriteBarrier being the + /// other way around. + /// + /// EAReadWriteBarrier synchronizes both reads and writes to system memory + /// between processors and their caches on multiprocessor systems, particulary + /// SMP systems. This can be useful to ensure the state of global variables at + /// a particular point in your code for multithreaded applications. Higher level + /// thread synchronization level primitives such as mutexes achieve the same + /// effect (while providing the additional functionality of synchronizing code + /// execution) but at a significantly higher cost. + /// + /// A two-processor SMP system has two processors, each with its own instruction + /// and data caches. If the first processor writes to a memory location and the + /// second processor needs to read from that location, the first procesor's + /// write may still be in its cache and not committed to main memory and the + /// second processor may thus would not see the newly written value. The value + /// will eventually get written from the first cache to main memory, but if you + /// need to ensure that it is written at a particular time, you would use a + /// ReadWrite barrier. + /// + /// This function is similar to the Linux kernel rwb() function and to the + /// Windows kernel KeMemoryBarrier function. + #define EAReadWriteBarrier() + + + + + + /// EACompilerMemoryBarrier + /// + /// \Declaration + /// void EACompilerMemoryBarrier(); + /// + /// \Description + /// Provides a barrier for compiler optimization. The compiler will not make + /// assumptions about locations across an EACompilerMemoryBarrier statement. + /// For example, if a compiler has memory values temporarily cached in + /// registers but you need them to be written to memory, you can execute the + /// EACompilerMemoryBarrier statement. This is somewhat similar in concept to + /// the C volatile keyword except that it applies to all memory the compiler + /// is currently working with and applies its effect only where you specify + /// and not for every usage as with the volatile keyword. + /// + /// Under GCC, this statement is equivalent to the GCC `asm volatile("":::"memory")` + /// statement. Under VC++, this is equivalent to a _ReadWriteBarrier statement + /// (not to be confused with EAReadWriteBarrier above) and equivalent to the Windows + /// kernel function KeMemoryBarrierWithoutFence. This is also known as barrier() + /// undef Linux. + /// + /// EACompilerMemoryBarrier is a compiler-level statement and not a + /// processor-level statement. For processor-level memory barriers, + /// use EAReadBarrier, etc. + /// + /// \Example + /// Without the compiler memory barrier below, an optimizing compiler might + /// never assign 0 to gValue because gValue is reassigned to 1 later and + /// because gValue is not declared volatile. + /// + /// void ThreadFunction() { + /// extern int gValue; // Note that gValue is intentionally not declared volatile, + /// gValue = 0; + /// EACompilerMemoryBarrier(); + /// gValue = 1; + /// } + #define EACompilerMemoryBarrier() + + +#endif // EA_THREAD_SYNC_IMPLEMENTED + + +#endif // #ifdef EATHREAD_EATHREAD_SYNC_H + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/eathread_thread.h b/src/thirdparty/ea/EAThread/include/eathread/eathread_thread.h new file mode 100644 index 00000000..ee07286d --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/eathread_thread.h @@ -0,0 +1,795 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#ifndef EATHREAD_EATHREAD_THREAD_H +#define EATHREAD_EATHREAD_THREAD_H + +#include +#include +#include +EA_DISABLE_ALL_VC_WARNINGS() +#include +#include +#include +EA_RESTORE_ALL_VC_WARNINGS() + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +#if defined(EA_DLL) && defined(EA_COMPILER_MSVC) + // Suppress warning about class 'AtomicInt32' needs to have a + // dll-interface to be used by clients of class which have a templated member. + // + // These templates cannot be instantiated outside of the DLL. If you try, a + // link error will result. This compiler warning is intended to notify users + // of this. + EA_DISABLE_VC_WARNING(4251) +#endif + + +///////////////////////////////////////////////////////////////////////// +/// ThreadData +/// +/// This is used internally by class Thread. +/// To consider: Move this declaration into a platform-specific +/// header file. +///////////////////////////////////////////////////////////////////////// + +#if !EA_THREADS_AVAILABLE + + struct EAThreadDynamicData + { + }; + + struct EAThreadData + { + EAThreadDynamicData* mpData; + }; + +#elif EA_USE_CPP11_CONCURRENCY + #include + #include + + EA_DISABLE_VC_WARNING(4062 4265 4365 4836 4571 4625 4626 4628 4193 4127 4548 4350) + #if EA_PLATFORM_WINDOWS + #include // workaround for compile errors in winrt. see http://connect.microsoft.com/VisualStudio/feedback/details/730564/ppl-in-winrt-projects-fail-to-compile + #endif + #include + #include + + struct EAThreadDynamicData + { + typedef void (*ThreadFunc)(EAThreadDynamicData* tdd, void* userFunc, void* userContext, void* userWrapperFunc); + EAThreadDynamicData(EA::Thread::ThreadUniqueId uniqueThreadId, const char* pThreadName); + EAThreadDynamicData(void* userFunc, void* userContext, void* userWrapperFunc, ThreadFunc threadFunc); + ~EAThreadDynamicData(); + + void AddRef(); + void Release(); + + EA::Thread::AtomicInt32 mnRefCount; + EA::Thread::AtomicInt32 mStatus; + intptr_t mReturnValue; + char mName[EATHREAD_NAME_SIZE]; + void* mpStackBase; + EA::Thread::ThreadAffinityMask mnThreadAffinityMask; + + EA::Thread::ThreadUniqueId mUniqueThreadId; + struct EAThreadComposite + { + EAThreadComposite() + : mReturnPromise() + , mReturnFuture(mReturnPromise.get_future()) + , mGetStatusFuture(mReturnFuture) + { + } + + std::promise mReturnPromise; + std::shared_future mReturnFuture; + std::shared_future mGetStatusFuture; + std::thread mThread; + } *mpComp; + + private: + // Disable copy and assignment + EAThreadDynamicData(const EAThreadDynamicData&); + EAThreadDynamicData operator=(const EAThreadDynamicData&); + }; + + struct EAThreadData + { + EAThreadDynamicData* mpData; + }; + + EA_RESTORE_VC_WARNING() + +// TODO: collapse the defines. +#elif defined(EA_PLATFORM_SONY) + #include + #include + #include + #include + + // Internal queue wrapper which is used to allow for a higher resolution sleep than what is provided by Sony's sleep functions + // as despite the names, sceKernelSleep, sceKernelUSleep and sceKernelNanosleep are all 1 ms resolution whereas this timer is 100 microseconds + struct EAThreadTimerQueue + { + EAThreadTimerQueue() + { + int result = sceKernelCreateEqueue(&mTimerEventQueue, "EAThread Timer Queue"); + mbEnabled = result == SCE_OK; + + // A timer queue will fail to be created when there are too many kernel objects open. It is a valid + // use-case for the Event Queue to fail being created as the ThreadSleep function implements a fallback. + // + // EAT_ASSERT_FORMATTED(mbEnabled, "Failed to initialize the EAThread Timer Queue (0x%x)", result); + } + + ~EAThreadTimerQueue() + { + if(mbEnabled) // only destroy the queue if it was created. + sceKernelDeleteEqueue(mTimerEventQueue); + + mbEnabled = false; + } + + SceKernelEqueue mTimerEventQueue; + EA::Thread::AtomicUint32 mCurrentId = 0; + bool mbEnabled = false; + }; + + struct EAThreadDynamicData + { + EAThreadDynamicData(); + ~EAThreadDynamicData(); + + void AddRef(); + void Release(); + + EA::Thread::ThreadId mThreadId; + EA::Thread::SysThreadId mSysThreadId; + pid_t mThreadPid; // For Linux this is the thread ID from gettid(). Otherwise it's the getpid() value. + volatile int mnStatus; + intptr_t mnReturnValue; + void* mpStartContext[2]; + void* mpBeginThreadUserWrapper; // User-specified BeginThread function wrapper or class wrapper + void* mpStackBase; + EA::Thread::AtomicInt32 mnRefCount; + char mName[EATHREAD_NAME_SIZE]; + int mStartupProcessor; // The thread affinity for the thread to set itself to after it starts. We need to do this because we currently have no way to set the affinity of another thread until after it has started. + EA::Thread::Mutex mRunMutex; // Locked while the thread is running. The reason for this mutex is that it allows timeouts to be specified in the WaitForEnd function. + EA::Thread::Semaphore mStartedSemaphore; // Signaled when the thread starts. This allows us to know in a thread-safe way when the thread has actually started executing. + EA::Thread::ThreadAffinityMask mnThreadAffinityMask; + EAThreadTimerQueue mThreadTimerQueue; // This queue allows for high resolution timer events to be submitted per thread allowing for better sleep resolution than Sony's provided sleep functions + }; + + + struct EAThreadData{ + EAThreadDynamicData* mpData; + }; + +#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + #include + #include + + struct EAThreadDynamicData + { + EAThreadDynamicData(); + ~EAThreadDynamicData(); + + void AddRef(); + void Release(); + + EA::Thread::ThreadId mThreadId; + EA::Thread::SysThreadId mSysThreadId; + pid_t mThreadPid; // For Linux this is the thread ID from gettid(). Otherwise it's the getpid() value. + volatile int mnStatus; + intptr_t mnReturnValue; + void* mpStartContext[2]; + void* mpBeginThreadUserWrapper; // User-specified BeginThread function wrapper or class wrapper + void* mpStackBase; + EA::Thread::AtomicInt32 mnRefCount; + char mName[EATHREAD_NAME_SIZE]; + int mStartupProcessor; // DEPRECATED: The thread affinity for the thread to set itself to after it starts. We need to do this because we currently have no way to set the affinity of another thread until after it has started. + EA::Thread::ThreadAffinityMask mnThreadAffinityMask; // mStartupProcessor is deprecated in favor of using the the mnThreadAffinityMask and doesn't suffer from the limitations of only specifying the value at thread startup time. + EA::Thread::Mutex mRunMutex; // Locked while the thread is running. The reason for this mutex is that it allows timeouts to be specified in the WaitForEnd function. + EA::Thread::Semaphore mStartedSemaphore; // Signaled when the thread starts. This allows us to know in a thread-safe way when the thread has actually started executing. + }; + + + struct EAThreadData + { + EAThreadDynamicData* mpData; + }; + +#elif defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE + + struct EAThreadDynamicData + { + EAThreadDynamicData(); + ~EAThreadDynamicData(); + void AddRef(); + void Release(); + + EA::Thread::ThreadId mhThread; + unsigned int mnThreadId; // EA::Thread::SysThreadId + int mnStatus; + EA::Thread::ThreadAffinityMask mnThreadAffinityMask; + intptr_t mnReturnValue; + void* mpStartContext[3]; + void* mpBeginThreadUserWrapper; // User-specified BeginThread function wrapper or class wrapper + void* mpStackBase; + EA::Thread::AtomicInt32 mnRefCount; + char mName[EATHREAD_NAME_SIZE]; + }; + + + struct EAThreadData + { + EAThreadDynamicData* mpData; + }; + +#endif + +namespace EA +{ +namespace Thread +{ + +struct EATHREADLIB_API ThreadEnumData +{ + ThreadEnumData(); + ~ThreadEnumData(); + + EAThreadDynamicData* mpThreadDynamicData; + void Release(); +}; + +} +} +///////////////////////////////////////////////////////////////////////// + + + + +namespace EA +{ + namespace Thread + { + /// FindThreadDynamicData + /// Utility functionality, not needed for most uses. + EATHREADLIB_API EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId); + EATHREADLIB_API EAThreadDynamicData* FindThreadDynamicData(SysThreadId threadId); + + /// EnumerateThreads + /// Enumerates known threads. For some platforms the returned thread list is limited + /// to the main thread and threads created by EAThread. + /// Returns the required count to enumerate all threads. + /// Fills in thread data up till the supplied capacity. + /// + /// Example usage: + /// ThreadEnumData enumData[32]; + /// size_t count = EA::Thread::EnumerateThreads(enumData, EAArrayCount(enumData)); + /// + /// for(size_t i = 0; i < count; i++) + /// { + /// printf("Thread id: %s\n", EAThreadIdToString(enumData[i].mpThreadDynamicData->mThreadId)); + /// enumData[i].Release(); + /// } + size_t EATHREADLIB_API EnumerateThreads(ThreadEnumData* pDataArray, size_t dataArrayCapacity); + + /// RunnableFunction + /// Defines the prototype of a standalone thread function. + /// The return value is of type intptr_t, which is a standard integral + /// data type that is large enough to hold an int or void*. + typedef intptr_t (*RunnableFunction)(void* pContext); + + /// IRunnable + /// Defines a class whose Run function executes in a separate thread. + /// An implementation of this interface can be run using a Thread class instance. + struct EATHREADLIB_API IRunnable + { + virtual ~IRunnable() { } + + /// \brief Task run entry point + /// The thread terminates when this method returns. + /// The return value is of type intptr_t, which is a standard integral + /// data type that is large enough to hold an int or void*. + virtual intptr_t Run(void* pContext = NULL) = 0; + }; + + /// RunnableFunctionUserWrapper + /// Defines the prototype of a user callback function when thread function is started. + /// \param pContext: thread start context void* passed in from thread Thread::Begin() + /// \param defaultRunnableFunction: default function Thread::Begin() normally would + /// call, user must call this function with passed in pContext. + /// + /// Here's an example: + /// \code + /// int ThreadFunction(void*) + /// { + /// printf("Throw NULL pointer Exception.\n"); + /// char* pTest = NULL; + /// *pTest = 1; + /// return 0; + /// } + /// + /// intptr_t MyThreadBeginWrapper(RunnableFunction defaultRunnableFunction, void* pContext) + /// { + /// // Do pre-start thread function stuff + /// try { + /// // must call defaultRunnableFunction to execute thread function, if don't then + /// // thread function will never gets executed. + /// intptr_t retValue = defaultRunnableFunction(pContext); + /// } + /// catch(...) { + /// printf("Exception detected.\n"); + /// } + /// + /// // do post-start thread function stuff + /// return retValue; + /// } + /// \endcode + /// + /// In your thread begin() function: + /// \code + /// ... + /// threadIds = threads.Begin(ThreadFunction, NULL, NULL, MyThreadBeginWrapper); + /// ... + /// \endcode + typedef intptr_t (*RunnableFunctionUserWrapper)(RunnableFunction defaultRunnableFunction, void* pContext); + + + /// RunnableClassUserWrapper + /// Defines the prototype of a user callback function when thread function is started. + /// \param pContext: thread start context void* passed in from thread Thread::Begin() + /// \param defaultRunnableFunction: default function Thread::Begin() normally would + /// call, user must call this function with passed in pContext. + /// + /// Here's an example: + /// \code + /// class MyThreadClass + /// { + /// virtual intptr_t Run(void* pContext = NULL) + /// { + /// printf("Throw NULL pointer Exception.\n"); + /// char* pTest = NULL; + /// *pTest = 1; + /// return 0; + /// } + /// } + /// + /// intptr_t MyThreadBeginWrapper(IRunnable defaultRunnableFunction, void* pContext) + /// { + /// // do pre-start thread function stuff + /// + /// // a good example is try catch block + /// try + /// { + /// // must call defaultRunnableFunction to execute thread function, if don't then + /// // thread function will never gets executed. + /// intptr_t retValue = defaultRunnableFunction->Run(pContext); + /// } + /// catch(...) + /// { + /// printf("Exception detected.\n"); + /// } + /// + /// // do post-start thread function stuff + /// return retValue; + /// } + /// \endcode + /// + /// In your thread begin() function: + /// + /// \code + /// ... + /// MyThreadClass myThreadClass = new MyThreadClass(); + /// threadIds = threads.Begin(&myThreadClass, NULL, NULL, MyThreadBeginWrapper); + /// ... + /// \endcode + typedef intptr_t (*RunnableClassUserWrapper)(IRunnable* defaultRunnableClass, void* pContext); + + + /// ThreadParameters + /// Used for specifying thread starting parameters. Note that we do not + /// include a 'start paused' parameter. The reason for this is that such + /// a thing is not portable and other mechanisms can achieve the same + /// effect. Thread pause/resume in general is considered bad practice. + struct EATHREADLIB_API ThreadParameters + { + void* mpStack; /// Pointer to stack memory. This would be the low address of the memory. A NULL value means to create a default stack. Default is NULL. Note that some platforms (such as Windows) don't support a user-supplied stack. + size_t mnStackSize; /// Size of the stack memory. Default is variable, depending on the platform. + int mnPriority; /// Value in the range of [kThreadPriorityMin, kThreadPriorityMax]. Default is kThreadPriorityDefault. + int mnProcessor; /// 0-based index of which processor to run the thread on. A value of -1 means to use default. Default is -1. See SetThreadProcessor for caveats regarding this value. + const char* mpName; /// A name to give to the thread. Useful for identifying threads in a descriptive way. + EA::Thread::ThreadAffinityMask mnAffinityMask; /// A bitmask representing the cores that the thread is allowed to run on. NOTE: This affinity mask is only applied when mnProcessor is set to kProcessorAny. + bool mbDisablePriorityBoost; /// Whether the system should override the default behavior of boosting the thread priority as they come out of a wait state (currently only supported on Windows). + + ThreadParameters(); + }; + + + + /// Thread + /// + /// Note that we do not provide thread suspend and resume functions. + /// The reason for this is that such things are inherently unsafe as + /// you usually cannot know where the thread is executing when the + /// suspension occurs. The safe alternative is to use signal or + /// semaphore primitives to achieve the same thing in a safe way. + /// + /// For performance reasons, the thread creation functions of this + /// class are themselves not thread-safe. Thus if you want to call + /// the Begin functions for an instance of this class from multiple + /// threads, you will need to synchronize access to the begin + /// functions yourself. + class EATHREADLIB_API Thread + { + public: + enum Status + { + kStatusNone, /// The thread has neither started nor ended. + kStatusRunning, /// The thread has started but not ended. + kStatusEnded /// The thread has both started and ended. + }; + + /// Thread + /// \brief Thread constructor. + Thread(); + + /// Thread + /// \brief Thread copy constructor. + Thread(const Thread& t); + + /// Thread + /// \brief Thread destructor. The destructor does not take any + /// action on the thread associated with it. Any threads created + /// by this class will continue to run and exit normally after + /// this destructor has executed. + ~Thread(); + + /// operator= + /// \brief Thread assignment operator. + Thread& operator=(const Thread& t); + + /// \brief Return global RunnableFunctionUserWrapper set by user. + /// \return function pointer to RunnableFunctionUserWrapper function user + /// set, if NULL, nothing is set. + /// \sa RunnableFunctionUserWrapper + static RunnableFunctionUserWrapper GetGlobalRunnableFunctionUserWrapper(); + + /// \brief Set global RunnableFunctionUserWrapper. This can only be + /// set once in the application life time. + /// \param pUserWrapper user specified wrapper function pointer. + /// \sa RunnableFunctionUserWrapper + static void SetGlobalRunnableFunctionUserWrapper(RunnableFunctionUserWrapper pUserWrapper); + + /// \brief Return global RunnableClassUserWrapper set by user. + /// \return function pointer to RunnableClassUserWrapper function user + /// set, if NULL, nothing is set. + /// \sa RunnableClassUserWrapper + static RunnableClassUserWrapper GetGlobalRunnableClassUserWrapper(); + + /// \brief Set global RunnableClassUserWrapper. This can only be + /// set once in the application life time. + /// \sa RunnableClassUserWrapper + static void SetGlobalRunnableClassUserWrapper(RunnableClassUserWrapper pUserWrapper); + + /// Begin + /// \brief Starts a thread via a RunnableFunction. + /// Returns the thread id of the newly running thread. + /// The pContext argument is passed to the RunnableFunction and serves + /// to allow the caller to pass information to the thread. + /// The pThreadParameters argument allows the caller to specify additional + /// information about how to start the thread. If this parameter is NULL, + /// then default settings will be chosen. + /// The Begin function itself is not thread-safe. While this Thread class + /// can be used to Begin multiple threads, the Begin function itself cannot + /// safely be executed by multiple threads at a time. This is by design and + /// allows for a simpler more efficient library. + /// User can have their own RunnableFunction wrapper by specifying one in + /// pUserWrapper. When pUserWrapper is used, pUserWrapper will get called + /// first, then pUserWrapper function can do whatever is desired before the + /// just-created thread's entry point is called. + /// \sa RunnableFunctionUserWrapper + ThreadId Begin(RunnableFunction pFunction, void* pContext = NULL, const ThreadParameters* pThreadParameters = NULL, RunnableFunctionUserWrapper pUserWrapper = GetGlobalRunnableFunctionUserWrapper()); + + /// Begin + /// Starts a thread via an object of the IRunnable interface. + /// Returns the thread id of the newly running thread. + /// The pContext argument is passed to the RunnableFunction and serves + /// to allow the caller to pass information to the thread. + /// The pThreadParameters argument allows the caller to specify additional + /// information about how to start the thread. If this parameter is NULL, + /// then default settings will be chosen. + /// The Begin function itself is not thread-safe. While this Thread class + /// can be used to Begin multiple threads, the Begin function itself cannot + /// safely be executed by multiple threads at a time. This is by design and + /// allows for a simpler more efficient library. + /// User can have their own RunnableClass wrapper by specifying one pUserWrapper. + /// When pUserWrapper is used, pUserWrapper will get called first, then + /// pUserWrapper function can do whatever is desired before the just-created + /// thread's entry point is called. + /// \sa RunnableClassUserWrapper + ThreadId Begin(IRunnable* pRunnable, void* pContext = NULL, const ThreadParameters* pThreadParameters = NULL, RunnableClassUserWrapper pUserWrapper = GetGlobalRunnableClassUserWrapper()); + + /// WaitForEnd + /// Waits for the thread associated with an object of this class + /// to end. Returns one of enum Status to indicate the status upon + /// return of this call. + /// This function is similar to the Posix pthread_join function and + /// the Windows WaitForSingleObject function. + /// If input pThreadReturnValue is non-NULL, it will be filled in with + /// the return value of the thread. + /// This function must be called only by a single thread at a time. + /// The resulting behaviour is undefined if multiple threads call this function. + /// + /// Note that the timeout is specified in absolute time and not relative time. + /// + /// Note also that due to the way thread scheduling works -- particularly in a + /// time-sliced threading environment -- that the timeout value is a hint and + /// the actual amount of time passed before the timeout occurs may be significantly + /// more or less than the specified timeout time. + /// + Status WaitForEnd(const ThreadTime& timeoutAbsolute = kTimeoutNone, intptr_t* pThreadReturnValue = NULL); + + /// GetStatus + /// Returns one of enum GetStatus. Note that in the most general sense + /// the running status may change if the thread quit right after + /// this call was made. But this function is useful if you know that + /// a function was running and you want to poll for its status while + /// waiting for it to exit. + /// If input pThreadReturnValue is non-NULL, it will be filled in with + /// the return value of the thread if the Status is kStatusEnded. + /// If the Status is not kStatusEnded, pThreadReturnValue will be ignored. + Status GetStatus(intptr_t* pThreadReturnValue = NULL) const; + + /// GetId + /// Gets the Id of the thread associated with an object of this class. + /// This Id is unique throughout the system. This function returns a + /// value that under Posix threads would be synonymous with pthread_t + /// and under Windows would be synonymous with a thread HANDLE (and not + /// a Windows thread id). + ThreadId GetId() const; + + /// GetPriority + /// Gets the priority of the thread. Return kThreadPriorityUnknown if + /// the thread associated with this class isn't running. If a thread + /// wants to get its own priority, it can use this class member or it + /// can simply use the global SetThreadPriority function and not need + /// an instance of this class. If you want to manipulate the thread + /// priority via the native platform interface, you can use GetId to + /// get the platform-specific identifier and use that value with native APIs. + /// + /// This function can return any int except for kThreadPriorityUnknown, as the + /// current thread's priority will always be knowable. A return value of kThreadPriorityDefault + /// means that this thread is of normal (a.k.a. default) priority. + /// See the documentation for thread priority constants (e.g. kThreadPriorityDefault) + /// for more information about thread priority values and behaviour. + int GetPriority() const; + + /// SetPriority + /// Sets the priority of the thread. Returns false if the thread associated + /// with this class isn't running. If a thread wants to set its own priority, + /// it can use this class member or it can simply use the global SetThreadPriority + /// function and not need an instance of this class. If you want to manipulate + /// the thread priority via the native platform interface, you can use GetId to + /// get the platform-specific identifier and use that value with native APIs. + /// + /// Accepts any integer priority value except kThreadPriorityUnknown. + /// On some platforms, this function will automatically convert any invalid + /// priority for that particular platform to a valid one. A normal (a.k.a. default) thread + /// priority is identified by kThreadPriorityDefault. + /// + /// You can set the priority of a Thread object only if it has already begun. + /// You can also set the priority with the Begin function via the ThreadParameters + /// argument to Begin. This design is so in order to simply the implementation, + /// but being able to set ThreadParameters before Begin is something that can + /// be considered in the future. + bool SetPriority(int priority); + + /// SetProcessor + /// Sets the processor the given thread should run on. Valid values + /// are kThreadProcessorDefault, kThreadProcessorAny, or a processor + /// index in the range of [0, processor count). If the input value + /// is >= the processor count, it will be reduced to be a modulo of + /// the processor count. Any other invalid value will cause the processor + /// to be set to zero. + /// + /// For some platforms you can set the processor of a Thread object only if it + /// has already begun. + /// + /// You can also set the processor with the Begin function via the ThreadParameters + /// argument to Begin. This design is so in order to simply the implementation, + /// but being able to set ThreadParameters before Begin is something that can + /// be considered in the future. This is the most reliable way to set the thread + /// processor, as it works on all platforms. + void SetProcessor(int nProcessor); + + /// Wake + /// Wakes up a sleeping thread if it is sleeping. This necessarily can only + /// be called from a thread other than the sleeping thread. You must be careful + /// to not rely on this function as a synchronization primitive. For example, + /// in the general case you cannot be sure that after calling Wake that the + /// thread will be awake, as it is possible that right after you called Wake + /// the thread immediately went back to sleep before you could do anything. + /// Nevertheless, this function is useful in waking up a thread from a + /// (potentially long) sleep so that it can examine data, lock a synchronization + /// primitive, or simply exit. + /// + /// Note that this class has no member Sleep function. The reason is that a + /// thread can only put itself to sleep and cannot put other threads to sleep. + /// The thread should use the static Sleep function to put itself to sleep. + void Wake(); + + /// GetName + /// Returns the name of the thread assigned by the SetName function. + /// If the thread was not named by the SetName function, then the name is empty (""). + const char* GetName() const; + + /// SetName + /// Sets a descriptive name or the thread. On some platforms this name is passed + /// on to the debugging tools so they can see this name. The name length, including + /// a terminating 0 char, is limited to EATHREAD_NAME_SIZE characters. Any characters + /// beyond that are ignored. + /// + /// You can set the name of a Thread object only if it has already begun. + /// You can also set the name with the Begin function via the ThreadParameters + /// argument to Begin. This design is so in order to simply the implementation, + /// but being able to set ThreadParameters before Begin is something that can + /// be considered in the future. + /// + /// Some platforms (e.g. Linux) have the restriction this function works propertly only + /// when called by the same thread that you want to name. Given this situation, + /// the most portable way to use this SetName function is to either always call + /// it from the thread to be named or to use the ThreadParameters to give the + /// thread a name before it is started and let the started thread name itself. + void SetName(const char* pName); + + /// SetAffinityMask + /// Sets an affinity mask for the thread. On some platforms, this OS feature is + /// not supported. In this situation, you are at the mercy of the OS thread scheduler. + /// + /// Example(s): + /// "00000100" -> thread is pinned to processor 2 + /// "01010100" -> thread is pinned to processor 2, 4, and 6. + void SetAffinityMask(ThreadAffinityMask mnAffinityMask); + + /// GetAffinityMask + /// Returns the affinity mask for this specific thread. + ThreadAffinityMask GetAffinityMask(); + + /// SetDefaultProcessor + /// Sets the default processor to create threads with. To specify the processor + /// for a running thread, use SetProcessor() or specify the processor in the + /// thread creation ThreadParameters. + /// + /// If nProcessor is set to kProcessorAny, EAThread will automatically determine + /// which processor to launch threads to. + /// + /// Please refer to SetProcessor for valid values for the nProcessor argument. + static void SetDefaultProcessor(int nProcessor) + { sDefaultProcessor = nProcessor; } + + + /// GetDefaultProcessor + /// Gets the default processor to create threads with. + static int GetDefaultProcessor() + { return sDefaultProcessor; } + + + /// SetDefaultProcessorMask + /// Sets which processors created threads should be explicitly run on. + /// The default value is 0xffffffffffffffff. + /// Each bit refers to the associated processor. A mask of 0xffffffffffffffff + /// means to allow running on any processor, and on desktop platforms such + /// as Windows it means that the OS decides what processor to use on its own. + /// Not all platforms support this functionality, even if multiple processors are present. + static void SetDefaultProcessorMask(uint64_t mask) + { sDefaultProcessorMask.SetValue(mask); } + + + /// GetDefaultProcessorMask + /// Returns the mask set by SetDefaultProcessorMask. + static uint64_t GetDefaultProcessorMask() + { return sDefaultProcessorMask.GetValue(); } + + + /// GetPlatformData + /// Returns platform-specific data for this thread for debugging uses or + /// other cases whereby special (and non-portable) uses are required. + /// The value returned is a struct of type EAThreadData. + void* GetPlatformData() + { return &mThreadData; } + + protected: + static RunnableFunctionUserWrapper sGlobalRunnableFunctionUserWrapper; + static RunnableClassUserWrapper sGlobalRunnableClassUserWrapper; + static EA::Thread::AtomicInt32 sDefaultProcessor; + static EA::Thread::AtomicUint64 sDefaultProcessorMask; + EAThreadData mThreadData; + }; + + + /// ThreadFactory + /// + /// Implements a factory-based creation and destruction mechanism for class Thread. + /// A primary use of this would be to allow the Thread implementation to reside in + /// a private library while users of the class interact only with the interface + /// header and the factory. The factory provides conventional create/destroy + /// semantics which use global operator new, but also provides manual construction/ + /// destruction semantics so that the user can provide for memory allocation + /// and deallocation. + class EATHREADLIB_API ThreadFactory + { + public: + static Thread* CreateThread(); // Internally implemented as: return new Thread; + static void DestroyThread(Thread* pThread); // Internally implemented as: delete pThread; + + static size_t GetThreadSize(); // Internally implemented as: return sizeof(Thread); + static Thread* ConstructThread(void* pMemory); // Internally implemented as: return new(pMemory) Thread; + static void DestructThread(Thread* pThread); // Internally implemented as: pThread->~Thread(); + }; + + + /// MakeThread + /// + /// Simplify creating threads with lambdas + /// + template + auto MakeThread(F&& f, const EA::Thread::ThreadParameters& params = EA::Thread::ThreadParameters()) + { + typedef std::decay_t decayed_f_t; + + auto get_memory = [] + { + const auto sz = sizeof(decayed_f_t); + auto* pAllocator = EA::Thread::GetAllocator(); + + if(pAllocator) + return pAllocator->Alloc(sz); + else + return malloc(sz); + }; + + auto thread_enty = [](void* pMemory) -> intptr_t + { + auto free_memory = [](void* p) + { + auto* pAllocator = EA::Thread::GetAllocator(); + if(pAllocator) + return pAllocator->Free(p); + else + return free(p); + }; + + auto* pF = reinterpret_cast(pMemory); + (*pF)(); + pF->~decayed_f_t(); + free_memory(pF); + return 0; + }; + + EA::Thread::Thread thread; + thread.Begin(thread_enty, new(get_memory()) decayed_f_t(std::forward(f)), ¶ms); // deleted in the thread entry function + return thread; + } + + } // namespace Thread + +} // namespace EA + + +#if defined(EA_DLL) && defined(EA_COMPILER_MSVC) + // re-enable warning 4251 (it's a level-1 warning and should not be suppressed globally) + EA_RESTORE_VC_WARNING() +#endif + + +#endif // EATHREAD_EATHREAD_THREAD_H diff --git a/src/thirdparty/ea/EAThread/include/eathread/internal/config.h b/src/thirdparty/ea/EAThread/include/eathread/internal/config.h new file mode 100644 index 00000000..d4701fd7 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/internal/config.h @@ -0,0 +1,631 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_INTERNAL_CONFIG_H +#define EATHREAD_INTERNAL_CONFIG_H + + +#include + +EA_DISABLE_VC_WARNING(4574) +#include +EA_RESTORE_VC_WARNING() + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_VERSION +// +// We more or less follow the conventional EA packaging approach to versioning +// here. A primary distinction here is that minor versions are defined as two +// digit entities (e.g. .03") instead of minimal digit entities ".3"). The logic +// here is that the value is a counter and not a floating point fraction. +// Note that the major version doesn't have leading zeros. +// +// Example version strings: +// "0.91.00" // Major version 0, minor version 91, patch version 0. +// "1.00.00" // Major version 1, minor and patch version 0. +// "3.10.02" // Major version 3, minor version 10, patch version 02. +// "12.03.01" // Major version 12, minor version 03, patch version +// +// Example usage: +// printf("EATHREAD_VERSION version: %s", EATHREAD_VERSION); +// printf("EATHREAD_VERSION version: %d.%d.%d", EATHREAD_VERSION_N / 10000 % 100, EATHREAD_VERSION_N / 100 % 100, EATHREAD_VERSION_N % 100); +// +#ifndef EATHREAD_VERSION + #define EATHREAD_VERSION "1.33.03" + #define EATHREAD_VERSION_N 13303 + + // Older style version info + #define EATHREAD_VERSION_MAJOR (EATHREAD_VERSION_N / 100 / 100 % 100) + #define EATHREAD_VERSION_MINOR (EATHREAD_VERSION_N / 100 % 100) + #define EATHREAD_VERSION_PATCH (EATHREAD_VERSION_N % 100) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// _GNU_SOURCE +// +// Defined or not defined. +// If this is defined then GlibC extension functionality is enabled during +// calls to glibc header files. +// +#if !defined(_GNU_SOURCE) + #define _GNU_SOURCE +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_TLS_COUNT +// +// Defined as compile-time constant integer > 0. +// +#if !defined(EATHREAD_TLS_COUNT) + #define EATHREAD_TLS_COUNT 16 +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EA_THREADS_AVAILABLE +// +// Defined as 0 or 1 +// Defines if threading is supported on the given platform. +// If 0 then the EAThread implementation is not capable of creating threads, +// but other facilities (e.g. mutex) work in a non-thread-aware way. +// +#ifndef EA_THREADS_AVAILABLE + #define EA_THREADS_AVAILABLE 1 +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EA_USE_CPP11_CONCURRENCY +// +// Defined as 0 or 1 +// +#ifndef EA_USE_CPP11_CONCURRENCY + #if defined(EA_PLATFORM_WINDOWS) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) + #define EA_USE_CPP11_CONCURRENCY 1 + #else + #define EA_USE_CPP11_CONCURRENCY 0 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EA_USE_COMMON_ATOMICINT_IMPLEMENTATION +// +// Use the common EAThread AtomicInt implementation on all platforms. +// +// Defined as 0 or 1 +// +#ifndef EA_USE_COMMON_ATOMICINT_IMPLEMENTATION + #define EA_USE_COMMON_ATOMICINT_IMPLEMENTATION 1 +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EA_POSIX_THREADS_AVAILABLE +// +// Defined as 0 or 1 +// +#ifndef EA_POSIX_THREADS_AVAILABLE + #if defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_APPLE) + #define EA_POSIX_THREADS_AVAILABLE 1 + #elif defined(EA_PLATFORM_SONY) + #define EA_POSIX_THREADS_AVAILABLE 0 // POSIX threading API is present but use is discouraged by Sony. They want shipping code to use their scePthreads* API. + #else + #define EA_POSIX_THREADS_AVAILABLE 0 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EAT_ASSERT_ENABLED +// +// Defined as 0 or 1, default is 1 if EA_DEBUG or _DEBUG is defined. +// If defined as 1, then assertion failures are reported via EA::Thread::AssertionFailure(). +// +#ifndef EAT_ASSERT_ENABLED + #if defined(EA_DEBUG) || defined(_DEBUG) + #define EAT_ASSERT_ENABLED 1 + #else + #define EAT_ASSERT_ENABLED 0 + #endif +#endif + + + +#if EAT_ASSERT_ENABLED + #define EAT_ASSERT(expression) \ + EA_DISABLE_VC_WARNING(4127) \ + do { \ + EA_ANALYSIS_ASSUME(expression); \ + if (!(expression) ) \ + EA::Thread::AssertionFailure(__FILE__ "(" EA_STRINGIFY(__LINE__) "): " #expression); \ + } while(0) \ + EA_RESTORE_VC_WARNING() +#else + #define EAT_ASSERT(expression) +#endif + +#if EAT_ASSERT_ENABLED + #define EAT_ASSERT_MSG(expression, msg) \ + EA_DISABLE_VC_WARNING(4127) \ + do { \ + EA_ANALYSIS_ASSUME(expression); \ + if (!(expression) ) \ + EA::Thread::AssertionFailure(msg); \ + } while(0) \ + EA_RESTORE_VC_WARNING() +#else + #define EAT_ASSERT_MSG(expression, msg) +#endif + +#if EAT_ASSERT_ENABLED + #define EAT_ASSERT_FORMATTED(expression, pFormat, ...) \ + EA_DISABLE_VC_WARNING(4127) \ + do { \ + EA_ANALYSIS_ASSUME(expression); \ + if (!(expression) ) \ + EA::Thread::AssertionFailureV(pFormat, __VA_ARGS__); \ + } while(0) \ + EA_RESTORE_VC_WARNING() +#else + #define EAT_ASSERT_FORMATTED(expression, pFormat, ...) +#endif + +#if EAT_ASSERT_ENABLED + #define EAT_FAIL_MSG(msg) (EA::Thread::AssertionFailure(msg)) +#else + #define EAT_FAIL_MSG(msg) +#endif + +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// EAT_COMPILETIME_ASSERT +// +// Compile-time assertion for this module. +// C-like declaration: +// void EAT_COMPILETIME_ASSERT(bool bExpression); +// +#if !defined(EAT_COMPILETIME_ASSERT) + #define EAT_COMPILETIME_ASSERT(expression) static_assert(expression, EA_STRINGIFY(expression)) +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_TLSALLOC_DTOR_ENABLED +// +// Defined as 0 or 1. Default is 1. +// Defines if the TLSAlloc class destructor frees the TLS thread handle. +// This won't make a difference unless you were using EAThread in a DLL and +// you were repeatedly loading and unloading DLLs. +// See eathread_pc.cpp for usage of this and more info about the situation. +// +#ifndef EATHREAD_TLSALLOC_DTOR_ENABLED + #define EATHREAD_TLSALLOC_DTOR_ENABLED 1 +#endif +/////////////////////////////////////////////////////////////////////////////// + + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_LIKELY / EATHREAD_UNLIKELY +// +// Defined as a macro which gives a hint to the compiler for branch +// prediction. GCC gives you the ability to manually give a hint to +// the compiler about the result of a comparison, though it's often +// best to compile shipping code with profiling feedback under both +// GCC (-fprofile-arcs) and VC++ (/LTCG:PGO, etc.). However, there +// are times when you feel very sure that a boolean expression will +// usually evaluate to either true or false and can help the compiler +// by using an explicity directive... +// +// Example usage: +// if(EATHREAD_LIKELY(a == 0)) // Tell the compiler that a will usually equal 0. +// { ... } +// +// Example usage: +// if(EATHREAD_UNLIKELY(a == 0)) // Tell the compiler that a will usually not equal 0. +// { ... } +// +#ifndef EATHREAD_LIKELY + #define EATHREAD_LIKELY(x) EA_LIKELY(x) + #define EATHREAD_UNLIKELY(x) EA_UNLIKELY(x) +#endif +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_NAMING +// +// Defined as 0, 1 (enabled), or 2 (enabled only when debugger is present). +// +#define EATHREAD_NAMING_DISABLED 0 +#define EATHREAD_NAMING_ENABLED 1 +#define EATHREAD_NAMING_OPTIONAL 2 + +#ifndef EATHREAD_NAMING + #if defined(EA_SHIP) || defined(EA_FINAL) // These are two de-facto standard EA defines for identifying a shipping build. + #define EATHREAD_NAMING 0 + #else + #define EATHREAD_NAMING EATHREAD_NAMING_ENABLED // or EATHREAD_NAMING_OPTIONAL? + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_NAME_SIZE +// +// Specifies the max size to support for naming threads. +// This value can be changed as desired. +// +#ifndef EATHREAD_NAME_SIZE + #if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_UNIX) + #define EATHREAD_NAME_SIZE 64 + #else + #define EATHREAD_NAME_SIZE 32 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EA_XBDM_ENABLED +// +// Defined as 0 or 1, with 1 being the default for debug builds. +// This controls whether xbdm library usage is enabled on XBox 360. This library +// allows for runtime debug functionality. But shipping applications are not +// allowed to use xbdm. +// +#if !defined(EA_XBDM_ENABLED) + #if defined(EA_DEBUG) + #define EA_XBDM_ENABLED 1 + #else + #define EA_XBDM_ENABLED 0 + #endif +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_DLL +// +// Defined as 0 or 1. The default is dependent on the definition of EA_DLL. +// If EA_DLL is defined, then EATHREAD_DLL is 1, else EATHREAD_DLL is 0. +// EA_DLL is a define that controls DLL builds within the EAConfig build system. +// EATHREAD_DLL controls whether EATHREAD_VERSION is built and used as a DLL. +// Normally you wouldn't do such a thing, but there are use cases for such +// a thing, particularly in the case of embedding C++ into C# applications. +// +#ifndef EATHREAD_DLL + #if defined(EA_DLL) + #define EATHREAD_DLL 1 + #else + #define EATHREAD_DLL 0 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREADLIB_API +// +// This is used to label functions as DLL exports under Microsoft platforms. +// If EA_DLL is defined, then the user is building EAThread as a DLL and EAThread's +// non-templated functions will be exported. EAThread template functions are not +// labelled as EATHREADLIB_API (and are thus not exported in a DLL build). This is +// because it's not possible (or at least unsafe) to implement inline templated +// functions in a DLL. +// +// Example usage of EATHREADLIB_API: +// EATHREADLIB_API int someVariable = 10; // Export someVariable in a DLL build. +// +// struct EATHREADLIB_API SomeClass{ // Export SomeClass and its member functions in a DLL build. +// EATHREADLIB_LOCAL void PrivateMethod(); // Not exported. +// }; +// +// EATHREADLIB_API void SomeFunction(); // Export SomeFunction in a DLL build. +// +// For GCC, see http://gcc.gnu.org/wiki/Visibility +// +#ifndef EATHREADLIB_API // If the build file hasn't already defined this to be dllexport... + #if EATHREAD_DLL + #if defined(EA_COMPILER_MSVC) || defined(EA_PLATFORM_MINGW) + #define EATHREADLIB_API __declspec(dllimport) + #define EATHREADLIB_LOCAL + #elif defined(EA_PLATFORM_CYGWIN) + #define EATHREADLIB_API __attribute__((dllimport)) + #define EATHREADLIB_LOCAL + #elif (defined(__GNUC__) && (__GNUC__ >= 4)) // GCC AND Clang + #define EATHREADLIB_API __attribute__ ((visibility("default"))) + #define EATHREADLIB_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define EATHREADLIB_API + #define EATHREADLIB_LOCAL + #endif + #else + #define EATHREADLIB_API + #define EATHREADLIB_LOCAL + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_ALLOC_PREFIX +// +// Defined as a string literal. Defaults to this package's name. +// Can be overridden by the user by predefining it or by editing this file. +// This define is used as the default name used by this package for naming +// memory allocations and memory allocators. +// +// All allocations names follow the same naming pattern: +// /[/] +// +// Example usage: +// void* p = pCoreAllocator->Alloc(37, EATHREAD_ALLOC_PREFIX, 0); +// +// Example usage: +// gMessageServer.GetMessageQueue().get_allocator().set_name(EATHREAD_ALLOC_PREFIX "MessageSystem/Queue"); +// +#ifndef EATHREAD_ALLOC_PREFIX + #define EATHREAD_ALLOC_PREFIX "EAThread/" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_USE_STANDARD_NEW +// +// Defines whether we use the basic standard operator new or the named +// extended version of operator new, as per the EASTL package. +// +#ifndef EATHREAD_USE_STANDARD_NEW + #if EATHREAD_DLL // A DLL must provide its own implementation of new, so we just use built-in new. + #define EATHREAD_USE_STANDARD_NEW 1 + #else + #define EATHREAD_USE_STANDARD_NEW 0 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_NEW +// +// This is merely a wrapper for operator new which can be overridden and +// which has debug/release forms. +// +// Example usage: +// SomeClass* pObject = EATHREAD_NEW("SomeClass") SomeClass(1, 2, 3); +// +#ifndef EATHREAD_NEW + #if EATHREAD_USE_STANDARD_NEW + #define EATHREAD_NEW(name) new + #define EATHREAD_NEW_ALIGNED(alignment, offset, name) new + #define EATHREAD_DELETE delete + #else + #if defined(EA_DEBUG) + #define EATHREAD_NEW(name) new(name, 0, 0, __FILE__, __LINE__) + #define EATHREAD_NEW_ALIGNED(alignment, offset, name) new(alignment, offset, name, 0, 0, __FILE__, __LINE__) + #define EATHREAD_DELETE delete + #else + #define EATHREAD_NEW(name) new(name, 0, 0, 0, 0) + #define EATHREAD_NEW_ALIGNED(alignment, offset, name) new(alignment, offset, name, 0, 0, 0, 0) + #define EATHREAD_DELETE delete + #endif + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_HAS_EMULATED_AND_NATIVE_ATOMICS +// +// This symbol is defined if a platform has both native and emulated atomics. +// Currently the only platform that requires this is iOS as earlier versions +// of the operating system (ie: iOS 3) do not provide OS support for 64-bit +// atomics while later versions (ie: iOS 4/5) do. +#ifndef EATHREAD_HAS_EMULATED_AND_NATIVE_ATOMICS + #if defined(EA_PLATFORM_APPLE) + #define EATHREAD_HAS_EMULATED_AND_NATIVE_ATOMICS 1 + #else + #define EATHREAD_HAS_EMULATED_AND_NATIVE_ATOMICS 0 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_GLIBC_BACKTRACE_AVAILABLE +// +// You generally need to be using GCC, GLIBC, and Linux for backtrace to be available. +// And even then it's available only some of the time. +// +#if !defined(EATHREAD_GLIBC_BACKTRACE_AVAILABLE) + #if (defined(EA_COMPILER_CLANG) || defined(EA_COMPILER_GNUC)) && (defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_APPLE)) && !defined(EA_PLATFORM_CYGWIN) && !defined(EA_PLATFORM_ANDROID) + #define EATHREAD_GLIBC_BACKTRACE_AVAILABLE 1 + #else + #define EATHREAD_GLIBC_BACKTRACE_AVAILABLE 0 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_GLIBC_VERSION +// +// We provide our own GLIBC numeric version to determine when system library +// calls are available. +// +#if defined(__GLIBC__) + #define EATHREAD_GLIBC_VERSION ((__GLIBC__ * 1000) + __GLIBC_MINOR__) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_GETCALLSTACK_SUPPORTED +// +// Defined as 0 or 1. +// Identifies whether runtime callstack unwinding (i.e. GetCallstack()) is +// supported for the given platform. In some cases it may be that unwinding +// support code is present but it hasn't been tested for reliability and may +// have bugs preventing it from working properly. In some cases (e.g. x86) +// it may be that optimized builds make it difficult to read the callstack +// reliably, despite that we flag the platform as supported. +// +#if !defined(EATHREAD_GETCALLSTACK_SUPPORTED) + #if EATHREAD_GLIBC_BACKTRACE_AVAILABLE // Typically this means Linux on x86. + #define EATHREAD_GETCALLSTACK_SUPPORTED 1 + #elif defined(EA_PLATFORM_IPHONE) + #define EATHREAD_GETCALLSTACK_SUPPORTED 1 + #elif defined(EA_PLATFORM_ANDROID) + #define EATHREAD_GETCALLSTACK_SUPPORTED 1 + #elif defined(EA_PLATFORM_IPHONE_SIMULATOR) + #define EATHREAD_GETCALLSTACK_SUPPORTED 1 + #elif defined(EA_PLATFORM_WINDOWS_PHONE) && defined(EA_PROCESSOR_ARM) + #define EATHREAD_GETCALLSTACK_SUPPORTED 0 + #elif defined(EA_PLATFORM_MICROSOFT) + #define EATHREAD_GETCALLSTACK_SUPPORTED 1 + #elif defined(EA_PLATFORM_LINUX) + #define EATHREAD_GETCALLSTACK_SUPPORTED 1 + #elif defined(EA_PLATFORM_OSX) + #define EATHREAD_GETCALLSTACK_SUPPORTED 1 + #elif defined(EA_PLATFORM_SONY) + #define EATHREAD_GETCALLSTACK_SUPPORTED 1 + #elif defined(EA_PLATFORM_CYGWIN) // Support hasn't been verified. + #define EATHREAD_GETCALLSTACK_SUPPORTED 0 + #elif defined(EA_PLATFORM_MINGW) // Support hasn't been verified. + #define EATHREAD_GETCALLSTACK_SUPPORTED 0 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_DEBUG_DETAIL_ENABLED +// +// Defined as 0 or 1. +// If true then detailed debug info is displayed. Can be enabled in opt builds. +// +#ifndef EATHREAD_DEBUG_DETAIL_ENABLED + #define EATHREAD_DEBUG_DETAIL_ENABLED 0 +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_MIN_ABSOLUTE_TIME +// +// Defined as a time in milliseconds. +// Locks and waits allow the user to specify an absolute timeout time. In order +// to detect that the user accidentally specified a relative time, we define a +// minimum allowed absolute time which we assert on. This minimum time is one +// that in practice is impossible to be a future absolute time. +// +#ifndef EATHREAD_MIN_ABSOLUTE_TIME + #define EATHREAD_MIN_ABSOLUTE_TIME 10000 +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED +// +// Defined as 0 or 1. +// If true then the platform supports a user specified thread affinity mask. +// +#ifndef EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + #define EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED 1 + #elif defined(EA_PLATFORM_SONY) + #define EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED 1 + #elif defined(EA_PLATFORM_IPHONE) + // iPhone does not respect thread affinity + // https://forums.developer.apple.com/thread/44002 + // Consider: http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/processor_assign.html + #define EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED 0 + #elif defined(EA_PLATFORM_ANDROID) || defined(EA_PLATFORM_APPLE) || defined(EA_PLATFORM_UNIX) + #define EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED 1 + #elif defined(EA_PLATFORM_NX) + #define EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED 1 + #elif defined(EA_USE_CPP11_CONCURRENCY) && EA_USE_CPP11_CONCURRENCY + // CPP11 doesn't not provided a mechanism to set thread affinities. + #define EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED 0 + #else + #define EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED 1 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_GLOBAL_VARIABLE_DLL_SAFETY +// +// Defined as 0 or 1. +// +// +#ifndef EATHREAD_GLOBAL_VARIABLE_DLL_SAFETY + #define EATHREAD_GLOBAL_VARIABLE_DLL_SAFETY 0 +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_SCEDBG_ENABLED +// +// Defined as 0 or 1. +// Informs EAThread if Sony Debug libraries are available for us. +// +#ifndef EATHREAD_SCEDBG_ENABLED + #ifndef EA_SCEDBG_ENABLED + #define EATHREAD_SCEDBG_ENABLED 0 + #else + #define EATHREAD_SCEDBG_ENABLED EA_SCEDBG_ENABLED + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_DEBUG_BREAK +// +#ifndef EATHREAD_DEBUG_BREAK + #ifdef EA_COMPILER_MSVC + #define EATHREAD_DEBUG_BREAK() __debugbreak() + #else + #define EATHREAD_DEBUG_BREAK() *(volatile int*)(0) = 0 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_C11_ATOMICS_AVAILABLE +// +#ifndef EATHREAD_C11_ATOMICS_AVAILABLE + #if (defined(EA_ANDROID_SDK_LEVEL) && (EA_ANDROID_SDK_LEVEL >= 21)) + #define EATHREAD_C11_ATOMICS_AVAILABLE 1 + #else + #define EATHREAD_C11_ATOMICS_AVAILABLE 0 + #endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_ALIGNMENT_CHECK +// + +namespace EA { +namespace Thread { +namespace detail { + // Used to assert that memory accesses on x86-64 are atomic when "naturally" aligned to the size of registers. + template + inline bool IsNaturallyAligned(T* p) + { + return ((uintptr_t)p & (sizeof(EA_PLATFORM_WORD_SIZE) - 1)) == 0; + } +}}} + +#ifndef EATHREAD_ALIGNMENT_CHECK + #define EATHREAD_ALIGNMENT_CHECK(address) EAT_ASSERT_MSG(EA::Thread::detail::IsNaturallyAligned(address), "address is not naturally aligned.") +#endif + + +#endif // Header include guard + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/internal/dllinfo.h b/src/thirdparty/ea/EAThread/include/eathread/internal/dllinfo.h new file mode 100644 index 00000000..41eda781 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/internal/dllinfo.h @@ -0,0 +1,15 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#ifndef EATHREAD_DLLINFO_H +#define EATHREAD_DLLINFO_H + + +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +#endif diff --git a/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_atomic_standalone.h b/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_atomic_standalone.h new file mode 100644 index 00000000..50ffb2aa --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_atomic_standalone.h @@ -0,0 +1,36 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +/// Standalone atomic functions +/// These act the same as the class functions below. +/// The T return values are the previous value, except for the +/// AtomicFetchSwap function which returns the swapped out value. +/// +/// T AtomicGetValue(volatile T*); +/// T AtomicGetValue(const volatile T*); +/// void AtomicSetValue(volatile T*, T value); +/// T AtomicFetchIncrement(volatile T*); +/// T AtomicFetchDecrement(volatile T*); +/// T AtomicFetchAdd(volatile T*, T value); +/// T AtomicFetchSub(volatile T*, T value); +/// T AtomicFetchOr(volatile T*, T value); +/// T AtomicFetchAnd(volatile T*, T value); +/// T AtomicFetchXor(volatile T*, T value); +/// T AtomicFetchSwap(volatile T*, T value); +/// T AtomicFetchSwapConditional(volatile T*, T value, T condition); +/// bool AtomicSetValueConditional(volatile T*, T value, T condition); + +#if defined(EA_COMPILER_MSVC) + #include +#elif defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + #include +#else + #error unsupported platform +#endif + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_atomic_standalone_gcc.h b/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_atomic_standalone_gcc.h new file mode 100644 index 00000000..e00fb8e0 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_atomic_standalone_gcc.h @@ -0,0 +1,199 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +namespace EA +{ +namespace Thread +{ + +// TODO(rparolin): Consider use of clang builtin __sync_swap. +// https://clang.llvm.org/docs/LanguageExtensions.html#sync-swap + +// TODO(rparolin): Consider use of C11 atomics +// https://clang.llvm.org/docs/LanguageExtensions.html#c11-atomic-builtins + +namespace detail +{ + template + inline T AtomicGetValue(volatile T* ptr); +} // namespace detail + +// int +inline int AtomicGetValue(volatile int* ptr) { return detail::AtomicGetValue(ptr); } +inline int AtomicGetValue(const volatile int* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline int AtomicSetValue(volatile int* dest, int value) { return __sync_lock_test_and_set(dest, value); } +inline int AtomicFetchIncrement(volatile int* dest) { return __sync_fetch_and_add(dest, int(1)); } +inline int AtomicFetchDecrement(volatile int* dest) { return __sync_fetch_and_add(dest, int(-1)); } +inline int AtomicFetchAdd(volatile int* dest, int value) { return __sync_fetch_and_add(dest, value); } +inline int AtomicFetchSub(volatile int* dest, int value) { return __sync_fetch_and_sub(dest, value); } +inline int AtomicFetchOr(volatile int* dest, int value) { return __sync_fetch_and_or(dest, value); } +inline int AtomicFetchAnd(volatile int* dest, int value) { return __sync_fetch_and_and(dest, value); } +inline int AtomicFetchXor(volatile int* dest, int value) { return __sync_fetch_and_xor(dest, value); } +inline int AtomicFetchSwap(volatile int* dest, int value) { return __sync_lock_test_and_set(dest, value); } +inline int AtomicFetchSwapConditional(volatile int* dest, int value, int condition) { return __sync_val_compare_and_swap(dest, condition, value); } +inline bool AtomicSetValueConditional(volatile int* dest, int value, int condition) { return __sync_bool_compare_and_swap(dest, condition, value); } + +// unsigned int +inline unsigned int AtomicGetValue(volatile unsigned int* ptr) { return detail::AtomicGetValue(ptr); } +inline unsigned int AtomicGetValue(const volatile unsigned int* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline unsigned int AtomicSetValue(volatile unsigned int* dest, unsigned int value) { return __sync_lock_test_and_set(dest, value); } +inline unsigned int AtomicFetchIncrement(volatile unsigned int* dest) { return __sync_fetch_and_add(dest, (unsigned int)(1)); } +inline unsigned int AtomicFetchDecrement(volatile unsigned int* dest) { return __sync_fetch_and_add(dest, (unsigned int)(-1)); } +inline unsigned int AtomicFetchAdd(volatile unsigned int* dest, unsigned int value) { return __sync_fetch_and_add(dest, value); } +inline unsigned int AtomicFetchSub(volatile unsigned int* dest, unsigned int value) { return __sync_fetch_and_sub(dest, value); } +inline unsigned int AtomicFetchOr(volatile unsigned int* dest, unsigned int value) { return __sync_fetch_and_or(dest, value); } +inline unsigned int AtomicFetchAnd(volatile unsigned int* dest, unsigned int value) { return __sync_fetch_and_and(dest, value); } +inline unsigned int AtomicFetchXor(volatile unsigned int* dest, unsigned int value) { return __sync_fetch_and_xor(dest, value); } +inline unsigned int AtomicFetchSwap(volatile unsigned int* dest, unsigned int value) { return __sync_lock_test_and_set(dest, value); } +inline unsigned int AtomicFetchSwapConditional(volatile unsigned int* dest, unsigned int value, unsigned int condition) { return __sync_val_compare_and_swap(dest, condition, value); } +inline bool AtomicSetValueConditional(volatile unsigned int* dest, unsigned int value, unsigned int condition) { return __sync_bool_compare_and_swap(dest, condition, value); } + +// short +inline short AtomicGetValue(volatile short* ptr) { return detail::AtomicGetValue(ptr); } +inline short AtomicGetValue(const volatile short* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline short AtomicSetValue(volatile short* dest, short value) { return __sync_lock_test_and_set(dest, value); } +inline short AtomicFetchIncrement(volatile short* dest) { return __sync_fetch_and_add(dest, short(1)); } +inline short AtomicFetchDecrement(volatile short* dest) { return __sync_fetch_and_add(dest, short(-1)); } +inline short AtomicFetchAdd(volatile short* dest, short value) { return __sync_fetch_and_add(dest, value); } +inline short AtomicFetchSub(volatile short* dest, short value) { return __sync_fetch_and_sub(dest, value); } +inline short AtomicFetchOr(volatile short* dest, short value) { return __sync_fetch_and_or(dest, value); } +inline short AtomicFetchAnd(volatile short* dest, short value) { return __sync_fetch_and_and(dest, value); } +inline short AtomicFetchXor(volatile short* dest, short value) { return __sync_fetch_and_xor(dest, value); } +inline short AtomicFetchSwap(volatile short* dest, short value) { return __sync_lock_test_and_set(dest, value); } +inline short AtomicFetchSwapConditional(volatile short* dest, short value, short condition) { return __sync_val_compare_and_swap(reinterpret_cast(dest), static_cast(condition), static_cast(value)); } +inline bool AtomicSetValueConditional(volatile short* dest, short value, short condition) { return __sync_bool_compare_and_swap(reinterpret_cast(dest), static_cast(condition), static_cast(value)); } + +// unsigned short +inline unsigned short AtomicGetValue(volatile unsigned short* ptr) { return detail::AtomicGetValue(ptr); } +inline unsigned short AtomicGetValue(const volatile unsigned short* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline unsigned short AtomicSetValue(volatile unsigned short* dest, unsigned short value) { return __sync_lock_test_and_set(dest, value); } +inline unsigned short AtomicFetchIncrement(volatile unsigned short* dest) { return __sync_fetch_and_add(dest, (unsigned short)(1)); } +inline unsigned short AtomicFetchDecrement(volatile unsigned short* dest) { return __sync_fetch_and_add(dest, (unsigned short)(-1)); } +inline unsigned short AtomicFetchAdd(volatile unsigned short* dest, unsigned short value) { return __sync_fetch_and_add(dest, value); } +inline unsigned short AtomicFetchSub(volatile unsigned short* dest, unsigned short value) { return __sync_fetch_and_sub(dest, value); } +inline unsigned short AtomicFetchOr(volatile unsigned short* dest, unsigned short value) { return __sync_fetch_and_or(dest, value); } +inline unsigned short AtomicFetchAnd(volatile unsigned short* dest, unsigned short value) { return __sync_fetch_and_and(dest, value); } +inline unsigned short AtomicFetchXor(volatile unsigned short* dest, unsigned short value) { return __sync_fetch_and_xor(dest, value); } +inline unsigned short AtomicFetchSwap(volatile unsigned short* dest, unsigned short value) { return __sync_lock_test_and_set(dest, value); } +inline unsigned short AtomicFetchSwapConditional(volatile unsigned short* dest, unsigned short value, unsigned short condition) { return __sync_val_compare_and_swap(dest, condition, value); } +inline bool AtomicSetValueConditional(volatile unsigned short* dest, unsigned short value, unsigned short condition) { return __sync_bool_compare_and_swap(dest, condition, value); } + +// long +inline long AtomicGetValue(volatile long* ptr) { return detail::AtomicGetValue(ptr); } +inline long AtomicGetValue(const volatile long* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline long AtomicSetValue(volatile long* dest, long value) { return __sync_lock_test_and_set(dest, value); } +inline long AtomicFetchIncrement(volatile long* dest) { return __sync_fetch_and_add(dest, long(1)); } +inline long AtomicFetchDecrement(volatile long* dest) { return __sync_fetch_and_add(dest, long(-1)); } +inline long AtomicFetchAdd(volatile long* dest, long value) { return __sync_fetch_and_add(dest, value); } +inline long AtomicFetchSub(volatile long* dest, long value) { return __sync_fetch_and_sub(dest, value); } +inline long AtomicFetchOr(volatile long* dest, long value) { return __sync_fetch_and_or(dest, value); } +inline long AtomicFetchAnd(volatile long* dest, long value) { return __sync_fetch_and_and(dest, value); } +inline long AtomicFetchXor(volatile long* dest, long value) { return __sync_fetch_and_xor(dest, value); } +inline long AtomicFetchSwap(volatile long* dest, long value) { return __sync_lock_test_and_set(dest, value); } +inline long AtomicFetchSwapConditional(volatile long* dest, long value, long condition) { return __sync_val_compare_and_swap(dest, condition, value); } +inline bool AtomicSetValueConditional(volatile long* dest, long value, long condition) { return __sync_bool_compare_and_swap(dest, condition, value); } + +// unsigned long +inline unsigned long AtomicGetValue(volatile unsigned long* ptr) { return detail::AtomicGetValue(ptr); } +inline unsigned long AtomicGetValue(const volatile unsigned long* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline unsigned long AtomicSetValue(volatile unsigned long* dest, unsigned long value) { return __sync_lock_test_and_set(dest, value); } +inline unsigned long AtomicFetchIncrement(volatile unsigned long* dest) { return __sync_fetch_and_add(dest, (unsigned long)(1)); } +inline unsigned long AtomicFetchDecrement(volatile unsigned long* dest) { return __sync_fetch_and_add(dest, (unsigned long)(-1)); } +inline unsigned long AtomicFetchAdd(volatile unsigned long* dest, unsigned long value) { return __sync_fetch_and_add(dest, value); } +inline unsigned long AtomicFetchSub(volatile unsigned long* dest, unsigned long value) { return __sync_fetch_and_sub(dest, value); } +inline unsigned long AtomicFetchOr(volatile unsigned long* dest, unsigned long value) { return __sync_fetch_and_or(dest, value); } +inline unsigned long AtomicFetchAnd(volatile unsigned long* dest, unsigned long value) { return __sync_fetch_and_and(dest, value); } +inline unsigned long AtomicFetchXor(volatile unsigned long* dest, unsigned long value) { return __sync_fetch_and_xor(dest, value); } +inline unsigned long AtomicFetchSwap(volatile unsigned long* dest, unsigned long value) { return __sync_lock_test_and_set(dest, value); } +inline unsigned long AtomicFetchSwapConditional(volatile unsigned long* dest, unsigned long value, unsigned long condition) { return __sync_val_compare_and_swap(dest, condition, value); } +inline bool AtomicSetValueConditional(volatile unsigned long* dest, unsigned long value, unsigned long condition) { return __sync_bool_compare_and_swap(dest, condition, value); } + +// char32_t +#if EA_CHAR32_NATIVE + inline char32_t AtomicGetValue(volatile char32_t* ptr) { return detail::AtomicGetValue(ptr); } + inline char32_t AtomicGetValue(const volatile char32_t* ptr) { return AtomicGetValue(const_cast(ptr)); } + inline char32_t AtomicSetValue(volatile char32_t* dest, char32_t value) { return __sync_lock_test_and_set(dest, value); } + inline char32_t AtomicFetchIncrement(volatile char32_t* dest) { return __sync_fetch_and_add(dest, char32_t(1)); } + inline char32_t AtomicFetchDecrement(volatile char32_t* dest) { return __sync_fetch_and_add(dest, char32_t(-1)); } + inline char32_t AtomicFetchAdd(volatile char32_t* dest, char32_t value) { return __sync_fetch_and_add(dest, value); } + inline char32_t AtomicFetchSub(volatile char32_t* dest, char32_t value) { return __sync_fetch_and_sub(dest, value); } + inline char32_t AtomicFetchOr(volatile char32_t* dest, char32_t value) { return __sync_fetch_and_or(dest, value); } + inline char32_t AtomicFetchAnd(volatile char32_t* dest, char32_t value) { return __sync_fetch_and_and(dest, value); } + inline char32_t AtomicFetchXor(volatile char32_t* dest, char32_t value) { return __sync_fetch_and_xor(dest, value); } + inline char32_t AtomicFetchSwap(volatile char32_t* dest, char32_t value) { return __sync_lock_test_and_set(dest, value); } + inline char32_t AtomicFetchSwapConditional(volatile char32_t* dest, char32_t value, char32_t condition) { return __sync_val_compare_and_swap(dest, condition, value); } + inline bool AtomicSetValueConditional(volatile char32_t* dest, char32_t value, char32_t condition) { return __sync_bool_compare_and_swap(dest, condition, value); } +#endif + +// long long +inline long long AtomicGetValue(volatile long long* ptr) { return detail::AtomicGetValue(ptr); } +inline long long AtomicGetValue(const volatile long long* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline long long AtomicSetValue(volatile long long* dest, long long value) { return __sync_lock_test_and_set(dest, value); } +inline long long AtomicFetchIncrement(volatile long long* dest) { return __sync_fetch_and_add(dest, (long long)(1)); } +inline long long AtomicFetchDecrement(volatile long long* dest) { return __sync_fetch_and_add(dest, (long long)(-1)); } +inline long long AtomicFetchAdd(volatile long long* dest, long long value) { return __sync_fetch_and_add(dest, value); } +inline long long AtomicFetchSub(volatile long long* dest, long long value) { return __sync_fetch_and_sub(dest, value); } +inline long long AtomicFetchOr(volatile long long* dest, long long value) { return __sync_fetch_and_or(dest, value); } +inline long long AtomicFetchAnd(volatile long long* dest, long long value) { return __sync_fetch_and_and(dest, value); } +inline long long AtomicFetchXor(volatile long long* dest, long long value) { return __sync_fetch_and_xor(dest, value); } +inline long long AtomicFetchSwap(volatile long long* dest, long long value) { return __sync_lock_test_and_set(dest, value); } +inline long long AtomicFetchSwapConditional(volatile long long* dest, long long value, long long condition) { return __sync_val_compare_and_swap(dest, condition, value); } +inline bool AtomicSetValueConditional(volatile long long* dest, long long value, long long condition) { return __sync_bool_compare_and_swap(dest, condition, value); } + +// unsigned long long +inline unsigned long long AtomicGetValue(volatile unsigned long long* ptr) { return detail::AtomicGetValue(ptr); } +inline unsigned long long AtomicGetValue(const volatile unsigned long long* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline unsigned long long AtomicSetValue(volatile unsigned long long* dest, unsigned long long value) { return __sync_lock_test_and_set(dest, value); } +inline unsigned long long AtomicFetchIncrement(volatile unsigned long long* dest) { return __sync_fetch_and_add(dest, (unsigned long long)(1)); } +inline unsigned long long AtomicFetchDecrement(volatile unsigned long long* dest) { return __sync_fetch_and_add(dest, (unsigned long long)(-1)); } +inline unsigned long long AtomicFetchAdd(volatile unsigned long long* dest, unsigned long long value) { return __sync_fetch_and_add(dest, value); } +inline unsigned long long AtomicFetchSub(volatile unsigned long long* dest, unsigned long long value) { return __sync_fetch_and_sub(dest, value); } +inline unsigned long long AtomicFetchOr(volatile unsigned long long* dest, unsigned long long value) { return __sync_fetch_and_or(dest, value); } +inline unsigned long long AtomicFetchAnd(volatile unsigned long long* dest, unsigned long long value) { return __sync_fetch_and_and(dest, value); } +inline unsigned long long AtomicFetchXor(volatile unsigned long long* dest, unsigned long long value) { return __sync_fetch_and_xor(dest, value); } +inline unsigned long long AtomicFetchSwap(volatile unsigned long long* dest, unsigned long long value) { return __sync_lock_test_and_set(dest, value); } +inline unsigned long long AtomicFetchSwapConditional(volatile unsigned long long* dest, unsigned long long value, unsigned long long condition) { return __sync_val_compare_and_swap(dest, condition, value); } +inline bool AtomicSetValueConditional(volatile unsigned long long* dest, unsigned long long value, unsigned long long condition) { return __sync_bool_compare_and_swap(dest, condition, value); } + +// +// You can not simply define a template for the above atomics due to the explicit 128bit overloads +// below. The compiler will prefer those overloads during overload resolution and attempt to convert +// temporaries as they are more specialized than a template. +// +// template inline T AtomicGetValue(volatile T* source) { return __sync_fetch_and_add(source, (T)(0)); } +// template inline void AtomicSetValue(volatile T* dest, T value) { __sync_lock_test_and_set(dest, value); } +// template inline T AtomicFetchIncrement(volatile T* dest) { return __sync_fetch_and_add(dest, (T)(1)); } +// template inline T AtomicFetchDecrement(volatile T* dest) { return __sync_fetch_and_add(dest, (T)(-1)); } +// template inline T AtomicFetchAdd(volatile T* dest, T value) { return __sync_fetch_and_add(dest, value); } +// template inline T AtomicFetchOr(volatile T* dest, T value) { return __sync_fetch_and_or(dest, value); } +// template inline T AtomicFetchAnd(volatile T* dest, T value) { return __sync_fetch_and_and(dest, value); } +// template inline T AtomicFetchXor(volatile T* dest, T value) { return __sync_fetch_and_xor(dest, value); } +// template inline T AtomicFetchSwap(volatile T* dest, T value) { return __sync_lock_test_and_set(dest, value); } +// template inline bool AtomicSetValueConditional(volatile T* dest, T value, T condition) { return __sync_bool_compare_and_swap(dest, condition, value); } +// + +namespace detail +{ + template + inline T AtomicGetValue(volatile T* ptr) + { + #if EA_PLATFORM_WORD_SIZE >= 8 && defined(EA_PROCESSOR_X86_64) + EATHREAD_ALIGNMENT_CHECK(ptr); + EACompilerMemoryBarrier(); + T value = *ptr; + EACompilerMemoryBarrier(); + return value; + #else + return AtomicFetchAdd(ptr, T(0)); + #endif + } +} // namespace detail + +} // namespace Thread +} // namespace EA + diff --git a/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_atomic_standalone_msvc.h b/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_atomic_standalone_msvc.h new file mode 100644 index 00000000..fc96453b --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_atomic_standalone_msvc.h @@ -0,0 +1,260 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + +///////////////////////////////////////////////////////////////////////////// +// InterlockedXXX intrinsics +// +#if defined(EA_PLATFORM_MICROSOFT) + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() + + #if defined(EA_PROCESSOR_X86) + #if defined(_InterlockedExchange64_INLINE) + #define _InterlockedExchange64 _InterlockedExchange64_INLINE + #define _InterlockedExchangeAdd64 _InterlockedExchangeAdd64_INLINE + #define _InterlockedAnd64 _InterlockedAnd64_INLINE + #define _InterlockedOr64 _InterlockedOr64_INLINE + #define _InterlockedXor64 _InterlockedXor64_INLINE + #else + namespace EA { + namespace Thread { + namespace Internal { + template + T CAS64Helper(PtrT* ptr, T newVal, ValueTransformOp op) + { + T oldVal; + + do + { + oldVal = static_cast(*ptr); + } while (oldVal != _InterlockedCompareExchange64(ptr, op(oldVal, newVal), oldVal)); + + return oldVal; + } + }}} + + template + auto _InterlockedExchange64(T1* ptr, T2 value) + { + return EA::Thread::Internal::CAS64Helper(ptr, value, [](auto o, auto n) { return n; }); + } + + template + auto _InterlockedExchangeAdd64(T1* ptr, T2 value) + { + return EA::Thread::Internal::CAS64Helper(ptr, value, [](auto o, auto n) { return o + n; }); + } + + template + auto _InterlockedAnd64(T1* ptr, T2 value) + { + return EA::Thread::Internal::CAS64Helper(ptr, value, [](auto o, auto n) { return o & n; }); + } + + template + auto _InterlockedOr64(T1* ptr, T2 value) + { + return EA::Thread::Internal::CAS64Helper(ptr, value, [](auto o, auto n) { return o | n; }); + } + + template + auto _InterlockedXor64(T1* ptr, T2 value) + { + return EA::Thread::Internal::CAS64Helper(ptr, value, [](auto o, auto n) { return o ^ n; }); + } + #endif + #endif + + inline bool InterlockedSetIfEqual(volatile int64_t* dest, int64_t newValue, int64_t condition) + { + return (_InterlockedCompareExchange64(dest, newValue, condition) == condition); + } + + inline bool InterlockedSetIfEqual(volatile uint64_t* dest, uint64_t newValue, uint64_t condition) + { + return (_InterlockedCompareExchange64((int64_t volatile*)dest, (int64_t)newValue, (int64_t)condition) == (int64_t)condition); + } + +#endif // EA_PLATFORM_MICROSOFT + + + + +namespace EA +{ +namespace Thread +{ + +namespace detail +{ + template + inline T AtomicGetValue(volatile T* ptr); +} // namespace detail + +// int +inline int AtomicGetValue(volatile int* ptr) { return detail::AtomicGetValue(ptr); } +inline int AtomicGetValue(const volatile int* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline int AtomicSetValue(volatile int* ptr, int value) { return _InterlockedExchange((long*)ptr, (long)value); } +inline int AtomicFetchIncrement(volatile int* ptr) { return static_cast(_InterlockedIncrement((long*)ptr)) - 1; } +inline int AtomicFetchDecrement(volatile int* ptr) { return static_cast(_InterlockedDecrement((long*)ptr)) + 1; } +inline int AtomicFetchAdd(volatile int* ptr, int value) { return static_cast(_InterlockedExchangeAdd((long*)ptr, (long)value)); } +inline int AtomicFetchSub(volatile int* ptr, int value) { return static_cast(_InterlockedExchangeAdd((long*)ptr, -(long)value)); } +inline int AtomicFetchOr(volatile int* ptr, int value) { return static_cast(_InterlockedOr((long*)ptr, (long)value)); } +inline int AtomicFetchAnd(volatile int* ptr, int value) { return static_cast(_InterlockedAnd((long*)ptr, (long)value)); } +inline int AtomicFetchXor(volatile int* ptr, int value) { return static_cast(_InterlockedXor((long*)ptr, (long)value)); } +inline int AtomicFetchSwap(volatile int* ptr, int value) { return static_cast(_InterlockedExchange((long*)ptr, (long)value)); } +inline int AtomicFetchSwapConditional(volatile int* ptr, int value, int condition) { return _InterlockedCompareExchange((long*)ptr, (long)value, (long)condition); } +inline bool AtomicSetValueConditional(volatile int* ptr, int value, int condition) { return _InterlockedCompareExchange((long*)ptr, (long)value, (long)condition) == (long)condition; } + +// unsigned int +inline unsigned int AtomicGetValue(volatile unsigned int* ptr) { return detail::AtomicGetValue(ptr); } +inline unsigned int AtomicGetValue(const volatile unsigned int* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline unsigned int AtomicSetValue(volatile unsigned int* ptr, unsigned int value) { return static_cast(_InterlockedExchange((long*)ptr, (long)value)); } +inline unsigned int AtomicFetchIncrement(volatile unsigned int* ptr) { return static_cast(_InterlockedExchangeAdd((long*)ptr, (long)1)); } +inline unsigned int AtomicFetchDecrement(volatile unsigned int* ptr) { return static_cast(_InterlockedExchangeAdd((long*)ptr, (long)-1)); } +inline unsigned int AtomicFetchAdd(volatile unsigned int* ptr, unsigned int value) { return static_cast(_InterlockedExchangeAdd((long*)ptr, (long)value)); } +inline unsigned int AtomicFetchSub(volatile unsigned int* ptr, unsigned int value) { return static_cast(_InterlockedExchangeAdd((long*)ptr, -(long)value)); } +inline unsigned int AtomicFetchOr(volatile unsigned int* ptr, unsigned int value) { return static_cast(_InterlockedOr((long*)ptr, (long)value)); } +inline unsigned int AtomicFetchAnd(volatile unsigned int* ptr, unsigned int value) { return static_cast(_InterlockedAnd((long*)ptr, (long)value)); } +inline unsigned int AtomicFetchXor(volatile unsigned int* ptr, unsigned int value) { return static_cast(_InterlockedXor((long*)ptr, (long)value)); } +inline unsigned int AtomicFetchSwap(volatile unsigned int* ptr, unsigned int value) { return static_cast(_InterlockedExchange((long*)ptr, (long)value)); } +inline unsigned int AtomicFetchSwapConditional(volatile unsigned int* ptr, unsigned int value, unsigned int condition) { return (unsigned int)_InterlockedCompareExchange((long*)ptr, (long)value, (long)condition); } +inline bool AtomicSetValueConditional(volatile unsigned int* ptr, unsigned int value, unsigned int condition) { return _InterlockedCompareExchange((long*)ptr, (long)value, (long)condition) == (long)condition; } + +// short +inline short AtomicGetValue(volatile short* ptr) { return detail::AtomicGetValue(ptr); } +inline short AtomicGetValue(const volatile short* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline short AtomicSetValue(volatile short* ptr, short value) { return static_cast(_InterlockedExchange16((short*)ptr, (short)value)); } +inline short AtomicFetchIncrement(volatile short* ptr) { return static_cast(_InterlockedExchangeAdd16((short*)ptr, (short)1)); } +inline short AtomicFetchDecrement(volatile short* ptr) { return static_cast(_InterlockedExchangeAdd16((short*)ptr, (short)-1)); } +inline short AtomicFetchAdd(volatile short* ptr, short value) { return static_cast(_InterlockedExchangeAdd16((short*)ptr, (short)value)); } +inline short AtomicFetchSub(volatile short* ptr, short value) { return static_cast(_InterlockedExchangeAdd16((short*)ptr, -value)); } +inline short AtomicFetchOr(volatile short* ptr, short value) { return static_cast(_InterlockedOr16((short*)ptr, (short)value)); } +inline short AtomicFetchAnd(volatile short* ptr, short value) { return static_cast(_InterlockedAnd16((short*)ptr, (short)value)); } +inline short AtomicFetchXor(volatile short* ptr, short value) { return static_cast(_InterlockedXor16((short*)ptr, (short)value)); } +inline short AtomicFetchSwap(volatile short* ptr, short value) { return static_cast(_InterlockedExchange16((short*)ptr, (short)value)); } +inline short AtomicFetchSwapConditional(volatile short* ptr, short value, short condition) { return _InterlockedCompareExchange16(ptr, value, condition); } +inline bool AtomicSetValueConditional(volatile short* ptr, short value, short condition) { return _InterlockedCompareExchange16(ptr, value, condition) == condition; } + +// unsigned short +inline unsigned short AtomicGetValue(volatile unsigned short* ptr) { return detail::AtomicGetValue(ptr); } +inline unsigned short AtomicGetValue(const volatile unsigned short* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline unsigned short AtomicSetValue(volatile unsigned short* ptr, unsigned short value) { return static_cast(_InterlockedExchange16((short*)ptr, (short)value)); } +inline unsigned short AtomicFetchIncrement(volatile unsigned short* ptr) { return static_cast(_InterlockedExchangeAdd16((short*)ptr, (short)1)); } +inline unsigned short AtomicFetchDecrement(volatile unsigned short* ptr) { return static_cast(_InterlockedExchangeAdd16((short*)ptr, (short)-1)); } +inline unsigned short AtomicFetchAdd(volatile unsigned short* ptr, unsigned short value) { return static_cast(_InterlockedExchangeAdd16((short*)ptr, (short)value)); } +inline unsigned short AtomicFetchSub(volatile unsigned short* ptr, unsigned short value) { return static_cast(_InterlockedExchangeAdd16((short*)ptr, -(short)value)); } +inline unsigned short AtomicFetchOr(volatile unsigned short* ptr, unsigned short value) { return static_cast(_InterlockedOr16((short*)ptr, (short)value)); } +inline unsigned short AtomicFetchAnd(volatile unsigned short* ptr, unsigned short value) { return static_cast(_InterlockedAnd16((short*)ptr, (short)value)); } +inline unsigned short AtomicFetchXor(volatile unsigned short* ptr, unsigned short value) { return static_cast(_InterlockedXor16((short*)ptr, (short)value)); } +inline unsigned short AtomicFetchSwap(volatile unsigned short* ptr, unsigned short value) { return static_cast(_InterlockedExchange16((short*)ptr, (short)value)); } +inline unsigned short AtomicFetchSwapConditional(volatile unsigned short* ptr, unsigned short value, unsigned short condition) { return (unsigned short)_InterlockedCompareExchange16((short*)ptr, (short)value, (short)condition); } +inline bool AtomicSetValueConditional(volatile unsigned short* ptr, unsigned short value, unsigned short condition) { return _InterlockedCompareExchange16((short*)ptr, (short)value, (short)condition) == (short)condition; } + +// long +inline long AtomicGetValue(volatile long* ptr) { return detail::AtomicGetValue(ptr); } +inline long AtomicGetValue(const volatile long* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline long AtomicSetValue(volatile long* ptr, long value) { return _InterlockedExchange(ptr, value); } +inline long AtomicFetchIncrement(volatile long* ptr) { return _InterlockedIncrement(ptr) - 1; } +inline long AtomicFetchDecrement(volatile long* ptr) { return _InterlockedDecrement(ptr) + 1; } +inline long AtomicFetchAdd(volatile long* ptr, long value) { return _InterlockedExchangeAdd(ptr, value); } +inline long AtomicFetchSub(volatile long* ptr, long value) { return _InterlockedExchangeAdd(ptr, -value); } +inline long AtomicFetchOr(volatile long* ptr, long value) { return _InterlockedOr(ptr, value); } +inline long AtomicFetchAnd(volatile long* ptr, long value) { return _InterlockedAnd(ptr, value); } +inline long AtomicFetchXor(volatile long* ptr, long value) { return _InterlockedXor(ptr, value); } +inline long AtomicFetchSwap(volatile long* ptr, long value) { return _InterlockedExchange(ptr, value); } +inline long AtomicFetchSwapConditional(volatile long* ptr, long value, long condition) { return _InterlockedCompareExchange(ptr, value, condition); } +inline bool AtomicSetValueConditional(volatile long* ptr, long value, long condition) { return _InterlockedCompareExchange(ptr, value, condition) == condition; } + +// unsigned long +inline unsigned long AtomicGetValue(volatile unsigned long* ptr) { return detail::AtomicGetValue(ptr); } +inline unsigned long AtomicGetValue(const volatile unsigned long* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline unsigned long AtomicSetValue(volatile unsigned long* ptr, unsigned long value) { return static_cast(_InterlockedExchange((long*)ptr, (long)value)); } +inline unsigned long AtomicFetchIncrement(volatile unsigned long* ptr) { return static_cast(_InterlockedIncrement((long*)ptr)) - 1; } +inline unsigned long AtomicFetchDecrement(volatile unsigned long* ptr) { return static_cast(_InterlockedDecrement((long*)ptr)) + 1; } +inline unsigned long AtomicFetchAdd(volatile unsigned long* ptr, unsigned long value) { return static_cast(_InterlockedExchangeAdd((long*)ptr, (long)value)); } +inline unsigned long AtomicFetchSub(volatile unsigned long* ptr, unsigned long value) { return static_cast(_InterlockedExchangeAdd((long*)ptr, -(long)value)); } +inline unsigned long AtomicFetchOr(volatile unsigned long* ptr, unsigned long value) { return static_cast(_InterlockedOr((long*)ptr, (long)value)); } +inline unsigned long AtomicFetchAnd(volatile unsigned long* ptr, unsigned long value) { return static_cast(_InterlockedAnd((long*)ptr, (long)value)); } +inline unsigned long AtomicFetchXor(volatile unsigned long* ptr, unsigned long value) { return static_cast(_InterlockedXor((long*)ptr, (long)value)); } +inline unsigned long AtomicFetchSwap(volatile unsigned long* ptr, unsigned long value) { return static_cast(_InterlockedExchange((long*)ptr, (long)value)); } +inline unsigned long AtomicFetchSwapConditional(volatile unsigned long* ptr, unsigned long value, unsigned long condition) { return static_cast(_InterlockedCompareExchange((long*)ptr, (long)value, (long)condition)); } +inline bool AtomicSetValueConditional(volatile unsigned long* ptr, unsigned long value, unsigned long condition) { return static_cast(_InterlockedCompareExchange((long*)ptr, (long)value, (long)condition)) == condition; } + +// char32_t +#if EA_CHAR32_NATIVE + inline char32_t AtomicGetValue(volatile char32_t* ptr) { return detail::AtomicGetValue(ptr); } + inline char32_t AtomicGetValue(const volatile char32_t* ptr) { return AtomicGetValue(const_cast(ptr)); } + inline char32_t AtomicSetValue(volatile char32_t* ptr, char32_t value) { return static_cast(_InterlockedExchange((long*)ptr, (long)value)); } + inline char32_t AtomicFetchIncrement(volatile char32_t* ptr) { return static_cast(_InterlockedExchangeAdd((long*)ptr, (long)1)); } + inline char32_t AtomicFetchDecrement(volatile char32_t* ptr) { return static_cast(_InterlockedExchangeAdd((long*)ptr, (long)-1)); } + inline char32_t AtomicFetchAdd(volatile char32_t* ptr, char32_t value) { return static_cast(_InterlockedExchangeAdd((long*)ptr, (long)value)); } + inline char32_t AtomicFetchSub(volatile char32_t* ptr, char32_t value) { return static_cast(_InterlockedExchangeAdd((long*)ptr, -(long)value)); } + inline char32_t AtomicFetchOr(volatile char32_t* ptr, char32_t value) { return static_cast(_InterlockedOr((long*)ptr, (long)value)); } + inline char32_t AtomicFetchAnd(volatile char32_t* ptr, char32_t value) { return static_cast(_InterlockedAnd((long*)ptr, (long)value)); } + inline char32_t AtomicFetchXor(volatile char32_t* ptr, char32_t value) { return static_cast(_InterlockedXor((long*)ptr, (long)value)); } + inline char32_t AtomicFetchSwap(volatile char32_t* ptr, char32_t value) { return static_cast(_InterlockedExchange((long*)ptr, (long)value)); } + inline char32_t AtomicFetchSwapConditional(volatile char32_t* ptr, char32_t value, unsigned int condition) { return static_cast(_InterlockedCompareExchange((long*)ptr, (long)value, (long)condition)); } + inline bool AtomicSetValueConditional(volatile char32_t* ptr, char32_t value, unsigned int condition) { return _InterlockedCompareExchange((long*)ptr, (long)value, (long)condition) == (long)condition; } +#endif + + +// long long +inline long long AtomicGetValue(volatile long long* ptr) { return detail::AtomicGetValue(ptr); } +inline long long AtomicGetValue(const volatile long long* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline long long AtomicSetValue(volatile long long* ptr, long long value) { return static_cast(_InterlockedExchange64(ptr, value)); } +inline long long AtomicFetchIncrement(volatile long long* ptr) { return static_cast(_InterlockedExchangeAdd64(ptr, (long long)1)); } +inline long long AtomicFetchDecrement(volatile long long* ptr) { return static_cast(_InterlockedExchangeAdd64(ptr, (long long)-1)); } +inline long long AtomicFetchAdd(volatile long long* ptr, long long value) { return static_cast(_InterlockedExchangeAdd64(ptr, value)); } +inline long long AtomicFetchSub(volatile long long* ptr, long long value) { return static_cast(_InterlockedExchangeAdd64(ptr, -(long long)value)); } +inline long long AtomicFetchOr(volatile long long* ptr, long long value) { return static_cast(_InterlockedOr64(ptr, value)); } +inline long long AtomicFetchAnd(volatile long long* ptr, long long value) { return static_cast(_InterlockedAnd64(ptr, value)); } +inline long long AtomicFetchXor(volatile long long* ptr, long long value) { return static_cast(_InterlockedXor64(ptr, value)); } +inline long long AtomicFetchSwap(volatile long long* ptr, long long value) { return static_cast(_InterlockedExchange64(ptr, value)); } +inline long long AtomicFetchSwapConditional(volatile long long* ptr, long long value, long long condition) { return _InterlockedCompareExchange64(ptr, value, condition); } +inline bool AtomicSetValueConditional(volatile long long* ptr, long long value, long long condition) { return _InterlockedCompareExchange64(ptr, value, condition) == condition; } + +// unsigned long long +inline unsigned long long AtomicGetValue(volatile unsigned long long* ptr) { return detail::AtomicGetValue(ptr); } +inline unsigned long long AtomicGetValue(const volatile unsigned long long* ptr) { return AtomicGetValue(const_cast(ptr)); } +inline unsigned long long AtomicSetValue(volatile unsigned long long* ptr, unsigned long long value) { return static_cast(_InterlockedExchange64(reinterpret_cast(ptr), (long long)value)); } +inline unsigned long long AtomicFetchIncrement(volatile unsigned long long* ptr) { return static_cast(_InterlockedExchangeAdd64(reinterpret_cast(ptr), (long long)1)); } +inline unsigned long long AtomicFetchDecrement(volatile unsigned long long* ptr) { return static_cast(_InterlockedExchangeAdd64(reinterpret_cast(ptr), (long long)-1)); } +inline unsigned long long AtomicFetchAdd(volatile unsigned long long* ptr, unsigned long long value) { return static_cast(_InterlockedExchangeAdd64(reinterpret_cast(ptr), (long long)value)); } +inline unsigned long long AtomicFetchSub(volatile unsigned long long* ptr, unsigned long long value) { return static_cast(_InterlockedExchangeAdd64(reinterpret_cast(ptr), -(long long)value)); } +inline unsigned long long AtomicFetchOr(volatile unsigned long long* ptr, unsigned long long value) { return static_cast(_InterlockedOr64(reinterpret_cast(ptr), (long long)value)); } +inline unsigned long long AtomicFetchAnd(volatile unsigned long long* ptr, unsigned long long value) { return static_cast(_InterlockedAnd64(reinterpret_cast(ptr),(long long) value)); } +inline unsigned long long AtomicFetchXor(volatile unsigned long long* ptr, unsigned long long value) { return static_cast(_InterlockedXor64(reinterpret_cast(ptr),(long long) value)); } +inline unsigned long long AtomicFetchSwap(volatile unsigned long long* ptr, unsigned long long value) { return static_cast(_InterlockedExchange64(reinterpret_cast(ptr),(long long) value)); } +inline unsigned long long AtomicFetchSwapConditional(volatile unsigned long long* ptr, unsigned long long value, unsigned long long condition) { return static_cast(_InterlockedCompareExchange64(reinterpret_cast(ptr), (long long)value, (long long)condition)); } +inline bool AtomicSetValueConditional(volatile unsigned long long* ptr, unsigned long long value, unsigned long long condition) { return static_cast(_InterlockedCompareExchange64(reinterpret_cast(ptr), (long long)value, (long long)condition)) == condition; } + + +namespace detail +{ + template + inline T AtomicGetValue(volatile T* ptr) + { + #if EA_PLATFORM_WORD_SIZE >= 8 && defined(EA_PROCESSOR_X86_64) + EATHREAD_ALIGNMENT_CHECK(ptr); + EACompilerMemoryBarrier(); + T value = *ptr; + EACompilerMemoryBarrier(); + return value; + #else + return AtomicFetchAdd(ptr, T(0)); + #endif + } +} // namespace detail + + +} // namespace Thread +} // namespace EA + diff --git a/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_global.h b/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_global.h new file mode 100644 index 00000000..9e930135 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/internal/eathread_global.h @@ -0,0 +1,32 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +///////////////////////////////////////////////////////////////////////////// +// NOTE(rparolin): Provides a unified method of access to EAThread global +// variables that (when specified by the user) can become DLL safe by adding a +// dependency on EAStdC EAGlobal implementation. +///////////////////////////////////////////////////////////////////////////// + +#ifndef EATHREAD_INTERNAL_GLOBAL_H +#define EATHREAD_INTERNAL_GLOBAL_H + +#if EATHREAD_GLOBAL_VARIABLE_DLL_SAFETY + #include + + #define EATHREAD_GLOBALVARS (*EA::StdC::AutoStaticOSGlobalPtr().get()) + #define EATHREAD_GLOBALVARS_CREATE_INSTANCE EA::StdC::AutoStaticOSGlobalPtr gGlobalVarsInstance; + #define EATHREAD_GLOBALVARS_EXTERN_INSTANCE + +#else + #define EATHREAD_GLOBALVARS gEAThreadGlobalVars + #define EATHREAD_GLOBALVARS_CREATE_INSTANCE EA::Thread::EAThreadGlobalVars gEAThreadGlobalVars + #define EATHREAD_GLOBALVARS_EXTERN_INSTANCE extern EA::Thread::EAThreadGlobalVars gEAThreadGlobalVars + +#endif + +#endif diff --git a/src/thirdparty/ea/EAThread/include/eathread/internal/timings.h b/src/thirdparty/ea/EAThread/include/eathread/internal/timings.h new file mode 100644 index 00000000..599bfd81 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/internal/timings.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +#ifndef EATHREAD_INTERNAL_TIMINGS_H +#define EATHREAD_INTERNAL_TIMINGS_H + +namespace EA +{ + namespace Thread + { + +#if defined(EA_PLATFORM_SONY) + // RelativeTimeoutFromAbsoluteTimeout returns a relative timeout in microseconds. + inline uint32_t RelativeTimeoutFromAbsoluteTimeout(EA::Thread::ThreadTime timeoutAbsolute) + { + using namespace EA::Thread; + + EAT_ASSERT((timeoutAbsolute == kTimeoutImmediate) || (timeoutAbsolute > EATHREAD_MIN_ABSOLUTE_TIME)); // Assert that the user didn't make the mistake of treating time as relative instead of absolute. + + uint32_t timeoutRelative = 0; + + if (timeoutAbsolute == kTimeoutNone) + { + timeoutRelative = 0xffffffff; + } + else if (timeoutAbsolute == kTimeoutImmediate) + { + timeoutRelative = 0; + } + else + { + ThreadTime timeCurrent(GetThreadTime()); + timeoutRelative = (timeoutAbsolute > timeCurrent) ? EA_THREADTIME_AS_UINT_MICROSECONDS(timeoutAbsolute - timeCurrent) : 0; + } + + EAT_ASSERT((timeoutRelative == 0xffffffff) || (timeoutRelative < 100000000)); // Assert that the timeout is a sane value and didn't wrap around. + + return timeoutRelative; + } +#endif + + } +} + +#endif diff --git a/src/thirdparty/ea/EAThread/include/eathread/shared_array_mt.h b/src/thirdparty/ea/EAThread/include/eathread/shared_array_mt.h new file mode 100644 index 00000000..b9a4d0f9 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/shared_array_mt.h @@ -0,0 +1,431 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// This is a multithread-safe version of shared_array_mt. +// For basic documentation, see shared_array_mt. +/////////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_SHARED_ARRAY_MT_H +#define EATHREAD_SHARED_ARRAY_MT_H + +#ifndef INCLUDED_eabase_H + #include +#endif +#ifndef EATHREAD_EATHREAD_FUTEX_H + #include +#endif +#include // More properly: #include // Definition of std::ptrdiff_t + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + + +/// namespace EA +/// The standard Electronic Arts namespace +namespace EA +{ + namespace Thread + { + /// class shared_array_mt + /// A shared_array_mt is the same as shared_ptr but for arrays. + template + class shared_array_mt + { + private: + /// this_type + /// This is an alias for shared_array_mt, this class. + typedef shared_array_mt this_type; + + /// reference_count_type + /// An internal reference count type. Must be convertable to int + /// so that the public use_count function can work. + typedef EA::Thread::AtomicInt32 reference_count_type; + + T* mpArray; /// The owned pointer. Points to an array of T. + reference_count_type* mpRefCount; /// Reference count for owned pointer. + mutable Futex mMutex; /// Mutex guarding access to this class. + + public: + typedef T element_type; + typedef T value_type; + + /// shared_array_mt + /// Takes ownership of the pointer and sets the reference count + /// to the pointer to 1. It is OK if the input pointer is null. + /// The shared reference count is allocated on the heap via operator new. + /// If an exception occurs during the allocation of the shared + /// reference count, the owned pointer is deleted and the exception + /// is rethrown. A null pointer is given a reference count of 1. + explicit shared_array_mt(T* pArray = 0) + : mpArray(pArray), mMutex() + { + // We don't lock our mutex in this function, as this is the constructor + // and we assume that construction is already done in a thread-safe way + // by the owner of this object. + #if defined(EA_COMPILER_NO_EXCEPTIONS) || defined(EA_COMPILER_NO_UNWIND) + mpRefCount = new reference_count_type(1); + #else + EA_DISABLE_VC_WARNING(4571) + try + { + mpRefCount = new reference_count_type(1); + } + catch(...) + { + delete[] mpArray; + //mpRefCount = 0; shouldn't be necessary. + throw; + } + EA_RESTORE_VC_WARNING() + #endif + } + + /// shared_array_mt + /// Shares ownership of a pointer with another instance of shared_array_mt. + /// This function increments the shared reference count on the pointer. + shared_array_mt(shared_array_mt const& sharedArray) + : mMutex() + { + sharedArray.lock(); + mpArray = sharedArray.mpArray; + mpRefCount = sharedArray.mpRefCount; + mpRefCount->Increment(); // Atomic operation + sharedArray.unlock(); + } + + /// ~shared_array_mt + /// Decrements the reference count for the owned pointer. If the + /// reference count goes to zero, the owned pointer is deleted and + /// the shared reference count is deleted. + ~shared_array_mt() + { + lock(); + const reference_count_type newRefCount(mpRefCount->Decrement()); // Atomic operation + // EAT_ASSERT(newRefCount >= 0); + if(newRefCount == 0) + { + delete[] mpArray; + delete mpRefCount; + } + unlock(); + } + + /// operator= + /// Copies another shared_array_mt to this object. Note that this object + /// may already own a shared pointer with another different pointer + /// (but still of the same type) before this call. In that case, + /// this function releases the old pointer, decrementing its reference + /// count and deleting it if zero, takes shared ownership of the new + /// pointer and increments its reference count. + shared_array_mt& operator=(shared_array_mt const& sharedArray) + { + // We don't lock mutexes here because we let the swap function + // below do the locking and assignment. The if statement below + // isn't protected within a lock operation because it wouldn't + // help by being so because if mpValue is changing during the + // the execution of this function then the user has an external + // race condition that needs to be managed at that level. + if(mpArray != sharedArray.mpArray) + { + // The easiest thing to do is to create a temporary and + // copy ourselves ourselves into it. This is a standard + // method for switching pointer ownership in systems like this. + shared_array_mt(sharedArray).swap(*this); + } + return *this; + } + + // operator= + // We do not defined this function in order to maintain compatibility + // with the currently proposed (2003) C++ standard addition. Use reset instead. + // shared_array_mt& operator=(T* pValue) + // { + // reset(pValue); + // return *this; + // } + + /// lock + /// @brief Locks our mutex for thread-safe access. + /// It is a const function because const-ness refers to the underlying pointer being + /// held and not this class. + void lock() const + { + mMutex.Lock(); + } + + /// unlock + /// @brief Unlocks our mutex which was previous locked. + /// It is a const function because const-ness refers to the underlying pointer being + /// held and not this class. + void unlock() const + { + mMutex.Unlock(); + } + + /// reset + /// Releases the owned pointer and takes ownership of the + /// passed in pointer. If the passed in pointer is the same + /// as the owned pointer, nothing is done. The passed in pointer + /// can be null, in which case the use count is set to 1. + void reset(T* pArray = 0) + { + // We don't lock any mutexes here because we let the swap function do that. + // We don't lock for the 'if' statement below because that wouldn't really buy anything. + if(pArray != mpArray) + { + // The easiest thing to do is to create a temporary and + // copy ourselves ourselves into it. This is a standard + // method for switching pointer ownership in systems like this. + shared_array_mt(pArray).swap(*this); + } + } + + /// swap + /// Exchanges the owned pointer beween two shared_array_mt objects. + void swap(shared_array_mt& sharedArray) + { + lock(); + sharedArray.lock(); + + // std::swap(mpArray, sharedArray.mpArray); // Not used so that we can reduce a dependency. + T* const pArray = sharedArray.mpArray; + sharedArray.mpArray = mpArray; + mpArray = pArray; + + // std::swap(mpRefCount, sharedArray.mpRefCount); // Not used so that we can reduce a dependency. + reference_count_type* const pRefCount = sharedArray.mpRefCount; + sharedArray.mpRefCount = mpRefCount; + mpRefCount = pRefCount; + + sharedArray.unlock(); + unlock(); + } + + /// operator[] + /// Returns a reference to the specified item in the owned pointer + /// array. + /// Example usage: + /// shared_array_mt ptr = new int[6]; + /// int x = ptr[2]; + T& operator[](ptrdiff_t i) const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + // EAT_ASSERT(mpArray && (i >= 0)); + return mpArray[i]; + } + + /// operator* + /// Returns the owner pointer dereferenced. + /// Example usage: + /// shared_array_mt ptr = new int(3); + /// int x = *ptr; + T& operator*() const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + // EAT_ASSERT(mpArray); + return *mpArray; + } + + /// operator-> + /// Allows access to the owned pointer via operator->() + /// Example usage: + /// struct X{ void DoSomething(); }; + /// shared_array_mt ptr = new X; + /// ptr->DoSomething(); + T* operator->() const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + // EAT_ASSERT(mpArray); + return mpArray; + } + + /// get + /// Returns the owned pointer. Note that this class does + /// not provide an operator T() function. This is because such + /// a thing (automatic conversion) is deemed unsafe. + /// Example usage: + /// struct X{ void DoSomething(); }; + /// shared_array_mt ptr = new X; + /// X* pX = ptr.get(); + /// pX->DoSomething(); + T* get() const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + return mpArray; + } + + /// use_count + /// Returns the reference count on the owned pointer. + /// The return value is one if the owned pointer is null. + int use_count() const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + // EAT_ASSERT(mpRefCount); + return (int)*mpRefCount; + } + + /// unique + /// Returns true if the reference count on the owned pointer is one. + /// The return value is true if the owned pointer is null. + bool unique() const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + // EAT_ASSERT(mpRefCount); + return (*mpRefCount == 1); + } + + /// add_ref + /// Manually increments the reference count on the owned pointer. + /// This is currently disabled because it isn't in part of the + /// proposed C++ language addition. + /// int add_ref() + /// { + /// lock(); + /// // EAT_ASSERT(mpRefCount); + /// ++*mpRefCount; // Atomic operation + /// unlock(); + /// } + + /// release_ref + /// Manually increments the reference count on the owned pointer. + /// If the reference count becomes zero, then the owned pointer + /// is deleted and reset(0) is called. For any given instance of + /// shared_ptr, release_ref can only be called as many times as -- + /// but no more than -- the number of times add_ref was called + /// for that same shared_ptr. Otherwise, separate instances of + /// shared_ptr would be left with dangling owned pointer instances. + /// This is currently disabled because it isn't in part of the + /// proposed C++ language addition. + /// int release_ref() + /// { + /// lock(); + /// // EAT_ASSERT(mpRefCount); + /// if(*mpRefCount > 1){ + /// const int nReturnValue = --*mpRefCount; // Atomic operation + /// unlock(); + /// return nReturnValue; + /// } + /// reset(0); + /// unlock(); + /// return 0; + /// } + + /// Implicit operator bool + /// Allows for using a scoped_ptr as a boolean. + /// Example usage: + /// shared_array_mt ptr = new int(3); + /// if(ptr) + /// ++*ptr; + /// + /// Note that below we do not use operator bool(). The reason for this + /// is that booleans automatically convert up to short, int, float, etc. + /// The result is that this: if(scopedPtr == 1) would yield true (bad). + typedef T* (this_type::*bool_)() const; + operator bool_() const + { + // We don't lock here because this is essentially a read operation. + if(mpArray) + return &this_type::get; + return 0; + } + + /// operator! + /// This returns the opposite of operator bool; it returns true if + /// the owned pointer is null. Some compilers require this and some don't. + /// shared_array_mt ptr = new int(3); + /// if(!ptr) + /// EAT_ASSERT(false); + bool operator!() const + { + // We don't lock here because this is essentially a read operation. + return (mpArray == 0); + } + + }; // class shared_array_mt + + + /// get_pointer + /// returns shared_array_mt::get() via the input shared_array_mt. + template + inline T* get_pointer(const shared_array_mt& sharedArray) + { + return sharedArray.get(); + } + + /// swap + /// Exchanges the owned pointer beween two shared_array_mt objects. + /// This non-member version is useful for compatibility of shared_array_mt + /// objects with the C++ Standard Library and other libraries. + template + inline void swap(shared_array_mt& sharedArray1, shared_array_mt& sharedArray2) + { + sharedArray1.swap(sharedArray2); + } + + + /// operator!= + /// Compares two shared_array_mt objects for equality. Equality is defined as + /// being true when the pointer shared between two shared_array_mt objects is equal. + /// It is debatable what the appropriate definition of equality is between two + /// shared_array_mt objects, but we follow the current 2nd generation C++ standard proposal. + template + inline bool operator==(const shared_array_mt& sharedArray1, const shared_array_mt& sharedArray2) + { + // EAT_ASSERT((sharedArray1.get() != sharedArray2.get()) || (sharedArray1.use_count() == sharedArray2.use_count())); + return (sharedArray1.get() == sharedArray2.get()); + } + + + /// operator!= + /// Compares two shared_array_mt objects for inequality. Equality is defined as + /// being true when the pointer shared between two shared_array_mt objects is equal. + /// It is debatable what the appropriate definition of equality is between two + /// shared_array_mt objects, but we follow the current 2nd generation C++ standard proposal. + template + inline bool operator!=(const shared_array_mt& sharedArray1, const shared_array_mt& sharedArray2) + { + // EAT_ASSERT((sharedArray1.get() != sharedArray2.get()) || (sharedArray1.use_count() == sharedArray2.use_count())); + return (sharedArray1.get() != sharedArray2.get()); + } + + + /// operator< + /// Returns which shared_array_mt is 'less' than the other. Useful when storing + /// sorted containers of scoped_ptr objects. + template + inline bool operator<(const shared_array_mt& sharedArray1, const shared_array_mt& sharedArray2) + { + return (sharedArray1.get() < sharedArray2.get()); // Alternatively use: std::less(a.get(), b.get()); + } + + } // namespace Thread + +} // namespace EA + + + + +#endif // EATHREAD_SHARED_ARRAY_MT_H + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/shared_ptr_mt.h b/src/thirdparty/ea/EAThread/include/eathread/shared_ptr_mt.h new file mode 100644 index 00000000..fe446e90 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/shared_ptr_mt.h @@ -0,0 +1,472 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// This is a multithread-safe version of shared_ptr_mt. +// For basic documentation, see shared_ptr_mt. +/////////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_SHARED_PTR_MT_H +#define EATHREAD_SHARED_PTR_MT_H + +#ifndef INCLUDED_eabase_H + #include +#endif +#ifndef EATHREAD_EATHREAD_FUTEX_H + #include +#endif +#ifndef EATHREAD_EATHREAD_ATOMIC_H + #include +#endif +// #include Temporarily disabled while we wait for compilers to modernize. // Declaration of std::auto_ptr. + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + + +/// namespace EA +/// The standard Electronic Arts namespace +namespace EA +{ + namespace Thread + { + /// class shared_ptr_mt + /// @brief Implements a thread-safe version of shared_ptr. + template + class shared_ptr_mt + { + private: + /// this_type + /// This is an alias for shared_ptr_mt, this class. + typedef shared_ptr_mt this_type; + + /// reference_count_type + /// An internal reference count type. Must be convertable to int + /// so that the public use_count function can work. + typedef EA::Thread::AtomicInt32 reference_count_type; + + T* mpValue; /// The owned pointer. + reference_count_type* mpRefCount; /// Reference count for owned pointer. + mutable Futex mMutex; /// Mutex guarding access to this class. + + public: + typedef T element_type; + typedef T value_type; + + /// shared_ptr_mt + /// Takes ownership of the pointer and sets the reference count + /// to the pointer to 1. It is OK if the input pointer is null. + /// The shared reference count is allocated on the heap via operator new. + /// If an exception occurs during the allocation of the shared + /// reference count, the owned pointer is deleted and the exception + /// is rethrown. A null pointer is given a reference count of 1. + explicit shared_ptr_mt(T* pValue = 0) + : mpValue(pValue), mMutex() + { + // We don't lock our mutex in this function, as this is the constructor + // and we assume that construction is already done in a thread-safe way + // by the owner of this object. + #if defined(EA_COMPILER_NO_EXCEPTIONS) || defined(EA_COMPILER_NO_UNWIND) + mpRefCount = new reference_count_type(1); + #else + EA_DISABLE_VC_WARNING(4571) + try + { + mpRefCount = new reference_count_type(1); + } + catch(...) + { + delete pValue; + //mpRefCount = 0; shouldn't be necessary. + throw; + } + EA_RESTORE_VC_WARNING() + #endif + } + + /// shared_ptr_mt + /// Shares ownership of a pointer with another instance of shared_ptr_mt. + /// This function increments the shared reference count on the pointer. + shared_ptr_mt(shared_ptr_mt const& sharedPtr) + : mMutex() + { + // We don't lock our mutex in this function, as this is the constructor + // and we assume that construction is already done in a thread-safe way + // by the owner of this object. + sharedPtr.lock(); + mpValue = sharedPtr.mpValue; + mpRefCount = sharedPtr.mpRefCount; + mpRefCount->Increment(); // Atomic operation + sharedPtr.unlock(); + } + + // Temporarily disabled while we wait for compilers to modernize. + // + // shared_ptr_mt + // Constructs a shared_ptr_mt from a std::auto_ptr. This class + // transfers ownership of the pointer from the auto_ptr by + // calling its release function. + // If an exception occurs during the allocation of the shared + // reference count, the owned pointer is deleted and the exception + // is rethrown. + //explicit shared_ptr_mt(std::auto_ptr& autoPtr) + // : mMutex() + //{ + // // We don't lock our mutex in this function, as this is the constructor + // // and we assume that construction is already done in a thread-safe way + // // by the owner of this object. + // mpValue = autoPtr.release(); + // + // #if defined(EA_COMPILER_NO_EXCEPTIONS) || defined(EA_COMPILER_NO_UNWIND) + // mpRefCount = new reference_count_type(1); + // #else + // try + // { + // mpRefCount = new reference_count_type(1); + // } + // catch(...) + // { + // delete mpValue; + // mpValue = 0; + // //mpRefCount = 0; shouldn't be necessary. + // throw; + // } + // #endif + //} + + /// ~shared_ptr_mt + /// Decrements the reference count for the owned pointer. If the + /// reference count goes to zero, the owned pointer is deleted and + /// the shared reference count is deleted. + ~shared_ptr_mt() + { + lock(); + const reference_count_type newRefCount(mpRefCount->Decrement()); // Atomic operation + // EAT_ASSERT(newRefCount >= 0); + if(newRefCount == 0) + { + // we should only be deleting the pointer if it is not null. It is possible that the + // user has created a shared ptr without passing in a value. + if (mpValue) + delete mpValue; + delete mpRefCount; + } + unlock(); + } + + /// operator= + /// Copies another shared_ptr_mt to this object. Note that this object + /// may already own a shared pointer with another different pointer + /// (but still of the same type) before this call. In that case, + /// this function releases the old pointer, decrementing its reference + /// count and deleting it if zero, takes shared ownership of the new + /// pointer and increments its reference count. + shared_ptr_mt& operator=(shared_ptr_mt const& sharedPtr) + { + // We don't lock mutexes here because we let the swap function + // below do the locking and assignment. The if statement below + // isn't protected within a lock operation because it wouldn't + // help by being so because if mpValue is changing during the + // the execution of this function then the user has an external + // race condition that needs to be managed at that level. + if(mpValue != sharedPtr.mpValue) + { + // The easiest thing to do is to create a temporary and + // copy ourselves ourselves into it. This is a standard + // method for switching pointer ownership in systems like this. + shared_ptr_mt(sharedPtr).swap(*this); + } + return *this; + } + + // Temporarily disabled while we wait for compilers to modernize. + // + // operator= + // Transfers ownership of a std::auto_ptr to this class. + //shared_ptr_mt& operator=(std::auto_ptr& autoPtr) + //{ + // // We don't lock any mutexes here because we let the swap function do that. + // // EAT_ASSERT(mpValue != autoPtr.get()); + // shared_ptr_mt(autoPtr).swap(*this); + // return *this; + //} + + // operator= + // We do not defined this function in order to maintain compatibility + // with the currently proposed (2003) C++ standard addition. Use reset instead. + // shared_ptr_mt& operator=(T* pValue); + // { + // reset(pValue); + // return *this; + // } + + /// lock + /// @brief Locks our mutex for thread-safe access. + /// It is a const function because const-ness refers to the underlying pointer being + /// held and not this class. + void lock() const + { + mMutex.Lock(); + } + + /// unlock + /// @brief Unlocks our mutex which was previous locked. + /// It is a const function because const-ness refers to the underlying pointer being + /// held and not this class. + void unlock() const + { + mMutex.Unlock(); + } + + /// reset + /// Releases the owned pointer and takes ownership of the + /// passed in pointer. If the passed in pointer is the same + /// as the owned pointer, nothing is done. The passed in pointer + /// can be null, in which case the use count is set to 1. + void reset(T* pValue = 0) + { + // We don't lock any mutexes here because we let the swap function do that. + // We don't lock for the 'if' statement below because that wouldn't really buy anything. + if(pValue != mpValue) + { + // The easiest thing to do is to create a temporary and + // copy ourselves ourselves into it. This is a standard + // method for switching pointer ownership in systems like this. + shared_ptr_mt(pValue).swap(*this); + } + } + + /// swap + /// Exchanges the owned pointer beween two shared_ptr_mt objects. + void swap(shared_ptr_mt& sharedPtr) + { + lock(); + sharedPtr.lock(); + + // std::swap(mpValue, sharedPtr.mpValue); // Not used so that we can reduce a dependency. + T* const pValue = sharedPtr.mpValue; + sharedPtr.mpValue = mpValue; + mpValue = pValue; + + // std::swap(mpRefCount, sharedPtr.mpRefCount); // Not used so that we can reduce a dependency. + reference_count_type* const pRefCount = sharedPtr.mpRefCount; + sharedPtr.mpRefCount = mpRefCount; + mpRefCount = pRefCount; + + sharedPtr.unlock(); + unlock(); + } + + /// operator* + /// Returns the owner pointer dereferenced. + /// Example usage: + /// shared_ptr_mt ptr = new int(3); + /// int x = *ptr; + T& operator*() const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + // EAT_ASSERT(mpValue); + return *mpValue; + } + + /// operator-> + /// Allows access to the owned pointer via operator->() + /// Example usage: + /// struct X{ void DoSomething(); }; + /// shared_ptr_mt ptr = new X; + /// ptr->DoSomething(); + T* operator->() const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + // EAT_ASSERT(mpValue); + return mpValue; + } + + /// get + /// Returns the owned pointer. Note that this class does + /// not provide an operator T() function. This is because such + /// a thing (automatic conversion) is deemed unsafe. + /// Example usage: + /// struct X{ void DoSomething(); }; + /// shared_ptr_mt ptr = new X; + /// X* pX = ptr.get(); + /// pX->DoSomething(); + T* get() const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + return mpValue; + } + + /// use_count + /// Returns the reference count on the owned pointer. + /// The return value is one if the owned pointer is null. + int use_count() const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + // EAT_ASSERT(mpRefCount); + return (int)*mpRefCount; + } + + /// unique + /// Returns true if the reference count on the owned pointer is one. + /// The return value is true if the owned pointer is null. + bool unique() const + { + // We don't lock here because this is essentially a read operation. + // We don't put a SMP read barrier here because we assume the caller does such things. + // EAT_ASSERT(mpRefCount); + return (*mpRefCount == 1); + } + + /// add_ref + /// Manually increments the reference count on the owned pointer. + /// This is currently disabled because it isn't in part of the + /// proposed C++ language addition. + /// int add_ref() + /// { + /// lock(); + /// // EAT_ASSERT(mpRefCount); + /// ++*mpRefCount; // Atomic operation + /// unlock(); + /// } + + /// release_ref + /// Manually increments the reference count on the owned pointer. + /// If the reference count becomes zero, then the owned pointer + /// is deleted and reset(0) is called. For any given instance of + /// shared_ptr_mt, release_ref can only be called as many times as -- + /// but no more than -- the number of times add_ref was called + /// for that same shared_ptr_mt. Otherwise, separate instances of + /// shared_ptr_mt would be left with dangling owned pointer instances. + /// This is currently disabled because it isn't in part of the + /// proposed C++ language addition. + /// int release_ref() + /// { + /// lock(); + /// // EAT_ASSERT(mpRefCount); + /// if(*mpRefCount > 1){ + /// const int nReturnValue = --*mpRefCount; // Atomic operation + /// unlock(); + /// return nReturnValue; + /// } + /// reset(0); + /// unlock(); + /// return 0; + /// } + + /// Implicit operator bool + /// Allows for using a scoped_ptr as a boolean. + /// Example usage: + /// shared_ptr_mt ptr = new int(3); + /// if(ptr) + /// ++*ptr; + /// + /// Note that below we do not use operator bool(). The reason for this + /// is that booleans automatically convert up to short, int, float, etc. + /// The result is that this: if(scopedPtr == 1) would yield true (bad). + typedef T* (this_type::*bool_)() const; + operator bool_() const + { + // We don't lock here because this is essentially a read operation. + if(mpValue) + return &this_type::get; + return 0; + } + + /// operator! + /// This returns the opposite of operator bool; it returns true if + /// the owned pointer is null. Some compilers require this and some don't. + /// shared_ptr_mt ptr = new int(3); + /// if(!ptr) + /// EAT_ASSERT(false); + bool operator!() const + { + // We don't lock here because this is essentially a read operation. + return (mpValue == 0); + } + + }; // class shared_ptr_mt + + + /// get_pointer + /// returns shared_ptr_mt::get() via the input shared_ptr_mt. + template + inline T* get_pointer(const shared_ptr_mt& sharedPtr) + { + return sharedPtr.get(); + } + + /// swap + /// Exchanges the owned pointer beween two shared_ptr_mt objects. + /// This non-member version is useful for compatibility of shared_ptr_mt + /// objects with the C++ Standard Library and other libraries. + template + inline void swap(shared_ptr_mt& sharedPtr1, shared_ptr_mt& sharedPtr2) + { + sharedPtr1.swap(sharedPtr2); + } + + + /// operator!= + /// Compares two shared_ptr_mt objects for equality. Equality is defined as + /// being true when the pointer shared between two shared_ptr_mt objects is equal. + /// It is debatable what the appropriate definition of equality is between two + /// shared_ptr_mt objects, but we follow the current 2nd generation C++ standard proposal. + template + inline bool operator==(const shared_ptr_mt& sharedPtr1, const shared_ptr_mt& sharedPtr2) + { + // EAT_ASSERT((sharedPtr1.get() != sharedPtr2.get()) || (sharedPtr1.use_count() == sharedPtr2.use_count())); + return (sharedPtr1.get() == sharedPtr2.get()); + } + + + /// operator!= + /// Compares two shared_ptr_mt objects for inequality. Equality is defined as + /// being true when the pointer shared between two shared_ptr_mt objects is equal. + /// It is debatable what the appropriate definition of equality is between two + /// shared_ptr_mt objects, but we follow the current 2nd generation C++ standard proposal. + template + inline bool operator!=(const shared_ptr_mt& sharedPtr1, const shared_ptr_mt& sharedPtr2) + { + // EAT_ASSERT((sharedPtr1.get() != sharedPtr2.get()) || (sharedPtr1.use_count() == sharedPtr2.use_count())); + return (sharedPtr1.get() != sharedPtr2.get()); + } + + + /// operator< + /// Returns which shared_ptr_mt is 'less' than the other. Useful when storing + /// sorted containers of shared_ptr_mt objects. + template + inline bool operator<(const shared_ptr_mt& sharedPtr1, const shared_ptr_mt& sharedPtr2) + { + return (sharedPtr1.get() < sharedPtr2.get()); // Alternatively use: std::less(a.get(), b.get()); + } + + } // namespace Thread + +} // namespace EA + + + + +#endif // EATHREAD_SHARED_PTR_MT_H + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/version.h b/src/thirdparty/ea/EAThread/include/eathread/version.h new file mode 100644 index 00000000..3568b708 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/version.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_VERSION_H +#define EATHREAD_VERSION_H + + +#include + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + + + +namespace EA +{ + namespace Thread + { + /// Version contains the version of the library when it was built. + /// This can be used to verify the correct version has been linked + /// into the executable or loaded by the O/S (in the case of a DLL). + struct Version + { + int mMajor; + int mMinor; + int mPatch; + }; + + /// Get the library version information. + EATHREADLIB_API const Version *GetVersion(); + + /// Check that the linked/loaded library is the same as the headers + /// are expecting. + /// + /// If the version numbers passed to CheckVersion match those + /// built into the library when it was compiled, true is returned. + /// If not, false is returned. + EATHREADLIB_API bool CheckVersion(int majorVersion, int minorVersion, int patchVersion); + + } + +} + +#endif diff --git a/src/thirdparty/ea/EAThread/include/eathread/x86-64/eathread_atomic_x86-64.h b/src/thirdparty/ea/EAThread/include/eathread/x86-64/eathread_atomic_x86-64.h new file mode 100644 index 00000000..5e443fc1 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/x86-64/eathread_atomic_x86-64.h @@ -0,0 +1,456 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +///////////////////////////////////////////////////////////////////////////// +// Defines functionality for threadsafe primitive operations. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_X86_64_EATHREAD_ATOMIC_X86_64_H +#define EATHREAD_X86_64_EATHREAD_ATOMIC_X86_64_H + +#include "EABase/eabase.h" +#include +#include + + +#ifdef EA_COMPILER_MSVC + EA_DISABLE_ALL_VC_WARNINGS() + #include // VS2008 has an acknowledged bug that requires math.h (and possibly also string.h) to be #included before intrin.h. + #include + EA_RESTORE_ALL_VC_WARNINGS() +#endif + +EA_DISABLE_VC_WARNING(4146) // unary minus operator applied to unsigned type, result still unsigned + +#if defined(EA_PROCESSOR_X86_64) + + #define EA_THREAD_ATOMIC_IMPLEMENTED + + namespace EA + { + namespace Thread + { + /// + /// Non-member 128-bit Atomics implementation + /// + #if (EA_COMPILER_MSVC >= 1500) // VS2008+ + + #define EATHREAD_ATOMIC_128_SUPPORTED 1 + + // Algorithm for implementing an arbitrary atomic modification via AtomicCompareAndSwap: + // int128_t oldValue; + // + // do { + // oldValue = AtomicGetValue(dest); + // newValue = + // } while(!AtomicCompareAndSwap(dest, oldValue, newValue)); + + // The following function is a wrapper for the Microsoft _InterlockedCompareExchange128 function. + // Early versions of AMD 64-bit hardware do not support 128 bit atomics. To check for hardware support + // for the cmpxchg16b instruction, call the __cpuid intrinsic with InfoType=0x00000001 (standard function 1). + // Bit 13 of CPUInfo[2] (ECX) is 1 if the instruction is supported. + + inline bool AtomicSetValueConditionall28(volatile int64_t* dest128, const int64_t* value128, const int64_t* condition128) + { + __int64 conditionCopy[2] = { condition128[0], condition128[1] }; // We make a copy because Microsoft modifies the output, which is inconsistent with the rest of our atomic API. + return _InterlockedCompareExchange128(dest128, value128[1], value128[0], conditionCopy) == 1; // Question: Do we need to reverse the order of value128 if running on big-endian? Microsoft's documentation currently doesn't address this. + } + + inline bool AtomicSetValueConditionall28(volatile uint64_t* dest128, const uint64_t* value128, const uint64_t* condition128) + { + __int64 conditionCopy[2] = { (int64_t) condition128[0], (int64_t)condition128[1] }; // We make a copy because Microsoft modifies the output, which is inconsistent with the rest of our atomic API. + return _InterlockedCompareExchange128((volatile int64_t*)dest128, (int64_t)value128[1], (int64_t)value128[0], conditionCopy) == 1; // Question: Do we need to reverse the order of value128 if running on big-endian? Microsoft's documentation currently doesn't address this. + } + + #elif defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + + #if defined(EA_COMPILER_CLANG) || (defined(EA_COMPILER_GNUC) && EA_COMPILER_VERSION >= 4003) // GCC 4.3 or later for 128 bit atomics + + #define EATHREAD_ATOMIC_128_SUPPORTED 1 + + // GCC on x64 implements all of its __sync functions below via the cmpxchg16b instruction, + // usually in the form of a loop. + // Use of 128 bit atomics on GCC requires compiling with the -mcx16 compiler argument. + // See http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86_002d64-Options.html. + + inline __int128_t AtomicGetValue(volatile __int128_t* source) + { + return __sync_add_and_fetch(source, __int128_t(0)); // Is there a better way to do an atomic read? + } + + inline void AtomicSetValue(volatile __int128_t* dest, __int128_t value) + { + __sync_lock_test_and_set(dest, value); + } + + inline __int128_t AtomicIncrement(volatile __int128_t* dest) + { + return __sync_add_and_fetch(dest, __int128_t(1)); + } + + inline __int128_t AtomicDecrement(volatile __int128_t* dest) + { + return __sync_add_and_fetch(dest, __int128_t(-1)); + } + + inline __int128_t AtomicAdd(volatile __int128_t* dest, __int128_t value) + { + return __sync_add_and_fetch(dest, value); + } + + inline __int128_t AtomicOr(volatile __int128_t* dest, __int128_t value) + { + return __sync_or_and_fetch(dest, value); + } + + inline __int128_t AtomicAnd(volatile __int128_t* dest, __int128_t value) + { + return __sync_and_and_fetch(dest, value); + } + + inline __int128_t AtomicXor(volatile __int128_t* dest, __int128_t value) + { + return __sync_xor_and_fetch(dest, value); + } + + inline __int128_t AtomicSwap(volatile __int128_t* dest, __int128_t value) + { + return __sync_lock_test_and_set(dest, value); + } + + inline bool AtomicSetValueConditional(volatile __int128_t* dest, __int128_t value, __int128_t condition) + { + return __sync_bool_compare_and_swap(dest, condition, value); + } + + inline bool AtomicSetValueConditional(volatile __uint128_t* dest, __uint128_t value, __uint128_t condition) + { + return __sync_bool_compare_and_swap(dest, condition, value); + } + + // The following 64-bit-based 128 bit atomic is provided for compatibility with the Microsoft version. + // GCC supports the native __int128_t data type and thus can support a 128-bit-based 128 bit atomic. + + inline bool AtomicSetValueConditionall28(volatile int64_t* dest128, const int64_t* value128, const int64_t* condition128) + { + // Use of this requires compiling with the -mcx16 compiler argument. See http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86_002d64-Options.html. + return __sync_bool_compare_and_swap((volatile __int128_t*)dest128, *(volatile __int128_t*)condition128, *(volatile __int128_t*)value128); + } + + inline bool AtomicSetValueConditionall28(volatile uint64_t* dest128, const uint64_t* value128, const uint64_t* condition128) + { + // Use of this requires compiling with the -mcx16 compiler argument. See http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86_002d64-Options.html. + return __sync_bool_compare_and_swap((volatile __uint128_t*)dest128, *(volatile __uint128_t*)condition128, *(volatile __uint128_t*)value128); + } + + #endif + + #endif + + + + /// class AtomicInt + /// Actual implementation may vary per platform. May require certain alignments, sizes, + /// and declaration specifications per platform. + + template + class AtomicInt + { + public: + typedef AtomicInt ThisType; + typedef T ValueType; + + /// AtomicInt + /// Empty constructor. Intentionally leaves mValue in an unspecified state. + /// This is done so that an AtomicInt acts like a standard built-in integer. + AtomicInt() + {} + + AtomicInt(ValueType n) + { SetValue(n); } + + AtomicInt(const ThisType& x) + : mValue(x.GetValue()) {} + + AtomicInt& operator=(const ThisType& x) + { mValue = x.GetValue(); return *this; } + + ValueType GetValueRaw() const + { return mValue; } + + ValueType GetValue() const; + ValueType SetValue(ValueType n); + bool SetValueConditional(ValueType n, ValueType condition); + ValueType Increment(); + ValueType Decrement(); + ValueType Add(ValueType n); + + // operators + inline operator const ValueType() const { return GetValue(); } // Should this be provided? Is it safe enough? Return value of 'const' attempts to make this safe from misuse. + inline ValueType operator =(ValueType n) { SetValue(n); return n; } + inline ValueType operator+=(ValueType n) { return Add(n);} + inline ValueType operator-=(ValueType n) { return Add(-n);} + inline ValueType operator++() { return Increment();} + inline ValueType operator++(int) { return Increment() - 1;} + inline ValueType operator--() { return Decrement(); } + inline ValueType operator--(int) { return Decrement() + 1;} + + protected: + volatile ValueType mValue; + }; + + + #if defined(EA_COMPILER_MSVC) + #pragma intrinsic(_InterlockedExchange) + #pragma intrinsic(_InterlockedExchangeAdd) + #pragma intrinsic(_InterlockedCompareExchange) + #pragma intrinsic(_InterlockedIncrement) + #pragma intrinsic(_InterlockedDecrement) + #pragma intrinsic(_InterlockedExchange64) + #pragma intrinsic(_InterlockedExchangeAdd64) + #pragma intrinsic(_InterlockedCompareExchange64) + #pragma intrinsic(_InterlockedIncrement64) + #pragma intrinsic(_InterlockedDecrement64) + + // The following should work under any compiler, including such compilers as GCC under + // WINE or some other Win32 emulation. Win32 InterlockedXXX functions must exist on + // any system that supports the Windows API, be it 32 or 64 bit Windows. + + // 32 bit versions + template<> inline + AtomicInt::ValueType AtomicInt::GetValue() const + { return (ValueType)_InterlockedExchangeAdd((long*)&mValue, 0); } // We shouldn't need to do this, as far as I know, given the x86 architecture. + + template<> inline + AtomicInt::ValueType AtomicInt::GetValue() const + { return (ValueType)_InterlockedExchangeAdd((long*)&mValue, 0); } // We shouldn't need to do this, as far as I know, given the x86 architecture. + + template<> inline + AtomicInt::ValueType AtomicInt::SetValue(ValueType n) + { return (ValueType)_InterlockedExchange((long*)&mValue, (long)n); } // Even though we shouldn't need to use _InterlockedExchange on x86, the intrinsic x86 _InterlockedExchange is at least as fast as C code we would otherwise put here. + + template<> inline + AtomicInt::ValueType AtomicInt::SetValue(ValueType n) + { return (ValueType)_InterlockedExchange((long*)&mValue, (long)n); } // Even though we shouldn't need to use _InterlockedExchange on x86, the intrinsic x86 _InterlockedExchange is at least as fast as C code we would otherwise put here. + + template<> inline + bool AtomicInt::SetValueConditional(ValueType n, ValueType condition) + { return ((ValueType)_InterlockedCompareExchange((long*)&mValue, (long)n, (long)condition) == condition); } + + template<> inline + bool AtomicInt::SetValueConditional(ValueType n, ValueType condition) + { return ((ValueType)_InterlockedCompareExchange((long*)&mValue, (long)n, (long)condition) == condition); } + + template<> inline + AtomicInt::ValueType AtomicInt::Increment() + { return (ValueType)_InterlockedIncrement((long*)&mValue); } + + template<> inline + AtomicInt::ValueType AtomicInt::Increment() + { return (ValueType)_InterlockedIncrement((long*)&mValue); } + + template<> inline + AtomicInt::ValueType AtomicInt::Decrement() + { return (ValueType)_InterlockedDecrement((long*)&mValue); } + + template<> inline + AtomicInt::ValueType AtomicInt::Decrement() + { return (ValueType)_InterlockedDecrement((long*)&mValue); } + + template<> inline + AtomicInt::ValueType AtomicInt::Add(ValueType n) + { return ((ValueType)_InterlockedExchangeAdd((long*)&mValue, (long)n) + n); } + + template<> inline + AtomicInt::ValueType AtomicInt::Add(ValueType n) + { return ((ValueType)_InterlockedExchangeAdd((long*)&mValue, (long)n) + n); } + + + + // 64 bit versions + template<> inline + AtomicInt::ValueType AtomicInt::GetValue() const + { return (ValueType)_InterlockedExchangeAdd64((__int64*)&mValue, 0); } // We shouldn't need to do this, as far as I know, given the x86 architecture. + + template<> inline + AtomicInt::ValueType AtomicInt::GetValue() const + { return (ValueType)_InterlockedExchangeAdd64((__int64*)&mValue, 0); } // We shouldn't need to do this, as far as I know, given the x86 architecture. + + template<> inline + AtomicInt::ValueType AtomicInt::SetValue(ValueType n) + { return (ValueType)_InterlockedExchange64((__int64*)&mValue, (__int64)n); } // Even though we shouldn't need to use _InterlockedExchange on x86, the intrinsic x86 _InterlockedExchange is at least as fast as C code we would otherwise put here. + + template<> inline + AtomicInt::ValueType AtomicInt::SetValue(ValueType n) + { return (ValueType)_InterlockedExchange64((__int64*)&mValue, (__int64)n); } // Even though we shouldn't need to use _InterlockedExchange on x86, the intrinsic x86 _InterlockedExchange is at least as fast as C code we would otherwise put here. + + template<> inline + bool AtomicInt::SetValueConditional(ValueType n, ValueType condition) + { return ((ValueType)_InterlockedCompareExchange64((__int64*)&mValue, (__int64)n, (__int64)condition) == condition); } + + template<> inline + bool AtomicInt::SetValueConditional(ValueType n, ValueType condition) + { return ((ValueType)_InterlockedCompareExchange64((__int64*)&mValue, (__int64)n, (__int64)condition) == condition); } + + template<> inline + AtomicInt::ValueType AtomicInt::Increment() + { return (ValueType)_InterlockedIncrement64((__int64*)&mValue); } + + template<> inline + AtomicInt::ValueType AtomicInt::Increment() + { return (ValueType)_InterlockedIncrement64((__int64*)&mValue); } + + template<> inline + AtomicInt::ValueType AtomicInt::Decrement() + { return (ValueType)_InterlockedDecrement64((__int64*)&mValue); } + + template<> inline + AtomicInt::ValueType AtomicInt::Decrement() + { return (ValueType)_InterlockedDecrement64((__int64*)&mValue); } + + template<> inline + AtomicInt::ValueType AtomicInt::Add(ValueType n) + { return ((ValueType)_InterlockedExchangeAdd64((__int64*)&mValue, (__int64)n) + n); } + + template<> inline + AtomicInt::ValueType AtomicInt::Add(ValueType n) + { return ((ValueType)_InterlockedExchangeAdd64((__int64*)&mValue, (__int64)n) + n); } + + + #elif defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + + // Recent versions of GCC have atomic primitives built into the compiler and standard library. + #if defined(EA_COMPILER_CLANG) || (defined(EA_COMPILER_GNUC) && EA_COMPILER_VERSION >= 4001) // GCC 4.1 or later + + template <> inline + AtomicInt::ValueType AtomicInt::GetValue() const + { return __sync_add_and_fetch(const_cast(&mValue), 0); } + + template <> inline + AtomicInt::ValueType AtomicInt::GetValue() const + { return __sync_add_and_fetch(const_cast(&mValue), 0); } + + template <> inline + AtomicInt::ValueType AtomicInt::SetValue(ValueType n) + { __sync_synchronize(); return __sync_lock_test_and_set(&mValue, n); } + + template <> inline + AtomicInt::ValueType AtomicInt::SetValue(ValueType n) + { __sync_synchronize(); return __sync_lock_test_and_set(&mValue, n); } + + template <> inline + bool AtomicInt::SetValueConditional(ValueType n, ValueType condition) + { return (__sync_val_compare_and_swap(&mValue, condition, n) == condition); } + + template <> inline + bool AtomicInt::SetValueConditional(ValueType n, ValueType condition) + { return (__sync_val_compare_and_swap(&mValue, condition, n) == condition); } + + template <> inline + AtomicInt::ValueType AtomicInt::Increment() + { return __sync_add_and_fetch(&mValue, 1); } + + template <> inline + AtomicInt::ValueType AtomicInt::Increment() + { return __sync_add_and_fetch(&mValue, 1); } + + template <> inline + AtomicInt::ValueType AtomicInt::Decrement() + { return __sync_sub_and_fetch(&mValue, 1); } + + template <> inline + AtomicInt::ValueType AtomicInt::Decrement() + { return __sync_sub_and_fetch(&mValue, 1); } + + template <> inline + AtomicInt::ValueType AtomicInt::Add(ValueType n) + { return __sync_add_and_fetch(&mValue, n); } + + template <> inline + AtomicInt::ValueType AtomicInt::Add(ValueType n) + { return __sync_add_and_fetch(&mValue, n); } + + + + template <> inline + AtomicInt::ValueType AtomicInt::GetValue() const + { return __sync_add_and_fetch(const_cast(&mValue), 0); } + + template <> inline + AtomicInt::ValueType AtomicInt::GetValue() const + { return __sync_add_and_fetch(const_cast(&mValue), 0); } + + template <> inline + AtomicInt::ValueType AtomicInt::SetValue(ValueType n) + { __sync_synchronize(); return __sync_lock_test_and_set(&mValue, n); } + + template <> inline + AtomicInt::ValueType AtomicInt::SetValue(ValueType n) + { __sync_synchronize(); return __sync_lock_test_and_set(&mValue, n); } + + template <> inline + bool AtomicInt::SetValueConditional(ValueType n, ValueType condition) + { return (__sync_val_compare_and_swap(&mValue, condition, n) == condition); } + + template <> inline + bool AtomicInt::SetValueConditional(ValueType n, ValueType condition) + { return (__sync_val_compare_and_swap(&mValue, condition, n) == condition); } + + template <> inline + AtomicInt::ValueType AtomicInt::Increment() + { return __sync_add_and_fetch(&mValue, 1); } + + template <> inline + AtomicInt::ValueType AtomicInt::Increment() + { return __sync_add_and_fetch(&mValue, 1); } + + template <> inline + AtomicInt::ValueType AtomicInt::Decrement() + { return __sync_sub_and_fetch(&mValue, 1); } + + template <> inline + AtomicInt::ValueType AtomicInt::Decrement() + { return __sync_sub_and_fetch(&mValue, 1); } + + template <> inline + AtomicInt::ValueType AtomicInt::Add(ValueType n) + { return __sync_add_and_fetch(&mValue, n); } + + template <> inline + AtomicInt::ValueType AtomicInt::Add(ValueType n) + { return __sync_add_and_fetch(&mValue, n); } + + #endif // GCC 4.1 or later + + #endif // GCC + + } // namespace Thread + + + } // namespace EA + + +#endif // EA_PROCESSOR_X86_64 + +EA_RESTORE_VC_WARNING() + +#endif // EATHREAD_X86_64_EATHREAD_ATOMIC_X86_64_H + + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/include/eathread/x86-64/eathread_sync_x86-64.h b/src/thirdparty/ea/EAThread/include/eathread/x86-64/eathread_sync_x86-64.h new file mode 100644 index 00000000..45d546f5 --- /dev/null +++ b/src/thirdparty/ea/EAThread/include/eathread/x86-64/eathread_sync_x86-64.h @@ -0,0 +1,108 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. +#endif + +///////////////////////////////////////////////////////////////////////////// +// Functionality related to memory and code generation synchronization. +///////////////////////////////////////////////////////////////////////////// + + +#ifndef EATHREAD_X86_64_EATHREAD_SYNC_X86_64_H +#define EATHREAD_X86_64_EATHREAD_SYNC_X86_64_H + + +#ifndef INCLUDED_eabase_H + #include "EABase/eabase.h" +#endif + + +#if defined(EA_PROCESSOR_X86_64) + #define EA_THREAD_SYNC_IMPLEMENTED + + #ifdef EA_COMPILER_MSVC + EA_DISABLE_ALL_VC_WARNINGS() + #include // VS2008 has an acknowledged bug that requires math.h (and possibly also string.h) to be #included before intrin.h. + #include + EA_RESTORE_ALL_VC_WARNINGS() + #endif + + // By default, we define EA_TARGET_SMP to be true. The reason for this is that most + // applications that users of this code are likely to write are going to be executables + // which run properly on any system, be it multiprocessing or not. + #ifndef EA_TARGET_SMP + #define EA_TARGET_SMP 1 + #endif + + // EAProcessorPause + // Intel has defined a 'pause' instruction for x86 processors starting with the P4, though this simply + // maps to the otherwise undocumented 'rep nop' instruction. This pause instruction is important for + // high performance spinning, as otherwise a high performance penalty incurs. + + #if defined(EA_COMPILER_MSVC) || defined(EA_COMPILER_INTEL) || defined(EA_COMPILER_BORLAND) + // Year 2003+ versions of the Microsoft SDK define 'rep nop' as YieldProcessor and/or __yield or _mm_pause. + #pragma intrinsic(_mm_pause) + #define EAProcessorPause() _mm_pause() // The __yield() intrinsic currently doesn't work on x86-64. + #elif defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + #define EAProcessorPause() __asm__ __volatile__ ("rep ; nop") + #else + // In this case we use an Intel-style asm statement. If this doesn't work for your compiler then + // there most likely is some way to make the `rep nop` inline asm statement. + #define EAProcessorPause() __asm { rep nop } // Alternatively: { __asm { _emit 0xf3 }; __asm { _emit 0x90 } } + #endif + + + // EAReadBarrier / EAWriteBarrier / EAReadWriteBarrier + // The x86 processor memory architecture ensures read and write consistency on both single and + // multi processing systems. This makes programming simpler but limits maximimum system performance. + // We define EAReadBarrier here to be the same as EACompilerMemory barrier in order to limit the + // compiler from making any assumptions at its level about memory usage. Year 2003+ versions of the + // Microsoft SDK define a 'MemoryBarrier' statement which has the same effect as EAReadWriteBarrier. + #if defined(EA_COMPILER_MSVC) + #pragma intrinsic(_ReadBarrier) + #pragma intrinsic(_WriteBarrier) + #pragma intrinsic(_ReadWriteBarrier) + + #define EAReadBarrier() _ReadBarrier() + #define EAWriteBarrier() _WriteBarrier() + #define EAReadWriteBarrier() _ReadWriteBarrier() + #elif defined(EA_PLATFORM_PS4) + #define EAReadBarrier() __asm__ __volatile__ ("lfence" ::: "memory"); + #define EAWriteBarrier() __asm__ __volatile__ ("sfence" ::: "memory"); + #define EAReadWriteBarrier() __asm__ __volatile__ ("mfence" ::: "memory"); + #elif defined(__GNUC__) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 401) // GCC 4.1 or later, includes clang + #define EAReadBarrier __sync_synchronize + #define EAWriteBarrier __sync_synchronize + #define EAReadWriteBarrier __sync_synchronize + #else + #define EAReadBarrier EACompilerMemoryBarrier // Need to implement this for non-VC++ + #define EAWriteBarrier EACompilerMemoryBarrier // Need to implement this for non-VC++ + #define EAReadWriteBarrier EACompilerMemoryBarrier // Need to implement this for non-VC++ + #endif + + + // EACompilerMemoryBarrier + #if defined(EA_COMPILER_MSVC) + #define EACompilerMemoryBarrier() _ReadWriteBarrier() + #elif defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + #define EACompilerMemoryBarrier() __asm__ __volatile__ ("":::"memory") + #else + #define EACompilerMemoryBarrier() // Possibly `EAT_ASSERT(false)` here? + #endif + + +#endif // EA_PROCESSOR_X86 + + +#endif // EATHREAD_X86_64_EATHREAD_SYNC_X86_64_H + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/cpp11/eathread_cpp11.cpp b/src/thirdparty/ea/EAThread/source/cpp11/eathread_cpp11.cpp new file mode 100644 index 00000000..b781961a --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/cpp11/eathread_cpp11.cpp @@ -0,0 +1,217 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include + +#include "eathread/eathread.h" +#include "eathread/eathread_thread.h" + +#include +#include +#include + +namespace EA +{ + namespace Thread + { + EA::Thread::AssertionFailureFunction gpAssertionFailureFunction = NULL; + void* gpAssertionFailureContext = NULL; + + EATHREADLIB_API ThreadId EA::Thread::GetThreadId() + { + return std::this_thread::get_id(); + } + + EATHREADLIB_API ThreadId EA::Thread::GetThreadId(EA::Thread::SysThreadId id) + { + EAThreadDynamicData* const pTDD = EA::Thread::FindThreadDynamicData(id); + if(pTDD) + { + return pTDD->mpComp->mThread.get_id(); + } + + return EA::Thread::kThreadIdInvalid; + } + + EATHREADLIB_API SysThreadId EA::Thread::GetSysThreadId(ThreadId threadId) + { + EAThreadDynamicData* tdd = EA::Thread::FindThreadDynamicData(threadId); + if (tdd && tdd->mpComp) + return tdd->mpComp->mThread.native_handle(); + + ThreadId threadIdCurrent = GetThreadId(); + if(threadId == threadIdCurrent) + { + #if defined(EA_PLATFORM_MICROSOFT) + std::thread::id stdId = std::this_thread::get_id(); + EAT_COMPILETIME_ASSERT(sizeof(_Thrd_t) == sizeof(std::thread::id)); + return ((_Thrd_t&)stdId)._Hnd; + #elif EA_POSIX_THREADS_AVAILABLE && defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) + std::thread::id stdId = std::this_thread::get_id(); + EAT_COMPILETIME_ASSERT(sizeof(_Thrd_t) == sizeof(std::thread::id)); + return reinterpret_cast<_Thrd_t>(stdId); + #else + #error Platform not supported yet. + #endif + } + + EAT_ASSERT_MSG(false, "Failed to find associated EAThreadDynamicData for this thread.\n"); + return SysThreadId(); + } + + EATHREADLIB_API SysThreadId EA::Thread::GetSysThreadId() + { + // There currently isn't a means to directly get the current SysThreadId, so we do it indirectly: + return GetSysThreadId(std::this_thread::get_id()); + } + + EATHREADLIB_API ThreadTime EA::Thread::GetThreadTime() + { + using namespace std::chrono; + auto nowMs = duration_cast(system_clock::now().time_since_epoch()); + return nowMs.count(); + } + + EATHREADLIB_API int GetThreadPriority() + { + // No way to query or set thread priority through standard C++11 thread library. + // On some platforms this could be implemented through platform specific APIs + return kThreadPriorityDefault; + } + + EATHREADLIB_API bool SetThreadPriority(int nPriority) + { + // No way to query or set thread priority through standard C++11 thread library. + // On some platforms this could be implemented through platform specific APIs + return false; + } + + EATHREADLIB_API void SetThreadProcessor(int nProcessor) + { + // No way to query or set thread processor through standard C++11 thread library. + // On some platforms this could be implemented through platform specific APIs + } + + EATHREADLIB_API int GetThreadProcessor() + { + // No way to query or set thread processor through standard C++11 thread library. + // On some platforms this could be implemented through platform specific APIs + return 0; + } + + EATHREADLIB_API int GetProcessorCount() + { + return static_cast(std::thread::hardware_concurrency()); + } + + EATHREADLIB_API void ThreadSleep(const ThreadTime& timeRelative) + { + std::this_thread::sleep_for(std::chrono::milliseconds(timeRelative)); + } + + void ThreadEnd(intptr_t threadReturnValue) + { + // No way to end a thread through standard C++11 thread library. + // On some platforms this could be implemented through platform specific APIs + EAT_ASSERT_MSG(false, "ThreadEnd is not implemented for C++11 threads.\n"); + } + + EATHREADLIB_API void EA::Thread::SetThreadAffinityMask(const EA::Thread::ThreadId& id, ThreadAffinityMask nAffinityMask) + { + // Update the affinity mask in the thread dynamic data cache. + EAThreadDynamicData* const pTDD = FindThreadDynamicData(id); + if(pTDD) + { + pTDD->mnThreadAffinityMask = nAffinityMask; + } + + #if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED + // Call the Windows library function. + #endif + } + + EATHREADLIB_API EA::Thread::ThreadAffinityMask EA::Thread::GetThreadAffinityMask(const EA::Thread::ThreadId& id) + { + // Update the affinity mask in the thread dynamic data cache. + EAThreadDynamicData* const pTDD = FindThreadDynamicData(id); + if(pTDD) + { + return pTDD->mnThreadAffinityMask; + } + + return kThreadAffinityMaskAny; + } + + EATHREADLIB_API void SetAssertionFailureFunction(AssertionFailureFunction pAssertionFailureFunction, void* pContext) + { + gpAssertionFailureFunction = pAssertionFailureFunction; + gpAssertionFailureContext = pContext; + } + + EATHREADLIB_API void AssertionFailure(const char* pExpression) + { + if(gpAssertionFailureFunction) + gpAssertionFailureFunction(pExpression, gpAssertionFailureContext); + } + + void* GetThreadStackBase() + { + return nullptr; + } + + // This can be removed once all remaining synchronization primitives are implemented in terms of C++11 APIs + uint32_t EA::Thread::RelativeTimeoutFromAbsoluteTimeout(ThreadTime timeoutAbsolute) + { + EAT_ASSERT((timeoutAbsolute == kTimeoutImmediate) || (timeoutAbsolute > EATHREAD_MIN_ABSOLUTE_TIME)); // Assert that the user didn't make the mistake of treating time as relative instead of absolute. + + DWORD timeoutRelative = 0; + + if (timeoutAbsolute == kTimeoutNone) + { + timeoutRelative = 0xffffffff; + } + else if (timeoutAbsolute == kTimeoutImmediate) + { + timeoutRelative = 0; + } + else + { + ThreadTime timeCurrent(GetThreadTime()); + timeoutRelative = (timeoutAbsolute > timeCurrent) ? static_cast(timeoutAbsolute - timeCurrent) : 0; + } + + EAT_ASSERT((timeoutRelative == 0xffffffff) || (timeoutRelative < 100000000)); // Assert that the timeout is a sane value and didn't wrap around. + + return timeoutRelative; + } + + // Implement native_handle_type comparison as a memcmp() - may need platform specific implementations on some future platforms. + bool Equals(const SysThreadId& a, const SysThreadId& b) + { + static_assert((std::is_fundamental::value || std::is_pointer::value || std::is_pod::value), + "SysThreadId should be comparable using memcmp()"); + return memcmp(&a, &b, sizeof(SysThreadId)) == 0; + } + + namespace detail + { + // Override the default EAThreadToString implementation + #define EAThreadIdToString_CUSTOM_IMPLEMENTATION + ThreadIdToStringBuffer::ThreadIdToStringBuffer(EA::Thread::ThreadId threadId) + { + std::stringstream formatStream; + formatStream << threadId; + strncpy(mBuf, formatStream.str().c_str(), BufSize - 1); + mBuf[BufSize - 1] = '\0'; + } + + SysThreadIdToStringBuffer::SysThreadIdToStringBuffer(EA::Thread::SysThreadId sysThreadId) + { + strncpy(mBuf, "Unknown", BufSize - 1); + mBuf[BufSize - 1] = '\0'; + } + } + } +} + diff --git a/src/thirdparty/ea/EAThread/source/cpp11/eathread_mutex_cpp11.cpp b/src/thirdparty/ea/EAThread/source/cpp11/eathread_mutex_cpp11.cpp new file mode 100644 index 00000000..f3182e52 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/cpp11/eathread_mutex_cpp11.cpp @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "eathread/eathread_mutex.h" + +EAMutexData::EAMutexData() : mnLockCount(0) {} + +EA::Thread::MutexParameters::MutexParameters(bool /*bIntraProcess*/, const char* pName) +{ + if(pName) + { + strncpy(mName, pName, sizeof(mName)-1); + mName[sizeof(mName)-1] = 0; + } + else + { + mName[0] = 0; + } +} + +EA::Thread::Mutex::Mutex(const MutexParameters* pMutexParameters, bool bDefaultParameters) +{ + if(!pMutexParameters && bDefaultParameters) + { + MutexParameters parameters; + Init(¶meters); + } + else + { + Init(pMutexParameters); + } +} + +EA::Thread::Mutex::~Mutex() +{ + EAT_ASSERT(mMutexData.mnLockCount == 0); +} + +bool EA::Thread::Mutex::Init(const MutexParameters* pMutexParameters) +{ + if (pMutexParameters) + { + mMutexData.mnLockCount = 0; + return true; + } + return false; +} + +int EA::Thread::Mutex::Lock(const ThreadTime& timeoutAbsolute) +{ + if (timeoutAbsolute == kTimeoutNone) + { + mMutexData.mMutex.lock(); + } + else + { + std::chrono::milliseconds timeoutAbsoluteMs(timeoutAbsolute); + std::chrono::time_point timeout_time(timeoutAbsoluteMs); + if (!mMutexData.mMutex.try_lock_until(timeout_time)) + { + return kResultTimeout; + } + } + + EAT_ASSERT((mMutexData.mThreadId = EA::Thread::GetThreadId()) != kThreadIdInvalid); + EAT_ASSERT(mMutexData.mnLockCount >= 0); + + return ++mMutexData.mnLockCount; // This is safe to do because we have the lock. +} + +int EA::Thread::Mutex::Unlock() +{ + EAT_ASSERT(mMutexData.mThreadId == EA::Thread::GetThreadId()); + EAT_ASSERT(mMutexData.mnLockCount > 0); + + const int nReturnValue(--mMutexData.mnLockCount); // This is safe to do because we have the lock. + mMutexData.mMutex.unlock(); + return nReturnValue; +} + +int EA::Thread::Mutex::GetLockCount() const +{ + return mMutexData.mnLockCount; +} + +bool EA::Thread::Mutex::HasLock() const +{ +#if EAT_ASSERT_ENABLED + return (mMutexData.mnLockCount > 0) && (mMutexData.mThreadId == EA::Thread::GetThreadId()); +#else + return (mMutexData.mnLockCount > 0); // This is the best we can do, though it is of limited use, since it doesn't tell you if you are the thread with the lock. +#endif +} + + + diff --git a/src/thirdparty/ea/EAThread/source/cpp11/eathread_semaphore_cpp11.cpp b/src/thirdparty/ea/EAThread/source/cpp11/eathread_semaphore_cpp11.cpp new file mode 100644 index 00000000..79b7d554 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/cpp11/eathread_semaphore_cpp11.cpp @@ -0,0 +1,5 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "eathread/eathread_semaphore.h" diff --git a/src/thirdparty/ea/EAThread/source/cpp11/eathread_thread_cpp11.cpp b/src/thirdparty/ea/EAThread/source/cpp11/eathread_thread_cpp11.cpp new file mode 100644 index 00000000..53211070 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/cpp11/eathread_thread_cpp11.cpp @@ -0,0 +1,488 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "eathread/eathread_thread.h" +#include "eathread/eathread.h" +#include "eathread/eathread_sync.h" +#include "eathread/eathread_callstack.h" +#include "eathread/internal/eathread_global.h" + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + + static AtomicInt32 nLastProcessor = 0; + const size_t kMaxThreadDynamicDataCount = 128; + + struct EAThreadGlobalVars + { + char gThreadDynamicData[kMaxThreadDynamicDataCount][sizeof(EAThreadDynamicData)]; + AtomicInt32 gThreadDynamicDataAllocated[kMaxThreadDynamicDataCount]; + Mutex gThreadDynamicMutex; + }; + EATHREAD_GLOBALVARS_CREATE_INSTANCE; + + EAThreadDynamicData* AllocateThreadDynamicData() + { + for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i) + { + if (EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[i].SetValueConditional(1, 0)) + return (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + } + + // This is a safety fallback mechanism. In practice it won't be used in almost all situations. + if (gpAllocator) + return (EAThreadDynamicData*)gpAllocator->Alloc(sizeof(EAThreadDynamicData)); + + return nullptr; + } + + void FreeThreadDynamicData(EAThreadDynamicData* pEAThreadDynamicData) + { + pEAThreadDynamicData->~EAThreadDynamicData(); + if ((pEAThreadDynamicData >= (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData) && (pEAThreadDynamicData < ((EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData + kMaxThreadDynamicDataCount))) + { + EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[pEAThreadDynamicData - (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData].SetValue(0); + } + else + { + // Assume the data was allocated via the fallback mechanism. + if (gpAllocator) + { + gpAllocator->Free(pEAThreadDynamicData); + } + } + } + + EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId) + { + for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i) + { + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + if (pTDD->mpComp && pTDD->mpComp->mThread.get_id() == threadId) + return pTDD; + } + return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved. + } + + EAThreadDynamicData* FindThreadDynamicData(EA::Thread::ThreadUniqueId threadId) + { + for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i) + { + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + if (pTDD->mUniqueThreadId == threadId) + return pTDD; + } + return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved. + } + + EAThreadDynamicData* FindThreadDynamicData(EA::Thread::SysThreadId sysThreadId) + { + for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i) + { + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + if (pTDD->mpComp && pTDD->mpComp->mThread.native_handle() == sysThreadId) + return pTDD; + } + + // NOTE: This function does not support finding externally created threads due to limitations in the CPP11 std::thread API. + // At the time of writing, it is not possible to retrieve the thread object of a thread not created by the CPP11 API. + return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved. + } + } +} + +EA_DISABLE_VC_WARNING(4355) // this used in base member initializer list - should be safe in this context +EAThreadDynamicData::EAThreadDynamicData(void* userFunc, void* userContext, void* userWrapperFunc, ThreadFunc threadFunc) : + mnRefCount(2), // Init ref count to 2, one corresponding release happens on threadFunc exit and the other when Thread class is destroyed or Begin is called again + mStatus(EA::Thread::Thread::kStatusNone), + mpComp(nullptr) +{ + mpComp = new EAThreadComposite(); + + if(mpComp) + mpComp->mThread = std::thread(threadFunc, this, userFunc, userContext, userWrapperFunc); // This doesn't spawn CPP11 threads when created within the EAThreadComposite constructor. +} + + +EAThreadDynamicData::EAThreadDynamicData(EA::Thread::ThreadUniqueId uniqueThreadId, const char* pThreadName) : + mnRefCount(2), // Init ref count to 2, one corresponding release happens on threadFunc exit and the other when Thread class is destroyed or Begin is called again + mStatus(EA::Thread::Thread::kStatusNone), + mpComp(nullptr), + mUniqueThreadId(uniqueThreadId) +{ + strncpy(mName, pThreadName, EATHREAD_NAME_SIZE); + mName[EATHREAD_NAME_SIZE - 1] = 0; +} + +EA_RESTORE_VC_WARNING() + + +EAThreadDynamicData::~EAThreadDynamicData() +{ + if (mpComp->mThread.joinable()) + mpComp->mThread.detach(); + + if(mpComp) + delete mpComp; + + mpComp = nullptr; + + // the threads, promises, and futures in this class will + // allocate memory with the Concurrency runtime new/delete operators. + // If you're crashing in here with access violations on process exit, + // then you likely have a static instance of EA::Thread::Thread somewhere + // that's being destructed after your memory system is uninitialized + // leaving dangling pointers to bad memory. Attempt to change + // these static instances to be constructed/destructed with the scope + // of normal app operation. +} + + +void EAThreadDynamicData::AddRef() +{ + mnRefCount.Increment(); +} + + +void EAThreadDynamicData::Release() +{ + if(mnRefCount.Decrement() == 0) + EA::Thread::FreeThreadDynamicData(this); +} + +namespace EA +{ + namespace Thread + { + ThreadParameters::ThreadParameters() : + mpStack(NULL), + mnStackSize(0), + mnPriority(kThreadPriorityDefault), + mnProcessor(kProcessorDefault), + mpName(""), + mbDisablePriorityBoost(false) + { + } + + RunnableFunctionUserWrapper Thread::sGlobalRunnableFunctionUserWrapper = NULL; + RunnableClassUserWrapper Thread::sGlobalRunnableClassUserWrapper = NULL; + AtomicInt32 Thread::sDefaultProcessor = kProcessorAny; + + RunnableFunctionUserWrapper Thread::GetGlobalRunnableFunctionUserWrapper() + { + return sGlobalRunnableFunctionUserWrapper; + } + + void Thread::SetGlobalRunnableFunctionUserWrapper(RunnableFunctionUserWrapper pUserWrapper) + { + if (sGlobalRunnableFunctionUserWrapper != NULL) + { + // Can only be set once in entire game. + EAT_ASSERT(false); + } + else + { + sGlobalRunnableFunctionUserWrapper = pUserWrapper; + } + } + + RunnableClassUserWrapper Thread::GetGlobalRunnableClassUserWrapper() + { + return sGlobalRunnableClassUserWrapper; + } + + void Thread::SetGlobalRunnableClassUserWrapper(RunnableClassUserWrapper pUserWrapper) + { + if (sGlobalRunnableClassUserWrapper != NULL) + { + // Can only be set once in entire game. + EAT_ASSERT(false); + } + else + { + sGlobalRunnableClassUserWrapper = pUserWrapper; + } + } + + Thread::Thread() + { + mThreadData.mpData = NULL; + } + + + Thread::Thread(const Thread& t) : + mThreadData(t.mThreadData) + { + if (mThreadData.mpData) + mThreadData.mpData->AddRef(); + } + + + Thread& Thread::operator=(const Thread& t) + { + // We don't synchronize access to mpData; we assume that the user + // synchronizes it or this Thread instances is used from a single thread. + if (t.mThreadData.mpData) + t.mThreadData.mpData->AddRef(); + + if (mThreadData.mpData) + mThreadData.mpData->Release(); + + mThreadData = t.mThreadData; + + return *this; + } + + + Thread::~Thread() + { + // We don't synchronize access to mpData; we assume that the user + // synchronizes it or this Thread instances is used from a single thread. + if (mThreadData.mpData) + mThreadData.mpData->Release(); + } + + static void RunnableFunctionInternal(EAThreadDynamicData* tdd, void* userFunc, void* userContext, void* userWrapperFunc) + { + tdd->mStatus = Thread::kStatusRunning; + tdd->mpStackBase = EA::Thread::GetStackBase(); + RunnableFunction pFunction = (RunnableFunction)userFunc; + + if (userWrapperFunc) + { + RunnableFunctionUserWrapper pWrapperFunction = (RunnableFunctionUserWrapper)userWrapperFunc; + // if user wrapper is specified, call user wrapper and pass down the pFunction and pContext + tdd->mpComp->mReturnPromise.set_value(pWrapperFunction(pFunction, userContext)); + } + else + { + tdd->mpComp->mReturnPromise.set_value(pFunction(userContext)); + } + + tdd->mStatus = Thread::kStatusEnded; + tdd->Release(); // Matches an implicit AddRef in EAThreadDynamicData constructor + } + + + ThreadId Thread::Begin(RunnableFunction pFunction, void* pContext, const ThreadParameters* pTP, RunnableFunctionUserWrapper pUserWrapper) + { + // Check there is an entry for the current thread context in our ThreadDynamicData array. + ThreadUniqueId threadUniqueId; + EAThreadGetUniqueId(threadUniqueId); + if(!FindThreadDynamicData(threadUniqueId)) + { + EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData(threadUniqueId, "external"); + if(pData) + { + pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread. + // Do no AddRef for thread execution because this is not an EAThread managed thread. + } + } + + if (mThreadData.mpData) + mThreadData.mpData->Release(); // Matches an implicit AddRef in EAThreadDynamicData constructor + + // C++11 Threads don't support user-supplied stacks. A user-supplied stack pointer + // here would be a waste of user memory, and so we assert that mpStack == NULL. + EAT_ASSERT(!pTP || (pTP->mpStack == NULL)); + + // We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be + // modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed + // during execution. + EAThreadDynamicData* pDataAddr = AllocateThreadDynamicData(); + EAT_ASSERT(pDataAddr != nullptr); + EAThreadDynamicData* pData = new(pDataAddr) EAThreadDynamicData(pFunction, pContext, pUserWrapper, RunnableFunctionInternal); // Note that we use a special new here which doesn't use the heap. + EAT_ASSERT(pData != nullptr); + mThreadData.mpData = pData; + if (pTP) + SetName(pTP->mpName); + + return pData->mpComp->mThread.get_id(); + } + + static void RunnableObjectInternal(EAThreadDynamicData* tdd, void* userFunc, void* userContext, void* userWrapperFunc) + { + tdd->mStatus = Thread::kStatusRunning; + IRunnable* pRunnable = (IRunnable*)userFunc; + + if (userWrapperFunc) + { + RunnableClassUserWrapper pWrapperFunction = (RunnableClassUserWrapper)userWrapperFunc; + // if user wrapper is specified, call user wrapper and pass down the pFunction and pContext + tdd->mpComp->mReturnPromise.set_value(pWrapperFunction(pRunnable, userContext)); + } + else + { + tdd->mpComp->mReturnPromise.set_value(pRunnable->Run(userContext)); + } + + tdd->mStatus = Thread::kStatusEnded; + tdd->Release(); // Matches implicit AddRef in EAThreadDynamicData constructor + } + + + ThreadId Thread::Begin(IRunnable* pRunnable, void* pContext, const ThreadParameters* pTP, RunnableClassUserWrapper pUserWrapper) + { + if (mThreadData.mpData) + mThreadData.mpData->Release(); // Matches an implicit AddRef in EAThreadDynamicData constructor + + // C++11 Threads don't support user-supplied stacks. A user-supplied stack pointer + // here would be a waste of user memory, and so we assert that mpStack == NULL. + EAT_ASSERT(!pTP || (pTP->mpStack == NULL)); + + // We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be + // modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed + // during execution. + EAThreadDynamicData* pDataAddr = AllocateThreadDynamicData(); + EAT_ASSERT(pDataAddr != nullptr); + EAThreadDynamicData* pData = new(pDataAddr) EAThreadDynamicData(pRunnable, pContext, pUserWrapper, RunnableObjectInternal); // Note that we use a special new here which doesn't use the heap. + EAT_ASSERT(pData != nullptr); + mThreadData.mpData = pData; + if (pTP) + SetName(pTP->mpName); + + EAT_ASSERT(pData && pData->mpComp); + return pData->mpComp->mThread.get_id(); + } + + Thread::Status Thread::WaitForEnd(const ThreadTime& timeoutAbsolute, intptr_t* pThreadReturnValue) + { + // The mThreadData memory is shared between threads and when + // reading it we must be synchronized. + EAReadWriteBarrier(); + + // A mutex lock around mpData is not needed below because + // mpData is never allowed to go from non-NULL to NULL. + // Todo: Consider that there may be a subtle race condition here if + // the user immediately calls WaitForEnd right after calling Begin. + if (mThreadData.mpData && mThreadData.mpData->mpComp) + { + // We must not call WaitForEnd from the thread we are waiting to end. That would result in a deadlock. + EAT_ASSERT(mThreadData.mpData->mpComp->mThread.get_id() != GetThreadId()); + + std::chrono::milliseconds timeoutAbsoluteMs(timeoutAbsolute); + std::chrono::time_point timeoutTime(timeoutAbsoluteMs); + if (mThreadData.mpData->mpComp->mReturnFuture.wait_until(timeoutTime) == std::future_status::timeout) + { + return kStatusRunning; + } + + if (pThreadReturnValue) + { + mThreadData.mpData->mReturnValue = mThreadData.mpData->mpComp->mReturnFuture.get(); + *pThreadReturnValue = mThreadData.mpData->mReturnValue; + } + + mThreadData.mpData->mpComp->mThread.join(); + + return kStatusEnded; // A thread was created, so it must have ended. + } + else + { + // Else the user hasn't started the thread yet, so we wait until the user starts it. + // Ideally, what we really want to do here is wait for some kind of signal. + // Instead for the time being we do a polling loop. + while ((!mThreadData.mpData) && (GetThreadTime() < timeoutAbsolute)) + { + ThreadSleep(1); + } + if (mThreadData.mpData) + return WaitForEnd(timeoutAbsolute); + } + return kStatusNone; // No thread has been started. + } + + Thread::Status Thread::GetStatus(intptr_t* pThreadReturnValue) const + { + if (mThreadData.mpData && mThreadData.mpData->mpComp) + { + auto status = static_cast(mThreadData.mpData->mStatus.GetValue()); + if (pThreadReturnValue && status == kStatusEnded) + { + if (mThreadData.mpData->mpComp->mGetStatusFuture.valid()) + mThreadData.mpData->mReturnValue = mThreadData.mpData->mpComp->mGetStatusFuture.get(); + *pThreadReturnValue = mThreadData.mpData->mReturnValue; + } + return status; + } + return kStatusNone; + } + + int Thread::GetPriority() const + { + // No way to query or set thread priority through standard C++11 thread library. + // On some platforms this could be implemented through platform specific APIs using native_handle() + return kThreadPriorityDefault; + } + + + bool Thread::SetPriority(int nPriority) + { + // No way to query or set thread priority through standard C++11 thread library. + // On some platforms this could be implemented through platform specific APIs using native_handle() + return false; + } + + + void Thread::SetProcessor(int nProcessor) + { + // No way to query or set thread priority through standard C++11 thread library. + // On some platforms this could be implemented through platform specific APIs using native_handle() + } + + void EA::Thread::Thread::SetAffinityMask(EA::Thread::ThreadAffinityMask nAffinityMask) + { + if(mThreadData.mpData) + { + EA::Thread::SetThreadAffinityMask(nAffinityMask); + } + } + + EA::Thread::ThreadAffinityMask EA::Thread::Thread::GetAffinityMask() + { + if(mThreadData.mpData) + { + return mThreadData.mpData->mnThreadAffinityMask; + } + + return kThreadAffinityMaskAny; + } + + void Thread::Wake() + { + // No way to wake a thread through standard C++11 thread library. + // On some platforms this could be implemented through platform specific APIs using native_handle() + } + + + const char* Thread::GetName() const + { + if (mThreadData.mpData) + return mThreadData.mpData->mName; + return ""; + } + + + void Thread::SetName(const char* pName) + { + if (mThreadData.mpData && pName) + { + strncpy(mThreadData.mpData->mName, pName, EATHREAD_NAME_SIZE); + mThreadData.mpData->mName[EATHREAD_NAME_SIZE - 1] = 0; + } + } + + ThreadId Thread::GetId() const + { + if (mThreadData.mpData && mThreadData.mpData->mpComp) + return mThreadData.mpData->mpComp->mThread.get_id(); + return kThreadIdInvalid; + } + + } +} + diff --git a/src/thirdparty/ea/EAThread/source/eathread.cpp b/src/thirdparty/ea/EAThread/source/eathread.cpp new file mode 100644 index 00000000..c963effa --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread.cpp @@ -0,0 +1,270 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + + +namespace EA +{ + namespace Thread + { + EA::Thread::Allocator* gpAllocator = NULL; + + EATHREADLIB_API void SetAllocator(Allocator* pEAThreadAllocator) + { + gpAllocator = pEAThreadAllocator; + } + + EATHREADLIB_API Allocator* GetAllocator() + { + return gpAllocator; + } + + + + // Currently we take advantage of the fact that ICoreAllocator + // is a binary mapping to EA::Thread::Allocator. + // To do: We need to come up with a better solution that this, + // as it is not future-safe and not even guaranteed to + // be portable. The problem is that we can't make this + // package dependent on the CoreAllocator package without + // breaking users who aren't using it. + + EATHREADLIB_API void SetAllocator(EA::Allocator::ICoreAllocator* pCoreAllocator) + { + gpAllocator = (EA::Thread::Allocator*)(uintptr_t)pCoreAllocator; + } + + EATHREADLIB_API void SetThreadAffinityMask(ThreadAffinityMask nAffinityMask) + { + EA::Thread::SetThreadAffinityMask(GetThreadId(), nAffinityMask); + } + + EATHREADLIB_API ThreadAffinityMask GetThreadAffinityMask() + { + return GetThreadAffinityMask(GetThreadId()); + } + } +} + +#if !EA_THREADS_AVAILABLE + // Do nothing +#elif EA_USE_CPP11_CONCURRENCY + #include "cpp11/eathread_cpp11.cpp" +#elif defined(EA_PLATFORM_SONY) + #include "sony/eathread_sony.cpp" +#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include "unix/eathread_unix.cpp" + #if defined(EA_PLATFORM_STADIA) + #include "unix/eathread_stadia.cpp" + #endif +#elif defined(EA_PLATFORM_MICROSOFT) + #include "pc/eathread_pc.cpp" +#endif + +EA::Thread::ThreadAffinityMask EA::Thread::GetAvailableCpuAffinityMask() +{ +#if defined(EA_PLATFORM_STADIA) + return getValidCpuAffinityMask(); +#else + // We assume that the available cores are 0...ProcessorCount-1. + int processorCount = GetProcessorCount(); + EA::Thread::ThreadAffinityMask ret = 0; + EA::Thread::ThreadAffinityMask mask = 1; + while (processorCount > 0) + { + ret |= mask; + mask <<= 1; + --processorCount; + } + return ret; +#endif +} + +namespace EA +{ + namespace Thread + { + namespace detail + { + #if !defined(EAThreadIdToString_CUSTOM_IMPLEMENTATION) + ThreadIdToStringBuffer::ThreadIdToStringBuffer(EA::Thread::ThreadId threadId) + { + sprintf(mBuf, "%d", (int)(intptr_t)threadId); + } + + SysThreadIdToStringBuffer::SysThreadIdToStringBuffer(EA::Thread::SysThreadId sysThreadId) + { + sprintf(mBuf, "%d", (int)(intptr_t)sysThreadId); + } + #endif + } + } +} + +#if !defined(EAT_ASSERT_SNPRINTF) + #if defined(EA_PLATFORM_MICROSOFT) + #define EAT_ASSERT_SNPRINTF _vsnprintf + #else + #define EAT_ASSERT_SNPRINTF snprintf + #endif +#endif + + void EA::Thread::AssertionFailureV(const char* pFormat, ...) + { + const size_t kBufferSize = 512; + char buffer[kBufferSize]; + + va_list arguments; + va_start(arguments, pFormat); + const int nReturnValue = EAT_ASSERT_SNPRINTF(buffer, kBufferSize, pFormat, arguments); + va_end(arguments); + + if(nReturnValue > 0) + { + buffer[kBufferSize - 1] = 0; + AssertionFailure(buffer); + } + } + +/////////////////////////////////////////////////////////////////////////////// +// non-threaded implementation +/////////////////////////////////////////////////////////////////////////////// + +#if !EA_THREADS_AVAILABLE + + #include + #if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + #include + #elif defined(EA_PLATFORM_WINDOWS) + extern "C" __declspec(dllimport) void __stdcall Sleep(unsigned long dwMilliseconds); + #endif + + + namespace EA + { + namespace Thread + { + // Assertion variables. + EA::Thread::AssertionFailureFunction gpAssertionFailureFunction = NULL; + void* gpAssertionFailureContext = NULL; + } + } + + EA::Thread::ThreadId EA::Thread::GetThreadId() + { + return 1; + } + + + int EA::Thread::GetThreadPriority() + { + return kThreadPriorityDefault; + } + + + bool EA::Thread::SetThreadPriority(int nPriority) + { + return true; + } + + + void* EA::Thread::GetThreadStackBase() + { + return NULL; + } + + + void EA::Thread::SetThreadProcessor(int /*nProcessor*/) + { + } + + + int EA::Thread::GetThreadProcessor() + { + return 0; + } + + + int EA::Thread::GetProcessorCount() + { + return 1; + } + + + void EA::Thread::ThreadSleep(const ThreadTime& timeRelative) + { + #if defined(EA_PLATFORM_WINDOWS) + + // There is no nanosleep on Windows, but there is Sleep. + if(timeRelative == kTimeoutImmediate) + Sleep(0); + else + Sleep((unsigned)((timeRelative.tv_sec * 1000) + (((timeRelative.tv_nsec % 1000) * 1000000)))); + + #elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + + if(timeRelative == kTimeoutImmediate) + sched_yield(); + else + nanosleep(&timeRelative, 0); + + #endif + } + + + void EA::Thread::ThreadEnd(intptr_t /*threadReturnValue*/) + { + // We could possibly call exit here. + } + + + EA::Thread::ThreadTime EA::Thread::GetThreadTime() + { + #if defined(EA_PLATFORM_WINDOWS) + + return (ThreadTime)GetTickCount(); + + #elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + + #if defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_CYGWIN) || (_POSIX_TIMERS > 0) + ThreadTime threadTime; + clock_gettime(CLOCK_REALTIME, &threadTime); // If you get a linker error about clock_getttime, you need to link librt.a (specify -lrt to the linker). + return threadTime; + #else + timeval temp; + gettimeofday(&temp, NULL); + return ThreadTime(temp.tv_sec, temp.tv_usec * 1000); + #endif + + #endif + } + + + void EA::Thread::SetAssertionFailureFunction(EA::Thread::AssertionFailureFunction pAssertionFailureFunction, void* pContext) + { + gpAssertionFailureFunction = pAssertionFailureFunction; + gpAssertionFailureContext = pContext; + } + + + void EA::Thread::AssertionFailure(const char* pExpression) + { + if(gpAssertionFailureFunction) + gpAssertionFailureFunction(pExpression, gpAssertionFailureContext); + else + { + #if EAT_ASSERT_ENABLED + printf("EA::Thread::AssertionFailure: %s\n", pExpression); + #endif + } + } + + +#endif // EA_THREADS_AVAILABLE + diff --git a/src/thirdparty/ea/EAThread/source/eathread_barrier.cpp b/src/thirdparty/ea/EAThread/source/eathread_barrier.cpp new file mode 100644 index 00000000..c47ef6e1 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_barrier.cpp @@ -0,0 +1,194 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +EA_DISABLE_VC_WARNING(4574) +#include +EA_RESTORE_VC_WARNING() + +#if defined(EA_PLATFORM_SONY) + #include "sony/eathread_barrier_sony.cpp" + +#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && EA_THREADS_AVAILABLE + // Posix already defines a barrier (via condition variables or directly with pthread_barrier). + #include "unix/eathread_barrier_unix.cpp" + +#else // All other platforms + + #include + #include + + + EABarrierData::EABarrierData() + : mnCurrent(0), mnHeight(0), mnIndex(0), mSemaphore0(NULL, false), mSemaphore1(NULL, false) + { + // Leave mSemaphores alone for now. We leave them constructed but not initialized. + } + + + EA::Thread::BarrierParameters::BarrierParameters(int height, bool bIntraProcess, const char* pName) + : mHeight(height), mbIntraProcess(bIntraProcess) + { + if(pName) + { + EA_DISABLE_VC_WARNING(4996); // This function or variable may be unsafe / deprecated. + strncpy(mName, pName, sizeof(mName)-1); + EA_RESTORE_VC_WARNING(); + mName[sizeof(mName)-1] = 0; + } + else + mName[0] = 0; + } + + + EA::Thread::Barrier::Barrier(const BarrierParameters* pBarrierParameters, bool bDefaultParameters) + { + if(!pBarrierParameters && bDefaultParameters) + { + BarrierParameters parameters; + Init(¶meters); + } + else + Init(pBarrierParameters); + } + + + EA::Thread::Barrier::Barrier(int height) + { + BarrierParameters parameters(height); + Init(¶meters); + } + + + EA::Thread::Barrier::~Barrier() + { + // Nothing to do. + } + + + bool EA::Thread::Barrier::Init(const BarrierParameters* pBarrierParameters) + { + // You cannot set the height after it's already been set. + EAT_ASSERT((mBarrierData.mnHeight == 0) && (mBarrierData.mnCurrent == 0)); + + if(pBarrierParameters && (mBarrierData.mnHeight == 0)) + { + mBarrierData.mnHeight = pBarrierParameters->mHeight; // We don't put mutex lock around this as it is only to be ever set once, before use. + mBarrierData.mnCurrent = pBarrierParameters->mHeight; + + SemaphoreParameters sp(0, pBarrierParameters->mbIntraProcess); + mBarrierData.mSemaphore0.Init(&sp); + mBarrierData.mSemaphore1.Init(&sp); + + return true; + } + + return false; + } + + + EA::Thread::Barrier::Result EA::Thread::Barrier::Wait(const ThreadTime& timeoutAbsolute) + { + int result; + const int nCurrentIndex = (int)mBarrierData.mnIndex; + + // Question: What do we do if a fifth thread calls Wait on a barrier with height + // of four after the fourth thread has decremented the current count below? + + EAT_ASSERT(mBarrierData.mnCurrent > 0); // If this assert fails then it means that more threads are waiting on the barrier than the barrier height. + + const int32_t nCurrent = mBarrierData.mnCurrent.Decrement(); // atomic integer operation. + + if(nCurrent == 0) // If the barrier has been breached... + { + mBarrierData.mnCurrent = mBarrierData.mnHeight; + + if(mBarrierData.mnHeight > 1) // If there are threads other than us... + { + // We don't have a potential race condition here because we use alternating + // semaphores and since we are here, all other threads are waiting on the + // current semaphore below. And if they haven't started waiting on the + // semaphore yet, they'll succeed anyway because we Post all directly below. + Semaphore* const pSemaphore = (nCurrentIndex == 0 ? &mBarrierData.mSemaphore0 : &mBarrierData.mSemaphore1); + + result = pSemaphore->Post(mBarrierData.mnHeight - 1); // Upon success, the return value will in practice be >= 1, but semaphore defines success as >= 0. + } + else // Else we are the only thead. + result = 0; + } + else + { + Semaphore* const pSemaphore = (nCurrentIndex == 0 ? &mBarrierData.mSemaphore0 : &mBarrierData.mSemaphore1); + + result = pSemaphore->Wait(timeoutAbsolute); + + if(result == Semaphore::kResultTimeout) + return kResultTimeout; + } + + if(result >= 0) // If the result wasn't an error such as Semaphore::kResultError or Semaphore::kResultTimeout. + { + // Use an atomic operation to change the index, which conveniently gives us a thread to designate as primary. + EAT_ASSERT((unsigned)nCurrentIndex <= 1); + + if(mBarrierData.mnIndex.SetValueConditional(1 - nCurrentIndex, nCurrentIndex)) // Toggle value between 0 and 1. + return kResultPrimary; + + return kResultSecondary; + } + + return kResultError; + } + + + EA::Thread::Barrier* EA::Thread::BarrierFactory::CreateBarrier() + { + EA::Thread::Allocator* pAllocator = EA::Thread::GetAllocator(); + + if(pAllocator) + return new(pAllocator->Alloc(sizeof(EA::Thread::Barrier))) EA::Thread::Barrier; + else + return new EA::Thread::Barrier; + } + + void EA::Thread::BarrierFactory::DestroyBarrier(EA::Thread::Barrier* pBarrier) + { + EA::Thread::Allocator* pAllocator = EA::Thread::GetAllocator(); + + if(pAllocator) + { + pBarrier->~Barrier(); + pAllocator->Free(pBarrier); + } + else + delete pBarrier; + } + + size_t EA::Thread::BarrierFactory::GetBarrierSize() + { + return sizeof(EA::Thread::Barrier); + } + + EA::Thread::Barrier* EA::Thread::BarrierFactory::ConstructBarrier(void* pMemory) + { + return new(pMemory) EA::Thread::Barrier; + } + + void EA::Thread::BarrierFactory::DestructBarrier(EA::Thread::Barrier* pBarrier) + { + pBarrier->~Barrier(); + } + + +#endif // EA_PLATFORM_XXX + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/eathread_callstack.cpp b/src/thirdparty/ea/EAThread/source/eathread_callstack.cpp new file mode 100644 index 00000000..2f97afb8 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_callstack.cpp @@ -0,0 +1,72 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#if defined(EA_PLATFORM_WIN32) && EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) + #include "pc/eathread_callstack_win32.cpp" +#elif defined(EA_PLATFORM_MICROSOFT) && defined(EA_PROCESSOR_X86_64) + #include "pc/eathread_callstack_win64.cpp" +#elif defined(EA_PLATFORM_SONY) + #include "sony/eathread_callstack_sony.cpp" + #include "sony/eathread_pthread_stack_info.cpp" +#elif defined(EA_PLATFORM_ANDROID) && (defined(EA_PROCESSOR_X86) || defined(EA_PROCESSOR_X86_64)) + #include "x86/eathread_callstack_x86.cpp" + #include "unix/eathread_pthread_stack_info.cpp" +#elif defined(EA_PLATFORM_ANDROID) + #include "libunwind/eathread_callstack_libunwind.cpp" + #include "unix/eathread_pthread_stack_info.cpp" +#elif defined(EA_PLATFORM_APPLE) // OSX, iPhone, iPhone Simulator + #include "apple/eathread_callstack_apple.cpp" + #include "unix/eathread_pthread_stack_info.cpp" +#elif defined(EA_PROCESSOR_ARM) + #include "arm/eathread_callstack_arm.cpp" + #if !defined(EA_PLATFORM_MICROSOFT) + #include "unix/eathread_pthread_stack_info.cpp" + #endif +#elif (defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_CYGWIN)) && (defined(EA_PROCESSOR_X86) || defined(EA_PROCESSOR_X86_64)) + #include "x86/eathread_callstack_x86.cpp" + #include "unix/eathread_pthread_stack_info.cpp" +#elif defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + #include "unix/eathread_callstack_glibc.cpp" + #include "unix/eathread_pthread_stack_info.cpp" +#else + #include "null/eathread_callstack_null.cpp" +#endif + +namespace EA +{ +namespace Thread +{ + +#if defined(EA_COMPILER_MSVC) + +#include + +#pragma intrinsic(_ReturnAddress) + + +/////////////////////////////////////////////////////////////////////////////// +// GetInstructionPointer +// +EATHREADLIB_API EA_NO_INLINE void GetInstructionPointer(void*& pInstruction) +{ + pInstruction = _ReturnAddress(); +} + +#elif defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + +/////////////////////////////////////////////////////////////////////////////// +// GetInstructionPointer +// +EATHREADLIB_API EA_NO_INLINE void GetInstructionPointer(void*& pInstruction) +{ + pInstruction = __builtin_return_address(0); +} + +#endif + +} // namespace Thread +} // namespace EA diff --git a/src/thirdparty/ea/EAThread/source/eathread_condition.cpp b/src/thirdparty/ea/EAThread/source/eathread_condition.cpp new file mode 100644 index 00000000..db86e3e6 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_condition.cpp @@ -0,0 +1,265 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +EA_DISABLE_VC_WARNING(4574) +#include +EA_RESTORE_VC_WARNING() + +#if defined(EA_PLATFORM_SONY) + // Posix already defines a Condition (via condition variables). + #include "sony/eathread_condition_sony.cpp" +#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && EA_THREADS_AVAILABLE + // Posix already defines a Condition (via condition variables). + #include "unix/eathread_condition_unix.cpp" + +#else // All other platforms + + #include + #include + + EAConditionData::EAConditionData() + : mnWaitersBlocked(0), mnWaitersToUnblock(0), mnWaitersDone(0), + mSemaphoreBlockQueue(NULL, false), // We will be initializing these ourselves specifically below. + mSemaphoreBlockLock(NULL, false), + mUnblockLock(NULL, false) + { + // Empty + } + + + EA::Thread::ConditionParameters::ConditionParameters(bool bIntraProcess, const char* pName) + : mbIntraProcess(bIntraProcess) + { + if(pName) + { + strncpy(mName, pName, sizeof(mName)-1); + mName[sizeof(mName)-1] = 0; + } + else + mName[0] = 0; + } + + + EA::Thread::Condition::Condition(const ConditionParameters* pConditionParameters, bool bDefaultParameters) + { + if(!pConditionParameters && bDefaultParameters) + { + ConditionParameters parameters; + Init(¶meters); + } + else + Init(pConditionParameters); + } + + + EA::Thread::Condition::~Condition() + { + // Empty + } + + + bool EA::Thread::Condition::Init(const ConditionParameters* pConditionParameters) + { + if(pConditionParameters) + { + // We have a problem with naming here. We implement our Condition variable with two semaphores and a mutex. + // It's not possible to have them all have the same name, since the OS will think you want them to be + // shared instances. What we really need is an explicit debug name that is separate from the OS name. + // And the ConditionParameters::mName should be that debug name only and not be applied to the child primitives. + + const SemaphoreParameters sp1(0, pConditionParameters->mbIntraProcess, NULL); // Set the name to NULL, regardless of what pConditionParameters->mName is. + const SemaphoreParameters sp2(1, pConditionParameters->mbIntraProcess, NULL); + const MutexParameters mp(pConditionParameters->mbIntraProcess, NULL); + + if(mConditionData.mSemaphoreBlockQueue.Init(&sp1) && + mConditionData.mSemaphoreBlockLock .Init(&sp2) && + mConditionData.mUnblockLock.Init(&mp)) + { + return true; + } + } + + return false; + } + + + EA::Thread::Condition::Result EA::Thread::Condition::Wait(Mutex* pMutex, const ThreadTime& timeoutAbsolute) + { + int lockResult, result; + + EAT_ASSERT(pMutex); // The user is required to pass a valid Mutex pointer. + + ++mConditionData.mnWaitersBlocked; // Note that this is an atomic operation. + + EAT_ASSERT(pMutex->GetLockCount() == 1); + lockResult = pMutex->Unlock(); + if(lockResult < 0) + return (Result)lockResult; + + result = mConditionData.mSemaphoreBlockQueue.Wait(timeoutAbsolute); + EAT_ASSERT(result != EA::Thread::Semaphore::kResultError); + // Regardless of the result of the above error, we must press on with the code below. + + mConditionData.mUnblockLock.Lock(); + + const int nWaitersToUnblock = mConditionData.mnWaitersToUnblock; + + if(nWaitersToUnblock != 0) + --mConditionData.mnWaitersToUnblock; + else if(++mConditionData.mnWaitersDone == (INT_MAX / 2)) // This is not an atomic operation. We are within a mutex lock. + { + // Normally this doesn't happen, but can happen under very + // unusual circumstances, such as spurious semaphore signals + // or cases whereby many many threads are timing out. + EAT_ASSERT(false); + mConditionData.mSemaphoreBlockLock.Wait(); + mConditionData.mnWaitersBlocked -= mConditionData.mnWaitersDone; + mConditionData.mSemaphoreBlockLock.Post(); + mConditionData.mnWaitersDone = 0; + } + + mConditionData.mUnblockLock.Unlock(); + + if(nWaitersToUnblock == 1) // If we were the last... + mConditionData.mSemaphoreBlockLock.Post(); + + // We cannot apply a timeout here. The caller always expects to have the + // lock upon return, even in the case of a wait timeout. Similarly, we + // may or may not want the result of the lock attempt to be propogated + // back to the caller. In this case, we do if it is an error. + lockResult = pMutex->Lock(); + + if(lockResult == Mutex::kResultError) + return kResultError; + else if(result >= 0) + return kResultOK; + + return (Result)result; // This is the result of the wait call above. + } + + + bool EA::Thread::Condition::Signal(bool bBroadcast) + { + int result; + int nSignalsToIssue; + + result = mConditionData.mUnblockLock.Lock(); + + if(result < 0) + return false; + + if(mConditionData.mnWaitersToUnblock) + { + if(mConditionData.mnWaitersBlocked == 0) + { + mConditionData.mUnblockLock.Unlock(); + return true; + } + + if(bBroadcast) + { + nSignalsToIssue = (int)mConditionData.mnWaitersBlocked.SetValue(0); + mConditionData.mnWaitersToUnblock += nSignalsToIssue; + } + else + { + nSignalsToIssue = 1; + mConditionData.mnWaitersToUnblock++; + mConditionData.mnWaitersBlocked--; + } + } + else if(mConditionData.mnWaitersBlocked > mConditionData.mnWaitersDone) + { + if(mConditionData.mSemaphoreBlockLock.Wait() == EA::Thread::Semaphore::kResultError) + { + mConditionData.mUnblockLock.Unlock(); + return false; + } + + if(mConditionData.mnWaitersDone != 0) + { + mConditionData.mnWaitersBlocked -= mConditionData.mnWaitersDone; + mConditionData.mnWaitersDone = 0; + } + + if(bBroadcast) + { + nSignalsToIssue = mConditionData.mnWaitersToUnblock = (int)mConditionData.mnWaitersBlocked.SetValue(0); + } + else + { + nSignalsToIssue = mConditionData.mnWaitersToUnblock = 1; + mConditionData.mnWaitersBlocked--; + } + } + else + { + mConditionData.mUnblockLock.Unlock(); + return true; + } + + mConditionData.mUnblockLock.Unlock(); + mConditionData.mSemaphoreBlockQueue.Post(nSignalsToIssue); + + return true; + } + +#endif // EA_PLATFORM_XXX + + + + +EA::Thread::Condition* EA::Thread::ConditionFactory::CreateCondition() +{ + Allocator* pAllocator = GetAllocator(); + + if(pAllocator) + return new(pAllocator->Alloc(sizeof(EA::Thread::Condition))) EA::Thread::Condition; + else + return new EA::Thread::Condition; +} + +void EA::Thread::ConditionFactory::DestroyCondition(EA::Thread::Condition* pCondition) +{ + Allocator* pAllocator = GetAllocator(); + + if(pAllocator) + { + pCondition->~Condition(); + pAllocator->Free(pCondition); + } + else + delete pCondition; +} + +size_t EA::Thread::ConditionFactory::GetConditionSize() +{ + return sizeof(EA::Thread::Condition); +} + +EA::Thread::Condition* EA::Thread::ConditionFactory::ConstructCondition(void* pMemory) +{ + return new(pMemory) EA::Thread::Condition; +} + +void EA::Thread::ConditionFactory::DestructCondition(EA::Thread::Condition* pCondition) +{ + pCondition->~Condition(); +} + + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/eathread_futex.cpp b/src/thirdparty/ea/EAThread/source/eathread_futex.cpp new file mode 100644 index 00000000..88e121af --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_futex.cpp @@ -0,0 +1,335 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#if defined(EA_THREAD_NONTHREADED_FUTEX) && EA_THREAD_NONTHREADED_FUTEX + + void EA::Thread::Futex::CreateFSemaphore() + { + mSemaphore.mnCount = 0; + } + + void EA::Thread::Futex::DestroyFSemaphore() + { + // Do nothing; + } + + void EA::Thread::Futex::SignalFSemaphore() + { + mSemaphore.mnCount++; + } + + void EA::Thread::Futex::WaitFSemaphore() + { + while(mSemaphore.mnCount <= 0) + EA_THREAD_DO_SPIN(); + mSemaphore.mnCount--; + } + + bool EA::Thread::Futex::WaitFSemaphore(const ThreadTime&) + { + WaitFSemaphore(); + return true; + } + +#elif defined(EA_PLATFORM_APPLE) && EATHREAD_MANUAL_FUTEX_ENABLED + #include + #include + #include + #include + #include + + void EA::Thread::Futex::CreateFSemaphore() + { + mSemaphore.Init(0); + } + + void EA::Thread::Futex::DestroyFSemaphore() + { + // Do nothing; + } + + void EA::Thread::Futex::SignalFSemaphore() + { + mSemaphore.Post(); + } + + void EA::Thread::Futex::WaitFSemaphore() + { + mSemaphore.Wait(); + } + + bool EA::Thread::Futex::WaitFSemaphore(const ThreadTime& timeoutAbsolute) + { + return (mSemaphore.Wait(timeoutAbsolute) >= 0); + } + +#elif defined(EA_PLATFORM_SONY) && !EATHREAD_MANUAL_FUTEX_ENABLED + #include + #include + + EA::Thread::Futex::Futex() + : mSpinCount(EATHREAD_FUTEX_SPIN_COUNT) + { + } + + EA::Thread::Futex::~Futex() + { + } + + void EA::Thread::Futex::Lock() + { + Uint spinCount(mSpinCount); + while(--spinCount) + { + if(TryLock()) + return; + } + + mMutex.Lock(); + } + + void EA::Thread::Futex::Unlock() + { + mMutex.Unlock(); + } + + bool EA::Thread::Futex::TryLock() + { + if(mMutex.Lock(EA::Thread::kTimeoutImmediate) > 0) // This calls scePthreadMutexTrylock + return true; + + return false; + } + + int EA::Thread::Futex::Lock(const ThreadTime& timeoutAbsolute) + { + return mMutex.Lock(timeoutAbsolute); + } + + int EA::Thread::Futex::GetLockCount() const + { + return mMutex.GetLockCount(); + } + + bool EA::Thread::Futex::HasLock() const + { + return mMutex.HasLock(); + } + + void EA::Thread::Futex::SetSpinCount(Uint spinCount) + { + mSpinCount = spinCount; + } + +#elif defined(EA_PLATFORM_SONY) && EATHREAD_MANUAL_FUTEX_ENABLED + #include + #include + + void EA::Thread::Futex::CreateFSemaphore() + { + // To consider: Copy the Futex name into this semaphore name. + int result = sceKernelCreateSema(&mSemaphore, "Futex", SCE_KERNEL_SEMA_ATTR_TH_FIFO, 0, 100000, NULL); + EA_UNUSED(result); + EAT_ASSERT(result == SCE_OK); + } + + void EA::Thread::Futex::DestroyFSemaphore() + { + int result = sceKernelDeleteSema(mSemaphore); + EA_UNUSED(result); + EAT_ASSERT(result == SCE_OK); + } + + void EA::Thread::Futex::SignalFSemaphore() + { + int result = sceKernelSignalSema(mSemaphore, 1); + EA_UNUSED(result); + EAT_ASSERT(result == SCE_OK); + } + + void EA::Thread::Futex::WaitFSemaphore() + { + int result = sceKernelWaitSema(mSemaphore, 1, NULL); + EA_UNUSED(result); + EAT_ASSERT(result == SCE_OK); + } + + bool EA::Thread::Futex::WaitFSemaphore(const ThreadTime& timeoutAbsolute) + { + SceKernelUseconds timeoutRelativeUs = static_cast(RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute)); + if(timeoutRelativeUs < 1) + timeoutRelativeUs = 1; + + return (sceKernelWaitSema(mSemaphore, 1, &timeoutRelativeUs) == SCE_OK); + } + +#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && EATHREAD_MANUAL_FUTEX_ENABLED + #include + #include + + void EA::Thread::Futex::CreateFSemaphore() + { + const int result = sem_init(&mSemaphore, 0, 0); + (void)result; + EAT_ASSERT(result != -1); + } + + void EA::Thread::Futex::DestroyFSemaphore() + { + #if defined (EA_PLATFORM_APPLE) + sem_close(&mSemaphore); + #elif defined(EA_PLATFORM_ANDROID) + sem_destroy(&mSemaphore); // Android's sem_destroy is broken. http://code.google.com/p/android/issues/detail?id=3106 + #else + int result = -1; + + for(;;) + { + result = sem_destroy(&mSemaphore); + + if((result == -1) && (errno == EBUSY)) // If another thread or process is blocked on this semaphore... + ThreadSleep(kTimeoutYield); // Yield. If we don't yield, it's possible we could block other other threads or processes from running, on some systems. + else + break; + } + + EAT_ASSERT(result != -1); + #endif + } + + void EA::Thread::Futex::SignalFSemaphore() + { + sem_post(&mSemaphore); + } + + void EA::Thread::Futex::WaitFSemaphore() + { + // We don't have much choice but to retry interrupted waits, + // as there is no lock failure return value. + while((sem_wait(&mSemaphore) == -1) && (errno == EINTR)) + continue; + } + + bool EA::Thread::Futex::WaitFSemaphore(const ThreadTime&) + { + WaitFSemaphore(); + return true; + } + +#elif defined(EA_PLATFORM_MICROSOFT) && !EA_USE_CPP11_CONCURRENCY && !EATHREAD_MANUAL_FUTEX_ENABLED + + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() + + // Validate what we assume to be invariants. + EAT_COMPILETIME_ASSERT(sizeof(CRITICAL_SECTION) <= (EA::Thread::FUTEX_PLATFORM_DATA_SIZE / sizeof(uint64_t) * sizeof(uint64_t))); + + #if defined(EA_PLATFORM_MICROSOFT) && (defined(EA_PROCESSOR_X86_64) || defined(EA_PROCESSOR_ARM64)) + EAT_COMPILETIME_ASSERT(offsetof(CRITICAL_SECTION, RecursionCount) == (3 * sizeof(int))); + EAT_COMPILETIME_ASSERT(offsetof(CRITICAL_SECTION, OwningThread) == (4 * sizeof(int))); + #elif defined(EA_PLATFORM_WIN32) + EAT_COMPILETIME_ASSERT(offsetof(CRITICAL_SECTION, RecursionCount) == (2 * sizeof(int))); + EAT_COMPILETIME_ASSERT(offsetof(CRITICAL_SECTION, OwningThread) == (3 * sizeof(int))); + #else + EAT_FAIL_MSG("Need to verify offsetof."); + #endif + + +#elif defined(EA_PLATFORM_MICROSOFT) && EATHREAD_MANUAL_FUTEX_ENABLED + + #if defined(EA_PLATFORM_WINDOWS) + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() + #endif + + void EA::Thread::Futex::CreateFSemaphore() + { + mSemaphore = CreateSemaphoreA(NULL, 0, INT_MAX / 2, NULL); + EAT_ASSERT(mSemaphore != 0); + } + + void EA::Thread::Futex::DestroyFSemaphore() + { + if(mSemaphore) + CloseHandle(mSemaphore); + } + + void EA::Thread::Futex::SignalFSemaphore() + { + ReleaseSemaphore(mSemaphore, 1, NULL); + } + + void EA::Thread::Futex::WaitFSemaphore() + { + WaitForSingleObject(mSemaphore, INFINITE); + } + + bool EA::Thread::Futex::WaitFSemaphore(const ThreadTime& timeoutAbsolute) + { + int64_t timeoutRelativeMS = (int64_t)(timeoutAbsolute - GetThreadTime()); + if(timeoutRelativeMS < 1) + timeoutRelativeMS = 1; + return WaitForSingleObject(mSemaphore, (DWORD)timeoutRelativeMS) == WAIT_OBJECT_0; + } + +#endif + + + + + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + } +} + + +EA::Thread::Futex* EA::Thread::FutexFactory::CreateFutex() +{ + if(gpAllocator) + return new(gpAllocator->Alloc(sizeof(EA::Thread::Futex))) EA::Thread::Futex; + else + return new EA::Thread::Futex; +} + +void EA::Thread::FutexFactory::DestroyFutex(EA::Thread::Futex* pFutex) +{ + if(gpAllocator) + { + pFutex->~Futex(); + gpAllocator->Free(pFutex); + } + else + delete pFutex; +} + +size_t EA::Thread::FutexFactory::GetFutexSize() +{ + return sizeof(EA::Thread::Futex); +} + +EA::Thread::Futex* EA::Thread::FutexFactory::ConstructFutex(void* pMemory) +{ + return new(pMemory) EA::Thread::Futex; +} + +void EA::Thread::FutexFactory::DestructFutex(EA::Thread::Futex* pFutex) +{ + pFutex->~Futex(); +} + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/eathread_mutex.cpp b/src/thirdparty/ea/EAThread/source/eathread_mutex.cpp new file mode 100644 index 00000000..22cabfe1 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_mutex.cpp @@ -0,0 +1,144 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include + +EA_DISABLE_VC_WARNING(4574) +#include +#include +EA_RESTORE_VC_WARNING() + +#if !EA_THREADS_AVAILABLE + #include +#elif EA_USE_CPP11_CONCURRENCY + #include "cpp11/eathread_mutex_cpp11.cpp" + #if defined(CreateMutex) + #undef CreateMutex + #endif +#elif defined(EA_PLATFORM_SONY) + #include "sony/eathread_mutex_sony.cpp" +#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include "unix/eathread_mutex_unix.cpp" +#elif defined(EA_PLATFORM_MICROSOFT) + #include "pc/eathread_mutex_pc.cpp" +#endif + + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + } +} + + +EA::Thread::Mutex* EA::Thread::MutexFactory::CreateMutex() +{ + if(gpAllocator) + return new(gpAllocator->Alloc(sizeof(EA::Thread::Mutex))) EA::Thread::Mutex; + else + return new EA::Thread::Mutex; +} + +void EA::Thread::MutexFactory::DestroyMutex(EA::Thread::Mutex* pMutex) +{ + if(gpAllocator) + { + pMutex->~Mutex(); + gpAllocator->Free(pMutex); + } + else + delete pMutex; +} + +size_t EA::Thread::MutexFactory::GetMutexSize() +{ + return sizeof(EA::Thread::Mutex); +} + +EA::Thread::Mutex* EA::Thread::MutexFactory::ConstructMutex(void* pMemory) +{ + return new(pMemory) EA::Thread::Mutex; +} + +void EA::Thread::MutexFactory::DestructMutex(EA::Thread::Mutex* pMutex) +{ + pMutex->~Mutex(); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// non-threaded implementation +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_THREAD_NONTHREADED_MUTEX) && EA_THREAD_NONTHREADED_MUTEX + + EAMutexData::EAMutexData() + : mnLockCount(0) + { + // Empty + } + + + EA::Thread::MutexParameters::MutexParameters(bool /*bIntraProcess*/, const char* /*pName*/) + : mbIntraProcess(true) + { + } + + + EA::Thread::Mutex::Mutex(const MutexParameters* pMutexParameters, bool bDefaultParameters) + { + if(!pMutexParameters && bDefaultParameters) + { + MutexParameters parameters; + Init(¶meters); + } + else + Init(pMutexParameters); + } + + + EA::Thread::Mutex::~Mutex() + { + EAT_ASSERT(mMutexData.mnLockCount == 0); + } + + + bool EA::Thread::Mutex::Init(const MutexParameters* /*pMutexParameters*/) + { + // Possibly copy pMutexParameters->mName to mMutexData.mName + return true; + } + + + int EA::Thread::Mutex::Lock(const ThreadTime& /*timeoutAbsolute*/) + { + EAT_ASSERT(mMutexData.mnLockCount < 100000); + + return ++mMutexData.mnLockCount; + } + + + int EA::Thread::Mutex::Unlock() + { + EAT_ASSERT(mMutexData.mnLockCount > 0); + + return --mMutexData.mnLockCount; + } + + + int EA::Thread::Mutex::GetLockCount() const + { + return mMutexData.mnLockCount; + } + + + bool EA::Thread::Mutex::HasLock() const + { + return (mMutexData.mnLockCount > 0); + } + +#endif // EA_THREAD_NONTHREADED_MUTEX diff --git a/src/thirdparty/ea/EAThread/source/eathread_pool.cpp b/src/thirdparty/ea/EAThread/source/eathread_pool.cpp new file mode 100644 index 00000000..e825dc69 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_pool.cpp @@ -0,0 +1,698 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +// 6011: Dereferencing NULL pointer 'gpAllocator' +// 6211: Leaking memory 'pThreadInfo' due to an exception. +// 6326: Potential comparison of a constant with another constant +EA_DISABLE_VC_WARNING(6011 6211 6326) + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + } +} + + +EA::Thread::ThreadPoolParameters::ThreadPoolParameters() + : mnMinCount(EA::Thread::ThreadPool::kDefaultMinCount), + mnMaxCount(EA::Thread::ThreadPool::kDefaultMaxCount), + mnInitialCount(EA::Thread::ThreadPool::kDefaultInitialCount), + mnIdleTimeoutMilliseconds(EA::Thread::ThreadPool::kDefaultIdleTimeout), // This is a relative time, not an absolute time. Can be a millisecond value or Thread::kTimeoutNone or Thread::kTimeoutImmediate. + mnProcessorMask(0xffffffff), + mDefaultThreadParameters() +{ + // Empty +} + + +EA::Thread::ThreadPool::Job::Job() + : mnJobID(-1), mpRunnable(NULL), mpFunction(NULL), mpContext(NULL) +{ + // Empty +} + + +EA::Thread::ThreadPool::ThreadInfo::ThreadInfo() + : mbActive(false), + mbQuit(false), + //mbPersistent(false), + mpThread(NULL), + mpThreadPool(NULL), + mCurrentJob() +{ + // Empty +} + + +EA::Thread::ThreadPool::ThreadPool(const ThreadPoolParameters* pThreadPoolParameters, bool bDefaultParameters) + : mbInitialized(false), + mnMinCount(kDefaultMinCount), + mnMaxCount(kDefaultMaxCount), + mnCurrentCount(0), + mnActiveCount(0), + mnIdleTimeoutMilliseconds(kDefaultIdleTimeout), + mnProcessorMask((unsigned)kDefaultProcessorMask), + mnProcessorCount(0), + mnNextProcessor(0), + mnPauseCount(0), + mnLastJobID(0), + mDefaultThreadParameters(), + mThreadCondition(NULL, false), // Explicitly don't initialize. + mThreadMutex(NULL, false), // Explicitly don't initialize. + mThreadInfoList(), + mJobList() +{ + if(!pThreadPoolParameters && bDefaultParameters) + { + ThreadPoolParameters parameters; + Init(¶meters); + } + else + Init(pThreadPoolParameters); +} + + +EA::Thread::ThreadPool::~ThreadPool() +{ + Shutdown(kJobWaitAll, kTimeoutNone); + EAT_ASSERT(mJobList.empty() && mThreadInfoList.empty() && (mnCurrentCount == 0) && (mnActiveCount == 0) && (mThreadMutex.GetLockCount() == 0)); +} + + +EA_DISABLE_VC_WARNING(4296 4706) // '>=' : expression is always true and assignment within conditional expression (in the assert) + + +#if EAT_ASSERT_ENABLED + template + inline bool EATIsUnsigned(T) + { return (((T)(-1) >> 1) != (T)(-1)); } +#endif + + +// If mDefaultThreadParameters.mnProcessor is set to kThreadPoolParametersProcessorDefault, +// then the ThreadPool controls what processors the thread executes on. Otherwise ThreadPool +// doesn't set the thread affinity itself. +static const int kThreadPoolParametersProcessorDefault = -1; + + +bool EA::Thread::ThreadPool::Init(const ThreadPoolParameters* pThreadPoolParameters) +{ + if(!mbInitialized) + { + if(pThreadPoolParameters && (mnCurrentCount == 0)) + { + mbInitialized = true; + + mnMinCount = pThreadPoolParameters->mnMinCount; + mnMaxCount = pThreadPoolParameters->mnMaxCount; + mnCurrentCount = (int)pThreadPoolParameters->mnInitialCount; + mnIdleTimeoutMilliseconds = pThreadPoolParameters->mnIdleTimeoutMilliseconds; + mnProcessorMask = pThreadPoolParameters->mnProcessorMask; + mDefaultThreadParameters = pThreadPoolParameters->mDefaultThreadParameters; + mnProcessorCount = (uint32_t)EA::Thread::GetProcessorCount(); // We currently assume this value is constant at runtime. + + // Do bounds checking. + //if(mnMinCount < 0) // This check is unnecessary because mnMinCount is of an + // mnMinCount = 0; // unsigned data type. We assert for this unsigned-ness below. + EAT_ASSERT(EATIsUnsigned(mnMinCount)); + + if(mnMaxCount > EA_THREAD_POOL_MAX_SIZE) + mnMaxCount = EA_THREAD_POOL_MAX_SIZE; + + if(mnCurrentCount < (int)mnMinCount) + mnCurrentCount = (int)mnMinCount; + + if(mnCurrentCount > (int)mnMaxCount) + mnCurrentCount = (int)mnMaxCount; + + // Make sure the processor mask refers to existing processors. + const int processorMask = (1 << mnProcessorCount) - 1; // So for a processor count of 8 we have a mask of 11111111 (255) + + if((mnProcessorMask & processorMask) == 0) + mnProcessorMask = 0xffffffff; + + mDefaultThreadParameters.mpStack = NULL; // You can't specify a default stack location, as every thread needs a unique one. + if(mDefaultThreadParameters.mnProcessor != EA::Thread::kProcessorAny) // If the user hasn't set threads to execute on any processor chosen by the OS... + mDefaultThreadParameters.mnProcessor = kThreadPoolParametersProcessorDefault; // then use our default processing, which is for us to currently round-robin the processor used. + + ConditionParameters mnp; + mThreadCondition.Init(&mnp); + + MutexParameters mtp; + mThreadMutex.Init(&mtp); + + mThreadMutex.Lock(); + const int nDesiredCount((int)mnCurrentCount); + mnCurrentCount = 0; + AdjustThreadCount((unsigned int)nDesiredCount); + mThreadMutex.Unlock(); + + return true; + } + } + return false; +} + + +EA_RESTORE_VC_WARNING() // 4296 4706 + + +bool EA::Thread::ThreadPool::Shutdown(JobWait jobWait, const ThreadTime& timeoutAbsolute) +{ + int nResult; + + if(mbInitialized) + { + mbInitialized = false; + + nResult = WaitForJobCompletion(-1, jobWait, timeoutAbsolute); + + mThreadMutex.Lock(); + + // If jobWait is kJobWaitNone, then we nuke all existing jobs. + if(jobWait == kJobWaitNone) + mJobList.clear(); + + // Leave a message to tell the thread to quit. + for(ThreadInfoList::iterator it(mThreadInfoList.begin()), itEnd(mThreadInfoList.end()); it != itEnd; ) + { + ThreadInfo* const pThreadInfo = *it; + + pThreadInfo->mbQuit = true; + //pThreadInfo->mbPersistent = false; + + // If somehow the thread isn't running (possibly because it never started), manually remove it. + if(pThreadInfo->mpThread->GetStatus() != EA::Thread::Thread::kStatusRunning) + it = mThreadInfoList.erase(it); + else + ++it; + } + + // Wake up any threads that may be blocked on a condition variable wait. + mThreadCondition.Signal(true); + + // Make sure we unlock after we signal, lest there be a certain kind of race condition. + mThreadMutex.Unlock(); + + // Wait for any existing threads to quit. + // Todo: Replace this poor polling loop with Thread::Wait calls. + // Doing so requires a little finessing with the thread + // objects in the list. Possibly make ThreadInfo ref-counted. + while(!mThreadInfoList.empty()) + { + ThreadSleep(1); + EAReadBarrier(); + } + + mThreadMutex.Lock(); + mnPauseCount = 0; + mThreadMutex.Unlock(); + } + else + nResult = kResultOK; + + return (nResult == kResultOK); +} + + +intptr_t EA::Thread::ThreadPool::ThreadFunction(void* pContext) +{ + ThreadInfo* const pThreadInfo = reinterpret_cast(pContext); + ThreadPool* const pThreadPool = pThreadInfo->mpThreadPool; + Condition* const pCondition = &pThreadPool->mThreadCondition; + Mutex* const pMutex = &pThreadPool->mThreadMutex; + + pMutex->Lock(); + + while(!pThreadInfo->mbQuit) + { + if(!pThreadPool->mJobList.empty()) + { + pThreadInfo->mCurrentJob = pThreadPool->mJobList.front(); + pThreadPool->mJobList.pop_front(); + pThreadInfo->mbActive = true; + ++pThreadPool->mnActiveCount; // Atomic integer operation. + pMutex->Unlock(); + + // Do the job here. It's important that we keep the mutex unlocked while doing the job. + if(pThreadInfo->mCurrentJob.mpRunnable) + pThreadInfo->mCurrentJob.mpRunnable->Run(pThreadInfo->mCurrentJob.mpContext); + else if(pThreadInfo->mCurrentJob.mpFunction) + pThreadInfo->mCurrentJob.mpFunction(pThreadInfo->mCurrentJob.mpContext); + else + pThreadInfo->mbQuit = true; // Tell ourself to quit. + + // Problem: We are not paying attention to the pThreadInfo->mbPersistent variable. + // We don't have an easy way of dealing with it because we don't have a means for + // the ThreadPool to direct quit commands to individual threads. For now we don't + // pay attention to mbPersistent and require that persistence be controlled by + // the min/max thread count settings. + + pMutex->Lock(); + + --pThreadPool->mnActiveCount; // Atomic integer operation. + pThreadInfo->mbActive = false; + } + else + { + // The wait call here will unlock the condition variable and will re-lock it upon return. + EA::Thread::ThreadTime timeoutAbsolute = (GetThreadTime() + pThreadPool->mnIdleTimeoutMilliseconds); + + if (pThreadPool->mnIdleTimeoutMilliseconds == kTimeoutNone) + timeoutAbsolute = kTimeoutNone; + else if(pThreadPool->mnIdleTimeoutMilliseconds == kTimeoutImmediate) + timeoutAbsolute = kTimeoutImmediate; + else if(timeoutAbsolute == kTimeoutNone) // If it coincidentally is the magic kTimeoutNone value... + timeoutAbsolute -= 1; + + const Condition::Result result = pCondition->Wait(pMutex, timeoutAbsolute); + + if(result != Condition::kResultOK) // If result is an error then what do we do? Is there a + pThreadInfo->mbQuit = true; // specific reason to quit? There's no good solution here, + } // but on the other hand this should never happen in practice. + } + + pThreadPool->RemoveThread(pThreadInfo); + + pMutex->Unlock(); + + return 0; +} + + +EA::Thread::ThreadPool::Result EA::Thread::ThreadPool::QueueJob(const Job& job, Thread** ppThread, bool /*bEnableDeferred*/) +{ + if(mbInitialized){ + mThreadMutex.Lock(); + + // If there are other threads busy with jobs or other threads soon to be busy with jobs and if the thread count is less than the maximum allowable, bump up the thread count by one. + EAT_ASSERT(mnActiveCount <= mnCurrentCount); + if((((int)mnActiveCount >= mnCurrentCount) || !mJobList.empty()) && (mnCurrentCount < (int)mnMaxCount)) + AdjustThreadCount((unsigned)(mnCurrentCount + 1)); + + mJobList.push_back(job); + FixThreads(); + + if(mnPauseCount == 0) + mThreadCondition.Signal(false); // Wake up one thread to work on this. + + mThreadMutex.Unlock(); + + if(ppThread){ + // In this case the caller wants to know what thread got the job. + // So we wait until we know what caller got the job. + // Todo: Complete this. + *ppThread = NULL; + } + + return kResultDeferred; + } + + return kResultError; +} + + +int EA::Thread::ThreadPool::Begin(IRunnable* pRunnable, void* pContext, Thread** ppThread, bool bEnableDeferred) +{ + Job job; + job.mnJobID = mnLastJobID.Increment(); + job.mpRunnable = pRunnable; + job.mpFunction = NULL; + job.mpContext = pContext; + + if(QueueJob(job, ppThread, bEnableDeferred) != kResultError) + return job.mnJobID; + return kResultError; +} + + +int EA::Thread::ThreadPool::Begin(RunnableFunction pFunction, void* pContext, Thread** ppThread, bool bEnableDeferred) +{ + Job job; + job.mnJobID = mnLastJobID.Increment(); + job.mpRunnable = NULL; + job.mpFunction = pFunction; + job.mpContext = pContext; + + if(QueueJob(job, ppThread, bEnableDeferred) != kResultError) + return job.mnJobID; + return kResultError; +} + + +int EA::Thread::ThreadPool::WaitForJobCompletion(int nJob, JobWait jobWait, const ThreadTime& timeoutAbsolute) +{ + int nResult = kResultError; + + if(nJob == -1){ + // We have a problem here in that we need to wait for all threads to finish + // but the only way to wait for them to finish is to use the Thread::WaitForEnd + // function. But when the thread exits, it destroys the Thread object rendering + // it unsafe for us to use that object in any safe way here. We can rearrange + // things to allow this to work more cleanly, but in the meantime we spin and + // sleep, which is not a good solution if the worker threads are of a lower + // priority than this sleeping thread, as this thread will steal their time. + + if(jobWait == kJobWaitNone){ + // Do nothing. + nResult = kResultOK; + } + else if(jobWait == kJobWaitCurrent){ + // Wait for currently running jobs to complete. + while((mnActiveCount != 0) && (GetThreadTime() < timeoutAbsolute)) + ThreadSleep(10); + if(mnActiveCount == 0) + nResult = kResultOK; + else + nResult = kResultTimeout; + } + else{ // jobWait == kJobWaitAll + // Wait for all current and queued jobs to complete. + bool shouldContinue = true; + + while(shouldContinue) + { + mThreadMutex.Lock(); + shouldContinue = (((mnActiveCount != 0) || !mJobList.empty()) && (GetThreadTime() < timeoutAbsolute)); + mThreadMutex.Unlock(); + if(shouldContinue) + ThreadSleep(10); + } + + mThreadMutex.Lock(); + + if((mnActiveCount == 0) && mJobList.empty()) + nResult = kResultOK; + else + nResult = kResultTimeout; + + mThreadMutex.Unlock(); + } + } + else{ + // Like above we do the wait via polling. Ideally we want to set up a + // mechanism whereby we sleep until an alarm wakes us. This can perhaps + // be done by setting a flag in the job which causes the job to signal + // the alarm when complete. In the meantime we will follow the simpler + // behaviour we have here. + + bool bJobExists; + + for(;;){ + bJobExists = false; + mThreadMutex.Lock(); + + // Search the list of jobs yet to become active to see if the job exists in there. + for(JobList::iterator it(mJobList.begin()); it != mJobList.end(); ++it){ + const Job& job = *it; + + if(job.mnJobID == nJob){ // If the user's job was found... + bJobExists = true; + nResult = kResultTimeout; + } + } + + // Search the list of jobs actively executing as well. + for(ThreadInfoList::iterator it(mThreadInfoList.begin()); it != mThreadInfoList.end(); ++it){ + const ThreadInfo* const pThreadInfo = *it; + const Job& job = pThreadInfo->mCurrentJob; + + // Note the thread must be active for the Job assigned to it be valid. + if(pThreadInfo->mbActive && job.mnJobID == nJob){ // If the user's job was found... + bJobExists = true; + nResult = kResultTimeout; + } + } + + mThreadMutex.Unlock(); + if(!bJobExists || (GetThreadTime() >= timeoutAbsolute)) + break; + ThreadSleep(10); + } + + if(!bJobExists) + nResult = kResultOK; + } + + return nResult; +} + + +void EA::Thread::ThreadPool::Pause(bool bPause) +{ + if(bPause) + ++mnPauseCount; + else{ + if(mnPauseCount.Decrement() == 0){ + mThreadMutex.Lock(); + if(!mJobList.empty()) + mThreadCondition.Signal(true); + mThreadMutex.Unlock(); + } + } +} + + +void EA::Thread::ThreadPool::Lock() +{ + mThreadMutex.Lock(); +} + + +void EA::Thread::ThreadPool::Unlock() +{ + mThreadMutex.Unlock(); +} + + +void EA::Thread::ThreadPool::SetupThreadParameters(EA::Thread::ThreadParameters& tp) +{ + if(tp.mnProcessor == kThreadPoolParametersProcessorDefault) // If we are to manipulate tp.mnProcessor... + { + if(mnProcessorMask != 0xffffffff) // If we are not using the default... + { + // We round-robin mnNextProcessor within our mnProcessorMask. + while(((1 << mnNextProcessor) & mnProcessorMask) == 0) + ++mnNextProcessor; + + mnNextProcessor %= mnProcessorCount; + tp.mnProcessor = (int)mnNextProcessor++; + } + } +} + + +EA::Thread::ThreadPool::ThreadInfo* EA::Thread::ThreadPool::AddThread(const EA::Thread::ThreadParameters& tp, bool bBeginThread) +{ + ThreadInfo* const pThreadInfo = CreateThreadInfo(); + EAT_ASSERT(pThreadInfo != NULL); + + if(pThreadInfo) + { + AddThread(pThreadInfo); + + if(bBeginThread) + { + ThreadParameters tpUsed(tp); + SetupThreadParameters(tpUsed); // This function sets tpUsed.mnProcessor + + pThreadInfo->mpThread->Begin(ThreadFunction, pThreadInfo, &tpUsed); + } + } + + return pThreadInfo; +} + + +// Gets the ThreadInfo for the nth Thread identified by index. +// You must call this function within a Lock/Unlock pair on the thread pool. +EA::Thread::ThreadPool::ThreadInfo* EA::Thread::ThreadPool::GetThreadInfo(int index) +{ + EA::Thread::AutoMutex autoMutex(mThreadMutex); + + int i = 0; + + for(ThreadInfoList::iterator it = mThreadInfoList.begin(); it != mThreadInfoList.end(); ++it) + { + if(i == index) + { + ThreadInfo* pThreadInfo = *it; + return pThreadInfo; + } + + ++i; + } + + return NULL; +} + + +// Unless you call this function while the Pool is locked (via Lock), the return +// value may be out of date by the time you read it. +int EA::Thread::ThreadPool::GetThreadCount() +{ + EA::Thread::AutoMutex autoMutex(mThreadMutex); + + return (int)mThreadInfoList.size(); +} + + +EA::Thread::ThreadPool::ThreadInfo* EA::Thread::ThreadPool::CreateThreadInfo() +{ + // Currently we assume that allocation never fails. + ThreadInfo* const pThreadInfo = gpAllocator ? new(gpAllocator->Alloc(sizeof(ThreadInfo))) ThreadInfo : new ThreadInfo; + + if(pThreadInfo) + { + pThreadInfo->mbActive = false; + pThreadInfo->mbQuit = false; + pThreadInfo->mpThreadPool = this; + pThreadInfo->mpThread = gpAllocator ? new(gpAllocator->Alloc(sizeof(Thread))) Thread : new Thread; + } + + return pThreadInfo; +} + + +void EA::Thread::ThreadPool::AdjustThreadCount(unsigned nDesiredCount) +{ + // This function doesn't read mnMinCount/mnMaxCount, as it expects the caller to do so. + // Assumes that condition variable is locked. + int nAdjustment = (int)(nDesiredCount - mnCurrentCount); + + while(nAdjustment > 0) // If we are to create threads... + { + ThreadInfo* const pThreadInfo = CreateThreadInfo(); + EAT_ASSERT(pThreadInfo != NULL); + + AddThread(pThreadInfo); + + ThreadParameters tpUsed(mDefaultThreadParameters); + SetupThreadParameters(tpUsed); // This function sets tpUsed.mnProcessor + + pThreadInfo->mpThread->Begin(ThreadFunction, pThreadInfo, &tpUsed); + nAdjustment--; + } + + while(nAdjustment < 0) // If we are to quit threads... + { + // An empty job is a signal for a thread to quit. + QueueJob(Job(), NULL, true); + nAdjustment++; + } + + FixThreads(); // Makes sure that mnCurrentCount really does match the number of threads waiting for work. +} + + + +void EA::Thread::ThreadPool::AddThread(ThreadInfo* pThreadInfo) +{ + // Assumes that condition variable is locked. + mThreadInfoList.push_back(pThreadInfo); + ++mnCurrentCount; +} + + +void EA::Thread::ThreadPool::RemoveThread(ThreadInfo* pThreadInfo) +{ + // Assumes that condition variable is locked. + ThreadInfoList::iterator it = mThreadInfoList.find(pThreadInfo); + EAT_ASSERT(it != mThreadInfoList.end()); + + if(it != mThreadInfoList.end()) + { + if(gpAllocator) + { + pThreadInfo->mpThread->~Thread(); + gpAllocator->Free(pThreadInfo->mpThread); + } + else + delete pThreadInfo->mpThread; + + pThreadInfo->mpThread = NULL; + mThreadInfoList.erase(it); + + if(gpAllocator) + { + pThreadInfo->~ThreadInfo(); + gpAllocator->Free(pThreadInfo); + } + else + delete pThreadInfo; + + --mnCurrentCount; + } +} + + +// FixThreads +// We have a small in problem in that the system allows threads to explicitly exit at any time without +// returning to the caller. Many operating systems with thread support don't have a mechanism to enable +// you to tell you via a callback when a thread has exited. Due to this latter problem, it is possible +// that threads could exit without us ever finding out about it. So we poll the threads to catch up +// to their state in such cases here. +void EA::Thread::ThreadPool::FixThreads() +{ + // Assumes that condition variable is locked. + for(ThreadInfoList::iterator it(mThreadInfoList.begin()), itEnd(mThreadInfoList.end()); it != itEnd; ++it) + { + ThreadInfo* const pThreadInfo = *it; + + // Fix any threads which have exited via a thread exit and not by simply returning to the caller. + const EA::Thread::Thread::Status status = pThreadInfo->mpThread->GetStatus(); + + if(status == EA::Thread::Thread::kStatusEnded) + pThreadInfo->mpThread->Begin(ThreadFunction, pThreadInfo, &mDefaultThreadParameters); + } +} + + +EA::Thread::ThreadPool* EA::Thread::ThreadPoolFactory::CreateThreadPool() +{ + if(gpAllocator) + return new(gpAllocator->Alloc(sizeof(EA::Thread::ThreadPool))) EA::Thread::ThreadPool; + else + return new EA::Thread::ThreadPool; +} + + +void EA::Thread::ThreadPoolFactory::DestroyThreadPool(EA::Thread::ThreadPool* pThreadPool) +{ + if(gpAllocator) + { + pThreadPool->~ThreadPool(); + gpAllocator->Free(pThreadPool); + } + else + delete pThreadPool; +} + + +size_t EA::Thread::ThreadPoolFactory::GetThreadPoolSize() +{ + return sizeof(EA::Thread::ThreadPool); +} + + +EA::Thread::ThreadPool* EA::Thread::ThreadPoolFactory::ConstructThreadPool(void* pMemory) +{ + return new(pMemory) EA::Thread::ThreadPool; +} + + +void EA::Thread::ThreadPoolFactory::DestructThreadPool(EA::Thread::ThreadPool* pThreadPool) +{ + pThreadPool->~ThreadPool(); +} + +EA_RESTORE_VC_WARNING() diff --git a/src/thirdparty/ea/EAThread/source/eathread_rwmutex.cpp b/src/thirdparty/ea/EAThread/source/eathread_rwmutex.cpp new file mode 100644 index 00000000..e4bec53b --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_rwmutex.cpp @@ -0,0 +1,259 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include + +EA_DISABLE_VC_WARNING(4985) // 'ceil': attributes not present on previous declaration.1> C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\INCLUDE\intrin.h(142) : see declaration of 'ceil' + +#include +#include +#include +#include // include new for placement new operator +#include + +EA_DISABLE_VC_WARNING(4996) // This function or variable may be unsafe / deprecated. + + EARWMutexData::EARWMutexData() + : mnReadWaiters(0), + mnWriteWaiters(0), + mnReaders(0), + mThreadIdWriter(EA::Thread::kThreadIdInvalid), + mMutex(NULL, false), + mReadCondition(NULL, false), + mWriteCondition(NULL, false) + { + // Empty + } + + + EA::Thread::RWMutexParameters::RWMutexParameters(bool bIntraProcess, const char* pName) + : mbIntraProcess(bIntraProcess) + { + (void)pName; // Suppress possible warnings. + + #ifdef EA_PLATFORM_WINDOWS + if(pName) + { + strncpy(mName, pName, sizeof(mName)-1); + mName[sizeof(mName)-1] = 0; + } + else + mName[0] = 0; + #endif + } + + + EA::Thread::RWMutex::RWMutex(const RWMutexParameters* pRWMutexParameters, bool bDefaultParameters) + { + if(!pRWMutexParameters && bDefaultParameters) + { + RWMutexParameters parameters; + Init(¶meters); + } + else + Init(pRWMutexParameters); + } + + + EA::Thread::RWMutex::~RWMutex() + { + // Possibly do asserts here. + } + + + bool EA::Thread::RWMutex::Init(const RWMutexParameters* pRWMutexParameters) + { + if(pRWMutexParameters) + { + #if EATHREAD_MULTIPROCESSING_OS + EAT_ASSERT(pRWMutexParameters->mbIntraProcess); // We don't currently have support for intra-process RWMutex on these platforms (and any multi-process platform). + #endif + + MutexParameters mup(pRWMutexParameters->mbIntraProcess); + mRWMutexData.mMutex.Init(&mup); + + ConditionParameters mop(pRWMutexParameters->mbIntraProcess); + mRWMutexData.mReadCondition.Init(&mop); + mRWMutexData.mWriteCondition.Init(&mop); + return true; + } + + return false; + } + + + int EA::Thread::RWMutex::Lock(LockType lockType, const ThreadTime& timeoutAbsolute) + { + int result = 0; + + mRWMutexData.mMutex.Lock(); // This lock should always be fast, as it belongs to us and we only hold onto it very temporarily. + EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1); + + // We cannot obtain a write lock recursively, else we will deadlock. + // Alternatively, we can build a bunch of extra logic to deal with this. + EAT_ASSERT(mRWMutexData.mThreadIdWriter != GetThreadId()); + + // Assert that there aren't both readers and writers at the same time. + EAT_ASSERT(!((mRWMutexData.mThreadIdWriter != kThreadIdInvalid) && mRWMutexData.mnReaders)); + + if(lockType == kLockTypeRead) + { + while(mRWMutexData.mThreadIdWriter != kThreadIdInvalid) + { + EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1); + + mRWMutexData.mnReadWaiters++; + const Condition::Result mresult = mRWMutexData.mReadCondition.Wait(&mRWMutexData.mMutex, timeoutAbsolute); + mRWMutexData.mnReadWaiters--; + + EAT_ASSERT(mresult != EA::Thread::Condition::kResultError); + EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1); + + if(mresult == Condition::kResultTimeout) + { + mRWMutexData.mMutex.Unlock(); + return kResultTimeout; + } + } + + result = ++mRWMutexData.mnReaders; // This is not an atomic operation. We are within a mutex lock. + } + else if(lockType == kLockTypeWrite) + { + while((mRWMutexData.mnReaders > 0) || (mRWMutexData.mThreadIdWriter != kThreadIdInvalid)) + { + EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1); + + mRWMutexData.mnWriteWaiters++; + const Condition::Result mresult = mRWMutexData.mWriteCondition.Wait(&mRWMutexData.mMutex, timeoutAbsolute); + mRWMutexData.mnWriteWaiters--; + + EAT_ASSERT(mresult != EA::Thread::Condition::kResultError); + EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1); + + if(mresult == Condition::kResultTimeout) + { + mRWMutexData.mMutex.Unlock(); + return kResultTimeout; + } + } + + result = 1; + mRWMutexData.mThreadIdWriter = GetThreadId(); + } + + EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1); + mRWMutexData.mMutex.Unlock(); + + return result; + } + + + int EA::Thread::RWMutex::Unlock() + { + mRWMutexData.mMutex.Lock(); // This lock should always be fast, as it belongs to us and we only hold onto it very temporarily. + EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1); + + if(mRWMutexData.mThreadIdWriter != kThreadIdInvalid) + { + EAT_ASSERT(mRWMutexData.mThreadIdWriter == GetThreadId()); + + //Possibly enable this if we want some runtime error checking at some cost. + //if(mRWMutexData.mThreadIdWriter == GetThreadId()){ + // mRWMutexData.mMutex.Unlock(); + // return kResultError; + //} + + mRWMutexData.mThreadIdWriter = kThreadIdInvalid; + } + else + { + EAT_ASSERT(mRWMutexData.mnReaders >= 1); + + //Possibly enable this if we want some runtime error checking at some cost. + //if(mRWMutexData.mnReaders < 1){ + // mRWMutexData.mMutex.Unlock(); + // return kResultError; + //} + + const int nNewReaders = --mRWMutexData.mnReaders; // This is not an atomic operation. We are within a mutex lock. + if(nNewReaders > 0) + { + EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1); + mRWMutexData.mMutex.Unlock(); + return nNewReaders; + } + } + + if(mRWMutexData.mnWriteWaiters > 0) + mRWMutexData.mWriteCondition.Signal(false); + else if(mRWMutexData.mnReadWaiters > 0) + mRWMutexData.mReadCondition.Signal(true); + + EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1); + mRWMutexData.mMutex.Unlock(); + + return 0; + } + + + int EA::Thread::RWMutex::GetLockCount(LockType lockType) + { + if(lockType == kLockTypeRead) + return mRWMutexData.mnReaders; + else if((lockType == kLockTypeWrite) && (mRWMutexData.mThreadIdWriter != kThreadIdInvalid)) + return 1; + return 0; + } + + + + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + } +} + + +EA::Thread::RWMutex* EA::Thread::RWMutexFactory::CreateRWMutex() +{ + if(gpAllocator) + return new(gpAllocator->Alloc(sizeof(EA::Thread::RWMutex))) EA::Thread::RWMutex; + else + return new EA::Thread::RWMutex; +} + +void EA::Thread::RWMutexFactory::DestroyRWMutex(EA::Thread::RWMutex* pRWMutex) +{ + if(gpAllocator) + { + pRWMutex->~RWMutex(); + gpAllocator->Free(pRWMutex); + } + else + delete pRWMutex; +} + +size_t EA::Thread::RWMutexFactory::GetRWMutexSize() +{ + return sizeof(EA::Thread::RWMutex); +} + +EA::Thread::RWMutex* EA::Thread::RWMutexFactory::ConstructRWMutex(void* pMemory) +{ + return new(pMemory) EA::Thread::RWMutex; +} + +void EA::Thread::RWMutexFactory::DestructRWMutex(EA::Thread::RWMutex* pRWMutex) +{ + pRWMutex->~RWMutex(); +} + +EA_RESTORE_VC_WARNING() // 4996 +EA_RESTORE_VC_WARNING() // 4985 + + diff --git a/src/thirdparty/ea/EAThread/source/eathread_rwmutex_ip.cpp b/src/thirdparty/ea/EAThread/source/eathread_rwmutex_ip.cpp new file mode 100644 index 00000000..00fc2ed5 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_rwmutex_ip.cpp @@ -0,0 +1,361 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include // include new for placement new operator +#include + + +#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) + + /////////////////////////////////////////////////////////////////////////// + // EARWMutexIPData + /////////////////////////////////////////////////////////////////////////// + + EA::Thread::EARWMutexIPData::EARWMutexIPData() + : mSharedData(), // This still needs to be Init-ed. + mMutex(NULL), + mReadSemaphore(NULL), + mWriteSemaphore(NULL) + { + } + + EA::Thread::EARWMutexIPData::~EARWMutexIPData() + { + // mSharedData.Shutdown(); // This shouldn't be necessary, as the SharedData dtor will do this itself. + } + + bool EA::Thread::EARWMutexIPData::Init(const char* pName) + { + char mutexName[256]; + mutexName[0] = '\0'; + if(pName) + strcpy(mutexName, pName); + strcat(mutexName, ".Mutex"); + mMutex = CreateMutexA(NULL, FALSE, mutexName); + + char readSemaphoreName[256]; + readSemaphoreName[0] = '\0'; + if(pName) + strcpy(readSemaphoreName, pName); + strcat(readSemaphoreName, ".SemR"); + mReadSemaphore = CreateSemaphoreA(NULL, 0, 9999, readSemaphoreName); + + char writeSemaphoreName[256]; + writeSemaphoreName[0] = '\0'; + if(pName) + strcpy(writeSemaphoreName, pName); + strcat(writeSemaphoreName, ".SemW"); + mWriteSemaphore = CreateSemaphoreA(NULL, 0, 9999, writeSemaphoreName); + + return mSharedData.Init(pName); + } + + void EA::Thread::EARWMutexIPData::Shutdown() + { + if(mMutex) + { + CloseHandle(mMutex); + mMutex = NULL; + } + + if(mReadSemaphore) + { + CloseHandle(mReadSemaphore); + mReadSemaphore = NULL; + } + + if(mWriteSemaphore) + { + CloseHandle(mWriteSemaphore); + mWriteSemaphore = NULL; + } + + mSharedData.Shutdown(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // RWMutexIPParameters + /////////////////////////////////////////////////////////////////////////// + + EA::Thread::RWMutexIPParameters::RWMutexIPParameters(bool bIntraProcess, const char* pName) + : mbIntraProcess(bIntraProcess) + { + #ifdef EA_PLATFORM_WINDOWS + if(pName) + { + strncpy(mName, pName, sizeof(mName)-1); + mName[sizeof(mName)-1] = 0; + } + else + mName[0] = 0; + #else + (void)pName; // Suppress possible warnings. + #endif + } + + + + /////////////////////////////////////////////////////////////////////////// + // RWMutexIP + /////////////////////////////////////////////////////////////////////////// + + EA::Thread::RWMutexIP::RWMutexIP(const RWMutexIPParameters* pRWMutexIPParameters, bool bDefaultParameters) + { + if(!pRWMutexIPParameters && bDefaultParameters) + { + RWMutexIPParameters parameters; + Init(¶meters); + } + else + Init(pRWMutexIPParameters); + } + + + EA::Thread::RWMutexIP::~RWMutexIP() + { + } + + + bool EA::Thread::RWMutexIP::Init(const RWMutexIPParameters* pRWMutexIPParameters) + { + if(pRWMutexIPParameters) + { + // Must provide a valid name for inter-process RWMutex. + EAT_ASSERT(pRWMutexIPParameters->mbIntraProcess || pRWMutexIPParameters->mName[0]); + + return mRWMutexIPData.Init(pRWMutexIPParameters->mName); + } + + return false; + } + + + int EA::Thread::RWMutexIP::Lock(LockType lockType, const ThreadTime& /*timeoutAbsolute*/) + { + int result = 0; + + WaitForSingleObject(mRWMutexIPData.mMutex, INFINITE); // This lock should always be fast, as it belongs to us and we only hold onto it very temporarily. + //EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1); + + // We cannot obtain a write lock recursively, else we will deadlock. + // Alternatively, we can build a bunch of extra logic to deal with this. + EAT_ASSERT(mRWMutexIPData.mSharedData->mThreadIdWriter != ::GetCurrentThreadId()); + + // Assert that there aren't both readers and writers at the same time. + EAT_ASSERT(!((mRWMutexIPData.mSharedData->mThreadIdWriter != kSysThreadIdInvalid) && mRWMutexIPData.mSharedData->mnReaders)); + + if(lockType == kLockTypeRead) + { + while(mRWMutexIPData.mSharedData->mThreadIdWriter != kSysThreadIdInvalid) + { + //EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1); + + mRWMutexIPData.mSharedData->mnReadWaiters++; + ReleaseMutex(mRWMutexIPData.mMutex); + DWORD dwResult = WaitForSingleObject(mRWMutexIPData.mReadSemaphore, INFINITE); // To do: support timeoutAbsolute + WaitForSingleObject(mRWMutexIPData.mMutex, INFINITE); + mRWMutexIPData.mSharedData->mnReadWaiters--; + + EAT_ASSERT(dwResult != WAIT_FAILED); + //EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1); + + if(dwResult == WAIT_TIMEOUT) + { + ReleaseMutex(mRWMutexIPData.mMutex); + return kResultTimeout; + } + } + + result = ++mRWMutexIPData.mSharedData->mnReaders; // This is not an atomic operation. We are within a mutex lock. + } + else if(lockType == kLockTypeWrite) + { + while((mRWMutexIPData.mSharedData->mnReaders > 0) || // While somebody has the read lock or + (mRWMutexIPData.mSharedData->mThreadIdWriter != kSysThreadIdInvalid)) // somebody has the write lock... go back to waiting. + { + //EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1); + + mRWMutexIPData.mSharedData->mnWriteWaiters++; + ReleaseMutex(mRWMutexIPData.mMutex); + DWORD dwResult = WaitForSingleObject(mRWMutexIPData.mWriteSemaphore, INFINITE); // To do: support timeoutAbsolute + WaitForSingleObject(mRWMutexIPData.mMutex, INFINITE); + mRWMutexIPData.mSharedData->mnWriteWaiters--; + + EAT_ASSERT(dwResult != WAIT_FAILED); + //EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1); + + if(dwResult == WAIT_TIMEOUT) + { + ReleaseMutex(mRWMutexIPData.mMutex); + return kResultTimeout; + } + } + + result = 1; + mRWMutexIPData.mSharedData->mThreadIdWriter = ::GetCurrentThreadId(); + } + + //EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1); + ReleaseMutex(mRWMutexIPData.mMutex); + + return result; + } + + + int EA::Thread::RWMutexIP::Unlock() + { + WaitForSingleObject(mRWMutexIPData.mMutex, INFINITE); // This lock should always be fast, as it belongs to us and we only hold onto it very temporarily. + //EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1); + + if(mRWMutexIPData.mSharedData->mThreadIdWriter != kSysThreadIdInvalid) // If we have a write lock... + { + EAT_ASSERT(mRWMutexIPData.mSharedData->mThreadIdWriter == ::GetCurrentThreadId()); + mRWMutexIPData.mSharedData->mThreadIdWriter = kSysThreadIdInvalid; + } + else // Else we have a read lock... + { + EAT_ASSERT(mRWMutexIPData.mSharedData->mnReaders >= 1); + + const int nNewReaders = --mRWMutexIPData.mSharedData->mnReaders; // This is not an atomic operation. We are within a mutex lock. + if(nNewReaders > 0) + { + //EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1); + ReleaseMutex(mRWMutexIPData.mMutex); + return nNewReaders; + } + } + + if(mRWMutexIPData.mSharedData->mnWriteWaiters > 0) // We ignore the possibility that + { + ReleaseSemaphore(mRWMutexIPData.mWriteSemaphore, 1, NULL); + // We rely on the released write waiter to decrement mnWriteWaiters. + // If the released write waiter doesn't wake up for a while, it's possible that the ReleaseMutex below + // will be called and another read unlocker will execute this code and release the semaphore again and + // we will have two writers that are released. But this isn't a problem because the released writers + // must still lock our mMutex and contend for the write lock, and one of the two will fail and go back + // to waiting on the semaphore. + } + else if(mRWMutexIPData.mSharedData->mnReadWaiters > 0) + { + // I'm a little concerned about this signal here. We release mnReadWaiters, though it's possible + // that a reader could timeout before this function completes and not all the semaphore count + // will be claimed by waiters. However, the read wait code in the Lock function above does + // seem to be able to handle this case, as it does do a check to make sure it can hold the read + // lock before it claims it. + ReleaseSemaphore(mRWMutexIPData.mReadSemaphore, mRWMutexIPData.mSharedData->mnReadWaiters, NULL); + } + + //EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1); + ReleaseMutex(mRWMutexIPData.mMutex); + + return 0; + } + + + int EA::Thread::RWMutexIP::GetLockCount(LockType lockType) + { + if(lockType == kLockTypeRead) + return mRWMutexIPData.mSharedData->mnReaders; + else if((lockType == kLockTypeWrite) && (mRWMutexIPData.mSharedData->mThreadIdWriter != kSysThreadIdInvalid)) + return 1; + return 0; + } + + +#else + + EA::Thread::RWMutexIPParameters::RWMutexIPParameters(bool /*bIntraProcess*/, const char* /*pName*/) + { + } + + + EA::Thread::RWMutexIP::RWMutexIP(const RWMutexIPParameters* /*pRWMutexIPParameters*/, bool /*bDefaultParameters*/) + { + } + + + EA::Thread::RWMutexIP::~RWMutexIP() + { + } + + + bool EA::Thread::RWMutexIP::Init(const RWMutexIPParameters* /*pRWMutexIPParameters*/) + { + return false; + } + + + int EA::Thread::RWMutexIP::Lock(LockType /*lockType*/, const ThreadTime& /*timeoutAbsolute*/) + { + return 0; + } + + + int EA::Thread::RWMutexIP::Unlock() + { + return 0; + } + + + int EA::Thread::RWMutexIP::GetLockCount(LockType /*lockType*/) + { + return 0; + } + +#endif // EA_PLATFORM_XXX + + + + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + } +} + + +EA::Thread::RWMutexIP* EA::Thread::RWMutexIPFactory::CreateRWMutexIP() +{ + if(gpAllocator) + return new(gpAllocator->Alloc(sizeof(EA::Thread::RWMutexIP))) EA::Thread::RWMutexIP; + else + return new EA::Thread::RWMutexIP; +} + +void EA::Thread::RWMutexIPFactory::DestroyRWMutexIP(EA::Thread::RWMutexIP* pRWMutexIP) +{ + if(gpAllocator) + { + pRWMutexIP->~RWMutexIP(); + gpAllocator->Free(pRWMutexIP); + } + else + delete pRWMutexIP; +} + +size_t EA::Thread::RWMutexIPFactory::GetRWMutexIPSize() +{ + return sizeof(EA::Thread::RWMutexIP); +} + +EA::Thread::RWMutexIP* EA::Thread::RWMutexIPFactory::ConstructRWMutexIP(void* pMemory) +{ + return new(pMemory) EA::Thread::RWMutexIP; +} + +void EA::Thread::RWMutexIPFactory::DestructRWMutexIP(EA::Thread::RWMutexIP* pRWMutexIP) +{ + pRWMutexIP->~RWMutexIP(); +} + + + + + diff --git a/src/thirdparty/ea/EAThread/source/eathread_semaphore.cpp b/src/thirdparty/ea/EAThread/source/eathread_semaphore.cpp new file mode 100644 index 00000000..a7c9a6fc --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_semaphore.cpp @@ -0,0 +1,351 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +EA_DISABLE_VC_WARNING(4574) +#include +#include +EA_RESTORE_VC_WARNING() + +#if !EA_THREADS_AVAILABLE + #include +#elif EATHREAD_USE_SYNTHESIZED_SEMAPHORE + // Fall through. +#elif 0 //EA_USE_CPP11_CONCURRENCY + #include "cpp11/eathread_semaphore_cpp11.cpp" +#elif defined(EA_PLATFORM_APPLE) + #include "apple/eathread_semaphore_apple.cpp" +#elif defined(EA_PLATFORM_ANDROID) + #include "android/eathread_semaphore_android.cpp" +#elif defined(EA_PLATFORM_SONY) + #include "sony/eathread_semaphore_sony.cpp" +#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include "unix/eathread_semaphore_unix.cpp" +#elif defined(EA_PLATFORM_MICROSOFT) + #include "pc/eathread_semaphore_pc.cpp" +#endif + + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + } +} + + +EA::Thread::Semaphore* EA::Thread::SemaphoreFactory::CreateSemaphore() +{ + if(gpAllocator) + return new(gpAllocator->Alloc(sizeof(EA::Thread::Semaphore))) EA::Thread::Semaphore; + else + return new EA::Thread::Semaphore; +} + +void EA::Thread::SemaphoreFactory::DestroySemaphore(EA::Thread::Semaphore* pSemaphore) +{ + if(gpAllocator) + { + pSemaphore->~Semaphore(); + gpAllocator->Free(pSemaphore); + } + else + delete pSemaphore; +} + +size_t EA::Thread::SemaphoreFactory::GetSemaphoreSize() +{ + return sizeof(EA::Thread::Semaphore); +} + +EA::Thread::Semaphore* EA::Thread::SemaphoreFactory::ConstructSemaphore(void* pMemory) +{ + return new(pMemory) EA::Thread::Semaphore; +} + +void EA::Thread::SemaphoreFactory::DestructSemaphore(EA::Thread::Semaphore* pSemaphore) +{ + pSemaphore->~Semaphore(); +} + + + +#if EATHREAD_USE_SYNTHESIZED_SEMAPHORE + + EASemaphoreData::EASemaphoreData() + : mCV(), + mMutex(), + mnCount(0), + mnMaxCount(INT_MAX), + mbValid(false) + { + // Empty + } + + + EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* pName) + : mInitialCount(initialCount), + mMaxCount(INT_MAX), + mbIntraProcess(bIntraProcess) + { + if(pName) + { + strncpy(mName, pName, sizeof(mName)-1); + mName[sizeof(mName)-1] = 0; + } + else + mName[0] = 0; + } + + + EA::Thread::Semaphore::Semaphore(const SemaphoreParameters* pSemaphoreParameters, bool bDefaultParameters) + { + if(!pSemaphoreParameters && bDefaultParameters) + { + SemaphoreParameters parameters; + Init(¶meters); + } + else + Init(pSemaphoreParameters); + } + + + EA::Thread::Semaphore::Semaphore(int initialCount) + { + SemaphoreParameters parameters(initialCount); + Init(¶meters); + } + + + EA::Thread::Semaphore::~Semaphore() + { + EAT_ASSERT(!mSemaphoreData.mMutex.HasLock()); // The mMutex destructor will also assert this, but here it makes it more obvious this mutex is ours. + } + + + bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters) + { + if(pSemaphoreParameters && (!mSemaphoreData.mbValid)) + { + mSemaphoreData.mbValid = true; // It's not really true unless our member mCV and mMutex init OK. To do: Added functions to our classes that verify they are OK. + mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount; + mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount; + + if(mSemaphoreData.mnCount < 0) + mSemaphoreData.mnCount = 0; + + return mSemaphoreData.mbValid; + } + + return false; + } + + + int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute) + { + int nReturnValue = kResultError; + int result = mSemaphoreData.mMutex.Lock(); // This mutex is owned by us and will be unlocked immediately in the mCV.Wait call, so we don't apply timeoutAbsolute. To consider: Maybe we should do so, though it's less efficient. + + if(result > 0) // If success... + { + if(timeoutAbsolute == kTimeoutImmediate) + { + if(mSemaphoreData.mnCount.GetValue() >= 1) + nReturnValue = mSemaphoreData.mnCount.Decrement(); + else + nReturnValue = kResultTimeout; + } + else + { + if(mSemaphoreData.mnCount.GetValue() >= 1) // If we can decrement it immediately... + nReturnValue = mSemaphoreData.mnCount.Decrement(); + else // Else we need to wait. + { + Condition::Result cResult; + + do{ + cResult = mSemaphoreData.mCV.Wait(&mSemaphoreData.mMutex, timeoutAbsolute); + } while((cResult == Condition::kResultOK) && (mSemaphoreData.mnCount.GetValue() < 1)); // Always need to check the condition and retry if not matched. In rare cases two threads could return from Wait. + + if(cResult == Condition::kResultOK) // If apparent success... + nReturnValue = mSemaphoreData.mnCount.Decrement(); + else if(cResult == Condition::kResultTimeout) + nReturnValue = kResultTimeout; + else + { + // We return immediately here because mCV.Wait has not locked the mutex for + // us and so we don't want to fall through and unlock it below. Also, it would + // be inefficient for us to lock here and fall through only to unlock it below. + return nReturnValue; + } + } + } + + result = mSemaphoreData.mMutex.Unlock(); + EAT_ASSERT(result >= 0); + if(result < 0) + nReturnValue = kResultError; // This Semaphore is now considered dead and unusable. + } + + return nReturnValue; + } + + + int EA::Thread::Semaphore::Post(int count) + { + EAT_ASSERT(mSemaphoreData.mnCount >= 0); + + int newValue = kResultError; + int result = mSemaphoreData.mMutex.Lock(); + + if(result > 0) + { + // Set the new value to be whatever the current value is. + newValue = mSemaphoreData.mnCount.GetValue(); + + if((mSemaphoreData.mnMaxCount - count) < newValue) // If count would cause an overflow... + return kResultError; // We do what most OS implementations of max-count do. count = (mSemaphoreData.mnMaxCount - newValue); + + newValue = mSemaphoreData.mnCount.Add(count); + + bool bResult = mSemaphoreData.mCV.Signal(true); // Signal broadcast (the true arg) because semaphores could have multiple counts and multiple threads waiting for them. There's a potential "thundering herd" problem here. + EAT_ASSERT(bResult); + EA_UNUSED(bResult); + + result = mSemaphoreData.mMutex.Unlock(); // Important that we lock after the mCV.Signal. + EAT_ASSERT(result >= 0); + if(result < 0) + newValue = kResultError; // This Semaphore is now considered dead and unusable. + } + + return newValue; + } + + + int EA::Thread::Semaphore::GetCount() const + { + return mSemaphoreData.mnCount.GetValue(); + } + +#elif !EA_THREADS_AVAILABLE + + /////////////////////////////////////////////////////////////////////////////// + // non-threaded implementation + /////////////////////////////////////////////////////////////////////////////// + + EASemaphoreData::EASemaphoreData() + : mnCount(0), + mnMaxCount(INT_MAX) + { + // Empty + } + + + EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* pName) + : mInitialCount(initialCount), + mMaxCount(INT_MAX), + mbIntraProcess(bIntraProcess) + { + if(pName) + { + strncpy(mName, pName, sizeof(mName)-1); + mName[sizeof(mName)-1] = 0; + } + else + mName[0] = 0; + } + + + EA::Thread::Semaphore::Semaphore(const SemaphoreParameters* pSemaphoreParameters, bool bDefaultParameters) + { + if(!pSemaphoreParameters && bDefaultParameters) + { + SemaphoreParameters parameters; + Init(¶meters); + } + else + Init(pSemaphoreParameters); + } + + + EA::Thread::Semaphore::Semaphore(int initialCount) + { + SemaphoreParameters parameters(initialCount); + Init(¶meters); + } + + + EA::Thread::Semaphore::~Semaphore() + { + } + + + bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters) + { + if(pSemaphoreParameters) + { + mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount; + mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount; + return true; + } + + return false; + } + + + int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute) + { + if(timeoutAbsolute == kTimeoutNone) + { + while(mSemaphoreData.mnCount <= 0) + ThreadSleep(1); + + --mSemaphoreData.mnCount; + } + else if(timeoutAbsolute == 0) + { + if(mSemaphoreData.mnCount) + --mSemaphoreData.mnCount; + else + return kResultTimeout; + } + else + { + while((mSemaphoreData.mnCount <= 0) && (GetThreadTime() < timeoutAbsolute)) + ThreadSleep(1); + + if(mSemaphoreData.mnCount <= 0) + return kResultTimeout; + } + + return mSemaphoreData.mnCount; + } + + + int EA::Thread::Semaphore::Post(int count) + { + EAT_ASSERT(mSemaphoreData.mnCount >= 0); + + // Ideally, what we would do is account for the number of waiters in + // this overflow calculation. If max-count = 4, count = 6, waiters = 8, + // we would release 6 waiters and leave the semaphore at 2. + // The problem is that some of those 6 waiters might time out while we + // are doing this and leave ourselves with count greater than max-count. + if((mSemaphoreData.mnMaxCount - count) < mSemaphoreData.mnCount) // If count would cause an overflow... + return kResultError; // We do what most OS implementations of max-count do. // count = (mSemaphoreData.mnMaxCount - nLastCount); + + return (mSemaphoreData.mnCount += count); + } + + + int EA::Thread::Semaphore::GetCount() const + { + return mSemaphoreData.mnCount; + } + +#endif // !EA_THREADS_AVAILABLE + diff --git a/src/thirdparty/ea/EAThread/source/eathread_storage.cpp b/src/thirdparty/ea/EAThread/source/eathread_storage.cpp new file mode 100644 index 00000000..eb4d8940 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_storage.cpp @@ -0,0 +1,350 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +EA_DISABLE_VC_WARNING(4574) +#include +EA_RESTORE_VC_WARNING() + +#if defined(EA_PLATFORM_SONY) + #include + + EA::Thread::ThreadLocalStorage::ThreadLocalStorage() + : mTLSData() + { + // To consider: Support the specification of a destructor instead of just passing NULL. + mTLSData.mResult = scePthreadKeyCreate(&mTLSData.mKey, NULL); + EAT_ASSERT(mTLSData.mResult == 0); + } + + + EA::Thread::ThreadLocalStorage::~ThreadLocalStorage() + { + if(mTLSData.mResult == 0) + scePthreadKeyDelete(mTLSData.mKey); + } + + + void* EA::Thread::ThreadLocalStorage::GetValue() + { + return scePthreadGetspecific(mTLSData.mKey); + } + + + bool EA::Thread::ThreadLocalStorage::SetValue(const void* pData) + { + if(scePthreadSetspecific(mTLSData.mKey, pData) == 0) + return true; + return false; + } + + + +#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && !defined(EA_PLATFORM_NX) + #if defined(EA_PLATFORM_UNIX) + #include + #elif defined(EA_PLATFORM_WINDOWS) + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() + #endif + + EA::Thread::ThreadLocalStorage::ThreadLocalStorage() + : mTLSData() + { + // To consider: Support the specification of a destructor instead of just passing NULL. + mTLSData.mResult = pthread_key_create(&mTLSData.mKey, NULL); + EAT_ASSERT(mTLSData.mResult == 0); + } + + + EA::Thread::ThreadLocalStorage::~ThreadLocalStorage() + { + if(mTLSData.mResult == 0) + pthread_key_delete(mTLSData.mKey); + } + + + void* EA::Thread::ThreadLocalStorage::GetValue() + { + return pthread_getspecific(mTLSData.mKey); + } + + + bool EA::Thread::ThreadLocalStorage::SetValue(const void* pData) + { + if(pthread_setspecific(mTLSData.mKey, pData) == 0) + return true; + return false; + } + + + +#elif defined(EA_PLATFORM_MICROSOFT) && !defined(EA_PLATFORM_WINDOWS_PHONE) && !(defined(EA_PLATFORM_WINDOWS) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)) + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() + + EA::Thread::ThreadLocalStorage::ThreadLocalStorage() + : mTLSData(TlsAlloc()) + { + EAT_ASSERT(mTLSData != TLS_OUT_OF_INDEXES); + } + + + EA::Thread::ThreadLocalStorage::~ThreadLocalStorage() + { + if(mTLSData != TLS_OUT_OF_INDEXES) + TlsFree(mTLSData); + } + + + void* EA::Thread::ThreadLocalStorage::GetValue() + { + return TlsGetValue(mTLSData); + } + + + bool EA::Thread::ThreadLocalStorage::SetValue(const void* pData) + { + if(TlsSetValue(mTLSData, (void*)pData)) + return true; + return false; + } + +#elif (!EA_THREADS_AVAILABLE || defined(EA_PLATFORM_CONSOLE)) && !defined(EA_PLATFORM_NX) + + #include + + #if !EA_THREADS_AVAILABLE + #define OSEnableInterrupts() + #define OSDisableInterrupts() + #else + #error Need to define EnableInterrupts/DisableInterrupts for the given platform. + #endif + + + EAThreadLocalStorageData::ThreadToDataPair* EAThreadLocalStorageData::GetTLSEntry(bool bCreateIfNotFound) + { + const int kArraySize = (sizeof(mDataArray) / sizeof(mDataArray[0])); + ThreadToDataPair* pCurrent, *pEnd; + + EA::Thread::ThreadUniqueId nCurrentThreadID; + EAThreadGetUniqueId(nCurrentThreadID); + + // The code below is likely to execute very quickly and never transfers + // execution outside the function, so we can very briefly disable interrupts + // for the period needed to do the logic below. + OSDisableInterrupts(); + + // We make the assumption that there are likely to be less than 10 threads most of + // the time. Thus, instead of maintaining a sorted array and do a binary search + // within that array, we do a linear search. An improvement would be to make the + // array be sorted if it goes above some preset size, such as 20. + for(pCurrent = mDataArray, pEnd = mDataArray + mDataArrayCount; pCurrent < pEnd; ++pCurrent) + { + if(pCurrent->mThreadID == nCurrentThreadID) + { + OSEnableInterrupts(); + return pCurrent; + } + } + + if((pCurrent >= pEnd) && ((mDataArrayCount + 1) < kArraySize) && bCreateIfNotFound) // If we didn't find it above and there is more room and we should create if not found... + { + pCurrent = mDataArray + mDataArrayCount++; + pCurrent->mThreadID = nCurrentThreadID; + } + else + pCurrent = NULL; + + OSEnableInterrupts(); + + return pCurrent; + } + + + EA::Thread::ThreadLocalStorage::ThreadLocalStorage() + { + memset(mTLSData.mDataArray, 0, sizeof(mTLSData.mDataArray)); + mTLSData.mDataArrayCount = 0; + } + + + EA::Thread::ThreadLocalStorage::~ThreadLocalStorage() + { + // Nothing to do. + } + + + void* EA::Thread::ThreadLocalStorage::GetValue() + { + EAThreadLocalStorageData::ThreadToDataPair* const pTDP = mTLSData.GetTLSEntry(false); + if(pTDP) + return (void*)pTDP->mpData; + return NULL; + } + + + bool EA::Thread::ThreadLocalStorage::SetValue(const void* pData) + { + if(pData == NULL) + { // We remove it from the container so that the container can have room for others. + EAThreadLocalStorageData::ThreadToDataPair* pTDP = mTLSData.GetTLSEntry(false); + + if(pTDP) + { + OSDisableInterrupts(); // Briefly disable interrupts for the duration of the logic below. + const EAThreadLocalStorageData::ThreadToDataPair* const pTDPEnd = mTLSData.mDataArray + mTLSData.mDataArrayCount; + while(++pTDP <= pTDPEnd) // What we do here is move all the other values downward. This is an O(n) operation, + pTDP[-1] = pTDP[0]; // but the number of unique threads usinug us is likely to be pretty small. + mTLSData.mDataArrayCount = (int)(pTDPEnd - mTLSData.mDataArray - 1); + OSEnableInterrupts(); + } + return true; + } + + EAThreadLocalStorageData::ThreadToDataPair* const pTDP = mTLSData.GetTLSEntry(true); + if(pTDP) + pTDP->mpData = pData; + return (pTDP != NULL); + } + +#else + + // Use reference std::map implementation. + EA_DISABLE_VC_WARNING(4574) + #include + EA_RESTORE_VC_WARNING() + + #include + + void** EAThreadLocalStorageData::GetTLSEntry(bool bCreateIfNotFound) + { + EA::Thread::ThreadUniqueId nThreadID; + EAThreadGetUniqueId(nThreadID); + + EA::Thread::AutoFutex autoFutex(mFutex); + + if(bCreateIfNotFound) // We expect this to be true most of the time. + { + // Create as needed + if (mThreadToDataMap == NULL) + { + mThreadToDataMap = new std::map; + } + + return (void**)(char*)&((*mThreadToDataMap)[nThreadID]); // Dereferencing a std::map value by index inserts the value if it is not present. + } + + if (mThreadToDataMap == NULL) + { + return NULL; + } + + std::map::iterator it(mThreadToDataMap->find(nThreadID)); + if(it != mThreadToDataMap->end()) + { + std::map::value_type& value = *it; + return (void**)(char*)&value.second; + } + return NULL; + } + + + EA::Thread::ThreadLocalStorage::ThreadLocalStorage() + { + } + + + EA::Thread::ThreadLocalStorage::~ThreadLocalStorage() + { + // Nothing to do. + } + + + void* EA::Thread::ThreadLocalStorage::GetValue() + { + void** const ppData = mTLSData.GetTLSEntry(false); + if(ppData) + return *ppData; + return NULL; + } + + + bool EA::Thread::ThreadLocalStorage::SetValue(const void* pData) + { + if(pData == NULL) + { + ThreadUniqueId nThreadID; + EAThreadGetUniqueId(nThreadID); + + EA::Thread::AutoFutex autoFutex(mTLSData.mFutex); + + if (mTLSData.mThreadToDataMap) + { + std::map::iterator it(mTLSData.mThreadToDataMap->find(nThreadID)); + if(it != mTLSData.mThreadToDataMap->end()) + mTLSData.mThreadToDataMap->erase(it); + } + return true; + } + + void** const ppData = mTLSData.GetTLSEntry(true); + if(ppData) + *ppData = (void*)pData; + return (*ppData != NULL); + } + +#endif + + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + } +} + + +EA::Thread::ThreadLocalStorage* EA::Thread::ThreadLocalStorageFactory::CreateThreadLocalStorage() +{ + if(gpAllocator) + return new(gpAllocator->Alloc(sizeof(EA::Thread::ThreadLocalStorage))) EA::Thread::ThreadLocalStorage; + else + return new EA::Thread::ThreadLocalStorage; +} + +void EA::Thread::ThreadLocalStorageFactory::DestroyThreadLocalStorage(EA::Thread::ThreadLocalStorage* pThreadLocalStorage) +{ + if(gpAllocator) + { + pThreadLocalStorage->~ThreadLocalStorage(); + gpAllocator->Free(pThreadLocalStorage); + } + else + delete pThreadLocalStorage; +} + +size_t EA::Thread::ThreadLocalStorageFactory::GetThreadLocalStorageSize() +{ + return sizeof(EA::Thread::ThreadLocalStorage); +} + +EA::Thread::ThreadLocalStorage* EA::Thread::ThreadLocalStorageFactory::ConstructThreadLocalStorage(void* pMemory) +{ + return new(pMemory) EA::Thread::ThreadLocalStorage; +} + +void EA::Thread::ThreadLocalStorageFactory::DestructThreadLocalStorage(EA::Thread::ThreadLocalStorage* pThreadLocalStorage) +{ + pThreadLocalStorage->~ThreadLocalStorage(); +} + + +#undef OSEnableInterrupts +#undef OSDisableInterrupts diff --git a/src/thirdparty/ea/EAThread/source/eathread_thread.cpp b/src/thirdparty/ea/EAThread/source/eathread_thread.cpp new file mode 100644 index 00000000..b0dc2454 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/eathread_thread.cpp @@ -0,0 +1,262 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "eathread/internal/eathread_global.h" +#include +#include +#include // include new for placement new operator + +#if !EA_THREADS_AVAILABLE + // Do nothing +#elif EA_USE_CPP11_CONCURRENCY + #include "cpp11/eathread_thread_cpp11.cpp" +#elif defined(EA_PLATFORM_SONY) + #include "sony/eathread_thread_sony.cpp" +#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include "unix/eathread_thread_unix.cpp" +#elif defined(EA_PLATFORM_MICROSOFT) + #include "pc/eathread_thread_pc.cpp" +#endif + + + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + } +} + + +EA::Thread::Thread* EA::Thread::ThreadFactory::CreateThread() +{ + if(gpAllocator) + return new(gpAllocator->Alloc(sizeof(EA::Thread::Thread))) EA::Thread::Thread; + else + return new EA::Thread::Thread; +} + +void EA::Thread::ThreadFactory::DestroyThread(EA::Thread::Thread* pThread) +{ + if(gpAllocator) + { + pThread->~Thread(); + gpAllocator->Free(pThread); + } + else + delete pThread; +} + +size_t EA::Thread::ThreadFactory::GetThreadSize() +{ + return sizeof(EA::Thread::Thread); +} + +EA::Thread::Thread* EA::Thread::ThreadFactory::ConstructThread(void* pMemory) +{ + return new(pMemory) EA::Thread::Thread; +} + +void EA::Thread::ThreadFactory::DestructThread(EA::Thread::Thread* pThread) +{ + pThread->~Thread(); +} + +EA::Thread::ThreadEnumData::ThreadEnumData() +: mpThreadDynamicData(NULL) +{ +} + +EA::Thread::ThreadEnumData::~ThreadEnumData() +{ + Release(); +} + +void EA::Thread::ThreadEnumData::Release() +{ + if(mpThreadDynamicData) + { + mpThreadDynamicData->Release(); + mpThreadDynamicData = NULL; + } +} + +extern const size_t kMaxThreadDynamicDataCount; +EATHREAD_GLOBALVARS_EXTERN_INSTANCE; +/////////////////////////////////////////////////////////////////////////////// +// +size_t EA::Thread::EnumerateThreads(ThreadEnumData* pDataArray, size_t dataArrayCapacity) +{ + size_t requiredCount = 0; + + if(dataArrayCapacity > EA::Thread::kMaxThreadDynamicDataCount) + dataArrayCapacity = EA::Thread::kMaxThreadDynamicDataCount; + + EATHREAD_GLOBALVARS.gThreadDynamicMutex.Lock(); + for(size_t i(0); i < EA::Thread::kMaxThreadDynamicDataCount; i++) + { + if(EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[i].GetValue() != 0) + { + if(requiredCount < dataArrayCapacity) + { + pDataArray[requiredCount].mpThreadDynamicData = (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + pDataArray[requiredCount].mpThreadDynamicData->AddRef(); + } + requiredCount++; + } + } + EATHREAD_GLOBALVARS.gThreadDynamicMutex.Unlock(); + + return requiredCount; +} + +/////////////////////////////////////////////////////////////////////////////// +// non-threaded implementation +/////////////////////////////////////////////////////////////////////////////// + +#if !EA_THREADS_AVAILABLE + + // If mulitithreading support is not available, we can't implement anything + // here that works. All we do is define a null implementation that links + // but fails all operations. + + + EA::Thread::ThreadParameters::ThreadParameters() + : mpStack(NULL), + mnStackSize(0), + mnPriority(kThreadPriorityDefault), + mnProcessor(kProcessorDefault), + mpName(""), + mbDisablePriorityBoost(false) + { + } + + + EA::Thread::Thread::Thread() + { + mThreadData.mpData = NULL; + } + + + EA::Thread::Thread::Thread(const Thread& /*t*/) + { + } + + + EA::Thread::Thread& EA::Thread::Thread::operator=(const Thread& /*t*/) + { + return *this; + } + + + EA::Thread::Thread::~Thread() + { + } + + + EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::sGlobalRunnableFunctionUserWrapper = NULL; + EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::sGlobalRunnableClassUserWrapper = NULL; + EA::Thread::AtomicInt32 EA::Thread::Thread::sDefaultProcessor = kProcessorAny; + EA::Thread::AtomicUint64 EA::Thread::Thread::sDefaultProcessorMask = UINT64_C(0xffffffffffffffff); + + + EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::GetGlobalRunnableFunctionUserWrapper() + { + return sGlobalRunnableFunctionUserWrapper; + } + + void EA::Thread::Thread::SetGlobalRunnableFunctionUserWrapper(EA::Thread::RunnableFunctionUserWrapper pUserWrapper) + { + if (sGlobalRunnableFunctionUserWrapper != NULL) + { + // Can only be set once in entire game. + EAT_ASSERT(false); + } + else + sGlobalRunnableFunctionUserWrapper = pUserWrapper; + } + + EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::GetGlobalRunnableClassUserWrapper() + { + return sGlobalRunnableClassUserWrapper; + } + + void EA::Thread::Thread::SetGlobalRunnableClassUserWrapper(EA::Thread::RunnableClassUserWrapper pUserWrapper) + { + if (sGlobalRunnableClassUserWrapper != NULL) + { + // Can only be set once in entire game. + EAT_ASSERT(false); + } + else + sGlobalRunnableClassUserWrapper = pUserWrapper; + } + + + EA::Thread::ThreadId EA::Thread::Thread::Begin(RunnableFunction /*pFunction*/, void* /*pContext*/, const ThreadParameters* /*pTP*/, RunnableFunctionUserWrapper /*pUserWrapper*/) + { + return kThreadIdInvalid; + } + + + EA::Thread::ThreadId EA::Thread::Thread::Begin(IRunnable* /*pRunnable*/, void* /*pContext*/, const ThreadParameters* /*pTP*/, RunnableClassUserWrapper /*pUserWrapper*/) + { + return kThreadIdInvalid; + } + + + EA::Thread::Thread::Status EA::Thread::Thread::WaitForEnd(const ThreadTime& /*timeoutAbsolute*/, intptr_t* /*pThreadReturnValue*/) + { + return kStatusNone; + } + + + EA::Thread::Thread::Status EA::Thread::Thread::GetStatus(intptr_t* /*pThreadReturnValue*/) const + { + return kStatusNone; + } + + + EA::Thread::ThreadId EA::Thread::Thread::GetId() const + { + return (ThreadId)kThreadIdInvalid; + } + + + int EA::Thread::Thread::GetPriority() const + { + return kThreadPriorityUnknown; + } + + + bool EA::Thread::Thread::SetPriority(int /*nPriority*/) + { + return false; + } + + + void EA::Thread::Thread::SetProcessor(int /*nProcessor*/) + { + } + + + void EA::Thread::Thread::Wake() + { + } + + + const char* EA::Thread::Thread::GetName() const + { + return ""; + } + + + void EA::Thread::Thread::SetName(const char* /*pName*/) + { + } + +#endif // !EA_THREADS_AVAILABLE + diff --git a/src/thirdparty/ea/EAThread/source/libunwind/eathread_callstack_libunwind.cpp b/src/thirdparty/ea/EAThread/source/libunwind/eathread_callstack_libunwind.cpp new file mode 100644 index 00000000..0ec763d5 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/libunwind/eathread_callstack_libunwind.cpp @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace EA +{ +namespace Thread +{ + +EA::Thread::ThreadLocalStorage sStackBase; + +/////////////////////////////////////////////////////////////////////////////// +// SetStackBase +// +EATHREADLIB_API void SetStackBase(void* pStackBase) +{ + if(pStackBase) + sStackBase.SetValue(pStackBase); + else + { + pStackBase = __builtin_frame_address(0); + + if(pStackBase) + SetStackBase(pStackBase); + // Else failure; do nothing. + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// GetStackBase +// +EATHREADLIB_API void* GetStackBase() +{ + void* pBase; + + if(GetPthreadStackInfo(&pBase, NULL)) + return pBase; + + // Else we require the user to have set this previously, usually via a call + // to SetStackBase() in the start function of this currently executing + // thread (or main for the main thread). + pBase = sStackBase.GetValue(); + + if(pBase == NULL) + pBase = (void*)(((uintptr_t)&pBase + 4095) & ~4095); // Make a guess, round up to next 4096. + + return pBase; +} + + +/////////////////////////////////////////////////////////////////////////////// +// GetStackLimit +// +EATHREADLIB_API void* GetStackLimit() +{ + void* pLimit; + + if(GetPthreadStackInfo(NULL, &pLimit)) + return pLimit; + + pLimit = __builtin_frame_address(0); + + return (void*)((uintptr_t)pLimit & ~4095); // Round down to nearest page, as the stack grows downward. +} + + + +} // namespace Thread +} // namespace EA + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/null/eathread_callstack_null.cpp b/src/thirdparty/ea/EAThread/source/null/eathread_callstack_null.cpp new file mode 100644 index 00000000..dcae4496 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/null/eathread_callstack_null.cpp @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + + +EA_DISABLE_VC_WARNING(4172) // returning address of local variable or temporary + + +namespace EA +{ +namespace Thread +{ + + +#if EA_THREADS_AVAILABLE + static EA::Thread::ThreadLocalStorage sStackBase; +#else + static void* sStackBase; +#endif + +/////////////////////////////////////////////////////////////////////////////// +// SetStackBase +// +EATHREADLIB_API void SetStackBase(void* pStackBase) +{ + if(pStackBase) + { + #if EA_THREADS_AVAILABLE + sStackBase.SetValue(pStackBase); + #else + sStackBase = pStackBase; + #endif + } + else + { + pStackBase = GetStackBase(); + SetStackBase(pStackBase); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// GetStackBase +// +EATHREADLIB_API void* GetStackBase() +{ + #if EA_THREADS_AVAILABLE + void* pStackBase = sStackBase.GetValue(); + #else + void* pStackBase = sStackBase; + #endif + + if(!pStackBase) + pStackBase = (void*)(((uintptr_t)GetStackLimit() + 4095) & ~4095); // Align up to nearest page, as the stack grows downward. + + return pStackBase; +} + + +/////////////////////////////////////////////////////////////////////////////// +// GetStackLimit +// +EATHREADLIB_API void* GetStackLimit() +{ + void* pStack = NULL; + + pStack = &pStack; + + return (void*)((uintptr_t)pStack & ~4095); // Round down to nearest page, as the stack grows downward. +} + +} // namespace Thread +} // namespace EA + +EA_RESTORE_VC_WARNING() + diff --git a/src/thirdparty/ea/EAThread/source/pc/eathread_callstack_win32.cpp b/src/thirdparty/ea/EAThread/source/pc/eathread_callstack_win32.cpp new file mode 100644 index 00000000..62122598 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/pc/eathread_callstack_win32.cpp @@ -0,0 +1,143 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +namespace EA +{ +namespace Thread +{ + +/////////////////////////////////////////////////////////////////////////////// +// GetThreadIdFromThreadHandle +// +// +EATHREADLIB_API uint32_t GetThreadIdFromThreadHandle(intptr_t threadId) +{ + struct THREAD_BASIC_INFORMATION_WIN32 + { + BOOL ExitStatus; + PVOID TebBaseAddress; + DWORD UniqueProcessId; + DWORD UniqueThreadId; + DWORD AffinityMask; + DWORD Priority; + DWORD BasePriority; + }; + + static HMODULE hKernel32 = NULL; + if (!hKernel32) + hKernel32 = LoadLibraryA("kernel32.dll"); + + if (hKernel32) + { + typedef DWORD (WINAPI *GetThreadIdFunc)(HANDLE); + + static GetThreadIdFunc pGetThreadIdFunc = NULL; + if (!pGetThreadIdFunc) + pGetThreadIdFunc = (GetThreadIdFunc)(uintptr_t)GetProcAddress(hKernel32, "GetThreadId"); + + if (pGetThreadIdFunc) + return pGetThreadIdFunc((HANDLE)threadId); + } + + + static HMODULE hNTDLL = NULL; + if (!hNTDLL) + hNTDLL = LoadLibraryA("ntdll.dll"); + + if (hNTDLL) + { + typedef LONG (WINAPI *NtQueryInformationThreadFunc)(HANDLE, int, PVOID, ULONG, PULONG); + + static NtQueryInformationThreadFunc pNtQueryInformationThread = NULL; + if (!pNtQueryInformationThread) + pNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread"); + + if (pNtQueryInformationThread) + { + THREAD_BASIC_INFORMATION_WIN32 tbi; + + if(pNtQueryInformationThread((HANDLE)threadId, 0, &tbi, sizeof(tbi), NULL) == 0) + return tbi.UniqueThreadId; + } + } + + return 0; +} + +namespace Internal +{ + +struct TIBStackInfo +{ + uintptr_t StackBase; + uintptr_t StackLimit; +}; + +static TIBStackInfo GetStackInfo() +{ + NT_TIB* pTib; + + /** + * Offset 0x18 from the FS segment register gives a pointer to + * the thread information block for the current thread + * https://en.wikipedia.org/wiki/Win32_Thread_Information_Block + */ + __asm { + mov eax, fs:[18h] + mov pTib, eax + } + + return { ((uintptr_t)pTib->StackBase), ((uintptr_t)pTib->StackLimit) }; +} + +} // namespace Internal + +/////////////////////////////////////////////////////////////////////////////// +// SetStackBase +// +EATHREADLIB_API void SetStackBase(void* /*pStackBase*/) +{ + // Nothing to do, as GetStackBase always works on its own. +} + + +/////////////////////////////////////////////////////////////////////////////// +// GetStackBase +// +EATHREADLIB_API void* GetStackBase() +{ + Internal::TIBStackInfo info = Internal::GetStackInfo(); + + return ((void*)info.StackBase); +} + + +/////////////////////////////////////////////////////////////////////////////// +// GetStackLimit +// +EATHREADLIB_API void* GetStackLimit() +{ + Internal::TIBStackInfo info = Internal::GetStackInfo(); + + return ((void*)info.StackLimit); + + // Alternative which returns a slightly different answer: + // We return our stack pointer, which is a good approximation of the stack limit of the caller. + // void* pStack = NULL; + // __asm { mov pStack, ESP}; + // return pStack; +} + + +} // namespace Thread +} // namespace EA diff --git a/src/thirdparty/ea/EAThread/source/pc/eathread_callstack_win64.cpp b/src/thirdparty/ea/EAThread/source/pc/eathread_callstack_win64.cpp new file mode 100644 index 00000000..327af371 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/pc/eathread_callstack_win64.cpp @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +namespace EA +{ +namespace Thread +{ + +/////////////////////////////////////////////////////////////////////////////// +// GetThreadIdFromThreadHandle +// +// This implementation is the same as the one in EAThread. +// Converts a thread HANDLE (threadId) to a thread id DWORD (sysThreadId). +// Recall that Windows has two independent thread identifier types. +// +EATHREADLIB_API uint32_t GetThreadIdFromThreadHandle(intptr_t threadId) +{ + // Win64 has this function natively, unlike earlier versions of 32 bit Windows. + return (uint32_t)::GetThreadId((HANDLE)threadId); +} + + +/////////////////////////////////////////////////////////////////////////////// +// SetStackBase +// +EATHREADLIB_API void SetStackBase(void* /*pStackBase*/) +{ + // Nothing to do, as GetStackBase always works on its own. +} + +/////////////////////////////////////////////////////////////////////////////// +// GetStackBase +// +EATHREADLIB_API void* GetStackBase() +{ + NT_TIB64* pTIB = (NT_TIB64*)NtCurrentTeb(); // NtCurrentTeb is defined in as an inline call to __readgsqword + return (void*)pTIB->StackBase; +} + + +/////////////////////////////////////////////////////////////////////////////// +// GetStackLimit +// +EATHREADLIB_API void* GetStackLimit() +{ + NT_TIB64* pTIB = (NT_TIB64*)NtCurrentTeb(); // NtCurrentTeb is defined in as an inline call to __readgsqword + return (void*)pTIB->StackLimit; + + // The following is an alternative implementation that returns the extent + // of the current stack usage as opposed to the stack limit as seen by the OS. + // This value will be a higher address than Tib.StackLimit (recall that the + // stack grows downward). It's debatable which of these two approaches is + // better, as one returns the thread's -usable- stack space while the + // other returns how much the thread is -currently- using. The determination + // of the usable stack space is complicated by the fact that Microsoft + // platforms auto-extend the stack if the process pushes beyond the current limit. + // In the end the Tib.StackLimit solution is actually the most portable across + // Microsoft OSs and compilers for those OSs (Microsoft or not). + + // Alternative implementation: + // We return our stack pointer, which is a good approximation of the stack limit of the caller. + // void* rsp = GetRSP(); + // return rsp; +} + + +} // namespace Thread +} // namespace EA diff --git a/src/thirdparty/ea/EAThread/source/pc/eathread_mutex_pc.cpp b/src/thirdparty/ea/EAThread/source/pc/eathread_mutex_pc.cpp new file mode 100644 index 00000000..23221a00 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/pc/eathread_mutex_pc.cpp @@ -0,0 +1,214 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "EABase/eabase.h" +#include "eathread/eathread_mutex.h" +#include "eathread/eathread.h" + +#if defined(EA_PLATFORM_MICROSOFT) + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() +#endif +#ifdef CreateMutex + #undef CreateMutex // Windows #defines CreateMutex to CreateMutexA or CreateMutexW. +#endif + + +#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE + #if defined(EA_PLATFORM_WINDOWS) + extern "C" WINBASEAPI BOOL WINAPI TryEnterCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection); + #endif + + EAMutexData::EAMutexData() + : mnLockCount(0), mbIntraProcess(true) + { + #if EAT_ASSERT_ENABLED + mThreadId = EA::Thread::kThreadIdInvalid; + mSysThreadId = EA::Thread::kSysThreadIdInvalid; + #endif + + ::memset(&mData, 0, sizeof(mData)); + } + + + EA::Thread::MutexParameters::MutexParameters(bool bIntraProcess, const char* pName) + : mbIntraProcess(bIntraProcess) + { + if(pName) + { + strncpy(mName, pName, sizeof(mName)-1); + mName[sizeof(mName)-1] = 0; + } + else + mName[0] = 0; + } + + + EA::Thread::Mutex::Mutex(const MutexParameters* pMutexParameters, bool bDefaultParameters) + { + if(!pMutexParameters && bDefaultParameters) + { + MutexParameters parameters; + Init(¶meters); + } + else + Init(pMutexParameters); + } + + + EA::Thread::Mutex::~Mutex() + { + EAT_ASSERT(mMutexData.mnLockCount == 0); + + // Consider doing something to verify the mutex object has been initialized. + #if defined(EA_PLATFORM_WINDOWS) + if(mMutexData.mbIntraProcess) + DeleteCriticalSection((CRITICAL_SECTION*)mMutexData.mData); + else + CloseHandle(*(HANDLE*)mMutexData.mData); + #else + DeleteCriticalSection((CRITICAL_SECTION*)mMutexData.mData); + #endif + } + + + bool EA::Thread::Mutex::Init(const MutexParameters* pMutexParameters) + { + // Make sure that internal structure is big enough to hold critical section data. + // If this assert fires, please adjust MUTEX_PLATFORM_DATA_SIZE in eathread_mutex.h accordingly. + EAT_COMPILETIME_ASSERT(sizeof(CRITICAL_SECTION) <= (MUTEX_PLATFORM_DATA_SIZE / sizeof(uint64_t) * sizeof(uint64_t))); + EAT_COMPILETIME_ASSERT(sizeof(HANDLE) <= MUTEX_PLATFORM_DATA_SIZE); + + if(pMutexParameters) + { + mMutexData.mnLockCount = 0; + + #if defined(EA_PLATFORM_WINDOWS) + mMutexData.mbIntraProcess = pMutexParameters->mbIntraProcess; + + if(mMutexData.mbIntraProcess) + { + // We use InitializeCriticalSectionAndSpinCount, as that has resulted in improved performance in practice on multiprocessors systems. + int rv = InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION*)mMutexData.mData, 256); + EAT_ASSERT(rv != 0); + EA_UNUSED(rv); + + return true; + } + else + { + EAT_COMPILETIME_ASSERT(sizeof(pMutexParameters->mName) <= MAX_PATH); + *(HANDLE*)mMutexData.mData = ::CreateMutexA(NULL, false, pMutexParameters->mName[0] ? pMutexParameters->mName : NULL); + EAT_ASSERT(*(HANDLE*)mMutexData.mData != 0); + return *(HANDLE*)mMutexData.mData != 0; + } + #else + // We use InitializeCriticalSectionAndSpinCount, as that has resulted in improved performance in practice on multiprocessors systems. + InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION*)mMutexData.mData, 256); + return true; + #endif + } + return false; + } + + + + EA_DISABLE_VC_WARNING(4706) // disable warning about assignment within a conditional expression + + int EA::Thread::Mutex::Lock(const ThreadTime& timeoutAbsolute) + { + EAT_ASSERT(mMutexData.mnLockCount < 100000); + + #if defined(EA_PLATFORM_WINDOWS) // Non-Windows is always assumed to be intra-process. + if(mMutexData.mbIntraProcess) + { + #endif + if(timeoutAbsolute == kTimeoutNone) + EnterCriticalSection((CRITICAL_SECTION*)mMutexData.mData); + else + { + // To consider: Have a pathway for kTimeoutImmediate which doesn't check the current time. + while(!TryEnterCriticalSection((CRITICAL_SECTION*)mMutexData.mData)) + { + if(GetThreadTime() >= timeoutAbsolute) + return kResultTimeout; + Sleep(1); + } + } + #if defined(EA_PLATFORM_WINDOWS) + } + else + { + EAT_ASSERT(*(HANDLE*)mMutexData.mData != 0); + + const DWORD dw = ::WaitForSingleObject(*(HANDLE*)mMutexData.mData, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute)); + + if(dw == WAIT_TIMEOUT) + return kResultTimeout; + + if(dw != WAIT_OBJECT_0) + { + EAT_ASSERT(false); + return kResultError; + } + } + #endif + + EAT_ASSERT((mMutexData.mSysThreadId = EA::Thread::GetSysThreadId()) != kSysThreadIdInvalid); + EAT_ASSERT(mMutexData.mnLockCount >= 0); + return ++mMutexData.mnLockCount; // This is safe to do because we have the lock. + } + + EA_RESTORE_VC_WARNING() + + + + int EA::Thread::Mutex::Unlock() + { + EAT_ASSERT(mMutexData.mSysThreadId == EA::Thread::GetSysThreadId()); + EAT_ASSERT(mMutexData.mnLockCount > 0); + + const int nReturnValue(--mMutexData.mnLockCount); // This is safe to do because we have the lock. + + #if defined(EA_PLATFORM_WINDOWS) + if(mMutexData.mbIntraProcess) + LeaveCriticalSection((CRITICAL_SECTION*)mMutexData.mData); + else + { + EAT_ASSERT(*(HANDLE*)mMutexData.mData != 0); + ReleaseMutex(*(HANDLE*)mMutexData.mData); + } + #else + LeaveCriticalSection((CRITICAL_SECTION*)mMutexData.mData); + #endif + + return nReturnValue; + } + + + int EA::Thread::Mutex::GetLockCount() const + { + return mMutexData.mnLockCount; + } + + + bool EA::Thread::Mutex::HasLock() const + { + #if EAT_ASSERT_ENABLED + return (mMutexData.mnLockCount > 0) && (mMutexData.mSysThreadId == EA::Thread::GetSysThreadId()); + #else + return (mMutexData.mnLockCount > 0); // This is the best we can do, though it is of limited use, since it doesn't tell you if you are the thread with the lock. + #endif + } + + +#endif // EA_PLATFORM_XXX + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/pc/eathread_pc.cpp b/src/thirdparty/ea/EAThread/source/pc/eathread_pc.cpp new file mode 100644 index 00000000..220a1a3d --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/pc/eathread_pc.cpp @@ -0,0 +1,919 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "eathread/eathread.h" +#include "eathread/eathread_thread.h" +#include "eathread/eathread_storage.h" + +#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE + #include + + EA_DISABLE_ALL_VC_WARNINGS() + #include + #include // for mbstowcs + #include + EA_RESTORE_ALL_VC_WARNINGS() + + #include "eathread/eathread_futex.h" + + extern "C" WINBASEAPI DWORD WINAPI SetThreadIdealProcessor(_In_ HANDLE hThread, _In_ DWORD dwIdealProcessor); + #if defined(EA_PLATFORM_WIN64) + extern "C" WINBASEAPI DWORD WINAPI GetThreadId(_In_ HANDLE hThread); + extern "C" WINBASEAPI ULONGLONG GetTickCount64(VOID); // Will not run on pre-Vista OS so 64 bit XP not supported + #endif + + // We set this module to initialize early. We want to do this because it + // allows statically initialized objects to call these functions safely. + EA_DISABLE_VC_WARNING(4074) // warning C4074: initializers put in compiler reserved initialization area + #pragma init_seg(compiler) + EA_RESTORE_VC_WARNING() + #ifndef EATHREAD_INIT_SEG_DEFINED + #define EATHREAD_INIT_SEG_DEFINED + #endif + + namespace EA + { + namespace Thread + { + // Note by Paul Pedriana: + // There is a bit of code here which implements "dynamic thread array maintenance". + // The reason for this is that we are trying to present to the user a consistently + // behaving GetThreadId function. The Windows threading API has a number of design + // characteristics that make it less than ideal for applications. One of these + // designs is that an application cannot ask the system what its thread id is and + // get a consistent answer; in fact you always get a different answer. + + // To consider: Use the VC++ undocumented __tlregdtor function to detect thread exits. + // __tlregdtor is a VC++ CRT function which detects the exiting of any threads created + // with the CRT beginthread family of functions. It cannot detect the exit of any threads + // that are begun via direct OS thread creation functions, nor can it detect the exit of + // threads that are exited by direct OS thread exit functions. This is may not be a major + // problem, because C/C++ programs should virtually always be calling the CRT thread begin + // and end functions so that the CRT can be maintained properly for the thread. + // + // typedef void (*_PVFV)(); + // void __tlregdtor(_PVFV func); + // void ThreadExit(){ Do something. May need to be careful about what APIs are called. } + + // Assertion variables. + EA::Thread::AssertionFailureFunction gpAssertionFailureFunction = NULL; + void* gpAssertionFailureContext = NULL; + + // Dynamic thread array maintenance. + // If the user calls GetThreadId from a thread that was created by some third + // party, then we don't have a thread handle for it. The only current way to get + // such a thread handle is to call OpenThread(GetCurrentThreadId()) or + // DuplicateHandle(GetCurrentThread()). In either case the return value is a + // handle which must be disposed of via CloseHandle. Additionally, since the + // thread was created by a thrid party, it's entirely possible that the thread + // will be exited without us ever finding about it. But we still need to call + // CloseHandle on the handle. So we maintain an array of handles and check their + // status periodically and upon process exit. + + const size_t kMaxThreadDynamicArrayCount = 128; + + struct DynamicThreadArray + { + static HANDLE mhDynamicThreadArray[kMaxThreadDynamicArrayCount]; + static CRITICAL_SECTION mCriticalSection; + static bool mbDynamicThreadArrayInitialized; + + static void Initialize(); + static void CheckDynamicThreadArray(bool bCloseAll); + static void AddDynamicThreadHandle(HANDLE hThread, bool bAdd); + }; + + HANDLE DynamicThreadArray::mhDynamicThreadArray[kMaxThreadDynamicArrayCount]; + CRITICAL_SECTION DynamicThreadArray::mCriticalSection; + bool DynamicThreadArray::mbDynamicThreadArrayInitialized; + + // DynamicThreadArray ctor/dtor were removed to because memory tracking systems that are required to run + // pre-main and post-main. In order to support memory tracking of allocations that occur post-main we + // intentially "leak" a operating system critical section and leave it to be cleaned up by the operating + // system at process shutdown. + // + // DynamicThreadArray::DynamicThreadArray() + // { + // Initialize(); + // } + + // DynamicThreadArray::~DynamicThreadArray() + // { + // CheckDynamicThreadArray(true); + // DeleteCriticalSection(&mCriticalSection); + // } + + void DynamicThreadArray::Initialize() + { + static EA::Thread::Futex m; + + const bool done = mbDynamicThreadArrayInitialized; + + // ensure that if we've seen previous writes to mbDynamicThreadArrayInitialized, we also + // see the writes to mCriticalSection, to avoid the case where another thread sees the flag + // before it sees the initialization + EAReadBarrier(); + + if(!done) + { + EA::Thread::AutoFutex _(m); + + if (!mbDynamicThreadArrayInitialized) + { + memset(mhDynamicThreadArray, 0, sizeof(mhDynamicThreadArray)); + InitializeCriticalSection(&mCriticalSection); + + // ensure writes to mCriticalSection and mhDynamicThreadArray are visible before writes + // to mbDynamicThreadArrayInitialized, to avoid the case where another thread sees the + // flag before it sees the initialization + EAWriteBarrier(); + + mbDynamicThreadArrayInitialized = true; + } + } + } + + // This function looks at the existing set of thread ids and see if any of them + // were quit. If so then this function removes their entry from our array of + // thread handles, and most importantly, calls CloseHandle on the thread handle. + void DynamicThreadArray::CheckDynamicThreadArray(bool bCloseAll) + { + Initialize(); + + EnterCriticalSection(&mCriticalSection); + + for(size_t i(0); i < sizeof(mhDynamicThreadArray)/sizeof(mhDynamicThreadArray[0]); i++) + { + if(mhDynamicThreadArray[i]) + { + DWORD dwExitCode(0); + + // Note that GetExitCodeThread is a hazard if the user of a thread exits + // with a return value that is equal to the value of STILL_ACTIVE (i.e. 259). + // We can document that users shouldn't do this, or we can change the code + // here to use WaitForSingleObject(hThread, 0) and assume the thread is + // still active if the return value is WAIT_TIMEOUT. + if(bCloseAll || !GetExitCodeThread(mhDynamicThreadArray[i], &dwExitCode) || (dwExitCode != STILL_ACTIVE)) // If the thread id is invalid or it has exited... + { + CloseHandle(mhDynamicThreadArray[i]); // This matches the DuplicateHandle call we made below. + mhDynamicThreadArray[i] = 0; + } + } + } + + LeaveCriticalSection(&mCriticalSection); + } + + void DynamicThreadArray::AddDynamicThreadHandle(HANDLE hThread, bool bAdd) + { + Initialize(); + + if(hThread) + { + EnterCriticalSection(&mCriticalSection); + + if(bAdd) + { + for(size_t i(0); i < sizeof(mhDynamicThreadArray)/sizeof(mhDynamicThreadArray[0]); i++) + { + if(mhDynamicThreadArray[i] == kThreadIdInvalid) + { + mhDynamicThreadArray[i] = hThread; + hThread = kThreadIdInvalid; // This tells us that we succeeded, and we'll use this result below. + break; + } + } + + EAT_ASSERT(hThread == kThreadIdInvalid); // Assert that there was enough room (that the above loop found a spot). + if(hThread != kThreadIdInvalid) // If not, then we need to free the handle. + CloseHandle(hThread); // This matches the DuplicateHandle call we made below. + } + else + { + for(size_t i(0); i < sizeof(mhDynamicThreadArray)/sizeof(mhDynamicThreadArray[0]); i++) + { + if(mhDynamicThreadArray[i] == hThread) + { + CloseHandle(hThread); // This matches the DuplicateHandle call we made below. + mhDynamicThreadArray[i] = kThreadIdInvalid; + break; + } + } + // By design, we don't consider a non-found handle an error. It may simply be the + // case that the given handle was not a dynamnic thread handle. Due to the way + // Windows works, there's just no way for us to tell. + } + + LeaveCriticalSection(&mCriticalSection); + } + } + + // Thread handle local storage. + // We have this code here in order to cache the thread handles for + // threads, so that the user gets a consistent return value from the + // GetThreadId function for each unique thread. + + static DWORD dwThreadHandleTLS = TLS_OUT_OF_INDEXES; // We intentionally make this an independent variable so that it is initialized unilaterally on segment load. + + struct TLSAlloc + { + TLSAlloc() + { + if(dwThreadHandleTLS == TLS_OUT_OF_INDEXES) // It turns out that the user might have set this to a + dwThreadHandleTLS = TlsAlloc(); // value before this constructor has run. So we check. + } + + #if EATHREAD_TLSALLOC_DTOR_ENABLED + // Since this class is used only as a static variable, this destructor would + // only get called during module destruction: app quit or DLL unload. + // In the case of DLL unload, we may have a problem if the DLL was unloaded + // before threads created by it were destroyed. Whether the problem is significant + // depends on the application. In most cases it won't be significant. + // + // We want to call TlsFree because not doing so results in a memory leak and eventual + // exhaustion of TLS ids by the system. + ~TLSAlloc() + { + if(dwThreadHandleTLS != TLS_OUT_OF_INDEXES) + { + // We don't read the hThread stored at dwThreadHandleTLS and call CloseHandle + // on it, as the DynamicThreadArray destructor will deal with closing any + // thread handles this module knows about. + + TlsFree(dwThreadHandleTLS); + dwThreadHandleTLS = TLS_OUT_OF_INDEXES; + } + } + #endif + }; + static TLSAlloc sTLSAlloc; + + void SetCurrentThreadHandle(HANDLE hThread, bool bDynamic) + { + // EAT_ASSERT(hThread != kThreadIdInvalid); We can't do this, as we can be intentionally called with an hThread of kThreadIdInvalid. + if(dwThreadHandleTLS == TLS_OUT_OF_INDEXES) // This should generally always evaluate to true because we init dwThreadHandleTLS on startup. + dwThreadHandleTLS = TlsAlloc(); + EAT_ASSERT(dwThreadHandleTLS != TLS_OUT_OF_INDEXES); + if(dwThreadHandleTLS != TLS_OUT_OF_INDEXES) + { + DynamicThreadArray::CheckDynamicThreadArray(false); + if(bDynamic) + { + if(hThread != kThreadIdInvalid) // If adding the hThread... + DynamicThreadArray::AddDynamicThreadHandle(hThread, true); + else // Else removing the existing current thread handle... + { + HANDLE hThreadOld = TlsGetValue(dwThreadHandleTLS); + if(hThreadOld != kThreadIdInvalid) // This should always evaluate to true in practice. + DynamicThreadArray::AddDynamicThreadHandle(hThreadOld, false); // Will Close the dynamic thread handle if it is one. + } + } + TlsSetValue(dwThreadHandleTLS, hThread); + } + } + } // namespace Thread + } // namespace EA + + +EATHREADLIB_API EA::Thread::ThreadId EA::Thread::GetThreadId() +{ + // We have some non-trivial code here because Windows doesn't provide a means for a + // thread to read its own thread id (thread handle) in a consistent way. + + // If we have allocated thread-local storage for this module... + if(dwThreadHandleTLS != TLS_OUT_OF_INDEXES) + { + void* const pValue = TlsGetValue(dwThreadHandleTLS); + + if(pValue) // If the current thread's ThreadId has been previously saved... + return pValue; // Under Win32, type ThreadId should be the same as HANDLE which should be the same as void*. + + // Else fall through and get the current thread handle and cache it so that next time the above code will succeed. + } + + // In this case the thread was not created by EAThread. So we give + // the thread a new Id, based on GetCurrentThread and DuplicateHandle. + // GetCurrentThread returns a "pseudo handle" which isn't actually the + // thread handle but is a hard-coded constant which means "current thread". + // If you want to get a real thread handle then you need to call DuplicateHandle + // on the pseudo handle. Every time you call DuplicateHandle you get a different + // result, yet we want this GetThreadId function to return a consistent value + // to the user, as that's what a rational user would expect. So after calling + // DuplicateHandle we save the value for the next time the user calls this + // function. We save the value in thread-local storage, so each unique thread + // sees a unique view of GetThreadId. + HANDLE hThread, hThreadPseudo = GetCurrentThread(); + BOOL bResult = DuplicateHandle(GetCurrentProcess(), hThreadPseudo, GetCurrentProcess(), &hThread, 0, true, DUPLICATE_SAME_ACCESS); + EAT_ASSERT(bResult && (hThread != kThreadIdInvalid)); + if(bResult) + EA::Thread::SetCurrentThreadHandle(hThread, true); // Need to eventually call CloseHandle on hThread, so we store it. + + return hThread; +} + +EATHREADLIB_API EA::Thread::ThreadId EA::Thread::GetThreadId(EA::Thread::SysThreadId id) +{ + EAThreadDynamicData* const pTDD = EA::Thread::FindThreadDynamicData(id); + if(pTDD) + { + return pTDD->mhThread; + } + + return EA::Thread::kThreadIdInvalid; +} + +EATHREADLIB_API EA::Thread::SysThreadId EA::Thread::GetSysThreadId(ThreadId id) +{ + #if defined(EA_PLATFORM_MICROSOFT) && (defined(EA_PROCESSOR_X86_64) || defined(EA_PROCESSOR_ARM64)) + // Win64 has this function natively. + return ::GetThreadId(id); + + // Fast implementation of this, which has been verified: + // uintptr_t pTIB = __readgsqword(0x30); + // uint32_t threadId = *((uint32_t*)(((uint8_t*)pTIB) + 0x48)); + // return (EA::Thread::SysThreadId)threadId; + + #elif defined(EA_PLATFORM_WIN32) + + // What we do here is first try to use the GetThreadId function, which is + // available on some later versions of WinXP and later OSs. If that doesn't + // work then we are using an earlier OS and we use the NtQueryInformationThread + // kernel function to read thread info. + + typedef DWORD (WINAPI *GetThreadIdFunc)(HANDLE); + typedef BOOL (WINAPI *NtQueryInformationThreadFunc)(HANDLE, int, PVOID, ULONG, PULONG); + + // We implement our own manual version of static variables here. We do this because + // the static variable mechanism the compiler provides wouldn't provide thread + // safety for us. + static volatile bool sInitialized = false; + static GetThreadIdFunc spGetThreadIdFunc = NULL; + static NtQueryInformationThreadFunc spNtQueryInformationThread = NULL; + + if(!sInitialized) + { + HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); + if(hKernel32) + spGetThreadIdFunc = (GetThreadIdFunc)(uintptr_t)GetProcAddress(hKernel32, "GetThreadId"); + + if(!spGetThreadIdFunc) + { + HMODULE hNTDLL = GetModuleHandleA("ntdll.dll"); + + if(hNTDLL) + spNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread"); + } + + sInitialized = true; + } + + if(spGetThreadIdFunc) + return (SysThreadId)spGetThreadIdFunc(id); + + if(spNtQueryInformationThread) + { + struct THREAD_BASIC_INFORMATION_WIN32 + { + BOOL ExitStatus; + PVOID TebBaseAddress; + DWORD UniqueProcessId; + DWORD UniqueThreadId; + DWORD AffinityMask; + DWORD Priority; + DWORD BasePriority; + }; + + THREAD_BASIC_INFORMATION_WIN32 tbi; + + if(spNtQueryInformationThread(id, 0, &tbi, sizeof(tbi), NULL) == 0) + return (SysThreadId)tbi.UniqueThreadId; + } + + return kSysThreadIdInvalid; + + #endif +} + + +EATHREADLIB_API EA::Thread::SysThreadId EA::Thread::GetSysThreadId() +{ + return ::GetCurrentThreadId(); +} + + +EATHREADLIB_API int EA::Thread::GetThreadPriority() +{ + const int nPriority = ::GetThreadPriority(GetCurrentThread()); + return kThreadPriorityDefault + (nPriority - THREAD_PRIORITY_NORMAL); +} + + +EATHREADLIB_API bool EA::Thread::SetThreadPriority(int nPriority) +{ + EAT_ASSERT(nPriority != kThreadPriorityUnknown); + int nNewPriority = THREAD_PRIORITY_NORMAL + (nPriority - kThreadPriorityDefault); + bool result = ::SetThreadPriority(GetCurrentThread(), nNewPriority) != 0; + + // Windows process running in NORMAL_PRIORITY_CLASS is picky about the priority passed in. + // So we need to set the priority to the next priority supported + #if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + HANDLE thread = GetCurrentThread(); + + while(!result) + { + if (nNewPriority >= THREAD_PRIORITY_TIME_CRITICAL) + return ::SetThreadPriority(thread, THREAD_PRIORITY_TIME_CRITICAL) != 0; + + if (nNewPriority <= THREAD_PRIORITY_IDLE) + return ::SetThreadPriority(thread, THREAD_PRIORITY_IDLE) != 0; + + result = ::SetThreadPriority(thread, nNewPriority) != 0; + nNewPriority++; + } + #endif + + return result; +} + + +EATHREADLIB_API void EA::Thread::SetThreadProcessor(int nProcessor) +{ + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + + DWORD mask = 0xFF; //Default to all + if (nProcessor >= 0) + mask = (DWORD)(1 << nProcessor); + + SetThreadAffinityMask(GetCurrentThread(), mask); + + #else + static const int nProcessorCount = GetProcessorCount(); + + if(nProcessor < 0) + nProcessor = MAXIMUM_PROCESSORS; // This cases the SetThreadIdealProcessor to reset to 'no ideal processor'. + else + { + if(nProcessor >= nProcessorCount) + nProcessor %= nProcessorCount; + } + + // SetThreadIdealProcessor differs from SetThreadAffinityMask in that SetThreadIdealProcessor is not + // a strict assignment, and it allows the OS to move the thread if the ideal processor is busy. + // SetThreadAffinityMask is a more rigid assignment, but it can result in slower performance and + // possibly hangs due to processor contention between threads. For Windows we use SetIdealThreadProcessor + // in the name of safety and likely better overall performance. + SetThreadIdealProcessor(GetCurrentThread(), (DWORD)nProcessor); + + #endif +} + + +void* EA::Thread::GetThreadStackBase() +{ + #if defined(EA_PLATFORM_WIN32) && defined(EA_PROCESSOR_X86) && defined(EA_COMPILER_MSVC) + // Offset 0x18 from the FS segment register gives a pointer to + // the thread information block for the current thread + // VC++ also offers the __readfsdword() intrinsic, which would be better to use here. + NT_TIB* pTib; + + __asm { + mov eax, fs:[18h] + mov pTib, eax + } + + return (void*)pTib->StackBase; + + #elif defined(EA_PLATFORM_MICROSOFT) && defined(EA_PROCESSOR_X86_64) && defined(EA_COMPILER_MSVC) + // VC++ also offers the __readgsdword() intrinsic, which is an alternative which could + // retrieve the current thread TEB if the following proves unreliable. + PNT_TIB64 pTib = reinterpret_cast(NtCurrentTeb()); + + return (void*)pTib->StackBase; + + #elif defined(EA_PLATFORM_MICROSOFT) && defined(EA_PROCESSOR_ARM64) && defined(EA_COMPILER_MSVC) + // TODO(rparolin): Requires an ARM64 implementation. + return nullptr; + + #elif defined(EA_PLATFORM_WIN32) && defined(EA_PROCESSOR_X86) && defined(EA_COMPILER_GCC) + NT_TIB* pTib; + + asm ( "movl %%fs:0x18, %0\n" + : "=r" (pTib) + ); + + return (void*)pTib->StackBase; + #endif +} + + +#if defined(EA_PLATFORM_WIN32) && defined(EA_PROCESSOR_X86) && defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1400) + // People report on the Internet that this function can get you what CPU the current thread + // is running on. But that's false, as this function has been seen to return values greater than + // the number of physical or real CPUs present. For example, this function returns 6 for my + // Single CPU that's dual-hyperthreaded. + static int GetCurrentProcessorNumberCPUID() + { + _asm { mov eax, 1 } + _asm { cpuid } + _asm { shr ebx, 24 } + _asm { mov eax, ebx } + } + + int GetCurrentProcessorNumberXP() + { + int cpuNumber = GetCurrentProcessorNumberCPUID(); + int cpuCount = EA::Thread::GetProcessorCount(); + + return (cpuNumber % cpuCount); // I don't know if this is the right thing to do, but it's better than returning an impossible number and Windows XP is a fading OS as it is. + } + +#endif + + +EATHREADLIB_API int EA::Thread::GetThreadProcessor() +{ + #if defined(EA_PLATFORM_WIN32) + // Only Windows Vista and later provides GetCurrentProcessorNumber. + // So we must dynamically link to this function. + static EA_THREAD_LOCAL bool bInitialized = false; + static EA_THREAD_LOCAL DWORD (WINAPI *pfnGetCurrentProcessorNumber)() = NULL; + + if(!bInitialized) + { + HMODULE hKernel32 = GetModuleHandleA("KERNEL32.DLL"); + if(hKernel32) + pfnGetCurrentProcessorNumber = (DWORD (WINAPI*)())(uintptr_t)GetProcAddress(hKernel32, "GetCurrentProcessorNumber"); + bInitialized = true; + } + + if(pfnGetCurrentProcessorNumber) + return (int)(unsigned)pfnGetCurrentProcessorNumber(); + + #if defined(EA_PLATFORM_WINDOWS) && defined(EA_PROCESSOR_X86) && defined(EA_COMPILER_MSVC) && (EA_COMPILER_MSVC >= 1400) + return GetCurrentProcessorNumberXP(); + #else + return 0; + #endif + + #elif defined(EA_PLATFORM_WIN64) + static EA_THREAD_LOCAL bool bInitialized = false; + static EA_THREAD_LOCAL DWORD (WINAPI *pfnGetCurrentProcessorNumber)() = NULL; + + if(!bInitialized) + { + HMODULE hKernel32 = GetModuleHandleA("KERNEL32.DLL"); // Yes, we want to use Kernel32.dll. There is no Kernel64.dll on Win64. + if(hKernel32) + pfnGetCurrentProcessorNumber = (DWORD (WINAPI*)())(uintptr_t)GetProcAddress(hKernel32, "GetCurrentProcessorNumber"); + bInitialized = true; + } + + if(pfnGetCurrentProcessorNumber) + return (int)(unsigned)pfnGetCurrentProcessorNumber(); + + return 0; + + #else + return (int)(unsigned)GetCurrentProcessorNumber(); + + #endif +} + + +EATHREADLIB_API void EA::Thread::SetThreadAffinityMask(const EA::Thread::ThreadId& id, ThreadAffinityMask nAffinityMask) +{ + // Update the affinity mask in the thread dynamic data cache. + EAThreadDynamicData* const pTDD = FindThreadDynamicData(id); + if(pTDD) + { + pTDD->mnThreadAffinityMask = nAffinityMask; + } + +#if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + DWORD_PTR nProcessorCountMask = 0x7F; // default to all 7 available cores. + #else + DWORD_PTR nProcessorCountMask = (DWORD_PTR)1 << GetProcessorCount(); + #endif + + // Call the Windows library function. + DWORD_PTR nProcessAffinityMask, nSystemAffinityMask; + if(EA_LIKELY(GetProcessAffinityMask(GetCurrentProcess(), &nProcessAffinityMask, &nSystemAffinityMask))) + nProcessorCountMask = nProcessAffinityMask; + + nAffinityMask &= nProcessorCountMask; + + auto opResult = ::SetThreadAffinityMask(id, static_cast(nAffinityMask)); + EA_UNUSED(opResult); + EAT_ASSERT_FORMATTED(opResult != 0, "The Windows platform SetThreadAffinityMask failed. GetLastError %x", GetLastError()); +#endif +} + +EATHREADLIB_API EA::Thread::ThreadAffinityMask EA::Thread::GetThreadAffinityMask(const EA::Thread::ThreadId& id) +{ + // Update the affinity mask in the thread dynamic data cache. + EAThreadDynamicData* const pTDD = FindThreadDynamicData(id); + if(pTDD) + { + return pTDD->mnThreadAffinityMask; + } + + return kThreadAffinityMaskAny; +} + + +// Internal SetThreadName API's so we don't repeat the implementations +namespace EA { +namespace Thread { +namespace Internal { + bool PixSetThreadName(EA::Thread::ThreadId threadId, const char* pName) + { + EA_UNUSED(threadId); EA_UNUSED(pName); + + bool result = true; + + #if (defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX)) && EA_XBOX_DBG_ENABLED == 1 + wchar_t wName[EATHREAD_NAME_SIZE]; + mbstowcs(wName, pName, EATHREAD_NAME_SIZE); + result = (::SetThreadName(threadId, wName) == TRUE); // requires toolhelpx.lib + EAT_ASSERT(result); + #endif + + return result; + } + + bool WinSetThreadName(EA::Thread::ThreadId threadId, const char* pName) + { + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + static auto pSetThreadDescription = SetThreadDescription; + #else + typedef HRESULT(WINAPI *SetThreadDescription)(HANDLE hThread, PCWSTR lpThreadDescription); + + // Check if Windows Operating System has the 'SetThreadDescription" API. + static auto pSetThreadDescription = (SetThreadDescription)GetProcAddress(GetModuleHandleA("kernel32.dll"), "SetThreadDescription"); + #endif + + bool result = true; + + if (pSetThreadDescription) + { + wchar_t wName[EATHREAD_NAME_SIZE]; + mbstowcs(wName, pName, EATHREAD_NAME_SIZE); + + result = SUCCEEDED(pSetThreadDescription(threadId, wName)); + EAT_ASSERT(result); + } + + return result; + } + + void WinSetThreadNameByException(EA::Thread::SysThreadId threadId, const char* pName) + { + struct ThreadNameInfo + { + DWORD dwType; + LPCSTR lpName; + DWORD dwThreadId; + DWORD dwFlags; + }; + + // This setjmp/longjmp weirdness is here to work around an apparent bug in the VS2013 debugger, + // whereby EBX will be trashed on return from RaiseException, causing bad things to happen in code + // which runs later. This only seems to happen when a debugger is attached and there's some managed + // code in the process. + + jmp_buf jmpbuf; + + EA_DISABLE_VC_WARNING(4611) + if (!setjmp(jmpbuf)) + { + ThreadNameInfo threadNameInfo = {0x1000, pName, threadId, 0}; + __try { RaiseException(0x406D1388, 0, sizeof(threadNameInfo) / sizeof(ULONG_PTR), (CONST ULONG_PTR*)(uintptr_t)&threadNameInfo); } + __except (GetExceptionCode() == 0x406D1388 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { } + longjmp(jmpbuf, 1); + } + EA_RESTORE_VC_WARNING() + } + + void SetThreadName(EAThreadDynamicData* pTDD, const char* pName) + { + strncpy(pTDD->mName, pName, EATHREAD_NAME_SIZE); + pTDD->mName[EATHREAD_NAME_SIZE - 1] = 0; + + #if defined(EA_PLATFORM_WINDOWS) && defined(EA_COMPILER_MSVC) || (defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX)) + if(pTDD->mName[0] && (pTDD->mhThread != EA::Thread::kThreadIdInvalid)) + { + #if EATHREAD_NAMING == EATHREAD_NAMING_DISABLED + bool namingEnabled = false; + #elif EATHREAD_NAMING == EATHREAD_NAMING_ENABLED + bool namingEnabled = true; + #else + bool namingEnabled = IsDebuggerPresent(); + #endif + + if(namingEnabled) + { + PixSetThreadName(pTDD->mhThread, pTDD->mName); + WinSetThreadName(pTDD->mhThread, pTDD->mName); + WinSetThreadNameByException(pTDD->mnThreadId, pTDD->mName); + } + } + #endif + } +} // namespace Internal +} // namespace Thread +} // namespace EA + +EATHREADLIB_API void EA::Thread::SetThreadName(const char* pName) { SetThreadName(GetThreadId(), pName); } +EATHREADLIB_API const char* EA::Thread::GetThreadName() { return GetThreadName(GetThreadId()); } + +EATHREADLIB_API void EA::Thread::SetThreadName(const EA::Thread::ThreadId& id, const char* pName) +{ + EAThreadDynamicData* const pTDD = FindThreadDynamicData(id); + if(pTDD) + { + Internal::SetThreadName(pTDD, pName); + } +} + +EATHREADLIB_API const char* EA::Thread::GetThreadName(const EA::Thread::ThreadId& id) +{ + EAThreadDynamicData* const pTDD = FindThreadDynamicData(id); + return pTDD ? pTDD->mName : ""; +} + +EATHREADLIB_API int EA::Thread::GetProcessorCount() +{ + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + // Capilano has 7-ish physical CPUs available to titles. We can access 50 - 90% of the 7th Core. + // Check platform documentation for details. + DWORD_PTR ProcessAffinityMask; + DWORD_PTR SystemAffinityMask; + unsigned long nCoreCount = 6; + + if(EA_LIKELY(GetProcessAffinityMask(GetCurrentProcess(), &ProcessAffinityMask, &SystemAffinityMask))) + { + _BitScanForward(&nCoreCount, (unsigned long)~ProcessAffinityMask); + } + + return (int) nCoreCount; + + #elif defined(EA_PLATFORM_WINDOWS) + static int nProcessorCount = 0; // This doesn't really need to be an atomic integer. + + if(nProcessorCount == 0) + { + // A better function to use would possibly be KeQueryActiveProcessorCount + // (NTKERNELAPI ULONG KeQueryActiveProcessorCount(PKAFFINITY ActiveProcessors)) + + SYSTEM_INFO systemInfo; + memset(&systemInfo, 0, sizeof(systemInfo)); + GetSystemInfo(&systemInfo); + nProcessorCount = (int)systemInfo.dwNumberOfProcessors; + } + + return nProcessorCount; + + #else + static int nProcessorCount = 0; // This doesn't really need to be an atomic integer. + + if(nProcessorCount == 0) + { + // A better function to use would possibly be KeQueryActiveProcessorCount + // (NTKERNELAPI ULONG KeQueryActiveProcessorCount(PKAFFINITY ActiveProcessors)) + + SYSTEM_INFO systemInfo; + memset(&systemInfo, 0, sizeof(systemInfo)); + GetNativeSystemInfo(&systemInfo); + nProcessorCount = (int)systemInfo.dwNumberOfProcessors; + } + + return nProcessorCount; + + #endif +} + + +EATHREADLIB_API void EA::Thread::ThreadSleep(const ThreadTime& timeRelative) +{ + // Sleep(0) sleeps the current thread if any other thread of equal priority is ready to run. + // Sleep(n) sleeps the current thread for up to n milliseconds if there is any other thread of any priority ready to run. + // SwitchToThread() sleeps the current thread for one time slice if there is any other thread of any priority ready to run. + + if(timeRelative == 0) + SwitchToThread(); // It's debateable whether we should do a SwitchToThread or a Sleep(0) here. + else // The only difference is that the former allows threads of lower priority to execute. + SleepEx((unsigned)timeRelative, TRUE); +} + + +namespace EA { + namespace Thread { + extern EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId); + extern EAThreadDynamicData* FindThreadDynamicData(SysThreadId sysThreadId); + } +} + +void EA::Thread::ThreadEnd(intptr_t threadReturnValue) +{ + EAThreadDynamicData* const pTDD = FindThreadDynamicData(GetThreadId()); + if(pTDD) + { + pTDD->mnStatus = Thread::kStatusEnded; + pTDD->mnReturnValue = threadReturnValue; + pTDD->Release(); + } + + EA::Thread::SetCurrentThreadHandle(kThreadIdInvalid, true); // We use 'true' here just to be safe, as we don't know who is calling this function. + + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + // _endthreadex is not supported on Capilano because it's not compatible with C++/CX and /ZW. Use of ExitThread could result in memory leaks + // as ExitThread does not clean up memory allocated by the C runtime library. + // https://forums.xboxlive.com/AnswerPage.aspx?qid=47c1607c-bb18-4bc4-a79a-a40c59444ff3&tgt=1 + ExitThread(static_cast(threadReturnValue)); + #elif defined(EA_PLATFORM_MICROSOFT) && defined(EA_PLATFORM_CONSOLE) && !defined(EA_PLATFORM_XBOXONE) && !defined(EA_PLATFORM_XBSX) + EAT_FAIL_MSG("EA::Thread::ThreadEnd: Not supported by this platform."); + #else + _endthreadex((unsigned int)threadReturnValue); + #endif +} + + +EATHREADLIB_API EA::Thread::ThreadTime EA::Thread::GetThreadTime() +{ + // We choose to use GetTickCount because it low overhead and + // still yields values that have a precision in the same range + // as the Win32 thread time slice time. In particular: + // rdtsc takes ~5 cycles and has a nanosecond resolution. But it is unreliable + // GetTickCount() takes ~80 cycles and has ~15ms resolution. + // timeGetTime() takes ~350 cpu cycles and has 1ms resolution. + // QueryPerformanceCounter() takes ~3000 cpu cycles on most machines and has ~1us resolution. + // We add EATHREAD_MIN_ABSOLUTE_TIME to this absolute time to ensure this absolute time is never less than our min + // (This fix was required because GetTickCount64 starts at 0x0 for titles on capilano) + #if defined(EA_PLATFORM_MICROSOFT) && defined(EA_PROCESSOR_X86_64) + return (ThreadTime)(GetTickCount64() + EATHREAD_MIN_ABSOLUTE_TIME); + #else // Note that this value matches the value used by some runtime assertion code within EA::Thread. It would be best to define this as a shared constant between modules. + return (ThreadTime)(GetTickCount() + EATHREAD_MIN_ABSOLUTE_TIME); + #endif +} + + +EATHREADLIB_API void EA::Thread::SetAssertionFailureFunction(EA::Thread::AssertionFailureFunction pAssertionFailureFunction, void* pContext) +{ + gpAssertionFailureFunction = pAssertionFailureFunction; + gpAssertionFailureContext = pContext; +} + + +EATHREADLIB_API void EA::Thread::AssertionFailure(const char* pExpression) +{ + if(gpAssertionFailureFunction) + gpAssertionFailureFunction(pExpression, gpAssertionFailureContext); + else + { + #if EAT_ASSERT_ENABLED + OutputDebugStringA("EA::Thread::AssertionFailure: "); + OutputDebugStringA(pExpression); + OutputDebugStringA("\n"); + #ifdef EA_COMPILER_MSVC + __debugbreak(); + #endif + #endif + } +} + +uint32_t EA::Thread::RelativeTimeoutFromAbsoluteTimeout(ThreadTime timeoutAbsolute) +{ + EAT_ASSERT((timeoutAbsolute == kTimeoutImmediate) || (timeoutAbsolute > EATHREAD_MIN_ABSOLUTE_TIME)); // Assert that the user didn't make the mistake of treating time as relative instead of absolute. + + DWORD timeoutRelative = 0; + + if (timeoutAbsolute == kTimeoutNone) + { + timeoutRelative = INFINITE; + } + else if (timeoutAbsolute == kTimeoutImmediate) + { + timeoutRelative = 0; + } + else + { + ThreadTime timeCurrent(GetThreadTime()); + timeoutRelative = (timeoutAbsolute > timeCurrent) ? static_cast(timeoutAbsolute - timeCurrent) : 0; + } + + EAT_ASSERT((timeoutRelative == INFINITE) || (timeoutRelative < 100000000)); // Assert that the timeout is a sane value and didn't wrap around. + + return timeoutRelative; +} + +#endif // EA_PLATFORM_XXX + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/pc/eathread_semaphore_pc.cpp b/src/thirdparty/ea/EAThread/source/pc/eathread_semaphore_pc.cpp new file mode 100644 index 00000000..4d45e4ab --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/pc/eathread_semaphore_pc.cpp @@ -0,0 +1,304 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "EABase/eabase.h" +#include "eathread/eathread_semaphore.h" +#include "eathread/eathread_sync.h" +#include + +#if defined(EA_PLATFORM_MICROSOFT) + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() + #if defined(EA_PLATFORM_WIN64) + #if !defined _Ret_maybenull_ + #define _Ret_maybenull_ + #endif + + #if !defined _Reserved_ + #define _Reserved_ + #endif + extern "C" WINBASEAPI _Ret_maybenull_ HANDLE WINAPI CreateSemaphoreExW(_In_opt_ LPSECURITY_ATTRIBUTES, _In_ LONG, _In_ LONG, _In_opt_ LPCWSTR, _Reserved_ DWORD, _In_ DWORD); + #endif +#endif +#ifdef CreateSemaphore + #undef CreateSemaphore +#endif + + + +#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE + + // Helper function to abstract away differences between APIs for different versions of Windows + static DWORD EASemaphoreWaitForSingleObject(HANDLE handle, DWORD milliseconds) + { + #if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) + return WaitForSingleObject(handle, milliseconds); + #else + return WaitForSingleObjectEx(handle, milliseconds, TRUE); + #endif + } + + EASemaphoreData::EASemaphoreData() + : mhSemaphore(0), mnCount(0), mnCancelCount(0), mnMaxCount(INT_MAX), mbIntraProcess(true) + { + EAWriteBarrier(); + static_assert(sizeof(int32_t) == sizeof(LONG), "We use int32_t and LONG interchangably. Windows (including Win64) uses 32 bit longs."); + } + + + void EASemaphoreData::UpdateCancelCount(int32_t cancelCount) + { + // This is used by the fast semaphore path. This function actually isn't called very often -- only under uncommon circumstances. + // This is based on an algorithm discussed on usenet in 2004. + // We safely increment count by min(cancelCount, -count) if count < 0. + int32_t oldCount, newCount, cmpCount; + + if(cancelCount > 0) + { + oldCount = mnCount; + + while(oldCount < 0) + { + // Increment old count by the number of cancels + if((newCount = oldCount + cancelCount) > 0) + newCount = 0; // ...but not greater then zero. + + cmpCount = oldCount; + oldCount = InterlockedCompareExchange((LONG*)&mnCount, newCount, cmpCount); + + if(oldCount == cmpCount) + { + cancelCount -= (newCount - oldCount); + break; + } + } + + if(cancelCount > 0) + InterlockedExchangeAdd((LONG*)&mnCancelCount, cancelCount); + } + } + + + EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* pName) + : mInitialCount(initialCount), mMaxCount(INT_MAX), mbIntraProcess(bIntraProcess) + { + if(pName) + { + strncpy(mName, pName, sizeof(mName)-1); + mName[sizeof(mName)-1] = 0; + } + else + mName[0] = 0; + } + + + EA::Thread::Semaphore::Semaphore(const SemaphoreParameters* pSemaphoreParameters, bool bDefaultParameters) + { + if(!pSemaphoreParameters && bDefaultParameters) + { + SemaphoreParameters parameters; + Init(¶meters); + } + else + Init(pSemaphoreParameters); + } + + + EA::Thread::Semaphore::Semaphore(int initialCount) + { + SemaphoreParameters parameters(initialCount); + Init(¶meters); + } + + + EA::Thread::Semaphore::~Semaphore() + { + if(mSemaphoreData.mhSemaphore) + CloseHandle(mSemaphoreData.mhSemaphore); + } + + + bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters) + { + if(pSemaphoreParameters && !mSemaphoreData.mhSemaphore) + { + mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount; + mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount; + + if(mSemaphoreData.mnCount < 0) + mSemaphoreData.mnCount = 0; + + mSemaphoreData.mbIntraProcess = pSemaphoreParameters->mbIntraProcess; + + // If the fast semaphore is disabled, then we always act like inter-process as opposed to intra-process. + #if EATHREAD_FAST_MS_SEMAPHORE_ENABLED + const bool bIntraProcess = mSemaphoreData.mbIntraProcess; + #else + const bool bIntraProcess = false; + #endif + + if(bIntraProcess) + { + // Note that we do things rather differently for intra-process, as we are + // implementing a fast semaphore. This semaphore will be at least 10 times + // faster than the OS semaphore for all Microsoft platforms for the case of + // successful immediate acquire of a semaphore. Semaphore posts (or releases + // will also be much faster than the OS version. + #if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) + mSemaphoreData.mhSemaphore = CreateSemaphoreA(NULL, 0, INT_MAX/2, NULL); // Intentionally ignore mnCount and mnMaxCount here. + #else + mSemaphoreData.mhSemaphore = CreateSemaphoreExW(NULL, 0, INT_MAX/2, NULL, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE); // Intentionally ignore mnCount and mnMaxCount here. + #endif + } + else // Else we create a conventional Win32-style semaphore. + { + #if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) + mSemaphoreData.mhSemaphore = CreateSemaphoreA(NULL, (LONG)mSemaphoreData.mnCount, (LONG)mSemaphoreData.mnMaxCount, pSemaphoreParameters->mName[0] ? pSemaphoreParameters->mName : NULL); + #else + wchar_t wName[EAArrayCount(pSemaphoreParameters->mName)]; // We do an ASCII conversion. + for(size_t c = 0; c < EAArrayCount(wName); c++) + wName[c] = (wchar_t)(uint8_t)pSemaphoreParameters->mName[c]; + mSemaphoreData.mhSemaphore = CreateSemaphoreExW(NULL, (LONG)mSemaphoreData.mnCount, (LONG)mSemaphoreData.mnMaxCount, wName[0] ? wName : NULL, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE); + #endif + } + + EAWriteBarrier(); + EAT_ASSERT(mSemaphoreData.mhSemaphore != 0); + return (mSemaphoreData.mhSemaphore != 0); + } + return false; + } + + + int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute) + { + EAT_ASSERT(mSemaphoreData.mhSemaphore != 0); + + // If the fast semaphore is disabled, then we always act like inter-process as opposed to intra-process. + #if EATHREAD_FAST_MS_SEMAPHORE_ENABLED + const bool bIntraProcess = mSemaphoreData.mbIntraProcess; + #else + const bool bIntraProcess = false; + #endif + + if(bIntraProcess) // If this is true, we are using the fast semaphore pathway. + { + if(InterlockedDecrement((LONG*)&mSemaphoreData.mnCount) < 0) // InterlockedDecrement returns the new value. If the mnCount was > 0 before this decrement, then this Wait function will return very quickly. + { + const DWORD dw = EASemaphoreWaitForSingleObject(mSemaphoreData.mhSemaphore, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute)); + + if(dw != WAIT_OBJECT_0) // If there was a timeout... + { + mSemaphoreData.UpdateCancelCount(1); // or InterlockedIncrement(&mSemaphoreData.mnCancelCount); // The latter has a bug whereby mnCancelCount can increment indefinitely. + + EAT_ASSERT(dw == WAIT_TIMEOUT); // Otherwise it was probably a timeout. + if(dw == WAIT_TIMEOUT) + return kResultTimeout; + return kResultError; // WAIT_FAILED + } + } + + // It is by design that a semaphore post does a full memory barrier. + // We don't need such a barrier for this pathway to work, but rather + // it is expected by the user that such a barrier is executed. Investigation + // into the choice of a full vs. just read or write barrier has concluded + // (based on the Posix standard) that a full read-write barrier is expected. + EAReadWriteBarrier(); + + const int count = (int)mSemaphoreData.mnCount; // Make temporary to avoid race condition in ternary operator below. + return (count > 0 ? count : 0); + } + else + { + const DWORD dw = EASemaphoreWaitForSingleObject(mSemaphoreData.mhSemaphore, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute)); + + if(dw == WAIT_OBJECT_0) + return (int)InterlockedDecrement((LONG*)&mSemaphoreData.mnCount); + else if(dw == WAIT_TIMEOUT) + return kResultTimeout; + return kResultError; + } + } + + + int EA::Thread::Semaphore::Post(int count) + { + EAT_ASSERT((mSemaphoreData.mhSemaphore != 0) && (count >= 0)); + + if(count > 0) + { + // If the fast semaphore is disabled, then we always act like inter-process as opposed to intra-process. + #if EATHREAD_FAST_MS_SEMAPHORE_ENABLED + const bool bIntraProcess = mSemaphoreData.mbIntraProcess; + #else + const bool bIntraProcess = false; + #endif + + if(bIntraProcess) // If this is true, we are using the fast semaphore pathway. + { + // It is by design that a semaphore post does a full memory barrier. + // We don't need such a barrier for this pathway to work, but rather + // it is expected by the user that such a barrier is executed. Investigation + // into the choice of a full vs. just read or write barrier has concluded + // (based on the Posix standard) that a full read-write barrier is expected. + EAReadWriteBarrier(); + + if((mSemaphoreData.mnCancelCount > 0) && (mSemaphoreData.mnCount < 0)) // Much of the time this will evaluate to false due to the first condition. + mSemaphoreData.UpdateCancelCount(InterlockedExchange((LONG*)&mSemaphoreData.mnCancelCount, 0)); // It's possible that mnCancelCount may have decremented down to zero between the previous line of code and this line of code. + + const int currentCount = mSemaphoreData.mnCount; + if((mSemaphoreData.mnMaxCount - count) < currentCount) // If count would cause an overflow... + return kResultError; // We do what most OS implementations of max-count do. count = (mSemaphoreData.mnMaxCount - currentCount); + + const int32_t nWaiterCount = -InterlockedExchangeAdd((LONG*)&mSemaphoreData.mnCount, count); // InterlockedExchangeAdd returns the initial value of mnCount. If it's below zero, then it's a count of waiters. + const int nNewCount = count - nWaiterCount; + + if(nWaiterCount > 0) // If there were waiters blocking... + { + const int32_t nReleaseCount = (count < nWaiterCount) ? count : nWaiterCount; // Call ReleaseSemaphore for as many waiters as possible. + ReleaseSemaphore(mSemaphoreData.mhSemaphore, nReleaseCount, NULL); // Note that by the time this executes, nReleaseCount might be > than the actual number of waiting threads, due to timeouts. + } + + return (nNewCount > 0 ? nNewCount : 0); + } + else + { + const int32_t nPreviousCount = InterlockedExchangeAdd((LONG*)&mSemaphoreData.mnCount, count); + const int nNewCount = nPreviousCount + count; + + const BOOL result = ReleaseSemaphore(mSemaphoreData.mhSemaphore, count, NULL); + + if(!result) + { + InterlockedExchangeAdd((LONG*)&mSemaphoreData.mnCount, -count); + EAT_ASSERT(result); + return kResultError; + } + + return nNewCount; + } + } + + return (int)mSemaphoreData.mnCount; // We don't worry if this value is changing. There is nothing that you can rely upon about this value anyway. + } + + + int EA::Thread::Semaphore::GetCount() const + { + // Under our fast pathway, mnCount can go below zero. + // Under the fast pathway, we need to add mnCancelCount to mnCount because mnCount is negative by the number of waiters and mnCancelCount is the number of waiters that have abandoned waiting but the value hasn't been rolled back into mnCount yet. + EAReadBarrier(); + const int count = (int)mSemaphoreData.mnCount + (int)mSemaphoreData.mnCancelCount; // Make temporary to avoid race condition in ternary operator below. + return (count > 0 ? count : 0); + } + +#endif // EA_PLATFORM_XXX + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/pc/eathread_thread_pc.cpp b/src/thirdparty/ea/EAThread/source/pc/eathread_thread_pc.cpp new file mode 100644 index 00000000..84fc76d7 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/pc/eathread_thread_pc.cpp @@ -0,0 +1,831 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "EABase/eabase.h" +#include "eathread/eathread.h" +#include "eathread/eathread_callstack.h" +#include "eathread/eathread_mutex.h" +#include "eathread/eathread_sync.h" +#include "eathread/eathread_thread.h" +#include "eathread/internal/eathread_global.h" + +#if defined(EA_COMPILER_MSVC) && EA_COMPILER_VERSION >= 1900 // VS2015+ +// required for windows.h that has mismatch that is included in this file + EA_DISABLE_VC_WARNING(5031 5032)// #pragma warning(pop): likely mismatch, popping warning state pushed in different file / detected #pragma warning(push) with no corresponding +#endif + + +// Warning 6312 and 6322 are spurious, as we are not execution a case that could possibly loop. +// 6312: Possible infinite loop: use of the constant EXCEPTION_CONTINUE_EXECUTION in the exception-filter expression of a try-except. Execution restarts in the protected block +// 6322: Empty _except block +EA_DISABLE_VC_WARNING(6312 6322) + + +#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE + #include + #include + + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() + + #if defined(EA_COMPILER_MSVC) + struct ThreadNameInfo{ + DWORD dwType; + LPCSTR lpName; + DWORD dwThreadId; + DWORD dwFlags; + }; + extern "C" WINBASEAPI DWORD WINAPI SetThreadIdealProcessor(_In_ HANDLE hThread, _In_ DWORD dwIdealProcessor); + extern "C" WINBASEAPI BOOL WINAPI IsDebuggerPresent(); + #endif + + + #ifdef EA_COMPILER_MSVC + #ifndef EATHREAD_INIT_SEG_DEFINED + #define EATHREAD_INIT_SEG_DEFINED + #endif + + // We are changing the initalization ordering here because in bulkbuild tool builds the initialization + // order of globals changes and causes a crash when we attempt to lock the Mutex guarding access + // of the EAThreadDynamicData objects. The code attempts to lock a mutex that has been destructed + // and causes a crash within the WindowsSDK. This ensures that global mutex object is not destructed + // until all user code has destructed. + // + #ifndef EATHREAD_INIT_SEG_DEFINED + #define EATHREAD_INIT_SEG_DEFINED + // warning C4075: initializers put in unrecognized initialization area + //warning C4073: initializers put in library initialization area + EA_DISABLE_VC_WARNING(4075 4073) + #pragma init_seg(lib) + #endif + #endif + + + namespace EA { + namespace Thread { + extern void SetCurrentThreadHandle(HANDLE hThread, bool bDynamic); + namespace Internal { extern void SetThreadName(EAThreadDynamicData* pTDD, const char* pName); }; + } + } + + + namespace EA + { + namespace Thread + { + extern Allocator* gpAllocator; + static AtomicInt32 nLastProcessor = 0; + const size_t kMaxThreadDynamicDataCount = 128; + + struct EAThreadGlobalVars + { + EA_PREFIX_ALIGN(8) + char gThreadDynamicData[kMaxThreadDynamicDataCount][sizeof(EAThreadDynamicData)] EA_POSTFIX_ALIGN(8); + AtomicInt32 gThreadDynamicDataAllocated[kMaxThreadDynamicDataCount]; + Mutex gThreadDynamicMutex; + + EAThreadGlobalVars() {} + EAThreadGlobalVars(const EAThreadGlobalVars&) {} + EAThreadGlobalVars& operator=(const EAThreadGlobalVars&) {} + }; + EATHREAD_GLOBALVARS_CREATE_INSTANCE; + + EAThreadDynamicData* AllocateThreadDynamicData() + { + AutoMutex am(EATHREAD_GLOBALVARS.gThreadDynamicMutex); + for(size_t i(0); i < kMaxThreadDynamicDataCount; i++) + { + if(EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[i].SetValueConditional(1, 0)) + return (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + } + + // This is a safety fallback mechanism. In practice it won't be used in almost all situations. + if(gpAllocator) + return (EAThreadDynamicData*)gpAllocator->Alloc(sizeof(EAThreadDynamicData)); + else + return new EAThreadDynamicData; // This is a small problem, as this doesn't just allocate it but also constructs it. + } + + void FreeThreadDynamicData(EAThreadDynamicData* pEAThreadDynamicData) + { + AutoMutex am(EATHREAD_GLOBALVARS.gThreadDynamicMutex); + if((pEAThreadDynamicData >= (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData) && (pEAThreadDynamicData < ((EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData + kMaxThreadDynamicDataCount))) + { + pEAThreadDynamicData->~EAThreadDynamicData(); + EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[pEAThreadDynamicData - (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData].SetValue(0); + } + else + { + // Assume the data was allocated via the fallback mechanism. + if(gpAllocator) + { + pEAThreadDynamicData->~EAThreadDynamicData(); + gpAllocator->Free(pEAThreadDynamicData); + } + else + delete pEAThreadDynamicData; + } + } + + EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId) + { + for(size_t i(0); i < kMaxThreadDynamicDataCount; i++) + { + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + if(pTDD->mhThread == threadId) + return pTDD; + } + return NULL; // This is no practical way we can find the data unless thread-specific storage was involved. + } + + EAThreadDynamicData* FindThreadDynamicData(EA::Thread::SysThreadId sysThreadId) + { + for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i) + { + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + if (pTDD->mnThreadId == sysThreadId) + return pTDD; + } + return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved. + } + + bool IsDebuggerPresent() + { + #if defined(EA_PLATFORM_MICROSOFT) + return ::IsDebuggerPresent() != 0; + #else + return false; + #endif + } + } + + } + + + EAThreadDynamicData::EAThreadDynamicData() + : mhThread(EA::Thread::kThreadIdInvalid), + mnThreadId(0), // Note that this is a Windows "thread id", wheras for us thread ids are what Windows calls a thread handle. + mnStatus(EA::Thread::Thread::kStatusNone), + mnReturnValue(0), + mpBeginThreadUserWrapper(NULL), + mnRefCount(0) + { + // Empty + } + + + EAThreadDynamicData::~EAThreadDynamicData() + { + if(mhThread) + ::CloseHandle(mhThread); + + mhThread = EA::Thread::kThreadIdInvalid; + mnThreadId = 0; + } + + + void EAThreadDynamicData::AddRef() + { + mnRefCount.Increment(); + } + + + void EAThreadDynamicData::Release() + { + if(mnRefCount.Decrement() == 0) + EA::Thread::FreeThreadDynamicData(this); + } + + EA::Thread::ThreadParameters::ThreadParameters() + : mpStack(NULL) + , mnStackSize(0) + , mnPriority(kThreadPriorityDefault) + , mnProcessor(kProcessorDefault) + , mnAffinityMask(kThreadAffinityMaskAny) + , mpName("") + , mbDisablePriorityBoost(false) + { + } + + EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::sGlobalRunnableFunctionUserWrapper = NULL; + EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::sGlobalRunnableClassUserWrapper = NULL; + EA::Thread::AtomicInt32 EA::Thread::Thread::sDefaultProcessor = kProcessorAny; + EA::Thread::AtomicUint64 EA::Thread::Thread::sDefaultProcessorMask = UINT64_C(0xffffffffffffffff); + + EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::GetGlobalRunnableFunctionUserWrapper() + { + return sGlobalRunnableFunctionUserWrapper; + } + + void EA::Thread::Thread::SetGlobalRunnableFunctionUserWrapper(EA::Thread::RunnableFunctionUserWrapper pUserWrapper) + { + if(sGlobalRunnableFunctionUserWrapper != NULL) + { + // Can only be set once in entire game. + EAT_ASSERT(false); + } + else + { + sGlobalRunnableFunctionUserWrapper = pUserWrapper; + } + } + + EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::GetGlobalRunnableClassUserWrapper() + { + return sGlobalRunnableClassUserWrapper; + } + + void EA::Thread::Thread::SetGlobalRunnableClassUserWrapper(EA::Thread::RunnableClassUserWrapper pUserWrapper) + { + if(sGlobalRunnableClassUserWrapper != NULL) + { + // Can only be set once. + EAT_ASSERT(false); + } + else + sGlobalRunnableClassUserWrapper = pUserWrapper; + } + + // Helper that selects a target processor based on the provided ThreadParameters structure and the various + // pieces of shared state that EAThread maintains to implement a 'round-robin' style processor selection. + int SelectProcessor(const EA::Thread::ThreadParameters* pTP, EA::Thread::AtomicInt32& sDefaultProcessor, EA::Thread::AtomicUint64& sDefaultProcessorMask) + { + int nProcessor; + + if (pTP && (pTP->mnProcessor >= 0)) + { + nProcessor = pTP->mnProcessor; + + // This is a small attempt to try to spread out threads between processors. We don't + // care much if another thread happens to be created here and races with this. + if (nProcessor == EA::Thread::nLastProcessor) + ++EA::Thread::nLastProcessor; + } + else + { + #if defined(EA_PLATFORM_MICROSOFT) + if (!pTP || pTP->mnProcessor == EA::Thread::kProcessorAny) + { + // If the processor is not specified, then allow the scheduler to + // run the thread on any available processor + nProcessor = EA::Thread::kProcessorDefault; + } + else + #endif + + if (sDefaultProcessor >= 0) // If the user has identified a specific processor... + nProcessor = sDefaultProcessor; + else if(sDefaultProcessor == EA::Thread::kProcessorDefault) // If the user explicitly asked for the default processor + nProcessor = sDefaultProcessor; + else + { + // NOTE(rparolin): The reason we have this round-robin code is that the + // originally we used it on Xenon OS which required us to pick a CPU to run on. + // After the Xenon was deprecated this code remained and is now a functional + // requirement. We should probably deprecate and remove in the future but + // currently teams are dependent on it. + const uint64_t processorMask = sDefaultProcessorMask.GetValue(); + + do + { + nProcessor = EA::Thread::nLastProcessor.Increment(); + + if (nProcessor == MAXIMUM_PROCESSORS) + { + EA::Thread::nLastProcessor.SetValueConditional(0, MAXIMUM_PROCESSORS); + nProcessor = 0; + } + } while ((((uint64_t)1 << nProcessor) & processorMask) == 0); + } + } + + return nProcessor; + } + + EA::Thread::Thread::Thread() + { + mThreadData.mpData = NULL; + } + + EA::Thread::Thread::Thread(const Thread& t) + : mThreadData(t.mThreadData) + { + if(mThreadData.mpData) + mThreadData.mpData->AddRef(); + } + + + EA::Thread::Thread& EA::Thread::Thread::operator=(const Thread& t) + { + // We don't synchronize access to mpData; we assume that the user + // synchronizes it or this Thread instances is used from a single thread. + if(t.mThreadData.mpData) + t.mThreadData.mpData->AddRef(); + + if(mThreadData.mpData) + mThreadData.mpData->Release(); + + mThreadData = t.mThreadData; + + return *this; + } + + + EA::Thread::Thread::~Thread() + { + // We don't synchronize access to mpData; we assume that the user + // synchronizes it or this Thread instances is used from a single thread. + if(mThreadData.mpData) + mThreadData.mpData->Release(); + } + + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + static DWORD WINAPI RunnableFunctionInternal(void* pContext) + #else + static unsigned int __stdcall RunnableFunctionInternal(void* pContext) + #endif + { + // The parent thread is sharing memory with us and we need to + // make sure our view of it is synchronized with the parent. + EAReadWriteBarrier(); + + + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)pContext; + EA::Thread::RunnableFunction pFunction = (EA::Thread::RunnableFunction)pTDD->mpStartContext[0]; + void* pCallContext = pTDD->mpStartContext[1]; + + EA::Thread::SetCurrentThreadHandle(pTDD->mpStartContext[2], false); + pTDD->mpStackBase = EA::Thread::GetStackBase(); + pTDD->mnStatus = EA::Thread::Thread::kStatusRunning; + + EA::Thread::SetThreadName(pTDD->mhThread, pTDD->mName); + + if(pTDD->mpBeginThreadUserWrapper != NULL) + { + EA::Thread::RunnableFunctionUserWrapper pWrapperFunction = (EA::Thread::RunnableFunctionUserWrapper)pTDD->mpBeginThreadUserWrapper; + // if user wrapper is specified, call user wrapper and pass down the pFunction and pContext + pTDD->mnReturnValue = pWrapperFunction(pFunction, pCallContext); + } + else + { + pTDD->mnReturnValue = pFunction(pCallContext); + } + + const unsigned int nReturnValue = (unsigned int)pTDD->mnReturnValue; + EA::Thread::SetCurrentThreadHandle(0, false); + pTDD->mnStatus = EA::Thread::Thread::kStatusEnded; + pTDD->Release(); + return nReturnValue; + } + + void EA::Thread::Thread::SetAffinityMask(EA::Thread::ThreadAffinityMask nAffinityMask) + { + if(mThreadData.mpData->mhThread) + { + EA::Thread::SetThreadAffinityMask(mThreadData.mpData->mhThread, nAffinityMask); + } + } + + EA::Thread::ThreadAffinityMask EA::Thread::Thread::GetAffinityMask() + { + if(mThreadData.mpData->mhThread) + { + return mThreadData.mpData->mnThreadAffinityMask; + } + + return kThreadAffinityMaskAny; + } + + EA::Thread::ThreadId EA::Thread::Thread::Begin(RunnableFunction pFunction, void* pContext, const ThreadParameters* pTP, RunnableFunctionUserWrapper pUserWrapper) + { + // Check there is an entry for the current thread context in our ThreadDynamicData array. + ThreadId thisThreadId = GetThreadId(); + if(!FindThreadDynamicData(thisThreadId)) + { + EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData; + if(pData) + { + pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread. + // Do no AddRef for thread execution because this is not an EAThread managed thread. + pData->AddRef(); // AddRef for this function, to be released upon this function's exit. + pData->mhThread = thisThreadId; + pData->mnThreadId = GetCurrentThreadId(); + strncpy(pData->mName, "external", EATHREAD_NAME_SIZE); + pData->mName[EATHREAD_NAME_SIZE - 1] = 0; + pData->mpStackBase = EA::Thread::GetStackBase(); + } + } + + if(mThreadData.mpData) + mThreadData.mpData->Release(); // Matches the "AddRef for ourselves" below. + + // Win32-like platforms don't support user-supplied stacks. A user-supplied stack pointer + // here would be a waste of user memory, and so we assert that mpStack == NULL. + EAT_ASSERT(!pTP || (pTP->mpStack == NULL)); + + // We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be + // modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed + // during execution. + EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData; // Note that we use a special new here which doesn't use the heap. + mThreadData.mpData = pData; + + pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread. + pData->AddRef(); // AddRef for the thread, to be released upon the thread exiting. + pData->AddRef(); // AddRef for this function, to be released upon this function's exit. + pData->mhThread = kThreadIdInvalid; + pData->mpStartContext[0] = pFunction; + pData->mpStartContext[1] = pContext; + pData->mpBeginThreadUserWrapper = pUserWrapper; + pData->mnThreadAffinityMask = pTP ? pTP->mnAffinityMask : kThreadAffinityMaskAny; + + const unsigned nStackSize = pTP ? (unsigned)pTP->mnStackSize : 0; + + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + // Capilano no longer supports _beginthreadex. Using CreateThread instead may cause issues when using the MS CRT + // according to MSDN (memory leaks or possibly crashes) as it does not initialize the CRT. This a reasonable + // workaround while we wait for clarification from MS on what the recommended threading APIs are for Capilano. + HANDLE hThread = CreateThread(NULL, nStackSize, RunnableFunctionInternal, pData, CREATE_SUSPENDED, reinterpret_cast(&pData->mnThreadId)); + #else + HANDLE hThread = (HANDLE)_beginthreadex(NULL, nStackSize, RunnableFunctionInternal, pData, CREATE_SUSPENDED, &pData->mnThreadId); + #endif + + if(hThread) + { + pData->mhThread = hThread; + + if(pTP) + SetName(pTP->mpName); + pData->mpStartContext[2] = hThread; + + if(pTP && (pTP->mnPriority != kThreadPriorityDefault)) + SetPriority(pTP->mnPriority); + + #if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + if (pTP) + { + auto result = SetThreadPriorityBoost(pData->mhThread, pTP->mbDisablePriorityBoost); + EAT_ASSERT(result != 0); + EA_UNUSED(result); + } + #endif + + #if defined(EA_PLATFORM_MICROSOFT) + int nProcessor = SelectProcessor(pTP, sDefaultProcessor, sDefaultProcessorMask); + if(pTP && pTP->mnProcessor == EA::Thread::kProcessorAny) + SetAffinityMask(pTP->mnAffinityMask); + else + SetProcessor(nProcessor); + #endif + + ResumeThread(hThread); + pData->Release(); // Matches AddRef for this function. + return hThread; + } + + pData->Release(); // Matches AddRef for this function. + pData->Release(); // Matches AddRef for this Thread class above. + pData->Release(); // Matches AddRef for the thread above. + mThreadData.mpData = NULL; // mThreadData.mpData == pData + + return (ThreadId)kThreadIdInvalid; + } + + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + static DWORD WINAPI RunnableObjectInternal(void* pContext) + #else + static unsigned int __stdcall RunnableObjectInternal(void* pContext) + #endif + { + // The parent thread is sharing memory with us and we need to + // make sure our view of it is synchronized with the parent. + EAReadWriteBarrier(); + + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)pContext; + EA::Thread::IRunnable* pRunnable = (EA::Thread::IRunnable*)pTDD->mpStartContext[0]; + void* pCallContext = pTDD->mpStartContext[1]; + + EA::Thread::SetCurrentThreadHandle(pTDD->mpStartContext[2], false); + pTDD->mnStatus = EA::Thread::Thread::kStatusRunning; + + EA::Thread::SetThreadName(pTDD->mhThread, pTDD->mName); + + if(pTDD->mpBeginThreadUserWrapper) + { + EA::Thread::RunnableClassUserWrapper pWrapperClass = (EA::Thread::RunnableClassUserWrapper)pTDD->mpBeginThreadUserWrapper; + // if user wrapper is specified, call user wrapper and pass down the pFunction and pContext + pTDD->mnReturnValue = pWrapperClass(pRunnable, pCallContext); + } + else + pTDD->mnReturnValue = pRunnable->Run(pCallContext); + + const unsigned int nReturnValue = (unsigned int)pTDD->mnReturnValue; + EA::Thread::SetCurrentThreadHandle(0, false); + pTDD->mnStatus = EA::Thread::Thread::kStatusEnded; + pTDD->Release(); + return nReturnValue; + } + + + EA::Thread::ThreadId EA::Thread::Thread::Begin(IRunnable* pRunnable, void* pContext, const ThreadParameters* pTP, RunnableClassUserWrapper pUserWrapper) + { + if(mThreadData.mpData) + mThreadData.mpData->Release(); // Matches the "AddRef for ourselves" below. + + // Win32-like platforms don't support user-supplied stacks. A user-supplied stack pointer + // here would be a waste of user memory, and so we assert that mpStack == NULL. + EAT_ASSERT(!pTP || (pTP->mpStack == NULL)); + + // We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be + // modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed + // during execution. + EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData; // Note that we use a special new here which doesn't use the heap. + mThreadData.mpData = pData; + + pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread. + pData->AddRef(); // AddRef for the thread, to be released upon the thread exiting. + pData->AddRef(); // AddRef for this function, to be released upon this function's exit. + pData->mhThread = kThreadIdInvalid; + pData->mpStartContext[0] = pRunnable; + pData->mpStartContext[1] = pContext; + pData->mpBeginThreadUserWrapper = pUserWrapper; + pData->mnThreadAffinityMask = pTP ? pTP->mnAffinityMask : kThreadAffinityMaskAny; + const unsigned nStackSize = pTP ? (unsigned)pTP->mnStackSize : 0; + + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + // Capilano no longer supports _beginthreadex. Using CreateThread instead may cause issues when using the MS CRT + // according to MSDN (memory leaks or possibly crashes) as it does not initialize the CRT. This a reasonable + // workaround while we wait for clarification from MS on what the recommended threading APIs are for Capilano. + HANDLE hThread = CreateThread(NULL, nStackSize, RunnableObjectInternal, pData, CREATE_SUSPENDED, reinterpret_cast(&pData->mnThreadId)); + #else + HANDLE hThread = (HANDLE)_beginthreadex(NULL, nStackSize, RunnableObjectInternal, pData, CREATE_SUSPENDED, &pData->mnThreadId); + #endif + + if(hThread) + { + pData->mhThread = hThread; + + if(pTP) + SetName(pTP->mpName); + + pData->mpStartContext[2] = hThread; + + if(pTP && (pTP->mnPriority != kThreadPriorityDefault)) + SetPriority(pTP->mnPriority); + + #if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + if (pTP) + { + auto result = SetThreadPriorityBoost(pData->mhThread, pTP->mbDisablePriorityBoost); + EAT_ASSERT(result != 0); + EA_UNUSED(result); + } + #endif + + #if defined(EA_PLATFORM_MICROSOFT) + int nProcessor = SelectProcessor(pTP, sDefaultProcessor, sDefaultProcessorMask); + if(pTP && pTP->mnProcessor == EA::Thread::kProcessorAny) + SetAffinityMask(pTP->mnAffinityMask); + else + SetProcessor(nProcessor); + #endif + + ResumeThread(hThread); // This will unsuspend the thread. + pData->Release(); // Matches AddRef for this function. + return hThread; + } + + pData->Release(); // Matches AddRef for this function. + pData->Release(); // Matches AddRef for this Thread class above. + pData->Release(); // Matches AddRef for the thread above. + mThreadData.mpData = NULL; + + return (ThreadId)kThreadIdInvalid; + } + + + EA::Thread::Thread::Status EA::Thread::Thread::WaitForEnd(const ThreadTime& timeoutAbsolute, intptr_t* pThreadReturnValue) + { + // The mThreadData memory is shared between threads and when + // reading it we must be synchronized. + EAReadWriteBarrier(); + + // A mutex lock around mpData is not needed below because + // mpData is never allowed to go from non-NULL to NULL. + // Todo: Consider that there may be a subtle race condition here if + // the user immediately calls WaitForEnd right after calling Begin. + if(mThreadData.mpData) + { + if(mThreadData.mpData->mhThread) // If it was started... + { + // We must not call WaitForEnd from the thread we are waiting to end. That would result in a deadlock. + EAT_ASSERT(mThreadData.mpData->mhThread != EA::Thread::GetThreadId()); + // dwResult normally should be 'WAIT_OBJECT_0', but can also be WAIT_ABANDONED or WAIT_FAILED. + const DWORD dwResult = ::WaitForSingleObject(mThreadData.mpData->mhThread, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute)); + if(dwResult == WAIT_TIMEOUT) + return kStatusRunning; + + // Close the handle now so as to minimize handle proliferation. + ::CloseHandle(mThreadData.mpData->mhThread); + mThreadData.mpData->mhThread = 0; + mThreadData.mpData->mnStatus = kStatusEnded; + } + + if(pThreadReturnValue) + { + EAReadWriteBarrier(); + *pThreadReturnValue = mThreadData.mpData->mnReturnValue; + } + return kStatusEnded; // A thread was created, so it must have ended. + } + else + { + // Else the user hasn't started the thread yet, so we wait until the user starts it. + // Ideally, what we really want to do here is wait for some kind of signal. + // Instead for the time being we do a polling loop. + while((!mThreadData.mpData || !mThreadData.mpData->mhThread) && (GetThreadTime() < timeoutAbsolute)) + { + ThreadSleep(1); + EAReadWriteBarrier(); + EACompilerMemoryBarrier(); + } + if(mThreadData.mpData) + return WaitForEnd(timeoutAbsolute); + } + return kStatusNone; // No thread has been started. + } + + + EA::Thread::Thread::Status EA::Thread::Thread::GetStatus(intptr_t* pThreadReturnValue) const + { + // The mThreadData memory is shared between threads and when + // reading it we must be synchronized. + EAReadWriteBarrier(); + + // A mutex lock around mpData is not needed below because + // mpData is never allowed to go from non-NULL to NULL. + if(mThreadData.mpData) + { + if(mThreadData.mpData->mhThread) // If the thread has been started... + { + DWORD dwExitStatus; + + // Note that GetExitCodeThread is a hazard if the user of a thread exits + // with a return value that is equal to the value of STILL_ACTIVE (i.e. 259). + // We can document that users shouldn't do this, or we can change the code + // here to use WaitForSingleObject(hThread, 0) and assume the thread is + // still active if the return value is WAIT_TIMEOUT. + if(::GetExitCodeThread(mThreadData.mpData->mhThread, &dwExitStatus)) + { + if(dwExitStatus == STILL_ACTIVE) + return kStatusRunning; // Nothing has changed. + ::CloseHandle(mThreadData.mpData->mhThread); // Do this now so as to minimize handle proliferation. + mThreadData.mpData->mhThread = 0; + } // else fall through. + } // else fall through. + + if(pThreadReturnValue) + *pThreadReturnValue = mThreadData.mpData->mnReturnValue; + mThreadData.mpData->mnStatus = kStatusEnded; + return kStatusEnded; + } + return kStatusNone; + } + + + EA::Thread::ThreadId EA::Thread::Thread::GetId() const + { + // A mutex lock around mpData is not needed below because + // mpData is never allowed to go from non-NULL to NULL. + if(mThreadData.mpData) + return (ThreadId)mThreadData.mpData->mhThread; + return kThreadIdInvalid; + } + + + int EA::Thread::Thread::GetPriority() const + { + // A mutex lock around mpData is not needed below because + // mpData is never allowed to go from non-NULL to NULL. + if(mThreadData.mpData) + { + const int nPriority = ::GetThreadPriority(mThreadData.mpData->mhThread); + return kThreadPriorityDefault + (nPriority - THREAD_PRIORITY_NORMAL); + } + return kThreadPriorityUnknown; + } + + + bool EA::Thread::Thread::SetPriority(int nPriority) + { + // A mutex lock around mpData is not needed below because + // mpData is never allowed to go from non-NULL to NULL. + + // For more information on how Windows handle thread priority based on process priority, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/scheduling_priorities.asp + + EAT_ASSERT(nPriority != kThreadPriorityUnknown); + if(mThreadData.mpData) + { + int nNewPriority = THREAD_PRIORITY_NORMAL + (nPriority - kThreadPriorityDefault); + bool result = ::SetThreadPriority(mThreadData.mpData->mhThread, nNewPriority) != 0; + + // Windows process running in NORMAL_PRIORITY_CLASS is picky about the priority passed in. + // So we need to set the priority to the next priority supported + #if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + while(!result) + { + if(nNewPriority >= THREAD_PRIORITY_TIME_CRITICAL) + return ::SetThreadPriority(mThreadData.mpData->mhThread, THREAD_PRIORITY_TIME_CRITICAL) != 0; + + if(nNewPriority <= THREAD_PRIORITY_IDLE) + return ::SetThreadPriority(mThreadData.mpData->mhThread, THREAD_PRIORITY_IDLE) != 0; + + result = ::SetThreadPriority(mThreadData.mpData->mhThread, nNewPriority) != 0; + nNewPriority++; + } + + #endif + + return result; + } + return false; + } + + + void EA::Thread::Thread::SetProcessor(int nProcessor) + { + if(mThreadData.mpData) + { + #if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + + static int nProcessorCount = GetProcessorCount(); + if(nProcessor >= nProcessorCount) + nProcessor %= nProcessorCount; + + ThreadAffinityMask mask = 0x7F; // default to all 7 available cores. + if (nProcessor >= 0) + mask = ((ThreadAffinityMask)1) << nProcessor; + + SetThreadAffinityMask(mThreadData.mpData->mhThread, mask); + + #else + static int nProcessorCount = GetProcessorCount(); + + if(nProcessor < 0) + nProcessor = MAXIMUM_PROCESSORS; // This causes the SetThreadIdealProcessor to reset to 'no ideal processor'. + else + { + if(nProcessor >= nProcessorCount) + nProcessor %= nProcessorCount; + } + + // SetThreadIdealProcessor differs from SetThreadAffinityMask in that SetThreadIdealProcessor is not + // a strict assignment, and it allows the OS to move the thread if the ideal processor is busy. + // SetThreadAffinityMask is a more rigid assignment, but it can result in slower performance and + // possibly hangs due to processor contention between threads. For Windows we use SetIdealThreadProcessor + // in the name of safety and likely better overall performance. + SetThreadIdealProcessor(mThreadData.mpData->mhThread, (DWORD)nProcessor); + + #endif + } + } + + + typedef VOID (APIENTRY *PAPCFUNC)(_In_ ULONG_PTR dwParam); + extern "C" WINBASEAPI DWORD WINAPI QueueUserAPC(_In_ PAPCFUNC pfnAPC, _In_ HANDLE hThread, _In_ ULONG_PTR dwData); + + void EA::Thread::Thread::Wake() + { + // A mutex lock around mpData is not needed below because + // mpData is never allowed to go from non-NULL to NULL. + struct ThreadWake{ static void WINAPI Empty(ULONG_PTR){} }; + if(mThreadData.mpData && mThreadData.mpData->mhThread) + ::QueueUserAPC((PAPCFUNC)ThreadWake::Empty, mThreadData.mpData->mhThread, 0); + } + + + const char* EA::Thread::Thread::GetName() const + { + if(mThreadData.mpData) + return mThreadData.mpData->mName; + + return ""; + } + + + void EA::Thread::Thread::SetName(const char* pName) + { + if (mThreadData.mpData && pName) + EA::Thread::Internal::SetThreadName(mThreadData.mpData, pName); + } + + +#endif // EA_PLATFORM_MICROSOFT + +EA_RESTORE_VC_WARNING() // 6312 6322 + +#if defined(EA_COMPILER_MSVC) && EA_COMPILER_VERSION >= 1900 // VS2015+ + EA_RESTORE_VC_WARNING() // 5031 5032 +#endif diff --git a/src/thirdparty/ea/EAThread/source/unix/eathread_barrier_unix.cpp b/src/thirdparty/ea/EAThread/source/unix/eathread_barrier_unix.cpp new file mode 100644 index 00000000..41d3a2d7 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/unix/eathread_barrier_unix.cpp @@ -0,0 +1,189 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include + + +#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + #include + #include + #include + #include + #ifdef EA_PLATFORM_WINDOWS + EA_DISABLE_ALL_VC_WARNINGS() + #include // Presumably we are using pthreads-win32. + EA_RESTORE_ALL_VC_WARNINGS() + #endif + + + EABarrierData::EABarrierData() + : mCV(), mMutex(), mnHeight(0), mnCurrent(0), mnCycle(0), mbValid(false) + {} + + + EA::Thread::BarrierParameters::BarrierParameters(int height, bool bIntraProcess, const char* pName) + : mHeight(height), mbIntraProcess(bIntraProcess) + { + if(pName) + strncpy(mName, pName, sizeof(mName)-1); + else + mName[0] = 0; + } + + + EA::Thread::Barrier::Barrier(const BarrierParameters* pBarrierParameters, bool bDefaultParameters) + { + if(!pBarrierParameters && bDefaultParameters) + { + BarrierParameters parameters; + Init(¶meters); + } + else + Init(pBarrierParameters); + } + + + EA::Thread::Barrier::Barrier(int height) + { + BarrierParameters parameters(height); + Init(¶meters); + } + + + EA::Thread::Barrier::~Barrier() + { + if(mBarrierData.mbValid){ + EAT_ASSERT(mBarrierData.mnCurrent == mBarrierData.mnHeight); + int result = pthread_mutex_destroy(&mBarrierData.mMutex); + EA_UNUSED(result); + EAT_ASSERT(result == 0); + result = pthread_cond_destroy(&mBarrierData.mCV); + EAT_ASSERT(result == 0); + EA_UNUSED( result ); //if compiling without asserts + } + } + + + bool EA::Thread::Barrier::Init(const BarrierParameters* pBarrierParameters) + { + if(pBarrierParameters && !mBarrierData.mbValid){ + mBarrierData.mbValid = false; + mBarrierData.mnHeight = pBarrierParameters->mHeight; + mBarrierData.mnCurrent = pBarrierParameters->mHeight; + mBarrierData.mnCycle = 0; + + int result = pthread_mutex_init(&mBarrierData.mMutex, NULL); + if(result == 0){ + result = pthread_cond_init(&mBarrierData.mCV, NULL); + if(result == 0) + mBarrierData.mbValid = true; + else + pthread_mutex_destroy(&mBarrierData.mMutex); + } + return mBarrierData.mbValid; + } + return false; + } + + + EA::Thread::Barrier::Result EA::Thread::Barrier::Wait(const ThreadTime& timeoutAbsolute) + { + if(!mBarrierData.mbValid){ + EAT_ASSERT(false); + return kResultError; + } + + int result = pthread_mutex_lock(&mBarrierData.mMutex); + if(result != 0){ + EAT_ASSERT(false); + return kResultError; + } + + const unsigned long nCurrentCycle = (unsigned)mBarrierData.mnCycle; + bool bPrimary = false; + + if(--mBarrierData.mnCurrent == 0){ // This is not an atomic operation. We are within a mutex lock. + // The last barrier can never time out, as its action is always immediate. + mBarrierData.mnCycle++; + mBarrierData.mnCurrent = mBarrierData.mnHeight; + result = pthread_cond_broadcast(&mBarrierData.mCV); + + // The last thread into the barrier will return a result of + // kResultPrimary rather than kResultSecondary. + if(result == 0) + bPrimary = true; + //else leave result as an error value. + } + else{ + // timeoutMilliseconds + // Wait with cancellation disabled, because pthreads barrier_wait + // should not be a cancellation point. + #if defined(PTHREAD_CANCEL_DISABLE) + int cancel; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel); + #endif + + // Wait until the barrier's cycle changes, which means that + // it has been broadcast, and we don't want to wait anymore. + while(nCurrentCycle == mBarrierData.mnCycle){ + do{ + // Under SMP systems, pthread_cond_wait can return the success value 'spuriously'. + // This is by design and we must retest the predicate condition and if it has + // not true, we must go back to waiting. + result = pthread_cond_timedwait(&mBarrierData.mCV, &mBarrierData.mMutex, &timeoutAbsolute); + } while((result == 0) && (nCurrentCycle == mBarrierData.mnCycle)); + if(result != 0) + break; + } + + #if defined(PTHREAD_CANCEL_DISABLE) + int cancelTemp; + pthread_setcancelstate(cancel, &cancelTemp); + #endif + } + + // We declare a new result2 value because the old one + // might have a special value from above in it. + const int result2 = pthread_mutex_unlock(&mBarrierData.mMutex); (void)result2; + EAT_ASSERT(result2 == 0); + + if(result == 0) + return bPrimary ? kResultPrimary : kResultSecondary; + else if(result == ETIMEDOUT) + return kResultTimeout; + return kResultError; + } + + + EA::Thread::Barrier* EA::Thread::BarrierFactory::CreateBarrier() + { + EA::Thread::Allocator* pAllocator = EA::Thread::GetAllocator(); + + if(pAllocator) + return new(pAllocator->Alloc(sizeof(EA::Thread::Barrier))) EA::Thread::Barrier; + else + return new EA::Thread::Barrier; + } + + void EA::Thread::BarrierFactory::DestroyBarrier(EA::Thread::Barrier* pBarrier) + { + EA::Thread::Allocator* pAllocator = EA::Thread::GetAllocator(); + + if(pAllocator) + { + pBarrier->~Barrier(); + pAllocator->Free(pBarrier); + } + else + delete pBarrier; + } + + +#endif // EA_PLATFORM_XXX + + diff --git a/src/thirdparty/ea/EAThread/source/unix/eathread_callstack_glibc.cpp b/src/thirdparty/ea/EAThread/source/unix/eathread_callstack_glibc.cpp new file mode 100644 index 00000000..5e050256 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/unix/eathread_callstack_glibc.cpp @@ -0,0 +1,84 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include + +#include + +namespace EA +{ +namespace Thread +{ + + +// To do: Remove the usage of sStackBase for the platforms that it's not needed, +// as can be seen from the logic below. For example Mac OSX probably doesn't need it. +static EA::Thread::ThreadLocalStorage sStackBase; + +/////////////////////////////////////////////////////////////////////////////// +// SetStackBase +// +EATHREADLIB_API void SetStackBase(void* pStackBase) +{ + if(pStackBase) + sStackBase.SetValue(pStackBase); + else + { + pStackBase = __builtin_frame_address(0); + + if(pStackBase) + SetStackBase(pStackBase); + // Else failure; do nothing. + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// GetStackBase +// +EATHREADLIB_API void* GetStackBase() +{ + #if defined(EA_PLATFORM_UNIX) + void* pBase; + if(GetPthreadStackInfo(&pBase, NULL)) + return pBase; + #endif + + // Else we require the user to have set this previously, usually via a call + // to SetStackBase() in the start function of this currently executing + // thread (or main for the main thread). + return sStackBase.GetValue(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// GetStackLimit +// +EATHREADLIB_API void* GetStackLimit() +{ + #if defined(EA_PLATFORM_UNIX) + void* pLimit; + if(GetPthreadStackInfo(NULL, &pLimit)) + return pLimit; + #endif + + // If this fails then we might have an issue where you are using GCC but not + // using the GCC standard library glibc. Or maybe glibc doesn't support + // __builtin_frame_address on this platform. Or maybe you aren't using GCC but + // rather a compiler that masquerades as GCC (common situation). + void* pStack = __builtin_frame_address(0); + return (void*)((uintptr_t)pStack & ~4095); // Round down to nearest page, as the stack grows downward. + +} + + +} // namespace Thread +} // namespace EA + + + diff --git a/src/thirdparty/ea/EAThread/source/unix/eathread_condition_unix.cpp b/src/thirdparty/ea/EAThread/source/unix/eathread_condition_unix.cpp new file mode 100644 index 00000000..a5e3503a --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/unix/eathread_condition_unix.cpp @@ -0,0 +1,150 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include + + +#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + #include + #include + #include + #ifdef EA_PLATFORM_WINDOWS + EA_DISABLE_ALL_VC_WARNINGS() + #include // Presumably we are using pthreads-win32. + EA_RESTORE_ALL_VC_WARNINGS() + #endif + + + EAConditionData::EAConditionData() + { + memset(&mCV, 0, sizeof(mCV)); + } + + + EA::Thread::ConditionParameters::ConditionParameters(bool bIntraProcess, const char* /*pName*/) + : mbIntraProcess(bIntraProcess) + { + // Empty + } + + + EA::Thread::Condition::Condition(const ConditionParameters* pConditionParameters, bool bDefaultParameters) + { + if(!pConditionParameters && bDefaultParameters) + { + ConditionParameters parameters; + Init(¶meters); + } + else + Init(pConditionParameters); + } + + + EA::Thread::Condition::~Condition() + { + pthread_cond_destroy(&mConditionData.mCV); + } + + + bool EA::Thread::Condition::Init(const ConditionParameters* pConditionParameters) + { + if(pConditionParameters) + { + #if defined(EA_PLATFORM_ANDROID) // Some platforms don't provide pthread_condattr_init, and pthread_condattr_t is just an int. + pthread_condattr_t cattr; + memset(&cattr, 0, sizeof(cattr)); + + const int result = pthread_cond_init(&mConditionData.mCV, &cattr); + + return (result == 0); + #else + pthread_condattr_t cattr; + pthread_condattr_init(&cattr); + + #if defined(PTHREAD_PROCESS_PRIVATE) // Some pthread implementations don't recognize this. PTHREAD_PROCESS_SHARED bugged on iphone + #if defined(EA_PLATFORM_IPHONE) || defined(EA_PLATFORM_OSX) + EAT_ASSERT( pConditionParameters->mbIntraProcess == true ); // shared conditions bugged on apple hardware + #elif defined(EA_PLATFORM_NX) + // NX platform headers don't support 'PTHREAD_PROCESS_SHARED'. We don't provide the else clause in the general code below. + if (pConditionParameters->mbIntraProcess) + pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_PRIVATE); + #else + if(pConditionParameters->mbIntraProcess) + pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_PRIVATE); + else + pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); + #endif + #endif + + const int result = pthread_cond_init(&mConditionData.mCV, &cattr); + + pthread_condattr_destroy(&cattr); + + return (result == 0); + #endif + } + + return false; + } + + + EA::Thread::Condition::Result EA::Thread::Condition::Wait(Mutex* pMutex, const ThreadTime& timeoutAbsolute) + { + int result; + pthread_mutex_t* pMutex_t; + EAMutexData* pMutexData; + + EAT_ASSERT(pMutex); + + // We have a small problem here in that if we are using the pMutex argument, + // the pthread_cond_wait call will unlock the mutex via the internal mutex data and + // not without calling the Mutex::Lock function. The result is that the Mutex doesn't + // have its lock count value reduced by one and so other threads will see the lock + // count as being 1 when in fact it should be zero. So we account for that here + // by manually maintaining the lock count, which we can do because we have the lock. + EAT_ASSERT(pMutex->GetLockCount() == 1); + pMutexData = (EAMutexData*)pMutex->GetPlatformData(); + pMutexData->SimulateLock(false); + pMutex_t = &pMutexData->mMutex; + + if(timeoutAbsolute == kTimeoutNone) + result = pthread_cond_wait(&mConditionData.mCV, pMutex_t); + else + result = pthread_cond_timedwait(&mConditionData.mCV, pMutex_t, &timeoutAbsolute); + + pMutexData->SimulateLock(true); + EAT_ASSERT(!pMutex || (pMutex->GetLockCount() == 1)); + + if(result != 0) + { + if(result == ETIMEDOUT) + return kResultTimeout; + EAT_ASSERT(false); + return kResultError; + } + return kResultOK; + } + + + bool EA::Thread::Condition::Signal(bool bBroadcast) + { + if(bBroadcast) + return (pthread_cond_broadcast(&mConditionData.mCV) == 0); + return (pthread_cond_signal(&mConditionData.mCV) == 0); + } + + +#endif // EA_PLATFORM_XXX + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/unix/eathread_mutex_unix.cpp b/src/thirdparty/ea/EAThread/source/unix/eathread_mutex_unix.cpp new file mode 100644 index 00000000..41978356 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/unix/eathread_mutex_unix.cpp @@ -0,0 +1,223 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include + + +#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + #include + #ifdef EA_PLATFORM_WINDOWS + EA_DISABLE_ALL_VC_WARNINGS() + #include // Presumably we are using pthreads-win32. + EA_RESTORE_ALL_VC_WARNINGS() + #ifdef CreateMutex + #undef CreateMutex // Windows #defines CreateMutex to CreateMutexA or CreateMutexW. + #endif + #endif + + + EAMutexData::EAMutexData() + : mMutex(), mnLockCount(0) + { + #if EAT_ASSERT_ENABLED + mThreadId = EA::Thread::kThreadIdInvalid; + #endif + + ::memset(&mMutex, 0, sizeof(mMutex)); + } + + void EAMutexData::SimulateLock(bool bLock) + { + if(bLock) + { + ++mnLockCount; + +#if EAT_ASSERT_ENABLED + mThreadId = EA::Thread::GetThreadId(); +#endif + } + else + { + --mnLockCount; + +#if EAT_ASSERT_ENABLED + mThreadId = EA::Thread::kThreadIdInvalid; +#endif + } + } + + + EA::Thread::MutexParameters::MutexParameters(bool bIntraProcess, const char* /*pName*/) + : mbIntraProcess(bIntraProcess) + { + // Empty + } + + + EA::Thread::Mutex::Mutex(const MutexParameters* pMutexParameters, bool bDefaultParameters) + { + if(!pMutexParameters && bDefaultParameters) + { + MutexParameters parameters; + Init(¶meters); + } + else + Init(pMutexParameters); + } + + + EA::Thread::Mutex::~Mutex() + { + EAT_ASSERT(mMutexData.mnLockCount == 0); + pthread_mutex_destroy(&mMutexData.mMutex); + } + + + bool EA::Thread::Mutex::Init(const MutexParameters* pMutexParameters) + { + if(pMutexParameters) + { + mMutexData.mnLockCount = 0; + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + #if !defined( EA_PLATFORM_NX ) // NX defines PTHREAD_PROCESS_PRIVATE but doesn't implement pthread_mutexattr_setpshared because they are all private. + #if defined(PTHREAD_PROCESS_PRIVATE) // Some pthread implementations don't recognize this. + #if defined(PTHREAD_PROCESS_SHARED) + if (pMutexParameters->mbIntraProcess) + pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); + else + pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + #else + pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); + #endif + #endif + #endif + + const int result = pthread_mutex_init(&mMutexData.mMutex, &attr); + pthread_mutexattr_destroy(&attr); + + EAT_ASSERT(result != -1); + return (result != -1); + } + + return false; + } + + + int EA::Thread::Mutex::Lock(const ThreadTime& timeoutAbsolute) + { + int result; + + EAT_ASSERT(mMutexData.mnLockCount < 100000); + + if(timeoutAbsolute == kTimeoutNone) + { + result = pthread_mutex_lock(&mMutexData.mMutex); + + if(result != 0) + { + EAT_ASSERT(false); + return kResultError; + } + } + else if(timeoutAbsolute == kTimeoutImmediate) + { + result = pthread_mutex_trylock(&mMutexData.mMutex); + + if(result != 0) + { + if(result == EBUSY) + return kResultTimeout; + + EAT_ASSERT(false); + return kResultError; + } + } + else + { + #if (defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_WINDOWS)) && !defined(EA_PLATFORM_CYGWIN) && !defined(EA_PLATFORM_ANDROID) + const timespec* pTimeSpec = &timeoutAbsolute; + result = pthread_mutex_timedlock(&mMutexData.mMutex, const_cast(pTimeSpec)); // Some pthread implementations use non-const timespec, so cast for them. + + if(result != 0) + { + if(result == ETIMEDOUT) + return kResultTimeout; + EAT_ASSERT(false); + return kResultError; + } + #else // OSX, BSD + // Some Posix systems don't have pthread_mutex_timedlock. In these + // cases we fall back to a polling mechanism. However, polling really + // isn't proper because the polling thread might be at a greater + // priority level than the lock-owning thread and thus this code + // might not work as well as desired. + while(((result = pthread_mutex_trylock(&mMutexData.mMutex)) != 0) && (GetThreadTime() < timeoutAbsolute)) + ThreadSleep(1); + + if(result != 0) + { + if(result == EBUSY) + return kResultTimeout; + EAT_ASSERT(false); + return kResultError; + } + #endif + } + + EAT_ASSERT(mMutexData.mThreadId = EA::Thread::GetThreadId()); // Intentionally '=' here and not '=='. + EAT_ASSERT(mMutexData.mnLockCount >= 0); + return ++mMutexData.mnLockCount; // This is safe to do because we have the lock. + } + + + int EA::Thread::Mutex::Unlock() + { + EAT_ASSERT(mMutexData.mThreadId == EA::Thread::GetThreadId()); + EAT_ASSERT(mMutexData.mnLockCount > 0); + + const int nReturnValue(--mMutexData.mnLockCount); // This is safe to do because we have the lock. + + if(pthread_mutex_unlock(&mMutexData.mMutex) != 0) + { + EAT_ASSERT(false); + return nReturnValue + 1; + } + + return nReturnValue; + } + + + int EA::Thread::Mutex::GetLockCount() const + { + return mMutexData.mnLockCount; + } + + + bool EA::Thread::Mutex::HasLock() const + { + #if EAT_ASSERT_ENABLED + return (mMutexData.mnLockCount > 0) && (mMutexData.mThreadId == GetThreadId()); + #else + return (mMutexData.mnLockCount > 0); // This is the best we can do. + #endif + } + + +#endif // EA_PLATFORM_XXX + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/unix/eathread_pthread_stack_info.cpp b/src/thirdparty/ea/EAThread/source/unix/eathread_pthread_stack_info.cpp new file mode 100644 index 00000000..248f7f9f --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/unix/eathread_pthread_stack_info.cpp @@ -0,0 +1,140 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#include +#include + +#if defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_APPLE) + #include +#endif + +namespace EA +{ +namespace Thread +{ + +#if defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_APPLE) || defined(EA_PLATFORM_SONY) + // With some implementations of pthread, the stack base is returned by pthread as NULL if it's the main thread, + // or possibly if it's a thread you created but didn't call pthread_attr_setstack manually to provide your + // own stack. It's impossible for us to tell here whether will be such a NULL return value, so we just do what + // we can and the user nees to beware that a NULL return value means that the system doesn't provide the + // given information for the current thread. This function returns false and sets pBase and pLimit to NULL in + // the case that the thread base and limit weren't returned by the system or were returned as NULL. + + #if defined(EA_PLATFORM_APPLE) + bool GetPthreadStackInfo(void** pBase, void** pLimit) + { + pthread_t thread = pthread_self(); + void* pBaseTemp = pthread_get_stackaddr_np(thread); + size_t stackSize = pthread_get_stacksize_np(thread); + + if(pBase) + *pBase = pBaseTemp; + if(pLimit) + { + if(pBaseTemp) + *pLimit = (void*)((size_t)pBaseTemp - stackSize); + else + *pLimit = NULL; + } + + return (pBaseTemp != NULL); + } + + #elif defined(EA_PLATFORM_SONY) + bool GetPthreadStackInfo(void** pBase, void** pLimit) + { + bool returnValue = false; + size_t stackSize; + void* pBaseTemp = NULL; + void* pLimitTemp = NULL; + + ScePthreadAttr attr; + + scePthreadAttrInit(&attr); + + int result = scePthreadAttrGet(scePthreadSelf(), &attr); + if(result == 0) // SCE_OK (=0) + { + result = scePthreadAttrGetstack(&attr, &pLimitTemp, &stackSize); + if((result == 0) && (pLimitTemp != NULL)) // If success... + { + pBaseTemp = (void*)((uintptr_t)pLimitTemp + stackSize); // p is returned by pthread_attr_getstack as the lowest address in the stack, and not the stack base. + returnValue = true; + } + else + { + pBaseTemp = NULL; + pLimitTemp = NULL; + } + } + + scePthreadAttrDestroy(&attr); + + if(pBase) + *pBase = pBaseTemp; + if(pLimit) + *pLimit = pLimitTemp; + + return returnValue; + } + #else + bool GetPthreadStackInfo(void** pBase, void** pLimit) + { + bool returnValue = false; + void* pBaseTemp = NULL; + void* pLimitTemp = NULL; + + pthread_attr_t attr; + pthread_attr_init(&attr); + + #if defined(EA_PLATFORM_LINUX) + int result = pthread_getattr_np(pthread_self(), &attr); + #else + int result = pthread_attr_get_np(pthread_self(), &attr); // __BSD__ or __FreeBSD__ + #endif + + if(result == 0) + { + // The pthread_attr_getstack() function returns the stack address and stack size + // attributes of the thread attributes object referred to by attr in the buffers + // pointed to by stackaddr and stacksize, respectively. According to the documentation, + // the stack address reported is the lowest memory address and not the stack 'base'. + // http://pubs.opengroup.org/onlinepubs/007904975/functions/pthread_attr_setstack.html + size_t stackSize; + result = pthread_attr_getstack(&attr, &pLimitTemp, &stackSize); + + if((result == 0) && (pLimitTemp != NULL)) // If success... + { + pBaseTemp = (void*)((uintptr_t)pLimitTemp + stackSize); // p is returned by pthread_attr_getstack as the lowest address in the stack, and not the stack base. + returnValue = true; + } + else + { + pBaseTemp = NULL; + pLimitTemp = NULL; + } + } + + pthread_attr_destroy(&attr); + + if(pBase) + *pBase = pBaseTemp; + if(pLimit) + *pLimit = pLimitTemp; + + return returnValue; + } + #endif +#endif + + +} // namespace Callstack +} // namespace EA + + + + + diff --git a/src/thirdparty/ea/EAThread/source/unix/eathread_semaphore_unix.cpp b/src/thirdparty/ea/EAThread/source/unix/eathread_semaphore_unix.cpp new file mode 100644 index 00000000..10f171b3 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/unix/eathread_semaphore_unix.cpp @@ -0,0 +1,261 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#include +#include + + +#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + #include + #include + #include + #include + #ifdef EA_PLATFORM_WINDOWS + EA_DISABLE_ALL_VC_WARNINGS() + #include + #include // Presumably we are using pthreads-win32. + EA_RESTORE_ALL_VC_WARNINGS() + #ifdef CreateSemaphore + #undef CreateSemaphore // Windows #defines CreateSemaphore to CreateSemaphoreA or CreateSemaphoreW. + #endif + #endif + + + EASemaphoreData::EASemaphoreData() + : mnCount(0), mnMaxCount(INT_MAX) + { + memset(&mSemaphore, 0, sizeof(mSemaphore)); + } + + + EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* /*pName*/) + : mInitialCount(initialCount), mMaxCount(INT_MAX), mbIntraProcess(bIntraProcess) + { + } + + + EA::Thread::Semaphore::Semaphore(const SemaphoreParameters* pSemaphoreParameters, bool bDefaultParameters) + { + if(!pSemaphoreParameters && bDefaultParameters) + { + SemaphoreParameters parameters; + Init(¶meters); + } + else + Init(pSemaphoreParameters); + } + + + EA::Thread::Semaphore::Semaphore(int initialCount) + { + SemaphoreParameters parameters(initialCount); + Init(¶meters); + } + + + EA::Thread::Semaphore::~Semaphore() + { + #if defined(EA_PLATFORM_ANDROID) + sem_destroy(&mSemaphoreData.mSemaphore); // Android's sem_destroy is broken. http://code.google.com/p/android/issues/detail?id=3106 + #else + int result = -1; + + for(;;) + { + result = sem_destroy(&mSemaphoreData.mSemaphore); + + if((result == -1) && (errno == EBUSY)) // If another thread or process is blocked on this semaphore... + ThreadSleep(kTimeoutYield); // Yield. If we don't yield, it's possible we could block other other threads or processes from running, on some systems. + else + break; + } + + EAT_ASSERT(result != -1); + #endif + } + + + bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters) + { + if(pSemaphoreParameters) + { + mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount; + mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount; + + if(mSemaphoreData.mnCount < 0) + mSemaphoreData.mnCount = 0; + + mSemaphoreData.mbIntraProcess = pSemaphoreParameters->mbIntraProcess; + + int result = sem_init(&mSemaphoreData.mSemaphore, mSemaphoreData.mbIntraProcess ? 1 : 0, (unsigned)mSemaphoreData.mnCount); + + // To consider: Remove this fallback and simply return false if the first attempt failed. + if((result == -1) && mSemaphoreData.mbIntraProcess) + { + result = sem_init(&mSemaphoreData.mSemaphore, 0, (unsigned)mSemaphoreData.mnCount); + + if(result == -1) + { + EAT_ASSERT(false); + memset(&mSemaphoreData.mSemaphore, 0, sizeof(mSemaphoreData.mSemaphore)); + } + else + mSemaphoreData.mbIntraProcess = false; + } + + return (result != -1); + } + + return false; + } + + + int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute) + { + int result; + + if(timeoutAbsolute == kTimeoutNone) + { + // We retry waits that were interrupted by signals. Should we instead require + // the user to deal with this and return an error value? Or should we require + // the user to disable the appropriate signal interruptions? + while(((result = sem_wait(&mSemaphoreData.mSemaphore)) == -1) && (errno == EINTR)) + continue; + + if(result == -1) + { + EAT_ASSERT(false); // This is an error condition. + return kResultError; + } + } + else if(timeoutAbsolute == kTimeoutImmediate) + { + // The sem_trywait() and sem_wait() functions shall return zero if the calling process successfully + // performed the semaphore lock operation on the semaphore designated by sem. If the call was + // unsuccessful, the state of the semaphore shall be unchanged, and the function shall return a + // value of -1 and set errno to indicate the error. + int trywaitResult = sem_trywait(&mSemaphoreData.mSemaphore); + + if(trywaitResult == -1) + { + if(errno == EAGAIN) // The sem_* family of functions are different from pthreads because they set errno instead of returning an error value. + return kResultTimeout; + + #ifdef EA_PLATFORM_WINDOWS // On Windows, the errno mechanism doesn't work unless you + if(mSemaphoreData.mnCount == 0) // are using the C runtime library as a shared dll between + return kResultTimeout; // the app and pthreads.dll. We try to account for the existence + #endif // of this problem here in a somewhat conservative way. + + return kResultError; + } + + // Android sem_trywait is broken and in earlier versions returns EAGAIN instead of setting + // errno to EAGAIN. http://source-android.frandroid.com/bionic/libc/docs/CHANGES.TXT + #if defined(EA_PLATFORM_ANDROID) + if(trywaitResult == EAGAIN) + return kResultTimeout; + #endif + } + else + { + // Some systems don't have a sem_timedwait. In these cases we + // fall back to a polling mechanism. However, polling really + // isn't proper because the polling thread might be at a greater + // priority level than the lock-owning thread and thus this code + // might not work as well as desired. + + #if defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_WINDOWS) + // We retry waits that were interrupted by signals. Should we instead require + // the user to deal with this and return an error value? Or should we require + // the user to disable the appropriate signal interruptions? + while(((result = sem_timedwait(&mSemaphoreData.mSemaphore, &timeoutAbsolute)) == -1) && (errno == EINTR)) + continue; + + if(result == -1) + { + if(errno == ETIMEDOUT) // The sem_* family of functions are different from pthreads because they set errno instead of returning an error value. + return kResultTimeout; + + #ifdef EA_PLATFORM_WINDOWS // On Windows, the errno mechanism doesn't work unless you + if(mSemaphoreData.mnCount == 0) // are using the C runtime library as a shared dll between + return kResultTimeout; // the app and pthreads.dll. We try to account for the existence + #endif // of this problem here in a somewhat conservative way. + + return kResultError; + } + #else + // BSD family of Unixes usually lack sem_trywait as of this writing. + // This is a major problem, as a polling solution doesn't work under some circumstances. + + while(((result = sem_trywait(&mSemaphoreData.mSemaphore)) == -1) && (errno == EAGAIN || errno == EINTR) && (GetThreadTime() < timeoutAbsolute)) + ThreadSleep(1); + + if(result == -1) + { + if(errno == EAGAIN) + return kResultTimeout; + + return kResultError; + } + #endif + } + + EAT_ASSERT(mSemaphoreData.mnCount > 0); + return (int)mSemaphoreData.mnCount.Decrement(); // AtomicInt32 operation. Note that the value of the semaphore count could change from the returned value by the time the caller reads it. This is fine but the user should understand this. + } + + + int EA::Thread::Semaphore::Post(int count) + { + // Some systems have a sem_post_multiple which we could take advantage + // of here to atomically post multiple times. + EAT_ASSERT(mSemaphoreData.mnCount >= 0); + + // It's hard to correctly implement mnMaxCount here, given that it + // may be modified by multiple threads during this execution. So if you want + // to use max-count with an IntraProcess semaphore safely then you need to + // post only from a single thread, or at least a single thread at a time. + + int currentCount = mSemaphoreData.mnCount; + + // If count would cause an overflow exit early + if ((mSemaphoreData.mnMaxCount - count) < currentCount) + return kResultError; + + currentCount += count; + + while(count-- > 0) + { + ++mSemaphoreData.mnCount; // AtomicInt32 operation. + + if(sem_post(&mSemaphoreData.mSemaphore) == -1) + { + --mSemaphoreData.mnCount; // AtomicInt32 operation. + EAT_ASSERT(false); + return kResultError; + } + } + + // If all count posts occurred... + return currentCount; // It's possible that another thread may have modified this value since we changed it, but that's not important. + } + + + int EA::Thread::Semaphore::GetCount() const + { + return (int)mSemaphoreData.mnCount; + } + + +#endif // EA_PLATFORM_XXX + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/unix/eathread_stadia.cpp b/src/thirdparty/ea/EAThread/source/unix/eathread_stadia.cpp new file mode 100644 index 00000000..41f863e8 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/unix/eathread_stadia.cpp @@ -0,0 +1,156 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(EA_PLATFORM_STADIA) +#include +#include +#include + +EATHREADLIB_API bool isUint(const char* buffer) +{ + if (buffer == nullptr || *buffer == '\0') + return false; + + while (isspace(*buffer) && *buffer != '\0') + ++buffer; + + return *buffer != '\0' && isdigit(*buffer); +} + +EATHREADLIB_API EA::Thread::ThreadAffinityMask parseSet(char const* buffer, size_t len) +{ + EA::Thread::ThreadAffinityMask mask = 0; + if (buffer == nullptr || len == 0) + return mask; + + char const* ptr = buffer; + char const* end_ptr = buffer + len; + if (!isUint(ptr)) + return mask; + + uint32_t prevVal = strtoul(ptr, (char**)&ptr, 10); + uint32_t val = 0; + + mask |= 1ULL << prevVal; + + char op; + while (ptr < end_ptr) + { + op = *ptr; + ++ptr; + switch (op) + { + case '-': + + if (!isUint(ptr)) + return mask; + val = strtoul(ptr, (char**)&ptr, 10); + for (int i = prevVal + 1; i <= val; ++i) + { + mask |= 1ULL << i; + } + break; + case ',': + if (!isUint(ptr)) + return mask; + prevVal = strtoul(ptr, (char**)&ptr, 10); + mask |= 1ULL << prevVal; + break; + default: + EAT_ASSERT_MSG(isspace(op), "Invalid op token."); + break; + } + } + return mask; +} + +EATHREADLIB_API bool readLine(const char* fileName, char* buffer, size_t len) +{ + if (fileName == nullptr || buffer == nullptr || len == 0) + return false; + + memset(buffer, 0, len); + + FILE* fp = fopen(fileName, "r"); + if (fp == nullptr) + return false; + + bool ret = fgets(buffer, len, fp) != nullptr; + fclose(fp); + if (ret) + { + size_t slen = strlen(buffer); + if (buffer[slen - 1] == '\n') + buffer[slen - 1] = '\0'; + } + + return ret; +} + +EATHREADLIB_API bool getProcessCpuSetCGroup(char* cgroup, size_t len) +{ + return readLine("/proc/self/cpuset", cgroup, len); +} + +EATHREADLIB_API EA::Thread::ThreadAffinityMask getInstanceCpuSet() +{ + static const char* instanceFileName = "/sys/devices/system/cpu/present"; + + EA::Thread::ThreadAffinityMask mask = 0; + char buf[256]; + if (!readLine(instanceFileName, buf, sizeof(buf))) + return mask; + + return parseSet(buf, strlen(buf)); +} + +EATHREADLIB_API EA::Thread::ThreadAffinityMask initValidCpuAffinityMask() +{ + char cgroup[32]; + if (!getProcessCpuSetCGroup(cgroup, sizeof(cgroup))) + { + return getInstanceCpuSet(); + } + char cpuSetFileName[256]; + sprintf(cpuSetFileName, "/sys/fs/cgroup/cpuset%s/cpuset.cpus", cgroup); + + char buf[256]; + if (!readLine(cpuSetFileName, buf, sizeof(buf))) + return getInstanceCpuSet(); + + return parseSet(buf, strlen(buf)); +} + +EATHREADLIB_API EA::Thread::ThreadAffinityMask getValidCpuAffinityMask() +{ + static EA::Thread::ThreadAffinityMask mask = initValidCpuAffinityMask(); + return mask; +} + +EATHREADLIB_API int initAvailableCpuCount() +{ + EA::Thread::ThreadAffinityMask mask = getValidCpuAffinityMask(); + + int count = 1 & mask; + EAT_ASSERT_MSG(count, "First available cpu is not 0"); + bool prevVal = count; + + for (int i = 1; i < sizeof(mask); ++i) + { + bool val = (1 << i) & mask; + EAT_ASSERT_MSG(!val || prevVal, "Non-contiguous range of available cpus"); + count += val; + prevVal = val; + } + + return count; +} + +EATHREADLIB_API int getAvailableCpuCount() +{ + static int count = initAvailableCpuCount(); + return count; +} + +#endif diff --git a/src/thirdparty/ea/EAThread/source/unix/eathread_thread_unix.cpp b/src/thirdparty/ea/EAThread/source/unix/eathread_thread_unix.cpp new file mode 100644 index 00000000..de4c162d --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/unix/eathread_thread_unix.cpp @@ -0,0 +1,1157 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include +#include +#include "eathread/internal/eathread_global.h" + +#if defined(EA_PLATFORM_NX) + #include // used by gDeadThreadsFutex +#endif + +#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + #include + #include + #include + #include + #include + #if defined(EA_PLATFORM_WINDOWS) + EA_DISABLE_ALL_VC_WARNINGS() + #include // Presumably we are using pthreads-win32. + EA_RESTORE_ALL_VC_WARNINGS() + #elif defined(EA_PLATFORM_LINUX) + #include + #include + #include + #include + #elif defined(EA_PLATFORM_APPLE) + #include + #include + #elif defined(EA_PLATFORM_BSD) || defined(EA_PLATFORM_CONSOLE_BSD) || defined(EA_PLATFORM_FREEBSD) + #include + #endif + + #if defined(EA_PLATFORM_LINUX) + #define EA_ALLOW_POSIX_THREADS_PRIORITIES 0 + #else + #define EA_ALLOW_POSIX_THREADS_PRIORITIES 1 + #endif + + #ifdef EA_PLATFORM_ANDROID + #include + #include "../android/com_ea_EAThread_EAThread.h" + #endif + +namespace +{ +#ifdef EA_PLATFORM_ANDROID + void SetCurrentThreadNameJava(JNIEnv* env, const char* name); +#endif + + // We convert a an EAThread priority (higher value implies higher priority) to a native priority + // value, as some implementations of pthreads use lower values to indicate higher priority. + void ConvertToNativePriority(int eathreadPriority, sched_param& param, int& policy) + { + using namespace EA::Thread; + + #if defined(EA_PLATFORM_WINDOWS) + param.sched_priority = THREAD_PRIORITY_NORMAL + (nPriority - kThreadPriorityDefault); + + #elif defined(EA_PLATFORM_LINUX) && !defined(EA_PLATFORM_CYGWIN) + // We are assuming Kernel 2.6 and later behaviour, but perhaps we should dynamically detect. + // Linux supports three scheduling policies SCHED_OTHER, SCHED_RR, and SCHED_FIFO. + // The process needs to be run with superuser privileges to use SCHED_RR or SCHED_FIFO. + // Thread priorities for SCHED_OTHER do not exist; there is only one allowed thread priority: 0. + // Thread priorities for SCHED_RR and SCHED_FIFO are limited to the range of [1, 99] (verified with Linux 2.6.17), + // despite documentation on the Internet that refers to ranges of 0-99, 1-100, 1-140, etc. + // Higher values in this range mean higher priority. + // All of the SCHED_RR and SCHED_FIFO privileges are higher than anything running at SCHED_OTHER, + // as they are considered to be real-time scheduling. A result of this is that there is no + // such thing as having a thread of lower priority than normal; there are only higher real-time priorities. + policy = 0; + + #if EA_ALLOW_POSIX_THREADS_PRIORITIES + if(eathreadPriority <= kThreadPriorityDefault) + #endif + { + #if defined(SCHED_OTHER) + policy = SCHED_OTHER; + #endif + param.sched_priority = 0; + } + #if EA_ALLOW_POSIX_THREADS_PRIORITIES + else + { + #if defined(SCHED_RR) + policy = SCHED_RR; + #endif + param.sched_priority = (eathreadPriority - kThreadPriorityDefault); + } + #else + EA_UNUSED(eathreadPriority); + #endif + + #else + #if defined(EA_PLATFORM_CYGWIN) + policy = SCHED_OTHER; + #else + policy = SCHED_FIFO; + #endif + + int nMin = sched_get_priority_min(policy); + int nMax = sched_get_priority_max(policy); + int adjustDir = 1; + + // Some implementations of Pthreads associate higher priorities with smaller + // integer values. We hide this. To the user, a higher value must always + // indicate higher priority. + if(nMin > nMax) + { + adjustDir = nMax; + nMax = nMin; + nMin = adjustDir; + adjustDir = -1; // Translate user's desire for higher priority to lower value + } + + // native_priority = EAThread_native_priority_default +/- EAThread_user_priority + // This calculation sets the default to be in the middle of low and high, which might not be so for all platforms in practice. + param.sched_priority = ((nMin + nMax) / 2) + (adjustDir * eathreadPriority); + + if(param.sched_priority < nMin) + param.sched_priority = nMin; + else if(param.sched_priority > nMax) + param.sched_priority = nMax; + #endif + } + + + // We convert a native priority value to an EAThread priority (higher value implies higher + // priority), as some implementations of pthreads use lower values to indicate higher priority. + int ConvertFromNativePriority(const sched_param& param, int policy) + { + using namespace EA::Thread; + + #if defined(EA_PLATFORM_LINUX) && !defined(EA_PLATFORM_CYGWIN) + EA_UNUSED(policy); + return kThreadPriorityDefault + param.sched_priority; // This works for both SCHED_OTHER, SCHED_RR, and SCHED_FIFO. + #else + #if defined(EA_PLATFORM_WINDOWS) // In the case of windows, we know for sure that normal priority is defined by 'THREAD_PRIORITY_NORMAL'. + if(param.sched_priority == THREAD_PRIORITY_NORMAL) + return kThreadPriorityDefault; + #elif !(defined(EA_PLATFORM_CYGWIN) || defined(EA_PLATFORM_NX)) + if(policy == SCHED_OTHER) + return 0; // 0 is the only priority permitted with the SCHED_OTHER scheduling scheme. + #endif + + // Some implementations of Pthreads associate higher priorities with smaller + // integer values. We hide this. To the user, a higher value must always + // indicate higher priority. + + // EAThread_user_priority = +/-(native_priority - EAThread_native_priority_default) + const int nMin = sched_get_priority_min(policy); + const int nMax = sched_get_priority_max(policy); + const int nativeBasePriority = (nMin + nMax) / 2; + const int adjustDir = (nMin < nMax) ? 1 : -1; + + return adjustDir * (param.sched_priority - nativeBasePriority); + #endif + } + + + // Setup stack and/or priority of a new thread + void SetupThreadAttributes(pthread_attr_t& creationAttribs, const EA::Thread::ThreadParameters* pTP) + { + int result = 0; + EA_UNUSED( result ); //only used for assertions + + // We create the thread as attached, and we'll call either pthread_join or pthread_detach, + // depending on whether WaitForEnd (pthread_join) is called or not (pthread_detach). + + if(pTP) + { + // Set thread stack address and/or size + if(pTP->mpStack) + { + EAT_ASSERT(pTP->mnStackSize != 0); + + #if !defined(EA_PLATFORM_CYGWIN) // Some implementations of pthreads does not support pthread_attr_setstack. + #if defined(EA_PLATFORM_NX) + EAT_ASSERT((pTP->mnStackSize & 4095) == 0); // stack size has to be 4k aligned on NX. + EAT_ASSERT(((uintptr_t)pTP->mpStack & 4095) == 0); // stack has to be 4k aligned on NX. + #endif + #if defined(PTHREAD_STACK_MIN) + EAT_ASSERT((pTP->mnStackSize >= PTHREAD_STACK_MIN)); + #endif + result = pthread_attr_setstack(&creationAttribs, (void*)pTP->mpStack, pTP->mnStackSize); + EAT_ASSERT(result == 0); + #endif + } + else if(pTP->mnStackSize) + { + #if defined(EA_PLATFORM_NX) + EAT_ASSERT((pTP->mnStackSize & 4095) == 0); // stack size has to be 4k aligned. + #endif + #if defined(PTHREAD_STACK_MIN) + EAT_ASSERT((pTP->mnStackSize >= PTHREAD_STACK_MIN)); + #endif + result = pthread_attr_setstacksize(&creationAttribs, pTP->mnStackSize); + EAT_ASSERT(result == 0); + } + + // Set initial non-zero priority + // Even if pTP->mnPriority == kThreadPriorityDefault, we need to run this on some platforms, as the thread priority for new threads on them isn't the same as the thread priority for the main thread. + int policy = SCHED_OTHER; + sched_param param; + + // initialize all fields of param before we start tinkering with them + result = pthread_attr_getschedparam(&creationAttribs, ¶m); + EAT_ASSERT(result == 0); + ConvertToNativePriority(pTP->mnPriority, param, policy); + result = pthread_attr_setschedpolicy(&creationAttribs, policy); + EAT_ASSERT(result == 0); + result = pthread_attr_setschedparam(&creationAttribs, ¶m); + EAT_ASSERT(result == 0); + + // Unix doesn't let you specify thread CPU affinity via pthread attributes. + // Instead you need to call sched_setaffinity or pthread_setaffinity_np. + } + } + + static void SetPlatformThreadAffinity(EAThreadDynamicData* pTDD) + { + if(pTDD->mThreadId != EA::Thread::kThreadIdInvalid) // If the thread has been created... + { + #if defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_NX) + EAT_ASSERT(pTDD && (pTDD->mThreadId != EA::Thread::kThreadIdInvalid)); + + #if defined(EA_PLATFORM_ANDROID) // Android doesn't provide pthread_setaffinity_np. + if(pTDD->mThreadPid != 0) // If the running thread has assigned its pid yet (it does so right after starting)... + { + int processor = (1 << pTDD->mStartupProcessor); + syscall(__NR_sched_setaffinity, pTDD->mThreadPid, sizeof(processor), &processor); + } + // Else wait till the thread has started and let it set its own affinity. + #else + cpu_set_t cpus; + CPU_ZERO(&cpus); + + EAT_ASSERT_MSG(((1 << pTDD->mStartupProcessor) & EA::Thread::GetAvailableCpuAffinityMask()) != 0, "Processor not available!"); + + CPU_SET(pTDD->mStartupProcessor, &cpus); + + int result = pthread_setaffinity_np(pTDD->mThreadId, sizeof(cpus), &cpus); + EAT_ASSERT_MSG(result == 0, "pthread_setaffinity_np: failure"); + EA_UNUSED(result); + #endif + #endif + } + // Else the thread hasn't started yet, or has already exited. Let the thread set its own + // affinity when it starts. + } + +#ifdef EA_PLATFORM_ANDROID + static JavaVM* gJavaVM = NULL; + static jclass gEAThreadClass = NULL; + static jmethodID gSetNameMethodId = NULL; + + // This function needs to be called by Java code on app startup, before + // the C main entrypoint is called, or very shortly thereafter. It's called + // via Java with EAThread.Init(); If your Java code doesn't call EAThread.Init + // then any C++ calls to Java code will fail (though those C++ calls could + // manually set the JNIEnv per call). + extern "C" __attribute__ ((visibility("default"))) JNIEXPORT void JNICALL Java_com_ea_EAThread_EAThread_Init(JNIEnv* env, jclass eaThreadClass) + { + gEAThreadClass = (jclass)env->NewGlobalRef(eaThreadClass); + int getJavaVMResult = env->GetJavaVM(&gJavaVM); + EA_UNUSED(getJavaVMResult); + EAT_ASSERT_MSG(getJavaVMResult == 0, "Unable to get the Java VM from the JNI environment."); + gSetNameMethodId = env->GetStaticMethodID(gEAThreadClass, "setCurrentThreadName", "(Ljava/lang/String;)V"); + EAT_ASSERT(gSetNameMethodId); + } + + // This is called just after creation of a C/C++ thread. + // The Java JNI requires you to do this, else bad things will happen. + static JNIEnv* AttachJavaThread() + { + if(gJavaVM) + { + JNIEnv* env; + jint resultCode = gJavaVM->AttachCurrentThread(&env, NULL); + (void)resultCode; + EAT_ASSERT_MSG(resultCode == 0, "Unable to attach thread"); + + return env; + } + + return NULL; + } + + // This is called just before destruction of a C/C++ thread. + // It is the complement of AttachJavaThread. + static void DetachJavaThread() + { + if(gJavaVM) + gJavaVM->DetachCurrentThread(); + } + + void SetCurrentThreadNameJava(JNIEnv* env, const char* name) + { + if(gJavaVM) + { + jstring threadName = env->NewStringUTF(name); + env->CallStaticVoidMethod(gEAThreadClass, gSetNameMethodId, threadName); + env->DeleteLocalRef(threadName); + + EAT_ASSERT(env->ExceptionOccurred() == NULL); + if (env->ExceptionOccurred() != NULL) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + } + } +#endif + +} // namespace + +namespace EA +{ + namespace Thread + { + extern Allocator* gpAllocator; + + const size_t kMaxThreadDynamicDataCount = 128; + + struct EAThreadGlobalVars + { + EA_PREFIX_ALIGN(8) + char gThreadDynamicData[kMaxThreadDynamicDataCount][sizeof(EAThreadDynamicData)] EA_POSTFIX_ALIGN(8); + AtomicInt32 gThreadDynamicDataAllocated[kMaxThreadDynamicDataCount]; + Mutex gThreadDynamicMutex; + }; + EATHREAD_GLOBALVARS_CREATE_INSTANCE; + + EAThreadDynamicData* AllocateThreadDynamicData() + { + for(size_t i(0); i < kMaxThreadDynamicDataCount; i++) + { + if(EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[i].SetValueConditional(1, 0)) + return (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + } + + // This is a safety fallback mechanism. In practice it won't be used in almost all situations. + if(gpAllocator) + return (EAThreadDynamicData*)gpAllocator->Alloc(sizeof(EAThreadDynamicData)); + else + return (EAThreadDynamicData*)new char[sizeof(EAThreadDynamicData)]; // We assume the returned alignment is sufficient. + } + + void FreeThreadDynamicData(EAThreadDynamicData* pEAThreadDynamicData) + { + if((pEAThreadDynamicData >= (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData) && (pEAThreadDynamicData < ((EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData + kMaxThreadDynamicDataCount))) + { + pEAThreadDynamicData->~EAThreadDynamicData(); + EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[pEAThreadDynamicData - (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData].SetValue(0); + } + else + { + // Assume the data was allocated via the fallback mechanism. + pEAThreadDynamicData->~EAThreadDynamicData(); + if(gpAllocator) + gpAllocator->Free(pEAThreadDynamicData); + else + delete[] (char*)pEAThreadDynamicData; + } + } + + // This is a public function. + EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId) + { + for(size_t i(0); i < kMaxThreadDynamicDataCount; i++) + { + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + + if(pTDD->mThreadId == threadId) + return pTDD; + } + + return NULL; // This is no practical way we can find the data unless thread-specific storage was involved. + } + + #if defined(EA_PLATFORM_APPLE) + EAThreadDynamicData* FindThreadDynamicData(EA::Thread::SysThreadId sysThreadId) + { + for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i) + { + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i]; + if (pTDD->mSysThreadId == sysThreadId) + return pTDD; + } + return NULL; // This is no practical way we can find the data unless thread-specific storage was involved. + } + #endif + +#if defined(EA_PLATFORM_NX) + // Nintendo doesn't properly support pthread_detach, so we need to use pthread_join instead. + // Since a thread is not allowed to join itself (and joining a not-yet-dead thread is not ideal) + // we queue up a couple of threadIds and have later threads to die, join the earlier ones. + + static ThreadId sDeadThreadIds[] = + { + EA::Thread::kThreadIdInvalid, // 0 + EA::Thread::kThreadIdInvalid, // 1 + EA::Thread::kThreadIdInvalid, // 2 + EA::Thread::kThreadIdInvalid, // 3 + }; + Futex gDeadThreadsFutex; + + static void NX_PushDeadThreadId(ThreadId threadId) + { + ThreadId tempId; + + { + AutoFutex _(gDeadThreadsFutex); + + // grab oldest entry, from end of array + tempId = sDeadThreadIds[EAArrayCount(sDeadThreadIds) - 1]; + + // shift array contents forward + memmove(sDeadThreadIds + 1, sDeadThreadIds, sizeof(sDeadThreadIds) - sizeof(sDeadThreadIds[0])); + + sDeadThreadIds[0] = threadId; + } + + if (tempId != EA::Thread::kThreadIdInvalid) + pthread_join(tempId, NULL); + } + + void NX_CleanupDeadThreads() + { + ThreadId tempIds[EAArrayCount(sDeadThreadIds)]; + + { + AutoFutex _(gDeadThreadsFutex); + for (int i = 0; i < EAArrayCount(sDeadThreadIds); ++i) + { + tempIds[i] = sDeadThreadIds[i]; + sDeadThreadIds[i] = EA::Thread::kThreadIdInvalid; + } + } + + for (int i = 0; i < EAArrayCount(tempIds); ++i) + { + if (tempIds[i] != EA::Thread::kThreadIdInvalid) + pthread_join(tempIds[i], NULL); + } + } +#endif // defined(EA_PLATFORM_NX) + } +} + + +EAThreadDynamicData::EAThreadDynamicData() + : mThreadId(EA::Thread::kThreadIdInvalid), + mSysThreadId(0), + mThreadPid(0), + mnStatus(EA::Thread::Thread::kStatusNone), + mnReturnValue(0), + //mpStartContext[], + mpBeginThreadUserWrapper(NULL), + mnRefCount(0), + //mName[], + mStartupProcessor(EA::Thread::kProcessorDefault), + mnThreadAffinityMask(EA::Thread::kThreadAffinityMaskAny), + mRunMutex(), + mStartedSemaphore() +{ + memset(mpStartContext, 0, sizeof(mpStartContext)); + memset(mName, 0, sizeof(mName)); +} + + +EAThreadDynamicData::~EAThreadDynamicData() +{ + if (mThreadId != EA::Thread::kThreadIdInvalid) + { + #if defined(EA_PLATFORM_NX) + EA::Thread::NX_PushDeadThreadId(mThreadId); + #else + pthread_detach(mThreadId); + #endif + } + + mThreadId = EA::Thread::kThreadIdInvalid; + mThreadPid = 0; + mSysThreadId = 0; +} + + +void EAThreadDynamicData::AddRef() +{ + mnRefCount.Increment(); // Note that mnRefCount is an AtomicInt32. +} + + +void EAThreadDynamicData::Release() +{ + if(mnRefCount.Decrement() == 0) // Note that mnRefCount is an AtomicInt32. + EA::Thread::FreeThreadDynamicData(this); +} + + +EA::Thread::ThreadParameters::ThreadParameters() + : mpStack(NULL), + mnStackSize(0), + #ifdef EA_PLATFORM_NX + mnPriority(kThreadPriorityMin), + #else + mnPriority(kThreadPriorityDefault), + #endif + mnProcessor(kProcessorDefault), + mpName(""), + mnAffinityMask(kThreadAffinityMaskAny), + mbDisablePriorityBoost(false) +{ + // Empty +} + + +EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::sGlobalRunnableFunctionUserWrapper = NULL; +EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::sGlobalRunnableClassUserWrapper = NULL; +EA::Thread::AtomicInt32 EA::Thread::Thread::sDefaultProcessor = kProcessorAny; +EA::Thread::AtomicUint64 EA::Thread::Thread::sDefaultProcessorMask = UINT64_C(0xffffffffffffffff); + + +EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::GetGlobalRunnableFunctionUserWrapper() +{ + return sGlobalRunnableFunctionUserWrapper; +} + + +void EA::Thread::Thread::SetGlobalRunnableFunctionUserWrapper(EA::Thread::RunnableFunctionUserWrapper pUserWrapper) +{ + if(sGlobalRunnableFunctionUserWrapper) + EAT_FAIL_MSG("Thread::SetGlobalRunnableFunctionUserWrapper already set."); // Can only be set once for the application. + else + sGlobalRunnableFunctionUserWrapper = pUserWrapper; +} + + +EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::GetGlobalRunnableClassUserWrapper() +{ + return sGlobalRunnableClassUserWrapper; +} + + +void EA::Thread::Thread::SetGlobalRunnableClassUserWrapper(EA::Thread::RunnableClassUserWrapper pUserWrapper) +{ + if(sGlobalRunnableClassUserWrapper) + EAT_FAIL_MSG("EAThread::SetGlobalRunnableClassUserWrapper already set."); // Can only be set once for the application. + else + sGlobalRunnableClassUserWrapper = pUserWrapper; +} + + +EA::Thread::Thread::Thread() +{ + mThreadData.mpData = NULL; +} + + +EA::Thread::Thread::Thread(const Thread& t) + : mThreadData(t.mThreadData) +{ + if(mThreadData.mpData) + mThreadData.mpData->AddRef(); +} + + +EA::Thread::Thread& EA::Thread::Thread::operator=(const Thread& t) +{ + // We don't synchronize access to mpData; we assume that the user + // synchronizes it or this Thread instances is used from a single thread. + if(t.mThreadData.mpData) + t.mThreadData.mpData->AddRef(); + + if(mThreadData.mpData) + mThreadData.mpData->Release(); + + mThreadData = t.mThreadData; + + return *this; +} + + +EA::Thread::Thread::~Thread() +{ + // We don't synchronize access to mpData; we assume that the user + // synchronizes it or this Thread instances is used from a single thread. + if(mThreadData.mpData) + mThreadData.mpData->Release(); +} + + +static void* RunnableFunctionInternal(void* pContext) +{ + // The parent thread is sharing memory with us and we need to + // make sure our view of it is synchronized with the parent. + EAReadWriteBarrier(); + + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)pContext; + EA::Thread::RunnableFunction pFunction = (EA::Thread::RunnableFunction)pTDD->mpStartContext[0]; + void* pCallContext = pTDD->mpStartContext[1]; + + #if defined(EA_PLATFORM_LINUX) && defined(__NR_gettid) + // Unfortunately there's no reliable way to translate a pthread_t to a + // thread pid value. Thus we can know the thread's pid only via the + // thread itself calling gettid(). + + //pTDD->mThreadPid = gettid(); // Some Linux compiler distributions declare gettid(), some don't. + pTDD->mThreadPid = (pid_t)syscall(__NR_gettid); // It's safest to just make the syscall directly. + + if(pTDD->mStartupProcessor != EA::Thread::kProcessorDefault && pTDD->mStartupProcessor != EA::Thread::kProcessorAny) + SetPlatformThreadAffinity(pTDD); + else if(pTDD->mStartupProcessor == EA::Thread::kProcessorAny) + EA::Thread::SetThreadAffinityMask(pTDD->mnThreadAffinityMask); + #elif defined(EA_PLATFORM_NX) + // We do this to emulate the behavior of other consoles that default create thread + // with an affinity capable of floating across all available cores. + if(pTDD->mStartupProcessor == EA::Thread::kProcessorDefault) + EA::Thread::SetThreadAffinityMask(EA::Thread::kThreadAffinityMaskAny); + + #elif !defined(EA_PLATFORM_CONSOLE) && !defined(EA_PLATFORM_MOBILE) + pTDD->mThreadPid = getpid(); // We can't set a thread affinity with a process id. + #else + pTDD->mThreadPid = 0; + #endif + + // Lock the runtime mutex which is used to allow other threads to wait on this thread with a timeout. + pTDD->mRunMutex.Lock(); // Important that this be before the semaphore post. + pTDD->mStartedSemaphore.Post(); // Announce that the thread has started. + pTDD->mnStatus = EA::Thread::Thread::kStatusRunning; + pTDD->mpStackBase = EA::Thread::GetStackBase(); + +#ifdef EA_PLATFORM_ANDROID + + JNIEnv* jni = AttachJavaThread(); + if(pTDD->mName[0]) + SetCurrentThreadNameJava(jni, pTDD->mName); +#elif !EATHREAD_OTHER_THREAD_NAMING_SUPPORTED + // Under Unix we need to set the thread name from the thread that is being named and not from an outside thread. + if(pTDD->mName[0]) + EA::Thread::SetThreadName(pTDD->mThreadId, pTDD->mName); +#endif + + if(pTDD->mpBeginThreadUserWrapper) + { + // If user wrapper is specified, call user wrapper and pass the pFunction and pContext. + EA::Thread::RunnableFunctionUserWrapper pWrapperFunction = (EA::Thread::RunnableFunctionUserWrapper)pTDD->mpBeginThreadUserWrapper; + pTDD->mnReturnValue = pWrapperFunction(pFunction, pCallContext); + } + else + pTDD->mnReturnValue = pFunction(pCallContext); + + #ifdef EA_PLATFORM_ANDROID + DetachJavaThread(); + #endif + + void* pReturnValue = (void*)pTDD->mnReturnValue; + pTDD->mnStatus = EA::Thread::Thread::kStatusEnded; + pTDD->mRunMutex.Unlock(); + pTDD->Release(); + + return pReturnValue; +} + + +static void* RunnableObjectInternal(void* pContext) +{ + EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)pContext; + EA::Thread::IRunnable* pRunnable = (EA::Thread::IRunnable*)pTDD->mpStartContext[0]; + void* pCallContext = pTDD->mpStartContext[1]; + + #if defined(EA_PLATFORM_LINUX) && defined(__NR_gettid) + // Unfortunately there's no reliable way to translate a pthread_t to a + // thread pid value. Thus we can know the thread's pid only via the + // thread itself calling gettid(). + + //pTDD->mThreadPid = gettid(); // Some Linux compiler distributions declare gettid(), some don't. + pTDD->mThreadPid = (pid_t)syscall(__NR_gettid); // It's safest to just make the syscall directly. + + if(pTDD->mStartupProcessor != EA::Thread::kProcessorDefault && pTDD->mStartupProcessor != EA::Thread::kProcessorAny) + SetPlatformThreadAffinity(pTDD); + else if(pTDD->mStartupProcessor == EA::Thread::kProcessorAny) + EA::Thread::SetThreadAffinityMask(pTDD->mnThreadAffinityMask); + + #elif !defined(EA_PLATFORM_CONSOLE) && !defined(EA_PLATFORM_MOBILE) + pTDD->mThreadPid = getpid(); // We can't set a thread affinity with a process id. + #else + pTDD->mThreadPid = 0; + #endif + + pTDD->mRunMutex.Lock(); // Important that this be before the semaphore post. + pTDD->mStartedSemaphore.Post(); + + pTDD->mnStatus = EA::Thread::Thread::kStatusRunning; + +#ifdef EA_PLATFORM_ANDROID + JNIEnv* jni = AttachJavaThread(); + if(pTDD->mName[0]) + SetCurrentThreadNameJava(jni, pTDD->mName); +#elif !EATHREAD_OTHER_THREAD_NAMING_SUPPORTED + // Under Unix we need to set the thread name from the thread that is being named and not from an outside thread. + if(pTDD->mName[0]) + EA::Thread::SetThreadName(pTDD->mThreadId, pTDD->mName); +#endif + + if(pTDD->mpBeginThreadUserWrapper) + { + // If user wrapper is specified, call user wrapper and pass the pFunction and pContext. + EA::Thread::RunnableClassUserWrapper pWrapperClass = (EA::Thread::RunnableClassUserWrapper)pTDD->mpBeginThreadUserWrapper; + pTDD->mnReturnValue = pWrapperClass(pRunnable, pCallContext); + } + else + pTDD->mnReturnValue = pRunnable->Run(pCallContext); + + #ifdef EA_PLATFORM_ANDROID + DetachJavaThread(); + #endif + + void* const pReturnValue = (void*)pTDD->mnReturnValue; + pTDD->mnStatus = EA::Thread::Thread::kStatusEnded; + pTDD->mRunMutex.Unlock(); + pTDD->Release(); + + return pReturnValue; +} + + +/// BeginThreadInternal +/// Extraction of both RunnableFunction and RunnableObject EA::Thread::Begin in order to have thread initialization +/// in one place +static EA::Thread::ThreadId BeginThreadInternal(EAThreadData& mThreadData, void* pRunnableOrFunction, void* pContext, const EA::Thread::ThreadParameters* pTP, + void* pUserWrapper, void* (*InternalThreadFunction)(void*)) +{ + using namespace EA::Thread; + + // The parent thread is sharing memory with us and we need to + // make sure our view of it is synchronized with the parent. + EAReadWriteBarrier(); + + // Check there is an entry for the current thread context in our ThreadDynamicData array. + EA::Thread::ThreadId thisThreadId = EA::Thread::GetThreadId(); + if(!FindThreadDynamicData(thisThreadId)) + { + EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData; + if(pData) + { + pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread. + // Do no AddRef for thread execution because this is not an EAThread managed thread. + pData->AddRef(); // AddRef for this function, to be released upon this function's exit. + pData->mThreadId = thisThreadId; + pData->mSysThreadId = GetSysThreadId(); + pData->mThreadPid = 0; + strncpy(pData->mName, "external", EATHREAD_NAME_SIZE); + pData->mName[EATHREAD_NAME_SIZE - 1] = 0; + pData->mpStackBase = EA::Thread::GetStackBase(); + } + } + + if(mThreadData.mpData) + mThreadData.mpData->Release(); // Matches the "AddRef for ourselves" below. + + // We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be + // modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed + // during execution. + EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData; // Note that we use a special new here which doesn't use the heap. + EAT_ASSERT(pData); + + if(pData) + { + mThreadData.mpData = pData; + + pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread. + pData->AddRef(); // AddRef for the thread, to be released upon the thread exiting. + pData->AddRef(); // AddRef for this function, to be released upon this function's exit. + pData->mThreadId = kThreadIdInvalid; + pData->mSysThreadId = 0; + pData->mThreadPid = 0; + pData->mnStatus = Thread::kStatusNone; + pData->mpStartContext[0] = pRunnableOrFunction; + pData->mpStartContext[1] = pContext; + pData->mpBeginThreadUserWrapper = pUserWrapper; + pData->mStartupProcessor = pTP ? pTP->mnProcessor % EA::Thread::GetProcessorCount() : kProcessorDefault; + pData->mnThreadAffinityMask = pTP ? pTP->mnAffinityMask : kThreadAffinityMaskAny; + if(pTP && pTP->mpName) + strncpy(pData->mName, pTP->mpName, EATHREAD_NAME_SIZE); + pData->mName[EATHREAD_NAME_SIZE - 1] = 0; + + // Pass NULL attribute pointer if there are no special setup steps + pthread_attr_t* pCreationAttribs = NULL; + int result(0); + + pthread_attr_t creationAttribs; + + // SetupThreadAttributes is always called in order to create threads as detached + pthread_attr_init(&creationAttribs); + + #ifndef EA_PLATFORM_ANDROID + // Posix has stated that we should call pthread_attr_setinheritsched, otherwise the + // thread priority set up in pthread_attr_t gets ignored by the newly created thread. + pthread_attr_setinheritsched(&creationAttribs, PTHREAD_EXPLICIT_SCHED); + #endif + + SetupThreadAttributes(creationAttribs, pTP); + pCreationAttribs = &creationAttribs; + + result = pthread_create(&pData->mThreadId, pCreationAttribs, InternalThreadFunction, pData); + + if(result == 0) // If success... + { + ThreadId threadIdTemp = pData->mThreadId; // Temp value because Release below might delete pData. + + // If additional attributes were used, free initialization data. + if(pCreationAttribs) + { + result = pthread_attr_destroy(pCreationAttribs); + EAT_ASSERT(result == 0); + } + + if(pData->mStartupProcessor != kProcessorDefault && pData->mStartupProcessor != EA::Thread::kProcessorAny) + SetPlatformThreadAffinity(pData); + else if(pData->mStartupProcessor == EA::Thread::kProcessorAny) + EA::Thread::SetThreadAffinityMask(pData->mThreadId, pData->mnThreadAffinityMask); + + + pData->Release(); // Matches AddRef for this function. + return threadIdTemp; + } + + // If additional attributes were used, free initialization data + if(pCreationAttribs) + { + result = pthread_attr_destroy(pCreationAttribs); + EAT_ASSERT(result == 0); + } + + pData->Release(); // Matches AddRef for "cleanup" above. + pData->Release(); // Matches AddRef for this Thread class above. + pData->Release(); // Matches AddRef for thread above. + mThreadData.mpData = NULL; // mThreadData.mpData == pData + } + + return (ThreadId)kThreadIdInvalid; +} + + +EA::Thread::ThreadId EA::Thread::Thread::Begin(RunnableFunction pFunction, void* pContext, const ThreadParameters* pTP, RunnableFunctionUserWrapper pUserWrapper) +{ + ThreadId threadId = BeginThreadInternal(mThreadData, reinterpret_cast((uintptr_t)pFunction), pContext, pTP, reinterpret_cast((uintptr_t)pUserWrapper), RunnableFunctionInternal); + + if(pTP && pTP->mnProcessor == EA::Thread::kProcessorAny) + EA::Thread::Thread::SetAffinityMask(pTP->mnAffinityMask); + + if(pTP && pTP->mpName) + SetName(pTP->mpName); + + return threadId; +} + + +EA::Thread::ThreadId EA::Thread::Thread::Begin(IRunnable* pRunnable, void* pContext, const ThreadParameters* pTP, RunnableClassUserWrapper pUserWrapper) +{ + ThreadId threadId = BeginThreadInternal(mThreadData, reinterpret_cast((uintptr_t)pRunnable), pContext, pTP, reinterpret_cast((uintptr_t)pUserWrapper), RunnableObjectInternal); + + if(pTP && pTP->mnProcessor == EA::Thread::kProcessorAny) + EA::Thread::Thread::SetAffinityMask(pTP->mnAffinityMask); + + if(pTP && pTP->mpName) + SetName(pTP->mpName); + + return threadId; +} + + +EA::Thread::Thread::Status EA::Thread::Thread::WaitForEnd(const ThreadTime& timeoutAbsolute, intptr_t* pThreadReturnValue) +{ + // In order to support timeoutAbsolute, we don't just call pthread_join, as that's an infinitely blocking call. + // Instead we wait on a Mutex (with a timeout) which the running thread locked, and will unlock as it is exiting. + // Only after the successful Mutex lock do we call pthread_join, as we know that it won't block for an indeterminate + // amount of time (barring a thread priority inversion problem). If the user never calls WaitForEnd, then we + // will eventually call pthread_detach in the EAThreadDynamicData destructor. + + // The mThreadData memory is shared between threads and when + // reading it we must be synchronized. + EAReadWriteBarrier(); + + // A mutex lock around mpData is not needed below because mpData is never allowed to go from non-NULL to NULL. + // However, there is an argument that can be made for placing a memory read barrier before reading it. + + if(mThreadData.mpData) // If this is non-zero then we must have created the thread. + { + EAT_ASSERT(mThreadData.mpData->mThreadId != kThreadIdInvalid); // WaitForEnd can't be called on a thread that hasn't been started + + // We must not call WaitForEnd from the thread we are waiting to end. + // That would result in a deadlock, at least if the timeout was infinite. + EAT_ASSERT(mThreadData.mpData->mThreadId != EA::Thread::GetThreadId()); + + Status currentStatus = GetStatus(); + + if(currentStatus == kStatusNone) // If the thread hasn't started yet... + { + // The thread has not been started yet. Wait on the semaphore (which is posted when the thread actually starts executing). + Semaphore::Result result = (Semaphore::Result)mThreadData.mpData->mStartedSemaphore.Wait(timeoutAbsolute); + EAT_ASSERT(result != Semaphore::kResultError); + + if(result >= 0) // If the Wait succeeded, as opposed to timing out... + { + // We know for sure that the thread status is running now. + currentStatus = kStatusRunning; + mThreadData.mpData->mStartedSemaphore.Post(); // Re-post the semaphore so that any other callers of WaitForEnd don't block on the Wait above. + } + } // fall through. + + if(currentStatus == kStatusRunning) // If the thread has started but not yet exited... + { + // Lock on the mutex (which is available when the thread is exiting) + Mutex::Result result = (Mutex::Result)mThreadData.mpData->mRunMutex.Lock(timeoutAbsolute); + EAT_ASSERT(result != Mutex::kResultError); + + if(result > 0) // If the Lock succeeded, as opposed to timing out... then the thread has exited or is in the process of exiting. + { + // Do a pthread join. This is a blocking call, but we know that it will end very soon, + // as the mutex unlock the thread did is done right before the thread returns to the OS. + // The return value of pthread_join has information that isn't currently useful to us. + pthread_join(mThreadData.mpData->mThreadId, NULL); + mThreadData.mpData->mThreadId = kThreadIdInvalid; + + // We know for sure that the thread status is ended now. + currentStatus = kStatusEnded; + mThreadData.mpData->mRunMutex.Unlock(); + } + // Else the Lock timed out, which means that the thread didn't exit before we ran out of time. + // In this case we need to return to the user that the status is kStatusRunning. + } + else + { + // Else currentStatus == kStatusEnded. + pthread_join(mThreadData.mpData->mThreadId, NULL); + mThreadData.mpData->mThreadId = kThreadIdInvalid; + } + + if(currentStatus == kStatusEnded) + { + // Call GetStatus again to get the thread return value. + currentStatus = GetStatus(pThreadReturnValue); + } + + return currentStatus; + } + else + { + // Else the user hasn't started the thread yet, so we wait until the user starts it. + // Ideally, what we really want to do here is wait for some kind of signal. + // Instead for the time being we do a polling loop. + while((!mThreadData.mpData || (mThreadData.mpData->mThreadId == kThreadIdInvalid)) && (GetThreadTime() < timeoutAbsolute)) + { + ThreadSleep(1); + EAReadWriteBarrier(); + EACompilerMemoryBarrier(); + } + + if(mThreadData.mpData) + return WaitForEnd(timeoutAbsolute); + } + + return kStatusNone; +} + + +EA::Thread::Thread::Status EA::Thread::Thread::GetStatus(intptr_t* pThreadReturnValue) const +{ + if(mThreadData.mpData) + { + EAReadBarrier(); + Status status = (Status)mThreadData.mpData->mnStatus; + + if(pThreadReturnValue && (status == kStatusEnded)) + *pThreadReturnValue = mThreadData.mpData->mnReturnValue; + + return status; + } + + return kStatusNone; +} + + +EA::Thread::ThreadId EA::Thread::Thread::GetId() const +{ + // A mutex lock around mpData is not needed below because + // mpData is never allowed to go from non-NULL to NULL. + if(mThreadData.mpData) + return mThreadData.mpData->mThreadId; + + return kThreadIdInvalid; +} + + +int EA::Thread::Thread::GetPriority() const +{ + // A mutex lock around mpData is not needed below because + // mpData is never allowed to go from non-NULL to NULL. + if(mThreadData.mpData) + { + int policy; + sched_param param; + + int result = pthread_getschedparam(mThreadData.mpData->mThreadId, &policy, ¶m); + + if(result == 0) + return ConvertFromNativePriority(param, policy); + + return kThreadPriorityDefault; + } + + return kThreadPriorityUnknown; +} + + +bool EA::Thread::Thread::SetPriority(int nPriority) +{ + // A mutex lock around mpData is not needed below because + // mpData is never allowed to go from non-NULL to NULL. + EAT_ASSERT(nPriority != kThreadPriorityUnknown); + + if(mThreadData.mpData) + { + int policy; + sched_param param; + + int result = pthread_getschedparam(mThreadData.mpData->mThreadId, &policy, ¶m); + + if(result == 0) // If success... + { + ConvertToNativePriority(nPriority, param, policy); + + result = pthread_setschedparam(mThreadData.mpData->mThreadId, policy, ¶m); + } + + return (result == 0); + } + + return false; +} + + +// To consider: Make it so we return a value. +void EA::Thread::Thread::SetProcessor(int nProcessor) +{ + #if defined(EA_PLATFORM_WINDOWS) + if(mThreadData.mpData) + { + static AtomicInt32 nProcessorCount = 0; + + if(nProcessorCount == 0) + { + SYSTEM_INFO systemInfo; + memset(&systemInfo, 0, sizeof(systemInfo)); + GetSystemInfo(&systemInfo); + nProcessorCount = (int)systemInfo.dwNumberOfProcessors; + } + + DWORD dwThreadAffinityMask; + + if(nProcessor < 0) + dwThreadAffinityMask = 0xffffffff; + else + { + if(nProcessor >= nProcessorCount) + nProcessor %= nProcessorCount; + + dwThreadAffinityMask = 1 << nProcessor; + } + + SetThreadAffinityMask(mThreadData.mpData->mThreadId, dwThreadAffinityMask); + } + #elif defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_NX) + if(mThreadData.mpData) + { + mThreadData.mpData->mStartupProcessor = nProcessor; // Assign this in case the thread hasn't started yet and thus we are leaving it a message to set it when it has started. + SetPlatformThreadAffinity(mThreadData.mpData); + } + #else + EA_UNUSED(nProcessor); + #endif +} + +void EA::Thread::Thread::SetAffinityMask(EA::Thread::ThreadAffinityMask nAffinityMask) +{ + if(mThreadData.mpData && mThreadData.mpData->mThreadId) + { + SetThreadAffinityMask(mThreadData.mpData->mThreadId, nAffinityMask); + } +} + +EA::Thread::ThreadAffinityMask EA::Thread::Thread::GetAffinityMask() +{ + if(mThreadData.mpData && mThreadData.mpData->mThreadId) + { + return mThreadData.mpData->mnThreadAffinityMask; + } + + return kThreadAffinityMaskAny; +} + +void EA::Thread::Thread::Wake() +{ + // Todo: implement this. The solution is to use a signal to wake the sleeping thread via an EINTR. + // Possibly use the SIGCONT signal. Have to look into this to tell what the best approach is. +} + +const char* EA::Thread::Thread::GetName() const +{ + if(mThreadData.mpData) + return mThreadData.mpData->mName; + + return ""; +} + +void EA::Thread::Thread::SetName(const char* pName) +{ + if(mThreadData.mpData && pName) + EA::Thread::SetThreadName(mThreadData.mpData->mThreadId, pName); +} + +#if defined(EA_PLATFORM_ANDROID) +namespace EA +{ + namespace Thread + { + void SetCurrentThreadName(JNIEnv* env, const char* pName) + { + SetCurrentThreadNameJava(env, pName); + } + } +} +#endif + + +#endif // EA_PLATFORM_XXX + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/unix/eathread_unix.cpp b/src/thirdparty/ea/EAThread/source/unix/eathread_unix.cpp new file mode 100644 index 00000000..ff1a9bec --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/unix/eathread_unix.cpp @@ -0,0 +1,813 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include + + +#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + #include + #include + #include + #include + #ifdef EA_PLATFORM_WINDOWS + EA_DISABLE_ALL_VC_WARNINGS() + #include // Presumably we are using pthreads-win32. + EA_RESTORE_ALL_VC_WARNINGS() + #include + #else + #include + #if defined(EA_HAVE_DINKUMWARE_CPP_LIBRARY) + #include + #else + #include + #endif + + #if defined(EA_PLATFORM_OSX) || defined(EA_PLATFORM_BSD) + #include + #include + #endif + + #if defined(EA_PLATFORM_LINUX) + #include + #endif + + #if defined(EA_PLATFORM_APPLE) + #include + #endif + + #if defined(EA_PLATFORM_BSD) || defined(EA_PLATFORM_CONSOLE_BSD) || defined(__FreeBSD__) + #include + #include + #endif + + #if defined(EA_PLATFORM_ANDROID) + #include + #include + #endif + + #if defined(EA_PLATFORM_NX) + #include + #endif + + #if defined(EA_PLATFORM_STADIA) + EATHREADLIB_API int getAvailableCpuCount(); + #endif + #endif + + namespace EA + { + namespace Thread + { + // Assertion variables. + EA::Thread::AssertionFailureFunction gpAssertionFailureFunction = NULL; + void* gpAssertionFailureContext = NULL; + } + } + + EA::Thread::ThreadId EA::Thread::GetThreadId() + { + return pthread_self(); + } + + EA::Thread::ThreadId EA::Thread::GetThreadId(EA::Thread::SysThreadId id) + { + EAThreadDynamicData* const pTDD = EA::Thread::FindThreadDynamicData(id); + if(pTDD) + { + return pTDD->mThreadId; + } + + return EA::Thread::kThreadIdInvalid; + } + + int EA::Thread::GetThreadPriority() + { + int policy; + sched_param param; + ThreadId currentThreadId = pthread_self(); + + int result = pthread_getschedparam(currentThreadId, &policy, ¶m); + + if(result == 0) + { + #if defined(EA_PLATFORM_LINUX) && !defined(EA_PLATFORM_CYGWIN) + return kThreadPriorityDefault + param.sched_priority; // This works for both SCHED_OTHER, SCHED_RR, and SCHED_FIFO. + #else + #if defined(EA_PLATFORM_WINDOWS) + if(param.sched_priority == THREAD_PRIORITY_NORMAL) + return kThreadPriorityDefault; + #elif !(defined(EA_PLATFORM_CYGWIN) || defined(EA_PLATFORM_NX)) + if(policy == SCHED_OTHER) + return 0; // 0 is the only native priority permitted with the SCHED_OTHER scheduling scheme. + #endif + + // The following needs to be tested on a Unix-by-Unix case. + const int nMin = sched_get_priority_min(policy); + const int nMax = sched_get_priority_max(policy); + + // Some implementations of Pthreads associate higher priorities with smaller + // integer values. We hide this. To the user, a higher value must always + // indicate higher priority. + const int adjustDir = (nMin < nMax) ? 1 : -1; + const int nativeBasePriority = (nMin + nMax) / 2; + + // EAThread_user_priority = +/-(native_priority - EAThread_native_priority_default) + return adjustDir * (param.sched_priority - nativeBasePriority); + #endif + } + + return kThreadPriorityDefault; + } + + + bool EA::Thread::SetThreadPriority(int nPriority) + { + ThreadId currentThreadId = pthread_self(); + int policy; + sched_param param; + int result = -1; + + EAT_ASSERT(nPriority != kThreadPriorityUnknown); + + #if defined(EA_PLATFORM_LINUX) && !defined(EA_PLATFORM_CYGWIN) + // We are assuming Kernel 2.6 and later behavior, but perhaps we should dynamically detect. + // Linux supports three scheduling policies SCHED_OTHER, SCHED_RR, and SCHED_FIFO. + // The process needs to be run with superuser privileges to use SCHED_RR or SCHED_FIFO. + // Thread priorities for SCHED_OTHER do not exist; there is only one allowed thread priority: 0. + // Thread priorities for SCHED_RR and SCHED_FIFO are limited to the range of [1, 99] (verified with Linux 2.6.17), + // despite documentation on the Internet that refers to ranges of 0-99, 1-100, 1-140, etc. + // Higher values in this range mean higher priority. + // All of the SCHED_RR and SCHED_FIFO privileges are higher than anything running at SCHED_OTHER, + // as they are considered to be real-time scheduling. A result of this is that there is no + // such thing as having a thread of lower priority than normal; there are only higher real-time priorities. + if(nPriority <= kThreadPriorityDefault) + { + policy = SCHED_OTHER; + param.sched_priority = 0; + } + else + { + policy = SCHED_RR; + param.sched_priority = (nPriority - kThreadPriorityDefault); + } + + result = pthread_setschedparam(currentThreadId, policy, ¶m); + #else + // The following needs to be tested on a Unix-by-Unix case. + result = pthread_getschedparam(currentThreadId, &policy, ¶m); + + if(result == 0) + { + // Cygwin does not support any scheduling policy other than SCHED_OTHER. + #if !defined(EA_PLATFORM_CYGWIN) + if(policy == SCHED_OTHER) + policy = SCHED_FIFO; + #endif + + int nMin = sched_get_priority_min(policy); + int nMax = sched_get_priority_max(policy); + int adjustDir = 1; + + // Some implementations of pthreads associate higher priorities with smaller integer values. + // To the EAThread user, a higher value indicates a higher priority. + if (nMin > nMax) + { + adjustDir = nMax; + nMax = nMin; + nMin = adjustDir; + adjustDir = -1; // Translate user's desire for higher priority into a native lower value. + } + + // native_priority = EAThread_native_priority_default +/- EAThread_user_priority. + // This calculation sets the default to be in the middle of low and high, which might not be so for all platforms in practice. + param.sched_priority = ((nMin + nMax) / 2) + (adjustDir * nPriority); + + // Clamp to min/max as appropriate for current scheduling policy + if(param.sched_priority < nMin) + param.sched_priority = nMin; + else if(param.sched_priority > nMax) + param.sched_priority = nMax; + + result = pthread_setschedparam(currentThreadId, policy, ¶m); + } + #endif + + return (result == 0); + } + + + void* EA::Thread::GetThreadStackBase() + { + #if defined(EA_PLATFORM_APPLE) + pthread_t threadId = pthread_self(); + return pthread_get_stackaddr_np(threadId); + + #elif (EA_PLATFORM_SOLARIS) + stack_t s; + thr_stksegment(&s); + return s.ss_sp; // Note that this is not the sp pointer (which would refer to the a location low in the stack address space). When returned by thr_stksegment(), ss_sp refers to the top (base) of the stack. + + #elif defined(EA_PLATFORM_CYGWIN) + // Cygwin reserves pthread_attr_getstackaddr and pthread_attr_getstacksize for future use. + // The solution here is probably to use the Windows implementation of this here. + return 0; + + #else // Other Unix + void* stackLow = NULL; + size_t stackSize = 0; + pthread_t threadId = pthread_self(); + + pthread_attr_t sattr; + pthread_attr_init(&sattr); + + #if defined(EA_PLATFORM_BSD) || defined(EA_PLATFORM_CONSOLE_BSD) || defined(EA_PLATFORM_FREEBSD) + pthread_attr_get_np(threadId, &sattr); + #elif defined(EA_HAVE_pthread_getattr_np_DECL) + // Note: this function is non-portable; various Unix systems may have different np alternatives + pthread_getattr_np(threadId, &sattr); + #else + EA_UNUSED(threadId); + // What to do? + #endif + + // See http://www.opengroup.org/onlinepubs/009695399/functions/pthread_attr_getstack.html + // stackLow is a constant. It is not the current low location but rather is the lowest allowed location. + + pthread_attr_getstack(&sattr, &stackLow, &stackSize); + pthread_attr_destroy(&sattr); + + return (char*)stackLow + stackSize; + #endif + } + + + void EA::Thread::SetThreadProcessor(int nProcessor) + { + // Posix threading doesn't have the ability to set the processor. + #if defined(EA_PLATFORM_WINDOWS) + DWORD dwThreadAffinityMask; + + if((nProcessor < 0) || (nProcessor >= EA::Thread::GetProcessorCount())) + dwThreadAffinityMask = 0xffffffff; + else + dwThreadAffinityMask = 1 << nProcessor; + + SetThreadAffinityMask(GetCurrentThread(), dwThreadAffinityMask); + + #elif (defined(EA_PLATFORM_LINUX) && !defined(EA_PLATFORM_ANDROID)) || defined(EA_PLATFORM_NX) + cpu_set_t cpus; + CPU_ZERO(&cpus); + + if (nProcessor >= 0) + { + // Ignore for processors we can't run on. + if ((EA::Thread::GetAvailableCpuAffinityMask() & (1 << nProcessor)) == 0) + { + EAT_FAIL_MSG("Requested processor is not available!"); + return; + } + + CPU_SET(nProcessor, &cpus); + } + else + { + for (int c = 0; c < EA::Thread::GetProcessorCount(); c++) + { + // Skip over processors that are not available. + if (((1 << c) & EA::Thread::GetAvailableCpuAffinityMask()) == 0) + continue; + + CPU_SET(c, &cpus); + } + } + + int result = pthread_setaffinity_np(pthread_self(), sizeof(cpus), &cpus); + EAT_ASSERT_FORMATTED(result == 0, "pthread_setaffinity_np: error %x %x", result, errno); + EA_UNUSED(result); + + #else + // Other Unix platforms don't provide a means to specify what processor a thread runs on. + // You have no choice but to let the OS schedule threads for you. + EA_UNUSED(nProcessor); + #endif + } + + + #if defined(EA_PLATFORM_WINDOWS) && defined(EA_PROCESSOR_X86) && defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1400) + int GetCurrentProcessorNumberXP() + { + _asm { mov eax, 1 } + _asm { cpuid } + _asm { shr ebx, 24 } + _asm { mov eax, ebx } + } + #endif + + + int EA::Thread::GetThreadProcessor() + { + #if defined(EA_PLATFORM_WINDOWS) + // We are using Posix threading on Windows. It happens to be mapped to Windows threading and + // so we can use Windows facilities to tell what processor the thread is running on. + // Only Windows Vista and later provides GetCurrentProcessorNumber. + // So we must dynamically link to this function. + static EA_THREAD_LOCAL bool bInitialized = false; + static EA_THREAD_LOCAL DWORD (WINAPI *pfnGetCurrentProcessorNumber)() = NULL; + + if(!bInitialized) + { + HMODULE hKernel32 = GetModuleHandle("KERNEL32.DLL"); + if(hKernel32) + pfnGetCurrentProcessorNumber = (DWORD (WINAPI*)())GetProcAddress(hKernel32, "GetCurrentProcessorNumber"); + bInitialized = true; + } + + if(pfnGetCurrentProcessorNumber) + return (int)(unsigned)pfnGetCurrentProcessorNumber(); + + #if defined(EA_PLATFORM_WINDOWS) && defined(EA_PROCESSOR_X86) && defined(EA_COMPILER_MSVC) && (EA_COMPILER_VERSION >= 1400) + return GetCurrentProcessorNumberXP(); + #else + return 0; + #endif + + #elif defined(EA_PLATFORM_ANDROID) + // return zero until Google provides a alternative to smp_processor_id() + return 0; + + #elif EA_VALGRIND_ENABLED + // Valgrind does not support the sched_getcpu() vsyscall. It causes it to detect a segfault in the program and stop it. + // https://bugs.kde.org/show_bug.cgi?id=187043 + // http://git.dorsal.polymtl.ca/?p=ust.git;a=commitdiff_plain;h=8f09cb9340387a52b483752c5d2d6c36035b26bc + return 0; + + #elif (defined(EA_PLATFORM_LINUX) && (defined(EATHREAD_GLIBC_VERSION) && (EATHREAD_GLIBC_VERSION > 2005))) + // http://www.kernel.org/doc/man-pages/online/pages/man3/sched_getcpu.3.html + // http://www.kernel.org/doc/man-pages/online/pages/man2/getcpu.2.html + // Another solution is to use the cpuid instruction like we do for Windows. + int cpu = sched_getcpu(); + if(cpu < 0) + cpu = 0; + + if(cpu >= 0) + return cpu; + + // Ideally we would never need to execute the following code: + cpu_set_t cpus; + CPU_ZERO(&cpus); + pthread_getaffinity_np(pthread_self(), sizeof(cpus), &cpus); + + for(int i = 0; i < CPU_SETSIZE; i++) + { + if(CPU_ISSET(i, &cpus)) + return i; + } + + return 0; + #elif EA_PLATFORM_NX + cpu_set_t cpus; + CPU_ZERO(&cpus); + pthread_getaffinity_np(pthread_self(), sizeof(cpus), &cpus); + for(int i = 0; i < CPU_SETSIZE; i++) + { + if(CPU_ISSET(i, &cpus)) + return i; + } + return 0; + #else + return 0; + #endif + } + +#if defined(EA_PLATFORM_APPLE) + #include + #include + #define SYSCTL_CORE_COUNT "machdep.cpu.core_count" + + typedef struct cpu_set + { + uint32_t count; + } cpu_set_t; + + static inline void CPU_ZERO(cpu_set_t* cs) { cs->count = 0; } + static inline void CPU_SET(int num, cpu_set_t* cs) { cs->count |= (1 << num); } + static inline int CPU_ISSET(int num, cpu_set_t* cs) { return (cs->count & (1 << num)); } + + int pthread_setaffinity_np(pthread_t thread, size_t cpu_size, cpu_set_t* cpu_set) + { + thread_port_t mach_thread; + int core = 0; + + for (core = 0; core < 8 * cpu_size; core++) + { + if (CPU_ISSET(core, cpu_set)) + break; + } + + thread_affinity_policy_data_t policy = {core}; + mach_thread = pthread_mach_thread_np(thread); + thread_policy_set(mach_thread, THREAD_AFFINITY_POLICY, (thread_policy_t)&policy, 1); + + return 0; + } +#endif + +#if defined(EA_PLATFORM_ANDROID) && __ANDROID_API__ <= 19 + typedef struct cpu_set + { + uint32_t count; + } cpu_set_t; + + static inline void CPU_ZERO(cpu_set_t* cs) { cs->count = 0; } + static inline void CPU_SET(int num, cpu_set_t* cs) { cs->count |= (1 << num); } + static inline int CPU_ISSET(int num, cpu_set_t* cs) { return (cs->count & (1 << num)); } +#endif + + EATHREADLIB_API void EA::Thread::SetThreadAffinityMask(const EA::Thread::ThreadId& id, ThreadAffinityMask nAffinityMask) + { + // Replace kThreadAffinityMaskAny, with AvailableCpuAffinityMask. + if (nAffinityMask == kThreadAffinityMaskAny) + nAffinityMask = EA::Thread::GetAvailableCpuAffinityMask(); + + ThreadAffinityMask sanitizedMask = nAffinityMask & EA::Thread::GetAvailableCpuAffinityMask(); + EAT_ASSERT_MSG(sanitizedMask != 0, "None of the requested processors are available!"); + + EAThreadDynamicData* const pTDD = FindThreadDynamicData(id); + if(pTDD) + { + pTDD->mnThreadAffinityMask = sanitizedMask; + + #if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED + cpu_set_t cpuSetMask; + memset(&cpuSetMask, 0, sizeof(cpu_set_t)); + + for (int c = 0; c < EA::Thread::GetProcessorCount(); c++, sanitizedMask >>= 1) + { + if (sanitizedMask & 1) + { + CPU_SET(c, &cpuSetMask); + } + } + + #if defined(EA_PLATFORM_NX) || defined(EA_PLATFORM_APPLE) + int result = pthread_setaffinity_np(pTDD->mThreadId, sizeof(cpu_set_t), &cpuSetMask); + EAT_ASSERT_FORMATTED(result == 0, "pthread_setaffinity_np: error %x %x", result, errno); + EA_UNUSED(result); + #elif defined(EA_PLATFORM_ANDROID) && __ANDROID_API__ <= 19 + if(pTDD->mThreadPid != 0) + { + syscall(__NR_sched_setaffinity, pTDD->mThreadPid, sizeof(nAffinityMask), &nAffinityMask); + } + #else + sched_setaffinity(pTDD->mThreadPid, sizeof(cpu_set_t), &cpuSetMask); + #endif + #endif + } + } + + + EATHREADLIB_API EA::Thread::ThreadAffinityMask EA::Thread::GetThreadAffinityMask(const EA::Thread::ThreadId& id) + { + EAThreadDynamicData* const pTDD = FindThreadDynamicData(id); + if(pTDD) + { + return pTDD->mnThreadAffinityMask; + } + + return kThreadAffinityMaskAny; + } + + // Internal SetThreadName API's so we don't repeat the implementations + namespace Internal + { + // This function is not currently used if the thread name can be set from any other thread + #if !EATHREAD_OTHER_THREAD_NAMING_SUPPORTED + + void SetCurrentThreadName(const char8_t* pName) + { + #if defined(EA_PLATFORM_LINUX) + // http://manpages.courier-mta.org/htmlman2/prctl.2.html + // The Linux documentation says PR_SET_NAME sets the process name, but that + // documentation is wrong and instead it sets the current thread name. + + // Also: http://0pointer.de/blog/projects/name-your-threads.html + // Stefan Kost recently pointed me to the fact that the Linux system call prctl(PR_SET_NAME) + // does not in fact change the process name, but the task name (comm field) -- in contrast + // to what the man page suggests. That makes it very useful for naming threads, since you + // can read back the name you set with PR_SET_NAME earlier from the /proc file system + // (/proc/$PID/task/$TID/comm on newer kernels, /proc/$PID/task/$TID/stat's second field + // on older kernels), and hence distinguish which thread might be responsible for the high + // CPU load or similar problems. + char8_t nameBuf[16]; // Limited to 16 bytes, null terminated if < 16 bytes + strncpy(nameBuf, pName, sizeof(nameBuf)); + nameBuf[15] = 0; + prctl(PR_SET_NAME, (unsigned long)nameBuf, 0, 0, 0); + + #elif defined(EA_PLATFORM_APPLE) + // http://src.chromium.org/viewvc/chrome/trunk/src/base/platform_thread_mac.mm?revision=49465&view=markup&pathrev=49465 + // "There's a non-portable function for doing this: pthread_setname_np. + // It's supported by OS X >= 10.6 and the Xcode debugger will show the thread + // names if they're provided." + // On OSX the return value is always -1 on error; use errno to tell the error value. + typedef int (*pthread_setname_np_type)(const char*); + pthread_setname_np_type pthread_setname_np_ptr = (pthread_setname_np_type)(uintptr_t)dlsym(RTLD_DEFAULT, "pthread_setname_np"); + + if(pthread_setname_np_ptr) + { + // Mac OS X does not expose the length limit of the name, so hardcode it. + char8_t nameBuf[63]; // It is not clear what the size limit actually is, though 63 is known to work because it was seen on the Internet. + strncpy(nameBuf, pName, sizeof(nameBuf)); + nameBuf[62] = 0; + pthread_setname_np_ptr(nameBuf); + } + + #elif defined(EA_PLATFORM_BSD) || defined(EA_PLATFORM_CONSOLE_BSD) || defined(EA_PLATFORM_FREEBSD) + // http://www.unix.com/man-page/freebsd/3/PTHREAD_SET_NAME_NP/ + pthread_set_name_np(pthread_self(), pName); + #elif defined(EA_PLATFORM_NX) + // http://www.unix.com/man-page/freebsd/3/PTHREAD_SET_NAME_NP/ + pthread_setname_np(pthread_self(), pName); + #endif + } + + #endif + + EA::Thread::ThreadId GetId(EAThreadDynamicData* pTDD) + { + if(pTDD) + return pTDD->mThreadId; + + return EA::Thread::kThreadIdInvalid; + } + + void SetThreadName(EAThreadDynamicData* pTDD) + { + #if defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_APPLE) + EAT_COMPILETIME_ASSERT(EATHREAD_OTHER_THREAD_NAMING_SUPPORTED == 0); + // http://stackoverflow.com/questions/2369738/can-i-set-the-name-of-a-thread-in-pthreads-linux + // Under some Unixes you can name only the current thread, so we apply the naming + // only if the currently executing thread is the one that is associated with + // this class object. + if(GetId(pTDD) == EA::Thread::GetThreadId()) + SetCurrentThreadName(pTDD->mName); + + #elif defined(EA_PLATFORM_BSD) + EAT_COMPILETIME_ASSERT(EATHREAD_OTHER_THREAD_NAMING_SUPPORTED == 1); + // http://www.unix.com/man-page/freebsd/3/PTHREAD_SET_NAME_NP/ + if(GetId(pTDD) != EA::Thread::kThreadIdInvalid) + pthread_set_name_np(GetId(pTDD), pTDD->mName); + + #elif defined(EA_PLATFORM_NX) + if (GetId(pTDD) != EA::Thread::kThreadIdInvalid) + pthread_setname_np(GetId(pTDD), pTDD->mName); + + #endif + } + } // namespace Internal + + + + EATHREADLIB_API void EA::Thread::SetThreadName(const char* pName) { SetThreadName(GetThreadId(), pName); } + EATHREADLIB_API const char* EA::Thread::GetThreadName() { return GetThreadName(GetThreadId()); } + + EATHREADLIB_API void EA::Thread::SetThreadName(const EA::Thread::ThreadId& id, const char* pName) + { + EAThreadDynamicData* const pTDD = FindThreadDynamicData(id); + if(pTDD) + { + if(pTDD->mName != pName) // self-assignment check + { + strncpy(pTDD->mName, pName, EATHREAD_NAME_SIZE); + pTDD->mName[EATHREAD_NAME_SIZE - 1] = 0; + } + + Internal::SetThreadName(pTDD); + } + } + + EATHREADLIB_API const char* EA::Thread::GetThreadName(const EA::Thread::ThreadId& id) + { + EAThreadDynamicData* const pTDD = FindThreadDynamicData(id); + return pTDD ? pTDD->mName : ""; + } + + + int EA::Thread::GetProcessorCount() + { + #if defined(EA_PLATFORM_WINDOWS) + static int nProcessorCount = 0; // This doesn't really need to be an atomic integer. + + if(nProcessorCount == 0) + { + // A better function to use would possibly be KeQueryActiveProcessorCount + // (NTKERNELAPI ULONG KeQueryActiveProcessorCount(PKAFFINITY ActiveProcessors)) + + SYSTEM_INFO systemInfo; + memset(&systemInfo, 0, sizeof(systemInfo)); + GetSystemInfo(&systemInfo); + nProcessorCount = (int)systemInfo.dwNumberOfProcessors; + } + + return nProcessorCount; + + #elif defined(EA_PLATFORM_OSX) || defined(EA_PLATFORM_BSD) + // http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man3/sysctlbyname.3.html + // We can use: + // int sysctl(int* name, u_int namelen, void* oldp, size_t* oldlenp, void* newp, size_t newlen); + // int sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen); + + #ifdef EA_PLATFORM_BSD + int mib[4] = { CTL_HW, HW_NCPU, 0, 0 }; + #else + int mib[4] = { CTL_HW, HW_AVAILCPU, 0, 0 }; + #endif + int cpuCount = 0; // Unfortunately, Apple's documentation fails to clarify if this needs to be 'int' or 'long'. + size_t len = sizeof(cpuCount); + + sysctl(mib, 2, &cpuCount, &len, NULL, 0); + + if(cpuCount < 1) + { + mib[1] = HW_NCPU; + sysctl(mib, 2, &cpuCount, &len, NULL, 0); + + if(cpuCount < 1) + cpuCount = 1; + } + + return cpuCount; + + // Maybe simpler, should try it out to make sure it works: + // + // int cpuCount = 0; + // size_t len = sizeof(cpuCount); + // if(sysctlbyname("hw.ncpu", &cpuCount, &len, NULL, 0) != 0) + // cpuCount = 1; + // return cpuCount; + #elif defined(EA_PLATFORM_NX) + return 3; + #elif defined(EA_PLATFORM_ANDROID) + return android_getCpuCount(); + #elif defined(EA_PLATFORM_STADIA) + return getAvailableCpuCount(); + #else + // Posix doesn't provide a means to get this information. + // Some Unixes provide sysconf() with the _SC_NPROCESSORS_ONLN or _SC_NPROCESSORS_CONF option. + // Another option is to count the number of entries in /proc/cpuinfo + #ifdef _SC_NPROCESSORS_ONLN + return (int)sysconf(_SC_NPROCESSORS_ONLN); + #else + return 1; + #endif + #endif + } + + #if defined(EA_PLATFORM_WINDOWS) + extern "C" __declspec(dllimport) void __stdcall Sleep(unsigned long dwMilliseconds); + #endif + + void EA::Thread::ThreadSleep(const ThreadTime& timeRelative) + { + #if defined(EA_PLATFORM_WINDOWS) + // There is no nanosleep on Windows, but there is Sleep. + if(timeRelative == kTimeoutImmediate) + Sleep(0); + else + Sleep((unsigned)((timeRelative.tv_sec * 1000) + (((timeRelative.tv_nsec % 1000) * 1000000)))); + #else + if(timeRelative == kTimeoutImmediate) + { + sched_yield(); + } + else + { + #if defined(EA_HAVE_nanosleep_DECL) + nanosleep(&timeRelative, 0); + #else + // What to do? + #endif + } + #endif + } + + + namespace EA + { + namespace Thread + { + EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId); + } + } + + void EA::Thread::ThreadEnd(intptr_t threadReturnValue) + { + EAThreadDynamicData* const pTDD = FindThreadDynamicData(GetThreadId()); + + if(pTDD) + { + pTDD->mnStatus = Thread::kStatusEnded; + pTDD->mnReturnValue = threadReturnValue; + pTDD->mRunMutex.Unlock(); + pTDD->Release(); + } + + pthread_exit((void*)threadReturnValue); + } + + + #if defined(EA_PLATFORM_APPLE) + EA::Thread::SysThreadId EA::Thread::GetSysThreadId(ThreadId id) + { + return pthread_mach_thread_np(id); + } + + EA::Thread::SysThreadId EA::Thread::GetSysThreadId() + { + return pthread_mach_thread_np(pthread_self()); // There isn't a self-specific version of pthread_mach_thread_np. + } + #endif + + + EA::Thread::ThreadTime EA::Thread::GetThreadTime() + { + #if defined(EA_PLATFORM_WINDOWS) && !defined(EA_PLATFORM_CYGWIN) + // We use this code instead of GetTickCount or similar because pthreads under + // Win32 uses the 'system file time' definition (e.g. GetSystemTimeAsFileTime()) + // for current time. The implementation here is just like that in the + // pthreads-Win32 ptw32_timespec.c file. + int64_t ft; + ThreadTime threadTime; + GetSystemTimeAsFileTime((FILETIME*)&ft); // nTime64 is in intervals of 100ns. + #define PTW32_TIMESPEC_TO_FILETIME_OFFSET (((int64_t)27111902 << 32) + (int64_t)3577643008) + threadTime.tv_sec = (int)((ft - PTW32_TIMESPEC_TO_FILETIME_OFFSET) / 10000000); + threadTime.tv_nsec = (int)((ft - PTW32_TIMESPEC_TO_FILETIME_OFFSET - ((int64_t)threadTime.tv_sec * (int64_t)10000000)) * 100); + return threadTime; + + // Alternative which will likely be slower: + //#include + //ThreadTime threadTime; + //_timeb fTime; _ftime(&fTime); + //threadTime.tv_sec = (long)fTime.time; + //threadTime.tv_nsec = fTime.millitm * 1000000; + //return threadTime; + #else + // For some systems we may need to use gettimeofday() instead of clock_gettime(). + #if defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_CYGWIN) || (_POSIX_TIMERS > 0) + ThreadTime threadTime; + clock_gettime(CLOCK_REALTIME, &threadTime); // If you get a linker error about clock_getttime, you need to link librt.a (specify -lrt to the linker). + return threadTime; + #else + timeval temp; + gettimeofday(&temp, NULL); + return ThreadTime((ThreadTime::seconds_t)temp.tv_sec, (ThreadTime::nseconds_t)temp.tv_usec * 1000); + #endif + #endif + } + + + void EA::Thread::SetAssertionFailureFunction(EA::Thread::AssertionFailureFunction pAssertionFailureFunction, void* pContext) + { + gpAssertionFailureFunction = pAssertionFailureFunction; + gpAssertionFailureContext = pContext; + } + + + void EA::Thread::AssertionFailure(const char* pExpression) + { + if(gpAssertionFailureFunction) + gpAssertionFailureFunction(pExpression, gpAssertionFailureContext); + else + { + #if EAT_ASSERT_ENABLED + #ifdef EA_PLATFORM_WINDOWS + OutputDebugStringA("EA::Thread::AssertionFailure: "); + OutputDebugStringA(pExpression); + OutputDebugStringA("\n"); + #else + printf("EA::Thread::AssertionFailure: "); + printf("%s", pExpression); + printf("\n"); + fflush(stdout); + fflush(stderr); + #endif + + EATHREAD_DEBUG_BREAK(); + #endif + } + } + + +#endif // defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/source/version.cpp b/src/thirdparty/ea/EAThread/source/version.cpp new file mode 100644 index 00000000..eb61cce6 --- /dev/null +++ b/src/thirdparty/ea/EAThread/source/version.cpp @@ -0,0 +1,32 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "eathread/version.h" + +namespace EA +{ + namespace Thread + { + const Version gVersion = + { + EATHREAD_VERSION_MAJOR, + EATHREAD_VERSION_MINOR, + EATHREAD_VERSION_PATCH + }; + + const Version* GetVersion() + { + return &gVersion; + } + + bool CheckVersion(int majorVersion, int minorVersion, int patchVersion) + { + return (majorVersion == EATHREAD_VERSION_MAJOR) && + (minorVersion == EATHREAD_VERSION_MINOR) && + (patchVersion == EATHREAD_VERSION_PATCH); + } + } +} + + diff --git a/src/thirdparty/ea/EAThread/test/dllsafety/source/TestDllSafety.cpp b/src/thirdparty/ea/EAThread/test/dllsafety/source/TestDllSafety.cpp new file mode 100644 index 00000000..b1ef3dda --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/dllsafety/source/TestDllSafety.cpp @@ -0,0 +1,143 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/////////////////////////////////////////////////////////////////////////////// +// operator new +// EASTL requires the following new operators to be defined. +// +void* operator new[](size_t size, const char*, int, unsigned, const char*, int) +{ + return new char[size]; +} + +void* operator new[](size_t size, size_t, size_t, const char*, int, unsigned, const char*, int) +{ + return new char[size]; +} + +const int NUM_THREADS = 5; + +/////////////////////////////////////////////////////////////////////////////// +// Structure where a single instance is passed to every active thread. +// +struct GlobalData +{ + EA::Thread::AtomicInt32 i; + GlobalData() + : i(0) + {} +}; + +typedef void (*DLL_ENTRY)(GlobalData*); + +/////////////////////////////////////////////////////////////////////////////// +// Thread Entry +// +intptr_t ThreadFunction(void* pData) +{ + GlobalData* const pGlobalData = static_cast(pData); + while(!pGlobalData->i) + { + EA::Thread::ThreadSleep(50); + } + + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// The function export below MUST be here as it forces lib files to be +// generated for the DLL's being built. +// +extern "C" +EA_EXPORT void ForceLibFilesToBeGenerated(GlobalData* pGlobal) +{ + using namespace EA::Thread; + using namespace EA::UnitTest; + + // Start two threads in this DLL instance + Thread thread; + for(size_t i = 0; i < NUM_THREADS; i++) + thread.Begin(ThreadFunction, pGlobal); + + // Get the number of threads in the system + ThreadEnumData enumData[32]; + size_t count = EnumerateThreads(enumData, EAArrayCount(enumData)); + //Report("Number of DLL threads detected: %d\n", count); + + for(size_t i = 0; i < count; i++) + enumData[i].Release(); +} + +/////////////////////////////////////////////////////////////////////////////// +// Main +// +int EAMain(int argc, char** argv) +{ + using namespace EA::Thread; + using namespace EA::UnitTest; + + int nErrorCount = 0; + GlobalData data; + +// TODO: DLL usage must be made portable through support at various levels of our technology stack. +#if defined(EA_PLATFORM_MICROSOFT) && defined(EA_PLATFORM_WIN32) + HINSTANCE__* mod1 = LoadLibrary("EAThreadTestDllSafetyMod1.dll"); + HINSTANCE__* mod2 = LoadLibrary("EAThreadTestDllSafetyMod2.dll"); + DLL_ENTRY dllmain1 = (DLL_ENTRY)GetProcAddress(mod1, "ForceLibFilesToBeGenerated"); + DLL_ENTRY dllmain2 = (DLL_ENTRY)GetProcAddress(mod2, "ForceLibFilesToBeGenerated"); + + if(dllmain1 != NULL) + dllmain1(&data); + + if(dllmain2 != NULL) + dllmain2(&data); +#endif + + EA::EAMain::PlatformStartup(); + { + // Start n threads in this DLL instance + Thread thread; + for(size_t i = 0; i < NUM_THREADS; i++) + thread.Begin(ThreadFunction, &data); + + // Get the number of threads in the system and validate. + ThreadEnumData enumData[32]; + size_t count = EnumerateThreads(enumData, EAArrayCount(enumData)); + Report("Number of threads detected: %d/%d. \n", count, NUM_THREADS*3); + EATEST_VERIFY_MSG(count >= NUM_THREADS, "Thread tracking data isn't DLL safe. We are missing data generated in other DLL's."); + + for(size_t i = 0; i < count; i++) + enumData[i].Release(); + + // Release the threads + data.i++; + } + EA::EAMain::PlatformShutdown(nErrorCount); + +#if defined(EA_PLATFORM_MICROSOFT) && defined(EA_PLATFORM_WIN32) + FreeLibrary(mod1); + FreeLibrary(mod2); +#endif + + return nErrorCount; +} + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/interprocess_test/source/TestThreadInterprocess.cpp b/src/thirdparty/ea/EAThread/test/interprocess_test/source/TestThreadInterprocess.cpp new file mode 100644 index 00000000..037c3495 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/interprocess_test/source/TestThreadInterprocess.cpp @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThreadInterprocess.h" +#include +#include +#include +#include +#include +#include +#include + +#include + + +/////////////////////////////////////////////////////////////////////////////// +// Globals +// +unsigned int gTestThreadCount = 4; +unsigned int gTestLengthSeconds = 10; + + +/////////////////////////////////////////////////////////////////////////////// +// EAThreadFailure +// +// This is called by EAThread's assert failure function. +// +static void EAThreadFailure(const char* pMessage, void* /*pContext*/) +{ + EA::UnitTest::Report("Thread test failure (EAThread assert): %s\n", pMessage); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// operator new +// EASTL requires the following new operators to be defined. +// +void* operator new[](size_t size, const char*, int, unsigned, const char*, int) +{ + return new char[size]; +} + +void* operator new[](size_t size, size_t, size_t, const char*, int, unsigned, const char*, int) +{ + return new char[size]; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// main +// +int EAMain(int argc, char** argv) +{ + using namespace EA::Thread; + using namespace EA::UnitTest; + using namespace EA::StdC; + + int nErrorCount = 0; + bool bDebugMode = false; + + EA::EAMain::PlatformStartup(); + + // Process possible command line parameters + for(int i(0); i < argc; i++) + { + // Look for -? + if(Stricmp(argv[i], "-?") == 0) + { + printf("Command line parameters:\n"); + printf(" -? Get Help.\n"); + printf(" -t Run the tests for seconds each.\n"); + printf(" -c Specifies test thread count.\n"); + printf(" -d Debug mode. Causes app to wait for a debugger to connect.\n"); + continue; + } + + // Run the tests for seconds each. + if((Stricmp(argv[i], "-t") == 0) && (i < (argc - 1))) + { + gTestLengthSeconds = (unsigned int) atoi(argv[i+1]); + if(gTestLengthSeconds < 3) + gTestLengthSeconds = 3; + continue; + } + + // Specifies test thread count. e.g. -c 10 + if((Stricmp(argv[i], "-c") == 0) && (i < (argc - 1))) + { + gTestThreadCount = (unsigned int) atoi(argv[i+1]); + if(gTestThreadCount < 1) + gTestThreadCount = 1; + if(gTestThreadCount > 100) + gTestThreadCount = 100; + continue; + } + + // Debug mode. Causes app to wait for a debugger to connect. + if(Stricmp(argv[i], "-d") == 0) + { + bDebugMode = true; + continue; + } + } + + + // Set EAThread to route its errors to our own error reporting function. + EA::Thread::SetAssertionFailureFunction(EAThreadFailure, NULL); + + ReportVerbosity(1, "Test time seconds: %u\n", gTestLengthSeconds); + ReportVerbosity(1, "Thread count: %u\n", gTestThreadCount); + + // Print ThreadId for this primary thread. + const ThreadId threadId = GetThreadId(); + ReportVerbosity(1, "Primary thread ThreadId: %08x\n", (int)(intptr_t)threadId); + + // Print SysThreadId for this primary thread. + const SysThreadId sysThreadId = GetSysThreadId(threadId); + ReportVerbosity(1, "Primary thread SysThreadId: %08d\n", (int)(intptr_t)sysThreadId); + + // Print thread priority for this primary thread. + const int nPriority = EA::Thread::GetThreadPriority(); + ReportVerbosity(1, "Primary thread priority: %d\n", nPriority); + + const int nProcessorCount = EA::Thread::GetProcessorCount(); + ReportVerbosity(1, "Currently active virtual processor count: %d\n", nProcessorCount); + + if(bDebugMode) + { + Report("Debug mode activated. Waiting for debugger to attach.\n"); + while(bDebugMode) + ThreadSleepRandom(500, 500, true); + Report("Continuing.\n"); + } + + // Add the tests + TestApplication testSuite("EAThread Interprocess Unit Tests", argc, argv); + + testSuite.AddTest("RWMutex", TestThreadRWMutex); + + nErrorCount += testSuite.Run(); + + EA::EAMain::PlatformShutdown(nErrorCount); + + return nErrorCount; +} + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/interprocess_test/source/TestThreadInterprocess.h b/src/thirdparty/ea/EAThread/test/interprocess_test/source/TestThreadInterprocess.h new file mode 100644 index 00000000..9a4a01c6 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/interprocess_test/source/TestThreadInterprocess.h @@ -0,0 +1,17 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#ifndef TESTTHREADINTERPROCESS_H +#define TESTTHREADINTERPROCESS_H + + +extern unsigned int gTestThreadCount; +extern unsigned int gTestLengthSeconds; + + +// Individual test functions +int TestThreadRWMutex(); + + +#endif // Header include guard diff --git a/src/thirdparty/ea/EAThread/test/interprocess_test/source/TestThreadInterprocessRWMutex.cpp b/src/thirdparty/ea/EAThread/test/interprocess_test/source/TestThreadInterprocessRWMutex.cpp new file mode 100644 index 00000000..62e01b2a --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/interprocess_test/source/TestThreadInterprocessRWMutex.cpp @@ -0,0 +1,259 @@ +/////////////////////////////////////////////////////////////////////////////// +// TestThreadInterprocessRWMutex.cpp +// +// Copyright (c) 2009, Electronic Arts Inc. All rights reserved. +// Created by Paul Pedriana +/////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include +#include +#include "TestThreadInterprocess.h" +#include + + +struct RWMWorkDataInterProcess +{ + volatile int mnExpectedValue; // Intentionally not an atomic variable. + volatile int mnCalculatedValue; // Intentionally not an atomic variable. + volatile int mnWriteLockCount; // How many times the write lock was owned, across all processes. + + RWMWorkDataInterProcess() + : mnExpectedValue(0), + mnCalculatedValue(0), + mnWriteLockCount(0) + { + printf("RWMWorkDataInterProcess\n"); + } + + ~RWMWorkDataInterProcess() + { + printf("~RWMWorkDataInterProcess\n"); + } +}; + + +struct RWMWorkDataInterThread +{ + volatile bool mbShouldQuit; // + EA::Thread::RWMutexIP mRWMutexIP; // + EA::Thread::AtomicInt32 mnThreadIndex; // + EA::Thread::AtomicInt32 mnErrorCount; // + EA::Thread::AtomicInt32 mnReadLockCount; // How many times the read lock was owned, within this process. + EA::Thread::AtomicInt32 mnWriteLockCount; // How many times the write lock was owned, within this process. + + RWMWorkDataInterThread() + : mbShouldQuit(false), + mRWMutexIP(NULL, false), + mnThreadIndex(0), + mnErrorCount(0), + mnReadLockCount(0), + mnWriteLockCount(0) + { + } + +protected: + RWMWorkDataInterThread(const RWMWorkDataInterThread& rhs); + RWMWorkDataInterThread& operator=(const RWMWorkDataInterThread& rhs); +}; + + +static intptr_t RWThreadFunction(void* pvWorkData) +{ + using namespace EA::Thread; + + int nErrorCount = 0; + RWMWorkDataInterThread* pWorkData = (RWMWorkDataInterThread*)pvWorkData; + ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "RWMutexIP test function created: %08x\n", (int)(intptr_t)threadId); + + // We use the interprocess mutex to control access to an interprocess data struct. + Shared gSharedData("RWMWorkDataIP"); + + // We track the amount of time we spend waiting for Locks. + //ThreadTime nInitialTime, nFinalTime; + const ThreadTime kMaxExpectedTime = 1000; + + while(!pWorkData->mbShouldQuit) + { + const bool bWriteLock((rand() % 10) == 0); // 10% of the time, do a write lock. + + if(bWriteLock) + { + //nInitialTime = EA::Thread::GetThreadTime(); + + int nLockResult = pWorkData->mRWMutexIP.Lock(RWMutexIP::kLockTypeWrite, GetThreadTime() + kMaxExpectedTime); + EATEST_VERIFY_MSG(nLockResult != RWMutexIP::kResultError, "RWMutexIP failure: write lock."); + + //nFinalTime = EA::Thread::GetThreadTime(); + //EATEST_VERIFY_MSG((nFinalTime - nInitialTime) < kMaxExpectedTime, "RWMutexIP failure: write lock slow."); + + if(nLockResult > 0) + { + gSharedData->mnWriteLockCount++; + pWorkData->mnWriteLockCount++; + + // Verify exactly one write lock is set. + nLockResult = pWorkData->mRWMutexIP.GetLockCount(RWMutexIP::kLockTypeWrite); + EATEST_VERIFY_MSG(nLockResult == 1, "RWMutexIP failure: write lock verify 1."); + + // What we do here is spend some time manipulating mnExpectedValue and mnCalculatedValue + // while we have the write lock. We change their values in a predicable way but before + // we are done mnCalculatedValue has been incremented by one and both values are equal. + const uintptr_t x = (uintptr_t)pWorkData; + + gSharedData->mnExpectedValue = -1; + EA::UnitTest::ThreadSleepRandom(10, 20); + gSharedData->mnCalculatedValue *= 50; + EA::UnitTest::ThreadSleepRandom(10, 20); + gSharedData->mnCalculatedValue /= (int)(((x + 1) / x) * 50); // This will always be the same as simply '/= 50'. + EA::UnitTest::ThreadSleepRandom(10, 20); + gSharedData->mnCalculatedValue += 1; + EA::UnitTest::ThreadSleepRandom(10, 20); + gSharedData->mnExpectedValue = gSharedData->mnCalculatedValue; + + // Verify no read locks are set. + nLockResult = pWorkData->mRWMutexIP.GetLockCount(RWMutexIP::kLockTypeRead); + EATEST_VERIFY_MSG(nLockResult == 0, "RWMutexIP failure: write lock verify 2."); + + // Verify exactly one write lock is set. + nLockResult = pWorkData->mRWMutexIP.GetLockCount(RWMutexIP::kLockTypeWrite); + EATEST_VERIFY_MSG(nLockResult == 1, "RWMutexIP failure: write lock verify 3."); + + // Verify there are now zero write locks set. + nLockResult = pWorkData->mRWMutexIP.Unlock(); + EATEST_VERIFY_MSG(nLockResult == 0, "RWMutexIP failure: write unlock."); + + EA::UnitTest::ThreadSleepRandom(40, 80); + } + } + else + { + const int nRecursiveLockCount(rand() % 2); + int i, nLockResult, nLocks = 0; + + for(i = 0; i < nRecursiveLockCount; i++) + { + //nInitialTime = EA::Thread::GetThreadTime(); + + nLockResult = pWorkData->mRWMutexIP.Lock(RWMutexIP::kLockTypeRead, GetThreadTime() + kMaxExpectedTime); + + //nFinalTime = EA::Thread::GetThreadTime(); + //EATEST_VERIFY_MSG(nLockResult != RWMutexIP::kResultError, "RWMutexIP failure: read lock."); + + if(nLockResult > 0) + { + nLocks++; + pWorkData->mnReadLockCount++; + + EA::UnitTest::ReportVerbosity(2, "CValue = %d; EValue = %d\n", gSharedData->mnCalculatedValue, gSharedData->mnExpectedValue); + EATEST_VERIFY_MSG(gSharedData->mnCalculatedValue == gSharedData->mnExpectedValue, "RWMutexIP failure: read lock 2"); + } + } + + while(nLocks > 0) + { + // Verify no write locks are set. + nLockResult = pWorkData->mRWMutexIP.GetLockCount(RWMutexIP::kLockTypeWrite); + EATEST_VERIFY_MSG(nLockResult == 0, "RWMutexIP failure: read lock verify 1."); + + // Verify at least N read locks are set. + nLockResult = pWorkData->mRWMutexIP.GetLockCount(RWMutexIP::kLockTypeRead); + EATEST_VERIFY_MSG(nLockResult >= nLocks, "RWMutexIP failure: read lock verify 2."); + + // Verify there is one less read lock set. + nLockResult = pWorkData->mRWMutexIP.Unlock(); + EATEST_VERIFY_MSG(nLockResult >= nLocks-1, "RWMutexIP failure: read unlock."); + + nLocks--; + } + + EA::UnitTest::ThreadSleepRandom(10, 20); + } + } + + pWorkData->mnErrorCount += nErrorCount; + + return 0; +} + + + +int TestThreadRWMutex() +{ + using namespace EA::Thread; + + int nErrorCount(0); + + EA::UnitTest::Report("Thread Pool Test\n"); + + /* + { // ctor tests + // We test various combinations of RWMutexIP ctor and RWMutexIPParameters. + // RWMutexIPParameters(bool bIntraProcess = true, const char* pName = NULL); + // RWMutexIP(const RWMutexIPParameters* pRWMutexIPParameters = NULL, bool bDefaultParameters = true); + + //RWMutexIPParameters mp1(true, NULL); + //RWMutexIPParameters mp2(true, "mp2"); + //RWMutexIPParameters mp3(false, "mp3"); + RWMutexIPParameters mp4(false, "mp4"); + RWMutexIPParameters mp6(false, "mp6"); + + //RWMutexIP mutex1(&mp1, false); + //RWMutexIP mutex2(&mp2, false); + //RWMutexIP mutex3(&mp3, false); + RWMutexIP mutex4(&mp4, false); + //RWMutexIP mutex5(NULL, true); + RWMutexIP mutex6(NULL, false); + mutex6.Init(&mp6); + + //AutoRWMutexIP am1(mutex1, RWMutexIP::kLockTypeRead); + //AutoRWMutexIP am2(mutex2, RWMutexIP::kLockTypeRead); + //AutoRWMutexIP am3(mutex3, RWMutexIP::kLockTypeRead); + AutoRWMutexIP am4(mutex4, RWMutexIP::kLockTypeRead); + AutoRWMutexIP am6(mutex6, RWMutexIP::kLockTypeRead); + } + */ + + { + RWMWorkDataInterThread workData; + RWMutexIPParameters rwMutexIPParameters(false, "RWMTest"); + + // Set up the RWMWorkData + workData.mRWMutexIP.Init(&rwMutexIPParameters); + + // Create the threads + Thread* pThreadArray = new Thread[gTestThreadCount]; + ThreadId* pThreadIdArray = new ThreadId[gTestThreadCount]; + Thread::Status status; + + for(unsigned i(0); i < gTestThreadCount; i++) + pThreadIdArray[i] = pThreadArray[i].Begin(RWThreadFunction, &workData); + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000); + + workData.mbShouldQuit = true; + + for(unsigned i(0); i < gTestThreadCount; i++) + { + if(pThreadIdArray[i] != kThreadIdInvalid) + { + status = pThreadArray[i].WaitForEnd(GetThreadTime() + 30000); + + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "RWMutexIP/Thread failure: status == kStatusRunning."); + } + } + + delete[] pThreadIdArray; + delete[] pThreadArray; + + nErrorCount += (int)workData.mnErrorCount; + } + + return nErrorCount; +} + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestCpuAvailableAffinityMask.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestCpuAvailableAffinityMask.cpp new file mode 100644 index 00000000..d3865bc5 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestCpuAvailableAffinityMask.cpp @@ -0,0 +1,96 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "TestThread.h" +#include +#include +#include +#include +#include + +#if defined(EA_PLATFORM_STADIA) + EATHREADLIB_API bool isUint(const char* buffer); + EATHREADLIB_API EA::Thread::ThreadAffinityMask parseSet(char const* buffer, size_t len); + EATHREADLIB_API bool readLine(const char* fileName, char* buffer, size_t len); + EATHREADLIB_API bool getProcessCpuSetCGroup(char* cgroup, size_t len); + EATHREADLIB_API EA::Thread::ThreadAffinityMask getInstanceCpuSet(); + EATHREADLIB_API EA::Thread::ThreadAffinityMask initValidCpuAffinityMask(); +#endif + + +int TestCpuAvailableAffinityMaskBasic() +{ + int nErrorCount = 0; + +#if defined(EA_PLATFORM_STADIA) + { // Test isUint() + static const char* isUintTest1 = "1"; + static const char* isUintTest2 = "wrong"; + static const char* isUintTest3 = " 9"; + static const char* isUintTest4 = " "; + static const char* isUintTest5 = ""; + + EATEST_VERIFY(isUint(isUintTest1)); + EATEST_VERIFY(!isUint(isUintTest2)); + EATEST_VERIFY(isUint(isUintTest3)); + EATEST_VERIFY(!isUint(isUintTest4)); + EATEST_VERIFY(!isUint(isUintTest5)); + } + + { // Test parseSet() + char const* parseSetTest1 = "0-6"; + char const* parseSetTest2 = "0, 2-6, 8"; + char const* parseSetTest3 = "wrong"; + + EA::Thread::ThreadAffinityMask parseSetVerify1 = 0x7f; + EA::Thread::ThreadAffinityMask parseSetVerify2 = 0x17d; + EA::Thread::ThreadAffinityMask parseSetVerify3 = 0; + + EA::Thread::ThreadAffinityMask parseSetResult1 = parseSet(parseSetTest1, EA::StdC::Strlen(parseSetTest1)); + EA::Thread::ThreadAffinityMask parseSetResult2 = parseSet(parseSetTest2, EA::StdC::Strlen(parseSetTest2)); + EA::Thread::ThreadAffinityMask parseSetResult3 = parseSet(parseSetTest3, EA::StdC::Strlen(parseSetTest3)); + EATEST_VERIFY(parseSetVerify1 == parseSetResult1); + EATEST_VERIFY(parseSetVerify2 == parseSetResult2); + EATEST_VERIFY(parseSetVerify3 == parseSetResult3); + } + + { // Test getProcessCpuSetCGroup() + EATEST_VERIFY(!getProcessCpuSetCGroup(nullptr, 0)); + + char cgroup[40]; + EATEST_VERIFY(getProcessCpuSetCGroup(cgroup, sizeof(cgroup))); + EATEST_VERIFY(EA::StdC::Strlen(cgroup) != 0); + } + + { // Test getInstanceCpuSet() + EA::Thread::ThreadAffinityMask instanceCpuSet = getInstanceCpuSet(); + EATEST_VERIFY(instanceCpuSet != 0); + } + + { // Test initValidCpuAffinityMask() + EA::Thread::ThreadAffinityMask cpuAffinityMask = initValidCpuAffinityMask(); + EATEST_VERIFY(cpuAffinityMask != 0); + } +#endif + + return nErrorCount; +} + + +int TestCpuAvailableAffinityMask() +{ + int nErrorCount = 0; + + nErrorCount += TestCpuAvailableAffinityMaskBasic(); + + { // Test GetAvailableCpuAffinityMask() + EA::Thread::ThreadAffinityMask availableCpuAffinityMask = EA::Thread::GetAvailableCpuAffinityMask(); + + EATEST_VERIFY(availableCpuAffinityMask != 0); + EATEST_VERIFY(availableCpuAffinityMask != EA::Thread::kThreadAffinityMaskAny); + } + + return nErrorCount; +} diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestEnumerateThreads.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestEnumerateThreads.cpp new file mode 100644 index 00000000..011cfd0e --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestEnumerateThreads.cpp @@ -0,0 +1,244 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "TestThread.h" +#include +#include +#include +#include + +EA_DISABLE_ALL_VC_WARNINGS() +#include +EA_RESTORE_ALL_VC_WARNINGS() + +using namespace EA::Thread; +using namespace EA::Thread::detail; +using namespace EA::UnitTest; + +//------------------------------------------------------------------------------- +// Globals +static Semaphore gSemaphore; + +//------------------------------------------------------------------------------- +// +static intptr_t TestFunction1(void*) +{ + // Wait until we are signaled by the unit test to complete. + gSemaphore.Wait(); + return 0; +} + +//------------------------------------------------------------------------------- +// +int TestSimpleEnumerateThreads() +{ + int nErrorCount = 0; + static EA::Thread::AtomicInt snThreadStartCount; + snThreadStartCount = 0; + + auto threadEntry = [](void*) -> intptr_t + { + snThreadStartCount++; + + // Wait until we are signaled by the unit test to complete. + gSemaphore.Wait(); + return 0; + }; + + const size_t kMaxTestThreadEnumCount = 16; + ThreadEnumData enumData[kMaxTestThreadEnumCount]; + + // Prevents all threads from returning. + gSemaphore.Init(0); + + // Startup all the threads we want to monitor. + Thread threads[kMaxTestThreadEnumCount]; + for(size_t i = 0; i < kMaxTestThreadEnumCount; i++) + { + threads[i].Begin(threadEntry); + } + + // Give all the threads a chance to start up. + while(snThreadStartCount != kMaxTestThreadEnumCount) + EA::Thread::ThreadSleep(0); + + // Enumerate the active threads + size_t threadCount = EA::Thread::EnumerateThreads(enumData, EAArrayCount(enumData)); + EATEST_VERIFY_MSG(threadCount >= kMaxTestThreadEnumCount, "Incorrect number of threads reported."); + // Report("Enumerated (at least) %d threads. Found (%d).\n", kMaxTestThreadEnumCount, threadCount); + + for(size_t j = 0; j < kMaxTestThreadEnumCount; j++) + { + //Report("\tThread id: %s\n", ThreadIdToStringBuffer(enumData[j].mpThreadDynamicData->mhThread).c_str()); + if(enumData[j].mpThreadDynamicData == NULL) + continue; + + #if !defined(EA_PLATFORM_NX) // mpStackBase is null because the necessary POSIX API (pthread_getattr_np) isn't implemented by Nintendo + if(strcmp(enumData[j].mpThreadDynamicData->mName, "external") != 0) // Disabled because we can't guarantee across all platforms that a stack base is available. This will be fixed in a future release. + { + EATEST_VERIFY_MSG(enumData[j].mpThreadDynamicData->mpStackBase != NULL, "All thread meta data is expected to have the stack base address."); + } + #endif + enumData[j].Release(); + } + + // Signal the threads to complete. + gSemaphore.Post(kMaxTestThreadEnumCount); + + // Wait for all threads to complete. + for(size_t i = 0; i < kMaxTestThreadEnumCount; i++) + { + if(threads[i].GetStatus() != Thread::kStatusEnded) + threads[i].WaitForEnd(); + } + + return nErrorCount; +} + +//------------------------------------------------------------------------------- +// +int TestSimpleEnumerateThreads_KillThreadsEarly() +{ + int nErrorCount = 0; + + const size_t kMaxTestThreadEnumCount = 16; + ThreadEnumData enumData[kMaxTestThreadEnumCount]; + + // Prevents all threads from returning. + gSemaphore.Init(0); + + // Startup all the threads we want to monitor. + Thread threads[kMaxTestThreadEnumCount]; + for(size_t i = 0; i < kMaxTestThreadEnumCount; i++) + { + threads[i].Begin(TestFunction1); + } + EA::Thread::ThreadSleep(300); // Give all the threads a chance to start up. + + // Enumerate the active threads + size_t threadCount = EA::Thread::EnumerateThreads(enumData, EAArrayCount(enumData)); + EATEST_VERIFY_MSG(threadCount >= kMaxTestThreadEnumCount, "Incorrect number of threads reported."); + // Report("Enumerated (at least) %d threads. Found (%d).\n", kMaxTestThreadEnumCount, threadCount); + + // Signal the threads to complete. + gSemaphore.Post(kMaxTestThreadEnumCount); + EA::Thread::ThreadSleep(500); + + // Terminate the threads before the user explicitly releases them. + for(size_t i = 0; i < kMaxTestThreadEnumCount; i++) + { + if(threads[i].GetStatus() != Thread::kStatusEnded) + threads[i].WaitForEnd(); + } + + for(size_t j = 0; j < kMaxTestThreadEnumCount; j++) + { + //Report("\tThread id: %s\n", ThreadIdToStringBuffer(enumData[j].mpThreadDynamicData->mhThread).c_str()); + enumData[j].Release(); + } + + return nErrorCount; +} + +//------------------------------------------------------------------------------- +// +int TestEnumerateThreads_EnumerateMain() +{ + int nErrorCount = 0; + + const size_t kMaxTestThreadEnumCount = 16; + ThreadEnumData enumData[kMaxTestThreadEnumCount]; + + size_t threadCount = EA::Thread::EnumerateThreads(enumData, EAArrayCount(enumData)); + EATEST_VERIFY_MSG(threadCount >= 1, "No threads found. We are expecting at least the main thread to be reported."); + + int compare_result = strcmp(enumData[0].mpThreadDynamicData->mName, "external"); + EATEST_VERIFY_MSG(compare_result == 0, "Not an externally created thread."); + +#if EA_USE_CPP11_CONCURRENCY + ThreadUniqueId thisThreadId; + EAThreadGetUniqueId(thisThreadId); + + ThreadUniqueId uniqueThreadId = enumData[0].mpThreadDynamicData->mUniqueThreadId; + EATEST_VERIFY_MSG(uniqueThreadId == thisThreadId, "Did not return the threadId of this call context."); +#elif defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE + ThreadId enumThreadId = enumData[0].mpThreadDynamicData->mhThread; // No portable thread id available in the ThreadDynamicDataStructure. + EATEST_VERIFY_MSG(enumThreadId == EA::Thread::GetThreadId(), "Did not return the threadId of this call context."); +#else + ThreadId enumThreadId = enumData[0].mpThreadDynamicData->mThreadId; + EATEST_VERIFY_MSG(enumThreadId == EA::Thread::GetThreadId(), "Did not return the threadId of this call context."); +#endif + + return nErrorCount; +} + +//------------------------------------------------------------------------------- +// +EA_DISABLE_ALL_VC_WARNINGS() +#include +#include +#include +#include +EA_RESTORE_ALL_VC_WARNINGS() +#include + + +// TODO(rparolin): This forces the build-farm to timeout. Re-enable in the future. +// +// int TestHeavyLoadThreadRegisteration() +// { +// int nErrorCount = 0; + +// #ifdef EA_PLATFORM_MICROSOFT +// // Only tested on Windows because its a reported regression in tools/pipeline related +// // technologies leveraging a large number of non-eathread threads which would exhaust internal +// // tracking system. +// std::atomic isDone = false; +// EA::Thread::Mutex s_mutex; + +// { +// int loopCount = 170; // must exceed the value of EA::Thread::kMaxThreadDynamicDataCount. +// std::vector threads; +// while(loopCount--) +// { +// threads.emplace_back(std::thread([&] +// { +// while (!isDone) +// { +// // We lock an EA::Thread::Mutex because it used to force a +// // non-EAThread thread to be registered due to debug functionality +// // requesting a thread id. Verify that locking a mutex no longer requires +// // external thread registration by locking more threads that we can track. +// EA::Thread::AutoMutex _(s_mutex); +// } +// })); +// } + +// std::this_thread::sleep_for(std::chrono::milliseconds(100)); +// isDone = true; + +// for (auto& th : threads) +// th.join(); +// } +// #endif + +// return nErrorCount; +// } + + +//------------------------------------------------------------------------------- +// +int TestEnumerateThreads() +{ + int nErrorCount = 0; + + nErrorCount += TestSimpleEnumerateThreads(); + nErrorCount += TestSimpleEnumerateThreads_KillThreadsEarly(); + nErrorCount += TestEnumerateThreads_EnumerateMain(); + // nErrorCount += TestHeavyLoadThreadRegisteration(); + + return nErrorCount; +} + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThread.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThread.cpp new file mode 100644 index 00000000..661450e6 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThread.cpp @@ -0,0 +1,252 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#include "TestThread.h" +#include +#include +#include +#include +#include +#include +#include + +//Prevents false positive memory leaks on GCC/Clang platforms +#if defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG) + #define EA_MEMORY_GCC_USE_FINALIZE +#endif + +#ifndef EA_OPENSOURCE + #include + #include + #include +#endif + + +#ifdef EA_OPENSOURCE +void* operator new[](size_t size, const char* /*pName*/, int /*flags*/, + unsigned /*debugFlags*/, const char* /*file*/, int /*line*/) +{ + return operator new[](size); +} + +void* operator new[](size_t size, size_t /*alignment*/, size_t /*alignmentOffset*/, const char* /*pName*/, + int /*flags*/, unsigned /*debugFlags*/, const char* /*file*/, int /*line*/) +{ + return operator new[](size); +} +#endif + + +#ifdef EA_DLL + #include +#endif + +#if defined(EA_PLATFORM_MICROSOFT) + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() +#endif + +#if defined(EA_COMPILER_MSVC) && defined(EA_PLATFORM_MICROSOFT) + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() +#endif + +#include +#include +#include + + +/////////////////////////////////////////////////////////////////////////////// +// gTestLengthSeconds +// +unsigned int gTestLengthSeconds = 2; + + + +/////////////////////////////////////////////////////////////////////////////// +// EAThreadFailure +// +// This is called by EAThread's assert failure function. +// +static void EAThreadFailure(const char* pMessage, void* /*pContext*/) +{ + EA::UnitTest::IncrementGlobalErrorCount(1); + EA::UnitTest::Report("Thread test failure (EAThread assert): %s\n", pMessage); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TestThreadMisc +// +// To do: Move this to its own file. +// +#if defined(EA_PLATFORM_APPLE) + #include +#endif + +#if defined(EA_PLATFORM_POSIX) +#include +#endif + +bool IsSuperUser() +{ +#if defined(EA_PLATFORM_POSIX) && !defined(EA_PLATFORM_SONY) && !defined(EA_PLATFORM_NX) // PS4 is a POSIX machine but doesn't implement 'getuid'. + // http://pubs.opengroup.org/onlinepubs/009695399/functions/geteuid.html + // http://pubs.opengroup.org/onlinepubs/009695399/functions/getuid.html + uid_t uid = getuid(), euid = geteuid(); + return (uid == 0 || euid == 0); +#else + return true; +#endif +} + + + +int TestThreadGetThreadTimeMin() +{ + int nErrorCount(0); +#if defined(EA_PLATFORM_MICROSOFT) || defined(EA_PLATFORM_PS4) + EATEST_VERIFY_MSG(EA::Thread::GetThreadTime() >= EATHREAD_MIN_ABSOLUTE_TIME, "Reported GetThreadTime absolute time is less than EATHREAD_MIN_ABSOLUTE_TIME. You are going to have a bad time."); +#endif + return nErrorCount; +} + +int TestThreadMisc() +{ + int nErrorCount = 0; + // this is here because its intended to be the first test run. Since it depends on tick count since title start. + nErrorCount += TestThreadGetThreadTimeMin(); + + #if defined(EA_PLATFORM_APPLE) && EATHREAD_APPLE_GETMODULEINFO_ENABLED + if(!IsSuperUser()) + { + EA::EAMain::Report("Skipping GetModuleInfoApple test because we don't have sufficient system privileges.\n"); + return nErrorCount; + } + + EA::Thread::ModuleInfoApple moduleInfoApple[15]; + size_t n = EA::Thread::GetModuleInfoApple(moduleInfoApple, EAArrayCount(moduleInfoApple), NULL, true); + + #if defined(EA_PLATFORM_OSX) + EATEST_VERIFY(n > 0); + for(size_t i = 0; i < eastl::min(n, EAArrayCount(moduleInfoApple)); i++) + EA::UnitTest::Report("%s\n", moduleInfoApple[i].mPath); + #else + EA_UNUSED(n); + #endif + #endif + + return nErrorCount; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// EAMain +// +int EAMain(int argc, char** argv) +{ + using namespace EA::Thread; + using namespace EA::UnitTest; + + int nErrorCount(0); + + EA::EAMain::PlatformStartup(); + + gTestLengthSeconds = (unsigned int)(3 * EA::UnitTest::GetSystemSpeed()); + if(gTestLengthSeconds == 0) + gTestLengthSeconds = 1; + + // Set EAThread to route its errors to our own error reporting function. + EA::Thread::SetAssertionFailureFunction(EAThreadFailure, NULL); + + #if defined(EA_DLL) && defined(EA_MEMORY_ENABLED) && EA_MEMORY_ENABLED + EA::Allocator::InitSharedDllAllocator(); + #endif + + //Set EAThread to use the Default Allocator to keep track of our memory usage +#ifndef EA_OPENSOURCE + EA::Thread::SetAllocator(EA::Allocator::ICoreAllocator::GetDefaultAllocator()); +#endif + + // Print ThreadId for this primary thread. + const ThreadId threadId = GetThreadId(); + ReportVerbosity(1, "Primary thread ThreadId: %s\n", EAThreadThreadIdToString(threadId)); + + // Print SysThreadId for this primary thread. + const SysThreadId sysThreadId = GetSysThreadId(threadId); + ReportVerbosity(1, "Primary thread SysThreadId: %s\n", EAThreadSysThreadIdToString(sysThreadId)); + + // Print thread priority for this primary thread. + const int nPriority = EA::Thread::GetThreadPriority(); + ReportVerbosity(1, "Primary thread priority: %d\n", nPriority); + + const int nProcessorCount = EA::Thread::GetProcessorCount(); + ReportVerbosity(1, "Currently active virtual processor count: %d\n", nProcessorCount); + + #if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE && !EA_USE_CPP11_CONCURRENCY + const DWORD dwCurrentThreadId = GetCurrentThreadId(); // This is a system OS call. + EATEST_VERIFY_F(sysThreadId == dwCurrentThreadId, "GetSysThreadId failed. SysThreadId = %u, sys thread id = %u.\n", (unsigned)sysThreadId, (unsigned)dwCurrentThreadId); + #endif + + // Add the tests + TestApplication testSuite("EAThread Unit Tests", argc, argv); + + #if defined(EA_PLATFORM_NX) + // We are enabling round-robin scheduling on the Nintendo NX by setting the main thread to the lowest thread + // priority. The pthread interfaces inherits the main threads priority when creating new threads so this + // ensures all worker threads spawned in tests will be assigned equal time on cores. This is a documented + // feature of the NX thread scheduler. The default behaviour of the scheduler is to not give up their core to + // threads of equal priority which causes live locks in our tests. + // + // Reference: + // https://developer.nintendo.com/html/online-docs/nx-en/g1kr9vj6-en/document.html?doc=Packages/SDK/NintendoSDK/Documents/Package/contents/Pages/Page_83955697.html + EA::Thread::SetThreadPriority(EA::Thread::kThreadPriorityMin); + #endif + + testSuite.AddTest("Atomic", TestThreadAtomic); + testSuite.AddTest("Barrier", TestThreadBarrier); + testSuite.AddTest("Callstack", TestThreadCallstack); + testSuite.AddTest("Condition", TestThreadCondition); + testSuite.AddTest("EnumerateThreads", TestEnumerateThreads); + testSuite.AddTest("Futex", TestThreadFutex); + testSuite.AddTest("Misc", TestThreadMisc); + testSuite.AddTest("Mutex", TestThreadMutex); + testSuite.AddTest("RWMutex", TestThreadRWMutex); + testSuite.AddTest("RWSemaphore", TestThreadRWSemaLock); + testSuite.AddTest("RWSpinLock", TestThreadRWSpinLock); + testSuite.AddTest("Semaphore", TestThreadSemaphore); + testSuite.AddTest("SmartPtr", TestThreadSmartPtr); + testSuite.AddTest("SpinLock", TestThreadSpinLock); + testSuite.AddTest("Storage", TestThreadStorage); + testSuite.AddTest("Sync", TestThreadSync); + testSuite.AddTest("TestCpuAvailableAffinityMask", TestCpuAvailableAffinityMask); + testSuite.AddTest("Thread", TestThreadThread); + testSuite.AddTest("ThreadPool", TestThreadThreadPool); + + nErrorCount += testSuite.Run(); + +#ifndef EA_USE_CPP11_CONCURRENCY + // Verify the converted a EA::Thread::ThreadId to a EA::Thread::SysThreadId id matches this thread context id. + const ThreadId convertedThreadId = GetThreadId(sysThreadId); + EATEST_VERIFY_F(threadId == convertedThreadId , "GetThreadId failed to convert SysThreadId. ThreadId = %s, converted thread id = %s.\n", EAThreadThreadIdToString(threadId), EAThreadThreadIdToString(convertedThreadId)); +#endif + + EA::EAMain::PlatformShutdown(nErrorCount); + + return nErrorCount; +} + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThread.h b/src/thirdparty/ea/EAThread/test/thread/source/TestThread.h new file mode 100644 index 00000000..f395aa0f --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThread.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#ifndef TESTTHREAD_H +#define TESTTHREAD_H + + +extern unsigned int gTestLengthSeconds; +extern bool IsSuperUser(); + +// The maximum number of threads spawned during EAThread unit tests. +#ifndef EATHREAD_MAX_CONCURRENT_THREAD_COUNT + #if defined(EA_PLATFORM_DESKTOP) + #define EATHREAD_MAX_CONCURRENT_THREAD_COUNT 16 + #elif defined(EA_PLATFORM_MOBILE) + #define EATHREAD_MAX_CONCURRENT_THREAD_COUNT 4 + #elif defined(EA_PLATFORM_NX) + #define EATHREAD_MAX_CONCURRENT_THREAD_COUNT 2 // -1 core so we don't kick off the main thread + #else + #define EATHREAD_MAX_CONCURRENT_THREAD_COUNT 8 + #endif +#endif + + +int TestThreadSync(); +int TestThreadAtomic(); +int TestThreadCallstack(); +int TestThreadStorage(); +int TestThreadSpinLock(); +int TestThreadRWSpinLock(); +int TestThreadFutex(); +int TestThreadMutex(); +int TestThreadRWMutex(); +int TestThreadSemaphore(); +int TestThreadRWSemaLock(); +int TestThreadCondition(); +int TestThreadBarrier(); +int TestThreadThread(); +int TestThreadThreadPool(); +int TestThreadSmartPtr(); +int TestThreadMisc(); +int TestEnumerateThreads(); +int TestCpuAvailableAffinityMask(); + +#endif // Header include guard + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadAtomic.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadAtomic.cpp new file mode 100644 index 00000000..abe7469a --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadAtomic.cpp @@ -0,0 +1,1007 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include + +EA_DISABLE_VC_WARNING(4265 4365 4836 4571 4625 4626 4628 4193 4127 4548) +#include +EA_RESTORE_VC_WARNING() + +#if defined(_MSC_VER) + #pragma warning(disable: 4996) // This function or variable may be unsafe / deprecated. +#endif + +#include + + +using namespace EA::Thread; + + +#if EA_THREADS_AVAILABLE + +const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + +struct AWorkData32 +{ + volatile bool mbShouldQuit; + AtomicInt32 mnAtomicInteger1; + AtomicInt32 mnAtomicInteger2; + AtomicInt32 mnErrorCount; + + AWorkData32() : mbShouldQuit(false), + mnAtomicInteger1(0), mnAtomicInteger2(0), + mnErrorCount(0) {} +}; + + + +static intptr_t Atomic32TestThreadFunction1(void* pvWorkData) +{ + int nErrorCount = 0; + AWorkData32* pWorkData = (AWorkData32*)pvWorkData; + const ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "Atomic test function 1 created, thread id %s\n", EAThreadThreadIdToString(threadId)); + + // Do a series of operations, the final result of which is zero. + while(!pWorkData->mbShouldQuit) + { + ++pWorkData->mnAtomicInteger1; + ++pWorkData->mnAtomicInteger2; + --pWorkData->mnAtomicInteger1; + --pWorkData->mnAtomicInteger2; + pWorkData->mnAtomicInteger1 += 5; + pWorkData->mnAtomicInteger2 += 5; + pWorkData->mnAtomicInteger1 -= 5; + pWorkData->mnAtomicInteger2 -= 5; + pWorkData->mnAtomicInteger1++; + pWorkData->mnAtomicInteger2++; + pWorkData->mnAtomicInteger1--; + pWorkData->mnAtomicInteger2--; + ThreadCooperativeYield(); + } + + pWorkData->mnErrorCount += nErrorCount; + + EA::UnitTest::ReportVerbosity(1, "Atomic test function 1 exiting, thread id %s\n", EAThreadThreadIdToString(threadId)); + return 0; +} + + +static intptr_t Atomic32TestThreadFunction2(void* pvWorkData) +{ + int nErrorCount = 0; + AWorkData32* pWorkData = (AWorkData32*)pvWorkData; + const ThreadId threadId = GetThreadId(); + ThreadUniqueId threadUniqueId; + EAThreadGetUniqueId(threadUniqueId); + int32_t threadUniqueId32 = (int32_t)threadUniqueId; + + EA::UnitTest::ReportVerbosity(1, "Atomic test function 2 created, thread id %s\n", EAThreadThreadIdToString(threadId)); + + // Test the SetValueConditional function. We basically create a spinlock here. + while(!pWorkData->mbShouldQuit) + { + if(pWorkData->mnAtomicInteger1.SetValueConditional(threadUniqueId32, 0x11223344)) + { + EATEST_VERIFY_MSG(pWorkData->mnAtomicInteger1 == threadUniqueId32, "AtomicInt SetValueConditional failure."); + pWorkData->mnAtomicInteger1 = 0x11223344; + } + + ThreadCooperativeYield(); + } + + pWorkData->mnErrorCount += nErrorCount; + + EA::UnitTest::ReportVerbosity(1, "Atomic test function 2 exiting, thread id %s\n", EAThreadThreadIdToString(threadId)); + return 0; +} + + +struct AWorkData64 +{ + volatile bool mbShouldQuit; + AtomicInt64 mnAtomicInteger1; + AtomicInt64 mnAtomicInteger2; + AtomicInt64 mnErrorCount; + + AWorkData64() : mbShouldQuit(false), + mnAtomicInteger1(0), mnAtomicInteger2(0), + mnErrorCount(0) {} +}; + + +static intptr_t Atomic64TestThreadFunction1(void* pvWorkData) +{ + int nErrorCount = 0; + AWorkData64* pWorkData = (AWorkData64*)pvWorkData; + const ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 1 created, thread id %s\n", EAThreadThreadIdToString(threadId)); + + // Do a series of operations, the final result of which is zero. + while(!pWorkData->mbShouldQuit) + { + ++pWorkData->mnAtomicInteger1; + ++pWorkData->mnAtomicInteger2; + --pWorkData->mnAtomicInteger1; + --pWorkData->mnAtomicInteger2; + pWorkData->mnAtomicInteger1 += UINT64_C(0x0000000fffffffff); + pWorkData->mnAtomicInteger2 += UINT64_C(0x0000000ffffffffe); + pWorkData->mnAtomicInteger1 -= UINT64_C(0x0000000fffffffff); + pWorkData->mnAtomicInteger2 -= UINT64_C(0x0000000ffffffffe); + pWorkData->mnAtomicInteger1++; + pWorkData->mnAtomicInteger2++; + pWorkData->mnAtomicInteger1--; + pWorkData->mnAtomicInteger2--; + ThreadCooperativeYield(); + } + + pWorkData->mnErrorCount += nErrorCount; + + EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 1 exiting, thread id %s\n", EAThreadThreadIdToString(threadId)); + return 0; +} + + +static intptr_t Atomic64TestThreadFunction2(void* pvWorkData) +{ + int nErrorCount = 0; + AWorkData64* pWorkData = (AWorkData64*)pvWorkData; + const ThreadId threadId = GetThreadId(); + ThreadUniqueId threadUniqueId; + EAThreadGetUniqueId(threadUniqueId); + uint64_t threadUnqueId64 = (uint64_t)threadUniqueId | UINT64_C(0xeeeeddddffffffff); + + EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 2 created, thread id %s\n", EAThreadThreadIdToString(threadId)); + + // Test the SetValueConditional function. We basically create a spinlock here. + while(!pWorkData->mbShouldQuit) + { + if(pWorkData->mnAtomicInteger1.SetValueConditional(threadUnqueId64, 0x1122334455667788)) + { + EATEST_VERIFY_MSG(pWorkData->mnAtomicInteger1 == static_cast(threadUnqueId64), "AtomicInt64 SetValueConditional failure."); + pWorkData->mnAtomicInteger1.SetValue(0x1122334455667788); + } + + ThreadCooperativeYield(); + } + + pWorkData->mnErrorCount += nErrorCount; + + EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 2 exiting, thread id %s\n", EAThreadThreadIdToString(threadId)); + return 0; +} + + +static intptr_t Atomic64TestThreadFunction3(void* pvWorkData) +{ + int nErrorCount = 0; + AWorkData64* pWorkData = (AWorkData64*)pvWorkData; + const ThreadId threadId = GetThreadId(); + const uint64_t value0 = UINT64_C(0x0000000000000000); + const uint64_t value1 = UINT64_C(0xffffffffffffffff); + + EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 2 created, thread id %s\n", EAThreadThreadIdToString(threadId)); + + // Test the SetValueConditional function. + while(!pWorkData->mbShouldQuit) + { + pWorkData->mnAtomicInteger1.SetValueConditional(value0, value1); + uint64_t currentValue = pWorkData->mnAtomicInteger1.GetValue(); + EATEST_VERIFY_MSG((currentValue == value0) || (currentValue == value1), "AtomicInt64 SetValueConditional failure."); + + pWorkData->mnAtomicInteger1.SetValueConditional(value1, value0); + currentValue = pWorkData->mnAtomicInteger1.GetValue(); + EATEST_VERIFY_MSG((currentValue == value0) || (currentValue == value1), "AtomicInt64 SetValueConditional failure."); + + ThreadCooperativeYield(); + } + + pWorkData->mnErrorCount += nErrorCount; + + EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 2 exiting, thread id %s\n", EAThreadThreadIdToString(threadId)); + return 0; +} + +template +int TestSimpleAtomicOps() +{ + int nErrorCount = 0; + bool result = false; + + alignas(16) T value = 0; + alignas(16) T dest = 0; + alignas(16) T conditionFail = 4; + alignas(16) T conditionSucceed = 0; + + // AtomicGetValue + dest = 3; + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 3, "AtomicGetValue failure\n"); + + // AtomicSetValue + value = AtomicSetValue(&dest, 4); + EATEST_VERIFY_MSG(value == 3, "AtomicSetValue failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 4, "AtomicSetValue failure\n"); + + // AtomicFetchIncrement + value = AtomicFetchIncrement(&dest); + EATEST_VERIFY_MSG(value == 4, "AtomicFetchIncrement failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 5, "AtomicFetchIncrement failure\n"); + + // AtomicFetchDecrement + value = AtomicFetchDecrement(&dest); + EATEST_VERIFY_MSG(value == 5, "AtomicFetchDecrement failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 4, "AtomicFetchDecrement failure\n"); + + // AtomicFetchAdd + value = AtomicFetchAdd(&dest, 3); + EATEST_VERIFY_MSG(value == 4, "AtomicFetchAdd failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 7, "AtomicFetchAdd failure\n"); + + // AtomicFetchSub + value = AtomicFetchSub(&dest, 3); + EATEST_VERIFY_MSG(value == 7, "AtomicFetchSub failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 4, "AtomicFetchSub failure\n"); + value = AtomicFetchSub(&dest, T(-3)); + EATEST_VERIFY_MSG(value == 4, "AtomicFetchSub failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 7, "AtomicFetchSub failure\n"); + + // AtomicFetchOr + value = AtomicFetchOr(&dest, 8); + EATEST_VERIFY_MSG(value == 7, "AtomicFetchOr failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 15, "AtomicFetchOr failure\n"); + + // AtomicFetchAnd + value = AtomicFetchAnd(&dest, 3); + EATEST_VERIFY_MSG(value == 15, "AtomicFetchAnd failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 3, "AtomicFetchAnd failure\n"); + + // AtomicFetchXor + value = AtomicFetchXor(&dest, dest); + EATEST_VERIFY_MSG(value == 3, "AtomicFetchXor failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 0, "AtomicFetchXor failure\n"); + + // AtomicFetchSwap + value = AtomicFetchSwap(&dest, 5); + EATEST_VERIFY_MSG(value == 0, "AtomicFetchSwap failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 5, "AtomicFetchSwap failure\n"); + + // AtomicSetValueConditional + dest = 0; + value = 1; + conditionFail = 4; + conditionSucceed = 0; + + // Try to do conditional fetch swap which should fail + value = EA::Thread::AtomicFetchSwapConditional(&dest, 1, conditionFail); + EATEST_VERIFY_MSG(value != conditionFail, "AtomicFetchSwapConditional failure 0\n"); + EATEST_VERIFY_MSG(dest == 0, "AtomicFetchSwapConditional failure 1\n"); + + // Try to do conditional fetch swap which should succeed + value = EA::Thread::AtomicFetchSwapConditional(&dest, 1, conditionSucceed); + EATEST_VERIFY_MSG(value == conditionSucceed, "AtomicFetchSwapConditional failure 2\n"); + EATEST_VERIFY_MSG(dest == 1, "AtomicFetchSwapConditional failure 3\n"); + + // reset before the next test + dest = 0; + value = 1; + + // Try to do an update which should fail. + result = EA::Thread::AtomicSetValueConditional(&dest, value, conditionFail); + EATEST_VERIFY_MSG(!result, "AtomicSetValueConditional failure 0\n"); + EATEST_VERIFY_MSG(dest == 0, "AtomicSetValueConditional failure 1\n"); + + // Try to do an update which should succeed. + result = EA::Thread::AtomicSetValueConditional(&dest, value, conditionSucceed); + EATEST_VERIFY_MSG(result, "AtomicSetValueConditional failure 2\n"); + EATEST_VERIFY_MSG(dest == 1, "AtomicSetValueConditional failure 3\n"); + + return nErrorCount; +} + +template +inline int TestAtomicsSizeBoundaries() +{ + static_assert(eastl::is_floating_point::value == false, "atomic floats not supported"); + + int nErrorCount = 0; + bool result = false; + alignas(16) T value = 0, dest = 0; + + T max = eastl::numeric_limits::max(); + T lowest = eastl::numeric_limits::lowest(); + + + /// Test the max boundary + /// + value = AtomicSetValue(&dest, max); + EATEST_VERIFY_MSG(value == 0, "max failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == max, "max failure\n"); + + value = AtomicFetchIncrement(&dest); + EATEST_VERIFY_MSG(value == max, "max failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == lowest, "max failure\n"); + + value = AtomicFetchDecrement(&dest); + EATEST_VERIFY_MSG(value == lowest, "max failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == max, "max failure\n"); + + value = AtomicFetchAdd(&dest, 1); + EATEST_VERIFY_MSG(value == max, "max failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == lowest, "max failure\n"); + + value = AtomicFetchAnd(&dest, lowest); + EATEST_VERIFY_MSG(value == lowest, "max failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == lowest, "max failure\n"); + + value = AtomicFetchXor(&dest, lowest); + EATEST_VERIFY_MSG(value == lowest, "max failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 0, "max failure\n"); + + value = AtomicFetchSwap(&dest, lowest); + EATEST_VERIFY_MSG(value == 0, "max failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == lowest, "max failure\n"); + + // reset to zero + result = AtomicSetValueConditional(&dest, 0, lowest); + EATEST_VERIFY_MSG(result, "max failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 0, "max failure\n"); + + + + /// Test the lowest boundary + /// + value = AtomicSetValue(&dest, lowest); + EATEST_VERIFY_MSG(value == 0, "lowest failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == lowest, "lowest failure\n"); + + // decrement the lowest to ensure we rollover to the highest value + value = AtomicFetchDecrement(&dest); + EATEST_VERIFY_MSG(value == lowest, "lowest failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == max, "lowest failure\n"); + + + return nErrorCount; +} + +template +inline int TestAtomicsConstFetch() +{ + int nErrorCount = 0; + + { + alignas(16) const T value = 13; + auto r = AtomicGetValue(&value); + EATEST_VERIFY_MSG(r == value, "failure\n"); + } + + { + struct Foo + { + Foo(uint32_t n) : baz(n) {} + uint32_t getBaz() const { return AtomicGetValue(&this->baz); } + uint32_t baz; + }; + + Foo foo(42); + auto r = foo.getBaz(); + EATEST_VERIFY_MSG(r == 42, "failure\n"); + } + + return nErrorCount; +} + +template +int TestAtomicIntT() +{ + int nErrorCount = 0; + + AtomicInt atomicInt = 0; + + ++atomicInt; + --atomicInt; + atomicInt += 5; + atomicInt -= 5; + atomicInt++; + atomicInt--; + + EATEST_VERIFY(atomicInt == 0); + + return nErrorCount; +} + +template +int TestNonMemberAtomics() +{ + int nErrorCount = 0; + nErrorCount += TestSimpleAtomicOps(); + nErrorCount += TestAtomicsSizeBoundaries(); + nErrorCount += TestAtomicsConstFetch(); + return nErrorCount; +} + +#endif // #if EA_THREADS_AVAILABLE + + +int TestThreadAtomic() +{ + int nErrorCount(0); + + { // Initial tests of 128 bit atomics + #if EATHREAD_ATOMIC_128_SUPPORTED // This will be true only for 64+ bit platforms. + + // To consider: Use __int128_t on GCC for GCC >= 4.1 + + EA_ALIGN(128) int64_t dest128[2] = { 0, 0 }; + EA_ALIGN(128) int64_t value128[2] = { 1, 2 }; + EA_ALIGN(128) int64_t condition128Fail[2] = { 4, 5 }; + EA_ALIGN(128) int64_t condition128Succeed[2] = { 0, 0 }; + bool result; + + // Try to do an update which should fail. + result = EA::Thread::AtomicSetValueConditionall28(dest128, value128, condition128Fail); + + EATEST_VERIFY_MSG(!result, "AtomicSetValueConditional failure: result should have been false.\n"); + EATEST_VERIFY_F((dest128[0] == 0) && (dest128[1] == 0), "AtomicSetValueConditional failure: dest128[0]:%I64d dest128[1]:%I64d\n", dest128[0], dest128[1]); + EATEST_VERIFY_F((value128[0] == 1) && (value128[1] == 2), "AtomicSetValueConditional failure: value128[0]:%I64d value128[1]:%I64d\n", value128[0], value128[1]); + EATEST_VERIFY_F((condition128Fail[0] == 4) && (condition128Fail[1] == 5), "AtomicSetValueConditional failure: condition128Fail[0]:%I64d condition128Fail[1]:%I64d\n", condition128Fail[0], condition128Fail[1]); + EATEST_VERIFY_F((condition128Succeed[0] == 0) && (condition128Succeed[1] == 0), "AtomicSetValueConditional failure: condition128Succeed[0]:%I64d condition128Succeed[1]:%I64d\n", condition128Succeed[0], condition128Succeed[1]); + + // Try to do an update which should succeed. + // VC++ for VS2010 misgenerates the atomic code below in optimized builds, by passing what appears to be the wrong value for dest128 to AtomicSetValueConditional128. + // We added some diagnostic code and now the compiler does the right thing. I (Paul Pedriana) wonder if the problem is related to + // the alignment specification for dest, which must be 128 byte aligned for AtomicSetValueConditional128 (cmpxchg16b) to work. + // The dest address is indeed being aligned to 16 bytes, so that's not the problem. + EA::UnitTest::ReportVerbosity(1, "%p %p %p\n", dest128, value128, condition128Succeed); + result = EA::Thread::AtomicSetValueConditionall28(dest128, value128, condition128Succeed); + + EATEST_VERIFY_MSG(result, "AtomicSetValueConditional failure: result should have been true.\n"); + EATEST_VERIFY_F((dest128[0] == 1) && (dest128[1] == 2), "AtomicSetValueConditional failure: dest128:%p dest128[0]:%I64d dest128[1]:%I64d\n", dest128, dest128[0], dest128[1]); + EATEST_VERIFY_F((value128[0] == 1) && (value128[1] == 2), "AtomicSetValueConditional failure: value128:%p value128[0]:%I64d value128[1]:%I64d\n", value128, value128[0], value128[1]); + EATEST_VERIFY_F((condition128Fail[0] == 4) && (condition128Fail[1] == 5), "AtomicSetValueConditional failure: condition128Fail:%p condition128Fail[0]:%I64d condition128Fail[1]:%I64d\n", condition128Fail, condition128Fail[0], condition128Fail[1]); + EATEST_VERIFY_F((condition128Succeed[0] == 0) && (condition128Succeed[1] == 0), "AtomicSetValueConditional failure: condition128Succeed:%p condition128Succeed[0]:%I64d condition128Succeed[1]:%I64d\n", condition128Succeed, condition128Succeed[0], condition128Succeed[1]); + + + #if defined(EA_COMPILER_GNUC) // GCC defines __int128_t as a built-in type. + + __int128_t dest; + __int128_t value; + __int128_t conditionFail; + __int128_t conditionSucceed; + + // AtomicGetValue + dest = 3; + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 3, "AtomicGetValue[128] failure\n"); + + // AtomicSetValue + AtomicSetValue(&dest, 4); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 4, "AtomicSetValue[128] failure\n"); + + // AtomicIncrement + value = AtomicIncrement(&dest); + EATEST_VERIFY_MSG(value == 5, "AtomicIncrement[128] failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 5, "AtomicIncrement[128] failure\n"); + + // AtomicDecrement + value = AtomicDecrement(&dest); + EATEST_VERIFY_MSG(value == 4, "AtomicDecrement[128] failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 4, "AtomicDecrement[128] failure\n"); + + // AtomicAdd + value = AtomicAdd(&dest, 3); + EATEST_VERIFY_MSG(value == 7, "AtomicAdd[128] failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 7, "AtomicAdd[128] failure\n"); + + // AtomicOr + value = AtomicOr(&dest, 8); + EATEST_VERIFY_MSG(value == 15, "AtomicOr[128] failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 15, "AtomicOr[128] failure\n"); + + // AtomicAnd + value = AtomicAnd(&dest, 3); + EATEST_VERIFY_MSG(value == 3, "AtomicAnd[128] failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 3, "AtomicAnd[128] failure\n"); + + // AtomicXor + value = AtomicXor(&dest, dest); + EATEST_VERIFY_MSG(value == 0, "AtomicXor[128] failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 0, "AtomicXor[128] failure\n"); + + // AtomicSwap + value = AtomicSwap(&dest, 5); + EATEST_VERIFY_MSG(value == 0, "AtomicSwap[128] failure\n"); + value = AtomicGetValue(&dest); + EATEST_VERIFY_MSG(value == 5, "AtomicSwap[128] failure\n"); + + // AtomicSetValueConditional + dest = 0; + value = 1; + conditionFail = 4; + conditionSucceed = 0; + + // Try to do an update which should fail. + result = EA::Thread::AtomicSetValueConditional(&dest, value, conditionFail); + + EATEST_VERIFY_MSG(!result, "AtomicSetValueConditional failure 0\n"); + EATEST_VERIFY_MSG(dest == 0, "AtomicSetValueConditional failure 1\n"); + + // Try to do an update which should succeed. + result = EA::Thread::AtomicSetValueConditional(&dest, value, conditionSucceed); + + EATEST_VERIFY_MSG(result, "AtomicSetValueConditional failure 2\n"); + EATEST_VERIFY_MSG(dest == 1, "AtomicSetValueConditional failure 3\n"); + + if(nErrorCount != 0) + return nErrorCount; + + #endif + + #endif + } + + { // Basic single-threaded Atomic test. + AtomicInt32 i32(1); + AtomicUint32 u32(1); + + EATEST_VERIFY_MSG(i32.GetValue() == 1, "AtomicInt32 failure."); + EATEST_VERIFY_MSG(u32.GetValue() == 1, "AtomicUint32 failure."); + + char buffer[64]; + sprintf(buffer, "%d %u", (signed int)i32.GetValue(), (unsigned int)u32.GetValue()); + EATEST_VERIFY_MSG(strcmp(buffer, "1 1") == 0, "AtomicInt32 failure."); + + // Copy ctor/operator=. + AtomicInt32 i32CopyA(i32); + AtomicInt32 i32CopyB(i32CopyA); + i32CopyA = i32CopyB; + + sprintf(buffer, "%d %d", (signed int)i32CopyA.GetValue(), (signed int)i32CopyB.GetValue()); + EATEST_VERIFY_MSG(strcmp(buffer, "1 1") == 0, "AtomicInt32 failure."); + + // Test platforms that support 64 bits.. + AtomicInt64 i64(1); + AtomicUint64 u64(1); + + sprintf(buffer, "%.0f %.0f", (double)i64.GetValue(), (double)u64.GetValue()); + EATEST_VERIFY_MSG(strcmp(buffer, "1 1") == 0, "AtomicInt64 failure."); + + // Copy ctor/operator=. + AtomicInt64 i64CopyA(i64); + AtomicInt64 i64CopyB(i64CopyA); + i64CopyA = i64CopyB; + + sprintf(buffer, "%d %d", (signed int)i64CopyA.GetValue(), (signed int)i64CopyB.GetValue()); + EATEST_VERIFY_MSG(strcmp(buffer, "1 1") == 0, "AtomicInt64 failure."); + + bool result = i64.SetValueConditional(2, 99999); // This should not set the value to 2. + EATEST_VERIFY_MSG(!result && (i64.GetValue() == 1), "AtomicInt64 failure."); + + i64.SetValueConditional(2, 1); // This should set the value to 2. + EATEST_VERIFY_MSG(!result && (i64.GetValue() == 2), "AtomicInt64 failure."); + } + + { // Basic single-threaded AtomicInt32 test. + AtomicInt32 i(0); // Note that this assignment goes through AtomicInt32 operator=(). + AtomicInt32::ValueType x; + + EATEST_VERIFY_MSG(i == 0, "AtomicInt32 failure."); + + ++i; + i++; + --i; + i--; + i += 7; + i -= 3; + EATEST_VERIFY_MSG(i == 4, "AtomicInt32 failure."); + + i = 2; + x = i.GetValue(); + EATEST_VERIFY_MSG(x == 2, "AtomicInt32 failure."); + + i.Increment(); + i.Decrement(); + i.Add(5); + i.Add(-2); + EATEST_VERIFY_MSG(i == 5, "AtomicInt32 failure."); + + i.SetValue(6); + EATEST_VERIFY_MSG(i == 6, "AtomicInt32 failure."); + + bool bWasEqualTo10000 = i.SetValueConditional(3, 10000); + EATEST_VERIFY_MSG(!bWasEqualTo10000, "AtomicInt32 failure."); + + bool bWasEqualTo6 = i.SetValueConditional(3, 6); + EATEST_VERIFY_MSG(bWasEqualTo6, "AtomicInt32 failure."); + } + + { // Verify pre-increment/post-increment works as intended. + AtomicInt32 i32(0); + AtomicInt32::ValueType x32; + + // ValueType SetValue(ValueType n) + // Safely sets a new value. Returns the old value. + x32 = i32.SetValue(1); + EATEST_VERIFY_MSG(x32 == 0, "AtomicInt return value failure."); + + // ValueType Increment() + // Safely increments the value. Returns the new value. + x32 = i32.Increment(); + EATEST_VERIFY_MSG(x32 == 2, "AtomicInt return value failure."); + + // ValueType Decrement() + // Safely decrements the value. Returns the new value. + x32 = i32.Decrement(); + EATEST_VERIFY_MSG(x32 == 1, "AtomicInt return value failure."); + + // ValueType Add(ValueType n) + // Safely adds a value, which can be negative. Returns the new value. + x32 = i32.Add(35); + EATEST_VERIFY_MSG(x32 == 36, "AtomicInt return value failure."); + + // ValueType operator=(ValueType n) + // Safely assigns the value. Returns the new value. + x32 = (i32 = 17); + EATEST_VERIFY_MSG(x32 == 17, "AtomicInt return value failure."); + + // ValueType operator+=(ValueType n) + // Safely adds a value, which can be negative. Returns the new value. + x32 = (i32 += 3); + EATEST_VERIFY_MSG(x32 == 20, "AtomicInt return value failure."); + + // ValueType operator-=(ValueType n) + // Safely subtracts a value, which can be negative. Returns the new value. + x32 = (i32 -= 6); + EATEST_VERIFY_MSG(x32 == 14, "AtomicInt return value failure."); + + // ValueType operator++() + // pre-increment operator++ + x32 = ++i32; + EATEST_VERIFY_MSG(x32 == 15, "AtomicInt return value failure."); + EATEST_VERIFY_MSG(i32 == 15, "AtomicInt return value failure."); + + // ValueType operator++(int) + // post-increment operator++ + x32 = i32++; + EATEST_VERIFY_MSG(x32 == 15, "AtomicInt return value failure."); + EATEST_VERIFY_MSG(i32 == 16, "AtomicInt return value failure."); + + // ValueType operator--() + // pre-increment operator-- + x32 = --i32; + EATEST_VERIFY_MSG(x32 == 15, "AtomicInt return value failure."); + EATEST_VERIFY_MSG(i32 == 15, "AtomicInt return value failure."); + + // ValueType operator--(int) + // post-increment operator-- + x32 = i32--; + EATEST_VERIFY_MSG(x32 == 15, "AtomicInt return value failure."); + EATEST_VERIFY_MSG(i32 == 14, "AtomicInt return value failure."); + } + + + { // Verify pre-increment/post-increment works as intended. + AtomicInt64 i64(0); + AtomicInt64::ValueType x64; + + // ValueType SetValue(ValueType n) + // Safely sets a new value. Returns the old value. + x64 = i64.SetValue(1); + EATEST_VERIFY_MSG(x64 == 0, "AtomicInt return value failure."); + + // ValueType Increment() + // Safely increments the value. Returns the new value. + x64 = i64.Increment(); + EATEST_VERIFY_MSG(x64 == 2, "AtomicInt return value failure."); + + // ValueType Decrement() + // Safely decrements the value. Returns the new value. + x64 = i64.Decrement(); + EATEST_VERIFY_MSG(x64 == 1, "AtomicInt return value failure."); + + // ValueType Add(ValueType n) + // Safely adds a value, which can be negative. Returns the new value. + x64 = i64.Add(35); + EATEST_VERIFY_MSG(x64 == 36, "AtomicInt return value failure."); + + // ValueType operator=(ValueType n) + // Safely assigns the value. Returns the new value. + x64 = (i64 = 17); + EATEST_VERIFY_MSG(x64 == 17, "AtomicInt return value failure."); + + // ValueType operator+=(ValueType n) + // Safely adds a value, which can be negative. Returns the new value. + x64 = (i64 += 3); + EATEST_VERIFY_MSG(x64 == 20, "AtomicInt return value failure."); + + // ValueType operator-=(ValueType n) + // Safely subtracts a value, which can be negative. Returns the new value. + x64 = (i64 -= 6); + EATEST_VERIFY_MSG(x64 == 14, "AtomicInt return value failure."); + + // ValueType operator++() + // pre-increment operator++ + x64 = ++i64; + EATEST_VERIFY_MSG(x64 == 15, "AtomicInt return value failure."); + EATEST_VERIFY_MSG(i64 == 15, "AtomicInt return value failure."); + + // ValueType operator++(int) + // post-increment operator++ + x64 = i64++; + EATEST_VERIFY_MSG(x64 == 15, "AtomicInt return value failure."); + EATEST_VERIFY_MSG(i64 == 16, "AtomicInt return value failure."); + + // ValueType operator--() + // pre-increment operator-- + x64 = --i64; + EATEST_VERIFY_MSG(x64 == 15, "AtomicInt return value failure."); + EATEST_VERIFY_MSG(i64 == 15, "AtomicInt return value failure."); + + // ValueType operator--(int) + // post-increment operator-- + x64 = i64--; + EATEST_VERIFY_MSG(x64 == 15, "AtomicInt return value failure."); + EATEST_VERIFY_MSG(i64 == 14, "AtomicInt return value failure."); + } + + { // Basic single-threaded AtomicPointer test. + AtomicPointer p(NULL); + AtomicPointer::PointerValueType pTemp; + + EATEST_VERIFY_MSG(p.GetValue() == NULL, "AtomicPointer failure."); + + ++p; + p++; + --p; + p--; + p += 7; + p -= 3; + EATEST_VERIFY_MSG(p == (void*)4, "AtomicPointer failure."); + + p = (void*)2; + pTemp = p.GetValue(); + EATEST_VERIFY_MSG((uintptr_t)pTemp == 2, "AtomicPointer failure."); + + p.Increment(); + p.Decrement(); + p.Add(5); + p.Add(-2); + EATEST_VERIFY_MSG(p == (void*)5, "AtomicPointer failure."); + + p.SetValue((void*)6); + EATEST_VERIFY_MSG(p == (void*)6, "AtomicPointer failure."); + + bool bWasEqualTo10000 = p.SetValueConditional((void*)3, (void*)10000); + EATEST_VERIFY_MSG(!bWasEqualTo10000, "AtomicPointer failure."); + + bool bWasEqualTo6 = p.SetValueConditional((void*)3, (void*)6); + EATEST_VERIFY_MSG(bWasEqualTo6, "AtomicPointer failure."); + } + + { + AtomicInt32 gA, gB; + + gA = gB = 0; + ++gA; + ++gB; + gA = gB = 0; + + EATEST_VERIFY_MSG((gA == 0) && (gB == 0), "AtomicInt32 operator= failure."); + } + + #if EA_THREADS_AVAILABLE + + { // Multithreaded test 1 + AWorkData32 workData32; + + const int kThreadCount(kMaxConcurrentThreadCount - 1); + Thread thread[kThreadCount]; + Thread::Status status; + + for(int i(0); i < kThreadCount; i++) + thread[i].Begin(Atomic32TestThreadFunction1, &workData32); + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000); + + workData32.mbShouldQuit = true; + + for(int i(0); i < kThreadCount; i++) + { + status = thread[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != EA::Thread::Thread::kStatusRunning, "Atomic/Thread failure."); + } + + // In the end, the sum must be zero. + EATEST_VERIFY_MSG(workData32.mnAtomicInteger1 == 0, "Atomic/Thread failure."); + EATEST_VERIFY_MSG(workData32.mnAtomicInteger2 == 0, "Atomic/Thread failure."); + + nErrorCount += (int)workData32.mnErrorCount; + } + + { // Multithreaded test 2 + AWorkData32 workData32; + const int kThreadCount(kMaxConcurrentThreadCount - 1); + Thread thread[kThreadCount]; + Thread::Status status; + + for(int i(0); i < kThreadCount; i++) + thread[i].Begin(Atomic32TestThreadFunction2, &workData32); + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000); + + workData32.mbShouldQuit = true; + + for(int i(0); i < kThreadCount; i++) + { + status = thread[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != EA::Thread::Thread::kStatusRunning, "Atomic/Thread failure."); + } + + nErrorCount += (int)workData32.mnErrorCount; + } + + + { // Multithreaded test 1 + AWorkData64 workData64; + + const int kThreadCount(kMaxConcurrentThreadCount - 1); + Thread thread[kThreadCount]; + Thread::Status status; + + for(int i(0); i < kThreadCount; i++) + thread[i].Begin(Atomic64TestThreadFunction1, &workData64); + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000); + + workData64.mbShouldQuit = true; + + for(int i(0); i < kThreadCount; i++) + { + status = thread[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != EA::Thread::Thread::kStatusRunning, "Atomic/Thread failure."); + } + + // In the end, the sum must be zero. + EATEST_VERIFY_MSG(workData64.mnAtomicInteger1 == 0, "Atomic/Thread failure."); + EATEST_VERIFY_MSG(workData64.mnAtomicInteger2 == 0, "Atomic/Thread failure."); + + nErrorCount += (int)workData64.mnErrorCount; + } + + { // Multithreaded test 2 + AWorkData64 workData64; + const int kThreadCount(kMaxConcurrentThreadCount - 1); + Thread thread[kThreadCount]; + Thread::Status status; + + for(int i(0); i < kThreadCount; i++) + thread[i].Begin(Atomic64TestThreadFunction2, &workData64); + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000); + + workData64.mbShouldQuit = true; + + for(int i(0); i < kThreadCount; i++) + { + status = thread[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != EA::Thread::Thread::kStatusRunning, "Atomic/Thread failure."); + } + + nErrorCount += (int)workData64.mnErrorCount; + } + + { // Multithreaded test 3 + AWorkData64 workData64; + const int kThreadCount(kMaxConcurrentThreadCount - 1); + Thread thread[kThreadCount]; + Thread::Status status; + + for(int i(0); i < kThreadCount; i++) + thread[i].Begin(Atomic64TestThreadFunction3, &workData64); + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000); + + workData64.mbShouldQuit = true; + + for(int i(0); i < kThreadCount; i++) + { + status = thread[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != EA::Thread::Thread::kStatusRunning, "Atomic/Thread failure."); + } + + nErrorCount += (int)workData64.mnErrorCount; + } + + #endif + + { + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + nErrorCount += TestAtomicIntT(); + } + + // Non-Member Atomics Tests + { + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + nErrorCount += TestNonMemberAtomics(); + } + + + return nErrorCount; +} + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadBarrier.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadBarrier.cpp new file mode 100644 index 00000000..26879f79 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadBarrier.cpp @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include +#include + + +using namespace EA::Thread; + + +const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + + +struct BT_WorkData +{ + Barrier* mpBarrier; + int mnSleepTime; + AtomicInt32 mnErrorCount; + + BT_WorkData(Barrier* pBarrier = NULL, int nSleepTime = 0) + : mpBarrier(pBarrier), mnSleepTime(nSleepTime), mnErrorCount(0) {} +}; + + + +static intptr_t BT_ConsumerFunction(void* pvWorkData) +{ + int nErrorCount = 0; + BT_WorkData* pWorkData = (BT_WorkData*)pvWorkData; + const ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "Barrier consumer test function created: %s\n", EAThreadThreadIdToString(threadId)); + + // Here we would actually do the job, but printing 'job done' is enough in itself. + ThreadSleep((ThreadTime)pWorkData->mnSleepTime); + EA::UnitTest::ReportVerbosity(1, "Job done by thread %s.\n", EAThreadThreadIdToString(threadId)); + EA::UnitTest::ReportVerbosity(1, "Start synchronizing by thread %s.\n", EAThreadThreadIdToString(threadId)); + const Barrier::Result result = pWorkData->mpBarrier->Wait(GetThreadTime() + 10000); + + if(result == Barrier::kResultPrimary) + { + // This is the first thread to be released: call producer function + EA::UnitTest::ReportVerbosity(1, "Serial execution at Barrier by thread %s\n", EAThreadThreadIdToString(threadId)); + } + else if(result == Barrier::kResultTimeout) + { + EA::UnitTest::Report("Barrier time-out by thread %s\n", EAThreadThreadIdToString(threadId)); + nErrorCount++; + } + else if(result == Barrier::kResultError) + { + EA::UnitTest::Report("Barrier error in thread %s\n", EAThreadThreadIdToString(threadId)); + nErrorCount++; + } + + pWorkData->mnErrorCount += nErrorCount; + + ThreadSleep((ThreadTime)pWorkData->mnSleepTime); + + EA::UnitTest::ReportVerbosity(1, "Job synchronized by thread %s.\n", EAThreadThreadIdToString(threadId)); + return 0; +} + + +static intptr_t BT_ConsumerFunction2(void * pvWorkData) +{ + ((Barrier*)pvWorkData)->Wait(); + ((Barrier*)pvWorkData)->Wait(); + return 0; +} + + +int TestThreadBarrier() +{ + int nErrorCount(0); + + #if EA_THREADS_AVAILABLE + + { + Thread::Status status; + const int kThreadCount(kMaxConcurrentThreadCount - 1); + BT_WorkData workData[kThreadCount]; + Thread threads[kThreadCount]; + ThreadId threadId[kThreadCount]; + Barrier barrier(kThreadCount); + int i; + + for(i = 0; i < kThreadCount; i++) + { + workData[i].mpBarrier = &barrier; + workData[i].mnSleepTime = (i + 1) * 500; + } + + for(i = 0; i < kThreadCount; i++) + { + threadId[i] = threads[i].Begin(BT_ConsumerFunction, &workData[i]); + EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Barrier/Thread failure: Couldn't create thread."); + } + + EA::UnitTest::ThreadSleepRandom(2000, 2000); + + for(i = 0; i < kThreadCount; i++) + { + if(threadId[i] != kThreadIdInvalid) + { + status = threads[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Barrier/Thread failure."); + } + + nErrorCount += workData[i].mnErrorCount; + } + } + + { + Thread threads[2]; + ThreadId threadId[2]; + Barrier barrier(2); + Thread::Status status; + + threadId[0] = threads[0].Begin(BT_ConsumerFunction2, &barrier); + EATEST_VERIFY_MSG(threadId[0] != kThreadIdInvalid, "Barrier/Thread failure: Couldn't create thread."); + + threadId[1] = threads[1].Begin(BT_ConsumerFunction2, &barrier); + EATEST_VERIFY_MSG(threadId[1] != kThreadIdInvalid, "Barrier/Thread failure: Couldn't create thread."); + + if(threadId[0] != kThreadIdInvalid) + { + status = threads[0].WaitForEnd(); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Barrier/Thread failure."); + } + + if(threadId[1] != kThreadIdInvalid) + { + status = threads[1].WaitForEnd(); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Barrier/Thread failure."); + } + } + + #endif + + return nErrorCount; +} + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadCallstack.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadCallstack.cpp new file mode 100644 index 00000000..479fac88 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadCallstack.cpp @@ -0,0 +1,64 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef _MSC_VER +#pragma warning(push, 0) +#include +#endif + + +int TestThreadCallstack() +{ + int nErrorCount(0); + + + #if defined(EA_PLATFORM_MICROSOFT) + // bool ThreadHandlesAreEqual(intptr_t threadId1, intptr_t threadId2); + // uint32_t GetThreadIdFromThreadHandle(intptr_t threadId); + #endif + + // To do: Implement tests for the following for supporting platforms. + // bool GetCallstackContext(CallstackContext& context, intptr_t threadId = 0); + // bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId = 0); + // void GetCallstackContext(CallstackContext& context, const Context* pContext = NULL); + // size_t GetModuleFromAddress(const void* pAddress, char* pModuleFileName, size_t moduleNameCapacity); + // ModuleHandle GetModuleHandleFromAddress(const void* pAddress); + + // EA::Thread::CallstackContext context; + // EA::Thread::GetCallstackContext(context, EA::Thread::GetThreadId()); + // EATEST_VERIFY(context.mRIP != 0); + // EATEST_VERIFY(context.mRSP != 0); + + // To consider: Test SetStackBase. It's not simple because SetStackBase is a backup for + // when GetStackBase's default functionality doesn't happen to work. + // void SetStackBase(void* pStackBase); + // void SetStackBase(uintptr_t pStackBase){ SetStackBase((void*)pStackBase); } + + void* pStackBase = EA::Thread::GetStackBase(); + void* pStackLimit = EA::Thread::GetStackLimit(); + + if(pStackBase && pStackLimit) + { + EATEST_VERIFY((uintptr_t)&nErrorCount < (uintptr_t)pStackBase); + EATEST_VERIFY((uintptr_t)&nErrorCount > (uintptr_t)pStackLimit); + } + + return nErrorCount; +} diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadCondition.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadCondition.cpp new file mode 100644 index 00000000..69f85b02 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadCondition.cpp @@ -0,0 +1,335 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace EA::Thread; + + +/////////////////////////////////////////////////////////////////////////////// +// EATHREAD_INTERPROCESS_CONDITION_SUPPORTED +// +#ifndef EATHREAD_INTERPROCESS_CONDITION_SUPPORTED + #if defined(EA_PLATFORM_MICROSOFT) || defined(EA_PLATFORM_LINUX) + #define EATHREAD_INTERPROCESS_CONDITION_SUPPORTED 1 + #else + #define EATHREAD_INTERPROCESS_CONDITION_SUPPORTED 0 + #endif +#endif + + +const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + + +struct TMWorkData +{ + volatile bool mbProducersShouldQuit; + volatile bool mbConsumersShouldQuit; + EA::Thread::simple_list mJobList; + Condition mCondition; + Mutex mMutex; + int mnLastJobID; + int mnConditionTimeout; + AtomicInt32 mnTotalJobsCreated; + AtomicInt32 mnTotalJobsCompleted; + + TMWorkData( const ConditionParameters* pCondParams ) : mbProducersShouldQuit(false), mbConsumersShouldQuit(false), mCondition( pCondParams ), + mMutex(NULL, true), mnLastJobID(0), mnConditionTimeout(60000), mnTotalJobsCreated(0), mnTotalJobsCompleted(0) + { + // Empty + } + + // define copy ctor and assignment operator + // so the compiler does define them intrisically + TMWorkData(const TMWorkData& rhs); // copy constructor + TMWorkData& operator=(const TMWorkData& rhs); // assignment operator +}; + + +static intptr_t ProducerFunction(void* pvWorkData) +{ + int nErrorCount = 0; + TMWorkData* pWorkData = (TMWorkData*)pvWorkData; + ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "Condition producer test function created: %s\n", EAThreadThreadIdToString(threadId)); + + EAReadWriteBarrier(); + + while(!pWorkData->mbProducersShouldQuit) + { + EA::UnitTest::ThreadSleepRandom(100, 200); + pWorkData->mMutex.Lock(); + + for(int i(0), iEnd(rand() % 3); i < iEnd; i++) + { + const int nJob(++pWorkData->mnLastJobID); + pWorkData->mJobList.push_back(nJob); + ++pWorkData->mnTotalJobsCreated; + EA::UnitTest::ReportVerbosity(1, "Job %d created by %s.\n", nJob, EAThreadThreadIdToString(threadId)); + ThreadCooperativeYield(); // Used by cooperative threading platforms. + } + + pWorkData->mMutex.Unlock(); + pWorkData->mCondition.Signal(false); + ThreadCooperativeYield(); // Used by cooperative threading platforms. + } + + EA::UnitTest::ReportVerbosity(1, "Producer exiting: %s.\n", EAThreadThreadIdToString(threadId)); + + return nErrorCount; +} + +static intptr_t ProducerFunction_DoesNotSignal(void* pvWorkData) +{ + int nErrorCount = 0; + TMWorkData* pWorkData = (TMWorkData*)pvWorkData; + ThreadId threadId = GetThreadId(); + EA_UNUSED(pWorkData); + + EA::UnitTest::ReportVerbosity(1, "Condition producer (does not signal) test function created: %s\n", EAThreadThreadIdToString(threadId)); + + // Intentionally do nothing here. We are testing the conditional variable time out code path by + // ensuring we do not signal the Consumer that any work has been added into the queue for them + // to consume therefor explicitly causing a condition variable timeout. + + EA::UnitTest::ReportVerbosity(1, "Producer (does not signal) exiting: %s.\n", EAThreadThreadIdToString(threadId)); + + return nErrorCount; +} + +static intptr_t ConsumerFunction(void* pvWorkData) +{ + int nErrorCount = 0; + TMWorkData* pWorkData = (TMWorkData*)pvWorkData; + ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "Condition producer test function created: %s\n", EAThreadThreadIdToString(threadId)); + + pWorkData->mMutex.Lock(); + + do{ + if(!pWorkData->mJobList.empty()) + { + const int nJob = pWorkData->mJobList.front(); + pWorkData->mJobList.pop_front(); + pWorkData->mMutex.Unlock(); + + ThreadCooperativeYield(); // Used by cooperative threading platforms. + + // Here we would actually do the job, but printing 'job done' is enough in itself. + ++pWorkData->mnTotalJobsCompleted; + EA::UnitTest::ReportVerbosity(1, "Job %d done by %s.\n", nJob, EAThreadThreadIdToString(threadId)); + + pWorkData->mMutex.Lock(); + } + else + { + const ThreadTime timeoutAbsolute = GetThreadTime() + pWorkData->mnConditionTimeout; + const Condition::Result result = pWorkData->mCondition.Wait(&pWorkData->mMutex, timeoutAbsolute); + if((result != Condition::kResultOK) && pWorkData->mJobList.empty()) + break; + } + }while(!pWorkData->mbConsumersShouldQuit || !pWorkData->mJobList.empty()); + + pWorkData->mMutex.Unlock(); + + EA::UnitTest::ReportVerbosity(1, "Consumer exiting: %s.\n", EAThreadThreadIdToString(threadId)); + + return nErrorCount; +} + + +int TestThreadCondition() +{ + int nErrorCount(0); + + { // ctor tests + // We test various combinations of Mutex ctor and ConditionParameters. + // ConditionParameters(bool bIntraProcess = true, const char* pName = NULL); + // Condition(const ConditionParameters* pConditionParameters = NULL, bool bDefaultParameters = true); + + ConditionParameters cp1(true, NULL); + ConditionParameters cp2(true, "EATcp2"); + + #if EATHREAD_INTERPROCESS_CONDITION_SUPPORTED + ConditionParameters cp3(false, NULL); + ConditionParameters cp4(false, "EATcp4"); + #else + ConditionParameters cp3(true, NULL); + ConditionParameters cp4(true, "EATcp4"); + #endif + + // Create separate scopes below because some platforms are so + // limited that they can't create all of them at once. + { + Condition cond1(&cp1, false); + Condition cond2(&cp2, false); + Condition cond3(&cp3, false); + + cond1.Signal(); + cond2.Signal(); + cond3.Signal(); + } + { + Condition cond4(&cp4, false); + Condition cond5(NULL, true); + Condition cond6(NULL, false); + cond6.Init(&cp1); + + cond4.Signal(); + cond5.Signal(); + cond6.Signal(); + } + } + + + #if EA_THREADS_AVAILABLE + { + // test producer/consumer wait condition with intra-process condition + { + ConditionParameters exlusiveConditionParams( true, NULL ); + TMWorkData workData( &exlusiveConditionParams ); + Thread::Status status; + int i; + + const int kThreadCount(kMaxConcurrentThreadCount - 1); + Thread threadProducer[kThreadCount]; + ThreadId threadIdProducer[kThreadCount]; + Thread threadConsumer[kThreadCount]; + ThreadId threadIdConsumer[kThreadCount]; + + // Create producers and consumers. + for(i = 0; i < kThreadCount; i++) + { + threadIdProducer[i] = threadProducer[i].Begin(ProducerFunction, &workData); + EATEST_VERIFY_MSG(threadIdProducer[i] != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed."); + + threadIdConsumer[i] = threadConsumer[i].Begin(ConsumerFunction, &workData); + EATEST_VERIFY_MSG(threadIdConsumer[i] != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed."); + } + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000); + + // Wait for producers to quit. + workData.mbProducersShouldQuit = true; + for(i = 0; i < kThreadCount; i++) + { + if(threadIdProducer[i] != kThreadIdInvalid) + { + status = threadProducer[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for producer end failed."); + } + } + + EA::UnitTest::ThreadSleepRandom(2000, 2000); + + // Wait for consumers to quit. + workData.mbConsumersShouldQuit = true; + workData.mCondition.Signal(true); + for(i = 0; i < kThreadCount; i++) + { + if(threadIdConsumer[i] != kThreadIdInvalid) + { + status = threadConsumer[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for consumer end failed."); + } + } + + EATEST_VERIFY_MSG(workData.mnTotalJobsCreated == workData.mnTotalJobsCompleted, "Condition failure: Not all consumer work was processed."); + } + + // test single producer/ single consumer wait condition with inter-process condition + #if /*EATHREAD_INTERPROCESS_CONDITION_SUPPORTED*/ 0 // Disabled because this code fails on most platforms. + { + ConditionParameters sharedConditionParams( false, NULL ); + TMWorkData workData( &sharedConditionParams ); // Inter-process. + Thread::Status status; + Thread threadProducer; + Thread threadConsumer; + + ThreadId threadIdProducer = threadProducer.Begin(ProducerFunction, &workData); + EATEST_VERIFY_MSG(threadIdProducer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed."); + + ThreadId threadIdConsumer = threadConsumer.Begin(ConsumerFunction, &workData); + EATEST_VERIFY_MSG(threadIdConsumer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed."); + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000); + + // Wait for producer to quit. + workData.mbProducersShouldQuit = true; + + if(threadIdProducer != kThreadIdInvalid) + { + status = threadProducer.WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for producer end failed."); + } + + EA::UnitTest::ThreadSleepRandom(2000, 2000); + + // Wait for consumers to quit. + workData.mbConsumersShouldQuit = true; + workData.mCondition.Signal(true); + if(threadIdConsumer != kThreadIdInvalid) + { + status = threadConsumer.WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for consumer end failed."); + } + + EATEST_VERIFY_MSG(workData.mnTotalJobsCreated == workData.mnTotalJobsCompleted, "Condition failure: Not all consumer work was processed."); + } + #endif + + // Test conditional variable timeout explicitly by not sending a signal. + { + //::EA::EAMain::SetVerbosity(5); + ConditionParameters sharedConditionParams( true, NULL ); + TMWorkData workData( &sharedConditionParams ); // Inter-process. + workData.mnConditionTimeout = 3000; // timeout value has to be less than thread timeout value below. + Thread::Status status; + Thread threadProducer; + Thread threadConsumer; + + ThreadId threadIdProducer = threadProducer.Begin(ProducerFunction_DoesNotSignal, &workData); + EATEST_VERIFY_MSG(threadIdProducer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed."); + + ThreadId threadIdConsumer = threadConsumer.Begin(ConsumerFunction, &workData); + EATEST_VERIFY_MSG(threadIdConsumer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed."); + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000); + + // Wait for producer to quit. + if(threadIdProducer != kThreadIdInvalid) + { + status = threadProducer.WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for producer end failed."); + } + + EA::UnitTest::ThreadSleepRandom(2000, 2000); + + // Wait for consumers to quit. + workData.mbConsumersShouldQuit = true; + if(threadIdConsumer != kThreadIdInvalid) + { + status = threadConsumer.WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for consumer end failed."); + } + } + } + #endif + + return nErrorCount; +} + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadFutex.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadFutex.cpp new file mode 100644 index 00000000..2a8c7662 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadFutex.cpp @@ -0,0 +1,607 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(EA_PLATFORM_MICROSOFT) + #pragma warning(push, 0) + #include + #pragma warning(pop) +#endif + + + +#if defined(_MSC_VER) + #pragma warning(disable: 4996) // This function or variable may be unsafe / deprecated. +#endif + + +using namespace EA::Thread; + + +typedef intptr_t (*FutexTestThreadFunction)(void*); + +const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + + +/////////////////////////////////////////////////////////////////////////////// +// FWorkData +// +struct FWorkData +{ + volatile bool mbShouldQuit; + Futex mFutex; + char mUnused[7000]; // We intentionally put a big buffer between these. + int mThreadWithLock[kMaxConcurrentThreadCount]; + AtomicInt32 mThreadCount; + AtomicInt32 mnErrorCount; + + FWorkData() : mbShouldQuit(false), mFutex(), mThreadCount(0), mnErrorCount(0) { memset(mThreadWithLock, 0, sizeof(mThreadWithLock)); } + +private: + // Prevent default generation of these functions by not defining them + FWorkData(const FWorkData& rhs); // copy constructor + FWorkData& operator=(const FWorkData& rhs); // assignment operator +}; + + + + +/////////////////////////////////////////////////////////////////////////////// +// TestThreadFutexSingle +// +static int TestThreadFutexSingle() +{ + + // Formerly declared as a global because VC++ for XBox 360 was found to be possibly throwing + // away the atomic operations when it found that the futex was local to the function. + + // Now that Xbox 360 support has been removed, the futex has been tentatively moved back to + // local scope. Of course, if this problem manifests again, the change will be reversed. + Futex futexSingle; + + int nErrorCount(0); + + EA::UnitTest::ReportVerbosity(1, "\nSimple test...\n"); + + // Single-threaded tests + int nLockCount; + + EATEST_VERIFY_MSG(!futexSingle.HasLock(), "Futex failure."); + + nLockCount = futexSingle.GetLockCount(); + EATEST_VERIFY_MSG(nLockCount == 0, "Futex failure."); + + futexSingle.Lock(); + nLockCount = futexSingle.GetLockCount(); + EATEST_VERIFY_MSG(nLockCount == 1, "Futex failure."); + + EATEST_VERIFY_MSG(futexSingle.HasLock(), "Futex failure."); + + futexSingle.Lock(); + nLockCount = futexSingle.GetLockCount(); + EATEST_VERIFY_MSG(nLockCount == 2, "Futex failure."); + + futexSingle.Unlock(); + nLockCount = futexSingle.GetLockCount(); + EATEST_VERIFY_MSG(nLockCount == 1, "Futex failure."); + + futexSingle.Unlock(); + nLockCount = futexSingle.GetLockCount(); + EATEST_VERIFY_MSG(nLockCount == 0, "Futex failure."); + + futexSingle.Lock(); + nLockCount = futexSingle.GetLockCount(); + EATEST_VERIFY_MSG(nLockCount == 1, "Futex failure."); + + futexSingle.Unlock(); + nLockCount = futexSingle.GetLockCount(); + EATEST_VERIFY_MSG(nLockCount == 0, "Futex failure."); + + EATEST_VERIFY_MSG(!futexSingle.HasLock(), "Futex failure."); + + return nErrorCount; +} + + + + +/////////////////////////////////////////////////////////////////////////////// +// FutexTestThreadFunction1 +// +static intptr_t FutexTestThreadFunction1(void* pvWorkData) +{ + int nErrorCount = 0; + FWorkData* pWorkData = (FWorkData*)pvWorkData; + const int32_t nThreadIndex = pWorkData->mThreadCount++; + ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "FutexTestThreadFunction1 created: %s, thread index %d\n", EAThreadThreadIdToString(threadId), nThreadIndex); + + while(!pWorkData->mbShouldQuit) + { + int nRecursiveLockCount(rand() % 3); + int i; + + for(i = 0; i < nRecursiveLockCount; ++i) + { + pWorkData->mFutex.Lock(); + pWorkData->mThreadWithLock[nThreadIndex]++; + + for(int j = 0; j < kMaxConcurrentThreadCount; ++j) + { + // Make sure this thread has the lock. + EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId)); + } + + ThreadCooperativeYield(); // Used by cooperative threading platforms. + } + + for(; nRecursiveLockCount > 0; --nRecursiveLockCount) + { + pWorkData->mThreadWithLock[nThreadIndex]--; + + // Verify that N locks are set. + int nLockCount = pWorkData->mFutex.GetLockCount(); + + EATEST_VERIFY_MSG(nLockCount == nRecursiveLockCount, "Futex failure."); + + if(nLockCount != nRecursiveLockCount) + pWorkData->mbShouldQuit = true; + + // Verify the unlock result. + pWorkData->mFutex.Unlock(); + nLockCount = pWorkData->mFutex.GetLockCount(); + + EATEST_VERIFY_MSG((nRecursiveLockCount == 1) || (nLockCount != nRecursiveLockCount), "Futex failure."); + + if(nRecursiveLockCount > 1) // If we have remaining locks... + { + for(int j = 0; j < kMaxConcurrentThreadCount; ++j) + { + // Make sure this thread has the lock. + EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId)); + } + } + + ThreadCooperativeYield(); // Used by cooperative threading platforms. + } + + if(nErrorCount) + pWorkData->mbShouldQuit = true; + else + EA::UnitTest::ThreadSleepRandom(100, 200); + } + + pWorkData->mnErrorCount += nErrorCount; + + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// FutexTestThreadFunction2 +// +// In this function we test Lock, attempting to detect memory synchronization +// problems that could occur with an incorrect Futex implementation. +// +static intptr_t FutexTestThreadFunction2(void* pvWorkData) +{ + int nErrorCount = 0; + FWorkData* pWorkData = (FWorkData*)pvWorkData; + const int32_t nThreadIndex = pWorkData->mThreadCount++; + ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "FutexTestThreadFunction2 created: %s, thread index %d\n", EAThreadThreadIdToString(threadId), nThreadIndex); + + while(!pWorkData->mbShouldQuit) + { + pWorkData->mFutex.Lock(); + pWorkData->mThreadWithLock[nThreadIndex] = 1; + + for(int j = 0; j < kMaxConcurrentThreadCount; ++j) + { + // Make sure this thread has the lock. + EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId)); + } + + pWorkData->mThreadWithLock[nThreadIndex] = 0; + pWorkData->mFutex.Unlock(); + ThreadCooperativeYield(); // Used by cooperative threading platforms. + + if(nErrorCount) + pWorkData->mbShouldQuit = true; + } + + pWorkData->mnErrorCount += nErrorCount; + + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// FutexTestThreadFunction3 +// +// In this function we test TryLock. +// +static intptr_t FutexTestThreadFunction3(void* pvWorkData) +{ + int nErrorCount = 0; + FWorkData* pWorkData = (FWorkData*)pvWorkData; + const int32_t nThreadIndex = pWorkData->mThreadCount++; + ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "FutexTestThreadFunction3 created: %s, thread index %d\n", EAThreadThreadIdToString(threadId), nThreadIndex); + + for(int i = 0; !pWorkData->mbShouldQuit; ++i) + { + // We make sure to mix TryLock usage with Lock usage. + bool bResult; + + if(((i % 4) != 0)) // 3/4 of the time, use TryLock, 1/4 of the time, use Lock. + bResult = pWorkData->mFutex.TryLock(); + else + { + pWorkData->mFutex.Lock(); + bResult = true; + } + + if(bResult) + { + pWorkData->mThreadWithLock[nThreadIndex] = 1; + + for(int j = 0; j < kMaxConcurrentThreadCount; ++j) + { + // Make sure this thread has the lock. + EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId)); + } + + pWorkData->mThreadWithLock[nThreadIndex] = 0; + pWorkData->mFutex.Unlock(); + + if(nErrorCount) + pWorkData->mbShouldQuit = true; + } + + ThreadCooperativeYield(); // Used by cooperative threading platforms. + } + + pWorkData->mnErrorCount += nErrorCount; + + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// TestThreadFutexMulti +// +static int TestThreadFutexMulti(FutexTestThreadFunction pFutexTestThreadFunction, int nThreadCount) +{ + int nErrorCount(0); + FWorkData* const pWorkData = new FWorkData; + Thread thread[kMaxConcurrentThreadCount]; + Thread::Status status; + int i; + + EA::UnitTest::ReportVerbosity(1, "Multithreaded test...\n"); + + if(nThreadCount > kMaxConcurrentThreadCount) + nThreadCount = kMaxConcurrentThreadCount; + + for(i = 0; i < nThreadCount; i++) + thread[i].Begin(pFutexTestThreadFunction, pWorkData); + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000); + + pWorkData->mbShouldQuit = true; + + for(i = 0; i < nThreadCount; i++) + { + status = thread[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status == EA::Thread::Thread::kStatusEnded, "Futex/Thread failure: Thread(s) didn't end."); + } + + nErrorCount += (int)pWorkData->mnErrorCount; + + delete pWorkData; + + return nErrorCount; +} + + +/////////////////////////////////////////////////////////////////////////////// +// TestThreadFutexSpeed +// +static int TestThreadFutexSpeed() +{ + int nErrorCount = 0; + size_t i; + uint64_t t0, t1, tDelta; + const size_t kLoopCount = 1000000; + + EA::UnitTest::ReportVerbosity(1, "\nSpeed test...\n"); + + ////////////////////////////////////////////////// + // Futex + { + Futex f; + + t0 = EA::StdC::Stopwatch::GetCPUCycle(); + f.Lock(); + f.Unlock(); + t1 = EA::StdC::Stopwatch::GetCPUCycle(); + + t0 = EA::StdC::Stopwatch::GetCPUCycle(); + + for(i = 0; i < kLoopCount; i++) + { + f.Lock(); + f.Unlock(); + } + + t1 = EA::StdC::Stopwatch::GetCPUCycle(); + tDelta = t1 - t0; + + EA::UnitTest::ReportVerbosity(1, "Futex time (ticks): %" PRIu64 "\n", tDelta); + } + ////////////////////////////////////////////////// + + + ////////////////////////////////////////////////// + // Mutex + { + EA::Thread::Mutex m; + + t0 = EA::StdC::Stopwatch::GetCPUCycle(); + m.Lock(); + m.Unlock(); + t1 = EA::StdC::Stopwatch::GetCPUCycle(); + + t0 = EA::StdC::Stopwatch::GetCPUCycle(); + + for(i = 0; i < kLoopCount; i++) + { + m.Lock(); + m.Unlock(); + } + + t1 = EA::StdC::Stopwatch::GetCPUCycle(); + tDelta = t1 - t0; + + EA::UnitTest::ReportVerbosity(1, "Mutex time (ticks): %" PRIu64 "\n", tDelta); + } + ////////////////////////////////////////////////// + + + #if defined(EA_PLATFORM_MICROSOFT) && !defined(EA_PLATFORM_WINDOWS_PHONE) && !(defined(EA_PLATFORM_WINDOWS) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)) + + ////////////////////////////////////////////////// + // HMUTEX + { + HANDLE hMutex = CreateMutexA(NULL, false, NULL); + + if (hMutex != NULL) + { + WaitForSingleObject(hMutex, INFINITE); + ReleaseMutex(hMutex); + + t0 = EA::StdC::Stopwatch::GetCPUCycle(); + + for(i = 0; i < kLoopCount; i++) + { + WaitForSingleObject(hMutex, INFINITE); + ReleaseMutex(hMutex); + } + + t1 = EA::StdC::Stopwatch::GetCPUCycle(); + tDelta = t1 - t0; + + CloseHandle(hMutex); + + EA::UnitTest::ReportVerbosity(1, "Windows HMUTEX time (ticks): %" PRIu64 "\n", tDelta); + } + } + ////////////////////////////////////////////////// + + ////////////////////////////////////////////////// + // Critical section + { + CRITICAL_SECTION cs; + InitializeCriticalSection(&cs); + + EnterCriticalSection(&cs); + LeaveCriticalSection(&cs); + + t0 = EA::StdC::Stopwatch::GetCPUCycle(); + + for(i = 0; i < kLoopCount; i++) + { + EnterCriticalSection(&cs); + LeaveCriticalSection(&cs); + } + + t1 = EA::StdC::Stopwatch::GetCPUCycle(); + tDelta = t1 - t0; + + DeleteCriticalSection(&cs); + + EA::UnitTest::ReportVerbosity(1, "Windows CriticalSection time (ticks): %" PRIu64 "\n", tDelta); + } + ////////////////////////////////////////////////// + + + #endif + + return nErrorCount; +} + + +static int TestThreadFutexRegressions() +{ + int nErrorCount(0); + + #if EA_THREADS_AVAILABLE && EATHREAD_DEBUG_FUTEX_HANG_ENABLED + { + // Test the ability of Futex to report the callstack of another thread holding a futex. + struct FutexCallstackTestThread : public EA::Thread::IRunnable + { + ThreadParameters mThreadParams; // + EA::Thread::Thread mThread; // The Thread object. + EA::Thread::Futex* mpFutex; // A Futex. + + FutexCallstackTestThread() : mThreadParams(), mThread(), mpFutex(NULL) {} + FutexCallstackTestThread(const FutexCallstackTestThread&){} // Avoid compiler warnings. + void operator=(const FutexCallstackTestThread&){} // Avoid compiler warnings. + + intptr_t Run(void*) + { + if(EA::StdC::Strstr(mThreadParams.mpName, "0")) // If we are thread 0... + { + mpFutex->Lock(); + mpFutex->Lock(); + EA::Thread::ThreadSleep(4000); + mpFutex->Unlock(); + mpFutex->Unlock(); + } + else + { + mpFutex->Lock(); // This should result in a printf tracing two thread 0 lock callstacks. + mpFutex->Unlock(); + } + return 0; + } + }; + + FutexCallstackTestThread thread[2]; + EA::Thread::Futex futex; + + for(int i = 0; i < 3; i++) + { + thread[0].mThreadParams.mpName = "FutexTest0"; + thread[0].mpFutex = &futex; + thread[0].mThread.Begin(&thread[0], NULL, &thread[0].mThreadParams); + + EA::UnitTest::ThreadSleep(300); + + thread[1].mThreadParams.mpName = "FutexTest1"; + thread[1].mpFutex = &futex; + thread[1].mThread.Begin(&thread[1], NULL, &thread[1].mThreadParams); + + EA::UnitTest::ThreadSleep(5000); + + for(int i = 0; i < 2; i++) + thread[i].mThread.WaitForEnd(); + } + } + #endif + + return nErrorCount; +} + + + +#define EATHREAD_DEBUG_FUTEX_HAMMER_ENABLED 1 +// Check multithreaded correctness of Futex. +// Here we hammer on the futex via multiple threads while incrementing non atomic counter +// if the lock ever fails the global count will be incorrect. + +volatile uint32_t gCommonCount = 0; + +static int TestThreadFutexHammer() +{ + int nErrorCount(0); + + #if EA_THREADS_AVAILABLE && EATHREAD_DEBUG_FUTEX_HAMMER_ENABLED + { + static const int NUM_SPINNING_THREADS = 4; + static const uint32_t MAX_NUM_LOOPS = 1 << 5; + + // Test the ability of Futex to report the callstack of another thread holding a futex. + struct FutexCallstackTestThread : public EA::Thread::IRunnable + { + ThreadParameters mThreadParams; // + EA::Thread::Thread mThread; // The Thread object. + EA::Thread::Futex* mpFutex; // A Futex. + uint32_t mThreadLocalId; + + FutexCallstackTestThread() : mThreadParams(), mThread(), mpFutex(NULL) {} + FutexCallstackTestThread(const FutexCallstackTestThread&) {} // Avoid compiler warnings. + void operator=(const FutexCallstackTestThread&) {} // Avoid compiler warnings. + + intptr_t Run(void*) + { + for (uint32_t i = 0; i < MAX_NUM_LOOPS; i++) + { + mpFutex->Lock(); + gCommonCount++; + mpFutex->Unlock(); + } + return 0; + } + }; + + + FutexCallstackTestThread thread[NUM_SPINNING_THREADS]; + EA::Thread::Futex futex; + gCommonCount = 0; // for multiple runs + + for(int i = 0; i < NUM_SPINNING_THREADS; i++) + { + thread[i].mpFutex = &futex; + thread[i].mThread.Begin(&thread[i], NULL, &thread[i].mThreadParams); + } + + EA::UnitTest::ThreadSleep(13); + for(int i = 0; i < NUM_SPINNING_THREADS; i++) + thread[i].mThread.WaitForEnd(); + + EATEST_VERIFY_MSG(gCommonCount == NUM_SPINNING_THREADS * MAX_NUM_LOOPS, "Multithreaded futex test failed, non atomic counter is incorrect"); + } + #endif + + return nErrorCount; +} + + + + +/////////////////////////////////////////////////////////////////////////////// +// TestThreadFutex +// +int TestThreadFutex() +{ + int nErrorCount(0); + + nErrorCount += TestThreadFutexSingle(); + nErrorCount += TestThreadFutexSpeed(); + nErrorCount += TestThreadFutexRegressions(); + + // hammer on the futex a few times + for(int j=0; j <1;j++) + { + nErrorCount += TestThreadFutexHammer(); + } + + #if EA_THREADS_AVAILABLE + nErrorCount += TestThreadFutexMulti(FutexTestThreadFunction1, 4); + nErrorCount += TestThreadFutexMulti(FutexTestThreadFunction2, 4); + nErrorCount += TestThreadFutexMulti(FutexTestThreadFunction3, 4); + #endif + + return nErrorCount; +} + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadMutex.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadMutex.cpp new file mode 100644 index 00000000..0b120da2 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadMutex.cpp @@ -0,0 +1,233 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include +#include + + +using namespace EA::Thread; + + +const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + + +struct MWorkData +{ + volatile bool mbShouldQuit; + Mutex mMutex; + volatile int mnExpectedValue; + volatile int mnCalculatedValue; + AtomicInt32 mnErrorCount; + + MWorkData() : mbShouldQuit(false), mMutex(NULL, true), + mnExpectedValue(0), mnCalculatedValue(0), + mnErrorCount(0) {} + + // Prevent default generation of these functions by not defining them +private: + MWorkData(const MWorkData& rhs); // copy constructor + MWorkData& operator=(const MWorkData& rhs); // assignment operator +}; + + +static intptr_t MutexTestThreadFunction(void* pvWorkData) +{ + int nErrorCount = 0; + MWorkData* pWorkData = (MWorkData*)pvWorkData; + const ThreadId threadId = GetThreadId(); + const int currentProcessor = GetThreadProcessor(); + + EA::UnitTest::ReportVerbosity(1, "Mutex test function created: %s (currently on processor %d).\n", EAThreadThreadIdToString(threadId), currentProcessor); + + while(!pWorkData->mbShouldQuit) + { + const int nRecursiveLockCount(rand() % 3); + int i, nLockResult, nLocks = 0; + + for(i = 0; i < nRecursiveLockCount; i++) + { + // Do a lock but allow for the possibility of occasional timeout. + ThreadTime expectedTime(GetThreadTime() + 1000); + nLockResult = pWorkData->mMutex.Lock(expectedTime); + + // Verify the lock succeeded or timed out. + EATEST_VERIFY_MSG(nLockResult != Mutex::kResultError, "Mutex failure."); + + // Verify the timeout of the lock + if (nLockResult == Mutex::kResultTimeout) + { + ThreadTime currentTime(GetThreadTime()); + EATEST_VERIFY_MSG(currentTime >= expectedTime, "Mutex timeout failure."); + } + + if(nLockResult > 0) // If there was no timeout... + { + nLocks++; + + // What we do here is spend some time manipulating mnExpectedValue and mnCalculatedValue + // while we have the lock. We change their values in a predicable way but before we + // are done mnCalculatedValue has been incremented by one and both values are equal. + const uintptr_t x = (uintptr_t)pWorkData; + + pWorkData->mnExpectedValue = -1; + EA::UnitTest::ThreadSleepRandom(10, 20); + pWorkData->mnCalculatedValue *= 50; + EA::UnitTest::ThreadSleepRandom(10, 20); + pWorkData->mnCalculatedValue /= (int)(((x + 1) / x) * 50); // This will always be the same as simply '/= 50'. + EA::UnitTest::ThreadSleepRandom(10, 20); + pWorkData->mnCalculatedValue += 1; + EA::UnitTest::ThreadSleepRandom(10, 20); + pWorkData->mnExpectedValue = pWorkData->mnCalculatedValue; + + EATEST_VERIFY_MSG(pWorkData->mnCalculatedValue == pWorkData->mnExpectedValue, "Mutex failure."); + } + ThreadCooperativeYield(); // Used by cooperative threading platforms. + } + + while(nLocks > 0) + { + // Verify that HasLock returns the expected value. + EATEST_VERIFY_MSG(pWorkData->mMutex.HasLock(), "Mutex failure."); + + // Verify that N locks are set. + nLockResult = pWorkData->mMutex.GetLockCount(); + EATEST_VERIFY_MSG(nLockResult == nLocks, "Mutex failure."); + + // Verify the unlock result. + nLockResult = pWorkData->mMutex.Unlock(); + EATEST_VERIFY_MSG(nLockResult >= nLocks-1, "Mutex failure."); + + nLocks--; + ThreadCooperativeYield(); // Used by cooperative threading platforms. + } + + // If EAT_ASSERT_ENABLED is 1, Mutex::HasLock() tests to see that that not only is the mutex + // locked, but that the lock is owned by the calling thread. + #if EAT_ASSERT_ENABLED + // Verify that HasLock returns the expected value. + EATEST_VERIFY_MSG(!pWorkData->mMutex.HasLock(), "Mutex failure."); + #endif + + EA::UnitTest::ThreadSleepRandom(100, 200); + } + + pWorkData->mnErrorCount += nErrorCount; + + return 0; +} + + +int TestThreadMutex() +{ + int nErrorCount(0); + + { // ctor tests + // We test various combinations of Mutex ctor and MutexParameters. + // MutexParameters(bool bIntraProcess = true, const char* pName = NULL); + // Mutex(const MutexParameters* pMutexParameters = NULL, bool bDefaultParameters = true); + + MutexParameters mp1(true, NULL); + MutexParameters mp2(true, "mp2"); + MutexParameters mp3(false, NULL); + MutexParameters mp4(false, "mp4WithNameThatIsMoreThan32Characters"); // testing kettle mutex name limitations + + Mutex mutex1(&mp1, false); + Mutex mutex2(&mp2, false); + Mutex mutex3(&mp3, false); + Mutex mutex4(&mp4, false); + Mutex mutex5(NULL, true); + Mutex mutex6(NULL, false); + mutex6.Init(&mp1); + + AutoMutex am1(mutex1); + AutoMutex am2(mutex2); + AutoMutex am3(mutex3); + AutoMutex am4(mutex4); + AutoMutex am5(mutex5); + AutoMutex am6(mutex6); + } + + { // Single-threaded tests + Mutex mutex(NULL, true); + + int nLockCount; + + nLockCount = mutex.GetLockCount(); + EATEST_VERIFY_MSG(nLockCount == 0, "Mutex failure."); + + nLockCount = mutex.Lock(); + EATEST_VERIFY_MSG(nLockCount == 1, "Mutex failure."); + + nLockCount = mutex.Lock(); + EATEST_VERIFY_MSG(nLockCount == 2, "Mutex failure."); + + nLockCount = mutex.Unlock(); + EATEST_VERIFY_MSG(nLockCount == 1, "Mutex failure."); + + nLockCount = mutex.GetLockCount(); + EATEST_VERIFY_MSG(nLockCount == 1, "Mutex failure."); + + nLockCount = mutex.Unlock(); + EATEST_VERIFY_MSG(nLockCount == 0, "Mutex failure."); + + nLockCount = mutex.Lock(); + EATEST_VERIFY_MSG(nLockCount == 1, "Mutex failure."); + + nLockCount = mutex.Unlock(); + EATEST_VERIFY_MSG(nLockCount == 0, "Mutex failure."); + } + + #ifdef EA_PLATFORM_PS4 + { + // Validate the amount of system resources being consumed by a Sony Mutex without a mutex name. It appears the + // Sony OS allocates 32 bytes per mutex regardless if the mutex name is empty or not. EAThread currently checks + // if the mutex name can be omited in an effort to save memory. + MutexParameters mp(false, nullptr); + + Mutex mutexes[4000]; + for(auto& m : mutexes) + m.Init(&mp); + } + #endif + + #if EA_THREADS_AVAILABLE + + { // Multithreaded test + MWorkData workData; + + const int kThreadCount(kMaxConcurrentThreadCount); + Thread thread[kThreadCount]; + Thread::Status status; + int i; + + for(i = 0; i < kThreadCount; i++) + thread[i].Begin(MutexTestThreadFunction, &workData); + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000); + + workData.mbShouldQuit = true; + + for(i = 0; i < kThreadCount; i++) + { + status = thread[i].WaitForEnd(GetThreadTime() + 30000); + + EATEST_VERIFY_MSG(status == EA::Thread::Thread::kStatusEnded, "Mutex/Thread failure: Thread(s) didn't end."); + } + + nErrorCount += (int)workData.mnErrorCount; + } + + #endif + + return nErrorCount; +} + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadRWMutex.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadRWMutex.cpp new file mode 100644 index 00000000..b40ac7cc --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadRWMutex.cpp @@ -0,0 +1,217 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include + + +using namespace EA::Thread; + + +const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + + +struct RWMWorkData +{ + volatile bool mbShouldQuit; + RWMutex mRWMutex; + volatile int mnExpectedValue; + volatile int mnCalculatedValue; + AtomicInt32 mnErrorCount; + + RWMWorkData() : mbShouldQuit(false), mRWMutex(NULL, true), mnExpectedValue(0), + mnCalculatedValue(0), mnErrorCount(0) {} + + // define copy ctor and assignment operator + // so the compiler does define them intrisically + RWMWorkData(const RWMWorkData& rhs); // copy constructor + RWMWorkData& operator=(const RWMWorkData& rhs); // assignment operator +}; + + +static intptr_t ReaderFunction(void* pvWorkData) +{ + int nErrorCount = 0; + RWMWorkData* pWorkData = (RWMWorkData*)pvWorkData; + ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "RWMutex reader test function created: %s\n", EAThreadThreadIdToString(threadId)); + + while(!pWorkData->mbShouldQuit) + { + const int nRecursiveLockCount(rand() % 3); + int i, nLockResult, nLocks = 0; + + for(i = 0; i < nRecursiveLockCount; i++) + { + // Do a lock but allow for the possibility of occasional timeout. + nLockResult = pWorkData->mRWMutex.Lock(RWMutex::kLockTypeRead, GetThreadTime() + 20); + + EATEST_VERIFY_MSG(nLockResult != RWMutex::kResultError, "RWMutex failure"); + + if(nLockResult > 0) + { + nLocks++; + + EA::UnitTest::ReportVerbosity(2, "CValue = %d; EValue = %d\n", pWorkData->mnCalculatedValue, pWorkData->mnExpectedValue); + EATEST_VERIFY_MSG(pWorkData->mnCalculatedValue == pWorkData->mnExpectedValue, "RWMutex failure"); + } + ThreadCooperativeYield(); // Used by cooperative threading platforms. + } + + while(nLocks > 0) + { + // Verify no write locks are set. + nLockResult = pWorkData->mRWMutex.GetLockCount(RWMutex::kLockTypeWrite); + EATEST_VERIFY_MSG(nLockResult == 0, "RWMutex failure"); + + // Verify at least N read locks are set. + nLockResult = pWorkData->mRWMutex.GetLockCount(RWMutex::kLockTypeRead); + EATEST_VERIFY_MSG(nLockResult >= nLocks, "RWMutex failure"); + + // Verify there is one less read lock set. + nLockResult = pWorkData->mRWMutex.Unlock(); + EATEST_VERIFY_MSG(nLockResult >= nLocks-1, "RWMutex failure"); + + nLocks--; + ThreadCooperativeYield(); // Used by cooperative threading platforms. + } + + EA::UnitTest::ThreadSleepRandom(100, 200); + } + + pWorkData->mnErrorCount += nErrorCount; + + return 0; +} + + +static intptr_t WriterFunction(void* pvWorkData) +{ + int nErrorCount = 0; + RWMWorkData* pWorkData = (RWMWorkData*)pvWorkData; + ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "RWMutex writer test function created: %s\n", EAThreadThreadIdToString(threadId)); + + while(!pWorkData->mbShouldQuit) + { + // Do a lock but allow for the possibility of occasional timeout. + int nLockResult = pWorkData->mRWMutex.Lock(RWMutex::kLockTypeWrite, GetThreadTime() + 30); + EATEST_VERIFY_MSG(nLockResult != RWMutex::kResultError, "RWMutex failure"); + + ThreadCooperativeYield(); // Used by cooperative threading platforms. + + if(nLockResult > 0) + { + // Verify exactly one write lock is set. + nLockResult = pWorkData->mRWMutex.GetLockCount(RWMutex::kLockTypeWrite); + EATEST_VERIFY_MSG(nLockResult == 1, "RWMutex failure"); + + // What we do here is spend some time manipulating mnExpectedValue and mnCalculatedValue + // while we have the write lock. We change their values in a predicable way but before + // we are done mnCalculatedValue has been incremented by one and both values are equal. + const uintptr_t x = (uintptr_t)pWorkData; + + pWorkData->mnExpectedValue = -1; + EA::UnitTest::ThreadSleepRandom(10, 20); + pWorkData->mnCalculatedValue *= 50; + EA::UnitTest::ThreadSleepRandom(10, 20); + pWorkData->mnCalculatedValue /= (int)(((x + 1) / x) * 50); // This will always be the same as simply '/= 50'. + EA::UnitTest::ThreadSleepRandom(10, 20); + pWorkData->mnCalculatedValue += 1; + EA::UnitTest::ThreadSleepRandom(10, 20); + pWorkData->mnExpectedValue = pWorkData->mnCalculatedValue; + + // Verify no read locks are set. + nLockResult = pWorkData->mRWMutex.GetLockCount(RWMutex::kLockTypeRead); + EATEST_VERIFY_MSG(nLockResult == 0, "RWMutex failure"); + + // Verify exactly one write lock is set. + nLockResult = pWorkData->mRWMutex.GetLockCount(RWMutex::kLockTypeWrite); + EATEST_VERIFY_MSG(nLockResult == 1, "RWMutex failure"); + + // Verify there are now zero write locks set. + nLockResult = pWorkData->mRWMutex.Unlock(); + EATEST_VERIFY_MSG(nLockResult == 0, "RWMutex failure"); + + EA::UnitTest::ThreadSleepRandom(400, 800); + } + } + + pWorkData->mnErrorCount += nErrorCount; + + return 0; +} + + +int TestThreadRWMutex() +{ + int nErrorCount(0); + + // Be careful adding when adding more mutexes to this test as the the CTR runs out of + // resources pretty early. + { + RWMutexParameters mp1(true, NULL); + RWMutexParameters mp2(true, "mp2"); + + { + RWMutex mutex1(&mp1, false); + RWMutex mutex2(&mp2, false); + } + + { + RWMutex mutex5(NULL, true); + RWMutex mutex6(NULL, false); + mutex6.Init(&mp1); + } + + { + RWMutex mutex1(&mp1, false); + AutoRWMutex am1(mutex1, RWMutex::kLockTypeRead); + } + } + + #if EA_THREADS_AVAILABLE + + { + RWMWorkData workData; + + const int kThreadCount(kMaxConcurrentThreadCount - 1); + Thread thread[kThreadCount]; + ThreadId threadId[kThreadCount]; + Thread::Status status; + + for(int i(0); i < kThreadCount; i++) + { + if(i < (kThreadCount * 3 / 4)) + threadId[i] = thread[i].Begin(ReaderFunction, &workData); + else + threadId[i] = thread[i].Begin(WriterFunction, &workData); + } + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000); + + workData.mbShouldQuit = true; + + for(int i(0); i < kThreadCount; i++) + { + if(threadId[i] != kThreadIdInvalid) + { + status = thread[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "RWMutex/Thread failure: status == kStatusRunning."); + } + } + + nErrorCount += (int)workData.mnErrorCount; + } + + #endif + + return nErrorCount; +} + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadRWSemaLock.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadRWSemaLock.cpp new file mode 100644 index 00000000..60bff93a --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadRWSemaLock.cpp @@ -0,0 +1,259 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + + +#include "TestThread.h" +#include +#include +#include +#include + + + +const int kThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + + + +/////////////////////////////////////////////////////////////////////////////// +// RWSTestType +// +enum RWSTestType +{ + kRWSTestTypeStandard, + kRWSTestTypeAllWriters, + kRWSTestTypeAllReaders, + kRWSTestTypeMostlyWriters, + kRWSTestTypeMostlyReaders, + kRWSTestTypeCount +}; + + +/////////////////////////////////////////////////////////////////////////////// +// RWSemaWorkData +// +struct RWSemaWorkData +{ + volatile bool mbShouldQuit; + EA::Thread::RWSemaLock mRWSemaLock; + volatile int mnWriterCount; + EA::Thread::AtomicInt32 mnErrorCount; + EA::Thread::AtomicInt32 mnCurrentTestType; + + RWSemaWorkData() + : mbShouldQuit(false) + , mRWSemaLock() + , mnWriterCount(0) + , mnErrorCount(0) + , mnCurrentTestType(kRWSTestTypeStandard) {} + +private: + RWSemaWorkData(const RWSemaWorkData& rhs); + RWSemaWorkData& operator=(const RWSemaWorkData& rhs); +}; + +/////////////////////////////////////////////////////////////////////////////// +// RWSThreadFunction +// +static intptr_t RWSThreadFunction(void* pvWorkData) +{ + using namespace EA::Thread; + + RWSemaWorkData* const pWorkData = (RWSemaWorkData*)pvWorkData; + + ThreadId threadId = GetThreadId(); + EA::UnitTest::ReportVerbosity(1, "RWSemaLock test function created: %s\n", EAThreadThreadIdToString(threadId)); + + int nErrorCount = 0; + + while(!pWorkData->mbShouldQuit) + { + int nWriteLockChance = 0; + const RWSTestType testType = (RWSTestType)pWorkData->mnCurrentTestType.GetValue(); + + switch (testType) + { + default: + case kRWSTestTypeStandard: + nWriteLockChance = 20; + break; + + case kRWSTestTypeAllWriters: + nWriteLockChance = 1000; + break; + + case kRWSTestTypeAllReaders: + nWriteLockChance = 0; + break; + + case kRWSTestTypeMostlyWriters: + nWriteLockChance = 700; + break; + + case kRWSTestTypeMostlyReaders: + nWriteLockChance = 5; + break; + } + + const bool bShouldWrite = ((rand() % 1000) < nWriteLockChance); + + if(bShouldWrite) + { + AutoSemaWriteLock _(pWorkData->mRWSemaLock); + pWorkData->mnWriterCount++; + EA::UnitTest::ThreadSleepRandom(2, 10); + pWorkData->mnWriterCount--; + } + else + { + AutoSemaReadLock _(pWorkData->mRWSemaLock); + EATEST_VERIFY_MSG(pWorkData->mnWriterCount == 0, "ReadLock is held, there should be no active WriteLocks."); + } + } + + pWorkData->mnErrorCount.SetValue(nErrorCount); + + return nErrorCount; +} + + +// NOTE(rparolin): +// This exists to introduce test-only functionality for the RWSemaLock. We can add these functions here because we +// guarantee they will not be called in a concurrent context and they simplify validation of assumption of the lock. +struct TestRWSemaLock : public EA::Thread::RWSemaLock +{ + TestRWSemaLock() = default; + TestRWSemaLock(const TestRWSemaLock&) = delete; + TestRWSemaLock(TestRWSemaLock&&) = delete; + TestRWSemaLock& operator=(const TestRWSemaLock&) = delete; + TestRWSemaLock& operator=(TestRWSemaLock&&) = delete; + + bool IsReadLocked() + { + Status status; + status.data = mStatus.GetValue(); + return status.readers > 0; + } + + bool IsWriteLocked() + { + Status status; + status.data = mStatus.GetValue(); + return status.writers > 0; + } +}; + + +int TestThreadRWSemaLock() +{ + using namespace EA::Thread; + + int nErrorCount = 0; + + { // RWSemaLock -- Basic single-threaded test. + + TestRWSemaLock rwSemaLock; // There are no construction parameters. + + EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure"); + + rwSemaLock.ReadTryLock(); + EATEST_VERIFY_MSG(rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.WriteTryLock(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure"); + + rwSemaLock.ReadLock(); + EATEST_VERIFY_MSG(rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + + rwSemaLock.ReadUnlock(); + EATEST_VERIFY_MSG(rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + + rwSemaLock.ReadUnlock(); + EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + + rwSemaLock.WriteTryLock(); + EATEST_VERIFY_MSG(rwSemaLock.IsWriteLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.ReadTryLock(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.WriteTryLock(), "RWSemaLock failure"); + } + + + { // AutoRWSemaLock -- Basic single-threaded test. + TestRWSemaLock rwSemaLock; // There are no construction parameters. + + { //Special scope just for the AutoRWSemaLock + AutoSemaReadLock autoRWSemaLock1(rwSemaLock); + AutoSemaReadLock autoRWSemaLock2(rwSemaLock); + + EATEST_VERIFY_MSG(rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.WriteTryLock(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure"); + } + + EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure"); + + { //Special scope just for the AutoRWSemaLock + AutoSemaWriteLock autoRWSemaLock(rwSemaLock); + + EATEST_VERIFY_MSG(rwSemaLock.IsWriteLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.ReadTryLock(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + } + + EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure"); + EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure"); + } + + + #if EA_THREADS_AVAILABLE + + { // Multithreaded test + + RWSemaWorkData workData; + + Thread thread[kThreadCount]; + ThreadId threadId[kThreadCount]; + Thread::Status status; + + for(int i(0); i < kThreadCount; i++) + threadId[i] = thread[i].Begin(RWSThreadFunction, &workData); + + for(int e = 0; e < kRWSTestTypeCount; e++) + { + workData.mnCurrentTestType.SetValue(e); + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 500, gTestLengthSeconds * 500); + } + + workData.mbShouldQuit = true; + for(int t(0); t < kThreadCount; t++) + { + if(threadId[t] != kThreadIdInvalid) + { + status = thread[t].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "RWSemalock/Thread failure: status == kStatusRunning.\n"); + } + } + + nErrorCount += (int)workData.mnErrorCount; + } + + #endif + + return nErrorCount; +} + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadRWSpinLock.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadRWSpinLock.cpp new file mode 100644 index 00000000..8bd75bb2 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadRWSpinLock.cpp @@ -0,0 +1,477 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include +#include +#include + + +const int kThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + + +/////////////////////////////////////////////////////////////////////////////// +// RWSTestType +// +enum RWSTestType +{ + kRWSTestTypeStandard, + kRWSTestTypeAllWriters, + kRWSTestTypeAllReaders, + kRWSTestTypeMostlyWriters, + kRWSTestTypeMostlyReaders, + kRWSTestTypeCount +}; + + +/////////////////////////////////////////////////////////////////////////////// +// RWSWorkData +// +struct RWSWorkData +{ + std::atomic mbShouldQuit; + EA::Thread::RWSpinLock mRWSpinLock; + EA::Thread::RWSpinLockW mRWSpinLockW; + volatile int mnExpectedValue; + volatile int mnCalculatedValue; + EA::Thread::AtomicInt32 mnErrorCount; + EA::Thread::AtomicInt32 mnCurrentTestType; + + RWSWorkData() : mbShouldQuit(false), mRWSpinLock(), mRWSpinLockW(), mnExpectedValue(0), mnCalculatedValue(0), + mnErrorCount(0), mnCurrentTestType(kRWSTestTypeStandard) {} + +private: + RWSWorkData(const RWSWorkData& rhs); + RWSWorkData& operator=(const RWSWorkData& rhs); +}; + + +/////////////////////////////////////////////////////////////////////////////// +// RWSWThreadFunction +// +static intptr_t RWSWThreadFunction(void* pvWorkData) +{ + using namespace EA::Thread; + + RWSWorkData* const pWorkData = (RWSWorkData*)pvWorkData; + + ThreadId threadId = GetThreadId(); + EA::UnitTest::ReportVerbosity(1, "RWSpinLockW test function created: %s\n", EAThreadThreadIdToString(threadId)); + + int nErrorCount = 0; + + while(!pWorkData->mbShouldQuit) + { + int nWriteLockChance = 0; + const RWSTestType testType = (RWSTestType)pWorkData->mnCurrentTestType.GetValue(); + + switch (testType) + { + default: + case kRWSTestTypeStandard: + nWriteLockChance = 20; + break; + + case kRWSTestTypeAllWriters: + nWriteLockChance = 1000; + break; + + case kRWSTestTypeAllReaders: + nWriteLockChance = 0; + break; + + case kRWSTestTypeMostlyWriters: + nWriteLockChance = 700; + break; + + case kRWSTestTypeMostlyReaders: + nWriteLockChance = 5; + break; + } + + const bool bShouldWrite = ((rand() % 1000) < nWriteLockChance); + + if(bShouldWrite) + { + pWorkData->mRWSpinLockW.WriteLock(); + + EATEST_VERIFY_MSG(!pWorkData->mRWSpinLockW.IsReadLocked(), "RWSpinlock failure: IsReadLocked\n"); + EATEST_VERIFY_MSG(pWorkData->mRWSpinLockW.IsWriteLocked(), "RWSpinlock failure: IsWriteLocked\n"); + + pWorkData->mRWSpinLockW.WriteUnlock(); + + ThreadCooperativeYield(); + } + else + { + const int nRecursiveLockCount = 1; // Disabled because we are not recursive: (rand() % 10) ? 1 : 2; + + int nLocks = 0; + + for(int i = 0; i < nRecursiveLockCount; i++) + { + pWorkData->mRWSpinLockW.ReadLock(); + nLocks++; + + ThreadCooperativeYield(); + } + + // Disabled because we are not recursive: + // if((rand() % 10) == 0) + // { + // if(pWorkData->mRWSpinLockW.ReadTryLock()) + // nLocks++; + // } + + while(nLocks > 0) + { + EATEST_VERIFY_MSG(pWorkData->mRWSpinLockW.IsReadLocked(), "RWSpinlock failure: IsReadLocked\n"); + EATEST_VERIFY_MSG(!pWorkData->mRWSpinLockW.IsWriteLocked(), "RWSpinlock failure: IsWriteLocked\n"); + + pWorkData->mRWSpinLockW.ReadUnlock(); + nLocks--; + + ThreadCooperativeYield(); + } + } + + //if((rand() % 1000) < 3) + // EA::UnitTest::ThreadSleepRandom(50, 100); + } + + pWorkData->mnErrorCount.SetValue(nErrorCount); + + return nErrorCount; +} + + + +static int TestThreadRWSpinLockW() +{ + using namespace EA::Thread; + + int nErrorCount = 0; + + { // RWSpinLockW -- Basic single-threaded test. + + RWSpinLockW rwSpinLock; // There are no construction parameters. + + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure"); + + rwSpinLock.ReadTryLock(); + EATEST_VERIFY_MSG( rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure"); + + // Disabled because we don't support read lock recursion. + // rwSpinLock.ReadLock(); + // EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + + // rwSpinLock.ReadUnlock(); + // EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + + rwSpinLock.ReadUnlock(); + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + + rwSpinLock.WriteTryLock(); + EATEST_VERIFY_MSG( rwSpinLock.IsWriteLocked(),"RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.ReadTryLock(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(), "RWSpinLockW failure"); + } + + + { // AutoRWSpinLockW -- Basic single-threaded test. + RWSpinLockW rwSpinLock; // There are no construction parameters. + + { //Special scope just for the AutoRWSpinLockW + AutoRWSpinLockW autoRWSpinLockW1(rwSpinLock, AutoRWSpinLockW::kLockTypeRead); + AutoRWSpinLockW autoRWSpinLockW2(rwSpinLock, AutoRWSpinLockW::kLockTypeRead); + + EATEST_VERIFY_MSG( rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure"); + } + + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure"); + + { //Special scope just for the AutoRWSpinLockW + AutoRWSpinLockW autoRWSpinLockW(rwSpinLock, AutoRWSpinLockW::kLockTypeWrite); + + EATEST_VERIFY_MSG( rwSpinLock.IsWriteLocked(),"RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.ReadTryLock(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + } + + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure"); + } + + + #if EA_THREADS_AVAILABLE + + { // Multithreaded test + + RWSWorkData workData; + + Thread thread[kThreadCount]; + ThreadId threadId[kThreadCount]; + Thread::Status status; + + for(int i(0); i < kThreadCount; i++) + threadId[i] = thread[i].Begin(RWSWThreadFunction, &workData); + + for(int e = 0; e < kRWSTestTypeCount; e++) + { + workData.mnCurrentTestType.SetValue(e); + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 500, gTestLengthSeconds * 500); + } + + workData.mbShouldQuit = true; + + for(int t(0); t < kThreadCount; t++) + { + if(threadId[t] != kThreadIdInvalid) + { + status = thread[t].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "RWSpinlock/Thread failure: status == kStatusRunning.\n"); + } + } + + nErrorCount += (int)workData.mnErrorCount; + } + + #endif + + return nErrorCount; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// RWSThreadFunction +// +static intptr_t RWSThreadFunction(void* pvWorkData) +{ + using namespace EA::Thread; + + RWSWorkData* const pWorkData = (RWSWorkData*)pvWorkData; + + ThreadId threadId = GetThreadId(); + EA::UnitTest::ReportVerbosity(1, "RWSpinLock test function created: %s\n", EAThreadThreadIdToString(threadId)); + + int nErrorCount = 0; + + while(!pWorkData->mbShouldQuit) + { + int nWriteLockChance = 0; + const RWSTestType testType = (RWSTestType)pWorkData->mnCurrentTestType.GetValue(); + + switch (testType) + { + default: + case kRWSTestTypeStandard: + nWriteLockChance = 20; + break; + + case kRWSTestTypeAllWriters: + nWriteLockChance = 1000; + break; + + case kRWSTestTypeAllReaders: + nWriteLockChance = 0; + break; + + case kRWSTestTypeMostlyWriters: + nWriteLockChance = 700; + break; + + case kRWSTestTypeMostlyReaders: + nWriteLockChance = 5; + break; + } + + const bool bShouldWrite = ((rand() % 1000) < nWriteLockChance); + + if(bShouldWrite) + { + pWorkData->mRWSpinLock.WriteLock(); + + EATEST_VERIFY_MSG(!pWorkData->mRWSpinLock.IsReadLocked(), "RWSpinlock failure: IsReadLocked\n"); + EATEST_VERIFY_MSG(pWorkData->mRWSpinLock.IsWriteLocked(), "RWSpinlock failure: IsWriteLocked\n"); + + pWorkData->mRWSpinLock.WriteUnlock(); + + ThreadCooperativeYield(); + } + else + { + const int nRecursiveLockCount = (rand() % 10) ? 1 : 2; + + int nLocks = 0; + + for(int i = 0; i < nRecursiveLockCount; i++) + { + pWorkData->mRWSpinLock.ReadLock(); + nLocks++; + + ThreadCooperativeYield(); + } + + if((rand() % 10) == 0) + { + if(pWorkData->mRWSpinLock.ReadTryLock()) + nLocks++; + } + + while(nLocks > 0) + { + const int32_t n = pWorkData->mRWSpinLock.mValue; (void)n; + + // It turns out IsReadLocked has a bug and can return a false negative. + // EATEST_VERIFY_MSG(pWorkData->mRWSpinLock.IsReadLocked(), "RWSpinlock failure: IsReadLocked\n"); + // EATEST_VERIFY_MSG(!pWorkData->mRWSpinLock.IsWriteLocked(), "RWSpinlock failure: IsWriteLocked\n"); + + pWorkData->mRWSpinLock.ReadUnlock(); + nLocks--; + + ThreadCooperativeYield(); + } + } + } + + pWorkData->mnErrorCount.SetValue(nErrorCount); + + return nErrorCount; +} + + + +int TestThreadRWSpinLock() +{ + using namespace EA::Thread; + + int nErrorCount = 0; + + { // RWSpinLock -- Basic single-threaded test. + + RWSpinLock rwSpinLock; // There are no construction parameters. + + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure"); + + rwSpinLock.ReadTryLock(); + EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure"); + + rwSpinLock.ReadLock(); + EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + + rwSpinLock.ReadUnlock(); + EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + + rwSpinLock.ReadUnlock(); + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + + rwSpinLock.WriteTryLock(); + EATEST_VERIFY_MSG(rwSpinLock.IsWriteLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.ReadTryLock(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(), "RWSpinLock failure"); + } + + + { // AutoRWSpinLock -- Basic single-threaded test. + RWSpinLock rwSpinLock; // There are no construction parameters. + + { //Special scope just for the AutoRWSpinLock + AutoRWSpinLock autoRWSpinLock1(rwSpinLock, AutoRWSpinLock::kLockTypeRead); + AutoRWSpinLock autoRWSpinLock2(rwSpinLock, AutoRWSpinLock::kLockTypeRead); + + EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure"); + } + + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure"); + + { //Special scope just for the AutoRWSpinLock + AutoRWSpinLock autoRWSpinLock(rwSpinLock, AutoRWSpinLock::kLockTypeWrite); + + EATEST_VERIFY_MSG(rwSpinLock.IsWriteLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.ReadTryLock(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + } + + EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure"); + EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure"); + } + + + #if EA_THREADS_AVAILABLE + + { // Multithreaded test + + RWSWorkData workData; + + Thread thread[kThreadCount]; + ThreadId threadId[kThreadCount]; + Thread::Status status; + + for(int i(0); i < kThreadCount; i++) + threadId[i] = thread[i].Begin(RWSThreadFunction, &workData); + + for(int e = 0; e < kRWSTestTypeCount; e++) + { + workData.mnCurrentTestType.SetValue(e); + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 500, gTestLengthSeconds * 500); + } + + workData.mbShouldQuit = true; + + for(int t(0); t < kThreadCount; t++) + { + if(threadId[t] != kThreadIdInvalid) + { + status = thread[t].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "RWSpinlock/Thread failure: status == kStatusRunning.\n"); + } + } + + nErrorCount += (int)workData.mnErrorCount; + } + + #endif + + // TestThreadRWSpinLockW + nErrorCount += TestThreadRWSpinLockW(); + + + return nErrorCount; +} + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSemaphore.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSemaphore.cpp new file mode 100644 index 00000000..bdd87af1 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSemaphore.cpp @@ -0,0 +1,353 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include +#include +#include + + +using namespace EA::Thread; + + +const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + + +struct SWorkData +{ + volatile bool mbShouldQuit; + Semaphore mSemaphore; + int mnPostCount; // The count to use for Semaphore::Post() + AtomicInt32 mnExpectedCount; + AtomicInt32 mnThreadCount; + AtomicInt32 mnErrorCount; + + SWorkData(const SemaphoreParameters& sp) + : mbShouldQuit(false), mSemaphore(&sp, false), + mnPostCount(1), mnExpectedCount(0), mnThreadCount(0), + mnErrorCount(0) {} + + // define copy ctor and assignment operator + // so the compiler does define them intrisically + SWorkData(const SWorkData& rhs); // copy constructor + SWorkData& operator=(const SWorkData& rhs); // assignment operator +}; + + + +static intptr_t SemaphoreTestThreadFunction(void* pvWorkData) +{ + SWorkData* pWorkData = (SWorkData*)pvWorkData; + int nErrorCount = 0; + ThreadId threadId = GetThreadId(); + + EA::UnitTest::ReportVerbosity(1, "Semaphore test function created: %s\n", EAThreadThreadIdToString(threadId)); + + const AtomicInt32::ValueType nThreadCount = pWorkData->mnThreadCount++; // AtomicInt32 operation. + const AtomicInt32::ValueType nPostCount = pWorkData->mnPostCount; + + #if defined(EA_PLATFORM_DESKTOP) // If the platform tends to run on fast hardware... + const unsigned kShortSleepMin = 50; + const unsigned kShortSleepMax = 100; + #else + const unsigned kShortSleepMin = 100; + const unsigned kShortSleepMax = 200; + #endif + + while(!pWorkData->mbShouldQuit) + { + if((nThreadCount % 2) == 0) // If the first thread or the third thread... + { + // Create 'work'. + pWorkData->mnExpectedCount += nPostCount; // Atomic operation. + pWorkData->mSemaphore.Post(nPostCount); + EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax); + + // Get the amount of 'work' that is expected. + int nWaitCount = nPostCount; + + // Wait for the 'work' to be queued and signalled. + while(nWaitCount-- > 0) + { + const int result = pWorkData->mSemaphore.Wait(GetThreadTime() + 2000); + + EATEST_VERIFY_MSG((result >= 0) || (result == Semaphore::kResultTimeout), "Semaphore failure 4a: semaphore.Wait()\n"); + + if(result >= 0) // If we we able to acquire the semaphore... + --pWorkData->mnExpectedCount; // Atomic operation. + // else timeout occurred. Every time we have this timeout we miss decrementing the semaphore by one, and thus the expected semaphore count rises by one. It doesn't mean there's a bug, it just means the expected count migrates higher as we get timeouts. + + EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax); + } + } + else + { + // Get the amount of 'work' that is expected. + int nWaitCount = nPostCount; + + // Wait for the 'work' to be queued and signalled. + while(nWaitCount-- > 0) + { + const int result = pWorkData->mSemaphore.Wait(GetThreadTime() + 2000); + + EATEST_VERIFY_MSG((result >= 0) || (result == Semaphore::kResultTimeout), "Semaphore failure 4b: semaphore.Wait()\n"); + + if(result >= 0) // If we we able to acquire the semaphore... + --pWorkData->mnExpectedCount; // Atomic operation. + // else timeout occurred. Every time we have this timeout we miss decrementing the semaphore by one, and thus the expected semaphore count rises by one. It doesn't mean there's a bug, it just means the expected count migrates higher as we get timeouts. + + EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax); + } + + // Create 'work'. + pWorkData->mnExpectedCount += nPostCount; // Atomic operation. + pWorkData->mSemaphore.Post(nPostCount); + EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax); + } + + EATEST_VERIFY_MSG(pWorkData->mSemaphore.GetCount() >= 0, "Semaphore failure 4c: The count should always be >= 0.\n"); + + // We restrict this assert to desktop platforms because the expected value migrates upward on every Wait timeout, and on slower platforms there could be a lot of such timeouts. + #if defined(EA_PLATFORM_DESKTOP) + EATEST_VERIFY_MSG(pWorkData->mSemaphore.GetCount() < 200, "Semaphore failure 4d: The count should always be a small value.\n"); + #endif + } + + pWorkData->mnErrorCount = nErrorCount; + + return 0; +} + + + +struct BadSignalTestData +{ + EA::Thread::Semaphore mSemaphore; // The Semaphore that we are using. + AtomicInt32 mnOKWaitCount; // The number of Wait timeouts that occurred. + AtomicInt32 mnTimeoutWaitCount; // The number of Wait OK returns that occurred. + AtomicInt32 mnErrorWaitCount; // The number of Wait error returns that occurred. + int mnWaitTimeout; // How long a waiter waits before timing out. + int mnWaitThreadCount; // How many waiter threads there are. Caller sets up this value. + int mnPostThreadCount; // How many poster threads there are. Caller sets up this value. + int mnPostCount; // How many signals the poster should Post. Needs to be less than mnWaitThreadCount for this test. Caller sets up this value. + int mnPostDelay; // How long the poster waits before Posting. Needs to be significantly less than mnWaitTimeout. + + BadSignalTestData() + : mSemaphore(), mnOKWaitCount(0), mnTimeoutWaitCount(0), mnErrorWaitCount(0), + mnWaitTimeout(0), mnWaitThreadCount(0), mnPostThreadCount(0), + mnPostCount(0), mnPostDelay(0) { } + + // Define copy ctor and assignment operator + // so the compiler does define them intrisically + BadSignalTestData(const BadSignalTestData& rhs); // copy constructor + BadSignalTestData& operator=(const BadSignalTestData& rhs); // assignment operator +}; + + +static intptr_t BadSignalTestWaitFunction(void *data) +{ + BadSignalTestData* const pBSTD = (BadSignalTestData*)data; + + const int waitResult = pBSTD->mSemaphore.Wait(EA::Thread::GetThreadTime() + pBSTD->mnWaitTimeout); + + if(waitResult == Semaphore::kResultTimeout) + pBSTD->mnTimeoutWaitCount.Increment(); + else if(waitResult >= 0) + pBSTD->mnOKWaitCount.Increment(); + else + pBSTD->mnErrorWaitCount.Increment(); + + return 0; +} + + +static intptr_t BadSignalTestPostFunction(void *data) +{ + BadSignalTestData* const pBSTD = (BadSignalTestData*)data; + + ThreadSleep((ThreadTime) pBSTD->mnPostDelay); + pBSTD->mSemaphore.Post(pBSTD->mnPostCount); // Intentionally post less than the number of waiting threads. + + return 0; +} + + + + +int TestThreadSemaphore() +{ + int nErrorCount(0); + + { // Single threaded test. + const int kInitialCount(4); + int nResult; + + SemaphoreParameters sp(kInitialCount, false, "SingleThreadTest"); + Semaphore semaphore(&sp); + + nResult = semaphore.GetCount(); + EATEST_VERIFY_F(nResult == kInitialCount, "Semaphore failure 2a: semaphore.GetCount(). result = %d\n", nResult); + + // This triggers an assert and so cannot be tested here: + // bResult = semaphore.SetCount(10); + // EATEST_VERIFY_MSG(!bResult, "Semaphore failure in semaphore.SetCount(10)\n"); + + // Grab the full count, leaving none. + for(int i(0); i < kInitialCount; i++) + { + nResult = semaphore.Wait(kTimeoutNone); + EATEST_VERIFY_F(nResult == kInitialCount - i - 1, "Semaphore failure 2b: semaphore.Wait(kTimeoutNone). result = %d\n", nResult); + + nResult = semaphore.GetCount(); + EATEST_VERIFY_F(nResult == kInitialCount - i - 1, "Semaphore failure 2c: semaphore.GetCount(). result = %d\n", nResult); + } + + nResult = semaphore.Wait(kTimeoutImmediate); + EATEST_VERIFY_F(nResult == Semaphore::kResultTimeout, "Semaphore failure 2d: semaphore.Wait(kTimeoutImmediate). result = %d\n", nResult); + + nResult = semaphore.GetCount(); + EATEST_VERIFY_F(nResult == 0, "Semaphore failure 2e: semaphore.GetCount(). result = %d\n", nResult); + + nResult = semaphore.Post(2); + EATEST_VERIFY_F(nResult == 2, "Semaphore failure 2f: semaphore.Post(2). result = %d\n", nResult); + + nResult = semaphore.Post(2); + EATEST_VERIFY_F(nResult == 4, "Semaphore failure 2g: semaphore.Post(2). result = %d\n", nResult); + } + + + #if EA_THREADS_AVAILABLE + + { // Bad signal test. + BadSignalTestData bstd; + Thread thread[4]; + + // These values need to be set up right or else this test won't work. + // What we are trying to do here is make certain that N number of Semaphore + // waiters time out, while M waiters succeed. + bstd.mnWaitTimeout = 5000; + bstd.mnPostDelay = 2000; + bstd.mnWaitThreadCount = 3; // mnWaitThreadCount + mnPostThreadCount should be [4]. + bstd.mnPostThreadCount = 1; + bstd.mnPostCount = 1; + + thread[0].Begin(BadSignalTestWaitFunction, &bstd); + thread[1].Begin(BadSignalTestWaitFunction, &bstd); + thread[2].Begin(BadSignalTestWaitFunction, &bstd); + thread[3].Begin(BadSignalTestPostFunction, &bstd); + + // Wait for the threads to be completed + thread[0].WaitForEnd(); + thread[1].WaitForEnd(); + thread[2].WaitForEnd(); + thread[3].WaitForEnd(); + + EATEST_VERIFY_MSG(bstd.mnTimeoutWaitCount == (bstd.mnWaitThreadCount - (bstd.mnPostThreadCount * bstd.mnPostCount)), "Semaphore failure 1a: bad signal test.\n"); + EATEST_VERIFY_MSG(bstd.mnErrorWaitCount == 0, "Semaphore failure 1b: bad signal test.\n"); + EATEST_VERIFY_MSG(bstd.mnOKWaitCount == (bstd.mnPostThreadCount * bstd.mnPostCount), "Semaphore failure 1c: bad signal test.\n"); + } + + { // Multithreaded test + + // Problem: In the case of the inter-process test below, since we are using a named semaphore it's possible that + // if two instances of this unit test are run on the same machine simultaneously, they will collide because + // they are using the same semaphore. This applies only to true multi-process-capable machines. + uint64_t cycle64 = EA::StdC::Stopwatch::GetCPUCycle(); + uint16_t cycle16 = (uint16_t)((uint16_t)(cycle64 >> 48) ^ (uint16_t)(cycle64 >> 32) ^ (uint16_t)(cycle64 >> 16) ^ (uint16_t)(cycle64 >> 0)); // Munge all the bits together because some platforms might have zeroed low bits; others high bits. This will work for all. + char name[16]; sprintf(name, "SMT%u", (unsigned)cycle16); + SemaphoreParameters semaphoreParameters(0, false, name); + #if defined(EA_PLATFORM_DESKTOP) + const int kLoopCount = 16; + #else + const int kLoopCount = 4; + #endif + + for(int j = 1; j <= kLoopCount; j++) // Intentionally start with 1, as we use j below. + { + if(semaphoreParameters.mbIntraProcess) + semaphoreParameters.mbIntraProcess = false; + else + semaphoreParameters.mbIntraProcess = true; + + SWorkData workData(semaphoreParameters); + workData.mnPostCount = (j % 4); + + const int kThreadCount(kMaxConcurrentThreadCount - 1); + Thread thread[kThreadCount]; + ThreadId threadId[kThreadCount]; + Thread::Status status; + int i; + + for(i = 0; i < kThreadCount; i++) + { + threadId[i] = thread[i].Begin(SemaphoreTestThreadFunction, &workData); + + EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Semaphore failure 3a: Couldn't create thread.\n"); + } + + EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*2000, gTestLengthSeconds*2000); + + workData.mbShouldQuit = true; + + #if defined(EA_PLATFORM_DESKTOP) + EA::UnitTest::ThreadSleepRandom(500, 500); + #else + EA::UnitTest::ThreadSleepRandom(1000, 1000); + #endif + + // We seed the semaphore with more posts because timing issues could cause the + // worker threads to get hung waiting for posts but it really isn't because + // of a problem with the semaphore. By the time all threads have quit, the + // expected count should be equal to the increment. + const int kIncrement = 20; + workData.mnExpectedCount += kIncrement; // AtomicInt32 operation + workData.mSemaphore.Post(kIncrement); + + #if defined(EA_PLATFORM_DESKTOP) + EA::UnitTest::ThreadSleepRandom(1000, 1000); + #else + EA::UnitTest::ThreadSleepRandom(2000, 2000); + #endif + + for(i = 0; i < kThreadCount; i++) + { + if(threadId[i] != kThreadIdInvalid) + { + status = thread[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Semaphore failure 3b: Thread(s) didn't end.\n"); + } + } + + // Normally the Semaphore::GetCount function returns a value that is volatile, but we know that + // there aren't any threads using mSemaphore any more, so GetCount returns a value we can rely on. + EATEST_VERIFY_MSG(workData.mnExpectedCount == workData.mSemaphore.GetCount(), "Semaphore failure 3c: Unexpected value.\n"); + if(workData.mnExpectedCount != workData.mSemaphore.GetCount()) + { + EA::UnitTest::ReportVerbosity(1, " Thread count: %d, Intraprocess: %s, Post count: %d, Expected count: %d, Semaphore GetCount: %d\n", + kThreadCount, semaphoreParameters.mbIntraProcess ? "yes" : "no", workData.mnPostCount, (int)workData.mnExpectedCount.GetValue(), workData.mSemaphore.GetCount()); + } + + nErrorCount += (int)workData.mnErrorCount; + } + + } + + #endif // EA_THREADS_AVAILABLE + + return nErrorCount; +} + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSmartPtr.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSmartPtr.cpp new file mode 100644 index 00000000..969d5959 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSmartPtr.cpp @@ -0,0 +1,217 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include +#include // required to test the c11 atomics macros + +using namespace EA::Thread; + + +struct TestClass +{ + int x; +}; + + +static bool IsTrue(bool b) +{ + return b; +} + +static bool IsFalse(bool b) +{ + return !b; +} + + +namespace not_eastl +{ + // Stub of std::shared_ptr implementation used to exercise the C11 atomic + // macro expansion. This was causing issues because the function names of + // the free standing eastl::shared_ptr functions and the C11 atomics + // functions collided. Since the C11 atomics were implemented as macros we + // can not utilize any scoping so we are forced to prevent the problem + // polluting the global namespace. + template class shared_ptr { }; + template inline bool atomic_is_lock_free(const shared_ptr*) { return false; } + template inline shared_ptr atomic_load(const shared_ptr* pSharedPtr) { return *pSharedPtr; } + template inline shared_ptr atomic_load_explicit(const shared_ptr* pSharedPtr, ...) { return *pSharedPtr; } + template inline void atomic_store(shared_ptr* pSharedPtrA, shared_ptr sharedPtrB) {} + template inline void atomic_store_explicit(shared_ptr* pSharedPtrA, shared_ptr sharedPtrB, ...) {} + template shared_ptr atomic_exchange(shared_ptr* pSharedPtrA, shared_ptr sharedPtrB) { return sharedPtrB; } + template inline shared_ptr atomic_exchange_explicit(shared_ptr* pSharedPtrA, shared_ptr sharedPtrB, ...){ return *pSharedPtrA; } + template bool atomic_compare_exchange_strong(shared_ptr* pSharedPtr, shared_ptr* pSharedPtrCondition, shared_ptr sharedPtrNew) { return false; } + template inline bool atomic_compare_exchange_weak(shared_ptr* pSharedPtr, shared_ptr* pSharedPtrCondition, shared_ptr sharedPtrNew) { return false; } + template inline bool atomic_compare_exchange_strong_explicit(shared_ptr* pSharedPtr, shared_ptr* pSharedPtrCondition, shared_ptr sharedPtrNew, ...) { return false; } + template inline bool atomic_compare_exchange_weak_explicit(shared_ptr* pSharedPtr, shared_ptr* pSharedPtrCondition, shared_ptr sharedPtrNew, ...) { return false; } +} + + +int TestThreadSmartPtr() +{ + int nErrorCount(0); + + // C11 atomics & eastl::shared_ptr name collision tests. + { + // If you are seeing compiler errors regarding C11 atomic macro + // expansions here look at header file eathread_atomic_android_c11.h. + // Ensure C11 atomic macros are being undefined properly. We hit this + // problem on android-gcc config. + using namespace not_eastl; + shared_ptr ptr1, ptr2, ptr3; + + atomic_is_lock_free(&ptr1); + atomic_load(&ptr1); + atomic_load_explicit(&ptr1); + atomic_store(&ptr1, ptr2); + atomic_store_explicit(&ptr1, ptr2); + atomic_exchange(&ptr1, ptr2); + atomic_exchange_explicit(&ptr1, ptr2); + atomic_compare_exchange_strong(&ptr1, &ptr2, ptr3); + atomic_compare_exchange_weak(&ptr1, &ptr2, ptr3); + atomic_compare_exchange_strong_explicit(&ptr1, &ptr2, ptr3); + atomic_compare_exchange_strong_explicit(&ptr1, &ptr2, ptr3); + } + + { // Basic single-threaded test. + typedef shared_ptr_mt TestClassSharedPtr; + + // typedef T element_type; + EATEST_VERIFY_MSG(sizeof(TestClassSharedPtr::element_type) > 0, "shared_ptr_mt failure."); + + // typedef T value_type; + EATEST_VERIFY_MSG(sizeof(TestClassSharedPtr::value_type) > 0, "shared_ptr_mt failure"); + + // explicit shared_ptr_mt(T* pValue = 0) + TestClassSharedPtr pSharedPtr0; + TestClassSharedPtr pSharedPtr1(new TestClass); + TestClassSharedPtr pSharedPtr2(new TestClass); + + // shared_ptr_mt(shared_ptr_mt const& sharedPtr) + TestClassSharedPtr pSharedPtr3(pSharedPtr1); + + // void lock() const + // void unlock() const + pSharedPtr0.lock(); + pSharedPtr0.unlock(); + + // operator bool_() const + EATEST_VERIFY_MSG(IsFalse(pSharedPtr0), "shared_ptr_mt failure"); + EATEST_VERIFY_MSG( IsTrue(pSharedPtr1), "shared_ptr_mt failure"); + + // bool operator!() const + EATEST_VERIFY_MSG( IsTrue(!pSharedPtr0), "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(IsFalse(!pSharedPtr1), "shared_ptr_mt failure"); + + // int use_count() const + EATEST_VERIFY_MSG(pSharedPtr0.use_count() == 1, "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(pSharedPtr1.use_count() == 2, "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(pSharedPtr2.use_count() == 1, "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(pSharedPtr3.use_count() == 2, "shared_ptr_mt failure"); + + // bool unique() const + EATEST_VERIFY_MSG( pSharedPtr0.unique(), "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(!pSharedPtr1.unique(), "shared_ptr_mt failure"); + EATEST_VERIFY_MSG( pSharedPtr2.unique(), "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(!pSharedPtr3.unique(), "shared_ptr_mt failure"); + + // shared_ptr_mt& operator=(shared_ptr_mt const& sharedPtr) + pSharedPtr3 = pSharedPtr2; + + EATEST_VERIFY_MSG(pSharedPtr1.use_count() == 1, "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(pSharedPtr2.use_count() == 2, "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(pSharedPtr3.use_count() == 2, "shared_ptr_mt failure"); + + // void reset(T* pValue = 0) + pSharedPtr2.reset(); + + EATEST_VERIFY_MSG(IsFalse(pSharedPtr2), "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(pSharedPtr1.use_count() == 1, "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(pSharedPtr2.use_count() == 1, "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(pSharedPtr3.use_count() == 1, "shared_ptr_mt failure"); + + pSharedPtr3.reset(new TestClass); + + EATEST_VERIFY_MSG(IsTrue(pSharedPtr3), "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(pSharedPtr3.use_count() == 1, "shared_ptr_mt failure"); + + // T* get() const + TestClass* pA = pSharedPtr1.get(); + TestClass* pB = pSharedPtr3.get(); + + // template + // inline T* get_pointer(const shared_ptr_mt& sharedPtr) + EATEST_VERIFY_MSG(get_pointer(pSharedPtr1) == pA, "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(get_pointer(pSharedPtr3) == pB, "shared_ptr_mt failure"); + + // void swap(shared_ptr_mt& sharedPtr) + pSharedPtr1.swap(pSharedPtr3); + + EATEST_VERIFY_MSG(pSharedPtr1.get() == pB, "shared_ptr_mt failure"); + EATEST_VERIFY_MSG(pSharedPtr3.get() == pA, "shared_ptr_mt failure"); + + // T& operator*() const + (*pSharedPtr1).x = 37; + + EATEST_VERIFY_MSG(pSharedPtr1.get()->x == 37, "shared_ptr_mt failure"); + + // T* operator->() const + pSharedPtr1->x = 73; + + EATEST_VERIFY_MSG(pSharedPtr1.get()->x == 73, "shared_ptr_mt failure"); + + // template + // inline void swap(shared_ptr_mt& sharedPtr1, shared_ptr_mt& sharedPtr2) + swap(pSharedPtr1, pSharedPtr2); + + EATEST_VERIFY_MSG(pSharedPtr2.get()->x == 73, "shared_ptr_mt failure"); + + // template + // inline bool operator==(const shared_ptr_mt& sharedPtr1, const shared_ptr_mt& sharedPtr2) + bool bEqual = pSharedPtr1 == pSharedPtr2; + + EATEST_VERIFY_MSG(!bEqual, "shared_ptr_mt failure"); + + // template + // inline bool operator!=(const shared_ptr_mt& sharedPtr1, const shared_ptr_mt& sharedPtr2) + bool bNotEqual = pSharedPtr1 != pSharedPtr2; + + EATEST_VERIFY_MSG(bNotEqual, "shared_ptr_mt failure"); + + // template + // inline bool operator<(const shared_ptr_mt& sharedPtr1, const shared_ptr_mt& sharedPtr2) + bool bLessA = pSharedPtr1 < pSharedPtr2; + bool bLessB = pSharedPtr2 < pSharedPtr1; + + EATEST_VERIFY_MSG(bLessA || bLessB, "shared_ptr_mt failure"); + } + + + { + typedef shared_array_mt TestClassSharedArray; + + TestClassSharedArray pSharedArray(new TestClass[5]); + TestClassSharedArray pSharedArray2(pSharedArray); + + // To do: Implement the tests above. + } + + return nErrorCount; +} + + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSpinLock.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSpinLock.cpp new file mode 100644 index 00000000..327d43de --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSpinLock.cpp @@ -0,0 +1,75 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include + + +using namespace EA::Thread; + + +int TestThreadSpinLock() +{ + int nErrorCount(0); + + { // SpinLock -- Basic single-threaded test. + + SpinLock spinLock; + + EATEST_VERIFY_MSG(!spinLock.IsLocked(), "SpinLock failure"); + + spinLock.Lock(); + EATEST_VERIFY_MSG(spinLock.IsLocked(), "SpinLock failure"); + + EATEST_VERIFY_MSG(!spinLock.TryLock(), "SpinLock failure"); + + spinLock.Unlock(); + EATEST_VERIFY_MSG(!spinLock.IsLocked(), "SpinLock failure"); + + EATEST_VERIFY_MSG(spinLock.TryLock(), "SpinLock failure"); + EATEST_VERIFY_MSG(spinLock.IsLocked(), "SpinLock failure"); + + spinLock.Unlock(); + EATEST_VERIFY_MSG(!spinLock.IsLocked(), "SpinLock failure"); + } + + + { // AutoSpinLock -- Basic single-threaded test. + + SpinLock spinLock; + + EATEST_VERIFY_MSG(!spinLock.IsLocked(), "AutoSpinLock failure"); + + { //Special scope just for the AutoSpinLock + AutoSpinLock autoSpinLock(spinLock); + + EATEST_VERIFY_MSG(spinLock.IsLocked(), "AutoSpinLock failure"); + } + + EATEST_VERIFY_MSG(!spinLock.IsLocked(), "AutoSpinLock failure"); + } + + + #if EA_THREADS_AVAILABLE + { // Multithreaded test + + // Implement this when we get the thread class working. + // gTestLengthSeconds; + } + #endif + + return nErrorCount; +} + + + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadStorage.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadStorage.cpp new file mode 100644 index 00000000..76665cb1 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadStorage.cpp @@ -0,0 +1,236 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include + +using namespace EA::Thread; + +const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + + +/////////////////////////////////////////////////////////////////////////////// +// EA_THREAD_LOCAL tests +// +#ifdef EA_THREAD_LOCAL + + // Test declaration of basic int. + EA_THREAD_LOCAL int gTLI = 0; + + // Test declaration of static int. + static EA_THREAD_LOCAL int sTLI = 0; + + // Test declaration of struct. + struct ThreadLocalData + { + int x; + }; + + EA_THREAD_LOCAL ThreadLocalData gTDL; + +#endif + + + + +struct TLSWorkData +{ + AtomicInt32 mShouldBegin; + AtomicInt32 mShouldEnd; + AtomicInt32 mnErrorCount; + ThreadLocalStorage* mpTLS; + + TLSWorkData() + : mShouldBegin(0), mShouldEnd(0), mnErrorCount(0), mpTLS(NULL) {} + + // define copy ctor and assignment operator + // so the compiler does define them intrisically + TLSWorkData(const TLSWorkData& rhs); // copy constructor + TLSWorkData& operator=(const TLSWorkData& rhs); // assignment operator +}; + + + +static intptr_t TLSTestThreadFunction(void* pvWorkData) +{ + TLSWorkData* pWorkData = (TLSWorkData*)pvWorkData; + void* pValue; + bool bResult; + size_t i = 1; + int nErrorCount = 0; + + while(pWorkData->mShouldBegin.GetValue() == 0) // Spin until we get the should-begin signal. We could also use a Semaphore for this, but our use here is very simple and not performance-oriented. + EA::Thread::ThreadSleep(100); + + pValue = pWorkData->mpTLS->GetValue(); + EATEST_VERIFY_MSG(pValue == NULL, "ThreadLocalStorage failure."); + + do{ + bResult = pWorkData->mpTLS->SetValue((void*)i); + EATEST_VERIFY_MSG(bResult, "ThreadLocalStorage failure."); + + pValue = pWorkData->mpTLS->GetValue(); + EATEST_VERIFY_MSG(pValue == (void*)i, "ThreadLocalStorage failure."); + + i++; + + ThreadCooperativeYield(); // Used by cooperative threading platforms. + }while(pWorkData->mShouldEnd.GetValue() == 0); + + pValue = pWorkData->mpTLS->GetValue(); + EATEST_VERIFY_MSG(pValue != NULL, "ThreadLocalStorage failure."); + + pWorkData->mnErrorCount += nErrorCount; + + return 0; +} + + +static int TestThreadStorageSingle() +{ + int nErrorCount(0); + + { // Re-use test + void* pValue; + ThreadLocalStorage tlsUnused; + + { // Give this its own scope. + ThreadLocalStorage tls0; + + pValue = tls0.GetValue(); + EATEST_VERIFY_MSG(pValue == NULL, "ThreadLocalStorage failure."); + + tls0.SetValue((void*)1); + } + + ThreadLocalStorage tls1; + + pValue = tls1.GetValue(); + EATEST_VERIFY_MSG(pValue == NULL, "ThreadLocalStorage failure."); + } + + + { // Single-threaded test + #ifdef EA_THREAD_LOCAL + // EA_THREAD_LOCAL tests + gTLI = 1; + sTLI = 1; + gTDL.x = 1; + + if((gTLI + sTLI + gTDL.x) == 100000) // Prevent compiler warnings due to non-usage. + EA::UnitTest::ReportVerbosity(1, "EA_THREAD_LOCAL: %d %d %d\n", gTLI, sTLI, gTDL.x); + #endif + + // Test ThreadLocalStorage + ThreadLocalStorage tlsA; + ThreadLocalStorage tlsB; + ThreadLocalStorage tlsC; + void* pValue; + void* pNULL = (void*)0; + void* p1 = (void*)1; + void* p2 = (void*)2; + bool bResult; + + pValue = tlsA.GetValue(); + EATEST_VERIFY_MSG(pValue == pNULL, "ThreadLocalStorage failure."); + + pValue = tlsB.GetValue(); + EATEST_VERIFY_MSG(pValue == pNULL, "ThreadLocalStorage failure."); + + pValue = tlsC.GetValue(); + EATEST_VERIFY_MSG(pValue == pNULL, "ThreadLocalStorage failure."); + + bResult = tlsA.SetValue(pNULL); + EATEST_VERIFY_MSG(bResult, "ThreadLocalStorage failure."); + + pValue = tlsA.GetValue(); + EATEST_VERIFY_MSG(pValue == pNULL, "ThreadLocalStorage failure."); + + bResult = tlsA.SetValue(p1); + EATEST_VERIFY_MSG(bResult, "ThreadLocalStorage failure."); + + pValue = tlsA.GetValue(); + EATEST_VERIFY_MSG(pValue == p1, "ThreadLocalStorage failure."); + + bResult = tlsA.SetValue(pNULL); + EATEST_VERIFY_MSG(bResult, "ThreadLocalStorage failure."); + + pValue = tlsA.GetValue(); + EATEST_VERIFY_MSG(pValue == pNULL, "ThreadLocalStorage failure."); + + bResult = tlsA.SetValue(p2); + EATEST_VERIFY_MSG(bResult, "ThreadLocalStorage failure."); + + pValue = tlsA.GetValue(); + EATEST_VERIFY_MSG(pValue == p2, "ThreadLocalStorage failure."); + } + + return nErrorCount; +} + + +static int TestThreadStorageMultiple() +{ + int nErrorCount(0); + + const int kThreadCount(kMaxConcurrentThreadCount - 1); + Thread thread[kThreadCount]; + ThreadId threadId[kThreadCount]; + Thread::Status status; + int i; + TLSWorkData workData; + + for(i = 0; i < kThreadCount; i++) + { + threadId[i] = thread[i].Begin(TLSTestThreadFunction, &workData); + EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: Couldn't create thread."); + } + + workData.mpTLS = new ThreadLocalStorage; + workData.mShouldBegin.SetValue(1); + + EA::UnitTest::ThreadSleepRandom(2000, 2000); + + workData.mShouldEnd.SetValue(1); + + EA::UnitTest::ThreadSleepRandom(1000, 1000); + + for(i = 0; i < kThreadCount; i++) + { + if(threadId[i] != kThreadIdInvalid) + { + status = thread[i].WaitForEnd(GetThreadTime() + 30000); + EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread(s) didn't end."); + } + } + + delete workData.mpTLS; + workData.mpTLS = NULL; + + nErrorCount += (int)workData.mnErrorCount; + + return nErrorCount; +} + + +int TestThreadStorage() +{ + int nErrorCount(0); + + nErrorCount += TestThreadStorageSingle(); + + #if EA_THREADS_AVAILABLE + // Call this twice, to make sure recyling of TLS works properly. + nErrorCount += TestThreadStorageMultiple(); + nErrorCount += TestThreadStorageMultiple(); + #endif + + return nErrorCount; +} + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSync.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSync.cpp new file mode 100644 index 00000000..964fc6f9 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadSync.cpp @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include + + +int TestThreadSync() +{ + using namespace EA::Thread; + + int nErrorCount(0); + + EAReadBarrier(); + EAWriteBarrier(); + EAReadWriteBarrier(); + EACompilerMemoryBarrier(); + + return nErrorCount; +} + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadThread.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadThread.cpp new file mode 100644 index 00000000..d5f3e1c6 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadThread.cpp @@ -0,0 +1,1508 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef EA_PLATFORM_LINUX + #include +#endif + +#ifdef EA_PLATFORM_MICROSOFT + EA_DISABLE_ALL_VC_WARNINGS() + #include + EA_RESTORE_ALL_VC_WARNINGS() +#endif + +#include + +using namespace EA::Thread; + + +const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; +static unsigned int sThreadTestTimeMS = 2000; // We potentially change this value below. +static AtomicInt32 sThreadCount = 0; +static AtomicInt32 sShouldGo = 0; + + +static intptr_t TestFunction1(void*) +{ + ThreadTime nTimeEnd = EA::Thread::GetThreadTime() + sThreadTestTimeMS; + + while(EA::Thread::GetThreadTime() < nTimeEnd) + ThreadSleep(); + return 0; +} + + +static intptr_t TestFunction3(void*) +{ + return 0; +} + + +static intptr_t TestFunction4(void* arg) +{ + const intptr_t returnValue = (intptr_t)arg; + + EA::UnitTest::ThreadSleepRandom(0, 5); + + return returnValue; +} + + +#if !defined(EA_PLATFORM_MOBILE) + static intptr_t TestFunction6(void* arg) + { + const intptr_t returnValue = (intptr_t)arg; + + sThreadCount++; + + while(sShouldGo == 0) + ThreadSleep(10); + + EA::UnitTest::ThreadSleepRandom(3, 8); + + sThreadCount--; + + return returnValue; + } +#endif + + +static intptr_t TestFunction7(void*) +{ + while(sShouldGo == 0) + ThreadSleep(10); + + return 0; +} + + +static intptr_t TestFunction_GetThreadProcessor(void*) +{ + return (intptr_t)GetThreadProcessor(); +} + +static int findSetBitIndex(int requestedIndex, uint64_t availableAffinityMask) +{ + int correctedIndex = -1; + int64_t mask = 1; + do + { + if ((availableAffinityMask & mask) != 0) + --requestedIndex; + + ++correctedIndex; + mask <<= 1; + } while (requestedIndex >= 0); + + return correctedIndex; +} + +static int calculateAvailableProcessorIndex(int index) +{ + using namespace EA::Thread; + const auto affinityMask = GetAvailableCpuAffinityMask(); + const int availableCpuCount = EA::StdC::CountBits64(affinityMask); + const int properIndex = findSetBitIndex(index % availableCpuCount, affinityMask); + return properIndex; +} + +static intptr_t TestFunction_SetAffinityAndWait(void* arg) +{ + const int requestedCore = *(static_cast(arg)); + const int coreIndex = calculateAvailableProcessorIndex(requestedCore); + const ThreadAffinityMask affinityMask = UINT64_C(1) << coreIndex; + + SetThreadAffinityMask(affinityMask); + + while (GetThreadProcessor() != coreIndex) + { + SetThreadAffinityMask(affinityMask); + ThreadSleep(0); + } + + return (intptr_t)coreIndex; +} + +static intptr_t TestFunction_SetThreadProcessorAndWait(void* arg) +{ + const int requestedCore = *(static_cast(arg)); + const int coreIndex = calculateAvailableProcessorIndex(requestedCore); + + SetThreadProcessor(coreIndex); + + while (GetThreadProcessor() != coreIndex) + { + SetThreadProcessor(coreIndex); + ThreadSleep(0); + } + + return (intptr_t)coreIndex; +} + +static intptr_t TestFunction_WaitForThreadMigration(void* arg) +{ + sThreadCount++; + + const int requestedCore = *(static_cast(arg)); + + while (GetThreadProcessor() != requestedCore) + ThreadSleep(0); + + sThreadCount--; + + return (intptr_t)requestedCore; +} + +static intptr_t TestFunction13(void* arg) +{ + ThreadSleep(10); + ThreadEnd(42); // 42 is a magic number we will verify gets passed through the user. + return 0; +} + +static intptr_t TestFunction3ExceptionWrapper(RunnableFunction defaultRunnableFunction, void* pContext) +{ + return defaultRunnableFunction(pContext); +} + + +class TestRunnable1 : public IRunnable +{ + intptr_t Run(void*) + { + const ThreadTime nTimeEnd = EA::Thread::GetThreadTime() + sThreadTestTimeMS; + while (EA::Thread::GetThreadTime() < nTimeEnd) + ThreadSleep(); + return 0; + } +} gTestRunnable1; + + +class TestRunnable2 : public IRunnable +{ + intptr_t Run(void*) + { + const ThreadTime nTimeEnd = EA::Thread::GetThreadTime() + sThreadTestTimeMS; + + while(EA::Thread::GetThreadTime() < nTimeEnd) + { + ThreadSleep(); + } + return 0; + } +} gTestRunnable2; + +static intptr_t TestRunnable3ExceptionWrapper(IRunnable* defaultRunnableFunction, void* pContext) +{ + return defaultRunnableFunction->Run(pContext); +} + + +class TestRunnable3 : public IRunnable +{ + intptr_t Run(void*) + { + ThreadSleep(500); + return 0; + } +} gTestRunnable3; + +class TestRunnable4 : public IRunnable +{ + intptr_t Run(void* args) + { + // IRunnable object that returns the thread id that executed on. + return TestFunction_WaitForThreadMigration(args); + } +} gTestRunnable4; + + +int TestThreadAffinityMask() +{ + int nErrorCount = 0; + const int TIMEOUT = 30000; + const int MAX_ITERATIONS = 16; + + auto VERIFY_AFFINITY_RESULT = [&](const char* name, intptr_t in_result, int count) + { + #if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED + const int nAvailableProcessors = EA::StdC::CountBits64(GetAvailableCpuAffinityMask()); + auto result = static_cast(in_result); + EATEST_VERIFY_F( + result == calculateAvailableProcessorIndex(count), + "Thread '%s' failure: SetAffinityMask not working properly. Thread ran on: %d/%d <=> Expected: %d\n", name, + result, nAvailableProcessors, calculateAvailableProcessorIndex(count)); + #endif + }; + + int count = MAX_ITERATIONS; + while(--count) + { + int properIndex = calculateAvailableProcessorIndex(count); + // Test Thread Affinity Masks (thread parameters) + Thread thread; + ThreadParameters params; + + params.mnAffinityMask = INT64_C(1) << properIndex; + params.mnProcessor = kProcessorAny; + thread.Begin(TestFunction_GetThreadProcessor, NULL, ¶ms); + + intptr_t result = 0; + thread.WaitForEnd(GetThreadTime() + TIMEOUT, &result); + + VERIFY_AFFINITY_RESULT("Parameters", result, properIndex); + } + + count = MAX_ITERATIONS; + while(--count) + { + int startIndex = calculateAvailableProcessorIndex(count + 1); // We explicitly want it to start on another core. + int properIndex = calculateAvailableProcessorIndex(count); + + sThreadCount = 0; + + // Test Thread Affinity Masks (thread object) + ThreadParameters params; + params.mnProcessor = startIndex; + + Thread thread; + thread.Begin(TestFunction_WaitForThreadMigration, &properIndex, ¶ms); // sleeps then grabs the current thread id. + + #if defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_NX) + // spin while we wait for the thread to startup + while (sThreadCount == 0) + ThreadSleep(1); + #endif + + // after thread has started, set the requested affinity + thread.SetAffinityMask(INT64_C(1) << properIndex); + + // wait for the thread to end + intptr_t result = 0; + thread.WaitForEnd(GetThreadTime() + TIMEOUT, &result); + + VERIFY_AFFINITY_RESULT("Object", result, properIndex); + } + + count = MAX_ITERATIONS; + while(--count) + { + int properIndex = calculateAvailableProcessorIndex(count); + + // Test Thread Affinity Masks (global functions) + ThreadParameters params; + params.mnProcessor = kProcessorAny; + + Thread thread; + thread.Begin(TestFunction_SetAffinityAndWait, &properIndex, ¶ms); + + intptr_t result = 0; + thread.WaitForEnd(GetThreadTime() + TIMEOUT, &result); + + VERIFY_AFFINITY_RESULT("Global Function", result, properIndex); + } + + count = MAX_ITERATIONS; + while(--count) + { + int properIndex = calculateAvailableProcessorIndex(count); + + // Test Thread Affinity Masks (thread parameters) - For IRunnable variant of the Thread::Begin function + ThreadParameters params; + params.mnProcessor = kProcessorAny; + params.mnAffinityMask = INT64_C(1) << properIndex; + params.mnProcessor = kProcessorAny; + + Thread thread; + thread.Begin(&gTestRunnable4, &properIndex, ¶ms); // sleeps then grabs the current thread id. + + intptr_t result = 0; + thread.WaitForEnd(GetThreadTime() + TIMEOUT, &result); + + VERIFY_AFFINITY_RESULT("IRunnable Thread Parameters", result, properIndex); + } + + // user regression of passing a -1 as an affinity mask. + { + // NOTE(rparolin): common user mistake. Ensure that we mask out invalid cpu flags and threads start up at least. + int userRegressionMask = ~0u; + + ThreadParameters params; + params.mnProcessor = kProcessorAny; + params.mnAffinityMask = userRegressionMask; + params.mnProcessor = kProcessorAny; + + Thread thread; + thread.Begin(TestFunction3, nullptr, ¶ms); + thread.WaitForEnd(GetThreadTime() + TIMEOUT); + } + + return nErrorCount; +} + + +int TestThreadPriorities() +{ + int nErrorCount = 0; + + if(!IsSuperUser()) + { + EA::EAMain::Report("Skipping Thread Priority test because we don't have sufficient system priviliages.\n"); + return nErrorCount; + } + + // Verify that thread priorities act as expected. + // Threads with higher priorities should execute instead of or before threads of lower priorities. + // On some platforms (e.g. Windows), lower priority threads do get some execution time, so we have to recognize that. + + // Create 20 threads of very high priority, 20 threads of high priority, and 20 threads of regular priority. + // Start the 20 very high priority threads first. + // Wait a bit then start the other 40 threads. + // Quit all the very high priority threads. + // Wait a bit, while having the 40 threads measure how much time they execute. + // Quit the remaining 40 threads. + // Verify that the 20 high priority threads executed much more than the regular threads. + + struct PriorityTestThread : public EA::Thread::IRunnable + { + EA::Thread::Thread mThread; + EA::Thread::ThreadParameters mParameters; + EA::Thread::Semaphore mSemaphore; + char mThreadName[16]; + volatile uint64_t mCounter = 0; + volatile bool mbShouldRun = true; + char mPadd[EA_CACHE_LINE_SIZE];// make sure that these structures end up on different cachelines + + intptr_t Run(void*) + { + mSemaphore.Wait(); + + while(mbShouldRun) + { + mCounter++; + EAReadBarrier(); + } + + return 0; + } + }; + + if((EA::Thread::GetProcessorCount() >= 4)) + { + const int kThreadCount = 4; + PriorityTestThread threadHighestPriority[kThreadCount]; + PriorityTestThread threadRegularPriority[kThreadCount]; + PriorityTestThread threadHighPriority[kThreadCount]; + EA::StdC::LimitStopwatch limitStopwatch(EA::StdC::Stopwatch::kUnitsSeconds); + const EA::Thread::ThreadAffinityMask kCommonAffinityMask = 0xf; // first 4 cores only + + EA::Thread::ThreadParameters commonParams; + commonParams.mbDisablePriorityBoost = true; // we can disable boosting if we want a better simulation of console-like behavior + commonParams.mnAffinityMask = kCommonAffinityMask; + commonParams.mnProcessor = kProcessorAny; + +#if defined(EA_PLATFORM_MICROSOFT) + // Due to how windows thread priorities work we need to further increase the thread priority of the high threads + // If this is not done the test will randomly have the regular priority have a higher count than the high threads. + const int kHigherPriorityDelta = kThreadPriorityMax - 2; +#else + const int kHigherPriorityDelta = 0; +#endif + + const auto highestPriority = EA::Thread::kThreadPriorityDefault + 2 + kHigherPriorityDelta; + const auto highPriority = EA::Thread::kThreadPriorityDefault + 1 + kHigherPriorityDelta; + const auto regularPriority = EA::Thread::kThreadPriorityDefault + 0; + + EA::Thread::SetThreadPriority(EA::Thread::kThreadPriorityDefault + 3); + for (int i = 0; i < kThreadCount; i++) + { + { + PriorityTestThread& highestThread = threadHighestPriority[i]; + highestThread.mParameters = commonParams; + highestThread.mParameters.mnPriority = highestPriority; + EA::StdC::Sprintf(highestThread.mThreadName, "Highest%d", i); + highestThread.mParameters.mpName = highestThread.mThreadName; + highestThread.mThread.Begin(&highestThread, NULL, &highestThread.mParameters); + } + { + PriorityTestThread& regularThread = threadRegularPriority[i]; + regularThread.mParameters = commonParams; + regularThread.mParameters.mnPriority = regularPriority; + EA::StdC::Sprintf(regularThread.mThreadName, "Reg%d", i); + regularThread.mParameters.mpName = regularThread.mThreadName; + regularThread.mThread.Begin(®ularThread, NULL, ®ularThread.mParameters); + } + { + PriorityTestThread& highThread = threadHighPriority[i]; + highThread.mParameters = commonParams; + highThread.mParameters.mnPriority = highPriority; + EA::StdC::Sprintf(highThread.mThreadName, "High%d", i); + highThread.mParameters.mpName = highThread.mThreadName; + highThread.mThread.Begin(&highThread, NULL, &highThread.mParameters); + } + } + + limitStopwatch.SetTimeLimit(1, true); + while(!limitStopwatch.IsTimeUp()) + { /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ } + + for(int i = 0; i < kThreadCount; i++) + { + threadHighestPriority[i].mSemaphore.Post(1); + threadHighPriority[i].mSemaphore.Post(1); + threadRegularPriority[i].mSemaphore.Post(1); + } + + limitStopwatch.SetTimeLimit(3, true); + while(!limitStopwatch.IsTimeUp()) + { /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ } + + for(int i = 0; i < kThreadCount; i++) + threadHighestPriority[i].mbShouldRun = false; + EAWriteBarrier(); + + limitStopwatch.SetTimeLimit(3, true); + while(!limitStopwatch.IsTimeUp()) + { /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ } + + for(int i = 0; i < kThreadCount; i++) + { + threadHighPriority[i].mbShouldRun = false; + threadRegularPriority[i].mbShouldRun = false; + } + EAWriteBarrier(); + + limitStopwatch.SetTimeLimit(3, true); + while(!limitStopwatch.IsTimeUp()) + { /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ } + + + // Wait for the threads to end before continuing. + for (int i = 0; i < kThreadCount; i++) + { + threadHighestPriority[i].mThread.WaitForEnd(); + threadHighPriority[i].mThread.WaitForEnd(); + threadRegularPriority[i].mThread.WaitForEnd(); + } + + EA::Thread::SetThreadPriority(EA::Thread::kThreadPriorityDefault); + + EAReadWriteBarrier(); + + // Tally the thread priority counters + uint64_t highPriorityCount = 0; + uint64_t regularPriorityCount = 0; + for(int i = 0; i < kThreadCount; i++) + highPriorityCount += threadHighPriority[i].mCounter; + for(int i = 0; i < kThreadCount; i++) + regularPriorityCount += threadRegularPriority[i].mCounter; + + #ifndef EA_PLATFORM_LINUX + // TODO(rparolin): This unreliable on various linux distros. Requires investigation. + EATEST_VERIFY_F(highPriorityCount > regularPriorityCount, + "Priority execution failure: highPriorityCount: %I64u, regularPriorityCount: %I64u", + highPriorityCount, regularPriorityCount); + #endif + } + + return nErrorCount; +} + +int TestSetThreadProcessConstants() +{ + int nErrorCount = 0; + + // testing a user reported regression of negative value shifts + for(auto k : { kProcessorDefault, kProcessorAny }) + { + Thread t; + + t.Begin([](void* param) -> intptr_t + { + int kConstant = *(int*)param; + SetThreadProcessor(kConstant); + return 0; + }, &k); + t.WaitForEnd(); + } + + return nErrorCount; +} + +int TestNullThreadNames() +{ + int nErrorCount = 0; + + { + ThreadParameters threadParams; + threadParams.mpName = nullptr; + + Thread t; + t.Begin([](void*) -> intptr_t { return 0; }, NULL, &threadParams); + t.WaitForEnd(); + } + + return nErrorCount; +} + +int TestLambdaThreads() +{ + int nErrorCount = 0; + + { // test rvalue + int foo = 0; + MakeThread([&] + { + EATEST_VERIFY(foo == 0); + foo = 42; + }) + .WaitForEnd(); + EATEST_VERIFY(foo == 42); + } + + { // test lvalue + int foo = 0; + + auto callme = [&] + { + EATEST_VERIFY(foo == 0); + foo = 42; + }; + + MakeThread(callme).WaitForEnd(); + EATEST_VERIFY(foo == 42); + } + + { // test thread parameters + const char* MY_THREAD_NAME = "my thread name"; + ThreadParameters params; + params.mpName = MY_THREAD_NAME; + + MakeThread( + [&] { EATEST_VERIFY(strncmp(MY_THREAD_NAME, GetThreadName(), EATHREAD_NAME_SIZE) == 0); }, params) + .WaitForEnd(); + } + + return nErrorCount; +} + +int TestThreadDynamicData() +{ + int nErrorCount = 0; + + const int kOverflowDynamicDataCount = 256; // Must be greater than EA::Thread::kMaxThreadDynamicDataCount. + + for(int i = 0; i < kOverflowDynamicDataCount; i++) + { + EA::Thread::ThreadId id = 0; + EA::Thread::SysThreadId sysId = 0; + + MakeThread([&] + { + id = EA::Thread::GetThreadId(); + sysId = EA::Thread::GetSysThreadId(); + }) + .WaitForEnd(); + + EATEST_VERIFY(FindThreadDynamicData(id) == nullptr); + EATEST_VERIFY(FindThreadDynamicData(sysId) == nullptr); + } + + return nErrorCount; +} + +int TestSetThreadProcessor() +{ + int nErrorCount = 0; + + const int TIMEOUT = 30000; + + auto VERIFY_THREAD_PROCESSOR_RESULT = [&](const char* name, intptr_t in_result, int count) + { + #if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED + const int nAvailableProcessors = EA::StdC::CountBits64(EA::Thread::GetAvailableCpuAffinityMask()); + auto result = static_cast(in_result); + EATEST_VERIFY_F( + result == calculateAvailableProcessorIndex(count), + "Thread '%s' failure: SetAffinityMask not working properly. Thread ran on: %d/%d <=> Expected: %d\n", name, + result, nAvailableProcessors, calculateAvailableProcessorIndex(count)); + #endif + }; + + int count = EA::StdC::CountBits64(EA::Thread::GetAvailableCpuAffinityMask()); + while(--count) + { + int correctIndex = calculateAvailableProcessorIndex(count); + Thread thread; + thread.Begin(TestFunction_SetThreadProcessorAndWait, &correctIndex, nullptr); // sleeps then grabs the current thread id. + + // wait for the thread to end + intptr_t result = 0; + thread.WaitForEnd(GetThreadTime() + TIMEOUT, &result); + + VERIFY_THREAD_PROCESSOR_RESULT("SetThreadProcessor", result, correctIndex); + } + + return nErrorCount; +} + +int TestSetProcessor() +{ + int nErrorCount = 0; + + const int TIMEOUT = 30000; + const int MAX_ITERATIONS = 16; + + auto VERIFY_SET_PROCESSOR_RESULT = [&](const char* name, intptr_t in_result, int count) + { + #if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED + const int nAvailableProcessors = EA::StdC::CountBits64(EA::Thread::GetAvailableCpuAffinityMask()); + auto result = static_cast(in_result); + EATEST_VERIFY_F( + result == calculateAvailableProcessorIndex(count), + "Thread '%s' failure: SetProcessor not working properly. Thread ran on: %d/%d <=> Expected: %d\n", name, + result, nAvailableProcessors, calculateAvailableProcessorIndex(count)); + #endif + }; + + + int count = MAX_ITERATIONS; + while(--count) + { + int startIndex = calculateAvailableProcessorIndex(count + 1); // We explicitly want it to start on another core. + int properIndex = calculateAvailableProcessorIndex(count); + + sThreadCount = 0; + + // Test Thread Affinity Masks (thread object) + ThreadParameters params; + params.mnProcessor = startIndex; + + Thread thread; + thread.Begin(TestFunction_WaitForThreadMigration, &properIndex, ¶ms); // sleeps then grabs the current thread id. + + #if defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_NX) + // spin while we wait for the thread to startup + while (sThreadCount == 0) + ThreadSleep(0); + #endif + + // after thread has started, set the requested affinity + thread.SetProcessor(properIndex); + + // wait for the thread to end + intptr_t result = 0; + thread.WaitForEnd(GetThreadTime() + TIMEOUT, &result); + + VERIFY_SET_PROCESSOR_RESULT("SetProcessor", result, properIndex); + } + + return nErrorCount; +} + +int TestSetThreadProcessorByThreadParams() +{ + // Exercise EA::Thread::GetThreadId, EA::Thread::GetSysThreadId, EAThreadGetUniqueId, SetThreadProcessor, GetThreadProcessor. + + // Create and start N threads paused. + // Release all threads to run at once. + // Have each of the threads record its EAThreadGetUniqueId value and exit. + // Verify that there were no collisions in the recorded id values. + int nErrorCount = 0; + + struct IdTestThread : public EA::Thread::IRunnable + { + EA::Thread::Thread mThread; // The Thread object. + EA::Thread::Semaphore mSemaphore; // Used to pause the thread after it starts. + EA::Thread::ThreadUniqueId mUniqueId; // The EAThreadUniqueId that this thread gets assigned by the OS. + EA::Thread::ThreadId mThreadId; // The EAThreadUniqueId that this thread gets assigned by the OS. + EA::Thread::SysThreadId mSysThreadId; // The EAThreadUniqueId that this thread gets assigned by the OS. + int mAssignedProcessorId; // The processor id that we ask the OS to run this thread on. + int mProcessorId; // The processor id that this thread gets assigned by the OS. Should equal mAssignedProcessorId. + + IdTestThread() : mThread(), mSemaphore(0), mUniqueId(), mThreadId(), mSysThreadId(), mAssignedProcessorId(), mProcessorId() {} + IdTestThread(const IdTestThread&){} // Avoid compiler warnings. + void operator=(const IdTestThread&){} // Avoid compiler warnings. + + intptr_t Run(void*) + { + mSemaphore.Wait(); + EAThreadGetUniqueId(mUniqueId); + mThreadId = EA::Thread::GetThreadId(); + mSysThreadId = EA::Thread::GetSysThreadId(); + mProcessorId = EA::Thread::GetThreadProcessor(); + EAWriteBarrier(); + return 0; + } + }; + + #if defined(EA_PLATFORM_DESKTOP) + const int kThreadCount = 100; + #elif (EA_PLATFORM_WORD_SIZE == 8) + const int kThreadCount = 50; + #else + const int kThreadCount = 16; + #endif + + IdTestThread thread[kThreadCount]; + ThreadParameters threadParams; + EA::UnitTest::RandGenT random(EA::UnitTest::GetRandSeed()); + const int processorCount = EA::StdC::CountBits64(EA::Thread::GetAvailableCpuAffinityMask()); + + for(int i = 0; i < kThreadCount; i++) + { + threadParams.mnProcessor = calculateAvailableProcessorIndex(random(processorCount)); + threadParams.mpName = "IdTest"; + thread[i].mAssignedProcessorId = threadParams.mnProcessor; + thread[i].mThread.Begin(&thread[i], NULL, &threadParams); + } + + EA::UnitTest::ThreadSleep(1000); + + for(int i = 0; i < kThreadCount; i++) + thread[i].mSemaphore.Post(1); + + EA::UnitTest::ThreadSleep(1000); + + for(int i = 0; i < kThreadCount; i++) + thread[i].mThread.WaitForEnd(); + + EAReadBarrier(); + + EA::Thread::ThreadUniqueId uniqueIdArray[kThreadCount]; + EA::Thread::ThreadId idArray[kThreadCount]; + EA::Thread::SysThreadId sysIdArray[kThreadCount]; + + // Problem: We don't have an EAThreadEqual(const ThreadId&, const ThreadId&) function, but could use one. + // If we had such a thing, then we wouldn't need the odd code below and could probably use an eastl::set. + for(int i = 0; i < kThreadCount; i++) + { + memset(&uniqueIdArray[i], 0, sizeof(EA::Thread::ThreadUniqueId)); + memset(&idArray[i], 0, sizeof(EA::Thread::ThreadId)); + memset(&sysIdArray[i], 0, sizeof(EA::Thread::SysThreadId)); + } + + for(int i = 0; i < kThreadCount; i++) + { + for(int j = 0; j < i; j++) + EATEST_VERIFY(memcmp(&uniqueIdArray[j], &thread[i].mUniqueId, sizeof(EA::Thread::ThreadUniqueId)) != 0); + uniqueIdArray[i] = thread[i].mUniqueId; + + for(int j = 0; j < i; j++) + EATEST_VERIFY(memcmp(&idArray[j], &thread[i].mThreadId, sizeof(EA::Thread::ThreadId)) != 0); + idArray[i] = thread[i].mThreadId; + + for(int j = 0; j < i; j++) + EATEST_VERIFY(memcmp(&sysIdArray[j], &thread[i].mSysThreadId, sizeof(EA::Thread::SysThreadId)) != 0); + sysIdArray[i] = thread[i].mSysThreadId; + + #if defined(EA_PLATFORM_CONSOLE) && defined(EA_PLATFORM_MICROSOFT) + EATEST_VERIFY_F(thread[i].mProcessorId == thread[i].mAssignedProcessorId, + " Error: Thread assigned to run on processor %d, found to be running on processor %d.", + thread[i].mAssignedProcessorId, thread[i].mProcessorId); + #endif + } + + return nErrorCount; +} + + +int TestThreadDisablePriorityBoost() +{ + int nErrorCount = 0; + +#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX) + { + Thread thread; + ThreadParameters params; + + auto priorityBoostTester = [&](BOOL expectedDisablePriorityBoost) + { + thread.Begin( + [](void* pInExpectedDisablePriorityBoost) -> intptr_t + { + BOOL* pExpectedDisablePriorityBoost = (BOOL*)pInExpectedDisablePriorityBoost; + int nErrorCount = 0; + PBOOL pDisablePriorityBoost = nullptr; + + auto result = GetThreadPriorityBoost(GetCurrentThread(), pDisablePriorityBoost); + EATEST_VERIFY_MSG(result != 0, "GetThreadPriorityBoost failed\n"); + EATEST_VERIFY_MSG((result != 0) && *pDisablePriorityBoost == *pExpectedDisablePriorityBoost, + "Thread Priority Boost was not disabled\n"); + + return nErrorCount; + }, + &expectedDisablePriorityBoost, ¶ms); + + intptr_t threadErrorCount = 0; + thread.WaitForEnd(GetThreadTime() + 30000, &threadErrorCount); + nErrorCount += (int)threadErrorCount; + }; + + + params.mbDisablePriorityBoost = true; + priorityBoostTester(TRUE); + + params.mbDisablePriorityBoost = false; + priorityBoostTester(FALSE); + } +#endif + + return nErrorCount; +} + + +int TestThreadParameters() +{ // Test ThreadParameters + int nErrorCount = 0; + Thread::Status status; + const int kThreadCount(kMaxConcurrentThreadCount - 1); + ThreadId threadId[kThreadCount]; + Thread thread[kThreadCount]; + int i; + ThreadParameters threadParameters; + + const int nOriginalPriority = GetThreadPriority(); + + // Set our thread priority to match that of the threads we will be creating below. + SetThreadPriority(kThreadPriorityDefault + 1); + + for(i = 0; i < kThreadCount; i++) + { + status = thread[i].GetStatus(); + EATEST_VERIFY_MSG(status == Thread::kStatusNone, "Thread failure: Thread should have kStatusNone (2).\n"); + + // ThreadParameters + threadParameters.mnStackSize = 32768; + threadParameters.mnPriority = kThreadPriorityDefault + 1; + threadParameters.mpName = "abcdefghijklmnopqrstuvwxyz"; // Make an overly large name. + + threadId[i] = thread[i].Begin(TestFunction1, NULL, &threadParameters); + + EATEST_VERIFY_MSG(threadId[i] != (ThreadId)kThreadIdInvalid, "Thread failure: ThreadBegin failed.\n"); + + // It turns out that you can't really do such a thing as set lower priority in native Linux, not with SCHED_OTHER, at least. + #if ((!defined(EA_PLATFORM_UNIX) && !defined(__APPLE__)) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY + EATEST_VERIFY_MSG(thread[i].GetPriority() == threadParameters.mnPriority, "Thread failure: Thread Priority not set correctly (2).\n"); + + #endif + + if(i > 0) + EATEST_VERIFY_MSG(threadId[i] != threadId[i-1], "Thread failure: Thread id collision (2).\n"); + } + + ThreadSleep(200); + + for(i = 0; i < kThreadCount; i++) + { + if(threadId[i] != kThreadIdInvalid) + thread[i].SetName("0123456789012345678901234567890"); // Make an overly large name. + } + + ThreadSleep(200); + + // It turns out that you can't really do such a thing as set lower priority in native Linux. + #if ((!defined(EA_PLATFORM_UNIX) || defined(__CYGWIN__)) && !defined(__APPLE__) && !EA_USE_CPP11_CONCURRENCY) + int nPriority; + + if(threadId[0] != kThreadIdInvalid) + { + nPriority = thread[0].GetPriority(); + + thread[0].SetPriority(nPriority - 1); + EATEST_VERIFY_MSG(thread[0].GetPriority() == nPriority - 1, "Thread failure: Thread Priority not set correctly (3.1).\n"); + + thread[0].SetPriority(nPriority); + EATEST_VERIFY_MSG(thread[0].GetPriority() == nPriority, "Thread failure: Thread Priority not set correctly (3.2).\n"); + + } + #endif + + for(i = 0; i < kThreadCount; i++) + { + if(threadId[i] != kThreadIdInvalid) + thread[i].WaitForEnd(GetThreadTime() + 30000); + } + + SetThreadPriority(kThreadPriorityDefault); + + for(i = 0; i < kThreadCount; i++) + { + status = thread[i].GetStatus(); + + if(threadId[i] != kThreadIdInvalid) + EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (2).\n"); + else + EATEST_VERIFY_MSG(status == Thread::kStatusNone, "Thread failure: Thread should have kStatusNone (2).\n"); + } + + SetThreadPriority(nOriginalPriority); + return nErrorCount; +} + +int TestThreadEnd() +{ + int nErrorCount(0); + EA::Thread::Thread thread; + Thread::Status status; + + thread.Begin(TestFunction13); + ThreadSleep(20); + + intptr_t returncode; + thread.WaitForEnd(); + status = thread.GetStatus(&returncode); + + EATEST_VERIFY_MSG(returncode == 42, "Thread return code failure: Expected return code 42."); + EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded.\n"); + + return nErrorCount; +} + +EA_OPTIMIZE_OFF() +int TestThreadThread() +{ + int nErrorCount(0); + + sThreadTestTimeMS = (gTestLengthSeconds * 1000) / 2; // '/2' because this test doesn't need so much time. + + nErrorCount += TestSetThreadProcessor(); + nErrorCount += TestSetProcessor(); + +#if !defined(EA_PLATFORM_UNIX) && !defined(EA_PLATFORM_MOBILE) + nErrorCount += TestThreadAffinityMask(); +#endif + + + nErrorCount += TestThreadEnd(); + + { + ThreadId threadId = GetThreadId(); + + EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "GetThreadId failure.\n"); + + SysThreadId sysThreadId = GetSysThreadId(threadId); + + EATEST_VERIFY_MSG(sysThreadId != kSysThreadIdInvalid, "GetSysThreadId failure.\n"); + + #if (defined(EA_PLATFORM_MICROSOFT) || defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_PS4)) && !EA_USE_CPP11_CONCURRENCY + const void* pStackBase = GetThreadStackBase(); + const void* pStackTop = &pStackBase; + const intptr_t stackSize = ((char*)pStackBase - (char*)pStackTop); + + // Verify that pStackBase is non-NULL and that the stack size is less than N MB. + EATEST_VERIFY_MSG(pStackBase && (stackSize < 10485760), "GetThreadStackBase failure.\n"); + + #endif + + // We disable this test for now on Kettle because although we have 7 cores available + // there is no guaranty the their ID are 0..6 and the system takes core ID 7 + #if !defined(EA_PLATFORM_PS4) + + int processorCount = GetProcessorCount(); + int processor = GetThreadProcessor(); + + // This isn't much of a test, but it at least exercizes the function. + EATEST_VERIFY_F(processor < processorCount, " Error: GetThreadProcessor [%d] >= GetProcessorCount [%d].\n", processor, processorCount); + #endif + + // To do: Test this: + // void SetThreadProcessor(int nProcessor); + } + + { // Test Current thread functionality + const int nOriginalPriority = GetThreadPriority(); + int nPriority = kThreadPriorityDefault; + + EATEST_VERIFY_MSG(nPriority >= kThreadPriorityMin, "Thread priority failure (1).\n"); + EATEST_VERIFY_MSG(nPriority <= kThreadPriorityMax, "Thread priority failure (2).\n"); + + // It turns out that you can't really do such a thing as set lower priority with most Unix threading subsystems. + // You can do so with Cygwin because it is just a pthreads API running on Windows OS/threading. + // C++11 thread libraries also provide no means to set or query thread priority. + #if (!defined(EA_PLATFORM_UNIX) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY + int nPriority1; + bool bResult; + + bResult = SetThreadPriority(nPriority); + EATEST_VERIFY_MSG(bResult, "Thread priority failure (3).\n"); + + bResult = SetThreadPriority(nPriority - 1); + EATEST_VERIFY_MSG(bResult, "Thread priority failure (4).\n"); + + nPriority1 = GetThreadPriority(); + EATEST_VERIFY_MSG(nPriority1 == nPriority - 1, "Thread priority failure (5).\n"); + + bResult = SetThreadPriority(nPriority + 1); + EATEST_VERIFY_MSG(bResult, "Thread priority failure (6).\n"); + + nPriority1 = GetThreadPriority(); + EATEST_VERIFY_MSG(nPriority1 == nPriority + 1, "Thread priority failure (7).\n"); + + bResult = SetThreadPriority(kThreadPriorityDefault); + EATEST_VERIFY_MSG(bResult, "Thread priority failure (8).\n"); + + nPriority1 = GetThreadPriority(); + EATEST_VERIFY_MSG(nPriority1 == kThreadPriorityDefault, "Thread priority failure (9).\n"); + + #endif + + SetThreadPriority(nOriginalPriority); + ThreadSleep(kTimeoutImmediate); + ThreadSleep(500); + } + + #if defined(EA_PLATFORM_WINDOWS) && !EA_USE_CPP11_CONCURRENCY + { // Try to reproduce Windows problem with Thread::GetStatus returning kStatusEnded when it should return kStatusRunning. + // On my current work machine (WinXP32, Single P4 CPU) this problem doesn't occur. But it might occur with others. + Thread::Status status; + Thread threadBackground[8]; + Thread thread; + + for(int i = 0; i < 8; i++) + threadBackground[i].Begin(TestFunction1); + + EA::Thread::SetThreadPriority(kThreadPriorityDefault + 2); + thread.Begin(TestFunction1); + status = thread.GetStatus(); + EA::Thread::SetThreadPriority(kThreadPriorityDefault); + + EATEST_VERIFY_MSG(status == Thread::kStatusRunning, "Thread failure: Thread should have kStatusRunning.\n"); + + thread.WaitForEnd(); + } + #endif + + + EA::Thread::Thread::SetGlobalRunnableFunctionUserWrapper(TestFunction3ExceptionWrapper); + EA::Thread::Thread::SetGlobalRunnableClassUserWrapper(TestRunnable3ExceptionWrapper); + + { // Test thread creation functionality. + Thread::Status status; + const int kThreadCount(kMaxConcurrentThreadCount - 1); + Thread thread[kThreadCount]; + ThreadId threadId[kThreadCount]; + int i; + + for(i = 0; i < kThreadCount; i++) + { + status = thread[i].GetStatus(); + EATEST_VERIFY_MSG(status == Thread::kStatusNone, "Thread failure: Thread should have kStatusNone (1).\n"); + + threadId[i] = thread[i].Begin(TestFunction1); + EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n"); + + threadId[i] = thread[i].Begin(TestFunction3); + EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction3) failed.\n"); + + threadId[i] = thread[i].Begin(&gTestRunnable3); + EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(&gTestRunnable3) failed.\n"); + + if(i > 0) + EATEST_VERIFY_MSG(threadId[i] != threadId[i-1], "Thread failure: Thread id collision (1).\n"); + } + + // It turns out that you can't really do such a thing as set lower priority in native Linux. + // C++11 threads also have no support for priorities + #if (!defined(EA_PLATFORM_UNIX) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY + int nPriority; + + nPriority = thread[0].GetPriority(); + thread[0].SetPriority(nPriority - 1); + + nPriority = thread[0].GetPriority(); + thread[0].SetPriority(nPriority + 1); + #endif + + ThreadSleep(200); + + for(i = 0; i < kThreadCount; i++) + { + if(threadId[i] != kThreadIdInvalid) + thread[i].WaitForEnd(GetThreadTime() + 30000); + } + + for(i = 0; i < kThreadCount; i++) + { + if(threadId[i] != kThreadIdInvalid) + { + status = thread[i].GetStatus(); + + EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (1).\n"); + } + } + } + + + nErrorCount += TestThreadParameters(); + + { + // Test if we can set and retrieve a custom thread name + Thread::Status status; + Thread thread; + ThreadId threadId; + const char* threadName = "Test_Thread"; + const char* defaultName = "DEFAULT"; + ThreadParameters threadParameters; + + threadParameters.mpName = defaultName; + + sThreadTestTimeMS = 10000; + threadId = thread.Begin(TestFunction1, NULL, &threadParameters); + + EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n"); + EATEST_VERIFY_MSG(strncmp(defaultName, thread.GetName(), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name used when initializing the thread."); + + thread.SetName(threadName); + + EATEST_VERIFY_MSG(strncmp(threadName, thread.GetName(), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name set in SetName."); + + ThreadSleep(sThreadTestTimeMS + 1000); + + if(threadId != kThreadIdInvalid) + { + thread.WaitForEnd(GetThreadTime() + 30000); + status = thread.GetStatus(); + EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (3).\n"); + } + } + + // Test the free standing function variant of the GetName/SetName API + { + // Test if we can set and retrieve a custom thread name + Thread::Status status; + Thread thread; + ThreadId threadId; + const char* threadName = "Test_Thread"; + const char* defaultName = "DEFAULT"; + + ThreadParameters threadParameters; + threadParameters.mpName = defaultName; + threadParameters.mnProcessor = EA::Thread::kProcessorAny; + threadParameters.mnAffinityMask = EA::Thread::GetAvailableCpuAffinityMask(); + + static volatile std::atomic sbThreadStarted; + static volatile std::atomic sbThreadTestDone; + sbThreadStarted = false; + sbThreadTestDone = false; + + threadId = thread.Begin( [](void*) -> intptr_t + { + sbThreadStarted = true; + while (!sbThreadTestDone) + ThreadSleep(); + return 0; + }, + NULL, &threadParameters); + + while(!sbThreadStarted) // Wait for thread to start up + ThreadSleep(); + + EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n"); + EATEST_VERIFY_MSG(strncmp(defaultName, GetThreadName(threadId), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name used when initializing the thread."); + + SetThreadName(threadId, threadName); + EATEST_VERIFY_MSG(strncmp(threadName, GetThreadName(threadId), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name set in SetName."); + + sbThreadTestDone = true; // signal that test is completed and the thread can shutdown + if(threadId != kThreadIdInvalid) + { + thread.WaitForEnd(GetThreadTime() + 30000); + status = thread.GetStatus(); + EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (3).\n"); + } + } + + + { + // Test the creation+destruction of many threads to make sure resources are recycled properly + + #if defined(EA_PLATFORM_DESKTOP) + const int kThreadCount(200); + #else + const int kThreadCount(20); + #endif + Thread thread; + ThreadId threadId; + intptr_t returnValue; + + EA::UnitTest::ReportVerbosity(1, "Creating many threads and then WaitForEnd()\n"); + + for(int i = 0; i < kThreadCount; i++) + { + threadId = thread.Begin(TestFunction4, reinterpret_cast((uintptr_t)i)); + + Thread::Status status = thread.GetStatus(&returnValue); + if(status != Thread::kStatusEnded) + thread.WaitForEnd(GetThreadTime() + 30000, &returnValue); + EA::UnitTest::ReportVerbosity(1, "Thread ended.\n"); + + EATEST_VERIFY_F(returnValue == i, "Thread failure: Thread return code is wrong (1). threadId: %s, status: %d, returnValue: %d\n", EAThreadThreadIdToString(threadId), (int)status, (int)returnValue); + if(returnValue != i) + EA::UnitTest::ReportVerbosity(1, " Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue); + + // Get the status again to make sure it returns the correct status. + thread.GetStatus(&returnValue); + EATEST_VERIFY_F(returnValue == i, "Thread failure: Thread return code is wrong (2). threadId: %s, status: %d, returnValue: %d\n", EAThreadThreadIdToString(threadId), (int)status, (int)returnValue); + if(returnValue != i) + EA::UnitTest::ReportVerbosity(1, " Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue); + } + + + EA::UnitTest::ReportVerbosity(1, "Creating many threads and then repeat GetStatus() until they finish\n"); + for(int i = 0; i < kThreadCount + 1; i++) + { + // Windows will get stuck in infinite loop if return value is 259 (STILL_ACTIVE) + if(i == 259) + ++i; + + threadId = thread.Begin(TestFunction4, reinterpret_cast((uintptr_t)i)); + + const ThreadTime nGiveUpTime = EA::Thread::GetThreadTime() + 20000; // Give up after N milliseconds. + + // Test to see if GetStatus() will recycle system resource properly + while(thread.GetStatus(&returnValue) != Thread::kStatusEnded) + { + ThreadSleep(100); + + if(EA::Thread::GetThreadTime() > nGiveUpTime) + { + EA::UnitTest::ReportVerbosity(1, "Thread failure: GetStatus failed.\n"); + break; + } + } + + EATEST_VERIFY_MSG(returnValue == i, "Thread failure: Thread return code is wrong (3).\n"); + if(returnValue != i) + EA::UnitTest::ReportVerbosity(1, " Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue); + + // Get the status again to make sure it returns the correct status. + thread.GetStatus(&returnValue); + EATEST_VERIFY_MSG(returnValue == i, "Thread failure: Thread return code is wrong (4).\n"); + if(returnValue != i) + EA::UnitTest::ReportVerbosity(1, " Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue); + + // See if calling WaitForEnd() now will result in a crash + thread.WaitForEnd(GetThreadTime() + 30000, &returnValue); + EATEST_VERIFY_MSG(returnValue == i, "Thread failure: Thread return code is wrong (5).\n"); + } + } + + { + // Test the creation of many threads + + #if defined(EA_PLATFORM_DESKTOP) + Thread::Status status; + const int kThreadCount(96); + Thread thread[kThreadCount]; + ThreadId threadId[kThreadCount]; + int i; + + sThreadTestTimeMS = 10000; + + for(i = 0; i < kThreadCount; i++) + { + threadId[i] = thread[i].Begin(TestFunction1); + EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n"); + } + + ThreadSleep(sThreadTestTimeMS + 1000); + + for(i = 0; i < kThreadCount; i++) + { + if(threadId[i] != kThreadIdInvalid) + thread[i].WaitForEnd(GetThreadTime() + 30000); + } + + for(i = 0; i < kThreadCount; i++) + { + if(threadId[i] != kThreadIdInvalid) + { + status = thread[i].GetStatus(); + EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (3).\n"); + } + } + #endif + } + + { + // Regression of Thread dtor behaviour - the dtor should not wait for created threads + // We reuse the atomic shouldgo to figure out what is really happening setting it to 0 + // initially and then incrementing it when the thread callback completes, allowing + // us to detect whether the dtor completed before the thread callback. + sShouldGo = 0; + { + // Create a scope for our thread object + Thread thread; + + // Get our thread going. It will sleep immediately for 2 seconds and then increment sShouldGo + ThreadId threadId = thread.Begin(TestFunction7); + EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: thread.Begin(TestFunction7) failed.\n"); + + // Now we exit our scope while our thread in theory still has a second before it completes + } // NOTE(rparolin): If your test hangs here, its most likely due to a semantic change in the thread object that is waiting for threads to complete. + + // Signal to the thread it is allowed to complete. + sShouldGo = 1; + } + + + { + int nOriginalPriority = EA::Thread::GetThreadPriority(); + EA::Thread::SetThreadPriority(kThreadPriorityDefault); + + // Tests setting and getting thread priority while the thread is active. + Thread::Status status; + Thread thread; + ThreadId threadId; + sShouldGo = 0; + + threadId = thread.Begin(TestFunction7); + EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction7) failed.\n"); + + // It turns out that you can't really do such a thing as set lower priority in native Linux. + #if ((!defined(EA_PLATFORM_LINUX) && !defined(__APPLE__) && !defined(EA_PLATFORM_BSD)) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY + int nPriority = thread.GetPriority(); + + thread.SetPriority(nPriority - 1); + if(EATEST_VERIFY_MSG(thread.GetPriority() == nPriority - 1, "Thread failure: Thread Priority not set correctly (2).\n")) + nErrorCount++; + + thread.SetPriority(nPriority); + EATEST_VERIFY_MSG(thread.GetPriority() == nPriority, "Thread failure: Thread Priority not set correctly (3).\n"); + #endif + + sShouldGo = 1; + thread.WaitForEnd(GetThreadTime() + 30000); + + status = thread.GetStatus(); + EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded.\n"); + EA::Thread::SetThreadPriority(nOriginalPriority); + } + + nErrorCount += TestThreadDynamicData(); + nErrorCount += TestThreadPriorities(); + nErrorCount += TestSetThreadProcessorByThreadParams(); + nErrorCount += TestSetThreadProcessConstants(); + nErrorCount += TestNullThreadNames(); + nErrorCount += TestLambdaThreads(); + + { + // Test SetDefaultProcessor + Thread::SetDefaultProcessor(kProcessorAny); + } + + + #if !defined(EA_PLATFORM_MOBILE) + // This test does not execute correctly on Android. The 'while(sThreadCount + // < kThreadCount)' loop sometimes hangs indefinitely because the spawned + // threads exit often before the loop continues, so the spawned + // thread count never gets up to kThreadCount. I guess the inner loop + // should be testing addedThreadCount too. (?) But, just disable for now, + // since I guess it's working on other platforms. + { + // Test very many threads starting and quitting, recycling + // The PS3 code had problems with leaking threads in this case. + // Create a bunch of threads. + // Every N ms quit one of them and create a new one. + + // This test tends to be quite taxing on system resources, so cut it back for + // certain platforms. To do: use EATest's platform speed metrics. + ThreadParameters params; + const int kThreadCount(48); // This needs to be greater than the eathread_thread.cpp kMaxThreadDynamicDataCount value. + #ifdef EA_PLATFORM_NX + const int kTotalThreadsToRun(64); + + const auto nAllCoresAffinityMask = ((INT64_C(1) << EA::Thread::GetProcessorCount()) - 1); + const auto nAllCoresButMainAffinityMask = nAllCoresAffinityMask & INT64_C(0XFFFFFFFFFFFFFE); + + params.mnAffinityMask = nAllCoresButMainAffinityMask; + params.mnProcessor = kProcessorAny; + #else + const int kTotalThreadsToRun(500); + #endif + + Thread thread; + ThreadId threadId; + int addedThreadCount = 0; + int i = 0; + + sThreadCount = 0; + sShouldGo = 0; + + // Create threads. + for(i = 0; i < kThreadCount; i++) + { + threadId = thread.Begin(TestFunction6, reinterpret_cast((uintptr_t)i), ¶ms); + EATEST_VERIFY(threadId != kThreadIdInvalid); + } + + // Wait until they are all created and started. + while(sThreadCount < kThreadCount) + ThreadSleep(500); + + // Let them run and exit as they go. + sShouldGo = 1; + + // Add new threads as existing ones leave. + while(addedThreadCount < kTotalThreadsToRun) + { + if(sThreadCount < kThreadCount) + { + threadId = thread.Begin(TestFunction6, reinterpret_cast((uintptr_t)i++), ¶ms); + + EATEST_VERIFY(threadId != kThreadIdInvalid); + + addedThreadCount++; + + #if defined(EA_PLATFORM_DESKTOP) + // The created threads will not get any time slices on weaker platforms. + // thus making the test create threads until the system is out of resources + ThreadSleep(kTimeoutYield); + + // Sometimes it will never exit this loop.. because it will leave faster then it is made + if(addedThreadCount >= kTotalThreadsToRun) + break; + #endif + } + else + ThreadSleep(10); + } + + // Wait until they have all exited. + while(sThreadCount != 0) // While there are threads... + ThreadSleep(500); + + // On some platforms it seems that thread entry/exit is so slow that the thread count might not necessarily + // reflect the actual number of threads running. This was causing an issue where the function was + // exiting before all threads completed, so I added this wait. It may be worth considering + // keeping track of all created threads in an array, and then using a thread-join operation here + // instead + ThreadSleep(1000); + } + + #endif + + return nErrorCount; +} + + + + + + + + + diff --git a/src/thirdparty/ea/EAThread/test/thread/source/TestThreadThreadPool.cpp b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadThreadPool.cpp new file mode 100644 index 00000000..198181b0 --- /dev/null +++ b/src/thirdparty/ea/EAThread/test/thread/source/TestThreadThreadPool.cpp @@ -0,0 +1,100 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +#include "TestThread.h" +#include +#include +#include +#include + + +using namespace EA::Thread; + + +const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT; + + +struct TPWorkData +{ + int mnWorkItem; + TPWorkData(int nWorkItem) : mnWorkItem(nWorkItem) {} +}; + + +static AtomicInt32 gWorkItemsCreated = 0; +static AtomicInt32 gWorkItemsProcessed = 0; + + +static intptr_t WorkerFunction(void* pvWorkData) +{ + TPWorkData* pWorkData = (TPWorkData*)pvWorkData; + + ThreadId threadId = GetThreadId(); + EA::UnitTest::ReportVerbosity(1, "Work %4d starting for thread %s.\n", pWorkData->mnWorkItem, EAThreadThreadIdToString(threadId)); + + EA::UnitTest::ThreadSleepRandom(200, 600); + ++gWorkItemsProcessed; + + EA::UnitTest::ReportVerbosity(1, "Work %4d ending for thread %s.\n", pWorkData->mnWorkItem, EAThreadThreadIdToString(threadId)); + + delete pWorkData; + + return 0; +} + + +int TestThreadThreadPool() +{ + int nErrorCount(0); + + #if EA_THREADS_AVAILABLE + { + ThreadPoolParameters tpp; + tpp.mnMinCount = kMaxConcurrentThreadCount - 1; + tpp.mnMaxCount = kMaxConcurrentThreadCount - 1; + tpp.mnInitialCount = 0; + + tpp.mnIdleTimeoutMilliseconds = EA::Thread::kTimeoutNone; // left in to test the usage of this kTimeout* defines. + tpp.mnIdleTimeoutMilliseconds = 20000; + + + ThreadPool threadPool(&tpp); + int nResult; + + for(unsigned int i = 0; i < gTestLengthSeconds * 3; i++) + { + const int nWorkItem = (int)gWorkItemsCreated++; + TPWorkData* const pWorkData = new TPWorkData(nWorkItem); + + EA::UnitTest::ReportVerbosity(1, "Work %4d created.\n", nWorkItem); + nResult = threadPool.Begin(WorkerFunction, pWorkData, NULL, true); + + EATEST_VERIFY_MSG(nResult != ThreadPool::kResultError, "Thread pool failure in Begin."); + + EA::UnitTest::ThreadSleepRandom(300, 700); + //Todo: If the pool task length gets too long, wait some more. + } + + EA::UnitTest::ReportVerbosity(1, "Shutting down thread pool.\n"); + bool bShutdownResult = threadPool.Shutdown(ThreadPool::kJobWaitAll, GetThreadTime() + 60000); + + EATEST_VERIFY_MSG(bShutdownResult, "Thread pool failure in Shutdown (waiting for jobs to complete)."); + } + + EATEST_VERIFY_MSG(gWorkItemsCreated == gWorkItemsProcessed, "Thread pool failure: gWorkItemsCreated != gWorkItemsProcessed."); + #endif + + return nErrorCount; +} + + + + + + + + + + + diff --git a/src/thirdparty/imgui/imgui.cpp b/src/thirdparty/imgui/imgui.cpp index 24934146..75946bef 100644 --- a/src/thirdparty/imgui/imgui.cpp +++ b/src/thirdparty/imgui/imgui.cpp @@ -6794,14 +6794,16 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags, void* float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y; //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons? window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); - window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - ((window->ScrollbarY && !(flags & ImGuiWindowFlags_OverlayHorizontalScrollbar)) ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); if (window->ScrollbarX && !window->ScrollbarY) window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar); window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); // Amend the partially filled window->DecorationXXX values. - window->DecoOuterSizeX2 += window->ScrollbarSizes.x; - window->DecoOuterSizeY2 += window->ScrollbarSizes.y; + if (!(flags & ImGuiWindowFlags_OverlayVerticalScrollbar)) + window->DecoOuterSizeX2 += window->ScrollbarSizes.x; + if (!(flags & ImGuiWindowFlags_OverlayHorizontalScrollbar)) + window->DecoOuterSizeY2 += window->ScrollbarSizes.y; } // UPDATE RECTANGLES (1- THOSE NOT AFFECTED BY SCROLLING) diff --git a/src/thirdparty/imgui/imgui.h b/src/thirdparty/imgui/imgui.h index 2cfe5bd5..8658edd3 100644 --- a/src/thirdparty/imgui/imgui.h +++ b/src/thirdparty/imgui/imgui.h @@ -1016,6 +1016,9 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_NoDecoration = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse, ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, + ImGuiWindowFlags_OverlayVerticalScrollbar = 1 << 19, // Draw the vertical scrollbar as an overlay (scrollbar space will not take up content region space) + ImGuiWindowFlags_OverlayHorizontalScrollbar = 1 << 20, // Draw the horizontal scrollbar as an overlay (scrollbar space will not take up content region space) + // [Internal] ImGuiWindowFlags_NavFlattened = 1 << 23, // [BETA] On child window: share focus scope, allow gamepad/keyboard navigation to cross over parent border to this child or between sibling child windows. ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() diff --git a/src/thirdparty/imgui/misc/imgui_logger.cpp b/src/thirdparty/imgui/misc/imgui_logger.cpp index 72f43d79..4e3721ed 100644 --- a/src/thirdparty/imgui/misc/imgui_logger.cpp +++ b/src/thirdparty/imgui/misc/imgui_logger.cpp @@ -1507,14 +1507,12 @@ void CTextLogger::EnsureCursorVisible() const float left = ImCeil(scrollX / m_CharAdvance.x); const float right = ImCeil((scrollX + width) / m_CharAdvance.x); - //const ImGuiWindow* const window = ImGui::GetCurrentWindow(); + const ImGuiWindow* const window = ImGui::GetCurrentWindow(); // For right offset, +.1f as otherwise it would render right below the // first pixel making up the right perimeter. - // TODO: when the scrollbar feedback issue is fixed, uncomment the window - // scrollbar check !!! - const float rightOffsetAmount = 3.1f;// window->ScrollbarY ? 3.1f : 1.1f; - const float bottomOffsetAmount = 3.f; // window->ScrollbarX ? 3.f : 2.f; + const float rightOffsetAmount = window->ScrollbarY ? 3.1f : 1.1f; + const float bottomOffsetAmount = window->ScrollbarX ? 3.f : 2.f; if (pos.m_nColumn < left) ImGui::SetScrollX(ImMax(0.0f, (pos.m_nColumn) * m_CharAdvance.x)); diff --git a/src/thirdparty/protobuf/CMakeLists.txt b/src/thirdparty/protobuf/CMakeLists.txt index 4edca7e5..d1d95650 100644 --- a/src/thirdparty/protobuf/CMakeLists.txt +++ b/src/thirdparty/protobuf/CMakeLists.txt @@ -4,58 +4,165 @@ add_module( "lib" "libprotobuf" "" ${FOLDER_CONTEXT} FALSE TRUE ) start_sources() add_sources( SOURCE_GROUP "Source" + "any.cc" + "any.pb.cc" "any_lite.cc" + "arena.cc" "arenastring.cc" "arenaz_sampler.cc" + + "descriptor.cc" + "descriptor.pb.cc" + "descriptor_database.cc" + + "duration.pb.cc" + "dynamic_message.cc" + + "empty.pb.cc" + "extension_set.cc" + "extension_set_heavy.cc" + + "field_mask.pb.cc" + "generated_enum_util.cc" "generated_message_tctable_lite.cc" "generated_message_util.cc" + "generated_message_bases.cc" + "generated_message_reflection.cc" + "generated_message_tctable_full.cc" + "generated_message_tctable_lite.cc" + "generated_message_util.cc" + "implicit_weak_message.cc" "inlined_string_field.cc" + "map.cc" + "map_field.cc" + + "message.cc" "message_lite.cc" + "parse_context.cc" + + "reflection_ops.cc" + "repeated_field.cc" "repeated_ptr_field.cc" + + "service.cc" + + "source_context.pb.cc" + "struct.pb.cc" + + "text_format.cc" + + "timestamp.pb.cc" + "type.pb.cc" + + "unknown_field_set.cc" + + "wire_format.cc" "wire_format_lite.cc" + + "wrappers.pb.cc" ) add_sources( SOURCE_GROUP "Include" "any.h" + "any.pb.h" + "arena.h" "arena_impl.h" "arenastring.h" "arenaz_sampler.h" + + "descriptor.h" + "descriptor.pb.h" + "descriptor_database.h" + + "duration.pb.h" + "dynamic_message.h" + + "empty.pb.h" + "endian.h" "explicitly_constructed.h" + "extension_set.h" "extension_set_inl.h" + + "field_mask.pb.h" + "generated_enum_util.h" "generated_message_tctable_decl.h" "generated_message_tctable_impl.h" "generated_message_util.h" + "generated_message_bases.h" + "generated_message_reflection.h" + "generated_message_tctable_decl.h" + "generated_message_tctable_impl.h" + "generated_message_util.h" + "has_bits.h" "implicit_weak_message.h" "inlined_string_field.h" + "map.h" "map_entry_lite.h" "map_field_lite.h" + "map_field_inl.h" + "map_type_handler.h" "message_lite.h" "metadata_lite.h" "parse_context.h" "port.h" + + "reflection.h" + "reflection_internal.h" + "reflection_ops.h" + "repeated_field.h" "repeated_ptr_field.h" + + "service.h" + "string_member_robber.h" + + "source_context.pb.h" + "struct.pb.h" + + "text_format.h" + + "timestamp.pb.h" + "type.pb.h" + + "unknown_field_set.h" + + "wire_format.h" "wire_format_lite.h" + + "wrappers.pb.h" +) + +add_sources( SOURCE_GROUP "Compiler" + "compiler/importer.cc" + "compiler/parser.cc" +) + +add_sources( SOURCE_GROUP "Compiler/Include" + "compiler/importer.h" + "compiler/parser.h" ) add_sources( SOURCE_GROUP "IO" "io/coded_stream.cc" + "io/gzip_stream.cc" "io/io_win32.cc" + "io/printer.cc" "io/strtod.cc" + "io/tokenizer.cc" "io/zero_copy_stream.cc" "io/zero_copy_stream_impl.cc" "io/zero_copy_stream_impl_lite.cc" @@ -63,8 +170,11 @@ add_sources( SOURCE_GROUP "IO" add_sources( SOURCE_GROUP "IO/Include" "io/coded_stream.h" + "io/gzip_stream.h" "io/io_win32.h" + "io/printer.cc" "io/strtod.h" + "io/tokenizer.h" "io/zero_copy_stream.h" "io/zero_copy_stream_impl.h" "io/zero_copy_stream_impl_lite.h" @@ -80,6 +190,7 @@ add_sources( SOURCE_GROUP "Stubs" "stubs/stringprintf.cc" "stubs/structurally_valid.cc" "stubs/strutil.cc" + "stubs/substitute.cc" "stubs/time.cc" ) @@ -103,9 +214,62 @@ add_sources( SOURCE_GROUP "Stubs/Include" "stubs/stringpiece.h" "stubs/stringprintf.h" "stubs/strutil.h" + "stubs/substitute.h" "stubs/template_util.h" "stubs/time.h" ) +add_sources( SOURCE_GROUP "Util" + "util/delimited_message_util.cc" + "util/field_comparator.cc" + "util/field_mask_util.cc" + "util/json_util.cc" + "util/message_differencer.cc" + "util/type_resolver_util.cc" +) + +add_sources( SOURCE_GROUP "Util/Include" + "util/delimited_message_util.h" + "util/field_comparator.h" + "util/field_mask_util.h" + "util/json_util.h" + "util/message_differencer.h" + "util/type_resolver.h" + "util/type_resolver_util.h" +) + +add_sources( SOURCE_GROUP "Util/Internal" + "util/internal/datapiece.cc" + "util/internal/default_value_objectwriter.cc" + "util/internal/error_listener.cc" + "util/internal/field_mask_utility.cc" + "util/internal/json_escaping.cc" + "util/internal/json_objectwriter.cc" + "util/internal/json_stream_parser.cc" + "util/internal/object_writer.cc" + "util/internal/proto_writer.cc" + "util/internal/protostream_objectwriter.cc" + "util/internal/protostream_objectsource.cc" + "util/internal/type_info.cc" + "util/internal/utility.cc" +) + +add_sources( SOURCE_GROUP "Utils/Internal/Include" + "util/internal/datapiece.h" + "util/internal/default_value_objectwriter.h" + "util/internal/error_listener.h" + "util/internal/field_mask_utility.h" + "util/internal/json_escaping.h" + "util/internal/json_objectwriter.h" + "util/internal/json_stream_parser.h" + "util/internal/object_source.h" + "util/internal/object_writer.h" + "util/internal/proto_writer.h" + "util/internal/protostream_objectwriter.h" + "util/internal/protostream_objectsource.h" + "util/internal/type_info.h" + "util/internal/utility.h" +) + end_sources() thirdparty_suppress_warnings() diff --git a/src/thirdparty/protobuf/util/internal/json_objectwriter.cc b/src/thirdparty/protobuf/util/internal/json_objectwriter.cc index ac14b37c..448de72c 100644 --- a/src/thirdparty/protobuf/util/internal/json_objectwriter.cc +++ b/src/thirdparty/protobuf/util/internal/json_objectwriter.cc @@ -99,18 +99,18 @@ JsonObjectWriter* JsonObjectWriter::RenderUint32(StringPiece name, JsonObjectWriter* JsonObjectWriter::RenderInt64(StringPiece name, int64_t value) { WritePrefix(name); - WriteChar('"'); + //WriteChar('"'); WriteRawString(StrCat(value)); - WriteChar('"'); + //WriteChar('"'); return this; } JsonObjectWriter* JsonObjectWriter::RenderUint64(StringPiece name, uint64_t value) { WritePrefix(name); - WriteChar('"'); + //WriteChar('"'); WriteRawString(StrCat(value)); - WriteChar('"'); + //WriteChar('"'); return this; } diff --git a/src/thirdparty/protobuf/util/json_util.cc b/src/thirdparty/protobuf/util/json_util.cc index 5ecd7321..8c71332d 100644 --- a/src/thirdparty/protobuf/util/json_util.cc +++ b/src/thirdparty/protobuf/util/json_util.cc @@ -99,7 +99,7 @@ util::Status BinaryToJsonStream(TypeResolver* resolver, converter::ProtoStreamObjectSource proto_source(&in_stream, resolver, type, render_options); io::CodedOutputStream out_stream(json_output); - converter::JsonObjectWriter json_writer(options.add_whitespace ? " " : "", + converter::JsonObjectWriter json_writer(options.add_whitespace ? " " : "", &out_stream); if (options.always_print_primitive_fields) { converter::DefaultValueObjectWriter default_value_writer(resolver, type, diff --git a/src/thirdparty/rapidjson/document.h b/src/thirdparty/rapidjson/document.h index 2cd9a70a..f183749a 100644 --- a/src/thirdparty/rapidjson/document.h +++ b/src/thirdparty/rapidjson/document.h @@ -1033,7 +1033,7 @@ public: return false; for (ConstMemberIterator lhsMemberItr = MemberBegin(); lhsMemberItr != MemberEnd(); ++lhsMemberItr) { typename RhsType::ConstMemberIterator rhsMemberItr = rhs.FindMember(lhsMemberItr->name); - if (rhsMemberItr == rhs.MemberEnd() || lhsMemberItr->value != rhsMemberItr->value) + if (rhsMemberItr == rhs.MemberEnd() || (!(lhsMemberItr->value == rhsMemberItr->value))) return false; } return true; @@ -1042,7 +1042,7 @@ public: if (data_.a.size != rhs.data_.a.size) return false; for (SizeType i = 0; i < data_.a.size; i++) - if ((*this)[i] != rhs[i]) + if (!((*this)[i] == rhs[i])) return false; return true; diff --git a/src/thirdparty/rapidjson/internal/regex.h b/src/thirdparty/rapidjson/internal/regex.h index 6446c403..7740dcd5 100644 --- a/src/thirdparty/rapidjson/internal/regex.h +++ b/src/thirdparty/rapidjson/internal/regex.h @@ -615,7 +615,7 @@ public: RAPIDJSON_ASSERT(regex_.IsValid()); if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); - stateSet_ = static_cast(allocator_->Malloc(GetStateSetSize())); + stateSet_ = static_cast(allocator_->Malloc(GetStateSetSize())); state0_.template Reserve(regex_.stateCount_); state1_.template Reserve(regex_.stateCount_); } diff --git a/src/thirdparty/rapidjson/schema.h b/src/thirdparty/rapidjson/schema.h index 973e935f..02a6d0f9 100644 --- a/src/thirdparty/rapidjson/schema.h +++ b/src/thirdparty/rapidjson/schema.h @@ -24,13 +24,9 @@ #if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) #define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 -#else -#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0 #endif -#if !RAPIDJSON_SCHEMA_USE_INTERNALREGEX && defined(RAPIDJSON_SCHEMA_USE_STDREGEX) && (__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) -#define RAPIDJSON_SCHEMA_USE_STDREGEX 1 -#else +#if !defined(RAPIDJSON_SCHEMA_USE_STDREGEX) || !(__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) #define RAPIDJSON_SCHEMA_USE_STDREGEX 0 #endif @@ -1645,9 +1641,13 @@ private: bool CheckDoubleMultipleOf(Context& context, double d) const { double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); - double q = std::floor(a / b); - double r = a - q * b; - if (r > 0.0) { + double q = a / b; + double qRounded = std::floor(q + 0.5); + double scaledEpsilon = (q + qRounded) * std::numeric_limits::epsilon(); + double difference = std::abs(qRounded - q); + bool isMultiple = (difference <= scaledEpsilon) + || (difference < std::numeric_limits::min()); + if (!isMultiple) { context.error_handler.NotMultipleOf(d, multipleOf_); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMultipleOf); } diff --git a/src/thirdparty/recast/Detour/Include/DetourNavMesh.h b/src/thirdparty/recast/Detour/Include/DetourNavMesh.h index ee4bae81..118f241a 100644 --- a/src/thirdparty/recast/Detour/Include/DetourNavMesh.h +++ b/src/thirdparty/recast/Detour/Include/DetourNavMesh.h @@ -103,6 +103,9 @@ enum dtTileFlags { /// The navigation mesh owns the tile memory and is responsible for freeing it. DT_TILE_FREE_DATA = 0x01, + + /// The navigation mesh owns the offmesh connection memory and is responsible for freeing it. + DT_OFFMESH_FREE_DATA = 0x02, }; /// Vertex flags returned by dtNavMeshQuery::findStraightPath. @@ -326,7 +329,7 @@ struct dtMeshTile int dataSize; ///< Size of the tile data. int flags; ///< Tile flags. (See: #dtTileFlags) dtMeshTile* next; ///< The next free tile, or the next tile in the spatial grid. - void* unused; + void* unknownVTableInstance; // See [r5apex_ds + F437D9] for usage private: dtMeshTile(const dtMeshTile&); dtMeshTile& operator=(const dtMeshTile&); diff --git a/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp b/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp index dad47b1b..8e359b54 100644 --- a/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp +++ b/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp @@ -206,7 +206,7 @@ dtNavMesh::dtNavMesh() : m_orig[2] = 0; } -dtNavMesh::~dtNavMesh() +dtNavMesh::~dtNavMesh() // TODO: see [r5apex_ds + F43720] to re-implement this correctly { for (int i = 0; i < m_maxTiles; ++i) { @@ -1021,7 +1021,7 @@ dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags, tile->data = data; tile->dataSize = dataSize; tile->flags = flags; - tile->unused = nullptr; + tile->unknownVTableInstance = nullptr; connectIntLinks(tile); diff --git a/src/thirdparty/zstd/Makefile b/src/thirdparty/zstd/Makefile index a4cf61ab..8bfdade9 100644 --- a/src/thirdparty/zstd/Makefile +++ b/src/thirdparty/zstd/Makefile @@ -8,6 +8,9 @@ # You may select, at your option, one of the above-listed licenses. # ################################################################ +# default target (when running `make` with no argument) +lib-release: + # Modules ZSTD_LIB_COMPRESSION ?= 1 ZSTD_LIB_DECOMPRESSION ?= 1 @@ -54,12 +57,11 @@ VERSION := $(ZSTD_VERSION) # Note: by default, the static library is built single-threaded and dynamic library is built # multi-threaded. It is possible to force multi or single threaded builds by appending # -mt or -nomt to the build target (like lib-mt for multi-threaded, lib-nomt for single-threaded). -.PHONY: default -default: lib-release + CPPFLAGS_DYNLIB += -DZSTD_MULTITHREAD # dynamic library build defaults to multi-threaded LDFLAGS_DYNLIB += -pthread -CPPFLAGS_STATLIB += # static library build defaults to single-threaded +CPPFLAGS_STATICLIB += # static library build defaults to single-threaded ifeq ($(findstring GCC,$(CCVER)),GCC) @@ -91,7 +93,7 @@ all: lib .PHONY: libzstd.a # must be run every time -libzstd.a: CPPFLAGS += $(CPPFLAGS_STATLIB) +libzstd.a: CPPFLAGS += $(CPPFLAGS_STATICLIB) SET_CACHE_DIRECTORY = \ +$(MAKE) --no-print-directory $@ \ @@ -109,19 +111,19 @@ libzstd.a: else # BUILD_DIR is defined -ZSTD_STATLIB_DIR := $(BUILD_DIR)/static -ZSTD_STATLIB := $(ZSTD_STATLIB_DIR)/libzstd.a -ZSTD_STATLIB_OBJ := $(addprefix $(ZSTD_STATLIB_DIR)/,$(ZSTD_LOCAL_OBJ)) -$(ZSTD_STATLIB): ARFLAGS = rcs -$(ZSTD_STATLIB): | $(ZSTD_STATLIB_DIR) -$(ZSTD_STATLIB): $(ZSTD_STATLIB_OBJ) +ZSTD_STATICLIB_DIR := $(BUILD_DIR)/static +ZSTD_STATICLIB := $(ZSTD_STATICLIB_DIR)/libzstd.a +ZSTD_STATICLIB_OBJ := $(addprefix $(ZSTD_STATICLIB_DIR)/,$(ZSTD_LOCAL_OBJ)) +$(ZSTD_STATICLIB): ARFLAGS = rcs +$(ZSTD_STATICLIB): | $(ZSTD_STATICLIB_DIR) +$(ZSTD_STATICLIB): $(ZSTD_STATICLIB_OBJ) # Check for multithread flag at target execution time $(if $(filter -DZSTD_MULTITHREAD,$(CPPFLAGS)),\ @echo compiling multi-threaded static library $(LIBVER),\ @echo compiling single-threaded static library $(LIBVER)) $(AR) $(ARFLAGS) $@ $^ -libzstd.a: $(ZSTD_STATLIB) +libzstd.a: $(ZSTD_STATICLIB) cp -f $< $@ endif @@ -182,14 +184,14 @@ lib : libzstd.a libzstd # make does not consider implicit pattern rule for .PHONY target %-mt : CPPFLAGS_DYNLIB := -DZSTD_MULTITHREAD -%-mt : CPPFLAGS_STATLIB := -DZSTD_MULTITHREAD +%-mt : CPPFLAGS_STATICLIB := -DZSTD_MULTITHREAD %-mt : LDFLAGS_DYNLIB := -pthread %-mt : % @echo multi-threaded build completed %-nomt : CPPFLAGS_DYNLIB := %-nomt : LDFLAGS_DYNLIB := -%-nomt : CPPFLAGS_STATLIB := +%-nomt : CPPFLAGS_STATICLIB := %-nomt : % @echo single-threaded build completed @@ -200,42 +202,52 @@ lib : libzstd.a libzstd # Generate .h dependencies automatically -DEPFLAGS = -MT $@ -MMD -MP -MF +# -MMD: compiler generates dependency information as a side-effect of compilation, without system headers +# -MP: adds phony target for each dependency other than main file. +DEPFLAGS = -MMD -MP -$(ZSTD_DYNLIB_DIR)/%.o : %.c $(ZSTD_DYNLIB_DIR)/%.d | $(ZSTD_DYNLIB_DIR) +# ensure that ZSTD_DYNLIB_DIR exists prior to generating %.o +$(ZSTD_DYNLIB_DIR)/%.o : %.c | $(ZSTD_DYNLIB_DIR) @echo CC $@ - $(COMPILE.c) $(DEPFLAGS) $(ZSTD_DYNLIB_DIR)/$*.d $(OUTPUT_OPTION) $< + $(COMPILE.c) $(DEPFLAGS) $(OUTPUT_OPTION) $< -$(ZSTD_STATLIB_DIR)/%.o : %.c $(ZSTD_STATLIB_DIR)/%.d | $(ZSTD_STATLIB_DIR) +$(ZSTD_STATICLIB_DIR)/%.o : %.c | $(ZSTD_STATICLIB_DIR) @echo CC $@ - $(COMPILE.c) $(DEPFLAGS) $(ZSTD_STATLIB_DIR)/$*.d $(OUTPUT_OPTION) $< + $(COMPILE.c) $(DEPFLAGS) $(OUTPUT_OPTION) $< $(ZSTD_DYNLIB_DIR)/%.o : %.S | $(ZSTD_DYNLIB_DIR) @echo AS $@ $(COMPILE.S) $(OUTPUT_OPTION) $< -$(ZSTD_STATLIB_DIR)/%.o : %.S | $(ZSTD_STATLIB_DIR) +$(ZSTD_STATICLIB_DIR)/%.o : %.S | $(ZSTD_STATICLIB_DIR) @echo AS $@ $(COMPILE.S) $(OUTPUT_OPTION) $< -MKDIR ?= mkdir -$(BUILD_DIR) $(ZSTD_DYNLIB_DIR) $(ZSTD_STATLIB_DIR): - $(MKDIR) -p $@ +MKDIR ?= mkdir -p +$(BUILD_DIR) $(ZSTD_DYNLIB_DIR) $(ZSTD_STATICLIB_DIR): + $(MKDIR) $@ -DEPFILES := $(ZSTD_DYNLIB_OBJ:.o=.d) $(ZSTD_STATLIB_OBJ:.o=.d) +DEPFILES := $(ZSTD_DYNLIB_OBJ:.o=.d) $(ZSTD_STATICLIB_OBJ:.o=.d) $(DEPFILES): -include $(wildcard $(DEPFILES)) +# The leading '-' means: do not fail is include fails (ex: directory does not exist yet) +-include $(wildcard $(DEPFILES)) -# Special case : building library in single-thread mode _and_ without zstdmt_compress.c -ZSTDMT_FILES = compress/zstdmt_compress.c -ZSTD_NOMT_FILES = $(filter-out $(ZSTDMT_FILES),$(ZSTD_FILES)) +# Special case : build library in single-thread mode _and_ without zstdmt_compress.c +# Note : we still need threading.c and pool.c for the dictionary builder, +# but they will correctly behave single-threaded. +ZSTDMT_FILES = zstdmt_compress.c +ZSTD_NOMT_FILES = $(filter-out $(ZSTDMT_FILES),$(notdir $(ZSTD_FILES))) libzstd-nomt: CFLAGS += -fPIC -fvisibility=hidden libzstd-nomt: LDFLAGS += -shared libzstd-nomt: $(ZSTD_NOMT_FILES) @echo compiling single-thread dynamic library $(LIBVER) @echo files : $(ZSTD_NOMT_FILES) + @if echo "$(ZSTD_NOMT_FILES)" | tr ' ' '\n' | $(GREP) -q zstdmt; then \ + echo "Error: Found zstdmt in list."; \ + exit 1; \ + fi $(CC) $(FLAGS) $^ $(LDFLAGS) $(SONAME_FLAGS) -o $@ .PHONY: clean @@ -249,7 +261,7 @@ clean: #----------------------------------------------------------------------------- # make install is validated only for below listed environments #----------------------------------------------------------------------------- -ifneq (,$(filter $(UNAME),Linux Darwin GNU/kFreeBSD GNU OpenBSD FreeBSD NetBSD DragonFly SunOS Haiku AIX)) +ifneq (,$(filter $(UNAME),Linux Darwin GNU/kFreeBSD GNU OpenBSD FreeBSD NetBSD DragonFly SunOS Haiku AIX MSYS_NT CYGWIN_NT)) lib: libzstd.pc diff --git a/src/thirdparty/zstd/README.md b/src/thirdparty/zstd/README.md index c3b5d181..a560f06c 100644 --- a/src/thirdparty/zstd/README.md +++ b/src/thirdparty/zstd/README.md @@ -88,7 +88,7 @@ The file structure is designed to make this selection manually achievable for an For example, advanced API for version `v0.4` is exposed in `lib/legacy/zstd_v04.h` . - While invoking `make libzstd`, it's possible to define build macros - `ZSTD_LIB_COMPRESSION, ZSTD_LIB_DECOMPRESSION`, `ZSTD_LIB_DICTBUILDER`, + `ZSTD_LIB_COMPRESSION`, `ZSTD_LIB_DECOMPRESSION`, `ZSTD_LIB_DICTBUILDER`, and `ZSTD_LIB_DEPRECATED` as `0` to forgo compilation of the corresponding features. This will also disable compilation of all dependencies (e.g. `ZSTD_LIB_COMPRESSION=0` will also disable @@ -119,6 +119,15 @@ The file structure is designed to make this selection manually achievable for an binary is achieved by using `HUF_FORCE_DECOMPRESS_X1` and `ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT` (implied by `ZSTD_LIB_MINIFY`). + On the compressor side, Zstd's compression levels map to several internal + strategies. In environments where the higher compression levels aren't used, + it is possible to exclude all but the fastest strategy with + `ZSTD_LIB_EXCLUDE_COMPRESSORS_DFAST_AND_UP=1`. (Note that this will change + the behavior of the default compression level.) Or if you want to retain the + default compressor as well, you can set + `ZSTD_LIB_EXCLUDE_COMPRESSORS_GREEDY_AND_UP=1`, at the cost of an additional + ~20KB or so. + For squeezing the last ounce of size out, you can also define `ZSTD_NO_INLINE`, which disables inlining, and `ZSTD_STRIP_ERROR_STRINGS`, which removes the error messages that are otherwise returned by @@ -169,6 +178,10 @@ The file structure is designed to make this selection manually achievable for an `ZSTDERRORLIB_VSIBILITY`, and `ZDICTLIB_VISIBILITY` if unset, for backwards compatibility with the old macro names. +- The C compiler macro `HUF_DISABLE_FAST_DECODE` disables the newer Huffman fast C + and assembly decoding loops. You may want to use this macro if these loops are + slower on your platform. + #### Windows : using MinGW+MSYS to create DLL DLL can be created using MinGW+MSYS with the `make libzstd` command. diff --git a/src/thirdparty/zstd/common/allocations.h b/src/thirdparty/zstd/common/allocations.h index a3153c4b..5e899550 100644 --- a/src/thirdparty/zstd/common/allocations.h +++ b/src/thirdparty/zstd/common/allocations.h @@ -14,7 +14,7 @@ #define ZSTD_DEPS_NEED_MALLOC #include "zstd_deps.h" /* ZSTD_malloc, ZSTD_calloc, ZSTD_free, ZSTD_memset */ -#include "mem.h" /* MEM_STATIC */ +#include "compiler.h" /* MEM_STATIC */ #define ZSTD_STATIC_LINKING_ONLY #include "../zstd.h" /* ZSTD_customMem */ diff --git a/src/thirdparty/zstd/common/bitstream.h b/src/thirdparty/zstd/common/bitstream.h index 72b0b3df..67604498 100644 --- a/src/thirdparty/zstd/common/bitstream.h +++ b/src/thirdparty/zstd/common/bitstream.h @@ -90,19 +90,20 @@ MEM_STATIC size_t BIT_closeCStream(BIT_CStream_t* bitC); /*-******************************************** * bitStream decoding API (read backward) **********************************************/ +typedef size_t BitContainerType; typedef struct { - size_t bitContainer; + BitContainerType bitContainer; unsigned bitsConsumed; const char* ptr; const char* start; const char* limitPtr; } BIT_DStream_t; -typedef enum { BIT_DStream_unfinished = 0, - BIT_DStream_endOfBuffer = 1, - BIT_DStream_completed = 2, - BIT_DStream_overflow = 3 } BIT_DStream_status; /* result of BIT_reloadDStream() */ - /* 1,2,4,8 would be better for bitmap combinations, but slows down performance a bit ... :( */ +typedef enum { BIT_DStream_unfinished = 0, /* fully refilled */ + BIT_DStream_endOfBuffer = 1, /* still some bits left in bitstream */ + BIT_DStream_completed = 2, /* bitstream entirely consumed, bit-exact */ + BIT_DStream_overflow = 3 /* user requested more bits than present in bitstream */ + } BIT_DStream_status; /* result of BIT_reloadDStream() */ MEM_STATIC size_t BIT_initDStream(BIT_DStream_t* bitD, const void* srcBuffer, size_t srcSize); MEM_STATIC size_t BIT_readBits(BIT_DStream_t* bitD, unsigned nbBits); @@ -112,7 +113,7 @@ MEM_STATIC unsigned BIT_endOfDStream(const BIT_DStream_t* bitD); /* Start by invoking BIT_initDStream(). * A chunk of the bitStream is then stored into a local register. -* Local register size is 64-bits on 64-bits systems, 32-bits on 32-bits systems (size_t). +* Local register size is 64-bits on 64-bits systems, 32-bits on 32-bits systems (BitContainerType). * You can then retrieve bitFields stored into the local register, **in reverse order**. * Local register is explicitly reloaded from memory by the BIT_reloadDStream() method. * A reload guarantee a minimum of ((8*sizeof(bitD->bitContainer))-7) bits when its result is BIT_DStream_unfinished. @@ -162,7 +163,7 @@ MEM_STATIC size_t BIT_initCStream(BIT_CStream_t* bitC, return 0; } -MEM_STATIC FORCE_INLINE_ATTR size_t BIT_getLowerBits(size_t bitContainer, U32 const nbBits) +FORCE_INLINE_TEMPLATE size_t BIT_getLowerBits(size_t bitContainer, U32 const nbBits) { #if defined(STATIC_BMI2) && STATIC_BMI2 == 1 && !defined(ZSTD_NO_INTRINSICS) return _bzhi_u64(bitContainer, nbBits); @@ -267,22 +268,22 @@ MEM_STATIC size_t BIT_initDStream(BIT_DStream_t* bitD, const void* srcBuffer, si bitD->bitContainer = *(const BYTE*)(bitD->start); switch(srcSize) { - case 7: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[6]) << (sizeof(bitD->bitContainer)*8 - 16); + case 7: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[6]) << (sizeof(bitD->bitContainer)*8 - 16); ZSTD_FALLTHROUGH; - case 6: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[5]) << (sizeof(bitD->bitContainer)*8 - 24); + case 6: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[5]) << (sizeof(bitD->bitContainer)*8 - 24); ZSTD_FALLTHROUGH; - case 5: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[4]) << (sizeof(bitD->bitContainer)*8 - 32); + case 5: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[4]) << (sizeof(bitD->bitContainer)*8 - 32); ZSTD_FALLTHROUGH; - case 4: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[3]) << 24; + case 4: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[3]) << 24; ZSTD_FALLTHROUGH; - case 3: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[2]) << 16; + case 3: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[2]) << 16; ZSTD_FALLTHROUGH; - case 2: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[1]) << 8; + case 2: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[1]) << 8; ZSTD_FALLTHROUGH; default: break; @@ -297,12 +298,12 @@ MEM_STATIC size_t BIT_initDStream(BIT_DStream_t* bitD, const void* srcBuffer, si return srcSize; } -MEM_STATIC FORCE_INLINE_ATTR size_t BIT_getUpperBits(size_t bitContainer, U32 const start) +FORCE_INLINE_TEMPLATE size_t BIT_getUpperBits(BitContainerType bitContainer, U32 const start) { return bitContainer >> start; } -MEM_STATIC FORCE_INLINE_ATTR size_t BIT_getMiddleBits(size_t bitContainer, U32 const start, U32 const nbBits) +FORCE_INLINE_TEMPLATE size_t BIT_getMiddleBits(BitContainerType bitContainer, U32 const start, U32 const nbBits) { U32 const regMask = sizeof(bitContainer)*8 - 1; /* if start > regMask, bitstream is corrupted, and result is undefined */ @@ -325,7 +326,7 @@ MEM_STATIC FORCE_INLINE_ATTR size_t BIT_getMiddleBits(size_t bitContainer, U32 c * On 32-bits, maxNbBits==24. * On 64-bits, maxNbBits==56. * @return : value extracted */ -MEM_STATIC FORCE_INLINE_ATTR size_t BIT_lookBits(const BIT_DStream_t* bitD, U32 nbBits) +FORCE_INLINE_TEMPLATE size_t BIT_lookBits(const BIT_DStream_t* bitD, U32 nbBits) { /* arbitrate between double-shift and shift+mask */ #if 1 @@ -348,7 +349,7 @@ MEM_STATIC size_t BIT_lookBitsFast(const BIT_DStream_t* bitD, U32 nbBits) return (bitD->bitContainer << (bitD->bitsConsumed & regMask)) >> (((regMask+1)-nbBits) & regMask); } -MEM_STATIC FORCE_INLINE_ATTR void BIT_skipBits(BIT_DStream_t* bitD, U32 nbBits) +FORCE_INLINE_TEMPLATE void BIT_skipBits(BIT_DStream_t* bitD, U32 nbBits) { bitD->bitsConsumed += nbBits; } @@ -357,7 +358,7 @@ MEM_STATIC FORCE_INLINE_ATTR void BIT_skipBits(BIT_DStream_t* bitD, U32 nbBits) * Read (consume) next n bits from local register and update. * Pay attention to not read more than nbBits contained into local register. * @return : extracted value. */ -MEM_STATIC FORCE_INLINE_ATTR size_t BIT_readBits(BIT_DStream_t* bitD, unsigned nbBits) +FORCE_INLINE_TEMPLATE size_t BIT_readBits(BIT_DStream_t* bitD, unsigned nbBits) { size_t const value = BIT_lookBits(bitD, nbBits); BIT_skipBits(bitD, nbBits); @@ -374,6 +375,21 @@ MEM_STATIC size_t BIT_readBitsFast(BIT_DStream_t* bitD, unsigned nbBits) return value; } +/*! BIT_reloadDStream_internal() : + * Simple variant of BIT_reloadDStream(), with two conditions: + * 1. bitstream is valid : bitsConsumed <= sizeof(bitD->bitContainer)*8 + * 2. look window is valid after shifted down : bitD->ptr >= bitD->start + */ +MEM_STATIC BIT_DStream_status BIT_reloadDStream_internal(BIT_DStream_t* bitD) +{ + assert(bitD->bitsConsumed <= sizeof(bitD->bitContainer)*8); + bitD->ptr -= bitD->bitsConsumed >> 3; + assert(bitD->ptr >= bitD->start); + bitD->bitsConsumed &= 7; + bitD->bitContainer = MEM_readLEST(bitD->ptr); + return BIT_DStream_unfinished; +} + /*! BIT_reloadDStreamFast() : * Similar to BIT_reloadDStream(), but with two differences: * 1. bitsConsumed <= sizeof(bitD->bitContainer)*8 must hold! @@ -384,31 +400,35 @@ MEM_STATIC BIT_DStream_status BIT_reloadDStreamFast(BIT_DStream_t* bitD) { if (UNLIKELY(bitD->ptr < bitD->limitPtr)) return BIT_DStream_overflow; - assert(bitD->bitsConsumed <= sizeof(bitD->bitContainer)*8); - bitD->ptr -= bitD->bitsConsumed >> 3; - bitD->bitsConsumed &= 7; - bitD->bitContainer = MEM_readLEST(bitD->ptr); - return BIT_DStream_unfinished; + return BIT_reloadDStream_internal(bitD); } /*! BIT_reloadDStream() : * Refill `bitD` from buffer previously set in BIT_initDStream() . - * This function is safe, it guarantees it will not read beyond src buffer. + * This function is safe, it guarantees it will not never beyond src buffer. * @return : status of `BIT_DStream_t` internal register. * when status == BIT_DStream_unfinished, internal register is filled with at least 25 or 57 bits */ -MEM_STATIC FORCE_INLINE_ATTR BIT_DStream_status BIT_reloadDStream(BIT_DStream_t* bitD) +FORCE_INLINE_TEMPLATE BIT_DStream_status BIT_reloadDStream(BIT_DStream_t* bitD) { - if (bitD->bitsConsumed > (sizeof(bitD->bitContainer)*8)) /* overflow detected, like end of stream */ + /* note : once in overflow mode, a bitstream remains in this mode until it's reset */ + if (UNLIKELY(bitD->bitsConsumed > (sizeof(bitD->bitContainer)*8))) { + static const BitContainerType zeroFilled = 0; + bitD->ptr = (const char*)&zeroFilled; /* aliasing is allowed for char */ + /* overflow detected, erroneous scenario or end of stream: no update */ return BIT_DStream_overflow; + } + + assert(bitD->ptr >= bitD->start); if (bitD->ptr >= bitD->limitPtr) { - return BIT_reloadDStreamFast(bitD); + return BIT_reloadDStream_internal(bitD); } if (bitD->ptr == bitD->start) { + /* reached end of bitStream => no update */ if (bitD->bitsConsumed < sizeof(bitD->bitContainer)*8) return BIT_DStream_endOfBuffer; return BIT_DStream_completed; } - /* start < ptr < limitPtr */ + /* start < ptr < limitPtr => cautious update */ { U32 nbBytes = bitD->bitsConsumed >> 3; BIT_DStream_status result = BIT_DStream_unfinished; if (bitD->ptr - nbBytes < bitD->start) { diff --git a/src/thirdparty/zstd/common/compiler.h b/src/thirdparty/zstd/common/compiler.h index 73f8d019..31880ecb 100644 --- a/src/thirdparty/zstd/common/compiler.h +++ b/src/thirdparty/zstd/common/compiler.h @@ -11,6 +11,8 @@ #ifndef ZSTD_COMPILER_H #define ZSTD_COMPILER_H +#include + #include "portability_macros.h" /*-******************************************************* @@ -51,12 +53,19 @@ # define WIN_CDECL #endif +/* UNUSED_ATTR tells the compiler it is okay if the function is unused. */ +#if defined(__GNUC__) +# define UNUSED_ATTR __attribute__((unused)) +#else +# define UNUSED_ATTR +#endif + /** * FORCE_INLINE_TEMPLATE is used to define C "templates", which take constant * parameters. They must be inlined for the compiler to eliminate the constant * branches. */ -#define FORCE_INLINE_TEMPLATE static INLINE_KEYWORD FORCE_INLINE_ATTR +#define FORCE_INLINE_TEMPLATE static INLINE_KEYWORD FORCE_INLINE_ATTR UNUSED_ATTR /** * HINT_INLINE is used to help the compiler generate better code. It is *not* * used for "templates", so it can be tweaked based on the compilers @@ -71,14 +80,28 @@ #if !defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8 && __GNUC__ < 5 # define HINT_INLINE static INLINE_KEYWORD #else -# define HINT_INLINE static INLINE_KEYWORD FORCE_INLINE_ATTR +# define HINT_INLINE FORCE_INLINE_TEMPLATE #endif -/* UNUSED_ATTR tells the compiler it is okay if the function is unused. */ +/* "soft" inline : + * The compiler is free to select if it's a good idea to inline or not. + * The main objective is to silence compiler warnings + * when a defined function in included but not used. + * + * Note : this macro is prefixed `MEM_` because it used to be provided by `mem.h` unit. + * Updating the prefix is probably preferable, but requires a fairly large codemod, + * since this name is used everywhere. + */ +#ifndef MEM_STATIC /* already defined in Linux Kernel mem.h */ #if defined(__GNUC__) -# define UNUSED_ATTR __attribute__((unused)) +# define MEM_STATIC static __inline UNUSED_ATTR +#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# define MEM_STATIC static inline +#elif defined(_MSC_VER) +# define MEM_STATIC static __inline #else -# define UNUSED_ATTR +# define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ +#endif #endif /* force no inlining */ @@ -109,10 +132,10 @@ /* prefetch * can be disabled, by declaring NO_PREFETCH build macro */ #if defined(NO_PREFETCH) -# define PREFETCH_L1(ptr) (void)(ptr) /* disabled */ -# define PREFETCH_L2(ptr) (void)(ptr) /* disabled */ +# define PREFETCH_L1(ptr) do { (void)(ptr); } while (0) /* disabled */ +# define PREFETCH_L2(ptr) do { (void)(ptr); } while (0) /* disabled */ #else -# if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_I86)) /* _mm_prefetch() is not defined outside of x86/x64 */ +# if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_I86)) && !defined(_M_ARM64EC) /* _mm_prefetch() is not defined outside of x86/x64 */ # include /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */ # define PREFETCH_L1(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0) # define PREFETCH_L2(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T1) @@ -120,24 +143,25 @@ # define PREFETCH_L1(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 3 /* locality */) # define PREFETCH_L2(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 2 /* locality */) # elif defined(__aarch64__) -# define PREFETCH_L1(ptr) __asm__ __volatile__("prfm pldl1keep, %0" ::"Q"(*(ptr))) -# define PREFETCH_L2(ptr) __asm__ __volatile__("prfm pldl2keep, %0" ::"Q"(*(ptr))) +# define PREFETCH_L1(ptr) do { __asm__ __volatile__("prfm pldl1keep, %0" ::"Q"(*(ptr))); } while (0) +# define PREFETCH_L2(ptr) do { __asm__ __volatile__("prfm pldl2keep, %0" ::"Q"(*(ptr))); } while (0) # else -# define PREFETCH_L1(ptr) (void)(ptr) /* disabled */ -# define PREFETCH_L2(ptr) (void)(ptr) /* disabled */ +# define PREFETCH_L1(ptr) do { (void)(ptr); } while (0) /* disabled */ +# define PREFETCH_L2(ptr) do { (void)(ptr); } while (0) /* disabled */ # endif #endif /* NO_PREFETCH */ #define CACHELINE_SIZE 64 -#define PREFETCH_AREA(p, s) { \ - const char* const _ptr = (const char*)(p); \ - size_t const _size = (size_t)(s); \ - size_t _pos; \ - for (_pos=0; _pos<_size; _pos+=CACHELINE_SIZE) { \ - PREFETCH_L2(_ptr + _pos); \ - } \ -} +#define PREFETCH_AREA(p, s) \ + do { \ + const char* const _ptr = (const char*)(p); \ + size_t const _size = (size_t)(s); \ + size_t _pos; \ + for (_pos=0; _pos<_size; _pos+=CACHELINE_SIZE) { \ + PREFETCH_L2(_ptr + _pos); \ + } \ + } while (0) /* vectorization * older GCC (pre gcc-4.3 picked as the cutoff) uses a different syntax, @@ -166,9 +190,9 @@ #endif #if __has_builtin(__builtin_unreachable) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))) -# define ZSTD_UNREACHABLE { assert(0), __builtin_unreachable(); } +# define ZSTD_UNREACHABLE do { assert(0), __builtin_unreachable(); } while (0) #else -# define ZSTD_UNREACHABLE { assert(0); } +# define ZSTD_UNREACHABLE do { assert(0); } while (0) #endif /* disable warnings */ @@ -281,6 +305,74 @@ * Sanitizer *****************************************************************/ +/** + * Zstd relies on pointer overflow in its decompressor. + * We add this attribute to functions that rely on pointer overflow. + */ +#ifndef ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +# if __has_attribute(no_sanitize) +# if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 8 + /* gcc < 8 only has signed-integer-overlow which triggers on pointer overflow */ +# define ZSTD_ALLOW_POINTER_OVERFLOW_ATTR __attribute__((no_sanitize("signed-integer-overflow"))) +# else + /* older versions of clang [3.7, 5.0) will warn that pointer-overflow is ignored. */ +# define ZSTD_ALLOW_POINTER_OVERFLOW_ATTR __attribute__((no_sanitize("pointer-overflow"))) +# endif +# else +# define ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +# endif +#endif + +/** + * Helper function to perform a wrapped pointer difference without trigging + * UBSAN. + * + * @returns lhs - rhs with wrapping + */ +MEM_STATIC +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +ptrdiff_t ZSTD_wrappedPtrDiff(unsigned char const* lhs, unsigned char const* rhs) +{ + return lhs - rhs; +} + +/** + * Helper function to perform a wrapped pointer add without triggering UBSAN. + * + * @return ptr + add with wrapping + */ +MEM_STATIC +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +unsigned char const* ZSTD_wrappedPtrAdd(unsigned char const* ptr, ptrdiff_t add) +{ + return ptr + add; +} + +/** + * Helper function to perform a wrapped pointer subtraction without triggering + * UBSAN. + * + * @return ptr - sub with wrapping + */ +MEM_STATIC +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +unsigned char const* ZSTD_wrappedPtrSub(unsigned char const* ptr, ptrdiff_t sub) +{ + return ptr - sub; +} + +/** + * Helper function to add to a pointer that works around C's undefined behavior + * of adding 0 to NULL. + * + * @returns `ptr + add` except it defines `NULL + 0 == NULL`. + */ +MEM_STATIC +unsigned char* ZSTD_maybeNullPtrAdd(unsigned char* ptr, ptrdiff_t add) +{ + return add > 0 ? ptr + add : ptr; +} + /* Issue #3240 reports an ASAN failure on an llvm-mingw build. Out of an * abundance of caution, disable our custom poisoning on mingw. */ #ifdef __MINGW32__ diff --git a/src/thirdparty/zstd/common/cpu.h b/src/thirdparty/zstd/common/cpu.h index 8bc34a36..0e684d9a 100644 --- a/src/thirdparty/zstd/common/cpu.h +++ b/src/thirdparty/zstd/common/cpu.h @@ -35,6 +35,7 @@ MEM_STATIC ZSTD_cpuid_t ZSTD_cpuid(void) { U32 f7b = 0; U32 f7c = 0; #if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) +#if !defined(__clang__) int reg[4]; __cpuid((int*)reg, 0); { @@ -50,6 +51,41 @@ MEM_STATIC ZSTD_cpuid_t ZSTD_cpuid(void) { f7c = (U32)reg[2]; } } +#else + /* Clang compiler has a bug (fixed in https://reviews.llvm.org/D101338) in + * which the `__cpuid` intrinsic does not save and restore `rbx` as it needs + * to due to being a reserved register. So in that case, do the `cpuid` + * ourselves. Clang supports inline assembly anyway. + */ + U32 n; + __asm__( + "pushq %%rbx\n\t" + "cpuid\n\t" + "popq %%rbx\n\t" + : "=a"(n) + : "a"(0) + : "rcx", "rdx"); + if (n >= 1) { + U32 f1a; + __asm__( + "pushq %%rbx\n\t" + "cpuid\n\t" + "popq %%rbx\n\t" + : "=a"(f1a), "=c"(f1c), "=d"(f1d) + : "a"(1) + :); + } + if (n >= 7) { + __asm__( + "pushq %%rbx\n\t" + "cpuid\n\t" + "movq %%rbx, %%rax\n\t" + "popq %%rbx" + : "=a"(f7b), "=c"(f7c) + : "a"(7), "c"(0) + : "rdx"); + } +#endif #elif defined(__i386__) && defined(__PIC__) && !defined(__clang__) && defined(__GNUC__) /* The following block like the normal cpuid branch below, but gcc * reserves ebx for use of its pic register so we must specially diff --git a/src/thirdparty/zstd/common/debug.c b/src/thirdparty/zstd/common/debug.c index ebf7bfcc..9d0b7d22 100644 --- a/src/thirdparty/zstd/common/debug.c +++ b/src/thirdparty/zstd/common/debug.c @@ -21,4 +21,10 @@ #include "debug.h" +#if !defined(ZSTD_LINUX_KERNEL) || (DEBUGLEVEL>=2) +/* We only use this when DEBUGLEVEL>=2, but we get -Werror=pedantic errors if a + * translation unit is empty. So remove this from Linux kernel builds, but + * otherwise just leave it in. + */ int g_debuglevel = DEBUGLEVEL; +#endif diff --git a/src/thirdparty/zstd/common/debug.h b/src/thirdparty/zstd/common/debug.h index 0e9817ea..a16b69e5 100644 --- a/src/thirdparty/zstd/common/debug.h +++ b/src/thirdparty/zstd/common/debug.h @@ -85,18 +85,27 @@ extern int g_debuglevel; /* the variable is only declared, It's useful when enabling very verbose levels on selective conditions (such as position in src) */ -# define RAWLOG(l, ...) { \ - if (l<=g_debuglevel) { \ - ZSTD_DEBUG_PRINT(__VA_ARGS__); \ - } } -# define DEBUGLOG(l, ...) { \ - if (l<=g_debuglevel) { \ - ZSTD_DEBUG_PRINT(__FILE__ ": " __VA_ARGS__); \ - ZSTD_DEBUG_PRINT(" \n"); \ - } } +# define RAWLOG(l, ...) \ + do { \ + if (l<=g_debuglevel) { \ + ZSTD_DEBUG_PRINT(__VA_ARGS__); \ + } \ + } while (0) + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define LINE_AS_STRING TOSTRING(__LINE__) + +# define DEBUGLOG(l, ...) \ + do { \ + if (l<=g_debuglevel) { \ + ZSTD_DEBUG_PRINT(__FILE__ ":" LINE_AS_STRING ": " __VA_ARGS__); \ + ZSTD_DEBUG_PRINT(" \n"); \ + } \ + } while (0) #else -# define RAWLOG(l, ...) {} /* disabled */ -# define DEBUGLOG(l, ...) {} /* disabled */ +# define RAWLOG(l, ...) do { } while (0) /* disabled */ +# define DEBUGLOG(l, ...) do { } while (0) /* disabled */ #endif diff --git a/src/thirdparty/zstd/common/error_private.h b/src/thirdparty/zstd/common/error_private.h index 325daad4..0156010c 100644 --- a/src/thirdparty/zstd/common/error_private.h +++ b/src/thirdparty/zstd/common/error_private.h @@ -60,8 +60,13 @@ ERR_STATIC unsigned ERR_isError(size_t code) { return (code > ERROR(maxCode)); } ERR_STATIC ERR_enum ERR_getErrorCode(size_t code) { if (!ERR_isError(code)) return (ERR_enum)0; return (ERR_enum) (0-code); } /* check and forward error code */ -#define CHECK_V_F(e, f) size_t const e = f; if (ERR_isError(e)) return e -#define CHECK_F(f) { CHECK_V_F(_var_err__, f); } +#define CHECK_V_F(e, f) \ + size_t const e = f; \ + do { \ + if (ERR_isError(e)) \ + return e; \ + } while (0) +#define CHECK_F(f) do { CHECK_V_F(_var_err__, f); } while (0) /*-**************************************** @@ -95,10 +100,12 @@ void _force_has_format_string(const char *format, ...) { * We want to force this function invocation to be syntactically correct, but * we don't want to force runtime evaluation of its arguments. */ -#define _FORCE_HAS_FORMAT_STRING(...) \ - if (0) { \ - _force_has_format_string(__VA_ARGS__); \ - } +#define _FORCE_HAS_FORMAT_STRING(...) \ + do { \ + if (0) { \ + _force_has_format_string(__VA_ARGS__); \ + } \ + } while (0) #define ERR_QUOTE(str) #str @@ -109,48 +116,50 @@ void _force_has_format_string(const char *format, ...) { * In order to do that (particularly, printing the conditional that failed), * this can't just wrap RETURN_ERROR(). */ -#define RETURN_ERROR_IF(cond, err, ...) \ - if (cond) { \ - RAWLOG(3, "%s:%d: ERROR!: check %s failed, returning %s", \ - __FILE__, __LINE__, ERR_QUOTE(cond), ERR_QUOTE(ERROR(err))); \ - _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ - RAWLOG(3, ": " __VA_ARGS__); \ - RAWLOG(3, "\n"); \ - return ERROR(err); \ - } +#define RETURN_ERROR_IF(cond, err, ...) \ + do { \ + if (cond) { \ + RAWLOG(3, "%s:%d: ERROR!: check %s failed, returning %s", \ + __FILE__, __LINE__, ERR_QUOTE(cond), ERR_QUOTE(ERROR(err))); \ + _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ + RAWLOG(3, ": " __VA_ARGS__); \ + RAWLOG(3, "\n"); \ + return ERROR(err); \ + } \ + } while (0) /** * Unconditionally return the specified error. * * In debug modes, prints additional information. */ -#define RETURN_ERROR(err, ...) \ - do { \ - RAWLOG(3, "%s:%d: ERROR!: unconditional check failed, returning %s", \ - __FILE__, __LINE__, ERR_QUOTE(ERROR(err))); \ - _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ - RAWLOG(3, ": " __VA_ARGS__); \ - RAWLOG(3, "\n"); \ - return ERROR(err); \ - } while(0); +#define RETURN_ERROR(err, ...) \ + do { \ + RAWLOG(3, "%s:%d: ERROR!: unconditional check failed, returning %s", \ + __FILE__, __LINE__, ERR_QUOTE(ERROR(err))); \ + _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ + RAWLOG(3, ": " __VA_ARGS__); \ + RAWLOG(3, "\n"); \ + return ERROR(err); \ + } while(0) /** * If the provided expression evaluates to an error code, returns that error code. * * In debug modes, prints additional information. */ -#define FORWARD_IF_ERROR(err, ...) \ - do { \ - size_t const err_code = (err); \ - if (ERR_isError(err_code)) { \ - RAWLOG(3, "%s:%d: ERROR!: forwarding error in %s: %s", \ - __FILE__, __LINE__, ERR_QUOTE(err), ERR_getErrorName(err_code)); \ - _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ - RAWLOG(3, ": " __VA_ARGS__); \ - RAWLOG(3, "\n"); \ - return err_code; \ - } \ - } while(0); +#define FORWARD_IF_ERROR(err, ...) \ + do { \ + size_t const err_code = (err); \ + if (ERR_isError(err_code)) { \ + RAWLOG(3, "%s:%d: ERROR!: forwarding error in %s: %s", \ + __FILE__, __LINE__, ERR_QUOTE(err), ERR_getErrorName(err_code)); \ + _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ + RAWLOG(3, ": " __VA_ARGS__); \ + RAWLOG(3, "\n"); \ + return err_code; \ + } \ + } while(0) #if defined (__cplusplus) } diff --git a/src/thirdparty/zstd/common/fse.h b/src/thirdparty/zstd/common/fse.h index 02a1f0bc..2ae128e6 100644 --- a/src/thirdparty/zstd/common/fse.h +++ b/src/thirdparty/zstd/common/fse.h @@ -229,6 +229,7 @@ If there is an error, the function will return an error code, which can be teste #endif /* FSE_H */ + #if defined(FSE_STATIC_LINKING_ONLY) && !defined(FSE_H_FSE_STATIC_LINKING_ONLY) #define FSE_H_FSE_STATIC_LINKING_ONLY @@ -464,13 +465,13 @@ MEM_STATIC void FSE_encodeSymbol(BIT_CStream_t* bitC, FSE_CState_t* statePtr, un FSE_symbolCompressionTransform const symbolTT = ((const FSE_symbolCompressionTransform*)(statePtr->symbolTT))[symbol]; const U16* const stateTable = (const U16*)(statePtr->stateTable); U32 const nbBitsOut = (U32)((statePtr->value + symbolTT.deltaNbBits) >> 16); - BIT_addBits(bitC, statePtr->value, nbBitsOut); + BIT_addBits(bitC, (size_t)statePtr->value, nbBitsOut); statePtr->value = stateTable[ (statePtr->value >> nbBitsOut) + symbolTT.deltaFindState]; } MEM_STATIC void FSE_flushCState(BIT_CStream_t* bitC, const FSE_CState_t* statePtr) { - BIT_addBits(bitC, statePtr->value, statePtr->stateLog); + BIT_addBits(bitC, (size_t)statePtr->value, statePtr->stateLog); BIT_flushBits(bitC); } diff --git a/src/thirdparty/zstd/common/fse_decompress.c b/src/thirdparty/zstd/common/fse_decompress.c index 1e1c9f92..0dcc4640 100644 --- a/src/thirdparty/zstd/common/fse_decompress.c +++ b/src/thirdparty/zstd/common/fse_decompress.c @@ -22,8 +22,7 @@ #define FSE_STATIC_LINKING_ONLY #include "fse.h" #include "error_private.h" -#define ZSTD_DEPS_NEED_MALLOC -#include "zstd_deps.h" +#include "zstd_deps.h" /* ZSTD_memcpy */ #include "bits.h" /* ZSTD_highbit32 */ @@ -84,7 +83,7 @@ static size_t FSE_buildDTable_internal(FSE_DTable* dt, const short* normalizedCo symbolNext[s] = 1; } else { if (normalizedCounter[s] >= largeLimit) DTableH.fastMode=0; - symbolNext[s] = normalizedCounter[s]; + symbolNext[s] = (U16)normalizedCounter[s]; } } } ZSTD_memcpy(dt, &DTableH, sizeof(DTableH)); } @@ -99,8 +98,7 @@ static size_t FSE_buildDTable_internal(FSE_DTable* dt, const short* normalizedCo * all symbols have counts <= 8. We ensure we have 8 bytes at the end of * our buffer to handle the over-write. */ - { - U64 const add = 0x0101010101010101ull; + { U64 const add = 0x0101010101010101ull; size_t pos = 0; U64 sv = 0; U32 s; @@ -111,9 +109,8 @@ static size_t FSE_buildDTable_internal(FSE_DTable* dt, const short* normalizedCo for (i = 8; i < n; i += 8) { MEM_write64(spread + pos + i, sv); } - pos += n; - } - } + pos += (size_t)n; + } } /* Now we spread those positions across the table. * The benefit of doing it in two stages is that we avoid the * variable size inner loop, which caused lots of branch misses. @@ -232,12 +229,12 @@ FORCE_INLINE_TEMPLATE size_t FSE_decompress_usingDTable_generic( break; } } - return op-ostart; + assert(op >= ostart); + return (size_t)(op-ostart); } typedef struct { short ncount[FSE_MAX_SYMBOL_VALUE + 1]; - FSE_DTable dtable[1]; /* Dynamically sized */ } FSE_DecompressWksp; @@ -252,13 +249,18 @@ FORCE_INLINE_TEMPLATE size_t FSE_decompress_wksp_body( unsigned tableLog; unsigned maxSymbolValue = FSE_MAX_SYMBOL_VALUE; FSE_DecompressWksp* const wksp = (FSE_DecompressWksp*)workSpace; + size_t const dtablePos = sizeof(FSE_DecompressWksp) / sizeof(FSE_DTable); + FSE_DTable* const dtable = (FSE_DTable*)workSpace + dtablePos; - DEBUG_STATIC_ASSERT((FSE_MAX_SYMBOL_VALUE + 1) % 2 == 0); + FSE_STATIC_ASSERT((FSE_MAX_SYMBOL_VALUE + 1) % 2 == 0); if (wkspSize < sizeof(*wksp)) return ERROR(GENERIC); + /* correct offset to dtable depends on this property */ + FSE_STATIC_ASSERT(sizeof(FSE_DecompressWksp) % sizeof(FSE_DTable) == 0); + /* normal FSE decoding mode */ - { - size_t const NCountLength = FSE_readNCount_bmi2(wksp->ncount, &maxSymbolValue, &tableLog, istart, cSrcSize, bmi2); + { size_t const NCountLength = + FSE_readNCount_bmi2(wksp->ncount, &maxSymbolValue, &tableLog, istart, cSrcSize, bmi2); if (FSE_isError(NCountLength)) return NCountLength; if (tableLog > maxLog) return ERROR(tableLog_tooLarge); assert(NCountLength <= cSrcSize); @@ -271,16 +273,16 @@ FORCE_INLINE_TEMPLATE size_t FSE_decompress_wksp_body( workSpace = (BYTE*)workSpace + sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog); wkspSize -= sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog); - CHECK_F( FSE_buildDTable_internal(wksp->dtable, wksp->ncount, maxSymbolValue, tableLog, workSpace, wkspSize) ); + CHECK_F( FSE_buildDTable_internal(dtable, wksp->ncount, maxSymbolValue, tableLog, workSpace, wkspSize) ); { - const void* ptr = wksp->dtable; + const void* ptr = dtable; const FSE_DTableHeader* DTableH = (const FSE_DTableHeader*)ptr; const U32 fastMode = DTableH->fastMode; /* select fast mode (static) */ - if (fastMode) return FSE_decompress_usingDTable_generic(dst, dstCapacity, ip, cSrcSize, wksp->dtable, 1); - return FSE_decompress_usingDTable_generic(dst, dstCapacity, ip, cSrcSize, wksp->dtable, 0); + if (fastMode) return FSE_decompress_usingDTable_generic(dst, dstCapacity, ip, cSrcSize, dtable, 1); + return FSE_decompress_usingDTable_generic(dst, dstCapacity, ip, cSrcSize, dtable, 0); } } diff --git a/src/thirdparty/zstd/common/huf.h b/src/thirdparty/zstd/common/huf.h index 73d1ee56..99bf85d6 100644 --- a/src/thirdparty/zstd/common/huf.h +++ b/src/thirdparty/zstd/common/huf.h @@ -197,9 +197,22 @@ size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void /** HUF_getNbBitsFromCTable() : * Read nbBits from CTable symbolTable, for symbol `symbolValue` presumed <= HUF_SYMBOLVALUE_MAX - * Note 1 : is not inlined, as HUF_CElt definition is private */ + * Note 1 : If symbolValue > HUF_readCTableHeader(symbolTable).maxSymbolValue, returns 0 + * Note 2 : is not inlined, as HUF_CElt definition is private + */ U32 HUF_getNbBitsFromCTable(const HUF_CElt* symbolTable, U32 symbolValue); +typedef struct { + BYTE tableLog; + BYTE maxSymbolValue; + BYTE unused[sizeof(size_t) - 2]; +} HUF_CTableHeader; + +/** HUF_readCTableHeader() : + * @returns The header from the CTable specifying the tableLog and the maxSymbolValue. + */ +HUF_CTableHeader HUF_readCTableHeader(HUF_CElt const* ctable); + /* * HUF_decompress() does the following: * 1. select the decompression algorithm (X1, X2) based on pre-computed heuristics diff --git a/src/thirdparty/zstd/common/mem.h b/src/thirdparty/zstd/common/mem.h index 98dd47a0..096f4be5 100644 --- a/src/thirdparty/zstd/common/mem.h +++ b/src/thirdparty/zstd/common/mem.h @@ -31,15 +31,6 @@ extern "C" { # include /* _byteswap_ulong */ # include /* _byteswap_* */ #endif -#if defined(__GNUC__) -# define MEM_STATIC static __inline __attribute__((unused)) -#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) -# define MEM_STATIC static inline -#elif defined(_MSC_VER) -# define MEM_STATIC static __inline -#else -# define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ -#endif /*-************************************************************** * Basic Types diff --git a/src/thirdparty/zstd/common/pool.c b/src/thirdparty/zstd/common/pool.c index d5ca5a78..3adcefc9 100644 --- a/src/thirdparty/zstd/common/pool.c +++ b/src/thirdparty/zstd/common/pool.c @@ -223,7 +223,7 @@ static int POOL_resize_internal(POOL_ctx* ctx, size_t numThreads) { ZSTD_pthread_t* const threadPool = (ZSTD_pthread_t*)ZSTD_customCalloc(numThreads * sizeof(ZSTD_pthread_t), ctx->customMem); if (!threadPool) return 1; /* replace existing thread pool */ - ZSTD_memcpy(threadPool, ctx->threads, ctx->threadCapacity * sizeof(*threadPool)); + ZSTD_memcpy(threadPool, ctx->threads, ctx->threadCapacity * sizeof(ZSTD_pthread_t)); ZSTD_customFree(ctx->threads, ctx->customMem); ctx->threads = threadPool; /* Initialize additional threads */ diff --git a/src/thirdparty/zstd/common/pool.h b/src/thirdparty/zstd/common/pool.h index eb22ff50..cca4de73 100644 --- a/src/thirdparty/zstd/common/pool.h +++ b/src/thirdparty/zstd/common/pool.h @@ -47,7 +47,7 @@ void POOL_joinJobs(POOL_ctx* ctx); /*! POOL_resize() : * Expands or shrinks pool's number of threads. * This is more efficient than releasing + creating a new context, - * since it tries to preserve and re-use existing threads. + * since it tries to preserve and reuse existing threads. * `numThreads` must be at least 1. * @return : 0 when resize was successful, * !0 (typically 1) if there is an error. diff --git a/src/thirdparty/zstd/common/portability_macros.h b/src/thirdparty/zstd/common/portability_macros.h index 8fd6ea82..e50314a7 100644 --- a/src/thirdparty/zstd/common/portability_macros.h +++ b/src/thirdparty/zstd/common/portability_macros.h @@ -68,6 +68,8 @@ /* Mark the internal assembly functions as hidden */ #ifdef __ELF__ # define ZSTD_HIDE_ASM_FUNCTION(func) .hidden func +#elif defined(__APPLE__) +# define ZSTD_HIDE_ASM_FUNCTION(func) .private_extern func #else # define ZSTD_HIDE_ASM_FUNCTION(func) #endif diff --git a/src/thirdparty/zstd/common/threading.c b/src/thirdparty/zstd/common/threading.c index ca155b9b..25bb8b98 100644 --- a/src/thirdparty/zstd/common/threading.c +++ b/src/thirdparty/zstd/common/threading.c @@ -73,10 +73,12 @@ int ZSTD_pthread_create(ZSTD_pthread_t* thread, const void* unused, ZSTD_thread_params_t thread_param; (void)unused; + if (thread==NULL) return -1; + *thread = NULL; + thread_param.start_routine = start_routine; thread_param.arg = arg; thread_param.initialized = 0; - *thread = NULL; /* Setup thread initialization synchronization */ if(ZSTD_pthread_cond_init(&thread_param.initialized_cond, NULL)) { @@ -91,7 +93,7 @@ int ZSTD_pthread_create(ZSTD_pthread_t* thread, const void* unused, /* Spawn thread */ *thread = (HANDLE)_beginthreadex(NULL, 0, worker, &thread_param, 0, NULL); - if (!thread) { + if (*thread==NULL) { ZSTD_pthread_mutex_destroy(&thread_param.initialized_mutex); ZSTD_pthread_cond_destroy(&thread_param.initialized_cond); return errno; @@ -137,6 +139,7 @@ int ZSTD_pthread_join(ZSTD_pthread_t thread) int ZSTD_pthread_mutex_init(ZSTD_pthread_mutex_t* mutex, pthread_mutexattr_t const* attr) { + assert(mutex != NULL); *mutex = (pthread_mutex_t*)ZSTD_malloc(sizeof(pthread_mutex_t)); if (!*mutex) return 1; @@ -145,6 +148,7 @@ int ZSTD_pthread_mutex_init(ZSTD_pthread_mutex_t* mutex, pthread_mutexattr_t con int ZSTD_pthread_mutex_destroy(ZSTD_pthread_mutex_t* mutex) { + assert(mutex != NULL); if (!*mutex) return 0; { @@ -156,6 +160,7 @@ int ZSTD_pthread_mutex_destroy(ZSTD_pthread_mutex_t* mutex) int ZSTD_pthread_cond_init(ZSTD_pthread_cond_t* cond, pthread_condattr_t const* attr) { + assert(cond != NULL); *cond = (pthread_cond_t*)ZSTD_malloc(sizeof(pthread_cond_t)); if (!*cond) return 1; @@ -164,6 +169,7 @@ int ZSTD_pthread_cond_init(ZSTD_pthread_cond_t* cond, pthread_condattr_t const* int ZSTD_pthread_cond_destroy(ZSTD_pthread_cond_t* cond) { + assert(cond != NULL); if (!*cond) return 0; { diff --git a/src/thirdparty/zstd/common/xxhash.c b/src/thirdparty/zstd/common/xxhash.c index fd237c90..052cd522 100644 --- a/src/thirdparty/zstd/common/xxhash.c +++ b/src/thirdparty/zstd/common/xxhash.c @@ -1,24 +1,18 @@ /* - * xxHash - Fast Hash algorithm - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * You can contact the author at : - * - xxHash homepage: https://cyan4973.github.io/xxHash/ - * - xxHash source repository : https://github.com/Cyan4973/xxHash + * xxHash - Extremely Fast Hash algorithm + * Copyright (c) Yann Collet - Meta Platforms, Inc * * This source code is licensed under both the BSD-style license (found in the * LICENSE file in the root directory of this source tree) and the GPLv2 (found * in the COPYING file in the root directory of this source tree). * You may select, at your option, one of the above-listed licenses. -*/ - - + */ /* * xxhash.c instantiates functions defined in xxhash.h */ -#define XXH_STATIC_LINKING_ONLY /* access advanced declarations */ -#define XXH_IMPLEMENTATION /* access definitions */ +#define XXH_STATIC_LINKING_ONLY /* access advanced declarations */ +#define XXH_IMPLEMENTATION /* access definitions */ #include "xxhash.h" diff --git a/src/thirdparty/zstd/common/xxhash.h b/src/thirdparty/zstd/common/xxhash.h index b8b73290..e59e4426 100644 --- a/src/thirdparty/zstd/common/xxhash.h +++ b/src/thirdparty/zstd/common/xxhash.h @@ -1,17 +1,15 @@ /* - * xxHash - Fast Hash algorithm - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * You can contact the author at : - * - xxHash homepage: https://cyan4973.github.io/xxHash/ - * - xxHash source repository : https://github.com/Cyan4973/xxHash + * xxHash - Extremely Fast Hash algorithm + * Header File + * Copyright (c) Yann Collet - Meta Platforms, Inc * * This source code is licensed under both the BSD-style license (found in the * LICENSE file in the root directory of this source tree) and the GPLv2 (found * in the COPYING file in the root directory of this source tree). * You may select, at your option, one of the above-listed licenses. -*/ + */ +/* Local adaptations for Zstandard */ #ifndef XXH_NO_XXH3 # define XXH_NO_XXH3 @@ -24,46 +22,210 @@ /*! * @mainpage xxHash * + * xxHash is an extremely fast non-cryptographic hash algorithm, working at RAM speed + * limits. + * + * It is proposed in four flavors, in three families: + * 1. @ref XXH32_family + * - Classic 32-bit hash function. Simple, compact, and runs on almost all + * 32-bit and 64-bit systems. + * 2. @ref XXH64_family + * - Classic 64-bit adaptation of XXH32. Just as simple, and runs well on most + * 64-bit systems (but _not_ 32-bit systems). + * 3. @ref XXH3_family + * - Modern 64-bit and 128-bit hash function family which features improved + * strength and performance across the board, especially on smaller data. + * It benefits greatly from SIMD and 64-bit without requiring it. + * + * Benchmarks + * --- + * The reference system uses an Intel i7-9700K CPU, and runs Ubuntu x64 20.04. + * The open source benchmark program is compiled with clang v10.0 using -O3 flag. + * + * | Hash Name | ISA ext | Width | Large Data Speed | Small Data Velocity | + * | -------------------- | ------- | ----: | ---------------: | ------------------: | + * | XXH3_64bits() | @b AVX2 | 64 | 59.4 GB/s | 133.1 | + * | MeowHash | AES-NI | 128 | 58.2 GB/s | 52.5 | + * | XXH3_128bits() | @b AVX2 | 128 | 57.9 GB/s | 118.1 | + * | CLHash | PCLMUL | 64 | 37.1 GB/s | 58.1 | + * | XXH3_64bits() | @b SSE2 | 64 | 31.5 GB/s | 133.1 | + * | XXH3_128bits() | @b SSE2 | 128 | 29.6 GB/s | 118.1 | + * | RAM sequential read | | N/A | 28.0 GB/s | N/A | + * | ahash | AES-NI | 64 | 22.5 GB/s | 107.2 | + * | City64 | | 64 | 22.0 GB/s | 76.6 | + * | T1ha2 | | 64 | 22.0 GB/s | 99.0 | + * | City128 | | 128 | 21.7 GB/s | 57.7 | + * | FarmHash | AES-NI | 64 | 21.3 GB/s | 71.9 | + * | XXH64() | | 64 | 19.4 GB/s | 71.0 | + * | SpookyHash | | 64 | 19.3 GB/s | 53.2 | + * | Mum | | 64 | 18.0 GB/s | 67.0 | + * | CRC32C | SSE4.2 | 32 | 13.0 GB/s | 57.9 | + * | XXH32() | | 32 | 9.7 GB/s | 71.9 | + * | City32 | | 32 | 9.1 GB/s | 66.0 | + * | Blake3* | @b AVX2 | 256 | 4.4 GB/s | 8.1 | + * | Murmur3 | | 32 | 3.9 GB/s | 56.1 | + * | SipHash* | | 64 | 3.0 GB/s | 43.2 | + * | Blake3* | @b SSE2 | 256 | 2.4 GB/s | 8.1 | + * | HighwayHash | | 64 | 1.4 GB/s | 6.0 | + * | FNV64 | | 64 | 1.2 GB/s | 62.7 | + * | Blake2* | | 256 | 1.1 GB/s | 5.1 | + * | SHA1* | | 160 | 0.8 GB/s | 5.6 | + * | MD5* | | 128 | 0.6 GB/s | 7.8 | + * @note + * - Hashes which require a specific ISA extension are noted. SSE2 is also noted, + * even though it is mandatory on x64. + * - Hashes with an asterisk are cryptographic. Note that MD5 is non-cryptographic + * by modern standards. + * - Small data velocity is a rough average of algorithm's efficiency for small + * data. For more accurate information, see the wiki. + * - More benchmarks and strength tests are found on the wiki: + * https://github.com/Cyan4973/xxHash/wiki + * + * Usage + * ------ + * All xxHash variants use a similar API. Changing the algorithm is a trivial + * substitution. + * + * @pre + * For functions which take an input and length parameter, the following + * requirements are assumed: + * - The range from [`input`, `input + length`) is valid, readable memory. + * - The only exception is if the `length` is `0`, `input` may be `NULL`. + * - For C++, the objects must have the *TriviallyCopyable* property, as the + * functions access bytes directly as if it was an array of `unsigned char`. + * + * @anchor single_shot_example + * **Single Shot** + * + * These functions are stateless functions which hash a contiguous block of memory, + * immediately returning the result. They are the easiest and usually the fastest + * option. + * + * XXH32(), XXH64(), XXH3_64bits(), XXH3_128bits() + * + * @code{.c} + * #include + * #include "xxhash.h" + * + * // Example for a function which hashes a null terminated string with XXH32(). + * XXH32_hash_t hash_string(const char* string, XXH32_hash_t seed) + * { + * // NULL pointers are only valid if the length is zero + * size_t length = (string == NULL) ? 0 : strlen(string); + * return XXH32(string, length, seed); + * } + * @endcode + * + * + * @anchor streaming_example + * **Streaming** + * + * These groups of functions allow incremental hashing of unknown size, even + * more than what would fit in a size_t. + * + * XXH32_reset(), XXH64_reset(), XXH3_64bits_reset(), XXH3_128bits_reset() + * + * @code{.c} + * #include + * #include + * #include "xxhash.h" + * // Example for a function which hashes a FILE incrementally with XXH3_64bits(). + * XXH64_hash_t hashFile(FILE* f) + * { + * // Allocate a state struct. Do not just use malloc() or new. + * XXH3_state_t* state = XXH3_createState(); + * assert(state != NULL && "Out of memory!"); + * // Reset the state to start a new hashing session. + * XXH3_64bits_reset(state); + * char buffer[4096]; + * size_t count; + * // Read the file in chunks + * while ((count = fread(buffer, 1, sizeof(buffer), f)) != 0) { + * // Run update() as many times as necessary to process the data + * XXH3_64bits_update(state, buffer, count); + * } + * // Retrieve the finalized hash. This will not change the state. + * XXH64_hash_t result = XXH3_64bits_digest(state); + * // Free the state. Do not use free(). + * XXH3_freeState(state); + * return result; + * } + * @endcode + * + * Streaming functions generate the xxHash value from an incremental input. + * This method is slower than single-call functions, due to state management. + * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized. + * + * An XXH state must first be allocated using `XXH*_createState()`. + * + * Start a new hash by initializing the state with a seed using `XXH*_reset()`. + * + * Then, feed the hash state by calling `XXH*_update()` as many times as necessary. + * + * The function returns an error code, with 0 meaning OK, and any other value + * meaning there is an error. + * + * Finally, a hash value can be produced anytime, by using `XXH*_digest()`. + * This function returns the nn-bits hash as an int or long long. + * + * It's still possible to continue inserting input into the hash state after a + * digest, and generate new hash values later on by invoking `XXH*_digest()`. + * + * When done, release the state using `XXH*_freeState()`. + * + * + * @anchor canonical_representation_example + * **Canonical Representation** + * + * The default return values from XXH functions are unsigned 32, 64 and 128 bit + * integers. + * This the simplest and fastest format for further post-processing. + * + * However, this leaves open the question of what is the order on the byte level, + * since little and big endian conventions will store the same number differently. + * + * The canonical representation settles this issue by mandating big-endian + * convention, the same convention as human-readable numbers (large digits first). + * + * When writing hash values to storage, sending them over a network, or printing + * them, it's highly recommended to use the canonical representation to ensure + * portability across a wider range of systems, present and future. + * + * The following functions allow transformation of hash values to and from + * canonical format. + * + * XXH32_canonicalFromHash(), XXH32_hashFromCanonical(), + * XXH64_canonicalFromHash(), XXH64_hashFromCanonical(), + * XXH128_canonicalFromHash(), XXH128_hashFromCanonical(), + * + * @code{.c} + * #include + * #include "xxhash.h" + * + * // Example for a function which prints XXH32_hash_t in human readable format + * void printXxh32(XXH32_hash_t hash) + * { + * XXH32_canonical_t cano; + * XXH32_canonicalFromHash(&cano, hash); + * size_t i; + * for(i = 0; i < sizeof(cano.digest); ++i) { + * printf("%02x", cano.digest[i]); + * } + * printf("\n"); + * } + * + * // Example for a function which converts XXH32_canonical_t to XXH32_hash_t + * XXH32_hash_t convertCanonicalToXxh32(XXH32_canonical_t cano) + * { + * XXH32_hash_t hash = XXH32_hashFromCanonical(&cano); + * return hash; + * } + * @endcode + * + * * @file xxhash.h * xxHash prototypes and implementation */ -/* TODO: update */ -/* Notice extracted from xxHash homepage: - -xxHash is an extremely fast hash algorithm, running at RAM speed limits. -It also successfully passes all tests from the SMHasher suite. - -Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz) - -Name Speed Q.Score Author -xxHash 5.4 GB/s 10 -CrapWow 3.2 GB/s 2 Andrew -MurmurHash 3a 2.7 GB/s 10 Austin Appleby -SpookyHash 2.0 GB/s 10 Bob Jenkins -SBox 1.4 GB/s 9 Bret Mulvey -Lookup3 1.2 GB/s 9 Bob Jenkins -SuperFastHash 1.2 GB/s 1 Paul Hsieh -CityHash64 1.05 GB/s 10 Pike & Alakuijala -FNV 0.55 GB/s 5 Fowler, Noll, Vo -CRC32 0.43 GB/s 9 -MD5-32 0.33 GB/s 10 Ronald L. Rivest -SHA1-32 0.28 GB/s 10 - -Q.Score is a measure of quality of the hash function. -It depends on successfully passing SMHasher test set. -10 is a perfect score. - -Note: SMHasher's CRC32 implementation is not the fastest one. -Other speed-oriented implementations can be faster, -especially in combination with PCLMUL instruction: -https://fastcompression.blogspot.com/2019/03/presenting-xxh3.html?showComment=1552696407071#c3490092340461170735 - -A 64-bit version, named XXH64, is available since r35. -It offers much better speed, but for 64-bit applications only. -Name Speed on 64 bits Speed on 32 bits -XXH64 13.8 GB/s 1.9 GB/s -XXH32 6.8 GB/s 6.0 GB/s -*/ #if defined (__cplusplus) extern "C" { @@ -73,21 +235,80 @@ extern "C" { * INLINE mode ******************************/ /*! - * XXH_INLINE_ALL (and XXH_PRIVATE_API) + * @defgroup public Public API + * Contains details on the public xxHash functions. + * @{ + */ +#ifdef XXH_DOXYGEN +/*! + * @brief Gives access to internal state declaration, required for static allocation. + * + * Incompatible with dynamic linking, due to risks of ABI changes. + * + * Usage: + * @code{.c} + * #define XXH_STATIC_LINKING_ONLY + * #include "xxhash.h" + * @endcode + */ +# define XXH_STATIC_LINKING_ONLY +/* Do not undef XXH_STATIC_LINKING_ONLY for Doxygen */ + +/*! + * @brief Gives access to internal definitions. + * + * Usage: + * @code{.c} + * #define XXH_STATIC_LINKING_ONLY + * #define XXH_IMPLEMENTATION + * #include "xxhash.h" + * @endcode + */ +# define XXH_IMPLEMENTATION +/* Do not undef XXH_IMPLEMENTATION for Doxygen */ + +/*! + * @brief Exposes the implementation and marks all functions as `inline`. + * * Use these build macros to inline xxhash into the target unit. * Inlining improves performance on small inputs, especially when the length is * expressed as a compile-time constant: * - * https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html + * https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html * * It also keeps xxHash symbols private to the unit, so they are not exported. * * Usage: + * @code{.c} * #define XXH_INLINE_ALL * #include "xxhash.h" - * + * @endcode * Do not compile and link xxhash.o as a separate object, as it is not useful. */ +# define XXH_INLINE_ALL +# undef XXH_INLINE_ALL +/*! + * @brief Exposes the implementation without marking functions as inline. + */ +# define XXH_PRIVATE_API +# undef XXH_PRIVATE_API +/*! + * @brief Emulate a namespace by transparently prefixing all symbols. + * + * If you want to include _and expose_ xxHash functions from within your own + * library, but also want to avoid symbol collisions with other libraries which + * may also include xxHash, you can use @ref XXH_NAMESPACE to automatically prefix + * any public symbol from xxhash library with the value of @ref XXH_NAMESPACE + * (therefore, avoid empty or numeric values). + * + * Note that no change is required within the calling program as long as it + * includes `xxhash.h`: Regular symbol names will be automatically translated + * by this header. + */ +# define XXH_NAMESPACE /* YOUR NAME HERE */ +# undef XXH_NAMESPACE +#endif + #if (defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)) \ && !defined(XXH_INLINE_ALL_31684351384) /* this section should be traversed only once */ @@ -202,21 +423,13 @@ extern "C" { # undef XXHASH_H_STATIC_13879238742 #endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */ - - /* **************************************************************** * Stable API *****************************************************************/ #ifndef XXHASH_H_5627135585666179 #define XXHASH_H_5627135585666179 1 - -/*! - * @defgroup public Public API - * Contains details on the public xxHash functions. - * @{ - */ -/* specific declaration modes for Windows */ +/*! @brief Marks a global symbol. */ #if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API) # if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT)) # ifdef XXH_EXPORT @@ -229,24 +442,6 @@ extern "C" { # endif #endif -#ifdef XXH_DOXYGEN -/*! - * @brief Emulate a namespace by transparently prefixing all symbols. - * - * If you want to include _and expose_ xxHash functions from within your own - * library, but also want to avoid symbol collisions with other libraries which - * may also include xxHash, you can use XXH_NAMESPACE to automatically prefix - * any public symbol from xxhash library with the value of XXH_NAMESPACE - * (therefore, avoid empty or numeric values). - * - * Note that no change is required within the calling program as long as it - * includes `xxhash.h`: Regular symbol names will be automatically translated - * by this header. - */ -# define XXH_NAMESPACE /* YOUR NAME HERE */ -# undef XXH_NAMESPACE -#endif - #ifdef XXH_NAMESPACE # define XXH_CAT(A,B) A##B # define XXH_NAME2(A,B) XXH_CAT(A,B) @@ -306,12 +501,40 @@ extern "C" { #endif +/* ************************************* +* Compiler specifics +***************************************/ + +/* specific declaration modes for Windows */ +#if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API) +# if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT)) +# ifdef XXH_EXPORT +# define XXH_PUBLIC_API __declspec(dllexport) +# elif XXH_IMPORT +# define XXH_PUBLIC_API __declspec(dllimport) +# endif +# else +# define XXH_PUBLIC_API /* do nothing */ +# endif +#endif + +#if defined (__GNUC__) +# define XXH_CONSTF __attribute__((const)) +# define XXH_PUREF __attribute__((pure)) +# define XXH_MALLOCF __attribute__((malloc)) +#else +# define XXH_CONSTF /* disable */ +# define XXH_PUREF +# define XXH_MALLOCF +#endif + /* ************************************* * Version ***************************************/ #define XXH_VERSION_MAJOR 0 #define XXH_VERSION_MINOR 8 -#define XXH_VERSION_RELEASE 1 +#define XXH_VERSION_RELEASE 2 +/*! @brief Version number, encoded as two digits each */ #define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE) /*! @@ -320,16 +543,22 @@ extern "C" { * This is mostly useful when xxHash is compiled as a shared library, * since the returned value comes from the library, as opposed to header file. * - * @return `XXH_VERSION_NUMBER` of the invoked library. + * @return @ref XXH_VERSION_NUMBER of the invoked library. */ -XXH_PUBLIC_API unsigned XXH_versionNumber (void); +XXH_PUBLIC_API XXH_CONSTF unsigned XXH_versionNumber (void); /* **************************** * Common basic types ******************************/ #include /* size_t */ -typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; +/*! + * @brief Exit code for the streaming API. + */ +typedef enum { + XXH_OK = 0, /*!< OK */ + XXH_ERROR /*!< Error */ +} XXH_errorcode; /*-********************************************************************** @@ -346,44 +575,44 @@ typedef uint32_t XXH32_hash_t; #elif !defined (__VMS) \ && (defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) -# include +# ifdef _AIX +# include +# else +# include +# endif typedef uint32_t XXH32_hash_t; #else # include # if UINT_MAX == 0xFFFFFFFFUL typedef unsigned int XXH32_hash_t; +# elif ULONG_MAX == 0xFFFFFFFFUL + typedef unsigned long XXH32_hash_t; # else -# if ULONG_MAX == 0xFFFFFFFFUL - typedef unsigned long XXH32_hash_t; -# else -# error "unsupported platform: need a 32-bit type" -# endif +# error "unsupported platform: need a 32-bit type" # endif #endif /*! * @} * - * @defgroup xxh32_family XXH32 family + * @defgroup XXH32_family XXH32 family * @ingroup public * Contains functions used in the classic 32-bit xxHash algorithm. * * @note * XXH32 is useful for older platforms, with no or poor 64-bit performance. - * Note that @ref xxh3_family provides competitive speed - * for both 32-bit and 64-bit systems, and offers true 64/128 bit hash results. + * Note that the @ref XXH3_family provides competitive speed for both 32-bit + * and 64-bit systems, and offers true 64/128 bit hash results. * - * @see @ref xxh64_family, @ref xxh3_family : Other xxHash families - * @see @ref xxh32_impl for implementation details + * @see @ref XXH64_family, @ref XXH3_family : Other xxHash families + * @see @ref XXH32_impl for implementation details * @{ */ /*! * @brief Calculates the 32-bit hash of @p input using xxHash32. * - * Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark): 5.4 GB/s - * * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. * @param seed The 32-bit seed to alter the hash's output predictably. @@ -393,66 +622,13 @@ typedef uint32_t XXH32_hash_t; * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * - * @return The calculated 32-bit hash value. + * @return The calculated 32-bit xxHash32 value. * - * @see - * XXH64(), XXH3_64bits_withSeed(), XXH3_128bits_withSeed(), XXH128(): - * Direct equivalents for the other variants of xxHash. - * @see - * XXH32_createState(), XXH32_update(), XXH32_digest(): Streaming version. - */ -XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed); - -/*! - * Streaming functions generate the xxHash value from an incremental input. - * This method is slower than single-call functions, due to state management. - * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized. - * - * An XXH state must first be allocated using `XXH*_createState()`. - * - * Start a new hash by initializing the state with a seed using `XXH*_reset()`. - * - * Then, feed the hash state by calling `XXH*_update()` as many times as necessary. - * - * The function returns an error code, with 0 meaning OK, and any other value - * meaning there is an error. - * - * Finally, a hash value can be produced anytime, by using `XXH*_digest()`. - * This function returns the nn-bits hash as an int or long long. - * - * It's still possible to continue inserting input into the hash state after a - * digest, and generate new hash values later on by invoking `XXH*_digest()`. - * - * When done, release the state using `XXH*_freeState()`. - * - * Example code for incrementally hashing a file: - * @code{.c} - * #include - * #include - * #define BUFFER_SIZE 256 - * - * // Note: XXH64 and XXH3 use the same interface. - * XXH32_hash_t - * hashFile(FILE* stream) - * { - * XXH32_state_t* state; - * unsigned char buf[BUFFER_SIZE]; - * size_t amt; - * XXH32_hash_t hash; - * - * state = XXH32_createState(); // Create a state - * assert(state != NULL); // Error check here - * XXH32_reset(state, 0xbaad5eed); // Reset state with our seed - * while ((amt = fread(buf, 1, sizeof(buf), stream)) != 0) { - * XXH32_update(state, buf, amt); // Hash the file in chunks - * } - * hash = XXH32_digest(state); // Finalize the hash - * XXH32_freeState(state); // Clean up - * return hash; - * } - * @endcode + * @see @ref single_shot_example "Single Shot Example" for an example. */ +XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed); +#ifndef XXH_NO_STREAM /*! * @typedef struct XXH32_state_s XXH32_state_t * @brief The opaque state struct for the XXH32 streaming API. @@ -464,16 +640,21 @@ typedef struct XXH32_state_s XXH32_state_t; /*! * @brief Allocates an @ref XXH32_state_t. * - * Must be freed with XXH32_freeState(). - * @return An allocated XXH32_state_t on success, `NULL` on failure. + * @return An allocated pointer of @ref XXH32_state_t on success. + * @return `NULL` on failure. + * + * @note Must be freed with XXH32_freeState(). */ -XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void); +XXH_PUBLIC_API XXH_MALLOCF XXH32_state_t* XXH32_createState(void); /*! * @brief Frees an @ref XXH32_state_t. * - * Must be allocated with XXH32_createState(). * @param statePtr A pointer to an @ref XXH32_state_t allocated with @ref XXH32_createState(). - * @return XXH_OK. + * + * @return @ref XXH_OK. + * + * @note @p statePtr must be allocated with XXH32_createState(). + * */ XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr); /*! @@ -489,23 +670,22 @@ XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_ /*! * @brief Resets an @ref XXH32_state_t to begin a new hash. * - * This function resets and seeds a state. Call it before @ref XXH32_update(). - * * @param statePtr The state struct to reset. * @param seed The 32-bit seed to alter the hash result predictably. * * @pre * @p statePtr must not be `NULL`. * - * @return @ref XXH_OK on success, @ref XXH_ERROR on failure. + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note This function resets and seeds a state. Call it before @ref XXH32_update(). */ XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, XXH32_hash_t seed); /*! * @brief Consumes a block of @p input to an @ref XXH32_state_t. * - * Call this to incrementally consume blocks of data. - * * @param statePtr The state struct to update. * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. @@ -517,47 +697,32 @@ XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, XXH32_hash_t * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * - * @return @ref XXH_OK on success, @ref XXH_ERROR on failure. + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note Call this to incrementally consume blocks of data. */ XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length); /*! * @brief Returns the calculated hash value from an @ref XXH32_state_t. * - * @note - * Calling XXH32_digest() will not affect @p statePtr, so you can update, - * digest, and update again. - * * @param statePtr The state struct to calculate the hash from. * * @pre * @p statePtr must not be `NULL`. * - * @return The calculated xxHash32 value from that state. + * @return The calculated 32-bit xxHash32 value from that state. + * + * @note + * Calling XXH32_digest() will not affect @p statePtr, so you can update, + * digest, and update again. */ -XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr); +XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr); +#endif /* !XXH_NO_STREAM */ /******* Canonical representation *******/ -/* - * The default return values from XXH functions are unsigned 32 and 64 bit - * integers. - * This the simplest and fastest format for further post-processing. - * - * However, this leaves open the question of what is the order on the byte level, - * since little and big endian conventions will store the same number differently. - * - * The canonical representation settles this issue by mandating big-endian - * convention, the same convention as human-readable numbers (large digits first). - * - * When writing hash values to storage, sending them over a network, or printing - * them, it's highly recommended to use the canonical representation to ensure - * portability across a wider range of systems, present and future. - * - * The following functions allow transformation of hash values to and from - * canonical format. - */ - /*! * @brief Canonical (big endian) representation of @ref XXH32_hash_t. */ @@ -568,11 +733,13 @@ typedef struct { /*! * @brief Converts an @ref XXH32_hash_t to a big endian @ref XXH32_canonical_t. * - * @param dst The @ref XXH32_canonical_t pointer to be stored to. + * @param dst The @ref XXH32_canonical_t pointer to be stored to. * @param hash The @ref XXH32_hash_t to be converted. * * @pre * @p dst must not be `NULL`. + * + * @see @ref canonical_representation_example "Canonical Representation Example" */ XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash); @@ -585,44 +752,75 @@ XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t * @p src must not be `NULL`. * * @return The converted hash. + * + * @see @ref canonical_representation_example "Canonical Representation Example" */ -XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); +XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); +/*! @cond Doxygen ignores this part */ #ifdef __has_attribute # define XXH_HAS_ATTRIBUTE(x) __has_attribute(x) #else # define XXH_HAS_ATTRIBUTE(x) 0 #endif +/*! @endcond */ +/*! @cond Doxygen ignores this part */ +/* + * C23 __STDC_VERSION__ number hasn't been specified yet. For now + * leave as `201711L` (C17 + 1). + * TODO: Update to correct value when its been specified. + */ +#define XXH_C23_VN 201711L +/*! @endcond */ + +/*! @cond Doxygen ignores this part */ /* C-language Attributes are added in C23. */ -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ > 201710L) && defined(__has_c_attribute) +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= XXH_C23_VN) && defined(__has_c_attribute) # define XXH_HAS_C_ATTRIBUTE(x) __has_c_attribute(x) #else # define XXH_HAS_C_ATTRIBUTE(x) 0 #endif +/*! @endcond */ +/*! @cond Doxygen ignores this part */ #if defined(__cplusplus) && defined(__has_cpp_attribute) # define XXH_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) #else # define XXH_HAS_CPP_ATTRIBUTE(x) 0 #endif +/*! @endcond */ +/*! @cond Doxygen ignores this part */ /* -Define XXH_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute -introduced in CPP17 and C23. -CPP17 : https://en.cppreference.com/w/cpp/language/attributes/fallthrough -C23 : https://en.cppreference.com/w/c/language/attributes/fallthrough -*/ -#if XXH_HAS_C_ATTRIBUTE(x) -# define XXH_FALLTHROUGH [[fallthrough]] -#elif XXH_HAS_CPP_ATTRIBUTE(x) + * Define XXH_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute + * introduced in CPP17 and C23. + * CPP17 : https://en.cppreference.com/w/cpp/language/attributes/fallthrough + * C23 : https://en.cppreference.com/w/c/language/attributes/fallthrough + */ +#if XXH_HAS_C_ATTRIBUTE(fallthrough) || XXH_HAS_CPP_ATTRIBUTE(fallthrough) # define XXH_FALLTHROUGH [[fallthrough]] #elif XXH_HAS_ATTRIBUTE(__fallthrough__) -# define XXH_FALLTHROUGH __attribute__ ((fallthrough)) +# define XXH_FALLTHROUGH __attribute__ ((__fallthrough__)) #else -# define XXH_FALLTHROUGH +# define XXH_FALLTHROUGH /* fallthrough */ #endif +/*! @endcond */ + +/*! @cond Doxygen ignores this part */ +/* + * Define XXH_NOESCAPE for annotated pointers in public API. + * https://clang.llvm.org/docs/AttributeReference.html#noescape + * As of writing this, only supported by clang. + */ +#if XXH_HAS_ATTRIBUTE(noescape) +# define XXH_NOESCAPE __attribute__((noescape)) +#else +# define XXH_NOESCAPE +#endif +/*! @endcond */ + /*! * @} @@ -644,7 +842,11 @@ typedef uint64_t XXH64_hash_t; #elif !defined (__VMS) \ && (defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) -# include +# ifdef _AIX +# include +# else +# include +# endif typedef uint64_t XXH64_hash_t; #else # include @@ -660,7 +862,7 @@ typedef uint64_t XXH64_hash_t; /*! * @} * - * @defgroup xxh64_family XXH64 family + * @defgroup XXH64_family XXH64 family * @ingroup public * @{ * Contains functions used in the classic 64-bit xxHash algorithm. @@ -671,13 +873,9 @@ typedef uint64_t XXH64_hash_t; * It provides better speed for systems with vector processing capabilities. */ - /*! * @brief Calculates the 64-bit hash of @p input using xxHash64. * - * This function usually runs faster on 64-bit systems, but slower on 32-bit - * systems (see benchmark). - * * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. * @param seed The 64-bit seed to alter the hash's output predictably. @@ -687,41 +885,145 @@ typedef uint64_t XXH64_hash_t; * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * - * @return The calculated 64-bit hash. + * @return The calculated 64-bit xxHash64 value. * - * @see - * XXH32(), XXH3_64bits_withSeed(), XXH3_128bits_withSeed(), XXH128(): - * Direct equivalents for the other variants of xxHash. - * @see - * XXH64_createState(), XXH64_update(), XXH64_digest(): Streaming version. + * @see @ref single_shot_example "Single Shot Example" for an example. */ -XXH_PUBLIC_API XXH64_hash_t XXH64(const void* input, size_t length, XXH64_hash_t seed); +XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed); /******* Streaming *******/ +#ifndef XXH_NO_STREAM /*! * @brief The opaque state struct for the XXH64 streaming API. * * @see XXH64_state_s for details. */ typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */ -XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void); + +/*! + * @brief Allocates an @ref XXH64_state_t. + * + * @return An allocated pointer of @ref XXH64_state_t on success. + * @return `NULL` on failure. + * + * @note Must be freed with XXH64_freeState(). + */ +XXH_PUBLIC_API XXH_MALLOCF XXH64_state_t* XXH64_createState(void); + +/*! + * @brief Frees an @ref XXH64_state_t. + * + * @param statePtr A pointer to an @ref XXH64_state_t allocated with @ref XXH64_createState(). + * + * @return @ref XXH_OK. + * + * @note @p statePtr must be allocated with XXH64_createState(). + */ XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr); -XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state); -XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH64_state_t* statePtr, XXH64_hash_t seed); -XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length); -XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* statePtr); +/*! + * @brief Copies one @ref XXH64_state_t to another. + * + * @param dst_state The state to copy to. + * @param src_state The state to copy from. + * @pre + * @p dst_state and @p src_state must not be `NULL` and must not overlap. + */ +XXH_PUBLIC_API void XXH64_copyState(XXH_NOESCAPE XXH64_state_t* dst_state, const XXH64_state_t* src_state); +/*! + * @brief Resets an @ref XXH64_state_t to begin a new hash. + * + * @param statePtr The state struct to reset. + * @param seed The 64-bit seed to alter the hash result predictably. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note This function resets and seeds a state. Call it before @ref XXH64_update(). + */ +XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH_NOESCAPE XXH64_state_t* statePtr, XXH64_hash_t seed); + +/*! + * @brief Consumes a block of @p input to an @ref XXH64_state_t. + * + * @param statePtr The state struct to update. + * @param input The block of data to be hashed, at least @p length bytes in size. + * @param length The length of @p input, in bytes. + * + * @pre + * @p statePtr must not be `NULL`. + * @pre + * The memory between @p input and @p input + @p length must be valid, + * readable, contiguous memory. However, if @p length is `0`, @p input may be + * `NULL`. In C++, this also must be *TriviallyCopyable*. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note Call this to incrementally consume blocks of data. + */ +XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH_NOESCAPE XXH64_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length); + +/*! + * @brief Returns the calculated hash value from an @ref XXH64_state_t. + * + * @param statePtr The state struct to calculate the hash from. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return The calculated 64-bit xxHash64 value from that state. + * + * @note + * Calling XXH64_digest() will not affect @p statePtr, so you can update, + * digest, and update again. + */ +XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64_digest (XXH_NOESCAPE const XXH64_state_t* statePtr); +#endif /* !XXH_NO_STREAM */ /******* Canonical representation *******/ + +/*! + * @brief Canonical (big endian) representation of @ref XXH64_hash_t. + */ typedef struct { unsigned char digest[sizeof(XXH64_hash_t)]; } XXH64_canonical_t; -XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash); -XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src); + +/*! + * @brief Converts an @ref XXH64_hash_t to a big endian @ref XXH64_canonical_t. + * + * @param dst The @ref XXH64_canonical_t pointer to be stored to. + * @param hash The @ref XXH64_hash_t to be converted. + * + * @pre + * @p dst must not be `NULL`. + * + * @see @ref canonical_representation_example "Canonical Representation Example" + */ +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH_NOESCAPE XXH64_canonical_t* dst, XXH64_hash_t hash); + +/*! + * @brief Converts an @ref XXH64_canonical_t to a native @ref XXH64_hash_t. + * + * @param src The @ref XXH64_canonical_t to convert. + * + * @pre + * @p src must not be `NULL`. + * + * @return The converted hash. + * + * @see @ref canonical_representation_example "Canonical Representation Example" + */ +XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64_hashFromCanonical(XXH_NOESCAPE const XXH64_canonical_t* src); #ifndef XXH_NO_XXH3 + /*! * @} * ************************************************************************ - * @defgroup xxh3_family XXH3 family + * @defgroup XXH3_family XXH3 family * @ingroup public * @{ * @@ -741,16 +1043,26 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src * * XXH3's speed benefits greatly from SIMD and 64-bit arithmetic, * but does not require it. - * Any 32-bit and 64-bit targets that can run XXH32 smoothly - * can run XXH3 at competitive speeds, even without vector support. - * Further details are explained in the implementation. + * Most 32-bit and 64-bit targets that can run XXH32 smoothly can run XXH3 + * at competitive speeds, even without vector support. Further details are + * explained in the implementation. * - * Optimized implementations are provided for AVX512, AVX2, SSE2, NEON, POWER8, - * ZVector and scalar targets. This can be controlled via the XXH_VECTOR macro. + * XXH3 has a fast scalar implementation, but it also includes accelerated SIMD + * implementations for many common platforms: + * - AVX512 + * - AVX2 + * - SSE2 + * - ARM NEON + * - WebAssembly SIMD128 + * - POWER8 VSX + * - s390x ZVector + * This can be controlled via the @ref XXH_VECTOR macro, but it automatically + * selects the best version according to predefined macros. For the x86 family, an + * automatic runtime dispatcher is included separately in @ref xxh_x86dispatch.c. * * XXH3 implementation is portable: * it has a generic C90 formulation that can be compiled on any platform, - * all implementations generage exactly the same hash value on all platforms. + * all implementations generate exactly the same hash value on all platforms. * Starting from v0.8.0, it's also labelled "stable", meaning that * any future version will also generate the same hash value. * @@ -762,24 +1074,59 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src * * The API supports one-shot hashing, streaming mode, and custom secrets. */ - /*-********************************************************************** * XXH3 64-bit variant ************************************************************************/ -/* XXH3_64bits(): - * default 64-bit variant, using default secret and default seed of 0. - * It's the fastest variant. */ -XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(const void* data, size_t len); - -/* - * XXH3_64bits_withSeed(): - * This variant generates a custom secret on the fly - * based on default secret altered using the `seed` value. - * While this operation is decently fast, note that it's not completely free. - * Note: seed==0 produces the same results as XXH3_64bits(). +/*! + * @brief Calculates 64-bit unseeded variant of XXH3 hash of @p input. + * + * @param input The block of data to be hashed, at least @p length bytes in size. + * @param length The length of @p input, in bytes. + * + * @pre + * The memory between @p input and @p input + @p length must be valid, + * readable, contiguous memory. However, if @p length is `0`, @p input may be + * `NULL`. In C++, this also must be *TriviallyCopyable*. + * + * @return The calculated 64-bit XXH3 hash value. + * + * @note + * This is equivalent to @ref XXH3_64bits_withSeed() with a seed of `0`, however + * it may have slightly better performance due to constant propagation of the + * defaults. + * + * @see + * XXH3_64bits_withSeed(), XXH3_64bits_withSecret(): other seeding variants + * @see @ref single_shot_example "Single Shot Example" for an example. */ -XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed(const void* data, size_t len, XXH64_hash_t seed); +XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits(XXH_NOESCAPE const void* input, size_t length); + +/*! + * @brief Calculates 64-bit seeded variant of XXH3 hash of @p input. + * + * @param input The block of data to be hashed, at least @p length bytes in size. + * @param length The length of @p input, in bytes. + * @param seed The 64-bit seed to alter the hash result predictably. + * + * @pre + * The memory between @p input and @p input + @p length must be valid, + * readable, contiguous memory. However, if @p length is `0`, @p input may be + * `NULL`. In C++, this also must be *TriviallyCopyable*. + * + * @return The calculated 64-bit XXH3 hash value. + * + * @note + * seed == 0 produces the same results as @ref XXH3_64bits(). + * + * This variant generates a custom secret on the fly based on default secret + * altered using the @p seed value. + * + * While this operation is decently fast, note that it's not completely free. + * + * @see @ref single_shot_example "Single Shot Example" for an example. + */ +XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSeed(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed); /*! * The bare minimum size for a custom secret. @@ -790,27 +1137,43 @@ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed(const void* data, size_t len, X */ #define XXH3_SECRET_SIZE_MIN 136 -/* - * XXH3_64bits_withSecret(): +/*! + * @brief Calculates 64-bit variant of XXH3 with a custom "secret". + * + * @param data The block of data to be hashed, at least @p len bytes in size. + * @param len The length of @p data, in bytes. + * @param secret The secret data. + * @param secretSize The length of @p secret, in bytes. + * + * @return The calculated 64-bit XXH3 hash value. + * + * @pre + * The memory between @p data and @p data + @p len must be valid, + * readable, contiguous memory. However, if @p length is `0`, @p data may be + * `NULL`. In C++, this also must be *TriviallyCopyable*. + * * It's possible to provide any blob of bytes as a "secret" to generate the hash. * This makes it more difficult for an external actor to prepare an intentional collision. - * The main condition is that secretSize *must* be large enough (>= XXH3_SECRET_SIZE_MIN). + * The main condition is that @p secretSize *must* be large enough (>= @ref XXH3_SECRET_SIZE_MIN). * However, the quality of the secret impacts the dispersion of the hash algorithm. * Therefore, the secret _must_ look like a bunch of random bytes. * Avoid "trivial" or structured data such as repeated sequences or a text document. * Whenever in doubt about the "randomness" of the blob of bytes, - * consider employing "XXH3_generateSecret()" instead (see below). + * consider employing @ref XXH3_generateSecret() instead (see below). * It will generate a proper high entropy secret derived from the blob of bytes. * Another advantage of using XXH3_generateSecret() is that * it guarantees that all bits within the initial blob of bytes * will impact every bit of the output. * This is not necessarily the case when using the blob of bytes directly * because, when hashing _small_ inputs, only a portion of the secret is employed. + * + * @see @ref single_shot_example "Single Shot Example" for an example. */ -XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize); +XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSecret(XXH_NOESCAPE const void* data, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize); /******* Streaming *******/ +#ifndef XXH_NO_STREAM /* * Streaming requires state maintenance. * This operation costs memory and CPU. @@ -819,40 +1182,124 @@ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecret(const void* data, size_t len, */ /*! - * @brief The state struct for the XXH3 streaming API. + * @brief The opaque state struct for the XXH3 streaming API. * * @see XXH3_state_s for details. */ typedef struct XXH3_state_s XXH3_state_t; -XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void); +XXH_PUBLIC_API XXH_MALLOCF XXH3_state_t* XXH3_createState(void); XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr); -XXH_PUBLIC_API void XXH3_copyState(XXH3_state_t* dst_state, const XXH3_state_t* src_state); -/* - * XXH3_64bits_reset(): - * Initialize with default parameters. - * digest will be equivalent to `XXH3_64bits()`. +/*! + * @brief Copies one @ref XXH3_state_t to another. + * + * @param dst_state The state to copy to. + * @param src_state The state to copy from. + * @pre + * @p dst_state and @p src_state must not be `NULL` and must not overlap. */ -XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset(XXH3_state_t* statePtr); -/* - * XXH3_64bits_reset_withSeed(): - * Generate a custom secret from `seed`, and store it into `statePtr`. - * digest will be equivalent to `XXH3_64bits_withSeed()`. +XXH_PUBLIC_API void XXH3_copyState(XXH_NOESCAPE XXH3_state_t* dst_state, XXH_NOESCAPE const XXH3_state_t* src_state); + +/*! + * @brief Resets an @ref XXH3_state_t to begin a new hash. + * + * @param statePtr The state struct to reset. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note + * - This function resets `statePtr` and generate a secret with default parameters. + * - Call this function before @ref XXH3_64bits_update(). + * - Digest will be equivalent to `XXH3_64bits()`. + * */ -XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed); -/* - * XXH3_64bits_reset_withSecret(): - * `secret` is referenced, it _must outlive_ the hash streaming session. - * Similar to one-shot API, `secretSize` must be >= `XXH3_SECRET_SIZE_MIN`, +XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr); + +/*! + * @brief Resets an @ref XXH3_state_t with 64-bit seed to begin a new hash. + * + * @param statePtr The state struct to reset. + * @param seed The 64-bit seed to alter the hash result predictably. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note + * - This function resets `statePtr` and generate a secret from `seed`. + * - Call this function before @ref XXH3_64bits_update(). + * - Digest will be equivalent to `XXH3_64bits_withSeed()`. + * + */ +XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed); + +/*! + * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash. + * + * @param statePtr The state struct to reset. + * @param secret The secret data. + * @param secretSize The length of @p secret, in bytes. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note + * `secret` is referenced, it _must outlive_ the hash streaming session. + * + * Similar to one-shot API, `secretSize` must be >= @ref XXH3_SECRET_SIZE_MIN, * and the quality of produced hash values depends on secret's entropy * (secret's content should look like a bunch of random bytes). * When in doubt about the randomness of a candidate `secret`, * consider employing `XXH3_generateSecret()` instead (see below). */ -XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize); +XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize); -XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update (XXH3_state_t* statePtr, const void* input, size_t length); -XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* statePtr); +/*! + * @brief Consumes a block of @p input to an @ref XXH3_state_t. + * + * @param statePtr The state struct to update. + * @param input The block of data to be hashed, at least @p length bytes in size. + * @param length The length of @p input, in bytes. + * + * @pre + * @p statePtr must not be `NULL`. + * @pre + * The memory between @p input and @p input + @p length must be valid, + * readable, contiguous memory. However, if @p length is `0`, @p input may be + * `NULL`. In C++, this also must be *TriviallyCopyable*. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note Call this to incrementally consume blocks of data. + */ +XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update (XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length); + +/*! + * @brief Returns the calculated XXH3 64-bit hash value from an @ref XXH3_state_t. + * + * @param statePtr The state struct to calculate the hash from. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return The calculated XXH3 64-bit hash value from that state. + * + * @note + * Calling XXH3_64bits_digest() will not affect @p statePtr, so you can update, + * digest, and update again. + */ +XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_digest (XXH_NOESCAPE const XXH3_state_t* statePtr); +#endif /* !XXH_NO_STREAM */ /* note : canonical representation of XXH3 is the same as XXH64 * since they both produce XXH64_hash_t values */ @@ -873,11 +1320,76 @@ typedef struct { XXH64_hash_t high64; /*!< `value >> 64` */ } XXH128_hash_t; -XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(const void* data, size_t len); -XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSeed(const void* data, size_t len, XXH64_hash_t seed); -XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize); +/*! + * @brief Calculates 128-bit unseeded variant of XXH3 of @p data. + * + * @param data The block of data to be hashed, at least @p length bytes in size. + * @param len The length of @p data, in bytes. + * + * @return The calculated 128-bit variant of XXH3 value. + * + * The 128-bit variant of XXH3 has more strength, but it has a bit of overhead + * for shorter inputs. + * + * This is equivalent to @ref XXH3_128bits_withSeed() with a seed of `0`, however + * it may have slightly better performance due to constant propagation of the + * defaults. + * + * @see XXH3_128bits_withSeed(), XXH3_128bits_withSecret(): other seeding variants + * @see @ref single_shot_example "Single Shot Example" for an example. + */ +XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits(XXH_NOESCAPE const void* data, size_t len); +/*! @brief Calculates 128-bit seeded variant of XXH3 hash of @p data. + * + * @param data The block of data to be hashed, at least @p length bytes in size. + * @param len The length of @p data, in bytes. + * @param seed The 64-bit seed to alter the hash result predictably. + * + * @return The calculated 128-bit variant of XXH3 value. + * + * @note + * seed == 0 produces the same results as @ref XXH3_64bits(). + * + * This variant generates a custom secret on the fly based on default secret + * altered using the @p seed value. + * + * While this operation is decently fast, note that it's not completely free. + * + * @see XXH3_128bits(), XXH3_128bits_withSecret(): other seeding variants + * @see @ref single_shot_example "Single Shot Example" for an example. + */ +XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSeed(XXH_NOESCAPE const void* data, size_t len, XXH64_hash_t seed); +/*! + * @brief Calculates 128-bit variant of XXH3 with a custom "secret". + * + * @param data The block of data to be hashed, at least @p len bytes in size. + * @param len The length of @p data, in bytes. + * @param secret The secret data. + * @param secretSize The length of @p secret, in bytes. + * + * @return The calculated 128-bit variant of XXH3 value. + * + * It's possible to provide any blob of bytes as a "secret" to generate the hash. + * This makes it more difficult for an external actor to prepare an intentional collision. + * The main condition is that @p secretSize *must* be large enough (>= @ref XXH3_SECRET_SIZE_MIN). + * However, the quality of the secret impacts the dispersion of the hash algorithm. + * Therefore, the secret _must_ look like a bunch of random bytes. + * Avoid "trivial" or structured data such as repeated sequences or a text document. + * Whenever in doubt about the "randomness" of the blob of bytes, + * consider employing @ref XXH3_generateSecret() instead (see below). + * It will generate a proper high entropy secret derived from the blob of bytes. + * Another advantage of using XXH3_generateSecret() is that + * it guarantees that all bits within the initial blob of bytes + * will impact every bit of the output. + * This is not necessarily the case when using the blob of bytes directly + * because, when hashing _small_ inputs, only a portion of the secret is employed. + * + * @see @ref single_shot_example "Single Shot Example" for an example. + */ +XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSecret(XXH_NOESCAPE const void* data, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize); /******* Streaming *******/ +#ifndef XXH_NO_STREAM /* * Streaming requires state maintenance. * This operation costs memory and CPU. @@ -890,39 +1402,163 @@ XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecret(const void* data, size_t le * All reset and streaming functions have same meaning as their 64-bit counterpart. */ -XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset(XXH3_state_t* statePtr); -XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed); -XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize); +/*! + * @brief Resets an @ref XXH3_state_t to begin a new hash. + * + * @param statePtr The state struct to reset. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note + * - This function resets `statePtr` and generate a secret with default parameters. + * - Call it before @ref XXH3_128bits_update(). + * - Digest will be equivalent to `XXH3_128bits()`. + */ +XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr); -XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update (XXH3_state_t* statePtr, const void* input, size_t length); -XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* statePtr); +/*! + * @brief Resets an @ref XXH3_state_t with 64-bit seed to begin a new hash. + * + * @param statePtr The state struct to reset. + * @param seed The 64-bit seed to alter the hash result predictably. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note + * - This function resets `statePtr` and generate a secret from `seed`. + * - Call it before @ref XXH3_128bits_update(). + * - Digest will be equivalent to `XXH3_128bits_withSeed()`. + */ +XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed); +/*! + * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash. + * + * @param statePtr The state struct to reset. + * @param secret The secret data. + * @param secretSize The length of @p secret, in bytes. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * `secret` is referenced, it _must outlive_ the hash streaming session. + * Similar to one-shot API, `secretSize` must be >= @ref XXH3_SECRET_SIZE_MIN, + * and the quality of produced hash values depends on secret's entropy + * (secret's content should look like a bunch of random bytes). + * When in doubt about the randomness of a candidate `secret`, + * consider employing `XXH3_generateSecret()` instead (see below). + */ +XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize); + +/*! + * @brief Consumes a block of @p input to an @ref XXH3_state_t. + * + * Call this to incrementally consume blocks of data. + * + * @param statePtr The state struct to update. + * @param input The block of data to be hashed, at least @p length bytes in size. + * @param length The length of @p input, in bytes. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @note + * The memory between @p input and @p input + @p length must be valid, + * readable, contiguous memory. However, if @p length is `0`, @p input may be + * `NULL`. In C++, this also must be *TriviallyCopyable*. + * + */ +XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update (XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length); + +/*! + * @brief Returns the calculated XXH3 128-bit hash value from an @ref XXH3_state_t. + * + * @param statePtr The state struct to calculate the hash from. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return The calculated XXH3 128-bit hash value from that state. + * + * @note + * Calling XXH3_128bits_digest() will not affect @p statePtr, so you can update, + * digest, and update again. + * + */ +XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_digest (XXH_NOESCAPE const XXH3_state_t* statePtr); +#endif /* !XXH_NO_STREAM */ /* Following helper functions make it possible to compare XXH128_hast_t values. * Since XXH128_hash_t is a structure, this capability is not offered by the language. * Note: For better performance, these functions can be inlined using XXH_INLINE_ALL */ /*! - * XXH128_isEqual(): - * Return: 1 if `h1` and `h2` are equal, 0 if they are not. + * @brief Check equality of two XXH128_hash_t values + * + * @param h1 The 128-bit hash value. + * @param h2 Another 128-bit hash value. + * + * @return `1` if `h1` and `h2` are equal. + * @return `0` if they are not. */ -XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2); +XXH_PUBLIC_API XXH_PUREF int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2); /*! - * XXH128_cmp(): + * @brief Compares two @ref XXH128_hash_t * * This comparator is compatible with stdlib's `qsort()`/`bsearch()`. * - * return: >0 if *h128_1 > *h128_2 - * =0 if *h128_1 == *h128_2 - * <0 if *h128_1 < *h128_2 + * @param h128_1 Left-hand side value + * @param h128_2 Right-hand side value + * + * @return >0 if @p h128_1 > @p h128_2 + * @return =0 if @p h128_1 == @p h128_2 + * @return <0 if @p h128_1 < @p h128_2 */ -XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2); +XXH_PUBLIC_API XXH_PUREF int XXH128_cmp(XXH_NOESCAPE const void* h128_1, XXH_NOESCAPE const void* h128_2); /******* Canonical representation *******/ typedef struct { unsigned char digest[sizeof(XXH128_hash_t)]; } XXH128_canonical_t; -XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash); -XXH_PUBLIC_API XXH128_hash_t XXH128_hashFromCanonical(const XXH128_canonical_t* src); + + +/*! + * @brief Converts an @ref XXH128_hash_t to a big endian @ref XXH128_canonical_t. + * + * @param dst The @ref XXH128_canonical_t pointer to be stored to. + * @param hash The @ref XXH128_hash_t to be converted. + * + * @pre + * @p dst must not be `NULL`. + * @see @ref canonical_representation_example "Canonical Representation Example" + */ +XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH_NOESCAPE XXH128_canonical_t* dst, XXH128_hash_t hash); + +/*! + * @brief Converts an @ref XXH128_canonical_t to a native @ref XXH128_hash_t. + * + * @param src The @ref XXH128_canonical_t to convert. + * + * @pre + * @p src must not be `NULL`. + * + * @return The converted hash. + * @see @ref canonical_representation_example "Canonical Representation Example" + */ +XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH128_hashFromCanonical(XXH_NOESCAPE const XXH128_canonical_t* src); #endif /* !XXH_NO_XXH3 */ @@ -996,7 +1632,6 @@ struct XXH64_state_s { XXH64_hash_t reserved64; /*!< Reserved field. Do not read or write to it. */ }; /* typedef'd to XXH64_state_t */ - #ifndef XXH_NO_XXH3 #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* >= C11 */ @@ -1032,6 +1667,7 @@ struct XXH64_state_s { #define XXH3_INTERNALBUFFER_SIZE 256 /*! + * @internal * @brief Default size of the secret buffer (and @ref XXH3_kSecret). * * This is the size used in @ref XXH3_kSecret and the seeded functions. @@ -1064,7 +1700,7 @@ struct XXH64_state_s { */ struct XXH3_state_s { XXH_ALIGN_MEMBER(64, XXH64_hash_t acc[8]); - /*!< The 8 accumulators. Similar to `vN` in @ref XXH32_state_s::v1 and @ref XXH64_state_s */ + /*!< The 8 accumulators. See @ref XXH32_state_s::v and @ref XXH64_state_s::v */ XXH_ALIGN_MEMBER(64, unsigned char customSecret[XXH3_SECRET_DEFAULT_SIZE]); /*!< Used to store a custom secret generated from a seed. */ XXH_ALIGN_MEMBER(64, unsigned char buffer[XXH3_INTERNALBUFFER_SIZE]); @@ -1104,69 +1740,148 @@ struct XXH3_state_s { * Note that this doesn't prepare the state for a streaming operation, * it's still necessary to use XXH3_NNbits_reset*() afterwards. */ -#define XXH3_INITSTATE(XXH3_state_ptr) { (XXH3_state_ptr)->seed = 0; } +#define XXH3_INITSTATE(XXH3_state_ptr) \ + do { \ + XXH3_state_t* tmp_xxh3_state_ptr = (XXH3_state_ptr); \ + tmp_xxh3_state_ptr->seed = 0; \ + tmp_xxh3_state_ptr->extSecret = NULL; \ + } while(0) -/* XXH128() : - * simple alias to pre-selected XXH3_128bits variant +/*! + * @brief Calculates the 128-bit hash of @p data using XXH3. + * + * @param data The block of data to be hashed, at least @p len bytes in size. + * @param len The length of @p data, in bytes. + * @param seed The 64-bit seed to alter the hash's output predictably. + * + * @pre + * The memory between @p data and @p data + @p len must be valid, + * readable, contiguous memory. However, if @p len is `0`, @p data may be + * `NULL`. In C++, this also must be *TriviallyCopyable*. + * + * @return The calculated 128-bit XXH3 value. + * + * @see @ref single_shot_example "Single Shot Example" for an example. */ -XXH_PUBLIC_API XXH128_hash_t XXH128(const void* data, size_t len, XXH64_hash_t seed); +XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH128(XXH_NOESCAPE const void* data, size_t len, XXH64_hash_t seed); /* === Experimental API === */ /* Symbols defined below must be considered tied to a specific library version. */ -/* - * XXH3_generateSecret(): +/*! + * @brief Derive a high-entropy secret from any user-defined content, named customSeed. + * + * @param secretBuffer A writable buffer for derived high-entropy secret data. + * @param secretSize Size of secretBuffer, in bytes. Must be >= XXH3_SECRET_DEFAULT_SIZE. + * @param customSeed A user-defined content. + * @param customSeedSize Size of customSeed, in bytes. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. * - * Derive a high-entropy secret from any user-defined content, named customSeed. * The generated secret can be used in combination with `*_withSecret()` functions. - * The `_withSecret()` variants are useful to provide a higher level of protection than 64-bit seed, - * as it becomes much more difficult for an external actor to guess how to impact the calculation logic. + * The `_withSecret()` variants are useful to provide a higher level of protection + * than 64-bit seed, as it becomes much more difficult for an external actor to + * guess how to impact the calculation logic. * * The function accepts as input a custom seed of any length and any content, - * and derives from it a high-entropy secret of length @secretSize - * into an already allocated buffer @secretBuffer. - * @secretSize must be >= XXH3_SECRET_SIZE_MIN + * and derives from it a high-entropy secret of length @p secretSize into an + * already allocated buffer @p secretBuffer. * * The generated secret can then be used with any `*_withSecret()` variant. - * Functions `XXH3_128bits_withSecret()`, `XXH3_64bits_withSecret()`, - * `XXH3_128bits_reset_withSecret()` and `XXH3_64bits_reset_withSecret()` + * The functions @ref XXH3_128bits_withSecret(), @ref XXH3_64bits_withSecret(), + * @ref XXH3_128bits_reset_withSecret() and @ref XXH3_64bits_reset_withSecret() * are part of this list. They all accept a `secret` parameter - * which must be large enough for implementation reasons (>= XXH3_SECRET_SIZE_MIN) + * which must be large enough for implementation reasons (>= @ref XXH3_SECRET_SIZE_MIN) * _and_ feature very high entropy (consist of random-looking bytes). - * These conditions can be a high bar to meet, so - * XXH3_generateSecret() can be employed to ensure proper quality. + * These conditions can be a high bar to meet, so @ref XXH3_generateSecret() can + * be employed to ensure proper quality. * - * customSeed can be anything. It can have any size, even small ones, - * and its content can be anything, even "poor entropy" sources such as a bunch of zeroes. - * The resulting `secret` will nonetheless provide all required qualities. + * @p customSeed can be anything. It can have any size, even small ones, + * and its content can be anything, even "poor entropy" sources such as a bunch + * of zeroes. The resulting `secret` will nonetheless provide all required qualities. * - * When customSeedSize > 0, supplying NULL as customSeed is undefined behavior. + * @pre + * - @p secretSize must be >= @ref XXH3_SECRET_SIZE_MIN + * - When @p customSeedSize > 0, supplying NULL as customSeed is undefined behavior. + * + * Example code: + * @code{.c} + * #include + * #include + * #include + * #define XXH_STATIC_LINKING_ONLY // expose unstable API + * #include "xxhash.h" + * // Hashes argv[2] using the entropy from argv[1]. + * int main(int argc, char* argv[]) + * { + * char secret[XXH3_SECRET_SIZE_MIN]; + * if (argv != 3) { return 1; } + * XXH3_generateSecret(secret, sizeof(secret), argv[1], strlen(argv[1])); + * XXH64_hash_t h = XXH3_64bits_withSecret( + * argv[2], strlen(argv[2]), + * secret, sizeof(secret) + * ); + * printf("%016llx\n", (unsigned long long) h); + * } + * @endcode */ -XXH_PUBLIC_API XXH_errorcode XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSeed, size_t customSeedSize); +XXH_PUBLIC_API XXH_errorcode XXH3_generateSecret(XXH_NOESCAPE void* secretBuffer, size_t secretSize, XXH_NOESCAPE const void* customSeed, size_t customSeedSize); - -/* - * XXH3_generateSecret_fromSeed(): +/*! + * @brief Generate the same secret as the _withSeed() variants. * - * Generate the same secret as the _withSeed() variants. - * - * The resulting secret has a length of XXH3_SECRET_DEFAULT_SIZE (necessarily). - * @secretBuffer must be already allocated, of size at least XXH3_SECRET_DEFAULT_SIZE bytes. + * @param secretBuffer A writable buffer of @ref XXH3_SECRET_SIZE_MIN bytes + * @param seed The 64-bit seed to alter the hash result predictably. * * The generated secret can be used in combination with *`*_withSecret()` and `_withSecretandSeed()` variants. - * This generator is notably useful in combination with `_withSecretandSeed()`, - * as a way to emulate a faster `_withSeed()` variant. + * + * Example C++ `std::string` hash class: + * @code{.cpp} + * #include + * #define XXH_STATIC_LINKING_ONLY // expose unstable API + * #include "xxhash.h" + * // Slow, seeds each time + * class HashSlow { + * XXH64_hash_t seed; + * public: + * HashSlow(XXH64_hash_t s) : seed{s} {} + * size_t operator()(const std::string& x) const { + * return size_t{XXH3_64bits_withSeed(x.c_str(), x.length(), seed)}; + * } + * }; + * // Fast, caches the seeded secret for future uses. + * class HashFast { + * unsigned char secret[XXH3_SECRET_SIZE_MIN]; + * public: + * HashFast(XXH64_hash_t s) { + * XXH3_generateSecret_fromSeed(secret, seed); + * } + * size_t operator()(const std::string& x) const { + * return size_t{ + * XXH3_64bits_withSecret(x.c_str(), x.length(), secret, sizeof(secret)) + * }; + * } + * }; + * @endcode */ -XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed); +XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(XXH_NOESCAPE void* secretBuffer, XXH64_hash_t seed); -/* - * *_withSecretandSeed() : +/*! + * @brief Calculates 64/128-bit seeded variant of XXH3 hash of @p data. + * + * @param data The block of data to be hashed, at least @p len bytes in size. + * @param len The length of @p data, in bytes. + * @param secret The secret data. + * @param secretSize The length of @p secret, in bytes. + * @param seed The 64-bit seed to alter the hash result predictably. + * * These variants generate hash values using either - * @seed for "short" keys (< XXH3_MIDSIZE_MAX = 240 bytes) - * or @secret for "large" keys (>= XXH3_MIDSIZE_MAX). + * @p seed for "short" keys (< @ref XXH3_MIDSIZE_MAX = 240 bytes) + * or @p secret for "large" keys (>= @ref XXH3_MIDSIZE_MAX). * * This generally benefits speed, compared to `_withSeed()` or `_withSecret()`. * `_withSeed()` has to generate the secret on the fly for "large" keys. @@ -1175,7 +1890,7 @@ XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_ * which requires more instructions than _withSeed() variants. * Therefore, _withSecretandSeed variant combines the best of both worlds. * - * When @secret has been generated by XXH3_generateSecret_fromSeed(), + * When @p secret has been generated by XXH3_generateSecret_fromSeed(), * this variant produces *exactly* the same results as `_withSeed()` variant, * hence offering only a pure speed benefit on "large" input, * by skipping the need to regenerate the secret for every large input. @@ -1184,33 +1899,71 @@ XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_ * for example with XXH3_64bits(), which then becomes the seed, * and then employ both the seed and the secret in _withSecretandSeed(). * On top of speed, an added benefit is that each bit in the secret - * has a 50% chance to swap each bit in the output, - * via its impact to the seed. + * has a 50% chance to swap each bit in the output, via its impact to the seed. + * * This is not guaranteed when using the secret directly in "small data" scenarios, * because only portions of the secret are employed for small data. */ -XXH_PUBLIC_API XXH64_hash_t -XXH3_64bits_withSecretandSeed(const void* data, size_t len, - const void* secret, size_t secretSize, +XXH_PUBLIC_API XXH_PUREF XXH64_hash_t +XXH3_64bits_withSecretandSeed(XXH_NOESCAPE const void* data, size_t len, + XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed); - -XXH_PUBLIC_API XXH128_hash_t -XXH3_128bits_withSecretandSeed(const void* data, size_t len, - const void* secret, size_t secretSize, +/*! + * @brief Calculates 128-bit seeded variant of XXH3 hash of @p data. + * + * @param input The block of data to be hashed, at least @p len bytes in size. + * @param length The length of @p data, in bytes. + * @param secret The secret data. + * @param secretSize The length of @p secret, in bytes. + * @param seed64 The 64-bit seed to alter the hash result predictably. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @see XXH3_64bits_withSecretandSeed() + */ +XXH_PUBLIC_API XXH_PUREF XXH128_hash_t +XXH3_128bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t length, + XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed64); - +#ifndef XXH_NO_STREAM +/*! + * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash. + * + * @param statePtr A pointer to an @ref XXH3_state_t allocated with @ref XXH3_createState(). + * @param secret The secret data. + * @param secretSize The length of @p secret, in bytes. + * @param seed64 The 64-bit seed to alter the hash result predictably. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @see XXH3_64bits_withSecretandSeed() + */ XXH_PUBLIC_API XXH_errorcode -XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr, - const void* secret, size_t secretSize, +XXH3_64bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, + XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed64); - +/*! + * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash. + * + * @param statePtr A pointer to an @ref XXH3_state_t allocated with @ref XXH3_createState(). + * @param secret The secret data. + * @param secretSize The length of @p secret, in bytes. + * @param seed64 The 64-bit seed to alter the hash result predictably. + * + * @return @ref XXH_OK on success. + * @return @ref XXH_ERROR on failure. + * + * @see XXH3_64bits_withSecretandSeed() + */ XXH_PUBLIC_API XXH_errorcode -XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, - const void* secret, size_t secretSize, +XXH3_128bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, + XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed64); +#endif /* !XXH_NO_STREAM */ - -#endif /* XXH_NO_XXH3 */ +#endif /* !XXH_NO_XXH3 */ #endif /* XXH_NO_LONG_LONG */ #if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) # define XXH_IMPLEMENTATION @@ -1264,7 +2017,7 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, /*! * @brief Define this to disable 64-bit code. * - * Useful if only using the @ref xxh32_family and you have a strict C90 compiler. + * Useful if only using the @ref XXH32_family and you have a strict C90 compiler. */ # define XXH_NO_LONG_LONG # undef XXH_NO_LONG_LONG /* don't actually */ @@ -1287,7 +2040,7 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, * Use `memcpy()`. Safe and portable. Note that most modern compilers will * eliminate the function call and treat it as an unaligned access. * - * - `XXH_FORCE_MEMORY_ACCESS=1`: `__attribute__((packed))` + * - `XXH_FORCE_MEMORY_ACCESS=1`: `__attribute__((aligned(1)))` * @par * Depends on compiler extensions and is therefore not portable. * This method is safe _if_ your compiler supports it, @@ -1307,7 +2060,7 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, * inline small `memcpy()` calls, and it might also be faster on big-endian * systems which lack a native byteswap instruction. However, some compilers * will emit literal byteshifts even if the target supports unaligned access. - * . + * * * @warning * Methods 1 and 2 rely on implementation-defined behavior. Use these with @@ -1320,6 +2073,34 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, */ # define XXH_FORCE_MEMORY_ACCESS 0 +/*! + * @def XXH_SIZE_OPT + * @brief Controls how much xxHash optimizes for size. + * + * xxHash, when compiled, tends to result in a rather large binary size. This + * is mostly due to heavy usage to forced inlining and constant folding of the + * @ref XXH3_family to increase performance. + * + * However, some developers prefer size over speed. This option can + * significantly reduce the size of the generated code. When using the `-Os` + * or `-Oz` options on GCC or Clang, this is defined to 1 by default, + * otherwise it is defined to 0. + * + * Most of these size optimizations can be controlled manually. + * + * This is a number from 0-2. + * - `XXH_SIZE_OPT` == 0: Default. xxHash makes no size optimizations. Speed + * comes first. + * - `XXH_SIZE_OPT` == 1: Default for `-Os` and `-Oz`. xxHash is more + * conservative and disables hacks that increase code size. It implies the + * options @ref XXH_NO_INLINE_HINTS == 1, @ref XXH_FORCE_ALIGN_CHECK == 0, + * and @ref XXH3_NEON_LANES == 8 if they are not already defined. + * - `XXH_SIZE_OPT` == 2: xxHash tries to make itself as small as possible. + * Performance may cry. For example, the single shot functions just use the + * streaming API. + */ +# define XXH_SIZE_OPT 0 + /*! * @def XXH_FORCE_ALIGN_CHECK * @brief If defined to non-zero, adds a special path for aligned inputs (XXH32() @@ -1341,9 +2122,11 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, * * In these cases, the alignment check can be removed by setting this macro to 0. * Then the code will always use unaligned memory access. - * Align check is automatically disabled on x86, x64 & arm64, + * Align check is automatically disabled on x86, x64, ARM64, and some ARM chips * which are platforms known to offer good unaligned memory accesses performance. * + * It is also disabled by default when @ref XXH_SIZE_OPT >= 1. + * * This option does not affect XXH3 (only XXH32 and XXH64). */ # define XXH_FORCE_ALIGN_CHECK 0 @@ -1365,11 +2148,28 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, * XXH_NO_INLINE_HINTS marks all internal functions as static, giving the * compiler full control on whether to inline or not. * - * When not optimizing (-O0), optimizing for size (-Os, -Oz), or using - * -fno-inline with GCC or Clang, this will automatically be defined. + * When not optimizing (-O0), using `-fno-inline` with GCC or Clang, or if + * @ref XXH_SIZE_OPT >= 1, this will automatically be defined. */ # define XXH_NO_INLINE_HINTS 0 +/*! + * @def XXH3_INLINE_SECRET + * @brief Determines whether to inline the XXH3 withSecret code. + * + * When the secret size is known, the compiler can improve the performance + * of XXH3_64bits_withSecret() and XXH3_128bits_withSecret(). + * + * However, if the secret size is not known, it doesn't have any benefit. This + * happens when xxHash is compiled into a global symbol. Therefore, if + * @ref XXH_INLINE_ALL is *not* defined, this will be defined to 0. + * + * Additionally, this defaults to 0 on GCC 12+, which has an issue with function pointers + * that are *sometimes* force inline on -Og, and it is impossible to automatically + * detect this optimization level. + */ +# define XXH3_INLINE_SECRET 0 + /*! * @def XXH32_ENDJMP * @brief Whether to use a jump for `XXH32_finalize`. @@ -1391,34 +2191,45 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, */ # define XXH_OLD_NAMES # undef XXH_OLD_NAMES /* don't actually use, it is ugly. */ + +/*! + * @def XXH_NO_STREAM + * @brief Disables the streaming API. + * + * When xxHash is not inlined and the streaming functions are not used, disabling + * the streaming functions can improve code size significantly, especially with + * the @ref XXH3_family which tends to make constant folded copies of itself. + */ +# define XXH_NO_STREAM +# undef XXH_NO_STREAM /* don't actually */ #endif /* XXH_DOXYGEN */ /*! * @} */ #ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ - /* prefer __packed__ structures (method 1) for gcc on armv7+ and mips */ -# if !defined(__clang__) && \ -( \ - (defined(__INTEL_COMPILER) && !defined(_WIN32)) || \ - ( \ - defined(__GNUC__) && ( \ - (defined(__ARM_ARCH) && __ARM_ARCH >= 7) || \ - ( \ - defined(__mips__) && \ - (__mips <= 5 || __mips_isa_rev < 6) && \ - (!defined(__mips16) || defined(__mips_mips16e2)) \ - ) \ - ) \ - ) \ -) + /* prefer __packed__ structures (method 1) for GCC + * < ARMv7 with unaligned access (e.g. Raspbian armhf) still uses byte shifting, so we use memcpy + * which for some reason does unaligned loads. */ +# if defined(__GNUC__) && !(defined(__ARM_ARCH) && __ARM_ARCH < 7 && defined(__ARM_FEATURE_UNALIGNED)) # define XXH_FORCE_MEMORY_ACCESS 1 # endif #endif +#ifndef XXH_SIZE_OPT + /* default to 1 for -Os or -Oz */ +# if (defined(__GNUC__) || defined(__clang__)) && defined(__OPTIMIZE_SIZE__) +# define XXH_SIZE_OPT 1 +# else +# define XXH_SIZE_OPT 0 +# endif +#endif + #ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ -# if defined(__i386) || defined(__x86_64__) || defined(__aarch64__) \ - || defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64) /* visual */ + /* don't check on sizeopt, x86, aarch64, or arm when unaligned access is available */ +# if XXH_SIZE_OPT >= 1 || \ + defined(__i386) || defined(__x86_64__) || defined(__aarch64__) || defined(__ARM_FEATURE_UNALIGNED) \ + || defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64) || defined(_M_ARM) /* visual */ # define XXH_FORCE_ALIGN_CHECK 0 # else # define XXH_FORCE_ALIGN_CHECK 1 @@ -1426,14 +2237,22 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, #endif #ifndef XXH_NO_INLINE_HINTS -# if defined(__OPTIMIZE_SIZE__) /* -Os, -Oz */ \ - || defined(__NO_INLINE__) /* -O0, -fno-inline */ +# if XXH_SIZE_OPT >= 1 || defined(__NO_INLINE__) /* -O0, -fno-inline */ # define XXH_NO_INLINE_HINTS 1 # else # define XXH_NO_INLINE_HINTS 0 # endif #endif +#ifndef XXH3_INLINE_SECRET +# if (defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 12) \ + || !defined(XXH_INLINE_ALL) +# define XXH3_INLINE_SECRET 0 +# else +# define XXH3_INLINE_SECRET 1 +# endif +#endif + #ifndef XXH32_ENDJMP /* generally preferable for performance */ # define XXH32_ENDJMP 0 @@ -1448,13 +2267,56 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, /* ************************************* * Includes & Memory related functions ***************************************/ -/* Modify the local functions below should you wish to use some other memory routines */ -/* for ZSTD_malloc(), ZSTD_free() */ -#define ZSTD_DEPS_NEED_MALLOC -#include "zstd_deps.h" /* size_t, ZSTD_malloc, ZSTD_free, ZSTD_memcpy */ -static void* XXH_malloc(size_t s) { return ZSTD_malloc(s); } -static void XXH_free (void* p) { ZSTD_free(p); } -static void* XXH_memcpy(void* dest, const void* src, size_t size) { return ZSTD_memcpy(dest,src,size); } +#if defined(XXH_NO_STREAM) +/* nothing */ +#elif defined(XXH_NO_STDLIB) + +/* When requesting to disable any mention of stdlib, + * the library loses the ability to invoked malloc / free. + * In practice, it means that functions like `XXH*_createState()` + * will always fail, and return NULL. + * This flag is useful in situations where + * xxhash.h is integrated into some kernel, embedded or limited environment + * without access to dynamic allocation. + */ + +static XXH_CONSTF void* XXH_malloc(size_t s) { (void)s; return NULL; } +static void XXH_free(void* p) { (void)p; } + +#else + +/* + * Modify the local functions below should you wish to use + * different memory routines for malloc() and free() + */ +#include + +/*! + * @internal + * @brief Modify this function to use a different routine than malloc(). + */ +static XXH_MALLOCF void* XXH_malloc(size_t s) { return malloc(s); } + +/*! + * @internal + * @brief Modify this function to use a different routine than free(). + */ +static void XXH_free(void* p) { free(p); } + +#endif /* XXH_NO_STDLIB */ + +#include + +/*! + * @internal + * @brief Modify this function to use a different routine than memcpy(). + */ +static void* XXH_memcpy(void* dest, const void* src, size_t size) +{ + return memcpy(dest,src,size); +} + +#include /* ULLONG_MAX */ /* ************************************* @@ -1487,6 +2349,11 @@ static void* XXH_memcpy(void* dest, const void* src, size_t size) { return ZSTD_ # define XXH_NO_INLINE static #endif +#if XXH3_INLINE_SECRET +# define XXH3_WITH_SECRET_INLINE XXH_FORCE_INLINE +#else +# define XXH3_WITH_SECRET_INLINE XXH_NO_INLINE +#endif /* ************************************* @@ -1512,14 +2379,17 @@ static void* XXH_memcpy(void* dest, const void* src, size_t size) { return ZSTD_ # include /* note: can still be disabled with NDEBUG */ # define XXH_ASSERT(c) assert(c) #else -# define XXH_ASSERT(c) ((void)0) +# if defined(__INTEL_COMPILER) +# define XXH_ASSERT(c) XXH_ASSUME((unsigned char) (c)) +# else +# define XXH_ASSERT(c) XXH_ASSUME(c) +# endif #endif /* note: use after variable declarations */ #ifndef XXH_STATIC_ASSERT # if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */ -# include -# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0) +# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { _Static_assert((c),m); } while(0) # elif defined(__cplusplus) && (__cplusplus >= 201103L) /* C++11 */ # define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0) # else @@ -1534,7 +2404,7 @@ static void* XXH_memcpy(void* dest, const void* src, size_t size) { return ZSTD_ * @brief Used to prevent unwanted optimizations for @p var. * * It uses an empty GCC inline assembly statement with a register constraint - * which forces @p var into a general purpose register (e.g. eax, ebx, ecx + * which forces @p var into a general purpose register (eg eax, ebx, ecx * on x86) and marks it as modified. * * This is used in a few places to avoid unwanted autovectorization (e.g. @@ -1545,18 +2415,30 @@ static void* XXH_memcpy(void* dest, const void* src, size_t size) { return ZSTD_ * XXH3_initCustomSecret_scalar(). */ #if defined(__GNUC__) || defined(__clang__) -# define XXH_COMPILER_GUARD(var) __asm__ __volatile__("" : "+r" (var)) +# define XXH_COMPILER_GUARD(var) __asm__("" : "+r" (var)) #else # define XXH_COMPILER_GUARD(var) ((void)0) #endif +/* Specifically for NEON vectors which use the "w" constraint, on + * Clang. */ +#if defined(__clang__) && defined(__ARM_ARCH) && !defined(__wasm__) +# define XXH_COMPILER_GUARD_CLANG_NEON(var) __asm__("" : "+w" (var)) +#else +# define XXH_COMPILER_GUARD_CLANG_NEON(var) ((void)0) +#endif + /* ************************************* * Basic Types ***************************************/ #if !defined (__VMS) \ && (defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) -# include +# ifdef _AIX +# include +# else +# include +# endif typedef uint8_t xxh_u8; #else typedef unsigned char xxh_u8; @@ -1564,6 +2446,7 @@ static void* XXH_memcpy(void* dest, const void* src, size_t size) { return ZSTD_ typedef XXH32_hash_t xxh_u32; #ifdef XXH_OLD_NAMES +# warning "XXH_OLD_NAMES is planned to be removed starting v0.9. If the program depends on it, consider moving away from it by employing newer type names directly" # define BYTE xxh_u8 # define U8 xxh_u8 # define U32 xxh_u32 @@ -1637,18 +2520,19 @@ static xxh_u32 XXH_read32(const void* memPtr) { return *(const xxh_u32*) memPtr; #elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) /* - * __pack instructions are safer but compiler specific, hence potentially - * problematic for some compilers. - * - * Currently only defined for GCC and ICC. + * __attribute__((aligned(1))) is supported by gcc and clang. Originally the + * documentation claimed that it only increased the alignment, but actually it + * can decrease it on gcc, clang, and icc: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69502, + * https://gcc.godbolt.org/z/xYez1j67Y. */ #ifdef XXH_OLD_NAMES typedef union { xxh_u32 u32; } __attribute__((packed)) unalign; #endif static xxh_u32 XXH_read32(const void* ptr) { - typedef union { xxh_u32 u32; } __attribute__((packed)) xxh_unalign; - return ((const xxh_unalign*)ptr)->u32; + typedef __attribute__((aligned(1))) xxh_u32 xxh_unalign32; + return *((const xxh_unalign32*)ptr); } #else @@ -1731,6 +2615,51 @@ static int XXH_isLittleEndian(void) # define XXH_HAS_BUILTIN(x) 0 #endif + + +/* + * C23 and future versions have standard "unreachable()". + * Once it has been implemented reliably we can add it as an + * additional case: + * + * ``` + * #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= XXH_C23_VN) + * # include + * # ifdef unreachable + * # define XXH_UNREACHABLE() unreachable() + * # endif + * #endif + * ``` + * + * Note C++23 also has std::unreachable() which can be detected + * as follows: + * ``` + * #if defined(__cpp_lib_unreachable) && (__cpp_lib_unreachable >= 202202L) + * # include + * # define XXH_UNREACHABLE() std::unreachable() + * #endif + * ``` + * NB: `__cpp_lib_unreachable` is defined in the `` header. + * We don't use that as including `` in `extern "C"` blocks + * doesn't work on GCC12 + */ + +#if XXH_HAS_BUILTIN(__builtin_unreachable) +# define XXH_UNREACHABLE() __builtin_unreachable() + +#elif defined(_MSC_VER) +# define XXH_UNREACHABLE() __assume(0) + +#else +# define XXH_UNREACHABLE() +#endif + +#if XXH_HAS_BUILTIN(__builtin_assume) +# define XXH_ASSUME(c) __builtin_assume(c) +#else +# define XXH_ASSUME(c) if (!(c)) { XXH_UNREACHABLE(); } +#endif + /*! * @internal * @def XXH_rotl32(x,r) @@ -1853,8 +2782,10 @@ XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; } *********************************************************************/ /*! * @} - * @defgroup xxh32_impl XXH32 implementation + * @defgroup XXH32_impl XXH32 implementation * @ingroup impl + * + * Details on the XXH32 implementation. * @{ */ /* #define instead of static const, to be used as initializers */ @@ -1888,7 +2819,7 @@ static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input) acc += input * XXH_PRIME32_2; acc = XXH_rotl32(acc, 13); acc *= XXH_PRIME32_1; -#if (defined(__SSE4_1__) || defined(__aarch64__)) && !defined(XXH_ENABLE_AUTOVECTORIZE) +#if (defined(__SSE4_1__) || defined(__aarch64__) || defined(__wasm_simd128__)) && !defined(XXH_ENABLE_AUTOVECTORIZE) /* * UGLY HACK: * A compiler fence is the only thing that prevents GCC and Clang from @@ -1918,9 +2849,12 @@ static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input) * can load data, while v3 can multiply. SSE forces them to operate * together. * - * This is also enabled on AArch64, as Clang autovectorizes it incorrectly - * and it is pointless writing a NEON implementation that is basically the - * same speed as scalar for XXH32. + * This is also enabled on AArch64, as Clang is *very aggressive* in vectorizing + * the loop. NEON is only faster on the A53, and with the newer cores, it is less + * than half the speed. + * + * Additionally, this is used on WASM SIMD128 because it JITs to the same + * SIMD instructions and has the same issue. */ XXH_COMPILER_GUARD(acc); #endif @@ -1934,17 +2868,17 @@ static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input) * The final mix ensures that all input bits have a chance to impact any bit in * the output digest, resulting in an unbiased distribution. * - * @param h32 The hash to avalanche. + * @param hash The hash to avalanche. * @return The avalanched hash. */ -static xxh_u32 XXH32_avalanche(xxh_u32 h32) +static xxh_u32 XXH32_avalanche(xxh_u32 hash) { - h32 ^= h32 >> 15; - h32 *= XXH_PRIME32_2; - h32 ^= h32 >> 13; - h32 *= XXH_PRIME32_3; - h32 ^= h32 >> 16; - return(h32); + hash ^= hash >> 15; + hash *= XXH_PRIME32_2; + hash ^= hash >> 13; + hash *= XXH_PRIME32_3; + hash ^= hash >> 16; + return hash; } #define XXH_get32bits(p) XXH_readLE32_align(p, align) @@ -1957,24 +2891,25 @@ static xxh_u32 XXH32_avalanche(xxh_u32 h32) * This final stage will digest them to ensure that all input bytes are present * in the final mix. * - * @param h32 The hash to finalize. + * @param hash The hash to finalize. * @param ptr The pointer to the remaining input. * @param len The remaining length, modulo 16. * @param align Whether @p ptr is aligned. * @return The finalized hash. + * @see XXH64_finalize(). */ -static xxh_u32 -XXH32_finalize(xxh_u32 h32, const xxh_u8* ptr, size_t len, XXH_alignment align) +static XXH_PUREF xxh_u32 +XXH32_finalize(xxh_u32 hash, const xxh_u8* ptr, size_t len, XXH_alignment align) { -#define XXH_PROCESS1 do { \ - h32 += (*ptr++) * XXH_PRIME32_5; \ - h32 = XXH_rotl32(h32, 11) * XXH_PRIME32_1; \ +#define XXH_PROCESS1 do { \ + hash += (*ptr++) * XXH_PRIME32_5; \ + hash = XXH_rotl32(hash, 11) * XXH_PRIME32_1; \ } while (0) -#define XXH_PROCESS4 do { \ - h32 += XXH_get32bits(ptr) * XXH_PRIME32_3; \ - ptr += 4; \ - h32 = XXH_rotl32(h32, 17) * XXH_PRIME32_4; \ +#define XXH_PROCESS4 do { \ + hash += XXH_get32bits(ptr) * XXH_PRIME32_3; \ + ptr += 4; \ + hash = XXH_rotl32(hash, 17) * XXH_PRIME32_4; \ } while (0) if (ptr==NULL) XXH_ASSERT(len == 0); @@ -1990,49 +2925,49 @@ XXH32_finalize(xxh_u32 h32, const xxh_u8* ptr, size_t len, XXH_alignment align) XXH_PROCESS1; --len; } - return XXH32_avalanche(h32); + return XXH32_avalanche(hash); } else { switch(len&15) /* or switch(bEnd - p) */ { case 12: XXH_PROCESS4; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 8: XXH_PROCESS4; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 4: XXH_PROCESS4; - return XXH32_avalanche(h32); + return XXH32_avalanche(hash); case 13: XXH_PROCESS4; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 9: XXH_PROCESS4; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 5: XXH_PROCESS4; XXH_PROCESS1; - return XXH32_avalanche(h32); + return XXH32_avalanche(hash); case 14: XXH_PROCESS4; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 10: XXH_PROCESS4; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 6: XXH_PROCESS4; XXH_PROCESS1; XXH_PROCESS1; - return XXH32_avalanche(h32); + return XXH32_avalanche(hash); case 15: XXH_PROCESS4; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 11: XXH_PROCESS4; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 7: XXH_PROCESS4; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 3: XXH_PROCESS1; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 2: XXH_PROCESS1; - XXH_FALLTHROUGH; + XXH_FALLTHROUGH; /* fallthrough */ case 1: XXH_PROCESS1; - XXH_FALLTHROUGH; - case 0: return XXH32_avalanche(h32); + XXH_FALLTHROUGH; /* fallthrough */ + case 0: return XXH32_avalanche(hash); } XXH_ASSERT(0); - return h32; /* reaching this point is deemed impossible */ + return hash; /* reaching this point is deemed impossible */ } } @@ -2052,7 +2987,7 @@ XXH32_finalize(xxh_u32 h32, const xxh_u8* ptr, size_t len, XXH_alignment align) * @param align Whether @p input is aligned. * @return The calculated hash. */ -XXH_FORCE_INLINE xxh_u32 +XXH_FORCE_INLINE XXH_PUREF xxh_u32 XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment align) { xxh_u32 h32; @@ -2085,10 +3020,10 @@ XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment return XXH32_finalize(h32, input, len&15, align); } -/*! @ingroup xxh32_family */ +/*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t len, XXH32_hash_t seed) { -#if 0 +#if !defined(XXH_NO_STREAM) && XXH_SIZE_OPT >= 2 /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ XXH32_state_t state; XXH32_reset(&state, seed); @@ -2107,27 +3042,26 @@ XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t len, XXH32_hash_t s /******* Hash streaming *******/ -/*! - * @ingroup xxh32_family - */ +#ifndef XXH_NO_STREAM +/*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void) { return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t)); } -/*! @ingroup xxh32_family */ +/*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr) { XXH_free(statePtr); return XXH_OK; } -/*! @ingroup xxh32_family */ +/*! @ingroup XXH32_family */ XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState) { XXH_memcpy(dstState, srcState, sizeof(*dstState)); } -/*! @ingroup xxh32_family */ +/*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, XXH32_hash_t seed) { XXH_ASSERT(statePtr != NULL); @@ -2140,7 +3074,7 @@ XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, XXH32_hash_t s } -/*! @ingroup xxh32_family */ +/*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH_errorcode XXH32_update(XXH32_state_t* state, const void* input, size_t len) { @@ -2195,7 +3129,7 @@ XXH32_update(XXH32_state_t* state, const void* input, size_t len) } -/*! @ingroup xxh32_family */ +/*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH32_hash_t XXH32_digest(const XXH32_state_t* state) { xxh_u32 h32; @@ -2213,31 +3147,18 @@ XXH_PUBLIC_API XXH32_hash_t XXH32_digest(const XXH32_state_t* state) return XXH32_finalize(h32, (const xxh_u8*)state->mem32, state->memsize, XXH_aligned); } - +#endif /* !XXH_NO_STREAM */ /******* Canonical representation *******/ -/*! - * @ingroup xxh32_family - * The default return values from XXH functions are unsigned 32 and 64 bit - * integers. - * - * The canonical representation uses big endian convention, the same convention - * as human-readable numbers (large digits first). - * - * This way, hash values can be written into a file or buffer, remaining - * comparable across different systems. - * - * The following functions allow transformation of hash values to and from their - * canonical format. - */ +/*! @ingroup XXH32_family */ XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) { - /* XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); */ + XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash); XXH_memcpy(dst, &hash, sizeof(*dst)); } -/*! @ingroup xxh32_family */ +/*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src) { return XXH_readBE32(src); @@ -2278,18 +3199,19 @@ static xxh_u64 XXH_read64(const void* memPtr) #elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) /* - * __pack instructions are safer, but compiler specific, hence potentially - * problematic for some compilers. - * - * Currently only defined for GCC and ICC. + * __attribute__((aligned(1))) is supported by gcc and clang. Originally the + * documentation claimed that it only increased the alignment, but actually it + * can decrease it on gcc, clang, and icc: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69502, + * https://gcc.godbolt.org/z/xYez1j67Y. */ #ifdef XXH_OLD_NAMES typedef union { xxh_u32 u32; xxh_u64 u64; } __attribute__((packed)) unalign64; #endif static xxh_u64 XXH_read64(const void* ptr) { - typedef union { xxh_u32 u32; xxh_u64 u64; } __attribute__((packed)) xxh_unalign64; - return ((const xxh_unalign64*)ptr)->u64; + typedef __attribute__((aligned(1))) xxh_u64 xxh_unalign64; + return *((const xxh_unalign64*)ptr); } #else @@ -2380,8 +3302,10 @@ XXH_readLE64_align(const void* ptr, XXH_alignment align) /******* xxh64 *******/ /*! * @} - * @defgroup xxh64_impl XXH64 implementation + * @defgroup XXH64_impl XXH64 implementation * @ingroup impl + * + * Details on the XXH64 implementation. * @{ */ /* #define rather that static const, to be used as initializers */ @@ -2399,11 +3323,29 @@ XXH_readLE64_align(const void* ptr, XXH_alignment align) # define PRIME64_5 XXH_PRIME64_5 #endif +/*! @copydoc XXH32_round */ static xxh_u64 XXH64_round(xxh_u64 acc, xxh_u64 input) { acc += input * XXH_PRIME64_2; acc = XXH_rotl64(acc, 31); acc *= XXH_PRIME64_1; +#if (defined(__AVX512F__)) && !defined(XXH_ENABLE_AUTOVECTORIZE) + /* + * DISABLE AUTOVECTORIZATION: + * A compiler fence is used to prevent GCC and Clang from + * autovectorizing the XXH64 loop (pragmas and attributes don't work for some + * reason) without globally disabling AVX512. + * + * Autovectorization of XXH64 tends to be detrimental, + * though the exact outcome may change depending on exact cpu and compiler version. + * For information, it has been reported as detrimental for Skylake-X, + * but possibly beneficial for Zen4. + * + * The default is to disable auto-vectorization, + * but you can select to enable it instead using `XXH_ENABLE_AUTOVECTORIZE` build variable. + */ + XXH_COMPILER_GUARD(acc); +#endif return acc; } @@ -2415,43 +3357,59 @@ static xxh_u64 XXH64_mergeRound(xxh_u64 acc, xxh_u64 val) return acc; } -static xxh_u64 XXH64_avalanche(xxh_u64 h64) +/*! @copydoc XXH32_avalanche */ +static xxh_u64 XXH64_avalanche(xxh_u64 hash) { - h64 ^= h64 >> 33; - h64 *= XXH_PRIME64_2; - h64 ^= h64 >> 29; - h64 *= XXH_PRIME64_3; - h64 ^= h64 >> 32; - return h64; + hash ^= hash >> 33; + hash *= XXH_PRIME64_2; + hash ^= hash >> 29; + hash *= XXH_PRIME64_3; + hash ^= hash >> 32; + return hash; } #define XXH_get64bits(p) XXH_readLE64_align(p, align) -static xxh_u64 -XXH64_finalize(xxh_u64 h64, const xxh_u8* ptr, size_t len, XXH_alignment align) +/*! + * @internal + * @brief Processes the last 0-31 bytes of @p ptr. + * + * There may be up to 31 bytes remaining to consume from the input. + * This final stage will digest them to ensure that all input bytes are present + * in the final mix. + * + * @param hash The hash to finalize. + * @param ptr The pointer to the remaining input. + * @param len The remaining length, modulo 32. + * @param align Whether @p ptr is aligned. + * @return The finalized hash + * @see XXH32_finalize(). + */ +static XXH_PUREF xxh_u64 +XXH64_finalize(xxh_u64 hash, const xxh_u8* ptr, size_t len, XXH_alignment align) { if (ptr==NULL) XXH_ASSERT(len == 0); len &= 31; while (len >= 8) { xxh_u64 const k1 = XXH64_round(0, XXH_get64bits(ptr)); ptr += 8; - h64 ^= k1; - h64 = XXH_rotl64(h64,27) * XXH_PRIME64_1 + XXH_PRIME64_4; + hash ^= k1; + hash = XXH_rotl64(hash,27) * XXH_PRIME64_1 + XXH_PRIME64_4; len -= 8; } if (len >= 4) { - h64 ^= (xxh_u64)(XXH_get32bits(ptr)) * XXH_PRIME64_1; + hash ^= (xxh_u64)(XXH_get32bits(ptr)) * XXH_PRIME64_1; ptr += 4; - h64 = XXH_rotl64(h64, 23) * XXH_PRIME64_2 + XXH_PRIME64_3; + hash = XXH_rotl64(hash, 23) * XXH_PRIME64_2 + XXH_PRIME64_3; len -= 4; } while (len > 0) { - h64 ^= (*ptr++) * XXH_PRIME64_5; - h64 = XXH_rotl64(h64, 11) * XXH_PRIME64_1; + hash ^= (*ptr++) * XXH_PRIME64_5; + hash = XXH_rotl64(hash, 11) * XXH_PRIME64_1; --len; } - return XXH64_avalanche(h64); + return XXH64_avalanche(hash); } #ifdef XXH_OLD_NAMES @@ -2464,7 +3422,15 @@ XXH64_finalize(xxh_u64 h64, const xxh_u8* ptr, size_t len, XXH_alignment align) # undef XXH_PROCESS8_64 #endif -XXH_FORCE_INLINE xxh_u64 +/*! + * @internal + * @brief The implementation for @ref XXH64(). + * + * @param input , len , seed Directly passed from @ref XXH64(). + * @param align Whether @p input is aligned. + * @return The calculated hash. + */ +XXH_FORCE_INLINE XXH_PUREF xxh_u64 XXH64_endian_align(const xxh_u8* input, size_t len, xxh_u64 seed, XXH_alignment align) { xxh_u64 h64; @@ -2501,10 +3467,10 @@ XXH64_endian_align(const xxh_u8* input, size_t len, xxh_u64 seed, XXH_alignment } -/*! @ingroup xxh64_family */ -XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t len, XXH64_hash_t seed) +/*! @ingroup XXH64_family */ +XXH_PUBLIC_API XXH64_hash_t XXH64 (XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed) { -#if 0 +#if !defined(XXH_NO_STREAM) && XXH_SIZE_OPT >= 2 /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ XXH64_state_t state; XXH64_reset(&state, seed); @@ -2522,27 +3488,27 @@ XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t len, XXH64_hash_t s } /******* Hash Streaming *******/ - -/*! @ingroup xxh64_family*/ +#ifndef XXH_NO_STREAM +/*! @ingroup XXH64_family*/ XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void) { return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t)); } -/*! @ingroup xxh64_family */ +/*! @ingroup XXH64_family */ XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr) { XXH_free(statePtr); return XXH_OK; } -/*! @ingroup xxh64_family */ -XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState) +/*! @ingroup XXH64_family */ +XXH_PUBLIC_API void XXH64_copyState(XXH_NOESCAPE XXH64_state_t* dstState, const XXH64_state_t* srcState) { XXH_memcpy(dstState, srcState, sizeof(*dstState)); } -/*! @ingroup xxh64_family */ -XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, XXH64_hash_t seed) +/*! @ingroup XXH64_family */ +XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH_NOESCAPE XXH64_state_t* statePtr, XXH64_hash_t seed) { XXH_ASSERT(statePtr != NULL); memset(statePtr, 0, sizeof(*statePtr)); @@ -2553,9 +3519,9 @@ XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, XXH64_hash_t s return XXH_OK; } -/*! @ingroup xxh64_family */ +/*! @ingroup XXH64_family */ XXH_PUBLIC_API XXH_errorcode -XXH64_update (XXH64_state_t* state, const void* input, size_t len) +XXH64_update (XXH_NOESCAPE XXH64_state_t* state, XXH_NOESCAPE const void* input, size_t len) { if (input==NULL) { XXH_ASSERT(len == 0); @@ -2605,8 +3571,8 @@ XXH64_update (XXH64_state_t* state, const void* input, size_t len) } -/*! @ingroup xxh64_family */ -XXH_PUBLIC_API XXH64_hash_t XXH64_digest(const XXH64_state_t* state) +/*! @ingroup XXH64_family */ +XXH_PUBLIC_API XXH64_hash_t XXH64_digest(XXH_NOESCAPE const XXH64_state_t* state) { xxh_u64 h64; @@ -2624,20 +3590,20 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_digest(const XXH64_state_t* state) return XXH64_finalize(h64, (const xxh_u8*)state->mem64, (size_t)state->total_len, XXH_aligned); } - +#endif /* !XXH_NO_STREAM */ /******* Canonical representation *******/ -/*! @ingroup xxh64_family */ -XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash) +/*! @ingroup XXH64_family */ +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH_NOESCAPE XXH64_canonical_t* dst, XXH64_hash_t hash) { - /* XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); */ + XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash); XXH_memcpy(dst, &hash, sizeof(*dst)); } -/*! @ingroup xxh64_family */ -XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src) +/*! @ingroup XXH64_family */ +XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(XXH_NOESCAPE const XXH64_canonical_t* src) { return XXH_readBE64(src); } @@ -2650,7 +3616,7 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src ************************************************************************ */ /*! * @} - * @defgroup xxh3_impl XXH3 implementation + * @defgroup XXH3_impl XXH3 implementation * @ingroup impl * @{ */ @@ -2658,11 +3624,19 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src /* === Compiler specifics === */ #if ((defined(sun) || defined(__sun)) && __cplusplus) /* Solaris includes __STDC_VERSION__ with C++. Tested with GCC 5.5 */ -# define XXH_RESTRICT /* disable */ +# define XXH_RESTRICT /* disable */ #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* >= C99 */ # define XXH_RESTRICT restrict +#elif (defined (__GNUC__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) \ + || (defined (__clang__)) \ + || (defined (_MSC_VER) && (_MSC_VER >= 1400)) \ + || (defined (__INTEL_COMPILER) && (__INTEL_COMPILER >= 1300)) +/* + * There are a LOT more compilers that recognize __restrict but this + * covers the major ones. + */ +# define XXH_RESTRICT __restrict #else -/* Note: it might be useful to define __restrict or __restrict__ for some C++ compilers */ # define XXH_RESTRICT /* disable */ #endif @@ -2676,10 +3650,26 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src # define XXH_unlikely(x) (x) #endif +#ifndef XXH_HAS_INCLUDE +# ifdef __has_include +/* + * Not defined as XXH_HAS_INCLUDE(x) (function-like) because + * this causes segfaults in Apple Clang 4.2 (on Mac OS X 10.7 Lion) + */ +# define XXH_HAS_INCLUDE __has_include +# else +# define XXH_HAS_INCLUDE(x) 0 +# endif +#endif + #if defined(__GNUC__) || defined(__clang__) +# if defined(__ARM_FEATURE_SVE) +# include +# endif # if defined(__ARM_NEON__) || defined(__ARM_NEON) \ - || defined(__aarch64__) || defined(_M_ARM) \ - || defined(_M_ARM64) || defined(_M_ARM64EC) + || (defined(_M_ARM) && _M_ARM >= 7) \ + || defined(_M_ARM64) || defined(_M_ARM64EC) \ + || (defined(__wasm_simd128__) && XXH_HAS_INCLUDE()) /* WASM SIMD128 via SIMDe */ # define inline __inline__ /* circumvent a clang bug */ # include # undef inline @@ -2790,7 +3780,7 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src * Note that these are actually implemented as macros. * * If this is not defined, it is detected automatically. - * @ref XXH_X86DISPATCH overrides this. + * internal macro XXH_X86DISPATCH overrides this. */ enum XXH_VECTOR_TYPE /* fake enum */ { XXH_SCALAR = 0, /*!< Portable scalar version */ @@ -2802,8 +3792,13 @@ enum XXH_VECTOR_TYPE /* fake enum */ { */ XXH_AVX2 = 2, /*!< AVX2 for Haswell and Bulldozer */ XXH_AVX512 = 3, /*!< AVX512 for Skylake and Icelake */ - XXH_NEON = 4, /*!< NEON for most ARMv7-A and all AArch64 */ + XXH_NEON = 4, /*!< + * NEON for most ARMv7-A, all AArch64, and WASM SIMD128 + * via the SIMDeverywhere polyfill provided with the + * Emscripten SDK. + */ XXH_VSX = 5, /*!< VSX and ZVector for POWER8/z13 (64-bit) */ + XXH_SVE = 6, /*!< SVE for some ARMv8-A and ARMv9-A */ }; /*! * @ingroup tuning @@ -2825,12 +3820,16 @@ enum XXH_VECTOR_TYPE /* fake enum */ { # define XXH_AVX512 3 # define XXH_NEON 4 # define XXH_VSX 5 +# define XXH_SVE 6 #endif #ifndef XXH_VECTOR /* can be defined on command line */ -# if ( \ +# if defined(__ARM_FEATURE_SVE) +# define XXH_VECTOR XXH_SVE +# elif ( \ defined(__ARM_NEON__) || defined(__ARM_NEON) /* gcc */ \ || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) /* msvc */ \ + || (defined(__wasm_simd128__) && XXH_HAS_INCLUDE()) /* wasm simd128 via SIMDe */ \ ) && ( \ defined(_WIN32) || defined(__LITTLE_ENDIAN__) /* little endian only */ \ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) \ @@ -2851,6 +3850,17 @@ enum XXH_VECTOR_TYPE /* fake enum */ { # endif #endif +/* __ARM_FEATURE_SVE is only supported by GCC & Clang. */ +#if (XXH_VECTOR == XXH_SVE) && !defined(__ARM_FEATURE_SVE) +# ifdef _MSC_VER +# pragma warning(once : 4606) +# else +# warning "__ARM_FEATURE_SVE isn't supported. Use SCALAR instead." +# endif +# undef XXH_VECTOR +# define XXH_VECTOR XXH_SCALAR +#endif + /* * Controls the alignment of the accumulator, * for compatibility with aligned vector loads, which are usually faster. @@ -2870,16 +3880,26 @@ enum XXH_VECTOR_TYPE /* fake enum */ { # define XXH_ACC_ALIGN 16 # elif XXH_VECTOR == XXH_AVX512 /* avx512 */ # define XXH_ACC_ALIGN 64 +# elif XXH_VECTOR == XXH_SVE /* sve */ +# define XXH_ACC_ALIGN 64 # endif #endif #if defined(XXH_X86DISPATCH) || XXH_VECTOR == XXH_SSE2 \ || XXH_VECTOR == XXH_AVX2 || XXH_VECTOR == XXH_AVX512 # define XXH_SEC_ALIGN XXH_ACC_ALIGN +#elif XXH_VECTOR == XXH_SVE +# define XXH_SEC_ALIGN XXH_ACC_ALIGN #else # define XXH_SEC_ALIGN 8 #endif +#if defined(__GNUC__) || defined(__clang__) +# define XXH_ALIASING __attribute__((may_alias)) +#else +# define XXH_ALIASING /* nothing */ +#endif + /* * UGLY HACK: * GCC usually generates the best code with -O3 for xxHash. @@ -2903,153 +3923,126 @@ enum XXH_VECTOR_TYPE /* fake enum */ { */ #if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \ && defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \ - && defined(__OPTIMIZE__) && !defined(__OPTIMIZE_SIZE__) /* respect -O0 and -Os */ + && defined(__OPTIMIZE__) && XXH_SIZE_OPT <= 0 /* respect -O0 and -Os */ # pragma GCC push_options # pragma GCC optimize("-O2") #endif - #if XXH_VECTOR == XXH_NEON + /* - * NEON's setup for vmlal_u32 is a little more complicated than it is on - * SSE2, AVX2, and VSX. + * UGLY HACK: While AArch64 GCC on Linux does not seem to care, on macOS, GCC -O3 + * optimizes out the entire hashLong loop because of the aliasing violation. * - * While PMULUDQ and VMULEUW both perform a mask, VMLAL.U32 performs an upcast. - * - * To do the same operation, the 128-bit 'Q' register needs to be split into - * two 64-bit 'D' registers, performing this operation:: - * - * [ a | b ] - * | '---------. .--------' | - * | x | - * | .---------' '--------. | - * [ a & 0xFFFFFFFF | b & 0xFFFFFFFF ],[ a >> 32 | b >> 32 ] - * - * Due to significant changes in aarch64, the fastest method for aarch64 is - * completely different than the fastest method for ARMv7-A. - * - * ARMv7-A treats D registers as unions overlaying Q registers, so modifying - * D11 will modify the high half of Q5. This is similar to how modifying AH - * will only affect bits 8-15 of AX on x86. - * - * VZIP takes two registers, and puts even lanes in one register and odd lanes - * in the other. - * - * On ARMv7-A, this strangely modifies both parameters in place instead of - * taking the usual 3-operand form. - * - * Therefore, if we want to do this, we can simply use a D-form VZIP.32 on the - * lower and upper halves of the Q register to end up with the high and low - * halves where we want - all in one instruction. - * - * vzip.32 d10, d11 @ d10 = { d10[0], d11[0] }; d11 = { d10[1], d11[1] } - * - * Unfortunately we need inline assembly for this: Instructions modifying two - * registers at once is not possible in GCC or Clang's IR, and they have to - * create a copy. - * - * aarch64 requires a different approach. - * - * In order to make it easier to write a decent compiler for aarch64, many - * quirks were removed, such as conditional execution. - * - * NEON was also affected by this. - * - * aarch64 cannot access the high bits of a Q-form register, and writes to a - * D-form register zero the high bits, similar to how writes to W-form scalar - * registers (or DWORD registers on x86_64) work. - * - * The formerly free vget_high intrinsics now require a vext (with a few - * exceptions) - * - * Additionally, VZIP was replaced by ZIP1 and ZIP2, which are the equivalent - * of PUNPCKL* and PUNPCKH* in SSE, respectively, in order to only modify one - * operand. - * - * The equivalent of the VZIP.32 on the lower and upper halves would be this - * mess: - * - * ext v2.4s, v0.4s, v0.4s, #2 // v2 = { v0[2], v0[3], v0[0], v0[1] } - * zip1 v1.2s, v0.2s, v2.2s // v1 = { v0[0], v2[0] } - * zip2 v0.2s, v0.2s, v1.2s // v0 = { v0[1], v2[1] } - * - * Instead, we use a literal downcast, vmovn_u64 (XTN), and vshrn_n_u64 (SHRN): - * - * shrn v1.2s, v0.2d, #32 // v1 = (uint32x2_t)(v0 >> 32); - * xtn v0.2s, v0.2d // v0 = (uint32x2_t)(v0 & 0xFFFFFFFF); - * - * This is available on ARMv7-A, but is less efficient than a single VZIP.32. + * However, GCC is also inefficient at load-store optimization with vld1q/vst1q, + * so the only option is to mark it as aliasing. */ +typedef uint64x2_t xxh_aliasing_uint64x2_t XXH_ALIASING; /*! - * Function-like macro: - * void XXH_SPLIT_IN_PLACE(uint64x2_t &in, uint32x2_t &outLo, uint32x2_t &outHi) - * { - * outLo = (uint32x2_t)(in & 0xFFFFFFFF); - * outHi = (uint32x2_t)(in >> 32); - * in = UNDEFINED; - * } + * @internal + * @brief `vld1q_u64` but faster and alignment-safe. + * + * On AArch64, unaligned access is always safe, but on ARMv7-a, it is only + * *conditionally* safe (`vld1` has an alignment bit like `movdq[ua]` in x86). + * + * GCC for AArch64 sees `vld1q_u8` as an intrinsic instead of a load, so it + * prohibits load-store optimizations. Therefore, a direct dereference is used. + * + * Otherwise, `vld1q_u8` is used with `vreinterpretq_u8_u64` to do a safe + * unaligned load. */ -# if !defined(XXH_NO_VZIP_HACK) /* define to disable */ \ - && (defined(__GNUC__) || defined(__clang__)) \ - && (defined(__arm__) || defined(__thumb__) || defined(_M_ARM)) -# define XXH_SPLIT_IN_PLACE(in, outLo, outHi) \ - do { \ - /* Undocumented GCC/Clang operand modifier: %e0 = lower D half, %f0 = upper D half */ \ - /* https://github.com/gcc-mirror/gcc/blob/38cf91e5/gcc/config/arm/arm.c#L22486 */ \ - /* https://github.com/llvm-mirror/llvm/blob/2c4ca683/lib/Target/ARM/ARMAsmPrinter.cpp#L399 */ \ - __asm__("vzip.32 %e0, %f0" : "+w" (in)); \ - (outLo) = vget_low_u32 (vreinterpretq_u32_u64(in)); \ - (outHi) = vget_high_u32(vreinterpretq_u32_u64(in)); \ - } while (0) -# else -# define XXH_SPLIT_IN_PLACE(in, outLo, outHi) \ - do { \ - (outLo) = vmovn_u64 (in); \ - (outHi) = vshrn_n_u64 ((in), 32); \ - } while (0) -# endif +#if defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__) +XXH_FORCE_INLINE uint64x2_t XXH_vld1q_u64(void const* ptr) /* silence -Wcast-align */ +{ + return *(xxh_aliasing_uint64x2_t const *)ptr; +} +#else +XXH_FORCE_INLINE uint64x2_t XXH_vld1q_u64(void const* ptr) +{ + return vreinterpretq_u64_u8(vld1q_u8((uint8_t const*)ptr)); +} +#endif + +/*! + * @internal + * @brief `vmlal_u32` on low and high halves of a vector. + * + * This is a workaround for AArch64 GCC < 11 which implemented arm_neon.h with + * inline assembly and were therefore incapable of merging the `vget_{low, high}_u32` + * with `vmlal_u32`. + */ +#if defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 11 +XXH_FORCE_INLINE uint64x2_t +XXH_vmlal_low_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs) +{ + /* Inline assembly is the only way */ + __asm__("umlal %0.2d, %1.2s, %2.2s" : "+w" (acc) : "w" (lhs), "w" (rhs)); + return acc; +} +XXH_FORCE_INLINE uint64x2_t +XXH_vmlal_high_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs) +{ + /* This intrinsic works as expected */ + return vmlal_high_u32(acc, lhs, rhs); +} +#else +/* Portable intrinsic versions */ +XXH_FORCE_INLINE uint64x2_t +XXH_vmlal_low_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs) +{ + return vmlal_u32(acc, vget_low_u32(lhs), vget_low_u32(rhs)); +} +/*! @copydoc XXH_vmlal_low_u32 + * Assume the compiler converts this to vmlal_high_u32 on aarch64 */ +XXH_FORCE_INLINE uint64x2_t +XXH_vmlal_high_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs) +{ + return vmlal_u32(acc, vget_high_u32(lhs), vget_high_u32(rhs)); +} +#endif /*! * @ingroup tuning * @brief Controls the NEON to scalar ratio for XXH3 * - * On AArch64 when not optimizing for size, XXH3 will run 6 lanes using NEON and - * 2 lanes on scalar by default. + * This can be set to 2, 4, 6, or 8. * - * This can be set to 2, 4, 6, or 8. ARMv7 will default to all 8 NEON lanes, as the - * emulated 64-bit arithmetic is too slow. + * ARM Cortex CPUs are _very_ sensitive to how their pipelines are used. * - * Modern ARM CPUs are _very_ sensitive to how their pipelines are used. + * For example, the Cortex-A73 can dispatch 3 micro-ops per cycle, but only 2 of those + * can be NEON. If you are only using NEON instructions, you are only using 2/3 of the CPU + * bandwidth. * - * For example, the Cortex-A73 can dispatch 3 micro-ops per cycle, but it can't - * have more than 2 NEON (F0/F1) micro-ops. If you are only using NEON instructions, - * you are only using 2/3 of the CPU bandwidth. - * - * This is even more noticeable on the more advanced cores like the A76 which + * This is even more noticeable on the more advanced cores like the Cortex-A76 which * can dispatch 8 micro-ops per cycle, but still only 2 NEON micro-ops at once. * - * Therefore, @ref XXH3_NEON_LANES lanes will be processed using NEON, and the - * remaining lanes will use scalar instructions. This improves the bandwidth - * and also gives the integer pipelines something to do besides twiddling loop - * counters and pointers. + * Therefore, to make the most out of the pipeline, it is beneficial to run 6 NEON lanes + * and 2 scalar lanes, which is chosen by default. + * + * This does not apply to Apple processors or 32-bit processors, which run better with + * full NEON. These will default to 8. Additionally, size-optimized builds run 8 lanes. * * This change benefits CPUs with large micro-op buffers without negatively affecting - * other CPUs: + * most other CPUs: * * | Chipset | Dispatch type | NEON only | 6:2 hybrid | Diff. | * |:----------------------|:--------------------|----------:|-----------:|------:| * | Snapdragon 730 (A76) | 2 NEON/8 micro-ops | 8.8 GB/s | 10.1 GB/s | ~16% | * | Snapdragon 835 (A73) | 2 NEON/3 micro-ops | 5.1 GB/s | 5.3 GB/s | ~5% | * | Marvell PXA1928 (A53) | In-order dual-issue | 1.9 GB/s | 1.9 GB/s | 0% | + * | Apple M1 | 4 NEON/8 micro-ops | 37.3 GB/s | 36.1 GB/s | ~-3% | * * It also seems to fix some bad codegen on GCC, making it almost as fast as clang. * + * When using WASM SIMD128, if this is 2 or 6, SIMDe will scalarize 2 of the lanes meaning + * it effectively becomes worse 4. + * * @see XXH3_accumulate_512_neon() */ # ifndef XXH3_NEON_LANES # if (defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) \ - && !defined(__OPTIMIZE_SIZE__) + && !defined(__APPLE__) && XXH_SIZE_OPT <= 0 # define XXH3_NEON_LANES 6 # else # define XXH3_NEON_LANES XXH_ACC_NB @@ -3066,27 +4059,42 @@ enum XXH_VECTOR_TYPE /* fake enum */ { * inconsistent intrinsics, spotty coverage, and multiple endiannesses. */ #if XXH_VECTOR == XXH_VSX +/* Annoyingly, these headers _may_ define three macros: `bool`, `vector`, + * and `pixel`. This is a problem for obvious reasons. + * + * These keywords are unnecessary; the spec literally says they are + * equivalent to `__bool`, `__vector`, and `__pixel` and may be undef'd + * after including the header. + * + * We use pragma push_macro/pop_macro to keep the namespace clean. */ +# pragma push_macro("bool") +# pragma push_macro("vector") +# pragma push_macro("pixel") +/* silence potential macro redefined warnings */ +# undef bool +# undef vector +# undef pixel + # if defined(__s390x__) # include # else -/* gcc's altivec.h can have the unwanted consequence to unconditionally - * #define bool, vector, and pixel keywords, - * with bad consequences for programs already using these keywords for other purposes. - * The paragraph defining these macros is skipped when __APPLE_ALTIVEC__ is defined. - * __APPLE_ALTIVEC__ is _generally_ defined automatically by the compiler, - * but it seems that, in some cases, it isn't. - * Force the build macro to be defined, so that keywords are not altered. - */ -# if defined(__GNUC__) && !defined(__APPLE_ALTIVEC__) -# define __APPLE_ALTIVEC__ -# endif # include # endif +/* Restore the original macro values, if applicable. */ +# pragma pop_macro("pixel") +# pragma pop_macro("vector") +# pragma pop_macro("bool") + typedef __vector unsigned long long xxh_u64x2; typedef __vector unsigned char xxh_u8x16; typedef __vector unsigned xxh_u32x4; +/* + * UGLY HACK: Similar to aarch64 macOS GCC, s390x GCC has the same aliasing issue. + */ +typedef xxh_u64x2 xxh_aliasing_u64x2 XXH_ALIASING; + # ifndef XXH_VSX_BE # if defined(__BIG_ENDIAN__) \ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) @@ -3138,8 +4146,9 @@ XXH_FORCE_INLINE xxh_u64x2 XXH_vec_loadu(const void *ptr) /* s390x is always big endian, no issue on this platform */ # define XXH_vec_mulo vec_mulo # define XXH_vec_mule vec_mule -# elif defined(__clang__) && XXH_HAS_BUILTIN(__builtin_altivec_vmuleuw) +# elif defined(__clang__) && XXH_HAS_BUILTIN(__builtin_altivec_vmuleuw) && !defined(__ibmxl__) /* Clang has a better way to control this, we can just use the builtin which doesn't swap. */ + /* The IBM XL Compiler (which defined __clang__) only implements the vec_* operations */ # define XXH_vec_mulo __builtin_altivec_vmulouw # define XXH_vec_mule __builtin_altivec_vmuleuw # else @@ -3160,13 +4169,28 @@ XXH_FORCE_INLINE xxh_u64x2 XXH_vec_mule(xxh_u32x4 a, xxh_u32x4 b) # endif /* XXH_vec_mulo, XXH_vec_mule */ #endif /* XXH_VECTOR == XXH_VSX */ +#if XXH_VECTOR == XXH_SVE +#define ACCRND(acc, offset) \ +do { \ + svuint64_t input_vec = svld1_u64(mask, xinput + offset); \ + svuint64_t secret_vec = svld1_u64(mask, xsecret + offset); \ + svuint64_t mixed = sveor_u64_x(mask, secret_vec, input_vec); \ + svuint64_t swapped = svtbl_u64(input_vec, kSwap); \ + svuint64_t mixed_lo = svextw_u64_x(mask, mixed); \ + svuint64_t mixed_hi = svlsr_n_u64_x(mask, mixed, 32); \ + svuint64_t mul = svmad_u64_x(mask, mixed_lo, mixed_hi, swapped); \ + acc = svadd_u64_x(mask, acc, mul); \ +} while (0) +#endif /* XXH_VECTOR == XXH_SVE */ /* prefetch * can be disabled, by declaring XXH_NO_PREFETCH build macro */ #if defined(XXH_NO_PREFETCH) # define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */ #else -# if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) /* _mm_prefetch() not defined outside of x86/x64 */ +# if XXH_SIZE_OPT >= 1 +# define XXH_PREFETCH(ptr) (void)(ptr) +# elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) /* _mm_prefetch() not defined outside of x86/x64 */ # include /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */ # define XXH_PREFETCH(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0) # elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) ) @@ -3203,6 +4227,8 @@ XXH_ALIGN(64) static const xxh_u8 XXH3_kSecret[XXH_SECRET_DEFAULT_SIZE] = { 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e, }; +static const xxh_u64 PRIME_MX1 = 0x165667919E3779F9ULL; /*!< 0b0001011001010110011001111001000110011110001101110111100111111001 */ +static const xxh_u64 PRIME_MX2 = 0x9FB21C651E98DF25ULL; /*!< 0b1001111110110010000111000110010100011110100110001101111100100101 */ #ifdef XXH_OLD_NAMES # define kSecret XXH3_kSecret @@ -3394,7 +4420,7 @@ XXH3_mul128_fold64(xxh_u64 lhs, xxh_u64 rhs) } /*! Seems to produce slightly better code on GCC for some reason. */ -XXH_FORCE_INLINE xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift) +XXH_FORCE_INLINE XXH_CONSTF xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift) { XXH_ASSERT(0 <= shift && shift < 64); return v64 ^ (v64 >> shift); @@ -3407,7 +4433,7 @@ XXH_FORCE_INLINE xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift) static XXH64_hash_t XXH3_avalanche(xxh_u64 h64) { h64 = XXH_xorshift64(h64, 37); - h64 *= 0x165667919E3779F9ULL; + h64 *= PRIME_MX1; h64 = XXH_xorshift64(h64, 32); return h64; } @@ -3421,9 +4447,9 @@ static XXH64_hash_t XXH3_rrmxmx(xxh_u64 h64, xxh_u64 len) { /* this mix is inspired by Pelle Evensen's rrmxmx */ h64 ^= XXH_rotl64(h64, 49) ^ XXH_rotl64(h64, 24); - h64 *= 0x9FB21C651E98DF25ULL; + h64 *= PRIME_MX2; h64 ^= (h64 >> 35) + len ; - h64 *= 0x9FB21C651E98DF25ULL; + h64 *= PRIME_MX2; return XXH_xorshift64(h64, 28); } @@ -3461,7 +4487,7 @@ static XXH64_hash_t XXH3_rrmxmx(xxh_u64 h64, xxh_u64 len) * * This adds an extra layer of strength for custom secrets. */ -XXH_FORCE_INLINE XXH64_hash_t +XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t XXH3_len_1to3_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(input != NULL); @@ -3483,7 +4509,7 @@ XXH3_len_1to3_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_h } } -XXH_FORCE_INLINE XXH64_hash_t +XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t XXH3_len_4to8_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(input != NULL); @@ -3499,7 +4525,7 @@ XXH3_len_4to8_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_h } } -XXH_FORCE_INLINE XXH64_hash_t +XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t XXH3_len_9to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(input != NULL); @@ -3516,7 +4542,7 @@ XXH3_len_9to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_ } } -XXH_FORCE_INLINE XXH64_hash_t +XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t XXH3_len_0to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(len <= 16); @@ -3586,7 +4612,7 @@ XXH_FORCE_INLINE xxh_u64 XXH3_mix16B(const xxh_u8* XXH_RESTRICT input, } /* For mid range keys, XXH3 uses a Mum-hash variant. */ -XXH_FORCE_INLINE XXH64_hash_t +XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t XXH3_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, XXH64_hash_t seed) @@ -3595,6 +4621,14 @@ XXH3_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len, XXH_ASSERT(16 < len && len <= 128); { xxh_u64 acc = len * XXH_PRIME64_1; +#if XXH_SIZE_OPT >= 1 + /* Smaller and cleaner, but slightly slower. */ + unsigned int i = (unsigned int)(len - 1) / 32; + do { + acc += XXH3_mix16B(input+16 * i, secret+32*i, seed); + acc += XXH3_mix16B(input+len-16*(i+1), secret+32*i+16, seed); + } while (i-- != 0); +#else if (len > 32) { if (len > 64) { if (len > 96) { @@ -3609,14 +4643,17 @@ XXH3_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len, } acc += XXH3_mix16B(input+0, secret+0, seed); acc += XXH3_mix16B(input+len-16, secret+16, seed); - +#endif return XXH3_avalanche(acc); } } +/*! + * @brief Maximum size of "short" key in bytes. + */ #define XXH3_MIDSIZE_MAX 240 -XXH_NO_INLINE XXH64_hash_t +XXH_NO_INLINE XXH_PUREF XXH64_hash_t XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, XXH64_hash_t seed) @@ -3628,13 +4665,17 @@ XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len, #define XXH3_MIDSIZE_LASTOFFSET 17 { xxh_u64 acc = len * XXH_PRIME64_1; - int const nbRounds = (int)len / 16; - int i; + xxh_u64 acc_end; + unsigned int const nbRounds = (unsigned int)len / 16; + unsigned int i; + XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX); for (i=0; i<8; i++) { acc += XXH3_mix16B(input+(16*i), secret+(16*i), seed); } - acc = XXH3_avalanche(acc); + /* last bytes */ + acc_end = XXH3_mix16B(input + len - 16, secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET, seed); XXH_ASSERT(nbRounds >= 8); + acc = XXH3_avalanche(acc); #if defined(__clang__) /* Clang */ \ && (defined(__ARM_NEON) || defined(__ARM_NEON__)) /* NEON */ \ && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable */ @@ -3661,11 +4702,13 @@ XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len, #pragma clang loop vectorize(disable) #endif for (i=8 ; i < nbRounds; i++) { - acc += XXH3_mix16B(input+(16*i), secret+(16*(i-8)) + XXH3_MIDSIZE_STARTOFFSET, seed); + /* + * Prevents clang for unrolling the acc loop and interleaving with this one. + */ + XXH_COMPILER_GUARD(acc); + acc_end += XXH3_mix16B(input+(16*i), secret+(16*(i-8)) + XXH3_MIDSIZE_STARTOFFSET, seed); } - /* last bytes */ - acc += XXH3_mix16B(input + len - 16, secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET, seed); - return XXH3_avalanche(acc); + return XXH3_avalanche(acc + acc_end); } } @@ -3681,6 +4724,47 @@ XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len, # define ACC_NB XXH_ACC_NB #endif +#ifndef XXH_PREFETCH_DIST +# ifdef __clang__ +# define XXH_PREFETCH_DIST 320 +# else +# if (XXH_VECTOR == XXH_AVX512) +# define XXH_PREFETCH_DIST 512 +# else +# define XXH_PREFETCH_DIST 384 +# endif +# endif /* __clang__ */ +#endif /* XXH_PREFETCH_DIST */ + +/* + * These macros are to generate an XXH3_accumulate() function. + * The two arguments select the name suffix and target attribute. + * + * The name of this symbol is XXH3_accumulate_() and it calls + * XXH3_accumulate_512_(). + * + * It may be useful to hand implement this function if the compiler fails to + * optimize the inline function. + */ +#define XXH3_ACCUMULATE_TEMPLATE(name) \ +void \ +XXH3_accumulate_##name(xxh_u64* XXH_RESTRICT acc, \ + const xxh_u8* XXH_RESTRICT input, \ + const xxh_u8* XXH_RESTRICT secret, \ + size_t nbStripes) \ +{ \ + size_t n; \ + for (n = 0; n < nbStripes; n++ ) { \ + const xxh_u8* const in = input + n*XXH_STRIPE_LEN; \ + XXH_PREFETCH(in + XXH_PREFETCH_DIST); \ + XXH3_accumulate_512_##name( \ + acc, \ + in, \ + secret + n*XXH_SECRET_CONSUME_RATE); \ + } \ +} + + XXH_FORCE_INLINE void XXH_writeLE64(void* dst, xxh_u64 v64) { if (!XXH_CPU_LITTLE_ENDIAN) v64 = XXH_swap64(v64); @@ -3749,7 +4833,7 @@ XXH3_accumulate_512_avx512(void* XXH_RESTRICT acc, /* data_key = data_vec ^ key_vec; */ __m512i const data_key = _mm512_xor_si512 (data_vec, key_vec); /* data_key_lo = data_key >> 32; */ - __m512i const data_key_lo = _mm512_shuffle_epi32 (data_key, (_MM_PERM_ENUM)_MM_SHUFFLE(0, 3, 0, 1)); + __m512i const data_key_lo = _mm512_srli_epi64 (data_key, 32); /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */ __m512i const product = _mm512_mul_epu32 (data_key, data_key_lo); /* xacc[0] += swap(data_vec); */ @@ -3759,6 +4843,7 @@ XXH3_accumulate_512_avx512(void* XXH_RESTRICT acc, *xacc = _mm512_add_epi64(product, sum); } } +XXH_FORCE_INLINE XXH_TARGET_AVX512 XXH3_ACCUMULATE_TEMPLATE(avx512) /* * XXH3_scrambleAcc: Scrambles the accumulators to improve mixing. @@ -3792,13 +4877,12 @@ XXH3_scrambleAcc_avx512(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) /* xacc[0] ^= (xacc[0] >> 47) */ __m512i const acc_vec = *xacc; __m512i const shifted = _mm512_srli_epi64 (acc_vec, 47); - __m512i const data_vec = _mm512_xor_si512 (acc_vec, shifted); /* xacc[0] ^= secret; */ __m512i const key_vec = _mm512_loadu_si512 (secret); - __m512i const data_key = _mm512_xor_si512 (data_vec, key_vec); + __m512i const data_key = _mm512_ternarylogic_epi32(key_vec, acc_vec, shifted, 0x96 /* key_vec ^ acc_vec ^ shifted */); /* xacc[0] *= XXH_PRIME32_1; */ - __m512i const data_key_hi = _mm512_shuffle_epi32 (data_key, (_MM_PERM_ENUM)_MM_SHUFFLE(0, 3, 0, 1)); + __m512i const data_key_hi = _mm512_srli_epi64 (data_key, 32); __m512i const prod_lo = _mm512_mul_epu32 (data_key, prime32); __m512i const prod_hi = _mm512_mul_epu32 (data_key_hi, prime32); *xacc = _mm512_add_epi64(prod_lo, _mm512_slli_epi64(prod_hi, 32)); @@ -3813,7 +4897,8 @@ XXH3_initCustomSecret_avx512(void* XXH_RESTRICT customSecret, xxh_u64 seed64) XXH_ASSERT(((size_t)customSecret & 63) == 0); (void)(&XXH_writeLE64); { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m512i); - __m512i const seed = _mm512_mask_set1_epi64(_mm512_set1_epi64((xxh_i64)seed64), 0xAA, (xxh_i64)(0U - seed64)); + __m512i const seed_pos = _mm512_set1_epi64((xxh_i64)seed64); + __m512i const seed = _mm512_mask_sub_epi64(seed_pos, 0xAA, _mm512_set1_epi8(0), seed_pos); const __m512i* const src = (const __m512i*) ((const void*) XXH3_kSecret); __m512i* const dest = ( __m512i*) customSecret; @@ -3821,14 +4906,7 @@ XXH3_initCustomSecret_avx512(void* XXH_RESTRICT customSecret, xxh_u64 seed64) XXH_ASSERT(((size_t)src & 63) == 0); /* control alignment */ XXH_ASSERT(((size_t)dest & 63) == 0); for (i=0; i < nbRounds; ++i) { - /* GCC has a bug, _mm512_stream_load_si512 accepts 'void*', not 'void const*', - * this will warn "discards 'const' qualifier". */ - union { - const __m512i* cp; - void* p; - } remote_const_void; - remote_const_void.cp = src + i; - dest[i] = _mm512_add_epi64(_mm512_stream_load_si512(remote_const_void.p), seed); + dest[i] = _mm512_add_epi64(_mm512_load_si512(src + i), seed); } } } @@ -3864,7 +4942,7 @@ XXH3_accumulate_512_avx2( void* XXH_RESTRICT acc, /* data_key = data_vec ^ key_vec; */ __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec); /* data_key_lo = data_key >> 32; */ - __m256i const data_key_lo = _mm256_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1)); + __m256i const data_key_lo = _mm256_srli_epi64 (data_key, 32); /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */ __m256i const product = _mm256_mul_epu32 (data_key, data_key_lo); /* xacc[i] += swap(data_vec); */ @@ -3874,6 +4952,7 @@ XXH3_accumulate_512_avx2( void* XXH_RESTRICT acc, xacc[i] = _mm256_add_epi64(product, sum); } } } +XXH_FORCE_INLINE XXH_TARGET_AVX2 XXH3_ACCUMULATE_TEMPLATE(avx2) XXH_FORCE_INLINE XXH_TARGET_AVX2 void XXH3_scrambleAcc_avx2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) @@ -3896,7 +4975,7 @@ XXH3_scrambleAcc_avx2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec); /* xacc[i] *= XXH_PRIME32_1; */ - __m256i const data_key_hi = _mm256_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1)); + __m256i const data_key_hi = _mm256_srli_epi64 (data_key, 32); __m256i const prod_lo = _mm256_mul_epu32 (data_key, prime32); __m256i const prod_hi = _mm256_mul_epu32 (data_key_hi, prime32); xacc[i] = _mm256_add_epi64(prod_lo, _mm256_slli_epi64(prod_hi, 32)); @@ -3928,12 +5007,12 @@ XXH_FORCE_INLINE XXH_TARGET_AVX2 void XXH3_initCustomSecret_avx2(void* XXH_RESTR XXH_ASSERT(((size_t)dest & 31) == 0); /* GCC -O2 need unroll loop manually */ - dest[0] = _mm256_add_epi64(_mm256_stream_load_si256(src+0), seed); - dest[1] = _mm256_add_epi64(_mm256_stream_load_si256(src+1), seed); - dest[2] = _mm256_add_epi64(_mm256_stream_load_si256(src+2), seed); - dest[3] = _mm256_add_epi64(_mm256_stream_load_si256(src+3), seed); - dest[4] = _mm256_add_epi64(_mm256_stream_load_si256(src+4), seed); - dest[5] = _mm256_add_epi64(_mm256_stream_load_si256(src+5), seed); + dest[0] = _mm256_add_epi64(_mm256_load_si256(src+0), seed); + dest[1] = _mm256_add_epi64(_mm256_load_si256(src+1), seed); + dest[2] = _mm256_add_epi64(_mm256_load_si256(src+2), seed); + dest[3] = _mm256_add_epi64(_mm256_load_si256(src+3), seed); + dest[4] = _mm256_add_epi64(_mm256_load_si256(src+4), seed); + dest[5] = _mm256_add_epi64(_mm256_load_si256(src+5), seed); } } @@ -3980,6 +5059,7 @@ XXH3_accumulate_512_sse2( void* XXH_RESTRICT acc, xacc[i] = _mm_add_epi64(product, sum); } } } +XXH_FORCE_INLINE XXH_TARGET_SSE2 XXH3_ACCUMULATE_TEMPLATE(sse2) XXH_FORCE_INLINE XXH_TARGET_SSE2 void XXH3_scrambleAcc_sse2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) @@ -4058,14 +5138,28 @@ XXH3_scalarScrambleRound(void* XXH_RESTRICT acc, /*! * @internal - * @brief The bulk processing loop for NEON. + * @brief The bulk processing loop for NEON and WASM SIMD128. * * The NEON code path is actually partially scalar when running on AArch64. This * is to optimize the pipelining and can have up to 15% speedup depending on the * CPU, and it also mitigates some GCC codegen issues. * * @see XXH3_NEON_LANES for configuring this and details about this optimization. + * + * NEON's 32-bit to 64-bit long multiply takes a half vector of 32-bit + * integers instead of the other platforms which mask full 64-bit vectors, + * so the setup is more complicated than just shifting right. + * + * Additionally, there is an optimization for 4 lanes at once noted below. + * + * Since, as stated, the most optimal amount of lanes for Cortexes is 6, + * there needs to be *three* versions of the accumulate operation used + * for the remaining 2 lanes. + * + * WASM's SIMD128 uses SIMDe's arm_neon.h polyfill because the intrinsics overlap + * nearly perfectly. */ + XXH_FORCE_INLINE void XXH3_accumulate_512_neon( void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, @@ -4073,101 +5167,182 @@ XXH3_accumulate_512_neon( void* XXH_RESTRICT acc, { XXH_ASSERT((((size_t)acc) & 15) == 0); XXH_STATIC_ASSERT(XXH3_NEON_LANES > 0 && XXH3_NEON_LANES <= XXH_ACC_NB && XXH3_NEON_LANES % 2 == 0); - { - uint64x2_t* const xacc = (uint64x2_t *) acc; + { /* GCC for darwin arm64 does not like aliasing here */ + xxh_aliasing_uint64x2_t* const xacc = (xxh_aliasing_uint64x2_t*) acc; /* We don't use a uint32x4_t pointer because it causes bus errors on ARMv7. */ - uint8_t const* const xinput = (const uint8_t *) input; - uint8_t const* const xsecret = (const uint8_t *) secret; + uint8_t const* xinput = (const uint8_t *) input; + uint8_t const* xsecret = (const uint8_t *) secret; size_t i; - /* NEON for the first few lanes (these loops are normally interleaved) */ - for (i=0; i < XXH3_NEON_LANES / 2; i++) { - /* data_vec = xinput[i]; */ - uint8x16_t data_vec = vld1q_u8(xinput + (i * 16)); - /* key_vec = xsecret[i]; */ - uint8x16_t key_vec = vld1q_u8(xsecret + (i * 16)); - uint64x2_t data_key; - uint32x2_t data_key_lo, data_key_hi; - /* xacc[i] += swap(data_vec); */ - uint64x2_t const data64 = vreinterpretq_u64_u8(data_vec); - uint64x2_t const swapped = vextq_u64(data64, data64, 1); - xacc[i] = vaddq_u64 (xacc[i], swapped); - /* data_key = data_vec ^ key_vec; */ - data_key = vreinterpretq_u64_u8(veorq_u8(data_vec, key_vec)); - /* data_key_lo = (uint32x2_t) (data_key & 0xFFFFFFFF); - * data_key_hi = (uint32x2_t) (data_key >> 32); - * data_key = UNDEFINED; */ - XXH_SPLIT_IN_PLACE(data_key, data_key_lo, data_key_hi); - /* xacc[i] += (uint64x2_t) data_key_lo * (uint64x2_t) data_key_hi; */ - xacc[i] = vmlal_u32 (xacc[i], data_key_lo, data_key_hi); - - } - /* Scalar for the remainder. This may be a zero iteration loop. */ +#ifdef __wasm_simd128__ + /* + * On WASM SIMD128, Clang emits direct address loads when XXH3_kSecret + * is constant propagated, which results in it converting it to this + * inside the loop: + * + * a = v128.load(XXH3_kSecret + 0 + $secret_offset, offset = 0) + * b = v128.load(XXH3_kSecret + 16 + $secret_offset, offset = 0) + * ... + * + * This requires a full 32-bit address immediate (and therefore a 6 byte + * instruction) as well as an add for each offset. + * + * Putting an asm guard prevents it from folding (at the cost of losing + * the alignment hint), and uses the free offset in `v128.load` instead + * of adding secret_offset each time which overall reduces code size by + * about a kilobyte and improves performance. + */ + XXH_COMPILER_GUARD(xsecret); +#endif + /* Scalar lanes use the normal scalarRound routine */ for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) { XXH3_scalarRound(acc, input, secret, i); } + i = 0; + /* 4 NEON lanes at a time. */ + for (; i+1 < XXH3_NEON_LANES / 2; i+=2) { + /* data_vec = xinput[i]; */ + uint64x2_t data_vec_1 = XXH_vld1q_u64(xinput + (i * 16)); + uint64x2_t data_vec_2 = XXH_vld1q_u64(xinput + ((i+1) * 16)); + /* key_vec = xsecret[i]; */ + uint64x2_t key_vec_1 = XXH_vld1q_u64(xsecret + (i * 16)); + uint64x2_t key_vec_2 = XXH_vld1q_u64(xsecret + ((i+1) * 16)); + /* data_swap = swap(data_vec) */ + uint64x2_t data_swap_1 = vextq_u64(data_vec_1, data_vec_1, 1); + uint64x2_t data_swap_2 = vextq_u64(data_vec_2, data_vec_2, 1); + /* data_key = data_vec ^ key_vec; */ + uint64x2_t data_key_1 = veorq_u64(data_vec_1, key_vec_1); + uint64x2_t data_key_2 = veorq_u64(data_vec_2, key_vec_2); + + /* + * If we reinterpret the 64x2 vectors as 32x4 vectors, we can use a + * de-interleave operation for 4 lanes in 1 step with `vuzpq_u32` to + * get one vector with the low 32 bits of each lane, and one vector + * with the high 32 bits of each lane. + * + * The intrinsic returns a double vector because the original ARMv7-a + * instruction modified both arguments in place. AArch64 and SIMD128 emit + * two instructions from this intrinsic. + * + * [ dk11L | dk11H | dk12L | dk12H ] -> [ dk11L | dk12L | dk21L | dk22L ] + * [ dk21L | dk21H | dk22L | dk22H ] -> [ dk11H | dk12H | dk21H | dk22H ] + */ + uint32x4x2_t unzipped = vuzpq_u32( + vreinterpretq_u32_u64(data_key_1), + vreinterpretq_u32_u64(data_key_2) + ); + /* data_key_lo = data_key & 0xFFFFFFFF */ + uint32x4_t data_key_lo = unzipped.val[0]; + /* data_key_hi = data_key >> 32 */ + uint32x4_t data_key_hi = unzipped.val[1]; + /* + * Then, we can split the vectors horizontally and multiply which, as for most + * widening intrinsics, have a variant that works on both high half vectors + * for free on AArch64. A similar instruction is available on SIMD128. + * + * sum = data_swap + (u64x2) data_key_lo * (u64x2) data_key_hi + */ + uint64x2_t sum_1 = XXH_vmlal_low_u32(data_swap_1, data_key_lo, data_key_hi); + uint64x2_t sum_2 = XXH_vmlal_high_u32(data_swap_2, data_key_lo, data_key_hi); + /* + * Clang reorders + * a += b * c; // umlal swap.2d, dkl.2s, dkh.2s + * c += a; // add acc.2d, acc.2d, swap.2d + * to + * c += a; // add acc.2d, acc.2d, swap.2d + * c += b * c; // umlal acc.2d, dkl.2s, dkh.2s + * + * While it would make sense in theory since the addition is faster, + * for reasons likely related to umlal being limited to certain NEON + * pipelines, this is worse. A compiler guard fixes this. + */ + XXH_COMPILER_GUARD_CLANG_NEON(sum_1); + XXH_COMPILER_GUARD_CLANG_NEON(sum_2); + /* xacc[i] = acc_vec + sum; */ + xacc[i] = vaddq_u64(xacc[i], sum_1); + xacc[i+1] = vaddq_u64(xacc[i+1], sum_2); + } + /* Operate on the remaining NEON lanes 2 at a time. */ + for (; i < XXH3_NEON_LANES / 2; i++) { + /* data_vec = xinput[i]; */ + uint64x2_t data_vec = XXH_vld1q_u64(xinput + (i * 16)); + /* key_vec = xsecret[i]; */ + uint64x2_t key_vec = XXH_vld1q_u64(xsecret + (i * 16)); + /* acc_vec_2 = swap(data_vec) */ + uint64x2_t data_swap = vextq_u64(data_vec, data_vec, 1); + /* data_key = data_vec ^ key_vec; */ + uint64x2_t data_key = veorq_u64(data_vec, key_vec); + /* For two lanes, just use VMOVN and VSHRN. */ + /* data_key_lo = data_key & 0xFFFFFFFF; */ + uint32x2_t data_key_lo = vmovn_u64(data_key); + /* data_key_hi = data_key >> 32; */ + uint32x2_t data_key_hi = vshrn_n_u64(data_key, 32); + /* sum = data_swap + (u64x2) data_key_lo * (u64x2) data_key_hi; */ + uint64x2_t sum = vmlal_u32(data_swap, data_key_lo, data_key_hi); + /* Same Clang workaround as before */ + XXH_COMPILER_GUARD_CLANG_NEON(sum); + /* xacc[i] = acc_vec + sum; */ + xacc[i] = vaddq_u64 (xacc[i], sum); + } } } +XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(neon) XXH_FORCE_INLINE void XXH3_scrambleAcc_neon(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 15) == 0); - { uint64x2_t* xacc = (uint64x2_t*) acc; + { xxh_aliasing_uint64x2_t* xacc = (xxh_aliasing_uint64x2_t*) acc; uint8_t const* xsecret = (uint8_t const*) secret; - uint32x2_t prime = vdup_n_u32 (XXH_PRIME32_1); size_t i; - /* NEON for the first few lanes (these loops are normally interleaved) */ - for (i=0; i < XXH3_NEON_LANES / 2; i++) { - /* xacc[i] ^= (xacc[i] >> 47); */ - uint64x2_t acc_vec = xacc[i]; - uint64x2_t shifted = vshrq_n_u64 (acc_vec, 47); - uint64x2_t data_vec = veorq_u64 (acc_vec, shifted); + /* WASM uses operator overloads and doesn't need these. */ +#ifndef __wasm_simd128__ + /* { prime32_1, prime32_1 } */ + uint32x2_t const kPrimeLo = vdup_n_u32(XXH_PRIME32_1); + /* { 0, prime32_1, 0, prime32_1 } */ + uint32x4_t const kPrimeHi = vreinterpretq_u32_u64(vdupq_n_u64((xxh_u64)XXH_PRIME32_1 << 32)); +#endif - /* xacc[i] ^= xsecret[i]; */ - uint8x16_t key_vec = vld1q_u8 (xsecret + (i * 16)); - uint64x2_t data_key = veorq_u64 (data_vec, vreinterpretq_u64_u8(key_vec)); - - /* xacc[i] *= XXH_PRIME32_1 */ - uint32x2_t data_key_lo, data_key_hi; - /* data_key_lo = (uint32x2_t) (xacc[i] & 0xFFFFFFFF); - * data_key_hi = (uint32x2_t) (xacc[i] >> 32); - * xacc[i] = UNDEFINED; */ - XXH_SPLIT_IN_PLACE(data_key, data_key_lo, data_key_hi); - { /* - * prod_hi = (data_key >> 32) * XXH_PRIME32_1; - * - * Avoid vmul_u32 + vshll_n_u32 since Clang 6 and 7 will - * incorrectly "optimize" this: - * tmp = vmul_u32(vmovn_u64(a), vmovn_u64(b)); - * shifted = vshll_n_u32(tmp, 32); - * to this: - * tmp = "vmulq_u64"(a, b); // no such thing! - * shifted = vshlq_n_u64(tmp, 32); - * - * However, unlike SSE, Clang lacks a 64-bit multiply routine - * for NEON, and it scalarizes two 64-bit multiplies instead. - * - * vmull_u32 has the same timing as vmul_u32, and it avoids - * this bug completely. - * See https://bugs.llvm.org/show_bug.cgi?id=39967 - */ - uint64x2_t prod_hi = vmull_u32 (data_key_hi, prime); - /* xacc[i] = prod_hi << 32; */ - xacc[i] = vshlq_n_u64(prod_hi, 32); - /* xacc[i] += (prod_hi & 0xFFFFFFFF) * XXH_PRIME32_1; */ - xacc[i] = vmlal_u32(xacc[i], data_key_lo, prime); - } - } - /* Scalar for the remainder. This may be a zero iteration loop. */ + /* AArch64 uses both scalar and neon at the same time */ for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) { XXH3_scalarScrambleRound(acc, secret, i); } + for (i=0; i < XXH3_NEON_LANES / 2; i++) { + /* xacc[i] ^= (xacc[i] >> 47); */ + uint64x2_t acc_vec = xacc[i]; + uint64x2_t shifted = vshrq_n_u64(acc_vec, 47); + uint64x2_t data_vec = veorq_u64(acc_vec, shifted); + + /* xacc[i] ^= xsecret[i]; */ + uint64x2_t key_vec = XXH_vld1q_u64(xsecret + (i * 16)); + uint64x2_t data_key = veorq_u64(data_vec, key_vec); + /* xacc[i] *= XXH_PRIME32_1 */ +#ifdef __wasm_simd128__ + /* SIMD128 has multiply by u64x2, use it instead of expanding and scalarizing */ + xacc[i] = data_key * XXH_PRIME32_1; +#else + /* + * Expanded version with portable NEON intrinsics + * + * lo(x) * lo(y) + (hi(x) * lo(y) << 32) + * + * prod_hi = hi(data_key) * lo(prime) << 32 + * + * Since we only need 32 bits of this multiply a trick can be used, reinterpreting the vector + * as a uint32x4_t and multiplying by { 0, prime, 0, prime } to cancel out the unwanted bits + * and avoid the shift. + */ + uint32x4_t prod_hi = vmulq_u32 (vreinterpretq_u32_u64(data_key), kPrimeHi); + /* Extract low bits for vmlal_u32 */ + uint32x2_t data_key_lo = vmovn_u64(data_key); + /* xacc[i] = prod_hi + lo(data_key) * XXH_PRIME32_1; */ + xacc[i] = vmlal_u32(vreinterpretq_u64_u32(prod_hi), data_key_lo, kPrimeLo); +#endif + } } } - #endif #if (XXH_VECTOR == XXH_VSX) @@ -4178,23 +5353,23 @@ XXH3_accumulate_512_vsx( void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { /* presumed aligned */ - unsigned int* const xacc = (unsigned int*) acc; - xxh_u64x2 const* const xinput = (xxh_u64x2 const*) input; /* no alignment restriction */ - xxh_u64x2 const* const xsecret = (xxh_u64x2 const*) secret; /* no alignment restriction */ + xxh_aliasing_u64x2* const xacc = (xxh_aliasing_u64x2*) acc; + xxh_u8 const* const xinput = (xxh_u8 const*) input; /* no alignment restriction */ + xxh_u8 const* const xsecret = (xxh_u8 const*) secret; /* no alignment restriction */ xxh_u64x2 const v32 = { 32, 32 }; size_t i; for (i = 0; i < XXH_STRIPE_LEN / sizeof(xxh_u64x2); i++) { /* data_vec = xinput[i]; */ - xxh_u64x2 const data_vec = XXH_vec_loadu(xinput + i); + xxh_u64x2 const data_vec = XXH_vec_loadu(xinput + 16*i); /* key_vec = xsecret[i]; */ - xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + i); + xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + 16*i); xxh_u64x2 const data_key = data_vec ^ key_vec; /* shuffled = (data_key << 32) | (data_key >> 32); */ xxh_u32x4 const shuffled = (xxh_u32x4)vec_rl(data_key, v32); /* product = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)shuffled & 0xFFFFFFFF); */ xxh_u64x2 const product = XXH_vec_mulo((xxh_u32x4)data_key, shuffled); /* acc_vec = xacc[i]; */ - xxh_u64x2 acc_vec = (xxh_u64x2)vec_xl(0, xacc + 4 * i); + xxh_u64x2 acc_vec = xacc[i]; acc_vec += product; /* swap high and low halves */ @@ -4203,18 +5378,18 @@ XXH3_accumulate_512_vsx( void* XXH_RESTRICT acc, #else acc_vec += vec_xxpermdi(data_vec, data_vec, 2); #endif - /* xacc[i] = acc_vec; */ - vec_xst((xxh_u32x4)acc_vec, 0, xacc + 4 * i); + xacc[i] = acc_vec; } } +XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(vsx) XXH_FORCE_INLINE void XXH3_scrambleAcc_vsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 15) == 0); - { xxh_u64x2* const xacc = (xxh_u64x2*) acc; - const xxh_u64x2* const xsecret = (const xxh_u64x2*) secret; + { xxh_aliasing_u64x2* const xacc = (xxh_aliasing_u64x2*) acc; + const xxh_u8* const xsecret = (const xxh_u8*) secret; /* constants */ xxh_u64x2 const v32 = { 32, 32 }; xxh_u64x2 const v47 = { 47, 47 }; @@ -4226,7 +5401,7 @@ XXH3_scrambleAcc_vsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) xxh_u64x2 const data_vec = acc_vec ^ (acc_vec >> v47); /* xacc[i] ^= xsecret[i]; */ - xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + i); + xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + 16*i); xxh_u64x2 const data_key = data_vec ^ key_vec; /* xacc[i] *= XXH_PRIME32_1 */ @@ -4240,8 +5415,148 @@ XXH3_scrambleAcc_vsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) #endif +#if (XXH_VECTOR == XXH_SVE) + +XXH_FORCE_INLINE void +XXH3_accumulate_512_sve( void* XXH_RESTRICT acc, + const void* XXH_RESTRICT input, + const void* XXH_RESTRICT secret) +{ + uint64_t *xacc = (uint64_t *)acc; + const uint64_t *xinput = (const uint64_t *)(const void *)input; + const uint64_t *xsecret = (const uint64_t *)(const void *)secret; + svuint64_t kSwap = sveor_n_u64_z(svptrue_b64(), svindex_u64(0, 1), 1); + uint64_t element_count = svcntd(); + if (element_count >= 8) { + svbool_t mask = svptrue_pat_b64(SV_VL8); + svuint64_t vacc = svld1_u64(mask, xacc); + ACCRND(vacc, 0); + svst1_u64(mask, xacc, vacc); + } else if (element_count == 2) { /* sve128 */ + svbool_t mask = svptrue_pat_b64(SV_VL2); + svuint64_t acc0 = svld1_u64(mask, xacc + 0); + svuint64_t acc1 = svld1_u64(mask, xacc + 2); + svuint64_t acc2 = svld1_u64(mask, xacc + 4); + svuint64_t acc3 = svld1_u64(mask, xacc + 6); + ACCRND(acc0, 0); + ACCRND(acc1, 2); + ACCRND(acc2, 4); + ACCRND(acc3, 6); + svst1_u64(mask, xacc + 0, acc0); + svst1_u64(mask, xacc + 2, acc1); + svst1_u64(mask, xacc + 4, acc2); + svst1_u64(mask, xacc + 6, acc3); + } else { + svbool_t mask = svptrue_pat_b64(SV_VL4); + svuint64_t acc0 = svld1_u64(mask, xacc + 0); + svuint64_t acc1 = svld1_u64(mask, xacc + 4); + ACCRND(acc0, 0); + ACCRND(acc1, 4); + svst1_u64(mask, xacc + 0, acc0); + svst1_u64(mask, xacc + 4, acc1); + } +} + +XXH_FORCE_INLINE void +XXH3_accumulate_sve(xxh_u64* XXH_RESTRICT acc, + const xxh_u8* XXH_RESTRICT input, + const xxh_u8* XXH_RESTRICT secret, + size_t nbStripes) +{ + if (nbStripes != 0) { + uint64_t *xacc = (uint64_t *)acc; + const uint64_t *xinput = (const uint64_t *)(const void *)input; + const uint64_t *xsecret = (const uint64_t *)(const void *)secret; + svuint64_t kSwap = sveor_n_u64_z(svptrue_b64(), svindex_u64(0, 1), 1); + uint64_t element_count = svcntd(); + if (element_count >= 8) { + svbool_t mask = svptrue_pat_b64(SV_VL8); + svuint64_t vacc = svld1_u64(mask, xacc + 0); + do { + /* svprfd(svbool_t, void *, enum svfprop); */ + svprfd(mask, xinput + 128, SV_PLDL1STRM); + ACCRND(vacc, 0); + xinput += 8; + xsecret += 1; + nbStripes--; + } while (nbStripes != 0); + + svst1_u64(mask, xacc + 0, vacc); + } else if (element_count == 2) { /* sve128 */ + svbool_t mask = svptrue_pat_b64(SV_VL2); + svuint64_t acc0 = svld1_u64(mask, xacc + 0); + svuint64_t acc1 = svld1_u64(mask, xacc + 2); + svuint64_t acc2 = svld1_u64(mask, xacc + 4); + svuint64_t acc3 = svld1_u64(mask, xacc + 6); + do { + svprfd(mask, xinput + 128, SV_PLDL1STRM); + ACCRND(acc0, 0); + ACCRND(acc1, 2); + ACCRND(acc2, 4); + ACCRND(acc3, 6); + xinput += 8; + xsecret += 1; + nbStripes--; + } while (nbStripes != 0); + + svst1_u64(mask, xacc + 0, acc0); + svst1_u64(mask, xacc + 2, acc1); + svst1_u64(mask, xacc + 4, acc2); + svst1_u64(mask, xacc + 6, acc3); + } else { + svbool_t mask = svptrue_pat_b64(SV_VL4); + svuint64_t acc0 = svld1_u64(mask, xacc + 0); + svuint64_t acc1 = svld1_u64(mask, xacc + 4); + do { + svprfd(mask, xinput + 128, SV_PLDL1STRM); + ACCRND(acc0, 0); + ACCRND(acc1, 4); + xinput += 8; + xsecret += 1; + nbStripes--; + } while (nbStripes != 0); + + svst1_u64(mask, xacc + 0, acc0); + svst1_u64(mask, xacc + 4, acc1); + } + } +} + +#endif + /* scalar variants - universal */ +#if defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) +/* + * In XXH3_scalarRound(), GCC and Clang have a similar codegen issue, where they + * emit an excess mask and a full 64-bit multiply-add (MADD X-form). + * + * While this might not seem like much, as AArch64 is a 64-bit architecture, only + * big Cortex designs have a full 64-bit multiplier. + * + * On the little cores, the smaller 32-bit multiplier is used, and full 64-bit + * multiplies expand to 2-3 multiplies in microcode. This has a major penalty + * of up to 4 latency cycles and 2 stall cycles in the multiply pipeline. + * + * Thankfully, AArch64 still provides the 32-bit long multiply-add (UMADDL) which does + * not have this penalty and does the mask automatically. + */ +XXH_FORCE_INLINE xxh_u64 +XXH_mult32to64_add64(xxh_u64 lhs, xxh_u64 rhs, xxh_u64 acc) +{ + xxh_u64 ret; + /* note: %x = 64-bit register, %w = 32-bit register */ + __asm__("umaddl %x0, %w1, %w2, %x3" : "=r" (ret) : "r" (lhs), "r" (rhs), "r" (acc)); + return ret; +} +#else +XXH_FORCE_INLINE xxh_u64 +XXH_mult32to64_add64(xxh_u64 lhs, xxh_u64 rhs, xxh_u64 acc) +{ + return XXH_mult32to64((xxh_u32)lhs, (xxh_u32)rhs) + acc; +} +#endif + /*! * @internal * @brief Scalar round for @ref XXH3_accumulate_512_scalar(). @@ -4264,7 +5579,7 @@ XXH3_scalarRound(void* XXH_RESTRICT acc, xxh_u64 const data_val = XXH_readLE64(xinput + lane * 8); xxh_u64 const data_key = data_val ^ XXH_readLE64(xsecret + lane * 8); xacc[lane ^ 1] += data_val; /* swap adjacent lanes */ - xacc[lane] += XXH_mult32to64(data_key & 0xFFFFFFFF, data_key >> 32); + xacc[lane] = XXH_mult32to64_add64(data_key /* & 0xFFFFFFFF */, data_key >> 32, xacc[lane]); } } @@ -4278,10 +5593,18 @@ XXH3_accumulate_512_scalar(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { size_t i; + /* ARM GCC refuses to unroll this loop, resulting in a 24% slowdown on ARMv6. */ +#if defined(__GNUC__) && !defined(__clang__) \ + && (defined(__arm__) || defined(__thumb2__)) \ + && defined(__ARM_FEATURE_UNALIGNED) /* no unaligned access just wastes bytes */ \ + && XXH_SIZE_OPT <= 0 +# pragma GCC unroll 8 +#endif for (i=0; i < XXH_ACC_NB; i++) { XXH3_scalarRound(acc, input, secret, i); } } +XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(scalar) /*! * @internal @@ -4333,10 +5656,10 @@ XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64) const xxh_u8* kSecretPtr = XXH3_kSecret; XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0); -#if defined(__clang__) && defined(__aarch64__) +#if defined(__GNUC__) && defined(__aarch64__) /* * UGLY HACK: - * Clang generates a bunch of MOV/MOVK pairs for aarch64, and they are + * GCC and Clang generate a bunch of MOV/MOVK pairs for aarch64, and they are * placed sequentially, in order, at the top of the unrolled loop. * * While MOVK is great for generating constants (2 cycles for a 64-bit @@ -4351,7 +5674,7 @@ XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64) * ADD * SUB STR * STR - * By forcing loads from memory (as the asm line causes Clang to assume + * By forcing loads from memory (as the asm line causes the compiler to assume * that XXH3_kSecretPtr has been changed), the pipelines are used more * efficiently: * I L S @@ -4368,17 +5691,11 @@ XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64) */ XXH_COMPILER_GUARD(kSecretPtr); #endif - /* - * Note: in debug mode, this overrides the asm optimization - * and Clang will emit MOVK chains again. - */ - XXH_ASSERT(kSecretPtr == XXH3_kSecret); - { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / 16; int i; for (i=0; i < nbRounds; i++) { /* - * The asm hack causes Clang to assume that kSecretPtr aliases with + * The asm hack causes the compiler to assume that kSecretPtr aliases with * customSecret, and on aarch64, this prevented LDP from merging two * loads together for free. Putting the loads together before the stores * properly generates LDP. @@ -4391,7 +5708,7 @@ XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64) } -typedef void (*XXH3_f_accumulate_512)(void* XXH_RESTRICT, const void*, const void*); +typedef void (*XXH3_f_accumulate)(xxh_u64* XXH_RESTRICT, const xxh_u8* XXH_RESTRICT, const xxh_u8* XXH_RESTRICT, size_t); typedef void (*XXH3_f_scrambleAcc)(void* XXH_RESTRICT, const void*); typedef void (*XXH3_f_initCustomSecret)(void* XXH_RESTRICT, xxh_u64); @@ -4399,82 +5716,63 @@ typedef void (*XXH3_f_initCustomSecret)(void* XXH_RESTRICT, xxh_u64); #if (XXH_VECTOR == XXH_AVX512) #define XXH3_accumulate_512 XXH3_accumulate_512_avx512 +#define XXH3_accumulate XXH3_accumulate_avx512 #define XXH3_scrambleAcc XXH3_scrambleAcc_avx512 #define XXH3_initCustomSecret XXH3_initCustomSecret_avx512 #elif (XXH_VECTOR == XXH_AVX2) #define XXH3_accumulate_512 XXH3_accumulate_512_avx2 +#define XXH3_accumulate XXH3_accumulate_avx2 #define XXH3_scrambleAcc XXH3_scrambleAcc_avx2 #define XXH3_initCustomSecret XXH3_initCustomSecret_avx2 #elif (XXH_VECTOR == XXH_SSE2) #define XXH3_accumulate_512 XXH3_accumulate_512_sse2 +#define XXH3_accumulate XXH3_accumulate_sse2 #define XXH3_scrambleAcc XXH3_scrambleAcc_sse2 #define XXH3_initCustomSecret XXH3_initCustomSecret_sse2 #elif (XXH_VECTOR == XXH_NEON) #define XXH3_accumulate_512 XXH3_accumulate_512_neon +#define XXH3_accumulate XXH3_accumulate_neon #define XXH3_scrambleAcc XXH3_scrambleAcc_neon #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar #elif (XXH_VECTOR == XXH_VSX) #define XXH3_accumulate_512 XXH3_accumulate_512_vsx +#define XXH3_accumulate XXH3_accumulate_vsx #define XXH3_scrambleAcc XXH3_scrambleAcc_vsx #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar +#elif (XXH_VECTOR == XXH_SVE) +#define XXH3_accumulate_512 XXH3_accumulate_512_sve +#define XXH3_accumulate XXH3_accumulate_sve +#define XXH3_scrambleAcc XXH3_scrambleAcc_scalar +#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar + #else /* scalar */ #define XXH3_accumulate_512 XXH3_accumulate_512_scalar +#define XXH3_accumulate XXH3_accumulate_scalar #define XXH3_scrambleAcc XXH3_scrambleAcc_scalar #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar #endif - - -#ifndef XXH_PREFETCH_DIST -# ifdef __clang__ -# define XXH_PREFETCH_DIST 320 -# else -# if (XXH_VECTOR == XXH_AVX512) -# define XXH_PREFETCH_DIST 512 -# else -# define XXH_PREFETCH_DIST 384 -# endif -# endif /* __clang__ */ -#endif /* XXH_PREFETCH_DIST */ - -/* - * XXH3_accumulate() - * Loops over XXH3_accumulate_512(). - * Assumption: nbStripes will not overflow the secret size - */ -XXH_FORCE_INLINE void -XXH3_accumulate( xxh_u64* XXH_RESTRICT acc, - const xxh_u8* XXH_RESTRICT input, - const xxh_u8* XXH_RESTRICT secret, - size_t nbStripes, - XXH3_f_accumulate_512 f_acc512) -{ - size_t n; - for (n = 0; n < nbStripes; n++ ) { - const xxh_u8* const in = input + n*XXH_STRIPE_LEN; - XXH_PREFETCH(in + XXH_PREFETCH_DIST); - f_acc512(acc, - in, - secret + n*XXH_SECRET_CONSUME_RATE); - } -} +#if XXH_SIZE_OPT >= 1 /* don't do SIMD for initialization */ +# undef XXH3_initCustomSecret +# define XXH3_initCustomSecret XXH3_initCustomSecret_scalar +#endif XXH_FORCE_INLINE void XXH3_hashLong_internal_loop(xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, - XXH3_f_accumulate_512 f_acc512, + XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble) { size_t const nbStripesPerBlock = (secretSize - XXH_STRIPE_LEN) / XXH_SECRET_CONSUME_RATE; @@ -4486,7 +5784,7 @@ XXH3_hashLong_internal_loop(xxh_u64* XXH_RESTRICT acc, XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); for (n = 0; n < nb_blocks; n++) { - XXH3_accumulate(acc, input + n*block_len, secret, nbStripesPerBlock, f_acc512); + f_acc(acc, input + n*block_len, secret, nbStripesPerBlock); f_scramble(acc, secret + secretSize - XXH_STRIPE_LEN); } @@ -4494,12 +5792,12 @@ XXH3_hashLong_internal_loop(xxh_u64* XXH_RESTRICT acc, XXH_ASSERT(len > XXH_STRIPE_LEN); { size_t const nbStripes = ((len - 1) - (block_len * nb_blocks)) / XXH_STRIPE_LEN; XXH_ASSERT(nbStripes <= (secretSize / XXH_SECRET_CONSUME_RATE)); - XXH3_accumulate(acc, input + nb_blocks*block_len, secret, nbStripes, f_acc512); + f_acc(acc, input + nb_blocks*block_len, secret, nbStripes); /* last stripe */ { const xxh_u8* const p = input + len - XXH_STRIPE_LEN; #define XXH_SECRET_LASTACC_START 7 /* not aligned on 8, last secret is different from acc & scrambler */ - f_acc512(acc, p, secret + secretSize - XXH_STRIPE_LEN - XXH_SECRET_LASTACC_START); + XXH3_accumulate_512(acc, p, secret + secretSize - XXH_STRIPE_LEN - XXH_SECRET_LASTACC_START); } } } @@ -4544,12 +5842,12 @@ XXH3_mergeAccs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secre XXH_FORCE_INLINE XXH64_hash_t XXH3_hashLong_64b_internal(const void* XXH_RESTRICT input, size_t len, const void* XXH_RESTRICT secret, size_t secretSize, - XXH3_f_accumulate_512 f_acc512, + XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble) { XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC; - XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, f_acc512, f_scramble); + XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, f_acc, f_scramble); /* converge into final hash */ XXH_STATIC_ASSERT(sizeof(acc) == 64); @@ -4563,13 +5861,15 @@ XXH3_hashLong_64b_internal(const void* XXH_RESTRICT input, size_t len, * It's important for performance to transmit secret's size (when it's static) * so that the compiler can properly optimize the vectorized loop. * This makes a big performance difference for "medium" keys (<1 KB) when using AVX instruction set. + * When the secret size is unknown, or on GCC 12 where the mix of NO_INLINE and FORCE_INLINE + * breaks -Og, this is XXH_NO_INLINE. */ -XXH_FORCE_INLINE XXH64_hash_t +XXH3_WITH_SECRET_INLINE XXH64_hash_t XXH3_hashLong_64b_withSecret(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen) { (void)seed64; - return XXH3_hashLong_64b_internal(input, len, secret, secretLen, XXH3_accumulate_512, XXH3_scrambleAcc); + return XXH3_hashLong_64b_internal(input, len, secret, secretLen, XXH3_accumulate, XXH3_scrambleAcc); } /* @@ -4578,12 +5878,12 @@ XXH3_hashLong_64b_withSecret(const void* XXH_RESTRICT input, size_t len, * Note that inside this no_inline function, we do inline the internal loop, * and provide a statically defined secret size to allow optimization of vector loop. */ -XXH_NO_INLINE XXH64_hash_t +XXH_NO_INLINE XXH_PUREF XXH64_hash_t XXH3_hashLong_64b_default(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen) { (void)seed64; (void)secret; (void)secretLen; - return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512, XXH3_scrambleAcc); + return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate, XXH3_scrambleAcc); } /* @@ -4600,18 +5900,20 @@ XXH3_hashLong_64b_default(const void* XXH_RESTRICT input, size_t len, XXH_FORCE_INLINE XXH64_hash_t XXH3_hashLong_64b_withSeed_internal(const void* input, size_t len, XXH64_hash_t seed, - XXH3_f_accumulate_512 f_acc512, + XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble, XXH3_f_initCustomSecret f_initSec) { +#if XXH_SIZE_OPT <= 0 if (seed == 0) return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), - f_acc512, f_scramble); + f_acc, f_scramble); +#endif { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE]; f_initSec(secret, seed); return XXH3_hashLong_64b_internal(input, len, secret, sizeof(secret), - f_acc512, f_scramble); + f_acc, f_scramble); } } @@ -4619,12 +5921,12 @@ XXH3_hashLong_64b_withSeed_internal(const void* input, size_t len, * It's important for performance that XXH3_hashLong is not inlined. */ XXH_NO_INLINE XXH64_hash_t -XXH3_hashLong_64b_withSeed(const void* input, size_t len, - XXH64_hash_t seed, const xxh_u8* secret, size_t secretLen) +XXH3_hashLong_64b_withSeed(const void* XXH_RESTRICT input, size_t len, + XXH64_hash_t seed, const xxh_u8* XXH_RESTRICT secret, size_t secretLen) { (void)secret; (void)secretLen; return XXH3_hashLong_64b_withSeed_internal(input, len, seed, - XXH3_accumulate_512, XXH3_scrambleAcc, XXH3_initCustomSecret); + XXH3_accumulate, XXH3_scrambleAcc, XXH3_initCustomSecret); } @@ -4656,37 +5958,37 @@ XXH3_64bits_internal(const void* XXH_RESTRICT input, size_t len, /* === Public entry point === */ -/*! @ingroup xxh3_family */ -XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(const void* input, size_t len) +/*! @ingroup XXH3_family */ +XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(XXH_NOESCAPE const void* input, size_t length) { - return XXH3_64bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_default); + return XXH3_64bits_internal(input, length, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_default); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH64_hash_t -XXH3_64bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize) +XXH3_64bits_withSecret(XXH_NOESCAPE const void* input, size_t length, XXH_NOESCAPE const void* secret, size_t secretSize) { - return XXH3_64bits_internal(input, len, 0, secret, secretSize, XXH3_hashLong_64b_withSecret); + return XXH3_64bits_internal(input, length, 0, secret, secretSize, XXH3_hashLong_64b_withSecret); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH64_hash_t -XXH3_64bits_withSeed(const void* input, size_t len, XXH64_hash_t seed) +XXH3_64bits_withSeed(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed) { - return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed); + return XXH3_64bits_internal(input, length, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed); } XXH_PUBLIC_API XXH64_hash_t -XXH3_64bits_withSecretandSeed(const void* input, size_t len, const void* secret, size_t secretSize, XXH64_hash_t seed) +XXH3_64bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t length, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed) { - if (len <= XXH3_MIDSIZE_MAX) - return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL); - return XXH3_hashLong_64b_withSecret(input, len, seed, (const xxh_u8*)secret, secretSize); + if (length <= XXH3_MIDSIZE_MAX) + return XXH3_64bits_internal(input, length, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL); + return XXH3_hashLong_64b_withSecret(input, length, seed, (const xxh_u8*)secret, secretSize); } /* === XXH3 streaming === */ - +#ifndef XXH_NO_STREAM /* * Malloc's a pointer that is always aligned to align. * @@ -4710,7 +6012,7 @@ XXH3_64bits_withSecretandSeed(const void* input, size_t len, const void* secret, * * Align must be a power of 2 and 8 <= align <= 128. */ -static void* XXH_alignedMalloc(size_t s, size_t align) +static XXH_MALLOCF void* XXH_alignedMalloc(size_t s, size_t align) { XXH_ASSERT(align <= 128 && align >= 8); /* range check */ XXH_ASSERT((align & (align-1)) == 0); /* power of 2 */ @@ -4752,7 +6054,15 @@ static void XXH_alignedFree(void* p) XXH_free(base); } } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ +/*! + * @brief Allocate an @ref XXH3_state_t. + * + * @return An allocated pointer of @ref XXH3_state_t on success. + * @return `NULL` on failure. + * + * @note Must be freed with XXH3_freeState(). + */ XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void) { XXH3_state_t* const state = (XXH3_state_t*)XXH_alignedMalloc(sizeof(XXH3_state_t), 64); @@ -4761,16 +6071,25 @@ XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void) return state; } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ +/*! + * @brief Frees an @ref XXH3_state_t. + * + * @param statePtr A pointer to an @ref XXH3_state_t allocated with @ref XXH3_createState(). + * + * @return @ref XXH_OK. + * + * @note Must be allocated with XXH3_createState(). + */ XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr) { XXH_alignedFree(statePtr); return XXH_OK; } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API void -XXH3_copyState(XXH3_state_t* dst_state, const XXH3_state_t* src_state) +XXH3_copyState(XXH_NOESCAPE XXH3_state_t* dst_state, XXH_NOESCAPE const XXH3_state_t* src_state) { XXH_memcpy(dst_state, src_state, sizeof(*dst_state)); } @@ -4802,18 +6121,18 @@ XXH3_reset_internal(XXH3_state_t* statePtr, statePtr->nbStripesPerBlock = statePtr->secretLimit / XXH_SECRET_CONSUME_RATE; } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_64bits_reset(XXH3_state_t* statePtr) +XXH3_64bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr) { if (statePtr == NULL) return XXH_ERROR; XXH3_reset_internal(statePtr, 0, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE); return XXH_OK; } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize) +XXH3_64bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize) { if (statePtr == NULL) return XXH_ERROR; XXH3_reset_internal(statePtr, 0, secret, secretSize); @@ -4822,9 +6141,9 @@ XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t return XXH_OK; } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed) +XXH3_64bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed) { if (statePtr == NULL) return XXH_ERROR; if (seed==0) return XXH3_64bits_reset(statePtr); @@ -4834,9 +6153,9 @@ XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed) return XXH_OK; } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, size_t secretSize, XXH64_hash_t seed64) +XXH3_64bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed64) { if (statePtr == NULL) return XXH_ERROR; if (secret == NULL) return XXH_ERROR; @@ -4846,35 +6165,61 @@ XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, return XXH_OK; } -/* Note : when XXH3_consumeStripes() is invoked, - * there must be a guarantee that at least one more byte must be consumed from input - * so that the function can blindly consume all stripes using the "normal" secret segment */ -XXH_FORCE_INLINE void +/*! + * @internal + * @brief Processes a large input for XXH3_update() and XXH3_digest_long(). + * + * Unlike XXH3_hashLong_internal_loop(), this can process data that overlaps a block. + * + * @param acc Pointer to the 8 accumulator lanes + * @param nbStripesSoFarPtr In/out pointer to the number of leftover stripes in the block* + * @param nbStripesPerBlock Number of stripes in a block + * @param input Input pointer + * @param nbStripes Number of stripes to process + * @param secret Secret pointer + * @param secretLimit Offset of the last block in @p secret + * @param f_acc Pointer to an XXH3_accumulate implementation + * @param f_scramble Pointer to an XXH3_scrambleAcc implementation + * @return Pointer past the end of @p input after processing + */ +XXH_FORCE_INLINE const xxh_u8 * XXH3_consumeStripes(xxh_u64* XXH_RESTRICT acc, size_t* XXH_RESTRICT nbStripesSoFarPtr, size_t nbStripesPerBlock, const xxh_u8* XXH_RESTRICT input, size_t nbStripes, const xxh_u8* XXH_RESTRICT secret, size_t secretLimit, - XXH3_f_accumulate_512 f_acc512, + XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble) { - XXH_ASSERT(nbStripes <= nbStripesPerBlock); /* can handle max 1 scramble per invocation */ - XXH_ASSERT(*nbStripesSoFarPtr < nbStripesPerBlock); - if (nbStripesPerBlock - *nbStripesSoFarPtr <= nbStripes) { - /* need a scrambling operation */ - size_t const nbStripesToEndofBlock = nbStripesPerBlock - *nbStripesSoFarPtr; - size_t const nbStripesAfterBlock = nbStripes - nbStripesToEndofBlock; - XXH3_accumulate(acc, input, secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE, nbStripesToEndofBlock, f_acc512); - f_scramble(acc, secret + secretLimit); - XXH3_accumulate(acc, input + nbStripesToEndofBlock * XXH_STRIPE_LEN, secret, nbStripesAfterBlock, f_acc512); - *nbStripesSoFarPtr = nbStripesAfterBlock; - } else { - XXH3_accumulate(acc, input, secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE, nbStripes, f_acc512); + const xxh_u8* initialSecret = secret + *nbStripesSoFarPtr * XXH_SECRET_CONSUME_RATE; + /* Process full blocks */ + if (nbStripes >= (nbStripesPerBlock - *nbStripesSoFarPtr)) { + /* Process the initial partial block... */ + size_t nbStripesThisIter = nbStripesPerBlock - *nbStripesSoFarPtr; + + do { + /* Accumulate and scramble */ + f_acc(acc, input, initialSecret, nbStripesThisIter); + f_scramble(acc, secret + secretLimit); + input += nbStripesThisIter * XXH_STRIPE_LEN; + nbStripes -= nbStripesThisIter; + /* Then continue the loop with the full block size */ + nbStripesThisIter = nbStripesPerBlock; + initialSecret = secret; + } while (nbStripes >= nbStripesPerBlock); + *nbStripesSoFarPtr = 0; + } + /* Process a partial block */ + if (nbStripes > 0) { + f_acc(acc, input, initialSecret, nbStripes); + input += nbStripes * XXH_STRIPE_LEN; *nbStripesSoFarPtr += nbStripes; } + /* Return end pointer */ + return input; } #ifndef XXH3_STREAM_USE_STACK -# ifndef __clang__ /* clang doesn't need additional stack space */ +# if XXH_SIZE_OPT <= 0 && !defined(__clang__) /* clang doesn't need additional stack space */ # define XXH3_STREAM_USE_STACK 1 # endif #endif @@ -4884,7 +6229,7 @@ XXH3_consumeStripes(xxh_u64* XXH_RESTRICT acc, XXH_FORCE_INLINE XXH_errorcode XXH3_update(XXH3_state_t* XXH_RESTRICT const state, const xxh_u8* XXH_RESTRICT input, size_t len, - XXH3_f_accumulate_512 f_acc512, + XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble) { if (input==NULL) { @@ -4900,7 +6245,8 @@ XXH3_update(XXH3_state_t* XXH_RESTRICT const state, * when operating accumulators directly into state. * Operating into stack space seems to enable proper optimization. * clang, on the other hand, doesn't seem to need this trick */ - XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[8]; memcpy(acc, state->acc, sizeof(acc)); + XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[8]; + XXH_memcpy(acc, state->acc, sizeof(acc)); #else xxh_u64* XXH_RESTRICT const acc = state->acc; #endif @@ -4908,7 +6254,7 @@ XXH3_update(XXH3_state_t* XXH_RESTRICT const state, XXH_ASSERT(state->bufferedSize <= XXH3_INTERNALBUFFER_SIZE); /* small input : just fill in tmp buffer */ - if (state->bufferedSize + len <= XXH3_INTERNALBUFFER_SIZE) { + if (len <= XXH3_INTERNALBUFFER_SIZE - state->bufferedSize) { XXH_memcpy(state->buffer + state->bufferedSize, input, len); state->bufferedSize += (XXH32_hash_t)len; return XXH_OK; @@ -4930,57 +6276,20 @@ XXH3_update(XXH3_state_t* XXH_RESTRICT const state, &state->nbStripesSoFar, state->nbStripesPerBlock, state->buffer, XXH3_INTERNALBUFFER_STRIPES, secret, state->secretLimit, - f_acc512, f_scramble); + f_acc, f_scramble); state->bufferedSize = 0; } XXH_ASSERT(input < bEnd); - - /* large input to consume : ingest per full block */ - if ((size_t)(bEnd - input) > state->nbStripesPerBlock * XXH_STRIPE_LEN) { + if (bEnd - input > XXH3_INTERNALBUFFER_SIZE) { size_t nbStripes = (size_t)(bEnd - 1 - input) / XXH_STRIPE_LEN; - XXH_ASSERT(state->nbStripesPerBlock >= state->nbStripesSoFar); - /* join to current block's end */ - { size_t const nbStripesToEnd = state->nbStripesPerBlock - state->nbStripesSoFar; - XXH_ASSERT(nbStripesToEnd <= nbStripes); - XXH3_accumulate(acc, input, secret + state->nbStripesSoFar * XXH_SECRET_CONSUME_RATE, nbStripesToEnd, f_acc512); - f_scramble(acc, secret + state->secretLimit); - state->nbStripesSoFar = 0; - input += nbStripesToEnd * XXH_STRIPE_LEN; - nbStripes -= nbStripesToEnd; - } - /* consume per entire blocks */ - while(nbStripes >= state->nbStripesPerBlock) { - XXH3_accumulate(acc, input, secret, state->nbStripesPerBlock, f_acc512); - f_scramble(acc, secret + state->secretLimit); - input += state->nbStripesPerBlock * XXH_STRIPE_LEN; - nbStripes -= state->nbStripesPerBlock; - } - /* consume last partial block */ - XXH3_accumulate(acc, input, secret, nbStripes, f_acc512); - input += nbStripes * XXH_STRIPE_LEN; - XXH_ASSERT(input < bEnd); /* at least some bytes left */ - state->nbStripesSoFar = nbStripes; - /* buffer predecessor of last partial stripe */ - XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN); - XXH_ASSERT(bEnd - input <= XXH_STRIPE_LEN); - } else { - /* content to consume <= block size */ - /* Consume input by a multiple of internal buffer size */ - if (bEnd - input > XXH3_INTERNALBUFFER_SIZE) { - const xxh_u8* const limit = bEnd - XXH3_INTERNALBUFFER_SIZE; - do { - XXH3_consumeStripes(acc, + input = XXH3_consumeStripes(acc, &state->nbStripesSoFar, state->nbStripesPerBlock, - input, XXH3_INTERNALBUFFER_STRIPES, - secret, state->secretLimit, - f_acc512, f_scramble); - input += XXH3_INTERNALBUFFER_SIZE; - } while (inputbuffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN); - } - } + input, nbStripes, + secret, state->secretLimit, + f_acc, f_scramble); + XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN); + } /* Some remaining input (always) : buffer it */ XXH_ASSERT(input < bEnd); XXH_ASSERT(bEnd - input <= XXH3_INTERNALBUFFER_SIZE); @@ -4989,19 +6298,19 @@ XXH3_update(XXH3_state_t* XXH_RESTRICT const state, state->bufferedSize = (XXH32_hash_t)(bEnd-input); #if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1 /* save stack accumulators into state */ - memcpy(state->acc, acc, sizeof(acc)); + XXH_memcpy(state->acc, acc, sizeof(acc)); #endif } return XXH_OK; } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_64bits_update(XXH3_state_t* state, const void* input, size_t len) +XXH3_64bits_update(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len) { return XXH3_update(state, (const xxh_u8*)input, len, - XXH3_accumulate_512, XXH3_scrambleAcc); + XXH3_accumulate, XXH3_scrambleAcc); } @@ -5010,37 +6319,40 @@ XXH3_digest_long (XXH64_hash_t* acc, const XXH3_state_t* state, const unsigned char* secret) { + xxh_u8 lastStripe[XXH_STRIPE_LEN]; + const xxh_u8* lastStripePtr; + /* * Digest on a local copy. This way, the state remains unaltered, and it can * continue ingesting more input afterwards. */ XXH_memcpy(acc, state->acc, sizeof(state->acc)); if (state->bufferedSize >= XXH_STRIPE_LEN) { + /* Consume remaining stripes then point to remaining data in buffer */ size_t const nbStripes = (state->bufferedSize - 1) / XXH_STRIPE_LEN; size_t nbStripesSoFar = state->nbStripesSoFar; XXH3_consumeStripes(acc, &nbStripesSoFar, state->nbStripesPerBlock, state->buffer, nbStripes, secret, state->secretLimit, - XXH3_accumulate_512, XXH3_scrambleAcc); - /* last stripe */ - XXH3_accumulate_512(acc, - state->buffer + state->bufferedSize - XXH_STRIPE_LEN, - secret + state->secretLimit - XXH_SECRET_LASTACC_START); + XXH3_accumulate, XXH3_scrambleAcc); + lastStripePtr = state->buffer + state->bufferedSize - XXH_STRIPE_LEN; } else { /* bufferedSize < XXH_STRIPE_LEN */ - xxh_u8 lastStripe[XXH_STRIPE_LEN]; + /* Copy to temp buffer */ size_t const catchupSize = XXH_STRIPE_LEN - state->bufferedSize; XXH_ASSERT(state->bufferedSize > 0); /* there is always some input buffered */ XXH_memcpy(lastStripe, state->buffer + sizeof(state->buffer) - catchupSize, catchupSize); XXH_memcpy(lastStripe + catchupSize, state->buffer, state->bufferedSize); - XXH3_accumulate_512(acc, - lastStripe, - secret + state->secretLimit - XXH_SECRET_LASTACC_START); + lastStripePtr = lastStripe; } + /* Last stripe */ + XXH3_accumulate_512(acc, + lastStripePtr, + secret + state->secretLimit - XXH_SECRET_LASTACC_START); } -/*! @ingroup xxh3_family */ -XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* state) +/*! @ingroup XXH3_family */ +XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (XXH_NOESCAPE const XXH3_state_t* state) { const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret; if (state->totalLen > XXH3_MIDSIZE_MAX) { @@ -5056,7 +6368,7 @@ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* state) return XXH3_64bits_withSecret(state->buffer, (size_t)(state->totalLen), secret, state->secretLimit + XXH_STRIPE_LEN); } - +#endif /* !XXH_NO_STREAM */ /* ========================================== @@ -5076,7 +6388,7 @@ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* state) * fast for a _128-bit_ hash on 32-bit (it usually clears XXH64). */ -XXH_FORCE_INLINE XXH128_hash_t +XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t XXH3_len_1to3_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { /* A doubled version of 1to3_64b with different constants. */ @@ -5105,7 +6417,7 @@ XXH3_len_1to3_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_ } } -XXH_FORCE_INLINE XXH128_hash_t +XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t XXH3_len_4to8_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(input != NULL); @@ -5125,14 +6437,14 @@ XXH3_len_4to8_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_ m128.low64 ^= (m128.high64 >> 3); m128.low64 = XXH_xorshift64(m128.low64, 35); - m128.low64 *= 0x9FB21C651E98DF25ULL; + m128.low64 *= PRIME_MX2; m128.low64 = XXH_xorshift64(m128.low64, 28); m128.high64 = XXH3_avalanche(m128.high64); return m128; } } -XXH_FORCE_INLINE XXH128_hash_t +XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t XXH3_len_9to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(input != NULL); @@ -5207,7 +6519,7 @@ XXH3_len_9to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64 /* * Assumption: `secret` size is >= XXH3_SECRET_SIZE_MIN */ -XXH_FORCE_INLINE XXH128_hash_t +XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t XXH3_len_0to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(len <= 16); @@ -5238,7 +6550,7 @@ XXH128_mix32B(XXH128_hash_t acc, const xxh_u8* input_1, const xxh_u8* input_2, } -XXH_FORCE_INLINE XXH128_hash_t +XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, XXH64_hash_t seed) @@ -5249,6 +6561,16 @@ XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len, { XXH128_hash_t acc; acc.low64 = len * XXH_PRIME64_1; acc.high64 = 0; + +#if XXH_SIZE_OPT >= 1 + { + /* Smaller, but slightly slower. */ + unsigned int i = (unsigned int)(len - 1) / 32; + do { + acc = XXH128_mix32B(acc, input+16*i, input+len-16*(i+1), secret+32*i, seed); + } while (i-- != 0); + } +#else if (len > 32) { if (len > 64) { if (len > 96) { @@ -5259,6 +6581,7 @@ XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len, acc = XXH128_mix32B(acc, input+16, input+len-32, secret+32, seed); } acc = XXH128_mix32B(acc, input, input+len-16, secret, seed); +#endif { XXH128_hash_t h128; h128.low64 = acc.low64 + acc.high64; h128.high64 = (acc.low64 * XXH_PRIME64_1) @@ -5271,7 +6594,7 @@ XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len, } } -XXH_NO_INLINE XXH128_hash_t +XXH_NO_INLINE XXH_PUREF XXH128_hash_t XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, XXH64_hash_t seed) @@ -5280,25 +6603,34 @@ XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len, XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX); { XXH128_hash_t acc; - int const nbRounds = (int)len / 32; - int i; + unsigned i; acc.low64 = len * XXH_PRIME64_1; acc.high64 = 0; - for (i=0; i<4; i++) { + /* + * We set as `i` as offset + 32. We do this so that unchanged + * `len` can be used as upper bound. This reaches a sweet spot + * where both x86 and aarch64 get simple agen and good codegen + * for the loop. + */ + for (i = 32; i < 160; i += 32) { acc = XXH128_mix32B(acc, - input + (32 * i), - input + (32 * i) + 16, - secret + (32 * i), + input + i - 32, + input + i - 16, + secret + i - 32, seed); } acc.low64 = XXH3_avalanche(acc.low64); acc.high64 = XXH3_avalanche(acc.high64); - XXH_ASSERT(nbRounds >= 4); - for (i=4 ; i < nbRounds; i++) { + /* + * NB: `i <= len` will duplicate the last 32-bytes if + * len % 32 was zero. This is an unfortunate necessity to keep + * the hash result stable. + */ + for (i=160; i <= len; i += 32) { acc = XXH128_mix32B(acc, - input + (32 * i), - input + (32 * i) + 16, - secret + XXH3_MIDSIZE_STARTOFFSET + (32 * (i - 4)), + input + i - 32, + input + i - 16, + secret + XXH3_MIDSIZE_STARTOFFSET + i - 160, seed); } /* last bytes */ @@ -5306,7 +6638,7 @@ XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len, input + len - 16, input + len - 32, secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET - 16, - 0ULL - seed); + (XXH64_hash_t)0 - seed); { XXH128_hash_t h128; h128.low64 = acc.low64 + acc.high64; @@ -5323,12 +6655,12 @@ XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len, XXH_FORCE_INLINE XXH128_hash_t XXH3_hashLong_128b_internal(const void* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, - XXH3_f_accumulate_512 f_acc512, + XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble) { XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC; - XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, secret, secretSize, f_acc512, f_scramble); + XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, secret, secretSize, f_acc, f_scramble); /* converge into final hash */ XXH_STATIC_ASSERT(sizeof(acc) == 64); @@ -5346,47 +6678,50 @@ XXH3_hashLong_128b_internal(const void* XXH_RESTRICT input, size_t len, } /* - * It's important for performance that XXH3_hashLong is not inlined. + * It's important for performance that XXH3_hashLong() is not inlined. */ -XXH_NO_INLINE XXH128_hash_t +XXH_NO_INLINE XXH_PUREF XXH128_hash_t XXH3_hashLong_128b_default(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen) { (void)seed64; (void)secret; (void)secretLen; return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), - XXH3_accumulate_512, XXH3_scrambleAcc); + XXH3_accumulate, XXH3_scrambleAcc); } /* - * It's important for performance to pass @secretLen (when it's static) + * It's important for performance to pass @p secretLen (when it's static) * to the compiler, so that it can properly optimize the vectorized loop. + * + * When the secret size is unknown, or on GCC 12 where the mix of NO_INLINE and FORCE_INLINE + * breaks -Og, this is XXH_NO_INLINE. */ -XXH_FORCE_INLINE XXH128_hash_t +XXH3_WITH_SECRET_INLINE XXH128_hash_t XXH3_hashLong_128b_withSecret(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen) { (void)seed64; return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, secretLen, - XXH3_accumulate_512, XXH3_scrambleAcc); + XXH3_accumulate, XXH3_scrambleAcc); } XXH_FORCE_INLINE XXH128_hash_t XXH3_hashLong_128b_withSeed_internal(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, - XXH3_f_accumulate_512 f_acc512, + XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble, XXH3_f_initCustomSecret f_initSec) { if (seed64 == 0) return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), - f_acc512, f_scramble); + f_acc, f_scramble); { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE]; f_initSec(secret, seed64); return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, sizeof(secret), - f_acc512, f_scramble); + f_acc, f_scramble); } } @@ -5399,7 +6734,7 @@ XXH3_hashLong_128b_withSeed(const void* input, size_t len, { (void)secret; (void)secretLen; return XXH3_hashLong_128b_withSeed_internal(input, len, seed64, - XXH3_accumulate_512, XXH3_scrambleAcc, XXH3_initCustomSecret); + XXH3_accumulate, XXH3_scrambleAcc, XXH3_initCustomSecret); } typedef XXH128_hash_t (*XXH3_hashLong128_f)(const void* XXH_RESTRICT, size_t, @@ -5429,94 +6764,93 @@ XXH3_128bits_internal(const void* input, size_t len, /* === Public XXH128 API === */ -/*! @ingroup xxh3_family */ -XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(const void* input, size_t len) +/*! @ingroup XXH3_family */ +XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(XXH_NOESCAPE const void* input, size_t len) { return XXH3_128bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_default); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t -XXH3_128bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize) +XXH3_128bits_withSecret(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize) { return XXH3_128bits_internal(input, len, 0, (const xxh_u8*)secret, secretSize, XXH3_hashLong_128b_withSecret); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t -XXH3_128bits_withSeed(const void* input, size_t len, XXH64_hash_t seed) +XXH3_128bits_withSeed(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed) { return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_withSeed); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t -XXH3_128bits_withSecretandSeed(const void* input, size_t len, const void* secret, size_t secretSize, XXH64_hash_t seed) +XXH3_128bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed) { if (len <= XXH3_MIDSIZE_MAX) return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL); return XXH3_hashLong_128b_withSecret(input, len, seed, secret, secretSize); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t -XXH128(const void* input, size_t len, XXH64_hash_t seed) +XXH128(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed) { return XXH3_128bits_withSeed(input, len, seed); } /* === XXH3 128-bit streaming === */ - +#ifndef XXH_NO_STREAM /* * All initialization and update functions are identical to 64-bit streaming variant. * The only difference is the finalization routine. */ -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_128bits_reset(XXH3_state_t* statePtr) +XXH3_128bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr) { return XXH3_64bits_reset(statePtr); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_128bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize) +XXH3_128bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize) { return XXH3_64bits_reset_withSecret(statePtr, secret, secretSize); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_128bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed) +XXH3_128bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed) { return XXH3_64bits_reset_withSeed(statePtr, seed); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, size_t secretSize, XXH64_hash_t seed) +XXH3_128bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed) { return XXH3_64bits_reset_withSecretandSeed(statePtr, secret, secretSize, seed); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_128bits_update(XXH3_state_t* state, const void* input, size_t len) +XXH3_128bits_update(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len) { - return XXH3_update(state, (const xxh_u8*)input, len, - XXH3_accumulate_512, XXH3_scrambleAcc); + return XXH3_64bits_update(state, input, len); } -/*! @ingroup xxh3_family */ -XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* state) +/*! @ingroup XXH3_family */ +XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (XXH_NOESCAPE const XXH3_state_t* state) { const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret; if (state->totalLen > XXH3_MIDSIZE_MAX) { @@ -5540,13 +6874,13 @@ XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* state) return XXH3_128bits_withSecret(state->buffer, (size_t)(state->totalLen), secret, state->secretLimit + XXH_STRIPE_LEN); } - +#endif /* !XXH_NO_STREAM */ /* 128-bit utility functions */ #include /* memcmp, memcpy */ /* return : 1 is equal, 0 if different */ -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2) { /* note : XXH128_hash_t is compact, it has no padding byte */ @@ -5554,11 +6888,11 @@ XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2) } /* This prototype is compatible with stdlib's qsort(). - * return : >0 if *h128_1 > *h128_2 - * <0 if *h128_1 < *h128_2 - * =0 if *h128_1 == *h128_2 */ -/*! @ingroup xxh3_family */ -XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2) + * @return : >0 if *h128_1 > *h128_2 + * <0 if *h128_1 < *h128_2 + * =0 if *h128_1 == *h128_2 */ +/*! @ingroup XXH3_family */ +XXH_PUBLIC_API int XXH128_cmp(XXH_NOESCAPE const void* h128_1, XXH_NOESCAPE const void* h128_2) { XXH128_hash_t const h1 = *(const XXH128_hash_t*)h128_1; XXH128_hash_t const h2 = *(const XXH128_hash_t*)h128_2; @@ -5570,9 +6904,9 @@ XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2) /*====== Canonical representation ======*/ -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API void -XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash) +XXH128_canonicalFromHash(XXH_NOESCAPE XXH128_canonical_t* dst, XXH128_hash_t hash) { XXH_STATIC_ASSERT(sizeof(XXH128_canonical_t) == sizeof(XXH128_hash_t)); if (XXH_CPU_LITTLE_ENDIAN) { @@ -5583,9 +6917,9 @@ XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash) XXH_memcpy((char*)dst + sizeof(hash.high64), &hash.low64, sizeof(hash.low64)); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t -XXH128_hashFromCanonical(const XXH128_canonical_t* src) +XXH128_hashFromCanonical(XXH_NOESCAPE const XXH128_canonical_t* src) { XXH128_hash_t h; h.high64 = XXH_readBE64(src); @@ -5607,9 +6941,9 @@ XXH_FORCE_INLINE void XXH3_combine16(void* dst, XXH128_hash_t h128) XXH_writeLE64( (char*)dst+8, XXH_readLE64((char*)dst+8) ^ h128.high64 ); } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode -XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSeed, size_t customSeedSize) +XXH3_generateSecret(XXH_NOESCAPE void* secretBuffer, size_t secretSize, XXH_NOESCAPE const void* customSeed, size_t customSeedSize) { #if (XXH_DEBUGLEVEL >= 1) XXH_ASSERT(secretBuffer != NULL); @@ -5652,9 +6986,9 @@ XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSee return XXH_OK; } -/*! @ingroup xxh3_family */ +/*! @ingroup XXH3_family */ XXH_PUBLIC_API void -XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed) +XXH3_generateSecret_fromSeed(XXH_NOESCAPE void* secretBuffer, XXH64_hash_t seed) { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE]; XXH3_initCustomSecret(secret, seed); @@ -5667,7 +7001,7 @@ XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed) /* Pop our optimization override from above */ #if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \ && defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \ - && defined(__OPTIMIZE__) && !defined(__OPTIMIZE_SIZE__) /* respect -O0 and -Os */ + && defined(__OPTIMIZE__) && XXH_SIZE_OPT <= 0 /* respect -O0 and -Os */ # pragma GCC pop_options #endif @@ -5682,5 +7016,5 @@ XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed) #if defined (__cplusplus) -} +} /* extern "C" */ #endif diff --git a/src/thirdparty/zstd/common/zstd_internal.h b/src/thirdparty/zstd/common/zstd_internal.h index 1f942f27..ecb9cfba 100644 --- a/src/thirdparty/zstd/common/zstd_internal.h +++ b/src/thirdparty/zstd/common/zstd_internal.h @@ -178,7 +178,7 @@ static void ZSTD_copy8(void* dst, const void* src) { ZSTD_memcpy(dst, src, 8); #endif } -#define COPY8(d,s) { ZSTD_copy8(d,s); d+=8; s+=8; } +#define COPY8(d,s) do { ZSTD_copy8(d,s); d+=8; s+=8; } while (0) /* Need to use memmove here since the literal buffer can now be located within the dst buffer. In circumstances where the op "catches up" to where the @@ -198,7 +198,7 @@ static void ZSTD_copy16(void* dst, const void* src) { ZSTD_memcpy(dst, copy16_buf, 16); #endif } -#define COPY16(d,s) { ZSTD_copy16(d,s); d+=16; s+=16; } +#define COPY16(d,s) do { ZSTD_copy16(d,s); d+=16; s+=16; } while (0) #define WILDCOPY_OVERLENGTH 32 #define WILDCOPY_VECLEN 16 @@ -227,7 +227,7 @@ void ZSTD_wildcopy(void* dst, const void* src, ptrdiff_t length, ZSTD_overlap_e if (ovtype == ZSTD_overlap_src_before_dst && diff < WILDCOPY_VECLEN) { /* Handle short offset copies. */ do { - COPY8(op, ip) + COPY8(op, ip); } while (op < oend); } else { assert(diff >= WILDCOPY_VECLEN || diff <= -WILDCOPY_VECLEN); @@ -366,13 +366,13 @@ typedef struct { /*! ZSTD_getcBlockSize() : * Provides the size of compressed block from block header `src` */ -/* Used by: decompress, fullbench (does not get its definition from here) */ +/* Used by: decompress, fullbench */ size_t ZSTD_getcBlockSize(const void* src, size_t srcSize, blockProperties_t* bpPtr); /*! ZSTD_decodeSeqHeaders() : * decode sequence header from src */ -/* Used by: decompress, fullbench (does not get its definition from here) */ +/* Used by: zstd_decompress_block, fullbench */ size_t ZSTD_decodeSeqHeaders(ZSTD_DCtx* dctx, int* nbSeqPtr, const void* src, size_t srcSize); diff --git a/src/thirdparty/zstd/compress/fse_compress.c b/src/thirdparty/zstd/compress/fse_compress.c index 5d377080..1ce3cf16 100644 --- a/src/thirdparty/zstd/compress/fse_compress.c +++ b/src/thirdparty/zstd/compress/fse_compress.c @@ -25,7 +25,7 @@ #include "../common/error_private.h" #define ZSTD_DEPS_NEED_MALLOC #define ZSTD_DEPS_NEED_MATH64 -#include "../common/zstd_deps.h" /* ZSTD_malloc, ZSTD_free, ZSTD_memcpy, ZSTD_memset */ +#include "../common/zstd_deps.h" /* ZSTD_memset */ #include "../common/bits.h" /* ZSTD_highbit32 */ @@ -225,8 +225,8 @@ size_t FSE_NCountWriteBound(unsigned maxSymbolValue, unsigned tableLog) size_t const maxHeaderSize = (((maxSymbolValue+1) * tableLog + 4 /* bitCount initialized at 4 */ + 2 /* first two symbols may use one additional bit each */) / 8) - + 1 /* round up to whole nb bytes */ - + 2 /* additional two bytes for bitstream flush */; + + 1 /* round up to whole nb bytes */ + + 2 /* additional two bytes for bitstream flush */; return maxSymbolValue ? maxHeaderSize : FSE_NCOUNTBOUND; /* maxSymbolValue==0 ? use default */ } @@ -255,7 +255,7 @@ FSE_writeNCount_generic (void* header, size_t headerBufferSize, /* Init */ remaining = tableSize+1; /* +1 for extra accuracy */ threshold = tableSize; - nbBits = tableLog+1; + nbBits = (int)tableLog+1; while ((symbol < alphabetSize) && (remaining>1)) { /* stops at 1 */ if (previousIs0) { @@ -274,7 +274,7 @@ FSE_writeNCount_generic (void* header, size_t headerBufferSize, } while (symbol >= start+3) { start+=3; - bitStream += 3 << bitCount; + bitStream += 3U << bitCount; bitCount += 2; } bitStream += (symbol-start) << bitCount; @@ -294,7 +294,7 @@ FSE_writeNCount_generic (void* header, size_t headerBufferSize, count++; /* +1 for extra accuracy */ if (count>=threshold) count += max; /* [0..max[ [max..threshold[ (...) [threshold+max 2*threshold[ */ - bitStream += count << bitCount; + bitStream += (U32)count << bitCount; bitCount += nbBits; bitCount -= (count>8); out+= (bitCount+7) /8; - return (out-ostart); + assert(out >= ostart); + return (size_t)(out-ostart); } diff --git a/src/thirdparty/zstd/compress/huf_compress.c b/src/thirdparty/zstd/compress/huf_compress.c index 29871877..ea000723 100644 --- a/src/thirdparty/zstd/compress/huf_compress.c +++ b/src/thirdparty/zstd/compress/huf_compress.c @@ -220,6 +220,25 @@ static void HUF_setValue(HUF_CElt* elt, size_t value) } } +HUF_CTableHeader HUF_readCTableHeader(HUF_CElt const* ctable) +{ + HUF_CTableHeader header; + ZSTD_memcpy(&header, ctable, sizeof(header)); + return header; +} + +static void HUF_writeCTableHeader(HUF_CElt* ctable, U32 tableLog, U32 maxSymbolValue) +{ + HUF_CTableHeader header; + HUF_STATIC_ASSERT(sizeof(ctable[0]) == sizeof(header)); + ZSTD_memset(&header, 0, sizeof(header)); + assert(tableLog < 256); + header.tableLog = (BYTE)tableLog; + assert(maxSymbolValue < 256); + header.maxSymbolValue = (BYTE)maxSymbolValue; + ZSTD_memcpy(ctable, &header, sizeof(header)); +} + typedef struct { HUF_CompressWeightsWksp wksp; BYTE bitsToWeight[HUF_TABLELOG_MAX + 1]; /* precomputed conversion table */ @@ -237,6 +256,9 @@ size_t HUF_writeCTable_wksp(void* dst, size_t maxDstSize, HUF_STATIC_ASSERT(HUF_CTABLE_WORKSPACE_SIZE >= sizeof(HUF_WriteCTableWksp)); + assert(HUF_readCTableHeader(CTable).maxSymbolValue == maxSymbolValue); + assert(HUF_readCTableHeader(CTable).tableLog == huffLog); + /* check conditions */ if (workspaceSize < sizeof(HUF_WriteCTableWksp)) return ERROR(GENERIC); if (maxSymbolValue > HUF_SYMBOLVALUE_MAX) return ERROR(maxSymbolValue_tooLarge); @@ -283,7 +305,9 @@ size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void if (tableLog > HUF_TABLELOG_MAX) return ERROR(tableLog_tooLarge); if (nbSymbols > *maxSymbolValuePtr+1) return ERROR(maxSymbolValue_tooSmall); - CTable[0] = tableLog; + *maxSymbolValuePtr = nbSymbols - 1; + + HUF_writeCTableHeader(CTable, tableLog, *maxSymbolValuePtr); /* Prepare base value per rank */ { U32 n, nextRankStart = 0; @@ -315,7 +339,6 @@ size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void { U32 n; for (n=0; n HUF_readCTableHeader(CTable).maxSymbolValue) + return 0; return (U32)HUF_getNbBits(ct[symbolValue]); } @@ -723,7 +748,8 @@ static void HUF_buildCTableFromTree(HUF_CElt* CTable, nodeElt const* huffNode, i HUF_setNbBits(ct + huffNode[n].byte, huffNode[n].nbBits); /* push nbBits per symbol, symbol order */ for (n=0; n 11) @@ -1255,7 +1288,7 @@ unsigned HUF_optimalTableLog( { BYTE* dst = (BYTE*)workSpace + sizeof(HUF_WriteCTableWksp); size_t dstSize = wkspSize - sizeof(HUF_WriteCTableWksp); - size_t maxBits, hSize, newSize; + size_t hSize, newSize; const unsigned symbolCardinality = HUF_cardinality(count, maxSymbolValue); const unsigned minTableLog = HUF_minTableLog(symbolCardinality); size_t optSize = ((size_t) ~0) - 1; @@ -1266,12 +1299,14 @@ unsigned HUF_optimalTableLog( /* Search until size increases */ for (optLogGuess = minTableLog; optLogGuess <= maxTableLog; optLogGuess++) { DEBUGLOG(7, "checking for huffLog=%u", optLogGuess); - maxBits = HUF_buildCTable_wksp(table, count, maxSymbolValue, optLogGuess, workSpace, wkspSize); - if (ERR_isError(maxBits)) continue; - if (maxBits < optLogGuess && optLogGuess > minTableLog) break; + { size_t maxBits = HUF_buildCTable_wksp(table, count, maxSymbolValue, optLogGuess, workSpace, wkspSize); + if (ERR_isError(maxBits)) continue; - hSize = HUF_writeCTable_wksp(dst, dstSize, table, maxSymbolValue, (U32)maxBits, workSpace, wkspSize); + if (maxBits < optLogGuess && optLogGuess > minTableLog) break; + + hSize = HUF_writeCTable_wksp(dst, dstSize, table, maxSymbolValue, (U32)maxBits, workSpace, wkspSize); + } if (ERR_isError(hSize)) continue; @@ -1372,12 +1407,6 @@ HUF_compress_internal (void* dst, size_t dstSize, huffLog = (U32)maxBits; DEBUGLOG(6, "bit distribution completed (%zu symbols)", showCTableBits(table->CTable + 1, maxSymbolValue+1)); } - /* Zero unused symbols in CTable, so we can check it for validity */ - { - size_t const ctableSize = HUF_CTABLE_SIZE_ST(maxSymbolValue); - size_t const unusedSize = sizeof(table->CTable) - ctableSize * sizeof(HUF_CElt); - ZSTD_memset(table->CTable + ctableSize, 0, unusedSize); - } /* Write table description header */ { CHECK_V_F(hSize, HUF_writeCTable_wksp(op, dstSize, table->CTable, maxSymbolValue, huffLog, @@ -1420,7 +1449,7 @@ size_t HUF_compress1X_repeat (void* dst, size_t dstSize, /* HUF_compress4X_repeat(): * compress input using 4 streams. * consider skipping quickly - * re-use an existing huffman compression table */ + * reuse an existing huffman compression table */ size_t HUF_compress4X_repeat (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned huffLog, diff --git a/src/thirdparty/zstd/compress/zstd_compress.c b/src/thirdparty/zstd/compress/zstd_compress.c index d6133e70..9284e2a4 100644 --- a/src/thirdparty/zstd/compress/zstd_compress.c +++ b/src/thirdparty/zstd/compress/zstd_compress.c @@ -178,6 +178,7 @@ static void ZSTD_freeCCtxContent(ZSTD_CCtx* cctx) size_t ZSTD_freeCCtx(ZSTD_CCtx* cctx) { + DEBUGLOG(3, "ZSTD_freeCCtx (address: %p)", (void*)cctx); if (cctx==NULL) return 0; /* support free on NULL */ RETURN_ERROR_IF(cctx->staticSize, memory_allocation, "not compatible with static CCtx"); @@ -649,10 +650,11 @@ static size_t ZSTD_cParam_clampBounds(ZSTD_cParameter cParam, int* value) return 0; } -#define BOUNDCHECK(cParam, val) { \ - RETURN_ERROR_IF(!ZSTD_cParam_withinBounds(cParam,val), \ - parameter_outOfBound, "Param out of bounds"); \ -} +#define BOUNDCHECK(cParam, val) \ + do { \ + RETURN_ERROR_IF(!ZSTD_cParam_withinBounds(cParam,val), \ + parameter_outOfBound, "Param out of bounds"); \ + } while (0) static int ZSTD_isUpdateAuthorized(ZSTD_cParameter param) @@ -868,7 +870,7 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, #else FORWARD_IF_ERROR(ZSTD_cParam_clampBounds(param, &value), ""); CCtxParams->nbWorkers = value; - return CCtxParams->nbWorkers; + return (size_t)(CCtxParams->nbWorkers); #endif case ZSTD_c_jobSize : @@ -892,7 +894,7 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, #else FORWARD_IF_ERROR(ZSTD_cParam_clampBounds(ZSTD_c_overlapLog, &value), ""); CCtxParams->overlapLog = value; - return CCtxParams->overlapLog; + return (size_t)CCtxParams->overlapLog; #endif case ZSTD_c_rsyncable : @@ -902,7 +904,7 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, #else FORWARD_IF_ERROR(ZSTD_cParam_clampBounds(ZSTD_c_overlapLog, &value), ""); CCtxParams->rsyncable = value; - return CCtxParams->rsyncable; + return (size_t)CCtxParams->rsyncable; #endif case ZSTD_c_enableDedicatedDictSearch : @@ -939,8 +941,10 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, return CCtxParams->ldmParams.hashRateLog; case ZSTD_c_targetCBlockSize : - if (value!=0) /* 0 ==> default */ + if (value!=0) { /* 0 ==> default */ + value = MAX(value, ZSTD_TARGETCBLOCKSIZE_MIN); BOUNDCHECK(ZSTD_c_targetCBlockSize, value); + } CCtxParams->targetCBlockSize = (U32)value; return CCtxParams->targetCBlockSize; @@ -968,7 +972,7 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, case ZSTD_c_validateSequences: BOUNDCHECK(ZSTD_c_validateSequences, value); CCtxParams->validateSequences = value; - return CCtxParams->validateSequences; + return (size_t)CCtxParams->validateSequences; case ZSTD_c_useBlockSplitter: BOUNDCHECK(ZSTD_c_useBlockSplitter, value); @@ -983,7 +987,7 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, case ZSTD_c_deterministicRefPrefix: BOUNDCHECK(ZSTD_c_deterministicRefPrefix, value); CCtxParams->deterministicRefPrefix = !!value; - return CCtxParams->deterministicRefPrefix; + return (size_t)CCtxParams->deterministicRefPrefix; case ZSTD_c_prefetchCDictTables: BOUNDCHECK(ZSTD_c_prefetchCDictTables, value); @@ -993,7 +997,7 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, case ZSTD_c_enableSeqProducerFallback: BOUNDCHECK(ZSTD_c_enableSeqProducerFallback, value); CCtxParams->enableMatchFinderFallback = value; - return CCtxParams->enableMatchFinderFallback; + return (size_t)CCtxParams->enableMatchFinderFallback; case ZSTD_c_maxBlockSize: if (value!=0) /* 0 ==> default */ @@ -1363,7 +1367,6 @@ size_t ZSTD_CCtx_reset(ZSTD_CCtx* cctx, ZSTD_ResetDirective reset) RETURN_ERROR_IF(cctx->streamStage != zcss_init, stage_wrong, "Reset parameters is only possible during init stage."); ZSTD_clearAllDicts(cctx); - ZSTD_memset(&cctx->externalMatchCtx, 0, sizeof(cctx->externalMatchCtx)); return ZSTD_CCtxParams_reset(&cctx->requestedParams); } return 0; @@ -1391,11 +1394,12 @@ size_t ZSTD_checkCParams(ZSTD_compressionParameters cParams) static ZSTD_compressionParameters ZSTD_clampCParams(ZSTD_compressionParameters cParams) { -# define CLAMP_TYPE(cParam, val, type) { \ - ZSTD_bounds const bounds = ZSTD_cParam_getBounds(cParam); \ - if ((int)valbounds.upperBound) val=(type)bounds.upperBound; \ - } +# define CLAMP_TYPE(cParam, val, type) \ + do { \ + ZSTD_bounds const bounds = ZSTD_cParam_getBounds(cParam); \ + if ((int)valbounds.upperBound) val=(type)bounds.upperBound; \ + } while (0) # define CLAMP(cParam, val) CLAMP_TYPE(cParam, val, unsigned) CLAMP(ZSTD_c_windowLog, cParams.windowLog); CLAMP(ZSTD_c_chainLog, cParams.chainLog); @@ -1467,6 +1471,48 @@ ZSTD_adjustCParams_internal(ZSTD_compressionParameters cPar, const U64 maxWindowResize = 1ULL << (ZSTD_WINDOWLOG_MAX-1); assert(ZSTD_checkCParams(cPar)==0); + /* Cascade the selected strategy down to the next-highest one built into + * this binary. */ +#ifdef ZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR + if (cPar.strategy == ZSTD_btultra2) { + cPar.strategy = ZSTD_btultra; + } + if (cPar.strategy == ZSTD_btultra) { + cPar.strategy = ZSTD_btopt; + } +#endif +#ifdef ZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR + if (cPar.strategy == ZSTD_btopt) { + cPar.strategy = ZSTD_btlazy2; + } +#endif +#ifdef ZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR + if (cPar.strategy == ZSTD_btlazy2) { + cPar.strategy = ZSTD_lazy2; + } +#endif +#ifdef ZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR + if (cPar.strategy == ZSTD_lazy2) { + cPar.strategy = ZSTD_lazy; + } +#endif +#ifdef ZSTD_EXCLUDE_LAZY_BLOCK_COMPRESSOR + if (cPar.strategy == ZSTD_lazy) { + cPar.strategy = ZSTD_greedy; + } +#endif +#ifdef ZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR + if (cPar.strategy == ZSTD_greedy) { + cPar.strategy = ZSTD_dfast; + } +#endif +#ifdef ZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR + if (cPar.strategy == ZSTD_dfast) { + cPar.strategy = ZSTD_fast; + cPar.targetLength = 0; + } +#endif + switch (mode) { case ZSTD_cpm_unknown: case ZSTD_cpm_noAttachDict: @@ -1617,8 +1663,8 @@ ZSTD_sizeof_matchState(const ZSTD_compressionParameters* const cParams, + ZSTD_cwksp_aligned_alloc_size((MaxLL+1) * sizeof(U32)) + ZSTD_cwksp_aligned_alloc_size((MaxOff+1) * sizeof(U32)) + ZSTD_cwksp_aligned_alloc_size((1<strategy, useRowMatchFinder) ? ZSTD_cwksp_aligned_alloc_size(hSize) : 0; @@ -1707,7 +1753,7 @@ size_t ZSTD_estimateCCtxSize_usingCCtxParams(const ZSTD_CCtx_params* params) * be needed. However, we still allocate two 0-sized buffers, which can * take space under ASAN. */ return ZSTD_estimateCCtxSize_usingCCtxParams_internal( - &cParams, ¶ms->ldmParams, 1, useRowMatchFinder, 0, 0, ZSTD_CONTENTSIZE_UNKNOWN, params->useSequenceProducer, params->maxBlockSize); + &cParams, ¶ms->ldmParams, 1, useRowMatchFinder, 0, 0, ZSTD_CONTENTSIZE_UNKNOWN, ZSTD_hasExtSeqProd(params), params->maxBlockSize); } size_t ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams) @@ -1768,7 +1814,7 @@ size_t ZSTD_estimateCStreamSize_usingCCtxParams(const ZSTD_CCtx_params* params) return ZSTD_estimateCCtxSize_usingCCtxParams_internal( &cParams, ¶ms->ldmParams, 1, useRowMatchFinder, inBuffSize, outBuffSize, - ZSTD_CONTENTSIZE_UNKNOWN, params->useSequenceProducer, params->maxBlockSize); + ZSTD_CONTENTSIZE_UNKNOWN, ZSTD_hasExtSeqProd(params), params->maxBlockSize); } } @@ -2001,8 +2047,8 @@ ZSTD_reset_matchState(ZSTD_matchState_t* ms, ms->opt.litLengthFreq = (unsigned*)ZSTD_cwksp_reserve_aligned(ws, (MaxLL+1) * sizeof(unsigned)); ms->opt.matchLengthFreq = (unsigned*)ZSTD_cwksp_reserve_aligned(ws, (MaxML+1) * sizeof(unsigned)); ms->opt.offCodeFreq = (unsigned*)ZSTD_cwksp_reserve_aligned(ws, (MaxOff+1) * sizeof(unsigned)); - ms->opt.matchTable = (ZSTD_match_t*)ZSTD_cwksp_reserve_aligned(ws, (ZSTD_OPT_NUM+1) * sizeof(ZSTD_match_t)); - ms->opt.priceTable = (ZSTD_optimal_t*)ZSTD_cwksp_reserve_aligned(ws, (ZSTD_OPT_NUM+1) * sizeof(ZSTD_optimal_t)); + ms->opt.matchTable = (ZSTD_match_t*)ZSTD_cwksp_reserve_aligned(ws, ZSTD_OPT_SIZE * sizeof(ZSTD_match_t)); + ms->opt.priceTable = (ZSTD_optimal_t*)ZSTD_cwksp_reserve_aligned(ws, ZSTD_OPT_SIZE * sizeof(ZSTD_optimal_t)); } ms->cParams = *cParams; @@ -2074,7 +2120,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, { size_t const windowSize = MAX(1, (size_t)MIN(((U64)1 << params->cParams.windowLog), pledgedSrcSize)); size_t const blockSize = MIN(params->maxBlockSize, windowSize); - size_t const maxNbSeq = ZSTD_maxNbSeq(blockSize, params->cParams.minMatch, params->useSequenceProducer); + size_t const maxNbSeq = ZSTD_maxNbSeq(blockSize, params->cParams.minMatch, ZSTD_hasExtSeqProd(params)); size_t const buffOutSize = (zbuff == ZSTDb_buffered && params->outBufferMode == ZSTD_bm_buffered) ? ZSTD_compressBound(blockSize) + 1 : 0; @@ -2091,8 +2137,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, size_t const neededSpace = ZSTD_estimateCCtxSize_usingCCtxParams_internal( ¶ms->cParams, ¶ms->ldmParams, zc->staticSize != 0, params->useRowMatchFinder, - buffInSize, buffOutSize, pledgedSrcSize, params->useSequenceProducer, params->maxBlockSize); - int resizeWorkspace; + buffInSize, buffOutSize, pledgedSrcSize, ZSTD_hasExtSeqProd(params), params->maxBlockSize); FORWARD_IF_ERROR(neededSpace, "cctx size estimate failed!"); @@ -2101,7 +2146,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, { /* Check if workspace is large enough, alloc a new one if needed */ int const workspaceTooSmall = ZSTD_cwksp_sizeof(ws) < neededSpace; int const workspaceWasteful = ZSTD_cwksp_check_wasteful(ws, neededSpace); - resizeWorkspace = workspaceTooSmall || workspaceWasteful; + int resizeWorkspace = workspaceTooSmall || workspaceWasteful; DEBUGLOG(4, "Need %zu B workspace", neededSpace); DEBUGLOG(4, "windowSize: %zu - blockSize: %zu", windowSize, blockSize); @@ -2176,10 +2221,10 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, } /* reserve space for block-level external sequences */ - if (params->useSequenceProducer) { + if (ZSTD_hasExtSeqProd(params)) { size_t const maxNbExternalSeq = ZSTD_sequenceBound(blockSize); - zc->externalMatchCtx.seqBufferCapacity = maxNbExternalSeq; - zc->externalMatchCtx.seqBuffer = + zc->extSeqBufCapacity = maxNbExternalSeq; + zc->extSeqBuf = (ZSTD_Sequence*)ZSTD_cwksp_reserve_aligned(ws, maxNbExternalSeq * sizeof(ZSTD_Sequence)); } @@ -2564,7 +2609,7 @@ ZSTD_reduceTable_internal (U32* const table, U32 const size, U32 const reducerVa assert(size < (1U<<31)); /* can be casted to int */ #if ZSTD_MEMORY_SANITIZER && !defined (ZSTD_MSAN_DONT_POISON_WORKSPACE) - /* To validate that the table re-use logic is sound, and that we don't + /* To validate that the table reuse logic is sound, and that we don't * access table space that we haven't cleaned, we re-"poison" the table * space every time we mark it dirty. * @@ -2992,40 +3037,43 @@ ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, ZSTD_paramS static const ZSTD_blockCompressor blockCompressor[4][ZSTD_STRATEGY_MAX+1] = { { ZSTD_compressBlock_fast /* default for 0 */, ZSTD_compressBlock_fast, - ZSTD_compressBlock_doubleFast, - ZSTD_compressBlock_greedy, - ZSTD_compressBlock_lazy, - ZSTD_compressBlock_lazy2, - ZSTD_compressBlock_btlazy2, - ZSTD_compressBlock_btopt, - ZSTD_compressBlock_btultra, - ZSTD_compressBlock_btultra2 }, + ZSTD_COMPRESSBLOCK_DOUBLEFAST, + ZSTD_COMPRESSBLOCK_GREEDY, + ZSTD_COMPRESSBLOCK_LAZY, + ZSTD_COMPRESSBLOCK_LAZY2, + ZSTD_COMPRESSBLOCK_BTLAZY2, + ZSTD_COMPRESSBLOCK_BTOPT, + ZSTD_COMPRESSBLOCK_BTULTRA, + ZSTD_COMPRESSBLOCK_BTULTRA2 + }, { ZSTD_compressBlock_fast_extDict /* default for 0 */, ZSTD_compressBlock_fast_extDict, - ZSTD_compressBlock_doubleFast_extDict, - ZSTD_compressBlock_greedy_extDict, - ZSTD_compressBlock_lazy_extDict, - ZSTD_compressBlock_lazy2_extDict, - ZSTD_compressBlock_btlazy2_extDict, - ZSTD_compressBlock_btopt_extDict, - ZSTD_compressBlock_btultra_extDict, - ZSTD_compressBlock_btultra_extDict }, + ZSTD_COMPRESSBLOCK_DOUBLEFAST_EXTDICT, + ZSTD_COMPRESSBLOCK_GREEDY_EXTDICT, + ZSTD_COMPRESSBLOCK_LAZY_EXTDICT, + ZSTD_COMPRESSBLOCK_LAZY2_EXTDICT, + ZSTD_COMPRESSBLOCK_BTLAZY2_EXTDICT, + ZSTD_COMPRESSBLOCK_BTOPT_EXTDICT, + ZSTD_COMPRESSBLOCK_BTULTRA_EXTDICT, + ZSTD_COMPRESSBLOCK_BTULTRA_EXTDICT + }, { ZSTD_compressBlock_fast_dictMatchState /* default for 0 */, ZSTD_compressBlock_fast_dictMatchState, - ZSTD_compressBlock_doubleFast_dictMatchState, - ZSTD_compressBlock_greedy_dictMatchState, - ZSTD_compressBlock_lazy_dictMatchState, - ZSTD_compressBlock_lazy2_dictMatchState, - ZSTD_compressBlock_btlazy2_dictMatchState, - ZSTD_compressBlock_btopt_dictMatchState, - ZSTD_compressBlock_btultra_dictMatchState, - ZSTD_compressBlock_btultra_dictMatchState }, + ZSTD_COMPRESSBLOCK_DOUBLEFAST_DICTMATCHSTATE, + ZSTD_COMPRESSBLOCK_GREEDY_DICTMATCHSTATE, + ZSTD_COMPRESSBLOCK_LAZY_DICTMATCHSTATE, + ZSTD_COMPRESSBLOCK_LAZY2_DICTMATCHSTATE, + ZSTD_COMPRESSBLOCK_BTLAZY2_DICTMATCHSTATE, + ZSTD_COMPRESSBLOCK_BTOPT_DICTMATCHSTATE, + ZSTD_COMPRESSBLOCK_BTULTRA_DICTMATCHSTATE, + ZSTD_COMPRESSBLOCK_BTULTRA_DICTMATCHSTATE + }, { NULL /* default for 0 */, NULL, NULL, - ZSTD_compressBlock_greedy_dedicatedDictSearch, - ZSTD_compressBlock_lazy_dedicatedDictSearch, - ZSTD_compressBlock_lazy2_dedicatedDictSearch, + ZSTD_COMPRESSBLOCK_GREEDY_DEDICATEDDICTSEARCH, + ZSTD_COMPRESSBLOCK_LAZY_DEDICATEDDICTSEARCH, + ZSTD_COMPRESSBLOCK_LAZY2_DEDICATEDDICTSEARCH, NULL, NULL, NULL, @@ -3038,18 +3086,26 @@ ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, ZSTD_paramS DEBUGLOG(4, "Selected block compressor: dictMode=%d strat=%d rowMatchfinder=%d", (int)dictMode, (int)strat, (int)useRowMatchFinder); if (ZSTD_rowMatchFinderUsed(strat, useRowMatchFinder)) { static const ZSTD_blockCompressor rowBasedBlockCompressors[4][3] = { - { ZSTD_compressBlock_greedy_row, - ZSTD_compressBlock_lazy_row, - ZSTD_compressBlock_lazy2_row }, - { ZSTD_compressBlock_greedy_extDict_row, - ZSTD_compressBlock_lazy_extDict_row, - ZSTD_compressBlock_lazy2_extDict_row }, - { ZSTD_compressBlock_greedy_dictMatchState_row, - ZSTD_compressBlock_lazy_dictMatchState_row, - ZSTD_compressBlock_lazy2_dictMatchState_row }, - { ZSTD_compressBlock_greedy_dedicatedDictSearch_row, - ZSTD_compressBlock_lazy_dedicatedDictSearch_row, - ZSTD_compressBlock_lazy2_dedicatedDictSearch_row } + { + ZSTD_COMPRESSBLOCK_GREEDY_ROW, + ZSTD_COMPRESSBLOCK_LAZY_ROW, + ZSTD_COMPRESSBLOCK_LAZY2_ROW + }, + { + ZSTD_COMPRESSBLOCK_GREEDY_EXTDICT_ROW, + ZSTD_COMPRESSBLOCK_LAZY_EXTDICT_ROW, + ZSTD_COMPRESSBLOCK_LAZY2_EXTDICT_ROW + }, + { + ZSTD_COMPRESSBLOCK_GREEDY_DICTMATCHSTATE_ROW, + ZSTD_COMPRESSBLOCK_LAZY_DICTMATCHSTATE_ROW, + ZSTD_COMPRESSBLOCK_LAZY2_DICTMATCHSTATE_ROW + }, + { + ZSTD_COMPRESSBLOCK_GREEDY_DEDICATEDDICTSEARCH_ROW, + ZSTD_COMPRESSBLOCK_LAZY_DEDICATEDDICTSEARCH_ROW, + ZSTD_COMPRESSBLOCK_LAZY2_DEDICATEDDICTSEARCH_ROW + } }; DEBUGLOG(4, "Selecting a row-based matchfinder"); assert(useRowMatchFinder != ZSTD_ps_auto); @@ -3192,7 +3248,7 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) /* External matchfinder + LDM is technically possible, just not implemented yet. * We need to revisit soon and implement it. */ RETURN_ERROR_IF( - zc->appliedParams.useSequenceProducer, + ZSTD_hasExtSeqProd(&zc->appliedParams), parameter_combination_unsupported, "Long-distance matching with external sequence producer enabled is not currently supported." ); @@ -3211,7 +3267,7 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) /* External matchfinder + LDM is technically possible, just not implemented yet. * We need to revisit soon and implement it. */ RETURN_ERROR_IF( - zc->appliedParams.useSequenceProducer, + ZSTD_hasExtSeqProd(&zc->appliedParams), parameter_combination_unsupported, "Long-distance matching with external sequence producer enabled is not currently supported." ); @@ -3230,18 +3286,18 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) zc->appliedParams.useRowMatchFinder, src, srcSize); assert(ldmSeqStore.pos == ldmSeqStore.size); - } else if (zc->appliedParams.useSequenceProducer) { + } else if (ZSTD_hasExtSeqProd(&zc->appliedParams)) { assert( - zc->externalMatchCtx.seqBufferCapacity >= ZSTD_sequenceBound(srcSize) + zc->extSeqBufCapacity >= ZSTD_sequenceBound(srcSize) ); - assert(zc->externalMatchCtx.mFinder != NULL); + assert(zc->appliedParams.extSeqProdFunc != NULL); { U32 const windowSize = (U32)1 << zc->appliedParams.cParams.windowLog; - size_t const nbExternalSeqs = (zc->externalMatchCtx.mFinder)( - zc->externalMatchCtx.mState, - zc->externalMatchCtx.seqBuffer, - zc->externalMatchCtx.seqBufferCapacity, + size_t const nbExternalSeqs = (zc->appliedParams.extSeqProdFunc)( + zc->appliedParams.extSeqProdState, + zc->extSeqBuf, + zc->extSeqBufCapacity, src, srcSize, NULL, 0, /* dict and dictSize, currently not supported */ zc->appliedParams.compressionLevel, @@ -3249,21 +3305,21 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) ); size_t const nbPostProcessedSeqs = ZSTD_postProcessSequenceProducerResult( - zc->externalMatchCtx.seqBuffer, + zc->extSeqBuf, nbExternalSeqs, - zc->externalMatchCtx.seqBufferCapacity, + zc->extSeqBufCapacity, srcSize ); /* Return early if there is no error, since we don't need to worry about last literals */ if (!ZSTD_isError(nbPostProcessedSeqs)) { ZSTD_sequencePosition seqPos = {0,0,0}; - size_t const seqLenSum = ZSTD_fastSequenceLengthSum(zc->externalMatchCtx.seqBuffer, nbPostProcessedSeqs); + size_t const seqLenSum = ZSTD_fastSequenceLengthSum(zc->extSeqBuf, nbPostProcessedSeqs); RETURN_ERROR_IF(seqLenSum > srcSize, externalSequences_invalid, "External sequences imply too large a block!"); FORWARD_IF_ERROR( ZSTD_copySequencesToSeqStoreExplicitBlockDelim( zc, &seqPos, - zc->externalMatchCtx.seqBuffer, nbPostProcessedSeqs, + zc->extSeqBuf, nbPostProcessedSeqs, src, srcSize, zc->appliedParams.searchForExternalRepcodes ), @@ -3280,9 +3336,11 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) } /* Fallback to software matchfinder */ - { ZSTD_blockCompressor const blockCompressor = ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, - zc->appliedParams.useRowMatchFinder, - dictMode); + { ZSTD_blockCompressor const blockCompressor = + ZSTD_selectBlockCompressor( + zc->appliedParams.cParams.strategy, + zc->appliedParams.useRowMatchFinder, + dictMode); ms->ldmSeqStore = NULL; DEBUGLOG( 5, @@ -3292,9 +3350,10 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) lastLLSize = blockCompressor(ms, &zc->seqStore, zc->blockState.nextCBlock->rep, src, srcSize); } } } else { /* not long range mode and no external matchfinder */ - ZSTD_blockCompressor const blockCompressor = ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, - zc->appliedParams.useRowMatchFinder, - dictMode); + ZSTD_blockCompressor const blockCompressor = ZSTD_selectBlockCompressor( + zc->appliedParams.cParams.strategy, + zc->appliedParams.useRowMatchFinder, + dictMode); ms->ldmSeqStore = NULL; lastLLSize = blockCompressor(ms, &zc->seqStore, zc->blockState.nextCBlock->rep, src, srcSize); } @@ -3304,29 +3363,38 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) return ZSTDbss_compress; } -static void ZSTD_copyBlockSequences(ZSTD_CCtx* zc) +static size_t ZSTD_copyBlockSequences(SeqCollector* seqCollector, const seqStore_t* seqStore, const U32 prevRepcodes[ZSTD_REP_NUM]) { - const seqStore_t* seqStore = ZSTD_getSeqStore(zc); - const seqDef* seqStoreSeqs = seqStore->sequencesStart; - size_t seqStoreSeqSize = seqStore->sequences - seqStoreSeqs; - size_t seqStoreLiteralsSize = (size_t)(seqStore->lit - seqStore->litStart); - size_t literalsRead = 0; - size_t lastLLSize; + const seqDef* inSeqs = seqStore->sequencesStart; + const size_t nbInSequences = seqStore->sequences - inSeqs; + const size_t nbInLiterals = (size_t)(seqStore->lit - seqStore->litStart); - ZSTD_Sequence* outSeqs = &zc->seqCollector.seqStart[zc->seqCollector.seqIndex]; + ZSTD_Sequence* outSeqs = seqCollector->seqIndex == 0 ? seqCollector->seqStart : seqCollector->seqStart + seqCollector->seqIndex; + const size_t nbOutSequences = nbInSequences + 1; + size_t nbOutLiterals = 0; + repcodes_t repcodes; size_t i; - repcodes_t updatedRepcodes; - assert(zc->seqCollector.seqIndex + 1 < zc->seqCollector.maxSequences); - /* Ensure we have enough space for last literals "sequence" */ - assert(zc->seqCollector.maxSequences >= seqStoreSeqSize + 1); - ZSTD_memcpy(updatedRepcodes.rep, zc->blockState.prevCBlock->rep, sizeof(repcodes_t)); - for (i = 0; i < seqStoreSeqSize; ++i) { - U32 rawOffset = seqStoreSeqs[i].offBase - ZSTD_REP_NUM; - outSeqs[i].litLength = seqStoreSeqs[i].litLength; - outSeqs[i].matchLength = seqStoreSeqs[i].mlBase + MINMATCH; + /* Bounds check that we have enough space for every input sequence + * and the block delimiter + */ + assert(seqCollector->seqIndex <= seqCollector->maxSequences); + RETURN_ERROR_IF( + nbOutSequences > (size_t)(seqCollector->maxSequences - seqCollector->seqIndex), + dstSize_tooSmall, + "Not enough space to copy sequences"); + + ZSTD_memcpy(&repcodes, prevRepcodes, sizeof(repcodes)); + for (i = 0; i < nbInSequences; ++i) { + U32 rawOffset; + outSeqs[i].litLength = inSeqs[i].litLength; + outSeqs[i].matchLength = inSeqs[i].mlBase + MINMATCH; outSeqs[i].rep = 0; + /* Handle the possible single length >= 64K + * There can only be one because we add MINMATCH to every match length, + * and blocks are at most 128K. + */ if (i == seqStore->longLengthPos) { if (seqStore->longLengthType == ZSTD_llt_literalLength) { outSeqs[i].litLength += 0x10000; @@ -3335,41 +3403,55 @@ static void ZSTD_copyBlockSequences(ZSTD_CCtx* zc) } } - if (seqStoreSeqs[i].offBase <= ZSTD_REP_NUM) { - /* Derive the correct offset corresponding to a repcode */ - outSeqs[i].rep = seqStoreSeqs[i].offBase; + /* Determine the raw offset given the offBase, which may be a repcode. */ + if (OFFBASE_IS_REPCODE(inSeqs[i].offBase)) { + const U32 repcode = OFFBASE_TO_REPCODE(inSeqs[i].offBase); + assert(repcode > 0); + outSeqs[i].rep = repcode; if (outSeqs[i].litLength != 0) { - rawOffset = updatedRepcodes.rep[outSeqs[i].rep - 1]; + rawOffset = repcodes.rep[repcode - 1]; } else { - if (outSeqs[i].rep == 3) { - rawOffset = updatedRepcodes.rep[0] - 1; + if (repcode == 3) { + assert(repcodes.rep[0] > 1); + rawOffset = repcodes.rep[0] - 1; } else { - rawOffset = updatedRepcodes.rep[outSeqs[i].rep]; + rawOffset = repcodes.rep[repcode]; } } + } else { + rawOffset = OFFBASE_TO_OFFSET(inSeqs[i].offBase); } outSeqs[i].offset = rawOffset; - /* seqStoreSeqs[i].offset == offCode+1, and ZSTD_updateRep() expects offCode - so we provide seqStoreSeqs[i].offset - 1 */ - ZSTD_updateRep(updatedRepcodes.rep, - seqStoreSeqs[i].offBase, - seqStoreSeqs[i].litLength == 0); - literalsRead += outSeqs[i].litLength; + + /* Update repcode history for the sequence */ + ZSTD_updateRep(repcodes.rep, + inSeqs[i].offBase, + inSeqs[i].litLength == 0); + + nbOutLiterals += outSeqs[i].litLength; } /* Insert last literals (if any exist) in the block as a sequence with ml == off == 0. * If there are no last literals, then we'll emit (of: 0, ml: 0, ll: 0), which is a marker * for the block boundary, according to the API. */ - assert(seqStoreLiteralsSize >= literalsRead); - lastLLSize = seqStoreLiteralsSize - literalsRead; - outSeqs[i].litLength = (U32)lastLLSize; - outSeqs[i].matchLength = outSeqs[i].offset = outSeqs[i].rep = 0; - seqStoreSeqSize++; - zc->seqCollector.seqIndex += seqStoreSeqSize; + assert(nbInLiterals >= nbOutLiterals); + { + const size_t lastLLSize = nbInLiterals - nbOutLiterals; + outSeqs[nbInSequences].litLength = (U32)lastLLSize; + outSeqs[nbInSequences].matchLength = 0; + outSeqs[nbInSequences].offset = 0; + assert(nbOutSequences == nbInSequences + 1); + } + seqCollector->seqIndex += nbOutSequences; + assert(seqCollector->seqIndex <= seqCollector->maxSequences); + + return 0; } size_t ZSTD_sequenceBound(size_t srcSize) { - return (srcSize / ZSTD_MINMATCH_MIN) + 1; + const size_t maxNbSeq = (srcSize / ZSTD_MINMATCH_MIN) + 1; + const size_t maxNbDelims = (srcSize / ZSTD_BLOCKSIZE_MAX_MIN) + 1; + return maxNbSeq + maxNbDelims; } size_t ZSTD_generateSequences(ZSTD_CCtx* zc, ZSTD_Sequence* outSeqs, @@ -3378,6 +3460,16 @@ size_t ZSTD_generateSequences(ZSTD_CCtx* zc, ZSTD_Sequence* outSeqs, const size_t dstCapacity = ZSTD_compressBound(srcSize); void* dst = ZSTD_customMalloc(dstCapacity, ZSTD_defaultCMem); SeqCollector seqCollector; + { + int targetCBlockSize; + FORWARD_IF_ERROR(ZSTD_CCtx_getParameter(zc, ZSTD_c_targetCBlockSize, &targetCBlockSize), ""); + RETURN_ERROR_IF(targetCBlockSize != 0, parameter_unsupported, "targetCBlockSize != 0"); + } + { + int nbWorkers; + FORWARD_IF_ERROR(ZSTD_CCtx_getParameter(zc, ZSTD_c_nbWorkers, &nbWorkers), ""); + RETURN_ERROR_IF(nbWorkers != 0, parameter_unsupported, "nbWorkers != 0"); + } RETURN_ERROR_IF(dst == NULL, memory_allocation, "NULL pointer!"); @@ -3387,8 +3479,12 @@ size_t ZSTD_generateSequences(ZSTD_CCtx* zc, ZSTD_Sequence* outSeqs, seqCollector.maxSequences = outSeqsSize; zc->seqCollector = seqCollector; - ZSTD_compress2(zc, dst, dstCapacity, src, srcSize); - ZSTD_customFree(dst, ZSTD_defaultCMem); + { + const size_t ret = ZSTD_compress2(zc, dst, dstCapacity, src, srcSize); + ZSTD_customFree(dst, ZSTD_defaultCMem); + FORWARD_IF_ERROR(ret, "ZSTD_compress2 failed"); + } + assert(zc->seqCollector.seqIndex <= ZSTD_sequenceBound(srcSize)); return zc->seqCollector.seqIndex; } @@ -3981,8 +4077,9 @@ ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, cSeqsSize = 1; } + /* Sequence collection not supported when block splitting */ if (zc->seqCollector.collectSequences) { - ZSTD_copyBlockSequences(zc); + FORWARD_IF_ERROR(ZSTD_copyBlockSequences(&zc->seqCollector, seqStore, dRepOriginal.rep), "copyBlockSequences failed"); ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState); return 0; } @@ -4204,6 +4301,7 @@ ZSTD_compressBlock_splitBlock(ZSTD_CCtx* zc, if (bss == ZSTDbss_noCompress) { if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat_valid) zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat_check; + RETURN_ERROR_IF(zc->seqCollector.collectSequences, sequenceProducer_failed, "Uncompressible block"); cSize = ZSTD_noCompressBlock(dst, dstCapacity, src, srcSize, lastBlock); FORWARD_IF_ERROR(cSize, "ZSTD_noCompressBlock failed"); DEBUGLOG(4, "ZSTD_compressBlock_splitBlock: Nocompress block"); @@ -4236,11 +4334,15 @@ ZSTD_compressBlock_internal(ZSTD_CCtx* zc, { const size_t bss = ZSTD_buildSeqStore(zc, src, srcSize); FORWARD_IF_ERROR(bss, "ZSTD_buildSeqStore failed"); - if (bss == ZSTDbss_noCompress) { cSize = 0; goto out; } + if (bss == ZSTDbss_noCompress) { + RETURN_ERROR_IF(zc->seqCollector.collectSequences, sequenceProducer_failed, "Uncompressible block"); + cSize = 0; + goto out; + } } if (zc->seqCollector.collectSequences) { - ZSTD_copyBlockSequences(zc); + FORWARD_IF_ERROR(ZSTD_copyBlockSequences(&zc->seqCollector, ZSTD_getSeqStore(zc), zc->blockState.prevCBlock->rep), "copyBlockSequences failed"); ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState); return 0; } @@ -4553,19 +4655,15 @@ size_t ZSTD_writeLastEmptyBlock(void* dst, size_t dstCapacity) } } -size_t ZSTD_referenceExternalSequences(ZSTD_CCtx* cctx, rawSeq* seq, size_t nbSeq) +void ZSTD_referenceExternalSequences(ZSTD_CCtx* cctx, rawSeq* seq, size_t nbSeq) { - RETURN_ERROR_IF(cctx->stage != ZSTDcs_init, stage_wrong, - "wrong cctx stage"); - RETURN_ERROR_IF(cctx->appliedParams.ldmParams.enableLdm == ZSTD_ps_enable, - parameter_unsupported, - "incompatible with ldm"); + assert(cctx->stage == ZSTDcs_init); + assert(nbSeq == 0 || cctx->appliedParams.ldmParams.enableLdm != ZSTD_ps_enable); cctx->externSeqStore.seq = seq; cctx->externSeqStore.size = nbSeq; cctx->externSeqStore.capacity = nbSeq; cctx->externSeqStore.pos = 0; cctx->externSeqStore.posInSequence = 0; - return 0; } @@ -4760,12 +4858,19 @@ static size_t ZSTD_loadDictionaryContent(ZSTD_matchState_t* ms, ZSTD_fillHashTable(ms, iend, dtlm, tfp); break; case ZSTD_dfast: +#ifndef ZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR ZSTD_fillDoubleHashTable(ms, iend, dtlm, tfp); +#else + assert(0); /* shouldn't be called: cparams should've been adjusted. */ +#endif break; case ZSTD_greedy: case ZSTD_lazy: case ZSTD_lazy2: +#if !defined(ZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_LAZY_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR) assert(srcSize >= HASH_READ_SIZE); if (ms->dedicatedDictSearch) { assert(ms->chainTable != NULL); @@ -4782,14 +4887,23 @@ static size_t ZSTD_loadDictionaryContent(ZSTD_matchState_t* ms, DEBUGLOG(4, "Using chain-based hash table for lazy dict"); } } +#else + assert(0); /* shouldn't be called: cparams should've been adjusted. */ +#endif break; case ZSTD_btlazy2: /* we want the dictionary table fully sorted */ case ZSTD_btopt: case ZSTD_btultra: case ZSTD_btultra2: +#if !defined(ZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR) assert(srcSize >= HASH_READ_SIZE); ZSTD_updateTree(ms, iend-HASH_READ_SIZE, iend); +#else + assert(0); /* shouldn't be called: cparams should've been adjusted. */ +#endif break; default: @@ -4836,11 +4950,10 @@ size_t ZSTD_loadCEntropy(ZSTD_compressedBlockState_t* bs, void* workspace, /* We only set the loaded table as valid if it contains all non-zero * weights. Otherwise, we set it to check */ - if (!hasZeroWeights) + if (!hasZeroWeights && maxSymbolValue == 255) bs->entropy.huf.repeatMode = HUF_repeat_valid; RETURN_ERROR_IF(HUF_isError(hufHeaderSize), dictionary_corrupted, ""); - RETURN_ERROR_IF(maxSymbolValue < 255, dictionary_corrupted, ""); dictPtr += hufHeaderSize; } @@ -5107,14 +5220,13 @@ static size_t ZSTD_writeEpilogue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity) { BYTE* const ostart = (BYTE*)dst; BYTE* op = ostart; - size_t fhSize = 0; DEBUGLOG(4, "ZSTD_writeEpilogue"); RETURN_ERROR_IF(cctx->stage == ZSTDcs_created, stage_wrong, "init missing"); /* special case : empty frame */ if (cctx->stage == ZSTDcs_init) { - fhSize = ZSTD_writeFrameHeader(dst, dstCapacity, &cctx->appliedParams, 0, 0); + size_t fhSize = ZSTD_writeFrameHeader(dst, dstCapacity, &cctx->appliedParams, 0, 0); FORWARD_IF_ERROR(fhSize, "ZSTD_writeFrameHeader failed"); dstCapacity -= fhSize; op += fhSize; @@ -5124,8 +5236,9 @@ static size_t ZSTD_writeEpilogue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity) if (cctx->stage != ZSTDcs_ending) { /* write one last empty block, make it the "last" block */ U32 const cBlockHeader24 = 1 /* last block */ + (((U32)bt_raw)<<1) + 0; - RETURN_ERROR_IF(dstCapacity<4, dstSize_tooSmall, "no room for epilogue"); - MEM_writeLE32(op, cBlockHeader24); + ZSTD_STATIC_ASSERT(ZSTD_BLOCKHEADERSIZE == 3); + RETURN_ERROR_IF(dstCapacity<3, dstSize_tooSmall, "no room for epilogue"); + MEM_writeLE24(op, cBlockHeader24); op += ZSTD_blockHeaderSize; dstCapacity -= ZSTD_blockHeaderSize; } @@ -5455,7 +5568,7 @@ ZSTD_CDict* ZSTD_createCDict_advanced2( cctxParams.useRowMatchFinder, cctxParams.enableDedicatedDictSearch, customMem); - if (ZSTD_isError( ZSTD_initCDict_internal(cdict, + if (!cdict || ZSTD_isError( ZSTD_initCDict_internal(cdict, dict, dictSize, dictLoadMethod, dictContentType, cctxParams) )) { @@ -5879,7 +5992,7 @@ static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, if (zcs->appliedParams.inBufferMode == ZSTD_bm_stable) { assert(input->pos >= zcs->stableIn_notConsumed); input->pos -= zcs->stableIn_notConsumed; - ip -= zcs->stableIn_notConsumed; + if (ip) ip -= zcs->stableIn_notConsumed; zcs->stableIn_notConsumed = 0; } if (zcs->appliedParams.inBufferMode == ZSTD_bm_buffered) { @@ -6138,7 +6251,7 @@ static size_t ZSTD_CCtx_init_compressStream2(ZSTD_CCtx* cctx, #ifdef ZSTD_MULTITHREAD /* If external matchfinder is enabled, make sure to fail before checking job size (for consistency) */ RETURN_ERROR_IF( - params.useSequenceProducer == 1 && params.nbWorkers >= 1, + ZSTD_hasExtSeqProd(¶ms) && params.nbWorkers >= 1, parameter_combination_unsupported, "External sequence producer isn't supported with nbWorkers >= 1" ); @@ -6430,7 +6543,7 @@ ZSTD_copySequencesToSeqStoreExplicitBlockDelim(ZSTD_CCtx* cctx, if (cctx->appliedParams.validateSequences) { seqPos->posInSrc += litLength + matchLength; FORWARD_IF_ERROR(ZSTD_validateSequence(offBase, matchLength, cctx->appliedParams.cParams.minMatch, seqPos->posInSrc, - cctx->appliedParams.cParams.windowLog, dictSize, cctx->appliedParams.useSequenceProducer), + cctx->appliedParams.cParams.windowLog, dictSize, ZSTD_hasExtSeqProd(&cctx->appliedParams)), "Sequence validation failed"); } RETURN_ERROR_IF(idx - seqPos->idx >= cctx->seqStore.maxNbSeq, externalSequences_invalid, @@ -6568,7 +6681,7 @@ ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_sequencePosition* if (cctx->appliedParams.validateSequences) { seqPos->posInSrc += litLength + matchLength; FORWARD_IF_ERROR(ZSTD_validateSequence(offBase, matchLength, cctx->appliedParams.cParams.minMatch, seqPos->posInSrc, - cctx->appliedParams.cParams.windowLog, dictSize, cctx->appliedParams.useSequenceProducer), + cctx->appliedParams.cParams.windowLog, dictSize, ZSTD_hasExtSeqProd(&cctx->appliedParams)), "Sequence validation failed"); } DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offBase, matchLength, litLength); @@ -7014,19 +7127,27 @@ ZSTD_parameters ZSTD_getParams(int compressionLevel, unsigned long long srcSizeH } void ZSTD_registerSequenceProducer( - ZSTD_CCtx* zc, void* mState, - ZSTD_sequenceProducer_F* mFinder + ZSTD_CCtx* zc, + void* extSeqProdState, + ZSTD_sequenceProducer_F extSeqProdFunc ) { - if (mFinder != NULL) { - ZSTD_externalMatchCtx emctx; - emctx.mState = mState; - emctx.mFinder = mFinder; - emctx.seqBuffer = NULL; - emctx.seqBufferCapacity = 0; - zc->externalMatchCtx = emctx; - zc->requestedParams.useSequenceProducer = 1; + assert(zc != NULL); + ZSTD_CCtxParams_registerSequenceProducer( + &zc->requestedParams, extSeqProdState, extSeqProdFunc + ); +} + +void ZSTD_CCtxParams_registerSequenceProducer( + ZSTD_CCtx_params* params, + void* extSeqProdState, + ZSTD_sequenceProducer_F extSeqProdFunc +) { + assert(params != NULL); + if (extSeqProdFunc != NULL) { + params->extSeqProdFunc = extSeqProdFunc; + params->extSeqProdState = extSeqProdState; } else { - ZSTD_memset(&zc->externalMatchCtx, 0, sizeof(zc->externalMatchCtx)); - zc->requestedParams.useSequenceProducer = 0; + params->extSeqProdFunc = NULL; + params->extSeqProdState = NULL; } } diff --git a/src/thirdparty/zstd/compress/zstd_compress_internal.h b/src/thirdparty/zstd/compress/zstd_compress_internal.h index 10f68d01..e41d7b78 100644 --- a/src/thirdparty/zstd/compress/zstd_compress_internal.h +++ b/src/thirdparty/zstd/compress/zstd_compress_internal.h @@ -39,7 +39,7 @@ extern "C" { It's not a big deal though : candidate will just be sorted again. Additionally, candidate position 1 will be lost. But candidate 1 cannot hide a large tree of candidates, so it's a minimal loss. - The benefit is that ZSTD_DUBT_UNSORTED_MARK cannot be mishandled after table re-use with a different strategy. + The benefit is that ZSTD_DUBT_UNSORTED_MARK cannot be mishandled after table reuse with a different strategy. This constant is required by ZSTD_compressBlock_btlazy2() and ZSTD_reduceTable_internal() */ @@ -159,23 +159,24 @@ typedef struct { UNUSED_ATTR static const rawSeqStore_t kNullRawSeqStore = {NULL, 0, 0, 0, 0}; typedef struct { - int price; - U32 off; - U32 mlen; - U32 litlen; - U32 rep[ZSTD_REP_NUM]; + int price; /* price from beginning of segment to this position */ + U32 off; /* offset of previous match */ + U32 mlen; /* length of previous match */ + U32 litlen; /* nb of literals since previous match */ + U32 rep[ZSTD_REP_NUM]; /* offset history after previous match */ } ZSTD_optimal_t; typedef enum { zop_dynamic=0, zop_predef } ZSTD_OptPrice_e; +#define ZSTD_OPT_SIZE (ZSTD_OPT_NUM+3) typedef struct { /* All tables are allocated inside cctx->workspace by ZSTD_resetCCtx_internal() */ unsigned* litFreq; /* table of literals statistics, of size 256 */ unsigned* litLengthFreq; /* table of litLength statistics, of size (MaxLL+1) */ unsigned* matchLengthFreq; /* table of matchLength statistics, of size (MaxML+1) */ unsigned* offCodeFreq; /* table of offCode statistics, of size (MaxOff+1) */ - ZSTD_match_t* matchTable; /* list of found matches, of size ZSTD_OPT_NUM+1 */ - ZSTD_optimal_t* priceTable; /* All positions tracked by optimal parser, of size ZSTD_OPT_NUM+1 */ + ZSTD_match_t* matchTable; /* list of found matches, of size ZSTD_OPT_SIZE */ + ZSTD_optimal_t* priceTable; /* All positions tracked by optimal parser, of size ZSTD_OPT_SIZE */ U32 litSum; /* nb of literals */ U32 litLengthSum; /* nb of litLength codes */ @@ -228,7 +229,7 @@ struct ZSTD_matchState_t { U32 rowHashLog; /* For row-based matchfinder: Hashlog based on nb of rows in the hashTable.*/ BYTE* tagTable; /* For row-based matchFinder: A row-based table containing the hashes and head index. */ U32 hashCache[ZSTD_ROW_HASH_CACHE_SIZE]; /* For row-based matchFinder: a cache of hashes to improve speed */ - U64 hashSalt; /* For row-based matchFinder: salts the hash for re-use of tag table */ + U64 hashSalt; /* For row-based matchFinder: salts the hash for reuse of tag table */ U32 hashSaltEntropy; /* For row-based matchFinder: collects entropy for salt generation */ U32* hashTable; @@ -360,10 +361,11 @@ struct ZSTD_CCtx_params_s { * if the external matchfinder returns an error code. */ int enableMatchFinderFallback; - /* Indicates whether an external matchfinder has been referenced. - * Users can't set this externally. - * It is set internally in ZSTD_registerSequenceProducer(). */ - int useSequenceProducer; + /* Parameters for the external sequence producer API. + * Users set these parameters through ZSTD_registerSequenceProducer(). + * It is not possible to set these parameters individually through the public API. */ + void* extSeqProdState; + ZSTD_sequenceProducer_F extSeqProdFunc; /* Adjust the max block size*/ size_t maxBlockSize; @@ -401,14 +403,6 @@ typedef struct { ZSTD_entropyCTablesMetadata_t entropyMetadata; } ZSTD_blockSplitCtx; -/* Context for block-level external matchfinder API */ -typedef struct { - void* mState; - ZSTD_sequenceProducer_F* mFinder; - ZSTD_Sequence* seqBuffer; - size_t seqBufferCapacity; -} ZSTD_externalMatchCtx; - struct ZSTD_CCtx_s { ZSTD_compressionStage_e stage; int cParamsChanged; /* == 1 if cParams(except wlog) or compression level are changed in requestedParams. Triggers transmission of new params to ZSTDMT (if available) then reset to 0. */ @@ -479,8 +473,9 @@ struct ZSTD_CCtx_s { /* Workspace for block splitter */ ZSTD_blockSplitCtx blockSplitCtx; - /* Workspace for external matchfinder */ - ZSTD_externalMatchCtx externalMatchCtx; + /* Buffer for output from external sequence producer */ + ZSTD_Sequence* extSeqBuf; + size_t extSeqBufCapacity; }; typedef enum { ZSTD_dtlm_fast, ZSTD_dtlm_full } ZSTD_dictTableLoadMethod_e; @@ -1053,7 +1048,9 @@ MEM_STATIC U32 ZSTD_window_needOverflowCorrection(ZSTD_window_t const window, * The least significant cycleLog bits of the indices must remain the same, * which may be 0. Every index up to maxDist in the past must be valid. */ -MEM_STATIC U32 ZSTD_window_correctOverflow(ZSTD_window_t* window, U32 cycleLog, +MEM_STATIC +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +U32 ZSTD_window_correctOverflow(ZSTD_window_t* window, U32 cycleLog, U32 maxDist, void const* src) { /* preemptive overflow correction: @@ -1246,7 +1243,9 @@ MEM_STATIC void ZSTD_window_init(ZSTD_window_t* window) { * forget about the extDict. Handles overlap of the prefix and extDict. * Returns non-zero if the segment is contiguous. */ -MEM_STATIC U32 ZSTD_window_update(ZSTD_window_t* window, +MEM_STATIC +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +U32 ZSTD_window_update(ZSTD_window_t* window, void const* src, size_t srcSize, int forceNonContiguous) { @@ -1467,11 +1466,10 @@ size_t ZSTD_writeLastEmptyBlock(void* dst, size_t dstCapacity); * This cannot be used when long range matching is enabled. * Zstd will use these sequences, and pass the literals to a secondary block * compressor. - * @return : An error code on failure. * NOTE: seqs are not verified! Invalid sequences can cause out-of-bounds memory * access and data corruption. */ -size_t ZSTD_referenceExternalSequences(ZSTD_CCtx* cctx, rawSeq* seq, size_t nbSeq); +void ZSTD_referenceExternalSequences(ZSTD_CCtx* cctx, rawSeq* seq, size_t nbSeq); /** ZSTD_cycleLog() : * condition for correct operation : hashLog > 1 */ @@ -1509,6 +1507,10 @@ ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_sequencePosition* const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, const void* src, size_t blockSize, ZSTD_paramSwitch_e externalRepSearch); +/* Returns 1 if an external sequence producer is registered, otherwise returns 0. */ +MEM_STATIC int ZSTD_hasExtSeqProd(const ZSTD_CCtx_params* params) { + return params->extSeqProdFunc != NULL; +} /* =============================================================== * Deprecated definitions that are still used internally to avoid diff --git a/src/thirdparty/zstd/compress/zstd_compress_superblock.c b/src/thirdparty/zstd/compress/zstd_compress_superblock.c index 638c4acb..628a2dcc 100644 --- a/src/thirdparty/zstd/compress/zstd_compress_superblock.c +++ b/src/thirdparty/zstd/compress/zstd_compress_superblock.c @@ -76,8 +76,8 @@ ZSTD_compressSubBlock_literal(const HUF_CElt* hufTable, } { int const flags = bmi2 ? HUF_flags_bmi2 : 0; - const size_t cSize = singleStream ? HUF_compress1X_usingCTable(op, oend-op, literals, litSize, hufTable, flags) - : HUF_compress4X_usingCTable(op, oend-op, literals, litSize, hufTable, flags); + const size_t cSize = singleStream ? HUF_compress1X_usingCTable(op, (size_t)(oend-op), literals, litSize, hufTable, flags) + : HUF_compress4X_usingCTable(op, (size_t)(oend-op), literals, litSize, hufTable, flags); op += cSize; cLitSize += cSize; if (cSize == 0 || ERR_isError(cSize)) { @@ -102,7 +102,7 @@ ZSTD_compressSubBlock_literal(const HUF_CElt* hufTable, switch(lhSize) { case 3: /* 2 - 2 - 10 - 10 */ - { U32 const lhc = hType + ((!singleStream) << 2) + ((U32)litSize<<4) + ((U32)cLitSize<<14); + { U32 const lhc = hType + ((U32)(!singleStream) << 2) + ((U32)litSize<<4) + ((U32)cLitSize<<14); MEM_writeLE24(ostart, lhc); break; } @@ -122,30 +122,30 @@ ZSTD_compressSubBlock_literal(const HUF_CElt* hufTable, } *entropyWritten = 1; DEBUGLOG(5, "Compressed literals: %u -> %u", (U32)litSize, (U32)(op-ostart)); - return op-ostart; + return (size_t)(op-ostart); } static size_t ZSTD_seqDecompressedSize(seqStore_t const* seqStore, - const seqDef* sequences, size_t nbSeq, - size_t litSize, int lastSequence) + const seqDef* sequences, size_t nbSeqs, + size_t litSize, int lastSubBlock) { - const seqDef* const sstart = sequences; - const seqDef* const send = sequences + nbSeq; - const seqDef* sp = sstart; size_t matchLengthSum = 0; size_t litLengthSum = 0; - (void)(litLengthSum); /* suppress unused variable warning on some environments */ - while (send-sp > 0) { - ZSTD_sequenceLength const seqLen = ZSTD_getSequenceLength(seqStore, sp); + size_t n; + for (n=0; n>8) + 0x80), op[1] = (BYTE)nbSeq, op+=2; else op[0]=0xFF, MEM_writeLE16(op+1, (U16)(nbSeq - LONGNBSEQ)), op+=3; if (nbSeq==0) { - return op - ostart; + return (size_t)(op - ostart); } /* seqHead : flags for FSE encoding type */ @@ -209,7 +209,7 @@ ZSTD_compressSubBlock_sequences(const ZSTD_fseCTables_t* fseTables, } { size_t const bitstreamSize = ZSTD_encodeSequences( - op, oend - op, + op, (size_t)(oend - op), fseTables->matchlengthCTable, mlCode, fseTables->offcodeCTable, ofCode, fseTables->litlengthCTable, llCode, @@ -253,7 +253,7 @@ ZSTD_compressSubBlock_sequences(const ZSTD_fseCTables_t* fseTables, #endif *entropyWritten = 1; - return op - ostart; + return (size_t)(op - ostart); } /** ZSTD_compressSubBlock() : @@ -279,7 +279,8 @@ static size_t ZSTD_compressSubBlock(const ZSTD_entropyCTables_t* entropy, litSize, nbSeq, writeLitEntropy, writeSeqEntropy, lastBlock); { size_t cLitSize = ZSTD_compressSubBlock_literal((const HUF_CElt*)entropy->huf.CTable, &entropyMetadata->hufMetadata, literals, litSize, - op, oend-op, bmi2, writeLitEntropy, litEntropyWritten); + op, (size_t)(oend-op), + bmi2, writeLitEntropy, litEntropyWritten); FORWARD_IF_ERROR(cLitSize, "ZSTD_compressSubBlock_literal failed"); if (cLitSize == 0) return 0; op += cLitSize; @@ -289,18 +290,18 @@ static size_t ZSTD_compressSubBlock(const ZSTD_entropyCTables_t* entropy, sequences, nbSeq, llCode, mlCode, ofCode, cctxParams, - op, oend-op, + op, (size_t)(oend-op), bmi2, writeSeqEntropy, seqEntropyWritten); FORWARD_IF_ERROR(cSeqSize, "ZSTD_compressSubBlock_sequences failed"); if (cSeqSize == 0) return 0; op += cSeqSize; } /* Write block header */ - { size_t cSize = (op-ostart)-ZSTD_blockHeaderSize; + { size_t cSize = (size_t)(op-ostart) - ZSTD_blockHeaderSize; U32 const cBlockHeader24 = lastBlock + (((U32)bt_compressed)<<1) + (U32)(cSize << 3); MEM_writeLE24(ostart, cBlockHeader24); } - return op-ostart; + return (size_t)(op-ostart); } static size_t ZSTD_estimateSubBlockSize_literal(const BYTE* literals, size_t litSize, @@ -389,7 +390,11 @@ static size_t ZSTD_estimateSubBlockSize_sequences(const BYTE* ofCodeTable, return cSeqSizeEstimate + sequencesSectionHeaderSize; } -static size_t ZSTD_estimateSubBlockSize(const BYTE* literals, size_t litSize, +typedef struct { + size_t estLitSize; + size_t estBlockSize; +} EstimatedBlockSize; +static EstimatedBlockSize ZSTD_estimateSubBlockSize(const BYTE* literals, size_t litSize, const BYTE* ofCodeTable, const BYTE* llCodeTable, const BYTE* mlCodeTable, @@ -397,15 +402,17 @@ static size_t ZSTD_estimateSubBlockSize(const BYTE* literals, size_t litSize, const ZSTD_entropyCTables_t* entropy, const ZSTD_entropyCTablesMetadata_t* entropyMetadata, void* workspace, size_t wkspSize, - int writeLitEntropy, int writeSeqEntropy) { - size_t cSizeEstimate = 0; - cSizeEstimate += ZSTD_estimateSubBlockSize_literal(literals, litSize, - &entropy->huf, &entropyMetadata->hufMetadata, - workspace, wkspSize, writeLitEntropy); - cSizeEstimate += ZSTD_estimateSubBlockSize_sequences(ofCodeTable, llCodeTable, mlCodeTable, + int writeLitEntropy, int writeSeqEntropy) +{ + EstimatedBlockSize ebs; + ebs.estLitSize = ZSTD_estimateSubBlockSize_literal(literals, litSize, + &entropy->huf, &entropyMetadata->hufMetadata, + workspace, wkspSize, writeLitEntropy); + ebs.estBlockSize = ZSTD_estimateSubBlockSize_sequences(ofCodeTable, llCodeTable, mlCodeTable, nbSeq, &entropy->fse, &entropyMetadata->fseMetadata, workspace, wkspSize, writeSeqEntropy); - return cSizeEstimate + ZSTD_blockHeaderSize; + ebs.estBlockSize += ebs.estLitSize + ZSTD_blockHeaderSize; + return ebs; } static int ZSTD_needSequenceEntropyTables(ZSTD_fseCTablesMetadata_t const* fseMetadata) @@ -419,13 +426,56 @@ static int ZSTD_needSequenceEntropyTables(ZSTD_fseCTablesMetadata_t const* fseMe return 0; } +static size_t countLiterals(seqStore_t const* seqStore, const seqDef* sp, size_t seqCount) +{ + size_t n, total = 0; + assert(sp != NULL); + for (n=0; n %zu bytes", seqCount, (const void*)sp, total); + return total; +} + +#define BYTESCALE 256 + +static size_t sizeBlockSequences(const seqDef* sp, size_t nbSeqs, + size_t targetBudget, size_t avgLitCost, size_t avgSeqCost, + int firstSubBlock) +{ + size_t n, budget = 0, inSize=0; + /* entropy headers */ + size_t const headerSize = (size_t)firstSubBlock * 120 * BYTESCALE; /* generous estimate */ + assert(firstSubBlock==0 || firstSubBlock==1); + budget += headerSize; + + /* first sequence => at least one sequence*/ + budget += sp[0].litLength * avgLitCost + avgSeqCost; + if (budget > targetBudget) return 1; + inSize = sp[0].litLength + (sp[0].mlBase+MINMATCH); + + /* loop over sequences */ + for (n=1; n targetBudget) + /* though continue to expand until the sub-block is deemed compressible */ + && (budget < inSize * BYTESCALE) ) + break; + } + + return n; +} + /** ZSTD_compressSubBlock_multi() : * Breaks super-block into multiple sub-blocks and compresses them. - * Entropy will be written to the first block. - * The following blocks will use repeat mode to compress. - * All sub-blocks are compressed blocks (no raw or rle blocks). - * @return : compressed size of the super block (which is multiple ZSTD blocks) - * Or 0 if it failed to compress. */ + * Entropy will be written into the first block. + * The following blocks use repeat_mode to compress. + * Sub-blocks are all compressed, except the last one when beneficial. + * @return : compressed size of the super block (which features multiple ZSTD blocks) + * or 0 if it failed to compress. */ static size_t ZSTD_compressSubBlock_multi(const seqStore_t* seqStorePtr, const ZSTD_compressedBlockState_t* prevCBlock, ZSTD_compressedBlockState_t* nextCBlock, @@ -438,10 +488,12 @@ static size_t ZSTD_compressSubBlock_multi(const seqStore_t* seqStorePtr, { const seqDef* const sstart = seqStorePtr->sequencesStart; const seqDef* const send = seqStorePtr->sequences; - const seqDef* sp = sstart; + const seqDef* sp = sstart; /* tracks progresses within seqStorePtr->sequences */ + size_t const nbSeqs = (size_t)(send - sstart); const BYTE* const lstart = seqStorePtr->litStart; const BYTE* const lend = seqStorePtr->lit; const BYTE* lp = lstart; + size_t const nbLiterals = (size_t)(lend - lstart); BYTE const* ip = (BYTE const*)src; BYTE const* const iend = ip + srcSize; BYTE* const ostart = (BYTE*)dst; @@ -450,96 +502,152 @@ static size_t ZSTD_compressSubBlock_multi(const seqStore_t* seqStorePtr, const BYTE* llCodePtr = seqStorePtr->llCode; const BYTE* mlCodePtr = seqStorePtr->mlCode; const BYTE* ofCodePtr = seqStorePtr->ofCode; - size_t targetCBlockSize = cctxParams->targetCBlockSize; - size_t litSize, seqCount; - int writeLitEntropy = entropyMetadata->hufMetadata.hType == set_compressed; + size_t const minTarget = ZSTD_TARGETCBLOCKSIZE_MIN; /* enforce minimum size, to reduce undesirable side effects */ + size_t const targetCBlockSize = MAX(minTarget, cctxParams->targetCBlockSize); + int writeLitEntropy = (entropyMetadata->hufMetadata.hType == set_compressed); int writeSeqEntropy = 1; - int lastSequence = 0; - DEBUGLOG(5, "ZSTD_compressSubBlock_multi (litSize=%u, nbSeq=%u)", - (unsigned)(lend-lp), (unsigned)(send-sstart)); + DEBUGLOG(5, "ZSTD_compressSubBlock_multi (srcSize=%u, litSize=%u, nbSeq=%u)", + (unsigned)srcSize, (unsigned)(lend-lstart), (unsigned)(send-sstart)); - litSize = 0; - seqCount = 0; - do { - size_t cBlockSizeEstimate = 0; - if (sstart == send) { - lastSequence = 1; - } else { - const seqDef* const sequence = sp + seqCount; - lastSequence = sequence == send - 1; - litSize += ZSTD_getSequenceLength(seqStorePtr, sequence).litLength; - seqCount++; + /* let's start by a general estimation for the full block */ + if (nbSeqs > 0) { + EstimatedBlockSize const ebs = + ZSTD_estimateSubBlockSize(lp, nbLiterals, + ofCodePtr, llCodePtr, mlCodePtr, nbSeqs, + &nextCBlock->entropy, entropyMetadata, + workspace, wkspSize, + writeLitEntropy, writeSeqEntropy); + /* quick estimation */ + size_t const avgLitCost = nbLiterals ? (ebs.estLitSize * BYTESCALE) / nbLiterals : BYTESCALE; + size_t const avgSeqCost = ((ebs.estBlockSize - ebs.estLitSize) * BYTESCALE) / nbSeqs; + const size_t nbSubBlocks = MAX((ebs.estBlockSize + (targetCBlockSize/2)) / targetCBlockSize, 1); + size_t n, avgBlockBudget, blockBudgetSupp=0; + avgBlockBudget = (ebs.estBlockSize * BYTESCALE) / nbSubBlocks; + DEBUGLOG(5, "estimated fullblock size=%u bytes ; avgLitCost=%.2f ; avgSeqCost=%.2f ; targetCBlockSize=%u, nbSubBlocks=%u ; avgBlockBudget=%.0f bytes", + (unsigned)ebs.estBlockSize, (double)avgLitCost/BYTESCALE, (double)avgSeqCost/BYTESCALE, + (unsigned)targetCBlockSize, (unsigned)nbSubBlocks, (double)avgBlockBudget/BYTESCALE); + /* simplification: if estimates states that the full superblock doesn't compress, just bail out immediately + * this will result in the production of a single uncompressed block covering @srcSize.*/ + if (ebs.estBlockSize > srcSize) return 0; + + /* compress and write sub-blocks */ + assert(nbSubBlocks>0); + for (n=0; n < nbSubBlocks-1; n++) { + /* determine nb of sequences for current sub-block + nbLiterals from next sequence */ + size_t const seqCount = sizeBlockSequences(sp, (size_t)(send-sp), + avgBlockBudget + blockBudgetSupp, avgLitCost, avgSeqCost, n==0); + /* if reached last sequence : break to last sub-block (simplification) */ + assert(seqCount <= (size_t)(send-sp)); + if (sp + seqCount == send) break; + assert(seqCount > 0); + /* compress sub-block */ + { int litEntropyWritten = 0; + int seqEntropyWritten = 0; + size_t litSize = countLiterals(seqStorePtr, sp, seqCount); + const size_t decompressedSize = + ZSTD_seqDecompressedSize(seqStorePtr, sp, seqCount, litSize, 0); + size_t const cSize = ZSTD_compressSubBlock(&nextCBlock->entropy, entropyMetadata, + sp, seqCount, + lp, litSize, + llCodePtr, mlCodePtr, ofCodePtr, + cctxParams, + op, (size_t)(oend-op), + bmi2, writeLitEntropy, writeSeqEntropy, + &litEntropyWritten, &seqEntropyWritten, + 0); + FORWARD_IF_ERROR(cSize, "ZSTD_compressSubBlock failed"); + + /* check compressibility, update state components */ + if (cSize > 0 && cSize < decompressedSize) { + DEBUGLOG(5, "Committed sub-block compressing %u bytes => %u bytes", + (unsigned)decompressedSize, (unsigned)cSize); + assert(ip + decompressedSize <= iend); + ip += decompressedSize; + lp += litSize; + op += cSize; + llCodePtr += seqCount; + mlCodePtr += seqCount; + ofCodePtr += seqCount; + /* Entropy only needs to be written once */ + if (litEntropyWritten) { + writeLitEntropy = 0; + } + if (seqEntropyWritten) { + writeSeqEntropy = 0; + } + sp += seqCount; + blockBudgetSupp = 0; + } } + /* otherwise : do not compress yet, coalesce current sub-block with following one */ } - if (lastSequence) { - assert(lp <= lend); - assert(litSize <= (size_t)(lend - lp)); - litSize = (size_t)(lend - lp); - } - /* I think there is an optimization opportunity here. - * Calling ZSTD_estimateSubBlockSize for every sequence can be wasteful - * since it recalculates estimate from scratch. - * For example, it would recount literal distribution and symbol codes every time. - */ - cBlockSizeEstimate = ZSTD_estimateSubBlockSize(lp, litSize, ofCodePtr, llCodePtr, mlCodePtr, seqCount, - &nextCBlock->entropy, entropyMetadata, - workspace, wkspSize, writeLitEntropy, writeSeqEntropy); - if (cBlockSizeEstimate > targetCBlockSize || lastSequence) { - int litEntropyWritten = 0; - int seqEntropyWritten = 0; - const size_t decompressedSize = ZSTD_seqDecompressedSize(seqStorePtr, sp, seqCount, litSize, lastSequence); - const size_t cSize = ZSTD_compressSubBlock(&nextCBlock->entropy, entropyMetadata, - sp, seqCount, - lp, litSize, - llCodePtr, mlCodePtr, ofCodePtr, - cctxParams, - op, oend-op, - bmi2, writeLitEntropy, writeSeqEntropy, - &litEntropyWritten, &seqEntropyWritten, - lastBlock && lastSequence); - FORWARD_IF_ERROR(cSize, "ZSTD_compressSubBlock failed"); - if (cSize > 0 && cSize < decompressedSize) { - DEBUGLOG(5, "Committed the sub-block"); - assert(ip + decompressedSize <= iend); - ip += decompressedSize; - sp += seqCount; - lp += litSize; - op += cSize; - llCodePtr += seqCount; - mlCodePtr += seqCount; - ofCodePtr += seqCount; - litSize = 0; - seqCount = 0; - /* Entropy only needs to be written once */ - if (litEntropyWritten) { - writeLitEntropy = 0; - } - if (seqEntropyWritten) { - writeSeqEntropy = 0; - } + } /* if (nbSeqs > 0) */ + + /* write last block */ + DEBUGLOG(5, "Generate last sub-block: %u sequences remaining", (unsigned)(send - sp)); + { int litEntropyWritten = 0; + int seqEntropyWritten = 0; + size_t litSize = (size_t)(lend - lp); + size_t seqCount = (size_t)(send - sp); + const size_t decompressedSize = + ZSTD_seqDecompressedSize(seqStorePtr, sp, seqCount, litSize, 1); + size_t const cSize = ZSTD_compressSubBlock(&nextCBlock->entropy, entropyMetadata, + sp, seqCount, + lp, litSize, + llCodePtr, mlCodePtr, ofCodePtr, + cctxParams, + op, (size_t)(oend-op), + bmi2, writeLitEntropy, writeSeqEntropy, + &litEntropyWritten, &seqEntropyWritten, + lastBlock); + FORWARD_IF_ERROR(cSize, "ZSTD_compressSubBlock failed"); + + /* update pointers, the nb of literals borrowed from next sequence must be preserved */ + if (cSize > 0 && cSize < decompressedSize) { + DEBUGLOG(5, "Last sub-block compressed %u bytes => %u bytes", + (unsigned)decompressedSize, (unsigned)cSize); + assert(ip + decompressedSize <= iend); + ip += decompressedSize; + lp += litSize; + op += cSize; + llCodePtr += seqCount; + mlCodePtr += seqCount; + ofCodePtr += seqCount; + /* Entropy only needs to be written once */ + if (litEntropyWritten) { + writeLitEntropy = 0; } + if (seqEntropyWritten) { + writeSeqEntropy = 0; + } + sp += seqCount; } - } while (!lastSequence); + } + + if (writeLitEntropy) { - DEBUGLOG(5, "ZSTD_compressSubBlock_multi has literal entropy tables unwritten"); + DEBUGLOG(5, "Literal entropy tables were never written"); ZSTD_memcpy(&nextCBlock->entropy.huf, &prevCBlock->entropy.huf, sizeof(prevCBlock->entropy.huf)); } if (writeSeqEntropy && ZSTD_needSequenceEntropyTables(&entropyMetadata->fseMetadata)) { /* If we haven't written our entropy tables, then we've violated our contract and * must emit an uncompressed block. */ - DEBUGLOG(5, "ZSTD_compressSubBlock_multi has sequence entropy tables unwritten"); + DEBUGLOG(5, "Sequence entropy tables were never written => cancel, emit an uncompressed block"); return 0; } + if (ip < iend) { - size_t const cSize = ZSTD_noCompressBlock(op, oend - op, ip, iend - ip, lastBlock); - DEBUGLOG(5, "ZSTD_compressSubBlock_multi last sub-block uncompressed, %zu bytes", (size_t)(iend - ip)); + /* some data left : last part of the block sent uncompressed */ + size_t const rSize = (size_t)((iend - ip)); + size_t const cSize = ZSTD_noCompressBlock(op, (size_t)(oend - op), ip, rSize, lastBlock); + DEBUGLOG(5, "Generate last uncompressed sub-block of %u bytes", (unsigned)(rSize)); FORWARD_IF_ERROR(cSize, "ZSTD_noCompressBlock failed"); assert(cSize != 0); op += cSize; /* We have to regenerate the repcodes because we've skipped some sequences */ if (sp < send) { - seqDef const* seq; + const seqDef* seq; repcodes_t rep; ZSTD_memcpy(&rep, prevCBlock->rep, sizeof(rep)); for (seq = sstart; seq < sp; ++seq) { @@ -548,14 +656,17 @@ static size_t ZSTD_compressSubBlock_multi(const seqStore_t* seqStorePtr, ZSTD_memcpy(nextCBlock->rep, &rep, sizeof(rep)); } } - DEBUGLOG(5, "ZSTD_compressSubBlock_multi compressed"); - return op-ostart; + + DEBUGLOG(5, "ZSTD_compressSubBlock_multi compressed all subBlocks: total compressed size = %u", + (unsigned)(op-ostart)); + return (size_t)(op-ostart); } size_t ZSTD_compressSuperBlock(ZSTD_CCtx* zc, void* dst, size_t dstCapacity, - void const* src, size_t srcSize, - unsigned lastBlock) { + const void* src, size_t srcSize, + unsigned lastBlock) +{ ZSTD_entropyCTablesMetadata_t entropyMetadata; FORWARD_IF_ERROR(ZSTD_buildBlockEntropyStats(&zc->seqStore, diff --git a/src/thirdparty/zstd/compress/zstd_cwksp.h b/src/thirdparty/zstd/compress/zstd_cwksp.h index cc7fb1c7..3eddbd33 100644 --- a/src/thirdparty/zstd/compress/zstd_cwksp.h +++ b/src/thirdparty/zstd/compress/zstd_cwksp.h @@ -192,6 +192,7 @@ MEM_STATIC void ZSTD_cwksp_assert_internal_consistency(ZSTD_cwksp* ws) { { intptr_t const offset = __msan_test_shadow(ws->initOnceStart, (U8*)ZSTD_cwksp_initialAllocStart(ws) - (U8*)ws->initOnceStart); + (void)offset; #if defined(ZSTD_MSAN_PRINT) if(offset!=-1) { __msan_print_shadow((U8*)ws->initOnceStart + offset - 8, 32); @@ -433,7 +434,7 @@ MEM_STATIC void* ZSTD_cwksp_reserve_aligned(ZSTD_cwksp* ws, size_t bytes) /** * Aligned on 64 bytes. These buffers have the special property that - * their values remain constrained, allowing us to re-use them without + * their values remain constrained, allowing us to reuse them without * memset()-ing them. */ MEM_STATIC void* ZSTD_cwksp_reserve_table(ZSTD_cwksp* ws, size_t bytes) @@ -525,7 +526,7 @@ MEM_STATIC void ZSTD_cwksp_mark_tables_dirty(ZSTD_cwksp* ws) DEBUGLOG(4, "cwksp: ZSTD_cwksp_mark_tables_dirty"); #if ZSTD_MEMORY_SANITIZER && !defined (ZSTD_MSAN_DONT_POISON_WORKSPACE) - /* To validate that the table re-use logic is sound, and that we don't + /* To validate that the table reuse logic is sound, and that we don't * access table space that we haven't cleaned, we re-"poison" the table * space every time we mark it dirty. * Since tableValidEnd space and initOnce space may overlap we don't poison @@ -602,9 +603,9 @@ MEM_STATIC void ZSTD_cwksp_clear(ZSTD_cwksp* ws) { DEBUGLOG(4, "cwksp: clearing!"); #if ZSTD_MEMORY_SANITIZER && !defined (ZSTD_MSAN_DONT_POISON_WORKSPACE) - /* To validate that the context re-use logic is sound, and that we don't + /* To validate that the context reuse logic is sound, and that we don't * access stuff that this compression hasn't initialized, we re-"poison" - * the workspace except for the areas in which we expect memory re-use + * the workspace except for the areas in which we expect memory reuse * without initialization (objects, valid tables area and init once * memory). */ { @@ -635,6 +636,15 @@ MEM_STATIC void ZSTD_cwksp_clear(ZSTD_cwksp* ws) { ZSTD_cwksp_assert_internal_consistency(ws); } +MEM_STATIC size_t ZSTD_cwksp_sizeof(const ZSTD_cwksp* ws) { + return (size_t)((BYTE*)ws->workspaceEnd - (BYTE*)ws->workspace); +} + +MEM_STATIC size_t ZSTD_cwksp_used(const ZSTD_cwksp* ws) { + return (size_t)((BYTE*)ws->tableEnd - (BYTE*)ws->workspace) + + (size_t)((BYTE*)ws->workspaceEnd - (BYTE*)ws->allocStart); +} + /** * The provided workspace takes ownership of the buffer [start, start+size). * Any existing values in the workspace are ignored (the previously managed @@ -666,6 +676,11 @@ MEM_STATIC size_t ZSTD_cwksp_create(ZSTD_cwksp* ws, size_t size, ZSTD_customMem MEM_STATIC void ZSTD_cwksp_free(ZSTD_cwksp* ws, ZSTD_customMem customMem) { void *ptr = ws->workspace; DEBUGLOG(4, "cwksp: freeing workspace"); +#if ZSTD_MEMORY_SANITIZER && !defined(ZSTD_MSAN_DONT_POISON_WORKSPACE) + if (ptr != NULL && customMem.customFree != NULL) { + __msan_unpoison(ptr, ZSTD_cwksp_sizeof(ws)); + } +#endif ZSTD_memset(ws, 0, sizeof(ZSTD_cwksp)); ZSTD_customFree(ptr, customMem); } @@ -679,15 +694,6 @@ MEM_STATIC void ZSTD_cwksp_move(ZSTD_cwksp* dst, ZSTD_cwksp* src) { ZSTD_memset(src, 0, sizeof(ZSTD_cwksp)); } -MEM_STATIC size_t ZSTD_cwksp_sizeof(const ZSTD_cwksp* ws) { - return (size_t)((BYTE*)ws->workspaceEnd - (BYTE*)ws->workspace); -} - -MEM_STATIC size_t ZSTD_cwksp_used(const ZSTD_cwksp* ws) { - return (size_t)((BYTE*)ws->tableEnd - (BYTE*)ws->workspace) - + (size_t)((BYTE*)ws->workspaceEnd - (BYTE*)ws->allocStart); -} - MEM_STATIC int ZSTD_cwksp_reserve_failed(const ZSTD_cwksp* ws) { return ws->allocFailed; } diff --git a/src/thirdparty/zstd/compress/zstd_double_fast.c b/src/thirdparty/zstd/compress/zstd_double_fast.c index 0ad88ffc..a4e9c50d 100644 --- a/src/thirdparty/zstd/compress/zstd_double_fast.c +++ b/src/thirdparty/zstd/compress/zstd_double_fast.c @@ -11,7 +11,11 @@ #include "zstd_compress_internal.h" #include "zstd_double_fast.h" -static void ZSTD_fillDoubleHashTableForCDict(ZSTD_matchState_t* ms, +#ifndef ZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR + +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +void ZSTD_fillDoubleHashTableForCDict(ZSTD_matchState_t* ms, void const* end, ZSTD_dictTableLoadMethod_e dtlm) { const ZSTD_compressionParameters* const cParams = &ms->cParams; @@ -47,7 +51,9 @@ static void ZSTD_fillDoubleHashTableForCDict(ZSTD_matchState_t* ms, } } } -static void ZSTD_fillDoubleHashTableForCCtx(ZSTD_matchState_t* ms, +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +void ZSTD_fillDoubleHashTableForCCtx(ZSTD_matchState_t* ms, void const* end, ZSTD_dictTableLoadMethod_e dtlm) { const ZSTD_compressionParameters* const cParams = &ms->cParams; @@ -95,6 +101,7 @@ void ZSTD_fillDoubleHashTable(ZSTD_matchState_t* ms, FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR size_t ZSTD_compressBlock_doubleFast_noDict_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize, U32 const mls /* template */) @@ -305,6 +312,7 @@ _match_stored: FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR size_t ZSTD_compressBlock_doubleFast_dictMatchState_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize, @@ -348,8 +356,8 @@ size_t ZSTD_compressBlock_doubleFast_dictMatchState_generic( if (ms->prefetchCDictTables) { size_t const hashTableBytes = (((size_t)1) << dictCParams->hashLog) * sizeof(U32); size_t const chainTableBytes = (((size_t)1) << dictCParams->chainLog) * sizeof(U32); - PREFETCH_AREA(dictHashLong, hashTableBytes) - PREFETCH_AREA(dictHashSmall, chainTableBytes) + PREFETCH_AREA(dictHashLong, hashTableBytes); + PREFETCH_AREA(dictHashSmall, chainTableBytes); } /* init */ @@ -589,7 +597,9 @@ size_t ZSTD_compressBlock_doubleFast_dictMatchState( } -static size_t ZSTD_compressBlock_doubleFast_extDict_generic( +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +size_t ZSTD_compressBlock_doubleFast_extDict_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize, U32 const mls /* template */) @@ -756,3 +766,5 @@ size_t ZSTD_compressBlock_doubleFast_extDict( return ZSTD_compressBlock_doubleFast_extDict_7(ms, seqStore, rep, src, srcSize); } } + +#endif /* ZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR */ diff --git a/src/thirdparty/zstd/compress/zstd_double_fast.h b/src/thirdparty/zstd/compress/zstd_double_fast.h index 6f0047c4..ce6ed8c9 100644 --- a/src/thirdparty/zstd/compress/zstd_double_fast.h +++ b/src/thirdparty/zstd/compress/zstd_double_fast.h @@ -18,9 +18,12 @@ extern "C" { #include "../common/mem.h" /* U32 */ #include "zstd_compress_internal.h" /* ZSTD_CCtx, size_t */ +#ifndef ZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR + void ZSTD_fillDoubleHashTable(ZSTD_matchState_t* ms, void const* end, ZSTD_dictTableLoadMethod_e dtlm, ZSTD_tableFillPurpose_e tfp); + size_t ZSTD_compressBlock_doubleFast( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); @@ -31,6 +34,14 @@ size_t ZSTD_compressBlock_doubleFast_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); +#define ZSTD_COMPRESSBLOCK_DOUBLEFAST ZSTD_compressBlock_doubleFast +#define ZSTD_COMPRESSBLOCK_DOUBLEFAST_DICTMATCHSTATE ZSTD_compressBlock_doubleFast_dictMatchState +#define ZSTD_COMPRESSBLOCK_DOUBLEFAST_EXTDICT ZSTD_compressBlock_doubleFast_extDict +#else +#define ZSTD_COMPRESSBLOCK_DOUBLEFAST NULL +#define ZSTD_COMPRESSBLOCK_DOUBLEFAST_DICTMATCHSTATE NULL +#define ZSTD_COMPRESSBLOCK_DOUBLEFAST_EXTDICT NULL +#endif /* ZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR */ #if defined (__cplusplus) } diff --git a/src/thirdparty/zstd/compress/zstd_fast.c b/src/thirdparty/zstd/compress/zstd_fast.c index 5f2c6a2e..6c4554cf 100644 --- a/src/thirdparty/zstd/compress/zstd_fast.c +++ b/src/thirdparty/zstd/compress/zstd_fast.c @@ -11,7 +11,9 @@ #include "zstd_compress_internal.h" /* ZSTD_hashPtr, ZSTD_count, ZSTD_storeSeq */ #include "zstd_fast.h" -static void ZSTD_fillHashTableForCDict(ZSTD_matchState_t* ms, +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +void ZSTD_fillHashTableForCDict(ZSTD_matchState_t* ms, const void* const end, ZSTD_dictTableLoadMethod_e dtlm) { @@ -46,7 +48,9 @@ static void ZSTD_fillHashTableForCDict(ZSTD_matchState_t* ms, } } } } } -static void ZSTD_fillHashTableForCCtx(ZSTD_matchState_t* ms, +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +void ZSTD_fillHashTableForCCtx(ZSTD_matchState_t* ms, const void* const end, ZSTD_dictTableLoadMethod_e dtlm) { @@ -139,8 +143,9 @@ void ZSTD_fillHashTable(ZSTD_matchState_t* ms, * * This is also the work we do at the beginning to enter the loop initially. */ -FORCE_INLINE_TEMPLATE size_t -ZSTD_compressBlock_fast_noDict_generic( +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +size_t ZSTD_compressBlock_fast_noDict_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize, U32 const mls, U32 const hasStep) @@ -456,6 +461,7 @@ size_t ZSTD_compressBlock_fast( } FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR size_t ZSTD_compressBlock_fast_dictMatchState_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize, U32 const mls, U32 const hasStep) @@ -502,7 +508,7 @@ size_t ZSTD_compressBlock_fast_dictMatchState_generic( if (ms->prefetchCDictTables) { size_t const hashTableBytes = (((size_t)1) << dictCParams->hashLog) * sizeof(U32); - PREFETCH_AREA(dictHashTable, hashTableBytes) + PREFETCH_AREA(dictHashTable, hashTableBytes); } /* init */ @@ -681,7 +687,9 @@ size_t ZSTD_compressBlock_fast_dictMatchState( } -static size_t ZSTD_compressBlock_fast_extDict_generic( +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +size_t ZSTD_compressBlock_fast_extDict_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize, U32 const mls, U32 const hasStep) { diff --git a/src/thirdparty/zstd/compress/zstd_lazy.c b/src/thirdparty/zstd/compress/zstd_lazy.c index 5ba88e86..67dd55fd 100644 --- a/src/thirdparty/zstd/compress/zstd_lazy.c +++ b/src/thirdparty/zstd/compress/zstd_lazy.c @@ -12,6 +12,11 @@ #include "zstd_lazy.h" #include "../common/bits.h" /* ZSTD_countTrailingZeros64 */ +#if !defined(ZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_LAZY_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR) + #define kLazySkippingStep 8 @@ -19,8 +24,9 @@ * Binary Tree search ***************************************/ -static void -ZSTD_updateDUBT(ZSTD_matchState_t* ms, +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +void ZSTD_updateDUBT(ZSTD_matchState_t* ms, const BYTE* ip, const BYTE* iend, U32 mls) { @@ -63,8 +69,9 @@ ZSTD_updateDUBT(ZSTD_matchState_t* ms, * sort one already inserted but unsorted position * assumption : curr >= btlow == (curr - btmask) * doesn't fail */ -static void -ZSTD_insertDUBT1(const ZSTD_matchState_t* ms, +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +void ZSTD_insertDUBT1(const ZSTD_matchState_t* ms, U32 curr, const BYTE* inputEnd, U32 nbCompares, U32 btLow, const ZSTD_dictMode_e dictMode) @@ -152,8 +159,9 @@ ZSTD_insertDUBT1(const ZSTD_matchState_t* ms, } -static size_t -ZSTD_DUBT_findBetterDictMatch ( +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +size_t ZSTD_DUBT_findBetterDictMatch ( const ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iend, size_t* offsetPtr, @@ -230,8 +238,9 @@ ZSTD_DUBT_findBetterDictMatch ( } -static size_t -ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +size_t ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iend, size_t* offBasePtr, U32 const mls, @@ -381,8 +390,9 @@ ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, /** ZSTD_BtFindBestMatch() : Tree updater, providing best match */ -FORCE_INLINE_TEMPLATE size_t -ZSTD_BtFindBestMatch( ZSTD_matchState_t* ms, +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +size_t ZSTD_BtFindBestMatch( ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iLimit, size_t* offBasePtr, const U32 mls /* template */, @@ -617,7 +627,9 @@ size_t ZSTD_dedicatedDictSearch_lazy_search(size_t* offsetPtr, size_t ml, U32 nb /* Update chains up to ip (excluded) Assumption : always within prefix (i.e. not within extDict) */ -FORCE_INLINE_TEMPLATE U32 ZSTD_insertAndFindFirstIndex_internal( +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +U32 ZSTD_insertAndFindFirstIndex_internal( ZSTD_matchState_t* ms, const ZSTD_compressionParameters* const cParams, const BYTE* ip, U32 const mls, U32 const lazySkipping) @@ -651,6 +663,7 @@ U32 ZSTD_insertAndFindFirstIndex(ZSTD_matchState_t* ms, const BYTE* ip) { /* inlining is important to hardwire a hot branch (template emulation) */ FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR size_t ZSTD_HcFindBestMatch( ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iLimit, @@ -819,7 +832,9 @@ FORCE_INLINE_TEMPLATE void ZSTD_row_prefetch(U32 const* hashTable, BYTE const* t * Fill up the hash cache starting at idx, prefetching up to ZSTD_ROW_HASH_CACHE_SIZE entries, * but not beyond iLimit. */ -FORCE_INLINE_TEMPLATE void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, const BYTE* base, +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, const BYTE* base, U32 const rowLog, U32 const mls, U32 idx, const BYTE* const iLimit) { @@ -845,7 +860,9 @@ FORCE_INLINE_TEMPLATE void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, const B * Returns the hash of base + idx, and replaces the hash in the hash cache with the byte at * base + idx + ZSTD_ROW_HASH_CACHE_SIZE. Also prefetches the appropriate rows from hashTable and tagTable. */ -FORCE_INLINE_TEMPLATE U32 ZSTD_row_nextCachedHash(U32* cache, U32 const* hashTable, +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +U32 ZSTD_row_nextCachedHash(U32* cache, U32 const* hashTable, BYTE const* tagTable, BYTE const* base, U32 idx, U32 const hashLog, U32 const rowLog, U32 const mls, @@ -863,10 +880,12 @@ FORCE_INLINE_TEMPLATE U32 ZSTD_row_nextCachedHash(U32* cache, U32 const* hashTab /* ZSTD_row_update_internalImpl(): * Updates the hash table with positions starting from updateStartIdx until updateEndIdx. */ -FORCE_INLINE_TEMPLATE void ZSTD_row_update_internalImpl(ZSTD_matchState_t* ms, - U32 updateStartIdx, U32 const updateEndIdx, - U32 const mls, U32 const rowLog, - U32 const rowMask, U32 const useCache) +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +void ZSTD_row_update_internalImpl(ZSTD_matchState_t* ms, + U32 updateStartIdx, U32 const updateEndIdx, + U32 const mls, U32 const rowLog, + U32 const rowMask, U32 const useCache) { U32* const hashTable = ms->hashTable; BYTE* const tagTable = ms->tagTable; @@ -892,9 +911,11 @@ FORCE_INLINE_TEMPLATE void ZSTD_row_update_internalImpl(ZSTD_matchState_t* ms, * Inserts the byte at ip into the appropriate position in the hash table, and updates ms->nextToUpdate. * Skips sections of long matches as is necessary. */ -FORCE_INLINE_TEMPLATE void ZSTD_row_update_internal(ZSTD_matchState_t* ms, const BYTE* ip, - U32 const mls, U32 const rowLog, - U32 const rowMask, U32 const useCache) +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +void ZSTD_row_update_internal(ZSTD_matchState_t* ms, const BYTE* ip, + U32 const mls, U32 const rowLog, + U32 const rowMask, U32 const useCache) { U32 idx = ms->nextToUpdate; const BYTE* const base = ms->window.base; @@ -1102,20 +1123,21 @@ ZSTD_row_getMatchMask(const BYTE* const tagRow, const BYTE tag, const U32 headGr /* The high-level approach of the SIMD row based match finder is as follows: * - Figure out where to insert the new entry: - * - Generate a hash from a byte along with an additional 1-byte "short hash". The additional byte is our "tag" - * - The hashTable is effectively split into groups or "rows" of 16 or 32 entries of U32, and the hash determines + * - Generate a hash for current input posistion and split it into a one byte of tag and `rowHashLog` bits of index. + * - The hash is salted by a value that changes on every contex reset, so when the same table is used + * we will avoid collisions that would otherwise slow us down by intorducing phantom matches. + * - The hashTable is effectively split into groups or "rows" of 15 or 31 entries of U32, and the index determines * which row to insert into. - * - Determine the correct position within the row to insert the entry into. Each row of 16 or 32 can - * be considered as a circular buffer with a "head" index that resides in the tagTable. - * - Also insert the "tag" into the equivalent row and position in the tagTable. - * - Note: The tagTable has 17 or 33 1-byte entries per row, due to 16 or 32 tags, and 1 "head" entry. - * The 17 or 33 entry rows are spaced out to occur every 32 or 64 bytes, respectively, - * for alignment/performance reasons, leaving some bytes unused. - * - Use SIMD to efficiently compare the tags in the tagTable to the 1-byte "short hash" and + * - Determine the correct position within the row to insert the entry into. Each row of 15 or 31 can + * be considered as a circular buffer with a "head" index that resides in the tagTable (overall 16 or 32 bytes + * per row). + * - Use SIMD to efficiently compare the tags in the tagTable to the 1-byte tag calculated for the position and * generate a bitfield that we can cycle through to check the collisions in the hash table. * - Pick the longest match. + * - Insert the tag into the equivalent row and position in the tagTable. */ FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR size_t ZSTD_RowFindBestMatch( ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iLimit, @@ -1489,8 +1511,9 @@ FORCE_INLINE_TEMPLATE size_t ZSTD_searchMax( * Common parser - lazy strategy *********************************/ -FORCE_INLINE_TEMPLATE size_t -ZSTD_compressBlock_lazy_generic( +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +size_t ZSTD_compressBlock_lazy_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize, @@ -1754,29 +1777,10 @@ _storeSequence: /* Return the last literals size */ return (size_t)(iend - anchor); } +#endif /* build exclusions */ -size_t ZSTD_compressBlock_btlazy2( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_binaryTree, 2, ZSTD_noDict); -} - -size_t ZSTD_compressBlock_lazy2( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 2, ZSTD_noDict); -} - -size_t ZSTD_compressBlock_lazy( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 1, ZSTD_noDict); -} - +#ifndef ZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR size_t ZSTD_compressBlock_greedy( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) @@ -1784,27 +1788,6 @@ size_t ZSTD_compressBlock_greedy( return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 0, ZSTD_noDict); } -size_t ZSTD_compressBlock_btlazy2_dictMatchState( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_binaryTree, 2, ZSTD_dictMatchState); -} - -size_t ZSTD_compressBlock_lazy2_dictMatchState( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 2, ZSTD_dictMatchState); -} - -size_t ZSTD_compressBlock_lazy_dictMatchState( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 1, ZSTD_dictMatchState); -} - size_t ZSTD_compressBlock_greedy_dictMatchState( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) @@ -1812,21 +1795,6 @@ size_t ZSTD_compressBlock_greedy_dictMatchState( return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 0, ZSTD_dictMatchState); } - -size_t ZSTD_compressBlock_lazy2_dedicatedDictSearch( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 2, ZSTD_dedicatedDictSearch); -} - -size_t ZSTD_compressBlock_lazy_dedicatedDictSearch( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 1, ZSTD_dedicatedDictSearch); -} - size_t ZSTD_compressBlock_greedy_dedicatedDictSearch( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) @@ -1834,21 +1802,6 @@ size_t ZSTD_compressBlock_greedy_dedicatedDictSearch( return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 0, ZSTD_dedicatedDictSearch); } -/* Row-based matchfinder */ -size_t ZSTD_compressBlock_lazy2_row( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 2, ZSTD_noDict); -} - -size_t ZSTD_compressBlock_lazy_row( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 1, ZSTD_noDict); -} - size_t ZSTD_compressBlock_greedy_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) @@ -1856,11 +1809,48 @@ size_t ZSTD_compressBlock_greedy_row( return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 0, ZSTD_noDict); } -size_t ZSTD_compressBlock_lazy2_dictMatchState_row( +size_t ZSTD_compressBlock_greedy_dictMatchState_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) { - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 2, ZSTD_dictMatchState); + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 0, ZSTD_dictMatchState); +} + +size_t ZSTD_compressBlock_greedy_dedicatedDictSearch_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 0, ZSTD_dedicatedDictSearch); +} +#endif + +#ifndef ZSTD_EXCLUDE_LAZY_BLOCK_COMPRESSOR +size_t ZSTD_compressBlock_lazy( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 1, ZSTD_noDict); +} + +size_t ZSTD_compressBlock_lazy_dictMatchState( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 1, ZSTD_dictMatchState); +} + +size_t ZSTD_compressBlock_lazy_dedicatedDictSearch( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 1, ZSTD_dedicatedDictSearch); +} + +size_t ZSTD_compressBlock_lazy_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 1, ZSTD_noDict); } size_t ZSTD_compressBlock_lazy_dictMatchState_row( @@ -1870,13 +1860,49 @@ size_t ZSTD_compressBlock_lazy_dictMatchState_row( return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 1, ZSTD_dictMatchState); } -size_t ZSTD_compressBlock_greedy_dictMatchState_row( +size_t ZSTD_compressBlock_lazy_dedicatedDictSearch_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) { - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 0, ZSTD_dictMatchState); + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 1, ZSTD_dedicatedDictSearch); +} +#endif + +#ifndef ZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR +size_t ZSTD_compressBlock_lazy2( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 2, ZSTD_noDict); } +size_t ZSTD_compressBlock_lazy2_dictMatchState( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 2, ZSTD_dictMatchState); +} + +size_t ZSTD_compressBlock_lazy2_dedicatedDictSearch( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 2, ZSTD_dedicatedDictSearch); +} + +size_t ZSTD_compressBlock_lazy2_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 2, ZSTD_noDict); +} + +size_t ZSTD_compressBlock_lazy2_dictMatchState_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 2, ZSTD_dictMatchState); +} size_t ZSTD_compressBlock_lazy2_dedicatedDictSearch_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], @@ -1884,22 +1910,30 @@ size_t ZSTD_compressBlock_lazy2_dedicatedDictSearch_row( { return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 2, ZSTD_dedicatedDictSearch); } +#endif -size_t ZSTD_compressBlock_lazy_dedicatedDictSearch_row( +#ifndef ZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR +size_t ZSTD_compressBlock_btlazy2( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) { - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 1, ZSTD_dedicatedDictSearch); + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_binaryTree, 2, ZSTD_noDict); } -size_t ZSTD_compressBlock_greedy_dedicatedDictSearch_row( +size_t ZSTD_compressBlock_btlazy2_dictMatchState( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) { - return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 0, ZSTD_dedicatedDictSearch); + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, search_binaryTree, 2, ZSTD_dictMatchState); } +#endif +#if !defined(ZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_LAZY_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR) FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR size_t ZSTD_compressBlock_lazy_extDict_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], @@ -2101,8 +2135,9 @@ _storeSequence: /* Return the last literals size */ return (size_t)(iend - anchor); } +#endif /* build exclusions */ - +#ifndef ZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR size_t ZSTD_compressBlock_greedy_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) @@ -2110,6 +2145,15 @@ size_t ZSTD_compressBlock_greedy_extDict( return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 0); } +size_t ZSTD_compressBlock_greedy_extDict_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +{ + return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 0); +} +#endif + +#ifndef ZSTD_EXCLUDE_LAZY_BLOCK_COMPRESSOR size_t ZSTD_compressBlock_lazy_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) @@ -2118,29 +2162,6 @@ size_t ZSTD_compressBlock_lazy_extDict( return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 1); } -size_t ZSTD_compressBlock_lazy2_extDict( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) - -{ - return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 2); -} - -size_t ZSTD_compressBlock_btlazy2_extDict( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) - -{ - return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_binaryTree, 2); -} - -size_t ZSTD_compressBlock_greedy_extDict_row( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize) -{ - return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 0); -} - size_t ZSTD_compressBlock_lazy_extDict_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) @@ -2148,6 +2169,16 @@ size_t ZSTD_compressBlock_lazy_extDict_row( { return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 1); } +#endif + +#ifndef ZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR +size_t ZSTD_compressBlock_lazy2_extDict( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) + +{ + return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_hashChain, 2); +} size_t ZSTD_compressBlock_lazy2_extDict_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], @@ -2155,3 +2186,14 @@ size_t ZSTD_compressBlock_lazy2_extDict_row( { return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 2); } +#endif + +#ifndef ZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR +size_t ZSTD_compressBlock_btlazy2_extDict( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) + +{ + return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_binaryTree, 2); +} +#endif diff --git a/src/thirdparty/zstd/compress/zstd_lazy.h b/src/thirdparty/zstd/compress/zstd_lazy.h index 3bde6733..3635813b 100644 --- a/src/thirdparty/zstd/compress/zstd_lazy.h +++ b/src/thirdparty/zstd/compress/zstd_lazy.h @@ -27,98 +27,173 @@ extern "C" { #define ZSTD_ROW_HASH_TAG_BITS 8 /* nb bits to use for the tag */ +#if !defined(ZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_LAZY_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR) U32 ZSTD_insertAndFindFirstIndex(ZSTD_matchState_t* ms, const BYTE* ip); void ZSTD_row_update(ZSTD_matchState_t* const ms, const BYTE* ip); void ZSTD_dedicatedDictSearch_lazy_loadDictionary(ZSTD_matchState_t* ms, const BYTE* const ip); void ZSTD_preserveUnsortedMark (U32* const table, U32 const size, U32 const reducerValue); /*! used in ZSTD_reduceIndex(). preemptively increase value of ZSTD_DUBT_UNSORTED_MARK */ +#endif -size_t ZSTD_compressBlock_btlazy2( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy2( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); +#ifndef ZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR size_t ZSTD_compressBlock_greedy( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy2_row( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy_row( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); size_t ZSTD_compressBlock_greedy_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); - -size_t ZSTD_compressBlock_btlazy2_dictMatchState( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy2_dictMatchState( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy_dictMatchState( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); size_t ZSTD_compressBlock_greedy_dictMatchState( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy2_dictMatchState_row( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy_dictMatchState_row( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); size_t ZSTD_compressBlock_greedy_dictMatchState_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); - -size_t ZSTD_compressBlock_lazy2_dedicatedDictSearch( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy_dedicatedDictSearch( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); size_t ZSTD_compressBlock_greedy_dedicatedDictSearch( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy2_dedicatedDictSearch_row( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy_dedicatedDictSearch_row( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); size_t ZSTD_compressBlock_greedy_dedicatedDictSearch_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); - size_t ZSTD_compressBlock_greedy_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy_extDict( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); -size_t ZSTD_compressBlock_lazy2_extDict( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); size_t ZSTD_compressBlock_greedy_extDict_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); + +#define ZSTD_COMPRESSBLOCK_GREEDY ZSTD_compressBlock_greedy +#define ZSTD_COMPRESSBLOCK_GREEDY_ROW ZSTD_compressBlock_greedy_row +#define ZSTD_COMPRESSBLOCK_GREEDY_DICTMATCHSTATE ZSTD_compressBlock_greedy_dictMatchState +#define ZSTD_COMPRESSBLOCK_GREEDY_DICTMATCHSTATE_ROW ZSTD_compressBlock_greedy_dictMatchState_row +#define ZSTD_COMPRESSBLOCK_GREEDY_DEDICATEDDICTSEARCH ZSTD_compressBlock_greedy_dedicatedDictSearch +#define ZSTD_COMPRESSBLOCK_GREEDY_DEDICATEDDICTSEARCH_ROW ZSTD_compressBlock_greedy_dedicatedDictSearch_row +#define ZSTD_COMPRESSBLOCK_GREEDY_EXTDICT ZSTD_compressBlock_greedy_extDict +#define ZSTD_COMPRESSBLOCK_GREEDY_EXTDICT_ROW ZSTD_compressBlock_greedy_extDict_row +#else +#define ZSTD_COMPRESSBLOCK_GREEDY NULL +#define ZSTD_COMPRESSBLOCK_GREEDY_ROW NULL +#define ZSTD_COMPRESSBLOCK_GREEDY_DICTMATCHSTATE NULL +#define ZSTD_COMPRESSBLOCK_GREEDY_DICTMATCHSTATE_ROW NULL +#define ZSTD_COMPRESSBLOCK_GREEDY_DEDICATEDDICTSEARCH NULL +#define ZSTD_COMPRESSBLOCK_GREEDY_DEDICATEDDICTSEARCH_ROW NULL +#define ZSTD_COMPRESSBLOCK_GREEDY_EXTDICT NULL +#define ZSTD_COMPRESSBLOCK_GREEDY_EXTDICT_ROW NULL +#endif + +#ifndef ZSTD_EXCLUDE_LAZY_BLOCK_COMPRESSOR +size_t ZSTD_compressBlock_lazy( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy_dictMatchState( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy_dictMatchState_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy_dedicatedDictSearch( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy_dedicatedDictSearch_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy_extDict( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); size_t ZSTD_compressBlock_lazy_extDict_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); + +#define ZSTD_COMPRESSBLOCK_LAZY ZSTD_compressBlock_lazy +#define ZSTD_COMPRESSBLOCK_LAZY_ROW ZSTD_compressBlock_lazy_row +#define ZSTD_COMPRESSBLOCK_LAZY_DICTMATCHSTATE ZSTD_compressBlock_lazy_dictMatchState +#define ZSTD_COMPRESSBLOCK_LAZY_DICTMATCHSTATE_ROW ZSTD_compressBlock_lazy_dictMatchState_row +#define ZSTD_COMPRESSBLOCK_LAZY_DEDICATEDDICTSEARCH ZSTD_compressBlock_lazy_dedicatedDictSearch +#define ZSTD_COMPRESSBLOCK_LAZY_DEDICATEDDICTSEARCH_ROW ZSTD_compressBlock_lazy_dedicatedDictSearch_row +#define ZSTD_COMPRESSBLOCK_LAZY_EXTDICT ZSTD_compressBlock_lazy_extDict +#define ZSTD_COMPRESSBLOCK_LAZY_EXTDICT_ROW ZSTD_compressBlock_lazy_extDict_row +#else +#define ZSTD_COMPRESSBLOCK_LAZY NULL +#define ZSTD_COMPRESSBLOCK_LAZY_ROW NULL +#define ZSTD_COMPRESSBLOCK_LAZY_DICTMATCHSTATE NULL +#define ZSTD_COMPRESSBLOCK_LAZY_DICTMATCHSTATE_ROW NULL +#define ZSTD_COMPRESSBLOCK_LAZY_DEDICATEDDICTSEARCH NULL +#define ZSTD_COMPRESSBLOCK_LAZY_DEDICATEDDICTSEARCH_ROW NULL +#define ZSTD_COMPRESSBLOCK_LAZY_EXTDICT NULL +#define ZSTD_COMPRESSBLOCK_LAZY_EXTDICT_ROW NULL +#endif + +#ifndef ZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR +size_t ZSTD_compressBlock_lazy2( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy2_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy2_dictMatchState( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy2_dictMatchState_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy2_dedicatedDictSearch( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy2_dedicatedDictSearch_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_lazy2_extDict( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); size_t ZSTD_compressBlock_lazy2_extDict_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); + +#define ZSTD_COMPRESSBLOCK_LAZY2 ZSTD_compressBlock_lazy2 +#define ZSTD_COMPRESSBLOCK_LAZY2_ROW ZSTD_compressBlock_lazy2_row +#define ZSTD_COMPRESSBLOCK_LAZY2_DICTMATCHSTATE ZSTD_compressBlock_lazy2_dictMatchState +#define ZSTD_COMPRESSBLOCK_LAZY2_DICTMATCHSTATE_ROW ZSTD_compressBlock_lazy2_dictMatchState_row +#define ZSTD_COMPRESSBLOCK_LAZY2_DEDICATEDDICTSEARCH ZSTD_compressBlock_lazy2_dedicatedDictSearch +#define ZSTD_COMPRESSBLOCK_LAZY2_DEDICATEDDICTSEARCH_ROW ZSTD_compressBlock_lazy2_dedicatedDictSearch_row +#define ZSTD_COMPRESSBLOCK_LAZY2_EXTDICT ZSTD_compressBlock_lazy2_extDict +#define ZSTD_COMPRESSBLOCK_LAZY2_EXTDICT_ROW ZSTD_compressBlock_lazy2_extDict_row +#else +#define ZSTD_COMPRESSBLOCK_LAZY2 NULL +#define ZSTD_COMPRESSBLOCK_LAZY2_ROW NULL +#define ZSTD_COMPRESSBLOCK_LAZY2_DICTMATCHSTATE NULL +#define ZSTD_COMPRESSBLOCK_LAZY2_DICTMATCHSTATE_ROW NULL +#define ZSTD_COMPRESSBLOCK_LAZY2_DEDICATEDDICTSEARCH NULL +#define ZSTD_COMPRESSBLOCK_LAZY2_DEDICATEDDICTSEARCH_ROW NULL +#define ZSTD_COMPRESSBLOCK_LAZY2_EXTDICT NULL +#define ZSTD_COMPRESSBLOCK_LAZY2_EXTDICT_ROW NULL +#endif + +#ifndef ZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR +size_t ZSTD_compressBlock_btlazy2( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_btlazy2_dictMatchState( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); size_t ZSTD_compressBlock_btlazy2_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); +#define ZSTD_COMPRESSBLOCK_BTLAZY2 ZSTD_compressBlock_btlazy2 +#define ZSTD_COMPRESSBLOCK_BTLAZY2_DICTMATCHSTATE ZSTD_compressBlock_btlazy2_dictMatchState +#define ZSTD_COMPRESSBLOCK_BTLAZY2_EXTDICT ZSTD_compressBlock_btlazy2_extDict +#else +#define ZSTD_COMPRESSBLOCK_BTLAZY2 NULL +#define ZSTD_COMPRESSBLOCK_BTLAZY2_DICTMATCHSTATE NULL +#define ZSTD_COMPRESSBLOCK_BTLAZY2_EXTDICT NULL +#endif + #if defined (__cplusplus) } diff --git a/src/thirdparty/zstd/compress/zstd_ldm.c b/src/thirdparty/zstd/compress/zstd_ldm.c index 3d74ff19..17c069fe 100644 --- a/src/thirdparty/zstd/compress/zstd_ldm.c +++ b/src/thirdparty/zstd/compress/zstd_ldm.c @@ -246,7 +246,11 @@ static size_t ZSTD_ldm_fillFastTables(ZSTD_matchState_t* ms, break; case ZSTD_dfast: +#ifndef ZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR ZSTD_fillDoubleHashTable(ms, iend, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx); +#else + assert(0); /* shouldn't be called: cparams should've been adjusted. */ +#endif break; case ZSTD_greedy: @@ -318,7 +322,9 @@ static void ZSTD_ldm_limitTableUpdate(ZSTD_matchState_t* ms, const BYTE* anchor) } } -static size_t ZSTD_ldm_generateSequences_internal( +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +size_t ZSTD_ldm_generateSequences_internal( ldmState_t* ldmState, rawSeqStore_t* rawSeqStore, ldmParams_t const* params, void const* src, size_t srcSize) { @@ -689,7 +695,6 @@ size_t ZSTD_ldm_blockCompress(rawSeqStore_t* rawSeqStore, /* maybeSplitSequence updates rawSeqStore->pos */ rawSeq const sequence = maybeSplitSequence(rawSeqStore, (U32)(iend - ip), minMatch); - int i; /* End signal */ if (sequence.offset == 0) break; @@ -702,6 +707,7 @@ size_t ZSTD_ldm_blockCompress(rawSeqStore_t* rawSeqStore, /* Run the block compressor */ DEBUGLOG(5, "pos %u : calling block compressor on segment of size %u", (unsigned)(ip-istart), sequence.litLength); { + int i; size_t const newLitLength = blockCompressor(ms, seqStore, rep, ip, sequence.litLength); ip += sequence.litLength; diff --git a/src/thirdparty/zstd/compress/zstd_opt.c b/src/thirdparty/zstd/compress/zstd_opt.c index f02a7609..e63073e5 100644 --- a/src/thirdparty/zstd/compress/zstd_opt.c +++ b/src/thirdparty/zstd/compress/zstd_opt.c @@ -12,6 +12,9 @@ #include "hist.h" #include "zstd_opt.h" +#if !defined(ZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR) #define ZSTD_LITFREQ_ADD 2 /* scaling factor for litFreq, so that frequencies adapt faster to new stats */ #define ZSTD_MAX_PRICE (1<<30) @@ -264,6 +267,7 @@ static U32 ZSTD_rawLiteralsCost(const BYTE* const literals, U32 const litLength, const optState_t* const optPtr, int optLevel) { + DEBUGLOG(8, "ZSTD_rawLiteralsCost (%u literals)", litLength); if (litLength == 0) return 0; if (!ZSTD_compressedLiterals(optPtr)) @@ -402,9 +406,11 @@ MEM_STATIC U32 ZSTD_readMINMATCH(const void* memPtr, U32 length) /* Update hashTable3 up to ip (excluded) Assumption : always within prefix (i.e. not within extDict) */ -static U32 ZSTD_insertAndFindFirstIndexHash3 (const ZSTD_matchState_t* ms, - U32* nextToUpdate3, - const BYTE* const ip) +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +U32 ZSTD_insertAndFindFirstIndexHash3 (const ZSTD_matchState_t* ms, + U32* nextToUpdate3, + const BYTE* const ip) { U32* const hashTable3 = ms->hashTable3; U32 const hashLog3 = ms->hashLog3; @@ -431,7 +437,9 @@ static U32 ZSTD_insertAndFindFirstIndexHash3 (const ZSTD_matchState_t* ms, * @param ip assumed <= iend-8 . * @param target The target of ZSTD_updateTree_internal() - we are filling to this position * @return : nb of positions added */ -static U32 ZSTD_insertBt1( +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +U32 ZSTD_insertBt1( const ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iend, U32 const target, @@ -550,6 +558,7 @@ static U32 ZSTD_insertBt1( } FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR void ZSTD_updateTree_internal( ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iend, @@ -558,7 +567,7 @@ void ZSTD_updateTree_internal( const BYTE* const base = ms->window.base; U32 const target = (U32)(ip - base); U32 idx = ms->nextToUpdate; - DEBUGLOG(6, "ZSTD_updateTree_internal, from %u to %u (dictMode:%u)", + DEBUGLOG(7, "ZSTD_updateTree_internal, from %u to %u (dictMode:%u)", idx, target, dictMode); while(idx < target) { @@ -575,7 +584,9 @@ void ZSTD_updateTree(ZSTD_matchState_t* ms, const BYTE* ip, const BYTE* iend) { ZSTD_updateTree_internal(ms, ip, iend, ms->cParams.minMatch, ZSTD_noDict); } -FORCE_INLINE_TEMPLATE U32 +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +U32 ZSTD_insertBtAndGetAllMatches ( ZSTD_match_t* matches, /* store result (found matches) in this table (presumed large enough) */ ZSTD_matchState_t* ms, @@ -816,7 +827,9 @@ typedef U32 (*ZSTD_getAllMatchesFn)( U32 const ll0, U32 const lengthToBeat); -FORCE_INLINE_TEMPLATE U32 ZSTD_btGetAllMatches_internal( +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +U32 ZSTD_btGetAllMatches_internal( ZSTD_match_t* matches, ZSTD_matchState_t* ms, U32* nextToUpdate3, @@ -1035,11 +1048,6 @@ ZSTD_optLdm_processMatchCandidate(ZSTD_optLdm_t* optLdm, * Optimal parser *********************************/ -static U32 ZSTD_totalLen(ZSTD_optimal_t sol) -{ - return sol.litlen + sol.mlen; -} - #if 0 /* debug */ static void @@ -1057,7 +1065,13 @@ listStats(const U32* table, int lastEltID) #endif -FORCE_INLINE_TEMPLATE size_t +#define LIT_PRICE(_p) (int)ZSTD_rawLiteralsCost(_p, 1, optStatePtr, optLevel) +#define LL_PRICE(_l) (int)ZSTD_litLengthPrice(_l, optStatePtr, optLevel) +#define LL_INCPRICE(_l) (LL_PRICE(_l) - LL_PRICE(_l-1)) + +FORCE_INLINE_TEMPLATE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +size_t ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], @@ -1083,10 +1097,10 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, ZSTD_optimal_t* const opt = optStatePtr->priceTable; ZSTD_match_t* const matches = optStatePtr->matchTable; - ZSTD_optimal_t lastSequence; + ZSTD_optimal_t lastStretch; ZSTD_optLdm_t optLdm; - ZSTD_memset(&lastSequence, 0, sizeof(ZSTD_optimal_t)); + ZSTD_memset(&lastStretch, 0, sizeof(ZSTD_optimal_t)); optLdm.seqStore = ms->ldmSeqStore ? *ms->ldmSeqStore : kNullRawSeqStore; optLdm.endPosInBlock = optLdm.startPosInBlock = optLdm.offset = 0; @@ -1108,19 +1122,31 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, U32 const ll0 = !litlen; U32 nbMatches = getAllMatches(matches, ms, &nextToUpdate3, ip, iend, rep, ll0, minMatch); ZSTD_optLdm_processMatchCandidate(&optLdm, matches, &nbMatches, - (U32)(ip-istart), (U32)(iend - ip)); - if (!nbMatches) { ip++; continue; } + (U32)(ip-istart), (U32)(iend-ip)); + if (!nbMatches) { + DEBUGLOG(8, "no match found at cPos %u", (unsigned)(ip-istart)); + ip++; + continue; + } + + /* Match found: let's store this solution, and eventually find more candidates. + * During this forward pass, @opt is used to store stretches, + * defined as "a match followed by N literals". + * Note how this is different from a Sequence, which is "N literals followed by a match". + * Storing stretches allows us to store different match predecessors + * for each literal position part of a literals run. */ /* initialize opt[0] */ - { U32 i ; for (i=0; i immediate encoding */ { U32 const maxML = matches[nbMatches-1].len; @@ -1129,82 +1155,106 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, nbMatches, maxML, maxOffBase, (U32)(ip-prefixStart)); if (maxML > sufficient_len) { - lastSequence.litlen = litlen; - lastSequence.mlen = maxML; - lastSequence.off = maxOffBase; - DEBUGLOG(6, "large match (%u>%u), immediate encoding", + lastStretch.litlen = 0; + lastStretch.mlen = maxML; + lastStretch.off = maxOffBase; + DEBUGLOG(6, "large match (%u>%u) => immediate encoding", maxML, sufficient_len); cur = 0; - last_pos = ZSTD_totalLen(lastSequence); + last_pos = maxML; goto _shortestPath; } } /* set prices for first matches starting position == 0 */ assert(opt[0].price >= 0); - { U32 const literalsPrice = (U32)opt[0].price + ZSTD_litLengthPrice(0, optStatePtr, optLevel); - U32 pos; + { U32 pos; U32 matchNb; for (pos = 1; pos < minMatch; pos++) { - opt[pos].price = ZSTD_MAX_PRICE; /* mlen, litlen and price will be fixed during forward scanning */ + opt[pos].price = ZSTD_MAX_PRICE; + opt[pos].mlen = 0; + opt[pos].litlen = litlen + pos; } for (matchNb = 0; matchNb < nbMatches; matchNb++) { U32 const offBase = matches[matchNb].off; U32 const end = matches[matchNb].len; for ( ; pos <= end ; pos++ ) { - U32 const matchPrice = ZSTD_getMatchPrice(offBase, pos, optStatePtr, optLevel); - U32 const sequencePrice = literalsPrice + matchPrice; + int const matchPrice = (int)ZSTD_getMatchPrice(offBase, pos, optStatePtr, optLevel); + int const sequencePrice = opt[0].price + matchPrice; DEBUGLOG(7, "rPos:%u => set initial price : %.2f", - pos, ZSTD_fCost((int)sequencePrice)); + pos, ZSTD_fCost(sequencePrice)); opt[pos].mlen = pos; opt[pos].off = offBase; - opt[pos].litlen = litlen; - opt[pos].price = (int)sequencePrice; - } } + opt[pos].litlen = 0; /* end of match */ + opt[pos].price = sequencePrice + LL_PRICE(0); + } + } last_pos = pos-1; + opt[pos].price = ZSTD_MAX_PRICE; } } /* check further positions */ for (cur = 1; cur <= last_pos; cur++) { const BYTE* const inr = ip + cur; - assert(cur < ZSTD_OPT_NUM); - DEBUGLOG(7, "cPos:%zi==rPos:%u", inr-istart, cur) + assert(cur <= ZSTD_OPT_NUM); + DEBUGLOG(7, "cPos:%zi==rPos:%u", inr-istart, cur); /* Fix current position with one literal if cheaper */ - { U32 const litlen = (opt[cur-1].mlen == 0) ? opt[cur-1].litlen + 1 : 1; + { U32 const litlen = opt[cur-1].litlen + 1; int const price = opt[cur-1].price - + (int)ZSTD_rawLiteralsCost(ip+cur-1, 1, optStatePtr, optLevel) - + (int)ZSTD_litLengthPrice(litlen, optStatePtr, optLevel) - - (int)ZSTD_litLengthPrice(litlen-1, optStatePtr, optLevel); + + LIT_PRICE(ip+cur-1) + + LL_INCPRICE(litlen); assert(price < 1000000000); /* overflow check */ if (price <= opt[cur].price) { + ZSTD_optimal_t const prevMatch = opt[cur]; DEBUGLOG(7, "cPos:%zi==rPos:%u : better price (%.2f<=%.2f) using literal (ll==%u) (hist:%u,%u,%u)", inr-istart, cur, ZSTD_fCost(price), ZSTD_fCost(opt[cur].price), litlen, opt[cur-1].rep[0], opt[cur-1].rep[1], opt[cur-1].rep[2]); - opt[cur].mlen = 0; - opt[cur].off = 0; + opt[cur] = opt[cur-1]; opt[cur].litlen = litlen; opt[cur].price = price; + if ( (optLevel >= 1) /* additional check only for higher modes */ + && (prevMatch.litlen == 0) /* replace a match */ + && (LL_INCPRICE(1) < 0) /* ll1 is cheaper than ll0 */ + && LIKELY(ip + cur < iend) + ) { + /* check next position, in case it would be cheaper */ + int with1literal = prevMatch.price + LIT_PRICE(ip+cur) + LL_INCPRICE(1); + int withMoreLiterals = price + LIT_PRICE(ip+cur) + LL_INCPRICE(litlen+1); + DEBUGLOG(7, "then at next rPos %u : match+1lit %.2f vs %ulits %.2f", + cur+1, ZSTD_fCost(with1literal), litlen+1, ZSTD_fCost(withMoreLiterals)); + if ( (with1literal < withMoreLiterals) + && (with1literal < opt[cur+1].price) ) { + /* update offset history - before it disappears */ + U32 const prev = cur - prevMatch.mlen; + repcodes_t const newReps = ZSTD_newRep(opt[prev].rep, prevMatch.off, opt[prev].litlen==0); + assert(cur >= prevMatch.mlen); + DEBUGLOG(7, "==> match+1lit is cheaper (%.2f < %.2f) (hist:%u,%u,%u) !", + ZSTD_fCost(with1literal), ZSTD_fCost(withMoreLiterals), + newReps.rep[0], newReps.rep[1], newReps.rep[2] ); + opt[cur+1] = prevMatch; /* mlen & offbase */ + ZSTD_memcpy(opt[cur+1].rep, &newReps, sizeof(repcodes_t)); + opt[cur+1].litlen = 1; + opt[cur+1].price = with1literal; + if (last_pos < cur+1) last_pos = cur+1; + } + } } else { - DEBUGLOG(7, "cPos:%zi==rPos:%u : literal would cost more (%.2f>%.2f) (hist:%u,%u,%u)", - inr-istart, cur, ZSTD_fCost(price), ZSTD_fCost(opt[cur].price), - opt[cur].rep[0], opt[cur].rep[1], opt[cur].rep[2]); + DEBUGLOG(7, "cPos:%zi==rPos:%u : literal would cost more (%.2f>%.2f)", + inr-istart, cur, ZSTD_fCost(price), ZSTD_fCost(opt[cur].price)); } } - /* Set the repcodes of the current position. We must do it here - * because we rely on the repcodes of the 2nd to last sequence being - * correct to set the next chunks repcodes during the backward - * traversal. + /* Offset history is not updated during match comparison. + * Do it here, now that the match is selected and confirmed. */ ZSTD_STATIC_ASSERT(sizeof(opt[cur].rep) == sizeof(repcodes_t)); assert(cur >= opt[cur].mlen); - if (opt[cur].mlen != 0) { + if (opt[cur].litlen == 0) { + /* just finished a match => alter offset history */ U32 const prev = cur - opt[cur].mlen; - repcodes_t const newReps = ZSTD_newRep(opt[prev].rep, opt[cur].off, opt[cur].litlen==0); + repcodes_t const newReps = ZSTD_newRep(opt[prev].rep, opt[cur].off, opt[prev].litlen==0); ZSTD_memcpy(opt[cur].rep, &newReps, sizeof(repcodes_t)); - } else { - ZSTD_memcpy(opt[cur].rep, opt[cur - 1].rep, sizeof(repcodes_t)); } /* last match must start at a minimum distance of 8 from oend */ @@ -1214,15 +1264,14 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, if ( (optLevel==0) /*static_test*/ && (opt[cur+1].price <= opt[cur].price + (BITCOST_MULTIPLIER/2)) ) { - DEBUGLOG(7, "move to next rPos:%u : price is <=", cur+1); + DEBUGLOG(7, "skip current position : next rPos(%u) price is cheaper", cur+1); continue; /* skip unpromising positions; about ~+6% speed, -0.01 ratio */ } assert(opt[cur].price >= 0); - { U32 const ll0 = (opt[cur].mlen != 0); - U32 const litlen = (opt[cur].mlen == 0) ? opt[cur].litlen : 0; - U32 const previousPrice = (U32)opt[cur].price; - U32 const basePrice = previousPrice + ZSTD_litLengthPrice(0, optStatePtr, optLevel); + { U32 const ll0 = (opt[cur].litlen == 0); + int const previousPrice = opt[cur].price; + int const basePrice = previousPrice + LL_PRICE(0); U32 nbMatches = getAllMatches(matches, ms, &nextToUpdate3, inr, iend, opt[cur].rep, ll0, minMatch); U32 matchNb; @@ -1234,18 +1283,17 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, continue; } - { U32 const maxML = matches[nbMatches-1].len; - DEBUGLOG(7, "cPos:%zi==rPos:%u, found %u matches, of maxLength=%u", - inr-istart, cur, nbMatches, maxML); + { U32 const longestML = matches[nbMatches-1].len; + DEBUGLOG(7, "cPos:%zi==rPos:%u, found %u matches, of longest ML=%u", + inr-istart, cur, nbMatches, longestML); - if ( (maxML > sufficient_len) - || (cur + maxML >= ZSTD_OPT_NUM) ) { - lastSequence.mlen = maxML; - lastSequence.off = matches[nbMatches-1].off; - lastSequence.litlen = litlen; - cur -= (opt[cur].mlen==0) ? opt[cur].litlen : 0; /* last sequence is actually only literals, fix cur to last match - note : may underflow, in which case, it's first sequence, and it's okay */ - last_pos = cur + ZSTD_totalLen(lastSequence); - if (cur > ZSTD_OPT_NUM) cur = 0; /* underflow => first match */ + if ( (longestML > sufficient_len) + || (cur + longestML >= ZSTD_OPT_NUM) + || (ip + cur + longestML >= iend) ) { + lastStretch.mlen = longestML; + lastStretch.off = matches[nbMatches-1].off; + lastStretch.litlen = 0; + last_pos = cur + longestML; goto _shortestPath; } } @@ -1257,19 +1305,24 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, U32 mlen; DEBUGLOG(7, "testing match %u => offBase=%4u, mlen=%2u, llen=%2u", - matchNb, matches[matchNb].off, lastML, litlen); + matchNb, matches[matchNb].off, lastML, opt[cur].litlen); for (mlen = lastML; mlen >= startML; mlen--) { /* scan downward */ U32 const pos = cur + mlen; - int const price = (int)basePrice + (int)ZSTD_getMatchPrice(offset, mlen, optStatePtr, optLevel); + int const price = basePrice + (int)ZSTD_getMatchPrice(offset, mlen, optStatePtr, optLevel); if ((pos > last_pos) || (price < opt[pos].price)) { DEBUGLOG(7, "rPos:%u (ml=%2u) => new better price (%.2f<%.2f)", pos, mlen, ZSTD_fCost(price), ZSTD_fCost(opt[pos].price)); - while (last_pos < pos) { opt[last_pos+1].price = ZSTD_MAX_PRICE; last_pos++; } /* fill empty positions */ + while (last_pos < pos) { + /* fill empty positions, for future comparisons */ + last_pos++; + opt[last_pos].price = ZSTD_MAX_PRICE; + opt[last_pos].litlen = !0; /* just needs to be != 0, to mean "not an end of match" */ + } opt[pos].mlen = mlen; opt[pos].off = offset; - opt[pos].litlen = litlen; + opt[pos].litlen = 0; opt[pos].price = price; } else { DEBUGLOG(7, "rPos:%u (ml=%2u) => new price is worse (%.2f>=%.2f)", @@ -1277,47 +1330,81 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, if (optLevel==0) break; /* early update abort; gets ~+10% speed for about -0.01 ratio loss */ } } } } + opt[last_pos+1].price = ZSTD_MAX_PRICE; } /* for (cur = 1; cur <= last_pos; cur++) */ - lastSequence = opt[last_pos]; - cur = last_pos > ZSTD_totalLen(lastSequence) ? last_pos - ZSTD_totalLen(lastSequence) : 0; /* single sequence, and it starts before `ip` */ - assert(cur < ZSTD_OPT_NUM); /* control overflow*/ + lastStretch = opt[last_pos]; + assert(cur >= lastStretch.mlen); + cur = last_pos - lastStretch.mlen; _shortestPath: /* cur, last_pos, best_mlen, best_off have to be set */ assert(opt[0].mlen == 0); + assert(last_pos >= lastStretch.mlen); + assert(cur == last_pos - lastStretch.mlen); - /* Set the next chunk's repcodes based on the repcodes of the beginning - * of the last match, and the last sequence. This avoids us having to - * update them while traversing the sequences. - */ - if (lastSequence.mlen != 0) { - repcodes_t const reps = ZSTD_newRep(opt[cur].rep, lastSequence.off, lastSequence.litlen==0); - ZSTD_memcpy(rep, &reps, sizeof(reps)); + if (lastStretch.mlen==0) { + /* no solution : all matches have been converted into literals */ + assert(lastStretch.litlen == (ip - anchor) + last_pos); + ip += last_pos; + continue; + } + assert(lastStretch.off > 0); + + /* Update offset history */ + if (lastStretch.litlen == 0) { + /* finishing on a match : update offset history */ + repcodes_t const reps = ZSTD_newRep(opt[cur].rep, lastStretch.off, opt[cur].litlen==0); + ZSTD_memcpy(rep, &reps, sizeof(repcodes_t)); } else { - ZSTD_memcpy(rep, opt[cur].rep, sizeof(repcodes_t)); + ZSTD_memcpy(rep, lastStretch.rep, sizeof(repcodes_t)); + assert(cur >= lastStretch.litlen); + cur -= lastStretch.litlen; } - { U32 const storeEnd = cur + 1; + /* Let's write the shortest path solution. + * It is stored in @opt in reverse order, + * starting from @storeEnd (==cur+2), + * effectively partially @opt overwriting. + * Content is changed too: + * - So far, @opt stored stretches, aka a match followed by literals + * - Now, it will store sequences, aka literals followed by a match + */ + { U32 const storeEnd = cur + 2; U32 storeStart = storeEnd; - U32 seqPos = cur; + U32 stretchPos = cur; DEBUGLOG(6, "start reverse traversal (last_pos:%u, cur:%u)", last_pos, cur); (void)last_pos; - assert(storeEnd < ZSTD_OPT_NUM); - DEBUGLOG(6, "last sequence copied into pos=%u (llen=%u,mlen=%u,ofc=%u)", - storeEnd, lastSequence.litlen, lastSequence.mlen, lastSequence.off); - opt[storeEnd] = lastSequence; - while (seqPos > 0) { - U32 const backDist = ZSTD_totalLen(opt[seqPos]); + assert(storeEnd < ZSTD_OPT_SIZE); + DEBUGLOG(6, "last stretch copied into pos=%u (llen=%u,mlen=%u,ofc=%u)", + storeEnd, lastStretch.litlen, lastStretch.mlen, lastStretch.off); + if (lastStretch.litlen > 0) { + /* last "sequence" is unfinished: just a bunch of literals */ + opt[storeEnd].litlen = lastStretch.litlen; + opt[storeEnd].mlen = 0; + storeStart = storeEnd-1; + opt[storeStart] = lastStretch; + } { + opt[storeEnd] = lastStretch; /* note: litlen will be fixed */ + storeStart = storeEnd; + } + while (1) { + ZSTD_optimal_t nextStretch = opt[stretchPos]; + opt[storeStart].litlen = nextStretch.litlen; + DEBUGLOG(6, "selected sequence (llen=%u,mlen=%u,ofc=%u)", + opt[storeStart].litlen, opt[storeStart].mlen, opt[storeStart].off); + if (nextStretch.mlen == 0) { + /* reaching beginning of segment */ + break; + } storeStart--; - DEBUGLOG(6, "sequence from rPos=%u copied into pos=%u (llen=%u,mlen=%u,ofc=%u)", - seqPos, storeStart, opt[seqPos].litlen, opt[seqPos].mlen, opt[seqPos].off); - opt[storeStart] = opt[seqPos]; - seqPos = (seqPos > backDist) ? seqPos - backDist : 0; + opt[storeStart] = nextStretch; /* note: litlen will be fixed */ + assert(nextStretch.litlen + nextStretch.mlen <= stretchPos); + stretchPos -= nextStretch.litlen + nextStretch.mlen; } /* save sequences */ - DEBUGLOG(6, "sending selected sequences into seqStore") + DEBUGLOG(6, "sending selected sequences into seqStore"); { U32 storePos; for (storePos=storeStart; storePos <= storeEnd; storePos++) { U32 const llen = opt[storePos].litlen; @@ -1339,6 +1426,9 @@ _shortestPath: /* cur, last_pos, best_mlen, best_off have to be set */ anchor += advance; ip = anchor; } } + DEBUGLOG(7, "new offset history : %u, %u, %u", rep[0], rep[1], rep[2]); + + /* update all costs */ ZSTD_setBasePrices(optStatePtr, optLevel); } } /* while (ip < ilimit) */ @@ -1346,21 +1436,27 @@ _shortestPath: /* cur, last_pos, best_mlen, best_off have to be set */ /* Return the last literals size */ return (size_t)(iend - anchor); } +#endif /* build exclusions */ +#ifndef ZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR static size_t ZSTD_compressBlock_opt0( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize, const ZSTD_dictMode_e dictMode) { return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 0 /* optLevel */, dictMode); } +#endif +#ifndef ZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR static size_t ZSTD_compressBlock_opt2( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize, const ZSTD_dictMode_e dictMode) { return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 2 /* optLevel */, dictMode); } +#endif +#ifndef ZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR size_t ZSTD_compressBlock_btopt( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize) @@ -1368,20 +1464,23 @@ size_t ZSTD_compressBlock_btopt( DEBUGLOG(5, "ZSTD_compressBlock_btopt"); return ZSTD_compressBlock_opt0(ms, seqStore, rep, src, srcSize, ZSTD_noDict); } +#endif +#ifndef ZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR /* ZSTD_initStats_ultra(): * make a first compression pass, just to seed stats with more accurate starting values. * only works on first block, with no dictionary and no ldm. * this function cannot error out, its narrow contract must be respected. */ -static void -ZSTD_initStats_ultra(ZSTD_matchState_t* ms, - seqStore_t* seqStore, - U32 rep[ZSTD_REP_NUM], - const void* src, size_t srcSize) +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +void ZSTD_initStats_ultra(ZSTD_matchState_t* ms, + seqStore_t* seqStore, + U32 rep[ZSTD_REP_NUM], + const void* src, size_t srcSize) { U32 tmpRep[ZSTD_REP_NUM]; /* updated rep codes will sink here */ ZSTD_memcpy(tmpRep, rep, sizeof(tmpRep)); @@ -1425,7 +1524,7 @@ size_t ZSTD_compressBlock_btultra2( * Consequently, this can only work if no data has been previously loaded in tables, * aka, no dictionary, no prefix, no ldm preprocessing. * The compression ratio gain is generally small (~0.5% on first block), - ** the cost is 2x cpu time on first block. */ + * the cost is 2x cpu time on first block. */ assert(srcSize <= ZSTD_BLOCKSIZE_MAX); if ( (ms->opt.litLengthSum==0) /* first block */ && (seqStore->sequences == seqStore->sequencesStart) /* no ldm */ @@ -1438,7 +1537,9 @@ size_t ZSTD_compressBlock_btultra2( return ZSTD_compressBlock_opt2(ms, seqStore, rep, src, srcSize, ZSTD_noDict); } +#endif +#ifndef ZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR size_t ZSTD_compressBlock_btopt_dictMatchState( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize) @@ -1446,19 +1547,21 @@ size_t ZSTD_compressBlock_btopt_dictMatchState( return ZSTD_compressBlock_opt0(ms, seqStore, rep, src, srcSize, ZSTD_dictMatchState); } -size_t ZSTD_compressBlock_btultra_dictMatchState( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - const void* src, size_t srcSize) -{ - return ZSTD_compressBlock_opt2(ms, seqStore, rep, src, srcSize, ZSTD_dictMatchState); -} - size_t ZSTD_compressBlock_btopt_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize) { return ZSTD_compressBlock_opt0(ms, seqStore, rep, src, srcSize, ZSTD_extDict); } +#endif + +#ifndef ZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR +size_t ZSTD_compressBlock_btultra_dictMatchState( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + const void* src, size_t srcSize) +{ + return ZSTD_compressBlock_opt2(ms, seqStore, rep, src, srcSize, ZSTD_dictMatchState); +} size_t ZSTD_compressBlock_btultra_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], @@ -1466,6 +1569,7 @@ size_t ZSTD_compressBlock_btultra_extDict( { return ZSTD_compressBlock_opt2(ms, seqStore, rep, src, srcSize, ZSTD_extDict); } +#endif /* note : no btultra2 variant for extDict nor dictMatchState, * because btultra2 is not meant to work with dictionaries diff --git a/src/thirdparty/zstd/compress/zstd_opt.h b/src/thirdparty/zstd/compress/zstd_opt.h index 342e5a31..d4e71131 100644 --- a/src/thirdparty/zstd/compress/zstd_opt.h +++ b/src/thirdparty/zstd/compress/zstd_opt.h @@ -17,28 +17,38 @@ extern "C" { #include "zstd_compress_internal.h" +#if !defined(ZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR) \ + || !defined(ZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR) /* used in ZSTD_loadDictionaryContent() */ void ZSTD_updateTree(ZSTD_matchState_t* ms, const BYTE* ip, const BYTE* iend); +#endif +#ifndef ZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR size_t ZSTD_compressBlock_btopt( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); -size_t ZSTD_compressBlock_btultra( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); -size_t ZSTD_compressBlock_btultra2( - ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize); - - size_t ZSTD_compressBlock_btopt_dictMatchState( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); -size_t ZSTD_compressBlock_btultra_dictMatchState( +size_t ZSTD_compressBlock_btopt_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); -size_t ZSTD_compressBlock_btopt_extDict( +#define ZSTD_COMPRESSBLOCK_BTOPT ZSTD_compressBlock_btopt +#define ZSTD_COMPRESSBLOCK_BTOPT_DICTMATCHSTATE ZSTD_compressBlock_btopt_dictMatchState +#define ZSTD_COMPRESSBLOCK_BTOPT_EXTDICT ZSTD_compressBlock_btopt_extDict +#else +#define ZSTD_COMPRESSBLOCK_BTOPT NULL +#define ZSTD_COMPRESSBLOCK_BTOPT_DICTMATCHSTATE NULL +#define ZSTD_COMPRESSBLOCK_BTOPT_EXTDICT NULL +#endif + +#ifndef ZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR +size_t ZSTD_compressBlock_btultra( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +size_t ZSTD_compressBlock_btultra_dictMatchState( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); size_t ZSTD_compressBlock_btultra_extDict( @@ -48,6 +58,20 @@ size_t ZSTD_compressBlock_btultra_extDict( /* note : no btultra2 variant for extDict nor dictMatchState, * because btultra2 is not meant to work with dictionaries * and is only specific for the first block (no prefix) */ +size_t ZSTD_compressBlock_btultra2( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); + +#define ZSTD_COMPRESSBLOCK_BTULTRA ZSTD_compressBlock_btultra +#define ZSTD_COMPRESSBLOCK_BTULTRA_DICTMATCHSTATE ZSTD_compressBlock_btultra_dictMatchState +#define ZSTD_COMPRESSBLOCK_BTULTRA_EXTDICT ZSTD_compressBlock_btultra_extDict +#define ZSTD_COMPRESSBLOCK_BTULTRA2 ZSTD_compressBlock_btultra2 +#else +#define ZSTD_COMPRESSBLOCK_BTULTRA NULL +#define ZSTD_COMPRESSBLOCK_BTULTRA_DICTMATCHSTATE NULL +#define ZSTD_COMPRESSBLOCK_BTULTRA_EXTDICT NULL +#define ZSTD_COMPRESSBLOCK_BTULTRA2 NULL +#endif #if defined (__cplusplus) } diff --git a/src/thirdparty/zstd/compress/zstdmt_compress.c b/src/thirdparty/zstd/compress/zstdmt_compress.c index 67860755..86ccce31 100644 --- a/src/thirdparty/zstd/compress/zstdmt_compress.c +++ b/src/thirdparty/zstd/compress/zstdmt_compress.c @@ -15,17 +15,13 @@ #endif -/* ====== Constants ====== */ -#define ZSTDMT_OVERLAPLOG_DEFAULT 0 - - /* ====== Dependencies ====== */ -#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */ +#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */ #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset, INT_MAX, UINT_MAX */ #include "../common/mem.h" /* MEM_STATIC */ #include "../common/pool.h" /* threadpool */ #include "../common/threading.h" /* mutex */ -#include "zstd_compress_internal.h" /* MIN, ERROR, ZSTD_*, ZSTD_highbit32 */ +#include "zstd_compress_internal.h" /* MIN, ERROR, ZSTD_*, ZSTD_highbit32 */ #include "zstd_ldm.h" #include "zstdmt_compress.h" @@ -44,12 +40,13 @@ # include # include -# define DEBUG_PRINTHEX(l,p,n) { \ - unsigned debug_u; \ - for (debug_u=0; debug_u<(n); debug_u++) \ - RAWLOG(l, "%02X ", ((const unsigned char*)(p))[debug_u]); \ - RAWLOG(l, " \n"); \ -} +# define DEBUG_PRINTHEX(l,p,n) \ + do { \ + unsigned debug_u; \ + for (debug_u=0; debug_u<(n); debug_u++) \ + RAWLOG(l, "%02X ", ((const unsigned char*)(p))[debug_u]); \ + RAWLOG(l, " \n"); \ + } while (0) static unsigned long long GetCurrentClockTimeMicroseconds(void) { @@ -61,25 +58,28 @@ static unsigned long long GetCurrentClockTimeMicroseconds(void) } } #define MUTEX_WAIT_TIME_DLEVEL 6 -#define ZSTD_PTHREAD_MUTEX_LOCK(mutex) { \ - if (DEBUGLEVEL >= MUTEX_WAIT_TIME_DLEVEL) { \ - unsigned long long const beforeTime = GetCurrentClockTimeMicroseconds(); \ - ZSTD_pthread_mutex_lock(mutex); \ - { unsigned long long const afterTime = GetCurrentClockTimeMicroseconds(); \ - unsigned long long const elapsedTime = (afterTime-beforeTime); \ - if (elapsedTime > 1000) { /* or whatever threshold you like; I'm using 1 millisecond here */ \ - DEBUGLOG(MUTEX_WAIT_TIME_DLEVEL, "Thread took %llu microseconds to acquire mutex %s \n", \ - elapsedTime, #mutex); \ - } } \ - } else { \ - ZSTD_pthread_mutex_lock(mutex); \ - } \ -} +#define ZSTD_PTHREAD_MUTEX_LOCK(mutex) \ + do { \ + if (DEBUGLEVEL >= MUTEX_WAIT_TIME_DLEVEL) { \ + unsigned long long const beforeTime = GetCurrentClockTimeMicroseconds(); \ + ZSTD_pthread_mutex_lock(mutex); \ + { unsigned long long const afterTime = GetCurrentClockTimeMicroseconds(); \ + unsigned long long const elapsedTime = (afterTime-beforeTime); \ + if (elapsedTime > 1000) { \ + /* or whatever threshold you like; I'm using 1 millisecond here */ \ + DEBUGLOG(MUTEX_WAIT_TIME_DLEVEL, \ + "Thread took %llu microseconds to acquire mutex %s \n", \ + elapsedTime, #mutex); \ + } } \ + } else { \ + ZSTD_pthread_mutex_lock(mutex); \ + } \ + } while (0) #else # define ZSTD_PTHREAD_MUTEX_LOCK(m) ZSTD_pthread_mutex_lock(m) -# define DEBUG_PRINTHEX(l,p,n) {} +# define DEBUG_PRINTHEX(l,p,n) do { } while (0) #endif @@ -100,18 +100,39 @@ typedef struct ZSTDMT_bufferPool_s { unsigned totalBuffers; unsigned nbBuffers; ZSTD_customMem cMem; - buffer_t bTable[1]; /* variable size */ + buffer_t* buffers; } ZSTDMT_bufferPool; +static void ZSTDMT_freeBufferPool(ZSTDMT_bufferPool* bufPool) +{ + DEBUGLOG(3, "ZSTDMT_freeBufferPool (address:%08X)", (U32)(size_t)bufPool); + if (!bufPool) return; /* compatibility with free on NULL */ + if (bufPool->buffers) { + unsigned u; + for (u=0; utotalBuffers; u++) { + DEBUGLOG(4, "free buffer %2u (address:%08X)", u, (U32)(size_t)bufPool->buffers[u].start); + ZSTD_customFree(bufPool->buffers[u].start, bufPool->cMem); + } + ZSTD_customFree(bufPool->buffers, bufPool->cMem); + } + ZSTD_pthread_mutex_destroy(&bufPool->poolMutex); + ZSTD_customFree(bufPool, bufPool->cMem); +} + static ZSTDMT_bufferPool* ZSTDMT_createBufferPool(unsigned maxNbBuffers, ZSTD_customMem cMem) { - ZSTDMT_bufferPool* const bufPool = (ZSTDMT_bufferPool*)ZSTD_customCalloc( - sizeof(ZSTDMT_bufferPool) + (maxNbBuffers-1) * sizeof(buffer_t), cMem); + ZSTDMT_bufferPool* const bufPool = + (ZSTDMT_bufferPool*)ZSTD_customCalloc(sizeof(ZSTDMT_bufferPool), cMem); if (bufPool==NULL) return NULL; if (ZSTD_pthread_mutex_init(&bufPool->poolMutex, NULL)) { ZSTD_customFree(bufPool, cMem); return NULL; } + bufPool->buffers = (buffer_t*)ZSTD_customCalloc(maxNbBuffers * sizeof(buffer_t), cMem); + if (bufPool->buffers==NULL) { + ZSTDMT_freeBufferPool(bufPool); + return NULL; + } bufPool->bufferSize = 64 KB; bufPool->totalBuffers = maxNbBuffers; bufPool->nbBuffers = 0; @@ -119,32 +140,19 @@ static ZSTDMT_bufferPool* ZSTDMT_createBufferPool(unsigned maxNbBuffers, ZSTD_cu return bufPool; } -static void ZSTDMT_freeBufferPool(ZSTDMT_bufferPool* bufPool) -{ - unsigned u; - DEBUGLOG(3, "ZSTDMT_freeBufferPool (address:%08X)", (U32)(size_t)bufPool); - if (!bufPool) return; /* compatibility with free on NULL */ - for (u=0; utotalBuffers; u++) { - DEBUGLOG(4, "free buffer %2u (address:%08X)", u, (U32)(size_t)bufPool->bTable[u].start); - ZSTD_customFree(bufPool->bTable[u].start, bufPool->cMem); - } - ZSTD_pthread_mutex_destroy(&bufPool->poolMutex); - ZSTD_customFree(bufPool, bufPool->cMem); -} - /* only works at initialization, not during compression */ static size_t ZSTDMT_sizeof_bufferPool(ZSTDMT_bufferPool* bufPool) { - size_t const poolSize = sizeof(*bufPool) - + (bufPool->totalBuffers - 1) * sizeof(buffer_t); + size_t const poolSize = sizeof(*bufPool); + size_t const arraySize = bufPool->totalBuffers * sizeof(buffer_t); unsigned u; size_t totalBufferSize = 0; ZSTD_pthread_mutex_lock(&bufPool->poolMutex); for (u=0; utotalBuffers; u++) - totalBufferSize += bufPool->bTable[u].capacity; + totalBufferSize += bufPool->buffers[u].capacity; ZSTD_pthread_mutex_unlock(&bufPool->poolMutex); - return poolSize + totalBufferSize; + return poolSize + arraySize + totalBufferSize; } /* ZSTDMT_setBufferSize() : @@ -187,9 +195,9 @@ static buffer_t ZSTDMT_getBuffer(ZSTDMT_bufferPool* bufPool) DEBUGLOG(5, "ZSTDMT_getBuffer: bSize = %u", (U32)bufPool->bufferSize); ZSTD_pthread_mutex_lock(&bufPool->poolMutex); if (bufPool->nbBuffers) { /* try to use an existing buffer */ - buffer_t const buf = bufPool->bTable[--(bufPool->nbBuffers)]; + buffer_t const buf = bufPool->buffers[--(bufPool->nbBuffers)]; size_t const availBufferSize = buf.capacity; - bufPool->bTable[bufPool->nbBuffers] = g_nullBuffer; + bufPool->buffers[bufPool->nbBuffers] = g_nullBuffer; if ((availBufferSize >= bSize) & ((availBufferSize>>3) <= bSize)) { /* large enough, but not too much */ DEBUGLOG(5, "ZSTDMT_getBuffer: provide buffer %u of size %u", @@ -250,14 +258,14 @@ static void ZSTDMT_releaseBuffer(ZSTDMT_bufferPool* bufPool, buffer_t buf) if (buf.start == NULL) return; /* compatible with release on NULL */ ZSTD_pthread_mutex_lock(&bufPool->poolMutex); if (bufPool->nbBuffers < bufPool->totalBuffers) { - bufPool->bTable[bufPool->nbBuffers++] = buf; /* stored for later use */ + bufPool->buffers[bufPool->nbBuffers++] = buf; /* stored for later use */ DEBUGLOG(5, "ZSTDMT_releaseBuffer: stored buffer of size %u in slot %u", (U32)buf.capacity, (U32)(bufPool->nbBuffers-1)); ZSTD_pthread_mutex_unlock(&bufPool->poolMutex); return; } ZSTD_pthread_mutex_unlock(&bufPool->poolMutex); - /* Reached bufferPool capacity (should not happen) */ + /* Reached bufferPool capacity (note: should not happen) */ DEBUGLOG(5, "ZSTDMT_releaseBuffer: pool capacity reached => freeing "); ZSTD_customFree(buf.start, bufPool->cMem); } @@ -350,16 +358,20 @@ typedef struct { int totalCCtx; int availCCtx; ZSTD_customMem cMem; - ZSTD_CCtx* cctx[1]; /* variable size */ + ZSTD_CCtx** cctxs; } ZSTDMT_CCtxPool; -/* note : all CCtx borrowed from the pool should be released back to the pool _before_ freeing the pool */ +/* note : all CCtx borrowed from the pool must be reverted back to the pool _before_ freeing the pool */ static void ZSTDMT_freeCCtxPool(ZSTDMT_CCtxPool* pool) { - int cid; - for (cid=0; cidtotalCCtx; cid++) - ZSTD_freeCCtx(pool->cctx[cid]); /* note : compatible with free on NULL */ + if (!pool) return; ZSTD_pthread_mutex_destroy(&pool->poolMutex); + if (pool->cctxs) { + int cid; + for (cid=0; cidtotalCCtx; cid++) + ZSTD_freeCCtx(pool->cctxs[cid]); /* free compatible with NULL */ + ZSTD_customFree(pool->cctxs, pool->cMem); + } ZSTD_customFree(pool, pool->cMem); } @@ -368,19 +380,24 @@ static void ZSTDMT_freeCCtxPool(ZSTDMT_CCtxPool* pool) static ZSTDMT_CCtxPool* ZSTDMT_createCCtxPool(int nbWorkers, ZSTD_customMem cMem) { - ZSTDMT_CCtxPool* const cctxPool = (ZSTDMT_CCtxPool*) ZSTD_customCalloc( - sizeof(ZSTDMT_CCtxPool) + (nbWorkers-1)*sizeof(ZSTD_CCtx*), cMem); + ZSTDMT_CCtxPool* const cctxPool = + (ZSTDMT_CCtxPool*) ZSTD_customCalloc(sizeof(ZSTDMT_CCtxPool), cMem); assert(nbWorkers > 0); if (!cctxPool) return NULL; if (ZSTD_pthread_mutex_init(&cctxPool->poolMutex, NULL)) { ZSTD_customFree(cctxPool, cMem); return NULL; } - cctxPool->cMem = cMem; cctxPool->totalCCtx = nbWorkers; + cctxPool->cctxs = (ZSTD_CCtx**)ZSTD_customCalloc(nbWorkers * sizeof(ZSTD_CCtx*), cMem); + if (!cctxPool->cctxs) { + ZSTDMT_freeCCtxPool(cctxPool); + return NULL; + } + cctxPool->cMem = cMem; + cctxPool->cctxs[0] = ZSTD_createCCtx_advanced(cMem); + if (!cctxPool->cctxs[0]) { ZSTDMT_freeCCtxPool(cctxPool); return NULL; } cctxPool->availCCtx = 1; /* at least one cctx for single-thread mode */ - cctxPool->cctx[0] = ZSTD_createCCtx_advanced(cMem); - if (!cctxPool->cctx[0]) { ZSTDMT_freeCCtxPool(cctxPool); return NULL; } DEBUGLOG(3, "cctxPool created, with %u workers", nbWorkers); return cctxPool; } @@ -402,16 +419,16 @@ static size_t ZSTDMT_sizeof_CCtxPool(ZSTDMT_CCtxPool* cctxPool) { ZSTD_pthread_mutex_lock(&cctxPool->poolMutex); { unsigned const nbWorkers = cctxPool->totalCCtx; - size_t const poolSize = sizeof(*cctxPool) - + (nbWorkers-1) * sizeof(ZSTD_CCtx*); - unsigned u; + size_t const poolSize = sizeof(*cctxPool); + size_t const arraySize = cctxPool->totalCCtx * sizeof(ZSTD_CCtx*); size_t totalCCtxSize = 0; + unsigned u; for (u=0; ucctx[u]); + totalCCtxSize += ZSTD_sizeof_CCtx(cctxPool->cctxs[u]); } ZSTD_pthread_mutex_unlock(&cctxPool->poolMutex); assert(nbWorkers > 0); - return poolSize + totalCCtxSize; + return poolSize + arraySize + totalCCtxSize; } } @@ -421,7 +438,7 @@ static ZSTD_CCtx* ZSTDMT_getCCtx(ZSTDMT_CCtxPool* cctxPool) ZSTD_pthread_mutex_lock(&cctxPool->poolMutex); if (cctxPool->availCCtx) { cctxPool->availCCtx--; - { ZSTD_CCtx* const cctx = cctxPool->cctx[cctxPool->availCCtx]; + { ZSTD_CCtx* const cctx = cctxPool->cctxs[cctxPool->availCCtx]; ZSTD_pthread_mutex_unlock(&cctxPool->poolMutex); return cctx; } } @@ -435,7 +452,7 @@ static void ZSTDMT_releaseCCtx(ZSTDMT_CCtxPool* pool, ZSTD_CCtx* cctx) if (cctx==NULL) return; /* compatibility with release on NULL */ ZSTD_pthread_mutex_lock(&pool->poolMutex); if (pool->availCCtx < pool->totalCCtx) - pool->cctx[pool->availCCtx++] = cctx; + pool->cctxs[pool->availCCtx++] = cctx; else { /* pool overflow : should not happen, since totalCCtx==nbWorkers */ DEBUGLOG(4, "CCtx pool overflow : free cctx"); @@ -601,11 +618,8 @@ static void ZSTDMT_serialState_update(serialState_t* serialState, ZSTD_pthread_mutex_unlock(&serialState->mutex); if (seqStore.size > 0) { - size_t const err = ZSTD_referenceExternalSequences( - jobCCtx, seqStore.seq, seqStore.size); + ZSTD_referenceExternalSequences(jobCCtx, seqStore.seq, seqStore.size); assert(serialState->params.ldmParams.enableLdm == ZSTD_ps_enable); - assert(!ZSTD_isError(err)); - (void)err; } } @@ -657,12 +671,13 @@ typedef struct { unsigned frameChecksumNeeded; /* used only by mtctx */ } ZSTDMT_jobDescription; -#define JOB_ERROR(e) { \ - ZSTD_PTHREAD_MUTEX_LOCK(&job->job_mutex); \ - job->cSize = e; \ - ZSTD_pthread_mutex_unlock(&job->job_mutex); \ - goto _endJob; \ -} +#define JOB_ERROR(e) \ + do { \ + ZSTD_PTHREAD_MUTEX_LOCK(&job->job_mutex); \ + job->cSize = e; \ + ZSTD_pthread_mutex_unlock(&job->job_mutex); \ + goto _endJob; \ + } while (0) /* ZSTDMT_compressionJob() is a POOL_function type */ static void ZSTDMT_compressionJob(void* jobDescription) @@ -1091,7 +1106,7 @@ ZSTD_frameProgression ZSTDMT_getFrameProgression(ZSTDMT_CCtx* mtctx) { unsigned jobNb; unsigned lastJobNb = mtctx->nextJobID + mtctx->jobReady; assert(mtctx->jobReady <= 1); DEBUGLOG(6, "ZSTDMT_getFrameProgression: jobs: from %u to <%u (jobReady:%u)", - mtctx->doneJobID, lastJobNb, mtctx->jobReady) + mtctx->doneJobID, lastJobNb, mtctx->jobReady); for (jobNb = mtctx->doneJobID ; jobNb < lastJobNb ; jobNb++) { unsigned const wJobID = jobNb & mtctx->jobIDMask; ZSTDMT_jobDescription* jobPtr = &mtctx->jobs[wJobID]; diff --git a/src/thirdparty/zstd/decompress/huf_decompress.c b/src/thirdparty/zstd/decompress/huf_decompress.c index 5b217ac5..f85dd0be 100644 --- a/src/thirdparty/zstd/decompress/huf_decompress.c +++ b/src/thirdparty/zstd/decompress/huf_decompress.c @@ -34,6 +34,12 @@ * Macros ****************************************************************/ +#ifdef HUF_DISABLE_FAST_DECODE +# define HUF_ENABLE_FAST_DECODE 0 +#else +# define HUF_ENABLE_FAST_DECODE 1 +#endif + /* These two optional macros force the use one way or another of the two * Huffman decompression implementations. You can't force in both directions * at the same time. @@ -158,17 +164,18 @@ static size_t HUF_initFastDStream(BYTE const* ip) { * op [in/out] - The output pointers, must be updated to reflect what is written. * bits [in/out] - The bitstream containers, must be updated to reflect the current state. * dt [in] - The decoding table. - * ilimit [in] - The input limit, stop when any input pointer is below ilimit. + * ilowest [in] - The beginning of the valid range of the input. Decoders may read + * down to this pointer. It may be below iend[0]. * oend [in] - The end of the output stream. op[3] must not cross oend. * iend [in] - The end of each input stream. ip[i] may cross iend[i], - * as long as it is above ilimit, but that indicates corruption. + * as long as it is above ilowest, but that indicates corruption. */ typedef struct { BYTE const* ip[4]; BYTE* op[4]; U64 bits[4]; void const* dt; - BYTE const* ilimit; + BYTE const* ilowest; BYTE* oend; BYTE const* iend[4]; } HUF_DecompressFastArgs; @@ -186,9 +193,9 @@ static size_t HUF_DecompressFastArgs_init(HUF_DecompressFastArgs* args, void* ds void const* dt = DTable + 1; U32 const dtLog = HUF_getDTableDesc(DTable).tableLog; - const BYTE* const ilimit = (const BYTE*)src + 6 + 8; + const BYTE* const istart = (const BYTE*)src; - BYTE* const oend = (BYTE*)dst + dstSize; + BYTE* const oend = ZSTD_maybeNullPtrAdd((BYTE*)dst, dstSize); /* The fast decoding loop assumes 64-bit little-endian. * This condition is false on x32. @@ -196,6 +203,11 @@ static size_t HUF_DecompressFastArgs_init(HUF_DecompressFastArgs* args, void* ds if (!MEM_isLittleEndian() || MEM_32bits()) return 0; + /* Avoid nullptr addition */ + if (dstSize == 0) + return 0; + assert(dst != NULL); + /* strict minimum : jump table + 1 byte per stream */ if (srcSize < 10) return ERROR(corruption_detected); @@ -209,7 +221,6 @@ static size_t HUF_DecompressFastArgs_init(HUF_DecompressFastArgs* args, void* ds /* Read the jump table. */ { - const BYTE* const istart = (const BYTE*)src; size_t const length1 = MEM_readLE16(istart); size_t const length2 = MEM_readLE16(istart+2); size_t const length3 = MEM_readLE16(istart+4); @@ -221,10 +232,8 @@ static size_t HUF_DecompressFastArgs_init(HUF_DecompressFastArgs* args, void* ds /* HUF_initFastDStream() requires this, and this small of an input * won't benefit from the ASM loop anyways. - * length1 must be >= 16 so that ip[0] >= ilimit before the loop - * starts. */ - if (length1 < 16 || length2 < 8 || length3 < 8 || length4 < 8) + if (length1 < 8 || length2 < 8 || length3 < 8 || length4 < 8) return 0; if (length4 > srcSize) return ERROR(corruption_detected); /* overflow */ } @@ -256,11 +265,12 @@ static size_t HUF_DecompressFastArgs_init(HUF_DecompressFastArgs* args, void* ds args->bits[2] = HUF_initFastDStream(args->ip[2]); args->bits[3] = HUF_initFastDStream(args->ip[3]); - /* If ip[] >= ilimit, it is guaranteed to be safe to - * reload bits[]. It may be beyond its section, but is - * guaranteed to be valid (>= istart). - */ - args->ilimit = ilimit; + /* The decoders must be sure to never read beyond ilowest. + * This is lower than iend[0], but allowing decoders to read + * down to ilowest can allow an extra iteration or two in the + * fast loop. + */ + args->ilowest = istart; args->oend = oend; args->dt = dt; @@ -285,13 +295,31 @@ static size_t HUF_initRemainingDStream(BIT_DStream_t* bit, HUF_DecompressFastArg assert(sizeof(size_t) == 8); bit->bitContainer = MEM_readLEST(args->ip[stream]); bit->bitsConsumed = ZSTD_countTrailingZeros64(args->bits[stream]); - bit->start = (const char*)args->iend[0]; + bit->start = (const char*)args->ilowest; bit->limitPtr = bit->start + sizeof(size_t); bit->ptr = (const char*)args->ip[stream]; return 0; } +/* Calls X(N) for each stream 0, 1, 2, 3. */ +#define HUF_4X_FOR_EACH_STREAM(X) \ + do { \ + X(0); \ + X(1); \ + X(2); \ + X(3); \ + } while (0) + +/* Calls X(N, var) for each stream 0, 1, 2, 3. */ +#define HUF_4X_FOR_EACH_STREAM_WITH_VAR(X, var) \ + do { \ + X(0, (var)); \ + X(1, (var)); \ + X(2, (var)); \ + X(3, (var)); \ + } while (0) + #ifndef HUF_FORCE_DECOMPRESS_X2 @@ -500,15 +528,19 @@ HUF_decodeSymbolX1(BIT_DStream_t* Dstream, const HUF_DEltX1* dt, const U32 dtLog } #define HUF_DECODE_SYMBOLX1_0(ptr, DStreamPtr) \ - *ptr++ = HUF_decodeSymbolX1(DStreamPtr, dt, dtLog) + do { *ptr++ = HUF_decodeSymbolX1(DStreamPtr, dt, dtLog); } while (0) -#define HUF_DECODE_SYMBOLX1_1(ptr, DStreamPtr) \ - if (MEM_64bits() || (HUF_TABLELOG_MAX<=12)) \ - HUF_DECODE_SYMBOLX1_0(ptr, DStreamPtr) +#define HUF_DECODE_SYMBOLX1_1(ptr, DStreamPtr) \ + do { \ + if (MEM_64bits() || (HUF_TABLELOG_MAX<=12)) \ + HUF_DECODE_SYMBOLX1_0(ptr, DStreamPtr); \ + } while (0) -#define HUF_DECODE_SYMBOLX1_2(ptr, DStreamPtr) \ - if (MEM_64bits()) \ - HUF_DECODE_SYMBOLX1_0(ptr, DStreamPtr) +#define HUF_DECODE_SYMBOLX1_2(ptr, DStreamPtr) \ + do { \ + if (MEM_64bits()) \ + HUF_DECODE_SYMBOLX1_0(ptr, DStreamPtr); \ + } while (0) HINT_INLINE size_t HUF_decodeStreamX1(BYTE* p, BIT_DStream_t* const bitDPtr, BYTE* const pEnd, const HUF_DEltX1* const dt, const U32 dtLog) @@ -546,7 +578,7 @@ HUF_decompress1X1_usingDTable_internal_body( const HUF_DTable* DTable) { BYTE* op = (BYTE*)dst; - BYTE* const oend = op + dstSize; + BYTE* const oend = ZSTD_maybeNullPtrAdd(op, dstSize); const void* dtPtr = DTable + 1; const HUF_DEltX1* const dt = (const HUF_DEltX1*)dtPtr; BIT_DStream_t bitD; @@ -574,6 +606,7 @@ HUF_decompress4X1_usingDTable_internal_body( { /* Check */ if (cSrcSize < 10) return ERROR(corruption_detected); /* strict minimum : jump table + 1 byte per stream */ + if (dstSize < 6) return ERROR(corruption_detected); /* stream 4-split doesn't work */ { const BYTE* const istart = (const BYTE*) cSrc; BYTE* const ostart = (BYTE*) dst; @@ -609,7 +642,7 @@ HUF_decompress4X1_usingDTable_internal_body( if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */ if (opStart4 > oend) return ERROR(corruption_detected); /* overflow */ - if (dstSize < 6) return ERROR(corruption_detected); /* stream 4-split doesn't work */ + assert(dstSize >= 6); /* validated above */ CHECK_F( BIT_initDStream(&bitD1, istart1, length1) ); CHECK_F( BIT_initDStream(&bitD2, istart2, length2) ); CHECK_F( BIT_initDStream(&bitD3, istart3, length3) ); @@ -692,7 +725,7 @@ void HUF_decompress4X1_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* BYTE* op[4]; U16 const* const dtable = (U16 const*)args->dt; BYTE* const oend = args->oend; - BYTE const* const ilimit = args->ilimit; + BYTE const* const ilowest = args->ilowest; /* Copy the arguments to local variables */ ZSTD_memcpy(&bits, &args->bits, sizeof(bits)); @@ -705,13 +738,12 @@ void HUF_decompress4X1_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* for (;;) { BYTE* olimit; int stream; - int symbol; /* Assert loop preconditions */ #ifndef NDEBUG for (stream = 0; stream < 4; ++stream) { assert(op[stream] <= (stream == 3 ? oend : op[stream + 1])); - assert(ip[stream] >= ilimit); + assert(ip[stream] >= ilowest); } #endif /* Compute olimit */ @@ -721,7 +753,7 @@ void HUF_decompress4X1_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* /* Each iteration consumes up to 11 bits * 5 = 55 bits < 7 bytes * per stream. */ - size_t const iiters = (size_t)(ip[0] - ilimit) / 7; + size_t const iiters = (size_t)(ip[0] - ilowest) / 7; /* We can safely run iters iterations before running bounds checks */ size_t const iters = MIN(oiters, iiters); size_t const symbols = iters * 5; @@ -732,8 +764,8 @@ void HUF_decompress4X1_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* */ olimit = op[3] + symbols; - /* Exit fast decoding loop once we get close to the end. */ - if (op[3] + 20 > olimit) + /* Exit fast decoding loop once we reach the end. */ + if (op[3] == olimit) break; /* Exit the decoding loop if any input pointer has crossed the @@ -752,27 +784,42 @@ void HUF_decompress4X1_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* } #endif +#define HUF_4X1_DECODE_SYMBOL(_stream, _symbol) \ + do { \ + int const index = (int)(bits[(_stream)] >> 53); \ + int const entry = (int)dtable[index]; \ + bits[(_stream)] <<= (entry & 0x3F); \ + op[(_stream)][(_symbol)] = (BYTE)((entry >> 8) & 0xFF); \ + } while (0) + +#define HUF_4X1_RELOAD_STREAM(_stream) \ + do { \ + int const ctz = ZSTD_countTrailingZeros64(bits[(_stream)]); \ + int const nbBits = ctz & 7; \ + int const nbBytes = ctz >> 3; \ + op[(_stream)] += 5; \ + ip[(_stream)] -= nbBytes; \ + bits[(_stream)] = MEM_read64(ip[(_stream)]) | 1; \ + bits[(_stream)] <<= nbBits; \ + } while (0) + + /* Manually unroll the loop because compilers don't consistently + * unroll the inner loops, which destroys performance. + */ do { /* Decode 5 symbols in each of the 4 streams */ - for (symbol = 0; symbol < 5; ++symbol) { - for (stream = 0; stream < 4; ++stream) { - int const index = (int)(bits[stream] >> 53); - int const entry = (int)dtable[index]; - bits[stream] <<= (entry & 63); - op[stream][symbol] = (BYTE)((entry >> 8) & 0xFF); - } - } - /* Reload the bitstreams */ - for (stream = 0; stream < 4; ++stream) { - int const ctz = ZSTD_countTrailingZeros64(bits[stream]); - int const nbBits = ctz & 7; - int const nbBytes = ctz >> 3; - op[stream] += 5; - ip[stream] -= nbBytes; - bits[stream] = MEM_read64(ip[stream]) | 1; - bits[stream] <<= nbBits; - } + HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 0); + HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 1); + HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 2); + HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 3); + HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 4); + + /* Reload each of the 4 the bitstreams */ + HUF_4X_FOR_EACH_STREAM(HUF_4X1_RELOAD_STREAM); } while (op[3] < olimit); + +#undef HUF_4X1_DECODE_SYMBOL +#undef HUF_4X1_RELOAD_STREAM } _out: @@ -797,8 +844,8 @@ HUF_decompress4X1_usingDTable_internal_fast( HUF_DecompressFastLoopFn loopFn) { void const* dt = DTable + 1; - const BYTE* const iend = (const BYTE*)cSrc + 6; - BYTE* const oend = (BYTE*)dst + dstSize; + BYTE const* const ilowest = (BYTE const*)cSrc; + BYTE* const oend = ZSTD_maybeNullPtrAdd((BYTE*)dst, dstSize); HUF_DecompressFastArgs args; { size_t const ret = HUF_DecompressFastArgs_init(&args, dst, dstSize, cSrc, cSrcSize, DTable); FORWARD_IF_ERROR(ret, "Failed to init fast loop args"); @@ -806,18 +853,22 @@ HUF_decompress4X1_usingDTable_internal_fast( return 0; } - assert(args.ip[0] >= args.ilimit); + assert(args.ip[0] >= args.ilowest); loopFn(&args); - /* Our loop guarantees that ip[] >= ilimit and that we haven't + /* Our loop guarantees that ip[] >= ilowest and that we haven't * overwritten any op[]. */ - assert(args.ip[0] >= iend); - assert(args.ip[1] >= iend); - assert(args.ip[2] >= iend); - assert(args.ip[3] >= iend); + assert(args.ip[0] >= ilowest); + assert(args.ip[0] >= ilowest); + assert(args.ip[1] >= ilowest); + assert(args.ip[2] >= ilowest); + assert(args.ip[3] >= ilowest); assert(args.op[3] <= oend); - (void)iend; + + assert(ilowest == args.ilowest); + assert(ilowest + 6 == args.iend[0]); + (void)ilowest; /* finish bit streams one by one. */ { size_t const segmentSize = (dstSize+3) / 4; @@ -868,7 +919,7 @@ static size_t HUF_decompress4X1_usingDTable_internal(void* dst, size_t dstSize, } #endif - if (!(flags & HUF_flags_disableFast)) { + if (HUF_ENABLE_FAST_DECODE && !(flags & HUF_flags_disableFast)) { size_t const ret = HUF_decompress4X1_usingDTable_internal_fast(dst, dstSize, cSrc, cSrcSize, DTable, loopFn); if (ret != 0) return ret; @@ -1239,15 +1290,19 @@ HUF_decodeLastSymbolX2(void* op, BIT_DStream_t* DStream, const HUF_DEltX2* dt, c } #define HUF_DECODE_SYMBOLX2_0(ptr, DStreamPtr) \ - ptr += HUF_decodeSymbolX2(ptr, DStreamPtr, dt, dtLog) + do { ptr += HUF_decodeSymbolX2(ptr, DStreamPtr, dt, dtLog); } while (0) -#define HUF_DECODE_SYMBOLX2_1(ptr, DStreamPtr) \ - if (MEM_64bits() || (HUF_TABLELOG_MAX<=12)) \ - ptr += HUF_decodeSymbolX2(ptr, DStreamPtr, dt, dtLog) +#define HUF_DECODE_SYMBOLX2_1(ptr, DStreamPtr) \ + do { \ + if (MEM_64bits() || (HUF_TABLELOG_MAX<=12)) \ + ptr += HUF_decodeSymbolX2(ptr, DStreamPtr, dt, dtLog); \ + } while (0) -#define HUF_DECODE_SYMBOLX2_2(ptr, DStreamPtr) \ - if (MEM_64bits()) \ - ptr += HUF_decodeSymbolX2(ptr, DStreamPtr, dt, dtLog) +#define HUF_DECODE_SYMBOLX2_2(ptr, DStreamPtr) \ + do { \ + if (MEM_64bits()) \ + ptr += HUF_decodeSymbolX2(ptr, DStreamPtr, dt, dtLog); \ + } while (0) HINT_INLINE size_t HUF_decodeStreamX2(BYTE* p, BIT_DStream_t* bitDPtr, BYTE* const pEnd, @@ -1307,7 +1362,7 @@ HUF_decompress1X2_usingDTable_internal_body( /* decode */ { BYTE* const ostart = (BYTE*) dst; - BYTE* const oend = ostart + dstSize; + BYTE* const oend = ZSTD_maybeNullPtrAdd(ostart, dstSize); const void* const dtPtr = DTable+1; /* force compiler to not use strict-aliasing */ const HUF_DEltX2* const dt = (const HUF_DEltX2*)dtPtr; DTableDesc const dtd = HUF_getDTableDesc(DTable); @@ -1332,6 +1387,7 @@ HUF_decompress4X2_usingDTable_internal_body( const HUF_DTable* DTable) { if (cSrcSize < 10) return ERROR(corruption_detected); /* strict minimum : jump table + 1 byte per stream */ + if (dstSize < 6) return ERROR(corruption_detected); /* stream 4-split doesn't work */ { const BYTE* const istart = (const BYTE*) cSrc; BYTE* const ostart = (BYTE*) dst; @@ -1367,7 +1423,7 @@ HUF_decompress4X2_usingDTable_internal_body( if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */ if (opStart4 > oend) return ERROR(corruption_detected); /* overflow */ - if (dstSize < 6) return ERROR(corruption_detected); /* stream 4-split doesn't work */ + assert(dstSize >= 6 /* validated above */); CHECK_F( BIT_initDStream(&bitD1, istart1, length1) ); CHECK_F( BIT_initDStream(&bitD2, istart2, length2) ); CHECK_F( BIT_initDStream(&bitD3, istart3, length3) ); @@ -1472,7 +1528,7 @@ void HUF_decompress4X2_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* BYTE* op[4]; BYTE* oend[4]; HUF_DEltX2 const* const dtable = (HUF_DEltX2 const*)args->dt; - BYTE const* const ilimit = args->ilimit; + BYTE const* const ilowest = args->ilowest; /* Copy the arguments to local registers. */ ZSTD_memcpy(&bits, &args->bits, sizeof(bits)); @@ -1490,13 +1546,12 @@ void HUF_decompress4X2_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* for (;;) { BYTE* olimit; int stream; - int symbol; /* Assert loop preconditions */ #ifndef NDEBUG for (stream = 0; stream < 4; ++stream) { assert(op[stream] <= oend[stream]); - assert(ip[stream] >= ilimit); + assert(ip[stream] >= ilowest); } #endif /* Compute olimit */ @@ -1509,7 +1564,7 @@ void HUF_decompress4X2_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* * We also know that each input pointer is >= ip[0]. So we can run * iters loops before running out of input. */ - size_t iters = (size_t)(ip[0] - ilimit) / 7; + size_t iters = (size_t)(ip[0] - ilowest) / 7; /* Each iteration can produce up to 10 bytes of output per stream. * Each output stream my advance at different rates. So take the * minimum number of safe iterations among all the output streams. @@ -1527,8 +1582,8 @@ void HUF_decompress4X2_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* */ olimit = op[3] + (iters * 5); - /* Exit the fast decoding loop if we are too close to the end. */ - if (op[3] + 10 > olimit) + /* Exit the fast decoding loop once we reach the end. */ + if (op[3] == olimit) break; /* Exit the decoding loop if any input pointer has crossed the @@ -1547,54 +1602,58 @@ void HUF_decompress4X2_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* } #endif +#define HUF_4X2_DECODE_SYMBOL(_stream, _decode3) \ + do { \ + if ((_decode3) || (_stream) != 3) { \ + int const index = (int)(bits[(_stream)] >> 53); \ + HUF_DEltX2 const entry = dtable[index]; \ + MEM_write16(op[(_stream)], entry.sequence); \ + bits[(_stream)] <<= (entry.nbBits) & 0x3F; \ + op[(_stream)] += (entry.length); \ + } \ + } while (0) + +#define HUF_4X2_RELOAD_STREAM(_stream) \ + do { \ + HUF_4X2_DECODE_SYMBOL(3, 1); \ + { \ + int const ctz = ZSTD_countTrailingZeros64(bits[(_stream)]); \ + int const nbBits = ctz & 7; \ + int const nbBytes = ctz >> 3; \ + ip[(_stream)] -= nbBytes; \ + bits[(_stream)] = MEM_read64(ip[(_stream)]) | 1; \ + bits[(_stream)] <<= nbBits; \ + } \ + } while (0) + + /* Manually unroll the loop because compilers don't consistently + * unroll the inner loops, which destroys performance. + */ do { - /* Do 5 table lookups for each of the first 3 streams */ - for (symbol = 0; symbol < 5; ++symbol) { - for (stream = 0; stream < 3; ++stream) { - int const index = (int)(bits[stream] >> 53); - HUF_DEltX2 const entry = dtable[index]; - MEM_write16(op[stream], entry.sequence); - bits[stream] <<= (entry.nbBits); - op[stream] += (entry.length); - } - } - /* Do 1 table lookup from the final stream */ - { - int const index = (int)(bits[3] >> 53); - HUF_DEltX2 const entry = dtable[index]; - MEM_write16(op[3], entry.sequence); - bits[3] <<= (entry.nbBits); - op[3] += (entry.length); - } - /* Do 4 table lookups from the final stream & reload bitstreams */ - for (stream = 0; stream < 4; ++stream) { - /* Do a table lookup from the final stream. - * This is interleaved with the reloading to reduce register - * pressure. This shouldn't be necessary, but compilers can - * struggle with codegen with high register pressure. - */ - { - int const index = (int)(bits[3] >> 53); - HUF_DEltX2 const entry = dtable[index]; - MEM_write16(op[3], entry.sequence); - bits[3] <<= (entry.nbBits); - op[3] += (entry.length); - } - /* Reload the bistreams. The final bitstream must be reloaded - * after the 5th symbol was decoded. - */ - { - int const ctz = ZSTD_countTrailingZeros64(bits[stream]); - int const nbBits = ctz & 7; - int const nbBytes = ctz >> 3; - ip[stream] -= nbBytes; - bits[stream] = MEM_read64(ip[stream]) | 1; - bits[stream] <<= nbBits; - } - } + /* Decode 5 symbols from each of the first 3 streams. + * The final stream will be decoded during the reload phase + * to reduce register pressure. + */ + HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0); + HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0); + HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0); + HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0); + HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0); + + /* Decode one symbol from the final stream */ + HUF_4X2_DECODE_SYMBOL(3, 1); + + /* Decode 4 symbols from the final stream & reload bitstreams. + * The final stream is reloaded last, meaning that all 5 symbols + * are decoded from the final stream before it is reloaded. + */ + HUF_4X_FOR_EACH_STREAM(HUF_4X2_RELOAD_STREAM); } while (op[3] < olimit); } +#undef HUF_4X2_DECODE_SYMBOL +#undef HUF_4X2_RELOAD_STREAM + _out: /* Save the final values of each of the state variables back to args. */ @@ -1611,8 +1670,8 @@ HUF_decompress4X2_usingDTable_internal_fast( const HUF_DTable* DTable, HUF_DecompressFastLoopFn loopFn) { void const* dt = DTable + 1; - const BYTE* const iend = (const BYTE*)cSrc + 6; - BYTE* const oend = (BYTE*)dst + dstSize; + const BYTE* const ilowest = (const BYTE*)cSrc; + BYTE* const oend = ZSTD_maybeNullPtrAdd((BYTE*)dst, dstSize); HUF_DecompressFastArgs args; { size_t const ret = HUF_DecompressFastArgs_init(&args, dst, dstSize, cSrc, cSrcSize, DTable); @@ -1621,16 +1680,19 @@ HUF_decompress4X2_usingDTable_internal_fast( return 0; } - assert(args.ip[0] >= args.ilimit); + assert(args.ip[0] >= args.ilowest); loopFn(&args); /* note : op4 already verified within main loop */ - assert(args.ip[0] >= iend); - assert(args.ip[1] >= iend); - assert(args.ip[2] >= iend); - assert(args.ip[3] >= iend); + assert(args.ip[0] >= ilowest); + assert(args.ip[1] >= ilowest); + assert(args.ip[2] >= ilowest); + assert(args.ip[3] >= ilowest); assert(args.op[3] <= oend); - (void)iend; + + assert(ilowest == args.ilowest); + assert(ilowest + 6 == args.iend[0]); + (void)ilowest; /* finish bitStreams one by one */ { @@ -1679,7 +1741,7 @@ static size_t HUF_decompress4X2_usingDTable_internal(void* dst, size_t dstSize, } #endif - if (!(flags & HUF_flags_disableFast)) { + if (HUF_ENABLE_FAST_DECODE && !(flags & HUF_flags_disableFast)) { size_t const ret = HUF_decompress4X2_usingDTable_internal_fast(dst, dstSize, cSrc, cSrcSize, DTable, loopFn); if (ret != 0) return ret; diff --git a/src/thirdparty/zstd/decompress/huf_decompress_amd64.S b/src/thirdparty/zstd/decompress/huf_decompress_amd64.S index 671624fe..78da291e 100644 --- a/src/thirdparty/zstd/decompress/huf_decompress_amd64.S +++ b/src/thirdparty/zstd/decompress/huf_decompress_amd64.S @@ -10,11 +10,32 @@ #include "../common/portability_macros.h" +#if defined(__ELF__) && defined(__GNUC__) /* Stack marking * ref: https://wiki.gentoo.org/wiki/Hardened/GNU_stack_quickstart */ -#if defined(__ELF__) && defined(__GNUC__) .section .note.GNU-stack,"",%progbits + +#if defined(__aarch64__) +/* Mark that this assembly supports BTI & PAC, because it is empty for aarch64. + * See: https://github.com/facebook/zstd/issues/3841 + * See: https://gcc.godbolt.org/z/sqr5T4ffK + * See: https://lore.kernel.org/linux-arm-kernel/20200429211641.9279-8-broonie@kernel.org/ + * See: https://reviews.llvm.org/D62609 + */ +.pushsection .note.gnu.property, "a" +.p2align 3 +.long 4 /* size of the name - "GNU\0" */ +.long 0x10 /* size of descriptor */ +.long 0x5 /* NT_GNU_PROPERTY_TYPE_0 */ +.asciz "GNU" +.long 0xc0000000 /* pr_type - GNU_PROPERTY_AARCH64_FEATURE_1_AND */ +.long 4 /* pr_datasz - 4 bytes */ +.long 3 /* pr_data - GNU_PROPERTY_AARCH64_FEATURE_1_BTI | GNU_PROPERTY_AARCH64_FEATURE_1_PAC */ +.p2align 3 /* pr_padding - bring everything to 8 byte alignment */ +.popsection +#endif + #endif #if ZSTD_ENABLE_ASM_X86_64_BMI2 @@ -131,7 +152,7 @@ HUF_decompress4X1_usingDTable_internal_fast_asm_loop: movq 88(%rax), %bits3 movq 96(%rax), %dtable push %rax /* argument */ - push 104(%rax) /* ilimit */ + push 104(%rax) /* ilowest */ push 112(%rax) /* oend */ push %olimit /* olimit space */ @@ -156,11 +177,11 @@ HUF_decompress4X1_usingDTable_internal_fast_asm_loop: shrq $2, %r15 movq %ip0, %rax /* rax = ip0 */ - movq 40(%rsp), %rdx /* rdx = ilimit */ - subq %rdx, %rax /* rax = ip0 - ilimit */ - movq %rax, %rbx /* rbx = ip0 - ilimit */ + movq 40(%rsp), %rdx /* rdx = ilowest */ + subq %rdx, %rax /* rax = ip0 - ilowest */ + movq %rax, %rbx /* rbx = ip0 - ilowest */ - /* rdx = (ip0 - ilimit) / 7 */ + /* rdx = (ip0 - ilowest) / 7 */ movabsq $2635249153387078803, %rdx mulq %rdx subq %rdx, %rbx @@ -183,9 +204,8 @@ HUF_decompress4X1_usingDTable_internal_fast_asm_loop: /* If (op3 + 20 > olimit) */ movq %op3, %rax /* rax = op3 */ - addq $20, %rax /* rax = op3 + 20 */ - cmpq %rax, %olimit /* op3 + 20 > olimit */ - jb .L_4X1_exit + cmpq %rax, %olimit /* op3 == olimit */ + je .L_4X1_exit /* If (ip1 < ip0) go to exit */ cmpq %ip0, %ip1 @@ -316,7 +336,7 @@ HUF_decompress4X1_usingDTable_internal_fast_asm_loop: /* Restore stack (oend & olimit) */ pop %rax /* olimit */ pop %rax /* oend */ - pop %rax /* ilimit */ + pop %rax /* ilowest */ pop %rax /* arg */ /* Save ip / op / bits */ @@ -387,7 +407,7 @@ HUF_decompress4X2_usingDTable_internal_fast_asm_loop: movq 96(%rax), %dtable push %rax /* argument */ push %rax /* olimit */ - push 104(%rax) /* ilimit */ + push 104(%rax) /* ilowest */ movq 112(%rax), %rax push %rax /* oend3 */ @@ -414,9 +434,9 @@ HUF_decompress4X2_usingDTable_internal_fast_asm_loop: /* We can consume up to 7 input bytes each iteration. */ movq %ip0, %rax /* rax = ip0 */ - movq 40(%rsp), %rdx /* rdx = ilimit */ - subq %rdx, %rax /* rax = ip0 - ilimit */ - movq %rax, %r15 /* r15 = ip0 - ilimit */ + movq 40(%rsp), %rdx /* rdx = ilowest */ + subq %rdx, %rax /* rax = ip0 - ilowest */ + movq %rax, %r15 /* r15 = ip0 - ilowest */ /* rdx = rax / 7 */ movabsq $2635249153387078803, %rdx @@ -426,7 +446,7 @@ HUF_decompress4X2_usingDTable_internal_fast_asm_loop: addq %r15, %rdx shrq $2, %rdx - /* r15 = (ip0 - ilimit) / 7 */ + /* r15 = (ip0 - ilowest) / 7 */ movq %rdx, %r15 /* r15 = min(r15, min(oend0 - op0, oend1 - op1, oend2 - op2, oend3 - op3) / 10) */ @@ -467,9 +487,8 @@ HUF_decompress4X2_usingDTable_internal_fast_asm_loop: /* If (op3 + 10 > olimit) */ movq %op3, %rax /* rax = op3 */ - addq $10, %rax /* rax = op3 + 10 */ - cmpq %rax, %olimit /* op3 + 10 > olimit */ - jb .L_4X2_exit + cmpq %rax, %olimit /* op3 == olimit */ + je .L_4X2_exit /* If (ip1 < ip0) go to exit */ cmpq %ip0, %ip1 @@ -537,7 +556,7 @@ HUF_decompress4X2_usingDTable_internal_fast_asm_loop: pop %rax /* oend1 */ pop %rax /* oend2 */ pop %rax /* oend3 */ - pop %rax /* ilimit */ + pop %rax /* ilowest */ pop %rax /* olimit */ pop %rax /* arg */ diff --git a/src/thirdparty/zstd/decompress/zstd_decompress.c b/src/thirdparty/zstd/decompress/zstd_decompress.c index 7bc27134..2f03cf7b 100644 --- a/src/thirdparty/zstd/decompress/zstd_decompress.c +++ b/src/thirdparty/zstd/decompress/zstd_decompress.c @@ -55,18 +55,19 @@ /*-******************************************************* * Dependencies *********************************************************/ -#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */ #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */ +#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */ +#include "../common/error_private.h" +#include "../common/zstd_internal.h" /* blockProperties_t */ #include "../common/mem.h" /* low level memory routines */ +#include "../common/bits.h" /* ZSTD_highbit32 */ #define FSE_STATIC_LINKING_ONLY #include "../common/fse.h" #include "../common/huf.h" #include "../common/xxhash.h" /* XXH64_reset, XXH64_update, XXH64_digest, XXH64 */ -#include "../common/zstd_internal.h" /* blockProperties_t */ #include "zstd_decompress_internal.h" /* ZSTD_DCtx */ #include "zstd_ddict.h" /* ZSTD_DDictDictContent */ #include "zstd_decompress_block.h" /* ZSTD_decompressBlock_internal */ -#include "../common/bits.h" /* ZSTD_highbit32 */ #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1) # include "../legacy/zstd_legacy.h" @@ -245,6 +246,7 @@ static void ZSTD_DCtx_resetParameters(ZSTD_DCtx* dctx) dctx->forceIgnoreChecksum = ZSTD_d_validateChecksum; dctx->refMultipleDDicts = ZSTD_rmd_refSingleDDict; dctx->disableHufAsm = 0; + dctx->maxBlockSizeParam = 0; } static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx) @@ -265,6 +267,7 @@ static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx) #endif dctx->noForwardProgress = 0; dctx->oversizedDuration = 0; + dctx->isFrameDecompression = 1; #if DYNAMIC_BMI2 dctx->bmi2 = ZSTD_cpuSupportsBmi2(); #endif @@ -726,17 +729,17 @@ static ZSTD_frameSizeInfo ZSTD_errorFrameSizeInfo(size_t ret) return frameSizeInfo; } -static ZSTD_frameSizeInfo ZSTD_findFrameSizeInfo(const void* src, size_t srcSize) +static ZSTD_frameSizeInfo ZSTD_findFrameSizeInfo(const void* src, size_t srcSize, ZSTD_format_e format) { ZSTD_frameSizeInfo frameSizeInfo; ZSTD_memset(&frameSizeInfo, 0, sizeof(ZSTD_frameSizeInfo)); #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) - if (ZSTD_isLegacy(src, srcSize)) + if (format == ZSTD_f_zstd1 && ZSTD_isLegacy(src, srcSize)) return ZSTD_findFrameSizeInfoLegacy(src, srcSize); #endif - if ((srcSize >= ZSTD_SKIPPABLEHEADERSIZE) + if (format == ZSTD_f_zstd1 && (srcSize >= ZSTD_SKIPPABLEHEADERSIZE) && (MEM_readLE32(src) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { frameSizeInfo.compressedSize = readSkippableFrameSize(src, srcSize); assert(ZSTD_isError(frameSizeInfo.compressedSize) || @@ -750,7 +753,7 @@ static ZSTD_frameSizeInfo ZSTD_findFrameSizeInfo(const void* src, size_t srcSize ZSTD_frameHeader zfh; /* Extract Frame Header */ - { size_t const ret = ZSTD_getFrameHeader(&zfh, src, srcSize); + { size_t const ret = ZSTD_getFrameHeader_advanced(&zfh, src, srcSize, format); if (ZSTD_isError(ret)) return ZSTD_errorFrameSizeInfo(ret); if (ret > 0) @@ -793,15 +796,17 @@ static ZSTD_frameSizeInfo ZSTD_findFrameSizeInfo(const void* src, size_t srcSize } } +static size_t ZSTD_findFrameCompressedSize_advanced(const void *src, size_t srcSize, ZSTD_format_e format) { + ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize, format); + return frameSizeInfo.compressedSize; +} + /** ZSTD_findFrameCompressedSize() : - * compatible with legacy mode - * `src` must point to the start of a ZSTD frame, ZSTD legacy frame, or skippable frame - * `srcSize` must be at least as large as the frame contained - * @return : the compressed size of the frame starting at `src` */ + * See docs in zstd.h + * Note: compatible with legacy mode */ size_t ZSTD_findFrameCompressedSize(const void *src, size_t srcSize) { - ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize); - return frameSizeInfo.compressedSize; + return ZSTD_findFrameCompressedSize_advanced(src, srcSize, ZSTD_f_zstd1); } /** ZSTD_decompressBound() : @@ -815,7 +820,7 @@ unsigned long long ZSTD_decompressBound(const void* src, size_t srcSize) unsigned long long bound = 0; /* Iterate over each frame */ while (srcSize > 0) { - ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize); + ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize, ZSTD_f_zstd1); size_t const compressedSize = frameSizeInfo.compressedSize; unsigned long long const decompressedBound = frameSizeInfo.decompressedBound; if (ZSTD_isError(compressedSize) || decompressedBound == ZSTD_CONTENTSIZE_ERROR) @@ -835,7 +840,7 @@ size_t ZSTD_decompressionMargin(void const* src, size_t srcSize) /* Iterate over each frame */ while (srcSize > 0) { - ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize); + ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize, ZSTD_f_zstd1); size_t const compressedSize = frameSizeInfo.compressedSize; unsigned long long const decompressedBound = frameSizeInfo.decompressedBound; ZSTD_frameHeader zfh; @@ -971,6 +976,10 @@ static size_t ZSTD_decompressFrame(ZSTD_DCtx* dctx, ip += frameHeaderSize; remainingSrcSize -= frameHeaderSize; } + /* Shrink the blockSizeMax if enabled */ + if (dctx->maxBlockSizeParam != 0) + dctx->fParams.blockSizeMax = MIN(dctx->fParams.blockSizeMax, (unsigned)dctx->maxBlockSizeParam); + /* Loop on each block */ while (1) { BYTE* oBlockEnd = oend; @@ -1003,7 +1012,8 @@ static size_t ZSTD_decompressFrame(ZSTD_DCtx* dctx, switch(blockProperties.blockType) { case bt_compressed: - decodedSize = ZSTD_decompressBlock_internal(dctx, op, (size_t)(oBlockEnd-op), ip, cBlockSize, /* frame */ 1, not_streaming); + assert(dctx->isFrameDecompression == 1); + decodedSize = ZSTD_decompressBlock_internal(dctx, op, (size_t)(oBlockEnd-op), ip, cBlockSize, not_streaming); break; case bt_raw : /* Use oend instead of oBlockEnd because this function is safe to overlap. It uses memmove. */ @@ -1016,12 +1026,14 @@ static size_t ZSTD_decompressFrame(ZSTD_DCtx* dctx, default: RETURN_ERROR(corruption_detected, "invalid block type"); } - - if (ZSTD_isError(decodedSize)) return decodedSize; - if (dctx->validateChecksum) + FORWARD_IF_ERROR(decodedSize, "Block decompression failure"); + DEBUGLOG(5, "Decompressed block of dSize = %u", (unsigned)decodedSize); + if (dctx->validateChecksum) { XXH64_update(&dctx->xxhState, op, decodedSize); - if (decodedSize != 0) + } + if (decodedSize) /* support dst = NULL,0 */ { op += decodedSize; + } assert(ip != NULL); ip += cBlockSize; remainingSrcSize -= cBlockSize; @@ -1051,7 +1063,9 @@ static size_t ZSTD_decompressFrame(ZSTD_DCtx* dctx, return (size_t)(op-ostart); } -static size_t ZSTD_decompressMultiFrame(ZSTD_DCtx* dctx, +static +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR +size_t ZSTD_decompressMultiFrame(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize, const void* dict, size_t dictSize, @@ -1071,7 +1085,7 @@ static size_t ZSTD_decompressMultiFrame(ZSTD_DCtx* dctx, while (srcSize >= ZSTD_startingInputLength(dctx->format)) { #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) - if (ZSTD_isLegacy(src, srcSize)) { + if (dctx->format == ZSTD_f_zstd1 && ZSTD_isLegacy(src, srcSize)) { size_t decodedSize; size_t const frameSize = ZSTD_findFrameCompressedSizeLegacy(src, srcSize); if (ZSTD_isError(frameSize)) return frameSize; @@ -1081,6 +1095,15 @@ static size_t ZSTD_decompressMultiFrame(ZSTD_DCtx* dctx, decodedSize = ZSTD_decompressLegacy(dst, dstCapacity, src, frameSize, dict, dictSize); if (ZSTD_isError(decodedSize)) return decodedSize; + { + unsigned long long const expectedSize = ZSTD_getFrameContentSize(src, srcSize); + RETURN_ERROR_IF(expectedSize == ZSTD_CONTENTSIZE_ERROR, corruption_detected, "Corrupted frame header!"); + if (expectedSize != ZSTD_CONTENTSIZE_UNKNOWN) { + RETURN_ERROR_IF(expectedSize != decodedSize, corruption_detected, + "Frame header size does not match decoded size!"); + } + } + assert(decodedSize <= dstCapacity); dst = (BYTE*)dst + decodedSize; dstCapacity -= decodedSize; @@ -1092,7 +1115,7 @@ static size_t ZSTD_decompressMultiFrame(ZSTD_DCtx* dctx, } #endif - if (srcSize >= 4) { + if (dctx->format == ZSTD_f_zstd1 && srcSize >= 4) { U32 const magicNumber = MEM_readLE32(src); DEBUGLOG(5, "reading magic number %08X", (unsigned)magicNumber); if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { @@ -1319,7 +1342,8 @@ size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, c { case bt_compressed: DEBUGLOG(5, "ZSTD_decompressContinue: case bt_compressed"); - rSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, /* frame */ 1, is_streaming); + assert(dctx->isFrameDecompression == 1); + rSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, is_streaming); dctx->expected = 0; /* Streaming not supported */ break; case bt_raw : @@ -1388,6 +1412,7 @@ size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, c case ZSTDds_decodeSkippableHeader: assert(src != NULL); assert(srcSize <= ZSTD_SKIPPABLEHEADERSIZE); + assert(dctx->format != ZSTD_f_zstd1_magicless); ZSTD_memcpy(dctx->headerBuffer + (ZSTD_SKIPPABLEHEADERSIZE - srcSize), src, srcSize); /* complete skippable header */ dctx->expected = MEM_readLE32(dctx->headerBuffer + ZSTD_FRAMEIDSIZE); /* note : dctx->expected can grow seriously large, beyond local buffer size */ dctx->stage = ZSTDds_skipFrame; @@ -1548,6 +1573,7 @@ size_t ZSTD_decompressBegin(ZSTD_DCtx* dctx) dctx->litEntropy = dctx->fseEntropy = 0; dctx->dictID = 0; dctx->bType = bt_reserved; + dctx->isFrameDecompression = 1; ZSTD_STATIC_ASSERT(sizeof(dctx->entropy.rep) == sizeof(repStartValue)); ZSTD_memcpy(dctx->entropy.rep, repStartValue, sizeof(repStartValue)); /* initial repcodes */ dctx->LLTptr = dctx->entropy.LLTable; @@ -1819,6 +1845,10 @@ ZSTD_bounds ZSTD_dParam_getBounds(ZSTD_dParameter dParam) bounds.lowerBound = 0; bounds.upperBound = 1; return bounds; + case ZSTD_d_maxBlockSize: + bounds.lowerBound = ZSTD_BLOCKSIZE_MAX_MIN; + bounds.upperBound = ZSTD_BLOCKSIZE_MAX; + return bounds; default:; } @@ -1863,6 +1893,9 @@ size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value case ZSTD_d_disableHuffmanAssembly: *value = (int)dctx->disableHufAsm; return 0; + case ZSTD_d_maxBlockSize: + *value = dctx->maxBlockSizeParam; + return 0; default:; } RETURN_ERROR(parameter_unsupported, ""); @@ -1900,6 +1933,10 @@ size_t ZSTD_DCtx_setParameter(ZSTD_DCtx* dctx, ZSTD_dParameter dParam, int value CHECK_DBOUNDS(ZSTD_d_disableHuffmanAssembly, value); dctx->disableHufAsm = value != 0; return 0; + case ZSTD_d_maxBlockSize: + if (value != 0) CHECK_DBOUNDS(ZSTD_d_maxBlockSize, value); + dctx->maxBlockSizeParam = value; + return 0; default:; } RETURN_ERROR(parameter_unsupported, ""); @@ -1911,6 +1948,7 @@ size_t ZSTD_DCtx_reset(ZSTD_DCtx* dctx, ZSTD_ResetDirective reset) || (reset == ZSTD_reset_session_and_parameters) ) { dctx->streamStage = zdss_init; dctx->noForwardProgress = 0; + dctx->isFrameDecompression = 1; } if ( (reset == ZSTD_reset_parameters) || (reset == ZSTD_reset_session_and_parameters) ) { @@ -1927,11 +1965,17 @@ size_t ZSTD_sizeof_DStream(const ZSTD_DStream* dctx) return ZSTD_sizeof_DCtx(dctx); } -size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long long frameContentSize) +static size_t ZSTD_decodingBufferSize_internal(unsigned long long windowSize, unsigned long long frameContentSize, size_t blockSizeMax) { - size_t const blockSize = (size_t) MIN(windowSize, ZSTD_BLOCKSIZE_MAX); - /* space is needed to store the litbuffer after the output of a given block without stomping the extDict of a previous run, as well as to cover both windows against wildcopy*/ - unsigned long long const neededRBSize = windowSize + blockSize + ZSTD_BLOCKSIZE_MAX + (WILDCOPY_OVERLENGTH * 2); + size_t const blockSize = MIN((size_t)MIN(windowSize, ZSTD_BLOCKSIZE_MAX), blockSizeMax); + /* We need blockSize + WILDCOPY_OVERLENGTH worth of buffer so that if a block + * ends at windowSize + WILDCOPY_OVERLENGTH + 1 bytes, we can start writing + * the block at the beginning of the output buffer, and maintain a full window. + * + * We need another blockSize worth of buffer so that we can store split + * literals at the end of the block without overwriting the extDict window. + */ + unsigned long long const neededRBSize = windowSize + (blockSize * 2) + (WILDCOPY_OVERLENGTH * 2); unsigned long long const neededSize = MIN(frameContentSize, neededRBSize); size_t const minRBSize = (size_t) neededSize; RETURN_ERROR_IF((unsigned long long)minRBSize != neededSize, @@ -1939,6 +1983,11 @@ size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long return minRBSize; } +size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long long frameContentSize) +{ + return ZSTD_decodingBufferSize_internal(windowSize, frameContentSize, ZSTD_BLOCKSIZE_MAX); +} + size_t ZSTD_estimateDStreamSize(size_t windowSize) { size_t const blockSize = MIN(windowSize, ZSTD_BLOCKSIZE_MAX); @@ -2134,12 +2183,12 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB if (zds->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN && zds->fParams.frameType != ZSTD_skippableFrame && (U64)(size_t)(oend-op) >= zds->fParams.frameContentSize) { - size_t const cSize = ZSTD_findFrameCompressedSize(istart, (size_t)(iend-istart)); + size_t const cSize = ZSTD_findFrameCompressedSize_advanced(istart, (size_t)(iend-istart), zds->format); if (cSize <= (size_t)(iend-istart)) { /* shortcut : using single-pass mode */ size_t const decompressedSize = ZSTD_decompress_usingDDict(zds, op, (size_t)(oend-op), istart, cSize, ZSTD_getDDict(zds)); if (ZSTD_isError(decompressedSize)) return decompressedSize; - DEBUGLOG(4, "shortcut to single-pass ZSTD_decompress_usingDDict()") + DEBUGLOG(4, "shortcut to single-pass ZSTD_decompress_usingDDict()"); assert(istart != NULL); ip = istart + cSize; op = op ? op + decompressedSize : op; /* can occur if frameContentSize = 0 (empty frame) */ @@ -2161,7 +2210,8 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB DEBUGLOG(4, "Consume header"); FORWARD_IF_ERROR(ZSTD_decompressBegin_usingDDict(zds, ZSTD_getDDict(zds)), ""); - if ((MEM_readLE32(zds->headerBuffer) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { /* skippable frame */ + if (zds->format == ZSTD_f_zstd1 + && (MEM_readLE32(zds->headerBuffer) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { /* skippable frame */ zds->expected = MEM_readLE32(zds->headerBuffer + ZSTD_FRAMEIDSIZE); zds->stage = ZSTDds_skipFrame; } else { @@ -2177,11 +2227,13 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB zds->fParams.windowSize = MAX(zds->fParams.windowSize, 1U << ZSTD_WINDOWLOG_ABSOLUTEMIN); RETURN_ERROR_IF(zds->fParams.windowSize > zds->maxWindowSize, frameParameter_windowTooLarge, ""); + if (zds->maxBlockSizeParam != 0) + zds->fParams.blockSizeMax = MIN(zds->fParams.blockSizeMax, (unsigned)zds->maxBlockSizeParam); /* Adapt buffer sizes to frame header instructions */ { size_t const neededInBuffSize = MAX(zds->fParams.blockSizeMax, 4 /* frame checksum */); size_t const neededOutBuffSize = zds->outBufferMode == ZSTD_bm_buffered - ? ZSTD_decodingBufferSize_min(zds->fParams.windowSize, zds->fParams.frameContentSize) + ? ZSTD_decodingBufferSize_internal(zds->fParams.windowSize, zds->fParams.frameContentSize, zds->fParams.blockSizeMax) : 0; ZSTD_DCtx_updateOversizedDuration(zds, neededInBuffSize, neededOutBuffSize); diff --git a/src/thirdparty/zstd/decompress/zstd_decompress_block.c b/src/thirdparty/zstd/decompress/zstd_decompress_block.c index 09896a93..76d7332e 100644 --- a/src/thirdparty/zstd/decompress/zstd_decompress_block.c +++ b/src/thirdparty/zstd/decompress/zstd_decompress_block.c @@ -51,6 +51,13 @@ static void ZSTD_copy4(void* dst, const void* src) { ZSTD_memcpy(dst, src, 4); } * Block decoding ***************************************************************/ +static size_t ZSTD_blockSizeMax(ZSTD_DCtx const* dctx) +{ + size_t const blockSizeMax = dctx->isFrameDecompression ? dctx->fParams.blockSizeMax : ZSTD_BLOCKSIZE_MAX; + assert(blockSizeMax <= ZSTD_BLOCKSIZE_MAX); + return blockSizeMax; +} + /*! ZSTD_getcBlockSize() : * Provides the size of compressed block from block header `src` */ size_t ZSTD_getcBlockSize(const void* src, size_t srcSize, @@ -73,41 +80,49 @@ size_t ZSTD_getcBlockSize(const void* src, size_t srcSize, static void ZSTD_allocateLiteralsBuffer(ZSTD_DCtx* dctx, void* const dst, const size_t dstCapacity, const size_t litSize, const streaming_operation streaming, const size_t expectedWriteSize, const unsigned splitImmediately) { - if (streaming == not_streaming && dstCapacity > ZSTD_BLOCKSIZE_MAX + WILDCOPY_OVERLENGTH + litSize + WILDCOPY_OVERLENGTH) - { - /* room for litbuffer to fit without read faulting */ - dctx->litBuffer = (BYTE*)dst + ZSTD_BLOCKSIZE_MAX + WILDCOPY_OVERLENGTH; + size_t const blockSizeMax = ZSTD_blockSizeMax(dctx); + assert(litSize <= blockSizeMax); + assert(dctx->isFrameDecompression || streaming == not_streaming); + assert(expectedWriteSize <= blockSizeMax); + if (streaming == not_streaming && dstCapacity > blockSizeMax + WILDCOPY_OVERLENGTH + litSize + WILDCOPY_OVERLENGTH) { + /* If we aren't streaming, we can just put the literals after the output + * of the current block. We don't need to worry about overwriting the + * extDict of our window, because it doesn't exist. + * So if we have space after the end of the block, just put it there. + */ + dctx->litBuffer = (BYTE*)dst + blockSizeMax + WILDCOPY_OVERLENGTH; dctx->litBufferEnd = dctx->litBuffer + litSize; dctx->litBufferLocation = ZSTD_in_dst; - } - else if (litSize > ZSTD_LITBUFFEREXTRASIZE) - { - /* won't fit in litExtraBuffer, so it will be split between end of dst and extra buffer */ + } else if (litSize <= ZSTD_LITBUFFEREXTRASIZE) { + /* Literals fit entirely within the extra buffer, put them there to avoid + * having to split the literals. + */ + dctx->litBuffer = dctx->litExtraBuffer; + dctx->litBufferEnd = dctx->litBuffer + litSize; + dctx->litBufferLocation = ZSTD_not_in_dst; + } else { + assert(blockSizeMax > ZSTD_LITBUFFEREXTRASIZE); + /* Literals must be split between the output block and the extra lit + * buffer. We fill the extra lit buffer with the tail of the literals, + * and put the rest of the literals at the end of the block, with + * WILDCOPY_OVERLENGTH of buffer room to allow for overreads. + * This MUST not write more than our maxBlockSize beyond dst, because in + * streaming mode, that could overwrite part of our extDict window. + */ if (splitImmediately) { /* won't fit in litExtraBuffer, so it will be split between end of dst and extra buffer */ dctx->litBuffer = (BYTE*)dst + expectedWriteSize - litSize + ZSTD_LITBUFFEREXTRASIZE - WILDCOPY_OVERLENGTH; dctx->litBufferEnd = dctx->litBuffer + litSize - ZSTD_LITBUFFEREXTRASIZE; - } - else { + } else { /* initially this will be stored entirely in dst during huffman decoding, it will partially be shifted to litExtraBuffer after */ dctx->litBuffer = (BYTE*)dst + expectedWriteSize - litSize; dctx->litBufferEnd = (BYTE*)dst + expectedWriteSize; } dctx->litBufferLocation = ZSTD_split; - } - else - { - /* fits entirely within litExtraBuffer, so no split is necessary */ - dctx->litBuffer = dctx->litExtraBuffer; - dctx->litBufferEnd = dctx->litBuffer + litSize; - dctx->litBufferLocation = ZSTD_not_in_dst; + assert(dctx->litBufferEnd <= (BYTE*)dst + expectedWriteSize); } } -/* Hidden declaration for fullbench */ -size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, - const void* src, size_t srcSize, - void* dst, size_t dstCapacity, const streaming_operation streaming); /*! ZSTD_decodeLiteralsBlock() : * Where it is possible to do so without being stomped by the output during decompression, the literals block will be stored * in the dstBuffer. If there is room to do so, it will be stored in full in the excess dst space after where the current @@ -116,7 +131,7 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, * * @return : nb of bytes read from src (< srcSize ) * note : symbol not declared but exposed for fullbench */ -size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, +static size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, const void* src, size_t srcSize, /* note : srcSize < BLOCKSIZE */ void* dst, size_t dstCapacity, const streaming_operation streaming) { @@ -125,6 +140,7 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, { const BYTE* const istart = (const BYTE*) src; symbolEncodingType_e const litEncType = (symbolEncodingType_e)(istart[0] & 3); + size_t const blockSizeMax = ZSTD_blockSizeMax(dctx); switch(litEncType) { @@ -140,7 +156,7 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, U32 const lhlCode = (istart[0] >> 2) & 3; U32 const lhc = MEM_readLE32(istart); size_t hufSuccess; - size_t expectedWriteSize = MIN(ZSTD_BLOCKSIZE_MAX, dstCapacity); + size_t expectedWriteSize = MIN(blockSizeMax, dstCapacity); int const flags = 0 | (ZSTD_DCtx_get_bmi2(dctx) ? HUF_flags_bmi2 : 0) | (dctx->disableHufAsm ? HUF_flags_disableAsm : 0); @@ -167,7 +183,7 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, break; } RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled"); - RETURN_ERROR_IF(litSize > ZSTD_BLOCKSIZE_MAX, corruption_detected, ""); + RETURN_ERROR_IF(litSize > blockSizeMax, corruption_detected, ""); if (!singleStream) RETURN_ERROR_IF(litSize < MIN_LITERALS_FOR_4_STREAMS, literals_headerWrong, "Not enough literals (%zu) for the 4-streams mode (min %u)", @@ -214,10 +230,12 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, } if (dctx->litBufferLocation == ZSTD_split) { + assert(litSize > ZSTD_LITBUFFEREXTRASIZE); ZSTD_memcpy(dctx->litExtraBuffer, dctx->litBufferEnd - ZSTD_LITBUFFEREXTRASIZE, ZSTD_LITBUFFEREXTRASIZE); ZSTD_memmove(dctx->litBuffer + ZSTD_LITBUFFEREXTRASIZE - WILDCOPY_OVERLENGTH, dctx->litBuffer, litSize - ZSTD_LITBUFFEREXTRASIZE); dctx->litBuffer += ZSTD_LITBUFFEREXTRASIZE - WILDCOPY_OVERLENGTH; dctx->litBufferEnd -= WILDCOPY_OVERLENGTH; + assert(dctx->litBufferEnd <= (BYTE*)dst + blockSizeMax); } RETURN_ERROR_IF(HUF_isError(hufSuccess), corruption_detected, ""); @@ -232,7 +250,7 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, case set_basic: { size_t litSize, lhSize; U32 const lhlCode = ((istart[0]) >> 2) & 3; - size_t expectedWriteSize = MIN(ZSTD_BLOCKSIZE_MAX, dstCapacity); + size_t expectedWriteSize = MIN(blockSizeMax, dstCapacity); switch(lhlCode) { case 0: case 2: default: /* note : default is impossible, since lhlCode into [0..3] */ @@ -251,6 +269,7 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, } RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled"); + RETURN_ERROR_IF(litSize > blockSizeMax, corruption_detected, ""); RETURN_ERROR_IF(expectedWriteSize < litSize, dstSize_tooSmall, ""); ZSTD_allocateLiteralsBuffer(dctx, dst, dstCapacity, litSize, streaming, expectedWriteSize, 1); if (lhSize+litSize+WILDCOPY_OVERLENGTH > srcSize) { /* risk reading beyond src buffer with wildcopy */ @@ -279,7 +298,7 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, case set_rle: { U32 const lhlCode = ((istart[0]) >> 2) & 3; size_t litSize, lhSize; - size_t expectedWriteSize = MIN(ZSTD_BLOCKSIZE_MAX, dstCapacity); + size_t expectedWriteSize = MIN(blockSizeMax, dstCapacity); switch(lhlCode) { case 0: case 2: default: /* note : default is impossible, since lhlCode into [0..3] */ @@ -298,7 +317,7 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, break; } RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled"); - RETURN_ERROR_IF(litSize > ZSTD_BLOCKSIZE_MAX, corruption_detected, ""); + RETURN_ERROR_IF(litSize > blockSizeMax, corruption_detected, ""); RETURN_ERROR_IF(expectedWriteSize < litSize, dstSize_tooSmall, ""); ZSTD_allocateLiteralsBuffer(dctx, dst, dstCapacity, litSize, streaming, expectedWriteSize, 1); if (dctx->litBufferLocation == ZSTD_split) @@ -320,6 +339,18 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, } } +/* Hidden declaration for fullbench */ +size_t ZSTD_decodeLiteralsBlock_wrapper(ZSTD_DCtx* dctx, + const void* src, size_t srcSize, + void* dst, size_t dstCapacity); +size_t ZSTD_decodeLiteralsBlock_wrapper(ZSTD_DCtx* dctx, + const void* src, size_t srcSize, + void* dst, size_t dstCapacity) +{ + dctx->isFrameDecompression = 0; + return ZSTD_decodeLiteralsBlock(dctx, src, srcSize, dst, dstCapacity, not_streaming); +} + /* Default FSE distribution tables. * These are pre-calculated FSE decoding tables using default distributions as defined in specification : * https://github.com/facebook/zstd/blob/release/doc/zstd_compression_format.md#default-distributions @@ -675,11 +706,6 @@ size_t ZSTD_decodeSeqHeaders(ZSTD_DCtx* dctx, int* nbSeqPtr, /* SeqHead */ nbSeq = *ip++; - if (!nbSeq) { - *nbSeqPtr=0; - RETURN_ERROR_IF(srcSize != 1, srcSize_wrong, ""); - return 1; - } if (nbSeq > 0x7F) { if (nbSeq == 0xFF) { RETURN_ERROR_IF(ip+2 > iend, srcSize_wrong, ""); @@ -692,8 +718,16 @@ size_t ZSTD_decodeSeqHeaders(ZSTD_DCtx* dctx, int* nbSeqPtr, } *nbSeqPtr = nbSeq; + if (nbSeq == 0) { + /* No sequence : section ends immediately */ + RETURN_ERROR_IF(ip != iend, corruption_detected, + "extraneous data present in the Sequences section"); + return (size_t)(ip - istart); + } + /* FSE table descriptors */ RETURN_ERROR_IF(ip+1 > iend, srcSize_wrong, ""); /* minimum possible size: 1 byte for symbol encoding types */ + RETURN_ERROR_IF(*ip & 3, corruption_detected, ""); /* The last field, Reserved, must be all-zeroes. */ { symbolEncodingType_e const LLtype = (symbolEncodingType_e)(*ip >> 6); symbolEncodingType_e const OFtype = (symbolEncodingType_e)((*ip >> 4) & 3); symbolEncodingType_e const MLtype = (symbolEncodingType_e)((*ip >> 2) & 3); @@ -840,7 +874,7 @@ static void ZSTD_safecopy(BYTE* op, const BYTE* const oend_w, BYTE const* ip, pt /* ZSTD_safecopyDstBeforeSrc(): * This version allows overlap with dst before src, or handles the non-overlap case with dst after src * Kept separate from more common ZSTD_safecopy case to avoid performance impact to the safecopy common case */ -static void ZSTD_safecopyDstBeforeSrc(BYTE* op, BYTE const* ip, ptrdiff_t length) { +static void ZSTD_safecopyDstBeforeSrc(BYTE* op, const BYTE* ip, ptrdiff_t length) { ptrdiff_t const diff = op - ip; BYTE* const oend = op + length; @@ -869,6 +903,7 @@ static void ZSTD_safecopyDstBeforeSrc(BYTE* op, BYTE const* ip, ptrdiff_t length * to be optimized for many small sequences, since those fall into ZSTD_execSequence(). */ FORCE_NOINLINE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR size_t ZSTD_execSequenceEnd(BYTE* op, BYTE* const oend, seq_t sequence, const BYTE** litPtr, const BYTE* const litLimit, @@ -916,6 +951,7 @@ size_t ZSTD_execSequenceEnd(BYTE* op, * This version is intended to be used during instances where the litBuffer is still split. It is kept separate to avoid performance impact for the good case. */ FORCE_NOINLINE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR size_t ZSTD_execSequenceEndSplitLitBuffer(BYTE* op, BYTE* const oend, const BYTE* const oend_w, seq_t sequence, const BYTE** litPtr, const BYTE* const litLimit, @@ -961,6 +997,7 @@ size_t ZSTD_execSequenceEndSplitLitBuffer(BYTE* op, } HINT_INLINE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR size_t ZSTD_execSequence(BYTE* op, BYTE* const oend, seq_t sequence, const BYTE** litPtr, const BYTE* const litLimit, @@ -1059,6 +1096,7 @@ size_t ZSTD_execSequence(BYTE* op, } HINT_INLINE +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR size_t ZSTD_execSequenceSplitLitBuffer(BYTE* op, BYTE* const oend, const BYTE* const oend_w, seq_t sequence, const BYTE** litPtr, const BYTE* const litLimit, @@ -1181,14 +1219,20 @@ ZSTD_updateFseStateWithDInfo(ZSTD_fseState* DStatePtr, BIT_DStream_t* bitD, U16 typedef enum { ZSTD_lo_isRegularOffset, ZSTD_lo_isLongOffset=1 } ZSTD_longOffset_e; +/** + * ZSTD_decodeSequence(): + * @p longOffsets : tells the decoder to reload more bit while decoding large offsets + * only used in 32-bit mode + * @return : Sequence (litL + matchL + offset) + */ FORCE_INLINE_TEMPLATE seq_t -ZSTD_decodeSequence(seqState_t* seqState, const ZSTD_longOffset_e longOffsets) +ZSTD_decodeSequence(seqState_t* seqState, const ZSTD_longOffset_e longOffsets, const int isLastSeq) { seq_t seq; /* - * ZSTD_seqSymbol is a structure with a total of 64 bits wide. So it can be - * loaded in one operation and extracted its fields by simply shifting or - * bit-extracting on aarch64. + * ZSTD_seqSymbol is a 64 bits wide structure. + * It can be loaded in one operation + * and its fields extracted by simply shifting or bit-extracting on aarch64. * GCC doesn't recognize this and generates more unnecessary ldr/ldrb/ldrh * operations that cause performance drop. This can be avoided by using this * ZSTD_memcpy hack. @@ -1261,7 +1305,7 @@ ZSTD_decodeSequence(seqState_t* seqState, const ZSTD_longOffset_e longOffsets) } else { offset = ofBase + ll0 + BIT_readBitsFast(&seqState->DStream, 1); { size_t temp = (offset==3) ? seqState->prevOffset[0] - 1 : seqState->prevOffset[offset]; - temp += !temp; /* 0 is not valid; input is corrupted; force offset to 1 */ + temp -= !temp; /* 0 is not valid: input corrupted => force offset to -1 => corruption detected at execSequence */ if (offset != 1) seqState->prevOffset[2] = seqState->prevOffset[1]; seqState->prevOffset[1] = seqState->prevOffset[0]; seqState->prevOffset[0] = offset = temp; @@ -1288,17 +1332,22 @@ ZSTD_decodeSequence(seqState_t* seqState, const ZSTD_longOffset_e longOffsets) DEBUGLOG(6, "seq: litL=%u, matchL=%u, offset=%u", (U32)seq.litLength, (U32)seq.matchLength, (U32)seq.offset); - ZSTD_updateFseStateWithDInfo(&seqState->stateLL, &seqState->DStream, llNext, llnbBits); /* <= 9 bits */ - ZSTD_updateFseStateWithDInfo(&seqState->stateML, &seqState->DStream, mlNext, mlnbBits); /* <= 9 bits */ - if (MEM_32bits()) BIT_reloadDStream(&seqState->DStream); /* <= 18 bits */ - ZSTD_updateFseStateWithDInfo(&seqState->stateOffb, &seqState->DStream, ofNext, ofnbBits); /* <= 8 bits */ + if (!isLastSeq) { + /* don't update FSE state for last Sequence */ + ZSTD_updateFseStateWithDInfo(&seqState->stateLL, &seqState->DStream, llNext, llnbBits); /* <= 9 bits */ + ZSTD_updateFseStateWithDInfo(&seqState->stateML, &seqState->DStream, mlNext, mlnbBits); /* <= 9 bits */ + if (MEM_32bits()) BIT_reloadDStream(&seqState->DStream); /* <= 18 bits */ + ZSTD_updateFseStateWithDInfo(&seqState->stateOffb, &seqState->DStream, ofNext, ofnbBits); /* <= 8 bits */ + BIT_reloadDStream(&seqState->DStream); + } } return seq; } -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -MEM_STATIC int ZSTD_dictionaryIsActive(ZSTD_DCtx const* dctx, BYTE const* prefixStart, BYTE const* oLitEnd) +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) +#if DEBUGLEVEL >= 1 +static int ZSTD_dictionaryIsActive(ZSTD_DCtx const* dctx, BYTE const* prefixStart, BYTE const* oLitEnd) { size_t const windowSize = dctx->fParams.windowSize; /* No dictionary used. */ @@ -1312,30 +1361,33 @@ MEM_STATIC int ZSTD_dictionaryIsActive(ZSTD_DCtx const* dctx, BYTE const* prefix /* Dictionary is active. */ return 1; } +#endif -MEM_STATIC void ZSTD_assertValidSequence( +static void ZSTD_assertValidSequence( ZSTD_DCtx const* dctx, BYTE const* op, BYTE const* oend, seq_t const seq, BYTE const* prefixStart, BYTE const* virtualStart) { #if DEBUGLEVEL >= 1 - size_t const windowSize = dctx->fParams.windowSize; - size_t const sequenceSize = seq.litLength + seq.matchLength; - BYTE const* const oLitEnd = op + seq.litLength; - DEBUGLOG(6, "Checking sequence: litL=%u matchL=%u offset=%u", - (U32)seq.litLength, (U32)seq.matchLength, (U32)seq.offset); - assert(op <= oend); - assert((size_t)(oend - op) >= sequenceSize); - assert(sequenceSize <= ZSTD_BLOCKSIZE_MAX); - if (ZSTD_dictionaryIsActive(dctx, prefixStart, oLitEnd)) { - size_t const dictSize = (size_t)((char const*)dctx->dictContentEndForFuzzing - (char const*)dctx->dictContentBeginForFuzzing); - /* Offset must be within the dictionary. */ - assert(seq.offset <= (size_t)(oLitEnd - virtualStart)); - assert(seq.offset <= windowSize + dictSize); - } else { - /* Offset must be within our window. */ - assert(seq.offset <= windowSize); + if (dctx->isFrameDecompression) { + size_t const windowSize = dctx->fParams.windowSize; + size_t const sequenceSize = seq.litLength + seq.matchLength; + BYTE const* const oLitEnd = op + seq.litLength; + DEBUGLOG(6, "Checking sequence: litL=%u matchL=%u offset=%u", + (U32)seq.litLength, (U32)seq.matchLength, (U32)seq.offset); + assert(op <= oend); + assert((size_t)(oend - op) >= sequenceSize); + assert(sequenceSize <= ZSTD_blockSizeMax(dctx)); + if (ZSTD_dictionaryIsActive(dctx, prefixStart, oLitEnd)) { + size_t const dictSize = (size_t)((char const*)dctx->dictContentEndForFuzzing - (char const*)dctx->dictContentBeginForFuzzing); + /* Offset must be within the dictionary. */ + assert(seq.offset <= (size_t)(oLitEnd - virtualStart)); + assert(seq.offset <= windowSize + dictSize); + } else { + /* Offset must be within our window. */ + assert(seq.offset <= windowSize); + } } #else (void)dctx, (void)op, (void)oend, (void)seq, (void)prefixStart, (void)virtualStart; @@ -1351,23 +1403,21 @@ DONT_VECTORIZE ZSTD_decompressSequences_bodySplitLitBuffer( ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { const BYTE* ip = (const BYTE*)seqStart; const BYTE* const iend = ip + seqSize; BYTE* const ostart = (BYTE*)dst; - BYTE* const oend = ostart + maxDstSize; + BYTE* const oend = ZSTD_maybeNullPtrAdd(ostart, maxDstSize); BYTE* op = ostart; const BYTE* litPtr = dctx->litPtr; const BYTE* litBufferEnd = dctx->litBufferEnd; const BYTE* const prefixStart = (const BYTE*) (dctx->prefixStart); const BYTE* const vBase = (const BYTE*) (dctx->virtualStart); const BYTE* const dictEnd = (const BYTE*) (dctx->dictEnd); - DEBUGLOG(5, "ZSTD_decompressSequences_bodySplitLitBuffer"); - (void)frame; + DEBUGLOG(5, "ZSTD_decompressSequences_bodySplitLitBuffer (%i seqs)", nbSeq); - /* Regen sequences */ + /* Literals are split between internal buffer & output buffer */ if (nbSeq) { seqState_t seqState; dctx->fseEntropy = 1; @@ -1386,8 +1436,7 @@ ZSTD_decompressSequences_bodySplitLitBuffer( ZSTD_DCtx* dctx, BIT_DStream_completed < BIT_DStream_overflow); /* decompress without overrunning litPtr begins */ - { - seq_t sequence = ZSTD_decodeSequence(&seqState, isLongOffset); + { seq_t sequence = {0,0,0}; /* some static analyzer believe that @sequence is not initialized (it necessarily is, since for(;;) loop as at least one iteration) */ /* Align the decompression loop to 32 + 16 bytes. * * zstd compiled with gcc-9 on an Intel i9-9900k shows 10% decompression @@ -1449,27 +1498,26 @@ ZSTD_decompressSequences_bodySplitLitBuffer( ZSTD_DCtx* dctx, #endif /* Handle the initial state where litBuffer is currently split between dst and litExtraBuffer */ - for (; litPtr + sequence.litLength <= dctx->litBufferEnd; ) { - size_t const oneSeqSize = ZSTD_execSequenceSplitLitBuffer(op, oend, litPtr + sequence.litLength - WILDCOPY_OVERLENGTH, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd); + for ( ; nbSeq; nbSeq--) { + sequence = ZSTD_decodeSequence(&seqState, isLongOffset, nbSeq==1); + if (litPtr + sequence.litLength > dctx->litBufferEnd) break; + { size_t const oneSeqSize = ZSTD_execSequenceSplitLitBuffer(op, oend, litPtr + sequence.litLength - WILDCOPY_OVERLENGTH, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) - assert(!ZSTD_isError(oneSeqSize)); - if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); + assert(!ZSTD_isError(oneSeqSize)); + ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); #endif - if (UNLIKELY(ZSTD_isError(oneSeqSize))) - return oneSeqSize; - DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize); - op += oneSeqSize; - if (UNLIKELY(!--nbSeq)) - break; - BIT_reloadDStream(&(seqState.DStream)); - sequence = ZSTD_decodeSequence(&seqState, isLongOffset); - } + if (UNLIKELY(ZSTD_isError(oneSeqSize))) + return oneSeqSize; + DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize); + op += oneSeqSize; + } } + DEBUGLOG(6, "reached: (litPtr + sequence.litLength > dctx->litBufferEnd)"); /* If there are more sequences, they will need to read literals from litExtraBuffer; copy over the remainder from dst and update litPtr and litEnd */ if (nbSeq > 0) { const size_t leftoverLit = dctx->litBufferEnd - litPtr; - if (leftoverLit) - { + DEBUGLOG(6, "There are %i sequences left, and %zu/%zu literals left in buffer", nbSeq, leftoverLit, sequence.litLength); + if (leftoverLit) { RETURN_ERROR_IF(leftoverLit > (size_t)(oend - op), dstSize_tooSmall, "remaining lit must fit within dstBuffer"); ZSTD_safecopyDstBeforeSrc(op, litPtr, leftoverLit); sequence.litLength -= leftoverLit; @@ -1478,24 +1526,22 @@ ZSTD_decompressSequences_bodySplitLitBuffer( ZSTD_DCtx* dctx, litPtr = dctx->litExtraBuffer; litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE; dctx->litBufferLocation = ZSTD_not_in_dst; - { - size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd); + { size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) assert(!ZSTD_isError(oneSeqSize)); - if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); + ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); #endif if (UNLIKELY(ZSTD_isError(oneSeqSize))) return oneSeqSize; DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize); op += oneSeqSize; - if (--nbSeq) - BIT_reloadDStream(&(seqState.DStream)); } + nbSeq--; } } - if (nbSeq > 0) /* there is remaining lit from extra buffer */ - { + if (nbSeq > 0) { + /* there is remaining lit from extra buffer */ #if defined(__GNUC__) && defined(__x86_64__) __asm__(".p2align 6"); @@ -1514,35 +1560,34 @@ ZSTD_decompressSequences_bodySplitLitBuffer( ZSTD_DCtx* dctx, # endif #endif - for (; ; ) { - seq_t const sequence = ZSTD_decodeSequence(&seqState, isLongOffset); + for ( ; nbSeq ; nbSeq--) { + seq_t const sequence = ZSTD_decodeSequence(&seqState, isLongOffset, nbSeq==1); size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) assert(!ZSTD_isError(oneSeqSize)); - if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); + ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); #endif if (UNLIKELY(ZSTD_isError(oneSeqSize))) return oneSeqSize; DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize); op += oneSeqSize; - if (UNLIKELY(!--nbSeq)) - break; - BIT_reloadDStream(&(seqState.DStream)); } } /* check if reached exact end */ DEBUGLOG(5, "ZSTD_decompressSequences_bodySplitLitBuffer: after decode loop, remaining nbSeq : %i", nbSeq); RETURN_ERROR_IF(nbSeq, corruption_detected, ""); - RETURN_ERROR_IF(BIT_reloadDStream(&seqState.DStream) < BIT_DStream_completed, corruption_detected, ""); + DEBUGLOG(5, "bitStream : start=%p, ptr=%p, bitsConsumed=%u", seqState.DStream.start, seqState.DStream.ptr, seqState.DStream.bitsConsumed); + RETURN_ERROR_IF(!BIT_endOfDStream(&seqState.DStream), corruption_detected, ""); /* save reps for next block */ { U32 i; for (i=0; ientropy.rep[i] = (U32)(seqState.prevOffset[i]); } } /* last literal segment */ - if (dctx->litBufferLocation == ZSTD_split) /* split hasn't been reached yet, first get dst then copy litExtraBuffer */ - { - size_t const lastLLSize = litBufferEnd - litPtr; + if (dctx->litBufferLocation == ZSTD_split) { + /* split hasn't been reached yet, first get dst then copy litExtraBuffer */ + size_t const lastLLSize = (size_t)(litBufferEnd - litPtr); + DEBUGLOG(6, "copy last literals from segment : %u", (U32)lastLLSize); RETURN_ERROR_IF(lastLLSize > (size_t)(oend - op), dstSize_tooSmall, ""); if (op != NULL) { ZSTD_memmove(op, litPtr, lastLLSize); @@ -1552,15 +1597,17 @@ ZSTD_decompressSequences_bodySplitLitBuffer( ZSTD_DCtx* dctx, litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE; dctx->litBufferLocation = ZSTD_not_in_dst; } - { size_t const lastLLSize = litBufferEnd - litPtr; + /* copy last literals from internal buffer */ + { size_t const lastLLSize = (size_t)(litBufferEnd - litPtr); + DEBUGLOG(6, "copy last literals from internal buffer : %u", (U32)lastLLSize); RETURN_ERROR_IF(lastLLSize > (size_t)(oend-op), dstSize_tooSmall, ""); if (op != NULL) { ZSTD_memcpy(op, litPtr, lastLLSize); op += lastLLSize; - } - } + } } - return op-ostart; + DEBUGLOG(6, "decoded block of size %u bytes", (U32)(op - ostart)); + return (size_t)(op - ostart); } FORCE_INLINE_TEMPLATE size_t @@ -1568,13 +1615,12 @@ DONT_VECTORIZE ZSTD_decompressSequences_body(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { const BYTE* ip = (const BYTE*)seqStart; const BYTE* const iend = ip + seqSize; BYTE* const ostart = (BYTE*)dst; - BYTE* const oend = dctx->litBufferLocation == ZSTD_not_in_dst ? ostart + maxDstSize : dctx->litBuffer; + BYTE* const oend = dctx->litBufferLocation == ZSTD_not_in_dst ? ZSTD_maybeNullPtrAdd(ostart, maxDstSize) : dctx->litBuffer; BYTE* op = ostart; const BYTE* litPtr = dctx->litPtr; const BYTE* const litEnd = litPtr + dctx->litSize; @@ -1582,7 +1628,6 @@ ZSTD_decompressSequences_body(ZSTD_DCtx* dctx, const BYTE* const vBase = (const BYTE*)(dctx->virtualStart); const BYTE* const dictEnd = (const BYTE*)(dctx->dictEnd); DEBUGLOG(5, "ZSTD_decompressSequences_body: nbSeq = %d", nbSeq); - (void)frame; /* Regen sequences */ if (nbSeq) { @@ -1597,11 +1642,6 @@ ZSTD_decompressSequences_body(ZSTD_DCtx* dctx, ZSTD_initFseState(&seqState.stateML, &seqState.DStream, dctx->MLTptr); assert(dst != NULL); - ZSTD_STATIC_ASSERT( - BIT_DStream_unfinished < BIT_DStream_completed && - BIT_DStream_endOfBuffer < BIT_DStream_completed && - BIT_DStream_completed < BIT_DStream_overflow); - #if defined(__GNUC__) && defined(__x86_64__) __asm__(".p2align 6"); __asm__("nop"); @@ -1616,73 +1656,70 @@ ZSTD_decompressSequences_body(ZSTD_DCtx* dctx, # endif #endif - for ( ; ; ) { - seq_t const sequence = ZSTD_decodeSequence(&seqState, isLongOffset); + for ( ; nbSeq ; nbSeq--) { + seq_t const sequence = ZSTD_decodeSequence(&seqState, isLongOffset, nbSeq==1); size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litEnd, prefixStart, vBase, dictEnd); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) assert(!ZSTD_isError(oneSeqSize)); - if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); + ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); #endif if (UNLIKELY(ZSTD_isError(oneSeqSize))) return oneSeqSize; DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize); op += oneSeqSize; - if (UNLIKELY(!--nbSeq)) - break; - BIT_reloadDStream(&(seqState.DStream)); } /* check if reached exact end */ - DEBUGLOG(5, "ZSTD_decompressSequences_body: after decode loop, remaining nbSeq : %i", nbSeq); - RETURN_ERROR_IF(nbSeq, corruption_detected, ""); - RETURN_ERROR_IF(BIT_reloadDStream(&seqState.DStream) < BIT_DStream_completed, corruption_detected, ""); + assert(nbSeq == 0); + RETURN_ERROR_IF(!BIT_endOfDStream(&seqState.DStream), corruption_detected, ""); /* save reps for next block */ { U32 i; for (i=0; ientropy.rep[i] = (U32)(seqState.prevOffset[i]); } } /* last literal segment */ - { size_t const lastLLSize = litEnd - litPtr; + { size_t const lastLLSize = (size_t)(litEnd - litPtr); + DEBUGLOG(6, "copy last literals : %u", (U32)lastLLSize); RETURN_ERROR_IF(lastLLSize > (size_t)(oend-op), dstSize_tooSmall, ""); if (op != NULL) { ZSTD_memcpy(op, litPtr, lastLLSize); op += lastLLSize; - } - } + } } - return op-ostart; + DEBUGLOG(6, "decoded block of size %u bytes", (U32)(op - ostart)); + return (size_t)(op - ostart); } static size_t ZSTD_decompressSequences_default(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { - return ZSTD_decompressSequences_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequences_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } static size_t ZSTD_decompressSequencesSplitLitBuffer_default(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { - return ZSTD_decompressSequences_bodySplitLitBuffer(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequences_bodySplitLitBuffer(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */ #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT -FORCE_INLINE_TEMPLATE size_t -ZSTD_prefetchMatch(size_t prefetchPos, seq_t const sequence, +FORCE_INLINE_TEMPLATE + +size_t ZSTD_prefetchMatch(size_t prefetchPos, seq_t const sequence, const BYTE* const prefixStart, const BYTE* const dictEnd) { prefetchPos += sequence.litLength; { const BYTE* const matchBase = (sequence.offset > prefetchPos) ? dictEnd : prefixStart; - const BYTE* const match = matchBase + prefetchPos - sequence.offset; /* note : this operation can overflow when seq.offset is really too large, which can only happen when input is corrupted. - * No consequence though : memory address is only used for prefetching, not for dereferencing */ + /* note : this operation can overflow when seq.offset is really too large, which can only happen when input is corrupted. + * No consequence though : memory address is only used for prefetching, not for dereferencing */ + const BYTE* const match = ZSTD_wrappedPtrSub(ZSTD_wrappedPtrAdd(matchBase, prefetchPos), sequence.offset); PREFETCH_L1(match); PREFETCH_L1(match+CACHELINE_SIZE); /* note : it's safe to invoke PREFETCH() on any memory address, including invalid ones */ } return prefetchPos + sequence.matchLength; @@ -1697,20 +1734,18 @@ ZSTD_decompressSequencesLong_body( ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { const BYTE* ip = (const BYTE*)seqStart; const BYTE* const iend = ip + seqSize; BYTE* const ostart = (BYTE*)dst; - BYTE* const oend = dctx->litBufferLocation == ZSTD_in_dst ? dctx->litBuffer : ostart + maxDstSize; + BYTE* const oend = dctx->litBufferLocation == ZSTD_in_dst ? dctx->litBuffer : ZSTD_maybeNullPtrAdd(ostart, maxDstSize); BYTE* op = ostart; const BYTE* litPtr = dctx->litPtr; const BYTE* litBufferEnd = dctx->litBufferEnd; const BYTE* const prefixStart = (const BYTE*) (dctx->prefixStart); const BYTE* const dictStart = (const BYTE*) (dctx->virtualStart); const BYTE* const dictEnd = (const BYTE*) (dctx->dictEnd); - (void)frame; /* Regen sequences */ if (nbSeq) { @@ -1735,20 +1770,17 @@ ZSTD_decompressSequencesLong_body( ZSTD_initFseState(&seqState.stateML, &seqState.DStream, dctx->MLTptr); /* prepare in advance */ - for (seqNb=0; (BIT_reloadDStream(&seqState.DStream) <= BIT_DStream_completed) && (seqNblitBufferLocation == ZSTD_split && litPtr + sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK].litLength > dctx->litBufferEnd) - { + if (dctx->litBufferLocation == ZSTD_split && litPtr + sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK].litLength > dctx->litBufferEnd) { /* lit buffer is reaching split point, empty out the first buffer and transition to litExtraBuffer */ const size_t leftoverLit = dctx->litBufferEnd - litPtr; if (leftoverLit) @@ -1761,26 +1793,26 @@ ZSTD_decompressSequencesLong_body( litPtr = dctx->litExtraBuffer; litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE; dctx->litBufferLocation = ZSTD_not_in_dst; - oneSeqSize = ZSTD_execSequence(op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd); + { size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) - assert(!ZSTD_isError(oneSeqSize)); - if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], prefixStart, dictStart); + assert(!ZSTD_isError(oneSeqSize)); + ZSTD_assertValidSequence(dctx, op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], prefixStart, dictStart); #endif - if (ZSTD_isError(oneSeqSize)) return oneSeqSize; + if (ZSTD_isError(oneSeqSize)) return oneSeqSize; - prefetchPos = ZSTD_prefetchMatch(prefetchPos, sequence, prefixStart, dictEnd); - sequences[seqNb & STORED_SEQS_MASK] = sequence; - op += oneSeqSize; - } + prefetchPos = ZSTD_prefetchMatch(prefetchPos, sequence, prefixStart, dictEnd); + sequences[seqNb & STORED_SEQS_MASK] = sequence; + op += oneSeqSize; + } } else { /* lit buffer is either wholly contained in first or second split, or not split at all*/ - oneSeqSize = dctx->litBufferLocation == ZSTD_split ? + size_t const oneSeqSize = dctx->litBufferLocation == ZSTD_split ? ZSTD_execSequenceSplitLitBuffer(op, oend, litPtr + sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK].litLength - WILDCOPY_OVERLENGTH, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd) : ZSTD_execSequence(op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) assert(!ZSTD_isError(oneSeqSize)); - if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], prefixStart, dictStart); + ZSTD_assertValidSequence(dctx, op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], prefixStart, dictStart); #endif if (ZSTD_isError(oneSeqSize)) return oneSeqSize; @@ -1789,17 +1821,15 @@ ZSTD_decompressSequencesLong_body( op += oneSeqSize; } } - RETURN_ERROR_IF(seqNblitBufferLocation == ZSTD_split && litPtr + sequence->litLength > dctx->litBufferEnd) - { + if (dctx->litBufferLocation == ZSTD_split && litPtr + sequence->litLength > dctx->litBufferEnd) { const size_t leftoverLit = dctx->litBufferEnd - litPtr; - if (leftoverLit) - { + if (leftoverLit) { RETURN_ERROR_IF(leftoverLit > (size_t)(oend - op), dstSize_tooSmall, "remaining lit must fit within dstBuffer"); ZSTD_safecopyDstBeforeSrc(op, litPtr, leftoverLit); sequence->litLength -= leftoverLit; @@ -1808,11 +1838,10 @@ ZSTD_decompressSequencesLong_body( litPtr = dctx->litExtraBuffer; litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE; dctx->litBufferLocation = ZSTD_not_in_dst; - { - size_t const oneSeqSize = ZSTD_execSequence(op, oend, *sequence, &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd); + { size_t const oneSeqSize = ZSTD_execSequence(op, oend, *sequence, &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) assert(!ZSTD_isError(oneSeqSize)); - if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequences[seqNb&STORED_SEQS_MASK], prefixStart, dictStart); + ZSTD_assertValidSequence(dctx, op, oend, sequences[seqNb&STORED_SEQS_MASK], prefixStart, dictStart); #endif if (ZSTD_isError(oneSeqSize)) return oneSeqSize; op += oneSeqSize; @@ -1825,7 +1854,7 @@ ZSTD_decompressSequencesLong_body( ZSTD_execSequence(op, oend, *sequence, &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) assert(!ZSTD_isError(oneSeqSize)); - if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequences[seqNb&STORED_SEQS_MASK], prefixStart, dictStart); + ZSTD_assertValidSequence(dctx, op, oend, sequences[seqNb&STORED_SEQS_MASK], prefixStart, dictStart); #endif if (ZSTD_isError(oneSeqSize)) return oneSeqSize; op += oneSeqSize; @@ -1837,8 +1866,7 @@ ZSTD_decompressSequencesLong_body( } /* last literal segment */ - if (dctx->litBufferLocation == ZSTD_split) /* first deplete literal buffer in dst, then copy litExtraBuffer */ - { + if (dctx->litBufferLocation == ZSTD_split) { /* first deplete literal buffer in dst, then copy litExtraBuffer */ size_t const lastLLSize = litBufferEnd - litPtr; RETURN_ERROR_IF(lastLLSize > (size_t)(oend - op), dstSize_tooSmall, ""); if (op != NULL) { @@ -1856,17 +1884,16 @@ ZSTD_decompressSequencesLong_body( } } - return op-ostart; + return (size_t)(op - ostart); } static size_t ZSTD_decompressSequencesLong_default(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { - return ZSTD_decompressSequencesLong_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequencesLong_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ @@ -1880,20 +1907,18 @@ DONT_VECTORIZE ZSTD_decompressSequences_bmi2(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { - return ZSTD_decompressSequences_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequences_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } static BMI2_TARGET_ATTRIBUTE size_t DONT_VECTORIZE ZSTD_decompressSequencesSplitLitBuffer_bmi2(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { - return ZSTD_decompressSequences_bodySplitLitBuffer(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequences_bodySplitLitBuffer(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */ @@ -1902,10 +1927,9 @@ static BMI2_TARGET_ATTRIBUTE size_t ZSTD_decompressSequencesLong_bmi2(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { - return ZSTD_decompressSequencesLong_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequencesLong_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ @@ -1915,37 +1939,34 @@ typedef size_t (*ZSTD_decompressSequences_t)( ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame); + const ZSTD_longOffset_e isLongOffset); #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG static size_t ZSTD_decompressSequences(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { DEBUGLOG(5, "ZSTD_decompressSequences"); #if DYNAMIC_BMI2 if (ZSTD_DCtx_get_bmi2(dctx)) { - return ZSTD_decompressSequences_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequences_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } #endif - return ZSTD_decompressSequences_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequences_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } static size_t ZSTD_decompressSequencesSplitLitBuffer(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { DEBUGLOG(5, "ZSTD_decompressSequencesSplitLitBuffer"); #if DYNAMIC_BMI2 if (ZSTD_DCtx_get_bmi2(dctx)) { - return ZSTD_decompressSequencesSplitLitBuffer_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequencesSplitLitBuffer_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } #endif - return ZSTD_decompressSequencesSplitLitBuffer_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequencesSplitLitBuffer_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */ @@ -1960,16 +1981,15 @@ static size_t ZSTD_decompressSequencesLong(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, - const ZSTD_longOffset_e isLongOffset, - const int frame) + const ZSTD_longOffset_e isLongOffset) { DEBUGLOG(5, "ZSTD_decompressSequencesLong"); #if DYNAMIC_BMI2 if (ZSTD_DCtx_get_bmi2(dctx)) { - return ZSTD_decompressSequencesLong_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequencesLong_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } #endif - return ZSTD_decompressSequencesLong_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequencesLong_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); } #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ @@ -2051,20 +2071,20 @@ static size_t ZSTD_maxShortOffset(void) size_t ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, - const void* src, size_t srcSize, const int frame, const streaming_operation streaming) + const void* src, size_t srcSize, const streaming_operation streaming) { /* blockType == blockCompressed */ const BYTE* ip = (const BYTE*)src; - DEBUGLOG(5, "ZSTD_decompressBlock_internal (size : %u)", (U32)srcSize); + DEBUGLOG(5, "ZSTD_decompressBlock_internal (cSize : %u)", (unsigned)srcSize); /* Note : the wording of the specification - * allows compressed block to be sized exactly ZSTD_BLOCKSIZE_MAX. + * allows compressed block to be sized exactly ZSTD_blockSizeMax(dctx). * This generally does not happen, as it makes little sense, * since an uncompressed block would feature same size and have no decompression cost. * Also, note that decoder from reference libzstd before < v1.5.4 * would consider this edge case as an error. - * As a consequence, avoid generating compressed blocks of size ZSTD_BLOCKSIZE_MAX + * As a consequence, avoid generating compressed blocks of size ZSTD_blockSizeMax(dctx) * for broader compatibility with the deployed ecosystem of zstd decoders */ - RETURN_ERROR_IF(srcSize > ZSTD_BLOCKSIZE_MAX, srcSize_wrong, ""); + RETURN_ERROR_IF(srcSize > ZSTD_blockSizeMax(dctx), srcSize_wrong, ""); /* Decode literals section */ { size_t const litCSize = ZSTD_decodeLiteralsBlock(dctx, src, srcSize, dst, dstCapacity, streaming); @@ -2079,8 +2099,8 @@ ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, /* Compute the maximum block size, which must also work when !frame and fParams are unset. * Additionally, take the min with dstCapacity to ensure that the totalHistorySize fits in a size_t. */ - size_t const blockSizeMax = MIN(dstCapacity, (frame ? dctx->fParams.blockSizeMax : ZSTD_BLOCKSIZE_MAX)); - size_t const totalHistorySize = ZSTD_totalHistorySize((BYTE*)dst + blockSizeMax, (BYTE const*)dctx->virtualStart); + size_t const blockSizeMax = MIN(dstCapacity, ZSTD_blockSizeMax(dctx)); + size_t const totalHistorySize = ZSTD_totalHistorySize(ZSTD_maybeNullPtrAdd((BYTE*)dst, blockSizeMax), (BYTE const*)dctx->virtualStart); /* isLongOffset must be true if there are long offsets. * Offsets are long if they are larger than ZSTD_maxShortOffset(). * We don't expect that to be the case in 64-bit mode. @@ -2145,21 +2165,22 @@ ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, { #endif #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT - return ZSTD_decompressSequencesLong(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequencesLong(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset); #endif } #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG /* else */ if (dctx->litBufferLocation == ZSTD_split) - return ZSTD_decompressSequencesSplitLitBuffer(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequencesSplitLitBuffer(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset); else - return ZSTD_decompressSequences(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequences(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset); #endif } } +ZSTD_ALLOW_POINTER_OVERFLOW_ATTR void ZSTD_checkContinuity(ZSTD_DCtx* dctx, const void* dst, size_t dstSize) { if (dst != dctx->previousDstEnd && dstSize > 0) { /* not contiguous */ @@ -2176,8 +2197,10 @@ size_t ZSTD_decompressBlock_deprecated(ZSTD_DCtx* dctx, const void* src, size_t srcSize) { size_t dSize; + dctx->isFrameDecompression = 0; ZSTD_checkContinuity(dctx, dst, dstCapacity); - dSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, /* frame */ 0, not_streaming); + dSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, not_streaming); + FORWARD_IF_ERROR(dSize, ""); dctx->previousDstEnd = (char*)dst + dSize; return dSize; } diff --git a/src/thirdparty/zstd/decompress/zstd_decompress_block.h b/src/thirdparty/zstd/decompress/zstd_decompress_block.h index 9d131888..ab152404 100644 --- a/src/thirdparty/zstd/decompress/zstd_decompress_block.h +++ b/src/thirdparty/zstd/decompress/zstd_decompress_block.h @@ -47,7 +47,7 @@ typedef enum { */ size_t ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, - const void* src, size_t srcSize, const int frame, const streaming_operation streaming); + const void* src, size_t srcSize, const streaming_operation streaming); /* ZSTD_buildFSETable() : * generate FSE decoding table for one symbol (ll, ml or off) diff --git a/src/thirdparty/zstd/decompress/zstd_decompress_internal.h b/src/thirdparty/zstd/decompress/zstd_decompress_internal.h index c2ec5d9f..83a7a011 100644 --- a/src/thirdparty/zstd/decompress/zstd_decompress_internal.h +++ b/src/thirdparty/zstd/decompress/zstd_decompress_internal.h @@ -153,6 +153,7 @@ struct ZSTD_DCtx_s size_t litSize; size_t rleSize; size_t staticSize; + int isFrameDecompression; #if DYNAMIC_BMI2 != 0 int bmi2; /* == 1 if the CPU supports BMI2 and 0 otherwise. CPU support is determined dynamically once per context lifetime. */ #endif @@ -166,6 +167,7 @@ struct ZSTD_DCtx_s ZSTD_DDictHashSet* ddictSet; /* Hash set for multiple ddicts */ ZSTD_refMultipleDDicts_e refMultipleDDicts; /* User specified: if == 1, will allow references to multiple DDicts. Default == 0 (disabled) */ int disableHufAsm; + int maxBlockSizeParam; /* streaming */ ZSTD_dStreamStage streamStage; diff --git a/src/thirdparty/zstd/dictBuilder/cover.c b/src/thirdparty/zstd/dictBuilder/cover.c index 9e5e7d5b..44f9029a 100644 --- a/src/thirdparty/zstd/dictBuilder/cover.c +++ b/src/thirdparty/zstd/dictBuilder/cover.c @@ -31,8 +31,8 @@ #endif #include "../common/mem.h" /* read */ -#include "../common/pool.h" -#include "../common/threading.h" +#include "../common/pool.h" /* POOL_ctx */ +#include "../common/threading.h" /* ZSTD_pthread_mutex_t */ #include "../common/zstd_internal.h" /* includes zstd.h */ #include "../common/bits.h" /* ZSTD_highbit32 */ #include "../zdict.h" @@ -78,7 +78,7 @@ static clock_t g_time = 0; #undef LOCALDISPLAYUPDATE #define LOCALDISPLAYUPDATE(displayLevel, l, ...) \ if (displayLevel >= l) { \ - if ((clock() - g_time > g_refreshRate) || (displayLevel >= 4)) { \ + if ((clock() - g_time > g_refreshRate) || (displayLevel >= 4)) { \ g_time = clock(); \ DISPLAY(__VA_ARGS__); \ } \ @@ -301,9 +301,10 @@ static int WIN_CDECL COVER_strict_cmp8(const void *lp, const void *rp) { * Returns the first pointer in [first, last) whose element does not compare * less than value. If no such element exists it returns last. */ -static const size_t *COVER_lower_bound(const size_t *first, const size_t *last, +static const size_t *COVER_lower_bound(const size_t* first, const size_t* last, size_t value) { - size_t count = last - first; + size_t count = (size_t)(last - first); + assert(last >= first); while (count != 0) { size_t step = count / 2; const size_t *ptr = first; @@ -549,7 +550,8 @@ static void COVER_ctx_destroy(COVER_ctx_t *ctx) { */ static size_t COVER_ctx_init(COVER_ctx_t *ctx, const void *samplesBuffer, const size_t *samplesSizes, unsigned nbSamples, - unsigned d, double splitPoint) { + unsigned d, double splitPoint) +{ const BYTE *const samples = (const BYTE *)samplesBuffer; const size_t totalSamplesSize = COVER_sum(samplesSizes, nbSamples); /* Split samples into testing and training sets */ @@ -733,7 +735,7 @@ static size_t COVER_buildDictionary(const COVER_ctx_t *ctx, U32 *freqs, return tail; } -ZDICTLIB_API size_t ZDICT_trainFromBuffer_cover( +ZDICTLIB_STATIC_API size_t ZDICT_trainFromBuffer_cover( void *dictBuffer, size_t dictBufferCapacity, const void *samplesBuffer, const size_t *samplesSizes, unsigned nbSamples, ZDICT_cover_params_t parameters) @@ -907,8 +909,10 @@ void COVER_best_start(COVER_best_t *best) { * Decrements liveJobs and signals any waiting threads if liveJobs == 0. * If this dictionary is the best so far save it and its parameters. */ -void COVER_best_finish(COVER_best_t *best, ZDICT_cover_params_t parameters, - COVER_dictSelection_t selection) { +void COVER_best_finish(COVER_best_t* best, + ZDICT_cover_params_t parameters, + COVER_dictSelection_t selection) +{ void* dict = selection.dictContent; size_t compressedSize = selection.totalCompressedSize; size_t dictSize = selection.dictSize; @@ -980,8 +984,8 @@ COVER_dictSelection_t COVER_selectDict(BYTE* customDictContent, size_t dictBuffe size_t largestCompressed = 0; BYTE* customDictContentEnd = customDictContent + dictContentSize; - BYTE * largestDictbuffer = (BYTE *)malloc(dictBufferCapacity); - BYTE * candidateDictBuffer = (BYTE *)malloc(dictBufferCapacity); + BYTE* largestDictbuffer = (BYTE*)malloc(dictBufferCapacity); + BYTE* candidateDictBuffer = (BYTE*)malloc(dictBufferCapacity); double regressionTolerance = ((double)params.shrinkDictMaxRegression / 100.0) + 1.00; if (!largestDictbuffer || !candidateDictBuffer) { @@ -1119,7 +1123,7 @@ _cleanup: free(freqs); } -ZDICTLIB_API size_t ZDICT_optimizeTrainFromBuffer_cover( +ZDICTLIB_STATIC_API size_t ZDICT_optimizeTrainFromBuffer_cover( void* dictBuffer, size_t dictBufferCapacity, const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples, ZDICT_cover_params_t* parameters) diff --git a/src/thirdparty/zstd/dictBuilder/cover.h b/src/thirdparty/zstd/dictBuilder/cover.h index 252624bd..a5d7506e 100644 --- a/src/thirdparty/zstd/dictBuilder/cover.h +++ b/src/thirdparty/zstd/dictBuilder/cover.h @@ -12,14 +12,8 @@ # define ZDICT_STATIC_LINKING_ONLY #endif -#include /* fprintf */ -#include /* malloc, free, qsort */ -#include /* memset */ -#include /* clock */ -#include "../common/mem.h" /* read */ -#include "../common/pool.h" -#include "../common/threading.h" -#include "../common/zstd_internal.h" /* includes zstd.h */ +#include "../common/threading.h" /* ZSTD_pthread_mutex_t */ +#include "../common/mem.h" /* U32, BYTE */ #include "../zdict.h" /** diff --git a/src/thirdparty/zstd/dictBuilder/fastcover.c b/src/thirdparty/zstd/dictBuilder/fastcover.c index 46bba012..a958eb33 100644 --- a/src/thirdparty/zstd/dictBuilder/fastcover.c +++ b/src/thirdparty/zstd/dictBuilder/fastcover.c @@ -545,7 +545,7 @@ FASTCOVER_convertToFastCoverParams(ZDICT_cover_params_t coverParams, } -ZDICTLIB_API size_t +ZDICTLIB_STATIC_API size_t ZDICT_trainFromBuffer_fastCover(void* dictBuffer, size_t dictBufferCapacity, const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples, @@ -614,7 +614,7 @@ ZDICT_trainFromBuffer_fastCover(void* dictBuffer, size_t dictBufferCapacity, } -ZDICTLIB_API size_t +ZDICTLIB_STATIC_API size_t ZDICT_optimizeTrainFromBuffer_fastCover( void* dictBuffer, size_t dictBufferCapacity, const void* samplesBuffer, diff --git a/src/thirdparty/zstd/dictBuilder/zdict.c b/src/thirdparty/zstd/dictBuilder/zdict.c index 58290f45..82e999e8 100644 --- a/src/thirdparty/zstd/dictBuilder/zdict.c +++ b/src/thirdparty/zstd/dictBuilder/zdict.c @@ -74,9 +74,9 @@ static const U32 g_selectivity_default = 9; * Console display ***************************************/ #undef DISPLAY -#define DISPLAY(...) { fprintf(stderr, __VA_ARGS__); fflush( stderr ); } +#define DISPLAY(...) do { fprintf(stderr, __VA_ARGS__); fflush( stderr ); } while (0) #undef DISPLAYLEVEL -#define DISPLAYLEVEL(l, ...) if (notificationLevel>=l) { DISPLAY(__VA_ARGS__); } /* 0 : no display; 1: errors; 2: default; 3: details; 4: debug */ +#define DISPLAYLEVEL(l, ...) do { if (notificationLevel>=l) { DISPLAY(__VA_ARGS__); } } while (0) /* 0 : no display; 1: errors; 2: default; 3: details; 4: debug */ static clock_t ZDICT_clockSpan(clock_t nPrevious) { return clock() - nPrevious; } @@ -477,10 +477,16 @@ static size_t ZDICT_trainBuffer_legacy(dictItem* dictList, U32 dictListSize, clock_t const refreshRate = CLOCKS_PER_SEC * 3 / 10; # undef DISPLAYUPDATE -# define DISPLAYUPDATE(l, ...) if (notificationLevel>=l) { \ - if (ZDICT_clockSpan(displayClock) > refreshRate) \ - { displayClock = clock(); DISPLAY(__VA_ARGS__); \ - if (notificationLevel>=4) fflush(stderr); } } +# define DISPLAYUPDATE(l, ...) \ + do { \ + if (notificationLevel>=l) { \ + if (ZDICT_clockSpan(displayClock) > refreshRate) { \ + displayClock = clock(); \ + DISPLAY(__VA_ARGS__); \ + } \ + if (notificationLevel>=4) fflush(stderr); \ + } \ + } while (0) /* init */ DISPLAYLEVEL(2, "\r%70s\r", ""); /* clean display line */ diff --git a/src/thirdparty/zstd/legacy/zstd_legacy.h b/src/thirdparty/zstd/legacy/zstd_legacy.h index dd173251..7a8a04e5 100644 --- a/src/thirdparty/zstd/legacy/zstd_legacy.h +++ b/src/thirdparty/zstd/legacy/zstd_legacy.h @@ -124,6 +124,20 @@ MEM_STATIC size_t ZSTD_decompressLegacy( const void* dict,size_t dictSize) { U32 const version = ZSTD_isLegacy(src, compressedSize); + char x; + /* Avoid passing NULL to legacy decoding. */ + if (dst == NULL) { + assert(dstCapacity == 0); + dst = &x; + } + if (src == NULL) { + assert(compressedSize == 0); + src = &x; + } + if (dict == NULL) { + assert(dictSize == 0); + dict = &x; + } (void)dst; (void)dstCapacity; (void)dict; (void)dictSize; /* unused when ZSTD_LEGACY_SUPPORT >= 8 */ switch(version) { @@ -287,6 +301,12 @@ MEM_STATIC size_t ZSTD_freeLegacyStreamContext(void* legacyContext, U32 version) MEM_STATIC size_t ZSTD_initLegacyStream(void** legacyContext, U32 prevVersion, U32 newVersion, const void* dict, size_t dictSize) { + char x; + /* Avoid passing NULL to legacy decoding. */ + if (dict == NULL) { + assert(dictSize == 0); + dict = &x; + } DEBUGLOG(5, "ZSTD_initLegacyStream for v0.%u", newVersion); if (prevVersion != newVersion) ZSTD_freeLegacyStreamContext(*legacyContext, prevVersion); switch(newVersion) @@ -346,6 +366,16 @@ MEM_STATIC size_t ZSTD_initLegacyStream(void** legacyContext, U32 prevVersion, U MEM_STATIC size_t ZSTD_decompressLegacyStream(void* legacyContext, U32 version, ZSTD_outBuffer* output, ZSTD_inBuffer* input) { + static char x; + /* Avoid passing NULL to legacy decoding. */ + if (output->dst == NULL) { + assert(output->size == 0); + output->dst = &x; + } + if (input->src == NULL) { + assert(input->size == 0); + input->src = &x; + } DEBUGLOG(5, "ZSTD_decompressLegacyStream for v0.%u", version); switch(version) { diff --git a/src/thirdparty/zstd/legacy/zstd_v01.c b/src/thirdparty/zstd/legacy/zstd_v01.c index 1a3aad07..6cf51234 100644 --- a/src/thirdparty/zstd/legacy/zstd_v01.c +++ b/src/thirdparty/zstd/legacy/zstd_v01.c @@ -14,6 +14,7 @@ ******************************************/ #include /* size_t, ptrdiff_t */ #include "zstd_v01.h" +#include "../common/compiler.h" #include "../common/error_private.h" @@ -2118,6 +2119,7 @@ size_t ZSTDv01_decompressContinue(ZSTDv01_Dctx* dctx, void* dst, size_t maxDstSi } ctx->phase = 1; ctx->expected = ZSTD_blockHeaderSize; + if (ZSTDv01_isError(rSize)) return rSize; ctx->previousDstEnd = (void*)( ((char*)dst) + rSize); return rSize; } diff --git a/src/thirdparty/zstd/legacy/zstd_v02.c b/src/thirdparty/zstd/legacy/zstd_v02.c index e09bb4a2..6d39b6e5 100644 --- a/src/thirdparty/zstd/legacy/zstd_v02.c +++ b/src/thirdparty/zstd/legacy/zstd_v02.c @@ -11,6 +11,7 @@ #include /* size_t, ptrdiff_t */ #include "zstd_v02.h" +#include "../common/compiler.h" #include "../common/error_private.h" @@ -71,20 +72,6 @@ extern "C" { #include /* memcpy */ -/****************************************** -* Compiler-specific -******************************************/ -#if defined(__GNUC__) -# define MEM_STATIC static __attribute__((unused)) -#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) -# define MEM_STATIC static inline -#elif defined(_MSC_VER) -# define MEM_STATIC static __inline -#else -# define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ -#endif - - /**************************************************************** * Basic Types *****************************************************************/ @@ -875,7 +862,7 @@ extern "C" { * Streaming functions ***************************************/ -typedef struct ZSTD_DCtx_s ZSTD_DCtx; +typedef struct ZSTDv02_Dctx_s ZSTD_DCtx; /* Use above functions alternatively. @@ -2750,7 +2737,7 @@ static unsigned ZSTD_isError(size_t code) { return ERR_isError(code); } /* ************************************************************* * Decompression section ***************************************************************/ -struct ZSTD_DCtx_s +struct ZSTDv02_Dctx_s { U32 LLTable[FSE_DTABLE_SIZE_U32(LLFSELog)]; U32 OffTable[FSE_DTABLE_SIZE_U32(OffFSELog)]; @@ -3431,6 +3418,7 @@ static size_t ZSTD_decompressContinue(ZSTD_DCtx* ctx, void* dst, size_t maxDstSi } ctx->phase = 1; ctx->expected = ZSTD_blockHeaderSize; + if (ZSTD_isError(rSize)) return rSize; ctx->previousDstEnd = (void*)( ((char*)dst) + rSize); return rSize; } diff --git a/src/thirdparty/zstd/legacy/zstd_v03.c b/src/thirdparty/zstd/legacy/zstd_v03.c index b0d7f521..47195f33 100644 --- a/src/thirdparty/zstd/legacy/zstd_v03.c +++ b/src/thirdparty/zstd/legacy/zstd_v03.c @@ -11,6 +11,7 @@ #include /* size_t, ptrdiff_t */ #include "zstd_v03.h" +#include "../common/compiler.h" #include "../common/error_private.h" @@ -72,20 +73,6 @@ extern "C" { #include /* memcpy */ -/****************************************** -* Compiler-specific -******************************************/ -#if defined(__GNUC__) -# define MEM_STATIC static __attribute__((unused)) -#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) -# define MEM_STATIC static inline -#elif defined(_MSC_VER) -# define MEM_STATIC static __inline -#else -# define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ -#endif - - /**************************************************************** * Basic Types *****************************************************************/ @@ -875,7 +862,7 @@ extern "C" { * Streaming functions ***************************************/ -typedef struct ZSTD_DCtx_s ZSTD_DCtx; +typedef struct ZSTDv03_Dctx_s ZSTD_DCtx; /* Use above functions alternatively. @@ -2390,7 +2377,7 @@ static unsigned ZSTD_isError(size_t code) { return ERR_isError(code); } /* ************************************************************* * Decompression section ***************************************************************/ -struct ZSTD_DCtx_s +struct ZSTDv03_Dctx_s { U32 LLTable[FSE_DTABLE_SIZE_U32(LLFSELog)]; U32 OffTable[FSE_DTABLE_SIZE_U32(OffFSELog)]; @@ -3071,6 +3058,7 @@ static size_t ZSTD_decompressContinue(ZSTD_DCtx* ctx, void* dst, size_t maxDstSi } ctx->phase = 1; ctx->expected = ZSTD_blockHeaderSize; + if (ZSTD_isError(rSize)) return rSize; ctx->previousDstEnd = (void*)( ((char*)dst) + rSize); return rSize; } diff --git a/src/thirdparty/zstd/legacy/zstd_v04.c b/src/thirdparty/zstd/legacy/zstd_v04.c index 57be832b..0da316c1 100644 --- a/src/thirdparty/zstd/legacy/zstd_v04.c +++ b/src/thirdparty/zstd/legacy/zstd_v04.c @@ -16,6 +16,7 @@ #include /* memcpy */ #include "zstd_v04.h" +#include "../common/compiler.h" #include "../common/error_private.h" @@ -37,15 +38,6 @@ extern "C" { # include /* _byteswap_ulong */ # include /* _byteswap_* */ #endif -#if defined(__GNUC__) -# define MEM_STATIC static __attribute__((unused)) -#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) -# define MEM_STATIC static inline -#elif defined(_MSC_VER) -# define MEM_STATIC static __inline -#else -# define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ -#endif /**************************************************************** @@ -3218,6 +3210,7 @@ static size_t ZSTD_decompressContinue(ZSTD_DCtx* ctx, void* dst, size_t maxDstSi } ctx->stage = ZSTDds_decodeBlockHeader; ctx->expected = ZSTD_blockHeaderSize; + if (ZSTD_isError(rSize)) return rSize; ctx->previousDstEnd = (char*)dst + rSize; return rSize; } @@ -3545,8 +3538,8 @@ static size_t ZBUFF_decompressContinue(ZBUFF_DCtx* zbc, void* dst, size_t* maxDs unsigned ZBUFFv04_isError(size_t errorCode) { return ERR_isError(errorCode); } const char* ZBUFFv04_getErrorName(size_t errorCode) { return ERR_getErrorName(errorCode); } -size_t ZBUFFv04_recommendedDInSize() { return BLOCKSIZE + 3; } -size_t ZBUFFv04_recommendedDOutSize() { return BLOCKSIZE; } +size_t ZBUFFv04_recommendedDInSize(void) { return BLOCKSIZE + 3; } +size_t ZBUFFv04_recommendedDOutSize(void) { return BLOCKSIZE; } diff --git a/src/thirdparty/zstd/legacy/zstd_v05.c b/src/thirdparty/zstd/legacy/zstd_v05.c index 93a1169f..44a877bf 100644 --- a/src/thirdparty/zstd/legacy/zstd_v05.c +++ b/src/thirdparty/zstd/legacy/zstd_v05.c @@ -3600,6 +3600,7 @@ size_t ZSTDv05_decompressContinue(ZSTDv05_DCtx* dctx, void* dst, size_t maxDstSi } dctx->stage = ZSTDv05ds_decodeBlockHeader; dctx->expected = ZSTDv05_blockHeaderSize; + if (ZSTDv05_isError(rSize)) return rSize; dctx->previousDstEnd = (char*)dst + rSize; return rSize; } diff --git a/src/thirdparty/zstd/legacy/zstd_v06.c b/src/thirdparty/zstd/legacy/zstd_v06.c index 175f7cc4..00d6ef79 100644 --- a/src/thirdparty/zstd/legacy/zstd_v06.c +++ b/src/thirdparty/zstd/legacy/zstd_v06.c @@ -14,6 +14,7 @@ #include /* size_t, ptrdiff_t */ #include /* memcpy */ #include /* malloc, free, qsort */ +#include "../common/compiler.h" #include "../common/error_private.h" @@ -67,15 +68,6 @@ extern "C" { # include /* _byteswap_ulong */ # include /* _byteswap_* */ #endif -#if defined(__GNUC__) -# define MEM_STATIC static __attribute__((unused)) -#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) -# define MEM_STATIC static inline -#elif defined(_MSC_VER) -# define MEM_STATIC static __inline -#else -# define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ -#endif /*-************************************************************** @@ -3745,6 +3737,7 @@ size_t ZSTDv06_decompressContinue(ZSTDv06_DCtx* dctx, void* dst, size_t dstCapac } dctx->stage = ZSTDds_decodeBlockHeader; dctx->expected = ZSTDv06_blockHeaderSize; + if (ZSTDv06_isError(rSize)) return rSize; dctx->previousDstEnd = (char*)dst + rSize; return rSize; } diff --git a/src/thirdparty/zstd/legacy/zstd_v07.c b/src/thirdparty/zstd/legacy/zstd_v07.c index 15dc3ef7..8778f079 100644 --- a/src/thirdparty/zstd/legacy/zstd_v07.c +++ b/src/thirdparty/zstd/legacy/zstd_v07.c @@ -24,6 +24,7 @@ #define HUFv07_STATIC_LINKING_ONLY /* HUFv07_TABLELOG_ABSOLUTEMAX */ #define ZSTDv07_STATIC_LINKING_ONLY +#include "../common/compiler.h" #include "../common/error_private.h" @@ -227,15 +228,6 @@ extern "C" { # include /* _byteswap_ulong */ # include /* _byteswap_* */ #endif -#if defined(__GNUC__) -# define MEM_STATIC static __attribute__((unused)) -#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) -# define MEM_STATIC static inline -#elif defined(_MSC_VER) -# define MEM_STATIC static __inline -#else -# define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ -#endif /*-************************************************************** @@ -4015,8 +4007,8 @@ size_t ZSTDv07_decompressContinue(ZSTDv07_DCtx* dctx, void* dst, size_t dstCapac } dctx->stage = ZSTDds_decodeBlockHeader; dctx->expected = ZSTDv07_blockHeaderSize; - dctx->previousDstEnd = (char*)dst + rSize; if (ZSTDv07_isError(rSize)) return rSize; + dctx->previousDstEnd = (char*)dst + rSize; if (dctx->fParams.checksumFlag) XXH64_update(&dctx->xxhState, dst, rSize); return rSize; } diff --git a/src/thirdparty/zstd/libzstd.mk b/src/thirdparty/zstd/libzstd.mk index 5e11d5d2..a308a6ef 100644 --- a/src/thirdparty/zstd/libzstd.mk +++ b/src/thirdparty/zstd/libzstd.mk @@ -8,12 +8,21 @@ # You may select, at your option, one of the above-listed licenses. # ################################################################ +# This included Makefile provides the following variables : +# LIB_SRCDIR, LIB_BINDIR + +# Ensure the file is not included twice +# Note : must be included after setting the default target +ifndef LIBZSTD_MK_INCLUDED +LIBZSTD_MK_INCLUDED := 1 + ################################################################## # Input Variables ################################################################## -# Zstd lib directory -LIBZSTD ?= ./ +# By default, library's directory is same as this included makefile +LIB_SRCDIR ?= $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) +LIB_BINDIR ?= $(LIBSRC_DIR) # ZSTD_LIB_MINIFY is a helper variable that # configures a bunch of other variables to space-optimized defaults. @@ -47,6 +56,9 @@ endif # Assembly support ZSTD_NO_ASM ?= 0 +ZSTD_LIB_EXCLUDE_COMPRESSORS_DFAST_AND_UP ?= 0 +ZSTD_LIB_EXCLUDE_COMPRESSORS_GREEDY_AND_UP ?= 0 + ################################################################## # libzstd helpers ################################################################## @@ -57,6 +69,7 @@ VOID ?= /dev/null NUM_SYMBOL := \# # define silent mode as default (verbose mode with V=1 or VERBOSE=1) +# Note : must be defined _after_ the default target $(V)$(VERBOSE).SILENT: # When cross-compiling from linux to windows, @@ -66,7 +79,7 @@ $(V)$(VERBOSE).SILENT: TARGET_SYSTEM ?= $(OS) # Version numbers -LIBVER_SRC := $(LIBZSTD)/zstd.h +LIBVER_SRC := $(LIB_SRCDIR)/zstd.h LIBVER_MAJOR_SCRIPT:=`sed -n '/define ZSTD_VERSION_MAJOR/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < $(LIBVER_SRC)` LIBVER_MINOR_SCRIPT:=`sed -n '/define ZSTD_VERSION_MINOR/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < $(LIBVER_SRC)` LIBVER_PATCH_SCRIPT:=`sed -n '/define ZSTD_VERSION_RELEASE/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < $(LIBVER_SRC)` @@ -133,14 +146,14 @@ ifeq ($(HAVE_COLORNEVER), 1) endif GREP = grep $(GREP_OPTIONS) -ZSTD_COMMON_FILES := $(sort $(wildcard $(LIBZSTD)/common/*.c)) -ZSTD_COMPRESS_FILES := $(sort $(wildcard $(LIBZSTD)/compress/*.c)) -ZSTD_DECOMPRESS_FILES := $(sort $(wildcard $(LIBZSTD)/decompress/*.c)) -ZSTD_DICTBUILDER_FILES := $(sort $(wildcard $(LIBZSTD)/dictBuilder/*.c)) -ZSTD_DEPRECATED_FILES := $(sort $(wildcard $(LIBZSTD)/deprecated/*.c)) +ZSTD_COMMON_FILES := $(sort $(wildcard $(LIB_SRCDIR)/common/*.c)) +ZSTD_COMPRESS_FILES := $(sort $(wildcard $(LIB_SRCDIR)/compress/*.c)) +ZSTD_DECOMPRESS_FILES := $(sort $(wildcard $(LIB_SRCDIR)/decompress/*.c)) +ZSTD_DICTBUILDER_FILES := $(sort $(wildcard $(LIB_SRCDIR)/dictBuilder/*.c)) +ZSTD_DEPRECATED_FILES := $(sort $(wildcard $(LIB_SRCDIR)/deprecated/*.c)) ZSTD_LEGACY_FILES := -ZSTD_DECOMPRESS_AMD64_ASM_FILES := $(sort $(wildcard $(LIBZSTD)/decompress/*_amd64.S)) +ZSTD_DECOMPRESS_AMD64_ASM_FILES := $(sort $(wildcard $(LIB_SRCDIR)/decompress/*_amd64.S)) ifneq ($(ZSTD_NO_ASM), 0) CPPFLAGS += -DZSTD_DISABLE_ASM @@ -178,9 +191,17 @@ ifneq ($(ZSTD_LEGACY_MULTITHREADED_API), 0) CFLAGS += -DZSTD_LEGACY_MULTITHREADED_API endif +ifneq ($(ZSTD_LIB_EXCLUDE_COMPRESSORS_DFAST_AND_UP), 0) + CFLAGS += -DZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR +else +ifneq ($(ZSTD_LIB_EXCLUDE_COMPRESSORS_GREEDY_AND_UP), 0) + CFLAGS += -DZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR +endif +endif + ifneq ($(ZSTD_LEGACY_SUPPORT), 0) ifeq ($(shell test $(ZSTD_LEGACY_SUPPORT) -lt 8; echo $$?), 0) - ZSTD_LEGACY_FILES += $(shell ls $(LIBZSTD)/legacy/*.c | $(GREP) 'v0[$(ZSTD_LEGACY_SUPPORT)-7]') + ZSTD_LEGACY_FILES += $(shell ls $(LIB_SRCDIR)/legacy/*.c | $(GREP) 'v0[$(ZSTD_LEGACY_SUPPORT)-7]') endif endif CPPFLAGS += -DZSTD_LEGACY_SUPPORT=$(ZSTD_LEGACY_SUPPORT) @@ -209,6 +230,8 @@ ifeq ($(HAVE_HASH),0) endif endif # BUILD_DIR -ZSTD_SUBDIR := $(LIBZSTD)/common $(LIBZSTD)/compress $(LIBZSTD)/decompress $(LIBZSTD)/dictBuilder $(LIBZSTD)/legacy $(LIBZSTD)/deprecated +ZSTD_SUBDIR := $(LIB_SRCDIR)/common $(LIB_SRCDIR)/compress $(LIB_SRCDIR)/decompress $(LIB_SRCDIR)/dictBuilder $(LIB_SRCDIR)/legacy $(LIB_SRCDIR)/deprecated vpath %.c $(ZSTD_SUBDIR) vpath %.S $(ZSTD_SUBDIR) + +endif # LIBZSTD_MK_INCLUDED diff --git a/src/thirdparty/zstd/zstd.h b/src/thirdparty/zstd/zstd.h index e5c3f8b6..5d1fef8a 100644 --- a/src/thirdparty/zstd/zstd.h +++ b/src/thirdparty/zstd/zstd.h @@ -106,7 +106,7 @@ extern "C" { /*------ Version ------*/ #define ZSTD_VERSION_MAJOR 1 #define ZSTD_VERSION_MINOR 5 -#define ZSTD_VERSION_RELEASE 5 +#define ZSTD_VERSION_RELEASE 6 #define ZSTD_VERSION_NUMBER (ZSTD_VERSION_MAJOR *100*100 + ZSTD_VERSION_MINOR *100 + ZSTD_VERSION_RELEASE) /*! ZSTD_versionNumber() : @@ -228,7 +228,7 @@ ZSTDLIB_API size_t ZSTD_findFrameCompressedSize(const void* src, size_t srcSize) * for example to size a static array on stack. * Will produce constant value 0 if srcSize too large. */ -#define ZSTD_MAX_INPUT_SIZE ((sizeof(size_t)==8) ? 0xFF00FF00FF00FF00LLU : 0xFF00FF00U) +#define ZSTD_MAX_INPUT_SIZE ((sizeof(size_t)==8) ? 0xFF00FF00FF00FF00ULL : 0xFF00FF00U) #define ZSTD_COMPRESSBOUND(srcSize) (((size_t)(srcSize) >= ZSTD_MAX_INPUT_SIZE) ? 0 : (srcSize) + ((srcSize)>>8) + (((srcSize) < (128<<10)) ? (((128<<10) - (srcSize)) >> 11) /* margin, from 64 to 0 */ : 0)) /* this formula ensures that bound(A) + bound(B) <= bound(A+B) as long as A and B >= 128 KB */ ZSTDLIB_API size_t ZSTD_compressBound(size_t srcSize); /*!< maximum compressed size in worst case single-pass scenario */ /* ZSTD_isError() : @@ -249,7 +249,7 @@ ZSTDLIB_API int ZSTD_defaultCLevel(void); /*!< default compres /*= Compression context * When compressing many times, * it is recommended to allocate a context just once, - * and re-use it for each successive compression operation. + * and reuse it for each successive compression operation. * This will make workload friendlier for system's memory. * Note : re-using context is just a speed / resource optimization. * It doesn't change the compression ratio, which remains identical. @@ -262,9 +262,9 @@ ZSTDLIB_API size_t ZSTD_freeCCtx(ZSTD_CCtx* cctx); /* accept NULL pointer * /*! ZSTD_compressCCtx() : * Same as ZSTD_compress(), using an explicit ZSTD_CCtx. - * Important : in order to behave similarly to `ZSTD_compress()`, - * this function compresses at requested compression level, - * __ignoring any other parameter__ . + * Important : in order to mirror `ZSTD_compress()` behavior, + * this function compresses at the requested compression level, + * __ignoring any other advanced parameter__ . * If any advanced parameter was set using the advanced API, * they will all be reset. Only `compressionLevel` remains. */ @@ -276,7 +276,7 @@ ZSTDLIB_API size_t ZSTD_compressCCtx(ZSTD_CCtx* cctx, /*= Decompression context * When decompressing many times, * it is recommended to allocate a context only once, - * and re-use it for each successive compression operation. + * and reuse it for each successive compression operation. * This will make workload friendlier for system's memory. * Use one context per thread for parallel execution. */ typedef struct ZSTD_DCtx_s ZSTD_DCtx; @@ -286,7 +286,7 @@ ZSTDLIB_API size_t ZSTD_freeDCtx(ZSTD_DCtx* dctx); /* accept NULL pointer * /*! ZSTD_decompressDCtx() : * Same as ZSTD_decompress(), * requires an allocated ZSTD_DCtx. - * Compatible with sticky parameters. + * Compatible with sticky parameters (see below). */ ZSTDLIB_API size_t ZSTD_decompressDCtx(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, @@ -302,12 +302,12 @@ ZSTDLIB_API size_t ZSTD_decompressDCtx(ZSTD_DCtx* dctx, * using ZSTD_CCtx_set*() functions. * Pushed parameters are sticky : they are valid for next compressed frame, and any subsequent frame. * "sticky" parameters are applicable to `ZSTD_compress2()` and `ZSTD_compressStream*()` ! - * __They do not apply to "simple" one-shot variants such as ZSTD_compressCCtx()__ . + * __They do not apply to one-shot variants such as ZSTD_compressCCtx()__ . * * It's possible to reset all parameters to "default" using ZSTD_CCtx_reset(). * * This API supersedes all other "advanced" API entry points in the experimental section. - * In the future, we expect to remove from experimental API entry points which are redundant with this API. + * In the future, we expect to remove API entry points from experimental which are redundant with this API. */ @@ -390,6 +390,19 @@ typedef enum { * The higher the value of selected strategy, the more complex it is, * resulting in stronger and slower compression. * Special: value 0 means "use default strategy". */ + + ZSTD_c_targetCBlockSize=130, /* v1.5.6+ + * Attempts to fit compressed block size into approximatively targetCBlockSize. + * Bound by ZSTD_TARGETCBLOCKSIZE_MIN and ZSTD_TARGETCBLOCKSIZE_MAX. + * Note that it's not a guarantee, just a convergence target (default:0). + * No target when targetCBlockSize == 0. + * This is helpful in low bandwidth streaming environments to improve end-to-end latency, + * when a client can make use of partial documents (a prominent example being Chrome). + * Note: this parameter is stable since v1.5.6. + * It was present as an experimental parameter in earlier versions, + * but it's not recommended using it with earlier library versions + * due to massive performance regressions. + */ /* LDM mode parameters */ ZSTD_c_enableLongDistanceMatching=160, /* Enable long distance matching. * This parameter is designed to improve compression ratio @@ -469,7 +482,6 @@ typedef enum { * ZSTD_c_forceMaxWindow * ZSTD_c_forceAttachDict * ZSTD_c_literalCompressionMode - * ZSTD_c_targetCBlockSize * ZSTD_c_srcSizeHint * ZSTD_c_enableDedicatedDictSearch * ZSTD_c_stableInBuffer @@ -490,7 +502,7 @@ typedef enum { ZSTD_c_experimentalParam3=1000, ZSTD_c_experimentalParam4=1001, ZSTD_c_experimentalParam5=1002, - ZSTD_c_experimentalParam6=1003, + /* was ZSTD_c_experimentalParam6=1003; is now ZSTD_c_targetCBlockSize */ ZSTD_c_experimentalParam7=1004, ZSTD_c_experimentalParam8=1005, ZSTD_c_experimentalParam9=1006, @@ -575,6 +587,7 @@ ZSTDLIB_API size_t ZSTD_CCtx_reset(ZSTD_CCtx* cctx, ZSTD_ResetDirective reset); /*! ZSTD_compress2() : * Behave the same as ZSTD_compressCCtx(), but compression parameters are set using the advanced API. + * (note that this entry point doesn't even expose a compression level parameter). * ZSTD_compress2() always starts a new frame. * Should cctx hold data from a previously unfinished frame, everything about it is forgotten. * - Compression parameters are pushed into CCtx before starting compression, using ZSTD_CCtx_set*() @@ -618,6 +631,7 @@ typedef enum { * ZSTD_d_forceIgnoreChecksum * ZSTD_d_refMultipleDDicts * ZSTD_d_disableHuffmanAssembly + * ZSTD_d_maxBlockSize * Because they are not stable, it's necessary to define ZSTD_STATIC_LINKING_ONLY to access them. * note : never ever use experimentalParam? names directly */ @@ -625,7 +639,8 @@ typedef enum { ZSTD_d_experimentalParam2=1001, ZSTD_d_experimentalParam3=1002, ZSTD_d_experimentalParam4=1003, - ZSTD_d_experimentalParam5=1004 + ZSTD_d_experimentalParam5=1004, + ZSTD_d_experimentalParam6=1005 } ZSTD_dParameter; @@ -680,14 +695,14 @@ typedef struct ZSTD_outBuffer_s { * A ZSTD_CStream object is required to track streaming operation. * Use ZSTD_createCStream() and ZSTD_freeCStream() to create/release resources. * ZSTD_CStream objects can be reused multiple times on consecutive compression operations. -* It is recommended to re-use ZSTD_CStream since it will play nicer with system's memory, by re-using already allocated memory. +* It is recommended to reuse ZSTD_CStream since it will play nicer with system's memory, by re-using already allocated memory. * * For parallel execution, use one separate ZSTD_CStream per thread. * * note : since v1.3.0, ZSTD_CStream and ZSTD_CCtx are the same thing. * * Parameters are sticky : when starting a new compression on the same context, -* it will re-use the same sticky parameters as previous compression session. +* it will reuse the same sticky parameters as previous compression session. * When in doubt, it's recommended to fully initialize the context before usage. * Use ZSTD_CCtx_reset() to reset the context and ZSTD_CCtx_setParameter(), * ZSTD_CCtx_setPledgedSrcSize(), or ZSTD_CCtx_loadDictionary() and friends to @@ -776,6 +791,11 @@ typedef enum { * only ZSTD_e_end or ZSTD_e_flush operations are allowed. * Before starting a new compression job, or changing compression parameters, * it is required to fully flush internal buffers. + * - note: if an operation ends with an error, it may leave @cctx in an undefined state. + * Therefore, it's UB to invoke ZSTD_compressStream2() of ZSTD_compressStream() on such a state. + * In order to be re-employed after an error, a state must be reset, + * which can be done explicitly (ZSTD_CCtx_reset()), + * or is sometimes implied by methods starting a new compression job (ZSTD_initCStream(), ZSTD_compressCCtx()) */ ZSTDLIB_API size_t ZSTD_compressStream2( ZSTD_CCtx* cctx, ZSTD_outBuffer* output, @@ -835,7 +855,7 @@ ZSTDLIB_API size_t ZSTD_endStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output); * * A ZSTD_DStream object is required to track streaming operations. * Use ZSTD_createDStream() and ZSTD_freeDStream() to create/release resources. -* ZSTD_DStream objects can be re-used multiple times. +* ZSTD_DStream objects can be reused multiple times. * * Use ZSTD_initDStream() to start a new decompression operation. * @return : recommended first input size @@ -889,6 +909,12 @@ ZSTDLIB_API size_t ZSTD_initDStream(ZSTD_DStream* zds); * @return : 0 when a frame is completely decoded and fully flushed, * or an error code, which can be tested using ZSTD_isError(), * or any other value > 0, which means there is some decoding or flushing to do to complete current frame. + * + * Note: when an operation returns with an error code, the @zds state may be left in undefined state. + * It's UB to invoke `ZSTD_decompressStream()` on such a state. + * In order to re-use such a state, it must be first reset, + * which can be done explicitly (`ZSTD_DCtx_reset()`), + * or is implied for operations starting some new decompression job (`ZSTD_initDStream`, `ZSTD_decompressDCtx()`, `ZSTD_decompress_usingDict()`) */ ZSTDLIB_API size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inBuffer* input); @@ -1021,7 +1047,7 @@ ZSTDLIB_API unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize); * * This API allows dictionaries to be used with ZSTD_compress2(), * ZSTD_compressStream2(), and ZSTD_decompressDCtx(). - * Dictionaries are sticky, they remain valid when same context is re-used, + * Dictionaries are sticky, they remain valid when same context is reused, * they only reset when the context is reset * with ZSTD_reset_parameters or ZSTD_reset_session_and_parameters. * In contrast, Prefixes are single-use. @@ -1239,7 +1265,7 @@ ZSTDLIB_API size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict); #define ZSTD_LDM_HASHRATELOG_MAX (ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN) /* Advanced parameter bounds */ -#define ZSTD_TARGETCBLOCKSIZE_MIN 64 +#define ZSTD_TARGETCBLOCKSIZE_MIN 1340 /* suitable to fit into an ethernet / wifi / 4G transport frame */ #define ZSTD_TARGETCBLOCKSIZE_MAX ZSTD_BLOCKSIZE_MAX #define ZSTD_SRCSIZEHINT_MIN 0 #define ZSTD_SRCSIZEHINT_MAX INT_MAX @@ -1527,25 +1553,38 @@ typedef enum { ZSTDLIB_STATIC_API size_t ZSTD_sequenceBound(size_t srcSize); /*! ZSTD_generateSequences() : + * WARNING: This function is meant for debugging and informational purposes ONLY! + * Its implementation is flawed, and it will be deleted in a future version. + * It is not guaranteed to succeed, as there are several cases where it will give + * up and fail. You should NOT use this function in production code. + * + * This function is deprecated, and will be removed in a future version. + * * Generate sequences using ZSTD_compress2(), given a source buffer. * + * @param zc The compression context to be used for ZSTD_compress2(). Set any + * compression parameters you need on this context. + * @param outSeqs The output sequences buffer of size @p outSeqsSize + * @param outSeqsSize The size of the output sequences buffer. + * ZSTD_sequenceBound(srcSize) is an upper bound on the number + * of sequences that can be generated. + * @param src The source buffer to generate sequences from of size @p srcSize. + * @param srcSize The size of the source buffer. + * * Each block will end with a dummy sequence * with offset == 0, matchLength == 0, and litLength == length of last literals. * litLength may be == 0, and if so, then the sequence of (of: 0 ml: 0 ll: 0) * simply acts as a block delimiter. * - * @zc can be used to insert custom compression params. - * This function invokes ZSTD_compress2(). - * - * The output of this function can be fed into ZSTD_compressSequences() with CCtx - * setting of ZSTD_c_blockDelimiters as ZSTD_sf_explicitBlockDelimiters - * @return : number of sequences generated + * @returns The number of sequences generated, necessarily less than + * ZSTD_sequenceBound(srcSize), or an error code that can be checked + * with ZSTD_isError(). */ - +ZSTD_DEPRECATED("For debugging only, will be replaced by ZSTD_extractSequences()") ZSTDLIB_STATIC_API size_t -ZSTD_generateSequences( ZSTD_CCtx* zc, - ZSTD_Sequence* outSeqs, size_t outSeqsSize, - const void* src, size_t srcSize); +ZSTD_generateSequences(ZSTD_CCtx* zc, + ZSTD_Sequence* outSeqs, size_t outSeqsSize, + const void* src, size_t srcSize); /*! ZSTD_mergeBlockDelimiters() : * Given an array of ZSTD_Sequence, remove all sequences that represent block delimiters/last literals @@ -1640,56 +1679,59 @@ ZSTDLIB_API unsigned ZSTD_isSkippableFrame(const void* buffer, size_t size); /*! ZSTD_estimate*() : * These functions make it possible to estimate memory usage * of a future {D,C}Ctx, before its creation. + * This is useful in combination with ZSTD_initStatic(), + * which makes it possible to employ a static buffer for ZSTD_CCtx* state. * * ZSTD_estimateCCtxSize() will provide a memory budget large enough - * for any compression level up to selected one. - * Note : Unlike ZSTD_estimateCStreamSize*(), this estimate - * does not include space for a window buffer. - * Therefore, the estimation is only guaranteed for single-shot compressions, not streaming. + * to compress data of any size using one-shot compression ZSTD_compressCCtx() or ZSTD_compress2() + * associated with any compression level up to max specified one. * The estimate will assume the input may be arbitrarily large, * which is the worst case. * + * Note that the size estimation is specific for one-shot compression, + * it is not valid for streaming (see ZSTD_estimateCStreamSize*()) + * nor other potential ways of using a ZSTD_CCtx* state. + * * When srcSize can be bound by a known and rather "small" value, - * this fact can be used to provide a tighter estimation - * because the CCtx compression context will need less memory. - * This tighter estimation can be provided by more advanced functions + * this knowledge can be used to provide a tighter budget estimation + * because the ZSTD_CCtx* state will need less memory for small inputs. + * This tighter estimation can be provided by employing more advanced functions * ZSTD_estimateCCtxSize_usingCParams(), which can be used in tandem with ZSTD_getCParams(), * and ZSTD_estimateCCtxSize_usingCCtxParams(), which can be used in tandem with ZSTD_CCtxParams_setParameter(). * Both can be used to estimate memory using custom compression parameters and arbitrary srcSize limits. * * Note : only single-threaded compression is supported. * ZSTD_estimateCCtxSize_usingCCtxParams() will return an error code if ZSTD_c_nbWorkers is >= 1. - * - * Note 2 : ZSTD_estimateCCtxSize* functions are not compatible with the Block-Level Sequence Producer API at this time. - * Size estimates assume that no external sequence producer is registered. */ -ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize(int compressionLevel); +ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize(int maxCompressionLevel); ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams); ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize_usingCCtxParams(const ZSTD_CCtx_params* params); ZSTDLIB_STATIC_API size_t ZSTD_estimateDCtxSize(void); /*! ZSTD_estimateCStreamSize() : - * ZSTD_estimateCStreamSize() will provide a budget large enough for any compression level up to selected one. - * It will also consider src size to be arbitrarily "large", which is worst case. + * ZSTD_estimateCStreamSize() will provide a memory budget large enough for streaming compression + * using any compression level up to the max specified one. + * It will also consider src size to be arbitrarily "large", which is a worst case scenario. * If srcSize is known to always be small, ZSTD_estimateCStreamSize_usingCParams() can provide a tighter estimation. * ZSTD_estimateCStreamSize_usingCParams() can be used in tandem with ZSTD_getCParams() to create cParams from compressionLevel. * ZSTD_estimateCStreamSize_usingCCtxParams() can be used in tandem with ZSTD_CCtxParams_setParameter(). Only single-threaded compression is supported. This function will return an error code if ZSTD_c_nbWorkers is >= 1. * Note : CStream size estimation is only correct for single-threaded compression. - * ZSTD_DStream memory budget depends on window Size. + * ZSTD_estimateCStreamSize_usingCCtxParams() will return an error code if ZSTD_c_nbWorkers is >= 1. + * Note 2 : ZSTD_estimateCStreamSize* functions are not compatible with the Block-Level Sequence Producer API at this time. + * Size estimates assume that no external sequence producer is registered. + * + * ZSTD_DStream memory budget depends on frame's window Size. * This information can be passed manually, using ZSTD_estimateDStreamSize, * or deducted from a valid frame Header, using ZSTD_estimateDStreamSize_fromFrame(); + * Any frame requesting a window size larger than max specified one will be rejected. * Note : if streaming is init with function ZSTD_init?Stream_usingDict(), * an internal ?Dict will be created, which additional size is not estimated here. * In this case, get total size by adding ZSTD_estimate?DictSize - * Note 2 : only single-threaded compression is supported. - * ZSTD_estimateCStreamSize_usingCCtxParams() will return an error code if ZSTD_c_nbWorkers is >= 1. - * Note 3 : ZSTD_estimateCStreamSize* functions are not compatible with the Block-Level Sequence Producer API at this time. - * Size estimates assume that no external sequence producer is registered. */ -ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize(int compressionLevel); +ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize(int maxCompressionLevel); ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize_usingCParams(ZSTD_compressionParameters cParams); ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize_usingCCtxParams(const ZSTD_CCtx_params* params); -ZSTDLIB_STATIC_API size_t ZSTD_estimateDStreamSize(size_t windowSize); +ZSTDLIB_STATIC_API size_t ZSTD_estimateDStreamSize(size_t maxWindowSize); ZSTDLIB_STATIC_API size_t ZSTD_estimateDStreamSize_fromFrame(const void* src, size_t srcSize); /*! ZSTD_estimate?DictSize() : @@ -1946,11 +1988,6 @@ ZSTDLIB_STATIC_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const vo */ #define ZSTD_c_literalCompressionMode ZSTD_c_experimentalParam5 -/* Tries to fit compressed block size to be around targetCBlockSize. - * No target when targetCBlockSize == 0. - * There is no guarantee on compressed block size (default:0) */ -#define ZSTD_c_targetCBlockSize ZSTD_c_experimentalParam6 - /* User's best guess of source size. * Hint is not valid when srcSizeHint == 0. * There is no guarantee that hint is close to actual source size, @@ -2430,6 +2467,22 @@ ZSTDLIB_STATIC_API size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParamete */ #define ZSTD_d_disableHuffmanAssembly ZSTD_d_experimentalParam5 +/* ZSTD_d_maxBlockSize + * Allowed values are between 1KB and ZSTD_BLOCKSIZE_MAX (128KB). + * The default is ZSTD_BLOCKSIZE_MAX, and setting to 0 will set to the default. + * + * Forces the decompressor to reject blocks whose content size is + * larger than the configured maxBlockSize. When maxBlockSize is + * larger than the windowSize, the windowSize is used instead. + * This saves memory on the decoder when you know all blocks are small. + * + * This option is typically used in conjunction with ZSTD_c_maxBlockSize. + * + * WARNING: This causes the decoder to reject otherwise valid frames + * that have block sizes larger than the configured maxBlockSize. + */ +#define ZSTD_d_maxBlockSize ZSTD_d_experimentalParam6 + /*! ZSTD_DCtx_setFormat() : * This function is REDUNDANT. Prefer ZSTD_DCtx_setParameter(). @@ -2557,7 +2610,7 @@ size_t ZSTD_initCStream_usingCDict_advanced(ZSTD_CStream* zcs, * explicitly specified. * * start a new frame, using same parameters from previous frame. - * This is typically useful to skip dictionary loading stage, since it will re-use it in-place. + * This is typically useful to skip dictionary loading stage, since it will reuse it in-place. * Note that zcs must be init at least once before using ZSTD_resetCStream(). * If pledgedSrcSize is not known at reset time, use macro ZSTD_CONTENTSIZE_UNKNOWN. * If pledgedSrcSize > 0, its value must be correct, as it will be written in header, and controlled at the end. @@ -2633,7 +2686,7 @@ ZSTDLIB_STATIC_API size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* zds, const Z * * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only); * - * re-use decompression parameters from previous init; saves dictionary loading + * reuse decompression parameters from previous init; saves dictionary loading */ ZSTD_DEPRECATED("use ZSTD_DCtx_reset, see zstd.h for detailed instructions") ZSTDLIB_STATIC_API size_t ZSTD_resetDStream(ZSTD_DStream* zds); @@ -2765,7 +2818,7 @@ ZSTDLIB_STATIC_API size_t ZSTD_resetDStream(ZSTD_DStream* zds); #define ZSTD_SEQUENCE_PRODUCER_ERROR ((size_t)(-1)) -typedef size_t ZSTD_sequenceProducer_F ( +typedef size_t (*ZSTD_sequenceProducer_F) ( void* sequenceProducerState, ZSTD_Sequence* outSeqs, size_t outSeqsCapacity, const void* src, size_t srcSize, @@ -2797,7 +2850,23 @@ ZSTDLIB_STATIC_API void ZSTD_registerSequenceProducer( ZSTD_CCtx* cctx, void* sequenceProducerState, - ZSTD_sequenceProducer_F* sequenceProducer + ZSTD_sequenceProducer_F sequenceProducer +); + +/*! ZSTD_CCtxParams_registerSequenceProducer() : + * Same as ZSTD_registerSequenceProducer(), but operates on ZSTD_CCtx_params. + * This is used for accurate size estimation with ZSTD_estimateCCtxSize_usingCCtxParams(), + * which is needed when creating a ZSTD_CCtx with ZSTD_initStaticCCtx(). + * + * If you are using the external sequence producer API in a scenario where ZSTD_initStaticCCtx() + * is required, then this function is for you. Otherwise, you probably don't need it. + * + * See tests/zstreamtest.c for example usage. */ +ZSTDLIB_STATIC_API void +ZSTD_CCtxParams_registerSequenceProducer( + ZSTD_CCtx_params* params, + void* sequenceProducerState, + ZSTD_sequenceProducer_F sequenceProducer ); @@ -2820,7 +2889,7 @@ ZSTD_registerSequenceProducer( A ZSTD_CCtx object is required to track streaming operations. Use ZSTD_createCCtx() / ZSTD_freeCCtx() to manage resource. - ZSTD_CCtx object can be re-used multiple times within successive compression operations. + ZSTD_CCtx object can be reused multiple times within successive compression operations. Start by initializing a context. Use ZSTD_compressBegin(), or ZSTD_compressBegin_usingDict() for dictionary compression. @@ -2841,7 +2910,7 @@ ZSTD_registerSequenceProducer( It's possible to use srcSize==0, in which case, it will write a final empty block to end the frame. Without last block mark, frames are considered unfinished (hence corrupted) by compliant decoders. - `ZSTD_CCtx` object can be re-used (ZSTD_compressBegin()) to compress again. + `ZSTD_CCtx` object can be reused (ZSTD_compressBegin()) to compress again. */ /*===== Buffer-less streaming compression functions =====*/ @@ -2873,7 +2942,7 @@ size_t ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx* const cctx, const ZSTD_ A ZSTD_DCtx object is required to track streaming operations. Use ZSTD_createDCtx() / ZSTD_freeDCtx() to manage it. - A ZSTD_DCtx object can be re-used multiple times. + A ZSTD_DCtx object can be reused multiple times. First typical operation is to retrieve frame parameters, using ZSTD_getFrameHeader(). Frame header is extracted from the beginning of compressed frame, so providing only the frame's beginning is enough. diff --git a/src/tier0/cpu.cpp b/src/tier0/cpu.cpp index b450daeb..7393b4bd 100644 --- a/src/tier0/cpu.cpp +++ b/src/tier0/cpu.cpp @@ -470,7 +470,9 @@ const CPUInformation& GetCPUInformation(void) pi.m_bSSE41 = (cpuid1.ecx >> 19) & 1; pi.m_bSSE42 = (cpuid1.ecx >> 20) & 1; pi.m_b3DNow = Check3DNowTechnology(); + pi.m_bPOPCNT= (cpuid1.ecx >> 23) & 1; pi.m_bAVX = (cpuid1.ecx >> 28) & 1; + pi.m_bHRVSR = (cpuid1.ecx >> 31) & 1; pi.m_szProcessorID = const_cast(GetProcessorVendorId()); pi.m_szProcessorBrand = const_cast(GetProcessorBrand()); pi.m_bHT = (pi.m_nPhysicalProcessors < pi.m_nLogicalProcessors); //HTSupported(); @@ -573,16 +575,36 @@ const CPUInformation& GetCPUInformation(void) return pi; } -void CheckCPUforSSE2() +void CheckSystemCPU() { const CPUInformation& pi = GetCPUInformation(); if (!(pi.m_bSSE && pi.m_bSSE2)) { - Assert(0); if (MessageBoxA(NULL, "SSE and SSE2 are required.", "Unsupported CPU", MB_ICONERROR | MB_OK)) { - TerminateProcess(GetCurrentProcess(), EXIT_FAILURE); + TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF); + } + } + if (!pi.m_bSSE3) + { + if (MessageBoxA(NULL, "SSE3 is required.", "Unsupported CPU", MB_ICONERROR | MB_OK)) + { + TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF); + } + } + if (!pi.m_bSSSE3) + { + if (MessageBoxA(NULL, "SSSE3 (Supplemental SSE3 Instructions) is required.", "Unsupported CPU", MB_ICONERROR | MB_OK)) + { + TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF); + } + } + if (!pi.m_bPOPCNT) + { + if (MessageBoxA(NULL, "POPCNT is required.", "Unsupported CPU", MB_ICONERROR | MB_OK)) + { + TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF); } } } diff --git a/src/tier0/cpu.h b/src/tier0/cpu.h index 52cf16bc..4425c486 100644 --- a/src/tier0/cpu.h +++ b/src/tier0/cpu.h @@ -62,7 +62,9 @@ struct CPUInformation m_bSSE4a : 1, m_bSSE41 : 1, m_bSSE42 : 1, - m_bAVX : 1; // Is AVX supported? + m_bPOPCNT: 1, // Pop count + m_bAVX : 1, // Advanced Vector Extensions + m_bHRVSR : 1; // Hypervisor uint32 m_nModel; uint32 m_nFeatures[3]; @@ -94,12 +96,14 @@ struct CPUInformation m_szProcessorID = nullptr; m_szProcessorBrand = nullptr; - m_bSSE3 = false; - m_bSSSE3 = false; - m_bSSE4a = false; - m_bSSE41 = false; - m_bSSE42 = false; - m_bAVX = false; + m_bSSE3 = false; + m_bSSSE3 = false; + m_bSSE4a = false; + m_bSSE41 = false; + m_bSSE42 = false; + m_bPOPCNT = false; + m_bAVX = false; + m_bHRVSR = false; m_nModel = 0; m_nFeatures[0] = 0; @@ -126,6 +130,6 @@ const char* GetProcessorBrand(bool bRemovePadding); const CPUInformation& GetCPUInformation(void); -void CheckCPUforSSE2(); +void CheckSystemCPU(); #endif // CPU_H diff --git a/src/tier0/jobthread.cpp b/src/tier0/jobthread.cpp index 70c5d2c3..6ada54cb 100644 --- a/src/tier0/jobthread.cpp +++ b/src/tier0/jobthread.cpp @@ -15,6 +15,11 @@ JobID_t JTGuts_AddJob(JobTypeID_t jobTypeId, JobID_t jobId, void* callbackFunc, return JTGuts_AddJob_Internal(jobTypeId, jobId, callbackFunc, callbackArg, jobIndex, &job_JT_Context[jobIndex]); } +JobID_t JT_GetCurrentJob() +{ + return *(_DWORD*)(*(_QWORD*)CModule::GetThreadEnvironmentBlock()->ThreadLocalStoragePointer + 12i64); +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/src/tier0/memaddr.cpp b/src/tier0/memaddr.cpp index ceeb1e7c..88042128 100644 --- a/src/tier0/memaddr.cpp +++ b/src/tier0/memaddr.cpp @@ -91,9 +91,9 @@ CMemory CMemory::FindPattern(const char* szPattern, const Direction searchDirect { // If either the current byte equals to the byte in our pattern or our current byte in the pattern is a wildcard // our if clause will be false. - uint8_t currentByte = *(pScanBytes + nMemOffset + j); - _mm_prefetch(reinterpret_cast(static_cast(currentByte + nMemOffset + 64)), _MM_HINT_T0); // precache some data in L1. - if (currentByte != bytesInfo.second[j] && bytesInfo.second[j] != -1) + uint8_t* const pCurrentAddr = (pScanBytes + nMemOffset + j); + _mm_prefetch(reinterpret_cast(pCurrentAddr + 64), _MM_HINT_T0); // precache some data in L1. + if (*pCurrentAddr != bytesInfo.second[j] && bytesInfo.second[j] != -1) { bFound = false; break; @@ -138,9 +138,9 @@ CMemory CMemory::FindPatternSelf(const char* szPattern, const Direction searchDi { // If either the current byte equals to the byte in our pattern or our current byte in the pattern is a wildcard // our if clause will be false. - uint8_t currentByte = *(pScanBytes + nMemOffset + j); - _mm_prefetch(reinterpret_cast(static_cast(currentByte + nMemOffset + 64)), _MM_HINT_T0); // precache some data in L1. - if (currentByte != bytesInfo.second[j] && bytesInfo.second[j] != -1) + uint8_t* const pCurrentAddr = (pScanBytes + nMemOffset + j); + _mm_prefetch(reinterpret_cast(pCurrentAddr + 64), _MM_HINT_T0); // precache some data in L1. + if (*pCurrentAddr != bytesInfo.second[j] && bytesInfo.second[j] != -1) { bFound = false; break; diff --git a/src/tier0/threadtools.cpp b/src/tier0/threadtools.cpp index 952cd4cc..ecc5333e 100644 --- a/src/tier0/threadtools.cpp +++ b/src/tier0/threadtools.cpp @@ -7,6 +7,7 @@ //===========================================================================// #include "tier0/threadtools.h" +#include "tier0/jobthread.h" #define INIT_SEM_COUNT 0 #define MAX_SEM_COUNT 1 @@ -198,6 +199,42 @@ void CThreadSpinRWLock::SpinLockForRead() } } +bool ThreadInMainThread() +{ + return (ThreadGetCurrentId() == (*g_ThreadMainThreadID)); +} + +bool ThreadInServerFrameThread() +{ + return (ThreadGetCurrentId() == (*g_ThreadServerFrameThreadID) + && JT_GetCurrentJob() == (*g_CurrentServerFrameJobID)); +} + +bool ThreadInMainOrServerFrameThread() +{ + return (ThreadInMainThread() || ThreadInServerFrameThread()); +} + +bool ThreadCouldDoServerWork() +{ + if (*g_ThreadServerFrameThreadID == -1) + return ThreadInMainThread(); + + return ThreadInServerFrameThread(); +} + +void ThreadJoinServerJob() +{ + if (ThreadCouldDoServerWork()) + return; // No job to join + + if (*g_AllocatedServerFrameJobID) + { + JT_WaitForJobAndOnlyHelpWithJobTypes(*g_AllocatedServerFrameJobID, NULL, 0xFFFFFFFFFFFFFFFF); + *g_AllocatedServerFrameJobID = 0; + } +} + // NOTE: originally the game exported 'ThreadInMainThread()' and ThreadInServerFrameThread(), // but since the game is built static, and all instances of said functions are inline, we had // to export the variable symbols instead and get them here to reimplement said functions. diff --git a/src/tier0/utility.cpp b/src/tier0/utility.cpp index f3105773..0c55dfd1 100644 --- a/src/tier0/utility.cpp +++ b/src/tier0/utility.cpp @@ -1148,6 +1148,16 @@ int CompareIPv6(const IN6_ADDR& ipA, const IN6_ADDR& ipB) return 0; } +/////////////////////////////////////////////////////////////////////////////// +// For obtaining the current timestamp. +uint64_t GetUnixTimeStamp() +{ + __time64_t time; + _time64(&time); + + return time; +} + /////////////////////////////////////////////////////////////////////////////// // For obtaining a duration from a certain interval. std::chrono::nanoseconds IntervalToDuration(const float flInterval) diff --git a/src/tier1/strtools.cpp b/src/tier1/strtools.cpp index 98bfebe8..b7958849 100644 --- a/src/tier1/strtools.cpp +++ b/src/tier1/strtools.cpp @@ -18,6 +18,20 @@ static int FastToLower(char c) return i; } +//----------------------------------------------------------------------------- +// Allocate a string buffer +//----------------------------------------------------------------------------- +char* AllocString(const char* pStr, ssize_t nMaxChars) +{ + const ssize_t allocLen = (nMaxChars == -1) + ? strlen(pStr) + 1 + : Min((ssize_t)strlen(pStr), nMaxChars) + 1; + + char* const pOut = new char[allocLen]; + V_strncpy(pOut, pStr, allocLen); + + return pOut; +} //----------------------------------------------------------------------------- // A special high-performance case-insensitive compare function @@ -404,6 +418,57 @@ ssize_t V_StrTrim(char* pStr) return pDest - pStart; } +void V_SplitString2(const char* pString, const char** pSeparators, ssize_t nSeparators, CUtlStringList& outStrings) +{ + outStrings.Purge(); + const char* pCurPos = pString; + + while (true) + { + ssize_t iFirstSeparator = -1; + const char* pFirstSeparator = nullptr; + + for (ssize_t i = 0; i < nSeparators; i++) + { + const char* const pTest = V_stristr(pCurPos, pSeparators[i]); + + if (pTest && (!pFirstSeparator || pTest < pFirstSeparator)) + { + iFirstSeparator = i; + pFirstSeparator = pTest; + } + } + + if (pFirstSeparator) + { + // Split on this separator and continue on. + const ssize_t separatorLen = strlen(pSeparators[iFirstSeparator]); + + if (pFirstSeparator > pCurPos) + { + outStrings.AddToTail(AllocString(pCurPos, pFirstSeparator - pCurPos)); + } + + pCurPos = pFirstSeparator + separatorLen; + } + else + { + // Copy the rest of the string + if (strlen(pCurPos)) + { + outStrings.AddToTail(AllocString(pCurPos, -1)); + } + + return; + } + } +} + +void V_SplitString(const char* pString, const char* pSeparator, CUtlStringList& outStrings) +{ + V_SplitString2(pString, &pSeparator, 1, outStrings); +} + //----------------------------------------------------------------------------- // Purpose: Converts a UTF-8 string into a unicode string //----------------------------------------------------------------------------- diff --git a/src/tier2/CMakeLists.txt b/src/tier2/CMakeLists.txt index bad7615e..3cff30a8 100644 --- a/src/tier2/CMakeLists.txt +++ b/src/tier2/CMakeLists.txt @@ -9,6 +9,7 @@ add_sources( SOURCE_GROUP "Utility" "meshutils.cpp" "renderutils.cpp" "socketcreator.cpp" + "websocket.cpp" ) file( GLOB TIER2_PUBLIC_HEADERS @@ -20,4 +21,12 @@ add_sources( SOURCE_GROUP "Public" end_sources() -target_include_directories( ${PROJECT_NAME} PRIVATE "${ENGINE_SOURCE_DIR}/tier0/" "${ENGINE_SOURCE_DIR}/tier1/" ) +target_include_directories( ${PROJECT_NAME} PRIVATE + "${ENGINE_SOURCE_DIR}/tier0/" + "${ENGINE_SOURCE_DIR}/tier1/" +) + +target_include_directories( ${PROJECT_NAME} PRIVATE + "${THIRDPARTY_SOURCE_DIR}/dirtysdk/include/" + "${THIRDPARTY_SOURCE_DIR}/ea/" +) diff --git a/src/tier2/websocket.cpp b/src/tier2/websocket.cpp new file mode 100644 index 00000000..5957aaf4 --- /dev/null +++ b/src/tier2/websocket.cpp @@ -0,0 +1,332 @@ +//===========================================================================// +// +// Purpose: WebSocket implementation +// +//===========================================================================// +#include "tier2/websocket.h" + +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/proto/protowebsocket.h" + + +//----------------------------------------------------------------------------- +// constructors/destructors +//----------------------------------------------------------------------------- +CWebSocket::CWebSocket() +{ + m_initialized = false; +} + +//----------------------------------------------------------------------------- +// Purpose: initialization of the socket system +//----------------------------------------------------------------------------- +bool CWebSocket::Init(const char* const addressList, const ConnParams_s& params, const char*& initError) +{ + Assert(addressList); + + if (m_initialized) + { + initError = "Already initialized"; + return false; + } + + if (!NetConnStatus('open', 0, NULL, 0)) + { + initError = "Network connection module not initialized"; + return false; + } + + if (!UpdateAddressList(addressList)) + { + initError = (*addressList) + ? "Address list is invalid" + : "Address list is empty"; + + return false; + } + + m_connParams = params; + m_initialized = true; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: shutdown of the socket system +//----------------------------------------------------------------------------- +void CWebSocket::Shutdown() +{ + if (!m_initialized) + return; + + m_initialized = false; + ClearAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: adds comma separated addresses to connection list, returns false if +// connection list is empty +//----------------------------------------------------------------------------- +bool CWebSocket::UpdateAddressList(const char* const addressList) +{ + Assert(addressList); + const CUtlStringList addresses(addressList, ","); + + FOR_EACH_VEC(addresses, i) + { + const ConnContext_s conn(addresses[i]); + m_addressList.AddToTail(conn); + } + + return addresses.Count() != 0; +} + +//----------------------------------------------------------------------------- +// Purpose: update parameters for each connection +//----------------------------------------------------------------------------- +void CWebSocket::UpdateParams(const ConnParams_s& params) +{ + m_connParams = params; + + for (ConnContext_s& conn : m_addressList) + { + if (conn.webSocket) + conn.SetParams(params); + } +} + +//----------------------------------------------------------------------------- +// Purpose: socket state machine +//----------------------------------------------------------------------------- +void CWebSocket::Update() +{ + if (!IsInitialized()) + return; + + const double queryTime = Plat_FloatTime(); + + for (ConnContext_s& conn : m_addressList) + { + if (conn.webSocket) + ProtoWebSocketUpdate(conn.webSocket); + + if (conn.state == CS_CREATE || conn.state == CS_RETRY) + { + conn.Connect(queryTime, m_connParams); + continue; + } + + if (conn.state == CS_CONNECTED || conn.state == CS_LISTENING) + { + conn.Process(queryTime); + continue; + } + + if (conn.state == CS_DESTROYED) + { + if (conn.tryCount > m_connParams.maxRetries) + { + // All retry attempts have been used; mark unavailable for deletion + conn.state = CS_UNAVAIL; + } + else + { + // Mark as retry, this will recreate the socket and reattempt + // the connection + conn.state = CS_RETRY; + } + } + } + + DeleteUnavailable(); +} + +//----------------------------------------------------------------------------- +// Purpose: delete all connections marked unavailable +//----------------------------------------------------------------------------- +void CWebSocket::DeleteUnavailable() +{ + FOR_EACH_VEC_BACK(m_addressList, i) + { + if (m_addressList[i].state == CS_UNAVAIL) + m_addressList.FastRemove(i); + } +} + +//----------------------------------------------------------------------------- +// Purpose: disconnect all connections +//----------------------------------------------------------------------------- +void CWebSocket::DisconnectAll() +{ + for (ConnContext_s& conn : m_addressList) + { + conn.Disconnect(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: reconnect all connections +//----------------------------------------------------------------------------- +void CWebSocket::ReconnectAll() +{ + for (ConnContext_s& conn : m_addressList) + { + conn.Reconnect(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: destroy and purge all connections +//----------------------------------------------------------------------------- +void CWebSocket::ClearAll() +{ + DisconnectAll(); + m_addressList.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: send data to all sockets +//----------------------------------------------------------------------------- +void CWebSocket::SendData(const char* const dataBuf, const int32_t dataSize) +{ + Assert(dataBuf); + Assert(dataSize); + + if (!IsInitialized()) + return; + + for (ConnContext_s& conn : m_addressList) + { + if (conn.state != CS_LISTENING) + continue; + + if (ProtoWebSocketSend(conn.webSocket, dataBuf, dataSize) < 0) + conn.Destroy(); // Reattempt the connection for this socket + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns whether the socket system is enabled and able to run +//----------------------------------------------------------------------------- +bool CWebSocket::IsInitialized() const +{ + return m_initialized; +} + +//----------------------------------------------------------------------------- +// Purpose: connect to a socket +//----------------------------------------------------------------------------- +bool CWebSocket::ConnContext_s::Connect(const double queryTime, const ConnParams_s& params) +{ + if (state == CS_RETRY) + { + const double retryTimeTotal = lastQueryTime + params.retryTime; + const double currTime = Plat_FloatTime(); + + if (retryTimeTotal > currTime) + return false; // Still within retry period + } + + tryCount++; + webSocket = ProtoWebSocketCreate(params.bufSize); + + if (!webSocket) + { + state = CS_UNAVAIL; + return false; + } + + SetParams(params); + + if (ProtoWebSocketConnect(webSocket, address.String()) != NULL) + { + // Failure + Destroy(); + return false; + } + + state = CS_CONNECTED; + lastQueryTime = queryTime; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: check the connection status and destroy if not connected (-1) +//----------------------------------------------------------------------------- +bool CWebSocket::ConnContext_s::Process(const double queryTime) +{ + const int32_t status = ProtoWebSocketStatus(webSocket, 'stat', NULL, 0); + + if (status == -1) + { + Destroy(); + lastQueryTime = queryTime; + + return false; + } + else if (!status) + { + lastQueryTime = queryTime; + return false; + } + + tryCount = 0; + state = CS_LISTENING; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: set parameters for this socket +//----------------------------------------------------------------------------- +void CWebSocket::ConnContext_s::SetParams(const ConnParams_s& params) +{ + Assert(webSocket, "Can't set parameters on a NULL instance!"); + + if (params.timeOut > 0) + ProtoWebSocketControl(webSocket, 'time', params.timeOut, 0, NULL); + + if (params.keepAlive > 0) + ProtoWebSocketControl(webSocket, 'keep', params.keepAlive, 0, NULL); + + ProtoWebSocketControl(webSocket, 'ncrt', params.laxSSL, 0, NULL); + ProtoWebSocketUpdate(webSocket); +} + +//----------------------------------------------------------------------------- +// Purpose: disconnect and mark socket as unavailable for removal +//----------------------------------------------------------------------------- +void CWebSocket::ConnContext_s::Disconnect() +{ + if (webSocket) + { + ProtoWebSocketDisconnect(webSocket); + ProtoWebSocketUpdate(webSocket); + ProtoWebSocketDestroy(webSocket); + + webSocket = nullptr; + } + + state = CS_UNAVAIL; +} + +//----------------------------------------------------------------------------- +// Purpose: reconnect without burning retry attempts +//----------------------------------------------------------------------------- +void CWebSocket::ConnContext_s::Reconnect() +{ + Disconnect(); + state = CS_CREATE; +} + +//----------------------------------------------------------------------------- +// Purpose: reconnect while burning retry attempts +//----------------------------------------------------------------------------- +void CWebSocket::ConnContext_s::Destroy() +{ + Disconnect(); + state = CS_DESTROYED; +} diff --git a/src/vscript/CMakeLists.txt b/src/vscript/CMakeLists.txt index 7697e2cc..f7c28ec1 100644 --- a/src/vscript/CMakeLists.txt +++ b/src/vscript/CMakeLists.txt @@ -15,11 +15,14 @@ add_sources( SOURCE_GROUP "Squirrel_RE" add_sources( SOURCE_GROUP "Squirrel_RE/squirrel" "languages/squirrel_re/squirrel/sqapi.cpp" + "languages/squirrel_re/squirrel/sqdebug.cpp" #"languages/squirrel_re/squirrel/sqcompiler.cpp" "languages/squirrel_re/squirrel/sqfuncstate.cpp" #"languages/squirrel_re/squirrel/sqlexer.cpp" "languages/squirrel_re/squirrel/sqobject.cpp" "languages/squirrel_re/squirrel/sqstring.cpp" + "languages/squirrel_re/squirrel/sqtable.cpp" + "languages/squirrel_re/squirrel/sqmem.cpp" "languages/squirrel_re/squirrel/sqvm.cpp" ) @@ -33,11 +36,14 @@ add_sources( SOURCE_GROUP "Squirrel_RE/include" #"languages/squirrel_re/include/sqcompiler.h" "languages/squirrel_re/include/sqfuncstate.h" #"languages/squirrel_re/include/sqlexer.h" + "languages/squirrel_re/include/sqarray.h" + "languages/squirrel_re/include/squtils.h" "languages/squirrel_re/include/sqobject.h" "languages/squirrel_re/include/sqopcodes.h" "languages/squirrel_re/include/sqstate.h" "languages/squirrel_re/include/sqstdaux.h" "languages/squirrel_re/include/sqstring.h" + "languages/squirrel_re/include/sqtable.h" "languages/squirrel_re/include/squirrel.h" "languages/squirrel_re/include/sqvm.h" ) diff --git a/src/vscript/languages/squirrel_re/include/sqarray.h b/src/vscript/languages/squirrel_re/include/sqarray.h new file mode 100644 index 00000000..140cba3a --- /dev/null +++ b/src/vscript/languages/squirrel_re/include/sqarray.h @@ -0,0 +1,30 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQARRAY_H_ +#define _SQARRAY_H_ + +struct SQArray : public CHAINABLE_OBJ +{ +public: + bool Get(const SQInteger nidx, SQObjectPtr& val) + { + if (nidx >= 0 && nidx < (SQInteger)_values.size()) { + SQObjectPtr& o = _values[nidx]; + val = _realval(o); + return true; + } + else return false; + } + bool Set(const SQInteger nidx, const SQObjectPtr& val) + { + if (nidx >= 0 && nidx < (SQInteger)_values.size()) { + _values[nidx] = val; + return true; + } + else return false; + } + SQInteger Size() const { return _values.size(); } + + SQObjectPtrVec _values; +}; + +#endif //_SQARRAY_H_ diff --git a/src/vscript/languages/squirrel_re/include/sqfuncstate.h b/src/vscript/languages/squirrel_re/include/sqfuncstate.h index 3f2a9128..5c7f6d1f 100644 --- a/src/vscript/languages/squirrel_re/include/sqfuncstate.h +++ b/src/vscript/languages/squirrel_re/include/sqfuncstate.h @@ -7,7 +7,7 @@ struct SQFuncState { - _BYTE gap0[17408]; + _BYTE gap0[17400]; sqvector _instructions; _BYTE gap4418[88]; SQObjectPtr _sourcename; diff --git a/src/vscript/languages/squirrel_re/include/sqobject.h b/src/vscript/languages/squirrel_re/include/sqobject.h index 42b88dbc..c9c2498a 100644 --- a/src/vscript/languages/squirrel_re/include/sqobject.h +++ b/src/vscript/languages/squirrel_re/include/sqobject.h @@ -1,10 +1,15 @@ -#ifndef SQOBJECT_H -#define SQOBJECT_H +/* see copyright notice in squirrel.h */ +#ifndef _SQOBJECT_H_ +#define _SQOBJECT_H_ + #include "squirrel.h" +#include "squtils.h" + +struct SQSharedState; struct SQRefCounted { - SQRefCounted() { _uiRef = 0; _weakref = NULL; } + SQRefCounted() { _uiRef = 0; _weakref = NULL; _globalnum = NULL; unk2 = NULL; } virtual ~SQRefCounted(); virtual void Release() = 0; @@ -13,8 +18,21 @@ struct SQRefCounted SQUnsignedInteger _uiRef; SQObject* _weakref; // this is not an sqobject! + + // index into the global array; see [r5apex + B1C7DE] for assignment + // and [r5apex + B2CAF8] for usage + SQInteger _globalnum; + void* unk2; // unknown }; +struct SQWeakRef : SQRefCounted +{ + void Release(); + SQObject _obj; +}; + +#define _realval(o) (sq_type((o)) != OT_WEAKREF?(SQObject)o:_weakref(o)->_obj) + #define __AddRef(type, unval) \ if(ISREFCOUNTED(type)) \ { \ @@ -27,17 +45,79 @@ struct SQRefCounted unval.pRefCounted->Release(); \ } +#define __ObjAddRef(obj) { \ + SQ_VALIDATE_REF_COUNT( obj ); \ + (obj)->_uiRef++; \ +} + +#define __ObjRelease(obj) { \ + if((obj)) { \ + if( ( -- ((obj)->_uiRef) ) <= 0 ) \ + { \ + SQ_VALIDATE_REF_COUNT( obj ); \ + (obj)->Release(); \ + } \ + (obj) = NULL; \ + } \ +} + +//#define type(obj) ((obj)._type) +// VALVE_BUILD -- define a version of 'type' that is less name-space polluting so that we can #undef +// and redef type around VS 2013 STL header files. +#define sq_type(obj) ((obj)._type) +#define is_delegable(t) (type(t)&SQOBJECT_DELEGABLE) +#define raw_type(obj) _RAW_TYPE((obj)._type) + +#define _bool(obj) (!!(obj)._unVal.nInteger) +#define _integer(obj) ((obj)._unVal.nInteger) +#define _float(obj) ((obj)._unVal.fFloat) +#define _string(obj) ((obj)._unVal.pString) +#define _table(obj) ((obj)._unVal.pTable) +#define _array(obj) ((obj)._unVal.pArray) +#define _closure(obj) ((obj)._unVal.pClosure) +#define _generator(obj) ((obj)._unVal.pGenerator) +#define _nativeclosure(obj) ((obj)._unVal.pNativeClosure) +#define _userdata(obj) ((obj)._unVal.pUserData) +#define _userpointer(obj) ((obj)._unVal.pUserPointer) +#define _thread(obj) ((obj)._unVal.pThread) +#define _funcproto(obj) ((obj)._unVal.pFunctionProto) +#define _class(obj) ((obj)._unVal.pClass) +#define _instance(obj) ((obj)._unVal.pInstance) +#define _delegable(obj) ((SQDelegable *)(obj)._unVal.pDelegable) +#define _weakref(obj) ((obj)._unVal.pWeakRef) +#define _refcounted(obj) ((obj)._unVal.pRefCounted) +#define _rawval(obj) ((obj)._unVal.raw) + +#define _vector3d(obj) ((Vector3D*)&(obj)._pad) + +#define _stringval(obj) (obj)._unVal.pString->_val +#define _userdataval(obj) (obj)._unVal.pUserData->_val + +#define tofloat(num) ((sq_type(num)==OT_INTEGER)?(SQFloat)_integer(num):_float(num)) +#define tointeger(num) ((sq_type(num)==OT_FLOAT)?(SQInteger)_float(num):_integer(num)) +///////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////// struct SQObjectPtr : public SQObject { SQObjectPtr() { _type = OT_NULL; + _pad = NULL; _unVal.pUserPointer = nullptr; } SQObjectPtr(const SQObjectPtr& o) { _type = o._type; + _pad = o._pad; + _unVal = o._unVal; + __AddRef(_type, _unVal); + } + + SQObjectPtr(const SQObject& o) + { + _type = o._type; + _pad = NULL; _unVal = o._unVal; __AddRef(_type, _unVal); } @@ -47,6 +127,7 @@ struct SQObjectPtr : public SQObject assert(pString); _type = OT_STRING; + _pad = NULL; _unVal.pString = pString; __AddRef(_type, _unVal); } @@ -54,18 +135,21 @@ struct SQObjectPtr : public SQObject SQObjectPtr(bool bBool) { _type = OT_BOOL; + _pad = NULL; _unVal.nInteger = bBool ? 1 : 0; } SQObjectPtr(SQInteger nInteger) { _type = OT_INTEGER; + _pad = NULL; _unVal.nInteger = nInteger; } SQObjectPtr(SQFloat fFloat) { _type = OT_FLOAT; + _pad = NULL; _unVal.fFloat = fFloat; } @@ -80,6 +164,7 @@ struct SQObjectPtr : public SQObject SQObjectValue oldVal = _unVal; _type = obj._type; + _pad = obj._pad; _unVal = obj._unVal; __AddRef(_type, _unVal); @@ -87,17 +172,37 @@ struct SQObjectPtr : public SQObject return *this; } - - const char* GetCString() const; - - SQString* GetSQString() const - { - assert(_type == OT_STRING); - - return _unVal.pString; - } }; static SQObjectPtr _null_; -#endif // SQOBJECT_H \ No newline at end of file +struct SQCollectable : public SQRefCounted { + SQCollectable* _next; + SQCollectable* _prev; + SQSharedState* _sharedstate; + virtual void Release() = 0; + virtual void Mark(SQCollectable** chain) = 0; + //void UnMark(); + virtual void Finalize() = 0; + //static void AddToChain(SQCollectable** chain, SQCollectable* c); + //static void RemoveFromChain(SQCollectable** chain, SQCollectable* c); +}; + +#define CHAINABLE_OBJ SQCollectable + +struct SQDelegable : public CHAINABLE_OBJ { + //bool SetDelegate(SQTable* m); + + virtual void DumpToString(SQChar* buf, SQUnsignedInteger len, SQInteger start, SQInteger end) = 0; + + //enum SQMetaMethod; // TODO + virtual bool GetMetaMethod(SQVM* v, /*SQMetaMethod*/ int mm, SQObjectPtr& res) = 0; + SQTable* _delegate; +}; + +typedef sqvector SQObjectPtrVec; +typedef sqvector SQIntVec; + +const SQChar* IdType2Name(const SQObjectType type); + +#endif //_SQOBJECT_H_ diff --git a/src/vscript/languages/squirrel_re/include/sqstate.h b/src/vscript/languages/squirrel_re/include/sqstate.h index 15dd96a1..1adbf85a 100644 --- a/src/vscript/languages/squirrel_re/include/sqstate.h +++ b/src/vscript/languages/squirrel_re/include/sqstate.h @@ -1,37 +1,71 @@ #ifndef SQSTATE_H #define SQSTATE_H #include "squirrel.h" +#include "sqobject.h" //#include "sqcompiler.h" struct SQCompiler; +class CSquirrelVM; + +struct RefTable { + struct RefNode { + SQObjectPtr obj; + SQUnsignedInteger refs; + struct RefNode* next; + }; +private: + SQUnsignedInteger _numofslots; + SQUnsignedInteger _slotused; + RefNode* _nodes; + RefNode* _freelist; + RefNode** _buckets; +}; -#pragma pack(push, 1) struct SQSharedState { _BYTE gap0[16456]; void* _stringtable; // allocated with a size of 17488 in CSquirrelVM::Init - possibly stringtable - _BYTE gap4070[488]; - SQCompiler* _compiler; - uint8_t gap1[320]; + RefTable _refs_table; + uint8_t gap1[138]; + SQTable* _unknowntable0; + uint8_t gap2[140]; void* _compilererrorhandler; void* _printfunc; uint8_t gap4390[33]; - SQChar _contextname[8]; - char gap43b9[135]; - - char* _scratchpad; - int _scratchpadsize; + SQChar _contextname_small_maybe[8]; + char gap43b9[7]; + SQTable* _unknowntable2; + char gap41E0[88]; + SQCompiler* _compiler; + char gap4240[24]; + char* _scratchpad_probablynot; + int _scratchpadsize_probablynot; + char unk[32]; + SQCollectable* _gc_chain; + char pad_4290[289]; + SQChar _contextname[32]; + char pad_43D1[87]; + CSquirrelVM* _scriptvm; + SQInteger _globalnum; + char pad_4434[4]; + int _internal_error; + SQChar* _scratchpad; + SQInteger _scratchpadsize; SQCompiler* GetCompiler() { return _compiler; } -}; -#pragma pack(pop) -static_assert(offsetof(SQSharedState, _compiler) == 0x4238); -static_assert(offsetof(SQSharedState, _printfunc) == 0x4388); + CSquirrelVM* GetScriptVM() + { + return _scriptvm; + } +}; + +//static_assert(offsetof(SQSharedState, _compiler) == 0x4238); +//static_assert(offsetof(SQSharedState, _printfunc) == 0x4388); struct SQBufState { diff --git a/src/vscript/languages/squirrel_re/include/sqstring.h b/src/vscript/languages/squirrel_re/include/sqstring.h index d9a94362..9ed30439 100644 --- a/src/vscript/languages/squirrel_re/include/sqstring.h +++ b/src/vscript/languages/squirrel_re/include/sqstring.h @@ -6,10 +6,10 @@ struct SQString : public SQRefCounted { - char gap18[16]; SQSharedState* _sharedstate; - int _len; - char gap34[12]; + SQInteger _len; + char gap34[4]; + SQHash _hash; SQChar _val[1]; static SQString* Create(SQSharedState* sharedstate, const SQChar* s, SQInteger len) diff --git a/src/vscript/languages/squirrel_re/include/sqtable.h b/src/vscript/languages/squirrel_re/include/sqtable.h new file mode 100644 index 00000000..8b0fd1f3 --- /dev/null +++ b/src/vscript/languages/squirrel_re/include/sqtable.h @@ -0,0 +1,56 @@ +#ifndef SQTABLE_H +#define SQTABLE_H +#include "sqobject.h" +#include "sqstring.h" + +#define hashptr(p) ((SQHash)(((SQInteger)((intp)p)) >> 3)) + +inline SQHash HashObj(const SQObjectPtr& key) // TODO: untested +{ + switch (sq_type(key)) { + case OT_STRING: return _string(key)->_hash; + case OT_FLOAT: return (SQHash)((SQInteger)_float(key)); + case OT_BOOL: case OT_INTEGER: return (SQHash)((SQInteger)_integer(key)); + default: return hashptr(key._unVal.pRefCounted); + } +} + +struct SQTable : public SQDelegable +{ +public: + struct _HashNode + { + _HashNode() { hash = NULL; prev = NULL; next = NULL; } + + SQObjectPtr val; + SQObjectPtr key; + SQInteger hash; + SQShort prev; + SQShort next; + }; + + void _ClearNodes(); + inline _HashNode* _Get(const SQObjectPtr& key, SQHash hash) // TODO: untested + { + _HashNode* n = &_nodes[hash]; + do { + if (_rawval(n->key) == _rawval(key) && sq_type(n->key) == sq_type(key)) { + return n; + } + } while (n->hash != -1); + return NULL; + } + bool Get(const SQObjectPtr& key, SQObjectPtr& val); + + _HashNode* _nodes; + SQInteger _numofnodes; + SQInteger _usednodes; + SQInteger _prev; + SQInteger _next; + SQInteger _last; +}; + +#define SQ_FOR_EACH_TABLE(tableName, iteratorName) \ + for (int iteratorName = 0; iteratorName < tableName->_numofnodes; iteratorName++) + +#endif // SQTABLE_H diff --git a/src/vscript/languages/squirrel_re/include/squirrel.h b/src/vscript/languages/squirrel_re/include/squirrel.h index 48b09195..6b5c5b64 100644 --- a/src/vscript/languages/squirrel_re/include/squirrel.h +++ b/src/vscript/languages/squirrel_re/include/squirrel.h @@ -1,7 +1,38 @@ -#ifndef SQTYPE_H -#define SQTYPE_H +/* +Copyright (c) 2003-2009 Alberto Demichelis + +This software is provided 'as-is', without any +express or implied warranty. In no event will the +authors be held liable for any damages arising from +the use of this software. + +Permission is granted to anyone to use this software +for any purpose, including commercial applications, +and to alter it and redistribute it freely, subject +to the following restrictions: + + 1. The origin of this software must not be + misrepresented; you must not claim that + you wrote the original software. If you + use this software in a product, an + acknowledgment in the product + documentation would be appreciated but is + not required. + + 2. Altered source versions must be plainly + marked as such, and must not be + misrepresented as being the original + software. + + 3. This notice may not be removed or + altered from any source distribution. + +*/ +#ifndef _SQUIRREL_H_ +#define _SQUIRREL_H_ #define SQ_OK (1) +#define SQ_FAIL (0) #define SQ_ERROR (-1) #define SQ_FAILED(res) (res<0) #define SQ_SUCCEEDED(res) (res>=0) @@ -14,10 +45,17 @@ #define GET_FLAG_RAW 0x00000001 #define GET_FLAG_DO_NOT_RAISE_ERROR 0x00000002 -typedef char SQChar; -typedef float SQFloat; +#define _SC(a) a + typedef long SQInteger; typedef unsigned long SQUnsignedInteger; +typedef short SQShort; +typedef unsigned short SQUnsignedShort; + +typedef uint64 SQHash; /*should be the same size of a pointer*/ + +typedef float SQFloat; + typedef void* SQFunctor; typedef SQUnsignedInteger SQBool; @@ -28,32 +66,67 @@ typedef int ScriptDataType_t; typedef struct SQVM* HSQUIRRELVM; struct SQBufState; +typedef char SQChar; struct SQString; #define SQOBJECT_REF_COUNTED 0x08000000 +#define SQOBJECT_NUMERIC 0x04000000 +#define SQOBJECT_DELEGABLE 0x02000000 +#define SQOBJECT_CANBEFALSE 0x01000000 + +#define SQ_MATCHTYPEMASKSTRING (-99999) + +#define _RT_MASK 0x00FFFFFF +#define _RAW_TYPE(type) (type&_RT_MASK) + +#define _RT_NULL 0x00000001 +#define _RT_INTEGER 0x00000002 +#define _RT_FLOAT 0x00000004 +#define _RT_BOOL 0x00000008 +#define _RT_STRING 0x00000010 +#define _RT_TABLE 0x00000020 +#define _RT_ARRAY 0x00000040 +#define _RT_USERDATA 0x00000080 +#define _RT_CLOSURE 0x00000100 +#define _RT_NATIVECLOSURE 0x00000200 +#define _RT_ASSET 0x00000400 +#define _RT_USERPOINTER 0x00000800 +#define _RT_THREAD 0x00001000 +#define _RT_FUNCPROTO 0x00002000 +#define _RT_CLASS 0x00004000 +#define _RT_INSTANCE 0x00008000 +#define _RT_WEAKREF 0x00010000 +#define _RT_VECTOR 0x00040000 +#define _RT_UNIMPLEMENTED 0x00080000 +#define _RT_STRUCTDEF 0x00100000 +#define _RT_STRUCTINSTANCE 0x00200000 +#define _RT_ENTITY 0x00400000 typedef enum tagSQObjectType { - OT_VECTOR = 0x40000, - OT_NULL = 0x1000001, - OT_INTEGER = 0x5000002, - OT_FLOAT = 0x5000004, - OT_BOOL = 0x1000008, - OT_STRING = 0x8000010, - OT_TABLE = 0xA000020, - OT_ARRAY = 0x8000040, - OT_USERDATA = 0xA000080, - OT_CLOSURE = 0x8000100, - OT_NATIVECLOSURE = 0x8000200, - OT_ASSET = 0x8000400, - OT_USERPOINTER = 0x800, - OT_THREAD = 0x8001000, - OT_FUNCPROTO = 0x8002000, - OT_CLASS = 0x8004000, - OT_STRUCT = 0x8200000, - OT_INSTANCE = 0xA008000, - OT_ENTITY = 0xA400000, - OT_WEAKREF = 0x8010000, + OT_NULL = (_RT_NULL|SQOBJECT_CANBEFALSE), + OT_INTEGER = (_RT_INTEGER|SQOBJECT_NUMERIC|SQOBJECT_CANBEFALSE), + OT_FLOAT = (_RT_FLOAT|SQOBJECT_NUMERIC|SQOBJECT_CANBEFALSE), + OT_BOOL = (_RT_BOOL|SQOBJECT_CANBEFALSE), + OT_STRING = (_RT_STRING|SQOBJECT_REF_COUNTED), + OT_TABLE = (_RT_TABLE|SQOBJECT_REF_COUNTED|SQOBJECT_DELEGABLE), + OT_ARRAY = (_RT_ARRAY|SQOBJECT_REF_COUNTED), + OT_VAR = NULL, + OT_USERDATA = (_RT_USERDATA|SQOBJECT_REF_COUNTED|SQOBJECT_DELEGABLE), + OT_CLOSURE = (_RT_CLOSURE|SQOBJECT_REF_COUNTED), + OT_NATIVECLOSURE = (_RT_NATIVECLOSURE|SQOBJECT_REF_COUNTED), + OT_ASSET = (_RT_ASSET|SQOBJECT_REF_COUNTED), + OT_USERPOINTER = _RT_USERPOINTER, + OT_THREAD = (_RT_THREAD|SQOBJECT_REF_COUNTED) , + OT_FUNCPROTO = (_RT_FUNCPROTO|SQOBJECT_REF_COUNTED), //internal usage only + OT_CLASS = (_RT_CLASS|SQOBJECT_REF_COUNTED), + OT_WEAKREF = (_RT_WEAKREF|SQOBJECT_REF_COUNTED), + OT_VECTOR = _RT_VECTOR, + OT_UNIMPLEMENTED = (_RT_UNIMPLEMENTED|SQOBJECT_REF_COUNTED), + OT_STRUCTDEF = (_RT_STRUCTDEF|SQOBJECT_REF_COUNTED), + OT_STRUCTINSTANCE = (_RT_STRUCTINSTANCE|SQOBJECT_REF_COUNTED), + OT_INSTANCE = (_RT_INSTANCE|SQOBJECT_REF_COUNTED|SQOBJECT_DELEGABLE), + OT_ENTITY = (_RT_ENTITY|SQOBJECT_REF_COUNTED|SQOBJECT_DELEGABLE), } SQObjectType; // does the type keep track of references? @@ -84,21 +157,19 @@ typedef union tagSQObjectValue typedef struct tagSQObject { SQObjectType _type; + SQInteger _pad; SQObjectValue _unVal; } SQObject; -template class sqvector -{ -public: - T* _vals; - SQUnsignedInteger _size; - SQUnsignedInteger _allocated; -}; - /////////////////////////////////////////////////////////////////////////////// SQRESULT sq_pushroottable(HSQUIRRELVM v); -SQChar* sq_getstring(HSQUIRRELVM v, SQInteger i); -SQInteger sq_getinteger(HSQUIRRELVM v, SQInteger i); +SQRESULT sq_getinteger(HSQUIRRELVM v, SQInteger idx, SQInteger* i); +SQRESULT sq_getfloat(HSQUIRRELVM v, SQInteger idx, SQFloat* f); +SQRESULT sq_getbool(HSQUIRRELVM v, SQInteger idx, SQBool* b); +SQRESULT sq_getthread(HSQUIRRELVM v, SQInteger idx, HSQUIRRELVM* thread); +SQRESULT sq_getstring(HSQUIRRELVM v, SQInteger idx, const SQChar** c); +SQRESULT sq_get(HSQUIRRELVM v, SQInteger idx); +SQInteger sq_gettop(HSQUIRRELVM v); SQRESULT sq_pushroottable(HSQUIRRELVM v); void sq_pushbool(HSQUIRRELVM v, SQBool b); void sq_pushstring(HSQUIRRELVM v, const SQChar* string, SQInteger len); @@ -115,6 +186,27 @@ SQRESULT sq_call(HSQUIRRELVM v, SQInteger params, SQBool retval, SQBool raiseerr SQRESULT sq_startconsttable(HSQUIRRELVM v); SQRESULT sq_endconsttable(HSQUIRRELVM v); +/*UTILITY MACRO*/ +#define sq_isnumeric(o) ((o)._type&SQOBJECT_NUMERIC) +#define sq_istable(o) ((o)._type==OT_TABLE) +#define sq_isarray(o) ((o)._type==OT_ARRAY) +#define sq_isfunction(o) ((o)._type==OT_FUNCPROTO) +#define sq_isclosure(o) ((o)._type==OT_CLOSURE) +#define sq_isgenerator(o) ((o)._type==OT_GENERATOR) +#define sq_isnativeclosure(o) ((o)._type==OT_NATIVECLOSURE) +#define sq_isstring(o) ((o)._type==OT_STRING) +#define sq_isinteger(o) ((o)._type==OT_INTEGER) +#define sq_isfloat(o) ((o)._type==OT_FLOAT) +#define sq_isuserpointer(o) ((o)._type==OT_USERPOINTER) +#define sq_isuserdata(o) ((o)._type==OT_USERDATA) +#define sq_isthread(o) ((o)._type==OT_THREAD) +#define sq_isnull(o) ((o)._type==OT_NULL) +#define sq_isclass(o) ((o)._type==OT_CLASS) +#define sq_isinstance(o) ((o)._type==OT_INSTANCE) +#define sq_isbool(o) ((o)._type==OT_BOOL) +#define sq_isweakref(o) ((o)._type==OT_WEAKREF) +#define sq_type(o) ((o)._type) + /* ==== SQUIRREL ======================================================================================================================================================== */ inline SQRESULT(*v_sq_pushroottable)(HSQUIRRELVM v); inline void(*v_sq_pushbool)(HSQUIRRELVM v, SQBool b); @@ -128,6 +220,7 @@ inline SQRESULT(*v_sq_arrayappend)(HSQUIRRELVM v, SQInteger idx); inline SQRESULT(*v_sq_pushstructure)(HSQUIRRELVM v, const SQChar* name, const SQChar* member, const SQChar* codeclass1, const SQChar* codeclass2); inline SQRESULT(*v_sq_compilebuffer)(HSQUIRRELVM v, SQBufState* bufferstate, const SQChar* buffer, SQInteger level); inline SQRESULT(*v_sq_call)(HSQUIRRELVM v, SQInteger params, SQBool retval, SQBool raiseerror); +inline SQRESULT(*v_sq_get)(HSQUIRRELVM v, SQInteger idx); inline SQRESULT (*v_sq_startconsttable)(HSQUIRRELVM v); inline SQRESULT (*v_sq_endconsttable)(HSQUIRRELVM v); @@ -151,6 +244,7 @@ class VSquirrelAPI : public IDetour LogFunAdr("sq_pushstructure", v_sq_pushstructure); LogFunAdr("sq_compilebuffer", v_sq_compilebuffer); LogFunAdr("sq_call", v_sq_call); + LogFunAdr("sq_get", v_sq_get); LogFunAdr("sq_startconsttable", v_sq_startconsttable); LogFunAdr("sq_endconsttable", v_sq_endconsttable); @@ -171,6 +265,7 @@ class VSquirrelAPI : public IDetour g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 55 41 54 41 55 41 56 41 57 48 8B EC 48 83 EC 60 48 8B 59 60").GetPtr(v_sq_pushstructure); g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 50 41 8B E9 49 8B F8").GetPtr(v_sq_compilebuffer); g_GameDll.FindPatternSIMD("4C 8B DC 49 89 5B 08 49 89 6B 10 49 89 73 18 57 48 83 EC 50 8B F2").GetPtr(v_sq_call); + g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 40 48 8B F9 8B 49 78").GetPtr(v_sq_get); g_GameDll.FindPatternSIMD("8B 51 78 4C 8B 49 60 44 8B C2 49 C1 E0 04 4C 03 81 ?? ?? ?? ?? 8D 42 01 89 41 78 41 F7 81 ?? ?? ?? ?? ?? ?? ?? ?? 74 0A 49 8B 81 ?? ?? ?? ?? FF 40 08 41 F7 00 ?? ?? ?? ?? 41 0F 10 81 ?? ?? ?? ?? 74 15").GetPtr(v_sq_startconsttable); g_GameDll.FindPatternSIMD("8B 41 78 45 33 C0 FF C8 8B D0 89 41 78 48 C1 E2 04 48 03 91 ?? ?? ?? ?? 8B 02 48 C7 02 ?? ?? ?? ?? 25 ?? ?? ?? ?? 74 15").GetPtr(v_sq_endconsttable); @@ -182,4 +277,4 @@ class VSquirrelAPI : public IDetour virtual void Detour(const bool bAttach) const; }; /////////////////////////////////////////////////////////////////////////////// -#endif // SQTYPE_H +#endif // _SQUIRREL_H_ diff --git a/src/vscript/languages/squirrel_re/include/squtils.h b/src/vscript/languages/squirrel_re/include/squtils.h new file mode 100644 index 00000000..c792fba1 --- /dev/null +++ b/src/vscript/languages/squirrel_re/include/squtils.h @@ -0,0 +1,25 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQUTILS_H_ +#define _SQUTILS_H_ + +template class sqvector +{ +private: + SQInteger _globalnum; + +public: + T& top() const { return _vals[_size - 1]; } + inline SQUnsignedInteger size() const { return _size; } + bool empty() const { return (_size <= 0); } + + SQUnsignedInteger capacity() { return _allocated; } + inline T& back() const { return _vals[_size - 1]; } + inline T& operator[](SQUnsignedInteger pos) const { return _vals[pos]; } + T* _vals; + +private: + SQUnsignedInteger _size; + SQUnsignedInteger _allocated; +}; + +#endif //_SQUTILS_H_ diff --git a/src/vscript/languages/squirrel_re/include/sqvm.h b/src/vscript/languages/squirrel_re/include/sqvm.h index a5c43cad..7f82b708 100644 --- a/src/vscript/languages/squirrel_re/include/sqvm.h +++ b/src/vscript/languages/squirrel_re/include/sqvm.h @@ -3,6 +3,8 @@ #include "sqstate.h" #include "sqobject.h" +class CSquirrelVM; + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- @@ -14,45 +16,32 @@ enum class SQCONTEXT : SQInteger NONE }; -struct SQVM +struct SQVM : public CHAINABLE_OBJ { - SQVM* GetVTable() const - { - return _vftable; - } - SQCONTEXT GetContext() const - { - return _contextidx; - } - - eDLL_T GetNativeContext() const - { - return (eDLL_T)GetContext(); - } + void PrintObjVal(const SQObject* oin, SQObject* oout); // push sqobjectptr on to the stack - inline void Push(const SQObjectPtr& o) - { - this->_stack._vals[_top++] = o; - } + void Push(const SQObjectPtr& o); + + SQObjectPtr& Top(); + SQObjectPtr& PopGet(); + SQObjectPtr& GetUp(SQInteger n); + SQObjectPtr& GetAt(SQInteger n); + + CSquirrelVM* GetScriptVM(); + SQChar* GetContextName(); + SQCONTEXT GetContext(); + eDLL_T GetNativeContext(); // ================================= // - SQVM* _vftable; - _BYTE gap000[16]; - SQCONTEXT _contextidx; - _BYTE gap001[8]; - _BYTE gap002[4]; - void* _ncvftable; - void* _table; - _BYTE gap003[14]; + _BYTE gap1C[8]; void* _callstack; int _unk; int _bottom; SQObjectPtr* _stackbase; SQSharedState* _sharedstate; - char gap004[16]; + char gap68[16]; int _top; - __int64 gap80; sqvector _stack; char gap_98[24]; SQObjectPtr temp_reg; @@ -69,6 +58,8 @@ struct SQVM static_assert(offsetof(SQVM, _top) == 0x78); static_assert(offsetof(SQVM, _nnativecalls) == 0x130); +inline SQObjectPtr& stack_get(HSQUIRRELVM v, SQInteger idx) { return ((idx >= 0) ? (v->_stackbase[idx-1]) : (v->GetUp(idx))); } + /* ==== SQUIRREL ======================================================================================================================================================== */ inline SQRESULT(*v_SQVM_PrintFunc)(HSQUIRRELVM v, SQChar* fmt, ...); inline SQRESULT(*v_SQVM_sprintf)(HSQUIRRELVM v, SQInteger a2, SQInteger a3, SQInteger* nStringSize, SQChar** ppString); @@ -78,15 +69,12 @@ inline void(*v_SQVM_CompileError)(HSQUIRRELVM v, const SQChar* pszError, const S inline void(*v_SQVM_LogicError)(SQBool bPrompt); inline SQInteger(*v_SQVM_ScriptError)(const SQChar* pszFormat, ...); inline SQInteger(*v_SQVM_RaiseError)(HSQUIRRELVM v, const SQChar* pszFormat, ...); -inline SQBool(*v_SQVM_ThrowError)(__int64 a1, HSQUIRRELVM v); +inline void(*v_SQVM_PrintObjVal)(HSQUIRRELVM v, const SQObject* oin, SQObject* oout); SQRESULT SQVM_PrintFunc(HSQUIRRELVM v, SQChar* fmt, ...); SQRESULT SQVM_sprintf(HSQUIRRELVM v, SQInteger a2, SQInteger a3, SQInteger* nStringSize, SQChar** ppString); void SQVM_CompileError(HSQUIRRELVM v, const SQChar* pszError, const SQChar* pszFile, SQUnsignedInteger nLine, SQInteger nColumn); -const SQChar* SQVM_GetContextName(SQCONTEXT context); -const SQCONTEXT SQVM_GetContextIndex(HSQUIRRELVM v); - /////////////////////////////////////////////////////////////////////////////// class VSquirrelVM : public IDetour { @@ -100,7 +88,7 @@ class VSquirrelVM : public IDetour LogFunAdr("SQVM_LogicError", v_SQVM_LogicError); LogFunAdr("SQVM_ScriptError", v_SQVM_ScriptError); LogFunAdr("SQVM_RaiseError", v_SQVM_RaiseError); - LogFunAdr("SQVM_ThrowError", v_SQVM_ThrowError); + LogFunAdr("SQVM_PrintObjVal", v_SQVM_PrintObjVal); } virtual void GetFun(void) const { @@ -112,7 +100,7 @@ class VSquirrelVM : public IDetour g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 81 EC ?? ?? ?? ?? 48 8B D9 4C 8B F2").GetPtr(v_SQVM_CompileError); g_GameDll.FindPatternSIMD("E9 ?? ?? ?? ?? F7 D2").FollowNearCallSelf().GetPtr(v_SQVM_ScriptError); g_GameDll.FindPatternSIMD("48 89 54 24 ?? 4C 89 44 24 ?? 4C 89 4C 24 ?? 53 56 57 48 83 EC 40").GetPtr(v_SQVM_RaiseError); - g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? BB ?? ?? ?? ?? 8B C3").FollowNearCallSelf().GetPtr(v_SQVM_ThrowError); + g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ? 55 41 54 41 55 41 56 41 57 48 8B EC 48 83 EC 50 45 33 ED").GetPtr(v_SQVM_PrintObjVal); } virtual void GetVar(void) const { } virtual void GetCon(void) const { } diff --git a/src/vscript/languages/squirrel_re/squirrel/sqapi.cpp b/src/vscript/languages/squirrel_re/squirrel/sqapi.cpp index 0b2f7655..707df29f 100644 --- a/src/vscript/languages/squirrel_re/squirrel/sqapi.cpp +++ b/src/vscript/languages/squirrel_re/squirrel/sqapi.cpp @@ -7,18 +7,91 @@ #include "core/stdafx.h" #include "squirrel.h" #include "sqvm.h" +#include "sqarray.h" #include "sqstring.h" //--------------------------------------------------------------------------------- -SQChar* sq_getstring(HSQUIRRELVM v, SQInteger i) +bool sq_aux_gettypedarg(HSQUIRRELVM v, SQInteger idx, SQObjectType type, SQObjectPtr** o) { - return v->_stackbase[i]._unVal.pString->_val; + *o = &stack_get(v, idx); + if (sq_type(**o) != type) { + SQObjectPtr oval; + v->PrintObjVal(*o, &oval); + v_SQVM_RaiseError(v, _SC("wrong argument type, expected '%s' got '%.50s'"), IdType2Name(type), _stringval(oval)); + return false; + } + return true; } //--------------------------------------------------------------------------------- -SQInteger sq_getinteger(HSQUIRRELVM v, SQInteger i) +#define _GETSAFE_OBJ(v,idx,type,o) { if(!sq_aux_gettypedarg(v,idx,type,&o)) return SQ_ERROR; } + +#define sq_aux_paramscheck(v,count) \ +{ \ + if(sq_gettop(v) < count){ v_SQVM_RaiseError(v, _SC("not enough params in the stack")); return SQ_ERROR; }\ +} + +//--------------------------------------------------------------------------------- +SQRESULT sq_getinteger(HSQUIRRELVM v, SQInteger idx, SQInteger* i) { - return v->_stackbase[i]._unVal.nInteger; + SQObjectPtr& o = stack_get(v, idx); + if (sq_isnumeric(o)) { + *i = tointeger(o); + return SQ_OK; + } + return SQ_ERROR; +} + +//--------------------------------------------------------------------------------- +SQRESULT sq_getfloat(HSQUIRRELVM v, SQInteger idx, SQFloat* f) +{ + SQObjectPtr& o = stack_get(v, idx); + if (sq_isnumeric(o)) { + *f = tofloat(o); + return SQ_OK; + } + return SQ_ERROR; +} + +//--------------------------------------------------------------------------------- +SQRESULT sq_getbool(HSQUIRRELVM v, SQInteger idx, SQBool* b) +{ + SQObjectPtr& o = stack_get(v, idx); + if (sq_isbool(o)) { + *b = _integer(o); + return SQ_OK; + } + return SQ_ERROR; +} + +//--------------------------------------------------------------------------------- +SQRESULT sq_getthread(HSQUIRRELVM v, SQInteger idx, HSQUIRRELVM* thread) +{ + SQObjectPtr* o = NULL; + _GETSAFE_OBJ(v, idx, OT_THREAD, o); + *thread = _thread(*o); + return SQ_OK; +} + +//--------------------------------------------------------------------------------- +SQRESULT sq_getstring(HSQUIRRELVM v, SQInteger idx, const SQChar** c) +{ + SQObjectPtr* o = NULL; + _GETSAFE_OBJ(v, idx, OT_STRING, o); + *c = _stringval(*o); + return SQ_OK; +} + +//--------------------------------------------------------------------------------- +SQRESULT sq_get(HSQUIRRELVM v, SQInteger idx) +{ + return v_sq_get(v, idx); +} + +//--------------------------------------------------------------------------------- +SQInteger sq_gettop(HSQUIRRELVM v) +{ + return (v->_top - v->_bottom); } //--------------------------------------------------------------------------------- diff --git a/src/vscript/languages/squirrel_re/squirrel/sqdebug.cpp b/src/vscript/languages/squirrel_re/squirrel/sqdebug.cpp new file mode 100644 index 00000000..e68caa3b --- /dev/null +++ b/src/vscript/languages/squirrel_re/squirrel/sqdebug.cpp @@ -0,0 +1,10 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqvm.h" +#include "sqstring.h" + +void SQVM::PrintObjVal(const SQObject* oin, SQObject* oout) +{ + v_SQVM_PrintObjVal(this, oin, oout); +} diff --git a/src/vscript/languages/squirrel_re/squirrel/sqmem.cpp b/src/vscript/languages/squirrel_re/squirrel/sqmem.cpp new file mode 100644 index 00000000..ad984e04 --- /dev/null +++ b/src/vscript/languages/squirrel_re/squirrel/sqmem.cpp @@ -0,0 +1,12 @@ +/* + see copyright notice in squirrel.h +*/ +//#include "sqpcheader.h" +// TODO: commented because we need to hook up globals '14D4F3900' and '14D4F38F8', which is used for +// the global memory management of the squirrel implementation in this game + +//void *sq_vm_malloc(SQUnsignedInteger size){ return malloc(size); } + +//void *sq_vm_realloc(void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size){ return realloc(p, size); } + +//void sq_vm_free(void *p, SQUnsignedInteger size){ free(p); } diff --git a/src/vscript/languages/squirrel_re/squirrel/sqobject.cpp b/src/vscript/languages/squirrel_re/squirrel/sqobject.cpp index 1f7e574b..f7ebd0bb 100644 --- a/src/vscript/languages/squirrel_re/squirrel/sqobject.cpp +++ b/src/vscript/languages/squirrel_re/squirrel/sqobject.cpp @@ -2,6 +2,39 @@ #include "sqobject.h" #include "sqstring.h" +const SQChar* IdType2Name(const SQObjectType type) +{ + switch (type) + { + case OT_NULL:return _SC("null"); + case OT_INTEGER:return _SC("int"); + case OT_FLOAT:return _SC("float"); + case OT_BOOL:return _SC("bool"); + case OT_STRING:return _SC("string"); + case OT_TABLE:return _SC("table"); + case OT_VAR:return _SC("var"); + case OT_ARRAY:return _SC("array"); + case OT_CLOSURE: return _SC("function"); + case OT_THREAD: return _SC("thread"); + case OT_FUNCPROTO: return _SC("function"); + case OT_UNIMPLEMENTED: return _SC("unimplemented function"); + case OT_CLASS: return _SC("class"); + case OT_INSTANCE: return _SC("instance"); + case OT_WEAKREF: return _SC("weakref"); + case OT_VECTOR: return _SC("vector"); + case OT_ASSET: return _SC("asset"); + case OT_STRUCTDEF: return _SC("structdef"); + case OT_STRUCTINSTANCE: return _SC("struct instance"); + case OT_ENTITY: return _SC("entity"); + default: + const int ival = (int)type; + if (ival == SQOBJECT_NUMERIC) return _SC("float or int"); + if (ival == _RT_USERDATA || ival == _RT_USERPOINTER) return _SC("userdata"); + + return NULL; + } +} + SQRefCounted::~SQRefCounted() { if (_weakref) { @@ -9,13 +42,3 @@ SQRefCounted::~SQRefCounted() _weakref->_unVal.pRefCounted = NULL; } } - -const char* SQObjectPtr::GetCString() const -{ - //assert(_type == OT_STRING); - - if (_type == OT_STRING) - return _unVal.pString->_val; - else - return "(unknown)"; -} \ No newline at end of file diff --git a/src/vscript/languages/squirrel_re/squirrel/sqtable.cpp b/src/vscript/languages/squirrel_re/squirrel/sqtable.cpp new file mode 100644 index 00000000..2eb882a4 --- /dev/null +++ b/src/vscript/languages/squirrel_re/squirrel/sqtable.cpp @@ -0,0 +1,19 @@ +#include "sqtable.h" + +void SQTable::_ClearNodes() +{ + for (SQInteger i = 0; i < _numofnodes; i++) { _nodes[i].key = _null_; _nodes[i].val = _null_; } +} + +// TODO: untested +bool SQTable::Get(const SQObjectPtr& key, SQObjectPtr& val) +{ + if (sq_type(key) == OT_NULL) + return false; + _HashNode* n = _Get(key, HashObj(key) & (_numofnodes - 1)); + if (n) { + val = _realval(n->val); + return true; + } + return false; +} diff --git a/src/vscript/languages/squirrel_re/squirrel/sqvm.cpp b/src/vscript/languages/squirrel_re/squirrel/sqvm.cpp index ec54c900..ae478ea1 100644 --- a/src/vscript/languages/squirrel_re/squirrel/sqvm.cpp +++ b/src/vscript/languages/squirrel_re/squirrel/sqvm.cpp @@ -147,15 +147,13 @@ SQRESULT SQVM_sprintf(HSQUIRRELVM v, SQInteger a2, SQInteger a3, SQInteger* nStr //--------------------------------------------------------------------------------- void SQVM_CompileError(HSQUIRRELVM v, const SQChar* pszError, const SQChar* pszFile, SQUnsignedInteger nLine, SQInteger nColumn) { - static SQCONTEXT context{}; static char szContextBuf[256]{}; - - context = v->GetContext(); v_SQVM_GetErrorLine(pszFile, nLine, szContextBuf, sizeof(szContextBuf) - 1); - Error(static_cast(context), NO_ERROR, "%s SCRIPT COMPILE ERROR: %s\n", SQVM_GetContextName(context), pszError); - Error(static_cast(context), NO_ERROR, " -> %s\n\n", szContextBuf); - Error(static_cast(context), NO_ERROR, "%s line [%d] column [%d]\n", pszFile, nLine, nColumn); + const eDLL_T context = v->GetNativeContext(); + Error(context, NO_ERROR, "%s SCRIPT COMPILE ERROR: %s\n", v->GetContextName(), pszError); + Error(context, NO_ERROR, " -> %s\n\n", szContextBuf); + Error(context, NO_ERROR, "%s line [%d] column [%d]\n", pszFile, nLine, nColumn); } //--------------------------------------------------------------------------------- @@ -176,41 +174,31 @@ void SQVM_LogicError(SQBool bPrompt) v_SQVM_LogicError(bPrompt); } -//--------------------------------------------------------------------------------- -// Purpose: Returns the VM name by context -// Input : context - -// Output : const SQChar* -//--------------------------------------------------------------------------------- -const SQChar* SQVM_GetContextName(SQCONTEXT context) +void SQVM::Push(const SQObjectPtr& o) { _stack[_top++] = o; } +SQObjectPtr& SQVM::Top() { return _stack[_top - 1]; } +SQObjectPtr& SQVM::PopGet() { return _stack[--_top]; } +SQObjectPtr& SQVM::GetUp(SQInteger n) { return _stack[_top + n]; } +SQObjectPtr& SQVM::GetAt(SQInteger n) { return _stack[n]; } + +#include "vscript/languages/squirrel_re/vsquirrel.h" +CSquirrelVM* SQVM::GetScriptVM() { - switch (context) - { - case SQCONTEXT::SERVER: - return "SERVER"; - case SQCONTEXT::CLIENT: - return "CLIENT"; - case SQCONTEXT::UI: - return "UI"; - default: - return nullptr; - } + return _sharedstate->GetScriptVM(); } -//--------------------------------------------------------------------------------- -// Purpose: Returns the VM context by name -// Input : *sqvm - -// Output : const SQCONTEXT* -//--------------------------------------------------------------------------------- -const SQCONTEXT SQVM_GetContextIndex(HSQUIRRELVM v) +SQChar* SQVM::GetContextName() { - if (strcmp(v->_sharedstate->_contextname, "SERVER") == 0) - return SQCONTEXT::SERVER; - if (strcmp(v->_sharedstate->_contextname, "CLIENT") == 0) - return SQCONTEXT::CLIENT; - if (strcmp(v->_sharedstate->_contextname, "UI") == 0) - return SQCONTEXT::UI; + return _sharedstate->_contextname; +} - return SQCONTEXT::NONE; +SQCONTEXT SQVM::GetContext() +{ + return GetScriptVM()->GetContext(); +} + +eDLL_T SQVM::GetNativeContext() +{ + return (eDLL_T)GetContext(); } //--------------------------------------------------------------------------------- diff --git a/src/vscript/languages/squirrel_re/vsquirrel.cpp b/src/vscript/languages/squirrel_re/vsquirrel.cpp index 416f563f..a7b6fb3d 100644 --- a/src/vscript/languages/squirrel_re/vsquirrel.cpp +++ b/src/vscript/languages/squirrel_re/vsquirrel.cpp @@ -13,6 +13,11 @@ void(*ServerScriptRegister_Callback)(CSquirrelVM* s) = nullptr; void(*ClientScriptRegister_Callback)(CSquirrelVM* s) = nullptr; void(*UiScriptRegister_Callback)(CSquirrelVM* s) = nullptr; +// Callbacks for registering script enums. +void(*ServerScriptRegisterEnum_Callback)(CSquirrelVM* const s) = nullptr; +void(*ClientScriptRegisterEnum_Callback)(CSquirrelVM* const s) = nullptr; +void(*UIScriptRegisterEnum_Callback)(CSquirrelVM* const s) = nullptr; + // Admin panel functions, NULL on client only builds. void(*CoreServerScriptRegister_Callback)(CSquirrelVM* s) = nullptr; void(*AdminPanelScriptRegister_Callback)(CSquirrelVM* s) = nullptr; @@ -96,6 +101,16 @@ SQRESULT CSquirrelVM::RegisterConstant(const SQChar* name, SQInteger value) return CSquirrelVM__RegisterConstant(this, name, value); } +//--------------------------------------------------------------------------------- +// Purpose: executes a code callback +// Input : *name - +// Output : true on success, false otherwise +//--------------------------------------------------------------------------------- +bool CSquirrelVM::ExecuteCodeCallback(const SQChar* const callbackName) +{ + return CSquirrelVM__ExecuteCodeCallback(this, callbackName); +} + //--------------------------------------------------------------------------------- // Purpose: registers a code function // Input : *s - @@ -159,7 +174,7 @@ void CSquirrelVM::CompileModScripts() RSON::Node_t* rson = mod->LoadScriptCompileList(); if (!rson) - Error(GetVM()->GetNativeContext(), NO_ERROR, + Error(GetNativeContext(), NO_ERROR, "%s: Failed to load RSON file '%s'\n", __FUNCTION__, mod->GetScriptCompileListPath().Get()); @@ -197,7 +212,7 @@ void CSquirrelVM::CompileModScripts() scriptPathArray[j] = pszScriptPath; } - switch (GetVM()->GetContext()) + switch (GetContext()) { case SQCONTEXT::SERVER: { diff --git a/src/vscript/languages/squirrel_re/vsquirrel.h b/src/vscript/languages/squirrel_re/vsquirrel.h index 2a45d061..6928195a 100644 --- a/src/vscript/languages/squirrel_re/vsquirrel.h +++ b/src/vscript/languages/squirrel_re/vsquirrel.h @@ -1,14 +1,18 @@ #ifndef VSQUIRREL_H #define VSQUIRREL_H +#include "tier1/utlmap.h" +#include "tier1/utlhash.h" +#include "tier1/utlbuffer.h" + #include "vscript/languages/squirrel_re/include/squirrel.h" #include "vscript/languages/squirrel_re/include/sqstate.h" #include "vscript/languages/squirrel_re/include/sqvm.h" #include "vscript/ivscript.h" + #include "rtech/rson.h" #define MAX_PRECOMPILED_SCRIPTS 1024 -#pragma pack(push, 4) class CSquirrelVM { public: @@ -21,27 +25,42 @@ public: SQRESULT RegisterFunction(const SQChar* scriptname, const SQChar* nativename, const SQChar* helpstring, const SQChar* returntype, const SQChar* parameters, void* functor); SQRESULT RegisterConstant(const SQChar* name, SQInteger value); - FORCEINLINE HSQUIRRELVM GetVM() const { return m_sqVM; } + bool ExecuteCodeCallback(const SQChar* const callbackName); + + FORCEINLINE HSQUIRRELVM GetVM() const { return m_hVM; } FORCEINLINE SQCONTEXT GetContext() const { return m_iContext; } + FORCEINLINE eDLL_T GetNativeContext() const { return (eDLL_T)GetContext(); } private: - SQChar pad0[0x8]; - HSQUIRRELVM m_sqVM; - SQChar pad1[0x8]; - SQInteger m_nFlags; - SQChar pad2[4]; - SQChar pad3[16]; - SQChar pad4[4]; + bool unk_00; + SQChar pad0[7]; + HSQUIRRELVM m_hVM; + void* m_hDbg; + SQObjectPtr m_ErrorString; + SQChar pad3[8]; SQInteger m_nTick; - SQCONTEXT m_iContext; // 0x38 - void* m_pCompareFunc; + int unk_34; + SQCONTEXT m_iContext; + SQChar pad6[4]; + CUtlMap m_TypeMap; + CUtlBuffer* m_pBuffer; + CUtlMap m_PtrMap; + bool unk_A8; + int64_t unk_B0; + int64_t unk_B8; + bool unk_C0; + int64_t unk_C8; + int64_t unk_D0; }; -#pragma pack(pop) extern void(*ServerScriptRegister_Callback)(CSquirrelVM* s); extern void(*ClientScriptRegister_Callback)(CSquirrelVM* s); extern void(*UiScriptRegister_Callback)(CSquirrelVM* s); +extern void(*ServerScriptRegisterEnum_Callback)(CSquirrelVM* const s); +extern void(*ClientScriptRegisterEnum_Callback)(CSquirrelVM* const s); +extern void(*UIScriptRegisterEnum_Callback)(CSquirrelVM* const s); + extern void(*CoreServerScriptRegister_Callback)(CSquirrelVM* s); extern void(*AdminPanelScriptRegister_Callback)(CSquirrelVM* s); @@ -59,6 +78,8 @@ inline bool(*CSquirrelVM__PrecompileClientScripts)(CSquirrelVM* vm, SQCONTEXT co #ifndef CLIENT_DLL inline bool(*CSquirrelVM__PrecompileServerScripts)(CSquirrelVM* vm, SQCONTEXT context, char** scriptArray, int scriptCount); #endif +inline bool(*CSquirrelVM__ExecuteCodeCallback)(CSquirrelVM* s, const SQChar* callbackName); +inline bool(*CSquirrelVM__ThrowError)(CSquirrelVM* vm, HSQUIRRELVM v); #ifndef CLIENT_DLL inline CSquirrelVM* g_pServerScript; @@ -70,24 +91,34 @@ inline CSquirrelVM* g_pUIScript; #endif // !DEDICATED #define DEFINE_SCRIPTENUM_NAMED(s, enumName, startValue, ...) \ - do { \ - HSQUIRRELVM const v = s->GetVM(); \ - const eDLL_T context = static_cast(s->GetContext());\ - sq_startconsttable(v); \ - sq_pushstring(v, enumName, -1); \ - sq_newtable(v); \ - const char* const enumFields[] = { __VA_ARGS__ }; \ - int enumValue = startValue; \ - for (int i = 0; i < V_ARRAYSIZE(enumFields); i++) { \ - sq_pushstring(v, enumFields[i], -1); \ - sq_pushinteger(v, enumValue++); \ - if (sq_newslot(v, -3) < 0) \ - Error(context, EXIT_FAILURE, "Error adding entry '%s' for enum '%s'.", enumFields[i], enumName); \ - } \ + HSQUIRRELVM const v = s->GetVM(); \ + const eDLL_T context = static_cast(s->GetContext());\ + sq_startconsttable(v); \ + sq_pushstring(v, enumName, -1); \ + sq_newtable(v); \ + const char* const enumFields[] = { __VA_ARGS__ }; \ + int enumValue = startValue; \ + for (int i = 0; i < V_ARRAYSIZE(enumFields); i++) { \ + sq_pushstring(v, enumFields[i], -1); \ + sq_pushinteger(v, enumValue++); \ if (sq_newslot(v, -3) < 0) \ - Error(context, EXIT_FAILURE, "Error adding enum '%s' to const table.", enumName); \ - sq_endconsttable(v); \ - } while (0) + Error(context, EXIT_FAILURE, "Error adding entry '%s' for enum '%s'.", enumFields[i], enumName); \ + } \ + if (sq_newslot(v, -3) < 0) \ + Error(context, EXIT_FAILURE, "Error adding enum '%s' to const table.", enumName); \ + sq_endconsttable(v); \ + +// Use this to return from any script func +#define SCRIPT_CHECK_AND_RETURN(v, val) \ + { \ + SQSharedState* const sharedState = v->_sharedstate; \ + if (sharedState->_internal_error) { \ + \ + CSquirrelVM__ThrowError(sharedState->_scriptvm, v); \ + return SQ_ERROR; \ + } \ + return val; \ + } /////////////////////////////////////////////////////////////////////////////// class VSquirrel : public IDetour @@ -105,6 +136,8 @@ class VSquirrel : public IDetour #ifndef DEDICATED LogFunAdr("CSquirrelVM::PrecompileClientScripts", CSquirrelVM__PrecompileClientScripts); #endif // !DEDICATED + LogFunAdr("CSquirrelVM::ExecuteCodeCallback", CSquirrelVM__ExecuteCodeCallback); + LogFunAdr("CSquirrelVM::ThrowError", CSquirrelVM__ThrowError); } virtual void GetFun(void) const { @@ -122,6 +155,8 @@ class VSquirrel : public IDetour // cl/ui scripts.rson compiling g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? 44 0F B6 F0 48 85 DB").FollowNearCallSelf().GetPtr(CSquirrelVM__PrecompileClientScripts); #endif + g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? C6 47 1C 01").FollowNearCallSelf().GetPtr(CSquirrelVM__ExecuteCodeCallback); + g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? BB ?? ?? ?? ?? 8B C3").FollowNearCallSelf().GetPtr(CSquirrelVM__ThrowError); } virtual void GetVar(void) const { } virtual void GetCon(void) const { } diff --git a/src/vscript/vscript.cpp b/src/vscript/vscript.cpp index 39ca5e03..8fcec3b4 100644 --- a/src/vscript/vscript.cpp +++ b/src/vscript/vscript.cpp @@ -12,6 +12,8 @@ #include "game/shared/vscript_shared.h" #include "pluginsystem/modsystem.h" +static const char* s_scriptContextNames[] = { "SERVER", "CLIENT", "UI", "NONE" }; + //--------------------------------------------------------------------------------- // Purpose: Returns the script VM pointer by context // Input : context - @@ -130,28 +132,22 @@ SQBool Script_PrecompileClientScripts(CSquirrelVM* vm) //--------------------------------------------------------------------------------- void Script_Execute(const SQChar* code, const SQCONTEXT context) { - if (!ThreadInMainThread()) - { - const string scode(code); - g_TaskQueue.Dispatch([scode, context]() - { - Script_Execute(scode.c_str(), context); - }, 0); - - return; // Only run in main thread. - } + Assert(context != SQCONTEXT::NONE); + Assert(ThreadInMainOrServerFrameThread()); CSquirrelVM* s = Script_GetScriptHandle(context); + const char* const contextName = s_scriptContextNames[(int)context]; + if (!s) { - Error(eDLL_T::ENGINE, NO_ERROR, "Attempted to run %s script with no handle to VM\n", SQVM_GetContextName(context)); + Error(eDLL_T::ENGINE, NO_ERROR, "Attempted to run %s script with no handle to VM\n", contextName); return; } HSQUIRRELVM v = s->GetVM(); if (!v) { - Error(eDLL_T::ENGINE, NO_ERROR, "Attempted to run %s script while VM isn't initialized\n", SQVM_GetContextName(context)); + Error(eDLL_T::ENGINE, NO_ERROR, "Attempted to run %s script while VM isn't initialized\n", contextName); return; } @@ -165,7 +161,7 @@ void Script_Execute(const SQChar* code, const SQCONTEXT context) if (!SQ_SUCCEEDED(callResult)) { - Error(eDLL_T::ENGINE, NO_ERROR, "Failed to execute %s script \"%s\"\n", SQVM_GetContextName(context), code); + Error(eDLL_T::ENGINE, NO_ERROR, "Failed to execute %s script \"%s\"\n", contextName, code); } } } diff --git a/src/windows/console.cpp b/src/windows/console.cpp index e3d5bbb6..a0008c46 100644 --- a/src/windows/console.cpp +++ b/src/windows/console.cpp @@ -85,7 +85,10 @@ void Console_Init(const bool bAnsiColor) // Create the console window if (AllocConsole() == FALSE) { - OutputDebugStringA("Failed to create console window!\n"); + char szBuf[2048]; + snprintf(szBuf, sizeof(szBuf), "Failed to create console window! [%s]\n", std::system_category().message(static_cast(::GetLastError())).c_str()); + + OutputDebugStringA(szBuf); return; } @@ -120,8 +123,7 @@ void Console_Init(const bool bAnsiColor) if (!SetConsoleMode(hOutput, dwMode)) // Some editions of Windows have 'VirtualTerminalLevel' disabled by default. { // Warn the user if 'VirtualTerminalLevel' could not be set on users environment. - MessageBoxA(NULL, "Failed to set console mode 'VirtualTerminalLevel'.\n" - "Please disable ansi-colors and restart \nthe program if output logging appears distorted.", "SDK Warning", MB_ICONEXCLAMATION | MB_OK); + MessageBoxA(NULL, "Failed to set console mode 'VirtualTerminalLevel'; please disable ansi-colors and restart the program if output logging appears distorted.", "SDK Warning", MB_ICONEXCLAMATION | MB_OK); } SetConsoleBackgroundColor(0x00000000); @@ -142,7 +144,10 @@ void Console_Shutdown() // Destroy the console window if (FreeConsole() == FALSE) { - OutputDebugStringA("Failed to destroy console window!\n"); + char szBuf[2048]; + snprintf(szBuf, sizeof(szBuf), "Failed to destroy console window! [%s]\n", std::system_category().message(static_cast(::GetLastError())).c_str()); + + OutputDebugStringA(szBuf); return; } } diff --git a/src/windows/system.cpp b/src/windows/system.cpp index d72c47b0..bc248d20 100644 --- a/src/windows/system.cpp +++ b/src/windows/system.cpp @@ -1,4 +1,5 @@ #include "core/stdafx.h" +#include "core/init.h" #include "windows/system.h" #include "engine/host_state.h" @@ -61,14 +62,18 @@ ConsoleHandlerRoutine( case CTRL_CLOSE_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: - if (g_pHostState) - { - g_pHostState->m_iNextState = HostStates_t::HS_SHUTDOWN; - } + + if (!g_bSdkShutdownInitiatedFromConsoleHandler) + g_bSdkShutdownInitiatedFromConsoleHandler = true; - // Give it time to shutdown properly, value is set to the max possible + if (g_pHostState) // This tells the engine to gracefully shutdown on the next frame. + g_pHostState->m_iNextState = HostStates_t::HS_SHUTDOWN; + + // Give it time to shutdown properly, this loop waits for max time // of SPI_GETWAITTOKILLSERVICETIMEOUT, which is 20000ms by default. - Sleep(20000); + while (g_bSdkInitialized) + Sleep(50); + return TRUE; }